using axXez.TS3.Protocol; using System.Collections.Generic; using System.Linq; using System.Text; namespace axXez.TS3.Bot { /// /// Provides context for a command invocation, including the invoker, parsed arguments, reply helpers, and the command's result. /// public class CommandContext { private readonly BotContext _bot; private readonly Dictionary _replies = new(); private readonly Dictionary _allReplies = new(); /// The connection the command arrived on. Cast to for voice channel commands or for server-query commands. public ITeamspeakClient Source { get; } /// The client who invoked the command. This is the bot's own query client when the command is executed internally. public Client Invoker { get; } /// The scope in which the command was received. public MessageTargetMode TargetMode { get; } /// The full raw message text, including the command keyword and prefix. public string Message { get; } /// The raw argument string passed after the command keyword. public string ParameterString { get; } /// The arguments passed to the command, split by whitespace. public string[] Args { get; } /// The result set by the command via , , or . public CommandResponse Response { get; private set; } /// All replies ever added via , keyed by scope. Preserved across calls. public IReadOnlyDictionary Replies => _allReplies.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToString()); /// Whether the command was invoked internally by the bot itself rather than by a real client. public bool IsInternalInvocation => Invoker == _bot.Server.QueryClient; /// Whether the invoker has the role. Always true for internal invocations. public bool IsInvokerAdmin => _bot.IsAdmin(Invoker); /// Whether the invoker has the role or higher. Always true for internal invocations. public bool IsInvokerModerator => _bot.IsModerator(Invoker); /// Whether the invoker has the role or higher. Always true for internal invocations. public bool IsInvokerUser => _bot.IsUser(Invoker); /// Whether the invoker has the role (i.e. is in the server's default group). Always true for internal invocations. public bool IsInvokerGuest => _bot.IsGuest(Invoker); internal CommandContext(BotContext bot, ITeamspeakClient source, Client invoker, MessageTargetMode targetMode, string message, string paramStr, string[] args) { _bot = bot; Source = source; Invoker = invoker; TargetMode = targetMode; Message = message; ParameterString = paramStr; Args = args; } /// /// Buffers a reply in the same scope the command was received in, sent all at once when the command completes. /// Pass to send immediately instead. /// public void Reply(string message, bool flush = false) => Reply(message, TargetMode, flush); /// /// Buffers a reply in the specified scope, sent all at once when the command completes. /// Pass to send immediately instead. /// public void Reply(string message, MessageTargetMode targetMode, bool flush = false) { if (!_replies.TryGetValue(targetMode, out var sb)) _replies[targetMode] = sb = new StringBuilder(); if (!_allReplies.TryGetValue(targetMode, out var all)) _allReplies[targetMode] = all = new StringBuilder(); if (sb.Length > 0) sb.AppendLine(); if (all.Length > 0) all.AppendLine(); sb.Append(message); all.Append(message); if (flush) Flush(); } /// /// Buffers a private reply directly to the invoker, sent all at once when the command completes. /// Pass to send immediately instead. /// public void ReplyPrivate(string message, bool flush = false) => Reply(message, MessageTargetMode.Private, flush); /// /// Signals the command completed successfully with no message. /// Use as return ctx.Ok(); in a command method. /// public CommandResponse Ok() => Response = new(true); /// /// Buffers as a reply and signals success. Shorthand for ctx.Reply(message); return ctx.Ok(); /// Use as return ctx.Ok("message"); in a command method. /// public CommandResponse Ok(string message) { Reply(message); return Response = new(true); } /// /// Buffers as a reply in the specified scope and signals success. /// Use as return ctx.Ok("message", MessageTargetMode.Private); in a command method. /// public CommandResponse Ok(string message, MessageTargetMode targetMode) { Reply(message, targetMode); return Response = new(true); } /// /// Signals the command failed with an optional reason. The framework sends the message privately to the invoker and appends the command's help text. /// Use as return ctx.Error("reason"); in a command method. /// public CommandResponse Error(string message = null) => Response = new(false, message); /// Signals the command failed because the invoker lacks the required role. public CommandResponse ErrorNoPermission() => Error("You don't have permission to use that command!"); /// /// Immediately sends all buffered replies and clears the buffer. /// Use when a reply must be delivered before a blocking operation (e.g. reconnect). /// Has no effect on internal invocations. /// public void Flush() { if (IsInternalInvocation || _replies.Count == 0) return; foreach (var (mode, sb) in _replies) { var client = mode == MessageTargetMode.Server ? _bot.Server.Query : Source; client.SendTextMessage(mode, mode == MessageTargetMode.Private ? Invoker.ID : 0, sb.ToString()); } _replies.Clear(); } /// Returns all reply text for the given scope (including already-flushed replies), or null if nothing was replied in that scope. public string GetReply(MessageTargetMode mode) => _allReplies.TryGetValue(mode, out var sb) ? sb.ToString() : null; } }