refactor: Initial release

This commit is contained in:
Mira 2025-01-27 17:17:53 +01:00
commit 9505750e29
Signed by untrusted user who does not match committer: Xorog
GPG key ID: 983798ED9C3E7C36
447 changed files with 41522 additions and 0 deletions

View file

@ -0,0 +1,21 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.Debug;
internal sealed class ThrowCommand : BaseCommand
{
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
throw new InvalidCastException();
});
}
}

View file

@ -0,0 +1,43 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class BanGuildCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var guild = (ulong)arguments["guild"];
var reason = (string)arguments["reason"];
if (reason.IsNullOrWhiteSpace())
reason = "No reason provided.";
if (ctx.Bot.bannedGuilds.ContainsKey(guild))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`Guild '{guild}' is already banned from using the bot.`").AsError(ctx));
return;
}
ctx.Bot.bannedGuilds.Add(guild, new(ctx.Bot, "banned_guilds", guild) { Reason = reason, Moderator = ctx.User.Id });
foreach (var b in ctx.Client.Guilds.Where(x => x.Key == guild))
{
Log.Information("Leaving guild '{guild}'..", b.Key);
await b.Value.LeaveAsync();
}
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`Guild '{guild}' was banned from using the bot.`").AsSuccess(ctx));
});
}
}

View file

@ -0,0 +1,49 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class BanUserCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var victim = (DiscordUser)arguments["victim"];
var reason = (string)arguments["reason"];
if (reason.IsNullOrWhiteSpace())
reason = "No reason provided.";
if (ctx.Bot.status.TeamMembers.Contains(victim.Id))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`'{victim.GetUsernameWithIdentifier()}' is registered in the staff team.`").AsError(ctx));
return;
}
if (ctx.Bot.bannedUsers.ContainsKey(victim.Id))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`'{victim.GetUsernameWithIdentifier()}' is already banned from using the bot.`").AsError(ctx));
return;
}
ctx.Bot.bannedUsers.Add(victim.Id, new(ctx.Bot, "banned_users", victim.Id) { Reason = reason, Moderator = ctx.User.Id });
foreach (var b in ctx.Client.Guilds.Where(x => x.Value.OwnerId == victim.Id))
{
Log.Information("Leaving guild '{guild}'..", b.Key);
await b.Value.LeaveAsync();
}
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`'{victim.GetUsernameWithIdentifier()}' was banned from using the bot.`").AsSuccess(ctx));
});
}
}

View file

@ -0,0 +1,43 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class BatchLookupCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var IDs = ((string)arguments["IDs"]).Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Select(x => x.ToUInt64()).ToList();
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`Looking up {IDs.Count} users..`\n`{StringTools.GenerateASCIIProgressbar(0d, IDs.Count)}`").AsLoading(ctx));
Dictionary<ulong, DiscordUser> fetched = new();
for (var i = 0; i < IDs.Count; i++)
{
try
{
fetched.Add(IDs[i], await ctx.Client.GetUserAsync(IDs[i]));
}
catch (Exception)
{
fetched.Add(IDs[i], null);
}
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`Looking up {IDs.Count} users..`\n`{StringTools.GenerateASCIIProgressbar(i, IDs.Count)}`").AsLoading(ctx));
}
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription(string.Join("\n", fetched.Select(x => $"{(x.Value is null ? $" `Failed to fetch '{x.Key}'`" : $" {x.Value.Mention} `{x.Value.GetUsernameWithIdentifier()}` (`{x.Value.Id}`)")}"))).AsSuccess(ctx));
});
}
}

View file

@ -0,0 +1,37 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class BotnickCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var newNickname = (string)arguments["newNickname"];
try
{
await ctx.Guild.CurrentMember.ModifyAsync(x => x.Nickname = newNickname);
if (newNickname.IsNullOrWhiteSpace())
_ = await this.RespondOrEdit($"My nickname on this server has been reset.");
else
_ = await this.RespondOrEdit($"My nickname on this server has been changed to **{newNickname}**.");
}
catch (Exception)
{
_ = await this.RespondOrEdit($"My nickname could not be changed.");
}
});
}
}

View file

@ -0,0 +1,142 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class CommandManageCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var EnableCommandButton = new DiscordButtonComponent(ButtonStyle.Success, Guid.NewGuid().ToString(), "Enable Command", ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Count == 0, "".UnicodeToEmoji().ToComponent());
var DisableCommandButton = new DiscordButtonComponent(ButtonStyle.Danger, Guid.NewGuid().ToString(), "Disable Command", false, "".UnicodeToEmoji().ToComponent());
_ = await this.RespondOrEdit(new DiscordMessageBuilder()
.AddEmbed(new DiscordEmbedBuilder()
.WithTitle("Disabled Commands")
.WithDescription($"{(ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Count != 0 ? string.Join(", ", ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Select(x => $"`{x}`")) : "`No commands disabled.`")}")
.AsAwaitingInput(ctx))
.AddComponents(EnableCommandButton, DisableCommandButton)
.AddComponents(MessageComponents.GetCancelButton(ctx.DbUser, ctx.Bot)));
var Button = await ctx.WaitForButtonAsync(TimeSpan.FromMinutes(2));
if (Button.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
_ = Button.Result.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
if (Button.GetCustomId() == EnableCommandButton.CustomId)
{
var SelectionResult = await this.PromptCustomSelection(ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Select(x => new DiscordStringSelectComponentOption(x, x)).ToList());
if (SelectionResult.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
else if (SelectionResult.Cancelled)
{
await this.ExecuteCommand(ctx, arguments);
return;
}
else if (SelectionResult.Errored)
{
throw SelectionResult.Exception;
}
if (!ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Contains(SelectionResult.Result))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder()
.WithDescription("`That command is already enabled.`")
.AsError(ctx));
await this.ExecuteCommand(ctx, arguments);
return;
}
_ = ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Remove(SelectionResult.Result);
ctx.Bot.status.LoadedConfig.Save();
await this.ExecuteCommand(ctx, arguments);
return;
}
else if (Button.GetCustomId() == DisableCommandButton.CustomId)
{
List<string> CommandList = new();
foreach (var cmd in ctx.Client.GetCommandList(ctx.Bot))
{
if (ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Contains(cmd.Name.ToLower()))
continue;
CommandList.Add(cmd.Name.ToLower());
foreach (var sub in cmd.Options?.Where(x => x.Type == ApplicationCommandOptionType.SubCommand) ?? new List<DiscordApplicationCommandOption>())
{
if (ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Contains($"{cmd.Name} {sub.Name}".ToLower()))
continue;
CommandList.Add($"{cmd.Name} {sub.Name}".ToLower());
}
}
if (CommandList.Count == 0)
{
await this.ExecuteCommand(ctx, arguments);
return;
}
var SelectionResult = await this.PromptCustomSelection(CommandList.Select(x =>
new DiscordStringSelectComponentOption(x.FirstLetterToUpper(), x,
(x.Contains(' ') ? "Sub Command" : (CommandList.Where(y => y.StartsWith(x)).Count() >= 2 ? "Command Group" : "Single Command")))).ToList(), "Select a command to disable..");
if (SelectionResult.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
else if (SelectionResult.Cancelled)
{
await this.ExecuteCommand(ctx, arguments);
return;
}
else if (SelectionResult.Errored)
{
throw SelectionResult.Exception;
}
if (ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Contains(SelectionResult.Result))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder()
.WithDescription("`That command is already disabled.`")
.AsError(ctx));
await this.ExecuteCommand(ctx, arguments);
return;
}
ctx.Bot.status.LoadedConfig.Discord.DisabledCommands.Add(SelectionResult.Result);
ctx.Bot.status.LoadedConfig.Save();
await this.ExecuteCommand(ctx, arguments);
return;
}
else if (Button.GetCustomId() == MessageComponents.CancelButtonId)
{
this.DeleteOrInvalidate();
return;
}
});
}
}

View file

@ -0,0 +1,92 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
using Octokit;
namespace ProjectMakoto.Commands.DevTools;
internal sealed class CreateIssueCommand : BaseCommand
{
public override async Task<bool> BeforeExecution(SharedCommandContext ctx) => (await this.CheckMaintenance() && await this.CheckSource(Enums.CommandType.ApplicationCommand));
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var UseOldTagsSelector = (bool)arguments["UseOldTagsSelector"];
if (ctx.Bot.status.LoadedConfig.Secrets.Github.TokenExperiation.GetTotalSecondsUntil() <= 0)
{
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithContent($"❌ `The GitHub Token expired, please update.`"));
return;
}
var labels = await ctx.Bot.GithubClient.Issue.Labels.GetAllForRepository(ctx.Bot.status.LoadedConfig.Secrets.Github.Username, ctx.Bot.status.LoadedConfig.Secrets.Github.Repository);
var modal = new DiscordInteractionModalBuilder().WithCustomId(Guid.NewGuid().ToString()).WithTitle("Create new Issue on Github")
.AddModalComponents(new DiscordTextComponent(TextComponentStyle.Small, "title", "Title", "New issue", 4, 250, true))
.AddModalComponents(new DiscordTextComponent(TextComponentStyle.Paragraph, "description", "Description", required: false));
if (!UseOldTagsSelector)
_ = modal.AddModalComponents(new DiscordStringSelectComponent("Select tags", labels.Select(x => new DiscordStringSelectComponentOption(x.Name, x.Name.ToLower().MakeValidFileName(), "", false, new DiscordComponentEmoji(new DiscordColor(x.Color).GetClosestColorEmoji(ctx.Client)))), "labels", 1, labels.Count));
else
_ = modal.AddModalComponents(new DiscordTextComponent(TextComponentStyle.Paragraph, "labels", "Labels", "", null, null, false, $"Put a # in front of every label you want to add.\n\n{string.Join("\n", labels.Select(x => x.Name))}"));
await ctx.OriginalInteractionContext.CreateModalResponseAsync(modal);
CancellationTokenSource cancellationTokenSource = new();
ctx.Client.ComponentInteractionCreated += RunInteraction;
async Task RunInteraction(DiscordClient s, ComponentInteractionCreateEventArgs e)
{
_ = Task.Run(async () =>
{
if (e.GetCustomId() == modal.CustomId)
{
cancellationTokenSource.Cancel();
ctx.Client.ComponentInteractionCreated -= RunInteraction;
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
var followup = await e.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder { IsEphemeral = true }.WithContent(":arrows_counterclockwise: `Submitting your issue..`"));
var labelComp = e.Interaction.Data.Components.Where(x => x.CustomId == "labels").First();
var title = e.Interaction.Data.Components.Where(x => x.CustomId == "title").First().Value;
var description = e.Interaction.Data.Components.Where(x => x.CustomId == "description").First().Value;
var labels = labelComp.Type == ComponentType.StringSelect
? labelComp.Values.ToList()
: labelComp.Value.Split("\n", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Where(x => x.StartsWith('#')).Select(x => x.Replace("#", "")).ToList();
if (ctx.Bot.status.LoadedConfig.Secrets.Github.TokenExperiation.GetTotalSecondsUntil() <= 0)
{
_ = e.Interaction.EditFollowupMessageAsync(followup.Id, new DiscordWebhookBuilder().WithContent($"❌ `The GitHub Token expired, please update.`"));
return;
}
var issue = await ctx.Bot.GithubClient.Issue.Create(ctx.Bot.status.LoadedConfig.Secrets.Github.Username, ctx.Bot.status.LoadedConfig.Secrets.Github.Repository, new NewIssue(title) { Body = $"{(description.IsNullOrWhiteSpace() ? "_No description provided_" : description)}\n\n<b/>\n\n##### <img align=\"left\" style=\"align:center;\" width=\"32\" height=\"32\" src=\"{ctx.User.AvatarUrl}\">_Submitted by [`{ctx.User.GetUsernameWithIdentifier()}`]({ctx.User.ProfileUrl}) (`{ctx.User.Id}`) via Discord._" });
if (labels.Count > 0)
_ = await ctx.Bot.GithubClient.Issue.Labels.ReplaceAllForIssue(ctx.Bot.status.LoadedConfig.Secrets.Github.Username, ctx.Bot.status.LoadedConfig.Secrets.Github.Repository, issue.Number, labels.ToArray());
_ = e.Interaction.EditFollowupMessageAsync(followup.Id, new DiscordWebhookBuilder().WithContent($"✅ `Issue submitted:` {issue.HtmlUrl}"));
}
}).Add(ctx.Bot, ctx);
}
try
{
await Task.Delay(TimeSpan.FromMinutes(15), cancellationTokenSource.Token);
ctx.Client.ComponentInteractionCreated -= RunInteraction;
}
catch { }
});
}
}

View file

@ -0,0 +1,33 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class Disenroll2FAUserCommand : BaseCommand
{
public override async Task<bool> BeforeExecution(SharedCommandContext ctx)
=> await this.CheckMaintenance() && await this.CheckBotOwner();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var victim = (DiscordUser)arguments["victim"];
if (!ctx.Client.CheckTwoFactorEnrollmentFor(victim.Id))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`{victim.GetUsernameWithIdentifier()} is not enrolled in Two Factor Authentication.`").AsError(ctx));
return;
}
ctx.Client.DisenrollTwoFactor(victim.Id);
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`Two Factor Authentication removed for {victim.GetUsername()}.`").AsSuccess(ctx));
});
}
}

View file

@ -0,0 +1,93 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
using DisCatSharp.Extensions.TwoFactorCommands.Enums;
namespace ProjectMakoto.Commands.DevTools;
internal sealed class EnrollTwoFactorCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx)
=> this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
if (ctx.Client.CheckTwoFactorEnrollmentFor(ctx.User.Id))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`You're already enrolled in Two Factor Authentication.`").AsError(ctx));
return;
}
var Confirmed = false;
var ConfirmButton = new DiscordButtonComponent(ButtonStyle.Primary, Guid.NewGuid().ToString(), "Confirm Two Factor Authentication", false, DiscordEmoji.FromUnicode("✅").ToComponent());
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Enrolling you into Two Factor Authentication..`").AsLoading(ctx));
var (Secret, QrCode) = ctx.Client.EnrollTwoFactor(ctx.User);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithContent($"Please scan this QR Code or use the Secret below to register the Two Factor in an App of your choosing." +
$"\n\n`{Secret}`\n\n" +
$"When you're done, please press the button below to confirm the success of the registration.")
.WithFile("2fa.png", QrCode, false, "This is a QR Code for an Authenticator App.")
.AddComponents(ConfirmButton, MessageComponents.GetCancelButton(ctx.DbUser, ctx.Bot)));
_ = Task.Delay(120000).ContinueWith((_) =>
{
ctx.Client.ComponentInteractionCreated -= RunInteraction;
if (!Confirmed)
{
ctx.Client.DisenrollTwoFactor(ctx.User.Id);
_ = this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Failed to authenticate. Enrollment reverted.`").AsError(ctx));
}
});
async Task RunInteraction(DiscordClient s, ComponentInteractionCreateEventArgs e)
{
_ = Task.Run(async () =>
{
if (e.Message?.Id == ctx.ResponseMessage.Id && e.User.Id == ctx.User.Id)
{
try
{
if (e.GetCustomId() == ConfirmButton.CustomId)
{
var tfa_result = await e.RequestTwoFactorAsync(s);
if (tfa_result.Result is TwoFactorResult.ValidCode or TwoFactorResult.InvalidCode)
_ = tfa_result.ComponentInteraction.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
if (tfa_result.Result == TwoFactorResult.ValidCode)
{
Confirmed = true;
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Enrolled successfully.`").AsSuccess(ctx));
return;
}
throw new Exception("Invalid Code");
}
else if (e.GetCustomId() == MessageComponents.CancelButtonId)
{
throw new Exception("Cancelled");
}
}
catch (Exception)
{
ctx.Client.DisenrollTwoFactor(ctx.User.Id);
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Failed to authenticate. Enrollment reverted.`").AsError(ctx));
}
}
});
}
ctx.Client.ComponentInteractionCreated += RunInteraction;
});
}
}

View file

@ -0,0 +1,83 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
namespace ProjectMakoto.Commands.DevTools;
internal sealed class EvaluationCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckBotOwner();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
if (ctx.CommandType is not Enums.CommandType.ApplicationCommand and not Enums.CommandType.ContextMenu)
{
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithEmbed(new DiscordEmbedBuilder().WithDescription("Evaluating CScript has the potential of leaking confidential information. Are you sure you want to run this command as Prefix Command?").AsWarning(ctx))
.AddComponents(new List<DiscordComponent> { new DiscordButtonComponent(ButtonStyle.Success, "yes", "Yes"),
new DiscordButtonComponent(ButtonStyle.Danger, "no", "No")}));
var result = await ctx.ResponseMessage.WaitForButtonAsync(ctx.User);
if (result.TimedOut || result.GetCustomId() != "yes")
{
this.DeleteOrInvalidate();
return;
}
}
var rawCode = (string)arguments["code"];
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Evaluating..`").AsLoading(ctx));
var code = RegexTemplates.Code.Match(rawCode).Groups[1]?.Value?.Trim() ?? "";
if (code.IsNullOrWhiteSpace())
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`No code block was found.`").AsError(ctx));
return;
}
try
{
var options = ScriptOptions.Default;
options = options.WithImports(
"System",
"System.Collections.Generic",
"System.Linq",
"System.Text",
"System.Threading.Tasks",
"DisCatSharp",
"DisCatSharp.Entities",
"DisCatSharp.Interactivity",
"DisCatSharp.Interactivity.Extensions",
"DisCatSharp.Interactivity.Enums",
"DisCatSharp.Enums",
"Newtonsoft.Json"
);
options = options.WithReferences(AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.IsDynamic && !x.Location.IsNullOrWhiteSpace()));
var script = CSharpScript.Create(code, options, typeof(SharedCommandContext));
_ = script.Compile();
var result = await script.RunAsync(ctx).ConfigureAwait(false);
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithTitle("Successful Evaluation")
.WithDescription($"{(result.ReturnValue?.ToString().IsNullOrWhiteSpace() ?? true ? "`The evaluation did not return any result.`" : $"{result.ReturnValue}")}").AsSuccess(ctx));
}
catch (Exception ex)
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithTitle("Failed Evaluation").WithDescription($"```{ex.Message.SanitizeForCode()}```").AsError(ctx));
}
});
}
}

View file

@ -0,0 +1,183 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class GlobalBanCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var victims = await DiscordExtensions.ParseStringAsUserArray((string)arguments["victims"], ctx.Client);
var reason = (string)arguments["reason"];
if (victims?.Length <= 0)
{
_ = this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Please provide user(s).`").AsError(ctx, "Global Ban"));
return;
}
if (reason.IsNullOrWhiteSpace())
{
_ = this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Please provide a reason for the global ban.`").AsError(ctx, "Global Ban"));
return;
}
var currentStatus = new Dictionary<DiscordUser, CurrentStatus>([
..victims.Select(x => new KeyValuePair<DiscordUser, CurrentStatus>(x, CurrentStatus.InQueue)).ToList()
]);
_ = Task.Run(async () =>
{
while (true)
{
var desc = string.Empty;
lock (currentStatus)
{
desc = $"{string.Join("\n\n", currentStatus
.Select(x =>
{
var emoji = x.Value switch
{
CurrentStatus.Invalid => DiscordEmoji.FromUnicode("❌"),
CurrentStatus.Added => DiscordEmoji.FromUnicode("✅"),
CurrentStatus.Changed => DiscordEmoji.FromUnicode("🔄"),
CurrentStatus.InQueue => DiscordEmoji.FromUnicode("🕒"),
CurrentStatus.InProgress => EmojiTemplates.GetLoading(ctx.Bot),
_ => throw new NotImplementedException(),
};
var text = x.Value switch
{
CurrentStatus.Invalid => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `This user cannot be global banned.`",
CurrentStatus.Added => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `User was added to global ban list.`",
CurrentStatus.Changed => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `User was already global banned, updated entry.`",
CurrentStatus.InQueue => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `In queue..`",
CurrentStatus.InProgress => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `Processing..`",
_ => throw new NotImplementedException(),
};
return $"{emoji} {text}";
}))}";
}
var embed = new DiscordEmbedBuilder();
var done = true;
if (currentStatus.All(x => x.Value is CurrentStatus.Changed or CurrentStatus.Added))
_ = embed.AsSuccess(ctx, "Global Ban").WithDescription(desc.TruncateWithIndication(2000));
else if (currentStatus.All(x => x.Value is CurrentStatus.Changed or CurrentStatus.Added or CurrentStatus.Invalid))
_ = embed.AsWarning(ctx, "Global Ban").WithDescription(desc.TruncateWithIndication(2000));
else
{
_ = embed.AsLoading(ctx, "Global Ban").WithDescription($"`Global banning {currentStatus.Count} users..`\n\n{desc}".TruncateWithIndication(2000));
done = false;
}
_ = await this.RespondOrEdit(embed);
if (done)
return;
await Task.Delay(1000);
}
}).Add(ctx.Bot, ctx);
foreach (var victim in currentStatus)
{
currentStatus[victim.Key] = CurrentStatus.InProgress;
await Task.Delay(2000);
if (ctx.Bot.globalBans.ContainsKey(victim.Key.Id))
{
ctx.Bot.globalBans[victim.Key.Id].Reason = reason;
ctx.Bot.globalBans[victim.Key.Id].Moderator = ctx.User.Id;
currentStatus[victim.Key] = CurrentStatus.Changed;
var announceChannel1 = await ctx.Client.GetChannelAsync(ctx.Bot.status.LoadedConfig.Channels.GlobalBanAnnouncements);
_ = await announceChannel1.SendMessageAsync(new DiscordEmbedBuilder
{
Author = new DiscordEmbedBuilder.EmbedAuthor
{
Name = ctx.CurrentUser.GetUsername(),
IconUrl = AuditLogIcons.UserUpdated
},
Description = $"The global ban entry of {victim.Key.Mention} `{victim.Key.GetUsernameWithIdentifier()}` (`{victim.Key.Id}`) was updated.\n\n" +
$"Reason: `{reason.SanitizeForCode()}`\n" +
$"Moderator: {ctx.User.Mention} `{ctx.User.GetUsernameWithIdentifier()}` (`{ctx.User.Id}`)",
Color = EmbedColors.Warning,
Timestamp = DateTime.UtcNow
});
continue;
}
if (ctx.Bot.status.TeamMembers.Contains(victim.Key.Id))
{
currentStatus[victim.Key] = CurrentStatus.Invalid;
continue;
}
ctx.Bot.globalBans.Add(victim.Key.Id, new(ctx.Bot, "globalbans", victim.Key.Id) { Reason = reason, Moderator = ctx.User.Id });
var Success = 0;
var Failed = 0;
foreach (var b in ctx.Client.Guilds.OrderByDescending(x => x.Key == ctx.Guild.Id))
{
if (!ctx.Bot.Guilds.ContainsKey(b.Key))
ctx.Bot.Guilds.Add(b.Key, new Guild(ctx.Bot, b.Key));
if (ctx.Bot.Guilds[b.Key].Join.AutoBanGlobalBans)
{
try
{
await b.Value.BanMemberAsync(victim.Key.Id, 7, $"Globalban: {reason}");
Success++;
}
catch (Exception ex)
{
Log.Error(ex, "Exception occurred while trying to ban user from {guild}", b.Key);
Failed++;
}
}
}
currentStatus[victim.Key] = CurrentStatus.Added;
var announceChannel = await ctx.Client.GetChannelAsync(ctx.Bot.status.LoadedConfig.Channels.GlobalBanAnnouncements);
_ = await announceChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Author = new DiscordEmbedBuilder.EmbedAuthor
{
Name = ctx.CurrentUser.GetUsername(),
IconUrl = AuditLogIcons.UserBanned
},
Description = $"{victim.Key.Mention} `{victim.Key.GetUsernameWithIdentifier()}` (`{victim.Key.Id}`) was added to the global ban list.\n\n" +
$"Reason: `{reason.SanitizeForCode()}`\n" +
$"Moderator: {ctx.User.Mention} `{ctx.User.GetUsernameWithIdentifier()}` (`{ctx.User.Id}`)",
Color = EmbedColors.Error,
Timestamp = DateTime.UtcNow
});
}
});
}
private enum CurrentStatus
{
InQueue,
InProgress,
Changed,
Added,
Invalid
}
}

View file

@ -0,0 +1,121 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class GlobalNotesCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
if (await ctx.DbUser.Cooldown.WaitForModerate(ctx, true))
return;
var victim = (DiscordUser)arguments["victim"];
var ModeratorCache = new Dictionary<ulong, DiscordUser>();
if (ctx.Bot.globalNotes.TryGetValue(victim.Id, out var globalNotes))
foreach (var b in globalNotes.Notes)
{
if (ModeratorCache.ContainsKey(b.Moderator))
continue;
try
{
ModeratorCache.Add(b.Moderator, await ctx.Client.GetUserAsync(b.Moderator));
}
catch (Exception)
{
ModeratorCache.Add(b.Moderator, null);
}
}
var AddButton = new DiscordButtonComponent(ButtonStyle.Primary, Guid.NewGuid().ToString(), "Add Notes", false, DiscordEmoji.FromUnicode("").ToComponent());
var RemoveButton = new DiscordButtonComponent(ButtonStyle.Primary, Guid.NewGuid().ToString(), "Remove Notes", (!ctx.Bot.globalNotes.ContainsKey(victim.Id)), DiscordEmoji.FromUnicode("").ToComponent());
_ = await this.RespondOrEdit(new DiscordMessageBuilder()
.WithEmbed(new DiscordEmbedBuilder()
.WithDescription($"{victim.Mention} `has {(ctx.Bot.globalNotes.TryGetValue(victim.Id, out var noteObj) ? noteObj.Notes.Length : 0)} global notes.`")
.AddFields((noteObj is not null ? noteObj.Notes.Take(20).Select(x => new DiscordEmbedField("󠂪 󠂪", $"{x.Reason.FullSanitize()} - `{(ModeratorCache[x.Moderator] is null ? "Unknown#0000" : ModeratorCache[x.Moderator].GetUsernameWithIdentifier())}` {x.Timestamp.ToTimestamp()}")) : new List<DiscordEmbedField>())))
.AddComponents(new List<DiscordComponent> { AddButton, RemoveButton })
.AddComponents(MessageComponents.GetCancelButton(ctx.DbUser, ctx.Bot)));
var Button = await ctx.WaitForButtonAsync(TimeSpan.FromMinutes(2));
if (Button.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
if (Button.GetCustomId() == AddButton.CustomId)
{
var ModalResult = await this.PromptModalWithRetry(Button.Result.Interaction,
new DiscordInteractionModalBuilder().AddTextComponent(new DiscordTextComponent(TextComponentStyle.Paragraph, "Note", "New Note", "", 1, 256, true)), false);
if (ModalResult.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
else if (ModalResult.Cancelled)
{
await this.ExecuteCommand(ctx, arguments);
return;
}
else if (ModalResult.Errored)
{
throw ModalResult.Exception;
}
var note = ModalResult.Result.Interaction.GetModalValueByCustomId("Note");
ctx.Bot.globalNotes[victim.Id].Notes = ctx.Bot.globalNotes[victim.Id].Notes.Add(new GlobalNote.Note() { Moderator = ctx.User.Id, Reason = note });
await this.ExecuteCommand(ctx, arguments);
return;
}
else if (Button.GetCustomId() == RemoveButton.CustomId)
{
_ = Button.Result.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
var SelectionResult = await this.PromptCustomSelection(ctx.Bot.globalNotes[victim.Id].Notes
.Select(x => new DiscordStringSelectComponentOption(x.Reason.TruncateWithIndication(100), x.Timestamp.Ticks.ToString(), $"Added by {(ModeratorCache[x.Moderator] is null ? "Unknown#0000" : ModeratorCache[x.Moderator].GetUsernameWithIdentifier())} {x.Timestamp.GetTimespanSince().GetHumanReadable()} ago")).ToList());
if (SelectionResult.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
else if (SelectionResult.Cancelled)
{
await this.ExecuteCommand(ctx, arguments);
return;
}
else if (SelectionResult.Errored)
{
throw SelectionResult.Exception;
}
ctx.Bot.globalNotes[victim.Id].Notes = ctx.Bot.globalNotes[victim.Id].Notes.Remove(x => x.UUID, ctx.Bot.globalNotes[victim.Id].Notes.First(x => x.Timestamp.Ticks.ToString() == SelectionResult.Result));
await this.ExecuteCommand(ctx, arguments);
return;
}
else if (Button.GetCustomId() == MessageComponents.CancelButtonId)
{
this.DeleteOrInvalidate();
return;
}
});
}
}

View file

@ -0,0 +1,155 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class GlobalUnbanCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var victims = await DiscordExtensions.ParseStringAsUserArray((string)arguments["victims"], ctx.Client);
var UnbanFromGuilds = (bool)arguments["UnbanFromGuilds"];
if (victims?.Length <= 0)
{
_ = this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Please provide user(s).`").AsError(ctx, "Global Ban"));
return;
}
var currentStatus = new Dictionary<DiscordUser, CurrentStatus>([
..victims.Select(x => new KeyValuePair<DiscordUser, CurrentStatus>(x, CurrentStatus.InQueue)).ToList()
]);
_ = Task.Run(async () =>
{
while (true)
{
var desc = string.Empty;
lock (currentStatus)
{
desc = $"{string.Join("\n\n", currentStatus
.Select(x =>
{
var emoji = x.Value switch
{
CurrentStatus.Invalid => DiscordEmoji.FromUnicode("❌"),
CurrentStatus.Removed => DiscordEmoji.FromUnicode("✅"),
CurrentStatus.InQueue => DiscordEmoji.FromUnicode("🕒"),
CurrentStatus.InProgress => EmojiTemplates.GetLoading(ctx.Bot),
_ => throw new NotImplementedException(),
};
var text = x.Value switch
{
CurrentStatus.Invalid => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `This user is not on the global ban list.`",
CurrentStatus.Removed => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `User was removed from the global ban list.`",
CurrentStatus.InQueue => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `In queue..`",
CurrentStatus.InProgress => $"**`{x.Key.GetUsernameWithIdentifier()} ({x.Key.Id})`**\n{EmojiTemplates.GetInVisible(ctx.Bot)} `Processing..`",
_ => throw new NotImplementedException(),
};
return $"{emoji} {text}";
}))}";
}
var embed = new DiscordEmbedBuilder();
var done = true;
if (currentStatus.All(x => x.Value is CurrentStatus.Removed))
_ = embed.AsSuccess(ctx, "Global Ban").WithDescription(desc.TruncateWithIndication(2000));
else if (currentStatus.All(x => x.Value is CurrentStatus.Removed or CurrentStatus.Invalid))
_ = embed.AsWarning(ctx, "Global Ban").WithDescription(desc.TruncateWithIndication(2000));
else
{
_ = embed.AsLoading(ctx, "Global Ban").WithDescription($"`Removing Global ban for {currentStatus.Count} users..`\n\n{desc}".TruncateWithIndication(2000));
done = false;
}
_ = await this.RespondOrEdit(embed);
if (done)
return;
await Task.Delay(1000);
}
}).Add(ctx.Bot, ctx);
foreach (var victim in currentStatus)
{
currentStatus[victim.Key] = CurrentStatus.InProgress;
await Task.Delay(2000);
if (!ctx.Bot.globalBans.ContainsKey(victim.Key.Id))
{
currentStatus[victim.Key] = CurrentStatus.Invalid;
continue;
}
_ = ctx.Bot.globalBans.Remove(victim.Key.Id);
currentStatus[victim.Key] = CurrentStatus.Removed;
var Success = 0;
var Failed = 0;
if (UnbanFromGuilds)
foreach (var b in ctx.Client.Guilds.OrderByDescending(x => x.Key == ctx.Guild.Id))
{
if (!ctx.Bot.Guilds.ContainsKey(b.Key))
ctx.Bot.Guilds.Add(b.Key, new Guild(ctx.Bot, b.Key));
if (ctx.Bot.Guilds[b.Key].Join.AutoBanGlobalBans)
{
try
{
var Ban = await b.Value.GetBanAsync(victim.Key);
if (Ban.Reason.StartsWith("Globalban: "))
await b.Value.UnbanMemberAsync(victim.Key, $"Globalban removed.");
Success++;
}
catch (Exception ex)
{
Log.Error(ex, "Exception occurred while trying to unban user from {guild}", b.Key);
Failed++;
}
}
}
var announceChannel = await ctx.Client.GetChannelAsync(ctx.Bot.status.LoadedConfig.Channels.GlobalBanAnnouncements);
_ = await announceChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Author = new DiscordEmbedBuilder.EmbedAuthor
{
Name = ctx.CurrentUser.GetUsername(),
IconUrl = AuditLogIcons.UserBanRemoved
},
Description = $"{victim.Key.Mention} `{victim.Key.GetUsernameWithIdentifier()}` (`{victim.Key.Id}`) was removed from the global ban list.\n\n" +
$"Moderator: {ctx.User.Mention} `{ctx.User.GetUsernameWithIdentifier()}` (`{ctx.User.Id}`)",
Color = EmbedColors.Success,
Timestamp = DateTime.UtcNow
});
}
});
}
private enum CurrentStatus
{
InQueue,
InProgress,
Removed,
Invalid
}
}

View file

@ -0,0 +1,211 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class InfoCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
if (await ctx.DbUser.Cooldown.WaitForModerate(ctx))
return;
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Fetching system details..`").AsLoading(ctx));
Dictionary<DateTime, Entities.SystemMonitor.SystemInfo> history = new();
try
{
var rawHistory = ctx.Bot.MonitorClient.GetHistory().GroupBy(x => $"{x.Key.Hour}-{(int)Math.Floor(x.Key.Minute / 6d)}");
foreach (var entry in rawHistory)
{
history.Add(entry.Last().Key, new()
{
Cpu = new()
{
Load = entry.Average(x => x.Value.Cpu.Load),
Temperature = entry.Average(x => x.Value.Cpu.Temperature)
},
Memory = new()
{
Available = entry.Average(x => x.Value.Memory.Available),
Used = entry.Average(x => x.Value.Memory.Used),
}
});
}
}
catch {}
history.Add(DateTime.UtcNow, await ctx.Bot.MonitorClient.GetCurrent());
history = history.OrderBy(x => x.Key.Ticks).ToDictionary(x => x.Key, x => x.Value);
var ServerUptime = "";
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
ProcessStartInfo info = new()
{
FileName = "bash",
Arguments = $"-c uptime",
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false
};
var b = Process.Start(info);
b.WaitForExit();
var Output = b.StandardOutput.ReadToEnd();
ServerUptime = Output.Remove(Output.IndexOf(','), Output.Length - Output.IndexOf(',')).TrimStart();
}
IEnumerable<string> bFile;
try
{
bFile = File.ReadLines("LatestGitPush.cfg");
}
catch (Exception)
{
bFile = new List<string>
{
"Developer Version",
"dev",
$"{DateTime.UtcNow:dd.MM.yy}",
$"{DateTime.UtcNow:HH:mm:ss},00"
};
}
var Version = bFile.First().Trim();
var Branch = bFile.Skip(1).First().Trim();
var Date = bFile.Skip(2).First().Trim().Replace("/", ".");
var Time = bFile.Skip(3).First().Trim();
Time = Time[..Time.IndexOf(',')];
var miscEmbed = new DiscordEmbedBuilder().WithTitle($"{ctx.CurrentUser.GetUsername()} Details")
.AddField(new DiscordEmbedField("Currently running as", $"`{ctx.CurrentUser.GetUsernameWithIdentifier()}`", true))
.AddField(new DiscordEmbedField("Process PID", $"`{Environment.ProcessId}`", true))
.AddField(new DiscordEmbedField("󠂪 󠂪", $"󠂪 󠂪", true))
.AddField(new DiscordEmbedField("Bot uptime", $"`{Math.Round((DateTime.UtcNow - ctx.Bot.status.startupTime).TotalHours, 2)} hours`", true))
.AddField(new DiscordEmbedField("Discord API Latency", $"`{ctx.Client.Ping}ms`", true))
.AddField(new DiscordEmbedField("Server uptime", $"`{(ServerUptime.IsNullOrWhiteSpace() ? "Currently unavailable" : ServerUptime)}`", true))
.AddField(new DiscordEmbedField("Currently running software", $"`Project Makoto by {(await ctx.Client.GetUserAsync(411950662662881290)).GetUsernameWithIdentifier()} ({Version} ({Branch}) built on the {Date} at {Time})`"))
.AddField(new DiscordEmbedField("Current bot library and version", $"[`{ctx.Client.BotLibrary} {ctx.Client.VersionString}`](https://github.com/Aiko-IT-Systems/DisCatSharp)"))
.AddField(new DiscordEmbedField("Plugin Status",
$"{(ctx.Bot.status.LoadedConfig.EnablePlugins && ctx.Bot.Plugins.Count > 0 ?
$"`{ctx.Bot.Plugins.Count}/{new DirectoryInfo("Plugins")?.GetFiles()?.Where(x => !x.Name.StartsWith('.') && x.Extension == ".pmpl")?.Count() ?? 0} loaded`\n\n" +
$"{string.Join("\n", ctx.Bot.Plugins.Select(x => $"- {x.Value.OfficialPlugin.ToEmote(ctx.Bot)} `{x.Value.Name}` `v{x.Value.Version}` by {x.Value.AuthorUser?.Mention ?? "`N/A`"} (`{x.Value.Author}`)"))}" :
"`Disabled`")}"))
.AsInfo(ctx).WithFooter().WithTimestamp(null);
var cpuEmbed1 = new DiscordEmbedBuilder()
.WithTitle("CPU")
.AddField(new DiscordEmbedField("Load", $"`{history.MaxBy(x => x.Key).Value.Cpu.Load.ToString("N0", CultureInfo.CreateSpecificCulture("en-US")),3}%`", true))
.AddField(new DiscordEmbedField("Temperature", $"`{history.MaxBy(x => x.Key).Value.Cpu.Temperature.ToString("N0", CultureInfo.CreateSpecificCulture("en-US")),2}°C`", true))
.AsLoading(ctx).WithFooter().WithTimestamp(null).WithAuthor();
var memoryEmbed = new DiscordEmbedBuilder()
.WithTitle("Memory")
.AddField(new DiscordEmbedField("Usage", $"`{history.MaxBy(x => x.Key).Value.Memory.Used.ToString("N0", CultureInfo.CreateSpecificCulture("en-US"))}/{history.MaxBy(x => x.Key).Value.Memory.Total.ToString("N0", CultureInfo.CreateSpecificCulture("en-US"))} MB`", true))
.AsLoading(ctx);
var embeds = new List<DiscordEmbed>() { miscEmbed };
if (ctx.Bot.status.LoadedConfig.MonitorSystem.Enabled)
embeds.AddRange(cpuEmbed1, memoryEmbed);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbeds(embeds));
if (!ctx.Bot.status.LoadedConfig.MonitorSystem.Enabled)
return;
Dictionary<string, byte[]> charts = new();
try
{
var prev = "";
var qc = ctx.Bot.ChartsClient.GetChart(800, 600, history.Select(x =>
{
var value = x.Key.ToString("HH:mm");
if (prev == value)
return " ";
prev = value;
return $"{value}";
}), new ChartGeneration.Dataset[]
{
new("Usage (%)", history.Select(x => $"{(int)x.Value.Cpu.Load}"), "getGradientFillHelper('vertical', ['#ff0000', '#00ff00'])"),
new("Temperature (°C)", history.Select(x => $"{(int)x.Value.Cpu.Temperature}"), "getGradientFillHelper('vertical', ['#4287f5', '#ff0000'])"),
}, 0, 100);
charts.Add("cpu.png", qc.ToByteArray());
cpuEmbed1.ImageUrl = "attachment://cpu.png";
}
catch (Exception ex)
{
Log.Error(ex, "Failed to generate cpu usage graph");
cpuEmbed1.ImageUrl = "attachment://1.png";
}
finally
{
_ = cpuEmbed1.AsInfo(ctx).WithFooter().WithTimestamp(null).WithAuthor();
}
try
{
var prev = "";
var qc = ctx.Bot.ChartsClient.GetChart(800, 600, history.Select(x =>
{
var value = x.Key.ToString("HH:mm");
if (prev == value)
return " ";
prev = value;
return $"{value}";
}),
new ChartGeneration.Dataset[]
{
new("Usage (MB)", history.Select(x => $"{(int)x.Value.Memory.Used}"))
}, 0, (int)history.First().Value.Memory.Total);
charts.Add("mem.png", qc.ToByteArray());
memoryEmbed.ImageUrl = "attachment://mem.png";
}
catch (Exception ex)
{
Log.Error(ex, "Failed to generate memory graph");
memoryEmbed.ImageUrl = "attachment://1.png";
}
finally
{
_ = memoryEmbed.AsInfo(ctx).WithAuthor();
}
var list = new List<DiscordEmbed>();
list.Add(miscEmbed.WithImageUrl("attachment://1.png"));
list.Add(cpuEmbed1);
list.Add(memoryEmbed);
var files = charts.ToDictionary(x => x.Key, y => (Stream)new MemoryStream(y.Value));
try
{
files.Add("1.png", new FileStream("Assets/1.png", FileMode.Open));
_ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbeds(new DiscordEmbed[] { miscEmbed.Build(), cpuEmbed1.Build(), memoryEmbed.Build() }).WithFiles(files));
}
finally
{
foreach (var file in files)
file.Value.Dispose();
}
});
}
}

View file

@ -0,0 +1,29 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class LogCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var Level = (int)arguments["Level"];
if (Level is > ((int)LogEventLevel.Fatal) or < ((int)LogEventLevel.Verbose))
throw new Exception("Invalid Log Level");
ctx.Bot.loggingLevel.MinimumLevel = (LogEventLevel)Level;
_ = await this.RespondOrEdit($"`Changed LogLevel to '{Enum.GetName((LogEventLevel)Level)}'`");
});
}
}

View file

@ -0,0 +1,25 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class Quit2FASessionCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx)
=> this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
ctx.DbUser.LastSuccessful2FA = DateTime.MinValue;
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription("`Your active 2FA Session, if present, has been quit.`").AsSuccess(ctx));
});
}
}

View file

@ -0,0 +1,29 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class RawGuildCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var guild = (ulong?)arguments["guild"];
guild ??= ctx.Guild.Id;
_ = await this.RespondOrEdit(new DiscordMessageBuilder().WithFile("guild.json", JsonConvert.SerializeObject(ctx.Bot.Guilds[guild.Value], Formatting.Indented, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
}).ToStream()));
});
}
}

View file

@ -0,0 +1,35 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class StopCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var msg = await this.RespondOrEdit(new DiscordMessageBuilder().WithContent("Confirm?").AddComponents(new DiscordButtonComponent(ButtonStyle.Danger, "Shutdown", "Confirm shutdown", false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("⛔")))));
var x = await ctx.WaitForButtonAsync(TimeSpan.FromMinutes(1));
if (x.TimedOut)
{
_ = await this.RespondOrEdit("_Interaction timed out._");
return;
}
_ = await this.RespondOrEdit("Shutting down!");
await ctx.Bot.ExitApplication(true);
});
}
}

View file

@ -0,0 +1,32 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class UnbanGuildCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var guild = (ulong)arguments["guild"];
if (!ctx.Bot.bannedGuilds.ContainsKey(guild))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`Guild '{guild}' is not banned from using the bot.`").AsError(ctx));
return;
}
_ = ctx.Bot.bannedGuilds.Remove(guild);
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`Guild '{guild}' was unbanned from using the bot.`").AsSuccess(ctx));
});
}
}

View file

@ -0,0 +1,32 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Commands.DevTools;
internal sealed class UnbanUserCommand : BaseCommand
{
public override Task<bool> BeforeExecution(SharedCommandContext ctx) => this.CheckMaintenance();
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
return Task.Run(async () =>
{
var victim = (DiscordUser)arguments["victim"];
if (!ctx.Bot.bannedUsers.ContainsKey(victim.Id))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`'{victim.GetUsernameWithIdentifier()}' is not banned from using the bot.`").AsError(ctx));
return;
}
_ = ctx.Bot.bannedUsers.Remove(victim.Id);
_ = await this.RespondOrEdit(new DiscordEmbedBuilder().WithDescription($"`'{victim.GetUsernameWithIdentifier()}' was unbanned from using the bot.`").AsSuccess(ctx));
});
}
}