mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-05 21:37:55 +00:00
Merge branch 'main' into proxyswitch-add
This commit is contained in:
commit
fbebe38afe
59 changed files with 1000 additions and 423 deletions
|
|
@ -32,6 +32,7 @@ public partial class CommandTree
|
|||
public static Command ConfigGroupDefaultPrivacy = new("config private group", "config private group [on|off]", "Sets whether group privacy is automatically set to private when creating a new group");
|
||||
public static Command ConfigProxySwitch = new Command("config proxyswitch", "config proxyswitch [new|add|off]", "Switching behavior when proxy tags are used");
|
||||
public static Command ConfigNameFormat = new Command("config nameformat", "config nameformat [format]", "Changes your system's username formatting");
|
||||
public static Command ConfigServerNameFormat = new Command("config servernameformat", "config servernameformat [format]", "Changes your system's username formatting in the current server");
|
||||
public static Command AutoproxySet = new Command("autoproxy", "autoproxy [off|front|latch|member]", "Sets your system's autoproxy mode for the current server");
|
||||
public static Command AutoproxyOff = new Command("autoproxy off", "autoproxy off", "Disables autoproxying for your system in the current server");
|
||||
public static Command AutoproxyFront = new Command("autoproxy front", "autoproxy front", "Sets your system's autoproxy in this server to proxy the first member currently registered as front");
|
||||
|
|
@ -150,7 +151,7 @@ public partial class CommandTree
|
|||
{
|
||||
ConfigAutoproxyAccount, ConfigAutoproxyTimeout, ConfigTimezone, ConfigPing,
|
||||
ConfigMemberDefaultPrivacy, ConfigGroupDefaultPrivacy, ConfigShowPrivate,
|
||||
ConfigProxySwitch, ConfigNameFormat
|
||||
ConfigProxySwitch, ConfigNameFormat, ConfigServerNameFormat
|
||||
};
|
||||
|
||||
public static Command[] ServerConfigCommands =
|
||||
|
|
|
|||
|
|
@ -596,6 +596,8 @@ public partial class CommandTree
|
|||
return ctx.Execute<Config>(null, m => m.LimitUpdate(ctx));
|
||||
if (ctx.MatchMultiple(new[] { "proxy" }, new[] { "switch" }) || ctx.Match("proxyswitch", "ps"))
|
||||
return ctx.Execute<Config>(null, m => m.ProxySwitch(ctx));
|
||||
if (ctx.MatchMultiple(new[] { "server" }, new[] { "name" }, new[] { "format" }) || ctx.MatchMultiple(new[] { "server", "servername" }, new[] { "format", "nameformat", "nf" }) || ctx.Match("snf", "servernf", "servernameformat", "snameformat"))
|
||||
return ctx.Execute<Config>(null, m => m.ServerNameFormat(ctx));
|
||||
|
||||
// todo: maybe add the list of configuration keys here?
|
||||
return ctx.Reply($"{Emojis.Error} Could not find a setting with that name. Please see `pk;commands config` for the list of possible config settings.");
|
||||
|
|
|
|||
|
|
@ -98,6 +98,15 @@ public static class ContextArgumentsExt
|
|||
return ReplyFormat.Standard;
|
||||
}
|
||||
|
||||
public static ReplyFormat PeekMatchFormat(this Context ctx)
|
||||
{
|
||||
int ptr1 = ctx.Parameters._ptr;
|
||||
int ptr2 = ctx.Parameters._ptr;
|
||||
if (ctx.PeekMatch(ref ptr1, new[] { "r", "raw" }) || ctx.MatchFlag("r", "raw")) return ReplyFormat.Raw;
|
||||
if (ctx.PeekMatch(ref ptr2, new[] { "pt", "plaintext" }) || ctx.MatchFlag("pt", "plaintext")) return ReplyFormat.Plaintext;
|
||||
return ReplyFormat.Standard;
|
||||
}
|
||||
|
||||
public static bool MatchToggle(this Context ctx, bool? defaultValue = null)
|
||||
{
|
||||
var value = ctx.MatchToggleOrNull(defaultValue);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ public static class ContextAvatarExt
|
|||
return new ParsedImage { Url = url, Source = AvatarSource.User, SourceUser = user };
|
||||
}
|
||||
|
||||
// If we have raw or plaintext, don't try to parse as a URL
|
||||
if (ctx.PeekMatchFormat() != ReplyFormat.Standard)
|
||||
return null;
|
||||
|
||||
// If we have a positional argument, try to parse it as a URL
|
||||
var arg = ctx.RemainderOrNull();
|
||||
if (arg != null)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Text;
|
||||
|
||||
using Humanizer;
|
||||
|
||||
using Myriad.Builders;
|
||||
using NodaTime;
|
||||
using NodaTime.Text;
|
||||
using NodaTime.TimeZones;
|
||||
|
|
@ -137,6 +137,25 @@ public class Config
|
|||
ProxyMember.DefaultFormat
|
||||
));
|
||||
|
||||
if (ctx.Guild == null)
|
||||
{
|
||||
items.Add(new(
|
||||
"Server Name Format",
|
||||
"Format string used to display a member's name in the current server",
|
||||
"only available in servers",
|
||||
"only available in servers"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
items.Add(new(
|
||||
"Server Name Format",
|
||||
"Format string used to display a member's name in the current server",
|
||||
(await ctx.Repository.GetSystemGuild(ctx.Guild.Id, ctx.System.Id)).NameFormat ?? "none set",
|
||||
"none set"
|
||||
));
|
||||
}
|
||||
|
||||
await ctx.Paginate<PaginatedConfigItem>(
|
||||
items.ToAsyncEnumerable(),
|
||||
items.Count,
|
||||
|
|
@ -599,6 +618,48 @@ public class Config
|
|||
await ctx.Reply($"Member names are now formatted as `{formatString}`");
|
||||
}
|
||||
|
||||
public async Task ServerNameFormat(Context ctx)
|
||||
{
|
||||
ctx.CheckGuildContext();
|
||||
var clearFlag = ctx.MatchClear();
|
||||
var format = ctx.MatchFormat();
|
||||
|
||||
// if there's nothing next or what's next is raw/plaintext and we're not clearing, it's a query
|
||||
if ((!ctx.HasNext() || format != ReplyFormat.Standard) && !clearFlag)
|
||||
{
|
||||
var guildCfg = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, ctx.System.Id);
|
||||
if (guildCfg.NameFormat == null)
|
||||
await ctx.Reply("You do not have a specific name format set for this server and member names are formatted with your global name format.");
|
||||
else
|
||||
switch (format)
|
||||
{
|
||||
case ReplyFormat.Raw:
|
||||
await ctx.Reply($"`{guildCfg.NameFormat}`");
|
||||
break;
|
||||
case ReplyFormat.Plaintext:
|
||||
var eb = new EmbedBuilder()
|
||||
.Description($"Showing guild Name Format for system {ctx.System.DisplayHid(ctx.Config)}");
|
||||
await ctx.Reply(guildCfg.NameFormat, eb.Build());
|
||||
break;
|
||||
default:
|
||||
await ctx.Reply($"Your member names in this server are currently formatted as `{guildCfg.NameFormat}`");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
string? formatString = null;
|
||||
if (!clearFlag)
|
||||
{
|
||||
formatString = ctx.RemainderOrNull();
|
||||
}
|
||||
await ctx.Repository.UpdateSystemGuild(ctx.System.Id, ctx.Guild.Id, new() { NameFormat = formatString });
|
||||
if (formatString == null)
|
||||
await ctx.Reply($"Member names are now formatted with your global name format in this server.");
|
||||
else
|
||||
await ctx.Reply($"Member names are now formatted as `{formatString}` in this server.");
|
||||
}
|
||||
|
||||
public Task LimitUpdate(Context ctx)
|
||||
{
|
||||
throw new PKError("You cannot update your own member or group limits. If you need a limit update, please join the " +
|
||||
|
|
|
|||
|
|
@ -312,21 +312,28 @@ public class Groups
|
|||
ctx.CheckSystemPrivacy(target.System, target.IconPrivacy);
|
||||
|
||||
if ((target.Icon?.Trim() ?? "").Length > 0)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Title("Group icon")
|
||||
.Image(new Embed.EmbedImage(target.Icon.TryGetCleanCdnUrl()));
|
||||
|
||||
if (target.System == ctx.System?.Id)
|
||||
eb.Description($"To clear, use `pk;group {target.Reference(ctx)} icon -clear`.");
|
||||
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
switch (ctx.MatchFormat())
|
||||
{
|
||||
case ReplyFormat.Raw:
|
||||
await ctx.Reply($"`{target.Icon.TryGetCleanCdnUrl()}`");
|
||||
break;
|
||||
case ReplyFormat.Plaintext:
|
||||
var ebP = new EmbedBuilder()
|
||||
.Description($"Showing avatar for group {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
|
||||
await ctx.Reply(text: $"<{target.Icon.TryGetCleanCdnUrl()}>", embed: ebP.Build());
|
||||
break;
|
||||
default:
|
||||
var ebS = new EmbedBuilder()
|
||||
.Title("Group icon")
|
||||
.Image(new Embed.EmbedImage(target.Icon.TryGetCleanCdnUrl()));
|
||||
if (target.System == ctx.System?.Id)
|
||||
ebS.Description($"To clear, use `pk;group {target.Reference(ctx)} icon -clear`.");
|
||||
await ctx.Reply(embed: ebS.Build());
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PKSyntaxError(
|
||||
"This group does not have an avatar set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.MatchClear())
|
||||
|
|
@ -378,22 +385,29 @@ public class Groups
|
|||
{
|
||||
ctx.CheckSystemPrivacy(target.System, target.BannerPrivacy);
|
||||
|
||||
if ((target.BannerImage?.Trim() ?? "").Length > 0)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Title("Group banner image")
|
||||
.Image(new Embed.EmbedImage(target.BannerImage));
|
||||
|
||||
if (target.System == ctx.System?.Id)
|
||||
eb.Description($"To clear, use `pk;group {target.Reference(ctx)} banner clear`.");
|
||||
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
if ((target.Icon?.Trim() ?? "").Length > 0)
|
||||
switch (ctx.MatchFormat())
|
||||
{
|
||||
case ReplyFormat.Raw:
|
||||
await ctx.Reply($"`{target.BannerImage.TryGetCleanCdnUrl()}`");
|
||||
break;
|
||||
case ReplyFormat.Plaintext:
|
||||
var ebP = new EmbedBuilder()
|
||||
.Description($"Showing banner for group {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
|
||||
await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build());
|
||||
break;
|
||||
default:
|
||||
var ebS = new EmbedBuilder()
|
||||
.Title("Group banner image")
|
||||
.Image(new Embed.EmbedImage(target.BannerImage.TryGetCleanCdnUrl()));
|
||||
if (target.System == ctx.System?.Id)
|
||||
ebS.Description($"To clear, use `pk;group {target.Reference(ctx)} banner clear`.");
|
||||
await ctx.Reply(embed: ebS.Build());
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PKSyntaxError(
|
||||
"This group does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL.");
|
||||
}
|
||||
"This group does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
|
||||
}
|
||||
|
||||
if (ctx.MatchClear())
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public class Help
|
|||
{
|
||||
Title = "PluralKit",
|
||||
Description = "PluralKit is a bot designed for plural communities on Discord, and is open for anyone to use. It allows you to register systems, maintain system information, set up message proxying, log switches, and more.",
|
||||
Footer = new("By @ske | Myriad design by @layl, art by @tedkalashnikov | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/"),
|
||||
Footer = new("By @ske | Myriad design by @layl, icon by @tedkalashnikov, banner by @fulmine | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/"),
|
||||
Color = DiscordUtils.Blue,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -86,12 +86,28 @@ public class MemberAvatar
|
|||
if (location == MemberAvatarLocation.Server)
|
||||
field += $" (for {ctx.Guild.Name})";
|
||||
|
||||
var eb = new EmbedBuilder()
|
||||
.Title($"{target.NameFor(ctx)}'s {field}")
|
||||
.Image(new Embed.EmbedImage(currentValue?.TryGetCleanCdnUrl()));
|
||||
if (target.System == ctx.System?.Id)
|
||||
eb.Description($"To clear, use `pk;member {target.Reference(ctx)} {location.Command()} clear`.");
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
var format = ctx.MatchFormat();
|
||||
if (format == ReplyFormat.Raw)
|
||||
{
|
||||
await ctx.Reply($"`{currentValue?.TryGetCleanCdnUrl()}`");
|
||||
}
|
||||
else if (format == ReplyFormat.Plaintext)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Description($"Showing {field} link for member {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
|
||||
await ctx.Reply($"<{currentValue?.TryGetCleanCdnUrl()}>", embed: eb.Build());
|
||||
return;
|
||||
}
|
||||
else if (format == ReplyFormat.Standard)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Title($"{target.NameFor(ctx)}'s {field}")
|
||||
.Image(new Embed.EmbedImage(currentValue?.TryGetCleanCdnUrl()));
|
||||
if (target.System == ctx.System?.Id)
|
||||
eb.Description($"To clear, use `pk;member {target.Reference(ctx)} {location.Command()} clear`.");
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
else throw new PKError("Format Not Recognized");
|
||||
}
|
||||
|
||||
public async Task ServerAvatar(Context ctx, PKMember target)
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public class MemberEdit
|
|||
{
|
||||
var noPronounsSetMessage = "This member does not have pronouns set.";
|
||||
if (ctx.System?.Id == target.System)
|
||||
noPronounsSetMessage += $"To set some, type `pk;member {target.Reference(ctx)} pronouns <pronouns>`.";
|
||||
noPronounsSetMessage += $" To set some, type `pk;member {target.Reference(ctx)} pronouns <pronouns>`.";
|
||||
|
||||
ctx.CheckSystemPrivacy(target.System, target.PronounPrivacy);
|
||||
|
||||
|
|
@ -194,16 +194,18 @@ public class MemberEdit
|
|||
|
||||
public async Task BannerImage(Context ctx, PKMember target)
|
||||
{
|
||||
ctx.CheckOwnMember(target);
|
||||
|
||||
async Task ClearBannerImage()
|
||||
{
|
||||
ctx.CheckOwnMember(target);
|
||||
await ctx.ConfirmClear("this member's banner image");
|
||||
|
||||
await ctx.Repository.UpdateMember(target.Id, new MemberPatch { BannerImage = null });
|
||||
await ctx.Reply($"{Emojis.Success} Member banner image cleared.");
|
||||
}
|
||||
|
||||
async Task SetBannerImage(ParsedImage img)
|
||||
{
|
||||
ctx.CheckOwnMember(target);
|
||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Banner, ctx.Author.Id, ctx.System);
|
||||
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, true);
|
||||
|
||||
|
|
@ -229,21 +231,31 @@ public class MemberEdit
|
|||
async Task ShowBannerImage()
|
||||
{
|
||||
if ((target.BannerImage?.Trim() ?? "").Length > 0)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Title($"{target.NameFor(ctx)}'s banner image")
|
||||
.Image(new Embed.EmbedImage(target.BannerImage))
|
||||
.Description($"To clear, use `pk;member {target.Reference(ctx)} banner clear`.");
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
switch (ctx.MatchFormat())
|
||||
{
|
||||
case ReplyFormat.Raw:
|
||||
await ctx.Reply($"`{target.BannerImage.TryGetCleanCdnUrl()}`");
|
||||
break;
|
||||
case ReplyFormat.Plaintext:
|
||||
var ebP = new EmbedBuilder()
|
||||
.Description($"Showing banner for member {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
|
||||
await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build());
|
||||
break;
|
||||
default:
|
||||
var ebS = new EmbedBuilder()
|
||||
.Title($"{target.NameFor(ctx)}'s banner image")
|
||||
.Image(new Embed.EmbedImage(target.BannerImage.TryGetCleanCdnUrl()));
|
||||
if (target.System == ctx.System?.Id)
|
||||
ebS.Description($"To clear, use `pk;member {target.Reference(ctx)} banner clear`.");
|
||||
await ctx.Reply(embed: ebS.Build());
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PKSyntaxError(
|
||||
"This member does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL.");
|
||||
}
|
||||
"This member does not have a banner image set." + ((target.System == ctx.System?.Id) ? " Set one by attaching an image to this command, or by passing an image URL." : ""));
|
||||
}
|
||||
|
||||
if (ctx.MatchClear() && await ctx.ConfirmClear("this member's banner image"))
|
||||
if (ctx.MatchClear())
|
||||
await ClearBannerImage();
|
||||
else if (await ctx.MatchImage() is { } img)
|
||||
await SetBannerImage(img);
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ public class ProxiedMessage
|
|||
|
||||
// Should we clear embeds?
|
||||
var clearEmbeds = ctx.MatchFlag("clear-embed", "ce");
|
||||
if (clearEmbeds && newContent == null)
|
||||
var clearAttachments = ctx.MatchFlag("clear-attachments", "ca");
|
||||
if ((clearEmbeds || clearAttachments) && newContent == null)
|
||||
newContent = originalMsg.Content!;
|
||||
|
||||
if (newContent == null)
|
||||
|
|
@ -218,7 +219,7 @@ public class ProxiedMessage
|
|||
try
|
||||
{
|
||||
var editedMsg =
|
||||
await _webhookExecutor.EditWebhookMessage(msg.Guild ?? 0, msg.Channel, msg.Mid, newContent, clearEmbeds);
|
||||
await _webhookExecutor.EditWebhookMessage(msg.Guild ?? 0, msg.Channel, msg.Mid, newContent, clearEmbeds, clearAttachments);
|
||||
|
||||
if (ctx.Guild == null)
|
||||
await _rest.CreateReaction(ctx.Channel.Id, ctx.Message.Id, new Emoji { Name = Emojis.Success });
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ public class ServerConfig
|
|||
await ctx.Reply(
|
||||
$"{Emojis.Success} Message logging for the given channels {(enable ? "enabled" : "disabled")}." +
|
||||
(logChannel == null
|
||||
? $"\n{Emojis.Warn} Please note that no logging channel is set, so there is nowhere to log messages to. You can set a logging channel using `pk;log channel #your-log-channel`."
|
||||
? $"\n{Emojis.Warn} Please note that no logging channel is set, so there is nowhere to log messages to. You can set a logging channel using `pk;serverconfig log channel #your-log-channel`."
|
||||
: ""));
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +351,10 @@ public class ServerConfig
|
|||
await ctx.Repository.UpdateGuild(ctx.Guild.Id, new GuildPatch { LogBlacklist = blacklist.ToArray() });
|
||||
|
||||
await ctx.Reply(
|
||||
$"{Emojis.Success} Channels {(shouldAdd ? "added to" : "removed from")} the logging blacklist.");
|
||||
$"{Emojis.Success} Channels {(shouldAdd ? "added to" : "removed from")} the logging blacklist." +
|
||||
(guild.LogChannel == null
|
||||
? $"\n{Emojis.Warn} Please note that no logging channel is set, so there is nowhere to log messages to. You can set a logging channel using `pk;serverconfig log channel #your-log-channel`."
|
||||
: ""));
|
||||
}
|
||||
|
||||
public async Task SetLogCleanup(Context ctx)
|
||||
|
|
|
|||
|
|
@ -565,19 +565,28 @@ public class SystemEdit
|
|||
async Task ShowIcon()
|
||||
{
|
||||
if ((target.AvatarUrl?.Trim() ?? "").Length > 0)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Title("System icon")
|
||||
.Image(new Embed.EmbedImage(target.AvatarUrl.TryGetCleanCdnUrl()));
|
||||
if (target.Id == ctx.System?.Id)
|
||||
eb.Description("To clear, use `pk;system icon clear`.");
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
switch (ctx.MatchFormat())
|
||||
{
|
||||
case ReplyFormat.Raw:
|
||||
await ctx.Reply($"`{target.AvatarUrl.TryGetCleanCdnUrl()}`");
|
||||
break;
|
||||
case ReplyFormat.Plaintext:
|
||||
var ebP = new EmbedBuilder()
|
||||
.Description($"Showing icon for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
|
||||
await ctx.Reply(text: $"<{target.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build());
|
||||
break;
|
||||
default:
|
||||
var ebS = new EmbedBuilder()
|
||||
.Title("System icon")
|
||||
.Image(new Embed.EmbedImage(target.AvatarUrl.TryGetCleanCdnUrl()));
|
||||
if (target.Id == ctx.System?.Id)
|
||||
ebS.Description("To clear, use `pk;system icon clear`.");
|
||||
await ctx.Reply(embed: ebS.Build());
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PKSyntaxError(
|
||||
"This system does not have an icon set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null && target?.Id != ctx.System?.Id)
|
||||
|
|
@ -639,19 +648,28 @@ public class SystemEdit
|
|||
var settings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id);
|
||||
|
||||
if ((settings.AvatarUrl?.Trim() ?? "").Length > 0)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Title("System server icon")
|
||||
.Image(new Embed.EmbedImage(settings.AvatarUrl.TryGetCleanCdnUrl()));
|
||||
if (target.Id == ctx.System?.Id)
|
||||
eb.Description("To clear, use `pk;system servericon clear`.");
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
switch (ctx.MatchFormat())
|
||||
{
|
||||
case ReplyFormat.Raw:
|
||||
await ctx.Reply($"`{settings.AvatarUrl.TryGetCleanCdnUrl()}`");
|
||||
break;
|
||||
case ReplyFormat.Plaintext:
|
||||
var ebP = new EmbedBuilder()
|
||||
.Description($"Showing icon for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
|
||||
await ctx.Reply(text: $"<{settings.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build());
|
||||
break;
|
||||
default:
|
||||
var ebS = new EmbedBuilder()
|
||||
.Title("System server icon")
|
||||
.Image(new Embed.EmbedImage(settings.AvatarUrl.TryGetCleanCdnUrl()));
|
||||
if (target.Id == ctx.System?.Id)
|
||||
ebS.Description("To clear, use `pk;system servericon clear`.");
|
||||
await ctx.Reply(embed: ebS.Build());
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PKSyntaxError(
|
||||
"This system does not have a icon specific to this server. Set one by attaching an image to this command, or by passing an image URL or @mention.");
|
||||
}
|
||||
}
|
||||
|
||||
ctx.CheckGuildContext();
|
||||
|
|
@ -676,24 +694,31 @@ public class SystemEdit
|
|||
|
||||
var isOwnSystem = target.Id == ctx.System?.Id;
|
||||
|
||||
if (!ctx.HasNext() && ctx.Message.Attachments.Length == 0)
|
||||
if ((!ctx.HasNext() && ctx.Message.Attachments.Length == 0) || ctx.PeekMatchFormat() != ReplyFormat.Standard)
|
||||
{
|
||||
if ((target.BannerImage?.Trim() ?? "").Length > 0)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Title("System banner image")
|
||||
.Image(new Embed.EmbedImage(target.BannerImage));
|
||||
|
||||
if (isOwnSystem)
|
||||
eb.Description("To clear, use `pk;system banner clear`.");
|
||||
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
switch (ctx.MatchFormat())
|
||||
{
|
||||
case ReplyFormat.Raw:
|
||||
await ctx.Reply($"`{target.BannerImage.TryGetCleanCdnUrl()}`");
|
||||
break;
|
||||
case ReplyFormat.Plaintext:
|
||||
var ebP = new EmbedBuilder()
|
||||
.Description($"Showing banner for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
|
||||
await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build());
|
||||
break;
|
||||
default:
|
||||
var ebS = new EmbedBuilder()
|
||||
.Title("System banner image")
|
||||
.Image(new Embed.EmbedImage(target.BannerImage.TryGetCleanCdnUrl()));
|
||||
if (target.Id == ctx.System?.Id)
|
||||
ebS.Description("To clear, use `pk;system banner clear`.");
|
||||
await ctx.Reply(embed: ebS.Build());
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PKSyntaxError("This system does not have a banner image set."
|
||||
+ (isOwnSystem ? "Set one by attaching an image to this command, or by passing an image URL or @mention." : ""));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -842,7 +867,7 @@ public class SystemEdit
|
|||
.Field(new Embed.Field("Current fronter(s)", target.FrontPrivacy.Explanation()))
|
||||
.Field(new Embed.Field("Front/switch history", target.FrontHistoryPrivacy.Explanation()))
|
||||
.Description(
|
||||
"To edit privacy settings, use the command:\n`pk;system privacy <subject> <level>`\n\n- `subject` is one of `name`, `avatar`, `description`, `banner`, `list`, `front`, `fronthistory`, `groups`, or `all` \n- `level` is either `public` or `private`.");
|
||||
"To edit privacy settings, use the command:\n`pk;system privacy <subject> <level>`\n\n- `subject` is one of `name`, `avatar`, `description`, `banner`, `pronouns`, `list`, `front`, `fronthistory`, `groups`, or `all` \n- `level` is either `public` or `private`.");
|
||||
return ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,13 +52,19 @@ public class MessageEdited: IEventHandler<MessageUpdateEvent>
|
|||
if (!evt.Content.HasValue || !evt.Author.HasValue || !evt.Member.HasValue)
|
||||
return;
|
||||
|
||||
var guildIdMaybe = evt.GuildId.HasValue ? evt.GuildId.Value ?? 0 : 0;
|
||||
// we only use message edit event for proxying, so ignore messages from DMs
|
||||
if (!evt.GuildId.HasValue || evt.GuildId.Value == null) return;
|
||||
ulong guildId = evt.GuildId!.Value!.Value;
|
||||
|
||||
var channel = await _cache.GetChannel(guildIdMaybe, evt.ChannelId); // todo: is this correct for message update?
|
||||
var channel = await _cache.TryGetChannel(guildId, evt.ChannelId); // todo: is this correct for message update?
|
||||
if (channel == null)
|
||||
throw new Exception("could not find self channel in MessageEdited event");
|
||||
if (!DiscordUtils.IsValidGuildChannel(channel))
|
||||
return;
|
||||
var rootChannel = await _cache.GetRootChannel(guildIdMaybe, channel.Id);
|
||||
var guild = await _cache.GetGuild(channel.GuildId!.Value);
|
||||
var rootChannel = await _cache.GetRootChannel(guildId, channel.Id);
|
||||
var guild = await _cache.TryGetGuild(channel.GuildId!.Value);
|
||||
if (guild == null)
|
||||
throw new Exception("could not find self guild in MessageEdited event");
|
||||
var lastMessage = _lastMessageCache.GetLastMessage(evt.ChannelId)?.Current;
|
||||
|
||||
// Only react to the last message in the channel
|
||||
|
|
@ -73,7 +79,7 @@ public class MessageEdited: IEventHandler<MessageUpdateEvent>
|
|||
return;
|
||||
|
||||
var equivalentEvt = await GetMessageCreateEvent(evt, lastMessage, channel);
|
||||
var botPermissions = await _cache.BotPermissionsIn(guildIdMaybe, channel.Id);
|
||||
var botPermissions = await _cache.BotPermissionsIn(guildId, channel.Id);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -496,10 +496,10 @@ public class ProxyService
|
|||
async Task SaveMessageInRedis()
|
||||
{
|
||||
// logclean info
|
||||
await _redis.SetLogCleanup(triggerMessage.Author.Id, triggerMessage.GuildId.Value);
|
||||
await _redis.SetLogCleanup(triggerMessage.Author.Id, proxyMessage.GuildId!.Value);
|
||||
|
||||
// last message info (edit/reproxy)
|
||||
await _redis.SetLastMessage(triggerMessage.Author.Id, triggerMessage.ChannelId, sentMessage.Mid);
|
||||
await _redis.SetLastMessage(triggerMessage.Author.Id, proxyMessage.ChannelId, sentMessage.Mid);
|
||||
|
||||
// "by original mid" lookup
|
||||
await _redis.SetOriginalMid(triggerMessage.Id, proxyMessage.Id);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ public class LoggerCleanService
|
|||
private static readonly Regex _ProBotRegex = new("\\*\\*Message sent by <@(\\d{17,19})> deleted in <#\\d{17,19}>.\\*\\*");
|
||||
private static readonly Regex _DozerRegex = new("Message ID: (\\d{17,19}) - (\\d{17,19})\nUserID: (\\d{17,19})");
|
||||
private static readonly Regex _SkyraRegex = new("https://discord.com/channels/(\\d{17,19})/(\\d{17,19})/(\\d{17,19})");
|
||||
private static readonly Regex _AnnabelleRegex = new("```\n(\\d{17,19})\n```");
|
||||
private static readonly Regex _AnnabelleRegexFuzzy = new("\\<t:(\\d+)\\> A message from \\*\\*[\\w.]{2,32}\\*\\* \\(`(\\d{17,19})`\\) was deleted in <#\\d{17,19}>");
|
||||
|
||||
private static readonly Regex _VortexRegex =
|
||||
new("`\\[(\\d\\d:\\d\\d:\\d\\d)\\]` .* \\(ID:(\\d{17,19})\\).* <#\\d{17,19}>:");
|
||||
|
|
@ -79,6 +81,7 @@ public class LoggerCleanService
|
|||
new LoggerBot("ProBot Prime", 567703512763334685, fuzzyExtractFunc: ExtractProBot), // webhook (?)
|
||||
new LoggerBot("Dozer", 356535250932858885, ExtractDozer),
|
||||
new LoggerBot("Skyra", 266624760782258186, ExtractSkyra),
|
||||
new LoggerBot("Annabelle", 231241068383961088, fuzzyExtractFunc: ExtractAnnabelleFuzzy),
|
||||
}.ToDictionary(b => b.Id);
|
||||
|
||||
private static Dictionary<ulong, LoggerBot> _botsByApplicationId
|
||||
|
|
@ -119,28 +122,33 @@ public class LoggerCleanService
|
|||
try
|
||||
{
|
||||
// We try two ways of extracting the actual message, depending on the bots
|
||||
// Some bots have different log formats so we check for both types of extract function
|
||||
if (bot.FuzzyExtractFunc != null)
|
||||
{
|
||||
// Some bots (Carl, Circle, etc) only give us a user ID and a rough timestamp, so we try our best to
|
||||
// Some bots (Carl, Circle, etc) only give us a user ID, so we try our best to
|
||||
// "cross-reference" those with the message DB. We know the deletion event happens *after* the message
|
||||
// was sent, so we're checking for any messages sent in the same guild within 3 seconds before the
|
||||
// delete event timestamp, which is... good enough, I think? Potential for false positives and negatives
|
||||
// delete event log, which is... good enough, I think? Potential for false positives and negatives
|
||||
// either way but shouldn't be too much, given it's constrained by user ID and guild.
|
||||
var fuzzy = bot.FuzzyExtractFunc(msg);
|
||||
if (fuzzy == null) return;
|
||||
if (fuzzy != null)
|
||||
{
|
||||
|
||||
_logger.Debug("Fuzzy logclean for {BotName} on {MessageId}: {@FuzzyExtractResult}",
|
||||
bot.Name, msg.Id, fuzzy);
|
||||
_logger.Debug("Fuzzy logclean for {BotName} on {MessageId}: {@FuzzyExtractResult}",
|
||||
bot.Name, msg.Id, fuzzy);
|
||||
|
||||
var exists = await _redis.HasLogCleanup(fuzzy.Value.User, msg.GuildId.Value);
|
||||
var exists = await _redis.HasLogCleanup(fuzzy.Value.User, msg.GuildId.Value);
|
||||
_logger.Debug(exists.ToString());
|
||||
|
||||
// If we didn't find a corresponding message, bail
|
||||
if (!exists) return;
|
||||
// If we didn't find a corresponding message, bail
|
||||
if (!exists) return;
|
||||
|
||||
// Otherwise, we can *reasonably assume* that this is a logged deletion, so delete the log message.
|
||||
await _client.DeleteMessage(msg.ChannelId, msg.Id);
|
||||
// Otherwise, we can *reasonably assume* that this is a logged deletion, so delete the log message.
|
||||
await _client.DeleteMessage(msg.ChannelId, msg.Id);
|
||||
|
||||
}
|
||||
}
|
||||
else if (bot.ExtractFunc != null)
|
||||
if (bot.ExtractFunc != null)
|
||||
{
|
||||
// Other bots give us the message ID itself, and we can just extract that from the database directly.
|
||||
var extractedId = bot.ExtractFunc(msg);
|
||||
|
|
@ -150,10 +158,11 @@ public class LoggerCleanService
|
|||
bot.Name, msg.Id, extractedId);
|
||||
|
||||
var mid = await _redis.GetOriginalMid(extractedId.Value);
|
||||
if (mid == null) return;
|
||||
|
||||
// If we've gotten this far, we found a logged deletion of a trigger message. Just yeet it!
|
||||
await _client.DeleteMessage(msg.ChannelId, msg.Id);
|
||||
if (mid != null)
|
||||
{
|
||||
// If we've gotten this far, we found a logged deletion of a trigger message. Just yeet it!
|
||||
await _client.DeleteMessage(msg.ChannelId, msg.Id);
|
||||
}
|
||||
} // else should not happen, but idk, it might
|
||||
}
|
||||
catch (NotFoundException)
|
||||
|
|
@ -258,8 +267,8 @@ public class LoggerCleanService
|
|||
private static FuzzyExtractResult? ExtractCircle(Message msg)
|
||||
{
|
||||
// Like Auttaja, Circle has both embed and compact modes, but the regex works for both.
|
||||
// Compact: "Message from [user] ([id]) deleted in [channel]", no timestamp (use message time)
|
||||
// Embed: Message Author field: "[user] ([id])", then an embed timestamp
|
||||
// Compact: "Message from [user] ([id]) deleted in [channel]"
|
||||
// Embed: Message Author field: "[user] ([id])"
|
||||
var stringWithId = msg.Content;
|
||||
if (msg.Embeds?.Length > 0)
|
||||
{
|
||||
|
|
@ -276,24 +285,21 @@ public class LoggerCleanService
|
|||
return match.Success
|
||||
? new FuzzyExtractResult
|
||||
{
|
||||
User = ulong.Parse(match.Groups[1].Value),
|
||||
ApproxTimestamp = msg.Timestamp().ToInstant()
|
||||
User = ulong.Parse(match.Groups[1].Value)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
private static FuzzyExtractResult? ExtractPancake(Message msg)
|
||||
{
|
||||
// Embed, author is "Message Deleted", description includes a mention, timestamp is *message send time* (but no ID)
|
||||
// so we use the message timestamp to get somewhere *after* the message was proxied
|
||||
// Embed, author is "Message Deleted", description includes a mention
|
||||
var embed = msg.Embeds?.FirstOrDefault();
|
||||
if (embed?.Description == null || embed.Author?.Name != "Message Deleted") return null;
|
||||
var match = _pancakeRegex.Match(embed.Description);
|
||||
return match.Success
|
||||
? new FuzzyExtractResult
|
||||
{
|
||||
User = ulong.Parse(match.Groups[1].Value),
|
||||
ApproxTimestamp = msg.Timestamp().ToInstant()
|
||||
User = ulong.Parse(match.Groups[1].Value)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
|
@ -316,8 +322,7 @@ public class LoggerCleanService
|
|||
return match.Success
|
||||
? new FuzzyExtractResult
|
||||
{
|
||||
User = ulong.Parse(match.Groups[1].Value),
|
||||
ApproxTimestamp = msg.Timestamp().ToInstant()
|
||||
User = ulong.Parse(match.Groups[1].Value)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
|
@ -333,8 +338,7 @@ public class LoggerCleanService
|
|||
return match.Success
|
||||
? new FuzzyExtractResult
|
||||
{
|
||||
User = ulong.Parse(match.Groups[1].Value),
|
||||
ApproxTimestamp = msg.Timestamp().ToInstant()
|
||||
User = ulong.Parse(match.Groups[1].Value)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
|
@ -342,14 +346,12 @@ public class LoggerCleanService
|
|||
private static FuzzyExtractResult? ExtractGearBot(Message msg)
|
||||
{
|
||||
// Simple text based message log.
|
||||
// No message ID, but we have timestamp and author ID.
|
||||
// Not using timestamp here though (seems to be same as message timestamp), might be worth implementing in the future.
|
||||
// No message ID, but we have author ID.
|
||||
var match = _GearBotRegex.Match(msg.Content);
|
||||
return match.Success
|
||||
? new FuzzyExtractResult
|
||||
{
|
||||
User = ulong.Parse(match.Groups[1].Value),
|
||||
ApproxTimestamp = msg.Timestamp().ToInstant()
|
||||
User = ulong.Parse(match.Groups[1].Value)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
|
@ -364,14 +366,11 @@ public class LoggerCleanService
|
|||
|
||||
private static FuzzyExtractResult? ExtractVortex(Message msg)
|
||||
{
|
||||
// timestamp is HH:MM:SS
|
||||
// however, that can be set to the user's timezone, so we just use the message timestamp
|
||||
var match = _VortexRegex.Match(msg.Content);
|
||||
return match.Success
|
||||
? new FuzzyExtractResult
|
||||
{
|
||||
User = ulong.Parse(match.Groups[2].Value),
|
||||
ApproxTimestamp = msg.Timestamp().ToInstant()
|
||||
User = ulong.Parse(match.Groups[2].Value)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
|
@ -379,15 +378,12 @@ public class LoggerCleanService
|
|||
private static FuzzyExtractResult? ExtractProBot(Message msg)
|
||||
{
|
||||
// user ID and channel ID are in the embed description (we don't use channel ID)
|
||||
// timestamp is in the embed footer
|
||||
if (msg.Embeds.Length == 0 || msg.Embeds[0].Description == null) return null;
|
||||
var match = _ProBotRegex.Match(msg.Embeds[0].Description);
|
||||
return match.Success
|
||||
? new FuzzyExtractResult
|
||||
{
|
||||
User = ulong.Parse(match.Groups[1].Value),
|
||||
ApproxTimestamp = OffsetDateTimePattern.Rfc3339
|
||||
.Parse(msg.Embeds[0].Timestamp).GetValueOrThrow().ToInstant()
|
||||
User = ulong.Parse(match.Groups[1].Value)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
|
@ -407,6 +403,30 @@ public class LoggerCleanService
|
|||
return match.Success ? ulong.Parse(match.Groups[3].Value) : null;
|
||||
}
|
||||
|
||||
private static ulong? ExtractAnnabelle(Message msg)
|
||||
{
|
||||
// this bot has both an embed and a non-embed log format
|
||||
// the embed is precise matching (this), the non-embed is fuzzy (below)
|
||||
var embed = msg.Embeds?.FirstOrDefault();
|
||||
if (embed?.Author?.Name == null || !embed.Author.Name.EndsWith("Deleted Message")) return null;
|
||||
var match = _AnnabelleRegex.Match(embed.Fields[2].Value);
|
||||
return match.Success ? ulong.Parse(match.Groups[1].Value) : null;
|
||||
}
|
||||
|
||||
private static FuzzyExtractResult? ExtractAnnabelleFuzzy(Message msg)
|
||||
{
|
||||
// matching for annabelle's non-precise non-embed format
|
||||
// it has a discord (unix) timestamp for the message so we use that
|
||||
if (msg.Embeds.Length != 0) return null;
|
||||
var match = _AnnabelleRegexFuzzy.Match(msg.Content);
|
||||
return match.Success
|
||||
? new FuzzyExtractResult
|
||||
{
|
||||
User = ulong.Parse(match.Groups[2].Value)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
public class LoggerBot
|
||||
{
|
||||
public ulong Id;
|
||||
|
|
@ -431,6 +451,5 @@ public class LoggerCleanService
|
|||
public struct FuzzyExtractResult
|
||||
{
|
||||
public ulong User { get; set; }
|
||||
public Instant ApproxTimestamp { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ using Newtonsoft.Json.Linq;
|
|||
|
||||
using Serilog;
|
||||
|
||||
using PluralKit.Core;
|
||||
using Myriad.Utils;
|
||||
|
||||
namespace PluralKit.Bot;
|
||||
|
|
@ -87,7 +86,8 @@ public class WebhookExecutorService
|
|||
return webhookMessage;
|
||||
}
|
||||
|
||||
public async Task<Message> EditWebhookMessage(ulong guildId, ulong channelId, ulong messageId, string newContent, bool clearEmbeds = false)
|
||||
public async Task<Message> EditWebhookMessage(ulong guildId, ulong channelId, ulong messageId, string newContent,
|
||||
bool clearEmbeds = false, bool clearAttachments = false)
|
||||
{
|
||||
var allowedMentions = newContent.ParseMentions() with
|
||||
{
|
||||
|
|
@ -108,7 +108,10 @@ public class WebhookExecutorService
|
|||
{
|
||||
Content = newContent,
|
||||
AllowedMentions = allowedMentions,
|
||||
Embeds = (clearEmbeds == true ? Optional<Embed[]>.Some(new Embed[] { }) : Optional<Embed[]>.None()),
|
||||
Embeds = (clearEmbeds ? Optional<Embed[]>.Some(new Embed[] { }) : Optional<Embed[]>.None()),
|
||||
Attachments = (clearAttachments
|
||||
? Optional<Message.Attachment[]>.Some(new Message.Attachment[] { })
|
||||
: Optional<Message.Attachment[]>.None())
|
||||
};
|
||||
|
||||
return await _rest.EditWebhookMessage(webhook.Id, webhook.Token, messageId, editReq, threadId);
|
||||
|
|
|
|||
|
|
@ -116,6 +116,15 @@
|
|||
"App.Metrics.Formatters.InfluxDB": "4.1.0"
|
||||
}
|
||||
},
|
||||
"AppFact.SerilogOpenSearchSink": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.0.8",
|
||||
"contentHash": "RI3lfmvAwhqrYwy5KPqsBT/tB/opSzeoVH2WUfvGKNBpl6ILCw/5wE8+19L+XMzBFVqgZ5QmkQ2PqTzG9I/ckA==",
|
||||
"dependencies": {
|
||||
"OpenSearch.Client": "1.4.0",
|
||||
"Serilog": "2.12.0"
|
||||
}
|
||||
},
|
||||
"Autofac": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
|
|
@ -464,6 +473,24 @@
|
|||
"Npgsql": "4.1.5"
|
||||
}
|
||||
},
|
||||
"OpenSearch.Client": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "91TXm+I8PzxT0yxkf0q5Quee5stVcIFys56pU8+M3+Kiakx+aiaTAlZJHfA3Oy6dMJndNkVm37IPKYCqN3dS4g==",
|
||||
"dependencies": {
|
||||
"OpenSearch.Net": "1.4.0"
|
||||
}
|
||||
},
|
||||
"OpenSearch.Net": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "xCM6m3aArN9gXIl2DvWXaDlbpjOBbgMeRPsAM7s1eDbWhu8wKNyfPNKSfep4JlYXkZ7N6Oi/+lmi+G3/SpcqlQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.CSharp": "4.7.0",
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Diagnostics.DiagnosticSource": "6.0.1"
|
||||
}
|
||||
},
|
||||
"Pipelines.Sockets.Unofficial": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.8",
|
||||
|
|
@ -726,8 +753,8 @@
|
|||
},
|
||||
"System.Buffers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A=="
|
||||
"resolved": "4.5.1",
|
||||
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
|
||||
},
|
||||
"System.Collections": {
|
||||
"type": "Transitive",
|
||||
|
|
@ -780,8 +807,11 @@
|
|||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.7.1",
|
||||
"contentHash": "j81Lovt90PDAq8kLpaJfJKV/rWdWuEk6jfV+MBkee33vzYLEUsy4gXK8laa9V2nZlLM9VM9yA/OOQxxPEJKAMw=="
|
||||
"resolved": "6.0.1",
|
||||
"contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==",
|
||||
"dependencies": {
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"System.Diagnostics.Tools": {
|
||||
"type": "Transitive",
|
||||
|
|
@ -1123,8 +1153,8 @@
|
|||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.7.1",
|
||||
"contentHash": "zOHkQmzPCn5zm/BH+cxC1XbUS3P4Yoi3xzW7eRgVpDR2tPGSzyMZ17Ig1iRkfJuY0nhxkQQde8pgePNiA7z7TQ=="
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
||||
},
|
||||
"System.Runtime.Extensions": {
|
||||
"type": "Transitive",
|
||||
|
|
@ -1459,6 +1489,7 @@
|
|||
"dependencies": {
|
||||
"App.Metrics": "[4.1.0, )",
|
||||
"App.Metrics.Reporting.InfluxDB": "[4.1.0, )",
|
||||
"AppFact.SerilogOpenSearchSink": "[0.0.8, )",
|
||||
"Autofac": "[6.0.0, )",
|
||||
"Autofac.Extensions.DependencyInjection": "[7.1.0, )",
|
||||
"Dapper": "[2.0.35, )",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue