commit 609d441dce614560dfcf297efb8c2e2228f45e48 Author: Gervasio Marchand Date: Fri Dec 2 16:38:42 2022 -0300 First commit... it kinda works, but it's missing most things diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c826dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.vscode/ +bin/ +obj/ +.vs/ +*.csproj.user +launchSettings.json +.idea/ +.DS_Store diff --git a/Helpers/CryptoHelper.cs b/Helpers/CryptoHelper.cs new file mode 100644 index 0000000..2015d42 --- /dev/null +++ b/Helpers/CryptoHelper.cs @@ -0,0 +1,31 @@ +using System.Security.Cryptography; +using System.Text; + +namespace ImportDataAsRelay.Helpers; + +public static class CryptoHelper +{ + public static string GetSHA256Digest(string content) + { + using var sha256 = SHA256.Create(); + var hashValue = sha256.ComputeHash(Encoding.UTF8.GetBytes(content)); + return Convert.ToBase64String(hashValue); + } + + public static string ConvertPemToBase64(string filename) + { + var rsa = RSA.Create(); + rsa.ImportFromPem(File.ReadAllText(filename).ToCharArray()); + return Convert.ToBase64String(rsa.ExportRSAPrivateKey()); + } + + public static string Sign(string stringToSign) + { + using var rsaProvider = new RSACryptoServiceProvider(); + using var sha256 = SHA256.Create(); + rsaProvider.ImportRSAPrivateKey(Convert.FromBase64String(Environment.GetEnvironmentVariable("PRIVATE_KEY")), out _); + + var signature = rsaProvider.SignData(Encoding.UTF8.GetBytes(stringToSign), sha256); + return Convert.ToBase64String(signature); + } +} diff --git a/Helpers/MastodonHelper.cs b/Helpers/MastodonHelper.cs new file mode 100644 index 0000000..01c1e55 --- /dev/null +++ b/Helpers/MastodonHelper.cs @@ -0,0 +1,54 @@ +using System.Net.Http.Headers; + +namespace ImportDataAsRelay.Helpers; + +public static class MastodonHelper +{ + private static string TargetHost = Environment.GetEnvironmentVariable("TARGET_HOST"); + private static string RelayHost = Environment.GetEnvironmentVariable("RELAY_HOST"); + + public static async Task EnqueueStatusToFetch(string statusUrl) + { + var client = new HttpClient(); + + var date = DateTime.UtcNow; + + var content = $@"{{ + ""@context"": ""https://www.w3.org/ns/activitystreams"", + ""actor"": ""https://{RelayHost}/actor"", + ""id"": ""https://{RelayHost}/activities/23af173e-e1fd-4283-93eb-514f1e5e5408"", + ""object"": ""{statusUrl}"", + ""to"": [ + ""https://{RelayHost}/followers"" + ], + ""type"": ""Announce"" +}}"; + var digest = CryptoHelper.GetSHA256Digest(content); + var requestContent = new StringContent(content); + + requestContent.Headers.Add("Digest", "SHA-256=" + digest); + + var stringToSign = $"(request-target): post /inbox\ndate: {date.ToString("R")}\nhost: {TargetHost}\ndigest: SHA-256={digest}\ncontent-length: {content.Length}"; + var signature = CryptoHelper.Sign(stringToSign); + requestContent.Headers.Add("Signature", $@"keyId=""https://{RelayHost}/actor#main-key"",algorithm=""rsa-sha256"",headers=""(request-target) date host digest content-length"",signature=""{signature}"""); + + requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/activity+json"); + client.DefaultRequestHeaders.Date = date; + + var response = await client.PostAsync($"https://{TargetHost}/inbox", requestContent); + try + { + response.EnsureSuccessStatusCode(); + } + catch (Exception e) + { + Console.WriteLine("Error: " + e.Message); + Console.WriteLine("Status code: " + response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + Console.WriteLine("Response content: " + body); + + throw; + } + } +} diff --git a/ImportDataAsRelay.csproj b/ImportDataAsRelay.csproj new file mode 100644 index 0000000..0e4d787 --- /dev/null +++ b/ImportDataAsRelay.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..9ccca4a --- /dev/null +++ b/Program.cs @@ -0,0 +1,53 @@ +using ImportDataAsRelay.Helpers; +using Jil; + +var interestingTagsEverywhere = new[] { "dotnet", "csharp" }; +var sources = new Dictionary +{ + ["hachyderm.io"] = new [] { "hachyderm" }, + ["mastodon.social"] = Array.Empty(), + ["dotnet.social"] = Array.Empty(), +}; + +var client = new HttpClient(); + +foreach (var (site, specificTags) in sources) +{ + var tags = specificTags.Concat(interestingTagsEverywhere).ToList(); + foreach (var tag in tags) + { + Console.WriteLine($"Fetching tag #{tag} from {site}"); + var response = await client.GetAsync($"https://{site}/tags/{tag}.json"); + try + { + response.EnsureSuccessStatusCode(); + } + catch (Exception e) + { + Console.WriteLine($"Error fetching tag, status code: {response.StatusCode}. Error: {e.Message}"); + continue; + } + + var json = await response.Content.ReadAsStringAsync(); + var data = JSON.Deserialize(json, Options.CamelCase); + + foreach (var statusLink in data.OrderedItems) + { + Console.WriteLine($"Bringing in {statusLink}"); + try + { + await MastodonHelper.EnqueueStatusToFetch(statusLink); + await Task.Delay(500); + } + catch (Exception e) + { + Console.WriteLine($"{e.Message}"); + } + } + } +} + +public class TagResponse +{ + public string[] OrderedItems { get; private set; } +} \ No newline at end of file