mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-07 14:27:54 +00:00
Merge branch 'main' into confirm-clear
This commit is contained in:
commit
17c3640fd3
25 changed files with 224 additions and 49 deletions
|
|
@ -9,6 +9,8 @@ using App.Metrics;
|
|||
|
||||
using Autofac;
|
||||
|
||||
using Dapper;
|
||||
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.Entities;
|
||||
using DSharpPlus.EventArgs;
|
||||
|
|
@ -34,18 +36,21 @@ namespace PluralKit.Bot
|
|||
private readonly PeriodicStatCollector _collector;
|
||||
private readonly IMetrics _metrics;
|
||||
private readonly ErrorMessageService _errorMessageService;
|
||||
private readonly IDatabase _db;
|
||||
|
||||
private bool _hasReceivedReady = false;
|
||||
private Timer _periodicTask; // Never read, just kept here for GC reasons
|
||||
|
||||
public Bot(DiscordShardedClient client, ILifetimeScope services, ILogger logger, PeriodicStatCollector collector, IMetrics metrics, ErrorMessageService errorMessageService)
|
||||
public Bot(DiscordShardedClient client, ILifetimeScope services, ILogger logger, PeriodicStatCollector collector, IMetrics metrics,
|
||||
ErrorMessageService errorMessageService, IDatabase db)
|
||||
{
|
||||
_client = client;
|
||||
_logger = logger.ForContext<Bot>();
|
||||
_services = services;
|
||||
_collector = collector;
|
||||
_metrics = metrics;
|
||||
_errorMessageService = errorMessageService;
|
||||
_logger = logger.ForContext<Bot>();
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public void Init()
|
||||
|
|
@ -177,6 +182,9 @@ namespace PluralKit.Bot
|
|||
|
||||
await UpdateBotStatus();
|
||||
|
||||
// Clean up message cache in postgres
|
||||
await _db.Execute(conn => conn.QueryAsync("select from cleanup_command_message()"));
|
||||
|
||||
// Collect some stats, submit them to the metrics backend
|
||||
await _collector.CollectStats();
|
||||
await Task.WhenAll(((IMetricsRoot) _metrics).ReportRunner.RunAllAsync());
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace PluralKit.Bot
|
|||
internal IDatabase Database => _db;
|
||||
internal ModelRepository Repository => _repo;
|
||||
|
||||
public Task<DiscordMessage> Reply(string text = null, DiscordEmbed embed = null, IEnumerable<IMention> mentions = null)
|
||||
public async Task<DiscordMessage> Reply(string text = null, DiscordEmbed embed = null, IEnumerable<IMention> mentions = null)
|
||||
{
|
||||
if (!this.BotHasAllPermissions(Permissions.SendMessages))
|
||||
// Will be "swallowed" during the error handler anyway, this message is never shown.
|
||||
|
|
@ -72,7 +72,12 @@ namespace PluralKit.Bot
|
|||
|
||||
if (embed != null && !this.BotHasAllPermissions(Permissions.EmbedLinks))
|
||||
throw new PKError("PluralKit does not have permission to send embeds in this channel. Please ensure I have the **Embed Links** permission enabled.");
|
||||
return Channel.SendMessageFixedAsync(text, embed: embed, mentions: mentions);
|
||||
var msg = await Channel.SendMessageFixedAsync(text, embed: embed, mentions: mentions);
|
||||
if (embed != null)
|
||||
// Sensitive information that might want to be deleted by :x: reaction is typically in an embed format (member cards, for example)
|
||||
// This may need to be changed at some point but works well enough for now
|
||||
await _db.Execute(conn => _repo.SaveCommandMessage(conn, msg.Id, Author.Id));
|
||||
return msg;
|
||||
}
|
||||
|
||||
public async Task Execute<T>(Command commandDef, Func<T, Task> handler)
|
||||
|
|
|
|||
|
|
@ -50,13 +50,13 @@ namespace PluralKit.Bot
|
|||
public static Command GroupList = new Command("group list", "group list", "Lists all groups in this system");
|
||||
public static Command GroupMemberList = new Command("group members", "group <group> list", "Lists all members in a group");
|
||||
public static Command GroupRename = new Command("group rename", "group <group> rename <new name>", "Renames a group");
|
||||
public static Command GroupDisplayName = new Command("group displayname", "group <member> displayname [display name]", "Changes a group's display name");
|
||||
public static Command GroupDisplayName = new Command("group displayname", "group <group> displayname [display name]", "Changes a group's display name");
|
||||
public static Command GroupDesc = new Command("group description", "group <group> description [description]", "Changes a group's description");
|
||||
public static Command GroupAdd = new Command("group add", "group <group> add <member> [member 2] [member 3...]", "Adds one or more members to a group");
|
||||
public static Command GroupRemove = new Command("group remove", "group <group> remove <member> [member 2] [member 3...]", "Removes one or more members from a group");
|
||||
public static Command GroupPrivacy = new Command("group privacy", "group <group> privacy <description|icon|visibility|all> <public|private>", "Changes a group's privacy settings");
|
||||
public static Command GroupDelete = new Command("group delete", "group <group> delete", "Deletes a group");
|
||||
public static Command GroupIcon = new Command("group icon", "group <group> icon [url|@mention]", "Changes a group's icon");
|
||||
public static Command GroupDelete = new Command("group delete", "group <group> delete", "Deletes a group");
|
||||
public static Command Switch = new Command("switch", "switch <member> [member 2] [member 3...]", "Registers a switch");
|
||||
public static Command SwitchOut = new Command("switch out", "switch out", "Registers a switch with no members");
|
||||
public static Command SwitchMove = new Command("switch move", "switch move <date/time>", "Moves the latest switch in time");
|
||||
|
|
@ -189,9 +189,9 @@ namespace PluralKit.Bot
|
|||
if (ctx.Match("random", "r"))
|
||||
return ctx.Execute<Member>(MemberRandom, m => m.MemberRandom(ctx));
|
||||
|
||||
ctx.Reply(
|
||||
// remove compiler warning
|
||||
return ctx.Reply(
|
||||
$"{Emojis.Error} Unknown command {ctx.PeekArgument().AsCode()}. For a list of possible commands, see <https://pluralkit.me/commands>.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task HandleSystemCommand(Context ctx)
|
||||
|
|
@ -382,7 +382,7 @@ namespace PluralKit.Bot
|
|||
await PrintCommandNotFoundError(ctx, GroupCommandsTargeted);
|
||||
}
|
||||
else if (!ctx.HasNext())
|
||||
await PrintCommandNotFoundError(ctx, GroupCommands);
|
||||
await PrintCommandExpectedError(ctx, GroupCommands);
|
||||
else
|
||||
await ctx.Reply($"{Emojis.Error} {ctx.CreateGroupNotFoundError(ctx.PopArgument())}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,9 @@ namespace PluralKit.Bot
|
|||
|
||||
// Check group cap
|
||||
var existingGroupCount = await conn.QuerySingleAsync<int>("select count(*) from groups where system = @System", new { System = ctx.System.Id });
|
||||
if (existingGroupCount >= Limits.MaxGroupCount)
|
||||
throw new PKError($"System has reached the maximum number of groups ({Limits.MaxGroupCount}). Please delete unused groups first in order to create new ones.");
|
||||
var groupLimit = ctx.System.GroupLimitOverride ?? Limits.MaxGroupCount;
|
||||
if (existingGroupCount >= groupLimit)
|
||||
throw new PKError($"System has reached the maximum number of groups ({groupLimit}). Please delete unused groups first in order to create new ones.");
|
||||
|
||||
// Warn if there's already a group by this name
|
||||
var existingGroup = await _repo.GetGroupByName(conn, ctx.System.Id, groupName);
|
||||
|
|
|
|||
|
|
@ -141,8 +141,9 @@ namespace PluralKit.Bot
|
|||
try
|
||||
{
|
||||
var dm = await ctx.Rest.CreateDmAsync(ctx.Author.Id);
|
||||
await dm.SendFileAsync("system.json", stream, $"{Emojis.Success} Here you go!");
|
||||
|
||||
var msg = await dm.SendFileAsync("system.json", stream, $"{Emojis.Success} Here you go!");
|
||||
await dm.SendMessageAsync($"<{msg.Attachments[0].Url}>");
|
||||
|
||||
// If the original message wasn't posted in DMs, send a public reminder
|
||||
if (!(ctx.Channel is DiscordDmChannel))
|
||||
await ctx.Reply($"{Emojis.Success} Check your DMs!");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dapper;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot
|
||||
|
|
@ -37,21 +39,26 @@ namespace PluralKit.Bot
|
|||
|
||||
// Enforce per-system member limit
|
||||
var memberCount = await _repo.GetSystemMemberCount(conn, ctx.System.Id);
|
||||
if (memberCount >= Limits.MaxMemberCount)
|
||||
throw Errors.MemberLimitReachedError;
|
||||
var memberLimit = ctx.System.MemberLimitOverride ?? Limits.MaxMemberCount;
|
||||
if (memberCount >= memberLimit)
|
||||
throw Errors.MemberLimitReachedError(memberLimit);
|
||||
|
||||
// Create the member
|
||||
var member = await _repo.CreateMember(conn, ctx.System.Id, memberName);
|
||||
memberCount++;
|
||||
|
||||
// Send confirmation and space hint
|
||||
await ctx.Reply($"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#members");
|
||||
await ctx.Reply($"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member");
|
||||
if (await _db.Execute(conn => conn.QuerySingleAsync<bool>("select has_private_members(@System)",
|
||||
new {System = ctx.System.Id}))) //if has private members
|
||||
await ctx.Reply($"{Emojis.Warn} This member is currently **public**. To change this, use `pk;member {member.Hid} private`.");
|
||||
|
||||
if (memberName.Contains(" "))
|
||||
await ctx.Reply($"{Emojis.Note} Note that this member's name contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it, or just use the member's 5-character ID (which is `{member.Hid}`).");
|
||||
if (memberCount >= Limits.MaxMemberCount)
|
||||
await ctx.Reply($"{Emojis.Warn} You have reached the per-system member limit ({Limits.MaxMemberCount}). You will be unable to create additional members until existing members are deleted.");
|
||||
else if (memberCount >= Limits.MaxMembersWarnThreshold)
|
||||
await ctx.Reply($"{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {Limits.MaxMemberCount} members). Please review your member list for unused or duplicate members.");
|
||||
if (memberCount >= memberLimit)
|
||||
await ctx.Reply($"{Emojis.Warn} You have reached the per-system member limit ({memberLimit}). You will be unable to create additional members until existing members are deleted.");
|
||||
else if (memberCount >= Limits.MaxMembersWarnThreshold(memberLimit))
|
||||
await ctx.Reply($"{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {memberLimit} members). Please review your member list for unused or duplicate members.");
|
||||
}
|
||||
|
||||
public async Task MemberRandom(Context ctx)
|
||||
|
|
@ -83,4 +90,4 @@ namespace PluralKit.Bot
|
|||
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild, ctx.LookupContextFor(system)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,20 +26,24 @@ namespace PluralKit.Bot
|
|||
{
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
|
||||
|
||||
DiscordChannel channel = null;
|
||||
if (ctx.MatchClear())
|
||||
{
|
||||
await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, new GuildPatch {LogChannel = null}));
|
||||
await ctx.Reply($"{Emojis.Success} Proxy logging channel cleared.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.HasNext())
|
||||
throw new PKSyntaxError("You must pass a #channel to set.");
|
||||
throw new PKSyntaxError("You must pass a #channel to set, or `clear` to clear it.");
|
||||
|
||||
DiscordChannel channel = null;
|
||||
var channelString = ctx.PeekArgument();
|
||||
channel = await ctx.MatchChannel();
|
||||
if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString);
|
||||
|
||||
var patch = new GuildPatch {LogChannel = channel?.Id};
|
||||
var patch = new GuildPatch {LogChannel = channel.Id};
|
||||
await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, patch));
|
||||
|
||||
if (channel != null)
|
||||
await ctx.Reply($"{Emojis.Success} Proxy logging channel set to #{channel.Name}.");
|
||||
else
|
||||
await ctx.Reply($"{Emojis.Success} Proxy logging channel cleared.");
|
||||
await ctx.Reply($"{Emojis.Success} Proxy logging channel set to #{channel.Name}.");
|
||||
}
|
||||
|
||||
public async Task SetLogEnabled(Context ctx, bool enable)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ namespace PluralKit.Bot
|
|||
|
||||
var msg = $"{account.Mention}, please confirm the link by clicking the {Emojis.Success} reaction on this message.";
|
||||
var mentions = new IMention[] { new UserMention(account) };
|
||||
if (!await ctx.PromptYesNo(msg, user: account, mentions: mentions)) throw Errors.MemberLinkCancelled;
|
||||
if (!await ctx.PromptYesNo(msg, user: account, mentions: mentions, matchFlag: false)) throw Errors.MemberLinkCancelled;
|
||||
await _repo.AddAccount(conn, ctx.System.Id, account.Id);
|
||||
await ctx.Reply($"{Emojis.Success} Account linked to system.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ namespace PluralKit.Bot {
|
|||
public static PKError DescriptionTooLongError(int length) => new PKError($"Description too long ({length}/{Limits.MaxDescriptionLength} characters).");
|
||||
public static PKError MemberNameTooLongError(int length) => new PKError($"Member name too long ({length}/{Limits.MaxMemberNameLength} characters).");
|
||||
public static PKError MemberPronounsTooLongError(int length) => new PKError($"Member pronouns too long ({length}/{Limits.MaxMemberNameLength} characters).");
|
||||
public static PKError MemberLimitReachedError => new PKError($"System has reached the maximum number of members ({Limits.MaxMemberCount}). Please delete unused members first in order to create new ones.");
|
||||
public static PKError MemberLimitReachedError(int limit) => new PKError($"System has reached the maximum number of members ({limit}). Please delete unused members first in order to create new ones.");
|
||||
public static PKError NoMembersError => new PKError("Your system has no members! Please create at least one member before using this command.");
|
||||
|
||||
public static PKError InvalidColorError(string color) => new PKError($"\"{color}\" is not a valid color. Color must be in 6-digit RGB hex format (eg. #ff0000).");
|
||||
|
|
|
|||
|
|
@ -48,12 +48,15 @@ namespace PluralKit.Bot
|
|||
_db.Execute(c => _repo.GetMessage(c, evt.Message.Id));
|
||||
|
||||
FullMessage msg;
|
||||
CommandMessage cmdmsg;
|
||||
switch (evt.Emoji.Name)
|
||||
{
|
||||
// Message deletion
|
||||
case "\u274C": // Red X
|
||||
if ((msg = await GetMessage()) != null)
|
||||
await HandleDeleteReaction(evt, msg);
|
||||
else if ((cmdmsg = await _db.Execute(conn => _repo.GetCommandMessage(conn, evt.Message.Id))) != null)
|
||||
await HandleCommandDeleteReaction(evt, cmdmsg);
|
||||
break;
|
||||
|
||||
case "\u2753": // Red question mark
|
||||
|
|
@ -92,6 +95,25 @@ namespace PluralKit.Bot
|
|||
await _db.Execute(c => _repo.DeleteMessage(c, evt.Message.Id));
|
||||
}
|
||||
|
||||
private async ValueTask HandleCommandDeleteReaction(MessageReactionAddEventArgs evt, CommandMessage msg)
|
||||
{
|
||||
if (!evt.Channel.BotHasAllPermissions(Permissions.ManageMessages)) return;
|
||||
|
||||
// Can only delete your own message
|
||||
if (msg.author_id != evt.User.Id) return;
|
||||
|
||||
try
|
||||
{
|
||||
await evt.Message.DeleteAsync();
|
||||
}
|
||||
catch (NotFoundException)
|
||||
{
|
||||
// Message was deleted by something/someone else before we got to it
|
||||
}
|
||||
|
||||
// No need to delete database row here, it'll get deleted by the once-per-minute scheduled task.
|
||||
}
|
||||
|
||||
private async ValueTask HandleQueryReaction(MessageReactionAddEventArgs evt, FullMessage msg)
|
||||
{
|
||||
// Try to DM the user info about the message
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ namespace PluralKit.Bot
|
|||
// It's possible to "move" a webhook to a different channel after creation
|
||||
// Here, we ensure it's actually still pointing towards the proper channel, and if not, wipe and refetch one.
|
||||
var webhook = await lazyWebhookValue.Value;
|
||||
if (webhook.ChannelId != channel.Id) return await InvalidateAndRefreshWebhook(channel, webhook);
|
||||
if (webhook.ChannelId != channel.Id && webhook.ChannelId != 0) return await InvalidateAndRefreshWebhook(channel, webhook);
|
||||
return webhook;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ namespace PluralKit.Bot {
|
|||
else return true;
|
||||
}
|
||||
|
||||
public static async Task<bool> PromptYesNo(this Context ctx, String msgString, DiscordUser user = null, Duration? timeout = null, IEnumerable<IMention> mentions = null)
|
||||
public static async Task<bool> PromptYesNo(this Context ctx, String msgString, DiscordUser user = null, Duration? timeout = null, IEnumerable<IMention> mentions = null, bool matchFlag = true)
|
||||
{
|
||||
DiscordMessage message;
|
||||
if (ctx.MatchFlag("y", "yes")) return true;
|
||||
if (matchFlag && ctx.MatchFlag("y", "yes")) return true;
|
||||
else message = await ctx.Reply(msgString, mentions: mentions);
|
||||
var cts = new CancellationTokenSource();
|
||||
if (user == null) user = ctx.Author;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ namespace PluralKit.Bot
|
|||
// Ignore "Database is shutting down" error
|
||||
if (e is PostgresException pe && pe.SqlState == "57P03") return false;
|
||||
|
||||
// Ignore thread pool exhaustion errors
|
||||
if (e is NpgsqlException npe && npe.Message.Contains("The connection pool has been exhausted")) return false;
|
||||
|
||||
// This may expanded at some point.
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue