Merge remote-tracking branch 'upstream/main' into rust-command-parser

This commit is contained in:
dusk 2025-01-21 00:46:50 +09:00
commit 07b616e422
No known key found for this signature in database
30 changed files with 1253 additions and 282 deletions

View file

@ -91,11 +91,12 @@ public class Groups
$"> {ctx.DefaultPrefix}group **{reference}** description **This is my new group, and here is the description!**"))
.Field(new Embed.Field("Set the group icon",
$"> {ctx.DefaultPrefix}group **{reference}** icon\n*(with an image attached)*"));
await ctx.Reply($"{Emojis.Success} Group created!", eb.Build());
var replyStr = $"{Emojis.Success} Group created!";
if (existingGroupCount >= Limits.WarnThreshold(groupLimit))
await ctx.Reply(
$"{Emojis.Warn} You are approaching the per-system group limit ({existingGroupCount} / {groupLimit} groups). Once you reach this limit, you will be unable to create new groups until existing groups are deleted, or you can ask for your limit to be raised in the PluralKit support server: <https://discord.gg/PczBt78>");
replyStr += $"\n{Emojis.Warn} You are approaching the per-system group limit ({existingGroupCount} / {groupLimit} groups). Once you reach this limit, you will be unable to create new groups until existing groups are deleted, or you can ask for your limit to be raised in the PluralKit support server: <https://discord.gg/PczBt78>";
await ctx.Reply(replyStr, eb.Build());
}
public async Task RenameGroup(Context ctx, PKGroup target)
@ -125,18 +126,19 @@ public class Groups
public async Task GroupDisplayName(Context ctx, PKGroup target)
{
var noDisplayNameSetMessage = "This group does not have a display name set.";
if (ctx.System?.Id == target.System)
noDisplayNameSetMessage +=
$" To set one, type `{ctx.DefaultPrefix}group {target.Reference(ctx)} displayname <display name>`.";
var noDisplayNameSetMessage = "This group does not have a display name set" +
(ctx.System?.Id == target.System
? $". To set one, type `{ctx.DefaultPrefix}group {target.Reference(ctx)} displayname <display name>`."
: " or name is private.");
// No perms check, display name isn't covered by member privacy
// Whether displayname is shown or not should depend on if group name privacy is set.
// If name privacy is on then displayname should look like name.
var format = ctx.MatchFormat();
// if there's nothing next or what's next is "raw"/"plaintext" we're doing a query, so check for null
if (!ctx.HasNext(false) || format != ReplyFormat.Standard)
if (target.DisplayName == null)
// if we're doing a raw or plaintext query check for null
if (format != ReplyFormat.Standard)
if (target.DisplayName == null || !target.NamePrivacy.CanAccess(ctx.DirectLookupContextFor(target.System)))
{
await ctx.Reply(noDisplayNameSetMessage);
return;
@ -157,9 +159,12 @@ public class Groups
if (!ctx.HasNext(false))
{
var showDisplayName = target.NamePrivacy.CanAccess(ctx.LookupContextFor(target.System)) && target.DisplayName != null;
var eb = new EmbedBuilder()
.Field(new Embed.Field("Name", target.Name))
.Field(new Embed.Field("Display Name", target.DisplayName));
.Title("Group names")
.Field(new Embed.Field("Name", target.NameFor(ctx)))
.Field(new Embed.Field("Display Name", showDisplayName ? target.DisplayName : "*(no displayname set or name is private)*"));
var reference = target.Reference(ctx);
@ -169,7 +174,7 @@ public class Groups
+ $"To clear it, type `{ctx.DefaultPrefix}group {reference} displayname -clear`.\n"
+ $"To print the raw display name, type `{ctx.DefaultPrefix}group {reference} displayname -raw`.");
if (ctx.System?.Id == target.System)
if (ctx.System?.Id == target.System && showDisplayName)
eb.Footer(new Embed.EmbedFooter($"Using {target.DisplayName.Length}/{Limits.MaxGroupNameLength} characters."));
await ctx.Reply(embed: eb.Build());
@ -184,9 +189,10 @@ public class Groups
var patch = new GroupPatch { DisplayName = Partial<string>.Null() };
await ctx.Repository.UpdateGroup(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Group display name cleared.");
var replyStr = $"{Emojis.Success} Group display name cleared.";
if (target.NamePrivacy == PrivacyLevel.Private)
await ctx.Reply($"{Emojis.Warn} Since this group no longer has a display name set, their name privacy **can no longer take effect**.");
replyStr += $"\n{Emojis.Warn} Since this group no longer has a display name set, their name privacy **can no longer take effect**.";
await ctx.Reply(replyStr);
}
else
{
@ -599,12 +605,12 @@ public class Groups
_ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})")
};
await ctx.Reply(
$"{Emojis.Success} {target.Name}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}");
var replyStr = $"{Emojis.Success} {target.Name}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}";
if (subject == GroupPrivacySubject.Name && level == PrivacyLevel.Private && target.DisplayName == null)
await ctx.Reply(
$"{Emojis.Warn} This group does not have a display name set, and name privacy **will not take effect**.");
replyStr += $"\n{Emojis.Warn} This group does not have a display name set, and name privacy **will not take effect**.";
await ctx.Reply(replyStr);
}
if (ctx.Match("all") || newValueFromCommand != null)

View file

@ -103,28 +103,23 @@ public class Member
});
// Send confirmation and space hint
await ctx.Reply(
$"{Emojis.Success} Member \"{memberName}\" (`{member.DisplayHid(ctx.Config)}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member");
var replyStr = $"{Emojis.Success} Member \"{memberName}\" (`{member.DisplayHid(ctx.Config)}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member";
// todo: move this to ModelRepository
if (await ctx.Database.Execute(conn => conn.QuerySingleAsync<bool>("select has_private_members(@System)",
new { System = ctx.System.Id })) && !ctx.Config.MemberDefaultPrivate) //if has private members
await ctx.Reply(
$"{Emojis.Warn} This member is currently **public**. To change this, use `{ctx.DefaultPrefix}member {member.DisplayHid(ctx.Config)} private`.");
replyStr += $"\n{Emojis.Warn} This member is currently **public**. To change this, use `{ctx.DefaultPrefix}member {member.DisplayHid(ctx.Config)} private`.";
if (avatarArg != null)
if (imageMatchError == null)
await ctx.Reply(
$"{Emojis.Success} Member avatar set to attached image." + (img.Source == AvatarSource.Attachment ? $"\n{Emojis.Warn} If you delete the message containing the attachment, the avatar will stop working." : ""));
replyStr += $"\n{Emojis.Success} Member avatar set to attached image." + (img.Source == AvatarSource.Attachment ? $"\n{Emojis.Warn} If you delete the message containing the attachment, the avatar will stop working." : "");
else
await ctx.Reply($"{Emojis.Error} Couldn't set avatar: {imageMatchError.Message}");
replyStr += $"\n{Emojis.Error} Couldn't set avatar: {imageMatchError.Message}";
if (memberName.Contains(" "))
await ctx.Reply(
$"{Emojis.Note} Note that this member's name contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it, or just use the member's short ID (which is `{member.DisplayHid(ctx.Config)}`).");
replyStr += $"\n{Emojis.Note} Note that this member's name contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it, or just use the member's short ID (which is `{member.DisplayHid(ctx.Config)}`).";
if (memberCount >= memberLimit)
await ctx.Reply(
$"{Emojis.Warn} You have reached the per-system member limit ({memberLimit}). If you need to add more members, you can either delete existing members, or ask for your limit to be raised in the PluralKit support server: <https://discord.gg/PczBt78>");
replyStr += $"\n{Emojis.Warn} You have reached the per-system member limit ({memberLimit}). If you need to add more members, you can either delete existing members, or ask for your limit to be raised in the PluralKit support server: <https://discord.gg/PczBt78>";
else if (memberCount >= Limits.WarnThreshold(memberLimit))
await ctx.Reply(
$"{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {memberLimit} members). Once you reach this limit, you will be unable to create new members until existing members are deleted, or you can ask for your limit to be raised in the PluralKit support server: <https://discord.gg/PczBt78>");
replyStr += $"\n{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {memberLimit} members). Once you reach this limit, you will be unable to create new members until existing members are deleted, or you can ask for your limit to be raised in the PluralKit support server: <https://discord.gg/PczBt78>";
await ctx.Reply(replyStr);
}
public async Task ViewMember(Context ctx)

