using System.Collections.Frozen; using ArcaneLibs.Extensions; using LibMatrix; using LibMatrix.EventTypes.Spec; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; using LibMatrix.RoomTypes; using LibMatrix.Utilities.Bot.Interfaces; using MiniUtils.Classes; using MiniUtils.Services; namespace MiniUtils.Commands; public class RedactCommand(IgnoreListManager ignoreListManager) : ICommand { public string Name => "redact"; public string[]? Aliases => []; public string Description => "Redact all user's events"; public bool Unlisted => false; public async Task Invoke(CommandContext ctx) { if (ctx.Args is ["banned"]) await RedactUsers(ctx, await ctx.Room.GetMemberIdsListAsync("ban")); else if (ctx.Args is [.. var senders]) { var sendersSet = senders.ToFrozenSet(); await RedactUsers(ctx, sendersSet); } } private async Task RedactUsers(CommandContext ctx, FrozenSet senders) { await ignoreListManager.MoveList(false, senders); var count = 0; // var subCount = 0; // List tasks = []; foreach (var senderChunk in senders.Chunk(10)) { var filter = new SyncFilter.EventFilter(senders: senderChunk.ToList(), notTypes: ["m.room.redaction"]); await foreach (var resp in ctx.Room.GetManyMessagesAsync(filter: filter.ToJson(false, ignoreNull: true), chunkSize: 1000)) { // foreach (var chunk in resp.Chunk.Chunk(49)) { // foreach (var evt in chunk) { // if (!senders.Contains(evt.Sender!)) continue; // if (!await IsRedactionNeeded(ctx.Room, evt.EventId!, evt)) continue; // tasks.Add(RedactEvent(ctx.Room, evt.EventId!)); // count++; // subCount++; // } // // if (subCount >= 40) { // await ctx.Room.SendMessageEventAsync(new MessageBuilder() // .WithBody( // $"[{Emojis.Hourglass}] {Emojis.Recycle} {count} ({Emojis.Checkmark} {tasks.Count(t => t.IsCompletedSuccessfully)} {Emojis.Prohibited} {tasks.Count(t => t.IsFaulted)} {Emojis.Hourglass} {tasks.Count(t => t.Status == TaskStatus.Running)})") // .Build()); // // await Task.WhenAll(tasks); // subCount = 0; // } // } var toRedactQueryTask = resp.Chunk .Where(x => senders.Contains(x.Sender!)) .Select(async x => (x, await IsRedactionNeeded(ctx.Room, x.EventId!, x))) .ToList(); var toRedact = (await Task.WhenAll(toRedactQueryTask)).Where(x => x.Item2).Select(x => x.x).ToList(); foreach (var chunk in toRedact.Chunk(49)) { var toSend = chunk.Select(x => new StateEvent() { Type = RoomRedactionEventContent.EventId, TypedContent = new RoomRedactionEventContent() { Redacts = x.EventId } }).ToList(); toSend.Add(new StateEvent() { Type = RoomMessageEventContent.EventId, TypedContent = new MessageBuilder() .WithBody( $"[{Emojis.Hourglass}] {Emojis.Recycle} {count} ({Emojis.Checkmark} {count} {Emojis.Hourglass} {toSend.Count})") .Build() }); count += toSend.Count - 1; await ctx.Room.BulkSendEventsAsync(toSend); } } } // await Task.WhenAll(tasks); await ctx.Room.SendMessageEventAsync(new MessageBuilder().WithBody($"{Emojis.Recycle} {count}").Build()); // await ctx.Room.SendReactionAsync(ctx.MessageEvent.EventId!, $"{Emojis.Recycle} {count}"); } private async Task IsRedactionNeeded(GenericRoom roomId, string eventId, StateEventResponse? evt = null) { evt ??= await roomId.GetEventAsync(eventId); // Ignore room member state events if (evt is { StateKey: not null, Type: not RoomMemberEventContent.EventId }) return false; // Ignore redaction events if (evt is { Type: RoomRedactionEventContent.EventId }) return false; // Ignore empty events if (evt is { RawContent: null or { Count: 0 } }) return false; // Ignore redacted events if (evt.Unsigned?.ContainsKey("redacted_because") == true) return false; return true; // throw new NotImplementedException("Redaction check not implemented"); } private async Task RedactEvent(GenericRoom room, string eventId) { bool success; do { try { await room.RedactEventAsync(eventId); success = true; } catch (Exception e) { success = false; Console.WriteLine($"Failed to redact event {eventId}: {e}"); } } while (!success); } }