Fix Build Errors

This commit is contained in:
Fennel 2020-04-24 15:50:28 -04:00 committed by Astrid
parent 23cf06df4c
commit c99784b9dc
19 changed files with 301 additions and 136 deletions

View File

@ -6,12 +6,11 @@ using App.Metrics;
using Autofac;
using Discord;
using Discord.WebSocket;
using DSharpPlus;
using DSharpPlus.Entities;
using PluralKit.Bot.Utils;
using PluralKit.Core;
namespace PluralKit.Bot
@ -20,6 +19,7 @@ namespace PluralKit.Bot
{
private ILifetimeScope _provider;
private readonly DiscordRestClient _rest;
private readonly DiscordShardedClient _client;
private readonly DiscordClient _shard;
private readonly DiscordMessage _message;
@ -34,6 +34,7 @@ namespace PluralKit.Bot
public Context(ILifetimeScope provider, DiscordClient shard, DiscordMessage message, int commandParseOffset,
PKSystem senderSystem)
{
_rest = provider.Resolve<DiscordRestClient>();
_client = provider.Resolve<DiscordShardedClient>();
_message = message;
_shard = shard;
@ -50,6 +51,9 @@ namespace PluralKit.Bot
public DiscordGuild Guild => _message.Channel.Guild;
public DiscordClient Shard => _shard;
public DiscordShardedClient Client => _client;
public DiscordRestClient Rest => _rest;
public PKSystem System => _senderSystem;
public string PopArgument() => _parameters.Pop();
@ -280,10 +284,11 @@ namespace PluralKit.Bot
public DiscordChannel MatchChannel()
{
if (!MentionUtils.TryParseChannel(PeekArgument(), out var channel)) return null;
if (!(_client.GetChannelAsync(channel) is ITextChannel textChannel)) return null;
var discordChannel = _rest.GetChannelAsync(channel).GetAwaiter().GetResult();
if (discordChannel.Type != ChannelType.Text) return null;
PopArgument();
return textChannel;
return null;// return textChannel;
}
}
}

View File

@ -1,8 +1,8 @@
using System;
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using DSharpPlus.Entities;
using PluralKit.Core;
@ -96,12 +96,12 @@ namespace PluralKit.Bot
await ctx.Reply($"{Emojis.Success} Autoproxy set to **{member.Name}** in this server.");
}
private async Task<Embed> CreateAutoproxyStatusEmbed(Context ctx)
private async Task<DiscordEmbed> CreateAutoproxyStatusEmbed(Context ctx)
{
var settings = await _data.GetSystemGuildSettings(ctx.System, ctx.Guild.Id);
var commandList = "**pk;autoproxy latch** - Autoproxies as last-proxied member\n**pk;autoproxy front** - Autoproxies as current (first) fronter\n**pk;autoproxy <member>** - Autoproxies as a specific member";
var eb = new EmbedBuilder().WithTitle($"Current autoproxy status (for {ctx.Guild.Name.EscapeMarkdown()})");
var eb = new DiscordEmbedBuilder().WithTitle($"Current autoproxy status (for {ctx.Guild.Name.EscapeMarkdown()})");
switch (settings.AutoproxyMode) {
case AutoproxyMode.Off: eb.WithDescription($"Autoproxy is currently **off** in this server. To enable it, use one of the following commands:\n{commandList}");

View File

@ -1,7 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using Discord.WebSocket;
using DSharpPlus;
using PluralKit.Core;
@ -81,6 +81,7 @@ namespace PluralKit.Bot
public CommandTree(DiscordShardedClient client)
{
_client = client;
}
@ -345,7 +346,7 @@ namespace PluralKit.Bot
{
// Try to resolve the user ID to find the associated account,
// so we can print their username.
var user = await _client.Rest.GetUserAsync(id);
var user = await ctx.Rest.GetUserAsync(id);
// Print descriptive errors based on whether we found the user or not.
if (user == null)

View File

@ -1,6 +1,6 @@
using System.Threading.Tasks;
using Discord;
using DSharpPlus.Entities;
using PluralKit.Core;
@ -10,7 +10,7 @@ namespace PluralKit.Bot
{
public async Task HelpRoot(Context ctx)
{
await ctx.Reply(embed: new EmbedBuilder()
await ctx.Reply(embed: new DiscordEmbedBuilder()
.WithTitle("PluralKit")
.WithDescription("PluralKit is a bot designed for plural communities on Discord. It allows you to register systems, maintain system information, set up message proxying, log switches, and more.")
.AddField("What is this for? What are systems?", "This bot detects messages with certain tags associated with a profile, then replaces that message under a \"pseudo-account\" of that profile using webhooks. This is useful for multiple people sharing one body (aka \"systems\"), people who wish to roleplay as different characters without having several accounts, or anyone else who may want to post messages as a different person from the same account.")
@ -20,7 +20,7 @@ namespace PluralKit.Bot
.AddField("More information", "For a full list of commands, see [the command list](https://pluralkit.me/commands).\nFor a more in-depth explanation of message proxying, see [the documentation](https://pluralkit.me/guide#proxying).\nIf you're an existing user of Tupperbox, type `pk;import` and attach a Tupperbox export file (from `tul!export`) to import your data from there.")
.AddField("Support server", "We also have a Discord server for support, discussion, suggestions, announcements, etc: https://discord.gg/PczBt78")
.WithFooter("By @Ske#6201 | GitHub: https://github.com/xSke/PluralKit/ | Website: https://pluralkit.me/")
.WithColor(Color.Blue)
.WithColor(DiscordColor.Blue)
.Build());
}
}

View File

@ -5,10 +5,9 @@ using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Net;
using Newtonsoft.Json;
using DSharpPlus.Exceptions;
using DSharpPlus.Entities;
using PluralKit.Core;
@ -134,13 +133,14 @@ namespace PluralKit.Bot
try
{
await ctx.Author.SendFileAsync(stream, "system.json", $"{Emojis.Success} Here you go!");
var dm = await ctx.Rest.CreateDmAsync(ctx.Author.Id);
await dm.SendFileAsync("system.json", stream, $"{Emojis.Success} Here you go!");
// If the original message wasn't posted in DMs, send a public reminder
if (!(ctx.Channel is IDMChannel))
if (!(ctx.Channel is DiscordDmChannel))
await ctx.Reply($"{Emojis.Success} Check your DMs!");
}
catch (HttpException)
catch (UnauthorizedException)
{
// If user has DMs closed, tell 'em to open them
await ctx.Reply(

View File

@ -1,7 +1,8 @@
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using DSharpPlus;
using DSharpPlus.Entities;
using PluralKit.Core;
@ -39,7 +40,7 @@ namespace PluralKit.Bot
{
if ((target.AvatarUrl?.Trim() ?? "").Length > 0)
{
var eb = new EmbedBuilder()
var eb = new DiscordEmbedBuilder()
.WithTitle($"{target.Name.SanitizeMentions()}'s avatar")
.WithImageUrl(target.AvatarUrl);
if (target.System == ctx.System?.Id)
@ -55,18 +56,17 @@ namespace PluralKit.Bot
return;
}
var user = await ctx.MatchUser();
if (ctx.System == null) throw Errors.NoSystemError;
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
else if (await ctx.MatchUser() is IUser user)
else if (user != null)
{
if (user.AvatarId == null) throw Errors.UserHasNoAvatar;
if (user.AvatarUrl == user.DefaultAvatarUrl) throw Errors.UserHasNoAvatar; //TODO: is this necessary?
target.AvatarUrl = user.GetAvatarUrl(ImageFormat.Png, size: 256);
await _data.SaveMember(target);
var embed = new EmbedBuilder().WithImageUrl(target.AvatarUrl).Build();
var embed = new DiscordEmbedBuilder().WithImageUrl(target.AvatarUrl).Build();
await ctx.Reply(
$"{Emojis.Success} Member avatar changed to {user.Username}'s avatar! {Emojis.Warn} Please note that if {user.Username} changes their avatar, the member's avatar will need to be re-set.", embed: embed);
}
@ -76,10 +76,10 @@ namespace PluralKit.Bot
target.AvatarUrl = url;
await _data.SaveMember(target);
var embed = new EmbedBuilder().WithImageUrl(url).Build();
var embed = new DiscordEmbedBuilder().WithImageUrl(url).Build();
await ctx.Reply($"{Emojis.Success} Member avatar changed.", embed: embed);
}
else if (ctx.Message.Attachments.FirstOrDefault() is Attachment attachment)
else if (ctx.Message.Attachments.FirstOrDefault() is DiscordAttachment attachment)
{
await AvatarUtils.VerifyAvatarOrThrow(attachment.Url);
target.AvatarUrl = attachment.Url;
@ -113,7 +113,7 @@ namespace PluralKit.Bot
{
if ((guildData.AvatarUrl?.Trim() ?? "").Length > 0)
{
var eb = new EmbedBuilder()
var eb = new DiscordEmbedBuilder()
.WithTitle($"{target.Name.SanitizeMentions()}'s server avatar (for {ctx.Guild.Name})")
.WithImageUrl(guildData.AvatarUrl);
if (target.System == ctx.System?.Id)
@ -125,17 +125,17 @@ namespace PluralKit.Bot
return;
}
var user = await ctx.MatchUser();
if (ctx.System == null) throw Errors.NoSystemError;
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
if (await ctx.MatchUser() is IUser user)
if (user != null)
{
if (user.AvatarId == null) throw Errors.UserHasNoAvatar;
if (user.AvatarUrl == user.DefaultAvatarUrl) throw Errors.UserHasNoAvatar;
guildData.AvatarUrl = user.GetAvatarUrl(ImageFormat.Png, size: 256);
await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildData);
var embed = new EmbedBuilder().WithImageUrl(guildData.AvatarUrl).Build();
var embed = new DiscordEmbedBuilder().WithImageUrl(guildData.AvatarUrl).Build();
await ctx.Reply(
$"{Emojis.Success} Member server avatar changed to {user.Username}'s avatar! This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**). {Emojis.Warn} Please note that if {user.Username} changes their avatar, the member's server avatar will need to be re-set.", embed: embed);
}
@ -145,10 +145,10 @@ namespace PluralKit.Bot
guildData.AvatarUrl = url;
await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildData);
var embed = new EmbedBuilder().WithImageUrl(url).Build();
var embed = new DiscordEmbedBuilder().WithImageUrl(url).Build();
await ctx.Reply($"{Emojis.Success} Member server avatar changed. This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**).", embed: embed);
}
else if (ctx.Message.Attachments.FirstOrDefault() is Attachment attachment)
else if (ctx.Message.Attachments.FirstOrDefault() is DiscordAttachment attachment)
{
await AvatarUtils.VerifyAvatarOrThrow(attachment.Url);
guildData.AvatarUrl = attachment.Url;

View File

@ -1,8 +1,8 @@
using System;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Discord;
using DSharpPlus.Entities;
using NodaTime;
@ -86,7 +86,7 @@ namespace PluralKit.Bot
else if (ctx.MatchFlag("r", "raw"))
await ctx.Reply($"```\n{target.Description.SanitizeMentions()}\n```");
else
await ctx.Reply(embed: new EmbedBuilder()
await ctx.Reply(embed: new DiscordEmbedBuilder()
.WithTitle("Member description")
.WithDescription(target.Description)
.AddField("\u200B", $"To print the description with formatting, type `pk;member {target.Hid} description -raw`."
@ -163,7 +163,7 @@ namespace PluralKit.Bot
else
await ctx.Reply("This member does not have a color set.");
else
await ctx.Reply(embed: new EmbedBuilder()
await ctx.Reply(embed: new DiscordEmbedBuilder()
.WithTitle("Member color")
.WithColor(target.Color.ToDiscordColor().Value)
.WithThumbnailUrl($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")
@ -180,7 +180,7 @@ namespace PluralKit.Bot
target.Color = color.ToLower();
await _data.SaveMember(target);
await ctx.Reply(embed: new EmbedBuilder()
await ctx.Reply(embed: new DiscordEmbedBuilder()
.WithTitle($"{Emojis.Success} Member color changed.")
.WithColor(target.Color.ToDiscordColor().Value)
.WithThumbnailUrl($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")
@ -220,13 +220,13 @@ namespace PluralKit.Bot
}
}
private async Task<EmbedBuilder> CreateMemberNameInfoEmbed(Context ctx, PKMember target)
private async Task<DiscordEmbedBuilder> CreateMemberNameInfoEmbed(Context ctx, PKMember target)
{
MemberGuildSettings memberGuildConfig = null;
if (ctx.Guild != null)
memberGuildConfig = await _data.GetMemberGuildSettings(target, ctx.Guild.Id);
var eb = new EmbedBuilder().WithTitle($"Member names")
var eb = new DiscordEmbedBuilder().WithTitle($"Member names")
.WithFooter($"Member ID: {target.Hid} | Active name in bold. Server name overrides display name, which overrides base name.");
if (target.DisplayName == null && memberGuildConfig?.DisplayName == null)

View File

@ -3,16 +3,18 @@ using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Net.WebSockets;
using App.Metrics;
using Discord;
using DSharpPlus;
using Humanizer;
using NodaTime;
using PluralKit.Core;
using DSharpPlus.Entities;
namespace PluralKit.Bot {
public class Misc
@ -36,18 +38,16 @@ namespace PluralKit.Bot {
public async Task Invite(Context ctx)
{
var clientId = _botConfig.ClientId ?? (await ctx.Client.GetApplicationInfoAsync()).Id;
var permissions = new GuildPermissions(
addReactions: true,
attachFiles: true,
embedLinks: true,
manageMessages: true,
manageWebhooks: true,
readMessageHistory: true,
sendMessages: true
);
var invite = $"https://discordapp.com/oauth2/authorize?client_id={clientId}&scope=bot&permissions={permissions.RawValue}";
var clientId = _botConfig.ClientId ?? ctx.Client.CurrentApplication.Id;
var permissions = new Permissions()
.Grant(Permissions.AddReactions)
.Grant(Permissions.AttachFiles)
.Grant(Permissions.EmbedLinks)
.Grant(Permissions.ManageMessages)
.Grant(Permissions.ManageWebhooks)
.Grant(Permissions.ReadMessageHistory)
.Grant(Permissions.SendMessages);
var invite = $"https://discordapp.com/oauth2/authorize?client_id={clientId}&scope=bot&permissions={(long)permissions}";
await ctx.Reply($"{Emojis.Success} Use this link to add PluralKit to your server:\n<{invite}>");
}
@ -65,8 +65,8 @@ namespace PluralKit.Bot {
var totalMessages = _metrics.Snapshot.GetForContext("Application").Gauges.First(m => m.MultidimensionalName == CoreMetrics.MessageCount.Name).Value;
var shardId = ctx.Shard.ShardId;
var shardTotal = ctx.Client.Shards.Count;
var shardUpTotal = ctx.Client.Shards.Select(s => s.ConnectionState == ConnectionState.Connected).Count();
var shardTotal = ctx.Client.ShardClients.Count;
var shardUpTotal = ctx.Client.ShardClients.Where(x => x.Value.IsConnected()).Count();
var shardInfo = _shards.GetShardInfo(ctx.Shard);
var process = Process.GetCurrentProcess();
@ -74,7 +74,7 @@ namespace PluralKit.Bot {
var shardUptime = SystemClock.Instance.GetCurrentInstant() - shardInfo.LastConnectionTime;
var embed = new EmbedBuilder()
var embed = new DiscordEmbedBuilder()
.AddField("Messages processed", $"{messagesReceived.OneMinuteRate * 60:F1}/m ({messagesReceived.FifteenMinuteRate * 60:F1}/m over 15m)", true)
.AddField("Messages proxied", $"{messagesProxied.OneMinuteRate * 60:F1}/m ({messagesProxied.FifteenMinuteRate * 60:F1}/m over 15m)", true)
.AddField("Commands executed", $"{commandsRun.OneMinuteRate * 60:F1}/m ({commandsRun.FifteenMinuteRate * 60:F1}/m over 15m)", true)
@ -84,17 +84,12 @@ namespace PluralKit.Bot {
.AddField("Memory usage", $"{memoryUsage / 1024 / 1024} MiB", true)
.AddField("Latency", $"API: {(msg.Timestamp - ctx.Message.Timestamp).TotalMilliseconds:F0} ms, shard: {shardInfo.ShardLatency} ms", true)
.AddField("Total numbers", $"{totalSystems:N0} systems, {totalMembers:N0} members, {totalSwitches:N0} switches, {totalMessages:N0} messages");
await msg.ModifyAsync(f =>
{
f.Content = "";
f.Embed = embed.Build();
});
await msg.ModifyAsync("", embed.Build());
}
public async Task PermCheckGuild(Context ctx)
{
IGuild guild;
DiscordGuild guild;
if (ctx.Guild != null && !ctx.HasNext())
{
@ -107,51 +102,52 @@ namespace PluralKit.Bot {
throw new PKSyntaxError($"Could not parse `{guildIdStr.SanitizeMentions()}` as an ID.");
// TODO: will this call break for sharding if you try to request a guild on a different bot instance?
guild = ctx.Client.GetGuild(guildId);
guild = await ctx.Rest.GetGuildAsync(guildId);
if (guild == null)
throw Errors.GuildNotFound(guildId);
}
var requiredPermissions = new []
{
ChannelPermission.ViewChannel,
ChannelPermission.SendMessages,
ChannelPermission.AddReactions,
ChannelPermission.AttachFiles,
ChannelPermission.EmbedLinks,
ChannelPermission.ManageMessages,
ChannelPermission.ManageWebhooks
Permissions.AccessChannels,
Permissions.SendMessages,
Permissions.AddReactions,
Permissions.AttachFiles,
Permissions.EmbedLinks,
Permissions.ManageMessages,
Permissions.ManageWebhooks
};
// Loop through every channel and group them by sets of permissions missing
var permissionsMissing = new Dictionary<ulong, List<ITextChannel>>();
foreach (var channel in await guild.GetTextChannelsAsync())
var permissionsMissing = new Dictionary<ulong, List<DiscordChannel>>();
var guildTextChannels = (await guild.GetChannelsAsync()).Where(x => x.Type == ChannelType.Text);
foreach (var channel in guildTextChannels)
{
// TODO: do we need to hide channels here to prevent info-leaking?
var perms = channel.PermissionsIn();
var perms = await channel.PermissionsIn(ctx.Client.CurrentUser);
// We use a bitfield so we can set individual permission bits in the loop
ulong missingPermissionField = 0;
foreach (var requiredPermission in requiredPermissions)
if (!perms.Has(requiredPermission))
if (!perms.HasPermission(requiredPermission))
missingPermissionField |= (ulong) requiredPermission;
// If we're not missing any permissions, don't bother adding it to the dict
// This means we can check if the dict is empty to see if all channels are proxyable
if (missingPermissionField != 0)
{
permissionsMissing.TryAdd(missingPermissionField, new List<ITextChannel>());
permissionsMissing.TryAdd(missingPermissionField, new List<DiscordChannel>());
permissionsMissing[missingPermissionField].Add(channel);
}
}
// Generate the output embed
var eb = new EmbedBuilder()
var eb = new DiscordEmbedBuilder()
.WithTitle($"Permission check for **{guild.Name.SanitizeMentions()}**");
if (permissionsMissing.Count == 0)
{
eb.WithDescription($"No errors found, all channels proxyable :)").WithColor(Color.Green);
eb.WithDescription($"No errors found, all channels proxyable :)").WithColor(DiscordColor.Green);
}
else
{
@ -159,15 +155,13 @@ namespace PluralKit.Bot {
{
// Each missing permission field can have multiple missing channels
// so we extract them all and generate a comma-separated list
var missingPermissionNames = string.Join(", ", new ChannelPermissions(missingPermissionField)
.ToList()
.Select(perm => perm.Humanize().Transform(To.TitleCase)));
var missingPermissionNames = ((Permissions)missingPermissionField).ToPermissionString();
var channelsList = string.Join("\n", channels
.OrderBy(c => c.Position)
.Select(c => $"#{c.Name}"));
eb.AddField($"Missing *{missingPermissionNames}*", channelsList.Truncate(1000));
eb.WithColor(Color.Red);
eb.WithColor(DiscordColor.Red);
}
}
@ -189,7 +183,7 @@ namespace PluralKit.Bot {
var message = await _data.GetMessage(messageId);
if (message == null) throw Errors.MessageNotFound(messageId);
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message));
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(ctx.Shard, message)); //TODO: test this
}
}
}

View File

@ -2,7 +2,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using DSharpPlus;
using DSharpPlus.Entities;
using PluralKit.Core;
@ -20,12 +21,13 @@ namespace PluralKit.Bot
public async Task SetLogChannel(Context ctx)
{
ctx.CheckGuildContext().CheckAuthorPermission(GuildPermission.ManageGuild, "Manage Server");
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
ITextChannel channel = null;
DiscordChannel channel = null;
if (ctx.HasNext())
channel = 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 (channel != null && channel.GuildId != ctx.Guild.Id) throw new PKError("That channel is not in this server!");
if (channel.Type != ChannelType.Text) throw new PKError("The logging channel must be a text channel."); //TODO: test this
var cfg = await _data.GetOrCreateGuildConfig(ctx.Guild.Id);
cfg.LogChannel = channel?.Id;
@ -39,15 +41,16 @@ namespace PluralKit.Bot
public async Task SetLogEnabled(Context ctx, bool enable)
{
ctx.CheckGuildContext().CheckAuthorPermission(GuildPermission.ManageGuild, "Manage Server");
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
var affectedChannels = new List<ITextChannel>();
var affectedChannels = new List<DiscordChannel>();
if (ctx.Match("all"))
affectedChannels = (await ctx.Guild.GetChannelsAsync()).OfType<ITextChannel>().ToList();
affectedChannels = (await ctx.Guild.GetChannelsAsync()).Where(x => x.Type == ChannelType.Text).ToList();
else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels.");
else while (ctx.HasNext())
{
if (!(ctx.MatchChannel() is ITextChannel channel))
var channel = ctx.MatchChannel(); //TODO: test this
if (channel.Type != ChannelType.Text)
throw new PKSyntaxError($"Channel \"{ctx.PopArgument().SanitizeMentions()}\" not found.");
if (channel.GuildId != ctx.Guild.Id) throw new PKError($"Channel {ctx.Guild.Id} is not in this server.");
affectedChannels.Add(channel);
@ -65,15 +68,16 @@ namespace PluralKit.Bot
public async Task SetBlacklisted(Context ctx, bool onBlacklist)
{
ctx.CheckGuildContext().CheckAuthorPermission(GuildPermission.ManageGuild, "Manage Server");
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
var affectedChannels = new List<ITextChannel>();
var affectedChannels = new List<DiscordChannel>();
if (ctx.Match("all"))
affectedChannels = (await ctx.Guild.GetChannelsAsync()).OfType<ITextChannel>().ToList();
affectedChannels = (await ctx.Guild.GetChannelsAsync()).Where(x => x.Type == ChannelType.Text).ToList();
else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels.");
else while (ctx.HasNext())
{
if (!(ctx.MatchChannel() is ITextChannel channel))
var channel = ctx.MatchChannel(); //TODO: test this
if (channel.Type != ChannelType.Text)
throw new PKSyntaxError($"Channel \"{ctx.PopArgument().SanitizeMentions()}\" not found.");
if (channel.GuildId != ctx.Guild.Id) throw new PKError($"Channel {ctx.Guild.Id} is not in this server.");
affectedChannels.Add(channel);
@ -89,7 +93,7 @@ namespace PluralKit.Bot
public async Task SetLogCleanup(Context ctx)
{
ctx.CheckGuildContext().CheckAuthorPermission(GuildPermission.ManageGuild, "Manage Server");
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
var guildCfg = await _data.GetOrCreateGuildConfig(ctx.Guild.Id);
var botList = string.Join(", ", _cleanService.Bots.Select(b => b.Name).OrderBy(x => x.ToLowerInvariant()));
@ -108,7 +112,7 @@ namespace PluralKit.Bot
}
else
{
var eb = new EmbedBuilder()
var eb = new DiscordEmbedBuilder()
.WithTitle("Log cleanup settings")
.AddField("Supported bots", botList);

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using DSharpPlus.Entities;
using NodaTime;
using NodaTime.TimeZones;
@ -140,7 +140,7 @@ namespace PluralKit.Bot
var lastSwitchMemberStr = string.Join(", ", await lastSwitchMembers.Select(m => m.Name).ToListAsync());
var lastSwitchDeltaStr = DateTimeFormats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[0].Timestamp);
IUserMessage msg;
DiscordMessage msg;
if (lastTwoSwitches.Count == 1)
{
msg = await ctx.Reply(

View File

@ -18,9 +18,9 @@ namespace PluralKit.Bot
public async Task Query(Context ctx, PKSystem system) {
if (system == null) throw Errors.NoSystemError;
await ctx.Reply(embed: await _embeds.CreateSystemEmbed(system, ctx.LookupContextFor(system)));
await ctx.Reply(embed: await _embeds.CreateSystemEmbed(ctx.Shard, system, ctx.LookupContextFor(system)));
}
public async Task New(Context ctx)
{
ctx.CheckNoSystem();

View File

@ -1,8 +1,9 @@
using System;
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using DSharpPlus;
using DSharpPlus.Entities;
using NodaTime;
using NodaTime.Text;
@ -70,7 +71,7 @@ namespace PluralKit.Bot
else if (ctx.MatchFlag("r", "raw"))
await ctx.Reply($"```\n{ctx.System.Description.SanitizeMentions()}\n```");
else
await ctx.Reply(embed: new EmbedBuilder()
await ctx.Reply(embed: new DiscordEmbedBuilder()
.WithTitle("System description")
.WithDescription(ctx.System.Description)
.WithFooter("To print the description with formatting, type `pk;s description -raw`. To clear it, type `pk;s description -clear`. To change it, type `pk;s description <new description>`.")
@ -128,7 +129,7 @@ namespace PluralKit.Bot
{
if ((ctx.System.AvatarUrl?.Trim() ?? "").Length > 0)
{
var eb = new EmbedBuilder()
var eb = new DiscordEmbedBuilder()
.WithTitle($"System avatar")
.WithImageUrl(ctx.System.AvatarUrl)
.WithDescription($"To clear, use `pk;system avatar clear`.");
@ -143,11 +144,11 @@ namespace PluralKit.Bot
var member = await ctx.MatchUser();
if (member != null)
{
if (member.AvatarId == null) throw Errors.UserHasNoAvatar;
if (member.AvatarUrl == member.DefaultAvatarUrl) throw Errors.UserHasNoAvatar;
ctx.System.AvatarUrl = member.GetAvatarUrl(ImageFormat.Png, size: 256);
await _data.SaveSystem(ctx.System);
var embed = new EmbedBuilder().WithImageUrl(ctx.System.AvatarUrl).Build();
var embed = new DiscordEmbedBuilder().WithImageUrl(ctx.System.AvatarUrl).Build();
await ctx.Reply(
$"{Emojis.Success} System avatar changed to {member.Username}'s avatar! {Emojis.Warn} Please note that if {member.Username} changes their avatar, the system's avatar will need to be re-set.", embed: embed);
}
@ -160,7 +161,7 @@ namespace PluralKit.Bot
ctx.System.AvatarUrl = url;
await _data.SaveSystem(ctx.System);
var embed = url != null ? new EmbedBuilder().WithImageUrl(url).Build() : null;
var embed = url != null ? new DiscordEmbedBuilder().WithImageUrl(url).Build() : null;
await ctx.Reply($"{Emojis.Success} System avatar changed.", embed: embed);
}
}
@ -249,7 +250,7 @@ namespace PluralKit.Bot
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
};
var eb = new EmbedBuilder()
var eb = new DiscordEmbedBuilder()
.WithTitle("Current privacy settings for your system")
.AddField("Description", PrivacyLevelString(ctx.System.DescriptionPrivacy))
.AddField("Member list", PrivacyLevelString(ctx.System.MemberListPrivacy))

View File

@ -1,7 +1,8 @@
using System.Linq;
using ArgumentException = System.ArgumentException;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using DSharpPlus.Entities;
using NodaTime;
@ -88,9 +89,14 @@ namespace PluralKit.Bot
stringToAdd =
$"**{membersStr}** ({DateTimeFormats.ZonedDateTimeFormat.Format(sw.Timestamp.InZone(system.Zone))}, {DateTimeFormats.DurationFormat.Format(switchSince)} ago)\n";
}
if (outputStr.Length + stringToAdd.Length > EmbedBuilder.MaxDescriptionLength) break;
outputStr += stringToAdd;
try // Unfortunately the only way to test DiscordEmbedBuilder.Description max length is this
{
builder.Description += stringToAdd;
}
catch (ArgumentException)
{
break;
}// TODO: Make sure this works
}
builder.Description = outputStr;

View File

@ -1,9 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using DSharpPlus.Entities;
using Humanizer;
@ -21,7 +21,7 @@ namespace PluralKit.Bot
}
private async Task RenderMemberList(Context ctx, PKSystem system, bool canShowPrivate, int membersPerPage, string embedTitle, Func<PKMember, bool> filter,
Func<EmbedBuilder, IEnumerable<PKMember>, Task>
Func<DiscordEmbedBuilder, IEnumerable<PKMember>, Task>
renderer)
{
var authCtx = ctx.LookupContextFor(system);
@ -54,7 +54,7 @@ namespace PluralKit.Bot
});
}
private Task ShortRenderer(EmbedBuilder eb, IEnumerable<PKMember> members)
private Task ShortRenderer(DiscordEmbedBuilder eb, IEnumerable<PKMember> members)
{
eb.Description = string.Join("\n", members.Select((m) =>
{
@ -73,7 +73,7 @@ namespace PluralKit.Bot
return Task.CompletedTask;
}
private Task LongRenderer(EmbedBuilder eb, IEnumerable<PKMember> members)
private Task LongRenderer(DiscordEmbedBuilder eb, IEnumerable<PKMember> members)
{
foreach (var m in members)
{

View File

@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Discord;
using DSharpPlus;
using DSharpPlus.Entities;
using PluralKit.Core;
@ -22,14 +23,15 @@ namespace PluralKit.Bot
var token = ctx.System.Token ?? await MakeAndSetNewToken(ctx.System);
// If we're not already in a DM, reply with a reminder to check
if (!(ctx.Channel is IDMChannel))
if (!(ctx.Channel is DiscordDmChannel))
{
await ctx.Reply($"{Emojis.Success} Check your DMs!");
}
// DM the user a security disclaimer, and then the token in a separate message (for easy copying on mobile)
await ctx.Author.SendMessageAsync($"{Emojis.Warn} Please note that this grants access to modify (and delete!) all your system data, so keep it safe and secure. If it leaks or you need a new one, you can invalidate this one with `pk;token refresh`.\n\nYour token is below:");
await ctx.Author.SendMessageAsync(token);
var dm = await ctx.Rest.CreateDmAsync(ctx.Author.Id);
await dm.SendMessageAsync($"{Emojis.Warn} Please note that this grants access to modify (and delete!) all your system data, so keep it safe and secure. If it leaks or you need a new one, you can invalidate this one with `pk;token refresh`.\n\nYour token is below:");
await dm.SendMessageAsync(token);
}
private async Task<string> MakeAndSetNewToken(PKSystem system)
@ -55,14 +57,15 @@ namespace PluralKit.Bot
var token = await MakeAndSetNewToken(ctx.System);
// If we're not already in a DM, reply with a reminder to check
if (!(ctx.Channel is IDMChannel))
if (!(ctx.Channel is DiscordDmChannel))
{
await ctx.Reply($"{Emojis.Success} Check your DMs!");
}
// DM the user an invalidation disclaimer, and then the token in a separate message (for easy copying on mobile)
await ctx.Author.SendMessageAsync($"{Emojis.Warn} Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\n\nYour token is below:");
await ctx.Author.SendMessageAsync(token);
var dm = await ctx.Rest.CreateDmAsync(ctx.Author.Id);
await dm.SendMessageAsync($"{Emojis.Warn} Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\n\nYour token is below:");
await dm.SendMessageAsync(token);
}
}
}

View File

@ -0,0 +1,23 @@
using DSharpPlus;
using System.Net.WebSockets;
namespace PluralKit.Bot
{
static class Extensions
{
//Unfortunately D#+ doesn't expose the connection state of the client, so we have to test for it instead
public static bool IsConnected(this DiscordClient client)
{
try
{
client.GetConnectionsAsync().GetAwaiter().GetResult();
}
catch(WebSocketException)
{
return false;
}
return true;
}
}
}

View File

@ -15,14 +15,20 @@ namespace PluralKit.Bot
{
protected override void Load(ContainerBuilder builder)
{
// Client
// Clients
builder.Register(c => new DiscordShardedClient(new DiscordConfiguration
{
Token = c.Resolve<BotConfig>().Token,
TokenType = TokenType.Bot,
MessageCacheSize = 0,
})).AsSelf().SingleInstance();
builder.Register(c => new DiscordRestClient(new DiscordConfiguration
{
Token = c.Resolve<BotConfig>().Token,
TokenType = TokenType.Bot,
MessageCacheSize = 0,
})).AsSelf().SingleInstance();
// Commands
builder.RegisterType<CommandTree>().AsSelf();
builder.RegisterType<Autoproxy>().AsSelf();

View File

@ -10,7 +10,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="DSharpPlus" Version="4.0.0-nightly-00684" />
<PackageReference Include="DSharpPlus" Version="4.0.0-nightly-00686" />
<PackageReference Include="DSharpPlus.Rest" Version="4.0.0-nightly-00686" />
<PackageReference Include="Humanizer.Core" Version="2.7.9" />
<PackageReference Include="Sentry" Version="2.0.0-beta7" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0007" />

View File

@ -0,0 +1,121 @@
using System;
using System.Globalization;
using System.Text;
namespace PluralKit.Bot.Utils
{
/// <summary>
/// Provides a series of helper methods for parsing mentions.
/// </summary>
public static class MentionUtils
{
private const char SanitizeChar = '\x200b';
//If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake)
internal static string MentionUser(string id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>";
/// <summary>
/// Returns a mention string based on the user ID.
/// </summary>
/// <returns>
/// A user mention string (e.g. &lt;@80351110224678912&gt;).
/// </returns>
public static string MentionUser(ulong id) => MentionUser(id.ToString(), true);
internal static string MentionChannel(string id) => $"<#{id}>";
/// <summary>
/// Returns a mention string based on the channel ID.
/// </summary>
/// <returns>
/// A channel mention string (e.g. &lt;#103735883630395392&gt;).
/// </returns>
public static string MentionChannel(ulong id) => MentionChannel(id.ToString());
internal static string MentionRole(string id) => $"<@&{id}>";
/// <summary>
/// Returns a mention string based on the role ID.
/// </summary>
/// <returns>
/// A role mention string (e.g. &lt;@&amp;165511591545143296&gt;).
/// </returns>
public static string MentionRole(ulong id) => MentionRole(id.ToString());
/// <summary>
/// Parses a provided user mention string.
/// </summary>
/// <exception cref="ArgumentException">Invalid mention format.</exception>
public static ulong ParseUser(string text)
{
if (TryParseUser(text, out ulong id))
return id;
throw new ArgumentException(message: "Invalid mention format.", paramName: nameof(text));
}
/// <summary>
/// Tries to parse a provided user mention string.
/// </summary>
public static bool TryParseUser(string text, out ulong userId)
{
if (text.Length >= 3 && text[0] == '<' && text[1] == '@' && text[text.Length - 1] == '>')
{
if (text.Length >= 4 && text[2] == '!')
text = text.Substring(3, text.Length - 4); //<@!123>
else
text = text.Substring(2, text.Length - 3); //<@123>
if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out userId))
return true;
}
userId = 0;
return false;
}
/// <summary>
/// Parses a provided channel mention string.
/// </summary>
/// <exception cref="ArgumentException">Invalid mention format.</exception>
public static ulong ParseChannel(string text)
{
if (TryParseChannel(text, out ulong id))
return id;
throw new ArgumentException(message: "Invalid mention format.", paramName: nameof(text));
}
/// <summary>
/// Tries to parse a provided channel mention string.
/// </summary>
public static bool TryParseChannel(string text, out ulong channelId)
{
if (text.Length >= 3 && text[0] == '<' && text[1] == '#' && text[text.Length - 1] == '>')
{
text = text.Substring(2, text.Length - 3); //<#123>
if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out channelId))
return true;
}
channelId = 0;
return false;
}
/// <summary>
/// Parses a provided role mention string.
/// </summary>
/// <exception cref="ArgumentException">Invalid mention format.</exception>
public static ulong ParseRole(string text)
{
if (TryParseRole(text, out ulong id))
return id;
throw new ArgumentException(message: "Invalid mention format.", paramName: nameof(text));
}
/// <summary>
/// Tries to parse a provided role mention string.
/// </summary>
public static bool TryParseRole(string text, out ulong roleId)
{
if (text.Length >= 4 && text[0] == '<' && text[1] == '@' && text[2] == '&' && text[text.Length - 1] == '>')
{
text = text.Substring(3, text.Length - 4); //<@&123>
if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out roleId))
return true;
}
roleId = 0;
return false;
}
}
}