about summary refs log tree commit diff
path: root/MiniUtils/Services/AutoTombstoneFollowerService.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MiniUtils/Services/AutoTombstoneFollowerService.cs')
-rw-r--r--MiniUtils/Services/AutoTombstoneFollowerService.cs85
1 files changed, 85 insertions, 0 deletions
diff --git a/MiniUtils/Services/AutoTombstoneFollowerService.cs b/MiniUtils/Services/AutoTombstoneFollowerService.cs
new file mode 100644

index 0000000..0b9a444 --- /dev/null +++ b/MiniUtils/Services/AutoTombstoneFollowerService.cs
@@ -0,0 +1,85 @@ +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Filters; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; + +namespace MiniUtils.Services; + +public class AutoTombstoneFollowerService( + AuthenticatedHomeserverGeneric hs, + ILogger<AutoTombstoneFollowerService> logger, + MiniUtilsConfiguration config +) + : IHostedService { + private Task? _listenerTask; + private readonly CancellationTokenSource _cts = new(); + + /// <summary>Triggered when the application host is ready to start the service.</summary> + /// <param name="cancellationToken">Indicates that the start process has been aborted.</param> + public Task StartAsync(CancellationToken cancellationToken) { + if (!config.FollowTombstones) return Task.CompletedTask; + _listenerTask = Run(_cts.Token); + logger.LogInformation("Tombstone follower started (StartAsync)!"); + return Task.CompletedTask; + } + + private async Task? Run(CancellationToken cancellationToken) { + logger.LogInformation("Starting Tombstone listener!"); + var filter = await hs.NamedCaches.FilterCache.GetOrSetValueAsync("gay.rory.miniutils.services.tombstone_follower", + new SyncFilter() { + AccountData = new SyncFilter.EventFilter(notTypes: ["*"], limit: 1), + Presence = new SyncFilter.EventFilter(notTypes: ["*"]), + Room = new SyncFilter.RoomFilter() { + AccountData = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]), + Ephemeral = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]), + State = new SyncFilter.RoomFilter.StateFilter(types: [RoomTombstoneEventContent.EventId]), + Timeline = new SyncFilter.RoomFilter.StateFilter(types: [RoomTombstoneEventContent.EventId]), + } + }); + + var syncHelper = new SyncHelper(hs, logger) { + FilterId = filter, + UseMsc4222StateAfter = true + }; + + syncHelper.SyncReceivedHandlers.Add(async sync => { + logger.LogInformation("Sync received!"); + var joinedRooms = await hs.GetJoinedRooms(); + foreach (var roomResp in sync.Rooms?.Join ?? []) { + if (roomResp.Value.StateAfter?.Events is null) continue; + foreach (var @event in roomResp.Value.StateAfter.Events) { + if (@event is not { Type: RoomTombstoneEventContent.EventId, StateKey: not null }) continue; + var replacement = @event.ContentAs<RoomTombstoneEventContent>()!.ReplacementRoom; + if (string.IsNullOrWhiteSpace(replacement)) { + logger.LogError("[{}] Tombstone event with no replacement room!", roomResp.Key); + continue; + } + + var room = hs.GetRoom(roomResp.Key); + if (joinedRooms.Any(x => x.RoomId == replacement)) { + // logger.LogWarning("[{}] Replacement room {} is already joined!", roomResp.Key, replacement); + continue; + } + + await room.JoinAsync(reason: "Following tombstone", homeservers: [replacement.Split(':', 2)[1]]); + await Task.Delay(1000, cancellationToken); + joinedRooms = await hs.GetJoinedRooms(); + } + } + }); + + await syncHelper.RunSyncLoopAsync(cancellationToken: _cts.Token); + } + + /// <summary>Triggered when the application host is performing a graceful shutdown.</summary> + /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param> + public async Task StopAsync(CancellationToken cancellationToken) { + logger.LogInformation("Shutting down command listener!"); + if (_listenerTask is null) { + logger.LogError("Could not shut down command listener task because it was null!"); + return; + } + + await _cts.CancelAsync(); + } +} \ No newline at end of file