added proper exception in user retrieval

This commit is contained in:
Nicolas Constant 2022-02-07 18:48:10 -05:00
parent 446b222881
commit 662f97e53c
No known key found for this signature in database
GPG key ID: 1E9F677FB01A5688
7 changed files with 212 additions and 72 deletions

View file

@ -9,6 +9,7 @@ using BirdsiteLive.Moderation.Actions;
using BirdsiteLive.Pipeline.Contracts; using BirdsiteLive.Pipeline.Contracts;
using BirdsiteLive.Pipeline.Models; using BirdsiteLive.Pipeline.Models;
using BirdsiteLive.Twitter; using BirdsiteLive.Twitter;
using BirdsiteLive.Twitter.Models;
namespace BirdsiteLive.Pipeline.Processors namespace BirdsiteLive.Pipeline.Processors
{ {
@ -35,26 +36,58 @@ namespace BirdsiteLive.Pipeline.Processors
foreach (var user in syncTwitterUsers) foreach (var user in syncTwitterUsers)
{ {
var userView = _twitterUserService.GetUser(user.Acct); TwitterUser userView = null;
if (userView == null)
{
await AnalyseFailingUserAsync(user);
}
else if (!userView.Protected)
{
user.FetchingErrorCount = 0;
var userWtData = new UserWithDataToSync
{
User = user
};
usersWtData.Add(userWtData);
}
}
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(); 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); var dbUser = await _twitterUserDal.GetTwitterUserAsync(user.Acct);
dbUser.FetchingErrorCount++; dbUser.FetchingErrorCount++;
@ -68,9 +101,6 @@ namespace BirdsiteLive.Pipeline.Processors
{ {
await _twitterUserDal.UpdateTwitterUserAsync(dbUser); await _twitterUserDal.UpdateTwitterUserAsync(dbUser);
} }
// Purge
_twitterUserService.PurgeUser(user.Acct);
} }
} }
} }

View file

@ -0,0 +1,9 @@
using System;
namespace BirdsiteLive.Twitter
{
public class RateLimitExceededException : Exception
{
}
}

View file

@ -0,0 +1,9 @@
using System;
namespace BirdsiteLive.Twitter
{
public class UserHasBeenSuspendedException : Exception
{
}
}

View file

@ -0,0 +1,9 @@
using System;
namespace BirdsiteLive.Twitter
{
public class UserNotFoundException : Exception
{
}
}

View file

@ -6,6 +6,7 @@ using BirdsiteLive.Twitter.Models;
using BirdsiteLive.Twitter.Tools; using BirdsiteLive.Twitter.Tools;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Tweetinvi; using Tweetinvi;
using Tweetinvi.Exceptions;
using Tweetinvi.Models; using Tweetinvi.Models;
namespace BirdsiteLive.Twitter namespace BirdsiteLive.Twitter
@ -45,17 +46,34 @@ namespace BirdsiteLive.Twitter
try try
{ {
user = User.GetUserFromScreenName(username); 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); throw new UserHasBeenSuspendedException();
return null; }
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) catch (Exception e)
{ {
_logger.LogError(e, "Error retrieving user {Username}", username); _logger.LogError(e, "Error retrieving user {Username}", username);
return null; throw;
}
finally
{
_statisticsHandler.CalledUserApi();
} }
// Expand URLs // Expand URLs

View file

@ -66,13 +66,42 @@ namespace BirdsiteLive.Controllers
id = id.Trim(new[] { ' ', '@' }).ToLowerInvariant(); id = id.Trim(new[] { ' ', '@' }).ToLowerInvariant();
TwitterUser user = null;
var isSaturated = false;
var notFound = false;
// Ensure valid username // Ensure valid username
// https://help.twitter.com/en/managing-your-account/twitter-username-rules // 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) 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"]; var acceptHeaders = Request.Headers["Accept"];
if (acceptHeaders.Any()) if (acceptHeaders.Any())
@ -80,16 +109,16 @@ namespace BirdsiteLive.Controllers
var r = acceptHeaders.First(); var r = acceptHeaders.First();
if (r.Contains("application/activity+json")) if (r.Contains("application/activity+json"))
{ {
if (user == null && isSaturated) return new ObjectResult("Too Many Requests") { StatusCode = 429 }; if (isSaturated) return new ObjectResult("Too Many Requests") { StatusCode = 429 };
if (user == null) return NotFound(); if (notFound) return NotFound();
var apUser = _userService.GetUser(user); var apUser = _userService.GetUser(user);
var jsonApUser = JsonConvert.SerializeObject(apUser); var jsonApUser = JsonConvert.SerializeObject(apUser);
return Content(jsonApUser, "application/activity+json; charset=utf-8"); return Content(jsonApUser, "application/activity+json; charset=utf-8");
} }
} }
if (user == null && isSaturated) return View("ApiSaturated"); if (isSaturated) return View("ApiSaturated");
if (user == null) return View("UserNotFound"); if (notFound) return View("UserNotFound");
var displayableUser = new DisplayTwitterUser var displayableUser = new DisplayTwitterUser
{ {
@ -138,46 +167,61 @@ namespace BirdsiteLive.Controllers
[HttpPost] [HttpPost]
public async Task<IActionResult> Inbox() public async Task<IActionResult> Inbox()
{ {
var r = Request; try
using (var reader = new StreamReader(Request.Body))
{ {
var body = await reader.ReadToEndAsync(); var r = Request;
using (var reader = new StreamReader(Request.Body))
_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 body = await reader.ReadToEndAsync();
{
var succeeded = await _userService.FollowRequestedAsync(signature, r.Method, r.Path, _logger.LogTrace("User Inbox: {Body}", body);
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityFollow, body); //System.IO.File.WriteAllText($@"C:\apdebug\{Guid.NewGuid()}.json", body);
if (succeeded) return Accepted();
else return Unauthorized(); var activity = ApDeserializer.ProcessActivity(body);
} var signature = r.Headers["Signature"].First();
case "Undo":
if (activity is ActivityUndoFollow) switch (activity?.type)
{ {
var succeeded = await _userService.UndoFollowRequestedAsync(signature, r.Method, r.Path, case "Follow":
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityUndoFollow, body); {
if (succeeded) return Accepted(); var succeeded = await _userService.FollowRequestedAsync(signature, r.Method, r.Path,
else return Unauthorized(); r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityFollow, body);
} if (succeeded) return Accepted();
return Accepted(); else return Unauthorized();
case "Delete": }
{ case "Undo":
var succeeded = await _userService.DeleteRequestedAsync(signature, r.Method, r.Path, if (activity is ActivityUndoFollow)
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityDelete, body); {
if (succeeded) return Accepted(); var succeeded = await _userService.UndoFollowRequestedAsync(signature, r.Method, r.Path,
else return Unauthorized(); r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityUndoFollow, body);
} if (succeeded) return Accepted();
default: else return Unauthorized();
return Accepted(); }
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")] [Route("/users/{id}/followers")]

View file

@ -12,6 +12,7 @@ using BirdsiteLive.Models;
using BirdsiteLive.Models.WellKnownModels; using BirdsiteLive.Models.WellKnownModels;
using BirdsiteLive.Twitter; using BirdsiteLive.Twitter;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace BirdsiteLive.Controllers namespace BirdsiteLive.Controllers
@ -23,13 +24,15 @@ namespace BirdsiteLive.Controllers
private readonly ITwitterUserService _twitterUserService; private readonly ITwitterUserService _twitterUserService;
private readonly ITwitterUserDal _twitterUserDal; private readonly ITwitterUserDal _twitterUserDal;
private readonly InstanceSettings _settings; private readonly InstanceSettings _settings;
private readonly ILogger<WellKnownController> _logger;
#region Ctor #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; _twitterUserService = twitterUserService;
_twitterUserDal = twitterUserDal; _twitterUserDal = twitterUserDal;
_moderationRepository = moderationRepository; _moderationRepository = moderationRepository;
_logger = logger;
_settings = settings; _settings = settings;
} }
#endregion #endregion
@ -174,9 +177,27 @@ namespace BirdsiteLive.Controllers
if (!string.IsNullOrWhiteSpace(domain) && domain != _settings.Domain) if (!string.IsNullOrWhiteSpace(domain) && domain != _settings.Domain)
return NotFound(); return NotFound();
var user = _twitterUserService.GetUser(name); try
if (user == null) {
_twitterUserService.GetUser(name);
}
catch (UserNotFoundException)
{
return NotFound(); 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); var actorUrl = UrlFactory.GetActorUrl(_settings.Domain, name);