2022-11-02 05:10:46 +01:00
using System ;
2022-12-28 23:52:05 +01:00
using System.Collections.Generic ;
2022-11-02 05:10:46 +01:00
using System.Linq ;
using BirdsiteLive.Twitter ;
using System.Security.Cryptography ;
using System.Text ;
using System.Threading.Tasks ;
2022-11-04 07:07:50 +01:00
using BirdsiteLive.ActivityPub ;
using BirdsiteLive.ActivityPub.Models ;
using BirdsiteLive.DAL.Contracts ;
using BirdsiteLive.ActivityPub.Converters ;
using BirdsiteLive.Common.Settings ;
2022-12-14 04:55:22 +01:00
using BirdsiteLive.DAL.Models ;
using BirdsiteLive.Domain.Enum ;
2022-12-28 23:52:05 +01:00
using System.Net.Http ;
using BirdsiteLive.Common.Regexes ;
2022-12-29 00:00:24 +01:00
using Microsoft.Extensions.Logging ;
2022-11-02 05:10:46 +01:00
namespace BirdsiteLive.Domain
{
public class MigrationService
{
2022-11-04 07:07:50 +01:00
private readonly InstanceSettings _instanceSettings ;
2022-12-28 23:52:05 +01:00
private readonly ITheFedInfoService _theFedInfoService ;
2022-11-02 05:10:46 +01:00
private readonly ITwitterTweetsService _twitterTweetsService ;
2022-11-02 06:15:05 +01:00
private readonly IActivityPubService _activityPubService ;
2022-11-04 07:07:50 +01:00
private readonly ITwitterUserDal _twitterUserDal ;
private readonly IFollowersDal _followersDal ;
2022-12-28 23:52:05 +01:00
private readonly IHttpClientFactory _httpClientFactory ;
2022-12-29 00:00:24 +01:00
private readonly ILogger < MigrationService > _logger ;
2022-11-02 05:10:46 +01:00
#region Ctor
2022-12-29 00:00:24 +01:00
public MigrationService ( ITwitterTweetsService twitterTweetsService , IActivityPubService activityPubService , ITwitterUserDal twitterUserDal , IFollowersDal followersDal , InstanceSettings instanceSettings , ITheFedInfoService theFedInfoService , IHttpClientFactory httpClientFactory , ILogger < MigrationService > logger )
2022-11-02 05:10:46 +01:00
{
_twitterTweetsService = twitterTweetsService ;
2022-11-02 06:15:05 +01:00
_activityPubService = activityPubService ;
2022-11-04 07:07:50 +01:00
_twitterUserDal = twitterUserDal ;
_followersDal = followersDal ;
_instanceSettings = instanceSettings ;
2022-12-28 23:52:05 +01:00
_theFedInfoService = theFedInfoService ;
_httpClientFactory = httpClientFactory ;
2022-12-29 00:00:24 +01:00
_logger = logger ;
2022-11-02 05:10:46 +01:00
}
#endregion
public string GetMigrationCode ( string acct )
{
var hash = GetHashString ( acct ) ;
return $"[[BirdsiteLIVE-MigrationCode|{hash.Substring(0, 10)}]]" ;
}
2022-12-14 04:55:22 +01:00
public string GetDeletionCode ( string acct )
2022-11-02 05:10:46 +01:00
{
2022-12-14 04:55:22 +01:00
var hash = GetHashString ( acct ) ;
return $"[[BirdsiteLIVE-DeletionCode|{hash.Substring(0, 10)}]]" ;
}
public bool ValidateTweet ( string acct , string tweetId , MigrationTypeEnum type )
{
string code ;
if ( type = = MigrationTypeEnum . Migration )
code = GetMigrationCode ( acct ) ;
else if ( type = = MigrationTypeEnum . Deletion )
code = GetDeletionCode ( acct ) ;
else
throw new NotImplementedException ( ) ;
2022-11-02 05:10:46 +01:00
var castedTweetId = ExtractedTweetId ( tweetId ) ;
var tweet = _twitterTweetsService . GetTweet ( castedTweetId ) ;
2022-11-04 07:07:50 +01:00
if ( tweet = = null )
throw new Exception ( "Tweet not found" ) ;
2022-12-14 04:55:22 +01:00
if ( tweet . CreatorName . Trim ( ) . ToLowerInvariant ( ) ! = acct . Trim ( ) . ToLowerInvariant ( ) )
2022-11-04 07:07:50 +01:00
throw new Exception ( $"Tweet not published by @{acct}" ) ;
2022-12-28 23:52:05 +01:00
2022-12-14 04:55:22 +01:00
if ( ! tweet . MessageContent . Contains ( code ) )
2022-12-26 00:15:54 +01:00
{
var message = "Tweet don't have migration code" ;
if ( type = = MigrationTypeEnum . Deletion )
message = "Tweet don't have deletion code" ;
throw new Exception ( message ) ;
}
2022-11-02 05:10:46 +01:00
return true ;
}
private long ExtractedTweetId ( string tweetId )
{
2022-12-26 00:15:54 +01:00
if ( string . IsNullOrWhiteSpace ( tweetId ) )
throw new ArgumentException ( "No provided Tweet ID" ) ;
2022-11-02 05:10:46 +01:00
long castedId ;
if ( long . TryParse ( tweetId , out castedId ) )
return castedId ;
var urlPart = tweetId . Split ( '/' ) . LastOrDefault ( ) ;
if ( long . TryParse ( urlPart , out castedId ) )
return castedId ;
throw new ArgumentException ( "Unvalid Tweet ID" ) ;
}
2022-11-04 07:07:50 +01:00
public async Task < ValidatedFediverseUser > ValidateFediverseAcctAsync ( string fediverseAcct )
2022-11-02 05:10:46 +01:00
{
2022-11-02 06:15:05 +01:00
if ( string . IsNullOrWhiteSpace ( fediverseAcct ) )
throw new ArgumentException ( "Please provide Fediverse account" ) ;
2022-12-14 04:55:22 +01:00
if ( ! fediverseAcct . Contains ( '@' ) | | ! fediverseAcct . StartsWith ( "@" ) | | fediverseAcct . Trim ( '@' ) . Split ( '@' ) . Length ! = 2 )
2022-11-02 06:15:05 +01:00
throw new ArgumentException ( "Please provide valid Fediverse handle" ) ;
var objectId = await _activityPubService . GetUserIdAsync ( fediverseAcct ) ;
var user = await _activityPubService . GetUser ( objectId ) ;
2022-11-04 07:07:50 +01:00
var result = new ValidatedFediverseUser
{
FediverseAcct = fediverseAcct ,
ObjectId = objectId ,
User = user ,
IsValid = user ! = null
} ;
return result ;
}
2022-11-02 06:15:05 +01:00
2022-12-21 00:47:21 +01:00
public async Task MigrateAccountAsync ( ValidatedFediverseUser validatedUser , string acct )
2022-11-04 07:07:50 +01:00
{
// Apply moved to
var twitterAccount = await _twitterUserDal . GetTwitterUserAsync ( acct ) ;
2022-11-04 08:14:00 +01:00
if ( twitterAccount = = null )
{
await _twitterUserDal . CreateTwitterUserAsync ( acct , - 1 , validatedUser . ObjectId , validatedUser . FediverseAcct ) ;
2022-12-14 04:55:22 +01:00
twitterAccount = await _twitterUserDal . GetTwitterUserAsync ( acct ) ;
2022-11-04 08:14:00 +01:00
}
2022-12-28 04:35:11 +01:00
twitterAccount . MovedTo = validatedUser . User . id ;
2022-12-14 04:55:22 +01:00
twitterAccount . MovedToAcct = validatedUser . FediverseAcct ;
2022-12-25 00:44:41 +01:00
twitterAccount . LastSync = DateTime . UtcNow ;
2022-12-14 04:55:22 +01:00
await _twitterUserDal . UpdateTwitterUserAsync ( twitterAccount ) ;
2022-11-04 07:07:50 +01:00
// Notify Followers
2022-12-28 04:13:08 +01:00
var message = $@"<p>[BSL MIRROR SERVICE NOTIFICATION]<br/>This bot has been disabled by its original owner.<br/>It has been redirected to {validatedUser.FediverseAcct}.</p>" ;
2022-12-21 00:47:21 +01:00
NotifyFollowers ( acct , twitterAccount , message ) ;
2022-12-14 04:55:22 +01:00
}
private void NotifyFollowers ( string acct , SyncTwitterUser twitterAccount , string message )
{
2022-11-04 07:07:50 +01:00
var t = Task . Run ( async ( ) = >
{
var followers = await _followersDal . GetFollowersAsync ( twitterAccount . Id ) ;
foreach ( var follower in followers )
{
try
{
var noteId = Guid . NewGuid ( ) . ToString ( ) ;
var actorUrl = UrlFactory . GetActorUrl ( _instanceSettings . Domain , acct ) ;
var noteUrl = UrlFactory . GetNoteUrl ( _instanceSettings . Domain , acct , noteId ) ;
2022-12-14 04:55:22 +01:00
//var to = validatedUser.ObjectId;
var to = follower . ActorId ;
2022-11-04 07:07:50 +01:00
var cc = new string [ 0 ] ;
var note = new Note
{
2022-12-28 04:13:08 +01:00
id = noteUrl ,
2022-11-04 07:07:50 +01:00
published = DateTime . UtcNow . ToString ( "s" ) + "Z" ,
url = noteUrl ,
attributedTo = actorUrl ,
to = new [ ] { to } ,
cc = cc ,
2022-12-28 04:13:08 +01:00
content = message ,
tag = new Tag [ ] {
new Tag ( )
{
type = "Mention" ,
href = follower . ActorId ,
name = $"@{follower.Acct}@{follower.Host}"
}
} ,
2022-11-04 07:07:50 +01:00
} ;
2022-12-28 04:13:08 +01:00
if ( ! string . IsNullOrWhiteSpace ( follower . SharedInboxRoute ) )
await _activityPubService . PostNewNoteActivity ( note , acct , noteId , follower . Host , follower . SharedInboxRoute ) ;
else
await _activityPubService . PostNewNoteActivity ( note , acct , noteId , follower . Host , follower . InboxRoute ) ;
2022-11-04 07:07:50 +01:00
}
catch ( Exception e )
{
2022-12-29 00:00:24 +01:00
_logger . LogError ( e , e . Message ) ;
2022-11-04 07:07:50 +01:00
}
}
} ) ;
2022-11-02 05:10:46 +01:00
}
2022-12-21 00:47:21 +01:00
public async Task DeleteAccountAsync ( string acct )
2022-12-14 04:55:22 +01:00
{
2022-12-21 07:06:45 +01:00
// Apply deleted state
2022-12-14 04:55:22 +01:00
var twitterAccount = await _twitterUserDal . GetTwitterUserAsync ( acct ) ;
if ( twitterAccount = = null )
{
await _twitterUserDal . CreateTwitterUserAsync ( acct , - 1 ) ;
twitterAccount = await _twitterUserDal . GetTwitterUserAsync ( acct ) ;
}
twitterAccount . Deleted = true ;
2022-12-25 00:44:41 +01:00
twitterAccount . LastSync = DateTime . UtcNow ;
2022-12-14 04:55:22 +01:00
await _twitterUserDal . UpdateTwitterUserAsync ( twitterAccount ) ;
2022-12-21 07:06:45 +01:00
2022-12-14 04:55:22 +01:00
// Notify Followers
2022-12-28 04:13:08 +01:00
var message = $@"<p>[BSL MIRROR SERVICE NOTIFICATION]<br/>This bot has been deleted by its original owner.<br/></p>" ;
2022-12-21 00:47:21 +01:00
NotifyFollowers ( acct , twitterAccount , message ) ;
2022-12-21 07:06:45 +01:00
// Delete remote accounts
DeleteRemoteAccounts ( acct ) ;
}
private void DeleteRemoteAccounts ( string acct )
{
var t = Task . Run ( async ( ) = >
{
var allUsers = await _followersDal . GetAllFollowersAsync ( ) ;
var followersWtSharedInbox = allUsers
. Where ( x = > ! string . IsNullOrWhiteSpace ( x . SharedInboxRoute ) )
. GroupBy ( x = > x . Host )
. ToList ( ) ;
foreach ( var followerGroup in followersWtSharedInbox )
{
var host = followerGroup . First ( ) . Host ;
var sharedInbox = followerGroup . First ( ) . SharedInboxRoute ;
var t1 = Task . Run ( async ( ) = >
{
2022-12-29 00:00:24 +01:00
try
{
await _activityPubService . DeleteUserAsync ( acct , host , sharedInbox ) ;
}
catch ( Exception e )
{
_logger . LogError ( e , e . Message ) ;
}
2022-12-21 07:06:45 +01:00
} ) ;
}
var followerWtInbox = allUsers
. Where ( x = > ! string . IsNullOrWhiteSpace ( x . SharedInboxRoute ) )
. ToList ( ) ;
foreach ( var followerGroup in followerWtInbox )
{
var host = followerGroup . Host ;
var sharedInbox = followerGroup . InboxRoute ;
var t1 = Task . Run ( async ( ) = >
{
2022-12-29 00:00:24 +01:00
try
{
await _activityPubService . DeleteUserAsync ( acct , host , sharedInbox ) ;
}
catch ( Exception e )
{
_logger . LogError ( e , e . Message ) ;
}
2022-12-21 07:06:45 +01:00
} ) ;
}
} ) ;
2022-12-14 04:55:22 +01:00
}
2022-12-28 23:52:05 +01:00
public async Task TriggerRemoteMigrationAsync ( string id , string tweetIdStg , string handle )
2022-11-02 05:10:46 +01:00
{
2022-12-28 23:52:05 +01:00
var url = $"https://{{0}}/migration/move/{{1}}/{{2}}/{handle}" ;
await ProcessRemoteMigrationAsync ( id , tweetIdStg , url ) ;
}
public async Task TriggerRemoteDeleteAsync ( string id , string tweetIdStg )
{
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 )
{
2022-12-29 00:00:24 +01:00
_logger . LogError ( e , e . Message ) ;
2022-12-28 23:52:05 +01:00
}
}
}
catch ( Exception e )
{
2022-12-29 00:00:24 +01:00
_logger . LogError ( e , e . Message ) ;
2022-12-28 23:52:05 +01:00
}
2022-11-02 05:10:46 +01:00
}
2022-12-28 23:52:05 +01:00
private async Task < List < BslInstanceInfo > > RetrieveCompatibleBslInstancesAsync ( )
2022-12-14 04:55:22 +01:00
{
2022-12-28 23:52:05 +01:00
var instances = await _theFedInfoService . GetBslInstanceListAsync ( ) ;
var filteredInstances = instances
. Where ( x = > x . Version > = new Version ( 0 , 21 , 0 ) )
2022-12-28 23:56:25 +01:00
. Where ( x = > string . Compare ( x . Host ,
_instanceSettings . Domain ,
StringComparison . InvariantCultureIgnoreCase ) ! = 0 )
2022-12-28 23:52:05 +01:00
. ToList ( ) ;
return filteredInstances ;
2022-12-14 04:55:22 +01:00
}
2022-11-02 05:10:46 +01:00
private byte [ ] GetHash ( string inputString )
{
using ( HashAlgorithm algorithm = SHA256 . Create ( ) )
return algorithm . ComputeHash ( Encoding . UTF8 . GetBytes ( inputString ) ) ;
}
private string GetHashString ( string inputString )
{
StringBuilder sb = new StringBuilder ( ) ;
foreach ( byte b in GetHash ( inputString ) )
sb . Append ( b . ToString ( "X2" ) ) ;
return sb . ToString ( ) ;
}
}
2022-11-04 07:07:50 +01:00
public class ValidatedFediverseUser
{
public string FediverseAcct { get ; set ; }
public string ObjectId { get ; set ; }
public Actor User { get ; set ; }
public bool IsValid { get ; set ; }
}
2022-11-02 05:10:46 +01:00
}