BirdsiteLive/src/BirdsiteLive/Controllers/UsersController.cs

264 lines
10 KiB
C#
Raw Normal View History

2020-06-06 06:14:42 +02:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
2020-07-23 01:27:25 +02:00
using System.Net.Mime;
using System.Text.RegularExpressions;
2020-06-06 06:14:42 +02:00
using System.Threading;
using System.Threading.Tasks;
2020-06-06 07:29:13 +02:00
using BirdsiteLive.ActivityPub;
2020-11-22 00:54:16 +01:00
using BirdsiteLive.ActivityPub.Models;
2021-02-02 02:13:10 +01:00
using BirdsiteLive.Common.Regexes;
2020-11-19 04:48:53 +01:00
using BirdsiteLive.Common.Settings;
2022-12-14 05:42:22 +01:00
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
2020-06-06 06:14:42 +02:00
using BirdsiteLive.Domain;
2020-11-19 04:48:53 +01:00
using BirdsiteLive.Models;
using BirdsiteLive.Tools;
2020-06-06 06:14:42 +02:00
using BirdsiteLive.Twitter;
using BirdsiteLive.Twitter.Models;
2020-06-06 07:29:13 +02:00
using Microsoft.AspNetCore.Http;
2020-06-06 06:14:42 +02:00
using Microsoft.AspNetCore.Mvc;
2021-04-16 03:21:22 +02:00
using Microsoft.Extensions.Logging;
2020-06-06 07:29:13 +02:00
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
2020-06-06 06:14:42 +02:00
namespace BirdsiteLive.Controllers
{
public class UsersController : Controller
{
private readonly ITwitterUserService _twitterUserService;
private readonly ITwitterTweetsService _twitterTweetService;
2022-12-14 05:42:22 +01:00
private readonly ITwitterUserDal _twitterUserDal;
2020-06-06 06:14:42 +02:00
private readonly IUserService _userService;
2020-07-23 01:27:25 +02:00
private readonly IStatusService _statusService;
2020-11-19 04:48:53 +01:00
private readonly InstanceSettings _instanceSettings;
2021-04-16 03:21:22 +02:00
private readonly ILogger<UsersController> _logger;
2020-06-06 06:14:42 +02:00
#region Ctor
2022-12-14 05:42:22 +01:00
public UsersController(ITwitterUserService twitterUserService, IUserService userService, IStatusService statusService, InstanceSettings instanceSettings, ITwitterTweetsService twitterTweetService, ILogger<UsersController> logger, ITwitterUserDal twitterUserDal)
2020-06-06 06:14:42 +02:00
{
_twitterUserService = twitterUserService;
2020-06-06 06:14:42 +02:00
_userService = userService;
2020-07-23 01:27:25 +02:00
_statusService = statusService;
2020-11-19 04:48:53 +01:00
_instanceSettings = instanceSettings;
_twitterTweetService = twitterTweetService;
2021-04-16 03:21:22 +02:00
_logger = logger;
2022-12-14 05:42:22 +01:00
_twitterUserDal = twitterUserDal;
2020-06-06 06:14:42 +02:00
}
#endregion
2020-12-29 08:12:44 +01:00
[Route("/users")]
public IActionResult Index()
{
var acceptHeaders = Request.Headers["Accept"];
if (acceptHeaders.Any())
{
var r = acceptHeaders.First();
if (r.Contains("application/activity+json")) return NotFound();
}
2020-12-29 08:12:44 +01:00
return View("UserNotFound");
}
2022-12-26 00:57:31 +01:00
2020-06-06 06:14:42 +02:00
[Route("/@{id}")]
[Route("/users/{id}")]
2022-12-14 05:42:22 +01:00
public async Task<IActionResult> Index(string id)
2020-06-06 06:14:42 +02:00
{
2021-04-16 03:21:22 +02:00
_logger.LogTrace("User Index: {Id}", id);
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
2021-02-02 02:19:14 +01:00
if (!string.IsNullOrWhiteSpace(id) && UserRegexes.TwitterAccount.IsMatch(id) && id.Length <= 15)
{
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;
}
2020-07-02 04:45:43 +02:00
//var isSaturated = _twitterUserService.IsUserApiRateLimited();
2022-12-14 05:42:22 +01:00
var dbUser = await _twitterUserDal.GetTwitterUserAsync(id);
var acceptHeaders = Request.Headers["Accept"];
if (acceptHeaders.Any())
2020-06-06 06:14:42 +02:00
{
var r = acceptHeaders.First();
if (r.Contains("application/activity+json"))
{
if (isSaturated) return new ObjectResult("Too Many Requests") { StatusCode = 429 };
if (notFound) return NotFound();
2022-12-14 06:35:25 +01:00
if (dbUser != null && dbUser.Deleted) return new ObjectResult("Gone") { StatusCode = 410 };
2022-12-14 05:42:22 +01:00
var apUser = _userService.GetUser(user, dbUser);
var jsonApUser = JsonConvert.SerializeObject(apUser);
return Content(jsonApUser, "application/activity+json; charset=utf-8");
}
2020-06-06 06:14:42 +02:00
}
if (isSaturated) return View("ApiSaturated");
if (notFound) return View("UserNotFound");
2020-11-19 04:48:53 +01:00
var displayableUser = new DisplayTwitterUser
{
Name = user.Name,
Description = user.Description,
2021-01-10 04:26:17 +01:00
Acct = user.Acct.ToLowerInvariant(),
2020-11-19 04:48:53 +01:00
Url = user.Url,
ProfileImageUrl = user.ProfileImageUrl,
2021-01-29 00:47:45 +01:00
Protected = user.Protected,
2022-12-14 05:42:22 +01:00
InstanceHandle = $"@{user.Acct.ToLowerInvariant()}@{_instanceSettings.Domain}",
2020-11-22 00:54:16 +01:00
2022-12-14 05:42:22 +01:00
MovedTo = dbUser?.MovedTo,
MovedToAcct = dbUser?.MovedToAcct,
Deleted = dbUser?.Deleted ?? false,
2020-11-19 04:48:53 +01:00
};
return View(displayableUser);
2020-06-06 06:14:42 +02:00
}
2022-12-26 00:57:31 +01:00
[Route("/users/{id}/remote_follow")]
public async Task<IActionResult> IndexRemoteFollow(string id)
{
return Redirect($"/users/{id}");
}
2020-06-06 06:14:42 +02:00
2020-07-02 04:45:43 +02:00
[Route("/@{id}/{statusId}")]
[Route("/users/{id}/statuses/{statusId}")]
public IActionResult Tweet(string id, string statusId)
{
var acceptHeaders = Request.Headers["Accept"];
if (acceptHeaders.Any())
2020-07-02 04:45:43 +02:00
{
var r = acceptHeaders.First();
if (r.Contains("application/activity+json"))
{
if (!long.TryParse(statusId, out var parsedStatusId))
return NotFound();
var tweet = _twitterTweetService.GetTweet(parsedStatusId);
if (tweet == null)
return NotFound();
//var user = _twitterService.GetUser(id);
//if (user == null) return NotFound();
var status = _statusService.GetStatus(id, tweet);
var jsonApUser = JsonConvert.SerializeObject(status);
return Content(jsonApUser, "application/activity+json; charset=utf-8");
}
2020-07-02 04:45:43 +02:00
}
return Redirect($"https://twitter.com/{id}/status/{statusId}");
2020-07-02 04:45:43 +02:00
}
2020-06-06 06:14:42 +02:00
[Route("/users/{id}/inbox")]
[HttpPost]
public async Task<IActionResult> Inbox()
{
try
2020-06-06 06:14:42 +02:00
{
var r = Request;
using (var reader = new StreamReader(Request.Body))
2020-06-06 07:29:13 +02:00
{
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":
2022-02-09 07:15:48 +01:00
{
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,
2022-02-09 07:15:48 +01:00
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers),
activity as ActivityUndoFollow, body);
if (succeeded) return Accepted();
else return Unauthorized();
}
2022-02-09 07:15:48 +01:00
return Accepted();
case "Delete":
2022-02-09 07:15:48 +01:00
{
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();
}
2020-06-06 07:29:13 +02:00
}
2020-06-06 06:14:42 +02:00
}
2022-02-09 07:54:35 +01:00
catch (FollowerIsGoneException) //TODO: check if user in DB
2022-02-09 07:15:48 +01:00
{
return Accepted();
}
catch (UserNotFoundException)
{
return NotFound();
}
catch (UserHasBeenSuspendedException)
{
return NotFound();
}
catch (RateLimitExceededException)
{
return new ObjectResult("Too Many Requests") { StatusCode = 429 };
}
2020-06-06 06:14:42 +02:00
}
2020-06-06 07:29:13 +02:00
2020-11-22 00:54:16 +01:00
[Route("/users/{id}/followers")]
[HttpGet]
2021-02-14 18:28:38 +01:00
public IActionResult Followers(string id)
2020-11-22 00:54:16 +01:00
{
var r = Request.Headers["Accept"].First();
if (!r.Contains("application/activity+json")) return NotFound();
var followers = new Followers
{
id = $"https://{_instanceSettings.Domain}/users/{id}/followers"
};
var jsonApUser = JsonConvert.SerializeObject(followers);
return Content(jsonApUser, "application/activity+json; charset=utf-8");
}
2020-06-06 06:14:42 +02:00
}
}