Skip to content

Commit

Permalink
Merge pull request #179 from NicolasConstant/develop
Browse files Browse the repository at this point in the history
0.22.0 PR
  • Loading branch information
NicolasConstant authored Dec 30, 2022
2 parents 8897b88 + 8a3ca81 commit 8b1a61c
Show file tree
Hide file tree
Showing 11 changed files with 402 additions and 48 deletions.
2 changes: 2 additions & 0 deletions src/BirdsiteLive.Common/Regexes/UrlRegexes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ namespace BirdsiteLive.Common.Regexes
public class UrlRegexes
{
public static readonly Regex Url = new Regex(@"(.?)(((http|ftp|https):\/\/)[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?)");

public static readonly Regex Domain = new Regex(@"^[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)+$");
}
}
89 changes: 80 additions & 9 deletions src/BirdsiteLive.Domain/MigrationService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BirdsiteLive.Twitter;
using System.Security.Cryptography;
Expand All @@ -11,25 +12,34 @@
using BirdsiteLive.Common.Settings;
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Domain.Enum;
using System.Net.Http;
using BirdsiteLive.Common.Regexes;
using Microsoft.Extensions.Logging;

namespace BirdsiteLive.Domain
{
public class MigrationService
{
private readonly InstanceSettings _instanceSettings;
private readonly ITheFedInfoService _theFedInfoService;
private readonly ITwitterTweetsService _twitterTweetsService;
private readonly IActivityPubService _activityPubService;
private readonly ITwitterUserDal _twitterUserDal;
private readonly IFollowersDal _followersDal;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<MigrationService> _logger;

#region Ctor
public MigrationService(ITwitterTweetsService twitterTweetsService, IActivityPubService activityPubService, ITwitterUserDal twitterUserDal, IFollowersDal followersDal, InstanceSettings instanceSettings)
public MigrationService(ITwitterTweetsService twitterTweetsService, IActivityPubService activityPubService, ITwitterUserDal twitterUserDal, IFollowersDal followersDal, InstanceSettings instanceSettings, ITheFedInfoService theFedInfoService, IHttpClientFactory httpClientFactory, ILogger<MigrationService> logger)
{
_twitterTweetsService = twitterTweetsService;
_activityPubService = activityPubService;
_twitterUserDal = twitterUserDal;
_followersDal = followersDal;
_instanceSettings = instanceSettings;
_theFedInfoService = theFedInfoService;
_httpClientFactory = httpClientFactory;
_logger = logger;
}
#endregion

Expand Down Expand Up @@ -63,7 +73,7 @@ public bool ValidateTweet(string acct, string tweetId, MigrationTypeEnum type)

if (tweet.CreatorName.Trim().ToLowerInvariant() != acct.Trim().ToLowerInvariant())
throw new Exception($"Tweet not published by @{acct}");

if (!tweet.MessageContent.Contains(code))
{
var message = "Tweet don't have migration code";
Expand Down Expand Up @@ -180,7 +190,7 @@ private void NotifyFollowers(string acct, SyncTwitterUser twitterAccount, string
}
catch (Exception e)
{
Console.WriteLine(e);
_logger.LogError(e, e.Message);
}
}
});
Expand Down Expand Up @@ -225,7 +235,14 @@ private void DeleteRemoteAccounts(string acct)

var t1 = Task.Run(async () =>
{
await _activityPubService.DeleteUserAsync(acct, host, sharedInbox);
try
{
await _activityPubService.DeleteUserAsync(acct, host, sharedInbox);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}
});
}

Expand All @@ -239,20 +256,74 @@ private void DeleteRemoteAccounts(string acct)

var t1 = Task.Run(async () =>
{
await _activityPubService.DeleteUserAsync(acct, host, sharedInbox);
try
{
await _activityPubService.DeleteUserAsync(acct, host, sharedInbox);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}
});
}
});
}

public async Task TriggerRemoteMigrationAsync(string id, string tweetid, string handle)
public async Task TriggerRemoteMigrationAsync(string id, string tweetIdStg, string handle)
{
var url = $"https://{{0}}/migration/move/{{1}}/{{2}}/{handle}";
await ProcessRemoteMigrationAsync(id, tweetIdStg, url);

}

public async Task TriggerRemoteDeleteAsync(string id, string tweetIdStg)
{
//TODO
var url = $"https://{{0}}/migration/delete/{{1}}/{{2}}";
await ProcessRemoteMigrationAsync(id, tweetIdStg, url);
}

private async Task ProcessRemoteMigrationAsync(string id, string tweetIdStg, string urlPattern)
{
try
{
var instances = await RetrieveCompatibleBslInstancesAsync();
var tweetId = ExtractedTweetId(tweetIdStg);

foreach (var instance in instances)
{
try
{
var host = instance.Host;
if(!UrlRegexes.Domain.IsMatch(host)) continue;

var url = string.Format(urlPattern, host, id, tweetId);

var client = _httpClientFactory.CreateClient();
var result = await client.PostAsync(url, null);
result.EnsureSuccessStatusCode();
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}
}
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}
}

public async Task TriggerRemoteDeleteAsync(string id, string tweetid)
private async Task<List<BslInstanceInfo>> RetrieveCompatibleBslInstancesAsync()
{
//TODO
var instances = await _theFedInfoService.GetBslInstanceListAsync();
var filteredInstances = instances
.Where(x => x.Version >= new Version(0, 21, 0))
.Where(x => string.Compare(x.Host,
_instanceSettings.Domain,
StringComparison.InvariantCultureIgnoreCase) != 0)
.ToList();
return filteredInstances;
}

private byte[] GetHash(string inputString)
Expand Down
162 changes: 162 additions & 0 deletions src/BirdsiteLive.Domain/TheFedInfoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace BirdsiteLive.Domain
{
public interface ITheFedInfoService
{
Task<List<BslInstanceInfo>> GetBslInstanceListAsync();
}

public class TheFedInfoService : ITheFedInfoService
{
private readonly IHttpClientFactory _httpClientFactory;

#region Ctor
public TheFedInfoService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
#endregion

public async Task<List<BslInstanceInfo>> GetBslInstanceListAsync()
{
var cancellationToken = CancellationToken.None;

var result = await CallGraphQlAsync<MyResponseData>(
new Uri("https://the-federation.info/graphql"),
HttpMethod.Get,
"query ($platform: String!) { nodes(platform: $platform) { host, version } }",
new
{
platform = "birdsitelive",
},
cancellationToken);

var convertedResults = ConvertResults(result);
return convertedResults;
}

private List<BslInstanceInfo> ConvertResults(GraphQLResponse<MyResponseData> qlData)
{
var results = new List<BslInstanceInfo>();

foreach (var instanceInfo in qlData.Data.Nodes)
{
try
{
var rawVersion = instanceInfo.Version.Split('+').First();
if (string.IsNullOrWhiteSpace(rawVersion)) continue;
var version = Version.Parse(rawVersion);
if(version <= new Version(0,1,0)) continue;

var instance = new BslInstanceInfo
{
Host = instanceInfo.Host,
Version = version
};
results.Add(instance);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}

return results;
}

private async Task<GraphQLResponse<TResponse>> CallGraphQlAsync<TResponse>(Uri endpoint, HttpMethod method, string query, object variables, CancellationToken cancellationToken)
{
var content = new StringContent(SerializeGraphQlCall(query, variables), Encoding.UTF8, "application/json");
var httpRequestMessage = new HttpRequestMessage
{
Method = method,
Content = content,
RequestUri = endpoint,
};
//add authorization headers if necessary here
httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var httpClient = _httpClientFactory.CreateClient();
using (var response = await httpClient.SendAsync(httpRequestMessage, cancellationToken))
{
//if (response.IsSuccessStatusCode)
if (response?.Content.Headers.ContentType?.MediaType == "application/json")
{
var responseString = await response.Content.ReadAsStringAsync(); //cancellationToken supported for .NET 5/6
return DeserializeGraphQlCall<TResponse>(responseString);
}
else
{
throw new ApplicationException($"Unable to contact '{endpoint}': {response.StatusCode} - {response.ReasonPhrase}");
}
}
}

private string SerializeGraphQlCall(string query, object variables)
{
var sb = new StringBuilder();
var textWriter = new StringWriter(sb);
var serializer = new JsonSerializer();
serializer.Serialize(textWriter, new
{
query = query,
variables = variables,
});
return sb.ToString();
}

private GraphQLResponse<TResponse> DeserializeGraphQlCall<TResponse>(string response)
{
var serializer = new JsonSerializer();
var stringReader = new StringReader(response);
var jsonReader = new JsonTextReader(stringReader);
var result = serializer.Deserialize<GraphQLResponse<TResponse>>(jsonReader);
return result;
}

private class GraphQLResponse<TResponse>
{
public List<GraphQLError> Errors { get; set; }
public TResponse Data { get; set; }
}

private class GraphQLError
{
public string Message { get; set; }
public List<GraphQLErrorLocation> Locations { get; set; }
public List<object> Path { get; set; } //either int or string
}

private class GraphQLErrorLocation
{
public int Line { get; set; }
public int Column { get; set; }
}

private class MyResponseData
{
public Node[] Nodes { get; set; }
}

private class Node
{
public string Host { get; set; }
public string Version { get; set; }
}
}

public class BslInstanceInfo
{
public string Host { get; set; }
public Version Version { get; set; }
}
}
2 changes: 1 addition & 1 deletion src/BirdsiteLive/BirdsiteLive.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<UserSecretsId>d21486de-a812-47eb-a419-05682bb68856</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<Version>0.21.0</Version>
<Version>0.22.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit 8b1a61c

Please sign in to comment.