DevGram Plugin SDKIntroduction
Python plugins running inside DevGram and using the native Telegram API.
DevGram plugins
The plugin inherits from BasePlugin and is executed by built-in Python 3.11. Root and external Xposed Framework are not needed.
Native formatPlugins are supplied as one file .plugin (or .py) or archive .dgplugin for complex extensions with resources and dependencies. The DevGram API does not copy the API of other clients.
SDK Features
EventsMessages, TL updates, requests and NotificationCenter.
InterfaceSettings, bulletins, alerts and Pill Stack.
Client APIMedia, formatting, accounts and controllers.
NativeJava class proxy and method hooks.
Reading order
- Preparation and first package
- BasePlugin and events
- Client API and UI
- Development, publishing and security
''
Execution model
The plugin is loaded as a separate runtime module, but runs inside the DevGram process. Therefore, a bug in the Java bridge, an infinite loop or heavy work on the UI thread can affect the client. Treat every callback like production application code: validate input data, limit runtime, and free up resources.
What is considered a public API
The stable layer is located in the modules devgram.*. Fields and methods with leading underscores, Telegram internal classes and specific layout IDs are not a contract. If you have to use a low-level point, add version checking and fallback.
Minimum plugin standard
- Unique ID and clear metadata.
- No secrets in the archive.
- Idempotent load/unload.
- Work in light, dark and system theme.
- Correct behavior after reload, disable and safe mode.
DevGram BuilderDevGramBuilder
The official tool for creating a project, checking sources and building the installable .dgplugin.
Installation
python -m pip install --upgrade "git+https://github.com/DevGramOfficial/DevGramBuilder.git"
dgb --version
On Windows you can use py -3. If Linux prohibits system installation, create a virtualenv or use pipx.
First build
mkdir my-plugin
cd my-plugin
dgb new
dgb build -a -v -nf
The finished file will appear in builds/<id>-<version>.dgplugin. It can be opened in DevGram or uploaded through Dev Server.
Creationdgb new creates a working structure and example.
Validation-a checks Python and JSON locales.
BuildA package with open .py sources.
Devicedgb upload installs the package through Dev Server.
DevGramBuilder Repository · Latest release
DevGram BuilderQuick start
From an empty folder to a finished file in a few commands.
1. Install Builder
# Windows
py -3 -m pip install --upgrade "git+https://github.com/DevGramOfficial/DevGramBuilder.git"
# Linux / macOS
python3 -m pip install --upgrade "git+https://github.com/DevGramOfficial/DevGramBuilder.git"
2. Create a project
mkdir hello-devgram
cd hello-devgram
dgb new
Builder will ask for the title, author, ID, version, description and link to the icon. A non-interactive mode is available for CI:
dgb new . --gen --name "Hello DevGram" --author "@username" --id "username.hello" --plugin-version "1.0.0" --description "Первый плагин"
3. Collect
dgb build -a -v -nf
-a includes syntax checking, -v - detailed output, -nf excludes the Builder service folder from the release.
4. Install
Send a file from builds/ in Telegram, open it in DevGram and click “Install”. For fast development use dgb upload.
DevGram BuilderProject structure
The original project and the finished archive have a predictable structure.
Project
my-plugin/
├── devgram-builder.json
├── .devgrambuilder/config.json
├── src/
│ ├── main.py
│ └── helpers.py
├── assets/
├── locales/
│ ├── en.json
│ └── ru.json
├── wheels/
└── builds/
Ready .dgplugin
manifest.json
main.py
helpers.py
assets/...
locales/en.json
locales/ru.json
wheels/...
Files from src/ go to the root of the archive. Builder creates manifest from devgram-builder.json. Git, IDE files, builds/, Python cache and logs are excluded automatically.
.devgrambuilder/config.json
{
"source": "src",
"ignoreAll": [".git/**", "builds/**", "**/__pycache__/**"],
"optionalAssets": []
}
| Field | Purpose |
|---|
source | Python source directory. |
ignoreAll | Files which are never included. |
optionalAssets | Excluded by --no-assets. |
DevGram BuilderCLI Reference
DevGramBuilder 1.4 commands.
| Command | Purpose |
|---|
dgb new [directory] | Create a project. |
dgb build | Build a source-only .dgplugin. |
dgb watch seconds | Rebuild when files change. |
dgb add-ignore / del-ignore | Configure exclusions. |
dgb stats | Show build, file, line and size statistics. |
dgb upload | Install through Dev Server. |
Build flags
| Flag | Meaning |
|---|
-a, --ast | Validate Python and locales. |
-v | Verbose output. |
-nf | Do not include the service config. |
-ni | Do not record Builder information. |
--no-assets | Exclude optionalAssets. |
-o PATH | Output file. |
Ignore
dgb add-ignore "assets/source.psd" --all
dgb add-ignore "assets/large/**" --no-assets
dgb del-ignore 0 --all
Statistics
dgb stats builds
dgb stats files
dgb stats lines
dgb stats size
Full help: dgb --help And dgb build --help.
DevGram BuilderProject metadata
devgram-builder.json converted to batch manifest.json.
{
"id": "username.my_plugin",
"name": "My Plugin",
"version": "1.0.0",
"author": "@username",
"description": "Что делает плагин",
"icon": "https://example.com/icon.png",
"main": "main.py",
"min_app_version": "12.10.3",
"min_devgram": "3",
"requirements": []
}
| Field | Rule |
|---|
id | Stable ID up to 64 characters: letters, numbers, ., _, -. |
name, version, author | Required for Builder. |
main | Entry point relative src/; usually main.py. |
description, icon | Description and direct HTTPS link to the image. |
min_app_version | Minimum version of DevGram. |
min_devgram | Minimum Plugin API level; current - 3. |
requirements | Up to 32 dependency names from wheels/. |
Do not change ID after publicationAnother ID is treated as a new plugin and receives separate settings.
DevGram BuilderModules and Imports
The project shares regular Python files inside src/.
# src/helpers.py
def greeting(name):
return f"Привет, {name}!"
# src/main.py
from devgram import BasePlugin
from .helpers import greeting
class HelloPlugin(BasePlugin):
id = "username.hello"
name = "Hello"
version = "1.0.0"
author = "@username"
def on_load(self):
self.bulletin(greeting(self.name), kind="success")
def on_unload(self):
self.unregister_pills()
self.unhook_all()
The loader itself will find the successor class BasePlugin and will create an instance. Line plugin = HelloPlugin() not required. For subfolders add __init__.py and use relative imports.
LifecycleCreate listeners, hooks, observers and pills in on_load(), and close in on_unload().
DevGram BuilderResources and localization
Builder transfers assets/ And locales/ while maintaining the structure.
Assets
# assets/icons/cat.png
path = self.asset_path("icons/cat.png")
asset_path() returns a safe absolute path only to the existing file inside assets/. The case of the name is important.
Localization
# locales/ru.json
{"welcome": "Привет, {name}!"}
text = self.string("welcome", "Hello!", name="FireDragoq")
Optional Resources
Add templates to optionalAssets dgb build --no-assets.
DevGram BuilderPlugin settings
Builder packages the code, and the settings screen is created by the public DevGram Plugin SDK.
from devgram import BasePlugin
from devgram.ui import Header, Switch, Input, Selector, Button, Text
class Plugin(BasePlugin):
id = "username.settings"
def settings(self):
return [
Header(text="Основное"),
Switch(key="enabled", text="Включено", default=True),
Input(key="name", text="Имя", default="DevGram"),
Selector(key="style", text="Стиль", items=["system", "compact"]),
Button(key="test", text="Проверить"),
Text(text="Версия 1.0.0"),
]
def on_setting_changed(self, key, value):
self.log(f"{key}={value}")
def on_setting_click(self, key):
if key == "test":
self.bulletin("Работает", kind="success")
There is no separate runtime API Builder: components and callbacks are taken from devgram.ui.
DevGram BuilderDependencies
Dependencies are supplied within the package: DevGram does not download anything during installation.
- Download compatible
.whl. -
wheels/. - Add distribution name to
requirements.
# devgram-builder.json
"requirements": ["requests"]
# файл
wheels/requests-2.32.3-py3-none-any.whl
Builder checks that names and files match. A maximum of 32 dependencies are allowed.
CompatibilityPrefer py3-none-any. Wheel with native code must be built for Android, device ABI and Python 3.11.
Python Standard Library and devgram.* are not added to requirements.
DevGram BuilderBuild and development
Source validation, watch mode and reproducible builds.
Build
dgb build -a -v -nf
-a validates Python through AST and checks JSON locales. Every .py file stays in the package; .pyc and .pyo are unsupported.
Watch
dgb watch 2 --args "-a -v -nf"
Changes are checked every two seconds.
Custom output path
dgb build -a -o dist/plugin.dgplugin
DevGram BuilderBuilder troubleshooting
CLI installation, configuration, validation and upload.
dgb not found
Run python -m devgram_builder --version. If the module works, add Python Scripts to PATH. On Windows use py -3 -m devgram_builder.
externally-managed-environment
python3 -m venv .venv
source .venv/bin/activate
python -m pip install "git+https://github.com/DevGramOfficial/DevGramBuilder.git"
devgram-builder.json not found
Run the command inside the project. For a new project run dgb new.
Entry point not found
main is relative to src/. Check letter case and ignore rules.
Package contains .pyc
DevGram accepts only open .py sources. Rebuild the project with the current Builder.
Missing dependency wheel
The name in requirements must match a file in wheels/.
Dev Server returned an error
Check ADB, developer mode, port and the current token. invalid package means the package structure is invalid.
Getting startedPreparation
An environment for developing, building and quickly installing plugins.
Install
- the current DevGram release;
- Python 3.10 or newer for Builder;
- ADB for Dev Server;
- an editor with Python support.
DevGramBuilder
python -m pip install --upgrade "git+https://github.com/DevGramOfficial/DevGramBuilder.git"
dgb --version
Developer Mode
adb devices
adb forward tcp:42690 tcp:42690
Getting startedFirst .dgplugin
Create a project, modify the example and get an installable package.
mkdir hello-devgram
cd hello-devgram
dgb new
Open src/main.py. Minimum entry point:
from devgram import BasePlugin
class Hello(BasePlugin):
id = "username.hello"
name = "Hello DevGram"
version = "1.0.0"
author = "@username"
description = "Мой первый плагин"
def on_load(self):
self.bulletin("Плагин загружен", kind="success")
def on_unload(self):
pass
Enter the same values in devgram-builder.json
Assembly
dgb build -a -v -nf
Open the finished file from builds/ in DevGram. After installation, check to enable, disable, re-download and uninstall.
Package .dgpluginmanifest.json
Manifest is located strictly at the root of the archive and describes the package before running the code.
{
"id": "username.my_plugin",
"name": "My Plugin",
"version": "1.0.0",
"author": "@username",
"description": "Описание плагина",
"icon": "https://example.com/icon.png",
"main": "main.py",
"min_app_version": "12.10.3",
"min_devgram": "3",
"requirements": []
}
| Field | Purpose |
|---|
id | Mandatory ID up to 64 characters. Letters, numbers, periods, hyphens and underscores are allowed. |
main | main.py. |
name, version, author | Card and catalog metadata. |
description, icon | Description and HTTPS icon. |
min_app_version | Minimum application version. |
min_devgram / min_sdk | Minimum Plugin API level. |
requirements | Names of dependencies present in wheels/. |
Correct field nameUsed main entrypoint. DevGramBuilder generates manifest automatically.
Priority
id, name, version, author, description And icon from manifest replace class attributes.
Package .dgpluginPlugin icon
The icon is displayed in the installation card, manager and directory.
In the Builder project
{
"id": "username.my_plugin",
"name": "My Plugin",
"icon": "https://example.com/plugin-icon.png"
}
Add icon V devgram-builder.json; Builder will transfer the link to manifest.
Requirements
- direct HTTPS link to the image, not the HTML page;
- PNG or JPG, preferably square;
- contrast on light and dark theme.
If there is no loading or an error occurs, DevGram displays a standard icon.
Package .dgpluginAssets and localization
The resources are extracted along with the package into the plugin's private directory.
Assets
assets/background.png
assets/icons/action.png
background = self.asset_path("background.png")
action_icon = self.asset_path("icons/action.png")
The method returns an empty string if the file is missing or the path is out of bounds assets/.
Locales
# locales/en.json
{"welcome": "Hello, {name}!"}
# locales/ru.json
{"welcome": "Привет, {name}!"}
text = self.string("welcome", "Hello!", name="FireDragoq")
English is used as fallback, after which the current language strings are applied. Keys and values must be strings.
Changeable data
assets/ intended for delivery files. Store user data via plugin_files_dir(), write_file() And read_file().
Package .dgpluginWheel dependencies
External libraries are provided inside the package.
my-plugin.dgplugin/
├── manifest.json
├── main.py
└── wheels/
└── requests-2.32.3-py3-none-any.whl
"requirements": ["requests"]
.whl from wheels/ in Python path. The installer does not access PyPI and does not run pip.
Rules
- no more than 32 simple names per
requirements; - URLs, local paths and pip flags are prohibited;
- each name must have a corresponding wheel;
_ And - are normalized equally.
Native librariesDesktop wheel does not fit Android. Use py3-none-any or an assembly for the desired Android ABI and Python 3.11.
SDKBasePlugin
Main extension class: lifecycle, metadata, storage and entry point to all DevGram APIs.
Plugin is a descendant class BasePlugin. The loader creates to the installed plugin ID and calls on_load(). The same object lives until unloaded, so store the state in the fields of the instance, and clear it in on_unload().
from devgram import BasePlugin
class MyPlugin(BasePlugin):
id = "my.plugin" # уникальный ID (обязателен)
name = "Мой плагин"
version = "1.0.0"
author = "@you"
description = "Что делает плагин"
icon = "https://.../icon.png" # необязательно
min_app_version = "12.9" # необязательно
def on_load(self):
self.log("загружен")
def on_unload(self):
self.unhook_all()
self.unregister_pills()
| Field | Purpose |
id | Unique identifier. It stores settings, hooks and files. |
name / version / author / description | Installation card and list of plugins. |
icon | URL or asset path for the plugin avatar. |
min_app_version | Minimum version of DevGram. |
In the package .dgplugin the same fields can (and better) be set in manifest.json — it takes precedence over class attributes.
Life cycle
| Method | When |
on_load(self) | Once at boot/power on. Register hooks, pills, observers. |
on_unload(self) | When unloading/disabling/reload. Remove EVERYTHING you registered. |
on_setting_changed(self, key, value) | A user has changed your setting. |
on_setting_click(self, key) | The setting button is pressed (type='button'). |
Plugin storage
self.set_setting("count", 5) # переживает перезапуск
n = int(self.get_setting("count", 0)) # значение по умолчанию — 0
self.write_file("data.json", text) # личная папка плагина
text = self.read_file("data.json")
API map (what to look for where)
| Region | Methods |
| Events | on_send_message, on_update, on_send_request, menu_items → section “Events” |
| Dispatch/client | send_message, edit_message, send_photo, send_request, controllers → “Client API” |
| Interface | bulletin, alert, toast, register_pill, register_panel_tab, settings |
| Java Hooks | hook, hook_all, invoke_original, java_class, implement → «Java hooks» |
| Appearance/Effects | glass, blur, tint, add_view, dp, canvas_* → “Effects and View” |
| Threads/utilities | run_on_ui, run_on_queue, copy, open_dialog, log |
IdempotencyEverything you create in on_load, should be filmed in on_unload: unhook_all(), unregister_pills(), unregister_panel_tab(), taken by observers. Otherwise, after reload there will be “double” hooks and leaks.
SDKMulti-account
A user can have multiple accounts. Work with a specific account, not just the active one.
from devgram import get_selected_account, get_client
acc = get_selected_account() # индекс активного аккаунта
client = get_client(acc) # клиент этого аккаунта
client.send_text(dialog_id, "hi")
Account-aware callbacks
Instead of “global” callbacks, override options with an account - they work for EVERY account:
def on_send_request_hook(self, account, name, request): ...
def on_receive_response_hook(self, account, name, response, error): ...
Controllers by account
from devgram.client_utils import get_messages_controller, get_user_config
mc = get_messages_controller(account) # без аргумента — активный аккаунт
cfg = get_user_config(account)
my_id = cfg.getClientUserId()
Don't close your active accountIf an event arrived for account B, and you send a response through active account A, the message will go to the wrong place. Always forward account from callback to controllers and dispatch.
SDKEvents
Callbacks for messages, TL updates and TL requests. Override the methods you need BasePlugin.
Messages
def on_send_message(self, text):
# перед отправкой. Вернуть: новый текст / None (без изменений) / False (отменить)
return text.replace(":shrug:", "¯\\_(ツ)_/¯")
def on_receive_message(self, text):
# входящее текстовое сообщение (только чтение/логика)
pass
TL updates and requests
def on_update(self, update):
# сырой TLRPC.Update (Java-объект). Поля читай рефлексией. Только чтение.
pass
def on_send_request(self, name, request):
# перед отправкой запроса. name = 'TL_messages_sendMessage' и т.п.
# request — Java TLObject (можно читать/менять поля)
pass
def on_receive_response(self, name, response, error):
# ответ сервера. response — Java-объект (или None), error — TL_error (или None)
pass
Account-aware options
In a multi-account, override these methods - they get a specific account:
def on_send_request_hook(self, account, name, request): ...
def on_receive_response_hook(self, account, name, response, error): ...
Messages and links menu
def menu_items(self):
return ["Перевести"] # пункты в меню долгого тапа по сообщению
def on_menu_click(self, label, message_text, dialog_id):
if label == "Перевести":
self.bulletin(translate(message_text))
def link_menu_items(self, url):
return ["Открыть в браузере"] # меню долгого тапа по ССЫЛКЕ
def on_link_menu_click(self, label, url, dialog_id):
from devgram.intents import open_uri
open_uri(url)
Don't block callbacksEvent callbacks are called on client hot paths. Take hard work to run_on_queue(), and UI updates are in run_on_ui(). Wrap the body in try/except: one exception should not crash the flow.
Return values
on_send_message - the only callback that changes the outgoing text: the string replaces, None leaves as is, False cancels sending. The remaining event callbacks are for observation; To change the behavior of queries/methods on the fly, use Java hooks.
SDKMessage menu
The plugin can add commands to the long press menu on a message.
def menu_items(self):
return ["Скопировать как Markdown", "Проверить текст"]
def on_menu_click(self, label, message_text, dialog_id):
if label == "Скопировать как Markdown":
self.copy(message_text)
self.bulletin("Скопировано", kind="success")
callback parameters
| Parameter | Meaning |
|---|
label | Pressed item from menu_items(). |
message_text | Text of the selected message. |
dialog_id | ID of the chat where the menu is open. |
Long press menu
Bring back stable human-readable labels and keep them short. The handler must re-check message and dialog before taking action: between building the menu and clicking, the message may have been deleted or changed.
Several plugins
Don't use too general names or override other people's commands. The plugin ID is added to the internal key by the loader, but the text displayed must explain the action.
Errors
If the action requires a network, immediately show a bulletin about the launch, execute the request in the queue and update the result on the UI thread. Don't block the menu by waiting for a response.
SDKClient API
Sending and editing messages, raw TL requests and access to all Telegram controllers.
Dispatch
self.send_message(dialog_id, "Привет", reply_to=0, entities=None)
send_text(dialog_id, "Быстрый текст") # from devgram.client_utils import send_text
self.edit_message(dialog_id, message_id, "Новый текст")
self.send_photo(dialog_id, "/path/img.jpg", caption="", entities=None)
self.send_file(dialog_id, "/path/file") # тип определится сам
self.send_video(dialog_id, "/path/v.mp4")
self.send_audio(dialog_id, "/path/a.mp3")
self.send_document(dialog_id, "/path/doc.pdf")
self.open_dialog(dialog_id) # открыть чат
self.update_profile_about("Новое био") # текущий аккаунт
dialog_id: user = his id, group/channel = −id, secret chat - just like in the client.
Raw TL queries
req = self.tl("TL_messages_getHistory") # пустой TLObject по короткому имени
req.peer = self.messages_controller().getInputPeer(dialog_id)
req.limit = 20
self.send_request(req, callback=lambda resp, err: self.log(resp))
Controllers
Return standard Telegram Java controllers (account-aware):
| Method | Java |
self.messages_controller() | MessagesController |
self.connections_manager() | ConnectionsManager |
self.user_config() | UserConfig |
self.send_messages_helper() | SendMessagesHelper |
self.media_data_controller() | MediaDataController |
self.contacts_controller() | ContactsController |
self.messages_storage() | MessagesStorage |
self.notification_center() | NotificationCenter |
self.file_loader() | FileLoader |
self.account_instance() | AccountInstance (all at once) |
client_utils module
from devgram.client_utils import (
send_text, send_formatted_text, observe, get_last_fragment,
get_messages_controller, get_media_controller, get_download_controller,
get_location_controller, get_notifications_controller)
# подписка на NotificationCenter с авто-отпиской
handle = observe(NotificationCenter.messagesDidLoad, my_callback)
handle.close() # в on_unload
Auxiliary: self.me() — account id, self.user_name(uid), self.chat_name(cid).
Work with real objectsControllers and TL objects are real Telegram Java classes, not wrappers. See the fields in the sources; fields/methods with leading underscores and internal layout IDs are not a contract.
SDKFormatting text
DevGram creates real TLRPC.MessageEntityrather than sending Markdown as plain text.
Supported entities
Bold, italic, underline, strike, spoiler, code, pre, blockquote and text links (custom signature for the link).
From Markdown/HTML
from devgram.client_utils import send_formatted_text
from devgram.text_formatting import parse_markdown, parse_html, parse_text
text, entities = parse_markdown("**жирный** и [ссылка](https://t.me)")
send_formatted_text(dialog_id, text, entities)
text, entities = parse_html("<b>жирный</b> <i>курсив</i>")
text, entities = parse_text(raw, parse_mode="markdown") # или "html"
Hand entities
ents = self.new_entities_list()
ents.add(self.bold_entity(0, 6)) # offset/length в UTF-16
ents.add(self.text_link_entity(7, 6, "https://t.me/DevGram"))
self.send_message(dialog_id, "Жирный ссылка", entities=ents)
Shielding
from devgram.text_formatting import escape_markdown, escape_html
safe = escape_markdown(user_input)
Offsets in UTF-16offset/length are calculated in UTF-16 code units (as in Telegram). For text with emoji/surrogates, use ready-made ones parse_* - they think correctly.
SDKSettings
Declarative settings page: describe the lines - DevGram will draw a native screen and save the values.
from devgram.ui import Header, Switch, Input, Selector, Text, Button, Card, Custom
class MyPlugin(BasePlugin):
def create_settings(self):
return [
Header("Общее"),
Switch("enabled", "Включить", default=True),
Input("api_key", "API-ключ", hint="вставьте токен"),
Selector("mode", "Режим", ["Авто", "Ручной"], default=0),
Text("Подсказка снизу серым"),
Button("reset", "Сбросить"),
]
def on_setting_changed(self, key, value):
if key == "enabled":
self.log("enabled = %s" % value)
def on_setting_click(self, key):
if key == "reset":
self.bulletin("Сброшено")
Widgets
| Class | Purpose |
Header(text) | Section title. |
Switch(key, text, default=False) | Toggle switch (bool). |
Input(key, text, hint="") | Text field (str). |
Selector(key, text, items, default=0) | Select from a list (index). |
Text(text) | |
Button(key, text) | Button → on_setting_click(key). |
Card(...) | Composite card. |
Custom(view) | Custom Android View in the settings bar. |
Reading and writing values
on = self.get_setting("enabled", True)
self.set_setting("mode", 1)
Keys are stableDo not rename keys between versions - the saved values will be lost. To rename, do a migration: read the old key, write the new one, delete the old one.
UtilitiesIntents
Registering Android Intent handlers and opening links safely.
from devgram.intents import register, open_uri
handle = register(on_link, action="android.intent.action.VIEW", scheme="https", host="example.org")
open_uri("https://devgram.space")
def on_unload(self):
handle.unhandle()
Filters register()
| Parameter | Description |
|---|
action | Android action. |
scheme | URI scheme: https, tg and others. |
host | Hostname. |
path | Path URI. |
IntentContext provides action, data, scheme, host, path and the original Java Intent.
Routing
Handlers are checked by priority, then by action, scheme, host, path, query and category. The narrower the matcher, the fewer random triggers. Callback returns True only if the Intent is actually processed.
URI Security
The URI from messages and network is not trusted. Compare scheme and host using a white list, limit the query length, and do not pass an arbitrary URI to the system resolver without warning.
Closing
Save HandlerHandle and call unhandle() in unload. When reloading, the old handler should not remain in the global list.
UtilitiesFile utilities
Text and binary file operations, as well as private plugin storage.
from devgram.file_utils import ensure_dir_exists, list_dir, read_file, write_file
ensure_dir_exists(path)
write_file(path, "text")
items = list_dir(path, extensions=[".json"], recursive=True)
data = read_file(path)
| Method | Purpose |
|---|
read_file_bytes / write_file_bytes | Binary content. |
delete_file(path) | Deleting a file. |
self.read_file(name) | Reading from the plugin's private folder. |
self.write_file(name,data) | Write to a private folder. |
File model
Pass an absolute path only after checking that it is inside a permitted directory. Don't use a custom filename directly: normalize the path, disable traversal and limit the size.
Text and bytes
Text functions use explicit encoding, binary helpers store bytes without conversion. Don't read a large file entirely if streaming or checking the size is enough.
Removal
Before deleting, check the object's existence, type, and directory membership. For important data, use temporary backup and clear confirmation in the UI.
UtilitiesAndroid utilities
Streams, clipboard, logging and ready-made Java listeners.
from devgram.android_utils import run_on_ui_thread, run_on_queue, log, copy_to_clipboard
run_on_ui_thread(lambda: update_view(), delay=250)
run_on_queue(lambda: load_data())
copy_to_clipboard("DevGram")
log("plugin loaded")
Listeners
from devgram.android_utils import OnClickListener, OnLongClickListener, R
view.setOnClickListener(OnClickListener(lambda view: clicked(view)))
view.setOnLongClickListener(OnLongClickListener(lambda view: True))
R(fn) creates a Java Runnable. Always send long operations to the queue, and change the View only on the UI thread.
UI and background
run_on_ui_thread() doesn't make the heavy function safe: it just schedules it in the UI queue. run_on_queue() does not give access to View. Perform the intersection of two worlds through a small immutable result.
Listeners
Save the listener handle or View reference for screen time only. Remove the listener when closing, otherwise the Activity may be held after navigation.
Clipboard and logs
Do not copy secrets without explicit user action and do not log private text. Bulletin should confirm action but not show sensitive content.
UI and runtimeBulletins and alerts
Native Telegram notifications and dialogs with callback buttons.
Bulletin
self.bulletin("Скопировано") # info
self.bulletin("Готово", kind="success", duration=2000)
self.bulletin("Ошибка", kind="error")
self.bulletin("Удалено", button="Отменить", callback=self.undo) # с кнопкой
kind: info / success / error. duration
Alert (dialogue)
self.alert(
"Заголовок", "Текст сообщения",
positive="OK", on_positive=self.ok,
negative="Отмена", on_negative=None,
neutral="Позже", on_neutral=self.later,
)
Toast
self.toast("Короткое сообщение")
From the UI threadBlocks/dialogs need to be shown on the UI thread. If calling from a background callback, wrap: self.run_on_ui(lambda: self.bulletin("...")).
UI and runtimePill Stack
Compact interactive plugin widgets in the line above the chat list. Each “pill” belongs to the plugin and is removed upon unloading.
def on_load(self):
self.register_pill(
"clock", "Часы",
text="—", # что показывать
value=self._now, # str или функция() -> str (живое значение)
on_click=self._open,
on_long_click=self._copy,
enabled=True,
)
def on_unload(self):
self.unregister_pills() # обязательно
| Parameter | Purpose |
pill_id | The unique id of the pill inside the plugin. |
name | Signature. |
text | Static text. |
value | A string or function with no arguments that returns a string (the value to be updated). |
on_click / on_long_click | Click handlers. |
enabled | Should I show the pill? |
ReuseRecall register_pill on_setting_changed. Always take your pills off on_unload.
UI and runtimePanel tab
The plugin can add its own tab to the Emoji / GIF / Stickers panel - with any Android View, a corner action button and an icon on the chat emoticon button.
def on_load(self):
self.register_panel_tab(
"DevWave", # подпись вкладки
self._build_tab_view, # (context, dialog_id) -> View
on_action=self._on_action, # (anchor_view, dialog_id) -> None
icon_path=self.asset_path("tab.png"), # иконка на кнопке-смайлике
)
def on_unload(self):
self.unregister_panel_tab()
def _build_tab_view(self, context, dialog_id):
from java import jclass
LinearLayout = jclass("android.widget.LinearLayout")
col = LinearLayout(context)
col.setOrientation(LinearLayout.VERTICAL)
# ... наполняем вкладку своими View, шлём в dialog_id
return col
| Parameter | Purpose |
|---|
title | Tab text in the Emoji/GIF/Stickers strip. |
build_view(context, dialog_id) | Builds the contents of a tab. It is called again every time it is opened - do not cache the View itself. |
on_action(anchor, dialog_id) | Optional. Enables an action button in the corner of the panel (next to Emoji/GIF/Stickers). anchor — for positioning the pop-up. |
icon_path | |
dialog_idbuild_view And on_action get the ID of the current chat - build the content and send the content specifically to it.
Action Popup Menu
IN on_action It’s convenient to open the native Telegram menu (like the “arrow” in reSwaga): ActionBarPopupWindow + ActionBarMenuSubItem, positioning by anchor.
def _on_action(self, anchor, dialog_id):
from java import jclass
ctx = anchor.getContext()
Popup = jclass("org.telegram.ui.ActionBar.ActionBarPopupWindow")
Layout = jclass("org.telegram.ui.ActionBar.ActionBarPopupWindow$ActionBarPopupWindowLayout")
Item = jclass("org.telegram.ui.ActionBar.ActionBarMenuSubItem")
Click = jclass("android.view.View$OnClickListener")
Gravity = jclass("android.view.Gravity")
layout = Layout(ctx)
row = Item(ctx, False, False) # 3-арг конструктор — без неоднозначности перегрузок
row.setTextAndIcon("Отправить", 0)
lst = self.implement(Click, onClick=lambda p, v: self._send(dialog_id))
self._refs.append(lst) # держим ссылку на прокси, иначе GC
row.setOnClickListener(lst)
layout.addView(row)
popup = Popup(layout, -2, -2) # -2 = WRAP_CONTENT литералом (не константой)
popup.setInputMethodMode(2) # INPUT_METHOD_NOT_NEEDED литералом
popup.showAsDropDown(anchor, 0, -self.dp(48), Gravity.TOP | Gravity.RIGHT)
From Python, specify sizes/flags as numeric literals (-1/-2, 2), and not through LayoutHelper.WRAP_CONTENT/INPUT_METHOD_NOT_NEEDED: such static final int are inlined by the compiler and cut out by R8 in release, which is why reflection gives AttributeError. LayoutHelper.createFrame/createLinear use it themselves dp() to the number.
Tab life cycle
The tab belongs to the plugin ID. build_view is called every time a tab is selected, so build the View from the current state and do not hold a reference to the Activity. Links to Java proxy listeners (self.implement(...)) stored in a plugin field, otherwise the GC will collect them. IN on_unload call unregister_panel_tab().
Icon on the smiley button
If set icon_path, while your tab is open, the chat emoticon button shows this icon instead of the usual animation, and tapping again opens your tab (not the emoji). The icon is saved when the panel is minimized.
UI and runtimeEffects and View
Embed and place View
# width/height в dp; -1 = на весь размер (MATCH), -2 = по контенту (WRAP)
self.add_view(parent, child, width=-1, height=-2,
left=16, top=0, right=16, bottom=0, gravity=80) # 80 = снизу
self.remove_view(child)
px = self.dp(12) # dp → пиксели
color = self.rgba(30, 30, 34, 210) # ARGB int
Liquid glass / blur / tint
panel = self.glass_panel(context_view, corner=22, blur=18, tint=0x26FFFFFF, border=0.6)
self.glass(existing_view) # обернуть существующую View стеклом
self.tint(view, self.rgba(20,20,24,200))
self.round_corners(view, 18)
self.blur(view, radius=40.0) # заморозить содержимое View (Android 12+)
self.unblur(view)
| Method | Description |
glass_panel(context_view,...) | New empty glass container panel (blurs the background behind). |
glass(view,...) | Wrap the existing View in glass in the same location. |
blur / unblur(view) | |
tint(view, argb) | |
round_corners(view, dp) | Round the corners (cut along the contour). |
add_view / remove_view | Add/remove child to ViewGroup. |
Drawing on canvas
IN onDraw his java_class-View draw with native helpers (without going to Python for each primitive):
self.paint_color(paint, 0xFFFF3B30)
self.canvas_draw_round_rect(canvas, rect, rx, ry, paint)
self.canvas_draw_circle(canvas, cx, cy, r, paint)
self.canvas_draw_text(canvas, "DevGram", x, y, paint)
self.canvas_draw_bitmap(canvas, bmp, left, top, paint)
self.canvas_save(canvas); self.canvas_clip_path(canvas, path); self.canvas_restore(canvas)
cv = self.new_canvas(bitmap) # Canvas по Bitmap (без hidden-API граблей)
Direct creation Canvas(bitmap)/calls drawRect
UI and runtimeJava hooks
Full Xposed-style hooks of any method/constructor and creation of real Java objects from Python. The engine is AliuHook on top of LSPlant: reliable on all Androids, even catches inline methods.
Method Hook
# before / after / replace + приоритет
self.hook("org.telegram.ui.ProfileActivity", "onResume", after=self.on_profile)
self.hook("org.telegram.messenger.MessagesController", "getUser", "long",
before=self.pre, after=self.post, priority=0)
# полная подмена: оригинал НЕ вызывается, результат берётся из fn
self.hook("...MessagesController", "getUser", "long", replace=self.fake_user)
# все перегрузки метода (или все конструкторы: method="<init>")
self.hook_all("org.telegram.ui.ChatActivity", "onResume", after=self.tweak)
Argument types - strings: "int", "long", "boolean", "float", "java.lang.String", nested classes via $ ("a.b.Outer$Inner"). For a constructor, the method name is "<init>".
frame - call object (MethodHookParam)
| Field/Method | What is this |
frame.thisObject | null for static). |
frame.args | Array of arguments. Changes: frame.args[0] = x (in before). |
frame.getResult() | Current result (in after). |
frame.setResult(x) | Replace the result. IN before |
frame.method | Which method worked (for hook_all). |
def on_profile(self, frame):
activity = frame.thisObject
uid = frame.args[0] # первый аргумент
frame.setResult(42) # подменить результат
orig = self.invoke_original(frame) # вызвать оригинал в обход хуков
Hook management
| Method | Purpose |
invoke_original(frame) | |
unhook_all() | Remove ALL plugin hooks (required in on_unload). |
deoptimize(cls, method, ...types) | Deoptimize the method (for “elusive” inline ones). |
is_hooked(cls, method, ...types) | Is the method now abandoned? |
make_class_inheritable(cls) | Take off final from class (for subclasses). |
allocate_instance(cls) | Create an object WITHOUT calling a constructor. |
java_class creates a REAL subclass (e.g. its View With onDraw). Logic object methods override Java methods; signature - (self, this, *java_args).
class Logic:
def onDraw(self, this, canvas):
BasePlugin.java_super() # super.onDraw(canvas)
p = jclass("android.graphics.Paint")()
self_plugin.paint_color(p, 0x8800FF88)
self_plugin.canvas_draw_circle(canvas, 40, 40, 30, p)
view = self.java_class("android.view.View", Logic(),
arg_types=["android.content.Context"], args=[context])
view.setWillNotDraw(False)
ocl = self.on_click(lambda v: self.bulletin("тап")) # View.OnClickListener
olc = self.on_long_click(lambda v: True) # View.OnLongClickListener
# произвольный интерфейс:
runnable = self.implement(jclass("java.lang.Runnable"), run=lambda s: self.tick())
Don't hook hot methods in PythonTime-lapse onDraw/measure/layout, called hundreds of times per second, gives an ANR when exiting in Python. For drawing, do one java_class-View and draw native canvas_*.
CompatibilityOld plugins with globals before_hook(frame)/after_hook(frame) continue to work. jclass/dynamic_proxy - standard ones from Chaquopy.
Low cost
The hook connects the plugin with implementation details: class, overload, constructor, result type. First look for a public event or controller. Place an optional hook in try/except, a critical hook with a version check and an understandable error. Store hooks only through the SDK (removable unhook_all()
UI and runtimeReflection
Access to private and static private fields of Java classes.
from devgram.hook_utils import find_class, get_private_field, set_private_field
from devgram.hook_utils import get_static_private_field, set_static_private_field
clazz = find_class("org.telegram.ui.ChatActivity")
value = get_private_field(instance, "chatActivityEnterView")
set_private_field(instance, "field", value)
Reflection depends on internal Telegram names and may require updating after changing the base version of the client. Always wrap such operations in error handling.
Private fields
The name private field and its type are not a stable contract. Check for presence before reading, don't change critical fields without fallback behavior, and log only safe information.
Types
The value must match the Java type, including primitive wrappers and nullability. The reflection error can only appear on a specific version of the device.
When to refuse
If the task is solved through the Client API, settings or NotificationCenter, reflection is not needed. The less private access, the easier it is to update the plugin.
DevelopmentDev Server
Local installation .dgplugin via ADB without sending via Telegram.
Preparation
- Copy the port and token.
- Connect your phone and confirm ADB.
Loading via Builder
# Windows PowerShell
$env:DEVGRAM_TOKEN="ваш_токен"
dgb upload
# Linux / macOS
export DEVGRAM_TOKEN="ваш_токен"
dgb upload
Without an argument, Builder first builds the project. The finished archive can be specified explicitly:
dgb upload builds/my.plugin-1.0.0.dgplugin --token "ваш_токен"
How does this work
/upload and passes the token in the header. The server checks the package and sets it enabled.
Diagnostics
adb devices
adb forward tcp:42690 tcp:42690
curl -H "X-DevGram-Token: ваш_токен" http://127.0.0.1:42690/status
Private tokenDon't publish it. If leaked, reset the token in the application.
PublicationSafety
The plugin code runs inside the client process, so trust the package source.
- do not keep secrets in the archive;
- do not block the UI thread;
- shoot observers and pills in
on_unload; - consider account in multi-account callbacks.
Safe modeAfter a fall in callback, DevGram disables callbacks of plugins until manual recovery.
Secure Border
Safe mode helps restore the client after a callback crash, but does not check the author's intentions. Installation remains the user's decision. The directory and changelog must honestly describe the network, hooks, and access to messages and files.
Error isolation
One invalid update should not disable all observers. One optional API shouldn't break loading. Use local try/except and short operations.
Secrets
Do not store bot token, Firebase credentials, signing keys and private URLs in the source code or archive. Scan the entire package and build history before publishing.
Publication.dgplugin problems
Errors in build, verification, installation and download.
The package does not open
- check the extension
.dgplugin; - make sure it's a ZIP;
-
manifest.json and file from field main at the root; - reassemble via
dgb build -a -v -nf.
invalid package manifest
There is no ID, the ID is longer than 64 characters, contains a prohibited character, or main indicates a missing or unsafe path.
missing bundled wheel
For a name from requirements no matching file in wheels/.
Find the first error package load or load in the plugin logs. This is usually an import, an incompatible wheel, or a module-level exception.
The update has rolled back
HTTP 401
Copy the current token. Reset it and update if necessary DEVGRAM_TOKEN.
HTTP 500
Check ADB forwarding, multipart format and Dev Server up-to-date.
After reload the event is called twice
on_unload()
PublicationAPI reference
A short list of public DevGram SDK modules.
| Module | Purpose |
|---|
devgram | BasePlugin, AccountClient, hooks, UI helpers and lifecycle. |
devgram.client_utils | Account-aware controllers, sending and NotificationCenter. |
devgram.text_formatting | Entities, HTML, Markdown and UTF-16. |
devgram.android_utils | Streams, listeners, clipboard and log. |
devgram.file_utils | Text and binary files. |
devgram.intents | Registering and sending Android Intent. |
devgram.hook_utils | Reflection helpers. |
devgram.ui | Settings, AlertDialogBuilder and BulletinHelper. |
BasePlugin shortcuts
toast, me, user_name, chat_name, copy, clipboard, send_message, send_photo, send_file, edit_message, send_request, tl, implement, java_class, hook, bulletin, alert, register_pill.
API Map
Start with BasePlugin and lifecycle, then move on to account-aware client_utils, formatting and UI. Connect Utilities as needed, and use native sections only after checking the public API.
Return Value Contract
None usually means no result or an action without a synchronous return; boolean callbacks report whether the event has been processed; handles have explicit close/unhandle. Always check the specific module table.
Version strategy
The stable plugin ID is preserved, the version is increased, and the minimum app version limits the launch. Breaking changes are accompanied by migration and changelog.
PublicationCookbook
Change the "DevGram" title on the chat screen
The title gives away DialogsActivity.devgramActionBarTitle() and reinstalls it on each onResume - so just setTitle rolls back. Let's hook the method itself:
def on_load(self):
self.hook("org.telegram.ui.DialogsActivity", "devgramActionBarTitle",
after=lambda f: f.setResult("Мой заголовок"))
from java import jclass
MC = jclass("org.telegram.messenger.MessagesController")
MC.getGlobalMainSettings().edit().putInt("dg_actionBarTitle", 0) \
.putString("forkCustomTitle", "Мой заголовок").apply()
Intercept outgoing request
def on_load(self):
self.hook("org.telegram.tgnet.ConnectionsManager", "sendRequest",
"org.telegram.tgnet.TLObject",
"org.telegram.tgnet.RequestDelegate",
before=self._pre)
def _pre(self, frame):
req = frame.args[0]
self.log("запрос: " + req.getClass().getSimpleName())
plugin = self
class Logic:
def onDraw(self, this, canvas):
p = jclass("android.graphics.Paint")(1) # ANTI_ALIAS
plugin.paint_color(p, 0xFFFF3B30)
plugin.canvas_draw_circle(canvas, this.getWidth()/2, this.getHeight()/2,
plugin.dp(12), p)
v = self.java_class("android.view.View", Logic(),
arg_types=["android.content.Context"], args=[ctx])
v.setWillNotDraw(False)
from java import jclass
BF = jclass("org.telegram.ui.Components.BulletinFactory")
BF.of(fragment).createEmojiBulletin(document_id, "Текст", None).show()
Profile is RecyclerListView with adapter and DiffUtil. The row is added, increasing rowCount V updateRowsIds and registering the position in DiffCallback.fillPositions (otherwise DiffUtil will start a phantom animation → crash). For a detailed analysis, see the “Reliable Java hooks” section.
Any recipe with a /View hook requires removal in on_unload: unhook_all(), remove_view(), unregister_pills().
Verified APISDK contracts
The exact module contracts that are included in the current DevGram build. The signatures on this page are verified against the Python runtime client.
Sharing of ResponsibilityDevGramBuilder creates the archive, and the modules devgram.* provide runtime API within the application.
devgram
The root module provides BasePlugin, AccountClient, HookResult, HookStrategy, get_selected_account() And get_client(account=None). A plugin instance is created by the loader. Don't create BasePlugin manually and do not store Android Activity in a global variable: after changing the screen, the link will become obsolete.
BasePlugin Metadata
| Field | Type | Purpose |
|---|
id | str | Permanent unique ID. Used for settings, files, hooks and updates. |
name | str | Display name. |
version | str | Package version; enlarge before publishing. |
author | str | Author or team. |
description | str | Brief description without markup. |
icon | str | PNG/JPG URL for a list of plugins. |
min_app_version | str | Minimum compatible version of DevGram. |
AccountClient
get_client() returns the facade of the current account, and get_client(account) binds operations to the specified index. Available get_messages_controller(), get_user_config(), get_connections_manager() And send_text(dialog_id, text). Account index and Telegram user ID are different values.
from devgram import get_client
def send_for_event(account, dialog_id):
client = get_client(account)
client.send_text(dialog_id, "Сообщение из нужного аккаунта")
devgram.client_utils
All account-aware helpers accept account=None. Without the parameter, the account selected in the interface is used. In event handlers, always pass the received account, otherwise the action may be performed by another profile.
| Function | Result | Application |
|---|
send_text(peer, text, account=None) | None | Sends plain text. |
send_formatted_text(peer, text, entities, account=None) | None | Sends text from native entities. |
get_messages_controller(account=None) | Java object | Users, chats and dialogues. |
get_connections_manager(account=None) | Java object | TL requests and network state. |
get_messages_storage(account=None) | Java object | Local base; perform heavy operations in queue. |
get_file_loader(account=None) | Java object | Paths and downloads of Telegram media. |
get_last_fragment() | BaseFragment/None | Current screen. Always check for None. |
NotificationCenter
observe(notification_id, callback, account=None) returns ObserverHandle. Save handle and call close() when unloading. Re-registering after a hot reload without closing the old handle results in double callbacks.
from devgram.client_utils import observe
def on_load(self):
self._observer = observe(1, self._on_notification)
def _on_notification(self, notification_id, account, args):
self.log("notification account=" + str(account))
def on_unload(self):
if self._observer:
self._observer.close()
self._observer = None
devgram.text_formatting
Entity(type, offset, length, url=None, language=None) describes the range in UTF-16. Available parse_html(), parse_markdown(), parse_text(), to_tlrpc_entities(), utf16_length(), escape_markdown() And escape_html(). Do not calculate the length using len()if the string contains emoji or non-BMP characters.
from devgram.text_formatting import Entity, utf16_length
from devgram.client_utils import send_formatted_text
text = "🔥 DevGram"
entities = [Entity("bold", 0, utf16_length(text))]
send_formatted_text(dialog_id, text, entities, account=account)
Parsing custom text
parse_text(text, parse_mode) returns cleared text and entities. Use parse_mode="html" or parse_mode="markdown". Escape user data before adding it to the template, otherwise the entered characters will become markup.
from devgram.text_formatting import escape_html, parse_text
source = "Автор: " + escape_html(user_name)
plain, entities = parse_text(source, "html")
devgram.file_utils
The module contains ensure_dir_exists(), list_dir(), read_file(), write_file(), binary options and delete_file(). list_dir(path, extensions=None, recursive=False, include_files=True, include_dirs=False) can filter extensions and traverse the tree.
from devgram.file_utils import ensure_dir_exists, write_file, read_file
folder = ensure_dir_exists(self.plugin_files_dir() + "/cache")
path = folder + "/state.json"
write_file(path, '{"enabled": true}')
state = read_file(path)
Check paths received from Intent or message. Prohibit .., absolute paths and leaving the plugin directory. Write JSON atomically if the file is important for state recovery.
devgram.android_utils
| Function | Contract |
|---|
run_on_ui_thread(func, delay=0) | Runs a callback in the UI queue; delay is specified in milliseconds. |
run_on_queue(func) | Moves files, network and computation to the background queue. |
log(data) | Writes a line with the DevGram prefix to FileLog. |
copy_to_clipboard(text) | Copies text via the system clipboard. |
OnClickListener(fn) | Creates a Java listener for Android View. |
OnLongClickListener(fn) | Callback should return boolean. |
from devgram.android_utils import run_on_queue, run_on_ui_thread
def refresh(self):
run_on_queue(self._load)
def _load(self):
result = read_remote_data()
run_on_ui_thread(lambda: self.bulletin("Данные обновлены"))
devgram.intents
register(callback, action=None, scheme=None, host=None, path=None, query=None, category=None, flags=0, priority=0) registers an incoming route. The higher priority is checked first. Callback receives IntentContext with margins intent, action, uri, parsed And query. Return strictly Trueto stop dispatch.
from devgram.intents import register, open_uri
def on_load(self):
self._route = register(self._open, scheme="devgram", host="plugin", priority=100)
def _open(self, context):
item = context.query.get("id", [None])[0]
if not item:
return False
self.bulletin("Открыт объект " + item)
return True
def on_unload(self):
self._route.unhandle()
open_uri(uri) passes the URI to Android resolver, and send(intent) launches an explicitly created Intent. Don't run untested schemas from remote data.
devgram.hook_utils
Reflection API includes find_class(), get_private_field(), set_private_field(), as well as static options. This is a low-level API: the names of private fields change between Telegram versions. Wrap lookup in error handling and declare the minimum version of DevGram.
devgram.ui
Settings are built from Header, Switch, Input, Selector, Text And Button. Dialogs are created by a chain of methods AlertDialogBuilder: title, message, positive, negative and neutral buttons.
from devgram.ui.alert import AlertDialogBuilder
AlertDialogBuilder() \
.set_title("Удалить данные?") \
.set_message("Действие нельзя отменить") \
.set_positive_button("Удалить", self.clear_data) \
.set_negative_button("Отмена") \
.show()
Threads and lifecycle
- IN
on_load() only register resources and run short operations. - Change all Android Views and notifications to UI thread.
- Execute files, JSON, network and large loops in queue.
- IN
on_unload() close observers, intent handles, hooks and pills. - Cleanup must be idempotent: calling it again should not fail.
- Callback after unloading should check that the plugin is still active.
Compatibility
The public Python API is more stable than Java reflection, but is also being developed. Do not import private names with underscores. Before publishing, test the installation, updating over the old version, disabling, re-enabling, hot reload, deleting and launching after safe mode.
Verified API · android_utilsAndroid Utils
Detailed help on the module android_utils, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
Functions
| Name | Signature | Purpose |
|---|
run_on_ui_thread | run_on_ui_thread(func, delay=0) | Working function of the module; check the return value and execution flow. |
run_on_queue | run_on_queue(func) | Working function of the module; check the return value and execution flow. |
log | log(data) | Working function of the module; check the return value and execution flow. |
copy_to_clipboard | copy_to_clipboard(text) | Working function of the module; check the return value and execution flow. |
R | R(fn) | Working function of the module; check the return value and execution flow. |
OnClickListener | OnClickListener(fn) | Working function of the module; check the return value and execution flow. |
OnLongClickListener | OnLongClickListener(fn) | Working function of the module; check the return value and execution flow. |
Import example
from android_utils import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · client_utilsClient Utils
Detailed help on the module client_utils, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
Functions
| Name | Signature | Purpose |
|---|
send_text | send_text(peer, text, account=None) | Working function of the module; check the return value and execution flow. |
send_formatted_text | send_formatted_text(peer, text, entities, account=None) | Working function of the module; check the return value and execution flow. |
get_messages_controller | get_messages_controller(account=None) | Working function of the module; check the return value and execution flow. |
get_user_config | get_user_config(account=None) | Working function of the module; check the return value and execution flow. |
get_connections_manager | get_connections_manager(account=None) | Working function of the module; check the return value and execution flow. |
get_account_instance | get_account_instance(account=None) | Working function of the module; check the return value and execution flow. |
get_send_messages_helper | get_send_messages_helper(account=None) | Working function of the module; check the return value and execution flow. |
get_media_data_controller | get_media_data_controller(account=None) | Working function of the module; check the return value and execution flow. |
get_contacts_controller | get_contacts_controller(account=None) | Working function of the module; check the return value and execution flow. |
get_messages_storage | get_messages_storage(account=None) | Working function of the module; check the return value and execution flow. |
get_notification_center | get_notification_center(account=None) | Working function of the module; check the return value and execution flow. |
get_file_loader | get_file_loader(account=None) | Working function of the module; check the return value and execution flow. |
get_media_controller | get_media_controller() | Working function of the module; check the return value and execution flow. |
get_notifications_controller | get_notifications_controller(account=None) | Working function of the module; check the return value and execution flow. |
get_notifications_settings | get_notifications_settings(account=None) | Working function of the module; check the return value and execution flow. |
get_location_controller | get_location_controller(account=None) | Working function of the module; check the return value and execution flow. |
get_secret_chat_helper | get_secret_chat_helper(account=None) | Working function of the module; check the return value and execution flow. |
get_download_controller | get_download_controller(account=None) | Working function of the module; check the return value and execution flow. |
get_last_fragment | get_last_fragment() | Working function of the module; check the return value and execution flow. |
observe | observe(notification_id, callback, account=None) | Observe one NotificationCenter event and return a removable ObserverHandle. |
_account | _account(account) | Working function of the module; check the return value and execution flow. |
NotificationCenterDelegate
Subclass and override didReceivedNotification(id, account, args).
Methods
| Method | Signature | Contract |
|---|
didReceivedNotification | didReceivedNotification(self, notification_id, account, args) | Check argument types and release created resources when unloading the plugin. |
ObserverHandle
Public class DevGram SDK.
Methods
| Method | Signature | Contract |
|---|
__init__ | __init__(self, center, delegate, notification_id) | Check argument types and release created resources when unloading the plugin. |
close | close(self) | Check argument types and release created resources when unloading the plugin. |
Import example
from client_utils import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · dev_serverDev Server
Detailed help on the module dev_server, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
Functions
| Name | Signature | Purpose |
|---|
_connect | _connect(platform, host, port, wait) | Working function of the module; check the return value and execution flow. |
start_debugger | start_debugger(platform='vscode', host='127.0.0.1', port=5678, wait=False) | Connect to a debugger exposed by the computer through adb reverse. |
stop_debugger | stop_debugger() | Working function of the module; check the return value and execution flow. |
debugger_status | debugger_status() | Working function of the module; check the return value and execution flow. |
Import example
from dev_server import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · file_utilsFile Utils
Detailed help on the module file_utils, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
Functions
| Name | Signature | Purpose |
|---|
ensure_dir_exists | ensure_dir_exists(path) | Working function of the module; check the return value and execution flow. |
list_dir | list_dir(path, extensions=None, recursive=False, include_files=True, include_dirs=False) | Working function of the module; check the return value and execution flow. |
read_file | read_file(path) | Working function of the module; check the return value and execution flow. |
write_file | write_file(path, content) | Working function of the module; check the return value and execution flow. |
read_file_bytes | read_file_bytes(path) | Working function of the module; check the return value and execution flow. |
write_file_bytes | write_file_bytes(path, content) | Working function of the module; check the return value and execution flow. |
delete_file | delete_file(path) | Working function of the module; check the return value and execution flow. |
Import example
from file_utils import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · hook_utilsHook Utils
Detailed help on the module hook_utils, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
Functions
| Name | Signature | Purpose |
|---|
find_class | find_class(class_name) | Working function of the module; check the return value and execution flow. |
_field | _field(clazz, name) | Working function of the module; check the return value and execution flow. |
get_private_field | get_private_field(obj, name) | Working function of the module; check the return value and execution flow. |
set_private_field | set_private_field(obj, name, value) | Working function of the module; check the return value and execution flow. |
get_static_private_field | get_static_private_field(clazz, name) | Working function of the module; check the return value and execution flow. |
set_static_private_field | set_static_private_field(clazz, name, value) | Working function of the module; check the return value and execution flow. |
Import example
from hook_utils import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · intentsIntents
Detailed help on the module intents, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
Functions
| Name | Signature | Purpose |
|---|
register | register(callback, *, action=None, scheme=None, host=None, path=None, query=None, category=None, flags=0, priority=0) | Register an incoming-intent handler. Return True from callback to consume it. |
_matches | _matches(entry, context) | Working function of the module; check the return value and execution flow. |
dispatch | dispatch(intent) | Working function of the module; check the return value and execution flow. |
open_uri | open_uri(uri) | Open a URI using Android's resolver. |
send | send(intent) | Launch an explicitly constructed Android Intent. |
IntentContext
Public class DevGram SDK.
Methods
| Method | Signature | Contract |
|---|
__init__ | __init__(self, intent) | Check argument types and release created resources when unloading the plugin. |
HandlerHandle
Public class DevGram SDK.
Methods
| Method | Signature | Contract |
|---|
__init__ | __init__(self, entry) | Check argument types and release created resources when unloading the plugin. |
unhandle | unhandle(self) | Check argument types and release created resources when unloading the plugin. |
Import example
from intents import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · text_formattingText Formatting
Detailed help on the module text_formatting, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
Functions
| Name | Signature | Purpose |
|---|
to_tlrpc_entities | to_tlrpc_entities(entities) | Convert Entity objects/dicts to Java ArrayList<TLRPC.MessageEntity>. |
add_surrogates | add_surrogates(text) | Working function of the module; check the return value and execution flow. |
remove_surrogates | remove_surrogates(text) | Working function of the module; check the return value and execution flow. |
utf16_length | utf16_length(text) | Working function of the module; check the return value and execution flow. |
parse_html | parse_html(text) | Working function of the module; check the return value and execution flow. |
parse_markdown | parse_markdown(text) | Working function of the module; check the return value and execution flow. |
_python_index_for_utf16 | _python_index_for_utf16(text, offset) | Working function of the module; check the return value and execution flow. |
parse_text | parse_text(text, parse_mode=None) | Working function of the module; check the return value and execution flow. |
escape_markdown | escape_markdown(text) | Working function of the module; check the return value and execution flow. |
escape_html | escape_html(text) | Working function of the module; check the return value and execution flow. |
Entity
Public class DevGram SDK.
_HTMLToEntities
Public class DevGram SDK.
Methods
| Method | Signature | Contract |
|---|
__init__ | __init__(self) | Check argument types and release created resources when unloading the plugin. |
length | length(self) | Check argument types and release created resources when unloading the plugin. |
handle_data | handle_data(self, data) | Check argument types and release created resources when unloading the plugin. |
handle_starttag | handle_starttag(self, tag, attrs) | Check argument types and release created resources when unloading the plugin. |
handle_endtag | handle_endtag(self, tag) | Check argument types and release created resources when unloading the plugin. |
Import example
from text_formatting import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · ui.alertUi.Alert
Detailed help on the module ui.alert, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
AlertDialogBuilder
Public class DevGram SDK.
Methods
| Method | Signature | Contract |
|---|
__init__ | __init__(self, context=None, progress_style=0, resources_provider=None) | Check argument types and release created resources when unloading the plugin. |
set_title | set_title(self, value) | Check argument types and release created resources when unloading the plugin. |
set_message | set_message(self, value) | Check argument types and release created resources when unloading the plugin. |
set_positive_button | set_positive_button(self, text, listener=None) | Check argument types and release created resources when unloading the plugin. |
set_negative_button | set_negative_button(self, text, listener=None) | Check argument types and release created resources when unloading the plugin. |
set_neutral_button | set_neutral_button(self, text, listener=None) | Check argument types and release created resources when unloading the plugin. |
create | create(self) | Check argument types and release created resources when unloading the plugin. |
show | show(self) | Check argument types and release created resources when unloading the plugin. |
dismiss | dismiss(self) | Check argument types and release created resources when unloading the plugin. |
Import example
from ui.alert import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · ui.bulletinUi.Bulletin
Detailed help on the module ui.bulletin, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
BulletinHelper
Public class DevGram SDK.
Methods
| Method | Signature | Contract |
|---|
_show | _show(kind, message, duration, button, callback) | Check argument types and release created resources when unloading the plugin. |
show_info | show_info(cls, message, fragment=None, duration=DURATION_LONG, button=None, callback=None) | Check argument types and release created resources when unloading the plugin. |
show_success | show_success(cls, message, fragment=None, duration=DURATION_SHORT, button=None, callback=None) | Check argument types and release created resources when unloading the plugin. |
show_error | show_error(cls, message, fragment=None, duration=DURATION_LONG, button=None, callback=None) | Check argument types and release created resources when unloading the plugin. |
show | show(cls, message, kind='info', duration=DURATION_LONG, button=None, callback=None, fragment=None) | Check argument types and release created resources when unloading the plugin. |
Import example
from ui.bulletin import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Verified API · ui.settingsUi.Settings
Detailed help on the module ui.settings, verified with the sources of the current DevGram runtime.
CompatibilityThe following symbols are considered public. Before publishing, check for API availability in the target version of DevGram and handle Java bridge errors.
Setting
Public class DevGram SDK.
Methods
| Method | Signature | Contract |
|---|
__init__ | __init__(self, key='', text='', default=None, on_change=None, items=None, **kwargs) | Check argument types and release created resources when unloading the plugin. |
as_row | as_row(self) | Check argument types and release created resources when unloading the plugin. |
Header
Public class DevGram SDK.
Methods
| Method | Signature | Contract |
|---|
__init__ | __init__(self, text='', **kwargs) | Check argument types and release created resources when unloading the plugin. |
Switch
Public class DevGram SDK.
Input
Public class DevGram SDK.
Selector
Public class DevGram SDK.
Text
Public class DevGram SDK.
Button
Public class DevGram SDK.
Import example
from ui.settings import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Errors and diagnostics
Wrap the call in try/except, record the context via devgram.android_utils.log() and show the user a short message via bulletin. Do not send stack traces to chat and do not leave secrets in logs.
How to read signatures
Parameters with default do not need to be passed. For Java objects check None before accessing the fields. Python exceptions mean a local preparation error, and Telegram error comes as a separate callback argument.
Flow of execution
Don't run network, large files, JSON, or heavy calculations during import or on the UI thread. Use run_on_queue(), and return the result via run_on_ui_thread(). After unloading, the callback must check that the instance is still active.
Resource life cycle
Any registration object belongs to the calling plugin. Save it in the field, close it in on_unload() and reset the link. Cleanup should work after partially completed on_load() and when called again.
Error Handling
def safe_call(self, operation):
try:
return operation()
except Exception as error:
self.log("DevGram operation failed: " + repr(error))
self.bulletin("Операция недоступна", kind="error")
return None
The user message explains the next action, but does not reveal the traceback. Leave the details in the debug log without tokens or personal data.
Compatibility
Public helper is preferable to Java lookup. For optional APIs, use feature detection and disable only the dependent feature. Don't rely on layout and View indexes that change between versions.
Examination
- Default and explicit account.
- Empty, long and Unicode values.
- Call again after reload.
- Call after closing the screen.
- Network, file and RPC error.
Developer guidePlugin architecture
How to divide a plugin into lifecycle, services, event handlers, storage and interface so that reload and updates are predictable.
Limits of responsibility
Entry point should remain small: it links the lifecycle and delegates work to individual objects. Don't put network client, parser, cache and UI in one class. Event handlers must quickly check the condition and leave the hard work to the service.
class Plugin(BasePlugin):
def on_load(self):
self.storage = Storage(self)
self.service = MessageService(self.storage)
self.routes = RouteRegistry(self)
self.routes.start()
def on_unload(self):
self.routes.close()
self.service.close()
State
Separate persistent state, session cache and references to Android objects. Persistent data is recorded in private storage; The cache can be lost when reloading; View, Fragment and Activity cannot be saved between screens. Get the current Fragment just before showing the UI.
Dependencies
Pass services through the constructor. This makes the code testable and eliminates hidden global singletons. Circular dependencies usually mean that the overall contract needs to be put into a separate module.
Closing
Every object that registers a listener, observer, hook, intent route, or pill must have close(). The closure is executed in the reverse order of creation and can be called again.
Developer guide.plugin format
The simplest plugin format is one file with code. Ideal for small plugins without resources or dependencies.
What is this
.plugin (or .py) is a regular Python file with a descendant class BasePlugin and the line plugin = MyPlugin() at the end. None manifest.json and archive: metadata (id, name, version, author, description, icon) are taken directly from the class attributes. The code is executed by built-in Python 3.11 inside the client - without root and without external Xposed.
Minimal example
from devgram import BasePlugin
class Hello(BasePlugin):
id = "hello.devgram"
name = "Hello"
version = "1.0.0"
author = "@you"
description = "Мой первый плагин"
icon = "https://example.com/plugin-icon.png"
def on_load(self):
self.toast("Hello from DevGram")
plugin = Hello()
Save the file with the extension .plugin (eg. hello.plugin). Any text editor will do; Python standard library is available, third party packages are not.
Installation and distribution
- Send a file
.plugin to chat or channel. - Tap on the file → installation card (icon, name, version, author, verification icon) → “Install”.
- Management: Settings → DevGram → Plugins (toggle switches, settings, “Share”, deletion).
Plugins from the developer channel with the 🧩 icon are considered automatically verified. The installation card is the same for .plugin And .dgplugin.
.plugin or .dgplugin?
- .plugin — one file, without resources and dependencies. Just write and send. Suitable for most plugins.
- .dgplugin - archive with
manifest.json, modules, assets/, locales/ And wheels/. Needed when there is localization, images, several modules or pip dependencies (see the next section).
Package .dgplugin.dgplugin format
The exact structure, verification, installation and update of the package plugin.
What is this
.dgplugin — ZIP archive with mandatory manifest.json and Python entry point. The extension cannot be changed.
plugin.dgplugin
├── manifest.json
├── main.py
├── helpers.py
├── assets/
├── locales/
└── wheels/
Loader validation
- a valid ZIP and JSON object in
manifest.json; - a valid
id and an existing .py entry point; - no
.pyc or .pyo files; - no absolute paths,
.. components or duplicate names; - at most 4096 files;
- every requirement has a matching bundled wheel;
- minimum versions are compatible with the installed DevGram.
Installation and updates
DevGram validates the package, copies it to a temporary file, and only then replaces the installed version. If loading fails, the previous archive is restored. Files are extracted to the private .devgram/<id> directory.
Correct build
dgb build -a -v -nf
# builds/username.my_plugin-1.0.0.dgplugin
Do not create the archive manually: Builder creates the correct root, manifest and reproducible file order.
Open sources only
Builder keeps Python code in .py files. DevGram rejects packages containing .pyc or .pyo during installation and moderation submission.
Developer guideThreads and Performance
Rules for working with UI thread, background queue, network, files and frequent Telegram callbacks.
UI thread
Short changes to the interface are allowed on the UI thread: creating a bulletin, updating the View, and showing a dialog. Directory reading, JSON parsing, HTTP, hashing and large message processing should be done via run_on_queue().
Return result
def load(self):
run_on_queue(self._load_background)
def _load_background(self):
try:
data = read_and_parse()
except Exception as error:
run_on_ui_thread(lambda: self.bulletin("Ошибка загрузки", kind="error"))
return
run_on_ui_thread(lambda: self.render(data))
Frequent callbacks
The message sending hook can be called frequently, and UI callbacks can be called several times per frame. Do not perform reflection lookup or disk access in them. Cache immutable class references, apply debounce to lookups, and coalesce to a series of identical updates.
Cancel
Background callback may complete after the plugin is unloaded. Store a generation token or activity flag and check it before updating the UI. Don't keep strong links to a closed Fragment.
Measurement
Log time only around the suspicious block. Remove detailed timing logs before release. If the callback takes more than one frame, move the work out of the UI thread.
Developer guideData and Migrations
How to store plugin settings and files, survive updates and recover from a corrupted state.
Settings
get_setting() And set_setting() suitable for small scalar values. Convert booleans and numbers explicitly because bridge can return a string representation. For structured data, use a JSON file in private storage.
Schema version
def migrate(self):
version = int(self.get_setting("schema", "0"))
if version < 1:
self.set_setting("enabled", "1")
version = 1
if version < 2:
migrate_cache_file(self.plugin_files_dir() + "/state.json")
version = 2
self.set_setting("schema", str(version))
Migrations are performed sequentially and must be idempotent. Do not write a new schema version until the step has completed successfully.
Atomic write
First write new JSON to a temporary file, sync and replace the main file. If there is a read error, save the damaged file for diagnostics and run with safe defaults.
Secrets
The package and private storage are not protected vault. Do not put bot tokens and persistent keys in the plugin. If the service requires authorization, use short-lived user tokens and the ability to revoke them.
Developer guideTL API and requests
Creating Telegram TL objects, sending requests and handling errors without being tied to the selected account.
Create a request
Get class via self.tl() or Java bridge, create an object and fill in the required fields. Check the type of each field with the current Telegram schema.
request = self.tl("TL_messages_getHistory")
request.peer = input_peer
request.offset_id = 0
request.offset_date = 0
request.add_offset = 0
request.limit = 20
request.max_id = 0
request.min_id = 0
request.hash = 0
self.send_request(request, self._result, account=account)
Callback
Callback receives response and error. Telegram RPC error is not a Python exception, so check error first. Do not access response fields until you have checked its type.
Flood wait
Do not repeat the request immediately. Show a clear message or queue the operation with a specified delay. Automatic loops must have a page limit and a backoff.
Multi-account
InputPeer, controller and request must belong to the same account. You cannot get a peer from the cache of the first account and send it through the ConnectionsManager of the second.
Developer guideInterface design
Native settings, bulletins, dialogs and interactive elements without Activity leaks or conflicts with the theme.
Component selection
Bulletin is suitable for a short action result and an optional button. Alert is used for confirmation or selection that cannot be made by chance. Permanent parameters are placed in settings rows. Don't show dialog for normal successful action.
Subject
Do not specify black or white text directly. Use theme keys or native DevGram components. Check day, night and system theme, larger font and long localization.
States
The screen must have loading, empty, error and content states. After an action, update the current model immediately, without forcing the user to close and open the screen. For a network, an optimistic update is only allowed if the rollback is correct.
Availability
Icon-only buttons must have a content description. Touch target should not become less than the system minimum. Don't just color code your status: add text or a familiar symbol.
Lifecycle
Before showing the UI, get the current Fragment and check for its presence. The callback of a button should not capture a closed Activity. After the plugin is unloaded, registered widgets are deleted.
Developer guideReliable Java hooks
Practice of low-level hooks and reflection, taking into account Telegram updates and the risk of process crashes.
When you need a hook
Use public events and the Client API first. Hook is justified if the required extension point is not available. It binds the plugin to a specific Java class implementation and requires stricter compatibility checking.
Search method
Check the full class name, parameter types, static/instance and return type. Overload cannot be selected by name alone. After updating the client, lookup may return a different method or fail.
Before and after
Before-hook can change the arguments or override the original; after-hook can replace the result. The returned object must be compatible with the Java type. Don't replace primitive with a value None.
Protection
def install(self):
try:
clazz = find_class("org.telegram.example.Target")
self._hook = self.hook(clazz, "method", self._before)
except Exception as error:
self.log("hook unavailable: " + repr(error))
self._hook = None
Unloading
Unhook should always be executed. Callback checks for plugin activity and does not hold View. If the hook is critical, declare a minimal version; if optional, disable only the related feature.
Developer guideTesting the plugin
Matrix of checks before publication: clean installation, update, accounts, themes, lifecycle and error recovery.
Minimum matrix
| Scenario | What to check |
|---|
| Clean install | Manifest, defaults, first launch, permissions. |
| Update | Migrations, saving settings, replacing resources. |
| Disable/enable | Observers are not duplicated, the UI disappears and comes back. |
| Hot reload | Python modules have been cleaned, hooks and handles have been replaced. |
| Uninstall | No callbacks or registered pills. |
| Safe mode | The client starts after an intentional plugin error. |
Accounts
Check at least two accounts, switching during a background operation, and an event from an unselected account. No operation should silently use the current UI account instead of the account event.
Interface
Check light and dark theme, small and large screen, system font scale, long Russian and English text, empty lists, offline and repeated request.
Negative cases
Corrupt the JSON, remove the asset, return an RPC error, close the screen before the callback completes, and call cleanup twice. The plugin should degrade locally without breaking the directory and application.
Developer guidePublishing .dgplugin
Preparing a version for users and the DevGram directory.
dgb build -a -v -nf
Use -c 2 only if necessary; the original assembly is easier to review and moderate.
Before publication
- Enlarge
version, without changing id. - Check the description, author, HTTPS icon and minimum versions.
- Check the list of archive files.
- Install cleanly and over the previous version.
- Check enable, disable, reload, delete and safe mode.
- Send the file exactly from
builds/.
Updates
The same ID updates an existing plugin. Replacement is atomic; If the download fails, the previous archive is restored.
Changelog
Describe custom changes, new permissions or network calls, minimum version, and incompatible changes.
Developer guideSecurity model
The plugin runs inside the DevGram process and has access to sensitive context, so security starts with minimal permissions and transparent behavior.
Trusted Boundary
Python sandbox does not make unknown code safe. The plugin can access the Java bridge and client data. Install packages only from a clear source and show the user why the network, files, or message interception are needed.
Deleted data
Any data from Telegram, Intent, HTTP and files are considered untrusted. Limit sizes, validate JSON schema, normalize paths, and escape markup. Do not pass a custom string to reflection lookup.
Net
Use HTTPS, timeouts and response limiting. Do not disable certificate verification. Do not send messages, user IDs or tokens to analytics without an explicit purpose.
Logs
Do not log access tokens, cookies, full text of private messages and file contents. For diagnostics, use the short request ID, operation type and safe error code.
Fault tolerance
An error in one function disables it locally. Do not create an endless retry and do not fall into the callback of the main thread. Safe mode should allow the user to remove the problematic package.
Developer guideCompatibility and versions
How to support multiple versions of DevGram and Telegram core without random crashes due to missing methods.
Stability levels
Public functions devgram.* are the preferred level. Java controllers change along with Telegram. Private reflection and method hooks are the most fragile level and require a version gate.
Feature detection
Check for the presence of a class, method or attribute, not just the version string. The version is useful for a clear message, but different builds may have a different set of backports.
try:
target = find_class(CLASS_NAME)
except Exception:
target = None
if target is None:
self.bulletin("Функция недоступна в этой версии")
Graceful fallback
Optional integrations are disabled separately, but other features continue to work. If the plugin is meaningless without an API, stop loading with clear diagnostics and the minimum supported version.
Deprecated API
Once a replacement appears, maintain the old path for a limited time. Do not remove a setting or change its meaning without migration. Indicate deprecated methods in documentation and changelog.
Developer guideDiagnostics and debugging
A systematic approach to import errors, blank screens, freezes, double callbacks and crashes after updating.
Import
First check the full traceback and the first frame inside the plugin. Typical reasons: the file is missing from the archive, the case of the name does not match, wheel is incompatible, or a circular import has occurred.
Empty interface
Separate data loading and render. Show loading before the operation begins, then content, empty or error. If the data has arrived and the screen is empty, log the generation of the screen and check that the callback has returned to the UI thread.
Double action
Almost always there remains an observer, hook or listener from the previous reload. Add registration log and cleanup with instance ID. Make sure on_unload() closes each handle.
Freeze
Remove the callback execution time and find the I/O on the main thread. Long-pressing, scrolling, or highlighting will often call hook multiple times; there should be no hard work inside it.
Crash after closing the screen
The Background callback holds the old Context or View. Before rendering, get the current Fragment, check the activity and cancel the update if the generation has changed.
Bug Report
Specify the DevGram version, Android SDK, device model, plugin ID/version, sequence of actions and full stack trace. Delete secrets and personal messages.
Developer guideGlossary
Key terms of DevGram Plugin SDK and differences between similar objects.
Account index
Local account number in the application. Not equal to Telegram user ID and is used to select controller.
Dialog ID
A conversation ID that can represent a user, group, or channel. Don't treat the number sign as your only type check.
TL object
Java type representation from Telegram schema. Fields and subclasses depend on the current version of core.
Observer
Subscribe to NotificationCenter. The returned handle belongs to the plugin and is closed upon unload.
Hook
Intercepting a Java method before or after the original call. It differs from the public plugin event in that it is more strictly dependent on the implementation.
Entity
Telegram text formatting range. Offset and length are measured in UTF-16 code units.
Safe mode
Recovery mode, in which problematic callbacks are not launched until manual action by the user.
Hot reload
Unloading instance and Python modules and then loading a new version without completely restarting the client.
Pill
A compact interactive Pill Stack element registered with a specific plugin ID.
Builder
Official CLI for creating a project, checking sources and building .dgplugin. Runtime APIs provide regular modules devgram.*.
Reference indexDeep reference
Navigation page for the complete DevGram SDK reference.
Stable Python API
Start with SDK contracts, then open the page for the desired verified module. It lists the actual classes, functions, signatures, and methods of the current assembly.
How-To Guides
For a large plugin, use the pages about architecture, flows, migrations, testing, and security.
Low-level runtime
Java hooks, reflection, class proxy and TL API depend on Telegram core. Check for API availability and provide fallback.
DevGramBuilder
Builder is responsible for the structure and packaging of the project. Executable code uses public modules devgram.*.
BasePlugin: contract and state
BasePlugin created by the loader once per package. Fields id, name, version, author, description And icon used by the directory and plugin manager. Don't change id after publication: it is the key for settings, private storage and registered hooks.
| Method | Call | Contract |
|---|
on_load() | After import | Registration hooks, observers, pills. Don't block the UI. |
on_unload() | Before reload/remove | Remove everything created by the plugin. Recall is acceptable. |
on_send_message(text) | Before outgoing text | Return new text, None without change or False to cancel. |
on_receive_message(text) | After incoming text | Observation and logic; message change is not guaranteed. |
on_update(update) | TL update | Java object TLRPC.Update. Check the type via getClass().getSimpleName(). |
on_setting_changed(key,value) | From native settings | The value comes as a string; convert explicitly. |
HookResult and strategy
For account-aware callback you can return HookResult. PASS leaves original behavior, REPLACE replaces the result, and the passed parameters replace the arguments before calling the original. Don't return a random Python object: the type must match the Java method.
from devgram import HookResult, HookStrategy
def on_send_message_hook(self, account, params):
if self.get_setting("block_links", "0") == "1":
return HookResult(HookStrategy.CANCEL)
return HookResult(HookStrategy.PASS)
client_utils: account and controllers
All functions accept optional account. If it is not transmitted, the selected account is used. For events, always pass account manually.
| Function | Returns | When to use |
|---|
send_text(peer,text,account) | None | Simple text. |
send_formatted_text(peer,text,entities,account) | None | Text with native MessageEntity. |
get_messages_controller(account) | MessagesController | Dialogues, users and chats. |
get_connections_manager(account) | ConnectionsManager | Low-level TL queries. |
get_messages_storage(account) | MessagesStorage | Reading the local cache. Don't block main. |
get_media_data_controller(account) | MediaDataController | Media and stickers. |
get_contacts_controller(account) | ContactsController | Contacts and username. |
get_file_loader(account) | FileLoader | Loading local media files. |
get_download_controller(account) | DownloadController | Download queue. |
Request callbacks
request = self.tl("TL_messages_getHistory")
request.peer = peer
request.limit = 20
def result(response, error):
if error:
self.log("Telegram error: " + str(error))
return
self.run_on_ui(lambda: self.bulletin("История получена"))
self.send_request(request, result)
NotificationCenter
observe() returns ObserverHandle. Store handle in an object field, close it in on_unload(). Callback receives a numerical notification ID, account and a Java array of arguments.
text_formatting
Entity has fields type, offset, length, and for links and pre - url And language. Offset and length are always UTF-16, not Python code points. This is fundamental for emoji.
from devgram.text_formatting import Entity, utf16_length
text = "🔥 DevGram"
entity = Entity("bold", 0, utf16_length(text))
send_formatted_text(peer, text, [entity], account=account)
Settings rows
| Class | kind | Fields |
|---|
Header(text) | header | Section title. |
Switch(key,text,default,on_change) | switch | Boolean is stored as 1/0. |
Input(key,text,default,on_change) | text | String value. |
Selector(key,text,items,on_change) | selector | List of options via separator. |
Button(key,text) | button | Calling on_setting_click. |
android_utils
run_on_ui_thread(fn,delay) puts a callback in the Telegram UI queue. run_on_queue(fn) uses global background queue. copy_to_clipboard() and bulletin should be called after returning to the UI.
file_utils and private storage
Global file helpers work with the absolute path passed in. Methods self.read_file() And self.write_file() work only inside the private folder of the current plugin ID. Don't use path from user input without validation.
intents
register() sorts handlers by priority. Callback should return Trueif the Intent is processed; otherwise dispatch will continue searching. HandlerHandle.unhandle() returns False if handle has already been removed.
Java class proxy
java_class(base,logic,arg_types,args) creates DexMaker subclass. Names of callable methods in logic become overrides. Inside override use BasePlugin.java_super(). The class must not be final and must have a compatible constructor.
Publication checklist
- Manifest is valid, ID is stable, version has been increased.
- No bot tokens, Firebase keys, signing keys and debug logs.
- Verified Android 7+ and at least two accounts.
- Install, update, disable, enable, reload and uninstall have been checked.
- After a crash, safe mode restores the application.
- Observer, hook, intent and pill are cleared in unload.
Extended referenceAdvanced reference
Materials from the old documentation have been transferred to a modern structure: practical Android scripts, CallFrame, visual effects, chat folders and ways to explore Telegram classes.
CallFrame object
A CallFrame represents a specific intercepted Java method call. It provides a receiver, an array of arguments, and control over the result. Before changing the argument, check the index, Java type and nullability. For overload, match the full method signature first, otherwise the hook might apply to a different implementation.
def before(frame):
args = frame.args
if args and args[0] is not None:
args[0] = str(args[0]).strip()
def after(frame):
result = frame.result
if result is None:
return
# Подмена допустима только совместимым Java-типом.
Types of hook arguments
Primitive types require an exact boolean/int/long-compatible value. Java String can be created from Python str via bridge, and complex Telegram objects cannot be replaced with a dictionary or arbitrary Python class. For a nullable parameter None only valid if the Java contract actually allows null.
Substitution of argument and result
Change the screen title before the original method, and process the returned object after. Do not override premium flags, authorization state, or server restrictions: a local value does not grant server rights and may break the interface.
Useful Telegram classes
| Region | Examples | Purpose |
|---|
| Screens | LaunchActivity, BaseFragment, ChatActivity | Navigation and current context. |
| Cells | Dialog, message and settings cells | Drawing list strings; Depends heavily on the version. |
| Data | MessagesController, MessagesStorage | Cache, users, chats and messages. |
| Net | ConnectionsManager | TL requests and connection state. |
How to examine a class and method
- Find a custom action that triggers the desired behavior.
- Find the corresponding Java class in the sources of the current version.
- Check overload, parameter types and return type.
- Install a temporary diagnostic hook without changing the behavior.
- Remove verbose logging after signature confirmation.
Do not transfer private field names from the old APK without checking: Telegram regularly changes its class structure.
Visual effects
Blur, glass panel, tint and border should take into account the theme, device performance and screen lifecycle. The effect is created after the View appears, and the resources are released when detached. On a weak GPU, reduce the blur radius and disable frame-by-frame updating.
Floating card
panel = self.glass_panel(
source_view,
corner=20,
blur=14,
tint=0x22FFFFFF,
border=0.5,
)
# Добавьте panel в контейнер экрана на UI thread.
# Удалите panel при закрытии Fragment.
GPU shader AGSL
RuntimeShader is not available on all Android APIs. Check the SDK, shader source compilation and provide the usual drawable fallback. Don't pass custom text to the shader source and don't create a new shader every frame.
Chat folders and tabs
Cyclic swipe of folders requires knowledge of the currently selected filter ID and the number of available tabs. Do not calculate the next tab by View position: the order may change after synchronization. Store the ID, not the index.
def next_filter(filters, current_id):
ids = [item.id for item in filters]
if not ids:
return None
try:
index = ids.index(current_id)
except ValueError:
return ids[0]
return ids[(index + 1) % len(ids)]
Chat screen: background and overlays
Overlay should not intercept touch events, close system insets, or hold ChatActivity after exit. Add the View to the correct container, respect the keyboard, and remove it when destroying.
Videophone MP4
For a looping video, use a system player with muted audio, surface lifecycle and stop when the application goes into the background. Don't manually decode videos in Python. Add static fallback, resolution limit and shutdown setting to save battery.
TL request hooks
The Request hook can observe the request name and object before sending. Do not store the full request with private data. Changing peer, random_id or authorization fields can lead to duplicates and server errors. For analytics, a query type and a safe result are enough.
Java interfaces from Python
For listener, use ready-made proxy helpers or class proxy with the exact method signature. The Python callback reference should live as long as the Java listener. When unloaded, remove the listener and clear both sides of the link.
Full production checklist
- Low-level functions have feature detection.
- Every hook and listener is removed.
- View works in both themes and does not overlap input.
- Videos and shaders have fallback.
- Two accounts do not mix controllers.
- Logs do not contain messages or tokens.
- The plugin survives reload and safe mode.