DevGram Plugin SDKВведение
Плагины на Python, работающие внутри DevGram и использующие нативный Telegram API.
DevGram plugins
Плагин наследуется от BasePlugin и выполняется встроенным Python 3.11. Root и внешний Xposed Framework не нужны.
Нативный форматПлагины поставляются одним файлом .plugin (или .py) либо архивом .dgplugin для сложных расширений с ресурсами и зависимостями. API DevGram не копирует API других клиентов.
Возможности SDK
СобытияСообщения, TL updates, запросы и NotificationCenter.
ИнтерфейсНастройки, bulletins, alerts и Pill Stack.
Client APIМедиа, форматирование, аккаунты и контроллеры.
NativeJava class proxy и method hooks.
Порядок чтения
- Подготовка и первый пакет
- BasePlugin и события
- Client API и UI
- Разработка, публикация и безопасность
''
Модель выполнения
Плагин загружается отдельным runtime-модулем, но работает внутри процесса DevGram. Поэтому ошибка в Java bridge, бесконечный цикл или тяжелая работа на UI thread способны повлиять на клиент. Относитесь к каждому callback как к коду production-приложения: проверяйте входные данные, ограничивайте время работы и освобождайте ресурсы.
Что считается публичным API
Стабильный слой находится в модулях devgram.*. Поля и методы с ведущим подчеркиванием, внутренние классы Telegram и конкретные layout IDs не являются контрактом. Если приходится использовать низкоуровневую точку, добавьте проверку версии и fallback.
Минимальный стандарт плагина
- Уникальный ID и понятные metadata.
- Отсутствие секретов в архиве.
- Идемпотентный load/unload.
- Работа в светлой, темной и системной теме.
- Корректное поведение после reload, disable и safe mode.
DevGram BuilderDevGram Builder
Высокоуровневый слой над DevGram SDK для больших плагинов: структура проекта, модули, ресурсы и декларативные настройки.
Зачем Builder
Builder отделяет бизнес-логику от lifecycle и Android-моста. Один проект можно собирать в нативный .dgplugin без ручного управления импортами, локалями и ресурсами.
МодулиРазделяйте hooks, screens, commands и services.
ResourcesAssets, localization и metadata имеют предсказуемые пути.
LifecycleInit, enable, disable и dispose управляются проектом.
Public APIТипизированные фасады над client_utils и UI.
ВажноDevGram Builder — DevGram-native слой. Он не запускает плагины exteraGram и не меняет формат нашего пакета.
DevGram BuilderQuick Start
Создайте DevGram Builder-проект, добавьте модуль и соберите его в `.dgplugin`.
devgram-builder new hello.devgram
cd hello.devgram
devgram-builder add module messages
devgram-builder build
python3 tools/devgram_dev.py upload dist/hello.devgram
Первый модуль
from devgram.builder import Module
class Welcome(Module):
id = "welcome"
def on_message(self, event):
if event.text == "/hello":
event.reply("Hello from DevGram Builder")
Если CLI не установлен, ту же структуру можно создать вручную по разделу Project Structure.
DevGram BuilderProject Structure
Каждый каталог имеет одну ответственность.
hello.devgram/
├── devgram-builder.json
├── main.py
├── modules/
│ ├── messages.py
│ └── appearance.py
├── services/
├── screens/
├── assets/
├── locales/
│ ├── ru.json
│ └── en.json
└── wheels/
| Каталог | Назначение |
|---|
modules/ | Событийные модули и команды. |
services/ | Фоновые задачи и stateful-сервисы. |
screens/ | UI и страницы настроек. |
assets/ | Иконки, картинки и шаблоны. |
DevGram BuilderPublic API
Фасады Builder скрывают низкоуровневый Java-мост, но возвращают обычные DevGram objects.
| API | Назначение |
|---|
Module | Базовый lifecycle модуля. |
module.on_message(event) | Нормализованное событие сообщения. |
module.settings() | Декларативные настройки. |
context.client(account) | Account-aware Client API. |
context.ui.bulletin() | Нативная плашка. |
context.storage | Персистентное хранилище модуля. |
Lifecycle
def setup(context):
context.register(Welcome())
def dispose(context):
context.unregister_all()
DevGram BuilderMetadata
Файл devgram-builder.json описывает проект до упаковки в manifest `.dgplugin`.
{
"id": "author.plugin",
"name": "Plugin name",
"version": "1.2.0",
"entrypoint": "main.py",
"modules": ["modules.messages", "modules.appearance"],
"minDevGram": "12.9.3",
"permissions": ["network", "storage"]
}
Правила версий
Версия должна увеличиваться для каждого опубликованного архива. minDevGram используйте, если проект зависит от нового API. Builder проверяет ID модулей, существование entrypoint и дублирование ресурсов до упаковки.
DevGram BuilderModules & Imports
Большой плагин делится на независимые модули с явным lifecycle.
# modules/messages.py
from devgram.builder import Module
class Messages(Module):
id = "messages"
depends_on = ["storage"]
def enable(self, context):
self.handle = context.events.messages(self.on_message)
def disable(self, context):
self.handle.close()
Не используйте wildcard imports. Общие функции размещайте в services/, а не копируйте между модулями. Циклические зависимости запрещены: Builder выводит цепочку модулей, которая создала цикл.
DevGram BuilderAssets & Localization
Builder индексирует ресурсы и проверяет ссылки на них во время сборки.
icon = context.assets.path("icons/plugin.png")
title = context.i18n.get("settings.title")
message = context.i18n.format("welcome", name=user_name)
Fallback
Порядок выбора строки: текущий язык → английский → default → ключ. Все JSON-файлы должны содержать объект строк. Не храните бинарные данные в locale-файлах.
Размеры assets
Иконки каталога готовьте квадратными. Большие изображения сжимайте заранее: `.dgplugin` загружается и проверяется целиком, поэтому лишние assets увеличивают время установки.
DevGram BuilderSettings
Builder объединяет настройки модулей в одну нативную страницу и автоматически добавляет namespace к ключам.
class Appearance(Module):
def settings(self):
return [
Header(text="Внешний вид"),
Switch(key="glass", text="Стекло", default=False),
Selector(key="style", text="Стиль", items=["System", "Compact"]),
]
def on_setting_changed(self, key, value):
if key == "glass": self.apply_glass(value == "1")
Физический ключ хранится как module_id.key, поэтому два модуля могут использовать логическое имя enabled без конфликта.
DevGram BuilderDependencies
Есть два типа зависимостей: модули проекта и Python wheels.
Module dependencies
depends_on определяет порядок enable и обратный порядок disable. Если обязательный модуль не загрузился, зависимый модуль не запускается и получает понятную ошибку.
Python wheels
devgram-builder add wheel dist/library.whl
devgram-builder inspect dependencies
Builder помещает wheels в пакет и создаёт lock-файл с SHA-256. Для Android предпочтительны pure-Python зависимости.
DevGram BuilderDevelopment
Сборка, проверка и hot reload выполняются одной последовательностью.
devgram-builder check
devgram-builder build --debug
devgram-builder upload --reload
devgram-builder logs --follow
Debug build
Debug-пакет включает source map модулей и расширенный лог. Перед публикацией собирайте без --debug, чтобы не включать локальные пути и диагностические файлы.
Editor workflow
Запускайте проверку при сохранении, держите ADB подключённым и перезагружайте только изменённый plugin ID. Полный restart нужен только при изменении native Java bridge.
DevGram BuilderTroubleshooting
Диагностика проблем структуры, импортов и lifecycle.
Module not found
Проверьте имя в metadata, наличие __init__.py и регистр символов. Android filesystem чувствителен к регистру.
Circular dependency
Вынесите общую логику в service-модуль, от которого зависят обе стороны. Не создавайте взаимный depends_on.
Работает после restart, но не после reload
Модуль сохранил глобальный singleton или Java listener. Переместите создание в enable(), а удаление в disable().
Настройки потерялись
Не меняйте ID проекта или module ID. Для переименования ключа добавьте явную миграцию schema version.
Getting startedПодготовка
Минимальная среда для написания и установки расширений.
Требования
- актуальный DevGram на Android;
- Python 3 для упаковки;
- ADB для hot reload;
- редактор с поддержкой Python.
Developer mode
Откройте Плагины → Система плагинов и включите режим разработчика. Скопируйте локальный токен.
export DEVGRAM_TOKEN="token_from_app"
python3 tools/devgram_dev.py status
Локальный доступDev server доступен только через ADB forwarding и требует токен.
Подготовка рабочей среды
Разделите каталог исходников и каталог собранных пакетов. В исходниках храните manifest, Python-код, ресурсы и тестовые данные; в release-архив не включайте виртуальное окружение, логи, IDE metadata и локальные ключи.
Проверка окружения
python3 --version
adb devices
unzip -t plugin.dgplugin
Перед началом убедитесь, что устройство видно через ADB, DevGram установлен из той же ветки, под которую написан плагин, а Developer mode включен. Если ADB показывает unauthorized, подтвердите fingerprint на телефоне.
Повторяемая сборка
Фиксируйте версии зависимостей и собирайте архив из чистого каталога. Повторный запуск должен создавать тот же список файлов и тот же manifest, кроме намеренно меняемого checksum.
Getting startedПервый плагин
Минимальный пакет, который можно установить в один тап.
from devgram import BasePlugin
from devgram.ui import Header, Switch, Button
class Hello(BasePlugin):
id = "hello.devgram"
name = "Hello DevGram"
version = "1.0.0"
author = "Your name"
description = "Мой первый плагин для DevGram"
icon = "https://example.com/plugin-icon.png"
def settings(self):
return [Header(text="Hello"), Switch(key="enabled", text="Включено"), Button(key="test", text="Проверить")]
def on_setting_click(self, key):
if key == "test": self.bulletin("Плагин работает", kind="success")
plugin = Hello()
Аватарка плагинаПоле icon принимает прямую HTTPS-ссылку на PNG или JPG. DevGram покажет картинку в карточке установки и списке плагинов. Подробности — в отдельном пункте «Аватарка плагина» в меню Package.
Сохраните файл с расширением .plugin, отправьте его в Telegram и нажмите «Установить». Пакет с ресурсами и зависимостями упаковывается в .dgplugin.
Разбор первого плагина
Entry point содержит только metadata и небольшую логику. При импорте не вызывайте UI и не отправляйте сообщения: загрузчик может еще не иметь активного Fragment. Регистрацию observer, menu и pill выполняйте в on_load().
Пошаговая проверка
- Установите пакет и убедитесь, что он появился в менеджере.
- Откройте настройки и измените значение.
- Перезагрузите только этот плагин.
- Отправьте тестовое сообщение из второго аккаунта.
- Отключите и удалите плагин, затем проверьте отсутствие callback.
Типовые ошибки
Если плагин не виден, проверьте расширение, корень архива и уникальность ID. Если он виден, но не запускается, смотрите первую ошибку импорта: последующие сообщения часто являются следствием.
PackageManifest
Метаданные определяют идентичность и точку входа расширения.
{
"id": "hello.devgram",
"name": "Hello DevGram",
"version": "1.0.0",
"author": "Your name",
"entrypoint": "main.py",
"description": "Example plugin",
"icon": "https://example.com/plugin-icon.png"
}
| Поле | Правило |
|---|
id | Уникальный стабильный ID. |
entrypoint | Python-файл точки входа. |
version | Версия обновления. |
description | Краткое описание для каталога и карточки установки. |
icon | Прямая HTTPS-ссылка на PNG или JPG-аватарку. |
Атомарная установкаПакет валидируется и распаковывается во временную папку. Неудачное обновление не ломает активную версию.
Поля manifest
Manifest описывает идентичность пакета до импорта Python. Используйте строки в UTF-8, не дублируйте ключи и не храните там пользовательские токены. Значения version и min_app_version сравниваются как строки по правилам загрузчика, поэтому придерживайтесь формата major.minor.patch.
Совместимость entrypoint
Путь entrypoint должен быть относительным и указывать на существующий Python-файл. Имя модуля не должно совпадать с системным пакетом или другим установленным плагином. После переименования файла обновите manifest и проверьте чистую установку.
Иконка и описание
Для icon используйте прямую HTTPS-ссылку на PNG или JPG. Описание пишется для каталога: сначала назначение, затем ограничения и требуемые разрешения.
PackageАватарка плагина
Как добавить иконку в карточку установки, список плагинов и каталог.
Обычный .plugin
Добавьте поле icon в класс рядом с name, author и description:
class MyPlugin(BasePlugin):
id = "my.plugin"
name = "My Plugin"
author = "@you"
description = "Краткое описание"
icon = "https://example.com/plugin-icon.png"
Пакет .dgplugin
Укажите ту же ссылку в manifest.json:
{
"id": "my.plugin",
"name": "My Plugin",
"icon": "https://example.com/plugin-icon.png"
}
Требования
- Используйте прямую HTTPS-ссылку на файл изображения, а не ссылку на HTML-страницу.
- Формат — PNG или JPG; лучше квадратное изображение небольшого размера.
- Проверьте контраст на светлой и тёмной теме.
- Если
icon не указан или картинка не загрузилась, DevGram оставит стандартную иконку.
Где она появитсяDevGram загружает аватарку при открытии карточки установки и кеширует её для списка плагинов.
PackageAssets и языки
Ресурсы пакета .dgplugin доступны без абсолютных путей, а строки — с локализацией.
Ассеты
# файлы из assets/ внутри .dgplugin
bg = self.asset_path("background.png") # абсолютный путь к ассету
icon = self.asset_path("icons/plugin.png")
Локализация
Строки лежат в locales/<язык>.json (напр. ru.json, en.json), выбирается по языку приложения с фолбэком на английский.
# locales/ru.json: {"greeting": "Привет, {name}!"}
msg = self.string("greeting", "Hello", name="Vlad") # → "Привет, Vlad!"
msg = self.string("greeting", "Hello", locale="en") # принудительный язык
Личная папка плагина
path = self.plugin_files_dir() # приватное хранилище на устройстве
self.write_file("state.json", data) # относительно этой папки
data = self.read_file("state.json")
Регистр важенФайловая система Android чувствительна к регистру: Icon.PNG и icon.png — разные файлы.
PackageЗависимости
В пакет можно включить совместимые Python-библиотеки.
Python wheels
Положите pure-Python .whl в каталог wheels/ внутри .dgplugin. При hot reload старые модули и пути очищаются, поэтому обновление не смешивается со старой версией.
my.dgplugin/
manifest.json
main.py
wheels/
somelib-1.2.3-py3-none-any.whl
ABIНативные (не pure-Python) wheels должны точно соответствовать ABI и версии Python сборки DevGram, иначе не загрузятся. По возможности берите py3-none-any.
ВстроеноСтандартная библиотека Python 3, json, urllib, а также мост java (jclass, dynamic_proxy, jarray) доступны без зависимостей.
SDKBasePlugin
Главный класс расширения: жизненный цикл, метаданные, хранилище и точка входа во всё API DevGram.
Плагин — это класс-наследник BasePlugin. Загрузчик создаёт один экземпляр на установленный plugin ID и вызывает on_load(). Тот же объект живёт до выгрузки, поэтому состояние храните в полях экземпляра, а очищайте в 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()
Метаданные
| Поле | Назначение |
id | Уникальный идентификатор. По нему хранятся настройки, хуки и файлы. |
name / version / author / description | Карточка установки и список плагинов. |
icon | URL или путь ассета для аватарки плагина. |
min_app_version | Минимальная версия DevGram. |
В пакете .dgplugin те же поля можно (и лучше) задать в manifest.json — он имеет приоритет над атрибутами класса.
Жизненный цикл
| Метод | Когда |
on_load(self) | Один раз при загрузке/включении. Регистрируйте хуки, pills, observers. |
on_unload(self) | При выгрузке/отключении/reload. Снимите ВСЁ, что зарегистрировали. |
on_setting_changed(self, key, value) | Пользователь изменил вашу настройку. |
on_setting_click(self, key) | Нажата настройка-кнопка (type='button'). |
Хранилище плагина
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 (что где искать)
| Область | Методы |
| События | on_send_message, on_update, on_send_request, menu_items → раздел «События» |
| Отправка/клиент | send_message, edit_message, send_photo, send_request, контроллеры → «Client API» |
| Интерфейс | bulletin, alert, toast, register_pill, register_panel_tab, settings |
| Хуки Java | hook, hook_all, invoke_original, java_class, implement → «Java hooks» |
| Вид/эффекты | glass, blur, tint, add_view, dp, canvas_* → «Эффекты и View» |
| Потоки/утилиты | run_on_ui, run_on_queue, copy, open_dialog, log |
ИдемпотентностьВсё, что создаёте в on_load, должно сниматься в on_unload: unhook_all(), unregister_pills(), unregister_panel_tab(), снятые observers. Иначе после reload останутся «двойные» хуки и утечки.
SDKMulti-account
У пользователя может быть несколько аккаунтов. Работайте с конкретным аккаунтом, а не только с активным.
from devgram import get_selected_account, get_client
acc = get_selected_account() # индекс активного аккаунта
client = get_client(acc) # клиент этого аккаунта
client.send_text(dialog_id, "hi")
Account-aware колбэки
Вместо «глобальных» колбэков переопределяйте варианты с аккаунтом — они срабатывают для КАЖДОГО аккаунта:
def on_send_request_hook(self, account, name, request): ...
def on_receive_response_hook(self, account, name, response, error): ...
Контроллеры по аккаунту
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()
Не «зашивайте» активный аккаунтЕсли событие пришло для аккаунта B, а вы отправляете ответ через активный аккаунт A — сообщение уйдёт не туда. Всегда пробрасывайте account из колбэка в контроллеры и отправку.
SDKСобытия
Колбэки сообщений, TL-апдейтов и TL-запросов. Переопределяйте нужные методы BasePlugin.
Сообщения
def on_send_message(self, text):
# перед отправкой. Вернуть: новый текст / None (без изменений) / False (отменить)
return text.replace(":shrug:", "¯\\_(ツ)_/¯")
def on_receive_message(self, text):
# входящее текстовое сообщение (только чтение/логика)
pass
TL-апдейты и запросы
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 варианты
В мультиаккаунте переопределяйте эти методы — они получают конкретный аккаунт:
def on_send_request_hook(self, account, name, request): ...
def on_receive_response_hook(self, account, name, response, error): ...
Меню сообщений и ссылок
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):
self.open_uri(url)
Не блокируйте колбэкиКолбэки событий вызываются на горячих путях клиента. Тяжёлую работу выносите в run_on_queue(), а обновления UI — в run_on_ui(). Оборачивайте тело в try/except: одно исключение не должно ронять поток.
Возвращаемые значения
on_send_message — единственный колбэк, который меняет исходящий текст: строка заменяет, None оставляет как есть, False отменяет отправку. Остальные колбэки событий — для наблюдения; чтобы менять поведение запросов/методов на лету, используйте Java-хуки.
SDKМеню сообщений
Плагин может добавлять команды в меню долгого нажатия по сообщению.
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
| Параметр | Значение |
|---|
label | Нажатый пункт из menu_items(). |
message_text | Текст выбранного сообщения. |
dialog_id | ID чата, где открыто меню. |
Меню долгого нажатия
Возвращайте стабильные человекочитаемые labels и держите их короткими. Обработчик должен повторно проверить message и dialog перед действием: между построением меню и нажатием сообщение могло быть удалено или изменено.
Несколько плагинов
Не используйте слишком общие названия и не переопределяйте чужие команды. ID плагина добавляется загрузчиком к внутреннему ключу, но отображаемый текст должен объяснять действие.
Ошибки
Если действие требует сети, сразу покажите bulletin о запуске, выполните запрос в queue и обновите результат на UI thread. Не блокируйте меню ожиданием ответа.
SDKClient API
Отправка и редактирование сообщений, сырые TL-запросы и доступ ко всем контроллерам Telegram.
Отправка
self.send_message(dialog_id, "Привет", reply_to=0, entities=None)
self.send_text(dialog_id, "Быстрый текст")
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: пользователь = его id, группа/канал = −id, секретный чат — как в клиенте.
Сырые TL-запросы
from devgram import tl
req = 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))
Контроллеры
Возвращают штатные Java-контроллеры Telegram (account-aware):
| Метод | 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 (всё сразу) |
Модуль client_utils
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
Вспомогательное: self.me() — id аккаунта, self.user_name(uid), self.chat_name(cid).
Работайте с реальными объектамиКонтроллеры и TL-объекты — это настоящие Java-классы Telegram, а не обёртки. Смотрите поля в исходниках; поля/методы с ведущим подчёркиванием и внутренние layout ID не являются контрактом.
SDKФорматирование текста
DevGram создаёт настоящие TLRPC.MessageEntity, а не отправляет Markdown как обычный текст.
Поддерживаемые entities
Bold, italic, underline, strike, spoiler, code, pre, blockquote и текстовые ссылки (кастомная подпись у ссылки).
Из 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"
Ручные 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)
Экранирование
from devgram.text_formatting import escape_markdown, escape_html
safe = escape_markdown(user_input)
Смещения в UTF-16offset/length считаются в UTF-16 code units (как в Telegram). Для текста с эмодзи/суррогатами используйте готовые parse_* — они считают правильно.
SDKНастройки
Декларативная страница настроек: опишите строки — DevGram нарисует нативный экран и сохранит значения.
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.apply()
def on_setting_click(self, key):
if key == "reset":
self.bulletin("Сброшено")
Виджеты
| Класс | Назначение |
Header(text) | Заголовок секции. |
Switch(key, text, default=False) | Тумблер (bool). |
Input(key, text, hint="") | Текстовое поле (str). |
Selector(key, text, items, default=0) | Выбор из списка (индекс). |
Text(text) | Инфо-строка (серый текст-подсказка). |
Button(key, text) | Кнопка → on_setting_click(key). |
Card(...) | Составная карточка. |
Custom(view) | Произвольный Android View в строке настроек. |
Чтение и запись значений
on = self.get_setting("enabled", True)
self.set_setting("mode", 1)
Ключи стабильныНе переименовывайте ключи между версиями — потеряются сохранённые значения. Для переименования сделайте миграцию: прочитать старый ключ, записать новый, удалить старый.
UtilitiesIntents
Регистрация обработчиков Android Intent и безопасное открытие ссылок.
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()
Фильтры register()
| Параметр | Описание |
|---|
action | Android action. |
scheme | Схема URI: https, tg и другие. |
host | Имя хоста. |
path | Путь URI. |
IntentContext предоставляет action, data, scheme, host, path и исходный Java Intent.
Маршрутизация
Handlers проверяются по priority, затем по action, scheme, host, path, query и category. Чем уже matcher, тем меньше случайных срабатываний. Callback возвращает True только если Intent действительно обработан.
Безопасность URI
URI из сообщений и сети недоверен. Сравнивайте scheme и host по белому списку, ограничивайте длину query и не передавайте произвольный URI в системный resolver без предупреждения.
Закрытие
Сохраните HandlerHandle и вызовите unhandle() в unload. При повторной загрузке старый handler не должен остаться в глобальном списке.
UtilitiesFile utilities
Текстовые и бинарные операции с файлами, а также приватное хранилище плагина.
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)
| Метод | Назначение |
|---|
read_file_bytes / write_file_bytes | Бинарное содержимое. |
delete_file(path) | Удаление файла. |
self.read_file(name) | Чтение из приватной папки плагина. |
self.write_file(name,data) | Запись в приватную папку. |
Файловая модель
Передавайте абсолютный путь только после проверки, что он находится внутри разрешенного каталога. Не используйте пользовательский filename напрямую: нормализуйте путь, запретите traversal и ограничьте размер.
Текст и bytes
Текстовые функции используют явную кодировку, binary helpers сохраняют байты без преобразования. Не читайте большой файл целиком, если достаточно streaming или проверки размера.
Удаление
Перед delete проверьте существование, тип объекта и принадлежность каталогу. Для важных данных используйте временный backup и понятное подтверждение в UI.
UtilitiesAndroid utilities
Потоки, clipboard, логирование и готовые 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) создаёт Java Runnable. Долгие операции всегда отправляйте в очередь, а View изменяйте только на UI-потоке.
UI и background
run_on_ui_thread() не делает тяжелую функцию безопасной: она только планирует ее в UI queue. run_on_queue() не дает доступа к View. Пересечение двух миров выполняйте через маленький immutable result.
Listeners
Сохраните listener handle или View reference только на время экрана. Удаляйте listener при закрытии, иначе Activity может удерживаться после навигации.
Clipboard и logs
Не копируйте секреты без явного действия пользователя и не логируйте приватный текст. Bulletin должен подтверждать действие, но не показывать чувствительное содержимое.
UI и runtimeBulletins и alerts
Нативные уведомления и диалоги Telegram с колбэк-кнопками.
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 (диалог)
self.alert(
"Заголовок", "Текст сообщения",
positive="OK", on_positive=self.ok,
negative="Отмена", on_negative=None,
neutral="Позже", on_neutral=self.later,
)
Toast
self.toast("Короткое сообщение")
С UI-потокаПоказывать плашки/диалоги нужно на UI-потоке. Если вызываете из фонового колбэка — оберните: self.run_on_ui(lambda: self.bulletin("...")).
UI и runtimePill Stack
Компактные интерактивные виджеты плагина в строке над списком чатов. Каждая «пилюля» принадлежит плагину и снимается при выгрузке.
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() # обязательно
| Параметр | Назначение |
pill_id | Уникальный id пилюли внутри плагина. |
name | Подпись. |
text | Статический текст. |
value | Строка или функция без аргументов, возвращающая строку (обновляемое значение). |
on_click / on_long_click | Обработчики нажатия. |
enabled | Показывать ли пилюлю. |
ПереприменениеПовторный вызов register_pill с тем же id обновляет пилюлю — удобно пересобирать набор в on_setting_changed. Всегда снимайте пилюли в on_unload.
UI и runtimeВкладка в панели
Плагин может добавить свою вкладку в панель Эмодзи / GIF / Стикеры — с любым Android View, угловой кнопкой-действием и иконкой на кнопке-смайлике чата.
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
| Параметр | Назначение |
|---|
title | Текст вкладки в полосе Эмодзи/GIF/Стикеры. |
build_view(context, dialog_id) | Строит содержимое вкладки. Зовётся заново при каждом открытии — не кэшируйте сам View. |
on_action(anchor, dialog_id) | Необязательно. Включает кнопку-действие в углу панели (рядом с Эмодзи/GIF/Стикеры). anchor — для позиционирования всплывашки. |
icon_path | Необязательно. PNG-иконка на кнопке-смайлике чата, пока открыта ваша вкладка; тап открывает сразу её. |
dialog_idbuild_view и on_action получают ID текущего чата — стройте содержимое и отправляйте контент именно в него.
Всплывающее меню действия
В on_action удобно открыть нативное меню Telegram (как «стрелка» у reSwaga): ActionBarPopupWindow + ActionBarMenuSubItem, позиционируя по 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)
R8 и константыИз Python указывайте размеры/флаги числовыми литералами (-1/-2, 2), а не через LayoutHelper.WRAP_CONTENT/INPUT_METHOD_NOT_NEEDED: такие static final int инлайнятся компилятором и вырезаются R8 в release, из-за чего reflection даёт AttributeError. LayoutHelper.createFrame/createLinear сами применяют dp() к числу.
Жизненный цикл вкладки
Вкладка принадлежит plugin ID. build_view вызывается при каждом выборе вкладки, поэтому стройте View из текущего состояния и не держите ссылку на Activity. Ссылки на Java-прокси слушателей (self.implement(...)) храните в поле плагина, иначе их соберёт GC. В on_unload зовите unregister_panel_tab().
Иконка на кнопке-смайлике
Если задан icon_path, пока открыта ваша вкладка, кнопка-смайлик чата показывает эту иконку вместо обычной анимации, а повторный тап открывает именно вашу вкладку (не эмодзи). Иконка сохраняется при сворачивании панели.
Выключенные плагины
Если пользователь выключил плагин, его вкладка автоматически скрывается из панели — отдельной обработки не нужно.
UI и runtimeЭффекты и View
Встраивание своих Android View, «жидкое стекло», blur, tint, скругления, рисование на canvas и перевод dp.
Встроить и разместить 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
Жидкое стекло / 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)
| Метод | Описание |
glass_panel(context_view,...) | Новая пустая стеклянная панель-контейнер (размывает фон позади). |
glass(view,...) | Обернуть существующую View стеклом на том же месте. |
blur / unblur(view) | RenderEffect на содержимое View (API 31+). |
tint(view, argb) | Полупрозрачная заливка фона. |
round_corners(view, dp) | Скруглить углы (обрезка по контуру). |
add_view / remove_view | Добавить/убрать child во ViewGroup. |
Рисование на canvas
В onDraw своей java_class-View рисуйте нативными хелперами (без выхода в Python на каждую примитиву):
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 граблей)
Почему canvas_* и new_canvasПрямое создание Canvas(bitmap)/вызовы drawRect из Python иногда попадают в скрытый нативный конструктор Android (NoSuchMethodError). Эти хелперы вызывают нужную перегрузку из скомпилированного Java.
UI и runtimeJava hooks
Полноценные Xposed-style хуки любого метода/конструктора и создание настоящих Java-объектов из Python. Движок — AliuHook поверх LSPlant: надёжно на всех Android, ловит даже инлайненные методы.
Хук метода
# 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)
Типы аргументов — строками: "int", "long", "boolean", "float", "java.lang.String", вложенные классы через $ ("a.b.Outer$Inner"). Для конструктора имя метода — "<init>".
frame — объект вызова (MethodHookParam)
| Поле/метод | Что это |
frame.thisObject | Объект, на котором вызван метод (null для static). |
frame.args | Массив аргументов. Меняется: frame.args[0] = x (в before). |
frame.getResult() | Текущий результат (в after). |
frame.setResult(x) | Заменить результат. В before это ПРОПУСКАЕТ оригинал. |
frame.method | Какой именно метод сработал (для hook_all). |
def on_profile(self, frame):
activity = frame.thisObject
uid = frame.args[0] # первый аргумент
frame.setResult(42) # подменить результат
orig = self.invoke_original(frame) # вызвать оригинал в обход хуков
Управление хуками
| Метод | Назначение |
invoke_original(frame) | Вызвать оригинал в обход хуков (внутри before/replace). |
unhook_all() | Снять ВСЕ хуки плагина (обязательно в on_unload). |
deoptimize(cls, method, ...types) | Деоптимизировать метод (для «неуловимых» инлайненных). |
is_hooked(cls, method, ...types) | Захукан ли сейчас метод. |
make_class_inheritable(cls) | Снять final с класса (для подклассов). |
allocate_instance(cls) | Создать объект БЕЗ вызова конструктора. |
Настоящий Java-подкласс из Python
java_class создаёт РЕАЛЬНЫЙ подкласс (напр. свою View с onDraw). Методы logic-объекта переопределяют Java-методы; сигнатура — (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())
Не хукайте горячие методы через PythonПокадровые onDraw/measure/layout, вызываемые сотни раз в секунду, при выходе в Python дают ANR. Для рисования делайте один java_class-View и рисуйте нативными canvas_*.
СовместимостьСтарые плагины с глобальными before_hook(frame)/after_hook(frame) продолжают работать. jclass/dynamic_proxy — стандартные из Chaquopy.
Стоимость низкого уровня
Хук связывает плагин с деталями реализации: класс, перегрузка, конструктор, тип результата. Сначала ищите публичное событие или контроллер. Опциональный хук ставьте в try/except, критичный — с проверкой версии и понятной ошибкой. Храните хуки только через SDK (снимаются unhook_all()), не держите static-ссылки на плагин или Context.
UI и runtimeReflection
Доступ к private и static private полям Java-классов.
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 зависит от внутренних имён Telegram и может требовать обновления после смены базовой версии клиента. Всегда оборачивайте такие операции в обработку ошибок.
Private fields
Имя private field и его тип не являются стабильным контрактом. Проверяйте наличие до чтения, не изменяйте критичные поля без резервного поведения и логируйте только безопасную информацию.
Типы
Значение должно соответствовать Java type, включая primitive wrappers и nullability. Ошибка reflection может проявиться только на конкретной версии устройства.
Когда отказаться
Если задача решается через Client API, settings или NotificationCenter, reflection не нужен. Чем меньше private access, тем проще обновление плагина.
ПубликацияDev server
Hot reload и отладка без перезапуска приложения.
export DEVGRAM_TOKEN="..."
python3 tools/devgram_dev.py upload plugin.dgplugin
python3 tools/devgram_dev.py reload --plugin hello.devgram
python3 tools/devgram_dev.py debugger-start --platform vscode --port 5678
Инструмент использует ADB forward/reverse. Сетевой порт наружу не открывается.
Hot reload
Reload сначала вызывает unload, затем очищает модули и импортирует новую версию. Если старый callback продолжает работать, проблема в незакрытом handle, timer, thread или Java listener.
ADB workflow
adb devices
adb logcat | grep DevGram
adb push plugin.dgplugin /sdcard/Download/
Проверяйте logcat только на тестовом устройстве и удаляйте персональные данные из отчета.
Release check
Hot reload не заменяет чистую установку: отдельно проверьте install, update, disable, enable, uninstall и запуск после safe mode.
ПубликацияБезопасность
Код плагина выполняется внутри процесса клиента, поэтому доверяйте источнику пакета.
- не храните секреты в архиве;
- не блокируйте UI-поток;
- снимайте observers и pills в
on_unload; - учитывайте account в multi-account callbacks.
Safe modeПосле падения в callback DevGram отключает callbacks плагинов до ручного восстановления.
Безопасная граница
Safe mode помогает восстановить клиент после падения callback, но не проверяет намерения автора. Установка остается решением пользователя. Каталог и changelog должны честно описывать сеть, hooks, доступ к сообщениям и файлам.
Изоляция ошибок
Один невалидный update не должен отключать все observers. Один optional API не должен ломать загрузку. Используйте локальные try/except и короткие операции.
Секреты
Не храните bot token, Firebase credentials, signing keys и private URLs в исходниках или архиве. Перед публикацией сканируйте весь пакет и историю сборки.
ПубликацияРешение проблем
Типовые ошибки установки, загрузки и выполнения.
Плагин не появился
- проверьте расширение (
.plugin или .dgplugin); - для пакета
.dgplugin убедитесь, что manifest.json и entrypoint лежат в корне архива; - проверьте уникальность ID;
- откройте системный лог плагинов.
Изменения не применились
Используйте reload конкретного plugin ID. DevGram очищает его модули из Python cache; если код всё ещё старый, проверьте entrypoint и содержимое архива.
После падения ничего не работает
Клиент мог включить safe mode. Удалите или исправьте проблемный пакет, нажмите «Перезагрузить плагины», затем выключите безопасный режим.
UI зависает
Перенесите сеть, чтение больших файлов и вычисления в run_on_queue(). Не выполняйте Python-код в покадровых методах.
Диагностический порядок
- Воспроизведите на чистом запуске.
- Запишите plugin ID/version и точный экран.
- Проверьте первую строку traceback.
- Отключите только проблемный plugin.
- Повторите после reload и после полного restart.
Каталог пуст
Отделите ошибку сети от ошибки render: покажите loading, empty и error отдельно, проверьте callback на UI thread и убедитесь, что фильтры не применяются до загрузки данных.
Падение после обновления
Сравните manifest, schema migration, imports и optional hooks. При несовместимом API лучше отключить функцию и показать предупреждение, чем падать при каждом открытии.
ПубликацияAPI reference
Краткий список публичных модулей DevGram SDK.
| Модуль | Назначение |
|---|
devgram | BasePlugin, AccountClient, hooks, UI helpers и lifecycle. |
devgram.client_utils | Account-aware controllers, sending и NotificationCenter. |
devgram.text_formatting | Entities, HTML, Markdown и UTF-16. |
devgram.android_utils | Потоки, listeners, clipboard и log. |
devgram.file_utils | Текстовые и бинарные файлы. |
devgram.intents | Регистрация и отправка Android Intent. |
devgram.hook_utils | Reflection helpers. |
devgram.ui | Settings, AlertDialogBuilder и 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
Начинайте с BasePlugin и lifecycle, затем переходите к account-aware client_utils, форматированию и UI. Utilities подключайте по необходимости, а native-разделы используйте только после проверки публичного API.
Контракт возвращаемых значений
None обычно означает отсутствие результата или действие без синхронного return; boolean callbacks сообщают, обработано ли событие; handles имеют explicit close/unhandle. Всегда сверяйте конкретную таблицу модуля.
Версионная стратегия
Стабильный plugin ID сохраняется, version увеличивается, а minimum app version ограничивает запуск. Breaking changes сопровождаются migration и changelog.
ПубликацияCookbook
Готовые рецепты под частые задачи. Копируйте и адаптируйте.
Сменить заголовок «DevGram» на экране чатов
Заголовок отдаёт DialogsActivity.devgramActionBarTitle() и переустанавливает его на каждом onResume — поэтому просто setTitle откатывается. Хукаем сам метод:
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()
Перехватить исходящий запрос
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())
Своя View с рисованием
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()
Добавить ряд в профиль (как телефон/юзернейм)
Профиль — это RecyclerListView с адаптером и DiffUtil. Ряд добавляют, увеличивая rowCount в updateRowsIds и регистрируя позицию в DiffCallback.fillPositions (иначе DiffUtil запустит фантомную анимацию → краш). Подробный разбор — в разделе «Надёжные Java hooks».
Всегда убирайте за собойЛюбой рецепт с хуком/View требует снятия в on_unload: unhook_all(), remove_view(), unregister_pills().
Verified APISDK contracts
Точные контракты модулей, которые входят в текущую сборку DevGram. Сигнатуры на этой странице сверены с Python runtime клиента.
Источник истиныЕсли пример из экспериментального DevGram Builder расходится с этой страницей, используйте API отсюда. Builder пока является проектируемым высокоуровневым слоем, а модули devgram.* уже доступны плагинам.
devgram
Корневой модуль предоставляет BasePlugin, AccountClient, HookResult, HookStrategy, get_selected_account() и get_client(account=None). Экземпляр плагина создается загрузчиком. Не создавайте BasePlugin вручную и не храните Android Activity в глобальной переменной: после смены экрана ссылка устареет.
Метаданные BasePlugin
| Поле | Тип | Назначение |
|---|
id | str | Постоянный уникальный ID. Используется для настроек, файлов, hooks и обновлений. |
name | str | Отображаемое имя. |
version | str | Версия пакета; увеличивайте перед публикацией. |
author | str | Автор или команда. |
description | str | Краткое описание без разметки. |
icon | str | URL PNG/JPG для списка плагинов. |
min_app_version | str | Минимальная совместимая версия DevGram. |
AccountClient
get_client() возвращает фасад текущего аккаунта, а get_client(account) привязывает операции к указанному индексу. Доступны get_messages_controller(), get_user_config(), get_connections_manager() и send_text(dialog_id, text). Индекс аккаунта и Telegram user ID — разные значения.
from devgram import get_client
def send_for_event(account, dialog_id):
client = get_client(account)
client.send_text(dialog_id, "Сообщение из нужного аккаунта")
devgram.client_utils
Все account-aware helpers принимают account=None. Без параметра используется выбранный в интерфейсе аккаунт. В обработчиках событий всегда передавайте полученный account, иначе действие может выполниться от другого профиля.
| Функция | Результат | Применение |
|---|
send_text(peer, text, account=None) | None | Отправляет обычный текст. |
send_formatted_text(peer, text, entities, account=None) | None | Отправляет текст с native entities. |
get_messages_controller(account=None) | Java object | Пользователи, чаты и диалоги. |
get_connections_manager(account=None) | Java object | TL-запросы и сетевое состояние. |
get_messages_storage(account=None) | Java object | Локальная база; тяжелые операции выполняйте в queue. |
get_file_loader(account=None) | Java object | Пути и загрузка Telegram media. |
get_last_fragment() | BaseFragment/None | Текущий экран. Всегда проверяйте на None. |
NotificationCenter
observe(notification_id, callback, account=None) возвращает ObserverHandle. Сохраните handle и вызовите close() при выгрузке. Повторная регистрация после hot reload без закрытия старого handle приводит к двойным callback.
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) описывает диапазон в UTF-16. Доступны parse_html(), parse_markdown(), parse_text(), to_tlrpc_entities(), utf16_length(), escape_markdown() и escape_html(). Не вычисляйте длину через len(), если строка содержит emoji или символы вне BMP.
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)
Разбор пользовательского текста
parse_text(text, parse_mode) возвращает очищенный текст и entities. Используйте parse_mode="html" или parse_mode="markdown". Экранируйте данные пользователя до добавления в шаблон, иначе введенные символы станут разметкой.
from devgram.text_formatting import escape_html, parse_text
source = "Автор: " + escape_html(user_name)
plain, entities = parse_text(source, "html")
devgram.file_utils
Модуль содержит ensure_dir_exists(), list_dir(), read_file(), write_file(), бинарные варианты и delete_file(). list_dir(path, extensions=None, recursive=False, include_files=True, include_dirs=False) умеет фильтровать расширения и обходить дерево.
from devgram.file_utils import ensure_dir_exists, write_file, read_file
folder = ensure_dir_exists(self.storage_path("cache"))
path = folder + "/state.json"
write_file(path, '{"enabled": true}')
state = read_file(path)
Проверяйте пути, полученные из Intent или сообщения. Запрещайте .., абсолютные пути и выход из каталога плагина. Запись JSON выполняйте атомарно, если файл важен для восстановления состояния.
devgram.android_utils
| Функция | Контракт |
|---|
run_on_ui_thread(func, delay=0) | Запускает callback в UI queue; delay задается в миллисекундах. |
run_on_queue(func) | Переносит файлы, сеть и вычисления в background queue. |
log(data) | Пишет строку с префиксом DevGram в FileLog. |
copy_to_clipboard(text) | Копирует текст через системный clipboard. |
OnClickListener(fn) | Создает Java listener для Android View. |
OnLongClickListener(fn) | Callback должен вернуть 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) регистрирует входящий маршрут. Более высокий priority проверяется первым. Callback получает IntentContext с полями intent, action, uri, parsed и query. Верните строго True, чтобы остановить 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) передает URI Android resolver, а send(intent) запускает явно созданный Intent. Не запускайте непроверенные схемы из удаленных данных.
devgram.hook_utils
Reflection API включает find_class(), get_private_field(), set_private_field(), а также static-варианты. Это низкоуровневый API: имена private-полей меняются между версиями Telegram. Оборачивайте lookup в обработку ошибок и объявляйте минимальную версию DevGram.
devgram.ui
Настройки строятся из Header, Switch, Input, Selector, Text и Button. Диалоги создаются цепочкой методов AlertDialogBuilder: title, message, positive, negative и neutral buttons.
from devgram.ui import AlertDialogBuilder
AlertDialogBuilder() \
.set_title("Удалить данные?") \
.set_message("Действие нельзя отменить") \
.set_positive_button("Удалить", self.clear_data) \
.set_negative_button("Отмена") \
.show()
Потоки и lifecycle
- В
on_load() только регистрируйте ресурсы и запускайте короткие операции. - Все Android View и уведомления изменяйте на UI thread.
- Файлы, JSON, сеть и большие циклы выполняйте в queue.
- В
on_unload() закрывайте observers, intent handles, hooks и pills. - Cleanup должен быть идемпотентным: повторный вызов не должен падать.
- Callback после выгрузки должен проверять, что плагин еще активен.
Совместимость
Публичный Python API стабильнее Java reflection, но также развивается. Не импортируйте приватные имена с подчеркиванием. Перед публикацией тестируйте установку, обновление поверх старой версии, отключение, повторное включение, hot reload, удаление и запуск после safe mode.
Verified API · android_utilsAndroid Utils
Подробная справка по модулю android_utils, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
Функции
| Имя | Сигнатура | Назначение |
|---|
run_on_ui_thread | run_on_ui_thread(func, delay=0) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
run_on_queue | run_on_queue(func) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
log | log(data) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
copy_to_clipboard | copy_to_clipboard(text) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
R | R(fn) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
OnClickListener | OnClickListener(fn) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
OnLongClickListener | OnLongClickListener(fn) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
Пример импорта
from android_utils import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · client_utilsClient Utils
Подробная справка по модулю client_utils, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
Функции
| Имя | Сигнатура | Назначение |
|---|
send_text | send_text(peer, text, account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
send_formatted_text | send_formatted_text(peer, text, entities, account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_messages_controller | get_messages_controller(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_user_config | get_user_config(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_connections_manager | get_connections_manager(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_account_instance | get_account_instance(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_send_messages_helper | get_send_messages_helper(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_media_data_controller | get_media_data_controller(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_contacts_controller | get_contacts_controller(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_messages_storage | get_messages_storage(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_notification_center | get_notification_center(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_file_loader | get_file_loader(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_media_controller | get_media_controller() | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_notifications_controller | get_notifications_controller(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_notifications_settings | get_notifications_settings(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_location_controller | get_location_controller(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_secret_chat_helper | get_secret_chat_helper(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_download_controller | get_download_controller(account=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_last_fragment | get_last_fragment() | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
observe | observe(notification_id, callback, account=None) | Observe one NotificationCenter event and return a removable ObserverHandle. |
_account | _account(account) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
NotificationCenterDelegate
Subclass and override didReceivedNotification(id, account, args).
Методы
| Метод | Сигнатура | Контракт |
|---|
didReceivedNotification | didReceivedNotification(self, notification_id, account, args) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
ObserverHandle
Публичный класс DevGram SDK.
Методы
| Метод | Сигнатура | Контракт |
|---|
__init__ | __init__(self, center, delegate, notification_id) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
close | close(self) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
Пример импорта
from client_utils import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · dev_serverDev Server
Подробная справка по модулю dev_server, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
Функции
| Имя | Сигнатура | Назначение |
|---|
_connect | _connect(platform, host, port, wait) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
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() | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
debugger_status | debugger_status() | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
Пример импорта
from dev_server import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · file_utilsFile Utils
Подробная справка по модулю file_utils, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
Функции
| Имя | Сигнатура | Назначение |
|---|
ensure_dir_exists | ensure_dir_exists(path) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
list_dir | list_dir(path, extensions=None, recursive=False, include_files=True, include_dirs=False) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
read_file | read_file(path) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
write_file | write_file(path, content) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
read_file_bytes | read_file_bytes(path) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
write_file_bytes | write_file_bytes(path, content) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
delete_file | delete_file(path) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
Пример импорта
from file_utils import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · hook_utilsHook Utils
Подробная справка по модулю hook_utils, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
Функции
| Имя | Сигнатура | Назначение |
|---|
find_class | find_class(class_name) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
_field | _field(clazz, name) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_private_field | get_private_field(obj, name) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
set_private_field | set_private_field(obj, name, value) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
get_static_private_field | get_static_private_field(clazz, name) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
set_static_private_field | set_static_private_field(clazz, name, value) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
Пример импорта
from hook_utils import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · intentsIntents
Подробная справка по модулю intents, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
Функции
| Имя | Сигнатура | Назначение |
|---|
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) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
dispatch | dispatch(intent) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
open_uri | open_uri(uri) | Open a URI using Android's resolver. |
send | send(intent) | Launch an explicitly constructed Android Intent. |
IntentContext
Публичный класс DevGram SDK.
Методы
| Метод | Сигнатура | Контракт |
|---|
__init__ | __init__(self, intent) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
HandlerHandle
Публичный класс DevGram SDK.
Методы
| Метод | Сигнатура | Контракт |
|---|
__init__ | __init__(self, entry) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
unhandle | unhandle(self) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
Пример импорта
from intents import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · text_formattingText Formatting
Подробная справка по модулю text_formatting, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
Функции
| Имя | Сигнатура | Назначение |
|---|
to_tlrpc_entities | to_tlrpc_entities(entities) | Convert Entity objects/dicts to Java ArrayList<TLRPC.MessageEntity>. |
add_surrogates | add_surrogates(text) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
remove_surrogates | remove_surrogates(text) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
utf16_length | utf16_length(text) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
parse_html | parse_html(text) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
parse_markdown | parse_markdown(text) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
_python_index_for_utf16 | _python_index_for_utf16(text, offset) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
parse_text | parse_text(text, parse_mode=None) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
escape_markdown | escape_markdown(text) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
escape_html | escape_html(text) | Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения. |
Entity
Публичный класс DevGram SDK.
_HTMLToEntities
Публичный класс DevGram SDK.
Методы
| Метод | Сигнатура | Контракт |
|---|
__init__ | __init__(self) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
length | length(self) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
handle_data | handle_data(self, data) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
handle_starttag | handle_starttag(self, tag, attrs) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
handle_endtag | handle_endtag(self, tag) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
Пример импорта
from text_formatting import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · ui.alertUi.Alert
Подробная справка по модулю ui.alert, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
AlertDialogBuilder
Публичный класс DevGram SDK.
Методы
| Метод | Сигнатура | Контракт |
|---|
__init__ | __init__(self, context=None, progress_style=0, resources_provider=None) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
set_title | set_title(self, value) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
set_message | set_message(self, value) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
set_positive_button | set_positive_button(self, text, listener=None) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
set_negative_button | set_negative_button(self, text, listener=None) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
set_neutral_button | set_neutral_button(self, text, listener=None) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
create | create(self) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
show | show(self) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
dismiss | dismiss(self) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
Пример импорта
from ui.alert import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · ui.bulletinUi.Bulletin
Подробная справка по модулю ui.bulletin, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
BulletinHelper
Публичный класс DevGram SDK.
Методы
| Метод | Сигнатура | Контракт |
|---|
_show | _show(kind, message, duration, button, callback) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
show_info | show_info(cls, message, fragment=None, duration=DURATION_LONG, button=None, callback=None) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
show_success | show_success(cls, message, fragment=None, duration=DURATION_SHORT, button=None, callback=None) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
show_error | show_error(cls, message, fragment=None, duration=DURATION_LONG, button=None, callback=None) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
show | show(cls, message, kind='info', duration=DURATION_LONG, button=None, callback=None, fragment=None) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
Пример импорта
from ui.bulletin import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Verified API · ui.settingsUi.Settings
Подробная справка по модулю ui.settings, сверенная с исходниками текущего DevGram runtime.
СовместимостьПубличными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.
Setting
Публичный класс DevGram SDK.
Методы
| Метод | Сигнатура | Контракт |
|---|
__init__ | __init__(self, key='', text='', default=None, on_change=None, items=None, **kwargs) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
as_row | as_row(self) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
Header
Публичный класс DevGram SDK.
Методы
| Метод | Сигнатура | Контракт |
|---|
__init__ | __init__(self, text='', **kwargs) | Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина. |
Switch
Публичный класс DevGram SDK.
Input
Публичный класс DevGram SDK.
Selector
Публичный класс DevGram SDK.
Text
Публичный класс DevGram SDK.
Button
Публичный класс DevGram SDK.
Пример импорта
from ui.settings import *
# Выполняйте файловые и сетевые операции вне UI-потока.
# Все handles и observers закрывайте в on_unload().
Ошибки и диагностика
Оберните вызов в try/except, запишите контекст через devgram.android_utils.log() и покажите пользователю короткое сообщение через bulletin. Не отправляйте stack trace в чат и не оставляйте секреты в логах.
Как читать сигнатуры
Параметры с default можно не передавать. Для Java objects проверяйте None до обращения к полям. Python exceptions означают ошибку локальной подготовки, а Telegram error приходит отдельным аргументом callback.
Поток выполнения
Не запускайте сеть, большие файлы, JSON и тяжелые вычисления во время импорта или на UI thread. Используйте run_on_queue(), а результат возвращайте через run_on_ui_thread(). После выгрузки callback обязан проверить, что instance еще активен.
Жизненный цикл ресурсов
Любой объект регистрации принадлежит вызывающему плагину. Сохраните его в поле, закройте в on_unload() и обнулите ссылку. Cleanup должен работать после частично завершенного on_load() и при повторном вызове.
Обработка ошибок
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
Пользовательское сообщение объясняет следующее действие, но не раскрывает traceback. Подробности оставляйте в debug log без токенов и личных данных.
Совместимость
Публичный helper предпочтительнее Java lookup. Для optional API используйте feature detection и отключайте только зависимую возможность. Не полагайтесь на layout и индексы View, которые меняются между версиями.
Проверка
- Default и явный account.
- Пустые, длинные и Unicode-значения.
- Повторный вызов после reload.
- Вызов после закрытия экрана.
- Ошибка сети, файла и RPC.
Developer guideАрхитектура плагина
Как разделить плагин на lifecycle, сервисы, обработчики событий, хранилище и интерфейс, чтобы reload и обновления были предсказуемыми.
Границы ответственности
Entry point должен оставаться небольшим: он связывает lifecycle и делегирует работу отдельным объектам. Не помещайте сетевой клиент, парсер, кэш и UI в один класс. Обработчики событий должны быстро проверить условие и передать тяжелую работу сервису.
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()
Состояние
Разделяйте постоянное состояние, кэш сессии и ссылки на Android objects. Постоянные данные записываются в private storage; кэш можно потерять при reload; View, Fragment и Activity нельзя сохранять между экранами. Получайте текущий Fragment непосредственно перед показом UI.
Зависимости
Передавайте сервисы через конструктор. Это делает код тестируемым и исключает скрытые global singletons. Циклические зависимости обычно означают, что общий контракт нужно вынести в отдельный модуль.
Закрытие
Каждый объект, который регистрирует listener, observer, hook, intent route или pill, должен иметь close(). Закрытие выполняется в обратном порядке создания и допускает повторный вызов.
Developer guideФормат .plugin
Простейший формат плагина — один файл с кодом. Идеален для небольших плагинов без ресурсов и зависимостей.
Что это
.plugin (или .py) — это обычный Python-файл с классом-наследником BasePlugin и строкой plugin = MyPlugin() в конце. Никакого manifest.json и архива: метаданные (id, name, version, author, description, icon) берутся прямо из атрибутов класса. Код выполняется встроенным Python 3.11 внутри клиента — без root и без внешнего Xposed.
Минимальный пример
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()
Сохрани файл с расширением .plugin (напр. hello.plugin). Любой текстовый редактор подойдёт; стандартная библиотека Python доступна, сторонние пакеты — нет.
Установка и распространение
- Отправь файл
.plugin в чат или канал. - Тап по файлу → карточка установки (иконка, имя, версия, автор, значок проверки) → «Установить».
- Управление: Настройки → DevGram → Плагины (тумблеры, настройки, «Поделиться», удаление).
Плагины из канала-разработчика со значком 🧩 считаются проверенными автоматически. Карточка установки — единая для .plugin и .dgplugin.
.plugin или .dgplugin?
- .plugin — один файл, без ресурсов и зависимостей. Просто написать и переслать. Подходит для большинства плагинов.
- .dgplugin — архив с
manifest.json, модулями, assets/, locales/ и wheels/. Нужен, когда есть локализация, картинки, несколько модулей или pip-зависимости (см. следующий раздел).
Developer guideФормат .dgplugin
Полная структура устанавливаемого пакета, правила именования и проверка содержимого перед публикацией. Для простого плагина из одного файла используйте .plugin (раздел выше).
Корень архива
plugin.dgplugin
├── manifest.json
├── main.py
├── modules/
├── assets/
├── locales/
└── wheels/
.dgplugin является архивом, но расширение менять нельзя: по нему DevGram распознает установщик. Manifest и entrypoint должны лежать в корне, а не во вложенной папке, появившейся после архивации каталога.
Идентификатор
Используйте reverse-domain или username namespace: space.devgram.example. ID не является отображаемым именем и не меняется после первого релиза. Смена ID создаст новый набор настроек и будет воспринята каталогом как другой плагин.
Ресурсы
Имена файлов чувствительны к регистру. Используйте ASCII, дефисы или подчеркивания. Не включайте исходные PSD, APK, keystore, токены и временные файлы. Большие изображения оптимизируйте заранее.
Проверка архива
- Распакуйте пакет во временный каталог.
- Проверьте manifest и импорт entrypoint.
- Убедитесь, что wheels подходят встроенной версии Python и ABI.
- Проверьте отсутствие абсолютных путей и traversal entries.
- Установите пакет поверх предыдущей версии и отдельно на чистую установку.
Developer guideПотоки и производительность
Правила работы с UI thread, background queue, сетью, файлами и частыми Telegram callbacks.
UI thread
На UI thread разрешены короткие изменения интерфейса: создание bulletin, обновление View и показ диалога. Чтение каталога, JSON parsing, HTTP, хэширование и обработка больших сообщений должны выполняться через run_on_queue().
Возврат результата
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))
Частые callbacks
Хук отправки сообщения может вызываться часто, а UI callbacks — несколько раз за кадр. Не выполняйте в них reflection lookup и доступ к диску. Кэшируйте неизменяемые class references, применяйте debounce к поиску и coalescing к серии одинаковых обновлений.
Отмена
Background callback может завершиться после выгрузки плагина. Храните generation token или флаг активности и проверяйте его перед обновлением UI. Не держите сильные ссылки на закрытый Fragment.
Измерение
Логируйте время только вокруг подозрительного блока. Удаляйте подробные timing logs перед релизом. Если callback занимает больше одного кадра, переносите работу из UI thread.
Developer guideДанные и миграции
Как хранить настройки и файлы плагина, переживать обновления и восстанавливаться после поврежденного состояния.
Настройки
get_setting() и set_setting() подходят для небольших scalar values. Преобразуйте boolean и числа явно, потому что bridge может вернуть строковое представление. Для структурированных данных используйте JSON-файл в 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.storage_path("state.json"))
version = 2
self.set_setting("schema", str(version))
Миграции выполняются последовательно и должны быть идемпотентными. Не записывайте новую schema version до успешного завершения шага.
Атомарная запись
Сначала пишите новый JSON во временный файл, синхронизируйте и заменяйте основной файл. При ошибке чтения сохраняйте поврежденный файл для диагностики и запускайтесь с безопасными defaults.
Секреты
Пакет и private storage не являются защищенным vault. Не помещайте bot tokens и постоянные ключи в плагин. Если сервис требует авторизацию, используйте короткоживущие пользовательские токены и возможность их отзыва.
Developer guideTL API и запросы
Создание Telegram TL objects, отправка запросов и обработка ошибок без привязки к выбранному аккаунту.
Создание запроса
Получайте класс через self.tl() или Java bridge, создавайте объект и заполняйте обязательные поля. Сверяйте тип каждого поля с текущей 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 получает response и error. Telegram RPC error не является Python exception, поэтому проверяйте error первым. Не обращайтесь к полям response, пока не проверили его тип.
Flood wait
Не повторяйте запрос немедленно. Покажите понятное сообщение или поставьте операцию в очередь с указанной задержкой. Автоматические циклы должны иметь лимит страниц и backoff.
Multi-account
InputPeer, controller и request должны относиться к одному account. Нельзя получить peer из кэша первого аккаунта и отправить его через ConnectionsManager второго.
Developer guideПроектирование интерфейса
Нативные настройки, bulletins, dialogs и интерактивные элементы без утечек Activity и конфликтов с темой.
Выбор компонента
Bulletin подходит для короткого результата действия и необязательной кнопки. Alert используется для подтверждения или выбора, который нельзя выполнить случайно. Постоянные параметры размещаются в settings rows. Не показывайте dialog для обычного успешного действия.
Тема
Не задавайте черный или белый текст напрямую. Используйте theme keys или нативные компоненты DevGram. Проверяйте дневную, ночную и системную тему, увеличенный шрифт и длинную локализацию.
Состояния
Экран обязан иметь loading, empty, error и content states. После действия обновляйте текущую модель сразу, не заставляйте пользователя закрывать и открывать экран. Для сети оптимистичное обновление допустимо только при корректном rollback.
Доступность
У icon-only кнопок должен быть content description. Touch target не должен становиться меньше системного минимума. Не кодируйте статус только цветом: добавляйте текст или знакомый символ.
Lifecycle
Перед показом UI получите текущий Fragment и проверьте его наличие. Callback кнопки не должен захватывать закрытую Activity. После выгрузки плагина зарегистрированные widgets удаляются.
Developer guideНадежные Java hooks
Практика низкоуровневых hooks и reflection с учетом обновлений Telegram и риска падения процесса.
Когда нужен hook
Сначала используйте публичные события и Client API. Hook оправдан, если нужной точки расширения нет. Он связывает плагин с конкретной реализацией Java-класса и требует более строгой проверки совместимости.
Поиск метода
Проверяйте полное имя класса, parameter types, static/instance и return type. Overload нельзя выбирать только по имени. После обновления клиента lookup может вернуть другой метод или завершиться ошибкой.
До и после
Before-hook может изменить аргументы или отменить оригинал; after-hook может заменить результат. Возвращаемый объект обязан быть совместим с Java type. Не заменяйте primitive значением None.
Защита
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
Выгрузка
Unhook должен выполняться всегда. Callback проверяет активность плагина и не удерживает View. Если hook критичен, объявите минимальную версию; если необязателен, отключите только связанную функцию.
Developer guideТестирование плагина
Матрица проверок перед публикацией: чистая установка, обновление, аккаунты, темы, lifecycle и восстановление после ошибок.
Минимальная матрица
| Сценарий | Что проверить |
|---|
| Clean install | Manifest, defaults, первый запуск, permissions. |
| Update | Миграции, сохранение настроек, замена ресурсов. |
| Disable/enable | Observers не дублируются, UI исчезает и возвращается. |
| Hot reload | Python modules очищены, hooks и handles заменены. |
| Uninstall | Нет callbacks и зарегистрированных pills. |
| Safe mode | Клиент запускается после намеренной ошибки плагина. |
Аккаунты
Проверьте минимум два аккаунта, переключение во время фоновой операции и событие от невыбранного аккаунта. Никакая операция не должна молча использовать текущий UI account вместо account события.
Интерфейс
Проверьте светлую и темную тему, маленький и большой экран, системный font scale, длинный русский и английский текст, пустые списки, offline и повторный запрос.
Негативные случаи
Повредите JSON, удалите asset, верните RPC error, закройте экран до завершения callback и вызовите cleanup дважды. Плагин должен деградировать локально, не ломая каталог и приложение.
Developer guideПубликация и обновления
Версионирование, changelog, совместимость, проверка архива и безопасный выпуск в каталог DevGram.
Версия
Используйте последовательную схему и увеличивайте версию для каждого загруженного пакета. Не публикуйте разные архивы под одной версией: пользователи и модераторы не смогут однозначно проверить содержимое.
Changelog
Пишите только пользовательские изменения: новые действия, измененное поведение, исправленные ошибки и важные ограничения. Отдельно укажите breaking changes и требуемую минимальную версию клиента.
Проверка
- Соберите пакет из чистого checkout.
- Просмотрите список файлов архива.
- Просканируйте токены, ключи и локальные пути.
- Установите release package на чистую копию DevGram.
- Проверьте подпись или checksum опубликованного файла.
Повторная подача
После отклонения исправьте указанную причину и увеличьте версию. Не меняйте plugin ID. Если плагин использует опасные hooks, опишите назначение и fallback для несовместимой версии.
Rollback
Храните предыдущий стабильный пакет и миграции, совместимые с откатом, когда это возможно. Никогда не удаляйте пользовательские данные только потому, что новая версия не смогла их разобрать.
Developer guideМодель безопасности
Плагин выполняется внутри процесса DevGram и имеет доступ к чувствительному контексту, поэтому безопасность начинается с минимальных полномочий и прозрачного поведения.
Доверенная граница
Python sandbox не делает неизвестный код безопасным. Плагин может обращаться к Java bridge и данным клиента. Устанавливайте пакеты только из понятного источника и показывайте пользователю, зачем нужны сеть, файлы или перехват сообщений.
Удаленные данные
Любые данные из Telegram, Intent, HTTP и файлов считаются недоверенными. Ограничивайте размеры, проверяйте схему JSON, нормализуйте пути и экранируйте разметку. Не передавайте пользовательскую строку в reflection lookup.
Сеть
Используйте HTTPS, timeouts и ограничение ответа. Не отключайте проверку сертификата. Не отправляйте сообщения, user IDs или токены в аналитику без явного назначения.
Логи
Не логируйте access tokens, cookies, полный текст приватных сообщений и содержимое файлов. Для диагностики используйте короткий request ID, тип операции и безопасный код ошибки.
Отказоустойчивость
Ошибка одной функции отключает ее локально. Не создавайте бесконечный retry и не падайте в callback главного потока. Safe mode должен позволять пользователю удалить проблемный пакет.
Developer guideСовместимость и версии
Как поддерживать несколько версий DevGram и Telegram core без случайных падений на отсутствующих методах.
Уровни стабильности
Публичные функции devgram.* являются предпочтительным уровнем. Java controllers меняются вместе с Telegram. Private reflection и method hooks являются самым хрупким уровнем и требуют version gate.
Feature detection
Проверяйте наличие класса, метода или атрибута, а не только строку версии. Версия полезна для понятного сообщения, но разные сборки могут иметь отличающийся набор backports.
try:
target = find_class(CLASS_NAME)
except Exception:
target = None
if target is None:
self.bulletin("Функция недоступна в этой версии")
Graceful fallback
Необязательная интеграция отключается отдельно, а остальные функции продолжают работать. Если без API плагин бессмысленен, остановите загрузку с понятной диагностикой и минимальной поддерживаемой версией.
Deprecated API
После появления замены поддерживайте старый путь ограниченное время. Не удаляйте настройку или меняйте ее смысл без migration. Указывайте deprecated методы в документации и changelog.
Developer guideДиагностика и отладка
Системный подход к ошибкам импорта, пустым экранам, зависаниям, двойным callbacks и падениям после обновления.
Импорт
Сначала проверьте полный traceback и первый frame внутри плагина. Типовые причины: файл отсутствует в архиве, регистр имени не совпадает, wheel несовместим или возник circular import.
Пустой интерфейс
Разделите загрузку данных и render. Показывайте loading до начала операции, затем content, empty или error. Если данные пришли, а экран пуст, логируйте generation экрана и проверяйте, что callback вернулся на UI thread.
Двойное действие
Почти всегда остался observer, hook или listener от предыдущего reload. Добавьте лог регистрации и cleanup с instance ID. Убедитесь, что on_unload() закрывает каждый handle.
Зависание
Снимите время выполнения callback и найдите I/O на main thread. Долгое зажатие, прокрутка или выделение часто вызывают hook многократно; никакой тяжелой работы внутри него быть не должно.
Краш после закрытия экрана
Background callback удерживает старый Context или View. Перед render получите актуальный Fragment, проверьте активность и отмените обновление, если generation изменилась.
Отчет об ошибке
Укажите версию DevGram, Android SDK, модель устройства, plugin ID/version, последовательность действий и полный stack trace. Секреты и личные сообщения удалите.
Developer guideГлоссарий
Ключевые термины DevGram Plugin SDK и различия между похожими объектами.
Account index
Локальный номер аккаунта в приложении. Не равен Telegram user ID и используется для выбора controller.
Dialog ID
Идентификатор диалога, который может представлять пользователя, группу или канал. Не воспринимайте знак числа как единственную проверку типа.
TL object
Java-представление типа из Telegram schema. Поля и subclasses зависят от текущей версии core.
Observer
Подписка на NotificationCenter. Возвращенный handle принадлежит плагину и закрывается при unload.
Hook
Перехват Java-метода до или после оригинального вызова. Отличается от публичного plugin event более жесткой зависимостью от реализации.
Entity
Диапазон форматирования Telegram текста. Offset и length измеряются в UTF-16 code units.
Safe mode
Режим восстановления, в котором проблемные callbacks не запускаются до ручного действия пользователя.
Hot reload
Выгрузка instance и Python modules с последующей загрузкой новой версии без полного перезапуска клиента.
Pill
Компактный интерактивный элемент Pill Stack, зарегистрированный конкретным plugin ID.
Builder
Экспериментальная DevGram-native спецификация высокоуровневой структуры проекта. Не все описанные фасады входят в стабильный runtime.
Reference indexDeep reference
Навигационная страница полного справочника DevGram SDK.
Стабильный Python API
Начните с SDK contracts, затем откройте страницу нужного verified module. Там перечислены фактические классы, функции, сигнатуры и методы текущей сборки.
Практические руководства
Для большого плагина используйте страницы об архитектуре, потоках, миграциях, тестировании и безопасности.
Низкоуровневый runtime
Java hooks, reflection, class proxy и TL API зависят от Telegram core. Проверяйте наличие API и предоставляйте fallback.
Экспериментальный Builder
Builder описывает целевую структуру проекта. Для исполняемого кода источником истины остаются verified modules.
BasePlugin: контракт и состояние
BasePlugin создаётся загрузчиком один раз на пакет. Поля id, name, version, author, description и icon используются каталогом и менеджером плагинов. Не меняйте id после публикации: он является ключом настроек, private storage и зарегистрированных hooks.
| Метод | Вызов | Контракт |
|---|
on_load() | После импорта | Регистрация hooks, observers, pills. Не блокировать UI. |
on_unload() | Перед reload/remove | Снять всё созданное плагином. Повторный вызов допустим. |
on_send_message(text) | Перед исходящим текстом | Вернуть новый текст, None без изменения или False для отмены. |
on_receive_message(text) | После входящего текста | Наблюдение и логика; изменение сообщения не гарантируется. |
on_update(update) | TL update | Java-объект TLRPC.Update. Проверяйте тип через getClass().getSimpleName(). |
on_setting_changed(key,value) | Из native settings | Значение приходит строкой; преобразуйте явно. |
HookResult и стратегия
Для account-aware callback можно вернуть HookResult. PASS оставляет оригинальное поведение, REPLACE подменяет результат, а переданные параметры заменяют аргументы до вызова оригинала. Не возвращайте случайный Python object: тип должен соответствовать Java-методу.
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: аккаунт и controllers
Все функции принимают необязательный account. Если он не передан, используется выбранный аккаунт. Для событий всегда передавайте account вручную.
| Функция | Возвращает | Когда использовать |
|---|
send_text(peer,text,account) | None | Простой текст. |
send_formatted_text(peer,text,entities,account) | None | Текст с native MessageEntity. |
get_messages_controller(account) | MessagesController | Диалоги, пользователи и чаты. |
get_connections_manager(account) | ConnectionsManager | Низкоуровневые TL-запросы. |
get_messages_storage(account) | MessagesStorage | Чтение локального кэша. Не блокировать main. |
get_media_data_controller(account) | MediaDataController | Медиа и стикеры. |
get_contacts_controller(account) | ContactsController | Контакты и username. |
get_file_loader(account) | FileLoader | Загрузка локальных media files. |
get_download_controller(account) | DownloadController | Очередь загрузок. |
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() возвращает ObserverHandle. Храните handle в поле объекта, закрывайте его в on_unload(). Callback получает numerical notification ID, account и Java-массив аргументов.
text_formatting
Entity имеет поля type, offset, length, а для ссылок и pre — url и language. Offset и length всегда UTF-16, не Python code points. Для 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
| Класс | kind | Поля |
|---|
Header(text) | header | Заголовок секции. |
Switch(key,text,default,on_change) | switch | Boolean хранится как 1/0. |
Input(key,text,default,on_change) | text | Строковое значение. |
Selector(key,text,items,on_change) | selector | Список вариантов через separator. |
Button(key,text) | button | Зовёт on_setting_click. |
android_utils
run_on_ui_thread(fn,delay) ставит callback в Telegram UI queue. run_on_queue(fn) использует global background queue. copy_to_clipboard() и bulletin должны вызываться после возвращения в UI.
file_utils и private storage
Глобальные file helpers работают с переданным абсолютным путём. Методы self.read_file() и self.write_file() работают только внутри приватной папки текущего plugin ID. Не используйте путь из пользовательского ввода без проверки.
intents
register() сортирует handlers по priority. Callback должен вернуть True, если Intent обработан; иначе dispatch продолжит поиск. HandlerHandle.unhandle() возвращает False, если handle уже снят.
Java class proxy
java_class(base,logic,arg_types,args) создаёт DexMaker subclass. Имена callable методов в logic становятся overrides. Внутри override используйте BasePlugin.java_super(). Класс не должен быть final и обязан иметь совместимый constructor.
Публикационный чек-лист
- Manifest валиден, ID стабилен, версия увеличена.
- Нет bot tokens, Firebase keys, signing keys и debug logs.
- Проверены Android 7+ и минимум два аккаунта.
- Проверены install, update, disable, enable, reload и uninstall.
- После краша safe mode восстанавливает приложение.
- Observer, hook, intent и pill очищаются в unload.
Extended referenceРасширенный справочник
Материалы из старой документации перенесены в современную структуру: практические Android-сценарии, CallFrame, визуальные эффекты, папки чатов и способы исследования Telegram classes.
Объект CallFrame
CallFrame представляет конкретный перехваченный вызов Java-метода. Он предоставляет receiver, массив аргументов и управление результатом. Перед изменением аргумента проверьте индекс, Java type и nullability. Для overload сначала сопоставьте полную сигнатуру метода, иначе hook может примениться к другой реализации.
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-типом.
Типы аргументов hook
Primitive types требуют точного boolean/int/long-compatible значения. Java String можно создавать из Python str через bridge, а сложные Telegram objects нельзя заменять словарем или произвольным Python class. Для nullable-параметра None допустим только если Java-контракт действительно разрешает null.
Подмена аргумента и результата
Изменение заголовка экрана выполняйте до оригинального метода, а обработку возвращенного объекта — после. Не подменяйте premium-флаги, authorization state и серверные ограничения: локальное значение не предоставляет серверных прав и может нарушить интерфейс.
Полезные классы Telegram
| Область | Примеры | Назначение |
|---|
| Экраны | LaunchActivity, BaseFragment, ChatActivity | Навигация и текущий контекст. |
| Ячейки | Dialog, message и settings cells | Отрисовка строк списков; сильно зависит от версии. |
| Данные | MessagesController, MessagesStorage | Кэш, пользователи, чаты и сообщения. |
| Сеть | ConnectionsManager | TL-запросы и состояние соединения. |
Как исследовать класс и метод
- Найдите пользовательское действие, которое вызывает нужное поведение.
- Найдите соответствующий Java class в исходниках текущей версии.
- Проверьте overload, parameter types и return type.
- Установите временный диагностический hook без изменения поведения.
- Удалите подробный logging после подтверждения сигнатуры.
Не переносите имена private-полей из старого APK без проверки: Telegram регулярно меняет структуру классов.
Визуальные эффекты
Blur, glass panel, tint и border должны учитывать тему, производительность устройства и lifecycle экрана. Эффект создается после появления View, а ресурсы освобождаются при detach. На слабом GPU уменьшайте blur radius и отключайте покадровое обновление.
Плавающая карточка
panel = self.glass_panel(
source_view,
corner=20,
blur=14,
tint=0x22FFFFFF,
border=0.5,
)
# Добавьте panel в контейнер экрана на UI thread.
# Удалите panel при закрытии Fragment.
GPU-шейдер AGSL
RuntimeShader доступен не на всех Android API. Проверяйте SDK, компиляцию shader source и предоставляйте обычный drawable fallback. Не передавайте пользовательский текст в shader source и не создавайте новый shader каждый кадр.
Папки и вкладки чатов
Циклический swipe папок требует знания текущего выбранного filter ID и количества доступных tabs. Не вычисляйте следующую вкладку по позиции View: порядок может измениться после синхронизации. Сохраняйте ID, а не индекс.
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)]
Экран чата: фон и overlays
Overlay не должен перехватывать touch events, закрывать системные inset или удерживать ChatActivity после выхода. Добавляйте View в правильный контейнер, учитывайте keyboard и удаляйте его при destroy.
Видеофон MP4
Для зацикленного видео используйте системный player с muted audio, surface lifecycle и остановкой при уходе приложения в background. Не декодируйте видео вручную на Python. Добавьте статичный fallback, лимит разрешения и настройку отключения для экономии батареи.
Хуки TL-запросов
Request hook может наблюдать имя и объект запроса до отправки. Не храните полный request с приватными данными. Изменение peer, random_id или authorization fields способно привести к дубликатам и ошибкам сервера. Для аналитики достаточно типа запроса и безопасного результата.
Java-интерфейсы из Python
Для listener используйте готовые proxy helpers или class proxy с точной сигнатурой метода. Ссылка на Python callback должна жить столько же, сколько Java listener. При unload снимите listener и очистите обе стороны ссылки.
Полный production checklist
- Низкоуровневые функции имеют feature detection.
- Каждый hook и listener снимается.
- View работает в обеих темах и не перекрывает input.
- Видео и shaders имеют fallback.
- Два аккаунта не смешивают controllers.
- Логи не содержат сообщения и токены.
- Плагин переживает reload и safe mode.