View file

@ -23,6 +23,32 @@ public class MemberEdit
public async Task Name(Context ctx, PKMember target)
{
var format = ctx.MatchFormat();
if (!ctx.HasNext() || format != ReplyFormat.Standard)
{
var lctx = ctx.DirectLookupContextFor(target.System);
switch (format)
{
case ReplyFormat.Raw:
await ctx.Reply($"```{target.NameFor(lctx)}```");
break;
case ReplyFormat.Plaintext:
var eb = new EmbedBuilder()
.Description($"Showing name for member {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
await ctx.Reply(target.NameFor(lctx), embed: eb.Build());
break;
default:
var replyStrQ = $"Name for member {target.DisplayHid(ctx.Config)} is **{target.NameFor(lctx)}**.";
if (target.System == ctx.System?.Id)
replyStrQ += $"\nTo rename {target.DisplayHid(ctx.Config)} type `{ctx.DefaultPrefix}member {target.NameFor(ctx)} rename <new name>`."
+ $" Using {target.NameFor(lctx).Length}/{Limits.MaxMemberNameLength} characters.";
await ctx.Reply(replyStrQ);
break;
}
return;
}
ctx.CheckSystem().CheckOwnMember(target);
var newName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a new name for the member.");
@ -44,21 +70,19 @@ public class MemberEdit
var patch = new MemberPatch { Name = Partial<string>.Present(newName) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Member renamed (using {newName.Length}/{Limits.MaxMemberNameLength} characters).");
var replyStr = $"{Emojis.Success} Member renamed (using {newName.Length}/{Limits.MaxMemberNameLength} characters).";
if (newName.Contains(" "))
await ctx.Reply(
$"{Emojis.Note} Note that this member's name now contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it, or just use the member's short ID (which is `{target.DisplayHid(ctx.Config)}`).");
replyStr += $"\n{Emojis.Note} Note that this member's name now contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it, or just use the member's short ID (which is `{target.DisplayHid(ctx.Config)}`).";
if (target.DisplayName != null)
await ctx.Reply(
$"{Emojis.Note} Note that this member has a display name set ({target.DisplayName}), and will be proxied using that name instead.");
replyStr += $"\n{Emojis.Note} Note that this member has a display name set ({target.DisplayName}), and will be proxied using that name instead.";
if (ctx.Guild != null)
{
var memberGuildConfig = await ctx.Repository.GetMemberGuild(ctx.Guild.Id, target.Id);
if (memberGuildConfig.DisplayName != null)
await ctx.Reply(
$"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName}) in this server ({ctx.Guild.Name}), and will be proxied using that name here.");
replyStr += $"\n{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName}) in this server ({ctx.Guild.Name}), and will be proxied using that name here.";
}
await ctx.Reply(replyStr);
}
public async Task Description(Context ctx, PKMember target)
@ -102,8 +126,8 @@ public class MemberEdit
$"To print the description with formatting, type `{ctx.DefaultPrefix}member {target.Reference(ctx)} description -raw`."
+ (ctx.System?.Id == target.System
? $" To clear it, type `{ctx.DefaultPrefix}member {target.Reference(ctx)} description -clear`."
: "")
+ $" Using {target.Description.Length}/{Limits.MaxDescriptionLength} characters."))
+ $" Using {target.Description.Length}/{Limits.MaxDescriptionLength} characters."
: "")))
.Build());
return;
}
@ -166,8 +190,8 @@ public class MemberEdit
$"**{target.NameFor(ctx)}**'s pronouns are **{target.Pronouns}**.\nTo print the pronouns with formatting, type `{ctx.DefaultPrefix}member {target.Reference(ctx)} pronouns -raw`."
+ (ctx.System?.Id == target.System
? $" To clear them, type `{ctx.DefaultPrefix}member {target.Reference(ctx)} pronouns -clear`."
: "")
+ $" Using {target.Pronouns.Length}/{Limits.MaxPronounsLength} characters.");
+ $" Using {target.Pronouns.Length}/{Limits.MaxPronounsLength} characters."
: ""));
return;
}
@ -377,7 +401,7 @@ public class MemberEdit
.Footer(new Embed.EmbedFooter(
$"Member ID: {target.DisplayHid(ctx.Config)} | Active name in bold. Server name overrides display name, which overrides base name."
+ (target.DisplayName != null && ctx.System?.Id == target.System ? $" Using {target.DisplayName.Length}/{Limits.MaxMemberNameLength} characters for the display name." : "")
+ (memberGuildConfig?.DisplayName != null ? $" Using {memberGuildConfig?.DisplayName.Length}/{Limits.MaxMemberNameLength} characters for the server name." : "")));
+ (memberGuildConfig?.DisplayName != null && ctx.System?.Id == target.System ? $" Using {memberGuildConfig?.DisplayName.Length}/{Limits.MaxMemberNameLength} characters for the server name." : "")));
var showDisplayName = target.NamePrivacy.CanAccess(lcx);
@ -388,7 +412,7 @@ public class MemberEdit
eb.Field(new Embed.Field("Display name", (target.DisplayName != null && showDisplayName)
? boldIf(target.DisplayName, memberGuildConfig?.DisplayName == null)
: "*(none)*"
: "*(no displayname set or name is private)*"
));
if (ctx.Guild != null)
@ -417,18 +441,18 @@ public class MemberEdit
await ctx.Reply(successStr);
}
var noDisplayNameSetMessage = "This member does not have a display name set.";
if (ctx.System?.Id == target.System)
noDisplayNameSetMessage +=
$" To set one, type `{ctx.DefaultPrefix}member {target.Reference(ctx)} displayname <display name>`.";
var isOwner = ctx.System?.Id == target.System;
var noDisplayNameSetMessage = $"This member does not have a display name set{(isOwner ? "" : " or name is private")}."
+ (isOwner ? $" To set one, type `{ctx.DefaultPrefix}member {target.Reference(ctx)} displayname <display name>`." : "");
// No perms check, display name isn't covered by member privacy
// Whether displayname is shown or not should depend on if member name privacy is set.
// If name privacy is on then displayname should look like name.
var format = ctx.MatchFormat();
// if what's next is "raw"/"plaintext" we need to check for null
if (format != ReplyFormat.Standard)
if (target.DisplayName == null)
if (target.DisplayName == null || !target.NamePrivacy.CanAccess(ctx.DirectLookupContextFor(target.System)))
{
await ctx.Reply(noDisplayNameSetMessage);
return;
@ -860,19 +884,18 @@ public class MemberEdit
_ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})")
};
await ctx.Reply(
$"{Emojis.Success} {target.NameFor(ctx)}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}");
var replyStr = $"{Emojis.Success} {target.NameFor(ctx)}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}";
// Name privacy only works given a display name
if (subject == MemberPrivacySubject.Name && level == PrivacyLevel.Private && target.DisplayName == null)
await ctx.Reply(
$"{Emojis.Warn} This member does not have a display name set, and name privacy **will not take effect**.");
replyStr += $"\n{Emojis.Warn} This member does not have a display name set, and name privacy **will not take effect**.";
// Avatar privacy doesn't apply when proxying if no server avatar is set
if (subject == MemberPrivacySubject.Avatar && level == PrivacyLevel.Private &&
guildSettings?.AvatarUrl == null)
await ctx.Reply(
$"{Emojis.Warn} This member does not have a server avatar set, so *proxying* will **still show the member avatar**. If you want to hide your avatar when proxying here, set a server avatar: `{ctx.DefaultPrefix}member {target.Reference(ctx)} serveravatar`");
replyStr += $"\n{Emojis.Warn} This member does not have a server avatar set, so *proxying* will **still show the member avatar**. If you want to hide your avatar when proxying here, set a server avatar: `{ctx.DefaultPrefix}member {target.Reference(ctx)} serveravatar`";
await ctx.Reply(replyStr);
}
if (ctx.Match("all") || newValueFromCommand != null)

