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;
}
}