diff --git a/.idea/.idea.ARC V2/.idea/.gitignore b/.idea/.idea.ARC V2/.idea/.gitignore
new file mode 100644
index 0000000..2be93dd
--- /dev/null
+++ b/.idea/.idea.ARC V2/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/contentModel.xml
+/.idea.ARC V2.iml
+/modules.xml
+/projectSettingsUpdater.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.ARC V2/.idea/.name b/.idea/.idea.ARC V2/.idea/.name
new file mode 100644
index 0000000..4c9dd3e
--- /dev/null
+++ b/.idea/.idea.ARC V2/.idea/.name
@@ -0,0 +1 @@
+ARC V2
\ No newline at end of file
diff --git a/.idea/.idea.ARC V2/.idea/encodings.xml b/.idea/.idea.ARC V2/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.ARC V2/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.ARC V2/.idea/indexLayout.xml b/.idea/.idea.ARC V2/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.ARC V2/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.ARC V2/.idea/vcs.xml b/.idea/.idea.ARC V2/.idea/vcs.xml
new file mode 100644
index 0000000..a329890
--- /dev/null
+++ b/.idea/.idea.ARC V2/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ARC/ARC.csproj b/ARC/ARC.csproj
index 653a127..a158861 100644
--- a/ARC/ARC.csproj
+++ b/ARC/ARC.csproj
@@ -27,6 +27,7 @@
+
@@ -34,7 +35,6 @@
-
diff --git a/ARC/Arc.cs b/ARC/Arc.cs
index 7b5ceac..f489435 100644
--- a/ARC/Arc.cs
+++ b/ARC/Arc.cs
@@ -2,7 +2,9 @@
using Arc.Exceptions;
using Arc.Schema;
using Arc.Services;
+using ARC.Services;
using DSharpPlus;
+using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -93,26 +95,48 @@ private static async Task StartDiscordBot(IConfigurationRoot settings)
LoggerFactory = logFactory
};
- _clientInstance = new DiscordClient(discordConfig);
+ _clientInstance = new DiscordClient(discordConfig)
+ {
+ ClientVersion = "2.0"
+ };
_serviceProvider = ConfigureServices(settings);
// Run any necessary steps before starting the bot here.
ServiceProvider.GetRequiredService();
ServiceProvider.GetRequiredService();
+ ServiceProvider.GetRequiredService();
+ ServiceProvider.GetRequiredService();
+ ServiceProvider.GetRequiredService();
+
// Connect to discord!
await _clientInstance.ConnectAsync();
_clientInstance.Ready += ClientInstanceOnReady;
+ _clientInstance.ClientErrored += ClientInstanceOnClientErrored;
await Task.Delay(-1);
}
+ private static async Task ClientInstanceOnClientErrored(DiscordClient sender, ClientErrorEventArgs args)
+ {
+ var debug_log = await ClientInstance.GetChannelAsync(ulong.Parse(GlobalConfig.GetSection("discord:debug_log").Value));
+ var errorEmbed = new DiscordEmbedBuilder()
+ .WithTitle("Error!")
+ .WithColor(DiscordColor.Red)
+ .WithDescription($"***An error occured !***\n```{args.Exception}```");
+
+ await debug_log.SendMessageAsync(errorEmbed);
+
+ }
+
private static async Task ClientInstanceOnReady(DiscordClient sender, ReadyEventArgs e)
{
await Task.Run(() =>
{
Log.Logger.Information($"Logged in as {sender.CurrentUser}");
Log.Logger.Information($"Ready!");
+ //ClientInstance.BulkOverwriteGlobalApplicationCommandsAsync(new List() { });
+ //ClientInstance.BulkOverwriteGuildApplicationCommandsAsync(975717691564376084, new List() { });
});
}
@@ -139,6 +163,9 @@ private static IServiceProvider ConfigureServices(IConfigurationRoot settings)
.AddSingleton(settings)
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
.BuildServiceProvider();
return services;
diff --git a/ARC/Exceptions/ArcExceptions.cs b/ARC/Exceptions/ArcExceptions.cs
index 54f2c89..638f747 100644
--- a/ARC/Exceptions/ArcExceptions.cs
+++ b/ARC/Exceptions/ArcExceptions.cs
@@ -13,4 +13,9 @@ public ArcNotInitializedException(string? message = null) : base($"ARC was not p
public class ArcInitFailedException : ArcException
{
public ArcInitFailedException(string? message = null) : base($"ARC Initialization failed: {message ?? Empty}") { }
+}
+
+public class ArcModmailFailedException : ArcException
+{
+ public ArcModmailFailedException(string? message = null) : base($"ARC Modmail failed: {message ?? Empty}") { }
}
\ No newline at end of file
diff --git a/ARC/Extensions/ClientExtensions.cs b/ARC/Extensions/ClientExtensions.cs
index f678a8c..9738209 100644
--- a/ARC/Extensions/ClientExtensions.cs
+++ b/ARC/Extensions/ClientExtensions.cs
@@ -1,9 +1,4 @@
using DSharpPlus;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace ARC.Extensions
{
diff --git a/ARC/Extensions/EmbedBuilderExtensions.cs b/ARC/Extensions/EmbedBuilderExtensions.cs
index b17e237..30818df 100644
--- a/ARC/Extensions/EmbedBuilderExtensions.cs
+++ b/ARC/Extensions/EmbedBuilderExtensions.cs
@@ -1,10 +1,6 @@
-using Arc.Schema;
+
using DSharpPlus.Entities;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+
namespace ARC.Extensions
{
diff --git a/ARC/Extensions/GuildExtensions.cs b/ARC/Extensions/GuildExtensions.cs
new file mode 100644
index 0000000..8ac1a9f
--- /dev/null
+++ b/ARC/Extensions/GuildExtensions.cs
@@ -0,0 +1,25 @@
+using Arc.Schema;
+using DSharpPlus.Entities;
+using DSharpPlus;
+namespace ARC.Extensions;
+
+public static class GuildExtensions
+{
+
+ private static ArcDbContext DbContext => Arc.Arc.ArcDbContext;
+ private static DiscordClient ClientInstance => Arc.Arc.ClientInstance;
+
+ public static async Task Log(this DiscordGuild guild, DiscordMessageBuilder message)
+ {
+
+ if (!DbContext.Config[guild.Id].ContainsKey("logchannel"))
+ return;
+
+ ulong logChannelSnowflake = ulong.Parse(DbContext.Config[guild.Id]["logchannel"]);
+ var channel = await ClientInstance.GetChannelAsync(logChannelSnowflake);
+
+ await channel.SendMessageAsync(message);
+
+ }
+
+}
\ No newline at end of file
diff --git a/ARC/Migrations/20230408134552_ArcV2-1.1.Designer.cs b/ARC/Migrations/20230408134552_ArcV2-1.1.Designer.cs
new file mode 100644
index 0000000..9306f90
--- /dev/null
+++ b/ARC/Migrations/20230408134552_ArcV2-1.1.Designer.cs
@@ -0,0 +1,124 @@
+//
+using System;
+using Arc.Schema;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace ARC.Migrations
+{
+ [DbContext(typeof(ArcDbContext))]
+ [Migration("20230408134552_ArcV2-1.1")]
+ partial class ArcV211
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.0-preview.2.23128.3")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Arc.Schema.Appeal", b =>
+ {
+ b.Property("AppealId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AppealId"));
+
+ b.Property("NextAppeal")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserSnowflake")
+ .HasColumnType("bigint");
+
+ b.HasKey("AppealId");
+
+ b.ToTable("Appeals");
+ });
+
+ modelBuilder.Entity("Arc.Schema.GuildConfig", b =>
+ {
+ b.Property("ConfigId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConfigId"));
+
+ b.Property("ConfigGuildSnowflake")
+ .HasColumnType("bigint");
+
+ b.Property("ConfigKey")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ConfigValue")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("ConfigId");
+
+ b.ToTable("GuildConfigs");
+ });
+
+ modelBuilder.Entity("Arc.Schema.Modmail", b =>
+ {
+ b.Property("ModmailId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ModmailId"));
+
+ b.Property("ChannelSnowflake")
+ .HasColumnType("bigint");
+
+ b.Property("UserSnowflake")
+ .HasColumnType("bigint");
+
+ b.Property("WebhookSnowflake")
+ .HasColumnType("bigint");
+
+ b.HasKey("ModmailId");
+
+ b.ToTable("Modmails");
+ });
+
+ modelBuilder.Entity("Arc.Schema.UserNote", b =>
+ {
+ b.Property("NoteId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NoteId"));
+
+ b.Property("AuthorSnowflake")
+ .HasColumnType("bigint");
+
+ b.Property("DateAdded")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("GuildSnowflake")
+ .HasColumnType("bigint");
+
+ b.Property("Note")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserSnowflake")
+ .HasColumnType("bigint");
+
+ b.HasKey("NoteId");
+
+ b.ToTable("UserNotes");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/ARC/Migrations/20230408134552_ArcV2-1.1.cs b/ARC/Migrations/20230408134552_ArcV2-1.1.cs
new file mode 100644
index 0000000..a8156eb
--- /dev/null
+++ b/ARC/Migrations/20230408134552_ArcV2-1.1.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace ARC.Migrations
+{
+ ///
+ public partial class ArcV211 : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "GuildSnowflake",
+ table: "UserNotes",
+ type: "bigint",
+ nullable: false,
+ defaultValue: 0L);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "GuildSnowflake",
+ table: "UserNotes");
+ }
+ }
+}
diff --git a/ARC/Migrations/ArcDbContextModelSnapshot.cs b/ARC/Migrations/ArcDbContextModelSnapshot.cs
index 96c516c..7987251 100644
--- a/ARC/Migrations/ArcDbContextModelSnapshot.cs
+++ b/ARC/Migrations/ArcDbContextModelSnapshot.cs
@@ -101,6 +101,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("DateAdded")
.HasColumnType("timestamp with time zone");
+ b.Property("GuildSnowflake")
+ .HasColumnType("bigint");
+
b.Property("Note")
.IsRequired()
.HasColumnType("text");
diff --git a/ARC/Modules/ArcModule.cs b/ARC/Modules/ArcModule.cs
new file mode 100644
index 0000000..c46aece
--- /dev/null
+++ b/ARC/Modules/ArcModule.cs
@@ -0,0 +1,38 @@
+
+using Arc.Schema;
+using DSharpPlus;
+using DSharpPlus.SlashCommands;
+using Microsoft.Extensions.Configuration;
+using Serilog;
+
+
+namespace ARC.Modules
+{
+ internal abstract class ArcModule : ApplicationCommandModule
+ {
+
+ protected static bool _loaded = false;
+ protected readonly ArcDbContext DbContext;
+ protected readonly IServiceProvider ServiceProvider;
+ protected readonly DiscordClient ClientInstance;
+ protected readonly IConfigurationRoot GlobalConfig;
+
+ protected ArcModule(string moduleName)
+ {
+
+ DbContext = Arc.Arc.ArcDbContext;
+ ServiceProvider = Arc.Arc.ServiceProvider;
+ ClientInstance = Arc.Arc.ClientInstance;
+ GlobalConfig = Arc.Arc.GlobalConfig;
+
+ if (_loaded)
+ return;
+ RegisterEvents();
+ Log.Logger.Information($"MODULE LOADED: {moduleName}");
+ _loaded = true;
+ }
+
+ protected abstract void RegisterEvents();
+
+ }
+}
diff --git a/ARC/Modules/ModerationModule.cs b/ARC/Modules/ModerationModule.cs
new file mode 100644
index 0000000..310b4e2
--- /dev/null
+++ b/ARC/Modules/ModerationModule.cs
@@ -0,0 +1,188 @@
+using Arc.Schema;
+using ARC.Services;
+using DSharpPlus;
+using DSharpPlus.Entities;
+using DSharpPlus.EventArgs;
+using DSharpPlus.Interactivity;
+using DSharpPlus.SlashCommands;
+using ARC.Extensions;
+
+namespace ARC.Modules
+{
+ internal class ModerationModule : ArcModule
+ {
+ public InteractionService InteractivityService { get; set; }
+
+ public ModerationModule() : base("Moderation") {
+
+ }
+
+ protected override void RegisterEvents()
+ {
+
+ ClientInstance.ComponentInteractionCreated += ClientInstance_ComponentInteractionCreated;
+ ClientInstance.ModalSubmitted += HandleUserNotesModal;
+
+ }
+
+ [ContextMenu(DSharpPlus.ApplicationCommandType.UserContextMenu, "User Notes", false),
+ SlashCommandPermissions(DSharpPlus.Permissions.ManageMessages)]
+ public async Task UserNotes(ContextMenuContext ctx)
+ {
+
+ var embed = new DiscordEmbedBuilder()
+ .WithAuthor($"{ctx.TargetUser} Notes", null, ctx.TargetMember.GetAvatarUrl(ImageFormat.Auto))
+ .WithColor(DiscordColor.Blurple);
+
+ var response = new DiscordInteractionResponseBuilder()
+ .AddComponents(new List() {
+ new DiscordButtonComponent(ButtonStyle.Primary, $"addnote.{ctx.TargetMember.Id}", "Add Note", false, new DiscordComponentEmoji("📝")),
+ new DiscordButtonComponent(ButtonStyle.Primary, $"viewnotes.{ctx.TargetMember.Id}", $"View {DbContext.GetUserNotes(ctx.TargetMember.Id, ctx.Guild.Id).Count} Notes", false, new DiscordComponentEmoji("📜"))
+ })
+ .AddEmbed(embed)
+ .AsEphemeral(true);
+
+ await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, response);
+
+ }
+
+ private async Task ClientInstance_ComponentInteractionCreated(DiscordClient sender, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs args)
+ {
+
+ if (args.Id.StartsWith("viewnotes."))
+ await ViewUserNotes(args);
+
+ if (args.Id.StartsWith("addnote."))
+ await AddUserNote(args);
+
+ if (args.Id.StartsWith("usernote.delete."))
+ await DeleteUserNote(args);
+
+ }
+
+ private async Task DeleteUserNote(ComponentInteractionCreateEventArgs args)
+ {
+
+ var noteId = long.Parse(args.Interaction.Data.CustomId.Split('.')[2]);
+ var notes = DbContext.UserNotes.Where(x => x.NoteId == noteId);
+
+ if (!notes.Any())
+ return;
+
+ var note = notes.ToList()[0];
+
+ DbContext.Remove(note);
+
+ // TODO: RESTORE BUTTON
+ Arc.Arc.ArcDbContext.SaveChanges();
+
+ var embed = new DiscordEmbedBuilder()
+ .WithAuthor($"A Note was deleted from {note.User.Username}#{note.User.Discriminator}", null, note.User.GetAvatarUrl(ImageFormat.Auto))
+ .WithDescription($"```{note.Note}```")
+ .AddField("Added By:", $"{note.Author.Mention}", true)
+ .AddField("Deleted By:", $"{args.User.Mention}", true)
+ .AddField("Time added:", $"", true)
+ .AddField("Time deleted:", $"", true)
+ .WithFooter($"BillieBot v{ClientInstance.ClientVersion} UserNotes", ClientInstance.CurrentUser.GetAvatarUrl(ImageFormat.Auto))
+ .WithTimestamp(DateTime.UtcNow)
+ .WithColor(DiscordColor.Red)
+ .Build();
+
+ await note.Guild.Log(new DiscordMessageBuilder().WithEmbed(embed));
+ }
+
+ private async Task AddUserNote(ComponentInteractionCreateEventArgs eventArgs)
+ {
+ ulong userId = ulong.Parse(eventArgs.Id.Split('.')[1]);
+ var user = await ClientInstance.GetUserAsync(userId);
+
+ var modal = new DiscordInteractionResponseBuilder()
+ .WithTitle($"Add Note To {user.Username}")
+ .WithCustomId($"addnote.{userId}")
+ .AddComponents(new TextInputComponent(label: "Note",
+ customId: $"usernote.content",
+ placeholder: "Enter user note...",
+ required: true,
+ style: TextInputStyle.Paragraph));
+
+ await eventArgs.Interaction.CreateResponseAsync(InteractionResponseType.Modal, modal);
+
+ }
+
+ private async Task ViewUserNotes(ComponentInteractionCreateEventArgs eventArgs)
+ {
+
+ await eventArgs.Interaction.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder() { IsEphemeral = true });
+
+ ulong userSnowflake = ulong.Parse(eventArgs.Id.Split('.')[1]);
+ List notes = DbContext.GetUserNotes(userSnowflake, eventArgs.Guild.Id);
+
+ List pages = new List();
+ foreach (var note in notes)
+ {
+ var embed = note.CreateEmbedPage();
+ var page = new Page(null, embed);
+ List buttons = new() {
+ new DiscordButtonComponent(ButtonStyle.Danger, $"usernote.delete.{note.NoteId}", "Delete", false, new DiscordComponentEmoji("🗑️"))
+ };
+
+ page.Components = buttons;
+ pages.Add(page);
+ }
+
+ await InteractivityService.CreatePaginationResponse(pages, eventArgs.Interaction);
+
+ }
+
+ private async Task HandleUserNotesModal(DiscordClient sender, ModalSubmitEventArgs args)
+ {
+ if (args.Interaction.Data.CustomId.StartsWith("addnote."))
+ {
+
+ ulong userSnowflake = ulong.Parse(args.Interaction.Data.CustomId.Split('.')[1]);
+ var author = args.Interaction.User;
+ var user = await ClientInstance.GetUserAsync(userSnowflake);
+ String content = args.Values["usernote.content"];
+ DateTime dateadded = DateTime.UtcNow;
+ var guild = args.Interaction.Guild;
+
+ var note = new UserNote((long)guild.Id, (long)user.Id, content, dateadded, (long)author.Id);
+
+ DbContext.UserNotes.Add(note);
+ await DbContext.SaveChangesAsync();
+
+ var embed = new DiscordEmbedBuilder()
+ .WithAuthor($"A New Note was Added to {user.Username}#{user.Discriminator}", null, user.GetAvatarUrl(ImageFormat.Auto))
+ .WithDescription($"```{content}```")
+ .AddField("Added By:", $"{author.Mention}", true)
+ .AddField("Time added:", $"", true)
+ .WithFooter($"BillieBot v{ClientInstance.ClientVersion} UserNotes", ClientInstance.CurrentUser.GetAvatarUrl(ImageFormat.Auto))
+ .WithTimestamp(dateadded)
+ .WithColor(DiscordColor.Green)
+ .Build();
+
+ await guild.Log(
+ new DiscordMessageBuilder()
+ .WithEmbed(embed));
+
+ var embed2 = new DiscordEmbedBuilder()
+ .WithAuthor($"{user} Notes", null, user.GetAvatarUrl(ImageFormat.Auto))
+ .WithColor(DiscordColor.Blurple);
+
+ var resp = new DiscordInteractionResponseBuilder()
+ .AddEmbed(embed2)
+ .AddComponents(new List()
+ {
+ new DiscordButtonComponent(ButtonStyle.Primary, $"addnote.{user.Id}", "Add Note", false,
+ new DiscordComponentEmoji("📝")),
+ new DiscordButtonComponent(ButtonStyle.Primary, $"viewnotes.{user.Id}",
+ $"View {DbContext.GetUserNotes(user.Id, guild.Id).Count} Notes", false,
+ new DiscordComponentEmoji("📜"))
+ });
+
+ await args.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, resp);
+ }
+ }
+
+ }
+}
diff --git a/ARC/Modules/UtilitiesModule.cs b/ARC/Modules/UtilitiesModule.cs
new file mode 100644
index 0000000..bdafb91
--- /dev/null
+++ b/ARC/Modules/UtilitiesModule.cs
@@ -0,0 +1,230 @@
+using Arc.Schema;
+using Arc.Services;
+
+using DSharpPlus;
+using DSharpPlus.Entities;
+using DSharpPlus.SlashCommands;
+
+using System.Diagnostics;
+
+
+namespace ARC.Modules
+{
+ internal class UtilitiesModule : ArcModule
+ {
+
+ public UptimeService UptimeService { get; set; }
+
+ public UtilitiesModule() : base("Utilities") {
+
+ }
+
+ protected override void RegisterEvents()
+ {
+ ClientInstance.ComponentInteractionCreated += ClientInstance_ComponentInteractionCreated;
+ }
+
+
+ #region Utilities commands
+
+ [SlashCommand("Ping", "Gets the latency numbers related to the bot.")]
+ public async Task PingCommand(InteractionContext ctx)
+ {
+
+ Stopwatch timer = new Stopwatch();
+ timer.Start();
+ await ctx.Channel.TriggerTypingAsync();
+ var msg = await ctx.Channel.SendMessageAsync(".");
+ timer.Stop();
+ await msg.DeleteAsync();
+
+
+ string wsText = "Websocket latency";
+ string wsPing = ctx.Client.Ping.ToString();
+
+ string rtText = "Roundtrip latency";
+ string rtPing = timer.ElapsedMilliseconds.ToString();
+
+ var embed = new DiscordEmbedBuilder()
+ .WithColor(DiscordColor.PhthaloBlue)
+ .WithDescription($"🌐 **{wsText}:** ``{wsPing}ms``\n💬 **{rtText}:** ``{rtPing}ms``");
+
+ var response = new DiscordInteractionResponseBuilder { }
+ .AddEmbed(embed);
+
+ await ctx.CreateResponseAsync(response);
+
+ }
+
+ [SlashCommand("Uptime", "Get the bot's uptime")]
+ public async Task UptimeCommand(InteractionContext ctx)
+ {
+
+ string uptimeMsg = "Uptime";
+ string uptimeDays = "Days";
+ string uptimeHours = "Hrs";
+ string uptimeMinutes = "Mins";
+ string uptimeSeconds = "Sec";
+
+ var uptime = UptimeService.Uptime.Elapsed;
+
+ var embed = new DiscordEmbedBuilder()
+ .WithAuthor(ClientInstance.CurrentUser.Username, null, ClientInstance.CurrentUser.AvatarUrl)
+ .WithColor(DiscordColor.PhthaloBlue)
+ .WithDescription($"**{uptimeMsg}:** ``{uptime.Days}{uptimeDays} {uptime.Hours}{uptimeHours} {uptime.Minutes}{uptimeMinutes} {uptime.Seconds}{uptimeSeconds}``");
+
+ var response = new DiscordInteractionResponseBuilder() { }
+ .AddEmbed(embed);
+
+ await ctx.CreateResponseAsync(response);
+
+ }
+
+ [SlashCommand("Avatar", "Get your own or a user's avatar")]
+ public async Task AvatarCommand(InteractionContext ctx, [Option("User", "Select which user to get the avatar from"),] DiscordUser user = null)
+ {
+
+ if (user is null)
+ {
+ user = ctx.User;
+ }
+
+ var selectOptions = new List() {
+ new DiscordSelectComponentOption("Global Avatar", $"global.{user.Id}.{ctx.User.Id}", "Get the user's global avatar", false, new DiscordComponentEmoji("🌐")),
+ new DiscordSelectComponentOption("Server Avatar", $"server.{user.Id}.{ctx.User.Id}", "Get the user's server avatar", false, new DiscordComponentEmoji("🖥️"))
+ };
+
+ var selectmenu = new DiscordSelectComponent("avatar_component", "Select...", selectOptions, false, 1, 1);
+
+ var res = new DiscordInteractionResponseBuilder()
+ .WithContent(user.GetAvatarUrl(ImageFormat.Auto))
+ .AddComponents(selectmenu);
+
+ await ctx.CreateResponseAsync(res);
+
+ }
+
+ private async Task ClientInstance_ComponentInteractionCreated(DiscordClient sender, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs eventArgs)
+ {
+ if (!eventArgs.Id.Equals("avatar_component"))
+ return;
+
+ string response = "";
+
+ if (eventArgs.Interaction.Data.Values[0].Split(".")[0] == "server")
+ {
+ response = eventArgs.Guild.Members[ulong.Parse(eventArgs.Interaction.Data.Values[0].Split(".")[1])].GetGuildAvatarUrl(ImageFormat.Auto);
+ }
+
+ if (eventArgs.Interaction.Data.Values[0].Split(".")[0] == "global")
+ {
+ var user = await eventArgs.Guild.GetMemberAsync(ulong.Parse(eventArgs.Interaction.Data.Values[0].Split(".")[1]));
+ response = user.GetAvatarUrl(ImageFormat.Auto);
+ }
+
+ if (ulong.Parse(eventArgs.Interaction.Data.Values[0].Split(".")[2]) == eventArgs.Interaction.User.Id)
+ {
+ await eventArgs.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder()
+ .WithContent(response)
+ .AddComponents(eventArgs.Message.Components));
+ }
+ else
+ {
+ await eventArgs.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder()
+ .WithContent(eventArgs.Message.Content)
+ .AddComponents(eventArgs.Message.Components));
+ }
+
+ }
+
+ #endregion
+
+ #region server management
+
+ [SlashCommand("SetConfig", "Set a config string"),
+ SlashCommandPermissions(Permissions.ManageGuild)]
+ public async Task SetConfigCommand(InteractionContext ctx, [Option("key", "The key name of the config string")] string configKey, [Option("value", "The value of the config string")] string configValue)
+ {
+ var config = DbContext.GuildConfigs.Where(c => c.ConfigGuildSnowflake == (long)ctx.Guild.Id && c.ConfigKey.Equals(configKey));
+
+ if (config.Any())
+ config.First().ConfigValue = configValue;
+ else
+ {
+ GuildConfig configin = new((long)ctx.Guild.Id, configKey, configValue);
+ DbContext.GuildConfigs.Add(configin);
+
+ }
+
+ await DbContext.SaveChangesAsync();
+
+ var embed = new DiscordEmbedBuilder()
+ .WithTitle("Config update was successful!")
+ .WithDescription($"``{configKey} --> {configValue}``");
+
+ var response = new DiscordInteractionResponseBuilder()
+ {
+ IsEphemeral = true
+ }
+ .AddEmbed(embed);
+
+ await ctx.CreateResponseAsync(response);
+
+ }
+
+ [SlashCommand("GetConfig", "Set a config string"),
+ SlashCommandPermissions(Permissions.ManageGuild)]
+ public async Task GetConfigCommand(InteractionContext ctx, [Option("key", "The key name of the config string")] string configKey)
+ {
+ var config = DbContext.GuildConfigs.Where(c => c.ConfigGuildSnowflake == (long)ctx.Guild.Id && c.ConfigKey.Equals(configKey));
+ string? configvalue = null;
+ string descriptionString;
+ if (config.Any())
+ configvalue = config.First().ConfigValue;
+
+ descriptionString = $"``{configKey}`` is currently set to ``{configvalue}``";
+
+ if (configvalue == null || string.IsNullOrWhiteSpace(configvalue) || configvalue.ToLower().Equals("null"))
+ descriptionString = $"``{configKey}`` is not currently set to anything";
+
+ var embed = new DiscordEmbedBuilder()
+ .WithTitle($"Config for {ctx.Guild}")
+ .WithDescription(descriptionString);
+
+ var response = new DiscordInteractionResponseBuilder()
+ {
+ IsEphemeral = true
+ }
+ .AddEmbed(embed);
+
+ await ctx.CreateResponseAsync(response);
+
+ }
+
+ [SlashCommand("BanAppealMsg", "Send the ban appeal message"),
+ SlashCommandPermissions(Permissions.Administrator)]
+ public async Task BanAppealMessage(InteractionContext ctx)
+ {
+
+ DiscordEmbed embedBuild = new DiscordEmbedBuilder()
+ .WithColor(DiscordColor.DarkRed)
+ .WithTitle("Ban Appeal")
+ .WithDescription($"Welcome to {ctx.Guild.Name}.\n\nTo open a ban appeal, please click the button below.")
+ .WithThumbnail("https://www.pngkey.com/png/full/382-3821512_tak-icon-hammer-01-hammer.png")
+ .Build();
+
+ var buttons = new List() {
+ new DiscordButtonComponent(ButtonStyle.Primary, $"banappeal.send", "Open A Ban Appeal")
+ };
+
+ var message = new DiscordMessageBuilder().WithEmbed(embedBuild).AddComponents(buttons);
+
+ await ctx.Channel.SendMessageAsync(message);
+ await ctx.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("Success!").AsEphemeral(true));
+
+ }
+
+ #endregion
+
+ }
+}
diff --git a/ARC/Schema/ArcDbContext.cs b/ARC/Schema/ArcDbContext.cs
index 942a566..9ceaed3 100644
--- a/ARC/Schema/ArcDbContext.cs
+++ b/ARC/Schema/ArcDbContext.cs
@@ -1,14 +1,13 @@
-using System.ComponentModel.DataAnnotations;
-using Arc.Exceptions;
+
using ARC.Extensions;
-using DocumentFormat.OpenXml.ExtendedProperties;
+
using DSharpPlus;
using DSharpPlus.Entities;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata;
+
using Microsoft.Extensions.Configuration;
using Npgsql;
-using Serilog;
+
namespace Arc.Schema;
@@ -70,7 +69,17 @@ public async Task CloseModmail(Modmail modmail)
await SaveChangesAsync();
}
+ public List GetUserNotes(ulong userSnowflake, ulong guildSnowflake)
+ {
+ var notes = UserNotes.Where(x => x.UserSnowflake == (long)userSnowflake && x.GuildSnowflake == (long)guildSnowflake).ToList();
+ return notes;
+ }
+ public List GetNextAppeal(ulong userSnowflake)
+ {
+ return Appeals.ToList().Where(x => x.UserSnowflake == (long)userSnowflake).ToList();
+ }
+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseNpgsql(new NpgsqlConnection(DbPath));
@@ -124,21 +133,15 @@ public async Task CreateSession()
$"Modmail with user: {User}"
);
- ChannelSnowflake = (long)mailChannel.Id;
- Uri avatarUri = new Uri(User.GetAvatarUrl(DSharpPlus.ImageFormat.Auto));
+
DiscordWebhook discordWebhook;
- using (var httpClient = new HttpClient())
- {
- var uriWoQuery = avatarUri.GetLeftPart(UriPartial.Path);
- var fileExt = Path.GetExtension(uriWoQuery);
-
- var path = Path.Combine(Path.GetTempPath(), $"avatar{fileExt}");
- var imageBytes = await httpClient.GetByteArrayAsync(avatarUri);
- Stream filestream = new MemoryStream(imageBytes);
- discordWebhook = await mailChannel.CreateWebhookAsync(User.Username, avatar: filestream);
- }
+ discordWebhook = await mailChannel.CreateWebhookAsync(User.Username);
+
+ ChannelSnowflake = (long)mailChannel.Id;
WebhookSnowflake = (long)discordWebhook.Id;
-
+
+ await Arc.ArcDbContext.Modmails.AddAsync(this);
+ await Arc.ArcDbContext.SaveChangesAsync();
return true;
}
@@ -233,8 +236,8 @@ public async Task SendModmailMenu()
public async Task SaveTranscript()
{
- File.Delete("./temp/transcript.html");
- File.Copy("./template.html", "./temp/transcript.html");
+ File.Delete($"./temp/transcript-{ModmailId}.html");
+ File.Copy("./template.html", $"./temp/transcript-{ModmailId}.html");
IReadOnlyList msgs = await Channel.GetMessagesAsync(2000);
@@ -242,7 +245,7 @@ public async Task SaveTranscript()
{
var message = msgs[i];
- await File.AppendAllTextAsync("./temp/transcript.html",
+ await File.AppendAllTextAsync($"./temp/transcript-{ModmailId}.html",
$@"
@@ -272,13 +275,13 @@ await File.AppendAllTextAsync("./temp/transcript.html",
}
- await File.AppendAllTextAsync("./temp/transcript.html", @"
+ await File.AppendAllTextAsync($"./temp/transcript-{ModmailId}.html", @"