added proper exception in user retrieval
This commit is contained in:
parent
446b222881
commit
662f97e53c
7 changed files with 212 additions and 72 deletions
|
@ -9,6 +9,7 @@ using BirdsiteLive.Moderation.Actions;
|
|||
using BirdsiteLive.Pipeline.Contracts;
|
||||
using BirdsiteLive.Pipeline.Models;
|
||||
using BirdsiteLive.Twitter;
|
||||
using BirdsiteLive.Twitter.Models;
|
||||
|
||||
namespace BirdsiteLive.Pipeline.Processors
|
||||
{
|
||||
|
@ -35,26 +36,58 @@ namespace BirdsiteLive.Pipeline.Processors
|
|||
|
||||
foreach (var user in syncTwitterUsers)
|
||||
{
|
||||
var userView = _twitterUserService.GetUser(user.Acct);
|
||||
if (userView == null)
|
||||
{
|
||||
await AnalyseFailingUserAsync(user);
|
||||
}
|
||||
else if (!userView.Protected)
|
||||
{
|
||||
user.FetchingErrorCount = 0;
|
||||
var userWtData = new UserWithDataToSync
|
||||
{
|
||||
User = user
|
||||
};
|
||||
usersWtData.Add(userWtData);
|
||||
}
|
||||
}
|
||||
TwitterUser userView = null;
|
||||
|
||||
try
|
||||
{
|
||||
userView = _twitterUserService.GetUser(user.Acct);
|
||||
}
|
||||
catch (UserNotFoundException)
|
||||
{
|
||||
await ProcessNotFoundUserAsync(user);
|
||||
}
|
||||
catch (UserHasBeenSuspendedException)
|
||||
{
|
||||
await ProcessNotFoundUserAsync(user);
|
||||
}
|
||||
catch (RateLimitExceededException)
|
||||
{
|
||||
await ProcessRateLimitExceededAsync(user);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (userView == null || userView.Protected)
|
||||
{
|
||||
await ProcessFailingUserAsync(user);
|
||||
continue;
|
||||
}
|
||||
|
||||
user.FetchingErrorCount = 0;
|
||||
var userWtData = new UserWithDataToSync
|
||||
{
|
||||
User = user
|
||||
};
|
||||
usersWtData.Add(userWtData);
|
||||
}
|
||||
return usersWtData.ToArray();
|
||||
}
|
||||
|
||||
private async Task AnalyseFailingUserAsync(SyncTwitterUser user)
|
||||
private async Task ProcessRateLimitExceededAsync(SyncTwitterUser user)
|
||||
{
|
||||
var dbUser = await _twitterUserDal.GetTwitterUserAsync(user.Acct);
|
||||
dbUser.LastSync = DateTime.UtcNow;
|
||||
await _twitterUserDal.UpdateTwitterUserAsync(dbUser);
|
||||
}
|
||||
|
||||
private async Task ProcessNotFoundUserAsync(SyncTwitterUser user)
|
||||
{
|
||||
await _removeTwitterAccountAction.ProcessAsync(user);
|
||||
}
|
||||
|
||||
private async Task ProcessFailingUserAsync(SyncTwitterUser user)
|
||||
{
|
||||
var dbUser = await _twitterUserDal.GetTwitterUserAsync(user.Acct);
|
||||
dbUser.FetchingErrorCount++;
|
||||
|
@ -68,9 +101,6 @@ namespace BirdsiteLive.Pipeline.Processors
|
|||
{
|
||||
await _twitterUserDal.UpdateTwitterUserAsync(dbUser);
|
||||
}
|
||||
|
||||
// Purge
|
||||
_twitterUserService.PurgeUser(user.Acct);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace BirdsiteLive.Twitter
|
||||
{
|
||||
public class RateLimitExceededException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace BirdsiteLive.Twitter
|
||||
{
|
||||
public class UserHasBeenSuspendedException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace BirdsiteLive.Twitter
|
||||
{
|
||||
public class UserNotFoundException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using BirdsiteLive.Twitter.Models;
|
|||
using BirdsiteLive.Twitter.Tools;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tweetinvi;
|
||||
using Tweetinvi.Exceptions;
|
||||
using Tweetinvi.Models;
|
||||
|
||||
namespace BirdsiteLive.Twitter
|
||||
|
@ -45,17 +46,34 @@ namespace BirdsiteLive.Twitter
|
|||
try
|
||||
{
|
||||
user = User.GetUserFromScreenName(username);
|
||||
_statisticsHandler.CalledUserApi();
|
||||
if (user == null)
|
||||
}
|
||||
catch (TwitterException e)
|
||||
{
|
||||
if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("User has been suspended".ToLowerInvariant())))
|
||||
{
|
||||
_logger.LogWarning("User {username} not found", username);
|
||||
return null;
|
||||
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);
|
||||
return null;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_statisticsHandler.CalledUserApi();
|
||||
}
|
||||
|
||||
// Expand URLs
|
||||
|
|
|
@ -66,13 +66,42 @@ namespace BirdsiteLive.Controllers
|
|||
|
||||
id = id.Trim(new[] { ' ', '@' }).ToLowerInvariant();
|
||||
|
||||
TwitterUser user = null;
|
||||
var isSaturated = false;
|
||||
var notFound = false;
|
||||
|
||||
// Ensure valid username
|
||||
// https://help.twitter.com/en/managing-your-account/twitter-username-rules
|
||||
TwitterUser user = null;
|
||||
if (!string.IsNullOrWhiteSpace(id) && UserRegexes.TwitterAccount.IsMatch(id) && id.Length <= 15)
|
||||
user = _twitterUserService.GetUser(id);
|
||||
{
|
||||
try
|
||||
{
|
||||
user = _twitterUserService.GetUser(id);
|
||||
}
|
||||
catch (UserNotFoundException)
|
||||
{
|
||||
notFound = true;
|
||||
}
|
||||
catch (UserHasBeenSuspendedException)
|
||||
{
|
||||
notFound = true;
|
||||
}
|
||||
catch (RateLimitExceededException)
|
||||
{
|
||||
isSaturated = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception getting {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
notFound = true;
|
||||
}
|
||||
|
||||
var isSaturated = _twitterUserService.IsUserApiRateLimited();
|
||||
//var isSaturated = _twitterUserService.IsUserApiRateLimited();
|
||||
|
||||
var acceptHeaders = Request.Headers["Accept"];
|
||||
if (acceptHeaders.Any())
|
||||
|
@ -80,17 +109,17 @@ namespace BirdsiteLive.Controllers
|
|||
var r = acceptHeaders.First();
|
||||
if (r.Contains("application/activity+json"))
|
||||
{
|
||||
if (user == null && isSaturated) return new ObjectResult("Too Many Requests") { StatusCode = 429 };
|
||||
if (user == null) return NotFound();
|
||||
if (isSaturated) return new ObjectResult("Too Many Requests") { StatusCode = 429 };
|
||||
if (notFound) return NotFound();
|
||||
var apUser = _userService.GetUser(user);
|
||||
var jsonApUser = JsonConvert.SerializeObject(apUser);
|
||||
return Content(jsonApUser, "application/activity+json; charset=utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
if (user == null && isSaturated) return View("ApiSaturated");
|
||||
if (user == null) return View("UserNotFound");
|
||||
|
||||
if (isSaturated) return View("ApiSaturated");
|
||||
if (notFound) return View("UserNotFound");
|
||||
|
||||
var displayableUser = new DisplayTwitterUser
|
||||
{
|
||||
Name = user.Name,
|
||||
|
@ -138,46 +167,61 @@ namespace BirdsiteLive.Controllers
|
|||
[HttpPost]
|
||||
public async Task<IActionResult> Inbox()
|
||||
{
|
||||
var r = Request;
|
||||
using (var reader = new StreamReader(Request.Body))
|
||||
try
|
||||
{
|
||||
var body = await reader.ReadToEndAsync();
|
||||
|
||||
_logger.LogTrace("User Inbox: {Body}", body);
|
||||
//System.IO.File.WriteAllText($@"C:\apdebug\{Guid.NewGuid()}.json", body);
|
||||
|
||||
var activity = ApDeserializer.ProcessActivity(body);
|
||||
var signature = r.Headers["Signature"].First();
|
||||
|
||||
switch (activity?.type)
|
||||
var r = Request;
|
||||
using (var reader = new StreamReader(Request.Body))
|
||||
{
|
||||
case "Follow":
|
||||
{
|
||||
var succeeded = await _userService.FollowRequestedAsync(signature, r.Method, r.Path,
|
||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityFollow, body);
|
||||
if (succeeded) return Accepted();
|
||||
else return Unauthorized();
|
||||
}
|
||||
case "Undo":
|
||||
if (activity is ActivityUndoFollow)
|
||||
{
|
||||
var succeeded = await _userService.UndoFollowRequestedAsync(signature, r.Method, r.Path,
|
||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityUndoFollow, body);
|
||||
if (succeeded) return Accepted();
|
||||
else return Unauthorized();
|
||||
}
|
||||
return Accepted();
|
||||
case "Delete":
|
||||
{
|
||||
var succeeded = await _userService.DeleteRequestedAsync(signature, r.Method, r.Path,
|
||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityDelete, body);
|
||||
if (succeeded) return Accepted();
|
||||
else return Unauthorized();
|
||||
}
|
||||
default:
|
||||
return Accepted();
|
||||
var body = await reader.ReadToEndAsync();
|
||||
|
||||
_logger.LogTrace("User Inbox: {Body}", body);
|
||||
//System.IO.File.WriteAllText($@"C:\apdebug\{Guid.NewGuid()}.json", body);
|
||||
|
||||
var activity = ApDeserializer.ProcessActivity(body);
|
||||
var signature = r.Headers["Signature"].First();
|
||||
|
||||
switch (activity?.type)
|
||||
{
|
||||
case "Follow":
|
||||
{
|
||||
var succeeded = await _userService.FollowRequestedAsync(signature, r.Method, r.Path,
|
||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityFollow, body);
|
||||
if (succeeded) return Accepted();
|
||||
else return Unauthorized();
|
||||
}
|
||||
case "Undo":
|
||||
if (activity is ActivityUndoFollow)
|
||||
{
|
||||
var succeeded = await _userService.UndoFollowRequestedAsync(signature, r.Method, r.Path,
|
||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityUndoFollow, body);
|
||||
if (succeeded) return Accepted();
|
||||
else return Unauthorized();
|
||||
}
|
||||
return Accepted();
|
||||
case "Delete":
|
||||
{
|
||||
var succeeded = await _userService.DeleteRequestedAsync(signature, r.Method, r.Path,
|
||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityDelete, body);
|
||||
if (succeeded) return Accepted();
|
||||
else return Unauthorized();
|
||||
}
|
||||
default:
|
||||
return Accepted();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UserNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
catch (UserHasBeenSuspendedException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
catch (RateLimitExceededException)
|
||||
{
|
||||
return new ObjectResult("Too Many Requests") { StatusCode = 429 };
|
||||
}
|
||||
}
|
||||
|
||||
[Route("/users/{id}/followers")]
|
||||
|
|
|
@ -12,6 +12,7 @@ using BirdsiteLive.Models;
|
|||
using BirdsiteLive.Models.WellKnownModels;
|
||||
using BirdsiteLive.Twitter;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BirdsiteLive.Controllers
|
||||
|
@ -23,13 +24,15 @@ namespace BirdsiteLive.Controllers
|
|||
private readonly ITwitterUserService _twitterUserService;
|
||||
private readonly ITwitterUserDal _twitterUserDal;
|
||||
private readonly InstanceSettings _settings;
|
||||
|
||||
private readonly ILogger<WellKnownController> _logger;
|
||||
|
||||
#region Ctor
|
||||
public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal, IModerationRepository moderationRepository)
|
||||
public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal, IModerationRepository moderationRepository, ILogger<WellKnownController> logger)
|
||||
{
|
||||
_twitterUserService = twitterUserService;
|
||||
_twitterUserDal = twitterUserDal;
|
||||
_moderationRepository = moderationRepository;
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
}
|
||||
#endregion
|
||||
|
@ -174,9 +177,27 @@ namespace BirdsiteLive.Controllers
|
|||
if (!string.IsNullOrWhiteSpace(domain) && domain != _settings.Domain)
|
||||
return NotFound();
|
||||
|
||||
var user = _twitterUserService.GetUser(name);
|
||||
if (user == null)
|
||||
try
|
||||
{
|
||||
_twitterUserService.GetUser(name);
|
||||
}
|
||||
catch (UserNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
catch (UserHasBeenSuspendedException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
catch (RateLimitExceededException)
|
||||
{
|
||||
return new ObjectResult("Too Many Requests") { StatusCode = 429 };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception getting {Name}", name);
|
||||
throw;
|
||||
}
|
||||
|
||||
var actorUrl = UrlFactory.GetActorUrl(_settings.Domain, name);
|
||||
|
||||
|
|
Reference in a new issue