generated from exteraGramPlugins/pluginBase
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>
131 lines
6.3 KiB
Markdown
131 lines
6.3 KiB
Markdown
# exteraGram plugin SDK — condensed notes
|
||
|
||
Gathered from <https://plugins.exteragram.app/docs>, the SDK type stubs (`stubs.zip` from
|
||
`exteraSquad/plugins-pysdk-builds`), the official `exteragram-utils` PyPI package
|
||
(`exteragram_utils/dev_client.py`), and the app's `gradle.properties`. Not exhaustive — see the
|
||
full docs site for anything not covered here.
|
||
|
||
## Plugin identity and metadata
|
||
|
||
Read statically via AST from top-level constant assignments in the plugin's single `.py` file
|
||
— the app never executes the file just to read metadata. Docs:
|
||
<https://plugins.exteragram.app/docs/metadata>.
|
||
|
||
| Constant | Required | Notes |
|
||
|---|---|---|
|
||
| `__id__` | yes | 2–32 chars, starts with a letter, only `A-Za-z0-9_-` |
|
||
| `__name__` | yes | display name |
|
||
| `__description__` | no | |
|
||
| `__author__` | no | |
|
||
| `__version__` | no | |
|
||
| `__icon__` | no | e.g. `"exteraPlugins/1"` |
|
||
| `__app_version__` | no | e.g. `">=12.5.1"`; legacy alias `__min_version__` |
|
||
| `__sdk_version__` | no | e.g. `">=1.4.4.3"` |
|
||
| `__requirements__` | no | list of pip package specs the host installs before loading |
|
||
|
||
## `BasePlugin` lifecycle and hooks
|
||
|
||
<https://plugins.exteragram.app/docs/base-plugin>
|
||
|
||
- `on_plugin_load(self)` — called once when the plugin is enabled/loaded. Hooks must be
|
||
registered here (e.g. `self.add_on_send_message_hook()`); registering later has no effect.
|
||
- `on_plugin_unload(self)` — called on disable/removal; release resources here.
|
||
- `create_settings(self)` — returns a list of `ui.settings` rows rendered in the plugin's
|
||
settings screen.
|
||
- Hooks: `on_send_message_hook(self, account, params)`, `pre_request_hook(self, ...)`,
|
||
`post_request_hook(self, ...)`, `on_update_hook(s)(self, ...)`, `on_app_event(self, ...)`.
|
||
Each returns a `HookResult`.
|
||
- `HookResult(strategy=HookStrategy.MODIFY, params=params)` applies the mutated `params` back
|
||
to the host; `HookStrategy.DEFAULT` (a bare `HookResult()`) passes through unchanged;
|
||
`HookStrategy.CANCEL` drops the action; `HookStrategy.MODIFY_FINAL` applies changes and
|
||
short-circuits any later hooks.
|
||
- Settings persistence: `self.get_setting(key, default)` / `self.set_setting(key, value)`.
|
||
|
||
## `ui.settings` row types
|
||
|
||
<https://plugins.exteragram.app/docs/ui-settings>
|
||
|
||
`Header`, `Input`, `Switch`, `Text`, `Selector`, `Divider` — plain kwargs-holding row
|
||
descriptors consumed by the host's settings renderer; construct and return them from
|
||
`create_settings`.
|
||
|
||
## Logging
|
||
|
||
<https://plugins.exteragram.app/docs/logging>
|
||
|
||
`android_utils.log(...)` writes to logcat. Chaquopy additionally redirects
|
||
`sys.stdout`/`sys.stderr` to the logcat tags `python.stdout` / `python.stderr` — useful when a
|
||
dependency logs with plain `print()`.
|
||
|
||
## Background work
|
||
|
||
<https://plugins.exteragram.app/docs/client-utils>
|
||
|
||
`client_utils.run_on_queue(...)` schedules work off the UI thread; use it for anything that
|
||
shouldn't block message handling.
|
||
|
||
## Reflection / private API access
|
||
|
||
<https://plugins.exteragram.app/docs/hook-utils>
|
||
|
||
`hook_utils.find_class(...)` and related helpers locate app classes and private fields for
|
||
plugins that need to reach beyond the public SDK surface.
|
||
|
||
## UI helpers
|
||
|
||
- `ui.alert.AlertDialogBuilder` — native alert dialogs. <https://plugins.exteragram.app/docs/ui-alert>
|
||
- `ui.bulletin` — transient bottom bulletins/toasts. <https://plugins.exteragram.app/docs/ui-bulletin>
|
||
- `intents.IntentsManager` — Android intent integration, **exteraGram 12.6.4+ only**.
|
||
<https://plugins.exteragram.app/docs/intents>
|
||
|
||
## On-device dev server protocol
|
||
|
||
<https://plugins.exteragram.app/docs/dev-server>
|
||
|
||
TCP `127.0.0.1:42690`, reachable from a PC via `adb forward tcp:42690 tcp:42690`. Wire format
|
||
is consecutive UTF-8 JSON objects with **no length framing** — parse with
|
||
`json.JSONDecoder().raw_decode` over an accumulating buffer, never assume one JSON object per
|
||
`recv()`. Requests: `{"@": <command>, "#": <id>, ...}`; responses echo `"#"`.
|
||
|
||
Commands used by this repo's `tools/devclient.py`: `ping`, `get_plugins`, `enable_plugin`,
|
||
`disable_plugin`, `reload_plugin`, `remove_plugin` (field `plugin_id`), `write_plugin`
|
||
(`plugin_id`, `content` — installs or updates), `start_debugger`/`stop_debugger` (`host`,
|
||
`port`, `platform` ∈ `vscode`|`pycharm`). The official `exteragram-utils` client (PyPI) uses
|
||
integer request ids from 1, a 30 s response timeout, a 1.0 s socket timeout, a
|
||
`write_plugin` → sleep 0.3 s → `reload_plugin` deploy sequence, and `adb forward` (not
|
||
`reverse`) for both the dev-server port and the debugger port. `elyx_*` commands exist for the
|
||
Elyx engine and are out of scope here.
|
||
|
||
## Runtime
|
||
|
||
Chaquopy 16 / Python 3.11. Anything shipped in a bundle must be 3.11-compatible.
|
||
|
||
## Why not Elyx/ElyxBuilder
|
||
|
||
The official structured plugin format (Elyx/ElyxBuilder) is a real alternative to a single
|
||
`.plugin` file, but it:
|
||
|
||
- requires the separate Elyx engine to be installed and enabled on the device;
|
||
- ships archives, not the `.plugin` files the base plugin engine understands;
|
||
- needs a manual first install before live reload works.
|
||
|
||
This repo's bundler instead compiles a normal multi-file `src/` project down to the one
|
||
`.plugin` file the base engine already knows how to load, so the dev-server write/reload loop
|
||
works from the first deploy with no extra on-device engine.
|
||
|
||
## Gotchas found on exteraGram 12.10.6 (client_commands)
|
||
|
||
- **Class checks.** `hook_utils.find_class` returns a Chaquopy class wrapper without
|
||
`isInstance`. Use `jutil.java_class(name)`, which gives the real `java.lang.Class`.
|
||
- **Plugin-made modules.** A module a plugin puts into `sys.modules` doesn't reliably survive
|
||
that plugin's reload. State that must outlive a reload (the library registry) hangs off
|
||
`builtins` instead.
|
||
- **Suggestion list.** `MentionsAdapter.notifyDataSetChanged` diffs rows by their item (the
|
||
command string). Rows whose label stays the same aren't rebound, so call
|
||
`notifyItemRangeChanged` too. The adapter's `:` emoji search is async and replaces the
|
||
rows, so replace the search (skip the method) instead of patching its result.
|
||
- **Recycled cells.** A cell restyled by an earlier load of the plugin keeps that style.
|
||
Remember the original in the view's tag, not in plugin memory.
|
||
- **Ephemeral messages.** A tap opens their menu; a long press does nothing. Deleting one goes
|
||
through `MessagesController.deleteEphemeralMessage`, which also asks Telegram to delete it.
|