Merge branch 'main' into feature/groups

This commit is contained in:
Ske
2020-08-21 18:31:22 +02:00
42 changed files with 359 additions and 219 deletions

View File

@@ -90,7 +90,7 @@ namespace PluralKit.Bot
// We're not actually properly disconnecting from the gateway (lol) so it'll linger for a few minutes
// Should be plenty of time for the bot to connect again next startup and set the real status
if (_hasReceivedReady)
await _client.UpdateStatusAsync(new DiscordActivity("Restarting... (please wait)"));
await _client.UpdateStatusAsync(new DiscordActivity("Restarting... (please wait)"), UserStatus.Idle);
}
private Task HandleEvent<T>(T evt) where T: DiscordEventArgs

View File

@@ -201,7 +201,7 @@ namespace PluralKit.Bot
await ctx.Execute<System>(SystemInfo, m => m.Query(ctx, ctx.System));
// First, we match own-system-only commands (ie. no target system parameter)
else if (ctx.Match("new", "create", "make", "add", "register", "init"))
else if (ctx.Match("new", "create", "make", "add", "register", "init", "n"))
await ctx.Execute<System>(SystemNew, m => m.New(ctx));
else if (ctx.Match("name", "rename", "changename"))
await ctx.Execute<SystemEdit>(SystemRename, m => m.Name(ctx));

View File

@@ -40,7 +40,8 @@ namespace PluralKit.Bot
if (ctx.MatchFlag("by-last-fronted", "by-last-front", "by-last-switch", "blf", "bls")) p.SortProperty = SortProperty.LastSwitch;
if (ctx.MatchFlag("by-last-message", "blm", "blp")) p.SortProperty = SortProperty.LastMessage;
if (ctx.MatchFlag("by-birthday", "by-birthdate", "bbd")) p.SortProperty = SortProperty.Birthdate;
if (ctx.MatchFlag("random")) p.SortProperty = SortProperty.Random;
// Sort reverse?
if (ctx.MatchFlag("r", "rev", "reverse"))
p.Reverse = true;
@@ -136,7 +137,7 @@ namespace PluralKit.Bot
profile.Append($"\n**Birthdate**: {m.BirthdayString}");
if (m.ProxyTags.Count > 0)
profile.Append($"\n**Proxy tags:** {m.ProxyTagsString()}");
profile.Append($"\n**Proxy tags**: {m.ProxyTagsString()}");
if (opts.IncludeMessageCount && m.MessageCountFor(lookupCtx) is {} count && count > 0)
profile.Append($"\n**Message count:** {count}");
@@ -161,4 +162,4 @@ namespace PluralKit.Bot
}
}
}
}
}

View File

@@ -29,7 +29,8 @@ namespace PluralKit.Bot
public string CreateFilterString()
{
var str = new StringBuilder();
str.Append("Sorting by ");
str.Append("Sorting ");
if (SortProperty != SortProperty.Random) str.Append("by ");
str.Append(SortProperty switch
{
SortProperty.Name => "member name",
@@ -40,6 +41,7 @@ namespace PluralKit.Bot
SortProperty.LastSwitch => "last switch",
SortProperty.MessageCount => "message count",
SortProperty.Birthdate => "birthday",
SortProperty.Random => "randomly",
_ => new ArgumentOutOfRangeException($"Couldn't find readable string for sort property {SortProperty}")
});
@@ -77,6 +79,8 @@ namespace PluralKit.Bot
IComparer<T> ReverseMaybe<T>(IComparer<T> c) =>
opts.Reverse ? Comparer<T>.Create((a, b) => c.Compare(b, a)) : c;
var randGen = new global::System.Random();
var culture = StringComparer.InvariantCultureIgnoreCase;
return (opts.SortProperty switch
{
@@ -98,6 +102,8 @@ namespace PluralKit.Bot
SortProperty.LastSwitch => input
.OrderByDescending(m => m.LastSwitchTime.HasValue)
.ThenByDescending(m => m.LastSwitchTime, ReverseMaybe(Comparer<Instant?>.Default)),
SortProperty.Random => input
.OrderBy(m => randGen.Next()),
_ => throw new ArgumentOutOfRangeException($"Unknown sort property {opts.SortProperty}")
})
// Lastly, add a by-name fallback order for collisions (generally hits w/ lots of null values)
@@ -114,7 +120,8 @@ namespace PluralKit.Bot
CreationDate,
LastSwitch,
LastMessage,
Birthdate
Birthdate,
Random
}
public enum ListType

View File

@@ -1,5 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using PluralKit.Core;
@@ -61,7 +62,11 @@ namespace PluralKit.Bot
//Maybe move this somewhere else in the file structure since it doesn't need to get created at every command
// TODO: don't buffer these, find something else to do ig
var members = await _data.GetSystemMembers(ctx.System).Where(m => m.MemberVisibility == PrivacyLevel.Public).ToListAsync();
List<PKMember> members;
if (ctx.MatchFlag("all", "a")) members = await _data.GetSystemMembers(ctx.System).ToListAsync();
else members = await _data.GetSystemMembers(ctx.System).Where(m => m.MemberVisibility == PrivacyLevel.Public).ToListAsync();
if (members == null || !members.Any())
throw Errors.NoMembersError;
var randInt = randGen.Next(members.Count);

View File

@@ -36,8 +36,8 @@ namespace PluralKit.Bot
// Warn if there's already a member by this name
var existingMember = await _data.GetMemberByName(ctx.System, newName);
if (existingMember != null)
if (existingMember.Id != target.Id) {
if (existingMember != null && existingMember.Id != target.Id)
{
var msg = $"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.NameFor(ctx)}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?";
if (!await ctx.PromptYesNo(msg)) throw new PKError("Member renaming cancelled.");
}
@@ -187,7 +187,7 @@ namespace PluralKit.Bot
await ctx.Reply(embed: new DiscordEmbedBuilder()
.WithTitle($"{Emojis.Success} Member color changed.")
.WithColor(color.ToDiscordColor().Value)
.WithThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")
.WithThumbnail($"https://fakeimg.pl/256x256/{color}/?text=%20")
.Build());
}
}

View File

@@ -94,27 +94,23 @@ namespace PluralKit.Bot {
public async Task PermCheckGuild(Context ctx)
{
DiscordGuild guild;
DiscordMember senderGuildUser = null;
if (ctx.Guild != null && !ctx.HasNext())
{
guild = ctx.Guild;
senderGuildUser = (DiscordMember)ctx.Author;
}
else
{
var guildIdStr = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a server ID or run this command as .");
var guildIdStr = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a server ID or run this command in a server.");
if (!ulong.TryParse(guildIdStr, out var guildId))
throw new PKSyntaxError($"Could not parse `{guildIdStr}` as an ID.");
// TODO: will this call break for sharding if you try to request a guild on a different bot instance?
guild = await ctx.Rest.GetGuild(guildId);
if (guild == null)
throw Errors.GuildNotFound(guildId);
guild = ctx.Client.GetGuild(guildId);
if (guild != null) senderGuildUser = await guild.GetMember(ctx.Author.Id);
if (guild == null || senderGuildUser == null) throw Errors.GuildNotFound(guildId);
}
// Ensure people can't query guilds they're not in + get their own permissions (for view access checking)
var senderGuildUser = await guild.GetMember(ctx.Author.Id);
if (senderGuildUser == null)
throw new PKError("You must be a member of the guild you are querying.");
var requiredPermissions = new []
{

View File

@@ -25,9 +25,11 @@ namespace PluralKit.Bot
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
DiscordChannel channel = null;
if (ctx.HasNext())
channel = await ctx.MatchChannel() ?? throw new PKSyntaxError("You must pass a #channel to set.");
if (channel != null && channel.GuildId != ctx.Guild.Id) throw new PKError("That channel is not in this server!");
if (!ctx.HasNext())
throw new PKSyntaxError("You must pass a #channel to set.");
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};
await _db.Execute(conn => conn.UpsertGuild(ctx.Guild.Id, patch));
@@ -48,8 +50,9 @@ namespace PluralKit.Bot
else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels.");
else while (ctx.HasNext())
{
var channel = await ctx.MatchChannel() ?? throw new PKSyntaxError($"Channel \"{ctx.PopArgument()}\" not found.");
if (channel.GuildId != ctx.Guild.Id) throw new PKError($"Channel {ctx.Guild.Id} is not in this server.");
var channelString = ctx.PeekArgument();
var channel = await ctx.MatchChannel();
if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString);
affectedChannels.Add(channel);
}
@@ -127,8 +130,9 @@ namespace PluralKit.Bot
else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels.");
else while (ctx.HasNext())
{
var channel = await ctx.MatchChannel() ?? throw new PKSyntaxError($"Channel \"{ctx.PopArgument()}\" not found.");
if (channel.GuildId != ctx.Guild.Id) throw new PKError($"Channel {ctx.Guild.Id} is not in this server.");
var channelString = ctx.PeekArgument();
var channel = await ctx.MatchChannel();
if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString);
affectedChannels.Add(channel);
}

View File

@@ -25,7 +25,11 @@ namespace PluralKit.Bot
{
ctx.CheckNoSystem();
var system = await _data.CreateSystem(ctx.RemainderOrNull());
var systemName = ctx.RemainderOrNull();
if (systemName != null && systemName.Length > Limits.MaxSystemNameLength)
throw Errors.SystemNameTooLongError(systemName.Length);
var system = await _data.CreateSystem(systemName);
await _data.AddAccount(system, ctx.Author.Id);
await ctx.Reply($"{Emojis.Success} Your system has been created. Type `pk;system` to view it, and type `pk;system help` for more information about commands you can use now. Now that you have that set up, check out the getting started guide on setting up members and proxies: <https://pluralkit.me/start>");
}

View File

@@ -99,7 +99,7 @@ namespace PluralKit.Bot {
public static PKError DurationParseError(string durationStr) => new PKError($"Could not parse '{durationStr}' as a valid duration. Try a format such as `30d`, `1d3h` or `20m30s`.");
public static PKError FrontPercentTimeInFuture => new PKError("Cannot get the front percent between now and a time in the future.");
public static PKError GuildNotFound(ulong guildId) => new PKError($"Guild with ID {guildId} not found.");
public static PKError GuildNotFound(ulong guildId) => new PKError($"Guild with ID {guildId} not found. Note that you must be a member of the guild you are querying.");
public static PKError DisplayNameTooLong(string displayName, int maxLength) => new PKError(
$"Display name too long ({displayName.Length} > {maxLength} characters). Use a shorter display name, or shorten your system tag.");
@@ -115,5 +115,6 @@ namespace PluralKit.Bot {
public static PKError AttachmentTooLarge => new PKError("PluralKit cannot proxy attachments over 8 megabytes (as webhooks aren't considered as having Discord Nitro) :(");
public static PKError LookupNotAllowed => new PKError("You do not have permission to access this information.");
public static PKError ChannelNotFound(string channelString) => new PKError($"Channel \"{channelString}\" not found or is not in this server.");
}
}

View File

@@ -37,8 +37,14 @@ namespace PluralKit.Bot {
if (logChannel == null || logChannel.Type != ChannelType.Text) return;
// Check bot permissions
if (!trigger.Channel.BotHasAllPermissions(Permissions.SendMessages | Permissions.EmbedLinks)) return;
if (!trigger.Channel.BotHasAllPermissions(Permissions.SendMessages | Permissions.EmbedLinks))
{
_logger.Information(
"Does not have permission to proxy log, ignoring (channel: {ChannelId}, guild: {GuildId}, bot permissions: {BotPermissions})",
ctx.LogChannel.Value, trigger.Channel.GuildId, trigger.Channel.BotPermissions());
return;
}
// Send embed!
await using var conn = await _db.Obtain();
var embed = _embed.CreateLoggedMessageEmbed(await conn.QuerySystem(ctx.SystemId.Value),
@@ -55,7 +61,7 @@ namespace PluralKit.Bot {
if (obj == null)
{
// Channel doesn't exist or we don't have permission to access it, let's remove it from the database too
_logger.Warning("Attempted to fetch missing log channel {LogChannel}, removing from database", channel);
_logger.Warning("Attempted to fetch missing log channel {LogChannel} for guild {Guild}, removing from database", channel, guild);
await using var conn = await _db.Obtain();
await conn.ExecuteAsync("update servers set log_channel = null where id = @Guild",
new {Guild = guild});

View File

@@ -67,7 +67,8 @@ namespace PluralKit.Bot
dwb.WithUsername(FixClyde(name).Truncate(80));
dwb.WithContent(content);
dwb.AddMentions(content.ParseAllMentions(allowEveryone, channel.Guild));
if (avatarUrl != null) dwb.WithAvatarUrl(avatarUrl);
if (!string.IsNullOrWhiteSpace(avatarUrl))
dwb.WithAvatarUrl(avatarUrl);
var attachmentChunks = ChunkAttachmentsOrThrow(attachments, 8 * 1024 * 1024);
if (attachmentChunks.Count > 0)

View File

@@ -212,7 +212,7 @@ namespace PluralKit.Bot
public static string EscapeBacktickPair(this string input){
Regex doubleBacktick = new Regex(@"``", RegexOptions.Multiline);
//Run twice to catch any pairs that are created from the first pass, pairs shouldn't be created in the second as they are created from odd numbers of backticks, even numbers are all caught on the first pass
if(input != null) return doubleBacktick.Replace(doubleBacktick.Replace(input, @"` `"),@"` `");
if(input != null) return doubleBacktick.Replace(doubleBacktick.Replace(input, "`\ufeff`"),"`\ufeff`");
else return input;
}
@@ -271,8 +271,8 @@ namespace PluralKit.Bot
{
// we need to know the channel's guild ID to get the cached guild object, so we grab it from the API
if (guildId == null) {
var guild = await WrapDiscordCall(client.ShardClients.Values.FirstOrDefault().GetChannelAsync(id));
if (guild != null) guildId = guild.Id;
var channel = await WrapDiscordCall(client.ShardClients.Values.FirstOrDefault().GetChannelAsync(id));
if (channel != null) guildId = channel.GuildId;
else return null; // we probably don't have the guild in cache if the API doesn't give it to us
}
return client.GetGuild(guildId.Value).GetChannel(id);
@@ -326,4 +326,4 @@ namespace PluralKit.Bot
return $"<{match.Value}>";
});
}
}
}