Move command functions around
This commit is contained in:
parent
a60be64551
commit
125ea81ec3
@ -8,18 +8,18 @@ using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class AutoproxyCommands
|
||||
public class Autoproxy
|
||||
{
|
||||
private IDataStore _data;
|
||||
private AutoproxyCacheService _cache;
|
||||
|
||||
public AutoproxyCommands(IDataStore data, AutoproxyCacheService cache)
|
||||
public Autoproxy(IDataStore data, AutoproxyCacheService cache)
|
||||
{
|
||||
_data = data;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task Autoproxy(Context ctx)
|
||||
public async Task AutoproxyRoot(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem().CheckGuildContext();
|
||||
|
@ -89,58 +89,58 @@ namespace PluralKit.Bot.Commands
|
||||
if (ctx.Match("switch", "sw"))
|
||||
return HandleSwitchCommand(ctx);
|
||||
if (ctx.Match("ap", "autoproxy", "auto"))
|
||||
return ctx.Execute<AutoproxyCommands>(Autoproxy, m => m.Autoproxy(ctx));
|
||||
return ctx.Execute<Autoproxy>(Autoproxy, m => m.AutoproxyRoot(ctx));
|
||||
if (ctx.Match("link"))
|
||||
return ctx.Execute<LinkCommands>(Link, m => m.LinkSystem(ctx));
|
||||
return ctx.Execute<SystemLink>(Link, m => m.LinkSystem(ctx));
|
||||
if (ctx.Match("unlink"))
|
||||
return ctx.Execute<LinkCommands>(Unlink, m => m.UnlinkAccount(ctx));
|
||||
return ctx.Execute<SystemLink>(Unlink, m => m.UnlinkAccount(ctx));
|
||||
if (ctx.Match("token"))
|
||||
if (ctx.Match("refresh", "renew", "invalidate", "reroll", "regen"))
|
||||
return ctx.Execute<APICommands>(TokenRefresh, m => m.RefreshToken(ctx));
|
||||
return ctx.Execute<Token>(TokenRefresh, m => m.RefreshToken(ctx));
|
||||
else
|
||||
return ctx.Execute<APICommands>(TokenGet, m => m.GetToken(ctx));
|
||||
return ctx.Execute<Token>(TokenGet, m => m.GetToken(ctx));
|
||||
if (ctx.Match("import"))
|
||||
return ctx.Execute<ImportExportCommands>(Import, m => m.Import(ctx));
|
||||
return ctx.Execute<ImportExport>(Import, m => m.Import(ctx));
|
||||
if (ctx.Match("export"))
|
||||
return ctx.Execute<ImportExportCommands>(Export, m => m.Export(ctx));
|
||||
return ctx.Execute<ImportExport>(Export, m => m.Export(ctx));
|
||||
if (ctx.Match("help"))
|
||||
if (ctx.Match("commands"))
|
||||
return ctx.Reply("For the list of commands, see the website: <https://pluralkit.me/commands>");
|
||||
else if (ctx.Match("proxy"))
|
||||
return ctx.Reply("The proxy help page has been moved! See the website: https://pluralkit.me/guide#proxying");
|
||||
else return ctx.Execute<HelpCommands>(Help, m => m.HelpRoot(ctx));
|
||||
else return ctx.Execute<Help>(Help, m => m.HelpRoot(ctx));
|
||||
if (ctx.Match("commands"))
|
||||
return ctx.Reply("For the list of commands, see the website: <https://pluralkit.me/commands>");
|
||||
if (ctx.Match("message", "msg"))
|
||||
return ctx.Execute<ModCommands>(Message, m => m.GetMessage(ctx));
|
||||
return ctx.Execute<Misc>(Message, m => m.GetMessage(ctx));
|
||||
if (ctx.Match("log"))
|
||||
if (ctx.Match("channel"))
|
||||
return ctx.Execute<ModCommands>(LogChannel, m => m.SetLogChannel(ctx));
|
||||
return ctx.Execute<ServerConfig>(LogChannel, m => m.SetLogChannel(ctx));
|
||||
else if (ctx.Match("enable", "on"))
|
||||
return ctx.Execute<ModCommands>(LogEnable, m => m.SetLogEnabled(ctx, true));
|
||||
return ctx.Execute<ServerConfig>(LogEnable, m => m.SetLogEnabled(ctx, true));
|
||||
else if (ctx.Match("disable", "off"))
|
||||
return ctx.Execute<ModCommands>(LogDisable, m => m.SetLogEnabled(ctx, false));
|
||||
return ctx.Execute<ServerConfig>(LogDisable, m => m.SetLogEnabled(ctx, false));
|
||||
else return PrintCommandExpectedError(ctx, LogCommands);
|
||||
if (ctx.Match("blacklist", "bl"))
|
||||
if (ctx.Match("enable", "on", "add", "deny"))
|
||||
return ctx.Execute<ModCommands>(BlacklistAdd, m => m.SetBlacklisted(ctx, true));
|
||||
return ctx.Execute<ServerConfig>(BlacklistAdd, m => m.SetBlacklisted(ctx, true));
|
||||
else if (ctx.Match("disable", "off", "remove", "allow"))
|
||||
return ctx.Execute<ModCommands>(BlacklistRemove, m => m.SetBlacklisted(ctx, false));
|
||||
return ctx.Execute<ServerConfig>(BlacklistRemove, m => m.SetBlacklisted(ctx, false));
|
||||
else return PrintCommandExpectedError(ctx, BlacklistAdd, BlacklistRemove);
|
||||
if (ctx.Match("proxy", "enable", "disable"))
|
||||
return ctx.Execute<SystemCommands>(SystemProxy, m => m.SystemProxy(ctx));
|
||||
if (ctx.Match("invite")) return ctx.Execute<MiscCommands>(Invite, m => m.Invite(ctx));
|
||||
if (ctx.Match("mn")) return ctx.Execute<MiscCommands>(null, m => m.Mn(ctx));
|
||||
if (ctx.Match("fire")) return ctx.Execute<MiscCommands>(null, m => m.Fire(ctx));
|
||||
if (ctx.Match("thunder")) return ctx.Execute<MiscCommands>(null, m => m.Thunder(ctx));
|
||||
if (ctx.Match("freeze")) return ctx.Execute<MiscCommands>(null, m => m.Freeze(ctx));
|
||||
if (ctx.Match("starstorm")) return ctx.Execute<MiscCommands>(null, m => m.Starstorm(ctx));
|
||||
if (ctx.Match("flash")) return ctx.Execute<MiscCommands>(null, m => m.Flash(ctx));
|
||||
if (ctx.Match("stats")) return ctx.Execute<MiscCommands>(null, m => m.Stats(ctx));
|
||||
return ctx.Execute<SystemEdit>(SystemProxy, m => m.SystemProxy(ctx));
|
||||
if (ctx.Match("invite")) return ctx.Execute<Misc>(Invite, m => m.Invite(ctx));
|
||||
if (ctx.Match("mn")) return ctx.Execute<Fun>(null, m => m.Mn(ctx));
|
||||
if (ctx.Match("fire")) return ctx.Execute<Fun>(null, m => m.Fire(ctx));
|
||||
if (ctx.Match("thunder")) return ctx.Execute<Fun>(null, m => m.Thunder(ctx));
|
||||
if (ctx.Match("freeze")) return ctx.Execute<Fun>(null, m => m.Freeze(ctx));
|
||||
if (ctx.Match("starstorm")) return ctx.Execute<Fun>(null, m => m.Starstorm(ctx));
|
||||
if (ctx.Match("flash")) return ctx.Execute<Fun>(null, m => m.Flash(ctx));
|
||||
if (ctx.Match("stats")) return ctx.Execute<Misc>(null, m => m.Stats(ctx));
|
||||
if (ctx.Match("permcheck"))
|
||||
return ctx.Execute<MiscCommands>(PermCheck, m => m.PermCheckGuild(ctx));
|
||||
return ctx.Execute<Misc>(PermCheck, m => m.PermCheckGuild(ctx));
|
||||
if (ctx.Match("random", "r"))
|
||||
return ctx.Execute<MemberCommands>(MemberRandom, m => m.MemberRandom(ctx));
|
||||
return ctx.Execute<Member>(MemberRandom, m => m.MemberRandom(ctx));
|
||||
|
||||
ctx.Reply(
|
||||
$"{Emojis.Error} Unknown command `{ctx.PeekArgument().SanitizeMentions()}`. For a list of possible commands, see <https://pluralkit.me/commands>.");
|
||||
@ -151,51 +151,51 @@ namespace PluralKit.Bot.Commands
|
||||
{
|
||||
// If we have no parameters, default to self-target
|
||||
if (!ctx.HasNext())
|
||||
await ctx.Execute<SystemCommands>(SystemInfo, m => m.Query(ctx, ctx.System));
|
||||
await ctx.Execute<System>(SystemInfo, m => m.Query(ctx, ctx.System));
|
||||
|
||||
// First, we match own-system-only commands (ie. no target system parameter)
|
||||
else if (ctx.Match("new", "create", "make", "add", "register", "init"))
|
||||
await ctx.Execute<SystemCommands>(SystemNew, m => m.New(ctx));
|
||||
await ctx.Execute<System>(SystemNew, m => m.New(ctx));
|
||||
else if (ctx.Match("name", "rename", "changename"))
|
||||
await ctx.Execute<SystemCommands>(SystemRename, m => m.Name(ctx));
|
||||
await ctx.Execute<SystemEdit>(SystemRename, m => m.Name(ctx));
|
||||
else if (ctx.Match("tag"))
|
||||
await ctx.Execute<SystemCommands>(SystemTag, m => m.Tag(ctx));
|
||||
await ctx.Execute<SystemEdit>(SystemTag, m => m.Tag(ctx));
|
||||
else if (ctx.Match("description", "desc", "bio"))
|
||||
await ctx.Execute<SystemCommands>(SystemDesc, m => m.Description(ctx));
|
||||
await ctx.Execute<SystemEdit>(SystemDesc, m => m.Description(ctx));
|
||||
else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp"))
|
||||
await ctx.Execute<SystemCommands>(SystemAvatar, m => m.SystemAvatar(ctx));
|
||||
await ctx.Execute<SystemEdit>(SystemAvatar, m => m.Avatar(ctx));
|
||||
else if (ctx.Match("delete", "remove", "destroy", "erase", "yeet"))
|
||||
await ctx.Execute<SystemCommands>(SystemDelete, m => m.Delete(ctx));
|
||||
await ctx.Execute<SystemEdit>(SystemDelete, m => m.Delete(ctx));
|
||||
else if (ctx.Match("timezone", "tz"))
|
||||
await ctx.Execute<SystemCommands>(SystemTimezone, m => m.SystemTimezone(ctx));
|
||||
await ctx.Execute<SystemEdit>(SystemTimezone, m => m.SystemTimezone(ctx));
|
||||
else if (ctx.Match("proxy"))
|
||||
await ctx.Execute<SystemCommands>(SystemProxy, m => m.SystemProxy(ctx));
|
||||
await ctx.Execute<SystemEdit>(SystemProxy, m => m.SystemProxy(ctx));
|
||||
else if (ctx.Match("list", "l", "members"))
|
||||
{
|
||||
if (ctx.Match("f", "full", "big", "details", "long"))
|
||||
await ctx.Execute<SystemCommands>(SystemList, m => m.MemberLongList(ctx, ctx.System));
|
||||
await ctx.Execute<SystemList>(SystemList, m => m.MemberLongList(ctx, ctx.System));
|
||||
else
|
||||
await ctx.Execute<SystemCommands>(SystemList, m => m.MemberShortList(ctx, ctx.System));
|
||||
await ctx.Execute<SystemList>(SystemList, m => m.MemberShortList(ctx, ctx.System));
|
||||
}
|
||||
else if (ctx.Match("f", "front", "fronter", "fronters"))
|
||||
{
|
||||
if (ctx.Match("h", "history"))
|
||||
await ctx.Execute<SystemCommands>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, ctx.System));
|
||||
await ctx.Execute<SystemFront>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, ctx.System));
|
||||
else if (ctx.Match("p", "percent", "%"))
|
||||
await ctx.Execute<SystemCommands>(SystemFrontPercent, m => m.SystemFrontPercent(ctx, ctx.System));
|
||||
await ctx.Execute<SystemFront>(SystemFrontPercent, m => m.SystemFrontPercent(ctx, ctx.System));
|
||||
else
|
||||
await ctx.Execute<SystemCommands>(SystemFronter, m => m.SystemFronter(ctx, ctx.System));
|
||||
await ctx.Execute<SystemFront>(SystemFronter, m => m.SystemFronter(ctx, ctx.System));
|
||||
}
|
||||
else if (ctx.Match("fh", "fronthistory", "history", "switches"))
|
||||
await ctx.Execute<SystemCommands>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, ctx.System));
|
||||
await ctx.Execute<SystemFront>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, ctx.System));
|
||||
else if (ctx.Match("fp", "frontpercent", "front%", "frontbreakdown"))
|
||||
await ctx.Execute<SystemCommands>(SystemFrontPercent, m => m.SystemFrontPercent(ctx, ctx.System));
|
||||
await ctx.Execute<SystemFront>(SystemFrontPercent, m => m.SystemFrontPercent(ctx, ctx.System));
|
||||
else if (ctx.Match("privacy"))
|
||||
await ctx.Execute<SystemCommands>(SystemPrivacy, m => m.SystemPrivacy(ctx));
|
||||
await ctx.Execute<SystemEdit>(SystemPrivacy, m => m.SystemPrivacy(ctx));
|
||||
else if (ctx.Match("commands", "help"))
|
||||
await PrintCommandList(ctx, "systems", SystemCommands);
|
||||
else if (!ctx.HasNext()) // Bare command
|
||||
await ctx.Execute<SystemCommands>(SystemInfo, m => m.Query(ctx, ctx.System));
|
||||
await ctx.Execute<System>(SystemInfo, m => m.Query(ctx, ctx.System));
|
||||
else
|
||||
await HandleSystemCommandTargeted(ctx);
|
||||
}
|
||||
@ -213,27 +213,27 @@ namespace PluralKit.Bot.Commands
|
||||
else if (ctx.Match("list", "l", "members"))
|
||||
{
|
||||
if (ctx.Match("f", "full", "big", "details", "long"))
|
||||
await ctx.Execute<SystemCommands>(SystemList, m => m.MemberLongList(ctx, target));
|
||||
await ctx.Execute<SystemList>(SystemList, m => m.MemberLongList(ctx, target));
|
||||
else
|
||||
await ctx.Execute<SystemCommands>(SystemList, m => m.MemberShortList(ctx, target));
|
||||
await ctx.Execute<SystemList>(SystemList, m => m.MemberShortList(ctx, target));
|
||||
}
|
||||
else if (ctx.Match("f", "front", "fronter", "fronters"))
|
||||
{
|
||||
if (ctx.Match("h", "history"))
|
||||
await ctx.Execute<SystemCommands>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, target));
|
||||
await ctx.Execute<SystemFront>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, target));
|
||||
else if (ctx.Match("p", "percent", "%"))
|
||||
await ctx.Execute<SystemCommands>(SystemFrontPercent, m => m.SystemFrontPercent(ctx, target));
|
||||
await ctx.Execute<SystemFront>(SystemFrontPercent, m => m.SystemFrontPercent(ctx, target));
|
||||
else
|
||||
await ctx.Execute<SystemCommands>(SystemFronter, m => m.SystemFronter(ctx, target));
|
||||
await ctx.Execute<SystemFront>(SystemFronter, m => m.SystemFronter(ctx, target));
|
||||
}
|
||||
else if (ctx.Match("fh", "fronthistory", "history", "switches"))
|
||||
await ctx.Execute<SystemCommands>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, target));
|
||||
await ctx.Execute<SystemFront>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, target));
|
||||
else if (ctx.Match("fp", "frontpercent", "front%", "frontbreakdown"))
|
||||
await ctx.Execute<SystemCommands>(SystemFrontPercent, m => m.SystemFrontPercent(ctx, target));
|
||||
await ctx.Execute<SystemFront>(SystemFrontPercent, m => m.SystemFrontPercent(ctx, target));
|
||||
else if (ctx.Match("info", "view", "show"))
|
||||
await ctx.Execute<SystemCommands>(SystemInfo, m => m.Query(ctx, target));
|
||||
await ctx.Execute<System>(SystemInfo, m => m.Query(ctx, target));
|
||||
else if (!ctx.HasNext())
|
||||
await ctx.Execute<SystemCommands>(SystemInfo, m => m.Query(ctx, target));
|
||||
await ctx.Execute<System>(SystemInfo, m => m.Query(ctx, target));
|
||||
else
|
||||
await PrintCommandNotFoundError(ctx, SystemList, SystemFronter, SystemFrontHistory, SystemFrontPercent,
|
||||
SystemInfo);
|
||||
@ -242,7 +242,7 @@ namespace PluralKit.Bot.Commands
|
||||
private async Task HandleMemberCommand(Context ctx)
|
||||
{
|
||||
if (ctx.Match("new", "n", "add", "create", "register"))
|
||||
await ctx.Execute<MemberCommands>(MemberNew, m => m.NewMember(ctx));
|
||||
await ctx.Execute<Member>(MemberNew, m => m.NewMember(ctx));
|
||||
else if (ctx.Match("commands", "help"))
|
||||
await PrintCommandList(ctx, "members", MemberCommands);
|
||||
else if (await ctx.MatchMember() is PKMember target)
|
||||
@ -258,31 +258,31 @@ namespace PluralKit.Bot.Commands
|
||||
{
|
||||
// Commands that have a member target (eg. pk;member <member> delete)
|
||||
if (ctx.Match("rename", "name", "changename", "setname"))
|
||||
await ctx.Execute<MemberCommands>(MemberRename, m => m.RenameMember(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberRename, m => m.Name(ctx, target));
|
||||
else if (ctx.Match("description", "info", "bio", "text", "desc"))
|
||||
await ctx.Execute<MemberCommands>(MemberDesc, m => m.MemberDescription(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberDesc, m => m.Description(ctx, target));
|
||||
else if (ctx.Match("pronouns", "pronoun"))
|
||||
await ctx.Execute<MemberCommands>(MemberPronouns, m => m.MemberPronouns(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberPronouns, m => m.Pronouns(ctx, target));
|
||||
else if (ctx.Match("color", "colour"))
|
||||
await ctx.Execute<MemberCommands>(MemberColor, m => m.MemberColor(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberColor, m => m.Color(ctx, target));
|
||||
else if (ctx.Match("birthday", "bday", "birthdate", "cakeday", "bdate"))
|
||||
await ctx.Execute<MemberCommands>(MemberBirthday, m => m.MemberBirthday(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberBirthday, m => m.Birthday(ctx, target));
|
||||
else if (ctx.Match("proxy", "tags", "proxytags", "brackets"))
|
||||
await ctx.Execute<MemberCommands>(MemberProxy, m => m.MemberProxy(ctx, target));
|
||||
await ctx.Execute<MemberProxy>(MemberProxy, m => m.Proxy(ctx, target));
|
||||
else if (ctx.Match("delete", "remove", "destroy", "erase", "yeet"))
|
||||
await ctx.Execute<MemberCommands>(MemberDelete, m => m.MemberDelete(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberDelete, m => m.Delete(ctx, target));
|
||||
else if (ctx.Match("avatar", "profile", "picture", "icon", "image", "pfp", "pic"))
|
||||
await ctx.Execute<MemberCommands>(MemberAvatar, m => m.MemberAvatar(ctx, target));
|
||||
await ctx.Execute<MemberAvatar>(MemberAvatar, m => m.Avatar(ctx, target));
|
||||
else if (ctx.Match("displayname", "dn", "dname", "nick", "nickname"))
|
||||
await ctx.Execute<MemberCommands>(MemberDisplayName, m => m.MemberDisplayName(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberDisplayName, m => m.DisplayName(ctx, target));
|
||||
else if (ctx.Match("servername", "sn", "sname", "snick", "snickname", "servernick", "servernickname", "serverdisplayname", "guildname", "guildnick", "guildnickname", "serverdn"))
|
||||
await ctx.Execute<MemberCommands>(MemberServerName, m => m.MemberServerName(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberServerName, m => m.ServerName(ctx, target));
|
||||
else if (ctx.Match("keepproxy", "keeptags", "showtags"))
|
||||
await ctx.Execute<MemberCommands>(MemberKeepProxy, m => m.MemberKeepProxy(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberKeepProxy, m => m.KeepProxy(ctx, target));
|
||||
else if (ctx.Match("private", "privacy", "hidden", "public"))
|
||||
await ctx.Execute<MemberCommands>(MemberPrivacy, m => m.MemberPrivacy(ctx, target));
|
||||
await ctx.Execute<MemberEdit>(MemberPrivacy, m => m.Privacy(ctx, target));
|
||||
else if (!ctx.HasNext()) // Bare command
|
||||
await ctx.Execute<MemberCommands>(MemberInfo, m => m.ViewMember(ctx, target));
|
||||
await ctx.Execute<Member>(MemberInfo, m => m.ViewMember(ctx, target));
|
||||
else
|
||||
await PrintCommandNotFoundError(ctx, MemberInfo, MemberRename, MemberDisplayName, MemberServerName ,MemberDesc, MemberPronouns, MemberColor, MemberBirthday, MemberProxy, MemberDelete, MemberAvatar, SystemList);
|
||||
}
|
||||
@ -290,15 +290,15 @@ namespace PluralKit.Bot.Commands
|
||||
private async Task HandleSwitchCommand(Context ctx)
|
||||
{
|
||||
if (ctx.Match("out"))
|
||||
await ctx.Execute<SwitchCommands>(SwitchOut, m => m.SwitchOut(ctx));
|
||||
await ctx.Execute<Switch>(SwitchOut, m => m.SwitchOut(ctx));
|
||||
else if (ctx.Match("move", "shift", "offset"))
|
||||
await ctx.Execute<SwitchCommands>(SwitchMove, m => m.SwitchMove(ctx));
|
||||
await ctx.Execute<Switch>(SwitchMove, m => m.SwitchMove(ctx));
|
||||
else if (ctx.Match("delete", "remove", "erase", "cancel", "yeet"))
|
||||
await ctx.Execute<SwitchCommands>(SwitchDelete, m => m.SwitchDelete(ctx));
|
||||
await ctx.Execute<Switch>(SwitchDelete, m => m.SwitchDelete(ctx));
|
||||
else if (ctx.Match("commands", "help"))
|
||||
await PrintCommandList(ctx, "switching", SwitchCommands);
|
||||
else if (ctx.HasNext()) // there are following arguments
|
||||
await ctx.Execute<SwitchCommands>(Switch, m => m.Switch(ctx));
|
||||
await ctx.Execute<Switch>(Switch, m => m.SwitchDo(ctx));
|
||||
else
|
||||
await PrintCommandNotFoundError(ctx, Switch, SwitchOut, SwitchMove, SwitchDelete, SystemFronter, SystemFrontHistory);
|
||||
}
|
||||
|
16
PluralKit.Bot/Commands/Fun.cs
Normal file
16
PluralKit.Bot/Commands/Fun.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class Fun
|
||||
{
|
||||
public Task Mn(Context ctx) => ctx.Reply("Gotta catch 'em all!");
|
||||
public Task Fire(Context ctx) => ctx.Reply("*A giant lightning bolt promptly erupts into a pillar of fire as it hits your opponent.*");
|
||||
public Task Thunder(Context ctx) => ctx.Reply("*A giant ball of lightning is conjured and fired directly at your opponent, vanquishing them.*");
|
||||
public Task Freeze(Context ctx) => ctx.Reply("*A giant crystal ball of ice is charged and hurled toward your opponent, bursting open and freezing them solid on contact.*");
|
||||
public Task Starstorm(Context ctx) => ctx.Reply("*Vibrant colours burst forth from the sky as meteors rain down upon your opponent.*");
|
||||
public Task Flash(Context ctx) => ctx.Reply("*A ball of green light appears above your head and flies towards your enemy, exploding on contact.*");
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class HelpCommands
|
||||
public class Help
|
||||
{
|
||||
public async Task HelpRoot(Context ctx)
|
||||
{
|
@ -12,10 +12,10 @@ using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class ImportExportCommands
|
||||
public class ImportExport
|
||||
{
|
||||
private DataFileService _dataFiles;
|
||||
public ImportExportCommands(DataFileService dataFiles)
|
||||
public ImportExport(DataFileService dataFiles)
|
||||
{
|
||||
_dataFiles = dataFiles;
|
||||
}
|
82
PluralKit.Bot/Commands/Member.cs
Normal file
82
PluralKit.Bot/Commands/Member.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class Member
|
||||
{
|
||||
private IDataStore _data;
|
||||
private EmbedService _embeds;
|
||||
|
||||
private ProxyCacheService _proxyCache;
|
||||
|
||||
public Member(IDataStore data, EmbedService embeds, ProxyCacheService proxyCache)
|
||||
{
|
||||
_data = data;
|
||||
_embeds = embeds;
|
||||
_proxyCache = proxyCache;
|
||||
}
|
||||
|
||||
public async Task NewMember(Context ctx) {
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
var memberName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a member name.");
|
||||
|
||||
// Hard name length cap
|
||||
if (memberName.Length > Limits.MaxMemberNameLength) throw Errors.MemberNameTooLongError(memberName.Length);
|
||||
|
||||
// Warn if there's already a member by this name
|
||||
var existingMember = await _data.GetMemberByName(ctx.System, memberName);
|
||||
if (existingMember != null) {
|
||||
var msg = await ctx.Reply($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.SanitizeMentions()}\" (with ID `{existingMember.Hid}`). Do you want to create another member with the same name?");
|
||||
if (!await ctx.PromptYesNo(msg)) throw new PKError("Member creation cancelled.");
|
||||
}
|
||||
|
||||
// Enforce per-system member limit
|
||||
var memberCount = await _data.GetSystemMemberCount(ctx.System, true);
|
||||
if (memberCount >= Limits.MaxMemberCount)
|
||||
throw Errors.MemberLimitReachedError;
|
||||
|
||||
// Create the member
|
||||
var member = await _data.CreateMember(ctx.System, memberName);
|
||||
memberCount++;
|
||||
|
||||
// Send confirmation and space hint
|
||||
await ctx.Reply($"{Emojis.Success} Member \"{memberName.SanitizeMentions()}\" (`{member.Hid}`) registered! See the user guide for commands for editing this member: https://pluralkit.me/guide#member-management");
|
||||
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 5-character ID (which is `{member.Hid}`).");
|
||||
if (memberCount >= Limits.MaxMemberCount)
|
||||
await ctx.Reply($"{Emojis.Warn} You have reached the per-system member limit ({Limits.MaxMemberCount}). You will be unable to create additional members until existing members are deleted.");
|
||||
else if (memberCount >= Limits.MaxMembersWarnThreshold)
|
||||
await ctx.Reply($"{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {Limits.MaxMemberCount} members). Please review your member list for unused or duplicate members.");
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberRandom(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
|
||||
var randGen = new global::System.Random();
|
||||
//Maybe move this somewhere else in the file structure since it doesn't need to get created at every command
|
||||
|
||||
// TODO: don't buffer these, find something else to do ig
|
||||
var members = await _data.GetSystemMembers(ctx.System).Where(m => m.MemberPrivacy == PrivacyLevel.Public).ToListAsync();
|
||||
if (members == null || !members.Any())
|
||||
throw Errors.NoMembersError;
|
||||
var randInt = randGen.Next(members.Count);
|
||||
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(ctx.System, members[randInt], ctx.Guild, ctx.LookupContextFor(ctx.System)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task ViewMember(Context ctx, PKMember target)
|
||||
{
|
||||
var system = await _data.GetSystemById(target.System);
|
||||
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild, ctx.LookupContextFor(system)));
|
||||
}
|
||||
}
|
||||
}
|
87
PluralKit.Bot/Commands/MemberAvatar.cs
Normal file
87
PluralKit.Bot/Commands/MemberAvatar.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class MemberAvatar
|
||||
{
|
||||
private IDataStore _data;
|
||||
private ProxyCacheService _proxyCache;
|
||||
|
||||
public MemberAvatar(IDataStore data, ProxyCacheService proxyCache)
|
||||
{
|
||||
_data = data;
|
||||
_proxyCache = proxyCache;
|
||||
}
|
||||
|
||||
public async Task Avatar(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.RemainderOrNull() == null && ctx.Message.Attachments.Count == 0)
|
||||
{
|
||||
if ((target.AvatarUrl?.Trim() ?? "").Length > 0)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.WithTitle($"{target.Name.SanitizeMentions()}'s avatar")
|
||||
.WithImageUrl(target.AvatarUrl);
|
||||
if (target.System == ctx.System?.Id)
|
||||
eb.WithDescription($"To clear, use `pk;member {target.Hid} avatar clear`.");
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target.System == ctx.System?.Id)
|
||||
throw new PKSyntaxError($"This member does not have an avatar set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
|
||||
throw new PKError($"This member does not have an avatar set.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
if (ctx.Match("clear", "remove"))
|
||||
{
|
||||
target.AvatarUrl = null;
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Member avatar cleared.");
|
||||
}
|
||||
else if (await ctx.MatchUser() is IUser user)
|
||||
{
|
||||
if (user.AvatarId == null) throw Errors.UserHasNoAvatar;
|
||||
target.AvatarUrl = user.GetAvatarUrl(ImageFormat.Png, size: 256);
|
||||
|
||||
await _data.SaveMember(target);
|
||||
|
||||
var embed = new EmbedBuilder().WithImageUrl(target.AvatarUrl).Build();
|
||||
await ctx.Reply(
|
||||
$"{Emojis.Success} Member avatar changed to {user.Username}'s avatar! {Emojis.Warn} Please note that if {user.Username} changes their avatar, the webhook's avatar will need to be re-set.", embed: embed);
|
||||
|
||||
}
|
||||
else if (ctx.RemainderOrNull() is string url)
|
||||
{
|
||||
await Utils.VerifyAvatarOrThrow(url);
|
||||
target.AvatarUrl = url;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
var embed = new EmbedBuilder().WithImageUrl(url).Build();
|
||||
await ctx.Reply($"{Emojis.Success} Member avatar changed.", embed: embed);
|
||||
}
|
||||
else if (ctx.Message.Attachments.FirstOrDefault() is Attachment attachment)
|
||||
{
|
||||
await Utils.VerifyAvatarOrThrow(attachment.Url);
|
||||
target.AvatarUrl = attachment.Url;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member avatar changed to attached image. Please note that if you delete the message containing the attachment, the avatar will stop working.");
|
||||
}
|
||||
// No-arguments no-attachment case covered by conditional at the very top
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,461 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using NodaTime;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class MemberCommands
|
||||
{
|
||||
private IDataStore _data;
|
||||
private EmbedService _embeds;
|
||||
|
||||
private ProxyCacheService _proxyCache;
|
||||
|
||||
public MemberCommands(IDataStore data, EmbedService embeds, ProxyCacheService proxyCache)
|
||||
{
|
||||
_data = data;
|
||||
_embeds = embeds;
|
||||
_proxyCache = proxyCache;
|
||||
}
|
||||
|
||||
public async Task NewMember(Context ctx) {
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
var memberName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a member name.");
|
||||
|
||||
// Hard name length cap
|
||||
if (memberName.Length > Limits.MaxMemberNameLength) throw Errors.MemberNameTooLongError(memberName.Length);
|
||||
|
||||
// Warn if there's already a member by this name
|
||||
var existingMember = await _data.GetMemberByName(ctx.System, memberName);
|
||||
if (existingMember != null) {
|
||||
var msg = await ctx.Reply($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.SanitizeMentions()}\" (with ID `{existingMember.Hid}`). Do you want to create another member with the same name?");
|
||||
if (!await ctx.PromptYesNo(msg)) throw new PKError("Member creation cancelled.");
|
||||
}
|
||||
|
||||
// Enforce per-system member limit
|
||||
var memberCount = await _data.GetSystemMemberCount(ctx.System, true);
|
||||
if (memberCount >= Limits.MaxMemberCount)
|
||||
throw Errors.MemberLimitReachedError;
|
||||
|
||||
// Create the member
|
||||
var member = await _data.CreateMember(ctx.System, memberName);
|
||||
memberCount++;
|
||||
|
||||
// Send confirmation and space hint
|
||||
await ctx.Reply($"{Emojis.Success} Member \"{memberName.SanitizeMentions()}\" (`{member.Hid}`) registered! See the user guide for commands for editing this member: https://pluralkit.me/guide#member-management");
|
||||
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 5-character ID (which is `{member.Hid}`).");
|
||||
if (memberCount >= Limits.MaxMemberCount)
|
||||
await ctx.Reply($"{Emojis.Warn} You have reached the per-system member limit ({Limits.MaxMemberCount}). You will be unable to create additional members until existing members are deleted.");
|
||||
else if (memberCount >= Limits.MaxMembersWarnThreshold)
|
||||
await ctx.Reply($"{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {Limits.MaxMemberCount} members). Please review your member list for unused or duplicate members.");
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberRandom(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
|
||||
var randGen = new System.Random();
|
||||
//Maybe move this somewhere else in the file structure since it doesn't need to get created at every command
|
||||
|
||||
// TODO: don't buffer these, find something else to do ig
|
||||
var members = await _data.GetSystemMembers(ctx.System).Where(m => m.MemberPrivacy == PrivacyLevel.Public).ToListAsync();
|
||||
if (members == null || !members.Any())
|
||||
throw Errors.NoMembersError;
|
||||
var randInt = randGen.Next(members.Count);
|
||||
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(ctx.System, members[randInt], ctx.Guild, ctx.LookupContextFor(ctx.System)));
|
||||
|
||||
}
|
||||
|
||||
public async Task RenameMember(Context ctx, PKMember target) {
|
||||
// TODO: this method is pretty much a 1:1 copy/paste of the above creation method, find a way to clean?
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var newName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a new name for the member.");
|
||||
|
||||
// Hard name length cap
|
||||
if (newName.Length > Limits.MaxMemberNameLength) throw Errors.MemberNameTooLongError(newName.Length);
|
||||
|
||||
// Warn if there's already a member by this name
|
||||
var existingMember = await _data.GetMemberByName(ctx.System, newName);
|
||||
if (existingMember != null) {
|
||||
var msg = await ctx.Reply($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.SanitizeMentions()}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?");
|
||||
if (!await ctx.PromptYesNo(msg)) throw new PKError("Member renaming cancelled.");
|
||||
}
|
||||
|
||||
// Rename the member
|
||||
target.Name = newName;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member renamed.");
|
||||
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.");
|
||||
if (target.DisplayName != null) await ctx.Reply($"{Emojis.Note} Note that this member has a display name set ({target.DisplayName.SanitizeMentions()}), and will be proxied using that name instead.");
|
||||
|
||||
if (ctx.Guild != null)
|
||||
{
|
||||
var memberGuildConfig = await _data.GetMemberGuildSettings(target, ctx.Guild.Id);
|
||||
if (memberGuildConfig.DisplayName != null)
|
||||
await ctx.Reply($"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName.SanitizeMentions()}) in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name here.");
|
||||
}
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberDescription(Context ctx, PKMember target) {
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var description = ctx.RemainderOrNull();
|
||||
if (description.IsLongerThan(Limits.MaxDescriptionLength)) throw Errors.DescriptionTooLongError(description.Length);
|
||||
|
||||
target.Description = description;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member description {(description == null ? "cleared" : "changed")}.");
|
||||
}
|
||||
|
||||
public async Task MemberPronouns(Context ctx, PKMember target) {
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var pronouns = ctx.RemainderOrNull();
|
||||
if (pronouns.IsLongerThan(Limits.MaxPronounsLength)) throw Errors.MemberPronounsTooLongError(pronouns.Length);
|
||||
|
||||
target.Pronouns = pronouns;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member pronouns {(pronouns == null ? "cleared" : "changed")}.");
|
||||
}
|
||||
|
||||
public async Task MemberColor(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var color = ctx.RemainderOrNull();
|
||||
if (color != null)
|
||||
{
|
||||
if (color.StartsWith("#")) color = color.Substring(1);
|
||||
if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color);
|
||||
}
|
||||
|
||||
target.Color = color;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member color {(color == null ? "cleared" : "changed")}.");
|
||||
}
|
||||
|
||||
public async Task MemberBirthday(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
LocalDate? date = null;
|
||||
var birthday = ctx.RemainderOrNull();
|
||||
if (birthday != null)
|
||||
{
|
||||
date = PluralKit.Utils.ParseDate(birthday, true);
|
||||
if (date == null) throw Errors.BirthdayParseError(birthday);
|
||||
}
|
||||
|
||||
target.Birthday = date;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member birthdate {(date == null ? "cleared" : $"changed to {target.BirthdayString}")}.");
|
||||
}
|
||||
|
||||
public async Task MemberProxy(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
ProxyTag ParseProxyTags(string exampleProxy)
|
||||
{
|
||||
// // Make sure there's one and only one instance of "text" in the example proxy given
|
||||
var prefixAndSuffix = exampleProxy.Split("text");
|
||||
if (prefixAndSuffix.Length < 2) throw Errors.ProxyMustHaveText;
|
||||
if (prefixAndSuffix.Length > 2) throw Errors.ProxyMultipleText;
|
||||
return new ProxyTag(prefixAndSuffix[0], prefixAndSuffix[1]);
|
||||
}
|
||||
|
||||
async Task<bool> WarnOnConflict(ProxyTag newTag)
|
||||
{
|
||||
var conflicts = (await _data.GetConflictingProxies(ctx.System, newTag))
|
||||
.Where(m => m.Id != target.Id)
|
||||
.ToList();
|
||||
|
||||
if (conflicts.Count <= 0) return true;
|
||||
|
||||
var conflictList = conflicts.Select(m => $"- **{m.Name}**");
|
||||
var msg = await ctx.Reply(
|
||||
$"{Emojis.Warn} The following members have conflicting proxy tags:\n{string.Join('\n', conflictList)}\nDo you want to proceed anyway?");
|
||||
return await ctx.PromptYesNo(msg);
|
||||
}
|
||||
|
||||
// "Sub"command: no arguments clearing
|
||||
// Also matches the pseudo-subcommand "text" which is equivalent to emoty proxy tags on both sides.
|
||||
if (!ctx.HasNext() || ctx.Match("text"))
|
||||
{
|
||||
// If we already have multiple tags, this would clear everything, so prompt that
|
||||
if (target.ProxyTags.Count > 1)
|
||||
{
|
||||
var msg = await ctx.Reply(
|
||||
$"{Emojis.Warn} You already have multiple proxy tags set: {target.ProxyTagsString()}\nDo you want to clear them all?");
|
||||
if (!await ctx.PromptYesNo(msg))
|
||||
throw Errors.GenericCancelled();
|
||||
}
|
||||
|
||||
target.ProxyTags = new ProxyTag[] { };
|
||||
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Proxy tags cleared.");
|
||||
}
|
||||
// Subcommand: "add"
|
||||
else if (ctx.Match("add"))
|
||||
{
|
||||
if (!ctx.HasNext()) throw new PKSyntaxError("You must pass an example proxy to add (eg. `[text]` or `J:text`).");
|
||||
|
||||
var tagToAdd = ParseProxyTags(ctx.RemainderOrNull());
|
||||
if (target.ProxyTags.Contains(tagToAdd))
|
||||
throw Errors.ProxyTagAlreadyExists(tagToAdd, target);
|
||||
|
||||
if (!await WarnOnConflict(tagToAdd))
|
||||
throw Errors.GenericCancelled();
|
||||
|
||||
// It's not guaranteed the list's mutable, so we force it to be
|
||||
target.ProxyTags = target.ProxyTags.ToList();
|
||||
target.ProxyTags.Add(tagToAdd);
|
||||
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Added proxy tags `{tagToAdd.ProxyString.SanitizeMentions()}`.");
|
||||
}
|
||||
// Subcommand: "remove"
|
||||
else if (ctx.Match("remove"))
|
||||
{
|
||||
if (!ctx.HasNext()) throw new PKSyntaxError("You must pass a proxy tag to remove (eg. `[text]` or `J:text`).");
|
||||
|
||||
var tagToRemove = ParseProxyTags(ctx.RemainderOrNull());
|
||||
if (!target.ProxyTags.Contains(tagToRemove))
|
||||
throw Errors.ProxyTagDoesNotExist(tagToRemove, target);
|
||||
|
||||
// It's not guaranteed the list's mutable, so we force it to be
|
||||
target.ProxyTags = target.ProxyTags.ToList();
|
||||
target.ProxyTags.Remove(tagToRemove);
|
||||
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Removed proxy tags `{tagToRemove.ProxyString.SanitizeMentions()}`.");
|
||||
}
|
||||
// Subcommand: bare proxy tag given
|
||||
else
|
||||
{
|
||||
if (!ctx.HasNext()) throw new PKSyntaxError("You must pass an example proxy to set (eg. `[text]` or `J:text`).");
|
||||
|
||||
var requestedTag = ParseProxyTags(ctx.RemainderOrNull());
|
||||
|
||||
// This is mostly a legacy command, so it's gonna error out if there's
|
||||
// already more than one proxy tag.
|
||||
if (target.ProxyTags.Count > 1)
|
||||
throw Errors.LegacyAlreadyHasProxyTag(requestedTag, target);
|
||||
|
||||
if (!await WarnOnConflict(requestedTag))
|
||||
throw Errors.GenericCancelled();
|
||||
|
||||
target.ProxyTags = new[] {requestedTag};
|
||||
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Member proxy tags set to `{requestedTag.ProxyString.SanitizeMentions()}`.");
|
||||
}
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberDelete(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete \"{target.Name.SanitizeMentions()}\"? If so, reply to this message with the member's ID (`{target.Hid}`). __***This cannot be undone!***__");
|
||||
if (!await ctx.ConfirmWithReply(target.Hid)) throw Errors.MemberDeleteCancelled;
|
||||
await _data.DeleteMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Member deleted.");
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberAvatar(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.RemainderOrNull() == null && ctx.Message.Attachments.Count == 0)
|
||||
{
|
||||
if ((target.AvatarUrl?.Trim() ?? "").Length > 0)
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.WithTitle($"{target.Name.SanitizeMentions()}'s avatar")
|
||||
.WithImageUrl(target.AvatarUrl);
|
||||
if (target.System == ctx.System?.Id)
|
||||
eb.WithDescription($"To clear, use `pk;member {target.Hid} avatar clear`.");
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target.System == ctx.System?.Id)
|
||||
throw new PKSyntaxError($"This member does not have an avatar set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
|
||||
throw new PKError($"This member does not have an avatar set.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
if (ctx.Match("clear", "remove"))
|
||||
{
|
||||
target.AvatarUrl = null;
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Member avatar cleared.");
|
||||
}
|
||||
else if (await ctx.MatchUser() is IUser user)
|
||||
{
|
||||
if (user.AvatarId == null) throw Errors.UserHasNoAvatar;
|
||||
target.AvatarUrl = user.GetAvatarUrl(ImageFormat.Png, size: 256);
|
||||
|
||||
await _data.SaveMember(target);
|
||||
|
||||
var embed = new EmbedBuilder().WithImageUrl(target.AvatarUrl).Build();
|
||||
await ctx.Reply(
|
||||
$"{Emojis.Success} Member avatar changed to {user.Username}'s avatar! {Emojis.Warn} Please note that if {user.Username} changes their avatar, the webhook's avatar will need to be re-set.", embed: embed);
|
||||
|
||||
}
|
||||
else if (ctx.RemainderOrNull() is string url)
|
||||
{
|
||||
await Utils.VerifyAvatarOrThrow(url);
|
||||
target.AvatarUrl = url;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
var embed = new EmbedBuilder().WithImageUrl(url).Build();
|
||||
await ctx.Reply($"{Emojis.Success} Member avatar changed.", embed: embed);
|
||||
}
|
||||
else if (ctx.Message.Attachments.FirstOrDefault() is Attachment attachment)
|
||||
{
|
||||
await Utils.VerifyAvatarOrThrow(attachment.Url);
|
||||
target.AvatarUrl = attachment.Url;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member avatar changed to attached image. Please note that if you delete the message containing the attachment, the avatar will stop working.");
|
||||
}
|
||||
// No-arguments no-attachment case covered by conditional at the very top
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberDisplayName(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var newDisplayName = ctx.RemainderOrNull();
|
||||
|
||||
target.DisplayName = newDisplayName;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
var successStr = $"{Emojis.Success} ";
|
||||
if (newDisplayName != null)
|
||||
successStr += $"Member display name changed. This member will now be proxied using the name \"{newDisplayName.SanitizeMentions()}\".";
|
||||
else
|
||||
successStr += $"Member display name cleared. This member will now be proxied using their member name \"{target.Name.SanitizeMentions()}\".";
|
||||
|
||||
if (ctx.Guild != null)
|
||||
{
|
||||
var memberGuildConfig = await _data.GetMemberGuildSettings(target, ctx.Guild.Id);
|
||||
if (memberGuildConfig.DisplayName != null)
|
||||
successStr += $" However, this member has a server name set in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name, \"{memberGuildConfig.DisplayName.SanitizeMentions()}\", here.";
|
||||
}
|
||||
|
||||
await ctx.Reply(successStr);
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberServerName(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
// TODO: allow setting server names for different servers/in DMs by ID
|
||||
ctx.CheckGuildContext();
|
||||
|
||||
var newServerName = ctx.RemainderOrNull();
|
||||
|
||||
var guildSettings = await _data.GetMemberGuildSettings(target, ctx.Guild.Id);
|
||||
guildSettings.DisplayName = newServerName;
|
||||
await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildSettings);
|
||||
|
||||
var successStr = $"{Emojis.Success} ";
|
||||
if (newServerName != null)
|
||||
successStr += $"Member server name changed. This member will now be proxied using the name \"{newServerName.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()}).";
|
||||
else if (target.DisplayName != null)
|
||||
successStr += $"Member server name cleared. This member will now be proxied using their global display name \"{target.DisplayName.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()}).";
|
||||
else
|
||||
successStr += $"Member server name cleared. This member will now be proxied using their member name \"{target.Name.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()}).";
|
||||
|
||||
await ctx.Reply(successStr);
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberKeepProxy(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
bool newValue;
|
||||
if (ctx.Match("on", "enabled", "true", "yes")) newValue = true;
|
||||
else if (ctx.Match("off", "disabled", "false", "no")) newValue = false;
|
||||
else if (ctx.HasNext()) throw new PKSyntaxError("You must pass either \"on\" or \"off\".");
|
||||
else newValue = !target.KeepProxy;
|
||||
|
||||
target.KeepProxy = newValue;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
if (newValue)
|
||||
await ctx.Reply($"{Emojis.Success} Member proxy tags will now be included in the resulting message when proxying.");
|
||||
else
|
||||
await ctx.Reply($"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying.");
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberPrivacy(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
bool newValue;
|
||||
if (ctx.Match("private", "hide", "hidden", "on", "enable", "yes")) newValue = true;
|
||||
else if (ctx.Match("public", "show", "shown", "displayed", "off", "disable", "no")) newValue = false;
|
||||
else if (ctx.HasNext()) throw new PKSyntaxError("You must pass either \"private\" or \"public\".");
|
||||
else newValue = target.MemberPrivacy != PrivacyLevel.Private;
|
||||
|
||||
target.MemberPrivacy = newValue ? PrivacyLevel.Private : PrivacyLevel.Public;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
if (newValue)
|
||||
await ctx.Reply($"{Emojis.Success} Member privacy set to **private**. This member will no longer show up in member lists and will return limited information when queried by other accounts.");
|
||||
else
|
||||
await ctx.Reply($"{Emojis.Success} Member privacy set to **public**. This member will now show up in member lists and will return all information when queried by other accounts.");
|
||||
}
|
||||
|
||||
public async Task ViewMember(Context ctx, PKMember target)
|
||||
{
|
||||
var system = await _data.GetSystemById(target.System);
|
||||
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild, ctx.LookupContextFor(system)));
|
||||
}
|
||||
}
|
||||
}
|
229
PluralKit.Bot/Commands/MemberEdit.cs
Normal file
229
PluralKit.Bot/Commands/MemberEdit.cs
Normal file
@ -0,0 +1,229 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class MemberEdit
|
||||
{
|
||||
private IDataStore _data;
|
||||
private ProxyCacheService _proxyCache;
|
||||
|
||||
public MemberEdit(IDataStore data, ProxyCacheService proxyCache)
|
||||
{
|
||||
_data = data;
|
||||
_proxyCache = proxyCache;
|
||||
}
|
||||
|
||||
public async Task Name(Context ctx, PKMember target) {
|
||||
// TODO: this method is pretty much a 1:1 copy/paste of the above creation method, find a way to clean?
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var newName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a new name for the member.");
|
||||
|
||||
// Hard name length cap
|
||||
if (newName.Length > Limits.MaxMemberNameLength) throw Errors.MemberNameTooLongError(newName.Length);
|
||||
|
||||
// Warn if there's already a member by this name
|
||||
var existingMember = await _data.GetMemberByName(ctx.System, newName);
|
||||
if (existingMember != null) {
|
||||
var msg = await ctx.Reply($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.SanitizeMentions()}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?");
|
||||
if (!await ctx.PromptYesNo(msg)) throw new PKError("Member renaming cancelled.");
|
||||
}
|
||||
|
||||
// Rename the member
|
||||
target.Name = newName;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member renamed.");
|
||||
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.");
|
||||
if (target.DisplayName != null) await ctx.Reply($"{Emojis.Note} Note that this member has a display name set ({target.DisplayName.SanitizeMentions()}), and will be proxied using that name instead.");
|
||||
|
||||
if (ctx.Guild != null)
|
||||
{
|
||||
var memberGuildConfig = await _data.GetMemberGuildSettings(target, ctx.Guild.Id);
|
||||
if (memberGuildConfig.DisplayName != null)
|
||||
await ctx.Reply($"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName.SanitizeMentions()}) in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name here.");
|
||||
}
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task Description(Context ctx, PKMember target) {
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var description = ctx.RemainderOrNull();
|
||||
if (description.IsLongerThan(Limits.MaxDescriptionLength)) throw Errors.DescriptionTooLongError(description.Length);
|
||||
|
||||
target.Description = description;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member description {(description == null ? "cleared" : "changed")}.");
|
||||
}
|
||||
|
||||
public async Task Pronouns(Context ctx, PKMember target) {
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var pronouns = ctx.RemainderOrNull();
|
||||
if (pronouns.IsLongerThan(Limits.MaxPronounsLength)) throw Errors.MemberPronounsTooLongError(pronouns.Length);
|
||||
|
||||
target.Pronouns = pronouns;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member pronouns {(pronouns == null ? "cleared" : "changed")}.");
|
||||
}
|
||||
|
||||
public async Task Color(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var color = ctx.RemainderOrNull();
|
||||
if (color != null)
|
||||
{
|
||||
if (color.StartsWith("#")) color = color.Substring(1);
|
||||
if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color);
|
||||
}
|
||||
|
||||
target.Color = color;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member color {(color == null ? "cleared" : "changed")}.");
|
||||
}
|
||||
|
||||
public async Task Birthday(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
LocalDate? date = null;
|
||||
var birthday = ctx.RemainderOrNull();
|
||||
if (birthday != null)
|
||||
{
|
||||
date = PluralKit.Utils.ParseDate(birthday, true);
|
||||
if (date == null) throw Errors.BirthdayParseError(birthday);
|
||||
}
|
||||
|
||||
target.Birthday = date;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member birthdate {(date == null ? "cleared" : $"changed to {target.BirthdayString}")}.");
|
||||
}
|
||||
|
||||
public async Task DisplayName(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
var newDisplayName = ctx.RemainderOrNull();
|
||||
|
||||
target.DisplayName = newDisplayName;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
var successStr = $"{Emojis.Success} ";
|
||||
if (newDisplayName != null)
|
||||
successStr += $"Member display name changed. This member will now be proxied using the name \"{newDisplayName.SanitizeMentions()}\".";
|
||||
else
|
||||
successStr += $"Member display name cleared. This member will now be proxied using their member name \"{target.Name.SanitizeMentions()}\".";
|
||||
|
||||
if (ctx.Guild != null)
|
||||
{
|
||||
var memberGuildConfig = await _data.GetMemberGuildSettings(target, ctx.Guild.Id);
|
||||
if (memberGuildConfig.DisplayName != null)
|
||||
successStr += $" However, this member has a server name set in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name, \"{memberGuildConfig.DisplayName.SanitizeMentions()}\", here.";
|
||||
}
|
||||
|
||||
await ctx.Reply(successStr);
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task ServerName(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
// TODO: allow setting server names for different servers/in DMs by ID
|
||||
ctx.CheckGuildContext();
|
||||
|
||||
var newServerName = ctx.RemainderOrNull();
|
||||
|
||||
var guildSettings = await _data.GetMemberGuildSettings(target, ctx.Guild.Id);
|
||||
guildSettings.DisplayName = newServerName;
|
||||
await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildSettings);
|
||||
|
||||
var successStr = $"{Emojis.Success} ";
|
||||
if (newServerName != null)
|
||||
successStr += $"Member server name changed. This member will now be proxied using the name \"{newServerName.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()}).";
|
||||
else if (target.DisplayName != null)
|
||||
successStr += $"Member server name cleared. This member will now be proxied using their global display name \"{target.DisplayName.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()}).";
|
||||
else
|
||||
successStr += $"Member server name cleared. This member will now be proxied using their member name \"{target.Name.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()}).";
|
||||
|
||||
await ctx.Reply(successStr);
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task KeepProxy(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
bool newValue;
|
||||
if (ctx.Match("on", "enabled", "true", "yes")) newValue = true;
|
||||
else if (ctx.Match("off", "disabled", "false", "no")) newValue = false;
|
||||
else if (ctx.HasNext()) throw new PKSyntaxError("You must pass either \"on\" or \"off\".");
|
||||
else newValue = !target.KeepProxy;
|
||||
|
||||
target.KeepProxy = newValue;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
if (newValue)
|
||||
await ctx.Reply($"{Emojis.Success} Member proxy tags will now be included in the resulting message when proxying.");
|
||||
else
|
||||
await ctx.Reply($"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying.");
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task Privacy(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
bool newValue;
|
||||
if (ctx.Match("private", "hide", "hidden", "on", "enable", "yes")) newValue = true;
|
||||
else if (ctx.Match("public", "show", "shown", "displayed", "off", "disable", "no")) newValue = false;
|
||||
else if (ctx.HasNext()) throw new PKSyntaxError("You must pass either \"private\" or \"public\".");
|
||||
else newValue = target.MemberPrivacy != PrivacyLevel.Private;
|
||||
|
||||
target.MemberPrivacy = newValue ? PrivacyLevel.Private : PrivacyLevel.Public;
|
||||
await _data.SaveMember(target);
|
||||
|
||||
if (newValue)
|
||||
await ctx.Reply($"{Emojis.Success} Member privacy set to **private**. This member will no longer show up in member lists and will return limited information when queried by other accounts.");
|
||||
else
|
||||
await ctx.Reply($"{Emojis.Success} Member privacy set to **public**. This member will now show up in member lists and will return all information when queried by other accounts.");
|
||||
}
|
||||
|
||||
public async Task Delete(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete \"{target.Name.SanitizeMentions()}\"? If so, reply to this message with the member's ID (`{target.Hid}`). __***This cannot be undone!***__");
|
||||
if (!await ctx.ConfirmWithReply(target.Hid)) throw Errors.MemberDeleteCancelled;
|
||||
await _data.DeleteMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Member deleted.");
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
}
|
||||
}
|
125
PluralKit.Bot/Commands/MemberProxy.cs
Normal file
125
PluralKit.Bot/Commands/MemberProxy.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class MemberProxy
|
||||
{
|
||||
private IDataStore _data;
|
||||
private ProxyCacheService _proxyCache;
|
||||
|
||||
public MemberProxy(IDataStore data, ProxyCacheService proxyCache)
|
||||
{
|
||||
_data = data;
|
||||
_proxyCache = proxyCache;
|
||||
}
|
||||
|
||||
public async Task Proxy(Context ctx, PKMember target)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
|
||||
|
||||
ProxyTag ParseProxyTags(string exampleProxy)
|
||||
{
|
||||
// // Make sure there's one and only one instance of "text" in the example proxy given
|
||||
var prefixAndSuffix = exampleProxy.Split("text");
|
||||
if (prefixAndSuffix.Length < 2) throw Errors.ProxyMustHaveText;
|
||||
if (prefixAndSuffix.Length > 2) throw Errors.ProxyMultipleText;
|
||||
return new ProxyTag(prefixAndSuffix[0], prefixAndSuffix[1]);
|
||||
}
|
||||
|
||||
async Task<bool> WarnOnConflict(ProxyTag newTag)
|
||||
{
|
||||
var conflicts = (await _data.GetConflictingProxies(ctx.System, newTag))
|
||||
.Where(m => m.Id != target.Id)
|
||||
.ToList();
|
||||
|
||||
if (conflicts.Count <= 0) return true;
|
||||
|
||||
var conflictList = conflicts.Select(m => $"- **{m.Name}**");
|
||||
var msg = await ctx.Reply(
|
||||
$"{Emojis.Warn} The following members have conflicting proxy tags:\n{string.Join('\n', conflictList)}\nDo you want to proceed anyway?");
|
||||
return await ctx.PromptYesNo(msg);
|
||||
}
|
||||
|
||||
// "Sub"command: no arguments clearing
|
||||
// Also matches the pseudo-subcommand "text" which is equivalent to emoty proxy tags on both sides.
|
||||
if (!ctx.HasNext() || ctx.Match("text"))
|
||||
{
|
||||
// If we already have multiple tags, this would clear everything, so prompt that
|
||||
if (target.ProxyTags.Count > 1)
|
||||
{
|
||||
var msg = await ctx.Reply(
|
||||
$"{Emojis.Warn} You already have multiple proxy tags set: {target.ProxyTagsString()}\nDo you want to clear them all?");
|
||||
if (!await ctx.PromptYesNo(msg))
|
||||
throw Errors.GenericCancelled();
|
||||
}
|
||||
|
||||
target.ProxyTags = new ProxyTag[] { };
|
||||
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Proxy tags cleared.");
|
||||
}
|
||||
// Subcommand: "add"
|
||||
else if (ctx.Match("add"))
|
||||
{
|
||||
if (!ctx.HasNext()) throw new PKSyntaxError("You must pass an example proxy to add (eg. `[text]` or `J:text`).");
|
||||
|
||||
var tagToAdd = ParseProxyTags(ctx.RemainderOrNull());
|
||||
if (target.ProxyTags.Contains(tagToAdd))
|
||||
throw Errors.ProxyTagAlreadyExists(tagToAdd, target);
|
||||
|
||||
if (!await WarnOnConflict(tagToAdd))
|
||||
throw Errors.GenericCancelled();
|
||||
|
||||
// It's not guaranteed the list's mutable, so we force it to be
|
||||
target.ProxyTags = target.ProxyTags.ToList();
|
||||
target.ProxyTags.Add(tagToAdd);
|
||||
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Added proxy tags `{tagToAdd.ProxyString.SanitizeMentions()}`.");
|
||||
}
|
||||
// Subcommand: "remove"
|
||||
else if (ctx.Match("remove"))
|
||||
{
|
||||
if (!ctx.HasNext()) throw new PKSyntaxError("You must pass a proxy tag to remove (eg. `[text]` or `J:text`).");
|
||||
|
||||
var tagToRemove = ParseProxyTags(ctx.RemainderOrNull());
|
||||
if (!target.ProxyTags.Contains(tagToRemove))
|
||||
throw Errors.ProxyTagDoesNotExist(tagToRemove, target);
|
||||
|
||||
// It's not guaranteed the list's mutable, so we force it to be
|
||||
target.ProxyTags = target.ProxyTags.ToList();
|
||||
target.ProxyTags.Remove(tagToRemove);
|
||||
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Removed proxy tags `{tagToRemove.ProxyString.SanitizeMentions()}`.");
|
||||
}
|
||||
// Subcommand: bare proxy tag given
|
||||
else
|
||||
{
|
||||
if (!ctx.HasNext()) throw new PKSyntaxError("You must pass an example proxy to set (eg. `[text]` or `J:text`).");
|
||||
|
||||
var requestedTag = ParseProxyTags(ctx.RemainderOrNull());
|
||||
|
||||
// This is mostly a legacy command, so it's gonna error out if there's
|
||||
// already more than one proxy tag.
|
||||
if (target.ProxyTags.Count > 1)
|
||||
throw Errors.LegacyAlreadyHasProxyTag(requestedTag, target);
|
||||
|
||||
if (!await WarnOnConflict(requestedTag))
|
||||
throw Errors.GenericCancelled();
|
||||
|
||||
target.ProxyTags = new[] {requestedTag};
|
||||
|
||||
await _data.SaveMember(target);
|
||||
await ctx.Reply($"{Emojis.Success} Member proxy tags set to `{requestedTag.ProxyString.SanitizeMentions()}`.");
|
||||
}
|
||||
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using App.Metrics;
|
||||
|
||||
@ -14,19 +15,23 @@ using PluralKit.Bot.CommandSystem;
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot.Commands {
|
||||
public class MiscCommands
|
||||
public class Misc
|
||||
{
|
||||
private BotConfig _botConfig;
|
||||
private IMetrics _metrics;
|
||||
private CpuStatService _cpu;
|
||||
private ShardInfoService _shards;
|
||||
private IDataStore _data;
|
||||
private EmbedService _embeds;
|
||||
|
||||
public MiscCommands(BotConfig botConfig, IMetrics metrics, CpuStatService cpu, ShardInfoService shards)
|
||||
public Misc(BotConfig botConfig, IMetrics metrics, CpuStatService cpu, ShardInfoService shards, IDataStore data, EmbedService embeds)
|
||||
{
|
||||
_botConfig = botConfig;
|
||||
_metrics = metrics;
|
||||
_cpu = cpu;
|
||||
_shards = shards;
|
||||
_data = data;
|
||||
_embeds = embeds;
|
||||
}
|
||||
|
||||
public async Task Invite(Context ctx)
|
||||
@ -46,13 +51,6 @@ namespace PluralKit.Bot.Commands {
|
||||
await ctx.Reply($"{Emojis.Success} Use this link to add PluralKit to your server:\n<{invite}>");
|
||||
}
|
||||
|
||||
public Task Mn(Context ctx) => ctx.Reply("Gotta catch 'em all!");
|
||||
public Task Fire(Context ctx) => ctx.Reply("*A giant lightning bolt promptly erupts into a pillar of fire as it hits your opponent.*");
|
||||
public Task Thunder(Context ctx) => ctx.Reply("*A giant ball of lightning is conjured and fired directly at your opponent, vanquishing them.*");
|
||||
public Task Freeze(Context ctx) => ctx.Reply("*A giant crystal ball of ice is charged and hurled toward your opponent, bursting open and freezing them solid on contact.*");
|
||||
public Task Starstorm(Context ctx) => ctx.Reply("*Vibrant colours burst forth from the sky as meteors rain down upon your opponent.*");
|
||||
public Task Flash(Context ctx) => ctx.Reply("*A ball of green light appears above your head and flies towards your enemy, exploding on contact.*");
|
||||
|
||||
public async Task Stats(Context ctx)
|
||||
{
|
||||
var msg = await ctx.Reply($"...");
|
||||
@ -176,5 +174,22 @@ namespace PluralKit.Bot.Commands {
|
||||
// Send! :)
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
|
||||
public async Task GetMessage(Context ctx)
|
||||
{
|
||||
var word = ctx.PopArgument() ?? throw new PKSyntaxError("You must pass a message ID or link.");
|
||||
|
||||
ulong messageId;
|
||||
if (ulong.TryParse(word, out var id))
|
||||
messageId = id;
|
||||
else if (Regex.Match(word, "https://discordapp.com/channels/\\d+/(\\d+)") is Match match && match.Success)
|
||||
messageId = ulong.Parse(match.Groups[1].Value);
|
||||
else throw new PKSyntaxError($"Could not parse `{word}` as a message ID or link.");
|
||||
|
||||
var message = await _data.GetMessage(messageId);
|
||||
if (message == null) throw Errors.MessageNotFound(messageId);
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
@ -8,18 +7,12 @@ using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class ModCommands
|
||||
public class ServerConfig
|
||||
{
|
||||
private LogChannelService _logChannels;
|
||||
private IDataStore _data;
|
||||
|
||||
private EmbedService _embeds;
|
||||
|
||||
public ModCommands(LogChannelService logChannels, IDataStore data, EmbedService embeds)
|
||||
public ServerConfig(IDataStore data)
|
||||
{
|
||||
_logChannels = logChannels;
|
||||
_data = data;
|
||||
_embeds = embeds;
|
||||
}
|
||||
|
||||
public async Task SetLogChannel(Context ctx)
|
||||
@ -89,24 +82,6 @@ namespace PluralKit.Bot.Commands
|
||||
|
||||
await _data.SaveGuildConfig(guildCfg);
|
||||
await ctx.Reply($"{Emojis.Success} Channels {(onBlacklist ? "added to" : "removed from")} the proxy blacklist.");
|
||||
|
||||
}
|
||||
|
||||
public async Task GetMessage(Context ctx)
|
||||
{
|
||||
var word = ctx.PopArgument() ?? throw new PKSyntaxError("You must pass a message ID or link.");
|
||||
|
||||
ulong messageId;
|
||||
if (ulong.TryParse(word, out var id))
|
||||
messageId = id;
|
||||
else if (Regex.Match(word, "https://discordapp.com/channels/\\d+/(\\d+)") is Match match && match.Success)
|
||||
messageId = ulong.Parse(match.Groups[1].Value);
|
||||
else throw new PKSyntaxError($"Could not parse `{word}` as a message ID or link.");
|
||||
|
||||
var message = await _data.GetMessage(messageId);
|
||||
if (message == null) throw Errors.MessageNotFound(messageId);
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message));
|
||||
}
|
||||
}
|
||||
}
|
@ -10,16 +10,16 @@ using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class SwitchCommands
|
||||
public class Switch
|
||||
{
|
||||
private IDataStore _data;
|
||||
|
||||
public SwitchCommands(IDataStore data)
|
||||
public Switch(IDataStore data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public async Task Switch(Context ctx)
|
||||
public async Task SwitchDo(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
var members = new List<PKMember>();
|
41
PluralKit.Bot/Commands/System.cs
Normal file
41
PluralKit.Bot/Commands/System.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Humanizer;
|
||||
using NodaTime;
|
||||
using NodaTime.Text;
|
||||
using NodaTime.TimeZones;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class System
|
||||
{
|
||||
private IDataStore _data;
|
||||
private EmbedService _embeds;
|
||||
|
||||
public System(EmbedService embeds, IDataStore data)
|
||||
{
|
||||
_embeds = embeds;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public async Task Query(Context ctx, PKSystem system) {
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateSystemEmbed(system, ctx.LookupContextFor(system)));
|
||||
}
|
||||
|
||||
public async Task New(Context ctx)
|
||||
{
|
||||
ctx.CheckNoSystem();
|
||||
|
||||
var system = await _data.CreateSystem(ctx.RemainderOrNull());
|
||||
await _data.AddAccount(system, ctx.Author.Id);
|
||||
await ctx.Reply($"{Emojis.Success} Your system has been created. Type `pk;system` to view it, and type `pk;help` for more information about commands you can use now.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord;
|
||||
using Humanizer;
|
||||
|
||||
using NodaTime;
|
||||
using NodaTime.Text;
|
||||
using NodaTime.TimeZones;
|
||||
@ -12,35 +13,19 @@ using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class SystemCommands
|
||||
public class SystemEdit
|
||||
{
|
||||
private IDataStore _data;
|
||||
private EmbedService _embeds;
|
||||
|
||||
private ProxyCacheService _proxyCache;
|
||||
|
||||
public SystemCommands(EmbedService embeds, ProxyCacheService proxyCache, IDataStore data)
|
||||
public SystemEdit(IDataStore data, EmbedService embeds, ProxyCacheService proxyCache)
|
||||
{
|
||||
_data = data;
|
||||
_embeds = embeds;
|
||||
_proxyCache = proxyCache;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public async Task Query(Context ctx, PKSystem system) {
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateSystemEmbed(system, ctx.LookupContextFor(system)));
|
||||
}
|
||||
|
||||
public async Task New(Context ctx)
|
||||
{
|
||||
ctx.CheckNoSystem();
|
||||
|
||||
var system = await _data.CreateSystem(ctx.RemainderOrNull());
|
||||
await _data.AddAccount(system, ctx.Author.Id);
|
||||
await ctx.Reply($"{Emojis.Success} Your system has been created. Type `pk;system` to view it, and type `pk;help` for more information about commands you can use now.");
|
||||
}
|
||||
|
||||
public async Task Name(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
@ -81,7 +66,7 @@ namespace PluralKit.Bot.Commands
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task SystemAvatar(Context ctx)
|
||||
public async Task Avatar(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
|
||||
@ -147,198 +132,6 @@ namespace PluralKit.Bot.Commands
|
||||
await _proxyCache.InvalidateResultsForSystem(ctx.System);
|
||||
}
|
||||
|
||||
public async Task MemberShortList(Context ctx, PKSystem system) {
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.MemberListPrivacy);
|
||||
|
||||
var authCtx = ctx.LookupContextFor(system);
|
||||
var shouldShowPrivate = authCtx == LookupContext.ByOwner && ctx.Match("all", "everyone", "private");
|
||||
|
||||
var embedTitle = system.Name != null ? $"Members of {system.Name.SanitizeMentions()} (`{system.Hid}`)" : $"Members of `{system.Hid}`";
|
||||
|
||||
var memberCountPublic = _data.GetSystemMemberCount(system, false);
|
||||
var memberCountAll = _data.GetSystemMemberCount(system, true);
|
||||
await Task.WhenAll(memberCountPublic, memberCountAll);
|
||||
|
||||
var memberCountDisplayed = shouldShowPrivate ? memberCountAll.Result : memberCountPublic.Result;
|
||||
|
||||
var members = _data.GetSystemMembers(system)
|
||||
.Where(m => m.MemberPrivacy == PrivacyLevel.Public || shouldShowPrivate)
|
||||
.OrderBy(m => m.Name, StringComparer.InvariantCultureIgnoreCase);
|
||||
var anyMembersHidden = !shouldShowPrivate && memberCountPublic.Result != memberCountAll.Result;
|
||||
|
||||
await ctx.Paginate(
|
||||
members,
|
||||
memberCountDisplayed,
|
||||
25,
|
||||
embedTitle,
|
||||
(eb, ms) =>
|
||||
{
|
||||
eb.Description = string.Join("\n", ms.Select((m) =>
|
||||
{
|
||||
if (m.HasProxyTags)
|
||||
{
|
||||
var proxyTagsString = m.ProxyTagsString().SanitizeMentions();
|
||||
if (proxyTagsString.Length > 100) // arbitrary threshold for now, tweak?
|
||||
proxyTagsString = "tags too long, see member card";
|
||||
|
||||
return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}** *({proxyTagsString})*";
|
||||
}
|
||||
|
||||
return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}**";
|
||||
}));
|
||||
|
||||
var footer = $"{memberCountDisplayed} total.";
|
||||
if (anyMembersHidden && authCtx == LookupContext.ByOwner)
|
||||
footer += "Private members have been hidden. type \"pk;system list all\" to include them.";
|
||||
eb.WithFooter(footer);
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task MemberLongList(Context ctx, PKSystem system) {
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.MemberListPrivacy);
|
||||
|
||||
var authCtx = ctx.LookupContextFor(system);
|
||||
var shouldShowPrivate = authCtx == LookupContext.ByOwner && ctx.Match("all", "everyone", "private");
|
||||
|
||||
var embedTitle = system.Name != null ? $"Members of {system.Name} (`{system.Hid}`)" : $"Members of `{system.Hid}`";
|
||||
|
||||
var memberCountPublic = _data.GetSystemMemberCount(system, false);
|
||||
var memberCountAll = _data.GetSystemMemberCount(system, true);
|
||||
await Task.WhenAll(memberCountPublic, memberCountAll);
|
||||
|
||||
var memberCountDisplayed = shouldShowPrivate ? memberCountAll.Result : memberCountPublic.Result;
|
||||
|
||||
var members = _data.GetSystemMembers(system)
|
||||
.Where(m => m.MemberPrivacy == PrivacyLevel.Public || shouldShowPrivate)
|
||||
.OrderBy(m => m.Name, StringComparer.InvariantCultureIgnoreCase);
|
||||
var anyMembersHidden = !shouldShowPrivate && memberCountPublic.Result != memberCountAll.Result;
|
||||
|
||||
await ctx.Paginate(
|
||||
members,
|
||||
memberCountDisplayed,
|
||||
5,
|
||||
embedTitle,
|
||||
(eb, ms) => {
|
||||
foreach (var m in ms) {
|
||||
var profile = $"**ID**: {m.Hid}";
|
||||
if (m.Pronouns != null) profile += $"\n**Pronouns**: {m.Pronouns}";
|
||||
if (m.Birthday != null) profile += $"\n**Birthdate**: {m.BirthdayString}";
|
||||
if (m.ProxyTags.Count > 0) profile += $"\n**Proxy tags:** {m.ProxyTagsString()}";
|
||||
if (m.Description != null) profile += $"\n\n{m.Description}";
|
||||
if (m.MemberPrivacy == PrivacyLevel.Private)
|
||||
profile += "*(this member is private)*";
|
||||
|
||||
eb.AddField(m.Name, profile.Truncate(1024));
|
||||
}
|
||||
|
||||
var footer = $"{memberCountDisplayed} total.";
|
||||
if (anyMembersHidden && authCtx == LookupContext.ByOwner)
|
||||
footer += " Private members have been hidden. type \"pk;system list full all\" to include them.";
|
||||
eb.WithFooter(footer);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async Task SystemFronter(Context ctx, PKSystem system)
|
||||
{
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.FrontPrivacy);
|
||||
|
||||
var sw = await _data.GetLatestSwitch(system);
|
||||
if (sw == null) throw Errors.NoRegisteredSwitches;
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateFronterEmbed(sw, system.Zone));
|
||||
}
|
||||
|
||||
struct FrontHistoryEntry
|
||||
{
|
||||
public Instant? LastTime;
|
||||
public PKSwitch ThisSwitch;
|
||||
|
||||
public FrontHistoryEntry(Instant? lastTime, PKSwitch thisSwitch)
|
||||
{
|
||||
LastTime = lastTime;
|
||||
ThisSwitch = thisSwitch;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SystemFrontHistory(Context ctx, PKSystem system)
|
||||
{
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.FrontHistoryPrivacy);
|
||||
|
||||
var sws = _data.GetSwitches(system)
|
||||
.Scan(new FrontHistoryEntry(null, null), (lastEntry, newSwitch) => new FrontHistoryEntry(lastEntry.ThisSwitch?.Timestamp, newSwitch));
|
||||
var totalSwitches = await _data.GetSwitchCount(system);
|
||||
if (totalSwitches == 0) throw Errors.NoRegisteredSwitches;
|
||||
|
||||
var embedTitle = system.Name != null ? $"Front history of {system.Name} (`{system.Hid}`)" : $"Front history of `{system.Hid}`";
|
||||
|
||||
await ctx.Paginate(
|
||||
sws,
|
||||
totalSwitches,
|
||||
10,
|
||||
embedTitle,
|
||||
async (builder, switches) =>
|
||||
{
|
||||
var outputStr = "";
|
||||
foreach (var entry in switches)
|
||||
{
|
||||
var lastSw = entry.LastTime;
|
||||
|
||||
var sw = entry.ThisSwitch;
|
||||
// Fetch member list and format
|
||||
var members = await _data.GetSwitchMembers(sw).ToListAsync();
|
||||
var membersStr = members.Any() ? string.Join(", ", members.Select(m => m.Name)) : "no fronter";
|
||||
|
||||
var switchSince = SystemClock.Instance.GetCurrentInstant() - sw.Timestamp;
|
||||
|
||||
// If this isn't the latest switch, we also show duration
|
||||
string stringToAdd;
|
||||
if (lastSw != null)
|
||||
{
|
||||
// Calculate the time between the last switch (that we iterated - ie. the next one on the timeline) and the current one
|
||||
var switchDuration = lastSw.Value - sw.Timestamp;
|
||||
stringToAdd =
|
||||
$"**{membersStr}** ({Formats.ZonedDateTimeFormat.Format(sw.Timestamp.InZone(system.Zone))}, {Formats.DurationFormat.Format(switchSince)} ago, for {Formats.DurationFormat.Format(switchDuration)})\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
stringToAdd =
|
||||
$"**{membersStr}** ({Formats.ZonedDateTimeFormat.Format(sw.Timestamp.InZone(system.Zone))}, {Formats.DurationFormat.Format(switchSince)} ago)\n";
|
||||
}
|
||||
|
||||
if (outputStr.Length + stringToAdd.Length > EmbedBuilder.MaxDescriptionLength) break;
|
||||
outputStr += stringToAdd;
|
||||
}
|
||||
|
||||
builder.Description = outputStr;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async Task SystemFrontPercent(Context ctx, PKSystem system)
|
||||
{
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.FrontHistoryPrivacy);
|
||||
|
||||
string durationStr = ctx.RemainderOrNull() ?? "30d";
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
var rangeStart = PluralKit.Utils.ParseDateTime(durationStr, true, system.Zone);
|
||||
if (rangeStart == null) throw Errors.InvalidDateTime(durationStr);
|
||||
if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture;
|
||||
|
||||
var frontpercent = await _data.GetFrontBreakdown(system, rangeStart.Value.ToInstant(), now);
|
||||
await ctx.Reply(embed: await _embeds.CreateFrontPercentEmbed(frontpercent, system.Zone));
|
||||
}
|
||||
|
||||
public async Task SystemProxy(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem().CheckGuildContext();
|
||||
@ -359,7 +152,7 @@ namespace PluralKit.Bot.Commands
|
||||
await ctx.Reply($"Message proxying in this server ({ctx.Guild.Name.EscapeMarkdown()}) is now **disabled** for your system.");
|
||||
}
|
||||
|
||||
public async Task SystemTimezone(Context ctx)
|
||||
public async Task SystemTimezone(Context ctx)
|
||||
{
|
||||
if (ctx.System == null) throw Errors.NoSystemError;
|
||||
|
||||
@ -517,4 +310,4 @@ namespace PluralKit.Bot.Commands
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
118
PluralKit.Bot/Commands/SystemFront.cs
Normal file
118
PluralKit.Bot/Commands/SystemFront.cs
Normal file
@ -0,0 +1,118 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class SystemFront
|
||||
{
|
||||
private IDataStore _data;
|
||||
private EmbedService _embeds;
|
||||
|
||||
public SystemFront(IDataStore data, EmbedService embeds)
|
||||
{
|
||||
_data = data;
|
||||
_embeds = embeds;
|
||||
}
|
||||
|
||||
struct FrontHistoryEntry
|
||||
{
|
||||
public Instant? LastTime;
|
||||
public PKSwitch ThisSwitch;
|
||||
|
||||
public FrontHistoryEntry(Instant? lastTime, PKSwitch thisSwitch)
|
||||
{
|
||||
LastTime = lastTime;
|
||||
ThisSwitch = thisSwitch;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SystemFronter(Context ctx, PKSystem system)
|
||||
{
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.FrontPrivacy);
|
||||
|
||||
var sw = await _data.GetLatestSwitch(system);
|
||||
if (sw == null) throw Errors.NoRegisteredSwitches;
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateFronterEmbed(sw, system.Zone));
|
||||
}
|
||||
|
||||
public async Task SystemFrontHistory(Context ctx, PKSystem system)
|
||||
{
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.FrontHistoryPrivacy);
|
||||
|
||||
var sws = _data.GetSwitches(system)
|
||||
.Scan(new FrontHistoryEntry(null, null), (lastEntry, newSwitch) => new FrontHistoryEntry(lastEntry.ThisSwitch?.Timestamp, newSwitch));
|
||||
var totalSwitches = await _data.GetSwitchCount(system);
|
||||
if (totalSwitches == 0) throw Errors.NoRegisteredSwitches;
|
||||
|
||||
var embedTitle = system.Name != null ? $"Front history of {system.Name} (`{system.Hid}`)" : $"Front history of `{system.Hid}`";
|
||||
|
||||
await ctx.Paginate(
|
||||
sws,
|
||||
totalSwitches,
|
||||
10,
|
||||
embedTitle,
|
||||
async (builder, switches) =>
|
||||
{
|
||||
var outputStr = "";
|
||||
foreach (var entry in switches)
|
||||
{
|
||||
var lastSw = entry.LastTime;
|
||||
|
||||
var sw = entry.ThisSwitch;
|
||||
// Fetch member list and format
|
||||
var members = await _data.GetSwitchMembers(sw).ToListAsync();
|
||||
var membersStr = members.Any() ? string.Join(", ", members.Select(m => m.Name)) : "no fronter";
|
||||
|
||||
var switchSince = SystemClock.Instance.GetCurrentInstant() - sw.Timestamp;
|
||||
|
||||
// If this isn't the latest switch, we also show duration
|
||||
string stringToAdd;
|
||||
if (lastSw != null)
|
||||
{
|
||||
// Calculate the time between the last switch (that we iterated - ie. the next one on the timeline) and the current one
|
||||
var switchDuration = lastSw.Value - sw.Timestamp;
|
||||
stringToAdd =
|
||||
$"**{membersStr}** ({Formats.ZonedDateTimeFormat.Format(sw.Timestamp.InZone(system.Zone))}, {Formats.DurationFormat.Format(switchSince)} ago, for {Formats.DurationFormat.Format(switchDuration)})\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
stringToAdd =
|
||||
$"**{membersStr}** ({Formats.ZonedDateTimeFormat.Format(sw.Timestamp.InZone(system.Zone))}, {Formats.DurationFormat.Format(switchSince)} ago)\n";
|
||||
}
|
||||
|
||||
if (outputStr.Length + stringToAdd.Length > EmbedBuilder.MaxDescriptionLength) break;
|
||||
outputStr += stringToAdd;
|
||||
}
|
||||
|
||||
builder.Description = outputStr;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async Task SystemFrontPercent(Context ctx, PKSystem system)
|
||||
{
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.FrontHistoryPrivacy);
|
||||
|
||||
string durationStr = ctx.RemainderOrNull() ?? "30d";
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
var rangeStart = PluralKit.Utils.ParseDateTime(durationStr, true, system.Zone);
|
||||
if (rangeStart == null) throw Errors.InvalidDateTime(durationStr);
|
||||
if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture;
|
||||
|
||||
var frontpercent = await _data.GetFrontBreakdown(system, rangeStart.Value.ToInstant(), now);
|
||||
await ctx.Reply(embed: await _embeds.CreateFrontPercentEmbed(frontpercent, system.Zone));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class LinkCommands
|
||||
public class SystemLink
|
||||
{
|
||||
private IDataStore _data;
|
||||
|
||||
public LinkCommands(IDataStore data)
|
||||
public SystemLink(IDataStore data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
117
PluralKit.Bot/Commands/SystemList.cs
Normal file
117
PluralKit.Bot/Commands/SystemList.cs
Normal file
@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Humanizer;
|
||||
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class SystemList
|
||||
{
|
||||
private IDataStore _data;
|
||||
|
||||
public SystemList(IDataStore data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public async Task MemberShortList(Context ctx, PKSystem system) {
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.MemberListPrivacy);
|
||||
|
||||
var authCtx = ctx.LookupContextFor(system);
|
||||
var shouldShowPrivate = authCtx == LookupContext.ByOwner && ctx.Match("all", "everyone", "private");
|
||||
|
||||
var embedTitle = system.Name != null ? $"Members of {system.Name.SanitizeMentions()} (`{system.Hid}`)" : $"Members of `{system.Hid}`";
|
||||
|
||||
var memberCountPublic = _data.GetSystemMemberCount(system, false);
|
||||
var memberCountAll = _data.GetSystemMemberCount(system, true);
|
||||
await Task.WhenAll(memberCountPublic, memberCountAll);
|
||||
|
||||
var memberCountDisplayed = shouldShowPrivate ? memberCountAll.Result : memberCountPublic.Result;
|
||||
|
||||
var members = _data.GetSystemMembers(system)
|
||||
.Where(m => m.MemberPrivacy == PrivacyLevel.Public || shouldShowPrivate)
|
||||
.OrderBy(m => m.Name, StringComparer.InvariantCultureIgnoreCase);
|
||||
var anyMembersHidden = !shouldShowPrivate && memberCountPublic.Result != memberCountAll.Result;
|
||||
|
||||
await ctx.Paginate(
|
||||
members,
|
||||
memberCountDisplayed,
|
||||
25,
|
||||
embedTitle,
|
||||
(eb, ms) =>
|
||||
{
|
||||
eb.Description = string.Join("\n", ms.Select((m) =>
|
||||
{
|
||||
if (m.HasProxyTags)
|
||||
{
|
||||
var proxyTagsString = m.ProxyTagsString().SanitizeMentions();
|
||||
if (proxyTagsString.Length > 100) // arbitrary threshold for now, tweak?
|
||||
proxyTagsString = "tags too long, see member card";
|
||||
|
||||
return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}** *({proxyTagsString})*";
|
||||
}
|
||||
|
||||
return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}**";
|
||||
}));
|
||||
|
||||
var footer = $"{memberCountDisplayed} total.";
|
||||
if (anyMembersHidden && authCtx == LookupContext.ByOwner)
|
||||
footer += "Private members have been hidden. type \"pk;system list all\" to include them.";
|
||||
eb.WithFooter(footer);
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task MemberLongList(Context ctx, PKSystem system) {
|
||||
if (system == null) throw Errors.NoSystemError;
|
||||
ctx.CheckSystemPrivacy(system, system.MemberListPrivacy);
|
||||
|
||||
var authCtx = ctx.LookupContextFor(system);
|
||||
var shouldShowPrivate = authCtx == LookupContext.ByOwner && ctx.Match("all", "everyone", "private");
|
||||
|
||||
var embedTitle = system.Name != null ? $"Members of {system.Name} (`{system.Hid}`)" : $"Members of `{system.Hid}`";
|
||||
|
||||
var memberCountPublic = _data.GetSystemMemberCount(system, false);
|
||||
var memberCountAll = _data.GetSystemMemberCount(system, true);
|
||||
await Task.WhenAll(memberCountPublic, memberCountAll);
|
||||
|
||||
var memberCountDisplayed = shouldShowPrivate ? memberCountAll.Result : memberCountPublic.Result;
|
||||
|
||||
var members = _data.GetSystemMembers(system)
|
||||
.Where(m => m.MemberPrivacy == PrivacyLevel.Public || shouldShowPrivate)
|
||||
.OrderBy(m => m.Name, StringComparer.InvariantCultureIgnoreCase);
|
||||
var anyMembersHidden = !shouldShowPrivate && memberCountPublic.Result != memberCountAll.Result;
|
||||
|
||||
await ctx.Paginate(
|
||||
members,
|
||||
memberCountDisplayed,
|
||||
5,
|
||||
embedTitle,
|
||||
(eb, ms) => {
|
||||
foreach (var m in ms) {
|
||||
var profile = $"**ID**: {m.Hid}";
|
||||
if (m.Pronouns != null) profile += $"\n**Pronouns**: {m.Pronouns}";
|
||||
if (m.Birthday != null) profile += $"\n**Birthdate**: {m.BirthdayString}";
|
||||
if (m.ProxyTags.Count > 0) profile += $"\n**Proxy tags:** {m.ProxyTagsString()}";
|
||||
if (m.Description != null) profile += $"\n\n{m.Description}";
|
||||
if (m.MemberPrivacy == PrivacyLevel.Private)
|
||||
profile += "*(this member is private)*";
|
||||
|
||||
eb.AddField(m.Name, profile.Truncate(1024));
|
||||
}
|
||||
|
||||
var footer = $"{memberCountDisplayed} total.";
|
||||
if (anyMembersHidden && authCtx == LookupContext.ByOwner)
|
||||
footer += " Private members have been hidden. type \"pk;system list full all\" to include them.";
|
||||
eb.WithFooter(footer);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,10 +5,10 @@ using PluralKit.Bot.CommandSystem;
|
||||
|
||||
namespace PluralKit.Bot.Commands
|
||||
{
|
||||
public class APICommands
|
||||
public class Token
|
||||
{
|
||||
private IDataStore _data;
|
||||
public APICommands(IDataStore data)
|
||||
public Token(IDataStore data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
@ -35,16 +35,23 @@ namespace PluralKit.Bot
|
||||
|
||||
// Commands
|
||||
builder.RegisterType<CommandTree>().AsSelf();
|
||||
builder.RegisterType<SystemCommands>().AsSelf();
|
||||
builder.RegisterType<MemberCommands>().AsSelf();
|
||||
builder.RegisterType<SwitchCommands>().AsSelf();
|
||||
builder.RegisterType<LinkCommands>().AsSelf();
|
||||
builder.RegisterType<APICommands>().AsSelf();
|
||||
builder.RegisterType<ImportExportCommands>().AsSelf();
|
||||
builder.RegisterType<HelpCommands>().AsSelf();
|
||||
builder.RegisterType<ModCommands>().AsSelf();
|
||||
builder.RegisterType<MiscCommands>().AsSelf();
|
||||
builder.RegisterType<AutoproxyCommands>().AsSelf();
|
||||
builder.RegisterType<Autoproxy>().AsSelf();
|
||||
builder.RegisterType<Fun>().AsSelf();
|
||||
builder.RegisterType<Help>().AsSelf();
|
||||
builder.RegisterType<ImportExport>().AsSelf();
|
||||
builder.RegisterType<Member>().AsSelf();
|
||||
builder.RegisterType<MemberAvatar>().AsSelf();
|
||||
builder.RegisterType<MemberEdit>().AsSelf();
|
||||
builder.RegisterType<MemberProxy>().AsSelf();
|
||||
builder.RegisterType<Misc>().AsSelf();
|
||||
builder.RegisterType<ServerConfig>().AsSelf();
|
||||
builder.RegisterType<Switch>().AsSelf();
|
||||
builder.RegisterType<Commands.System>().AsSelf();
|
||||
builder.RegisterType<SystemEdit>().AsSelf();
|
||||
builder.RegisterType<SystemFront>().AsSelf();
|
||||
builder.RegisterType<SystemLink>().AsSelf();
|
||||
builder.RegisterType<SystemList>().AsSelf();
|
||||
builder.RegisterType<Token>().AsSelf();
|
||||
|
||||
// Bot core
|
||||
builder.RegisterType<Bot>().AsSelf().SingleInstance();
|
||||
|
Loading…
Reference in New Issue
Block a user