diff --git a/.github/workflows/containerImages.yml b/.github/workflows/containerImages.yml new file mode 100644 index 0000000..a87d4fe --- /dev/null +++ b/.github/workflows/containerImages.yml @@ -0,0 +1,48 @@ +name: Publish Docker image +on: + push: + +jobs: + push_to_registry: + name: Publish Docker image + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and Push ModularAssistentForDiscordServer + uses: docker/build-push-action@v3 + with: + context: . + file: ./ModularAssistentForDiscordServer/Dockerfile + push: true + tags: | + ghcr.io/plerx2493/mads:latest + ghcr.io/plerx2493/mads:${{ github.sha }} + build-args: | + "BUILD_VER=${{ github.sha }}" + + - name: Build and Push QuartzDB + uses: docker/build-push-action@v3 + with: + context: . + file: ./QuartzNetDocker/Dockerfile + push: true + tags: | + ghcr.io/plerx2493/quartz-db-mysql:latest + ghcr.io/plerx2493/quartz-db-mysql:${{ github.sha }} + build-args: | + "BUILD_VER=${{ github.sha }}" \ No newline at end of file diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 87c3630..e807b04 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v2 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/ModularAssistentForDiscordServer/Commands/AutoCompletion/ReminderAutoCompletion.cs b/ModularAssistentForDiscordServer/Commands/AutoCompletion/ReminderAutoCompletion.cs index 42d34d0..ea3993f 100644 --- a/ModularAssistentForDiscordServer/Commands/AutoCompletion/ReminderAutoCompletion.cs +++ b/ModularAssistentForDiscordServer/Commands/AutoCompletion/ReminderAutoCompletion.cs @@ -22,7 +22,7 @@ namespace MADS.Commands.AutoCompletion; public class ReminderAutoCompletion : IAutocompleteProvider { - private IDbContextFactory _factory; + private readonly IDbContextFactory _factory; public ReminderAutoCompletion(IServiceProvider services) { @@ -31,11 +31,22 @@ public ReminderAutoCompletion(IServiceProvider services) public async Task> Provider(AutocompleteContext ctx) { + //TODO Userinput is not working (The input string '' was not in a correct format.) + /*long currentInput = (long?) ctx.OptionValue ?? 0; + string currentInputString = currentInput.ToString(); + if (currentInputString == "0") + { + currentInputString = ""; + } + */ + await using var db = await _factory.CreateDbContextAsync(); var choices = db.Reminders .Where(x => x.UserId == ctx.User.Id) - .Select(x => new DiscordAutoCompleteChoice(x.Id.ToString(), x.Id.ToString())) - .ToList(); + .Select(x => x.Id.ToString()) + .ToList() + //.Where(x => x.StartsWith(currentInputString)) + .Select(x => new DiscordAutoCompleteChoice(x, x)); return choices; } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/Slash/NoBtches.cs b/ModularAssistentForDiscordServer/Commands/AutoCompletion/TargetLanguageAutoCompletion.cs similarity index 50% rename from ModularAssistentForDiscordServer/Commands/Slash/NoBtches.cs rename to ModularAssistentForDiscordServer/Commands/AutoCompletion/TargetLanguageAutoCompletion.cs index 22674ce..1be66d6 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/NoBtches.cs +++ b/ModularAssistentForDiscordServer/Commands/AutoCompletion/TargetLanguageAutoCompletion.cs @@ -12,21 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +using DeepL; +using DSharpPlus.Entities; using DSharpPlus.SlashCommands; -using MADS.Extensions; -namespace MADS.Commands.Slash; +namespace MADS.Commands.AutoCompletion; -public sealed class NoBtches : MadsBaseApplicationCommand +public class TargetLanguageAutoCompletion : IAutocompleteProvider { - [SlashCommand("nobtches", "nobtches api")] - public async Task PingCommand - ( - InteractionContext ctx, - [Option("imageText", "Text on image")] string imageText - ) + private readonly Translator _translator; + + public TargetLanguageAutoCompletion(Translator translator) { - imageText = imageText.Replace(" ", "%20").Replace("?", "%3F"); - await ctx.CreateResponseAsync("https://api.no-bitch.es/" + imageText); + _translator = translator; + } + + public async Task> Provider(AutocompleteContext ctx) + { + var sourceLangs = await _translator.GetTargetLanguagesAsync(); + var choices = sourceLangs + .Select(x => new DiscordAutoCompleteChoice(x.Name, x.Code)) + .Take(25); + return choices; } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/AutoCompletion/VoiceAlertAutoCompletion.cs b/ModularAssistentForDiscordServer/Commands/AutoCompletion/VoiceAlertAutoCompletion.cs index 77fb1b9..404784b 100644 --- a/ModularAssistentForDiscordServer/Commands/AutoCompletion/VoiceAlertAutoCompletion.cs +++ b/ModularAssistentForDiscordServer/Commands/AutoCompletion/VoiceAlertAutoCompletion.cs @@ -14,16 +14,14 @@ using DSharpPlus.Entities; using DSharpPlus.SlashCommands; -using MADS.Entities; using MADS.Services; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace MADS.Commands.AutoCompletion; public class VoiceAlertAutoCompletion : IAutocompleteProvider { - private VoiceAlertService _voiceAlertService; + private readonly VoiceAlertService _voiceAlertService; public VoiceAlertAutoCompletion(IServiceProvider services) { diff --git a/ModularAssistentForDiscordServer/Commands/CommandUtillity.cs b/ModularAssistentForDiscordServer/Commands/CommandUtillity.cs index bb56922..f22ab1b 100644 --- a/ModularAssistentForDiscordServer/Commands/CommandUtillity.cs +++ b/ModularAssistentForDiscordServer/Commands/CommandUtillity.cs @@ -22,7 +22,7 @@ public static DiscordEmbedBuilder GetDiscordEmbed() { var standardEmbed = new DiscordEmbedBuilder { - Color = new Optional(new DiscordColor(0, 255, 194)) + Color = new DiscordColor(0, 255, 194) }; return standardEmbed; } diff --git a/ModularAssistentForDiscordServer/Commands/ContextMenu/StealEmojiMessage.cs b/ModularAssistentForDiscordServer/Commands/ContextMenu/StealEmojiMessage.cs index 9bd3884..f3b2cfb 100644 --- a/ModularAssistentForDiscordServer/Commands/ContextMenu/StealEmojiMessage.cs +++ b/ModularAssistentForDiscordServer/Commands/ContextMenu/StealEmojiMessage.cs @@ -21,17 +21,28 @@ namespace MADS.Commands.ContextMenu; -public class StealEmojiMessage : MadsBaseApplicationCommand +public partial class StealEmojiMessage : MadsBaseApplicationCommand { - private const string EmojiRegex = @""; - + private HttpClient _httpClient; + + public StealEmojiMessage(HttpClient httpClient) + { + _httpClient = httpClient; + } + [ContextMenu(ApplicationCommandType.MessageContextMenu, "Steal emoji(s)"), SlashRequirePermissions(Permissions.ManageEmojis)] public async Task YoinkAsync(ContextMenuContext ctx) { await ctx.DeferAsync(true); + + if (ctx.TargetMessage.Content is null) + { + await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("⚠️ Message has not content!")); + return; + } - var matches = Regex.Matches(ctx.TargetMessage.Content.Replace("><", "> <"), EmojiRegex, RegexOptions.Compiled); + var matches = EmojiRegex().Matches(ctx.TargetMessage.Content.Replace("><", "> <")); if (matches.Count < 1) { @@ -63,7 +74,7 @@ await ctx.EditResponseAsync( // ignored } - await IntendedWait(1000); + await IntendedWait(500); } var message = newEmojis.Aggregate("✅ Yoink! These emoji(s) have been added to your server: ", @@ -76,11 +87,10 @@ await ctx.EditResponseAsync( await ctx.EditResponseAsync(discordWebhook); } - private static async Task CopyEmoji(ContextMenuContext ctx, string name, ulong id, bool animated) + private async Task CopyEmoji(ContextMenuContext ctx, string name, ulong id, bool animated) { - using HttpClient httpClient = new(); var downloadedEmoji = - await httpClient.GetStreamAsync($"https://cdn.discordapp.com/emojis/{id}.{(animated ? "gif" : "png")}"); + await _httpClient.GetStreamAsync($"https://cdn.discordapp.com/emojis/{id}.{(animated ? "gif" : "png")}"); MemoryStream memory = new(); @@ -91,4 +101,7 @@ private static async Task CopyEmoji(ContextMenuContext ctx, string return newEmoji; } + + [GeneratedRegex("", RegexOptions.Compiled)] + private static partial Regex EmojiRegex(); } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/ContextMenu/TranslateMessage.cs b/ModularAssistentForDiscordServer/Commands/ContextMenu/TranslateMessage.cs new file mode 100644 index 0000000..9bdb5b1 --- /dev/null +++ b/ModularAssistentForDiscordServer/Commands/ContextMenu/TranslateMessage.cs @@ -0,0 +1,87 @@ +// Copyright 2023 Plerx2493 +// +// Licensed under the Apache License, Version 2.0 (the "License") +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using DeepL; +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.SlashCommands; +using MADS.CustomComponents; +using MADS.Extensions; +using MADS.Services; +using Quartz.Util; + +namespace MADS.Commands.ContextMenu; + +public class TranslateMessage : MadsBaseApplicationCommand +{ + private readonly TranslateInformationService _translateInformationService; + private readonly Translator _translator; + + public TranslateMessage(TranslateInformationService translateInformationService, Translator translator) + { + _translateInformationService = translateInformationService; + _translator = translator; + } + + [ContextMenu(ApplicationCommandType.MessageContextMenu, "Translate message")] + public async Task TranslateAsync(ContextMenuContext ctx) + { + await ctx.DeferAsync(true); + + var preferredLanguage = await _translateInformationService.GetPreferredLanguage(ctx.User.Id); + bool isPreferredLanguageSet = !preferredLanguage.IsNullOrWhiteSpace(); + + if(!isPreferredLanguageSet) preferredLanguage = "en-US"; + + var messageId = ctx.TargetMessage.Id; + var message = await ctx.Channel.GetMessageAsync(messageId); + var messageContent = message.Content; + + if (messageContent.IsNullOrWhiteSpace() || messageContent is null) + { + await ctx.CreateResponseAsync("⚠️ Message is empty!"); + return; + } + + if (preferredLanguage is null) + { + await ctx.CreateResponseAsync("⚠️ No language set!"); + return; + } + + var transaltedMessage = + await _translator.TranslateTextAsync(messageContent, null, preferredLanguage); + + var embed = new DiscordEmbedBuilder() + .WithAuthor(message.Author?.Username, + message.Author?.AvatarUrl) + .WithDescription(transaltedMessage.Text) + .WithColor(new DiscordColor(0, 255, 194)) + .WithFooter($"Translated from {transaltedMessage.DetectedSourceLanguageCode} to {preferredLanguage}") + .WithTimestamp(DateTime.Now); + + await ctx.CreateResponseAsync(embed); + + if (isPreferredLanguageSet) return; + + var followUpMessage = new DiscordFollowupMessageBuilder() + .WithContent("⚠️ You haven't set a preferred language yet. Default is english.") + .AddComponents(new DiscordButtonComponent(ButtonStyle.Primary, "setLanguage", "Set language").AsActionButton(ActionDiscordButtonEnum.SetTranslationLanguage)) + .AddComponents(new DiscordButtonComponent(ButtonStyle.Primary, "setLanguage", "Set your language to en-US").AsActionButton(ActionDiscordButtonEnum.SetTranslationLanguage, "en-US")) + .AsEphemeral(); + + + await ctx.FollowUpAsync(followUpMessage); + } +} \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/ContextMenu/UserInfoUser.cs b/ModularAssistentForDiscordServer/Commands/ContextMenu/UserInfoUser.cs index 02d4c57..b59ae9d 100644 --- a/ModularAssistentForDiscordServer/Commands/ContextMenu/UserInfoUser.cs +++ b/ModularAssistentForDiscordServer/Commands/ContextMenu/UserInfoUser.cs @@ -28,9 +28,8 @@ public async Task GetUserInfo(ContextMenuContext ctx) { var user = ctx.TargetUser; - DiscordMember member = null; - - user ??= ctx.User; + DiscordMember? member = null; + try { if (!ctx.Channel.IsPrivate) member = await ctx.Guild.GetMemberAsync(user.Id); @@ -57,7 +56,7 @@ public async Task GetUserInfo(ContextMenuContext ctx) embed.AddField("Joined at:", $"{member.JoinedAt.Humanize()} {Formatter.Timestamp(member.JoinedAt, TimestampFormat.ShortDate)}", true); - if (member.MfaEnabled.HasValue) embed.AddField("2FA:", member.MfaEnabled.ToString()); + if (member.MfaEnabled.HasValue) embed.AddField("2FA:", member.MfaEnabled.ToString()!); embed.AddField("Permissions:", member.Permissions.Humanize()); diff --git a/ModularAssistentForDiscordServer/Commands/Slash/About.cs b/ModularAssistentForDiscordServer/Commands/Slash/About.cs index 57b035d..386db5e 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/About.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/About.cs @@ -27,8 +27,8 @@ public async Task AboutCommand(InteractionContext ctx) { var discordEmbedBuilder = CommandUtility.GetDiscordEmbed(); var discordMessageBuilder = new DiscordInteractionResponseBuilder(); - var inviteUri = ctx.Client.CurrentApplication.GenerateOAuthUri(null, Permissions.Administrator, OAuthScope.Bot, - OAuthScope.ApplicationsCommands); + var inviteUri = ctx.Client.CurrentApplication.GenerateOAuthUri(null, Permissions.Administrator, DiscordOAuthScope.Bot, + DiscordOAuthScope.ApplicationsCommands); var addMe = $"[Click here!]({inviteUri.Replace(" ", "%20")})"; var diff = DateTime.Now - CommandService.StartTime; diff --git a/ModularAssistentForDiscordServer/Commands/Slash/BotStats.cs b/ModularAssistentForDiscordServer/Commands/Slash/BotStats.cs index 3e204b6..bc6748a 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/BotStats.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/BotStats.cs @@ -21,14 +21,13 @@ using MADS.Entities; using MADS.Extensions; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; namespace MADS.Commands.Slash; public sealed class BotStats : MadsBaseApplicationCommand { - private IDbContextFactory _contextFactory; - private DiscordRestClient _discordRestClient; + private readonly IDbContextFactory _contextFactory; + private readonly DiscordRestClient _discordRestClient; public BotStats(IDbContextFactory contextFactory, DiscordRestClient discordRestClient) { @@ -43,18 +42,20 @@ public async Task GetBotStatsAsync(InteractionContext ctx) var swDb = new Stopwatch(); var swRest = new Stopwatch(); + var _ = await db.Users.FirstOrDefaultAsync(); swDb.Start(); - var _ = await db.Guilds.FirstAsync(); + var __ = await db.Guilds.FirstOrDefaultAsync(); swDb.Stop(); + var ___ = await _discordRestClient.GetChannelAsync(ctx.Guild.Channels.Values.First().Id); swRest.Start(); - var __ = await _discordRestClient.GetChannelAsync(ctx.Channel.Id); + var ____ = await _discordRestClient.GetChannelAsync(ctx.Channel.Id); swRest.Stop(); using var process = Process.GetCurrentProcess(); - var members = ctx.Client.Guilds.Values.Select(x => x.MemberCount).Sum(); - var guilds = ctx.Client.Guilds.Count; + var members = db.Users.Count(); + var guilds = db.Guilds.Count(); var ping = ctx.Client.Ping; GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); var heapMemory = $"{process.PrivateMemorySize64 / 1024 / 1024} MB"; @@ -71,7 +72,7 @@ public async Task GetBotStatsAsync(InteractionContext ctx) .AddField("Rest Latency:", swRest.ElapsedMilliseconds.ToString("N0") + " ms", true) .AddField("Memory:", heapMemory, true) .AddField("Uptime:", - $"{DateTimeOffset.UtcNow.Subtract(process.StartTime).Humanize(2, minUnit: TimeUnit.Millisecond, maxUnit: TimeUnit.Day)}", + $"{DateTimeOffset.UtcNow.Subtract(process.StartTime).Humanize(3, minUnit: TimeUnit.Millisecond, maxUnit: TimeUnit.Day)}", true); await ctx.CreateResponseAsync(embed, true); diff --git a/ModularAssistentForDiscordServer/Commands/Slash/Jumpad.cs b/ModularAssistentForDiscordServer/Commands/Slash/Jumpad.cs index d81db95..33d11c2 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/Jumpad.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/Jumpad.cs @@ -17,6 +17,7 @@ using DSharpPlus.SlashCommands; using MADS.CustomComponents; using MADS.Extensions; +using Quartz.Util; namespace MADS.Commands.Slash; @@ -30,7 +31,9 @@ public async Task Test [Option("originChannel", "Channel where the users will be moved out"), ChannelTypes(ChannelType.Voice)] DiscordChannel originChannel, [Option("targetChannel", "Channel where the users will be put in"), ChannelTypes(ChannelType.Voice)] - DiscordChannel targetChannel + DiscordChannel targetChannel, + [Option("message", "Message to be sent")] + string? content = null ) { DiscordInteractionResponseBuilder message = new(); @@ -40,7 +43,7 @@ DiscordChannel targetChannel originChannel.Id, targetChannel.Id); message.AddComponents(newButton); - message.Content = "Jumppad"; + message.WithContent(!content.IsNullOrWhiteSpace() ? content! : "Jumppad"); await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, message); } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/Slash/MessageSnipe.cs b/ModularAssistentForDiscordServer/Commands/Slash/MessageSnipe.cs index 45e0844..df79998 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/MessageSnipe.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/MessageSnipe.cs @@ -18,13 +18,12 @@ using MADS.CustomComponents; using MADS.Extensions; using MADS.Services; -using Microsoft.Extensions.DependencyInjection; namespace MADS.Commands.Slash; public sealed class MessageSnipe : MadsBaseApplicationCommand { - private MessageSnipeService _messageSnipeService; + private readonly MessageSnipeService _messageSnipeService; public MessageSnipe(MessageSnipeService messageSnipeService) { @@ -46,32 +45,27 @@ public async Task SnipeEditAsync(InteractionContext ctx) private async Task DoSnipeAsync(InteractionContext ctx, bool edit) { await ctx.DeferAsync(true); - DiscordMessage message; + DiscordMessage? message; bool result; - - if (!edit) - { - result = _messageSnipeService.TryGetMessage(ctx.Channel.Id, out message); - } - else - { - result = _messageSnipeService.TryGetEditedMessage(ctx.Channel.Id, out message); - } - if (!result) + result = !edit + ? _messageSnipeService.TryGetMessage(ctx.Channel.Id, out message) + : _messageSnipeService.TryGetEditedMessage(ctx.Channel.Id, out message); + + if (!result || message is null) { await EditResponse_Error("⚠️ No message to snipe! Either nothing was deleted, or the message has expired (12 hours)!"); return; } var content = message.Content; - if (content.Length > 500) content = content.Substring(0, 497) + "..."; - var member = await ctx.Guild.GetMemberAsync(message.Author.Id); + if (content is not null && content.Length > 500) content = string.Concat(content.AsSpan(0, 497), "..."); + DiscordMember? member = message.Author is not null ? await ctx.Guild.GetMemberAsync(message.Author.Id) : null; var embed = new DiscordEmbedBuilder() .WithAuthor( - $"{member.DisplayName}" + (edit ? " (Edited)" : ""), - iconUrl: message.Author.GetAvatarUrl(ImageFormat.Png)) + $"{member?.DisplayName ?? message.Author?.GlobalName}" + (edit ? " (Edited)" : ""), + iconUrl: message.Author?.GetAvatarUrl(ImageFormat.Png)) .WithFooter( $"{(edit ? "Edit" : "Deletion")} sniped by {ctx.Member.DisplayName}", ctx.User.AvatarUrl); @@ -81,15 +75,15 @@ private async Task DoSnipeAsync(InteractionContext ctx, bool edit) embed.WithTimestamp(message.Id); var embeds = new List(); - var attachments = message.Attachments.Where(x => x.MediaType.StartsWith("image/")).ToList(); + var attachments = message.Attachments.Where(x => x.MediaType?.StartsWith("image/") ?? false).ToList(); - for (var i = 0; i < attachments.Count(); i++) + for (var i = 0; i < attachments.Count; i++) { var attachment = attachments.ElementAt(i); - if (i == 0) + if (i == 0 && attachment.Url is not null) embed.WithThumbnail(attachment.Url); - else + else if (attachment.Url is not null) embeds.Add(new DiscordEmbedBuilder() .WithTitle("Additional Image").WithThumbnail(attachment.Url)); } @@ -99,7 +93,7 @@ private async Task DoSnipeAsync(InteractionContext ctx, bool edit) var btn = new DiscordButtonComponent(ButtonStyle.Danger, "placeholder", "Delete (Author only)", emoji: new DiscordComponentEmoji("🗑")); - btn = btn.AsActionButton(ActionDiscordButtonEnum.DeleteOneUserOnly, message.Author.Id); + btn = btn.AsActionButton(ActionDiscordButtonEnum.DeleteOneUserOnly, message.Author!.Id); response.AddComponents(btn); diff --git a/ModularAssistentForDiscordServer/Commands/Slash/Ping.cs b/ModularAssistentForDiscordServer/Commands/Slash/Ping.cs index e0d1722..9a80335 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/Ping.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/Ping.cs @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics; using DSharpPlus.SlashCommands; using Humanizer; +using Humanizer.Localisation; using MADS.Extensions; namespace MADS.Commands.Slash; @@ -23,14 +25,13 @@ public sealed class Ping : MadsBaseApplicationCommand [SlashCommand("ping", "Get the bot's ping")] public async Task PingCommand(InteractionContext ctx) { - var diff = DateTime.Now - CommandService.StartTime; - var date = diff.Humanize(); - + using var process = Process.GetCurrentProcess(); + var discordEmbedBuilder = CommandUtility.GetDiscordEmbed(); discordEmbedBuilder .WithTitle("Status") .WithTimestamp(DateTime.Now) - .AddField("Uptime", date) + .AddField("Uptime", $"{DateTimeOffset.UtcNow.Subtract(process.StartTime).Humanize(3, minUnit: TimeUnit.Millisecond, maxUnit: TimeUnit.Day)}") .AddField("Websocket ping", $"{ctx.Client.Ping} ms"); await ctx.CreateResponseAsync(discordEmbedBuilder, true); diff --git a/ModularAssistentForDiscordServer/Commands/Slash/Quotes.cs b/ModularAssistentForDiscordServer/Commands/Slash/Quotes.cs index 92240b0..2119659 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/Quotes.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/Quotes.cs @@ -18,7 +18,6 @@ using MADS.Entities; using MADS.Extensions; using MADS.Services; -using Microsoft.Extensions.DependencyInjection; namespace MADS.Commands.Slash; diff --git a/ModularAssistentForDiscordServer/Commands/Slash/Reminder.cs b/ModularAssistentForDiscordServer/Commands/Slash/Reminder.cs index 70be7ab..f18cbc6 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/Reminder.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/Reminder.cs @@ -21,7 +21,6 @@ using MADS.Entities; using MADS.Extensions; using MADS.Services; -using Microsoft.Extensions.DependencyInjection; namespace MADS.Commands.Slash; diff --git a/ModularAssistentForDiscordServer/Commands/Slash/RoleSelection.cs b/ModularAssistentForDiscordServer/Commands/Slash/RoleSelection.cs index e78608b..ed66b4b 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/RoleSelection.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/RoleSelection.cs @@ -18,7 +18,6 @@ using DSharpPlus.SlashCommands; using DSharpPlus.SlashCommands.Attributes; using MADS.Extensions; -using Serilog; namespace MADS.Commands.Slash; @@ -56,7 +55,7 @@ await ctx.EditResponseAsync( //get all roles and Create a list of select menu options var options = Enumerable.Empty(); - var roles = ctx.Guild!.Roles.Values.ToList(); + var roles = ctx.Guild.Roles.Values.ToList(); //remove all roles from bots etc roles.RemoveAll(x => x.IsManaged); diff --git a/ModularAssistentForDiscordServer/Commands/Slash/StarboardConfig.cs b/ModularAssistentForDiscordServer/Commands/Slash/StarboardConfig.cs index 80aed56..202ade6 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/StarboardConfig.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/StarboardConfig.cs @@ -20,7 +20,6 @@ using MADS.Entities; using MADS.Extensions; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; namespace MADS.Commands.Slash; @@ -65,7 +64,7 @@ public async Task StarboardConfigCommand guildConfig.StarboardChannelId = channel.Id; guildConfig.StarboardThreshold = (int) threshhold; - guildConfig.StarboardEmojiId = (ulong?) emoji.Id ?? 0; + guildConfig.StarboardEmojiId = emoji.Id; guildConfig.StarboardEmojiName = emoji.Name; guildConfig.StarboardActive = true; diff --git a/ModularAssistentForDiscordServer/Commands/Slash/Translation.cs b/ModularAssistentForDiscordServer/Commands/Slash/Translation.cs new file mode 100644 index 0000000..4baf4be --- /dev/null +++ b/ModularAssistentForDiscordServer/Commands/Slash/Translation.cs @@ -0,0 +1,93 @@ +// Copyright 2023 Plerx2493 +// +// Licensed under the Apache License, Version 2.0 (the "License") +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using DeepL; +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.SlashCommands; +using MADS.Commands.AutoCompletion; +using MADS.Extensions; +using MADS.Services; + +namespace MADS.Commands.Slash; + +[SlashCommandGroup("translation", "Commands for translation")] +public class Translation : MadsBaseApplicationCommand +{ + private readonly TranslateInformationService _translationUserInfo; + private readonly Translator _translator; + + public Translation(TranslateInformationService translationUserInfo, Translator translator) + { + _translationUserInfo = translationUserInfo; + _translator = translator; + } + + [SlashCommand("setLanguage", "Set your preferred language")] + public async Task SetLanguageAsync + ( + InteractionContext ctx, + [Option("language", "The language you want to get (default: en)", true), Autocomplete(typeof(TargetLanguageAutoCompletion))] + string language = "en" + ) + { + if (string.IsNullOrWhiteSpace(language)) + { + await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, + new DiscordInteractionResponseBuilder() + .WithContent("⚠️ Language can't be empty!")); + return; + } + + var code = TranslateInformationService.StandardizeLang(language); + + _translationUserInfo.SetPreferredLanguage(ctx.User.Id, code); + + await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, + new DiscordInteractionResponseBuilder() + .WithContent($"✅ Language set to {code}")); + } + + [SlashCommand("translate", "Translate a text")] + public async Task TranslateText + ( + InteractionContext ctx, + [Option("text", "The text you want to translate")] + string text, + [Option("language", "The language you want to get (default: en)", true), Autocomplete(typeof(TargetLanguageAutoCompletion))] + string language = "en", + [Option("publicResult", "Weather the result should be public or not (default: false)")] + bool publicResult = false + ) + { + await ctx.DeferAsync(); + + if (string.IsNullOrWhiteSpace(text)) + { + await ctx.EditResponseAsync(new DiscordWebhookBuilder() + .WithContent("⚠️ Text can't be empty!")); + return; + } + + var translatedText = await _translator.TranslateTextAsync(text, null, language); + + var embed = new DiscordEmbedBuilder() + .WithDescription(translatedText.Text) + .WithColor(new DiscordColor(0, 255, 194)) + .WithFooter($"Translated from {translatedText.DetectedSourceLanguageCode} to {language}") + .WithTimestamp(DateTime.Now); + + await ctx.CreateResponseAsync(embed, publicResult); + } +} \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/Slash/VoiceAlerts.cs b/ModularAssistentForDiscordServer/Commands/Slash/VoiceAlerts.cs index 0c2b707..dbf1575 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/VoiceAlerts.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/VoiceAlerts.cs @@ -19,7 +19,6 @@ using MADS.Commands.AutoCompletion; using MADS.Extensions; using MADS.Services; -using Microsoft.Extensions.DependencyInjection; namespace MADS.Commands.Slash; diff --git a/ModularAssistentForDiscordServer/Commands/Slash/moveEmoji.cs b/ModularAssistentForDiscordServer/Commands/Slash/moveEmoji.cs index 04874c7..1a316e5 100644 --- a/ModularAssistentForDiscordServer/Commands/Slash/moveEmoji.cs +++ b/ModularAssistentForDiscordServer/Commands/Slash/moveEmoji.cs @@ -23,10 +23,8 @@ namespace MADS.Commands.Slash; -public sealed class MoveEmoji : MadsBaseApplicationCommand +public sealed partial class MoveEmoji : MadsBaseApplicationCommand { - private const string EmojiRegex = @""; - [SlashCommand("MoveEmoji", "Move emoji to your guild"), SlashRequirePermissions(Permissions.ManageEmojis)] public async Task MoveEmojiAsync (InteractionContext ctx, [Option("Emoji", "Emoji which should be moved")] string pEmoji) @@ -34,7 +32,7 @@ public async Task MoveEmojiAsync await ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral()); - var matches = Regex.Matches(pEmoji, EmojiRegex, RegexOptions.Compiled); + var matches = EmojiRegex().Matches(pEmoji); if (!matches.Any()) { @@ -120,4 +118,7 @@ private static async Task CopyEmoji(DiscordClient client, string name, ulong id, var targetGuildEntity = await client.GetGuildAsync(targetGuild); var _ = await targetGuildEntity.CreateEmojiAsync(name, memory); } + + [GeneratedRegex("", RegexOptions.Compiled)] + private static partial Regex EmojiRegex(); } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/Text/Base/Eval.cs b/ModularAssistentForDiscordServer/Commands/Text/Base/Eval.cs index 7b093c0..60390d0 100644 --- a/ModularAssistentForDiscordServer/Commands/Text/Base/Eval.cs +++ b/ModularAssistentForDiscordServer/Commands/Text/Base/Eval.cs @@ -96,17 +96,17 @@ public TestVariables(DiscordMessage msg, DiscordClient client, CommandContext ct ClientService = mdb; Message = msg; Channel = msg.Channel; - Guild = Channel.Guild; + Guild = Channel?.Guild; User = Message.Author; - if (Guild != null) + if (Guild is not null && User is not null) Member = Guild.GetMemberAsync(User.Id).GetAwaiter().GetResult(); Context = ctx; } public DiscordMessage Message { get; private set; } - public DiscordChannel Channel { get; private set; } - public DiscordGuild Guild { get; private set; } - public DiscordUser User { get; private set; } + public DiscordChannel? Channel { get; private set; } + public DiscordGuild? Guild { get; private set; } + public DiscordUser? User { get; private set; } public DiscordMember Member { get; private set; } public CommandContext Context { get; private set; } public DiscordClient Client { get; private set; } diff --git a/ModularAssistentForDiscordServer/Commands/Text/Base/ExitGuild.cs b/ModularAssistentForDiscordServer/Commands/Text/Base/ExitGuild.cs index 908765e..a62400d 100644 --- a/ModularAssistentForDiscordServer/Commands/Text/Base/ExitGuild.cs +++ b/ModularAssistentForDiscordServer/Commands/Text/Base/ExitGuild.cs @@ -12,13 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Diagnostics; using DSharpPlus; using DSharpPlus.CommandsNext; using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; using MADS.Extensions; -using Microsoft.Extensions.DependencyInjection; namespace MADS.Commands.Text.Base; @@ -37,23 +34,4 @@ public async Task LeaveGuildOwner(CommandContext ctx) await ctx.Message.DeleteAsync(); await ctx.Guild.LeaveAsync(); } - - [Command("test"), Description("Leave given server"), RequireGuild, Hidden, RequireOwner] - public async Task Test(CommandContext ctx, DiscordChannel chnl, int limit) - { - var client = ModularDiscordBot.Services.GetRequiredService(); - var channel = await client.GetChannelAsync(chnl.Id); - var messages = channel.GetMessagesBeforeAsync(ctx.Message.Id + 1221, limit); - - var sw = Stopwatch.StartNew(); - - int i = 0; - await foreach (var message in messages) - { - i++; - } - - await ctx.RespondAsync($"Found {i} messages in {sw.ElapsedMilliseconds}ms"); - - } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/CustomComponents/ActionDiscordButtonEnum.cs b/ModularAssistentForDiscordServer/CustomComponents/ActionDiscordButtonEnum.cs index 6644301..c230d57 100644 --- a/ModularAssistentForDiscordServer/CustomComponents/ActionDiscordButtonEnum.cs +++ b/ModularAssistentForDiscordServer/CustomComponents/ActionDiscordButtonEnum.cs @@ -23,5 +23,6 @@ public enum ActionDiscordButtonEnum : byte GetIdGuild, MoveVoiceChannel, AnswerDmChannel, - DeleteOneUserOnly + DeleteOneUserOnly, + SetTranslationLanguage, } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Dockerfile b/ModularAssistentForDiscordServer/Dockerfile new file mode 100644 index 0000000..aeb2848 --- /dev/null +++ b/ModularAssistentForDiscordServer/Dockerfile @@ -0,0 +1,14 @@ +# BUILD +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build +WORKDIR /src +COPY ./ModularAssistentForDiscordServer ./ +RUN dotnet restore +RUN dotnet publish -c Release -o out + +# RUNNER IMAGE +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine +WORKDIR /app +COPY --from=build /src/out . +WORKDIR /config + +ENTRYPOINT ["dotnet", "/app/ModularAssistentForDiscordServer.dll"] \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Entities/DiscordReactionUpdateEvent.cs b/ModularAssistentForDiscordServer/Entities/DiscordReactionUpdateEvent.cs index 55bd8b9..e3a8a98 100644 --- a/ModularAssistentForDiscordServer/Entities/DiscordReactionUpdateEvent.cs +++ b/ModularAssistentForDiscordServer/Entities/DiscordReactionUpdateEvent.cs @@ -19,9 +19,9 @@ namespace MADS.Entities; public class DiscordReactionUpdateEvent { - public AsyncEventArgs EventArgs; - public DiscordMessage Message; - public DiscordReactionUpdateType Type; + public AsyncEventArgs EventArgs { get; set; } + public DiscordMessage Message { get; set; } + public DiscordReactionUpdateType Type { get; set; } } public enum DiscordReactionUpdateType diff --git a/ModularAssistentForDiscordServer/Entities/GuildDbEntity.cs b/ModularAssistentForDiscordServer/Entities/GuildDbEntity.cs index dd67a31..778bbc0 100644 --- a/ModularAssistentForDiscordServer/Entities/GuildDbEntity.cs +++ b/ModularAssistentForDiscordServer/Entities/GuildDbEntity.cs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#nullable enable using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; diff --git a/ModularAssistentForDiscordServer/JsonModel/ConfigJson.cs b/ModularAssistentForDiscordServer/Entities/MadsConfig.cs similarity index 73% rename from ModularAssistentForDiscordServer/JsonModel/ConfigJson.cs rename to ModularAssistentForDiscordServer/Entities/MadsConfig.cs index f3f3e3f..1b2d604 100644 --- a/ModularAssistentForDiscordServer/JsonModel/ConfigJson.cs +++ b/ModularAssistentForDiscordServer/Entities/MadsConfig.cs @@ -13,30 +13,26 @@ // limitations under the License. using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -namespace MADS.JsonModel; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. -public class ConfigJson +namespace MADS.Entities; + +public class MadsConfig { - [JsonProperty("token")] public string Token { get; set; } - - [JsonProperty("defaultPrefix")] + public string Prefix { get; set; } - - [JsonProperty("minmumloglvl")] + public LogLevel LogLevel { get; set; } - - [JsonProperty("databaseConnectionString")] + public string ConnectionString { get; set; } - [JsonProperty("databaseConnectionStringQuartz")] public string ConnectionStringQuartz { get; set; } - [JsonProperty("discordWebhook")] public string DiscordWebhook { get; set; } - [JsonProperty("DmProxyChannelId")] public ulong? DmProxyChannelId { get; set; } + + public string? DeeplApiKey { get; set; } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Entities/UserDbEntity.cs b/ModularAssistentForDiscordServer/Entities/UserDbEntity.cs index 6226d75..71c663c 100644 --- a/ModularAssistentForDiscordServer/Entities/UserDbEntity.cs +++ b/ModularAssistentForDiscordServer/Entities/UserDbEntity.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -27,6 +28,9 @@ public class UserDbEntity [Column("discriminator")] public int Discriminator { get; set; } + + [Column("prefered_language")] + public string PreferedLanguage { get; set; } public List Incidents { get; set; } diff --git a/ModularAssistentForDiscordServer/Entities/VoiceAlert.cs b/ModularAssistentForDiscordServer/Entities/VoiceAlert.cs index 038bc82..6173467 100644 --- a/ModularAssistentForDiscordServer/Entities/VoiceAlert.cs +++ b/ModularAssistentForDiscordServer/Entities/VoiceAlert.cs @@ -14,7 +14,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using DSharpPlus.Entities; namespace MADS.Entities; diff --git a/ModularAssistentForDiscordServer/EventListeners/EventListener.ActionButtons.cs b/ModularAssistentForDiscordServer/EventListeners/EventListener.ActionButtons.cs index 82b64c5..39d80c1 100644 --- a/ModularAssistentForDiscordServer/EventListeners/EventListener.ActionButtons.cs +++ b/ModularAssistentForDiscordServer/EventListeners/EventListener.ActionButtons.cs @@ -18,6 +18,8 @@ using DSharpPlus.EventArgs; using DSharpPlus.Interactivity.Extensions; using MADS.CustomComponents; +using MADS.Services; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace MADS.EventListeners; @@ -26,69 +28,114 @@ internal static partial class EventListener { public static void EnableButtonListener(DiscordClient client) { - client.ComponentInteractionCreated += Task (sender, e) => + client.ComponentInteractionCreated += async Task (sender, e) => { + //Format of the button id: CMD:ActionCode:arg1:arg2:arg3 try { - if (e is null) return Task.CompletedTask; - sender.Logger.LogTrace("Button clicked with id: {Id}", e.Id); - if (!Regex.IsMatch(e.Id, @"^CMD:\d{1,4}(?::\d{1,20}){0,3}$", RegexOptions.Compiled)) - return Task.CompletedTask; + if (!CommandButtonRegex().IsMatch(e.Id)) + return; var substring = e.Id.Split(':'); - if (!int.TryParse(substring[1], out var actionCode)) return Task.CompletedTask; + if (!int.TryParse(substring[1], out var actionCode)) return; substring = substring.Skip(1).ToArray(); switch (actionCode) { case (int) ActionDiscordButtonEnum.BanUser: - BanUser(e, substring); + await BanUser(e, substring); break; case (int) ActionDiscordButtonEnum.KickUser: - KickUser(e, substring); + await KickUser(e, substring); break; case (int) ActionDiscordButtonEnum.GetIdUser: - GetUserId(e, substring); + await GetUserId(e, substring); break; case (int) ActionDiscordButtonEnum.GetIdGuild: - GetGuildId(e, substring); + await GetGuildId(e, substring); break; case (int) ActionDiscordButtonEnum.GetIdChannel: - GetChannelId(e, substring); + await GetChannelId(e, substring); break; case (int) ActionDiscordButtonEnum.MoveVoiceChannel: - MoveVoiceChannelUser(e, substring); + await MoveVoiceChannelUser(e, substring); break; case (int) ActionDiscordButtonEnum.DeleteOneUserOnly: - DeleteOneUserOnly(e, substring); + await DeleteOneUserOnly(e, substring); break; + case (int) ActionDiscordButtonEnum.AnswerDmChannel: - AnswerDmChannel(e, sender, substring); + await AnswerDmChannel(e, sender, substring); + break; + + case (int) ActionDiscordButtonEnum.SetTranslationLanguage: + await SetTranslationLanguage(e, sender, substring); break; } } catch (Exception exception) { - var _ = MainProgram.LogToWebhookAsync(exception); + await MainProgram.LogToWebhookAsync(exception); } - - return Task.CompletedTask; }; } - private static async void AnswerDmChannel(ComponentInteractionCreateEventArgs e, DiscordClient client, + // format: 8:(languageCode)? + private static async Task SetTranslationLanguage(ComponentInteractionCreateEventArgs args, DiscordClient sender, string[] substring) + { + var translationService = ModularDiscordBot.Services.GetRequiredService(); + + var langSetEmbed = new DiscordEmbedBuilder() + .WithTitle("Language set successful!") + .WithColor(DiscordColor.Green); + + if (substring.Length == 1) + { + var modal = new DiscordInteractionResponseBuilder() + .WithTitle("Set your preferred language:") + .WithCustomId($"setLanguage-{args.User.Id}") + .AddComponents(new TextInputComponent("Please enter your preferred language:", "answer-text", required: true, + style: TextInputStyle.Paragraph)); + await args.Interaction.CreateResponseAsync(InteractionResponseType.Modal, modal); + + var interactive = sender.GetInteractivity(); + var result = await interactive.WaitForModalAsync($"setLanguage-{args.User.Id}"); + + if (result.TimedOut) + { + await args.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().WithContent("408 - Request Timeout")); + return; + } + + translationService.SetPreferredLanguage(args.User.Id, result.Result.Values["answer-text"]); + return; + } + if(substring.Length != 2) + { + await args.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, + new DiscordInteractionResponseBuilder().WithContent("400 - Bad Request").AsEphemeral()); + return; + } + + translationService.SetPreferredLanguage(args.User.Id, substring[2]); + + await args.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, + new DiscordInteractionResponseBuilder().AddEmbed(langSetEmbed).AsEphemeral()); + } + + private static async Task AnswerDmChannel(ComponentInteractionCreateEventArgs e, DiscordClient client, IReadOnlyList substring) { - if (!client.CurrentApplication.Owners.Contains(e.User)) + if (!(client.CurrentApplication.Owners?.Contains(e.User) ?? false)) { await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("401 - Unauthorized").AsEphemeral()); @@ -147,40 +194,40 @@ await result.Result.Interaction.EditOriginalResponseAsync( await e.Message.ModifyAsync(editedMessage); } - private static async void DeleteOneUserOnly(ComponentInteractionCreateEventArgs e, IReadOnlyList substring) + private static async Task DeleteOneUserOnly(ComponentInteractionCreateEventArgs e, IReadOnlyList substring) { if (e.User.Id.ToString() != substring[1]) return; await e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate); await e.Message.DeleteAsync(); } - private static async void MoveVoiceChannelUser + private static async Task MoveVoiceChannelUser ( ComponentInteractionCreateEventArgs e, IReadOnlyList substring ) { + await e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate); + var member = await e.Guild.GetMemberAsync(e.User.Id); if (!member.Permissions.HasPermission(Permissions.MoveMembers)) return; var originChannel = e.Guild.GetChannel(ulong.Parse(substring[1])); var targetChannel = e.Guild.GetChannel(ulong.Parse(substring[2])); foreach (var voiceMember in originChannel.Users) await targetChannel.PlaceMemberAsync(voiceMember); - - await e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate); } - private static async void BanUser(ComponentInteractionCreateEventArgs e, IReadOnlyList substring) + private static async Task BanUser(ComponentInteractionCreateEventArgs e, IReadOnlyList substring) { + await e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate); var member = await e.Guild.GetMemberAsync(e.User.Id); if (!member.Permissions.HasPermission(Permissions.BanMembers)) return; var userId = ulong.Parse(substring[1]); await e.Guild.BanMemberAsync(userId); - await e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate); } - private static async void KickUser(ComponentInteractionCreateEventArgs e, IReadOnlyList substring) + private static async Task KickUser(ComponentInteractionCreateEventArgs e, IReadOnlyList substring) { var member = await e.Guild.GetMemberAsync(e.User.Id); if (!member.Permissions.HasPermission(Permissions.KickMembers)) return; @@ -190,7 +237,7 @@ private static async void KickUser(ComponentInteractionCreateEventArgs e, IReadO await e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate); } - private static async void GetUserId(InteractionCreateEventArgs e, IReadOnlyList substring) + private static async Task GetUserId(InteractionCreateEventArgs e, IReadOnlyList substring) { var response = new DiscordInteractionResponseBuilder(); @@ -200,7 +247,7 @@ private static async void GetUserId(InteractionCreateEventArgs e, IReadOnlyList< await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, response); } - private static async void GetGuildId(InteractionCreateEventArgs e, IReadOnlyList substring) + private static async Task GetGuildId(InteractionCreateEventArgs e, IReadOnlyList substring) { var response = new DiscordInteractionResponseBuilder(); @@ -210,7 +257,7 @@ private static async void GetGuildId(InteractionCreateEventArgs e, IReadOnlyList await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, response); } - private static async void GetChannelId(InteractionCreateEventArgs e, IReadOnlyList substring) + private static async Task GetChannelId(InteractionCreateEventArgs e, IReadOnlyList substring) { var response = new DiscordInteractionResponseBuilder(); @@ -219,4 +266,7 @@ private static async void GetChannelId(InteractionCreateEventArgs e, IReadOnlyLi await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, response); } + + [GeneratedRegex("^CMD:\\d{1,4}(?::\\d{1,20}){0,3}$", RegexOptions.Compiled)] + private static partial Regex CommandButtonRegex(); } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/EventListeners/EventListener.Dms.cs b/ModularAssistentForDiscordServer/EventListeners/EventListener.Dms.cs index 7822acd..f93c46c 100644 --- a/ModularAssistentForDiscordServer/EventListeners/EventListener.Dms.cs +++ b/ModularAssistentForDiscordServer/EventListeners/EventListener.Dms.cs @@ -22,34 +22,40 @@ namespace MADS.EventListeners; internal static partial class EventListener { - internal static async Task DmHandler(DiscordClient client, MessageCreateEventArgs e) + static EventListener() { - if (!e.Channel.IsPrivate) return; - if (e.Author.IsBot) return; - - //if (client.CurrentApplication.Owners.Contains(e.Author)) return Task.CompletedTask; - //retrieves the config.json var config = DataProvider.GetConfig(); - + //Create a discordWebhookClient and add the debug webhook from the config.json - var webhookClient = new DiscordWebhookClient(); + WebhookClient = new DiscordWebhookClient(); var webhookUrl = new Uri(config.DiscordWebhook); - webhookClient.AddWebhookAsync(webhookUrl).GetAwaiter().GetResult(); + WebhookClient.AddWebhookAsync(webhookUrl).GetAwaiter().GetResult(); + } + + internal static DiscordWebhookClient WebhookClient; + + internal static async Task DmHandler(DiscordClient client, MessageCreateEventArgs e) + { + if (!e.Channel.IsPrivate) return; + if (e.Author.IsBot) return; + if (client.CurrentApplication.Owners?.Contains(e.Author) ?? false) return; + if (client.CurrentApplication.Team?.Members.Any(x => x.User.Id == e.Author.Id) ?? false) return; + if (e.Message.Content is null) return; var embed = new DiscordEmbedBuilder() .WithAuthor("Mads-DMs") .WithColor(new DiscordColor(0, 255, 194)) .WithTimestamp(DateTime.UtcNow) - .WithTitle($"Dm from {e.Author.Username}#{e.Author.Discriminator}") + .WithTitle($"Dm from {e.Author.Username}") .WithDescription(e.Message.Content); var button = new DiscordButtonComponent(ButtonStyle.Success, "Placeholder", "Respond to User").AsActionButton( ActionDiscordButtonEnum.AnswerDmChannel, e.Channel.Id); - var channel = await client.GetChannelAsync(webhookClient.Webhooks[0].ChannelId); + var channel = await client.GetChannelAsync(WebhookClient.Webhooks[0].ChannelId); await channel.SendMessageAsync(new DiscordMessageBuilder().AddEmbed(embed).AddComponents(button)); } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/EventListeners/EventListener.Errors.cs b/ModularAssistentForDiscordServer/EventListeners/EventListener.Errors.cs index d572e40..19a2e0b 100644 --- a/ModularAssistentForDiscordServer/EventListeners/EventListener.Errors.cs +++ b/ModularAssistentForDiscordServer/EventListeners/EventListener.Errors.cs @@ -61,6 +61,7 @@ await e.Context.Interaction.CreateResponseAsync(InteractionResponseType.ChannelM await e.Context.Interaction.EditOriginalResponseAsync( new DiscordWebhookBuilder(new DiscordInteractionResponseBuilder().AddEmbed(discordEmbed) .AsEphemeral())); + return; } catch (BadRequestException) { @@ -68,8 +69,7 @@ await e.Context.Interaction.EditOriginalResponseAsync( await e.Context.Channel.SendMessageAsync(discordEmbed); } - - + internal static async Task OnCNextErrored(CommandsNextExtension sender, CommandErrorEventArgs e) { var typeOfException = e.Exception.GetType(); @@ -79,13 +79,6 @@ internal static async Task OnCNextErrored(CommandsNextExtension sender, CommandE await e.Context.Message.RespondAsync($"OOPS your command just errored... \n {e.Exception.Message}"); await e.Context.Message.RespondAsync(e.Exception.InnerException?.Message ?? "no inner exception"); - var reallyLongString = e.Exception.StackTrace; - - var interactivity = e.Context.Client.GetInteractivity(); - var pages = interactivity.GeneratePagesInEmbed(reallyLongString); - - await e.Context.Channel.SendPaginatedMessageAsync(e.Context.Member, pages, PaginationBehaviour.Ignore, - ButtonPaginationBehavior.DeleteButtons); } internal static async Task OnClientErrored(DiscordClient sender, ClientErrorEventArgs e) @@ -96,7 +89,7 @@ internal static async Task OnClientErrored(DiscordClient sender, ClientErrorEven //Create a discordWebhookClient and add the debug webhook from the config.json var webhookClient = new DiscordWebhookClient(); var webhookUrl = new Uri(config.DiscordWebhook); - webhookClient.AddWebhookAsync(webhookUrl).GetAwaiter().GetResult(); + await webhookClient.AddWebhookAsync(webhookUrl); var exceptionEmbed = new DiscordEmbedBuilder() @@ -112,4 +105,20 @@ internal static async Task OnClientErrored(DiscordClient sender, ClientErrorEven await webhookClient.BroadcastMessageAsync(webhookBuilder); } + + internal static async Task OnAutocompleteError(SlashCommandsExtension sender, AutocompleteErrorEventArgs e) + { + await e.Context.Channel.SendMessageAsync($"OOPS your command just errored... \n {e.Exception.Message}"); + await e.Context.Channel.SendMessageAsync(e.Exception.InnerException?.Message ?? "no inner exception"); + var reallyLongString = e.Exception.StackTrace; + + var interactivity = e.Context.Client.GetInteractivity(); + if (reallyLongString != null) + { + var pages = interactivity.GeneratePagesInEmbed(reallyLongString); + + await e.Context.Channel.SendPaginatedMessageAsync(e.Context.Member, pages, PaginationBehaviour.Ignore, + ButtonPaginationBehavior.DeleteButtons); + } + } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/EventListeners/EventListener.GuildChanged.cs b/ModularAssistentForDiscordServer/EventListeners/EventListener.GuildChanged.cs index d0088fe..4e54a40 100644 --- a/ModularAssistentForDiscordServer/EventListeners/EventListener.GuildChanged.cs +++ b/ModularAssistentForDiscordServer/EventListeners/EventListener.GuildChanged.cs @@ -14,38 +14,44 @@ using DSharpPlus; using DSharpPlus.Entities; +using DSharpPlus.EventArgs; using MADS.Services; +using Microsoft.Extensions.DependencyInjection; namespace MADS.EventListeners; internal static partial class EventListener { - public static void AddGuildNotifier(DiscordClient client, LoggingService loggingService) + public static async Task OnGuildCreated(DiscordClient sender, GuildCreateEventArgs args) { - client.GuildCreated += async (_, args) => - { - var embed = new DiscordEmbedBuilder() - .WithTitle($"New guild added: {args.Guild.Name}") - .AddField("Id:", args.Guild.Id.ToString()) - .AddField("Owner:", args.Guild.Owner.Username + "#" + args.Guild.Owner.Discriminator) - .AddField("Membercount:", args.Guild.MemberCount.ToString()) - .AddField("Added:", Formatter.Timestamp(DateTimeOffset.Now)) - .AddField("Created:", Formatter.Timestamp(args.Guild.CreationTimestamp)) - .WithColor(DiscordColor.Green); - await loggingService.LogToWebhook(new DiscordMessageBuilder().WithEmbed(embed)); - }; + var embed = new DiscordEmbedBuilder() + .WithTitle($"New guild added: {args.Guild.Name}") + .AddField("Id:", args.Guild.Id.ToString()) + .AddField("Owner:", args.Guild.Owner.Username + "#" + args.Guild.Owner.Discriminator) + .AddField("Membercount:", args.Guild.MemberCount.ToString()) + .AddField("Added:", Formatter.Timestamp(DateTimeOffset.Now)) + .AddField("Created:", Formatter.Timestamp(args.Guild.CreationTimestamp)) + .WithColor(DiscordColor.Green); + + await ModularDiscordBot.Services + .GetRequiredService() + .LogToWebhook(new DiscordMessageBuilder().AddEmbed(embed)); + } - client.GuildDeleted += async (_, args) => - { - var embed = new DiscordEmbedBuilder() - .WithTitle($"Guild removed: {args.Guild.Name}") - .AddField("Id:", args.Guild.Id.ToString()) - .AddField("Owner:", args.Guild.Owner.Username + "#" + args.Guild.Owner.Discriminator) - .AddField("Membercount:", args.Guild.MemberCount.ToString()) - .AddField("Removed:", Formatter.Timestamp(DateTimeOffset.Now)) - .AddField("Created:", Formatter.Timestamp(args.Guild.CreationTimestamp)) - .WithColor(DiscordColor.Red); - await loggingService.LogToWebhook(new DiscordMessageBuilder().WithEmbed(embed)); - }; + public static async Task OnGuildDeleted(DiscordClient sender, GuildDeleteEventArgs args) + { + var embed = new DiscordEmbedBuilder() + .WithTitle($"Guild removed: {args.Guild.Name}") + .AddField("Id:", args.Guild.Id.ToString()) + .AddField("Owner:", args.Guild.Owner.Username + "#" + args.Guild.Owner.Discriminator) + .AddField("Membercount:", args.Guild.MemberCount.ToString()) + .AddField("Removed:", Formatter.Timestamp(DateTimeOffset.Now)) + .AddField("Created:", Formatter.Timestamp(args.Guild.CreationTimestamp)) + .WithColor(DiscordColor.Red); + + await ModularDiscordBot.Services + .GetRequiredService() + .LogToWebhook(new DiscordMessageBuilder().AddEmbed(embed)); } + } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/EventListeners/EventListener.GuildDownload.cs b/ModularAssistentForDiscordServer/EventListeners/EventListener.GuildDownload.cs index 49102c5..ca7b162 100644 --- a/ModularAssistentForDiscordServer/EventListeners/EventListener.GuildDownload.cs +++ b/ModularAssistentForDiscordServer/EventListeners/EventListener.GuildDownload.cs @@ -13,27 +13,28 @@ // limitations under the License. using DSharpPlus; +using DSharpPlus.Entities; using DSharpPlus.EventArgs; using MADS.Entities; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace MADS.EventListeners; internal static partial class EventListener { - public static void GuildDownload(DiscordClient client, IDbContextFactory contextFactory) + public static void GuildDownload(DiscordClient client) { - client.GuildDownloadCompleted += async (_, args) => { await UpdateDb(args, contextFactory); }; + client.GuildDownloadCompleted += async (_, args) => { await UpdateDb(args); }; } - private static async Task UpdateDb - ( - GuildDownloadCompletedEventArgs args, - IDbContextFactory dbFactory - ) + private static async Task UpdateDb( GuildDownloadCompletedEventArgs args) { - await UpdateGuilds(args, dbFactory); - await UpdateUsersDb(args, dbFactory); + var dbFactory = ModularDiscordBot.Services.GetRequiredService>(); + var updateGuilds = UpdateGuilds(args, dbFactory); + var updateUsers = UpdateUsersDb(args, dbFactory); + + await Task.WhenAll(updateGuilds, updateUsers); } private static async Task UpdateUsersDb @@ -43,27 +44,39 @@ IDbContextFactory dbFactory ) { await using var db = await dbFactory.CreateDbContextAsync(); + var oldDbUsers = db.Users.Select(x => x.Id).ToList(); + var newUserIds = args.Guilds.Values .SelectMany(x => x.Members.Values) .Select(x => x.Id) .Distinct() - .Except(db.Users.Select(y => y.Id)); + .Except(oldDbUsers) + .ToArray(); - var newUserDbEntities = newUserIds.Select(userId => + DiscordMember[] users = args.Guilds.Values + .SelectMany(x => x.Members.Values) + .ToArray(); + + List userDbEntities = new(newUserIds.Length); + + foreach (ulong userId in newUserIds) { - var user = args.Guilds.Values - .SelectMany(x => x.Members.Values) - .First(x => x.Id == userId); + var user = users.FirstOrDefault(x => x.Id == userId); + + if (user is null) continue; - return new UserDbEntity + var dbEntity = new UserDbEntity { Id = user.Id, Username = user.Username, - Discriminator = Convert.ToInt32(user.Discriminator) + Discriminator = Convert.ToInt32(user.Discriminator), + PreferedLanguage = "en-US" }; - }); - - await db.Users.AddRangeAsync(newUserDbEntities); + + userDbEntities.Add(dbEntity); + } + + await db.Users.AddRangeAsync(userDbEntities); await db.SaveChangesAsync(); } @@ -89,7 +102,8 @@ IDbContextFactory dbFactory Prefix = "", StarboardActive = false } - }); + }) + .ToArray(); await db.Guilds.AddRangeAsync(newGuildEntities); await db.SaveChangesAsync(); diff --git a/ModularAssistentForDiscordServer/EventListeners/EventListener.RoleSelection.cs b/ModularAssistentForDiscordServer/EventListeners/EventListener.RoleSelection.cs index 22f7bb2..0229f3e 100644 --- a/ModularAssistentForDiscordServer/EventListeners/EventListener.RoleSelection.cs +++ b/ModularAssistentForDiscordServer/EventListeners/EventListener.RoleSelection.cs @@ -14,7 +14,6 @@ using DSharpPlus; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; namespace MADS.EventListeners; @@ -27,10 +26,7 @@ internal static void EnableRoleSelectionListener(DiscordClient client) if (e.Guild is null) return; if (e.Id != "RoleSelection:" + e.Guild.Id) return; - - client.Logger.LogDebug(new EventId(420, "MADS"), $"Roleselection triggered: [{e.Id}]"); - - + //TODO Test if "await e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);" is possible await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("Roles granted/revoked").AsEphemeral()); diff --git a/ModularAssistentForDiscordServer/EventListeners/EventListener.Zombied.cs b/ModularAssistentForDiscordServer/EventListeners/EventListener.Zombied.cs index 867a00d..e1302b9 100644 --- a/ModularAssistentForDiscordServer/EventListeners/EventListener.Zombied.cs +++ b/ModularAssistentForDiscordServer/EventListeners/EventListener.Zombied.cs @@ -14,6 +14,7 @@ using DSharpPlus; using DSharpPlus.EventArgs; +using Serilog; namespace MADS.EventListeners; @@ -23,4 +24,10 @@ internal static async Task OnZombied(DiscordClient sender, ZombiedEventArgs e) { await sender.ReconnectAsync(true); } + + internal static Task OnGuildAvailable(DiscordClient sender, GuildCreateEventArgs e) + { + Log.Warning("Guild available: {GuildName}", e.Guild.Name); + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Extensions/DataProvider.cs b/ModularAssistentForDiscordServer/Extensions/DataProvider.cs index 8062360..1ea25ff 100644 --- a/ModularAssistentForDiscordServer/Extensions/DataProvider.cs +++ b/ModularAssistentForDiscordServer/Extensions/DataProvider.cs @@ -12,49 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -using MADS.JsonModel; +using MADS.Entities; +using Microsoft.Extensions.Logging; +// ReSharper disable NotResolvedInText namespace MADS.Extensions; internal static class DataProvider { - public static ConfigJson GetConfig() + public static MadsConfig GetConfig() { - return JsonProvider.ReadFile(GetPath("config.json")); + var config = new MadsConfig + { + Token = Environment.GetEnvironmentVariable("MADS_DISCORD_TOKEN") ?? throw new ArgumentNullException("Missing env var MADS_DISCORD_TOKEN"), + Prefix = Environment.GetEnvironmentVariable("MADS_DEFAULT_PREFIX") ?? throw new ArgumentNullException("Missing env var MADS_DEFAULT_PREFIX"), + LogLevel = Enum.Parse(Environment.GetEnvironmentVariable("MADS_MINIMUM_LOG_LEVEL") ?? throw new ArgumentNullException("Missing env var MADS_MINIMUM_LOG_LEVEL")), + ConnectionString = Environment.GetEnvironmentVariable("MADS_DATABASE_CONNECTION_STRING") ?? throw new ArgumentNullException("Missing env var MADS_DATABASE_CONNECTION_STRING"), + ConnectionStringQuartz = Environment.GetEnvironmentVariable("MADS_DATABASE_CONNECTION_STRING_QUARTZ") ?? throw new ArgumentNullException("Missing env var MADS_DATABASE_CONNECTION_STRING_QUARTZ"), + DiscordWebhook = Environment.GetEnvironmentVariable("MADS_DISCORD_WEBHOOK") ?? throw new ArgumentNullException("Missing env var MADS_DISCORD_WEBHOOK"), + DmProxyChannelId = ulong.Parse(Environment.GetEnvironmentVariable("MADS_DM_PROXY_CHANNEL_ID") ?? throw new ArgumentNullException("Missing env var MADS_DM_PROXY_CHANNEL_ID")), + DeeplApiKey = Environment.GetEnvironmentVariable("MADS_DEEPL_API_KEY") ?? throw new ArgumentNullException("Missing env var MADS_DEEPL_API_KEY") + }; + + return config; } - - public static TJsonModel GetJson(string path) - { - return JsonProvider.ReadFile(GetPath(path)); - } - - public static void SetConfig(ConfigJson configJson) - { - JsonProvider.ParseJson(GetPath("config.json"), configJson); - } - public static string GetPath(params string[] path) { return Path.GetFullPath(Path.Combine(path)); } - - public static bool TryGetOAuthTokenByUser(ulong userId, out string token) - { - var file = JsonProvider.ReadFile(GetPath("oauthtoken.json")); - - return file.Token.TryGetValue(userId, out token); - } - - public static bool TryInsertUserToken(ulong userId, string token) - { - var file = JsonProvider.ReadFile(GetPath("oauthtoken.json")); - - var success = file.Token.TryAdd(userId, token); - - file.TokenCount++; - - JsonProvider.ParseJson(GetPath("config.json"), file); - - return success; - } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Extensions/ExtensionMethods.cs b/ModularAssistentForDiscordServer/Extensions/ExtensionMethods.cs index bae5295..7f1b1f9 100644 --- a/ModularAssistentForDiscordServer/Extensions/ExtensionMethods.cs +++ b/ModularAssistentForDiscordServer/Extensions/ExtensionMethods.cs @@ -15,7 +15,6 @@ using DSharpPlus; using DSharpPlus.Entities; using MADS.Entities; -using MADS.JsonModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -26,7 +25,7 @@ namespace MADS.Extensions; public static class ExtensionMethods { public static IServiceCollection AddDbFactoryDebugOrRelease(this IServiceCollection serviceCollection, - ConfigJson config) + MadsConfig config) { var logger = new LoggerFactory().AddSerilog(new LoggerConfiguration() .WriteTo.Console() @@ -38,6 +37,7 @@ public static IServiceCollection AddDbFactoryDebugOrRelease(this IServiceCollect { options.UseMySql(config.ConnectionString, ServerVersion.AutoDetect(config.ConnectionString)); options.UseLoggerFactory(logger); + options.EnableDetailedErrors(); } ); @@ -45,7 +45,7 @@ public static IServiceCollection AddDbFactoryDebugOrRelease(this IServiceCollect } public static IServiceCollection AddDiscordRestClient(this IServiceCollection serviceCollection, - ConfigJson config) + MadsConfig config) { var discordRestConfig = new DiscordConfiguration { @@ -87,7 +87,7 @@ public static async Task GetMessageAsync(this ReminderDbE .WithColor(DiscordColor.Green); message.WithContent($"<@!{user.Id}>"); - message.WithEmbed(embed).WithAllowedMention(userMention); + message.AddEmbed(embed).WithAllowedMention(userMention); return message; } diff --git a/ModularAssistentForDiscordServer/Extensions/JsonProvider.cs b/ModularAssistentForDiscordServer/Extensions/JsonProvider.cs deleted file mode 100644 index 423c48a..0000000 --- a/ModularAssistentForDiscordServer/Extensions/JsonProvider.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2023 Plerx2493 -// -// Licensed under the Apache License, Version 2.0 (the "License") -// You may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Newtonsoft.Json; - -namespace MADS.Extensions; - -internal static class JsonProvider -{ - public static TJsonModel ReadFile(string path) - { - using var streamReader = File.OpenText(path); - return JsonConvert.DeserializeObject(streamReader.ReadToEnd()); - } - - public static TJsonModel[] ReadFileToArray(string path) - { - using var streamReader = File.OpenText(path); - return JsonConvert.DeserializeObject(streamReader.ReadToEnd()); - } - - public static List ReadFileToList(string path) - { - using var streamReader = File.OpenText(path); - return JsonConvert.DeserializeObject>(streamReader.ReadToEnd()); - } - - public static void ParseJsonArray(string path, TJsonModel[] model) - { - var parsedJson = JsonConvert.SerializeObject(model, Formatting.Indented); - File.WriteAllText(path, parsedJson); - } - - public static void ParseJson(string path, TJsonModel model) - { - var parsedJson = JsonConvert.SerializeObject(model, Formatting.Indented); - File.WriteAllText(path, parsedJson); - } -} \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Extensions/MadsBaseApplicationCommand.cs b/ModularAssistentForDiscordServer/Extensions/MadsBaseApplicationCommand.cs index 773b908..ae1392f 100644 --- a/ModularAssistentForDiscordServer/Extensions/MadsBaseApplicationCommand.cs +++ b/ModularAssistentForDiscordServer/Extensions/MadsBaseApplicationCommand.cs @@ -23,7 +23,7 @@ namespace MADS.Extensions; [SlashModuleLifespan(SlashModuleLifespan.Transient)] public class MadsBaseApplicationCommand : ApplicationCommandModule { - private InteractionContext _ctx; + private InteractionContext? _ctx; private readonly Stopwatch _executionTimer = new(); public DiscordClientService CommandService => ModularDiscordBot.Services.GetRequiredService(); @@ -42,7 +42,7 @@ public override Task AfterSlashExecutionAsync(InteractionContext ctx) var _ = CommandService.Logging.LogCommandExecutionAsync(ctx, _executionTimer.Elapsed); - return Task.FromResult(true); + return Task.CompletedTask; } public override Task BeforeContextMenuExecutionAsync(ContextMenuContext ctx) @@ -81,7 +81,7 @@ protected async Task CreateResponse_Error(string message, bool isEphemeral = fal .AddEmbed(embed) .AsEphemeral(isEphemeral); - await _ctx.CreateResponseAsync(response); + await _ctx!.CreateResponseAsync(response); } protected async Task CreateResponse_Success(string message, bool isEphemeral = false) @@ -95,7 +95,7 @@ protected async Task CreateResponse_Success(string message, bool isEphemeral = f .AddEmbed(embed) .AsEphemeral(isEphemeral); - await _ctx.CreateResponseAsync(response); + await _ctx!.CreateResponseAsync(response); } protected async Task EditResponse_Error(string message) @@ -108,7 +108,7 @@ protected async Task EditResponse_Error(string message) var response = new DiscordWebhookBuilder() .AddEmbed(embed); - await _ctx.EditResponseAsync(response); + await _ctx!.EditResponseAsync(response); } protected async Task EditResponse_Success(string message) @@ -121,6 +121,6 @@ protected async Task EditResponse_Success(string message) var response = new DiscordWebhookBuilder() .AddEmbed(embed); - await _ctx.EditResponseAsync(response); + await _ctx!.EditResponseAsync(response); } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Extensions/SlashCommandsAttributes.cs b/ModularAssistentForDiscordServer/Extensions/SlashCommandsAttributes.cs index 9c4ea09..750a1f4 100644 --- a/ModularAssistentForDiscordServer/Extensions/SlashCommandsAttributes.cs +++ b/ModularAssistentForDiscordServer/Extensions/SlashCommandsAttributes.cs @@ -23,8 +23,9 @@ public override Task ExecuteChecksAsync(InteractionContext ctx) var app = ctx.Client.CurrentApplication; var me = ctx.Client.CurrentUser; - return app != null - ? Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + return app is not null + ? Task.FromResult(app.Owners?.Any(x => x.Id == ctx.User.Id) ?? false) : Task.FromResult(ctx.User.Id == me.Id); } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/JsonModel/OAuthTokenJson.cs b/ModularAssistentForDiscordServer/JsonModel/OAuthTokenJson.cs deleted file mode 100644 index dea9780..0000000 --- a/ModularAssistentForDiscordServer/JsonModel/OAuthTokenJson.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2023 Plerx2493 -// -// Licensed under the Apache License, Version 2.0 (the "License") -// You may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Newtonsoft.Json; - -namespace MADS.JsonModel; - -public class OAuthTokenJson -{ - [JsonProperty("userTokenDict")] - public Dictionary Token { get; set; } - - [JsonProperty("TokenCount")] - public int TokenCount { get; set; } - - [JsonProperty("ApplicationId")] - public ulong ApplicationId { get; set; } -} \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Migrations/20231128160315_TranslationUserInfo.Designer.cs b/ModularAssistentForDiscordServer/Migrations/20231128160315_TranslationUserInfo.Designer.cs new file mode 100644 index 0000000..08272bc --- /dev/null +++ b/ModularAssistentForDiscordServer/Migrations/20231128160315_TranslationUserInfo.Designer.cs @@ -0,0 +1,398 @@ +// +using System; +using MADS.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace MADS.Migrations +{ + [DbContext(typeof(MadsContext))] + [Migration("20231128160315_TranslationUserInfo")] + partial class TranslationUserInfo + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("MADS.Entities.GuildConfigDbEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("id"); + + b.Property("DiscordGuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("discordId"); + + b.Property("Prefix") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("prefix"); + + b.Property("StarboardActive") + .HasColumnType("tinyint(1)") + .HasColumnName("starboardEnabled"); + + b.Property("StarboardChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("starboardChannel"); + + b.Property("StarboardEmojiId") + .HasColumnType("bigint unsigned") + .HasColumnName("starboardEmojiId"); + + b.Property("StarboardEmojiName") + .HasColumnType("longtext") + .HasColumnName("starboardEmojiName"); + + b.Property("StarboardThreshold") + .HasColumnType("int") + .HasColumnName("starboardThreshold"); + + b.HasKey("Id"); + + b.ToTable("Configs"); + }); + + modelBuilder.Entity("MADS.Entities.GuildDbEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("id"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned") + .HasColumnName("discordId"); + + b.HasKey("Id"); + + b.ToTable("Guilds"); + }); + + modelBuilder.Entity("MADS.Entities.IncidentDbEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("id"); + + b.Property("CreationTimeStamp") + .HasColumnType("datetime(6)"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guild_id"); + + b.Property("ModeratorId") + .HasColumnType("bigint unsigned") + .HasColumnName("moderator_id"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("reason"); + + b.Property("TargetId") + .HasColumnType("bigint unsigned") + .HasColumnName("target_id"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.HasIndex("TargetId"); + + b.ToTable("Incidents"); + }); + + modelBuilder.Entity("MADS.Entities.QuoteDbEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("id"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)") + .HasColumnName("content"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnName("timestamp"); + + b.Property("DiscordGuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("discordGuildId"); + + b.Property("QuotedUserId") + .HasColumnType("bigint unsigned") + .HasColumnName("quotedUserId"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("UserId"); + + b.HasKey("Id"); + + b.HasIndex("DiscordGuildId"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("MADS.Entities.ReminderDbEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelId"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("creationTime"); + + b.Property("ExecutionTime") + .HasColumnType("datetime(6)") + .HasColumnName("executionTime"); + + b.Property("IsPrivate") + .HasColumnType("tinyint(1)") + .HasColumnName("isPrivate"); + + b.Property("MentionedChannel") + .HasColumnType("bigint unsigned") + .HasColumnName("mentionedChannel"); + + b.Property("MentionedMessage") + .HasColumnType("bigint unsigned") + .HasColumnName("MentionedMessage"); + + b.Property("MessageId") + .HasColumnType("bigint unsigned") + .HasColumnName("messageId"); + + b.Property("ReminderText") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("reminderText"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("MADS.Entities.StarboardMessageDbEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("id"); + + b.Property("DiscordChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("discordChannelId"); + + b.Property("DiscordGuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("discordGuildId"); + + b.Property("DiscordMessageId") + .HasColumnType("bigint unsigned") + .HasColumnName("discordMessageId"); + + b.Property("StarboardChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("starboardChannelId"); + + b.Property("StarboardGuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("starboardGuildId"); + + b.Property("StarboardMessageId") + .HasColumnType("bigint unsigned") + .HasColumnName("starboardMessageId"); + + b.Property("Stars") + .HasColumnType("int") + .HasColumnName("starCount"); + + b.HasKey("Id"); + + b.ToTable("Starboard"); + }); + + modelBuilder.Entity("MADS.Entities.UserDbEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("id"); + + b.Property("Discriminator") + .HasColumnType("int") + .HasColumnName("discriminator"); + + b.Property("PreferedLanguage") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("prefered_language"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("MADS.Entities.VoiceAlert", b => + { + b.Property("AlertId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channel_id"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guild_id"); + + b.Property("IsRepeatable") + .HasColumnType("tinyint(1)") + .HasColumnName("is_repeatable"); + + b.Property("LastAlert") + .HasColumnType("datetime(6)") + .HasColumnName("last_alert"); + + b.Property("MinTimeBetweenAlerts") + .HasColumnType("time(6)") + .HasColumnName("time_between"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("user_id"); + + b.HasKey("AlertId"); + + b.HasIndex("UserId"); + + b.ToTable("VoiceAlerts"); + }); + + modelBuilder.Entity("MADS.Entities.GuildDbEntity", b => + { + b.HasOne("MADS.Entities.GuildConfigDbEntity", "Settings") + .WithOne("Guild") + .HasForeignKey("MADS.Entities.GuildDbEntity", "DiscordId") + .HasPrincipalKey("MADS.Entities.GuildConfigDbEntity", "DiscordGuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Settings"); + }); + + modelBuilder.Entity("MADS.Entities.IncidentDbEntity", b => + { + b.HasOne("MADS.Entities.GuildDbEntity", "Guild") + .WithMany("Incidents") + .HasForeignKey("GuildId") + .HasPrincipalKey("DiscordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MADS.Entities.UserDbEntity", "TargetUser") + .WithMany("Incidents") + .HasForeignKey("TargetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Guild"); + + b.Navigation("TargetUser"); + }); + + modelBuilder.Entity("MADS.Entities.QuoteDbEntity", b => + { + b.HasOne("MADS.Entities.GuildDbEntity", "Guild") + .WithMany("Quotes") + .HasForeignKey("DiscordGuildId") + .HasPrincipalKey("DiscordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Guild"); + }); + + modelBuilder.Entity("MADS.Entities.ReminderDbEntity", b => + { + b.HasOne("MADS.Entities.UserDbEntity", "User") + .WithMany("Reminders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MADS.Entities.VoiceAlert", b => + { + b.HasOne("MADS.Entities.UserDbEntity", "User") + .WithMany("VoiceAlerts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MADS.Entities.GuildConfigDbEntity", b => + { + b.Navigation("Guild"); + }); + + modelBuilder.Entity("MADS.Entities.GuildDbEntity", b => + { + b.Navigation("Incidents"); + + b.Navigation("Quotes"); + }); + + modelBuilder.Entity("MADS.Entities.UserDbEntity", b => + { + b.Navigation("Incidents"); + + b.Navigation("Reminders"); + + b.Navigation("VoiceAlerts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ModularAssistentForDiscordServer/Migrations/20231128160315_TranslationUserInfo.cs b/ModularAssistentForDiscordServer/Migrations/20231128160315_TranslationUserInfo.cs new file mode 100644 index 0000000..9297c38 --- /dev/null +++ b/ModularAssistentForDiscordServer/Migrations/20231128160315_TranslationUserInfo.cs @@ -0,0 +1,173 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MADS.Migrations +{ + /// + public partial class TranslationUserInfo : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + table: "Users", + keyColumn: "username", + keyValue: null, + column: "username", + value: ""); + + migrationBuilder.AlterColumn( + name: "username", + table: "Users", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "prefered_language", + table: "Users", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.UpdateData( + table: "Reminders", + keyColumn: "reminderText", + keyValue: null, + column: "reminderText", + value: ""); + + migrationBuilder.AlterColumn( + name: "reminderText", + table: "Reminders", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.UpdateData( + table: "Quotes", + keyColumn: "content", + keyValue: null, + column: "content", + value: ""); + + migrationBuilder.AlterColumn( + name: "content", + table: "Quotes", + type: "varchar(1000)", + maxLength: 1000, + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(1000)", + oldMaxLength: 1000, + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.UpdateData( + table: "Incidents", + keyColumn: "reason", + keyValue: null, + column: "reason", + value: ""); + + migrationBuilder.AlterColumn( + name: "reason", + table: "Incidents", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.UpdateData( + table: "Configs", + keyColumn: "prefix", + keyValue: null, + column: "prefix", + value: ""); + + migrationBuilder.AlterColumn( + name: "prefix", + table: "Configs", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "prefered_language", + table: "Users"); + + migrationBuilder.AlterColumn( + name: "username", + table: "Users", + type: "longtext", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "reminderText", + table: "Reminders", + type: "longtext", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "content", + table: "Quotes", + type: "varchar(1000)", + maxLength: 1000, + nullable: true, + oldClrType: typeof(string), + oldType: "varchar(1000)", + oldMaxLength: 1000) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "reason", + table: "Incidents", + type: "longtext", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "prefix", + table: "Configs", + type: "longtext", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + } + } +} diff --git a/ModularAssistentForDiscordServer/Migrations/MadsContextModelSnapshot.cs b/ModularAssistentForDiscordServer/Migrations/MadsContextModelSnapshot.cs index 357fc44..8f49ecd 100644 --- a/ModularAssistentForDiscordServer/Migrations/MadsContextModelSnapshot.cs +++ b/ModularAssistentForDiscordServer/Migrations/MadsContextModelSnapshot.cs @@ -16,7 +16,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.0-preview.2.23128.3") + .HasAnnotation("ProductVersion", "8.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("MADS.Entities.GuildConfigDbEntity", b => @@ -94,6 +94,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnName("moderator_id"); b.Property("Reason") + .IsRequired() .HasColumnType("longtext") .HasColumnName("reason"); @@ -118,6 +119,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnName("id"); b.Property("Content") + .IsRequired() .HasMaxLength(1000) .HasColumnType("varchar(1000)") .HasColumnName("content"); @@ -181,6 +183,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnName("messageId"); b.Property("ReminderText") + .IsRequired() .HasColumnType("longtext") .HasColumnName("reminderText"); @@ -246,7 +249,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("int") .HasColumnName("discriminator"); + b.Property("PreferedLanguage") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("prefered_language"); + b.Property("Username") + .IsRequired() .HasColumnType("longtext") .HasColumnName("username"); diff --git a/ModularAssistentForDiscordServer/ModularAssistentForDiscordServer.csproj b/ModularAssistentForDiscordServer/ModularAssistentForDiscordServer.csproj index d18c801..ed2dd22 100644 --- a/ModularAssistentForDiscordServer/ModularAssistentForDiscordServer.csproj +++ b/ModularAssistentForDiscordServer/ModularAssistentForDiscordServer.csproj @@ -1,12 +1,12 @@  Exe - net7.0 + net8.0 enable - disable + enable MADS.MainProgram MADS - 11 + latest @@ -22,31 +22,34 @@ + + + + + + + - + - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/ModularAssistentForDiscordServer/ModularDiscordBot.cs b/ModularAssistentForDiscordServer/ModularDiscordBot.cs index 8f05a8e..944e87a 100644 --- a/ModularAssistentForDiscordServer/ModularDiscordBot.cs +++ b/ModularAssistentForDiscordServer/ModularDiscordBot.cs @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +using DeepL; using DSharpPlus; +using MADS.Entities; using MADS.Extensions; -using MADS.JsonModel; using MADS.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -30,14 +31,9 @@ public class ModularDiscordBot public static IServiceProvider Services; public static DateTimeOffset StartTime = DateTimeOffset.Now; public static ILogger Logger; - private readonly ConfigJson _config; + private readonly MadsConfig _config = DataProvider.GetConfig(); - public ModularDiscordBot() - { - _config = DataProvider.GetConfig(); - } - - public async Task RunAsync(CancellationToken token) + public async Task RunAsync(CancellationToken token) { await Host.CreateDefaultBuilder() .UseSerilog() @@ -82,18 +78,21 @@ await Host.CreateDefaultBuilder() .AddSingleton() .AddHostedService(s => s.GetRequiredService()) .AddSingleton(s => - new TokenListener("51151", s.GetRequiredService(), "/api/v1/mads/token/")) + new TokenListener("51151", "/api/v1/mads/token/")) .AddHostedService(s => s.GetRequiredService()) .AddSingleton() .AddHostedService(s => s.GetRequiredService()) .AddSingleton() - .AddHostedService(s => s.GetRequiredService()); + .AddHostedService(s => s.GetRequiredService()) + .AddSingleton(new Translator(_config.DeeplApiKey ?? "")) + .AddSingleton() + .AddSingleton() + .AddHttpClient(); Services = services.BuildServiceProvider(); Logger = Services.GetRequiredService>(); } ) - .RunConsoleAsync(); - return true; + .RunConsoleAsync(cancellationToken: token); } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Program.cs b/ModularAssistentForDiscordServer/Program.cs index a73507a..b50381c 100644 --- a/ModularAssistentForDiscordServer/Program.cs +++ b/ModularAssistentForDiscordServer/Program.cs @@ -17,11 +17,9 @@ using DSharpPlus.Entities; using MADS.Entities; using MADS.Extensions; -using MADS.JsonModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Serilog; using Serilog.Events; @@ -29,28 +27,22 @@ namespace MADS; internal static class MainProgram { - public async static Task Main() + public static async Task Main() { Log.Logger = new LoggerConfiguration() .WriteTo.Console() + .MinimumLevel.Verbose() .MinimumLevel.Override("Quartz", LogEventLevel.Warning) .CreateLogger(); //Create cancellationToken and hook the cancelKey var cancellationSource = new CancellationTokenSource(); - Console.CancelKeyPress += (_, args) => + Console.CancelKeyPress += async (_, args) => { args.Cancel = true; - cancellationSource.Cancel(); + await cancellationSource.CancelAsync(); }; - //Validate the config.json and create a new one when its not present/valid - if (!ValidateConfig()) - { - CreateConfig(); - return; - } - //retrieves the config.json var config = DataProvider.GetConfig(); @@ -69,55 +61,14 @@ public async static Task Main() catch (Exception e) { if (e is TaskCanceledException) return; - + Log.Error(e, "An uncaught exception occurred"); var _ = LogToWebhookAsync(e); + Environment.Exit(69); } cancellationSource.Token.WaitHandle.WaitOne(); } - private static bool ValidateConfig() - { - var configPath = DataProvider.GetPath("config.json"); - - if (!File.Exists(configPath)) return false; - - var lConfig = DataProvider.GetConfig(); - - if (lConfig.Token is null or "" or "") return false; - - if (lConfig.Prefix is null or "") lConfig.Prefix = "!"; - - if (lConfig.DiscordWebhook is null or "") return false; - lConfig.DmProxyChannelId ??= 0; - - DataProvider.SetConfig(lConfig); - - return true; - } - - private static void CreateConfig() - { - var configPath = DataProvider.GetPath("config.json"); - - var fileStream = File.Create(configPath); - fileStream.Close(); - - ConfigJson newConfig = new() - { - Token = "", - Prefix = "!", - LogLevel = LogLevel.Debug, - DmProxyChannelId = 0 - }; - JsonProvider.ParseJson(configPath, newConfig); - - Console.WriteLine("Please insert your token in the config file and restart"); - Console.WriteLine("Filepath: " + configPath); - Console.WriteLine("Press key to continue"); - Console.Read(); - } - public static async Task LogToWebhookAsync(Exception e) { //retrieves the config.json diff --git a/ModularAssistentForDiscordServer/Services/DiscordClientService.cs b/ModularAssistentForDiscordServer/Services/DiscordClientService.cs index 8ba731d..3f708cd 100644 --- a/ModularAssistentForDiscordServer/Services/DiscordClientService.cs +++ b/ModularAssistentForDiscordServer/Services/DiscordClientService.cs @@ -16,7 +16,6 @@ using System.Reflection; using DSharpPlus; using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Executors; using DSharpPlus.Entities; using DSharpPlus.EventArgs; using DSharpPlus.Interactivity; @@ -25,7 +24,6 @@ using DSharpPlus.SlashCommands; using MADS.Entities; using MADS.EventListeners; -using MADS.JsonModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -47,7 +45,7 @@ public class DiscordClientService : IHostedService public DiscordClientService ( - ConfigJson pConfig, + MadsConfig pConfig, IDbContextFactory dbDbContextFactory ) { @@ -65,13 +63,14 @@ IDbContextFactory dbDbContextFactory AutoReconnect = true, MinimumLogLevel = config.LogLevel, LoggerFactory = new LoggerFactory().AddSerilog(), - Intents = DiscordIntents.All + Intents = DiscordIntents.All ^ DiscordIntents.GuildPresences }; DiscordClient = new DiscordClient(discordConfig); - - EventListener.GuildDownload(DiscordClient, _dbContextFactory); - EventListener.AddGuildNotifier(DiscordClient, Logging); + EventListener.GuildDownload(DiscordClient); + DiscordClient.GuildCreated += EventListener.OnGuildCreated; + DiscordClient.GuildDeleted += EventListener.OnGuildDeleted; + DiscordClient.GuildAvailable += EventListener.OnGuildAvailable; var asm = Assembly.GetExecutingAssembly(); @@ -81,8 +80,7 @@ IDbContextFactory dbDbContextFactory CaseSensitive = false, DmHelp = false, EnableDms = true, - EnableMentionPrefix = true, - CommandExecutor = new ParallelQueuedCommandExecutor() + EnableMentionPrefix = true }; CommandsNext = DiscordClient.UseCommandsNext(cnextConfig); CommandsNext.RegisterCommands(asm); @@ -101,6 +99,7 @@ IDbContextFactory dbDbContextFactory SlashCommands.RegisterCommands(asm, 938120155974750288); #endif SlashCommands.SlashCommandErrored += EventListener.OnSlashCommandErrored; + SlashCommands.AutocompleteErrored += EventListener.OnAutocompleteError; //Custom buttons EventListener.EnableButtonListener(DiscordClient); @@ -129,9 +128,9 @@ public async Task StartAsync(CancellationToken cancellationToken) { _logger.Warning("DiscordClientService started"); //Update database to latest migration - using var context = await _dbContextFactory.CreateDbContextAsync(); - if ((await context.Database.GetPendingMigrationsAsync()).Any()) - await context.Database.MigrateAsync(); + await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if ((await context.Database.GetPendingMigrationsAsync(cancellationToken)).Any()) + await context.Database.MigrateAsync(cancellationToken); DiscordActivity act = new("Messing with code", ActivityType.Custom); diff --git a/ModularAssistentForDiscordServer/Services/LoggingService.cs b/ModularAssistentForDiscordServer/Services/LoggingService.cs index f36ce4f..6bbfae7 100644 --- a/ModularAssistentForDiscordServer/Services/LoggingService.cs +++ b/ModularAssistentForDiscordServer/Services/LoggingService.cs @@ -23,7 +23,6 @@ using MADS.Extensions; using Microsoft.Extensions.Logging; using Serilog; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace MADS.Services; @@ -35,7 +34,7 @@ public class LoggingService private readonly string _dirPath = DataProvider.GetPath("Logs"); private readonly string _logPath; private readonly DiscordClientService _modularDiscordBot; - private DiscordRestClient _discordRestClient; + private DiscordRestClient? _discordRestClient; private DiscordWebhookClient _discordWebhookClient = new(); private bool _isSetup; private List _ownerChannel = new(); @@ -103,8 +102,8 @@ private void AddRestClient() private async void AddOwnerChannels() { var application = _modularDiscordBot.DiscordClient.CurrentApplication; - var owners = application.Owners.ToArray(); - if (_ownerChannel.Count == owners.Length) return; + var owners = application.Owners?.ToArray(); + if (owners is null || _ownerChannel.Count == owners.Length) return; _ownerChannel = new List(); @@ -114,7 +113,7 @@ private async void AddOwnerChannels() try { - ownerChannel = await _discordRestClient.CreateDmAsync(owner.Id); + ownerChannel = await _discordRestClient!.CreateDmAsync(owner.Id); } catch (DiscordException) { @@ -167,6 +166,7 @@ private void SetupFeedback() string guildName; + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (e.Interaction.Guild is null) { guildName = "Dms"; @@ -180,7 +180,7 @@ private void SetupFeedback() { Title = "Feedback", Description = e.Values["feedback-text"], - Color = new Optional(new DiscordColor(0, 255, 194)), + Color = new DiscordColor(0, 255, 194), Timestamp = (DateTimeOffset) DateTime.Now, Footer = new DiscordEmbedBuilder.EmbedFooter { @@ -209,7 +209,7 @@ public async Task LogEvent(string message, string sender, LogLevel lvl) public async Task LogCommandExecutionAsync(CommandContext ctx, TimeSpan timespan) { await LogInfo( - $"[{ctx.User.Username}#{ctx.User.Discriminator} : {ctx.User.Id}] [{ctx.Command.Name}] {timespan.TotalMilliseconds} milliseconds to execute"); + $"[{ctx.User.Username}#{ctx.User.Discriminator} : {ctx.User.Id}] [{ctx.Command?.Name}] {timespan.TotalMilliseconds} milliseconds to execute"); } public async Task LogCommandExecutionAsync(InteractionContext ctx, TimeSpan timespan) @@ -237,7 +237,7 @@ public async Task> LogToOwner(string message, string sender { Title = logLevel.ToString(), Description = message, - Color = new Optional(new DiscordColor(0, 255, 194)), + Color = new DiscordColor(0, 255, 194), Timestamp = DateTime.Now, Footer = new DiscordEmbedBuilder.EmbedFooter { diff --git a/ModularAssistentForDiscordServer/Services/MessageSnipeService.cs b/ModularAssistentForDiscordServer/Services/MessageSnipeService.cs index e68a199..087ba1e 100644 --- a/ModularAssistentForDiscordServer/Services/MessageSnipeService.cs +++ b/ModularAssistentForDiscordServer/Services/MessageSnipeService.cs @@ -66,15 +66,14 @@ private Task MessageSniperDeleted MessageDeleteEventArgs e ) { - if (e.Message == null) return Task.CompletedTask; - if (e.Message.WebhookMessage) return Task.CompletedTask; + if (e.Message.WebhookMessage ?? false) return Task.CompletedTask; - if ((!string.IsNullOrEmpty(e.Message?.Content) || e.Message?.Attachments.Count > 0) && !e.Message.Author.IsBot) - { - AddMessage(e.Message); - _logger.Warning("Sniped!"); - _logger.Verbose("Message added to cache"); - } + if ((string.IsNullOrEmpty(e.Message.Content) && !(e.Message.Attachments.Count > 0)) || (e.Message.Author?.IsBot ?? false)) + return Task.CompletedTask; + + AddMessage(e.Message); + _logger.Warning("Sniped!"); + _logger.Verbose("Message added to cache"); return Task.CompletedTask; } @@ -85,11 +84,10 @@ private Task MessageSniperEdited MessageUpdateEventArgs e ) { - if (e.Message == null) return Task.CompletedTask; - if (e.Message.WebhookMessage) return Task.CompletedTask; + if (e.Message.WebhookMessage ?? false) return Task.CompletedTask; - if ((string.IsNullOrEmpty(e.MessageBefore?.Content) && !(e.MessageBefore?.Attachments.Count > 0)) - || e.Message.Author.IsBot) return Task.CompletedTask; + if ((string.IsNullOrEmpty(e.MessageBefore.Content) && !(e.MessageBefore.Attachments.Count > 0)) + || (e.Message.Author?.IsBot ?? false)) return Task.CompletedTask; AddEditedMessage(e.MessageBefore); _logger.Warning("Sniped!"); @@ -98,7 +96,7 @@ MessageUpdateEventArgs e return Task.CompletedTask; } - private static void PostEvictionCallback(object key, object value, EvictionReason reason, object state) + private static void PostEvictionCallback(object key, object? value, EvictionReason reason, object? state) { _logger.Verbose("MessageSniper: Message eviction - {Reason}", reason.Humanize()); } @@ -127,29 +125,23 @@ public void DeleteMessage(ulong channel) _memoryCache.Remove(id); } - public bool TryGetMessage(ulong channelId, out DiscordMessage message) + public bool TryGetMessage(ulong channelId, out DiscordMessage? message) { var id = CacheHelper.GetMessageSnipeKey(channelId); message = _memoryCache.Get(id); - if (message is not null) - { - _memoryCache.Remove(id); - return true; - } + if (message is null) return false; + _memoryCache.Remove(id); + return true; - return false; } - public bool TryGetEditedMessage(ulong channelId, out DiscordMessage message) + public bool TryGetEditedMessage(ulong channelId, out DiscordMessage? message) { var id = CacheHelper.GetMessageEditSnipeKey(channelId); message = _memoryCache.Get(id); - if (message is not null) - { - _memoryCache.Remove(id); - return true; - } + if (message is null) return false; + _memoryCache.Remove(id); + return true; - return false; } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Services/OAuthTokenListenerService.cs b/ModularAssistentForDiscordServer/Services/OAuthTokenListenerService.cs index 9a66b06..eb74fe6 100644 --- a/ModularAssistentForDiscordServer/Services/OAuthTokenListenerService.cs +++ b/ModularAssistentForDiscordServer/Services/OAuthTokenListenerService.cs @@ -14,11 +14,8 @@ using System.Net; using System.Text; -using DSharpPlus; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Serilog; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace MADS.Services; @@ -37,19 +34,17 @@ public class TokenListener : IDisposable, IHostedService """; - private static HttpListener _listener; - private static string _url; - private readonly DiscordClient _client; - private Thread _listenTask; + private HttpListener _listener; + private string _url; + private Thread? _listenTask; - private static Serilog.ILogger _logger = Log.ForContext(); + private static readonly ILogger Logger = Log.ForContext(); - public TokenListener(string port, DiscordClient client, string path = "/") + public TokenListener(string port, string path = "/") { _url = $"http://localhost:{port}{path}"; _listener = new HttpListener(); _listener.Prefixes.Add(_url); - _client = client; } public void Dispose() @@ -60,7 +55,7 @@ public void Dispose() public Task StartAsync(CancellationToken cancellationToken) { _listener.Start(); - _logger.Information("Listening for connections on {Url}", _url); + Logger.Information("Listening for connections on {Url}", _url); _listenTask = new Thread(() => _ = HandleIncomingConnections(cancellationToken)) { @@ -76,11 +71,11 @@ public Task StopAsync(CancellationToken cancellationToken) { _listener.Abort(); _listenTask.Interrupt(); - _logger.Information("Tokenlistener stopped"); + Logger.Information("Tokenlistener stopped"); return Task.CompletedTask; } - private static async Task HandleIncomingConnections(CancellationToken token) + private async Task HandleIncomingConnections(CancellationToken token) { while (!token.IsCancellationRequested) { diff --git a/ModularAssistentForDiscordServer/Services/ReminderService.cs b/ModularAssistentForDiscordServer/Services/ReminderService.cs index b7cba79..8f5b816 100644 --- a/ModularAssistentForDiscordServer/Services/ReminderService.cs +++ b/ModularAssistentForDiscordServer/Services/ReminderService.cs @@ -19,7 +19,6 @@ using MADS.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Quartz; using Serilog; using ILogger = Serilog.ILogger; diff --git a/ModularAssistentForDiscordServer/Services/StarboardService.cs b/ModularAssistentForDiscordServer/Services/StarboardService.cs index 7414828..d084462 100644 --- a/ModularAssistentForDiscordServer/Services/StarboardService.cs +++ b/ModularAssistentForDiscordServer/Services/StarboardService.cs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#nullable enable using System.Collections.Concurrent; using DSharpPlus; using DSharpPlus.Entities; @@ -33,7 +32,7 @@ public class StarboardService : IHostedService private readonly BlockingCollection _messageQueue; private bool _stopped; - private static ILogger _logger = Log.ForContext(); + private static readonly ILogger _logger = Log.ForContext(); public StarboardService(DiscordClientService client, IDbContextFactory dbFactory) { @@ -135,7 +134,8 @@ private void StartHandleQueue() private async Task HandleEvent(DiscordReactionUpdateEvent e) { - if (!e.Message.Channel.GuildId.HasValue) return; + if (e.Message.Channel is null) return; + if (!e.Message.Channel?.GuildId.HasValue ?? false) return; if (e.Type == DiscordReactionUpdateType.ReactionAdded) { @@ -145,7 +145,7 @@ private async Task HandleEvent(DiscordReactionUpdateEvent e) using MadsContext db = await _dbFactory.CreateDbContextAsync(); - var guildSettings = db.Configs.FirstOrDefault(x => x.DiscordGuildId == e.Message.Channel.GuildId); + var guildSettings = db.Configs.FirstOrDefault(x => x.DiscordGuildId == e.Message.Channel!.GuildId ); if (guildSettings is null) return; if (!guildSettings.StarboardActive) return; @@ -224,7 +224,7 @@ private async Task HandleEvent(DiscordReactionUpdateEvent e) { DiscordChannelId = e.Message.ChannelId, DiscordMessageId = e.Message.Id, - DiscordGuildId = e.Message.Channel.Guild.Id, + DiscordGuildId = e.Message.Channel!.Guild.Id, Stars = 1 }; isNew = true; @@ -338,7 +338,7 @@ DiscordEmoji emoji starDataOld.StarboardMessageId = starboardMessage.Id; starDataOld.StarboardChannelId = starboardMessage.ChannelId; - starDataOld.StarboardGuildId = starboardMessage.Channel.GuildId!.Value; + starDataOld.StarboardGuildId = starboardMessage.Channel!.GuildId!.Value; db.Update(starDataOld); await db.SaveChangesAsync(); @@ -365,7 +365,7 @@ DiscordEmoji emoji var embed = new DiscordEmbedBuilder() - .WithAuthor($"{message.Author.Username}#{message.Author.Discriminator}", + .WithAuthor($"{message.Author!.Username}#{message.Author.Discriminator}", iconUrl: string.IsNullOrEmpty(message.Author.AvatarHash) ? message.Author.DefaultAvatarUrl : message.Author.AvatarUrl) @@ -374,13 +374,13 @@ DiscordEmoji emoji .WithTimestamp(message.Id); var imageAttachments = message.Attachments.Where( - x => x.Url.ToLower().EndsWith(".jpg") || + x => x.Url!.ToLower().EndsWith(".jpg") || x.Url.ToLower().EndsWith(".png") || x.Url.ToLower().EndsWith(".jpeg") || x.Url.ToLower().EndsWith(".gif")) .ToList(); - if (imageAttachments.Any()) embed.WithImageUrl(imageAttachments.First().Url); + if (imageAttachments.Any()) embed.WithImageUrl(imageAttachments.First().Url!); var emotename = emoji.GetDiscordName().Replace(":", ""); @@ -391,12 +391,12 @@ DiscordEmoji emoji var refContent = message.ReferencedMessage.Content.Truncate(200, "...").Replace(")[", "​)[") + " "; embed.Description += - $"\n\n**➥** {message.ReferencedMessage.Author.Mention}: {refContent} {(message.ReferencedMessage.Attachments.Any() ? $"_<{message.ReferencedMessage.Attachments.Count} file(s)>_" : "")}"; + $"\n\n**➥** {message.ReferencedMessage.Author!.Mention}: {refContent} {(message.ReferencedMessage.Attachments.Any() ? $"_<{message.ReferencedMessage.Attachments.Count} file(s)>_" : "")}"; } var messageBuilder = new DiscordMessageBuilder() .AddEmbed(embed) - .WithContent($"{emoji} {starData.Stars} {emotename} in {message.Channel.Mention}"); + .WithContent($"{emoji} {starData.Stars} {emotename} in {message.Channel!.Mention}"); messageBuilder.AddComponents(new DiscordLinkButtonComponent(message.JumpLink.ToString(), "Go to message")); diff --git a/ModularAssistentForDiscordServer/Services/TranslateInformationService.cs b/ModularAssistentForDiscordServer/Services/TranslateInformationService.cs new file mode 100644 index 0000000..16a4a8e --- /dev/null +++ b/ModularAssistentForDiscordServer/Services/TranslateInformationService.cs @@ -0,0 +1,50 @@ +// Copyright 2023 Plerx2493 +// +// Licensed under the Apache License, Version 2.0 (the "License") +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using MADS.Entities; +using Microsoft.EntityFrameworkCore; + +namespace MADS.Services; + +public class TranslateInformationService +{ + private readonly IDbContextFactory _dbContextFactory; + + public TranslateInformationService(IDbContextFactory factory) + { + _dbContextFactory = factory; + } + + public async void SetPreferredLanguage(ulong userId, string language) + { + await using var db = await _dbContextFactory.CreateDbContextAsync(); + var user = db.Users.FirstOrDefault(x => x.Id == userId); + if (user == null) return; + user.PreferedLanguage = language; + await db.SaveChangesAsync(); + } + + public async Task GetPreferredLanguage(ulong userId) + { + await using var db = await _dbContextFactory.CreateDbContextAsync(); + var user = db.Users.FirstOrDefault(x => x.Id == userId); + return user?.PreferedLanguage; + } + + internal static string StandardizeLang(string code) + { + string[] strArray = code.Split(new char[1]{ '-' }, 2); + return strArray.Length != 1 ? strArray[0].ToLowerInvariant() + "-" + strArray[1].ToUpperInvariant() : strArray[0].ToLowerInvariant(); + } +} \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Services/VoiceAlertService.cs b/ModularAssistentForDiscordServer/Services/VoiceAlertService.cs index 236d92e..be480d2 100644 --- a/ModularAssistentForDiscordServer/Services/VoiceAlertService.cs +++ b/ModularAssistentForDiscordServer/Services/VoiceAlertService.cs @@ -106,7 +106,8 @@ public async Task HandleEvent(DiscordClient client, VoiceStateUpdateEventArgs e) user = new UserDbEntity { Id = userId, - VoiceAlerts = new List() + VoiceAlerts = new List(), + PreferedLanguage = "en-US" }; await context.Users.AddAsync(user); } diff --git a/ModularAssistentForDiscordServer/config.json b/ModularAssistentForDiscordServer/config.json deleted file mode 100644 index 498f542..0000000 --- a/ModularAssistentForDiscordServer/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "token": "", - "defaultPrefix": "!", - "minmumloglvl": 1, - "databaseConnectionString": "Server=192.168.178.61,Port=3306;Database=MadsDB;User=USR;Password=PWD;", - "databaseConnectionStringQuartz": "Server=192.168.178.61,Port=3306;Database=MadsDB;User=USR;Password=PWD;", - "discordWebhook": null, - "DmProxyChannelId": 0 -} \ No newline at end of file diff --git a/QuartzNetDocker/Dockerfile b/QuartzNetDocker/Dockerfile new file mode 100644 index 0000000..85fc188 --- /dev/null +++ b/QuartzNetDocker/Dockerfile @@ -0,0 +1,3 @@ +FROM mysql:latest + +ADD QuartzNetDocker/tables_mysql_innodb.sql /docker-entrypoint-initdb.d/ diff --git a/QuartzNetDocker/tables_mysql_innodb.sql b/QuartzNetDocker/tables_mysql_innodb.sql new file mode 100644 index 0000000..4380653 --- /dev/null +++ b/QuartzNetDocker/tables_mysql_innodb.sql @@ -0,0 +1,177 @@ +# By: Ron Cordell - roncordell +# I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM. + +USE quartznet; + +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +CREATE TABLE QRTZ_JOB_DETAILS( +SCHED_NAME VARCHAR(120) NOT NULL, +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +JOB_CLASS_NAME VARCHAR(250) NOT NULL, +IS_DURABLE BOOLEAN NOT NULL, +IS_NONCONCURRENT BOOLEAN NOT NULL, +IS_UPDATE_DATA BOOLEAN NOT NULL, +REQUESTS_RECOVERY BOOLEAN NOT NULL, +JOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +NEXT_FIRE_TIME BIGINT(19) NULL, +PREV_FIRE_TIME BIGINT(19) NULL, +PRIORITY INTEGER NULL, +TRIGGER_STATE VARCHAR(16) NOT NULL, +TRIGGER_TYPE VARCHAR(8) NOT NULL, +START_TIME BIGINT(19) NOT NULL, +END_TIME BIGINT(19) NULL, +CALENDAR_NAME VARCHAR(200) NULL, +MISFIRE_INSTR SMALLINT(2) NULL, +JOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) +REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +REPEAT_COUNT BIGINT(7) NOT NULL, +REPEAT_INTERVAL BIGINT(12) NOT NULL, +TIMES_TRIGGERED BIGINT(10) NOT NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_CRON_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +CRON_EXPRESSION VARCHAR(120) NOT NULL, +TIME_ZONE_ID VARCHAR(80), +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 BOOLEAN NULL, + BOOL_PROP_2 BOOLEAN NULL, + TIME_ZONE_ID VARCHAR(80), + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_BLOB_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +BLOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_CALENDARS ( +SCHED_NAME VARCHAR(120) NOT NULL, +CALENDAR_NAME VARCHAR(200) NOT NULL, +CALENDAR BLOB NOT NULL, +PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_FIRED_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +ENTRY_ID VARCHAR(140) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +INSTANCE_NAME VARCHAR(200) NOT NULL, +FIRED_TIME BIGINT(19) NOT NULL, +SCHED_TIME BIGINT(19) NOT NULL, +PRIORITY INTEGER NOT NULL, +STATE VARCHAR(16) NOT NULL, +JOB_NAME VARCHAR(200) NULL, +JOB_GROUP VARCHAR(200) NULL, +IS_NONCONCURRENT BOOLEAN NULL, +REQUESTS_RECOVERY BOOLEAN NULL, +PRIMARY KEY (SCHED_NAME,ENTRY_ID)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SCHEDULER_STATE ( +SCHED_NAME VARCHAR(120) NOT NULL, +INSTANCE_NAME VARCHAR(200) NOT NULL, +LAST_CHECKIN_TIME BIGINT(19) NOT NULL, +CHECKIN_INTERVAL BIGINT(19) NOT NULL, +PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_LOCKS ( +SCHED_NAME VARCHAR(120) NOT NULL, +LOCK_NAME VARCHAR(40) NOT NULL, +PRIMARY KEY (SCHED_NAME,LOCK_NAME)) +ENGINE=InnoDB; + +CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); + +CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); +CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); + +CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); +CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); + +commit; diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..bcaba01 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,57 @@ +version: "3.8" +services: + mads-bot: + image: plerx2493/mads:latest + environment: + - MADS_DISCORD_TOKEN= + - MADS_DEFAULT_PREFIX=! + - MADS_MINIMUM_LOG_LEVEL=0 + - MADS_DATABASE_CONNECTION_STRING=Server=mads-db-core,Port=3306;Database=mads;User=mads;Password=mads; + - MADS_DATABASE_CONNECTION_STRING_QUARTZ=Server=mads-db-quartz,Port=3306;Database=quartz;User=quartz;Password=quartz; + - MADS_DISCORD_WEBHOOK= + - MADS_DM_PROXY_CHANNEL_ID=0 + - MADS_DEEPL_API_KEY= + networks: + - mads_internal + depends_on: + - mads-db-core + - mads-db-quartz + restart: unless-stopped + + mads-db-core: + image: mariadb:latest + environment: + MYSQL_ROOT_PASSWORD: mads + MYSQL_DATABASE: mads + MYSQL_USER: mads + MYSQL_PASSWORD: mads + volumes: + - data_mads_core:/var/lib/mysql + networks: + - mads_internal + hostname: mads-db-core + restart: unless-stopped + + mads-db-quartz: + image: plerx2493/quartz-db-mysql:latest + environment: + MYSQL_ROOT_PASSWORD: quartz + MYSQL_DATABASE: quartz + MYSQL_USER: quartz + MYSQL_PASSWORD: quartz + volumes: + - data_mads_quartz:/var/lib/mysql + networks: + - mads_internal + hostname: mads-db-quartz + restart: unless-stopped + +networks: + mads_internal: + driver: bridge + +volumes: + data_mads_quartz: + driver: local + data_mads_core: + driver: local \ No newline at end of file