Files
chatSummaries/docs/sdk-notes.md
T
exteraGramPlugins a3b0b264c1 Initial commit
2026-09-12 02:03:47 +00:00

5.2 KiB
Raw Permalink Blame History

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

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.