From f4a9674206970b0c3e68cb2602cfe13d45e59667 Mon Sep 17 00:00:00 2001 From: JB Carreon Date: Thu, 8 Jun 2023 19:55:04 +0800 Subject: [PATCH] Added Report commands --- MacroBotApp/Config/BotConfig.cs | 1 + .../DataAccess/AutoMapper/ReportMapping.cs | 13 ++ .../DataAccess/Entities/ReportEntity.cs | 13 ++ .../ReportEntityConfiguration.cs | 34 ++++ MacroBotApp/DataAccess/MacroBotContext.cs | 8 +- .../Repositories/ReportRepository.cs | 98 ++++++++++ .../RepositoryInterfaces/IReportRepository.cs | 17 ++ MacroBotApp/Discord/Modules/Reports/Report.cs | 13 ++ .../Modules/Reports/ReportInteractions.cs | 182 ++++++++++++++++++ .../Discord/Modules/Reports/ReportModule.cs | 137 +++++++++++++ .../Discord/Modules/Reports/ReportUtils.cs | 128 ++++++++++++ MacroBotApp/Extensions/StringExtensions.cs | 20 ++ MacroBotApp/MacroBotApp.csproj | 23 +++ MacroBotApp/Startup.cs | 3 + 14 files changed, 687 insertions(+), 3 deletions(-) create mode 100644 MacroBotApp/DataAccess/AutoMapper/ReportMapping.cs create mode 100644 MacroBotApp/DataAccess/Entities/ReportEntity.cs create mode 100644 MacroBotApp/DataAccess/EntityConfiguations/ReportEntityConfiguration.cs create mode 100644 MacroBotApp/DataAccess/Repositories/ReportRepository.cs create mode 100644 MacroBotApp/DataAccess/RepositoryInterfaces/IReportRepository.cs create mode 100644 MacroBotApp/Discord/Modules/Reports/Report.cs create mode 100644 MacroBotApp/Discord/Modules/Reports/ReportInteractions.cs create mode 100644 MacroBotApp/Discord/Modules/Reports/ReportModule.cs create mode 100644 MacroBotApp/Discord/Modules/Reports/ReportUtils.cs diff --git a/MacroBotApp/Config/BotConfig.cs b/MacroBotApp/Config/BotConfig.cs index 4b8118b..4b43d02 100644 --- a/MacroBotApp/Config/BotConfig.cs +++ b/MacroBotApp/Config/BotConfig.cs @@ -19,6 +19,7 @@ public class ChannelsConfig public ulong ErrorLogChannelId { get; set; } public ulong MemberScreeningChannelId { get; set; } public ulong StatusCheckChannelId { get; set; } + public ulong ReportsChannelId { get; set; } public ulong[] ImageOnlyChannels { get; set; } } } diff --git a/MacroBotApp/DataAccess/AutoMapper/ReportMapping.cs b/MacroBotApp/DataAccess/AutoMapper/ReportMapping.cs new file mode 100644 index 0000000..3e4cb37 --- /dev/null +++ b/MacroBotApp/DataAccess/AutoMapper/ReportMapping.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using MacroBot.DataAccess.Entities; +using MacroBot.Discord.Modules.Reports; + +namespace MacroBot.DataAccess.AutoMapper; + +public class ReportMapping : Profile +{ + public ReportMapping() + { + CreateMap(); + } +} \ No newline at end of file diff --git a/MacroBotApp/DataAccess/Entities/ReportEntity.cs b/MacroBotApp/DataAccess/Entities/ReportEntity.cs new file mode 100644 index 0000000..62cbf68 --- /dev/null +++ b/MacroBotApp/DataAccess/Entities/ReportEntity.cs @@ -0,0 +1,13 @@ +namespace MacroBot.DataAccess.Entities; + +public class ReportEntity +{ + public string Id { get; set; } + public ulong Reporter { get; set; } + public ulong Guild { get; set; } + public ulong User { get; set; } + public ulong? Channel { get; set; } + public ulong? Message { get; set; } + public string Content { get; set; } + public DateTime Reported { get; set; } +} \ No newline at end of file diff --git a/MacroBotApp/DataAccess/EntityConfiguations/ReportEntityConfiguration.cs b/MacroBotApp/DataAccess/EntityConfiguations/ReportEntityConfiguration.cs new file mode 100644 index 0000000..90e8b36 --- /dev/null +++ b/MacroBotApp/DataAccess/EntityConfiguations/ReportEntityConfiguration.cs @@ -0,0 +1,34 @@ +using MacroBot.DataAccess.Entities; +using MacroBot.Discord.Modules.Reports; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace MacroBot.DataAccess.EntityConfiguations; + +public class ReportEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("reports"); + builder.HasKey(e => e.Id); + builder.Property(p => p.Id) + .HasColumnName("id") + .IsRequired(); + builder.Property(p => p.Reporter) + .HasColumnName("reporter") + .IsRequired(); + builder.Property(p => p.Content) + .HasColumnName("content") + .IsRequired(); + builder.Property(p => p.Guild) + .HasColumnName("guild") + .IsRequired(); + builder.Property(p => p.User) + .HasColumnName("user") + .IsRequired(); + builder.Property(p => p.Reported) + .HasColumnName("reported") + .IsRequired() + .HasColumnType("datetime2"); + } +} \ No newline at end of file diff --git a/MacroBotApp/DataAccess/MacroBotContext.cs b/MacroBotApp/DataAccess/MacroBotContext.cs index 8c9b029..03846a6 100644 --- a/MacroBotApp/DataAccess/MacroBotContext.cs +++ b/MacroBotApp/DataAccess/MacroBotContext.cs @@ -10,13 +10,14 @@ namespace MacroBot.DataAccess; public class MacroBotContext : DbContext { public DbSet TagEntities => Set(); - + public DbSet ReportEntities => Set(); + protected override void OnConfiguring(DbContextOptionsBuilder options) { if (options.IsConfigured) return; var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = Paths.DatabasePath }; var connectionString = connectionStringBuilder.ToString(); - var connection = new SqliteConnection(connectionString); + var connection = new SqliteConnection(connectionString); var loggerFactory = new LoggerFactory() .AddSerilog(); options.UseSqlite(connection, @@ -25,9 +26,10 @@ protected override void OnConfiguring(DbContextOptionsBuilder options) .Name)); options.UseLoggerFactory(loggerFactory); } - + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new TagEntityConfiguration()); + modelBuilder.ApplyConfiguration(new ReportEntityConfiguration()); } } \ No newline at end of file diff --git a/MacroBotApp/DataAccess/Repositories/ReportRepository.cs b/MacroBotApp/DataAccess/Repositories/ReportRepository.cs new file mode 100644 index 0000000..5d67d1d --- /dev/null +++ b/MacroBotApp/DataAccess/Repositories/ReportRepository.cs @@ -0,0 +1,98 @@ +using AutoMapper; +using MacroBot.DataAccess.Entities; +using MacroBot.DataAccess.RepositoryInterfaces; +using MacroBot.Discord.Modules.Reports; +using Microsoft.EntityFrameworkCore; +using Serilog; +using ILogger = Serilog.ILogger; + +namespace MacroBot.DataAccess.Repositories; + +public class ReportRepository : IReportRepository +{ + private readonly MacroBotContext _dbContext; + private readonly ILogger _logger = Log.ForContext(); + private readonly IMapper _mapper; + + public ReportRepository(MacroBotContext dbContext, IMapper mapper) + { + _dbContext = dbContext; + _mapper = mapper; + } + + public async Task GetReport(string id) + { + var existingReport = + await _dbContext.ReportEntities.FirstOrDefaultAsync(x => x.Id == id); + + if (existingReport is null) + { + _logger.Warning("Report {ReportId} not found", id); + return null; + } + + var mappedReport = _mapper.Map(existingReport); + return mappedReport; + } + + public async Task ReportExists(string id) + { + return await _dbContext.ReportEntities.AnyAsync(x => x.Id == id); + } + + public async Task ReportExists(ulong reporter, ulong user, ulong guildId) + { + return await _dbContext.ReportEntities.AnyAsync(x => + x.Reporter == reporter && x.User == user && x.Guild == guildId); + } + + public async Task CreateReport(ulong reporter, ulong user, ulong guild, + string content, ulong? channel = null, ulong? message = null, string? id = null, + DateTime? reported = null) + { + + var reportEntity = new ReportEntity + { + Reporter = reporter, + User = user, + Guild = guild, + Id = id ?? GenerateReportId(6), + Channel = channel, + Message = message, + Content = content, + Reported = reported ?? DateTime.Now + }; + await _dbContext.ReportEntities.AddAsync(reportEntity); + await _dbContext.SaveChangesAsync(); + return reportEntity.Id; + } + + public async Task DeleteReport(string id) + { + var existingReport = await _dbContext.ReportEntities.FirstOrDefaultAsync(x => x.Id == id); + if (existingReport is null) + { + _logger.Warning("Cannot delete report {ReportId} - Report ID not found", id); + return; + } + + _dbContext.ReportEntities.Remove(existingReport); + await _dbContext.SaveChangesAsync(); + } + + public async Task> GetReportsForGuild(ulong guildId) + { + var guildReports = await _dbContext.ReportEntities.Where(x => x.Guild == guildId).ToArrayAsync(); + if (guildReports.Length == 0) return Enumerable.Empty(); + var guildReportsMapped = _mapper.Map, IEnumerable>(guildReports); + return guildReportsMapped; + } + + public static string GenerateReportId(int length) + { + var random = new Random(); + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } +} \ No newline at end of file diff --git a/MacroBotApp/DataAccess/RepositoryInterfaces/IReportRepository.cs b/MacroBotApp/DataAccess/RepositoryInterfaces/IReportRepository.cs new file mode 100644 index 0000000..0b2bc23 --- /dev/null +++ b/MacroBotApp/DataAccess/RepositoryInterfaces/IReportRepository.cs @@ -0,0 +1,17 @@ +using MacroBot.Discord.Modules.Reports; + +namespace MacroBot.DataAccess.RepositoryInterfaces; + +public interface IReportRepository +{ + public Task GetReport(string id); + public Task> GetReportsForGuild(ulong guildId); + public Task DeleteReport(string id); + + public Task CreateReport(ulong reporter, ulong user, ulong guild, + string content, ulong? channel = null, ulong? message = null, string? id = null, + DateTime? reported = null); + + public Task ReportExists(ulong reporter, ulong user, ulong guildId); + public Task ReportExists(string id); +} \ No newline at end of file diff --git a/MacroBotApp/Discord/Modules/Reports/Report.cs b/MacroBotApp/Discord/Modules/Reports/Report.cs new file mode 100644 index 0000000..4882642 --- /dev/null +++ b/MacroBotApp/Discord/Modules/Reports/Report.cs @@ -0,0 +1,13 @@ +namespace MacroBot.Discord.Modules.Reports; + +public class Report +{ + public string Id { get; set; } + public ulong Reporter { get; set; } + public ulong Guild { get; set; } + public ulong User { get; set; } + public ulong? Channel { get; set; } + public ulong? Message { get; set; } + public string Content { get; set; } + public DateTime Reported { get; set; } +} \ No newline at end of file diff --git a/MacroBotApp/Discord/Modules/Reports/ReportInteractions.cs b/MacroBotApp/Discord/Modules/Reports/ReportInteractions.cs new file mode 100644 index 0000000..b22ee9e --- /dev/null +++ b/MacroBotApp/Discord/Modules/Reports/ReportInteractions.cs @@ -0,0 +1,182 @@ +using System.ComponentModel.Design; +using Discord; +using Discord.Interactions; +using Discord.WebSocket; +using JetBrains.Annotations; +using MacroBot.Config; +using MacroBot.DataAccess.Entities; +using MacroBot.DataAccess.RepositoryInterfaces; +using MacroBot.Extensions; +using Serilog; +using ILogger = Serilog.ILogger; + +namespace MacroBot.Discord.Modules.Reports; + +public class ReportingInteractions : InteractionModuleBase +{ + private readonly ILogger _logger = Log.ForContext(); + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ReportUtils _reportUtils; + private readonly BotConfig _botConfig; + + public ReportingInteractions(IServiceScopeFactory serviceScopeFactory, ReportUtils reportUtils, BotConfig botConfig) + { + _serviceScopeFactory = serviceScopeFactory; + _reportUtils = reportUtils; + _botConfig = botConfig; + } + + public async Task GenerateReportMessage(Report report) + { + try + { + await Context.Guild.GetTextChannel(_botConfig.Channels.ReportsChannelId).SendMessageAsync( + embed: await ReportUtils.GenerateReportEmbed(report, Context.Guild), + components: await ReportUtils.GenerateReportComponent(report, Context.Guild) + ); + } + catch (Exception e) + { + _logger.Error(e, "Cannot send report message!"); + } + } + + [ComponentInteraction("report-select-action")] + public async Task HandleReportAction(string[] actions) + { + await using var scope = _serviceScopeFactory.CreateAsyncScope(); + var reportRepository = scope.ServiceProvider.GetRequiredService(); + + foreach (string action in actions) + { + var splAct = action.Split('.'); + var report = await reportRepository.GetReport(splAct[0]); + + switch (splAct[1]) + { + case "deletemsg": + var msg = await Context.Client.GetGuild(report.Guild).GetTextChannel((ulong)report.Channel) + .GetMessageAsync((ulong)report.Message); + await msg.DeleteAsync(); + break; + case "timeout": + await Context.Client.GetGuild(report.Guild).GetUser(report.User).SetTimeOutAsync(TimeSpan.FromMinutes(splAct[2].ToInt32())); + break; + case "kick": + await Context.Client.GetGuild(report.Guild).GetUser(report.User).KickAsync(); + break; + case "ban": + await Context.Client.GetGuild(report.Guild).GetUser(report.User).BanAsync(); + break; + } + + await RespondAsync("Report taken action successfully.", ephemeral: true); + } + } + + [ModalInteraction("report_user_create")] + public async Task HandleCreateUserReportModal(ReportCreateUserModal modal) + { + var assignable = + ReportingCommands.createReportAssignments.Find(x => + x.guildId == Context.Guild.Id && x.reporterId == Context.User.Id); + + if (assignable is not null) + { + await using var scope = _serviceScopeFactory.CreateAsyncScope(); + var reportRepository = scope.ServiceProvider.GetRequiredService(); + var reportId = await reportRepository.CreateReport(assignable.reporterId, assignable.userId, + assignable.guildId, modal.ReportReason); + + var embedBuilder = new EmbedBuilder + { + Title = "User Reported", + }; + embedBuilder.AddField("Report ID (keep it handy!)", reportId); + embedBuilder.AddField("Reason", modal.ReportReason); + embedBuilder.WithColor(new Color(50, 255, 50)); + + ReportingCommands.createReportAssignments.Remove(assignable); + + await GenerateReportMessage(await reportRepository.GetReport(reportId)); + await RespondAsync( ephemeral:true, embed: embedBuilder.Build()); + } + else + { + _logger.Error( + $"Can't get Report info for user {Context.User.Username} ({Context.User.Id}) in {Context.Guild.Name}!"); + await RespondAsync(_reportUtils.getReportInfoError(), ephemeral: true); + } + } + + [ModalInteraction("report_message_create")] + public async Task HandleCreateMessageReportModal(ReportCreateMessageModal modal) + { + var assignable = + ReportingCommands.createReportAssignments.Find(x => + x.guildId == Context.Guild.Id && x.reporterId == Context.User.Id); + + if (assignable is not null) + { + await using var scope = _serviceScopeFactory.CreateAsyncScope(); + var reportRepository = scope.ServiceProvider.GetRequiredService(); + var reportId = await reportRepository.CreateReport(assignable.reporterId, assignable.userId, + assignable.guildId, modal.ReportReason, (UInt64)assignable.channelId, (ulong)assignable.messageId); + + var embedBuilder = new EmbedBuilder + { + Title = "Message Reported", + }; + embedBuilder.AddField("Report ID (keep it handy!)", reportId); + embedBuilder.AddField("Reason", modal.ReportReason); + embedBuilder.WithColor(new Color(50, 255, 50)); + + ReportingCommands.createReportAssignments.Remove(assignable); + + await GenerateReportMessage(await reportRepository.GetReport(reportId)); + await RespondAsync( ephemeral:true, embed: embedBuilder.Build()); + } + else + { + _logger.Error( + $"Can't get Report info for user {Context.User.Username} ({Context.User.Id}) in {Context.Guild.Name}!"); + await RespondAsync(_reportUtils.getReportInfoError(), ephemeral: true); + } + } +} + +public class ReportCreateMessageModal : IModal +{ + [InputLabel("Report Reason")] + [ModalTextInput("report_reason", TextInputStyle.Paragraph, minLength: 1, maxLength: 4000)] + public string ReportReason { get; set; } + + public string Title => "Report a message"; +} + +public class ReportCreateUserModal : IModal +{ + [InputLabel("Report Reason")] + [ModalTextInput("report_reason", TextInputStyle.Paragraph, minLength: 1, maxLength: 4000)] + public string ReportReason { get; set; } + + public string Title => "Report a user"; +} + +public class ReportApproveModal : IModal +{ + [InputLabel("Approve Reason")] + [ModalTextInput("report_reason", TextInputStyle.Paragraph, minLength: 1, maxLength: 4000)] + public string ReportReason { get; set; } + + public string Title => "Approve Report"; +} + +public class ReportDeclineModal : IModal +{ + [InputLabel("Decline Reason")] + [ModalTextInput("report_reason", TextInputStyle.Paragraph, minLength: 1, maxLength: 4000)] + public string ReportReason { get; set; } + + public string Title => "Decline Report"; +} \ No newline at end of file diff --git a/MacroBotApp/Discord/Modules/Reports/ReportModule.cs b/MacroBotApp/Discord/Modules/Reports/ReportModule.cs new file mode 100644 index 0000000..cf2b84f --- /dev/null +++ b/MacroBotApp/Discord/Modules/Reports/ReportModule.cs @@ -0,0 +1,137 @@ +using System.ComponentModel; +using Discord; +using Discord.Interactions; +using JetBrains.Annotations; +using MacroBot.Config; +using MacroBot.DataAccess.RepositoryInterfaces; + +namespace MacroBot.Discord.Modules.Reports; + +[Group("report", "Report system")] +[UsedImplicitly] +public class ReportingCommands : InteractionModuleBase +{ + public static List createReportAssignments = new(); + private readonly BotConfig _botConfig; + private readonly CommandsConfig _commandsConfig; + private readonly ReportUtils _reportingUtils; + private readonly IServiceScopeFactory _serviceScopeFactory; + + public ReportingCommands(CommandsConfig commandsConfig, IServiceScopeFactory serviceScopeFactory, + ReportUtils reportingUtils, BotConfig botConfig) + { + _commandsConfig = commandsConfig; + _serviceScopeFactory = serviceScopeFactory; + _reportingUtils = reportingUtils; + _botConfig = botConfig; + } + + // Commands + [MessageCommand("Report this Message")] + public async Task ReportMessage(IUserMessage message) + { + // Handle Assignables + var assignable = createReportAssignments.Find(x => x.guildId == Context.Guild.Id && x.reporterId == Context.User.Id); + if (assignable != null) createReportAssignments.Remove(assignable); + createReportAssignments.Add(new UserReportAssignable(Context.Guild.Id, message.Author.Id, Context.User.Id, message.Id, Context.Channel.Id)); + + if ((message.Author as IGuildUser).RoleIds.Contains(_botConfig.Roles.AdministratorRoleId) || + (message.Author as IGuildUser).RoleIds.Contains(_botConfig.Roles.ModeratorRoleId) || + message.Author.IsBot || message.Author.IsWebhook) + { + EmbedBuilder embed = new EmbedBuilder() + { + Title = "Cannot report message!", + Description = "It looks like the message is from an admin, a moderator, or a bot.", + Color = Color.Red + }; + + await RespondAsync(embed: embed.Build(), ephemeral: true); + return; + } + + await RespondWithModalAsync("report_message_create"); + } + + [UserCommand("Report this User")] + public async Task ReportUser(IGuildUser user) + { + // Handle Assignables + var assignable = createReportAssignments.Find(x => x.guildId == Context.Guild.Id && x.reporterId == Context.User.Id); + if (assignable != null) createReportAssignments.Remove(assignable); + createReportAssignments.Add(new UserReportAssignable(Context.Guild.Id, user.Id, Context.User.Id)); + + if (user.RoleIds.Contains(_botConfig.Roles.AdministratorRoleId) || + user.RoleIds.Contains(_botConfig.Roles.ModeratorRoleId) || + user.IsBot || user.IsWebhook) + { + EmbedBuilder embed = new EmbedBuilder() + { + Title = "Cannot report user!", + Description = "It looks like the user is an admin, a moderator, or a bot.", + Color = Color.Red + }; + + await RespondAsync(embed: embed.Build(), ephemeral: true); + return; + } + + await RespondWithModalAsync("report_user_create"); + } + + [SlashCommand("list", "List reports")] + public async Task ReportList() + { + await using var scope = _serviceScopeFactory.CreateAsyncScope(); + var reportRepository = scope.ServiceProvider.GetRequiredService(); + var r = await reportRepository.GetReportsForGuild(Context.Guild.Id); + var reports = r.ToArray(); + + // Haven't found a way to efficiently put 100s of reports in a single message, so here we go! + string str = ""; + foreach (Report report in reports) + { + str += $"[{report.Id}] > {report.User} by {report.Reporter} {(report.Message is not null ? $"on {report.Channel}/{report.Message}" : "")}\r\n"; + } + + await RespondAsync($"This is the reports!\r\n\r\n```{str}```", ephemeral: true); + } + + [SlashCommand("get", "Get a report")] + public async Task Get(string id) + { + var guild = Context.Guild; + await using var scope = _serviceScopeFactory.CreateAsyncScope(); + var reportRepository = scope.ServiceProvider.GetRequiredService(); + var report = await reportRepository.GetReport(id); + + if (report is null) + { + var embedBuildern = new EmbedBuilder + { + Title = String.Format("Report {0} does not exist!", id) + }; + await RespondAsync(embed: embedBuildern.Build(), ephemeral: true); + return; + } + + var componentBuilder = new ComponentBuilder(); + var embedBuilder = new EmbedBuilder + { + Title = $"Report {id}", + Timestamp = new DateTimeOffset(report.Reported), + ThumbnailUrl = guild.GetUser(report.User).GetDisplayAvatarUrl() + }; + embedBuilder.AddField("Reported by", guild.GetUser(report.Reporter).Mention, true); + embedBuilder.AddField("User Reported", guild.GetUser(report.User).Mention, true); + if (report.Channel != null && report.Message != null) + { + var msg = await guild.GetTextChannel((ulong)report.Channel).GetMessageAsync((ulong)report.Message); + embedBuilder.AddField("Content of the message reported", msg.Content); + componentBuilder.WithButton("Jump to message", style: ButtonStyle.Link, url: msg.GetJumpUrl()); + } + embedBuilder.AddField("Reason", report.Content); + + await RespondAsync(embed: embedBuilder.Build(), components: componentBuilder.Build(), ephemeral: true); + } +} \ No newline at end of file diff --git a/MacroBotApp/Discord/Modules/Reports/ReportUtils.cs b/MacroBotApp/Discord/Modules/Reports/ReportUtils.cs new file mode 100644 index 0000000..d43d809 --- /dev/null +++ b/MacroBotApp/Discord/Modules/Reports/ReportUtils.cs @@ -0,0 +1,128 @@ +using Discord; +using Discord.Interactions; +using Discord.WebSocket; +using MacroBot.DataAccess.RepositoryInterfaces; + +namespace MacroBot.Discord.Modules.Reports; + +public class ReportUtils +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + + public ReportUtils(IServiceScopeFactory serviceScopeFactory) + { + _serviceScopeFactory = serviceScopeFactory; + } + + public async Task AutoCompleteReport(SocketInteractionContext Context) + { + var userInput = (Context.Interaction as SocketAutocompleteInteraction).Data.Current.Value.ToString(); + + List resultList = new(); + await using var scope = _serviceScopeFactory.CreateAsyncScope(); + var reportRepository = scope.ServiceProvider.GetRequiredService(); + foreach (var tag in await reportRepository.GetReportsForGuild(Context.Guild.Id)) + resultList.Add(new AutocompleteResult($"{tag.User} reported by {tag.Reporter}", tag.Id)); + var results = resultList.AsEnumerable() + .Where(x => x.Name.StartsWith(userInput, StringComparison.InvariantCultureIgnoreCase)); + + await ((SocketAutocompleteInteraction)Context.Interaction).RespondAsync(results.Take(25)); + } + + public static async Task GenerateReportEmbed(Report report, SocketGuild guild) + { + var embedBuilder = new EmbedBuilder + { + Title = $"Report {report.Id}", + Timestamp = new DateTimeOffset(report.Reported), + ThumbnailUrl = guild.GetUser(report.User).GetDisplayAvatarUrl() + }; + embedBuilder.AddField("Reported by", guild.GetUser(report.Reporter).Mention, true); + embedBuilder.AddField("User Reported", guild.GetUser(report.User).Mention, true); + if (report.Channel != null && report.Message != null) + { + var msg = await guild.GetTextChannel((ulong)report.Channel).GetMessageAsync((ulong)report.Message); + embedBuilder.AddField("Content of the message reported", msg.Content); + } + embedBuilder.AddField("Reason", report.Content); + + return embedBuilder.Build(); + } + + public static async Task GenerateReportComponent(Report report, SocketGuild guild) + { + var componentBuilder = new ComponentBuilder(); + var selectMenuBuilder = new SelectMenuBuilder() + { + Placeholder = "Select an action...", + MaxValues = 2, + MinValues = 1, + CustomId = "report-select-action" + }; + selectMenuBuilder.AddOption("Decline report", $"{report.Id}.decline"); + + if (report.Channel != null && report.Message != null) + { + var msg = await guild.GetTextChannel((ulong)report.Channel).GetMessageAsync((ulong)report.Message); + componentBuilder.WithButton("Jump to message", style: ButtonStyle.Link, url: msg.GetJumpUrl()); + selectMenuBuilder.AddOption("Delete message", $"{report.Id}.deletemsg"); + } + + selectMenuBuilder.AddOption("Timeout user for 30 minutes", $"{report.Id}.timeout.30"); + selectMenuBuilder.AddOption("Timeout user for 3 hours", $"{report.Id}.timeout.180"); + selectMenuBuilder.AddOption("Timeout user for 1 day", $"{report.Id}.timeout.1440"); + selectMenuBuilder.AddOption("Kick user", $"{report.Id}.kick"); + selectMenuBuilder.AddOption("Ban user", $"{report.Id}.ban"); + + componentBuilder.WithSelectMenu(selectMenuBuilder); + return componentBuilder.Build(); + } + + public Embed buildAlreadyExistsError(string reportId) + { + var embedBuilder = new EmbedBuilder + { + Title = "Report already exists", + Description = $"The report `{reportId}` already exists!" + }; + embedBuilder.WithColor(new Color(255, 50, 50)); + + return embedBuilder.Build(); + } + + public Embed buildTagNotFoundError(string reportId) + { + var embedBuilder = new EmbedBuilder + { + Title = "Report not found", + Description = $"The report `{reportId}` could not be found in the database!" + }; + embedBuilder.WithColor(new Color(255, 50, 50)); + + return embedBuilder.Build(); + } + + public string getReportInfoError() + { + return "An internal error occured while getting Report information, please try again."; + } +} + +public class UserReportAssignable +{ + public UserReportAssignable(ulong guildID, ulong userID, ulong reporterID, ulong? messageID = null, ulong? channelID = null) + { + userId = userID; + guildId = guildID; + reporterId = reporterID; + messageId = messageID; + channelId = channelID; + } + // Apparently i have not found a more efficient way in asigning the report id to an discord modal so i needed to temporarly save this assignment into a List + + public ulong userId { get; private set; } + public ulong guildId { get; private set; } + public ulong reporterId { get; private set; } + public ulong? messageId { get; private set; } + public ulong? channelId { get; private set; } +} \ No newline at end of file diff --git a/MacroBotApp/Extensions/StringExtensions.cs b/MacroBotApp/Extensions/StringExtensions.cs index 82292e7..9f0d05b 100644 --- a/MacroBotApp/Extensions/StringExtensions.cs +++ b/MacroBotApp/Extensions/StringExtensions.cs @@ -8,4 +8,24 @@ public static class StringExtensions ? value[..maxLength] + truncationSuffix : value; } + + public static bool IsNullOrWhiteSpace(this string? value) + { + return string.IsNullOrWhiteSpace(value) && string.IsNullOrEmpty(value); + } + + public static string Remove(this string value, string toRemove) + { + return value.Replace(toRemove, string.Empty); + } + + public static bool Contains(this string source, string toCheck, StringComparison comp) + { + return source?.IndexOf(toCheck, comp) >= 0; + } + + public static int ToInt32(this string source) + { + return Convert.ToInt32(source); + } } \ No newline at end of file diff --git a/MacroBotApp/MacroBotApp.csproj b/MacroBotApp/MacroBotApp.csproj index 7648aed..b2a89de 100644 --- a/MacroBotApp/MacroBotApp.csproj +++ b/MacroBotApp/MacroBotApp.csproj @@ -49,6 +49,29 @@ .dockerignore + + + + + + + + + + + + + <_ContentIncludedByDefault Remove="__testConfigs__\BotConfig.json" /> + <_ContentIncludedByDefault Remove="__testConfigs__\BuyMeACoffee.json" /> + <_ContentIncludedByDefault Remove="__testConfigs__\Commands.json" /> + <_ContentIncludedByDefault Remove="__testConfigs__\ExtensionDetection.json" /> + <_ContentIncludedByDefault Remove="__testConfigs__\KoFi.json" /> + <_ContentIncludedByDefault Remove="__testConfigs__\StatusCheck.json" /> + <_ContentIncludedByDefault Remove="__testConfigs__\Webhooks.json" /> + + + + diff --git a/MacroBotApp/Startup.cs b/MacroBotApp/Startup.cs index cf22d07..c6b711b 100644 --- a/MacroBotApp/Startup.cs +++ b/MacroBotApp/Startup.cs @@ -6,6 +6,7 @@ using MacroBot.DataAccess.AutoMapper; using MacroBot.DataAccess.Repositories; using MacroBot.DataAccess.RepositoryInterfaces; +using MacroBot.Discord.Modules.Reports; using MacroBot.Discord.Modules.Tagging; using MacroBot.Extensions; using MacroBot.Manager; @@ -40,6 +41,8 @@ public void ConfigureServices(IServiceCollection services) services.AddAutoMapper(typeof(TagMapping)); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddSingleton(discordSocketConfig); services.AddSingleton(); services.AddSingleton(x => new InteractionService(x.GetRequiredService(), interactionServiceConfig));