View file

@ -3,12 +3,11 @@ using System.Diagnostics;
using App.Metrics;
using Myriad.Builders;
using Myriad.Cache;
using Myriad.Gateway;
using Myriad.Rest;
using Myriad.Rest.Types.Requests;
using Myriad.Types;
using Newtonsoft.Json;
using NodaTime;
using PluralKit.Core;
@ -57,78 +56,98 @@ public class Misc
var timeAfter = SystemClock.Instance.GetCurrentInstant();
var apiLatency = timeAfter - timeBefore;
var embed = new EmbedBuilder();
// todo: these will be inaccurate when the bot is actually multi-process
var messagesReceived = _metrics.Snapshot.GetForContext("Bot").Meters
.FirstOrDefault(m => m.MultidimensionalName == BotMetrics.MessagesReceived.Name)?.Value;
if (messagesReceived != null)
embed.Field(new Embed.Field("Messages processed",
$"{messagesReceived.OneMinuteRate * 60:F1}/m ({messagesReceived.FifteenMinuteRate * 60:F1}/m over 15m)",
true));
var messagesProxied = _metrics.Snapshot.GetForContext("Bot").Meters
.FirstOrDefault(m => m.MultidimensionalName == BotMetrics.MessagesProxied.Name)?.Value;
if (messagesProxied != null)
embed.Field(new Embed.Field("Messages proxied",
$"{messagesProxied.OneMinuteRate * 60:F1}/m ({messagesProxied.FifteenMinuteRate * 60:F1}/m over 15m)",
true));
var commandsRun = _metrics.Snapshot.GetForContext("Bot").Meters
.FirstOrDefault(m => m.MultidimensionalName == BotMetrics.CommandsRun.Name)?.Value;
if (commandsRun != null)
embed.Field(new Embed.Field("Commands executed",
$"{commandsRun.OneMinuteRate * 60:F1}/m ({commandsRun.FifteenMinuteRate * 60:F1}/m over 15m)",
true));
var isCluster = _botConfig.Cluster != null && _botConfig.Cluster.TotalShards != ctx.Cluster.Shards.Count;
var counts = await _repo.GetStats();
var process = Process.GetCurrentProcess();
var stats = await GetStats(ctx);
var shards = await _shards.GetShards();
var shardInfo = shards.Where(s => s.ShardId == ctx.ShardId).FirstOrDefault();
var shardsUp = shards.Where(s => s.Up).Count();
// todo: if we're running multiple processes, it is not useful to get the CPU/RAM usage of just the current one
var process = Process.GetCurrentProcess();
var memoryUsage = process.WorkingSet64;
if (stats == null)
{
var content = $"Stats unavailable (is scheduled_tasks service running?)\n\n**Quick info:**"
+ $"\nPluralKit [{BuildInfoService.Version}](<https://github.com/pluralkit/pluralkit/commit/{BuildInfoService.FullVersion}>)"
// + (BuildInfoService.IsDev ? ", **development build**" : "")
+ $"\nCurrently on shard {ctx.ShardId}, {shardsUp}/{shards.Count()} shards up,"
+ $" API latency: {apiLatency.TotalMilliseconds:F0}ms";
await ctx.Rest.EditMessage(msg.ChannelId, msg.Id,
new MessageEditRequest { Content = content });
return;
}
var now = SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds();
var shardUptime = Duration.FromSeconds(now - shardInfo.LastConnection);
var shardTotal = _botConfig.Cluster?.TotalShards ?? shards.Count();
int shardClusterTotal = ctx.Cluster.Shards.Count;
var shardUpTotal = shards.Where(x => x.Up).Count();
var embed = new EmbedBuilder();
embed
.Field(new Embed.Field("Current shard",
$"Shard #{ctx.ShardId} (of {shardTotal} total,"
+ (isCluster ? $" {shardClusterTotal} in this cluster," : "") + $" {shardUpTotal} are up)"
, true))
.Field(new Embed.Field("Shard uptime",
$"{shardUptime.FormatDuration()} ({shardInfo.DisconnectionCount} disconnections)", true))
.Field(new Embed.Field("CPU usage", $"{_cpu.LastCpuMeasure:P1}", true))
.Field(new Embed.Field("Memory usage", $"{memoryUsage / 1024 / 1024} MiB", true))
.Field(new Embed.Field("Latency",
$"API: {apiLatency.TotalMilliseconds:F0} ms, shard: {shardInfo.Latency} ms",
true));
.Field(new("Connection status", $"**{shards.Count()}** shards across **{shards.Select(s => s.ClusterId).Distinct().Count()}** clusters (**{shardsUp} up**)\n"
+ $"Current server is on **shard {ctx.ShardId} (cluster {shardInfo.ClusterId ?? 0})**\n"
+ $"Latency: API **{apiLatency.TotalMilliseconds:F0}ms** (p90: {stats.prom.nirn_proxy_latency_p90 * 1000:F0}ms, p99: {stats.prom.nirn_proxy_latency_p99 * 1000:F0}ms), "
+ $"shard **{shardInfo.Latency}ms** (avg: {stats.prom.shard_latency_average}ms)", true))
.Field(new("Resource usage", $"**CPU:** {stats.prom.cpu_used}% used / {stats.prom.cpu_total_cores} total cores ({stats.prom.cpu_total_threads} threads)\n"
+ $"**Memory:** {(stats.prom.memory_used / 1_000_000_000):N1}GB used / {(stats.prom.memory_total / 1_000_000_000):N1}GB total", true))
.Field(new("Usage metrics", $"Messages received: **{stats.prom.messages_1m}/s** ({stats.prom.messages_15m}/s over 15m)\n" +
$"Messages proxied: **{stats.prom.proxy_1m}/s** ({stats.prom.proxy_15m}/s over 15m, {stats.db.messages_24h:N0} total in last 24h)\n" +
$"Commands executed: **{stats.prom.commands_1m}/m** ({stats.prom.commands_15m}/m over 15m)"));
embed.Field(new("Total numbers", $" {counts.SystemCount:N0} systems,"
+ $" {counts.MemberCount:N0} members,"
+ $" {counts.GroupCount:N0} groups,"
+ $" {counts.SwitchCount:N0} switches,"
+ $" {counts.MessageCount:N0} messages"));
embed.Field(new("Total numbers", $"**{stats.db.systems:N0}** systems, **{stats.db.members:N0}** members, **{stats.db.groups:N0}** groups, "
+ $"**{stats.db.switches:N0}** switches, **{stats.db.messages:N0}** messages\n" +
$"**{stats.db.guilds:N0}** servers with **{stats.db.channels:N0}** channels"));
embed
.Footer(new(String.Join(" \u2022 ", new[] {
$"PluralKit {BuildInfoService.Version}",
(isCluster ? $"Cluster {_botConfig.Cluster.NodeIndex}" : ""),
"https://github.com/PluralKit/PluralKit",
"Last restarted:",
})))
.Timestamp(process.StartTime.ToString("O"));
embed.Footer(Help.helpEmbed.Footer);
var uptime = ((DateTimeOffset)process.StartTime).ToUnixTimeSeconds();
embed.Description($"### PluralKit [{BuildInfoService.Version}](https://github.com/pluralkit/pluralkit/commit/{BuildInfoService.FullVersion})\n" +
$"Built on <t:{BuildInfoService.Timestamp}> (<t:{BuildInfoService.Timestamp}:R>)"
// + (BuildInfoService.IsDev ? ", **development build**" : "")
+ $"\nLast restart: <t:{uptime}:R>");
await ctx.Rest.EditMessage(msg.ChannelId, msg.Id,
new MessageEditRequest { Content = "", Embeds = new[] { embed.Build() } });
}
}
private async Task<Stats?> GetStats(Context ctx)
{
var db = ctx.Redis.Connection.GetDatabase();
var data = await db.StringGetAsync("statsapi");
return data.HasValue ? JsonConvert.DeserializeObject<Stats>(data) : null;
}
}
// none of these fields are "assigned to" for some reason
#pragma warning disable CS0649
class Stats
{
public DbStats db;
public PrometheusStats prom;
};
class DbStats
{
public double systems;
public double members;
public double groups;
public double switches;
public double messages;
public double messages_24h;
public double guilds;
public double channels;
};
class PrometheusStats
{
public double messages_1m;
public double messages_15m;
public double proxy_1m;
public double proxy_15m;
public double commands_1m;
public double commands_15m;
public double cpu_total_cores;
public double cpu_total_threads;
public double cpu_used;
public double memory_total;
public double memory_used;
public double nirn_proxy_rps;
public double nirn_proxy_latency_p90;
public double nirn_proxy_latency_p99;
public double shard_latency_average;
};

View file

@ -10,6 +10,7 @@ using Myriad.Types;
using Newtonsoft.Json;
using PluralKit.Core;
using SqlKata.Compilers;
namespace PluralKit.Bot;
@ -64,8 +65,8 @@ public class SystemEdit
{
await ctx.Reply(
$"{(isOwnSystem ? "Your" : "This")} system's name is currently **{target.Name}**."
+ (isOwnSystem ? $" Type `{ctx.DefaultPrefix}system name -clear` to clear it." : "")
+ $" Using {target.Name.Length}/{Limits.MaxSystemNameLength} characters.");
+ (isOwnSystem ? $" Type `{ctx.DefaultPrefix}system name -clear` to clear it."
+ $" Using {target.Name.Length}/{Limits.MaxSystemNameLength} characters." : ""));
return;
}
@ -129,8 +130,8 @@ public class SystemEdit
{
await ctx.Reply(
$"{(isOwnSystem ? "Your" : "This")} system's name for this server is currently **{settings.DisplayName}**."
+ (isOwnSystem ? $" Type `{ctx.DefaultPrefix}system servername -clear` to clear it." : "")
+ $" Using {settings.DisplayName.Length}/{Limits.MaxSystemNameLength} characters.");
+ (isOwnSystem ? $" Type `{ctx.DefaultPrefix}system servername -clear` to clear it."
+ $" Using {settings.DisplayName.Length}/{Limits.MaxSystemNameLength} characters." : ""));
return;
}
@ -195,8 +196,8 @@ public class SystemEdit
.Description(target.Description)
.Footer(new Embed.EmbedFooter(
$"To print the description with formatting, type `{ctx.DefaultPrefix}s description -raw`."
+ (isOwnSystem ? $" To clear it, type `{ctx.DefaultPrefix}s description -clear`. To change it, type `{ctx.DefaultPrefix}s description <new description>`." : "")
+ $" Using {target.Description.Length}/{Limits.MaxDescriptionLength} characters."))
+ (isOwnSystem ? $" To clear it, type `{ctx.DefaultPrefix}s description -clear`. To change it, type `{ctx.DefaultPrefix}s description <new description>`."
+ $" Using {target.Description.Length}/{Limits.MaxDescriptionLength} characters." : "")))
.Build());
return;
}
@ -317,7 +318,19 @@ public class SystemEdit
{
await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { Tag = null });
await ctx.Reply($"{Emojis.Success} System tag cleared.");
var replyStr = $"{Emojis.Success} System tag cleared.";
if (ctx.Guild != null)
{
var servertag = (await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id)).Tag;
if (servertag is not null)
replyStr += $"\n{Emojis.Note} You have a server tag set in this server ({servertag}) so it will still be shown on proxies.";
else if (ctx.GuildConfig.RequireSystemTag)
replyStr += $"\n{Emojis.Warn} This server requires a tag in order to proxy. If you do not add a new tag you will not be able to proxy in this server.";
}
await ctx.Reply(replyStr);
}
else
{
@ -328,8 +341,24 @@ public class SystemEdit
await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { Tag = newTag });
await ctx.Reply(
$"{Emojis.Success} System tag changed (using {newTag.Length}/{Limits.MaxSystemTagLength} characters). Member names will now have the tag {newTag.AsCode()} when proxied.\n\nTo check or change where your tag appears in your name use the command `{ctx.DefaultPrefix}cfg name format`.");
var replyStr = $"{Emojis.Success} System tag changed (using {newTag.Length}/{Limits.MaxSystemTagLength} characters).";
if (ctx.Config.NameFormat is null || ctx.Config.NameFormat.Contains("{tag}"))
replyStr += $"Member names will now have the tag {newTag.AsCode()} when proxied.\n{Emojis.Note}To check or change where your tag appears in your name use the command `{ctx.DefaultPrefix}cfg name format`.";
else
replyStr += $"\n{Emojis.Warn} You do not have a designated place for a tag in your name format so it **will not be put in proxy names**. To change this type `{ctx.DefaultPrefix}cfg name format`.";
if (ctx.Guild != null)
{
var guildSettings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id);
if (guildSettings.Tag is not null)
replyStr += $"\n{Emojis.Note} Note that you have a server tag set ({guildSettings.Tag}) and it will be shown in proxies instead.";
if (!guildSettings.TagEnabled)
replyStr += $"\n{Emojis.Note} Note that your tag is disabled in this server and will not be shown in proxies. To change this type `{ctx.DefaultPrefix}system servertag enable`.";
if (guildSettings.NameFormat is not null && !guildSettings.NameFormat.Contains("{tag}"))
replyStr += $"\n{Emojis.Note} You do not have a designated place for a tag in your server name format so it **will not be put in proxy names**. To change this type `{ctx.DefaultPrefix}cfg server name format`.";
}
await ctx.Reply(replyStr);
}
}
@ -386,22 +415,24 @@ public class SystemEdit
await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { Tag = newTag });
await ctx.Reply(
$"{Emojis.Success} System server tag changed (using {newTag.Length}/{Limits.MaxSystemTagLength} characters). Member names will now have the tag {newTag.AsCode()} when proxied in the current server '{ctx.Guild.Name}'.\n\nTo check or change where your tag appears in your name use the command `{ctx.DefaultPrefix}cfg name format`.");
var replyStr = $"{Emojis.Success} System server tag changed (using {newTag.Length}/{Limits.MaxSystemTagLength} characters). Member names will now have the tag {newTag.AsCode()} when proxied in the current server '{ctx.Guild.Name}'.\n\nTo check or change where your tag appears in your name use the command `{ctx.DefaultPrefix}cfg name format`.";
if (!settings.TagEnabled)
await ctx.Reply(setDisabledWarning);
replyStr += "\n" + setDisabledWarning;
await ctx.Reply(replyStr);
}
async Task Clear()
{
await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { Tag = null });
await ctx.Reply(
$"{Emojis.Success} System server tag cleared. Member names will now use the global system tag, if there is one set.\n\nTo check or change where your tag appears in your name use the command `{ctx.DefaultPrefix}cfg name format`.");
var replyStr = $"{Emojis.Success} System server tag cleared. Member names will now use the global system tag, if there is one set.\n\nTo check or change where your tag appears in your name use the command `{ctx.DefaultPrefix}cfg name format`.";
if (!settings.TagEnabled)
await ctx.Reply(setDisabledWarning);
replyStr += "\n" + setDisabledWarning;
await ctx.Reply(replyStr);
}
async Task EnableDisable(bool newValue)
@ -500,8 +531,7 @@ public class SystemEdit
{
await ctx.Reply($"{(isOwnSystem ? "Your" : "This system's")} current pronouns are **{target.Pronouns}**.\nTo print the pronouns with formatting, type `{ctx.DefaultPrefix}system pronouns -raw`."
+ (isOwnSystem ? $" To clear them, type `{ctx.DefaultPrefix}system pronouns -clear`."
: "")
+ $" Using {target.Pronouns.Length}/{Limits.MaxPronounsLength} characters.");
+ $" Using {target.Pronouns.Length}/{Limits.MaxPronounsLength} characters." : ""));
return;
}
@ -567,6 +597,9 @@ public class SystemEdit
async Task ShowIcon()
{
if ((target.AvatarUrl?.Trim() ?? "").Length > 0)
{
if (!target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.Id)))
throw new PKSyntaxError("This system does not have an icon set or it is private.");
switch (ctx.MatchFormat())
{
case ReplyFormat.Raw:
@ -586,9 +619,14 @@ public class SystemEdit
await ctx.Reply(embed: ebS.Build());
break;
}
}
else
{
var isOwner = target.Id == ctx.System?.Id;
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.");
$"This system does not have an icon set{(isOwner ? "" : " or it is private")}."
+ (isOwner ? " 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)
@ -646,10 +684,12 @@ public class SystemEdit
async Task ShowIcon()
{
var settings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id);
if ((settings.AvatarUrl?.Trim() ?? "").Length > 0)
{
if (!target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.Id)))
throw new PKSyntaxError("This system does not have a icon specific to this server or it is private.");
switch (ctx.MatchFormat())
{
case ReplyFormat.Raw:
@ -669,9 +709,14 @@ public class SystemEdit
await ctx.Reply(embed: ebS.Build());
break;
}
}
else
{
var isOwner = target.Id == ctx.System?.Id;
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.");
$"This system does not have a icon specific to this server{(isOwner ? "" : " or it is private")}."
+ (isOwner ? " Set one by attaching an image to this command, or by passing an image URL or @mention." : ""));
}
}
ctx.CheckGuildContext();