using System; using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; using BirdsiteLive.Common.Settings; using BirdsiteLive.Statistics.Domain; using BirdsiteLive.Twitter.Models; using BirdsiteLive.Twitter.Tools; using Microsoft.Extensions.Logging; namespace BirdsiteLive.Twitter { public interface ITwitterUserService { TwitterUser GetUser(string username); TwitterUser GetUser(long id); bool IsUserApiRateLimited(); } public class TwitterUserService : ITwitterUserService { private readonly ITwitterAuthenticationInitializer _twitterAuthenticationInitializer; private readonly ITwitterStatisticsHandler _statisticsHandler; private readonly ILogger _logger; private HttpClient _httpClient = new HttpClient(); #region Ctor public TwitterUserService(ITwitterAuthenticationInitializer twitterAuthenticationInitializer, ITwitterStatisticsHandler statisticsHandler, ILogger logger) { _twitterAuthenticationInitializer = twitterAuthenticationInitializer; _statisticsHandler = statisticsHandler; _logger = logger; } #endregion public TwitterUser GetUser(long id) { return GetUserAsync(id).Result; } public TwitterUser GetUser(string username) { return GetUserAsync(username).Result; } public async Task GetUserAsync(string username) { //Check if API is saturated if (IsUserApiRateLimited()) throw new RateLimitExceededException(); //Proceed to account retrieval await _twitterAuthenticationInitializer.EnsureAuthenticationIsInitialized(); JsonDocument res; try { using (var request = new HttpRequestMessage(new HttpMethod("GET"), "https://api.twitter.com/2/users/by/username/"+ username + "?user.fields=name,username,protected,profile_image_url,url,description")) { request.Headers.TryAddWithoutValidation("Authorization", "Bearer " + _twitterAuthenticationInitializer.Token); var httpResponse = await _httpClient.SendAsync(request); httpResponse.EnsureSuccessStatusCode(); var c = await httpResponse.Content.ReadAsStringAsync(); res = JsonDocument.Parse(c); } } catch (HttpRequestException e) { throw; //if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("User has been suspended".ToLowerInvariant()))) //{ // throw new UserHasBeenSuspendedException(); //} //else if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("User not found".ToLowerInvariant()))) //{ // throw new UserNotFoundException(); //} //else if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("Rate limit exceeded".ToLowerInvariant()))) //{ // throw new RateLimitExceededException(); //} //else //{ // throw; //} } catch (Exception e) { _logger.LogError(e, "Error retrieving user {Username}", username); throw; } finally { _statisticsHandler.CalledUserApi(); } // Expand URLs //var description = user.Description; //foreach (var descriptionUrl in user.Entities?.Description?.Urls?.OrderByDescending(x => x.URL.Length)) // description = description.Replace(descriptionUrl.URL, descriptionUrl.ExpandedURL); return new TwitterUser { Id = long.Parse(res.RootElement.GetProperty("data").GetProperty("id").GetString()), Acct = res.RootElement.GetProperty("data").GetProperty("username").GetString(), Name = res.RootElement.GetProperty("data").GetProperty("name").GetString(), Description = res.RootElement.GetProperty("data").GetProperty("description").GetString(), Url = res.RootElement.GetProperty("data").GetProperty("url").GetString(), ProfileImageUrl = res.RootElement.GetProperty("data").GetProperty("profile_image_url").GetString(), ProfileBackgroundImageUrl = res.RootElement.GetProperty("data").GetProperty("profile_image_url").GetString(), //for now ProfileBannerURL = res.RootElement.GetProperty("data").GetProperty("profile_image_url").GetString(), //for now Protected = res.RootElement.GetProperty("data").GetProperty("protected").GetBoolean(), }; } public async Task GetUserAsync(long id) { //Check if API is saturated if (IsUserApiRateLimited()) throw new RateLimitExceededException(); //Proceed to account retrieval await _twitterAuthenticationInitializer.EnsureAuthenticationIsInitialized(); JsonDocument res; try { using (var request = new HttpRequestMessage(new HttpMethod("GET"), "https://api.twitter.com/2/users/"+ id + "?user.fields=name,username,protected,profile_image_url,url,description")) { request.Headers.TryAddWithoutValidation("Authorization", "Bearer " + _twitterAuthenticationInitializer.Token); var httpResponse = await _httpClient.SendAsync(request); httpResponse.EnsureSuccessStatusCode(); var c = await httpResponse.Content.ReadAsStringAsync(); res = JsonDocument.Parse(c); } } catch (HttpRequestException e) { throw; //if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("User has been suspended".ToLowerInvariant()))) //{ // throw new UserHasBeenSuspendedException(); //} //else if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("User not found".ToLowerInvariant()))) //{ // throw new UserNotFoundException(); //} //else if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("Rate limit exceeded".ToLowerInvariant()))) //{ // throw new RateLimitExceededException(); //} //else //{ // throw; //} } catch (Exception e) { _logger.LogError(e, "Error retrieving user id {id}", id); throw; } finally { _statisticsHandler.CalledUserApi(); } return new TwitterUser { Id = id, Acct = res.RootElement.GetProperty("data").GetProperty("username").GetString(), Name = res.RootElement.GetProperty("data").GetProperty("name").GetString(), Description = res.RootElement.GetProperty("data").GetProperty("description").GetString(), Url = res.RootElement.GetProperty("data").GetProperty("url").GetString(), ProfileImageUrl = res.RootElement.GetProperty("data").GetProperty("profile_image_url").GetString(), ProfileBackgroundImageUrl = res.RootElement.GetProperty("data").GetProperty("profile_image_url").GetString(), //for now ProfileBannerURL = res.RootElement.GetProperty("data").GetProperty("profile_image_url").GetString(), //for now Protected = res.RootElement.GetProperty("data").GetProperty("protected").GetBoolean(), }; } public bool IsUserApiRateLimited() { // Retrieve limit from tooling //_twitterAuthenticationInitializer.EnsureAuthenticationIsInitialized(); //ExceptionHandler.SwallowWebExceptions = false; //RateLimit.RateLimitTrackerMode = RateLimitTrackerMode.TrackOnly; //try //{ // var queryRateLimits = RateLimit.GetQueryRateLimit("https://api.twitter.com/1.1/users/show.json?screen_name=mastodon"); // if (queryRateLimits != null) // { // return queryRateLimits.Remaining <= 0; // } //} //catch (Exception e) //{ // _logger.LogError(e, "Error retrieving rate limits"); //} //// Fallback //var currentCalls = _statisticsHandler.GetCurrentUserCalls(); //var maxCalls = _statisticsHandler.GetStatistics().UserCallsMax; //return currentCalls >= maxCalls; return false; } } }