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,13 +36,35 @@ 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)
try
{ {
await AnalyseFailingUserAsync(user); userView = _twitterUserService.GetUser(user.Acct);
} }
else if (!userView.Protected) 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; user.FetchingErrorCount = 0;
var userWtData = new UserWithDataToSync var userWtData = new UserWithDataToSync
{ {
@ -49,12 +72,22 @@ namespace BirdsiteLive.Pipeline.Processors
}; };
usersWtData.Add(userWtData); 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)
{ {
_logger.LogWarning("User {username} not found", username); if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("User has been suspended".ToLowerInvariant())))
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) 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)
{
try
{
user = _twitterUserService.GetUser(id); 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
{ {
@ -137,6 +166,8 @@ namespace BirdsiteLive.Controllers
[Route("/users/{id}/inbox")] [Route("/users/{id}/inbox")]
[HttpPost] [HttpPost]
public async Task<IActionResult> Inbox() public async Task<IActionResult> Inbox()
{
try
{ {
var r = Request; var r = Request;
using (var reader = new StreamReader(Request.Body)) using (var reader = new StreamReader(Request.Body))
@ -179,6 +210,19 @@ namespace BirdsiteLive.Controllers
} }
} }
} }
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")]
[HttpGet] [HttpGet]

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);