You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

244 lines
9.3 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BirdsiteLive.ActivityPub;
using BirdsiteLive.ActivityPub.Models;
using BirdsiteLive.Common.Regexes;
using BirdsiteLive.Common.Settings;
using BirdsiteLive.Domain;
using BirdsiteLive.Models;
using BirdsiteLive.Twitter;
using BirdsiteLive.Twitter.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
namespace BirdsiteLive.Controllers
{
public class UsersController : Controller
{
private readonly ITwitterUserService _twitterUserService;
private readonly ITwitterTweetsService _twitterTweetService;
private readonly IUserService _userService;
private readonly IStatusService _statusService;
private readonly InstanceSettings _instanceSettings;
private readonly IActivityPubService _activityPubService;
private readonly ILogger<UsersController> _logger;
#region Ctor
public UsersController(ITwitterUserService twitterUserService, IUserService userService, IStatusService statusService, InstanceSettings instanceSettings, ITwitterTweetsService twitterTweetService, IActivityPubService activityPubService, ILogger<UsersController> logger)
{
_twitterUserService = twitterUserService;
_userService = userService;
_statusService = statusService;
_instanceSettings = instanceSettings;
_twitterTweetService = twitterTweetService;
_activityPubService = activityPubService;
_logger = logger;
}
#endregion
[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();
}
return View("UserNotFound");
}
[Route("/@{id}")]
[Route("/users/{id}")]
[Route("/users/{id}/remote_follow")]
public IActionResult Index(string id)
{
_logger.LogTrace("User Index: {Id}", id);
id = id.Trim(new[] { ' ', '@' }).ToLowerInvariant();
// 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);
var acceptHeaders = Request.Headers["Accept"];
if (acceptHeaders.Any())
{
var r = acceptHeaders.First();
if (r.Contains("application/activity+json"))
{
if (user == null) return NotFound();
var apUser = _userService.GetUser(user);
var jsonApUser = JsonConvert.SerializeObject(apUser, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
return Content(jsonApUser, "application/activity+json; charset=utf-8");
}
}
if (user == null) return View("UserNotFound");
var displayableUser = new DisplayTwitterUser
{
Name = user.Name,
Description = user.Description,
Acct = user.Acct.ToLowerInvariant(),
Url = user.Url,
ProfileImageUrl = user.ProfileImageUrl,
Protected = user.Protected,
InstanceHandle = $"@{user.Acct.ToLowerInvariant()}@{_instanceSettings.Domain}"
};
return View(displayableUser);
}
[Route("/@{id}/{statusId}")]
[Route("/users/{id}/statuses/{statusId}")]
public IActionResult Tweet(string id, string statusId)
{
var acceptHeaders = Request.Headers["Accept"];
if (acceptHeaders.Any())
{
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");
}
}
return Redirect($"https://{_instanceSettings.TwitterDomain}/{id}/status/{statusId}");
}
[Route("/users/{id}/inbox")]
[HttpPost]
public async Task<IActionResult> Inbox()
{
var r = Request;
using (var reader = new StreamReader(Request.Body))
{
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);
// Do something
var signature = r.Headers["Signature"].First();
switch (activity?.type)
{
case "Follow":
{
var succeeded = await _userService.FollowRequestedAsync(signature, r.Method, r.Path,
r.QueryString.ToString(), 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(), RequestHeaders(r.Headers), activity as ActivityUndoFollow, body);
if (succeeded) return Accepted();
else return Unauthorized();
}
return Accepted();
default:
return Accepted();
}
}
}
[Route("/users/{id}/followers")]
[HttpGet]
public IActionResult Followers(string id)
{
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");
}
private Dictionary<string, string> RequestHeaders(IHeaderDictionary header)
{
return header.ToDictionary<KeyValuePair<string, StringValues>, string, string>(h => h.Key.ToLowerInvariant(), h => h.Value);
}
[Route("/users/{actor}/remote_follow")]
[HttpPost]
public async Task<IActionResult> RemoteFollow(string actor)
{
StringValues webfingerValues;
if (!Request.Form.TryGetValue("webfinger", out webfingerValues)) return BadRequest();
var webfinger = webfingerValues.First();
if (webfinger.Length < 1 || actor.Length < 1) return BadRequest();
if (webfinger[0] == '@') webfinger = webfinger[1..];
if (webfinger.IndexOf("@") < 0 || ! new Regex("^[A-Za-z0-9_]*$").IsMatch(webfinger.Split('@')[0]) || ! new Regex("^[A-Za-z0-9_]*$").IsMatch(actor) || Uri.CheckHostName(webfinger.Split('@')[1]) == UriHostNameType.Unknown)
{
return BadRequest();
}
WebFingerData webfingerData;
try
{
webfingerData = await _activityPubService.WebFinger(webfinger);
}
catch(Exception e)
{
_logger.LogError("Could not WebFinger {user}: {exception}", webfinger, e);
return NotFound();
}
string redirectLink = "";
foreach(var link in webfingerData.links)
{
if(link.rel == "http://ostatus.org/schema/1.0/subscribe" && link.template.Length > 0)
{
redirectLink = link.template.Replace("{uri}", "https://" + _instanceSettings.Domain + "/users/" + actor);
}
}
if (redirectLink == "") return NotFound();
return Redirect(redirectLink);
}
}
}