mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-08 06:47:56 +00:00
Merge branch 'PluralKit:main' into main
This commit is contained in:
commit
07959763ea
179 changed files with 8551 additions and 3544 deletions
|
|
@ -73,7 +73,7 @@ public class Bot
|
|||
}
|
||||
};
|
||||
|
||||
_services.Resolve<RedisGatewayService>().OnEventReceived += (e) => OnEventReceivedInner(e.Item1, e.Item2);
|
||||
_services.Resolve<RedisGatewayService>().OnEventReceived += (e) => OnEventReceived(e.Item1, e.Item2);
|
||||
|
||||
// Init the shard stuff
|
||||
_services.Resolve<ShardInfoService>().Init();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,20 @@ public static class BotMetrics
|
|||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static MeterOptions DatabaseDMCacheHits => new()
|
||||
{
|
||||
Name = "Database DM Cache Hits",
|
||||
MeasurementUnit = Unit.Calls,
|
||||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static MeterOptions DMCacheMisses => new()
|
||||
{
|
||||
Name = "DM Cache Misses",
|
||||
MeasurementUnit = Unit.Calls,
|
||||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static MeterOptions CommandsRun => new()
|
||||
{
|
||||
Name = "Commands run",
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ public partial class CommandTree
|
|||
public static Command LogChannelClear = new Command("log channel", "log channel -clear", "Clears the currently set log channel");
|
||||
public static Command LogEnable = new Command("log enable", "log enable all|<channel> [channel 2] [channel 3...]", "Enables message logging in certain channels");
|
||||
public static Command LogDisable = new Command("log disable", "log disable all|<channel> [channel 2] [channel 3...]", "Disables message logging in certain channels");
|
||||
public static Command LogShow = new Command("log show", "log show", "Displays the current list of channels where logging is disabled");
|
||||
public static Command LogClean = new Command("logclean", "logclean [on|off]", "Toggles whether to clean up other bots' log channels");
|
||||
public static Command BlacklistShow = new Command("blacklist show", "blacklist show", "Displays the current proxy blacklist");
|
||||
public static Command BlacklistAdd = new Command("blacklist add", "blacklist add all|<channel> [channel 2] [channel 3...]", "Adds certain channels to the proxy blacklist");
|
||||
|
|
@ -142,7 +143,7 @@ public partial class CommandTree
|
|||
AutoproxyOff, AutoproxyFront, AutoproxyLatch, AutoproxyMember
|
||||
};
|
||||
|
||||
public static Command[] LogCommands = { LogChannel, LogChannelClear, LogEnable, LogDisable };
|
||||
public static Command[] LogCommands = { LogChannel, LogChannelClear, LogEnable, LogDisable, LogShow };
|
||||
|
||||
public static Command[] BlacklistCommands = { BlacklistAdd, BlacklistRemove, BlacklistShow };
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ public partial class CommandTree
|
|||
return ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx));
|
||||
if (ctx.Match("edit", "e"))
|
||||
return ctx.Execute<ProxiedMessage>(MessageEdit, m => m.EditMessage(ctx));
|
||||
if (ctx.Match("reproxy", "rp"))
|
||||
if (ctx.Match("reproxy", "rp", "crimes"))
|
||||
return ctx.Execute<ProxiedMessage>(MessageReproxy, m => m.ReproxyMessage(ctx));
|
||||
if (ctx.Match("log"))
|
||||
if (ctx.Match("channel"))
|
||||
|
|
@ -57,6 +57,8 @@ public partial class CommandTree
|
|||
return ctx.Execute<ServerConfig>(LogEnable, m => m.SetLogEnabled(ctx, true));
|
||||
else if (ctx.Match("disable", "off"))
|
||||
return ctx.Execute<ServerConfig>(LogDisable, m => m.SetLogEnabled(ctx, false));
|
||||
else if (ctx.Match("list", "show"))
|
||||
return ctx.Execute<ServerConfig>(LogShow, m => m.ShowLogDisabledChannels(ctx));
|
||||
else if (ctx.Match("commands"))
|
||||
return PrintCommandList(ctx, "message logging", LogCommands);
|
||||
else return PrintCommandExpectedError(ctx, LogCommands);
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ public class Context
|
|||
|
||||
private Command? _currentCommand;
|
||||
|
||||
public Context(ILifetimeScope provider, int shardId, Guild? guild, Channel channel, MessageCreateEvent message, int commandParseOffset,
|
||||
PKSystem senderSystem, SystemConfig config, MessageContext messageContext)
|
||||
public Context(ILifetimeScope provider, int shardId, Guild? guild, Channel channel, MessageCreateEvent message,
|
||||
int commandParseOffset, PKSystem senderSystem, SystemConfig config)
|
||||
{
|
||||
Message = (Message)message;
|
||||
ShardId = shardId;
|
||||
|
|
@ -37,7 +37,6 @@ public class Context
|
|||
Channel = channel;
|
||||
System = senderSystem;
|
||||
Config = config;
|
||||
MessageContext = messageContext;
|
||||
Cache = provider.Resolve<IDiscordCache>();
|
||||
Database = provider.Resolve<IDatabase>();
|
||||
Repository = provider.Resolve<ModelRepository>();
|
||||
|
|
@ -61,7 +60,6 @@ public class Context
|
|||
public readonly Guild Guild;
|
||||
public readonly int ShardId;
|
||||
public readonly Cluster Cluster;
|
||||
public readonly MessageContext MessageContext;
|
||||
|
||||
public Task<PermissionSet> BotPermissions => Cache.PermissionsIn(Channel.Id);
|
||||
public Task<PermissionSet> UserPermissions => Cache.PermissionsFor((MessageCreateEvent)Message);
|
||||
|
|
@ -96,12 +94,12 @@ public class Context
|
|||
AllowedMentions = mentions ?? new AllowedMentions()
|
||||
});
|
||||
|
||||
if (embed != null)
|
||||
{
|
||||
// Sensitive information that might want to be deleted by :x: reaction is typically in an embed format (member cards, for example)
|
||||
// This may need to be changed at some point but works well enough for now
|
||||
await _commandMessageService.RegisterMessage(msg.Id, msg.ChannelId, Author.Id);
|
||||
}
|
||||
// if (embed != null)
|
||||
// {
|
||||
// Sensitive information that might want to be deleted by :x: reaction is typically in an embed format (member cards, for example)
|
||||
// but since we can, we just store all sent messages for possible deletion
|
||||
await _commandMessageService.RegisterMessage(msg.Id, msg.ChannelId, Author.Id);
|
||||
// }
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
|
@ -110,6 +108,12 @@ public class Context
|
|||
{
|
||||
_currentCommand = commandDef;
|
||||
|
||||
if (deprecated && commandDef != null)
|
||||
{
|
||||
await Reply($"{Emojis.Warn} This command has been removed. please use `pk;{commandDef.Key}` instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (_metrics.Measure.Timer.Time(BotMetrics.CommandTime, new MetricTags("Command", commandDef?.Key ?? "null")))
|
||||
|
|
@ -130,9 +134,6 @@ public class Context
|
|||
// Got a complaint the old error was a bit too patronizing. Hopefully this is better?
|
||||
await Reply($"{Emojis.Error} Operation timed out, sorry. Try again, perhaps?");
|
||||
}
|
||||
|
||||
if (deprecated && commandDef != null)
|
||||
await Reply($"{Emojis.Warn} This command is deprecated and will be removed soon. In the future, please use `pk;{commandDef.Key}`.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -103,6 +103,13 @@ public static class ContextArgumentsExt
|
|||
ctx.Match("r", "raw") || ctx.MatchFlag("r", "raw");
|
||||
|
||||
public static bool MatchToggle(this Context ctx, bool? defaultValue = null)
|
||||
{
|
||||
var value = ctx.MatchToggleOrNull(defaultValue);
|
||||
if (value == null) throw new PKError("You must pass either \"on\" or \"off\" to this command.");
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public static bool? MatchToggleOrNull(this Context ctx, bool? defaultValue = null)
|
||||
{
|
||||
if (defaultValue != null && ctx.MatchClearInner())
|
||||
return defaultValue.Value;
|
||||
|
|
@ -114,8 +121,7 @@ public static class ContextArgumentsExt
|
|||
return true;
|
||||
else if (ctx.Match(noToggles) || ctx.MatchFlag(noToggles))
|
||||
return false;
|
||||
else
|
||||
throw new PKError("You must pass either \"on\" or \"off\" to this command.");
|
||||
else return null;
|
||||
}
|
||||
|
||||
public static (ulong? messageId, ulong? channelId) MatchMessage(this Context ctx, bool parseRawMessageId)
|
||||
|
|
|
|||
|
|
@ -89,10 +89,12 @@ public class Autoproxy
|
|||
var eb = new EmbedBuilder()
|
||||
.Title($"Current autoproxy status (for {ctx.Guild.Name.EscapeMarkdown()})");
|
||||
|
||||
var fronters = ctx.MessageContext.LastSwitchMembers;
|
||||
var sw = await ctx.Repository.GetLatestSwitch(ctx.System.Id);
|
||||
var fronters = sw == null ? new() : await ctx.Database.Execute(c => ctx.Repository.GetSwitchMembers(c, sw.Id)).ToListAsync();
|
||||
|
||||
var relevantMember = settings.AutoproxyMode switch
|
||||
{
|
||||
AutoproxyMode.Front => fronters.Length > 0 ? await ctx.Repository.GetMember(fronters[0]) : null,
|
||||
AutoproxyMode.Front => fronters.Count > 0 ? fronters[0] : null,
|
||||
AutoproxyMode.Member when settings.AutoproxyMember.HasValue => await ctx.Repository.GetMember(settings.AutoproxyMember.Value),
|
||||
_ => null
|
||||
};
|
||||
|
|
@ -104,7 +106,7 @@ public class Autoproxy
|
|||
break;
|
||||
case AutoproxyMode.Front:
|
||||
{
|
||||
if (fronters.Length == 0)
|
||||
if (fronters.Count == 0)
|
||||
{
|
||||
eb.Description("Autoproxy is currently set to **front mode** in this server, but there are currently no fronters registered. Use the `pk;switch` command to log a switch.");
|
||||
}
|
||||
|
|
@ -135,7 +137,8 @@ public class Autoproxy
|
|||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (!ctx.MessageContext.AllowAutoproxy)
|
||||
var allowAutoproxy = await ctx.Repository.GetAutoproxyEnabled(ctx.Author.Id);
|
||||
if (!allowAutoproxy)
|
||||
eb.Field(new Embed.Field("\u200b", $"{Emojis.Note} Autoproxy is currently **disabled** for your account (<@{ctx.Author.Id}>). To enable it, use `pk;autoproxy account enable`."));
|
||||
|
||||
return eb.Build();
|
||||
|
|
|
|||
|
|
@ -268,8 +268,7 @@ public class Checks
|
|||
try
|
||||
{
|
||||
_proxy.ShouldProxy(channel, msg, context);
|
||||
_matcher.TryMatch(context, autoproxySettings, members, out var match, msg.Content, msg.Attachments.Length > 0,
|
||||
context.AllowAutoproxy);
|
||||
_matcher.TryMatch(context, autoproxySettings, members, out var match, msg.Content, msg.Attachments.Length > 0, true);
|
||||
|
||||
await ctx.Reply("I'm not sure why this message was not proxied, sorry.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ public class Config
|
|||
{
|
||||
var items = new List<PaginatedConfigItem>();
|
||||
|
||||
var allowAutoproxy = await ctx.Repository.GetAutoproxyEnabled(ctx.Author.Id);
|
||||
|
||||
items.Add(new(
|
||||
"autoproxy account",
|
||||
"Whether autoproxy is enabled for the current account",
|
||||
EnabledDisabled(ctx.MessageContext.AllowAutoproxy),
|
||||
EnabledDisabled(allowAutoproxy),
|
||||
"enabled"
|
||||
));
|
||||
|
||||
|
|
@ -122,16 +124,18 @@ public class Config
|
|||
|
||||
public async Task AutoproxyAccount(Context ctx)
|
||||
{
|
||||
var allowAutoproxy = await ctx.Repository.GetAutoproxyEnabled(ctx.Author.Id);
|
||||
|
||||
if (!ctx.HasNext())
|
||||
{
|
||||
await ctx.Reply($"Autoproxy is currently **{EnabledDisabled(ctx.MessageContext.AllowAutoproxy)}** for account <@{ctx.Author.Id}>.");
|
||||
await ctx.Reply($"Autoproxy is currently **{EnabledDisabled(allowAutoproxy)}** for account <@{ctx.Author.Id}>.");
|
||||
return;
|
||||
}
|
||||
|
||||
var allow = ctx.MatchToggle(true);
|
||||
|
||||
var statusString = EnabledDisabled(allow);
|
||||
if (ctx.MessageContext.AllowAutoproxy == allow)
|
||||
if (allowAutoproxy == allow)
|
||||
{
|
||||
await ctx.Reply($"{Emojis.Note} Autoproxy is already {statusString} for account <@{ctx.Author.Id}>.");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -374,38 +374,39 @@ public class Groups
|
|||
|
||||
public async Task GroupColor(Context ctx, PKGroup target)
|
||||
{
|
||||
var color = ctx.RemainderOrNull();
|
||||
if (await ctx.MatchClear())
|
||||
{
|
||||
ctx.CheckOwnGroup(target);
|
||||
var isOwnSystem = ctx.System?.Id == target.System;
|
||||
var matchedRaw = ctx.MatchRaw();
|
||||
var matchedClear = await ctx.MatchClear();
|
||||
|
||||
var patch = new GroupPatch { Color = Partial<string>.Null() };
|
||||
await ctx.Repository.UpdateGroup(target.Id, patch);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Group color cleared.");
|
||||
}
|
||||
else if (!ctx.HasNext())
|
||||
if (!isOwnSystem || !(ctx.HasNext() || matchedClear))
|
||||
{
|
||||
if (target.Color == null)
|
||||
if (ctx.System?.Id == target.System)
|
||||
await ctx.Reply(
|
||||
$"This group does not have a color set. To set one, type `pk;group {target.Reference(ctx)} color <color>`.");
|
||||
else
|
||||
await ctx.Reply("This group does not have a color set.");
|
||||
await ctx.Reply(
|
||||
"This group does not have a color set." + (isOwnSystem ? $" To set one, type `pk;group {target.Reference(ctx)} color <color>`." : ""));
|
||||
else if (matchedRaw)
|
||||
await ctx.Reply("```\n#" + target.Color + "\n```");
|
||||
else
|
||||
await ctx.Reply(embed: new EmbedBuilder()
|
||||
.Title("Group color")
|
||||
.Color(target.Color.ToDiscordColor())
|
||||
.Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20"))
|
||||
.Description($"This group's color is **#{target.Color}**."
|
||||
+ (ctx.System?.Id == target.System
|
||||
? $" To clear it, type `pk;group {target.Reference(ctx)} color -clear`."
|
||||
: ""))
|
||||
+ (isOwnSystem ? $" To clear it, type `pk;group {target.Reference(ctx)} color -clear`." : ""))
|
||||
.Build());
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.CheckSystem().CheckOwnGroup(target);
|
||||
|
||||
if (matchedClear)
|
||||
{
|
||||
await ctx.Repository.UpdateGroup(target.Id, new() { Color = Partial<string>.Null() });
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Group color cleared.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.CheckOwnGroup(target);
|
||||
var color = ctx.RemainderOrNull();
|
||||
|
||||
if (color.StartsWith("#")) color = color.Substring(1);
|
||||
if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Myriad.Builders;
|
||||
using Myriad.Types;
|
||||
using Myriad.Rest.Types.Requests;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
|
|
@ -10,64 +10,159 @@ public class Help
|
|||
private static Embed helpEmbed = new()
|
||||
{
|
||||
Title = "PluralKit",
|
||||
Description = "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.",
|
||||
Fields = new[]
|
||||
{
|
||||
new Embed.Field
|
||||
(
|
||||
"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."
|
||||
),
|
||||
new
|
||||
(
|
||||
"Why are people's names saying [BOT] next to them?",
|
||||
"These people are not actually bots, this is just a Discord limitation. See [the documentation](https://pluralkit.me/guide#proxying) for an in-depth explanation."
|
||||
),
|
||||
new
|
||||
(
|
||||
"How do I get started?",
|
||||
String.Join("\n", new[]
|
||||
{
|
||||
"To get started using PluralKit, try running the following commands (of course replacing the relevant names with your own):",
|
||||
"**1**. `pk;system new` - Create a system (if you haven't already)",
|
||||
"**2**. `pk;member add John` - Add a new member to your system",
|
||||
"**3**. `pk;member John proxy [text]` - Set up [square brackets] as proxy tags",
|
||||
"**4**. You're done! You can now type [a message in brackets] and it'll be proxied appropriately.",
|
||||
"**5**. Optionally, you may set an avatar from the URL of an image with `pk;member John avatar [link to image]`, or from a file by typing `pk;member John avatar` and sending the message with an attached image.",
|
||||
"\nSee [the Getting Started guide](https://pluralkit.me/start) for more information."
|
||||
})
|
||||
),
|
||||
new
|
||||
(
|
||||
"Useful tips",
|
||||
String.Join("\n", new[] {
|
||||
$"React with {Emojis.Error} on a proxied message to delete it (only if you sent it!)",
|
||||
$"React with {Emojis.RedQuestion} on a proxied message to look up information about it (like who sent it)",
|
||||
$"React with {Emojis.Bell} on a proxied message to \"ping\" the sender",
|
||||
"Type **`pk;invite`** to get a link to invite this bot to your own server!"
|
||||
})
|
||||
),
|
||||
new
|
||||
(
|
||||
"More information",
|
||||
String.Join("\n", new[] {
|
||||
"For a full list of commands, see [the command list](https://pluralkit.me/commands).",
|
||||
"For a more in-depth explanation of message proxying, see [the documentation](https://pluralkit.me/guide#proxying).",
|
||||
"If 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."
|
||||
})
|
||||
),
|
||||
new
|
||||
(
|
||||
"Support server",
|
||||
"We also have a Discord server for support, discussion, suggestions, announcements, etc: https://discord.gg/PczBt78"
|
||||
)
|
||||
},
|
||||
Footer = new("By @Ske#6201 | Myriad by @Layl#8888 | GitHub: https://github.com/xSke/PluralKit/ | Website: https://pluralkit.me/"),
|
||||
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#6201 | Myriad by @Layl#8888 | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/"),
|
||||
Color = DiscordUtils.Blue,
|
||||
};
|
||||
|
||||
public Task HelpRoot(Context ctx) => ctx.Reply(embed: helpEmbed);
|
||||
private static Dictionary<string, Embed.Field[]> helpEmbedPages = new Dictionary<string, Embed.Field[]>
|
||||
{
|
||||
{
|
||||
"basicinfo",
|
||||
new Embed.Field[]
|
||||
{
|
||||
new
|
||||
(
|
||||
"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."
|
||||
),
|
||||
new
|
||||
(
|
||||
"Why are people's names saying [BOT] next to them?",
|
||||
"These people are not actually bots, this is just a Discord limitation. See [the documentation](https://pluralkit.me/guide#proxying) for an in-depth explanation."
|
||||
),
|
||||
}
|
||||
},
|
||||
{
|
||||
"gettingstarted",
|
||||
new Embed.Field[]
|
||||
{
|
||||
new
|
||||
(
|
||||
"How do I get started?",
|
||||
String.Join("\n", new[]
|
||||
{
|
||||
"To get started using PluralKit, try running the following commands (of course replacing the relevant names with your own):",
|
||||
"**1**. `pk;system new` - Create a system (if you haven't already)",
|
||||
"**2**. `pk;member add John` - Add a new member to your system",
|
||||
"**3**. `pk;member John proxy [text]` - Set up [square brackets] as proxy tags",
|
||||
"**4**. You're done! You can now type [a message in brackets] and it'll be proxied appropriately.",
|
||||
"**5**. Optionally, you may set an avatar from the URL of an image with `pk;member John avatar [link to image]`, or from a file by typing `pk;member John avatar` and sending the message with an attached image.",
|
||||
"\nSee [the Getting Started guide](https://pluralkit.me/start) for more information."
|
||||
})
|
||||
),
|
||||
}
|
||||
},
|
||||
{
|
||||
"usefultips",
|
||||
new Embed.Field[]
|
||||
{
|
||||
new
|
||||
(
|
||||
"Useful tips",
|
||||
String.Join("\n", new[] {
|
||||
$"React with {Emojis.Error} on a proxied message to delete it (only if you sent it!)",
|
||||
$"React with {Emojis.RedQuestion} on a proxied message to look up information about it (like who sent it)",
|
||||
$"React with {Emojis.Bell} on a proxied message to \"ping\" the sender",
|
||||
"Type **`pk;invite`** to get a link to invite this bot to your own server!"
|
||||
})
|
||||
),
|
||||
}
|
||||
},
|
||||
{
|
||||
"moreinfo",
|
||||
new Embed.Field[]
|
||||
{
|
||||
new
|
||||
(
|
||||
"More information",
|
||||
String.Join("\n", new[] {
|
||||
"For a full list of commands, see [the command list](https://pluralkit.me/commands), or type `pk;commands`.",
|
||||
"For a more in-depth explanation of message proxying, see [the documentation](https://pluralkit.me/guide#proxying).",
|
||||
"If 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.",
|
||||
"We also have a [web dashboard](https://dash.pluralkit.me) to edit your system info online."
|
||||
})
|
||||
),
|
||||
new
|
||||
(
|
||||
"Support server",
|
||||
"We also have a Discord server for support, discussion, suggestions, announcements, etc: https://discord.gg/PczBt78"
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static MessageComponent helpPageButtons(ulong userId) => new MessageComponent
|
||||
{
|
||||
Type = ComponentType.ActionRow,
|
||||
Components = new[]
|
||||
{
|
||||
new MessageComponent
|
||||
{
|
||||
Type = ComponentType.Button,
|
||||
Style = ButtonStyle.Secondary,
|
||||
Label = "Basic Info",
|
||||
CustomId = $"help-menu-basicinfo-{userId}",
|
||||
Emoji = new() { Name = "\u2139" },
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = ComponentType.Button,
|
||||
Style = ButtonStyle.Secondary,
|
||||
Label = "Getting Started",
|
||||
CustomId = $"help-menu-gettingstarted-{userId}",
|
||||
Emoji = new() { Name = "\u2753", },
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = ComponentType.Button,
|
||||
Style = ButtonStyle.Secondary,
|
||||
Label = "Useful Tips",
|
||||
CustomId = $"help-menu-usefultips-{userId}",
|
||||
Emoji = new() { Name = "\U0001f4a1", },
|
||||
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = ComponentType.Button,
|
||||
Style = ButtonStyle.Secondary,
|
||||
Label = "More Info",
|
||||
CustomId = $"help-menu-moreinfo-{userId}",
|
||||
Emoji = new() { Id = 986379675066593330, },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public Task HelpRoot(Context ctx)
|
||||
=> ctx.Rest.CreateMessage(ctx.Channel.Id, new MessageRequest
|
||||
{
|
||||
Embeds = new[] { helpEmbed with { Description = helpEmbed.Description + "\n\n**Use the buttons below to see more info!**" } },
|
||||
Components = new[] { helpPageButtons(ctx.Author.Id) },
|
||||
});
|
||||
|
||||
public static Task ButtonClick(InteractionContext ctx)
|
||||
{
|
||||
if (!ctx.CustomId.Contains(ctx.User.Id.ToString()))
|
||||
return ctx.Ignore();
|
||||
|
||||
var buttons = helpPageButtons(ctx.User.Id);
|
||||
|
||||
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 = helpEmbed.Description + "\n\n**Use the buttons below to see more info!**" } },
|
||||
Components = new[] { buttons }
|
||||
});
|
||||
|
||||
buttons.Components.Where(x => x.CustomId == ctx.CustomId).First().Style = ButtonStyle.Primary;
|
||||
|
||||
return ctx.Respond(InteractionResponse.ResponseType.UpdateMessage, new()
|
||||
{
|
||||
Embeds = new[] { helpEmbed with { Fields = helpEmbedPages.GetValueOrDefault(ctx.CustomId.Split("-")[2]) } },
|
||||
Components = new[] { buttons }
|
||||
});
|
||||
}
|
||||
|
||||
private static string explanation = String.Join("\n\n", new[]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -225,41 +225,39 @@ public class MemberEdit
|
|||
|
||||
public async Task Color(Context ctx, PKMember target)
|
||||
{
|
||||
var color = ctx.RemainderOrNull();
|
||||
if (await ctx.MatchClear())
|
||||
var isOwnSystem = ctx.System?.Id == target.System;
|
||||
var matchedRaw = ctx.MatchRaw();
|
||||
var matchedClear = await ctx.MatchClear();
|
||||
|
||||
if (!isOwnSystem || !(ctx.HasNext() || matchedClear))
|
||||
{
|
||||
ctx.CheckOwnMember(target);
|
||||
|
||||
var patch = new MemberPatch { Color = Partial<string>.Null() };
|
||||
await ctx.Repository.UpdateMember(target.Id, patch);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member color cleared.");
|
||||
}
|
||||
else if (!ctx.HasNext())
|
||||
{
|
||||
// if (!target.ColorPrivacy.CanAccess(ctx.LookupContextFor(target.System)))
|
||||
// throw Errors.LookupNotAllowed;
|
||||
|
||||
if (target.Color == null)
|
||||
if (ctx.System?.Id == target.System)
|
||||
await ctx.Reply(
|
||||
$"This member does not have a color set. To set one, type `pk;member {target.Reference(ctx)} color <color>`.");
|
||||
else
|
||||
await ctx.Reply("This member does not have a color set.");
|
||||
await ctx.Reply(
|
||||
"This member does not have a color set." + (isOwnSystem ? $" To set one, type `pk;member {target.Reference(ctx)} color <color>`." : ""));
|
||||
else if (matchedRaw)
|
||||
await ctx.Reply("```\n#" + target.Color + "\n```");
|
||||
else
|
||||
await ctx.Reply(embed: new EmbedBuilder()
|
||||
.Title("Member color")
|
||||
.Color(target.Color.ToDiscordColor())
|
||||
.Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20"))
|
||||
.Description($"This member's color is **#{target.Color}**."
|
||||
+ (ctx.System?.Id == target.System
|
||||
? $" To clear it, type `pk;member {target.Reference(ctx)} color -clear`."
|
||||
: ""))
|
||||
+ (isOwnSystem ? $" To clear it, type `pk;member {target.Reference(ctx)} color -clear`." : ""))
|
||||
.Build());
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.CheckSystem().CheckOwnMember(target);
|
||||
|
||||
if (matchedClear)
|
||||
{
|
||||
await ctx.Repository.UpdateMember(target.Id, new() { Color = Partial<string>.Null() });
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member color cleared.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.CheckOwnMember(target);
|
||||
var color = ctx.RemainderOrNull();
|
||||
|
||||
if (color.StartsWith("#")) color = color.Substring(1);
|
||||
if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Autofac;
|
||||
|
||||
using Myriad.Builders;
|
||||
using Myriad.Cache;
|
||||
using Myriad.Extensions;
|
||||
|
|
@ -45,7 +47,7 @@ public class ProxiedMessage
|
|||
_logChannel = logChannel;
|
||||
// _cache = cache;
|
||||
_metrics = metrics;
|
||||
_proxy = proxy;
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
public async Task ReproxyMessage(Context ctx)
|
||||
|
|
@ -61,7 +63,7 @@ public class ProxiedMessage
|
|||
throw new PKError("Could not find a member to reproxy the message with.");
|
||||
|
||||
// Fetch members and get the ProxyMember for `target`
|
||||
List <ProxyMember> members;
|
||||
List<ProxyMember> members;
|
||||
using (_metrics.Measure.Timer.Time(BotMetrics.ProxyMembersQueryTime))
|
||||
members = (await _repo.GetProxyMembers(ctx.Author.Id, msg.Message.Guild!.Value)).ToList();
|
||||
var match = members.Find(x => x.Id == target.Id);
|
||||
|
|
@ -70,7 +72,7 @@ public class ProxiedMessage
|
|||
|
||||
try
|
||||
{
|
||||
await _proxy.ExecuteReproxy(ctx.Message, msg.Message, match);
|
||||
await _proxy.ExecuteReproxy(ctx.Message, msg.Message, members, match);
|
||||
|
||||
if (ctx.Guild == null)
|
||||
await _rest.CreateReaction(ctx.Channel.Id, ctx.Message.Id, new Emoji { Name = Emojis.Success });
|
||||
|
|
@ -126,8 +128,7 @@ public class ProxiedMessage
|
|||
if ((await ctx.BotPermissions).HasFlag(PermissionSet.ManageMessages))
|
||||
await _rest.DeleteMessage(ctx.Channel.Id, ctx.Message.Id);
|
||||
|
||||
await _logChannel.LogMessage(ctx.MessageContext, msg.Message, ctx.Message, editedMsg,
|
||||
originalMsg!.Content!);
|
||||
await _logChannel.LogMessage(msg.Message, ctx.Message, editedMsg, originalMsg!.Content!);
|
||||
}
|
||||
catch (NotFoundException)
|
||||
{
|
||||
|
|
@ -140,13 +141,12 @@ public class ProxiedMessage
|
|||
var editType = isReproxy ? "reproxy" : "edit";
|
||||
var editTypeAction = isReproxy ? "reproxied" : "edited";
|
||||
|
||||
// todo: is it correct to get a connection here?
|
||||
await using var conn = await ctx.Database.Obtain();
|
||||
FullMessage? msg = null;
|
||||
|
||||
var (referencedMessage, _) = ctx.MatchMessage(false);
|
||||
if (referencedMessage != null)
|
||||
{
|
||||
await using var conn = await ctx.Database.Obtain();
|
||||
msg = await ctx.Repository.GetMessage(conn, referencedMessage.Value);
|
||||
if (msg == null)
|
||||
throw new PKError("This is not a message proxied by PluralKit.");
|
||||
|
|
@ -161,6 +161,7 @@ public class ProxiedMessage
|
|||
if (recent == null)
|
||||
throw new PKSyntaxError($"Could not find a recent message to {editType}.");
|
||||
|
||||
await using var conn = await ctx.Database.Obtain();
|
||||
msg = await ctx.Repository.GetMessage(conn, recent.Mid);
|
||||
if (msg == null)
|
||||
throw new PKSyntaxError($"Could not find a recent message to {editType}.");
|
||||
|
|
@ -305,14 +306,14 @@ public class ProxiedMessage
|
|||
|
||||
private async Task DeleteCommandMessage(Context ctx, ulong messageId)
|
||||
{
|
||||
var message = await ctx.Repository.GetCommandMessage(messageId);
|
||||
if (message == null)
|
||||
var (authorId, channelId) = await ctx.Services.Resolve<CommandMessageService>().GetCommandMessage(messageId);
|
||||
if (authorId == null)
|
||||
throw Errors.MessageNotFound(messageId);
|
||||
|
||||
if (message.AuthorId != ctx.Author.Id)
|
||||
if (authorId != ctx.Author.Id)
|
||||
throw new PKError("You can only delete command messages queried by this account.");
|
||||
|
||||
await ctx.Rest.DeleteMessage(message.ChannelId, message.MessageId);
|
||||
await ctx.Rest.DeleteMessage(channelId!.Value, messageId);
|
||||
|
||||
if (ctx.Guild != null)
|
||||
await ctx.Rest.DeleteMessage(ctx.Message);
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ public class Misc
|
|||
.Footer(new(String.Join(" \u2022 ", new[] {
|
||||
$"PluralKit {BuildInfoService.Version}",
|
||||
(isCluster ? $"Cluster {_botConfig.Cluster.NodeIndex}" : ""),
|
||||
"https://github.com/xSke/PluralKit",
|
||||
"https://github.com/PluralKit/PluralKit",
|
||||
"Last restarted:",
|
||||
})))
|
||||
.Timestamp(process.StartTime.ToString("O"));
|
||||
|
|
|
|||
|
|
@ -146,6 +146,57 @@ public class ServerConfig
|
|||
});
|
||||
}
|
||||
|
||||
public async Task ShowLogDisabledChannels(Context ctx)
|
||||
{
|
||||
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
||||
var config = await ctx.Repository.GetGuild(ctx.Guild.Id);
|
||||
|
||||
// Resolve all channels from the cache and order by position
|
||||
var channels = (await Task.WhenAll(config.LogBlacklist
|
||||
.Select(id => _cache.TryGetChannel(id))))
|
||||
.Where(c => c != null)
|
||||
.OrderBy(c => c.Position)
|
||||
.ToList();
|
||||
|
||||
if (channels.Count == 0)
|
||||
{
|
||||
await ctx.Reply("This server has no channels where logging is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.Paginate(channels.ToAsyncEnumerable(), channels.Count, 25,
|
||||
$"Channels where logging is disabled for {ctx.Guild.Name}",
|
||||
null,
|
||||
async (eb, l) =>
|
||||
{
|
||||
async Task<string> CategoryName(ulong? id) =>
|
||||
id != null ? (await _cache.GetChannel(id.Value)).Name : "(no category)";
|
||||
|
||||
ulong? lastCategory = null;
|
||||
|
||||
var fieldValue = new StringBuilder();
|
||||
foreach (var channel in l)
|
||||
{
|
||||
if (lastCategory != channel!.ParentId && fieldValue.Length > 0)
|
||||
{
|
||||
eb.Field(new Embed.Field(await CategoryName(lastCategory), fieldValue.ToString()));
|
||||
fieldValue.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldValue.Append("\n");
|
||||
}
|
||||
|
||||
fieldValue.Append(channel.Mention());
|
||||
lastCategory = channel.ParentId;
|
||||
}
|
||||
|
||||
eb.Field(new Embed.Field(await CategoryName(lastCategory), fieldValue.ToString()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async Task SetBlacklisted(Context ctx, bool shouldAdd)
|
||||
{
|
||||
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
|
@ -180,27 +231,26 @@ public class ServerConfig
|
|||
|
||||
public async Task SetLogCleanup(Context ctx)
|
||||
{
|
||||
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
||||
var botList = string.Join(", ", LoggerCleanService.Bots.Select(b => b.Name).OrderBy(x => x.ToLowerInvariant()));
|
||||
var eb = new EmbedBuilder()
|
||||
.Title("Log cleanup settings")
|
||||
.Field(new Embed.Field("Supported bots", botList));
|
||||
|
||||
if (ctx.Guild == null)
|
||||
{
|
||||
eb.Description("Run this command in a server to enable/disable log cleanup.");
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
||||
var guild = await ctx.Repository.GetGuild(ctx.Guild.Id);
|
||||
|
||||
bool newValue;
|
||||
if (ctx.Match("enable", "on", "yes"))
|
||||
{
|
||||
newValue = true;
|
||||
}
|
||||
else if (ctx.Match("disable", "off", "no"))
|
||||
{
|
||||
newValue = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Title("Log cleanup settings")
|
||||
.Field(new Embed.Field("Supported bots", botList));
|
||||
bool? newValue = ctx.MatchToggleOrNull();
|
||||
|
||||
if (newValue == null)
|
||||
{
|
||||
var guildCfg = await ctx.Repository.GetGuild(ctx.Guild.Id);
|
||||
if (guildCfg.LogCleanupEnabled)
|
||||
eb.Description(
|
||||
|
|
@ -212,9 +262,9 @@ public class ServerConfig
|
|||
return;
|
||||
}
|
||||
|
||||
await ctx.Repository.UpdateGuild(ctx.Guild.Id, new GuildPatch { LogCleanupEnabled = newValue });
|
||||
await ctx.Repository.UpdateGuild(ctx.Guild.Id, new GuildPatch { LogCleanupEnabled = newValue.Value });
|
||||
|
||||
if (newValue)
|
||||
if (newValue.Value)
|
||||
await ctx.Reply(
|
||||
$"{Emojis.Success} Log cleanup has been **enabled** for this server. Messages deleted by PluralKit will now be cleaned up from logging channels managed by the following bots:\n- **{botList}**\n\n{Emojis.Note} Make sure PluralKit has the **Manage Messages** permission in the channels in question.\n{Emojis.Note} Also, make sure to blacklist the logging channel itself from the bots in question to prevent conflicts.");
|
||||
else
|
||||
|
|
|
|||
|
|
@ -132,12 +132,16 @@ public class SystemEdit
|
|||
public async Task Color(Context ctx, PKSystem target)
|
||||
{
|
||||
var isOwnSystem = ctx.System?.Id == target.Id;
|
||||
var matchedRaw = ctx.MatchRaw();
|
||||
var matchedClear = await ctx.MatchClear();
|
||||
|
||||
if (!isOwnSystem || !ctx.HasNext(false))
|
||||
if (!isOwnSystem || !(ctx.HasNext() || matchedClear))
|
||||
{
|
||||
if (target.Color == null)
|
||||
await ctx.Reply(
|
||||
"This system does not have a color set." + (isOwnSystem ? " To set one, type `pk;system color <color>`." : ""));
|
||||
else if (matchedRaw)
|
||||
await ctx.Reply("```\n#" + target.Color + "\n```");
|
||||
else
|
||||
await ctx.Reply(embed: new EmbedBuilder()
|
||||
.Title("System color")
|
||||
|
|
@ -151,7 +155,7 @@ public class SystemEdit
|
|||
|
||||
ctx.CheckSystem().CheckOwnSystem(target);
|
||||
|
||||
if (await ctx.MatchClear())
|
||||
if (matchedClear)
|
||||
{
|
||||
await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { Color = Partial<string>.Null() });
|
||||
|
||||
|
|
@ -273,7 +277,7 @@ public class SystemEdit
|
|||
await ctx.Reply(
|
||||
$"{Emojis.Success} System server tag changed. Member names will now end with {newTag.AsCode()} when proxied in the current server '{ctx.Guild.Name}'.");
|
||||
|
||||
if (!ctx.MessageContext.TagEnabled)
|
||||
if (!settings.TagEnabled)
|
||||
await ctx.Reply(setDisabledWarning);
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +288,7 @@ public class SystemEdit
|
|||
await ctx.Reply(
|
||||
$"{Emojis.Success} System server tag cleared. Member names will now end with the global system tag, if there is one set.");
|
||||
|
||||
if (!ctx.MessageContext.TagEnabled)
|
||||
if (!settings.TagEnabled)
|
||||
await ctx.Reply(setDisabledWarning);
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +297,7 @@ public class SystemEdit
|
|||
await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id,
|
||||
new SystemGuildPatch { TagEnabled = newValue });
|
||||
|
||||
await ctx.Reply(PrintEnableDisableResult(newValue, newValue != ctx.MessageContext.TagEnabled));
|
||||
await ctx.Reply(PrintEnableDisableResult(newValue, newValue != settings.TagEnabled));
|
||||
}
|
||||
|
||||
string PrintEnableDisableResult(bool newValue, bool changedValue)
|
||||
|
|
@ -308,20 +312,20 @@ public class SystemEdit
|
|||
|
||||
if (newValue)
|
||||
{
|
||||
if (ctx.MessageContext.TagEnabled)
|
||||
if (settings.TagEnabled)
|
||||
{
|
||||
if (ctx.MessageContext.SystemGuildTag == null)
|
||||
if (settings.Tag == null)
|
||||
str +=
|
||||
" However, you do not have a system tag specific to this server. Messages will be proxied using your global system tag, if there is one set.";
|
||||
else
|
||||
str +=
|
||||
$" Your current system tag in '{ctx.Guild.Name}' is {ctx.MessageContext.SystemGuildTag.AsCode()}.";
|
||||
$" Your current system tag in '{ctx.Guild.Name}' is {settings.Tag.AsCode()}.";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.MessageContext.SystemGuildTag != null)
|
||||
if (settings.Tag != null)
|
||||
str +=
|
||||
$" Member names will now end with the server-specific tag {ctx.MessageContext.SystemGuildTag.AsCode()} when proxied in the current server '{ctx.Guild.Name}'.";
|
||||
$" Member names will now end with the server-specific tag {settings.Tag.AsCode()} when proxied in the current server '{ctx.Guild.Name}'.";
|
||||
else
|
||||
str +=
|
||||
" Member names will now end with the global system tag when proxied in the current server, if there is one set.";
|
||||
|
|
@ -529,8 +533,8 @@ public class SystemEdit
|
|||
|
||||
await ctx.Reply(
|
||||
$"{Emojis.Warn} Are you sure you want to delete your system? If so, reply to this message with your system's ID (`{target.Hid}`).\n"
|
||||
+$"**Note: this action is permanent,** but you will get a copy of your system's data that can be re-imported into PluralKit at a later date sent to you in DMs."
|
||||
+" If you don't want this to happen, use `pk;s delete -no-export` instead.");
|
||||
+ $"**Note: this action is permanent,** but you will get a copy of your system's data that can be re-imported into PluralKit at a later date sent to you in DMs."
|
||||
+ " If you don't want this to happen, use `pk;s delete -no-export` instead.");
|
||||
if (!await ctx.ConfirmWithReply(target.Hid))
|
||||
throw new PKError(
|
||||
$"System deletion cancelled. Note that you must reply with your system ID (`{target.Hid}`) *verbatim*.");
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class SystemLink
|
|||
|
||||
ulong id;
|
||||
if (!ctx.MatchUserRaw(out id))
|
||||
throw new PKSyntaxError("You must pass an account to link with (either ID or @mention).");
|
||||
throw new PKSyntaxError("You must pass an account to unlink from (either ID or @mention).");
|
||||
|
||||
var accountIds = (await ctx.Repository.GetSystemAccounts(ctx.System.Id)).ToList();
|
||||
if (!accountIds.Contains(id)) throw Errors.AccountNotLinked;
|
||||
|
|
|
|||
|
|
@ -21,11 +21,14 @@ public class InteractionCreated: IEventHandler<InteractionCreateEvent>
|
|||
if (evt.Type == Interaction.InteractionType.MessageComponent)
|
||||
{
|
||||
var customId = evt.Data?.CustomId;
|
||||
if (customId != null)
|
||||
{
|
||||
var ctx = new InteractionContext(evt, _services);
|
||||
if (customId == null) return;
|
||||
|
||||
var ctx = new InteractionContext(evt, _services);
|
||||
|
||||
if (customId.Contains("help-menu"))
|
||||
await Help.ButtonClick(ctx);
|
||||
else
|
||||
await _interactionDispatch.Dispatch(customId, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,8 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
|||
if (evt.Type != Message.MessageType.Default && evt.Type != Message.MessageType.Reply) return;
|
||||
if (IsDuplicateMessage(evt)) return;
|
||||
|
||||
if (!(await _cache.PermissionsIn(evt.ChannelId)).HasFlag(PermissionSet.SendMessages)) return;
|
||||
var botPermissions = await _cache.PermissionsIn(evt.ChannelId);
|
||||
if (!botPermissions.HasFlag(PermissionSet.SendMessages)) return;
|
||||
|
||||
// spawn off saving the private channel into another thread
|
||||
// it is not a fatal error if this fails, and it shouldn't block message processing
|
||||
|
|
@ -77,36 +78,33 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
|||
_metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived);
|
||||
_lastMessageCache.AddMessage(evt);
|
||||
|
||||
// Get message context from DB (tracking w/ metrics)
|
||||
MessageContext ctx;
|
||||
using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime))
|
||||
ctx = await _repo.GetMessageContext(evt.Author.Id, evt.GuildId ?? default, rootChannel.Id);
|
||||
// if the message was not sent by an user account, only try running log cleanup
|
||||
if (evt.Author.Bot || evt.WebhookId != null || evt.Author.System == true)
|
||||
{
|
||||
await TryHandleLogClean(channel, evt);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try each handler until we find one that succeeds
|
||||
if (await TryHandleLogClean(evt, ctx))
|
||||
|
||||
if (await TryHandleCommand(shardId, evt, guild, channel))
|
||||
return;
|
||||
|
||||
// Only do command/proxy handling if it's a user account
|
||||
if (evt.Author.Bot || evt.WebhookId != null || evt.Author.System == true)
|
||||
return;
|
||||
|
||||
if (await TryHandleCommand(shardId, evt, guild, channel, ctx))
|
||||
return;
|
||||
await TryHandleProxy(evt, guild, channel, ctx);
|
||||
await TryHandleProxy(evt, guild, channel, rootChannel.Id, botPermissions);
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryHandleLogClean(MessageCreateEvent evt, MessageContext ctx)
|
||||
private async Task TryHandleLogClean(Channel channel, MessageCreateEvent evt)
|
||||
{
|
||||
var channel = await _cache.GetChannel(evt.ChannelId);
|
||||
if (!evt.Author.Bot || channel.Type != Channel.ChannelType.GuildText ||
|
||||
!ctx.LogCleanupEnabled) return false;
|
||||
if (evt.GuildId == null) return;
|
||||
if (channel.Type != Channel.ChannelType.GuildText) return;
|
||||
|
||||
await _loggerClean.HandleLoggerBotCleanup(evt);
|
||||
return true;
|
||||
var guildSettings = await _repo.GetGuild(evt.GuildId!.Value);
|
||||
|
||||
if (guildSettings.LogCleanupEnabled)
|
||||
await _loggerClean.HandleLoggerBotCleanup(evt);
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryHandleCommand(int shardId, MessageCreateEvent evt, Guild? guild,
|
||||
Channel channel, MessageContext ctx)
|
||||
private async ValueTask<bool> TryHandleCommand(int shardId, MessageCreateEvent evt, Guild? guild, Channel channel)
|
||||
{
|
||||
var content = evt.Content;
|
||||
if (content == null) return false;
|
||||
|
|
@ -117,17 +115,6 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
|||
if (!HasCommandPrefix(content, ourUserId, out var cmdStart) || cmdStart == content.Length)
|
||||
return false;
|
||||
|
||||
if (ctx.IsDeleting)
|
||||
{
|
||||
await _rest.CreateMessage(evt.ChannelId, new()
|
||||
{
|
||||
Content = $"{Emojis.Error} Your system is currently being deleted."
|
||||
+ " Due to database issues, it is not possible to use commands while a system is being deleted. Please wait a few minutes and try again.",
|
||||
MessageReference = new(guild?.Id, channel.Id, evt.Id)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Trim leading whitespace from command without actually modifying the string
|
||||
// This just moves the argPos pointer by however much whitespace is at the start of the post-argPos string
|
||||
var trimStartLengthDiff =
|
||||
|
|
@ -136,9 +123,9 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
|||
|
||||
try
|
||||
{
|
||||
var system = ctx.SystemId != null ? await _repo.GetSystem(ctx.SystemId.Value) : null;
|
||||
var config = ctx.SystemId != null ? await _repo.GetSystemConfig(ctx.SystemId.Value) : null;
|
||||
await _tree.ExecuteCommand(new Context(_services, shardId, guild, channel, evt, cmdStart, system, config, ctx));
|
||||
var system = await _repo.GetSystemByAccount(evt.Author.Id);
|
||||
var config = system != null ? await _repo.GetSystemConfig(system.Id) : null;
|
||||
await _tree.ExecuteCommand(new Context(_services, shardId, guild, channel, evt, cmdStart, system, config));
|
||||
}
|
||||
catch (PKError)
|
||||
{
|
||||
|
|
@ -169,17 +156,16 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
|||
return false;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryHandleProxy(MessageCreateEvent evt, Guild guild, Channel channel,
|
||||
MessageContext ctx)
|
||||
private async ValueTask<bool> TryHandleProxy(MessageCreateEvent evt, Guild guild, Channel channel, ulong rootChannel, PermissionSet botPermissions)
|
||||
{
|
||||
if (ctx.IsDeleting) return false;
|
||||
|
||||
var botPermissions = await _cache.PermissionsIn(channel.Id);
|
||||
// Get message context from DB (tracking w/ metrics)
|
||||
MessageContext ctx;
|
||||
using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime))
|
||||
ctx = await _repo.GetMessageContext(evt.Author.Id, evt.GuildId ?? default, rootChannel);
|
||||
|
||||
try
|
||||
{
|
||||
return await _proxy.HandleIncomingMessage(evt, ctx, guild, channel, ctx.AllowAutoproxy,
|
||||
botPermissions);
|
||||
return await _proxy.HandleIncomingMessage(evt, ctx, guild, channel, true, botPermissions);
|
||||
}
|
||||
|
||||
// Catch any failed proxy checks so they get ignored in the global error handler
|
||||
|
|
|
|||
|
|
@ -73,10 +73,10 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
|||
return;
|
||||
}
|
||||
|
||||
var commandMsg = await _commandMessageService.GetCommandMessage(evt.MessageId);
|
||||
if (commandMsg != null)
|
||||
var (authorId, _) = await _commandMessageService.GetCommandMessage(evt.MessageId);
|
||||
if (authorId != null)
|
||||
{
|
||||
await HandleCommandDeleteReaction(evt, commandMsg);
|
||||
await HandleCommandDeleteReaction(evt, authorId.Value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -141,11 +141,11 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
|||
await _repo.DeleteMessage(evt.MessageId);
|
||||
}
|
||||
|
||||
private async ValueTask HandleCommandDeleteReaction(MessageReactionAddEvent evt, CommandMessage? msg)
|
||||
private async ValueTask HandleCommandDeleteReaction(MessageReactionAddEvent evt, ulong? authorId)
|
||||
{
|
||||
// Can only delete your own message
|
||||
// (except in DMs, where msg will be null)
|
||||
if (msg != null && msg.AuthorId != evt.UserId)
|
||||
if (authorId != null && authorId != evt.UserId)
|
||||
return;
|
||||
|
||||
// todo: don't try to delete the user's own messages in DMs
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ public class Init
|
|||
{
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
// set cluster config from Nomad node index env variable
|
||||
if (Environment.GetEnvironmentVariable("NOMAD_ALLOC_INDEX") is { } nodeIndex)
|
||||
Environment.SetEnvironmentVariable("PluralKit__Bot__Cluster__NodeName", $"pluralkit-{nodeIndex}");
|
||||
|
||||
// Load configuration and run global init stuff
|
||||
var config = InitUtils.BuildConfiguration(args).Build();
|
||||
InitUtils.InitStatic();
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.13.0"/>
|
||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.32.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.37.0" PrivateAssets="All"/>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.47.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.8.26"/>
|
||||
<PackageReference Include="Sentry" Version="3.11.1"/>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.2"/>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commandsystem/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=handlers/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utils/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
|
|
@ -43,6 +43,10 @@ public class ProxyMatcher
|
|||
{
|
||||
match = default;
|
||||
|
||||
if (!ctx.AllowAutoproxy)
|
||||
throw new ProxyService.ProxyChecksFailedException(
|
||||
"Autoproxy is disabled for your account. Type `pk;cfg autoproxy account enable` to re-enable it.");
|
||||
|
||||
// Skip autoproxy match if we hit the escape character
|
||||
if (messageContent.StartsWith(AutoproxyEscapeCharacter))
|
||||
throw new ProxyService.ProxyChecksFailedException(
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ public class ProxyService
|
|||
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match);
|
||||
}
|
||||
|
||||
public async Task ExecuteReproxy(Message trigger, PKMessage msg, ProxyMember member)
|
||||
public async Task ExecuteReproxy(Message trigger, PKMessage msg, List<ProxyMember> members, ProxyMember member)
|
||||
{
|
||||
var originalMsg = await _rest.GetMessageOrNull(msg.Channel, msg.Mid);
|
||||
if (originalMsg == null)
|
||||
|
|
@ -213,24 +213,34 @@ public class ProxyService
|
|||
throw new ProxyChecksFailedException(
|
||||
"Proxying was disabled in this channel by a server administrator (via the proxy blacklist).");
|
||||
|
||||
var autoproxySettings = await _repo.GetAutoproxySettings(ctx.SystemId.Value, msg.Guild!.Value, null);
|
||||
var prevMatched = _matcher.TryMatch(ctx, autoproxySettings, members, out var prevMatch, originalMsg.Content,
|
||||
originalMsg.Attachments.Length > 0, false);
|
||||
|
||||
var match = new ProxyMatch
|
||||
{
|
||||
Member = member,
|
||||
Content = prevMatched ? prevMatch.Content : originalMsg.Content,
|
||||
ProxyTags = member.ProxyTags.FirstOrDefault(),
|
||||
};
|
||||
|
||||
var messageChannel = await _rest.GetChannelOrNull(msg.Channel!);
|
||||
var rootChannel = await _rest.GetChannelOrNull(messageChannel.IsThread() ? messageChannel.ParentId!.Value : messageChannel.Id);
|
||||
var rootChannel = messageChannel.IsThread() ? await _rest.GetChannelOrNull(messageChannel.ParentId!.Value) : messageChannel;
|
||||
var threadId = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null;
|
||||
var guild = await _rest.GetGuildOrNull(msg.Guild!.Value);
|
||||
var guildMember = await _rest.GetGuildMember(msg.Guild!.Value, trigger.Author.Id);
|
||||
|
||||
// Grab user permissions
|
||||
var senderPermissions = PermissionExtensions.PermissionsFor(guild, rootChannel, trigger.Author.Id, null);
|
||||
var senderPermissions = PermissionExtensions.PermissionsFor(guild, rootChannel, trigger.Author.Id, guildMember);
|
||||
var allowEveryone = senderPermissions.HasFlag(PermissionSet.MentionEveryone);
|
||||
|
||||
// Make sure user has permissions to send messages
|
||||
if (!senderPermissions.HasFlag(PermissionSet.SendMessages))
|
||||
throw new PKError("You don't have permission to send messages in the channel that message is in.");
|
||||
|
||||
// Mangle embeds (for reply embed color changing)
|
||||
var mangledEmbeds = originalMsg.Embeds!.Select(embed => MangleReproxyEmbed(embed, member)).Where(embed => embed != null).ToArray();
|
||||
|
||||
// Send the reproxied webhook
|
||||
var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest
|
||||
{
|
||||
|
|
@ -239,15 +249,15 @@ public class ProxyService
|
|||
ThreadId = threadId,
|
||||
Name = match.Member.ProxyName(ctx),
|
||||
AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)),
|
||||
Content = originalMsg.Content!,
|
||||
Content = match.ProxyContent!,
|
||||
Attachments = originalMsg.Attachments!,
|
||||
FileSizeLimit = guild.FileSizeLimit(),
|
||||
Embeds = originalMsg.Embeds!.ToArray(),
|
||||
Embeds = mangledEmbeds,
|
||||
Stickers = originalMsg.StickerItems!,
|
||||
AllowEveryone = allowEveryone
|
||||
});
|
||||
|
||||
var autoproxySettings = await _repo.GetAutoproxySettings(ctx.SystemId.Value, msg.Guild!.Value, null);
|
||||
|
||||
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match, deletePrevious: false);
|
||||
await _rest.DeleteMessage(originalMsg.ChannelId!, originalMsg.Id!);
|
||||
}
|
||||
|
|
@ -271,6 +281,34 @@ public class ProxyService
|
|||
}
|
||||
}
|
||||
|
||||
private Embed? MangleReproxyEmbed(Embed embed, ProxyMember member)
|
||||
{
|
||||
// XXX: This is a naïve implementation of detecting reply embeds: looking for the same Unicode
|
||||
// characters as used in the reply embed generation, since we don't _really_ have a good way
|
||||
// to detect whether an embed is a PluralKit reply embed right now, whether a message is in
|
||||
// reply to another message isn't currently stored anywhere in the database.
|
||||
//
|
||||
// unicodes: [three-per-em space] [left arrow emoji] [force emoji presentation]
|
||||
if (embed.Author != null && embed.Author!.Name.EndsWith("\u2004\u21a9\ufe0f"))
|
||||
{
|
||||
return new Embed
|
||||
{
|
||||
Type = "rich",
|
||||
Author = embed.Author!,
|
||||
Description = embed.Description!,
|
||||
Color = member.Color?.ToDiscordColor()
|
||||
};
|
||||
}
|
||||
|
||||
// XXX: remove non-rich embeds as including them breaks link embeds completely
|
||||
else if (embed.Type != "rich")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
private Embed CreateReplyEmbed(ProxyMatch match, Message trigger, Message repliedTo, string? nickname,
|
||||
string? avatar)
|
||||
{
|
||||
|
|
@ -377,8 +415,8 @@ public class ProxyService
|
|||
{
|
||||
var sentMessage = new PKMessage
|
||||
{
|
||||
Channel = triggerMessage.ChannelId,
|
||||
Guild = triggerMessage.GuildId,
|
||||
Channel = proxyMessage.ChannelId,
|
||||
Guild = proxyMessage.GuildId,
|
||||
Member = match.Member.Id,
|
||||
Mid = proxyMessage.Id,
|
||||
OriginalMid = triggerMessage.Id,
|
||||
|
|
@ -389,7 +427,7 @@ public class ProxyService
|
|||
=> _repo.AddMessage(sentMessage);
|
||||
|
||||
Task LogMessageToChannel() =>
|
||||
_logChannel.LogMessage(ctx, sentMessage, triggerMessage, proxyMessage).AsTask();
|
||||
_logChannel.LogMessage(sentMessage, triggerMessage, proxyMessage).AsTask();
|
||||
|
||||
Task SaveLatchAutoproxy() => autoproxySettings.AutoproxyMode == AutoproxyMode.Latch
|
||||
? _repo.UpdateAutoproxy(ctx.SystemId.Value, triggerMessage.GuildId, null, new()
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public class ProxyTagParser
|
|||
// We got a match, extract inner text
|
||||
inner = input.Substring(prefix.Length, input.Length - prefix.Length - suffix.Length);
|
||||
|
||||
// (see https://github.com/xSke/PluralKit/pull/181)
|
||||
// (see https://github.com/PluralKit/PluralKit/pull/181)
|
||||
return inner.Trim() != "\U0000fe0f";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,28 +8,36 @@ namespace PluralKit.Bot;
|
|||
|
||||
public class CommandMessageService
|
||||
{
|
||||
private readonly IClock _clock;
|
||||
private readonly IDatabase _db;
|
||||
private readonly RedisService _redis;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ModelRepository _repo;
|
||||
private static readonly TimeSpan CommandMessageRetention = TimeSpan.FromHours(24);
|
||||
|
||||
public CommandMessageService(IDatabase db, ModelRepository repo, IClock clock, ILogger logger)
|
||||
public CommandMessageService(RedisService redis, IClock clock, ILogger logger)
|
||||
{
|
||||
_db = db;
|
||||
_repo = repo;
|
||||
_clock = clock;
|
||||
_redis = redis;
|
||||
_logger = logger.ForContext<CommandMessageService>();
|
||||
}
|
||||
|
||||
public async Task RegisterMessage(ulong messageId, ulong channelId, ulong authorId)
|
||||
{
|
||||
if (_redis.Connection == null) return;
|
||||
|
||||
_logger.Debug(
|
||||
"Registering command response {MessageId} from author {AuthorId} in {ChannelId}",
|
||||
messageId, authorId, channelId
|
||||
);
|
||||
await _repo.SaveCommandMessage(messageId, channelId, authorId);
|
||||
|
||||
await _redis.Connection.GetDatabase().StringSetAsync(messageId.ToString(), $"{authorId}-{channelId}", expiry: CommandMessageRetention);
|
||||
}
|
||||
|
||||
public async Task<CommandMessage?> GetCommandMessage(ulong messageId) =>
|
||||
await _repo.GetCommandMessage(messageId);
|
||||
public async Task<(ulong?, ulong?)> GetCommandMessage(ulong messageId)
|
||||
{
|
||||
var str = await _redis.Connection.GetDatabase().StringGetAsync(messageId.ToString());
|
||||
if (str.HasValue)
|
||||
{
|
||||
var split = ((string)str).Split("-");
|
||||
return (ulong.Parse(split[0]), ulong.Parse(split[1]));
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +72,8 @@ public class EmbedService
|
|||
.Thumbnail(new Embed.EmbedThumbnail(system.AvatarUrl.TryGetCleanCdnUrl()))
|
||||
.Footer(new Embed.EmbedFooter(
|
||||
$"System ID: {system.Hid} | Created on {system.Created.FormatZoned(cctx.Zone)}"))
|
||||
.Color(color);
|
||||
.Color(color)
|
||||
.Url($"https://dash.pluralkit.me/profile/s/{system.Hid}");
|
||||
|
||||
if (system.DescriptionPrivacy.CanAccess(ctx))
|
||||
eb.Image(new Embed.EmbedImage(system.BannerImage));
|
||||
|
|
@ -143,6 +144,9 @@ public class EmbedService
|
|||
$"System ID: {systemHid} | Member ID: {member.Hid} | Sender: {triggerMessage.Author.Username}#{triggerMessage.Author.Discriminator} ({triggerMessage.Author.Id}) | Message ID: {proxiedMessage.Id} | Original Message ID: {triggerMessage.Id}"))
|
||||
.Timestamp(timestamp.ToDateTimeOffset().ToString("O"));
|
||||
|
||||
if (oldContent == "")
|
||||
oldContent = "*no message content*";
|
||||
|
||||
if (oldContent != null)
|
||||
embed.Field(new Embed.Field("Old message", oldContent?.NormalizeLineEndSpacing().Truncate(1000)));
|
||||
|
||||
|
|
@ -179,8 +183,7 @@ public class EmbedService
|
|||
.ToListAsync();
|
||||
|
||||
var eb = new EmbedBuilder()
|
||||
// TODO: add URL of website when that's up
|
||||
.Author(new Embed.EmbedAuthor(name, IconUrl: avatar.TryGetCleanCdnUrl()))
|
||||
.Author(new Embed.EmbedAuthor(name, IconUrl: avatar.TryGetCleanCdnUrl(), Url: $"https://dash.pluralkit.me/profile/m/{member.Hid}"))
|
||||
// .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray)
|
||||
.Color(color)
|
||||
.Footer(new Embed.EmbedFooter(
|
||||
|
|
@ -264,7 +267,7 @@ public class EmbedService
|
|||
}
|
||||
|
||||
var eb = new EmbedBuilder()
|
||||
.Author(new Embed.EmbedAuthor(nameField, IconUrl: target.IconFor(pctx)))
|
||||
.Author(new Embed.EmbedAuthor(nameField, IconUrl: target.IconFor(pctx), Url: $"https://dash.pluralkit.me/profile/g/{target.Hid}"))
|
||||
.Color(color);
|
||||
|
||||
eb.Footer(new Embed.EmbedFooter($"System ID: {system.Hid} | Group ID: {target.Hid}{(target.MetadataPrivacy.CanAccess(pctx) ? $" | Created on {target.Created.FormatZoned(ctx.Zone)}" : "")}"));
|
||||
|
|
|
|||
|
|
@ -34,17 +34,16 @@ public class LogChannelService
|
|||
_logger = logger.ForContext<LogChannelService>();
|
||||
}
|
||||
|
||||
public async ValueTask LogMessage(MessageContext ctx, PKMessage proxiedMessage, Message trigger,
|
||||
Message hookMessage, string oldContent = null)
|
||||
public async ValueTask LogMessage(PKMessage proxiedMessage, Message trigger, Message hookMessage, string oldContent = null)
|
||||
{
|
||||
var logChannelId = await GetAndCheckLogChannel(ctx, trigger, proxiedMessage);
|
||||
var logChannelId = await GetAndCheckLogChannel(trigger, proxiedMessage);
|
||||
if (logChannelId == null)
|
||||
return;
|
||||
|
||||
var triggerChannel = await _cache.GetChannel(proxiedMessage.Channel);
|
||||
|
||||
var system = await _repo.GetSystem(ctx.SystemId.Value);
|
||||
var member = await _repo.GetMember(proxiedMessage.Member!.Value);
|
||||
var system = await _repo.GetSystem(member.System);
|
||||
|
||||
// Send embed!
|
||||
var embed = _embed.CreateLoggedMessageEmbed(trigger, hookMessage, system.Hid, member, triggerChannel.Name,
|
||||
|
|
@ -54,8 +53,7 @@ public class LogChannelService
|
|||
await _rest.CreateMessage(logChannelId.Value, new MessageRequest { Content = url, Embeds = new[] { embed } });
|
||||
}
|
||||
|
||||
private async Task<ulong?> GetAndCheckLogChannel(MessageContext ctx, Message trigger,
|
||||
PKMessage proxiedMessage)
|
||||
private async Task<ulong?> GetAndCheckLogChannel(Message trigger, PKMessage proxiedMessage)
|
||||
{
|
||||
if (proxiedMessage.Guild == null && proxiedMessage.Channel != trigger.ChannelId)
|
||||
// a very old message is being edited outside of its original channel
|
||||
|
|
@ -63,18 +61,15 @@ public class LogChannelService
|
|||
return null;
|
||||
|
||||
var guildId = proxiedMessage.Guild ?? trigger.GuildId.Value;
|
||||
var logChannelId = ctx.LogChannel;
|
||||
var isBlacklisted = ctx.InLogBlacklist;
|
||||
|
||||
if (proxiedMessage.Guild != trigger.GuildId)
|
||||
{
|
||||
// we're editing a message from a different server, get log channel info from the database
|
||||
var guild = await _repo.GetGuild(proxiedMessage.Guild.Value);
|
||||
logChannelId = guild.LogChannel;
|
||||
isBlacklisted = guild.LogBlacklist.Any(x => x == trigger.ChannelId);
|
||||
}
|
||||
// get log channel info from the database
|
||||
var guild = await _repo.GetGuild(guildId);
|
||||
var logChannelId = guild.LogChannel;
|
||||
var isBlacklisted = guild.LogBlacklist.Any(x => x == trigger.ChannelId);
|
||||
|
||||
if (ctx.SystemId == null || logChannelId == null || isBlacklisted) return null;
|
||||
// if (ctx.SystemId == null ||
|
||||
// removed the above, there shouldn't be a way to get to this code path if you don't have a system registered
|
||||
if (logChannelId == null || isBlacklisted) return null;
|
||||
|
||||
// Find log channel and check if valid
|
||||
var logChannel = await FindLogChannel(guildId, logChannelId.Value);
|
||||
|
|
@ -86,7 +81,7 @@ public class LogChannelService
|
|||
{
|
||||
_logger.Information(
|
||||
"Does not have permission to log proxy, ignoring (channel: {ChannelId}, guild: {GuildId}, bot permissions: {BotPermissions})",
|
||||
logChannel.Id, trigger.GuildId!.Value, perms);
|
||||
logChannel.Id, guildId, perms);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using App.Metrics;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using Myriad.Gateway;
|
||||
|
|
@ -9,13 +11,13 @@ namespace PluralKit.Bot;
|
|||
|
||||
public class PrivateChannelService
|
||||
{
|
||||
private static readonly Dictionary<ulong, ulong> _channelsCache = new();
|
||||
|
||||
private readonly IMetrics _metrics;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ModelRepository _repo;
|
||||
private readonly DiscordApiClient _rest;
|
||||
public PrivateChannelService(ILogger logger, ModelRepository repo, DiscordApiClient rest)
|
||||
public PrivateChannelService(IMetrics metrics, ILogger logger, ModelRepository repo, DiscordApiClient rest)
|
||||
{
|
||||
_metrics = metrics;
|
||||
_logger = logger;
|
||||
_repo = repo;
|
||||
_rest = rest;
|
||||
|
|
@ -23,35 +25,32 @@ public class PrivateChannelService
|
|||
|
||||
public async Task TrySavePrivateChannel(MessageCreateEvent evt)
|
||||
{
|
||||
if (evt.GuildId != null) return;
|
||||
if (_channelsCache.TryGetValue(evt.Author.Id, out _)) return;
|
||||
|
||||
await SaveDmChannel(evt.Author.Id, evt.ChannelId);
|
||||
if (evt.GuildId == null) await SaveDmChannel(evt.Author.Id, evt.ChannelId);
|
||||
}
|
||||
|
||||
public async Task<ulong> GetOrCreateDmChannel(ulong userId)
|
||||
{
|
||||
if (_channelsCache.TryGetValue(userId, out var cachedChannelId))
|
||||
return cachedChannelId;
|
||||
|
||||
var channelId = await _repo.GetDmChannel(userId);
|
||||
if (channelId == null)
|
||||
if (channelId != null)
|
||||
{
|
||||
var channel = await _rest.CreateDm(userId);
|
||||
channelId = channel.Id;
|
||||
_metrics.Measure.Meter.Mark(BotMetrics.DatabaseDMCacheHits);
|
||||
return channelId.Value;
|
||||
}
|
||||
|
||||
// spawn off saving the channel as to not block the current thread
|
||||
_ = SaveDmChannel(userId, channelId.Value);
|
||||
_metrics.Measure.Meter.Mark(BotMetrics.DMCacheMisses);
|
||||
|
||||
return channelId.Value;
|
||||
var channel = await _rest.CreateDm(userId);
|
||||
|
||||
// spawn off saving the channel as to not block the current thread
|
||||
_ = SaveDmChannel(userId, channel.Id);
|
||||
|
||||
return channel.Id;
|
||||
}
|
||||
|
||||
private async Task SaveDmChannel(ulong userId, ulong channelId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channelsCache.Add(userId, channelId);
|
||||
await _repo.UpdateAccount(userId, new() { DmChannel = channelId });
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class RedisGatewayService
|
|||
_redis = await ConnectionMultiplexer.ConnectAsync(_config.RedisGatewayUrl);
|
||||
|
||||
_logger.Debug("Subscribing to shard {ShardId} on redis", shardId);
|
||||
|
||||
|
||||
var channel = await _redis.GetSubscriber().SubscribeAsync($"evt-{shardId}");
|
||||
channel.OnMessage((evt) => Handle(shardId, evt));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,7 +174,8 @@ public class WebhookExecutorService
|
|||
// We don't care about whether the sending succeeds, and we don't want to *wait* for it, so we just fork it off
|
||||
var _ = TrySendRemainingAttachments(webhook, req.Name, req.AvatarUrl, attachmentChunks, req.ThreadId);
|
||||
|
||||
return webhookMessage;
|
||||
// for some reason discord may(?) return a null guildid here???
|
||||
return webhookMessage with { GuildId = webhookMessage.GuildId ?? req.GuildId };
|
||||
}
|
||||
|
||||
private async Task TrySendRemainingAttachments(Webhook webhook, string name, string avatarUrl,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class InteractionContext
|
|||
await Respond(InteractionResponse.ResponseType.DeferredUpdateMessage,
|
||||
new InteractionApplicationCommandCallbackData
|
||||
{
|
||||
// Components = _evt.Message.Components
|
||||
Components = Event.Message.Components
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ public class SentryEnricher:
|
|||
ISentryEnricher<MessageReactionAddEvent>
|
||||
{
|
||||
private readonly Bot _bot;
|
||||
private readonly BotConfig _config;
|
||||
|
||||
public SentryEnricher(Bot bot)
|
||||
public SentryEnricher(Bot bot, BotConfig config)
|
||||
{
|
||||
_bot = bot;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
// TODO: should this class take the Scope by dependency injection instead?
|
||||
|
|
@ -37,6 +39,8 @@ public class SentryEnricher:
|
|||
{"message", evt.Id.ToString()}
|
||||
});
|
||||
scope.SetTag("shard", shardId.ToString());
|
||||
if (_config.Cluster != null)
|
||||
scope.SetTag("cluster", _config.Cluster!.NodeIndex.ToString());
|
||||
|
||||
// Also report information about the bot's permissions in the channel
|
||||
// We get a lot of permission errors so this'll be useful for determining problems
|
||||
|
|
@ -56,6 +60,8 @@ public class SentryEnricher:
|
|||
{"messages", string.Join(",", evt.Ids)}
|
||||
});
|
||||
scope.SetTag("shard", shardId.ToString());
|
||||
if (_config.Cluster != null)
|
||||
scope.SetTag("cluster", _config.Cluster!.NodeIndex.ToString());
|
||||
}
|
||||
|
||||
public void Enrich(Scope scope, int shardId, MessageUpdateEvent evt)
|
||||
|
|
@ -68,6 +74,8 @@ public class SentryEnricher:
|
|||
{"message", evt.Id.ToString()}
|
||||
});
|
||||
scope.SetTag("shard", shardId.ToString());
|
||||
if (_config.Cluster != null)
|
||||
scope.SetTag("cluster", _config.Cluster!.NodeIndex.ToString());
|
||||
}
|
||||
|
||||
public void Enrich(Scope scope, int shardId, MessageDeleteEvent evt)
|
||||
|
|
@ -80,6 +88,8 @@ public class SentryEnricher:
|
|||
{"message", evt.Id.ToString()}
|
||||
});
|
||||
scope.SetTag("shard", shardId.ToString());
|
||||
if (_config.Cluster != null)
|
||||
scope.SetTag("cluster", _config.Cluster!.NodeIndex.ToString());
|
||||
}
|
||||
|
||||
public void Enrich(Scope scope, int shardId, MessageReactionAddEvent evt)
|
||||
|
|
@ -94,5 +104,7 @@ public class SentryEnricher:
|
|||
{"reaction", evt.Emoji.Name}
|
||||
});
|
||||
scope.SetTag("shard", shardId.ToString());
|
||||
if (_config.Cluster != null)
|
||||
scope.SetTag("cluster", _config.Cluster!.NodeIndex.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -24,9 +24,9 @@
|
|||
},
|
||||
"Grpc.Tools": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.37.0, )",
|
||||
"resolved": "2.37.0",
|
||||
"contentHash": "cud/urkbw3QoQ8+kNeCy2YI0sHrh7td/1cZkVbH6hDLIXX7zzmJbV/KjYSiqiYtflQf+S5mJPLzDQWScN/QdDg=="
|
||||
"requested": "[2.47.0, )",
|
||||
"resolved": "2.47.0",
|
||||
"contentHash": "nInNoLfT/zR7+0VNIC4Lu5nF8azjTz3KwHB1ckwsYUxvof4uSxIt/LlCKb/NH7GPfXfdvqDDinguPpP5t55nuA=="
|
||||
},
|
||||
"Humanizer.Core": {
|
||||
"type": "Direct",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue