diff options
Diffstat (limited to 'OsuFederatedBeatmapApi/Services')
3 files changed, 160 insertions, 0 deletions
diff --git a/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBot.cs b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBot.cs new file mode 100644 index 0000000..2b39d93 --- /dev/null +++ b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBot.cs @@ -0,0 +1,92 @@ +using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Services; +using LibMatrix.Utilities.Bot.Interfaces; + +namespace OsuFederatedBeatmapApi.Services; + +public class FederatedBeatmapApiBot(AuthenticatedHomeserverGeneric hs, + ILogger<FederatedBeatmapApiBot> logger, + FederatedBeatmapApiBotConfiguration configuration, + HomeserverResolverService hsResolver, + FederatedBeatmapApiBotAccountDataService accountDataService) : IHostedService { + private readonly IEnumerable<ICommand> _commands; + + private Task _listenerTask; + + /// <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 async Task StartAsync(CancellationToken cancellationToken) { + _listenerTask = Run(cancellationToken); + logger.LogInformation("Bot started!"); + } + + private async Task Run(CancellationToken cancellationToken) { + Directory.GetFiles("bot_data/cache").ToList().ForEach(File.Delete); + + var syncHelper = new SyncHelper(hs); + + List<string> admins = new(); + +#pragma warning disable CS4014 // We don't care if this doesn't wait + Task.Run(async () => { + while (!cancellationToken.IsCancellationRequested) { + var controlRoomMembers = accountDataService.ControlRoom.GetMembersAsync(); + await foreach (var member in controlRoomMembers) { + if ((member.TypedContent as RoomMemberEventContent)? + .Membership == "join") admins.Add(member.UserId); + } + + await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken); + } + }, cancellationToken); +#pragma warning restore CS4014 + + syncHelper.InviteReceivedHandlers.Add(async Task (args) => { + var inviteEvent = + args.Value.InviteState.Events.FirstOrDefault(x => + x.Type == "m.room.member" && x.StateKey == hs.UserId); + logger.LogInformation("Got invite to {RoomId} by {Sender} with reason: {Reason}", args.Key, inviteEvent!.Sender, + (inviteEvent.TypedContent as RoomMemberEventContent)!.Reason); + if (inviteEvent.Sender.EndsWith(":rory.gay") || inviteEvent!.Sender.EndsWith(":conduit.rory.gay") || admins.Contains(inviteEvent.Sender)) { + try { + var senderProfile = await hs.GetProfileAsync(inviteEvent.Sender); + await hs.GetRoom(args.Key).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!"); + } + catch (Exception e) { + logger.LogError("{}", e.ToString()); + await hs.GetRoom(args.Key).LeaveAsync(reason: "I was unable to join the room: " + e); + } + } + }); + + syncHelper.TimelineEventHandlers.Add(async @event => { + var room = hs.GetRoom(@event.RoomId); + try { + logger.LogInformation( + "Got timeline event in {}: {}", @event.RoomId, @event.ToJson(indent: true, ignoreNull: true)); + + if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message }) { } + } + catch (Exception e) { + logger.LogError("{}", e.ToString()); + await accountDataService.ControlRoom.SendMessageEventAsync( + MessageFormatter.FormatException($"Exception handling event {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); + await accountDataService.LogRoom.SendMessageEventAsync( + MessageFormatter.FormatException($"Exception handling event {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); + await using var stream = new MemoryStream(e.ToString().AsBytes().ToArray()); + await accountDataService.ControlRoom.SendFileAsync("error.log.cs", stream); + await accountDataService.LogRoom.SendFileAsync("error.log.cs", stream); + } + }); + } + + /// <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 bot!"); + } +} diff --git a/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotAccountDataService.cs b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotAccountDataService.cs new file mode 100644 index 0000000..0f4aa6a --- /dev/null +++ b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotAccountDataService.cs @@ -0,0 +1,60 @@ +using LibMatrix; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using LibMatrix.RoomTypes; +using OsuFederatedBeatmapApi.Events.AccountData; + +namespace OsuFederatedBeatmapApi.Services; + +public class FederatedBeatmapApiBotAccountDataService(ILogger<FederatedBeatmapApiBotAccountDataService> logger, AuthenticatedHomeserverGeneric hs, FederatedBeatmapApiBotConfiguration configuration) { + private const string BotDataKey = "gay.rory.federated_beatmap_repository_bot_data"; + + public GenericRoom LogRoom; + public GenericRoom ControlRoom; + private BotData botData; + + public async Task LoadAccountData() { + try { + botData = await hs.GetAccountDataAsync<BotData>(BotDataKey); + } + catch (Exception e) { + if (e is not MatrixException { ErrorCode: "M_NOT_FOUND" }) { + logger.LogError("{}", e.ToString()); + throw; + } + + botData = new BotData(); + var creationContent = CreateRoomRequest.CreatePrivate(hs, name: "Beatmap Repository - Control room", roomAliasName: "beatmap-repo-control-room"); + creationContent.Invite = configuration.Admins; + creationContent.CreationContent["type"] = "gay.rory.federated_beatmap_repository.control_room"; + + botData.ControlRoom = (await hs.CreateRoom(creationContent)).RoomId; + + //set access rules to allow joining via control room + creationContent.InitialState.Add(new StateEvent { + Type = "m.room.join_rules", + StateKey = "", + TypedContent = new RoomJoinRulesEventContent { + JoinRule = "knock_restricted", + Allow = new() { + new RoomJoinRulesEventContent.AllowEntry { + Type = "m.room_membership", + RoomId = botData.ControlRoom + } + } + } + }); + + creationContent.Name = "Beatmap Repository - Log room"; + creationContent.RoomAliasName = "beatmap-repo-log-room"; + creationContent.CreationContent["type"] = "gay.rory.media_moderator_poc.log_room"; + botData.LogRoom = (await hs.CreateRoom(creationContent)).RoomId; + + await hs.SetAccountData(BotDataKey, botData); + } + + LogRoom = hs.GetRoom(botData.LogRoom ?? botData.ControlRoom); + ControlRoom = hs.GetRoom(botData.ControlRoom); + } +} diff --git a/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotConfiguration.cs b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotConfiguration.cs new file mode 100644 index 0000000..b77cd75 --- /dev/null +++ b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotConfiguration.cs @@ -0,0 +1,8 @@ +namespace OsuFederatedBeatmapApi.Services; + +public class FederatedBeatmapApiBotConfiguration { + public FederatedBeatmapApiBotConfiguration(IConfiguration config) => config.GetRequiredSection("BeatmapApiBot").Bind(this); + + public List<string> Admins { get; set; } = new(); + public bool DemoMode { get; set; } = false; +} |