mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-08 14:57:54 +00:00
Merge remote-tracking branch 'upstream/main' into rust-command-parser
This commit is contained in:
commit
e8f8e5f0a3
37 changed files with 316 additions and 201 deletions
|
|
@ -213,6 +213,13 @@ public class Bot
|
|||
{
|
||||
_metrics.Measure.Meter.Mark(BotMetrics.BotErrors, exc.GetType().FullName);
|
||||
|
||||
if (exc is Myriad.Extensions.NotFoundInCacheException ce)
|
||||
{
|
||||
var scope = serviceScope.Resolve<Scope>();
|
||||
scope.SetTag("entity.id", ce.EntityId.ToString());
|
||||
scope.SetTag("entity.type", ce.EntityType);
|
||||
}
|
||||
|
||||
// Make this beforehand so we can access the event ID for logging
|
||||
var sentryEvent = new SentryEvent(exc);
|
||||
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ public class Groups
|
|||
ctx.CheckOwnGroup(target);
|
||||
|
||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Avatar, ctx.Author.Id, ctx.System);
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url);
|
||||
await _avatarHosting.VerifyAvatarOrThrow(img.Url);
|
||||
|
||||
await ctx.Repository.UpdateGroup(target.Id, new GroupPatch { Icon = img.CleanUrl ?? img.Url });
|
||||
|
||||
|
|
@ -354,8 +354,8 @@ public class Groups
|
|||
{
|
||||
async Task ClearBannerImage()
|
||||
{
|
||||
await ctx.ConfirmClear("this group's banner image");
|
||||
ctx.CheckOwnGroup(target);
|
||||
await ctx.ConfirmClear("this group's banner image");
|
||||
|
||||
await ctx.Repository.UpdateGroup(target.Id, new GroupPatch { BannerImage = null });
|
||||
await ctx.Reply($"{Emojis.Success} Group banner image cleared.");
|
||||
|
|
@ -366,7 +366,7 @@ public class Groups
|
|||
ctx.CheckOwnGroup(target);
|
||||
|
||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Banner, ctx.Author.Id, ctx.System);
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, true);
|
||||
await _avatarHosting.VerifyAvatarOrThrow(img.Url, true);
|
||||
|
||||
await ctx.Repository.UpdateGroup(target.Id, new GroupPatch { BannerImage = img.CleanUrl ?? img.Url });
|
||||
|
||||
|
|
@ -391,7 +391,7 @@ public class Groups
|
|||
{
|
||||
ctx.CheckSystemPrivacy(target.System, target.BannerPrivacy);
|
||||
|
||||
if ((target.Icon?.Trim() ?? "").Length > 0)
|
||||
if ((target.BannerImage?.Trim() ?? "").Length > 0)
|
||||
switch (ctx.MatchFormat())
|
||||
{
|
||||
case ReplyFormat.Raw:
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ public class Help
|
|||
"If PluralKit is useful to you, please consider donating on [Patreon](https://patreon.com/pluralkit) or [Buy Me A Coffee](https://buymeacoffee.com/pluralkit).\n" +
|
||||
"## Use the buttons below to see more info!";
|
||||
|
||||
public static string EmbedFooter = "-# PluralKit by @ske and contributors | Myriad design by @layl, icon by @tedkalashnikov, banner by @fulmine | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/";
|
||||
|
||||
public static Embed helpEmbed = new()
|
||||
{
|
||||
Title = "PluralKit",
|
||||
Footer = new("PluralKit by @ske and contributors | Myriad design by @layl, icon by @tedkalashnikov, banner by @fulmine | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/"),
|
||||
Color = DiscordUtils.Blue,
|
||||
};
|
||||
|
||||
|
|
@ -142,7 +143,7 @@ public class Help
|
|||
=> ctx.Rest.CreateMessage(ctx.Channel.Id, new MessageRequest
|
||||
{
|
||||
Content = $"{Emojis.Warn} If you cannot see the rest of this message see [the FAQ](<https://pluralkit.me/faq/#why-do-most-of-pluralkit-s-messages-look-blank-or-empty>)",
|
||||
Embeds = new[] { helpEmbed with { Description = Help.Description.Replace("{prefix}", ctx.DefaultPrefix) } },
|
||||
Embeds = new[] { helpEmbed with { Description = Help.Description.Replace("{prefix}", ctx.DefaultPrefix), Fields = new Embed.Field[] { new("", EmbedFooter) } } },
|
||||
Components = new[] { helpPageButtons(ctx.Author.Id) },
|
||||
});
|
||||
|
||||
|
|
@ -156,7 +157,7 @@ public class Help
|
|||
if (ctx.Event.Message.Components.First().Components.Where(x => x.CustomId == ctx.CustomId).First().Style == ButtonStyle.Primary)
|
||||
return ctx.Respond(InteractionResponse.ResponseType.UpdateMessage, new()
|
||||
{
|
||||
Embeds = new[] { helpEmbed with { Description = Help.Description.Replace("{prefix}", prefix) } },
|
||||
Embeds = new[] { helpEmbed with { Description = Help.Description.Replace("{prefix}", prefix), Fields = new Embed.Field[] { new("", EmbedFooter) } } },
|
||||
Components = new[] { buttons }
|
||||
});
|
||||
|
||||
|
|
@ -164,8 +165,9 @@ public class Help
|
|||
|
||||
return ctx.Respond(InteractionResponse.ResponseType.UpdateMessage, new()
|
||||
{
|
||||
Embeds = new[] { helpEmbed with { Fields = helpEmbedPages.GetValueOrDefault(ctx.CustomId.Split("-")[2]).Select((item, index) =>
|
||||
new Embed.Field(item.Name.Replace("{prefix}", prefix), item.Value.Replace("{prefix}", prefix))).ToArray() } },
|
||||
Embeds = new[] { helpEmbed with { Fields = helpEmbedPages.GetValueOrDefault(ctx.CustomId.Split("-")[2]).Select(
|
||||
(item, index) => new Embed.Field(item.Name.Replace("{prefix}", prefix), item.Value.Replace("{prefix}", prefix))
|
||||
).Append(new("", EmbedFooter)).ToArray() } },
|
||||
Components = new[] { buttons }
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public class Member
|
|||
|
||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Avatar, ctx.Author.Id, ctx.System);
|
||||
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url);
|
||||
await _avatarHosting.VerifyAvatarOrThrow(img.Url);
|
||||
await ctx.Repository.UpdateMember(member.Id, new MemberPatch { AvatarUrl = img.CleanUrl ?? img.Url }, conn);
|
||||
|
||||
dispatchData.Add("avatar_url", img.CleanUrl);
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ public class MemberAvatar
|
|||
ctx.CheckSystem().CheckOwnMember(target);
|
||||
|
||||
avatarArg = await _avatarHosting.TryRehostImage(avatarArg.Value, AvatarHostingService.RehostedImageType.Avatar, ctx.Author.Id, ctx.System);
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, avatarArg.Value.Url);
|
||||
await _avatarHosting.VerifyAvatarOrThrow(avatarArg.Value.Url);
|
||||
await UpdateAvatar(location, ctx, target, avatarArg.Value.CleanUrl ?? avatarArg.Value.Url);
|
||||
await PrintResponse(location, ctx, target, avatarArg.Value, guildData);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ public class MemberEdit
|
|||
{
|
||||
ctx.CheckOwnMember(target);
|
||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Banner, ctx.Author.Id, ctx.System);
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, true);
|
||||
await _avatarHosting.VerifyAvatarOrThrow(img.Url, true);
|
||||
|
||||
await ctx.Repository.UpdateMember(target.Id, new MemberPatch { BannerImage = img.CleanUrl ?? img.Url });
|
||||
|
||||
|
|
@ -254,6 +254,8 @@ public class MemberEdit
|
|||
|
||||
async Task ShowBannerImage()
|
||||
{
|
||||
ctx.CheckSystemPrivacy(target.System, target.BannerPrivacy);
|
||||
|
||||
if ((target.BannerImage?.Trim() ?? "").Length > 0)
|
||||
switch (ctx.MatchFormat())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -241,6 +241,12 @@ public class ProxiedMessage
|
|||
{
|
||||
throw new PKError("Could not edit message.");
|
||||
}
|
||||
catch (BadRequestException e)
|
||||
{
|
||||
if (e.Message == "Voice messages cannot be edited")
|
||||
throw new PKError($"{e.Message}.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(PKMessage, SystemId)> GetMessageToEdit(Context ctx, Duration timeout, bool isReproxy)
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public class Misc
|
|||
+ $"**{stats.db.switches:N0}** switches, **{stats.db.messages:N0}** messages\n" +
|
||||
$"**{stats.db.guilds:N0}** servers with **{stats.db.channels:N0}** channels"));
|
||||
|
||||
embed.Footer(Help.helpEmbed.Footer);
|
||||
embed.Field(new("", Help.EmbedFooter));
|
||||
|
||||
var uptime = ((DateTimeOffset)process.StartTime).ToUnixTimeSeconds();
|
||||
embed.Description($"### PluralKit [{BuildInfoService.Version}](https://github.com/pluralkit/pluralkit/commit/{BuildInfoService.FullVersion})\n" +
|
||||
|
|
|
|||
|
|
@ -572,7 +572,7 @@ public class SystemEdit
|
|||
ctx.CheckOwnSystem(target);
|
||||
|
||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Avatar, ctx.Author.Id, ctx.System);
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url);
|
||||
await _avatarHosting.VerifyAvatarOrThrow(img.Url);
|
||||
|
||||
await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { AvatarUrl = img.CleanUrl ?? img.Url });
|
||||
|
||||
|
|
@ -659,7 +659,7 @@ public class SystemEdit
|
|||
ctx.CheckOwnSystem(target);
|
||||
|
||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Avatar, ctx.Author.Id, ctx.System);
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url);
|
||||
await _avatarHosting.VerifyAvatarOrThrow(img.Url);
|
||||
|
||||
await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { AvatarUrl = img.CleanUrl ?? img.Url });
|
||||
|
||||
|
|
@ -781,7 +781,7 @@ public class SystemEdit
|
|||
else if (await ctx.MatchImage() is { } img)
|
||||
{
|
||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Banner, ctx.Author.Id, ctx.System);
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, true);
|
||||
await _avatarHosting.VerifyAvatarOrThrow(img.Url, true);
|
||||
|
||||
await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { BannerImage = img.CleanUrl ?? img.Url });
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,5 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="Sentry" Version="4.13.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -238,25 +238,33 @@ public class ProxyService
|
|||
if (trigger.Flags.HasFlag(Message.MessageFlags.VoiceMessage))
|
||||
flags |= Message.MessageFlags.VoiceMessage;
|
||||
|
||||
var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest
|
||||
try
|
||||
{
|
||||
GuildId = trigger.GuildId!.Value,
|
||||
ChannelId = rootChannel.Id,
|
||||
ThreadId = threadId,
|
||||
MessageId = trigger.Id,
|
||||
Name = await FixSameName(messageChannel.Id, ctx, match.Member),
|
||||
AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)),
|
||||
Content = content,
|
||||
Attachments = trigger.Attachments,
|
||||
FileSizeLimit = guild.FileSizeLimit(),
|
||||
Embeds = embeds.ToArray(),
|
||||
Stickers = trigger.StickerItems,
|
||||
AllowEveryone = allowEveryone,
|
||||
Flags = flags,
|
||||
Tts = tts,
|
||||
Poll = trigger.Poll,
|
||||
});
|
||||
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match);
|
||||
var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest
|
||||
{
|
||||
GuildId = trigger.GuildId!.Value,
|
||||
ChannelId = rootChannel.Id,
|
||||
ThreadId = threadId,
|
||||
MessageId = trigger.Id,
|
||||
Name = await FixSameName(messageChannel.Id, ctx, match.Member),
|
||||
AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)),
|
||||
Content = content,
|
||||
Attachments = trigger.Attachments,
|
||||
FileSizeLimit = guild.FileSizeLimit(),
|
||||
Embeds = embeds.ToArray(),
|
||||
Stickers = trigger.StickerItems,
|
||||
AllowEveryone = allowEveryone,
|
||||
Flags = flags,
|
||||
Tts = tts,
|
||||
Poll = trigger.Poll,
|
||||
});
|
||||
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match);
|
||||
}
|
||||
catch (PKError)
|
||||
{
|
||||
if (ctx.ProxyErrorMessageEnabled)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteReproxy(Message trigger, PKMessage msg, List<ProxyMember> members, ProxyMember member, string prefix)
|
||||
|
|
@ -391,6 +399,10 @@ public class ProxyService
|
|||
if (hasContent)
|
||||
{
|
||||
var msg = repliedTo.Content;
|
||||
|
||||
// strip out overly excessive line breaks
|
||||
msg = Regex.Replace(msg, @"(?:(?:([_\*]) \1)?\n){2,}", "\n");
|
||||
|
||||
if (msg.Length > 100)
|
||||
{
|
||||
msg = repliedTo.Content.Substring(0, 100);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,44 @@ public class AvatarHostingService
|
|||
};
|
||||
}
|
||||
|
||||
public async Task VerifyAvatarOrThrow(string url, bool isBanner = false)
|
||||
{
|
||||
if (url.Length > Limits.MaxUriLength)
|
||||
throw Errors.UrlTooLong(url);
|
||||
|
||||
if (!PluralKit.Core.MiscUtils.TryMatchUri(url, out var uri))
|
||||
throw Errors.InvalidUrl;
|
||||
|
||||
if (uri.Host.Contains("toyhou.se"))
|
||||
throw new PKError("Due to server issues, PluralKit is unable to read images hosted on toyhou.se.");
|
||||
|
||||
if (uri.Host == "cdn.pluralkit.me") return;
|
||||
|
||||
if (_config.AvatarServiceUrl == null)
|
||||
return;
|
||||
|
||||
var kind = isBanner ? "banner" : "avatar";
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _client.PostAsJsonAsync(_config.AvatarServiceUrl + "/verify",
|
||||
new { url, kind });
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
var error = await response.Content.ReadFromJsonAsync<ErrorResponse>();
|
||||
throw new PKError($"{error.Error}");
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
// don't show an internal error to users
|
||||
if (e.Message.Contains("HttpClient.Timeout"))
|
||||
throw new PKError("Temporary error setting image, please try again later");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ParsedImage> TryRehostImage(ParsedImage input, RehostedImageType type, ulong userId, PKSystem? system)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public class ErrorMessageService
|
|||
return new EmbedBuilder()
|
||||
.Color(0xE74C3C)
|
||||
.Title("Internal error occurred")
|
||||
.Description($"For support, please send the error code above in {channelInfo} with a description of what you were doing at the time.")
|
||||
.Description($"For support, please send the error code above as text in {channelInfo} with a description of what you were doing at the time.")
|
||||
.Footer(new Embed.EmbedFooter(errorId))
|
||||
.Timestamp(now.ToDateTimeOffset().ToString("O"))
|
||||
.Build();
|
||||
|
|
|
|||
|
|
@ -191,6 +191,9 @@ public class WebhookExecutorService
|
|||
}
|
||||
catch (BadRequestException e)
|
||||
{
|
||||
if (e.Message == "Cannot use one or more emoji included with this poll")
|
||||
throw new PKError($"Discord rejected proxy message: {e.Message}");
|
||||
|
||||
// explanation for hacky: I don't care if this code fails, it just means it wasn't a username error
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,63 +2,10 @@ using System.Text.RegularExpressions;
|
|||
|
||||
using PluralKit.Core;
|
||||
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace PluralKit.Bot;
|
||||
|
||||
public static class AvatarUtils
|
||||
{
|
||||
public static async Task VerifyAvatarOrThrow(HttpClient client, string url, bool isFullSizeImage = false)
|
||||
{
|
||||
if (url.Length > Limits.MaxUriLength)
|
||||
throw Errors.UrlTooLong(url);
|
||||
|
||||
// List of MIME types we consider acceptable
|
||||
var acceptableMimeTypes = new[]
|
||||
{
|
||||
"image/jpeg", "image/gif", "image/png", "image/webp"
|
||||
};
|
||||
|
||||
if (!PluralKit.Core.MiscUtils.TryMatchUri(url, out var uri))
|
||||
throw Errors.InvalidUrl;
|
||||
|
||||
if (uri.Host.Contains("toyhou.se"))
|
||||
throw new PKError("Due to server issues, PluralKit is unable to read images hosted on toyhou.se.");
|
||||
|
||||
url = TryRewriteCdnUrl(url);
|
||||
|
||||
var response = await client.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode) // Check status code
|
||||
throw Errors.AvatarServerError(response.StatusCode);
|
||||
if (response.Content.Headers.ContentLength == null) // Check presence of content length
|
||||
throw Errors.AvatarNotAnImage(null);
|
||||
try
|
||||
{
|
||||
if (!acceptableMimeTypes.Contains(response.Content.Headers.ContentType.MediaType)) // Check MIME type
|
||||
throw Errors.AvatarNotAnImage(response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
throw new PKError("Could not verify avatar is an image. This can happen when the server sends a malformed response."
|
||||
+ "\nPlease join the support server for help: <https://discord.gg/PczBt78>");
|
||||
}
|
||||
|
||||
if (isFullSizeImage)
|
||||
// no need to do size checking on banners
|
||||
return;
|
||||
|
||||
if (response.Content.Headers.ContentLength > Limits.AvatarFileSizeLimit) // Check content length
|
||||
throw Errors.AvatarFileSizeLimit(response.Content.Headers.ContentLength.Value);
|
||||
|
||||
// Parse the image header in a worker
|
||||
var stream = await response.Content.ReadAsStreamAsync();
|
||||
var image = await Task.Run(() => Image.Identify(stream));
|
||||
if (image == null) throw Errors.AvatarInvalid;
|
||||
if (image.Width > Limits.AvatarDimensionLimit ||
|
||||
image.Height > Limits.AvatarDimensionLimit) // Check image size
|
||||
throw Errors.AvatarDimensionsTooLarge(image.Width, image.Height);
|
||||
}
|
||||
|
||||
// Rewrite cdn.discordapp.com URLs to media.discordapp.net for jpg/png files
|
||||
// This lets us add resizing parameters to "borrow" their media proxy server to downsize the image
|
||||
// which in turn makes it more likely to be underneath the size limit!
|
||||
|
|
|
|||
|
|
@ -14,12 +14,6 @@
|
|||
"resolved": "4.13.0",
|
||||
"contentHash": "Wfw3M1WpFcrYaGzPm7QyUTfIOYkVXQ1ry6p4WYjhbLz9fPwV23SGQZTFDpdox67NHM0V0g1aoQ4YKLm4ANtEEg=="
|
||||
},
|
||||
"SixLabors.ImageSharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.1.6, )",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "dHQ5jugF9v+5/LCVHCWVzaaIL6WOehqJy6eju/0VFYFPEj2WtqkGPoEV9EVQP83dHsdoqYaTuWpZdwAd37UwfA=="
|
||||
},
|
||||
"App.Metrics": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue