mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
feat: implement switch commands
This commit is contained in:
parent
15191171f5
commit
10dd499835
9 changed files with 85 additions and 45 deletions
|
|
@ -152,6 +152,13 @@ public partial class CommandTree
|
|||
Commands.SystemShowPrivacy(var param, _) => ctx.Execute<SystemEdit>(SystemPrivacy, m => m.ShowSystemPrivacy(ctx, ctx.System)),
|
||||
Commands.SystemChangePrivacyAll(var param, _) => ctx.Execute<SystemEdit>(SystemPrivacy, m => m.ChangeSystemPrivacyAll(ctx, ctx.System, param.level)),
|
||||
Commands.SystemChangePrivacy(var param, _) => ctx.Execute<SystemEdit>(SystemPrivacy, m => m.ChangeSystemPrivacy(ctx, ctx.System, param.privacy, param.level)),
|
||||
Commands.SwitchOut(_, _) => ctx.Execute<Switch>(SwitchOut, m => m.SwitchOut(ctx)),
|
||||
Commands.SwitchDo(var param, _) => ctx.Execute<Switch>(Switch, m => m.SwitchDo(ctx, param.targets)),
|
||||
Commands.SwitchMove(var param, _) => ctx.Execute<Switch>(SwitchMove, m => m.SwitchMove(ctx, param.@string)),
|
||||
Commands.SwitchEdit(var param, var flags) => ctx.Execute<Switch>(SwitchEdit, m => m.SwitchEdit(ctx, param.targets, false, flags.first, flags.remove, flags.append, flags.prepend)),
|
||||
Commands.SwitchEditOut(_, _) => ctx.Execute<Switch>(SwitchEditOut, m => m.SwitchEditOut(ctx)),
|
||||
Commands.SwitchDelete(var param, var flags) => ctx.Execute<Switch>(SwitchDelete, m => m.SwitchDelete(ctx, flags.all)),
|
||||
Commands.SwitchCopy(var param, var flags) => ctx.Execute<Switch>(SwitchCopy, m => m.SwitchEdit(ctx, param.targets, true, flags.first, flags.remove, flags.append, flags.prepend)),
|
||||
_ =>
|
||||
// this should only ever occur when deving if commands are not implemented...
|
||||
ctx.Reply(
|
||||
|
|
@ -521,26 +528,8 @@ public partial class CommandTree
|
|||
|
||||
private async Task HandleSwitchCommand(Context ctx)
|
||||
{
|
||||
if (ctx.Match("out"))
|
||||
await ctx.Execute<Switch>(SwitchOut, m => m.SwitchOut(ctx));
|
||||
else if (ctx.Match("move", "m", "shift", "offset"))
|
||||
await ctx.Execute<Switch>(SwitchMove, m => m.SwitchMove(ctx));
|
||||
else if (ctx.Match("edit", "e", "replace"))
|
||||
if (ctx.Match("out"))
|
||||
await ctx.Execute<Switch>(SwitchEditOut, m => m.SwitchEditOut(ctx));
|
||||
else
|
||||
await ctx.Execute<Switch>(SwitchEdit, m => m.SwitchEdit(ctx));
|
||||
else if (ctx.Match("delete", "remove", "erase", "cancel", "yeet"))
|
||||
await ctx.Execute<Switch>(SwitchDelete, m => m.SwitchDelete(ctx));
|
||||
else if (ctx.Match("copy", "add", "duplicate", "dupe"))
|
||||
await ctx.Execute<Switch>(SwitchCopy, m => m.SwitchEdit(ctx, true));
|
||||
else if (ctx.Match("commands", "help"))
|
||||
await PrintCommandList(ctx, "switching", SwitchCommands);
|
||||
else if (ctx.HasNext()) // there are following arguments
|
||||
await ctx.Execute<Switch>(Switch, m => m.SwitchDo(ctx));
|
||||
else
|
||||
await PrintCommandNotFoundError(ctx, Switch, SwitchOut, SwitchMove, SwitchEdit, SwitchEditOut,
|
||||
SwitchDelete, SwitchCopy, SystemFronter, SystemFrontHistory);
|
||||
await PrintCommandNotFoundError(ctx, Switch, SwitchOut, SwitchMove, SwitchEdit, SwitchEditOut,
|
||||
SwitchDelete, SwitchCopy, SystemFronter, SystemFrontHistory);
|
||||
}
|
||||
|
||||
private async Task CommandHelpRoot(Context ctx)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Humanizer;
|
||||
using Myriad.Types;
|
||||
using PluralKit.Core;
|
||||
using uniffi.commands;
|
||||
|
|
@ -8,6 +9,7 @@ namespace PluralKit.Bot;
|
|||
public abstract record Parameter()
|
||||
{
|
||||
public record MemberRef(PKMember member): Parameter;
|
||||
public record MemberRefs(List<PKMember> members): Parameter;
|
||||
public record SystemRef(PKSystem system): Parameter;
|
||||
public record GuildRef(Guild guild): Parameter;
|
||||
public record MemberPrivacyTarget(MemberPrivacySubject target): Parameter;
|
||||
|
|
@ -56,14 +58,21 @@ public class Parameters
|
|||
|
||||
private async Task<Parameter?> ResolveFfiParam(Context ctx, uniffi.commands.Parameter ffi_param)
|
||||
{
|
||||
var byId = HasFlag("id", "by-id");
|
||||
switch (ffi_param)
|
||||
{
|
||||
case uniffi.commands.Parameter.MemberRef memberRef:
|
||||
var byId = HasFlag("id", "by-id");
|
||||
return new Parameter.MemberRef(
|
||||
await ctx.ParseMember(memberRef.member, byId)
|
||||
?? throw new PKError(ctx.CreateNotFoundError("Member", memberRef.member, byId))
|
||||
);
|
||||
case uniffi.commands.Parameter.MemberRefs memberRefs:
|
||||
return new Parameter.MemberRefs(
|
||||
await memberRefs.members.ToAsyncEnumerable().SelectAwait(async m =>
|
||||
await ctx.ParseMember(m, byId)
|
||||
?? throw new PKError(ctx.CreateNotFoundError("Member", m, byId))
|
||||
).ToListAsync()
|
||||
);
|
||||
case uniffi.commands.Parameter.SystemRef systemRef:
|
||||
// todo: do we need byId here?
|
||||
return new Parameter.SystemRef(
|
||||
|
|
|
|||
|
|
@ -8,11 +8,10 @@ namespace PluralKit.Bot;
|
|||
public class Switch
|
||||
{
|
||||
|
||||
public async Task SwitchDo(Context ctx)
|
||||
public async Task SwitchDo(Context ctx, ICollection<PKMember> members)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
|
||||
var members = await ctx.ParseMemberList(ctx.System.Id);
|
||||
await DoSwitchCommand(ctx, members);
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +20,7 @@ public class Switch
|
|||
ctx.CheckSystem();
|
||||
|
||||
// Switch with no members = switch-out
|
||||
await DoSwitchCommand(ctx, new PKMember[] { });
|
||||
await DoSwitchCommand(ctx, []);
|
||||
}
|
||||
|
||||
private async Task DoSwitchCommand(Context ctx, ICollection<PKMember> members)
|
||||
|
|
@ -57,12 +56,10 @@ public class Switch
|
|||
$"{Emojis.Success} Switch registered. Current fronters are now {string.Join(", ", members.Select(m => m.NameFor(ctx)))}.");
|
||||
}
|
||||
|
||||
public async Task SwitchMove(Context ctx)
|
||||
public async Task SwitchMove(Context ctx, string timeToMove)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
|
||||
var timeToMove = ctx.RemainderOrNull() ??
|
||||
throw new PKSyntaxError("Must pass a date or time to move the switch to.");
|
||||
var tz = TzdbDateTimeZoneSource.Default.ForId(ctx.Config?.UiTz ?? "UTC");
|
||||
|
||||
var result = DateUtils.ParseDateTime(timeToMove, true, tz);
|
||||
|
|
@ -104,31 +101,29 @@ public class Switch
|
|||
await ctx.Reply($"{Emojis.Success} Switch moved to <t:{newSwitchTime}> ({newSwitchDeltaStr} ago).");
|
||||
}
|
||||
|
||||
public async Task SwitchEdit(Context ctx, bool newSwitch = false)
|
||||
public async Task SwitchEdit(Context ctx, List<PKMember> newMembers, bool newSwitch = false, bool first = false, bool remove = false, bool append = false, bool prepend = false)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
|
||||
var newMembers = await ctx.ParseMemberList(ctx.System.Id);
|
||||
|
||||
await using var conn = await ctx.Database.Obtain();
|
||||
var currentSwitch = await ctx.Repository.GetLatestSwitch(ctx.System.Id);
|
||||
if (currentSwitch == null)
|
||||
throw Errors.NoRegisteredSwitches;
|
||||
var currentSwitchMembers = await ctx.Repository.GetSwitchMembers(conn, currentSwitch.Id).ToListAsync().AsTask();
|
||||
|
||||
if (ctx.MatchFlag("first", "f"))
|
||||
if (first)
|
||||
newMembers = FirstInSwitch(newMembers[0], currentSwitchMembers);
|
||||
else if (ctx.MatchFlag("remove", "r"))
|
||||
else if (remove)
|
||||
newMembers = RemoveFromSwitch(newMembers, currentSwitchMembers);
|
||||
else if (ctx.MatchFlag("append", "a"))
|
||||
else if (append)
|
||||
newMembers = AppendToSwitch(newMembers, currentSwitchMembers);
|
||||
else if (ctx.MatchFlag("prepend", "p"))
|
||||
else if (prepend)
|
||||
newMembers = PrependToSwitch(newMembers, currentSwitchMembers);
|
||||
|
||||
if (newSwitch)
|
||||
{
|
||||
// if there's no edit flag, assume we're appending
|
||||
if (!ctx.MatchFlag("first", "f", "remove", "r", "append", "a", "prepend", "p"))
|
||||
if (!prepend && !append && !remove && !first)
|
||||
newMembers = AppendToSwitch(newMembers, currentSwitchMembers);
|
||||
await DoSwitchCommand(ctx, newMembers);
|
||||
}
|
||||
|
|
@ -172,7 +167,7 @@ public class Switch
|
|||
public async Task SwitchEditOut(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
await DoEditCommand(ctx, new PKMember[] { });
|
||||
await DoEditCommand(ctx, []);
|
||||
}
|
||||
|
||||
public async Task DoEditCommand(Context ctx, ICollection<PKMember> members)
|
||||
|
|
@ -217,11 +212,11 @@ public class Switch
|
|||
await ctx.Reply($"{Emojis.Success} Switch edited. Current fronters are now {newSwitchMemberStr}.");
|
||||
}
|
||||
|
||||
public async Task SwitchDelete(Context ctx)
|
||||
public async Task SwitchDelete(Context ctx, bool all)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
|
||||
if (ctx.Match("all", "clear") || ctx.MatchFlag("all", "clear", "c"))
|
||||
if (all)
|
||||
{
|
||||
// Subcommand: "delete all"
|
||||
var purgeMsg =
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ pub fn all() -> impl Iterator<Item = Command> {
|
|||
.chain(member::cmds())
|
||||
.chain(config::cmds())
|
||||
.chain(fun::cmds())
|
||||
.map(|cmd| cmd.flag(("plaintext", ["pt"])))
|
||||
.map(|cmd| cmd.flag(("raw", ["r"])))
|
||||
.chain(switch::cmds())
|
||||
.map(|cmd| cmd.flag(("plaintext", ["pt"])).flag(("raw", ["r"])))
|
||||
}
|
||||
|
||||
pub const RESET: (&str, [&str; 2]) = ("reset", ["clear", "default"]);
|
||||
|
|
|
|||
|
|
@ -1 +1,31 @@
|
|||
use super::*;
|
||||
|
||||
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||
let switch = ("switch", ["sw"]);
|
||||
|
||||
let edit = ("edit", ["e", "replace"]);
|
||||
let r#move = ("move", ["m", "shift", "offset"]);
|
||||
let delete = ("delete", ["remove", "erase", "cancel", "yeet"]);
|
||||
let copy = ("copy", ["add", "duplicate", "dupe"]);
|
||||
let out = "out";
|
||||
|
||||
[
|
||||
command!(switch, out => "switch_out"),
|
||||
command!(switch, r#move, OpaqueString => "switch_move"), // TODO: datetime parsing
|
||||
command!(switch, delete => "switch_delete").flag(("all", ["clear", "c"])),
|
||||
command!(switch, edit, out => "switch_edit_out"),
|
||||
command!(switch, edit, MemberRefs => "switch_edit")
|
||||
.flag(("first", ["f"]))
|
||||
.flag(("remove", ["r"]))
|
||||
.flag(("append", ["a"]))
|
||||
.flag(("prepend", ["p"])),
|
||||
command!(switch, copy, MemberRefs => "switch_copy")
|
||||
.flag(("first", ["f"]))
|
||||
.flag(("remove", ["r"]))
|
||||
.flag(("append", ["a"]))
|
||||
.flag(("prepend", ["p"])),
|
||||
command!(switch, ("commands", ["help"]) => "switch_commands"),
|
||||
command!(switch, MemberRefs => "switch_do"),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::token::{Token, TokenMatchResult};
|
|||
pub enum ParameterValue {
|
||||
OpaqueString(String),
|
||||
MemberRef(String),
|
||||
MemberRefs(Vec<String>),
|
||||
SystemRef(String),
|
||||
GuildRef(String),
|
||||
MemberPrivacyTarget(String),
|
||||
|
|
@ -39,10 +40,14 @@ impl Parameter {
|
|||
impl Display for Parameter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.kind {
|
||||
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => {
|
||||
ParameterKind::OpaqueString => {
|
||||
write!(f, "[{}]", self.name)
|
||||
}
|
||||
ParameterKind::OpaqueStringRemainder => {
|
||||
write!(f, "[{}]...", self.name)
|
||||
}
|
||||
ParameterKind::MemberRef => write!(f, "<target member>"),
|
||||
ParameterKind::MemberRefs => write!(f, "<member 1> <member 2> <member 3>..."),
|
||||
ParameterKind::SystemRef => write!(f, "<target system>"),
|
||||
ParameterKind::GuildRef => write!(f, "<target guild>"),
|
||||
ParameterKind::MemberPrivacyTarget => write!(f, "<privacy target>"),
|
||||
|
|
@ -77,6 +82,7 @@ pub enum ParameterKind {
|
|||
OpaqueString,
|
||||
OpaqueStringRemainder,
|
||||
MemberRef,
|
||||
MemberRefs,
|
||||
SystemRef,
|
||||
GuildRef,
|
||||
MemberPrivacyTarget,
|
||||
|
|
@ -92,6 +98,7 @@ impl ParameterKind {
|
|||
ParameterKind::OpaqueString => "string",
|
||||
ParameterKind::OpaqueStringRemainder => "string",
|
||||
ParameterKind::MemberRef => "target",
|
||||
ParameterKind::MemberRefs => "targets",
|
||||
ParameterKind::SystemRef => "target",
|
||||
ParameterKind::GuildRef => "target",
|
||||
ParameterKind::MemberPrivacyTarget => "member_privacy_target",
|
||||
|
|
@ -103,7 +110,10 @@ impl ParameterKind {
|
|||
}
|
||||
|
||||
pub(crate) fn remainder(&self) -> bool {
|
||||
matches!(self, ParameterKind::OpaqueStringRemainder)
|
||||
matches!(
|
||||
self,
|
||||
ParameterKind::OpaqueStringRemainder | ParameterKind::MemberRefs
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
||||
|
|
@ -113,12 +123,14 @@ impl ParameterKind {
|
|||
Ok(ParameterValue::OpaqueString(input.into()))
|
||||
}
|
||||
ParameterKind::MemberRef => Ok(ParameterValue::MemberRef(input.into())),
|
||||
ParameterKind::MemberRefs => Ok(ParameterValue::MemberRefs(
|
||||
input.split(' ').map(|s| s.trim().to_string()).collect(),
|
||||
)),
|
||||
ParameterKind::SystemRef => Ok(ParameterValue::SystemRef(input.into())),
|
||||
ParameterKind::MemberPrivacyTarget => MemberPrivacyTargetKind::from_str(input)
|
||||
.map(|target| ParameterValue::MemberPrivacyTarget(target.as_ref().into())),
|
||||
ParameterKind::SystemPrivacyTarget => SystemPrivacyTargetKind::from_str(input).map(
|
||||
|target| ParameterValue::SystemPrivacyTarget(target.as_ref().into()),
|
||||
),
|
||||
ParameterKind::SystemPrivacyTarget => SystemPrivacyTargetKind::from_str(input)
|
||||
.map(|target| ParameterValue::SystemPrivacyTarget(target.as_ref().into())),
|
||||
ParameterKind::PrivacyLevel => PrivacyLevelKind::from_str(input)
|
||||
.map(|level| ParameterValue::PrivacyLevel(level.as_ref().into())),
|
||||
ParameterKind::Toggle => {
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ fn get_param_ty(kind: ParameterKind) -> &'static str {
|
|||
match kind {
|
||||
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => "string",
|
||||
ParameterKind::MemberRef => "PKMember",
|
||||
ParameterKind::MemberRefs => "List<PKMember>",
|
||||
ParameterKind::SystemRef => "PKSystem",
|
||||
ParameterKind::MemberPrivacyTarget => "MemberPrivacySubject",
|
||||
ParameterKind::SystemPrivacyTarget => "SystemPrivacySubject",
|
||||
|
|
@ -181,6 +182,7 @@ fn get_param_param_ty(kind: ParameterKind) -> &'static str {
|
|||
match kind {
|
||||
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => "Opaque",
|
||||
ParameterKind::MemberRef => "Member",
|
||||
ParameterKind::MemberRefs => "Members",
|
||||
ParameterKind::SystemRef => "System",
|
||||
ParameterKind::MemberPrivacyTarget => "MemberPrivacyTarget",
|
||||
ParameterKind::SystemPrivacyTarget => "SystemPrivacyTarget",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ interface CommandResult {
|
|||
[Enum]
|
||||
interface Parameter {
|
||||
MemberRef(string member);
|
||||
MemberRefs(sequence<string> members);
|
||||
SystemRef(string system);
|
||||
GuildRef(string guild);
|
||||
MemberPrivacyTarget(string target);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ pub enum CommandResult {
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum Parameter {
|
||||
MemberRef { member: String },
|
||||
MemberRefs { members: Vec<String> },
|
||||
SystemRef { system: String },
|
||||
GuildRef { guild: String },
|
||||
MemberPrivacyTarget { target: String },
|
||||
|
|
@ -37,6 +38,7 @@ impl From<ParameterValue> for Parameter {
|
|||
fn from(value: ParameterValue) -> Self {
|
||||
match value {
|
||||
ParameterValue::MemberRef(member) => Self::MemberRef { member },
|
||||
ParameterValue::MemberRefs(members) => Self::MemberRefs { members },
|
||||
ParameterValue::SystemRef(system) => Self::SystemRef { system },
|
||||
ParameterValue::MemberPrivacyTarget(target) => Self::MemberPrivacyTarget { target },
|
||||
ParameterValue::SystemPrivacyTarget(target) => Self::SystemPrivacyTarget { target },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue