Merge branch 'main' into confirm-clear

This commit is contained in:
Astrid 2020-10-23 11:14:36 +02:00 committed by GitHub
commit 17c3640fd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 224 additions and 49 deletions

View file

@ -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());

View file

@ -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)

View file

@ -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())}");
}

View file

@ -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);

View file

@ -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!");

View file

@ -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)));
}
}
}
}

View file

@ -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)

View file

@ -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.");
}

View file

@ -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).");

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}