Files
lyuksovannyyandClaude Opus 5.5 46a1f6ee1f Client Commands: telegram-panel slash commands with Discord-style parameters
Lists the panel account's client commands as Telegram slash commands (hourly
refresh, cached on the device), shows their parameters while typing, runs
complete commands on the panel instead of sending them and shows the answer
as an "Only visible to you" message (latest 20 kept in memory). Holding send
offers Run / Send as message / Send response in chat (red ! badge); an
answer's menu starts with Send in chat. Other plugins register their own
commands through the client_commands library module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:19:20 +02:00

3.4 KiB

The library: commands from other plugins

Client Commands publishes the module client_commands. Another plugin registers commands with it. Those commands show in the / list with their parameters and run on the device, and their answers show like the panel's: only to you, or in the chat when Send response in chat is on.

Registering

import random
import sys
import types

from base_plugin import BasePlugin

__id__ = "dice"
__name__ = "Dice"


def setup(api):
    api.register(
        "roll",
        roll,
        summary="Rolls a die",
        params=[
            api.Param("sides", "integer", required=False, default=6, description="How many sides"),
            api.Param("style", "choice", required=False, default="plain", choices=("plain", "big")),
        ],
        owner=__id__,
    )


def roll(ctx, sides=6, style="plain"):
    api = sys.modules["client_commands"]
    if sides < 2:
        raise api.CommandError("A die needs at least 2 sides")
    result = random.randint(1, sides)
    return f"🎲 <b>{result}</b>" if style == "big" else f"🎲 {result}"


class DicePlugin(BasePlugin):
    def on_plugin_load(self):
        # Works whichever plugin loads first.
        api = sys.modules.setdefault("client_commands", types.ModuleType("client_commands"))
        if hasattr(api, "register"):
            setup(api)
        else:
            api.__dict__.setdefault("pending", []).append(setup)

    def on_plugin_unload(self):
        api = sys.modules.get("client_commands")
        if api is not None and hasattr(api, "unregister_owner"):
            api.unregister_owner(__id__)

API

Name What it does
register(name, handler, *, params=(), summary="", description="", aliases=(), reply="none", owner=None) Adds or replaces a command. reply is none, optional or required. params are Params, or dicts shaped like the panel's.
unregister(name) Removes one command.
unregister_owner(owner) Removes every command owner registered. Call it from on_plugin_unload.
commands() The registered commands, as dicts.
Param(name, type="string", required=True, description="", default=None, choices=()) type is string, integer, number, boolean or choice.
CommandError Raise it for a user-facing error. The message is shown as the answer.
escape(text) Makes text safe to put into an HTML answer.
VERSION, available The API version, and whether Client Commands is loaded right now.

handler(ctx, **values) runs off the UI thread. Its arguments are the parsed parameter values, with defaults for those not given. It returns the answer as Telegram HTML, or None (the answer is then "✓ Done"). These tags work: b, i, u, s, code, pre, a href, blockquote, tg-spoiler, tg-emoji and br.

ctx has:

  • account, dialog_id and topic_id;
  • reply_to, the replied MessageObject or None;
  • text, the command as typed, and command, its name;
  • values, the parsed parameters;
  • send_to_chat, whether the answer goes into the chat as the account;
  • client, the SDK's AccountClient of the account.

Parsing follows the same rules as the panel's commands (see how-it-works.md). A panel command with the same name shadows yours.

Registrations survive Client Commands being updated or turned off and on. A client_commands module kept from before still works.