Files
lyuksovannyyandClaude Opus 5.5 9b2f26a845
Publish plugin / publish (push) Successful in 4s
Message Logger: show logged edits and deletions from telegram-panel [no bump]
Keeps deleted messages (75% opacity, red icon-pack trash icon), restores
logged messages deleted while away, reports deletions, and shows edit
history as a mock chat (Edit history in the message menu, Open topic).
Message links and history open as any signed-in account with access.
Per-master API tokens; chat state synced per checked range with an hour's
validity, cached in SQLite with a configurable lifetime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:55:23 +02:00

154 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (message_logger)
- **Java number boxing.** A Python `int` passed where Java takes `Object`, or to an overloaded
method, becomes a `Long` — even the result of `Integer.valueOf()`. Use `java.jint` /
`jfloat` explicitly: `ArrayList<Integer>` for `TL_*_getMessages.id` (a `Long` there fails
serialization on the network thread and the request silently never answers),
`Paint.setColor` (an `int` otherwise picks the `setColor(long)` ColorSpace overload),
reflective `Method.invoke` arguments, and replacing `param.args[i]` in hooks.
- **Requests.** `client_utils.send_request` did not invoke the callback for these requests;
`ConnectionsManager.getInstance(account).sendRequest(req, RequestDelegate)` with a
`dynamic_proxy(RequestDelegate)` kept strongly referenced works (`src/tg.py`).
- **Logs.** This device's logcat is silenced; `src/diag.py` mirrors to
`…/files/exteraGram/exteraGram Files/message_logger/diag.log` (readable over `adb shell cat`).
- **Deploys.** `devclient.py deploy --enable` only enables; an already enabled plugin needs a
deploy without `--enable` (which reloads) to pick up a new build.
- **Adding messages to an open chat.** Inserting into `ChatActivity.messages` directly (not in
`messagesDict`) breaks later arrivals in that chat; the private
`ChatActivity.processNewMessages(ArrayList, boolean)` places messages (also older ones) and
keeps the app's dicts and adapter consistent.
- **Cell height.** Changing a `ChatMessageCell`'s measured height grows its bubble; after the
state behind it changes, `forceLayout()` the cell and `chatAdapter.notifyItemChanged(...)`,
or the list keeps stale positions. Item animators reset cell alpha: scale it in a
`setAlpha` before-hook instead of setting it once.
- **Icon packs.** exteraGram applies icon packs through the activity's `ExteraResources`
(`getDrawableForDensity`); the application context's plain `Resources` and
`ColoredImageSpan(int)` give stock icons. Load drawables from
`get_last_fragment().getParentActivity().getResources()`.
- **Another account's chat.** `BaseFragment.setCurrentAccount(slot)` before `presentFragment`
opens e.g. a `ChatActivity` as another signed-in account without switching accounts.
- **Links.** Tapped links go through `org.telegram.messenger.browser.Browser.openUrl` (8 overloads,
the second argument a `Uri` or `String`); `adb shell am start -d URL` needs `-p <package>`.
- **Message menu rows.** `MenuItemData` items land in the menu's Plugins folder, and options
added to `ChatActivity.fillMessageMenu`'s lists are dropped by exteraGram. A row in the main
menu is an `ActionBarMenuSubItem` inserted into the built popup after `createMenu` (whose
overloads call one another: insert once); `selectedObject` is the message.
- **Topics as another account.** `ForumUtilities.openTopic` uses the presenting fragment's
account; do its steps yourself (`TopicsController.findTopic`/`loadTopic`, `ChatActivity`
with `setCurrentAccount` then `setThreadMessages`) — see `src/views/open_as.py`.