BirdsiteLive/src/BirdsiteLive.Domain/ActivityPubService.cs

204 lines
7.4 KiB
C#
Raw Normal View History

2020-06-29 03:56:10 +02:00
using System;
2020-08-01 19:19:41 +02:00
using System.Linq;
2020-06-29 03:56:10 +02:00
using System.Net;
using System.Net.Http;
2020-11-21 02:21:44 +01:00
using System.Security.Cryptography;
2020-06-29 03:56:10 +02:00
using System.Text;
2020-06-06 07:29:13 +02:00
using System.Threading.Tasks;
using BirdsiteLive.ActivityPub;
2021-01-10 04:26:17 +01:00
using BirdsiteLive.ActivityPub.Converters;
2020-08-01 04:13:52 +02:00
using BirdsiteLive.ActivityPub.Models;
using BirdsiteLive.Common.Settings;
using BirdsiteLive.DAL.Models;
2021-03-09 01:27:08 +01:00
using Microsoft.Extensions.Logging;
2020-06-06 07:29:13 +02:00
using Newtonsoft.Json;
namespace BirdsiteLive.Domain
{
public interface IActivityPubService
{
2022-11-02 06:15:05 +01:00
Task<string> GetUserIdAsync(string acct);
2020-06-06 07:29:13 +02:00
Task<Actor> GetUser(string objectId);
2020-06-29 05:42:23 +02:00
Task<HttpStatusCode> PostDataAsync<T>(T data, string targetHost, string actorUrl, string inbox = null);
2021-01-16 06:34:09 +01:00
Task PostNewNoteActivity(Note note, string username, string noteId, string targetHost,
string targetInbox);
2022-12-21 06:25:43 +01:00
Task DeleteUserAsync(string username, string targetHost, string targetInbox);
Task DeleteNoteAsync(SyncTweet tweet);
2020-06-06 07:29:13 +02:00
}
2022-11-02 06:15:05 +01:00
public class WebFinger
{
public string subject { get; set; }
public string[] aliases { get; set; }
}
2020-06-06 07:29:13 +02:00
public class ActivityPubService : IActivityPubService
{
private readonly InstanceSettings _instanceSettings;
2021-01-16 06:51:52 +01:00
private readonly IHttpClientFactory _httpClientFactory;
2020-06-29 03:56:10 +02:00
private readonly ICryptoService _cryptoService;
2021-03-09 01:27:08 +01:00
private readonly ILogger<ActivityPubService> _logger;
2020-06-29 03:56:10 +02:00
#region Ctor
2021-03-09 01:27:08 +01:00
public ActivityPubService(ICryptoService cryptoService, InstanceSettings instanceSettings, IHttpClientFactory httpClientFactory, ILogger<ActivityPubService> logger)
2020-06-29 03:56:10 +02:00
{
_cryptoService = cryptoService;
_instanceSettings = instanceSettings;
2021-01-16 06:51:52 +01:00
_httpClientFactory = httpClientFactory;
2021-03-09 01:27:08 +01:00
_logger = logger;
2020-06-29 03:56:10 +02:00
}
#endregion
2022-11-02 06:15:05 +01:00
public async Task<string> GetUserIdAsync(string acct)
{
var splittedAcct = acct.Trim('@').Split('@');
var url = $"https://{splittedAcct[1]}/.well-known/webfinger?resource=acct:{splittedAcct[0]}@{splittedAcct[1]}";
var httpClient = _httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
var result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
var content = await result.Content.ReadAsStringAsync();
var actor = JsonConvert.DeserializeObject<WebFinger>(content);
return actor.aliases.FirstOrDefault();
}
2020-06-06 07:29:13 +02:00
public async Task<Actor> GetUser(string objectId)
{
2021-01-16 06:51:52 +01:00
var httpClient = _httpClientFactory.CreateClient();
2021-02-06 06:11:11 +01:00
httpClient.DefaultRequestHeaders.Add("Accept", "application/activity+json");
2021-01-16 06:51:52 +01:00
var result = await httpClient.GetAsync(objectId);
2022-02-09 07:15:48 +01:00
if (result.StatusCode == HttpStatusCode.Gone)
2022-02-09 07:54:35 +01:00
throw new FollowerIsGoneException();
2022-02-09 07:15:48 +01:00
result.EnsureSuccessStatusCode();
2021-01-16 06:51:52 +01:00
var content = await result.Content.ReadAsStringAsync();
2021-04-13 07:25:36 +02:00
var actor = JsonConvert.DeserializeObject<Actor>(content);
if (string.IsNullOrWhiteSpace(actor.url)) actor.url = objectId;
return actor;
2020-06-06 07:29:13 +02:00
}
2020-06-29 03:56:10 +02:00
2022-12-21 06:25:43 +01:00
public async Task DeleteUserAsync(string username, string targetHost, string targetInbox)
{
try
{
var actor = UrlFactory.GetActorUrl(_instanceSettings.Domain, username);
var deleteUser = new ActivityDelete
{
context = "https://www.w3.org/ns/activitystreams",
id = $"{actor}#delete",
type = "Delete",
actor = actor,
to = new [] { "https://www.w3.org/ns/activitystreams#Public" },
apObject = actor
};
await PostDataAsync(deleteUser, targetHost, actor, targetInbox);
}
catch (Exception e)
{
_logger.LogError(e, "Error deleting {Username} to {Host}{Inbox}", username, targetHost, targetInbox);
throw;
}
}
public async Task DeleteNoteAsync(SyncTweet tweet)
{
var acct = tweet.Acct.ToLowerInvariant().Trim();
var actor = $"https://{_instanceSettings.Domain}/users/{acct}";
var noteId = $"https://{_instanceSettings.Domain}/users/{acct}/statuses/{tweet.TweetId}";
var delete = new ActivityDelete
{
context = "https://www.w3.org/ns/activitystreams",
id = $"{noteId}#delete",
type = "Delete",
actor = actor,
to = new[] { "https://www.w3.org/ns/activitystreams#Public" },
apObject = new Tombstone
{
id = noteId,
atomUrl = noteId
}
};
await PostDataAsync(delete, tweet.Host, actor, tweet.Inbox);
}
2021-01-16 06:34:09 +01:00
public async Task PostNewNoteActivity(Note note, string username, string noteId, string targetHost, string targetInbox)
{
2021-03-09 01:27:08 +01:00
try
{
var actor = UrlFactory.GetActorUrl(_instanceSettings.Domain, username);
var noteUri = UrlFactory.GetNoteUrl(_instanceSettings.Domain, username, noteId);
2021-03-09 01:27:08 +01:00
var now = DateTime.UtcNow;
var nowString = now.ToString("s") + "Z";
2021-01-16 06:51:52 +01:00
2021-03-09 01:27:08 +01:00
var noteActivity = new ActivityCreateNote()
{
context = "https://www.w3.org/ns/activitystreams",
id = $"{noteUri}/activity",
type = "Create",
actor = actor,
published = nowString,
2021-03-09 01:27:08 +01:00
to = note.to,
cc = note.cc,
apObject = note
};
2021-03-09 01:27:08 +01:00
await PostDataAsync(noteActivity, targetHost, actor, targetInbox);
}
catch (Exception e)
{
_logger.LogError(e, "Error sending {Username} post ({NoteId}) to {Host}{Inbox}", username, noteId, targetHost, targetInbox);
throw;
}
}
2020-06-29 05:42:23 +02:00
public async Task<HttpStatusCode> PostDataAsync<T>(T data, string targetHost, string actorUrl, string inbox = null)
2020-06-29 03:56:10 +02:00
{
2020-06-29 05:42:23 +02:00
var usedInbox = $"/inbox";
if (!string.IsNullOrWhiteSpace(inbox))
usedInbox = inbox;
2020-06-29 03:56:10 +02:00
var json = JsonConvert.SerializeObject(data);
var date = DateTime.UtcNow.ToUniversalTime();
var httpDate = date.ToString("r");
2020-06-29 05:42:23 +02:00
2020-12-28 06:43:02 +01:00
var digest = _cryptoService.ComputeSha256Hash(json);
2020-11-21 02:21:44 +01:00
var signature = _cryptoService.SignAndGetSignatureHeader(date, actorUrl, targetHost, digest, usedInbox);
2020-06-29 03:56:10 +02:00
2021-01-16 06:51:52 +01:00
var client = _httpClientFactory.CreateClient();
2020-06-29 03:56:10 +02:00
var httpRequestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
2020-11-21 01:00:31 +01:00
RequestUri = new Uri($"https://{targetHost}{usedInbox}"),
2020-06-29 03:56:10 +02:00
Headers =
{
{"Host", targetHost},
{"Date", httpDate},
2020-11-21 02:21:44 +01:00
{"Signature", signature},
{"Digest", $"SHA-256={digest}"}
2020-06-29 03:56:10 +02:00
},
Content = new StringContent(json, Encoding.UTF8, "application/ld+json")
};
var response = await client.SendAsync(httpRequestMessage);
2021-01-16 06:34:09 +01:00
response.EnsureSuccessStatusCode();
2020-06-29 03:56:10 +02:00
return response.StatusCode;
}
2020-06-06 07:29:13 +02:00
}
}