EN
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

Медиа, форматирование, аккаунты и контроллеры.

Native

Java class proxy и method hooks.

Порядок чтения

  1. Подготовка и первый пакет
  2. BasePlugin и события
  3. Client API и UI
  4. Разработка, публикация и безопасность
''

Модель выполнения

Плагин загружается отдельным runtime-модулем, но работает внутри процесса DevGram. Поэтому ошибка в Java bridge, бесконечный цикл или тяжелая работа на UI thread способны повлиять на клиент. Относитесь к каждому callback как к коду production-приложения: проверяйте входные данные, ограничивайте время работы и освобождайте ресурсы.

Что считается публичным API

Стабильный слой находится в модулях devgram.*. Поля и методы с ведущим подчеркиванием, внутренние классы Telegram и конкретные layout IDs не являются контрактом. Если приходится использовать низкоуровневую точку, добавьте проверку версии и fallback.

Минимальный стандарт плагина

  1. Уникальный ID и понятные metadata.
  2. Отсутствие секретов в архиве.
  3. Идемпотентный load/unload.
  4. Работа в светлой, темной и системной теме.
  5. Корректное поведение после reload, disable и safe mode.
DevGram Builder

DevGram Builder

Высокоуровневый слой над DevGram SDK для больших плагинов: структура проекта, модули, ресурсы и декларативные настройки.

Зачем Builder

Builder отделяет бизнес-логику от lifecycle и Android-моста. Один проект можно собирать в нативный .dgplugin без ручного управления импортами, локалями и ресурсами.

Модули

Разделяйте hooks, screens, commands и services.

Resources

Assets, localization и metadata имеют предсказуемые пути.

Lifecycle

Init, enable, disable и dispose управляются проектом.

Public API

Типизированные фасады над client_utils и UI.

Важно

DevGram Builder — DevGram-native слой. Он не запускает плагины exteraGram и не меняет формат нашего пакета.

DevGram Builder

Quick 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 Builder

Project 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 Builder

Public 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 Builder

Metadata

Файл 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 Builder

Modules & 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 Builder

Assets & 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 Builder

Settings

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 Builder

Dependencies

Есть два типа зависимостей: модули проекта и 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 Builder

Development

Сборка, проверка и 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 Builder

Troubleshooting

Диагностика проблем структуры, импортов и 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().

Пошаговая проверка

  1. Установите пакет и убедитесь, что он появился в менеджере.
  2. Откройте настройки и измените значение.
  3. Перезагрузите только этот плагин.
  4. Отправьте тестовое сообщение из второго аккаунта.
  5. Отключите и удалите плагин, затем проверьте отсутствие callback.

Типовые ошибки

Если плагин не виден, проверьте расширение, корень архива и уникальность ID. Если он виден, но не запускается, смотрите первую ошибку импорта: последующие сообщения часто являются следствием.

Package

Manifest

Метаданные определяют идентичность и точку входа расширения.

{
  "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.
entrypointPython-файл точки входа.
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 загружает аватарку при открытии карточки установки и кеширует её для списка плагинов.

Package

Assets и языки

Ресурсы пакета .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) доступны без зависимостей.

SDK

BasePlugin

Главный класс расширения: жизненный цикл, метаданные, хранилище и точка входа во всё 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Карточка установки и список плагинов.
iconURL или путь ассета для аватарки плагина.
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
Хуки Javahook, 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 останутся «двойные» хуки и утечки.

SDK

Multi-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_idID чата, где открыто меню.

Меню долгого нажатия

Возвращайте стабильные человекочитаемые labels и держите их короткими. Обработчик должен повторно проверить message и dialog перед действием: между построением меню и нажатием сообщение могло быть удалено или изменено.

Несколько плагинов

Не используйте слишком общие названия и не переопределяйте чужие команды. ID плагина добавляется загрузчиком к внутреннему ключу, но отображаемый текст должен объяснять действие.

Ошибки

Если действие требует сети, сразу покажите bulletin о запуске, выполните запрос в queue и обновите результат на UI thread. Не блокируйте меню ожиданием ответа.

SDK

Client 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-16

offset/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)
Ключи стабильны

Не переименовывайте ключи между версиями — потеряются сохранённые значения. Для переименования сделайте миграцию: прочитать старый ключ, записать новый, удалить старый.

Utilities

Intents

Регистрация обработчиков 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()

ПараметрОписание
actionAndroid 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 не должен остаться в глобальном списке.

Utilities

File 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.

Utilities

Android 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 и runtime

Bulletins и 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 и runtime

Pill 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_id

build_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 и runtime

Java 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 и runtime

Reflection

Доступ к 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-код в покадровых методах.

Диагностический порядок

  1. Воспроизведите на чистом запуске.
  2. Запишите plugin ID/version и точный экран.
  3. Проверьте первую строку traceback.
  4. Отключите только проблемный plugin.
  5. Повторите после reload и после полного restart.

Каталог пуст

Отделите ошибку сети от ошибки render: покажите loading, empty и error отдельно, проверьте callback на UI thread и убедитесь, что фильтры не применяются до загрузки данных.

Падение после обновления

Сравните manifest, schema migration, imports и optional hooks. При несовместимом API лучше отключить функцию и показать предупреждение, чем падать при каждом открытии.

Публикация

API reference

Краткий список публичных модулей DevGram SDK.

МодульНазначение
devgramBasePlugin, AccountClient, hooks, UI helpers и lifecycle.
devgram.client_utilsAccount-aware controllers, sending и NotificationCenter.
devgram.text_formattingEntities, HTML, Markdown и UTF-16.
devgram.android_utilsПотоки, listeners, clipboard и log.
devgram.file_utilsТекстовые и бинарные файлы.
devgram.intentsРегистрация и отправка Android Intent.
devgram.hook_utilsReflection helpers.
devgram.uiSettings, 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 API

SDK 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

ПолеТипНазначение
idstrПостоянный уникальный ID. Используется для настроек, файлов, hooks и обновлений.
namestrОтображаемое имя.
versionstrВерсия пакета; увеличивайте перед публикацией.
authorstrАвтор или команда.
descriptionstrКраткое описание без разметки.
iconstrURL PNG/JPG для списка плагинов.
min_app_versionstrМинимальная совместимая версия 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 objectTL-запросы и сетевое состояние.
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

  1. В on_load() только регистрируйте ресурсы и запускайте короткие операции.
  2. Все Android View и уведомления изменяйте на UI thread.
  3. Файлы, JSON, сеть и большие циклы выполняйте в queue.
  4. В on_unload() закрывайте observers, intent handles, hooks и pills.
  5. Cleanup должен быть идемпотентным: повторный вызов не должен падать.
  6. Callback после выгрузки должен проверять, что плагин еще активен.

Совместимость

Публичный Python API стабильнее Java reflection, но также развивается. Не импортируйте приватные имена с подчеркиванием. Перед публикацией тестируйте установку, обновление поверх старой версии, отключение, повторное включение, hot reload, удаление и запуск после safe mode.

Verified API · android_utils

Android Utils

Подробная справка по модулю android_utils, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

Функции

ИмяСигнатураНазначение
run_on_ui_threadrun_on_ui_thread(func, delay=0)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
run_on_queuerun_on_queue(func)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
loglog(data)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
copy_to_clipboardcopy_to_clipboard(text)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
RR(fn)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
OnClickListenerOnClickListener(fn)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
OnLongClickListenerOnLongClickListener(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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · client_utils

Client Utils

Подробная справка по модулю client_utils, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

Функции

ИмяСигнатураНазначение
send_textsend_text(peer, text, account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
send_formatted_textsend_formatted_text(peer, text, entities, account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_messages_controllerget_messages_controller(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_user_configget_user_config(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_connections_managerget_connections_manager(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_account_instanceget_account_instance(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_send_messages_helperget_send_messages_helper(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_media_data_controllerget_media_data_controller(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_contacts_controllerget_contacts_controller(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_messages_storageget_messages_storage(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_notification_centerget_notification_center(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_file_loaderget_file_loader(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_media_controllerget_media_controller()Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_notifications_controllerget_notifications_controller(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_notifications_settingsget_notifications_settings(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_location_controllerget_location_controller(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_secret_chat_helperget_secret_chat_helper(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_download_controllerget_download_controller(account=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_last_fragmentget_last_fragment()Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
observeobserve(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).

Методы

МетодСигнатураКонтракт
didReceivedNotificationdidReceivedNotification(self, notification_id, account, args)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.

ObserverHandle

Публичный класс DevGram SDK.

Методы

МетодСигнатураКонтракт
__init____init__(self, center, delegate, notification_id)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
closeclose(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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · dev_server

Dev Server

Подробная справка по модулю dev_server, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

Функции

ИмяСигнатураНазначение
_connect_connect(platform, host, port, wait)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
start_debuggerstart_debugger(platform='vscode', host='127.0.0.1', port=5678, wait=False)Connect to a debugger exposed by the computer through adb reverse.
stop_debuggerstop_debugger()Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
debugger_statusdebugger_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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · file_utils

File Utils

Подробная справка по модулю file_utils, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

Функции

ИмяСигнатураНазначение
ensure_dir_existsensure_dir_exists(path)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
list_dirlist_dir(path, extensions=None, recursive=False, include_files=True, include_dirs=False)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
read_fileread_file(path)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
write_filewrite_file(path, content)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
read_file_bytesread_file_bytes(path)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
write_file_byteswrite_file_bytes(path, content)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
delete_filedelete_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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · hook_utils

Hook Utils

Подробная справка по модулю hook_utils, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

Функции

ИмяСигнатураНазначение
find_classfind_class(class_name)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
_field_field(clazz, name)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_private_fieldget_private_field(obj, name)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
set_private_fieldset_private_field(obj, name, value)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
get_static_private_fieldget_static_private_field(clazz, name)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
set_static_private_fieldset_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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · intents

Intents

Подробная справка по модулю intents, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

Функции

ИмяСигнатураНазначение
registerregister(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)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
dispatchdispatch(intent)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
open_uriopen_uri(uri)Open a URI using Android's resolver.
sendsend(intent)Launch an explicitly constructed Android Intent.

IntentContext

Публичный класс DevGram SDK.

Методы

МетодСигнатураКонтракт
__init____init__(self, intent)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.

HandlerHandle

Публичный класс DevGram SDK.

Методы

МетодСигнатураКонтракт
__init____init__(self, entry)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
unhandleunhandle(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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · text_formatting

Text Formatting

Подробная справка по модулю text_formatting, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

Функции

ИмяСигнатураНазначение
to_tlrpc_entitiesto_tlrpc_entities(entities)Convert Entity objects/dicts to Java ArrayList<TLRPC.MessageEntity>.
add_surrogatesadd_surrogates(text)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
remove_surrogatesremove_surrogates(text)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
utf16_lengthutf16_length(text)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
parse_htmlparse_html(text)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
parse_markdownparse_markdown(text)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
_python_index_for_utf16_python_index_for_utf16(text, offset)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
parse_textparse_text(text, parse_mode=None)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
escape_markdownescape_markdown(text)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.
escape_htmlescape_html(text)Рабочая функция модуля; проверяйте возвращаемое значение и поток выполнения.

Entity

Публичный класс DevGram SDK.

_HTMLToEntities

Публичный класс DevGram SDK.

Методы

МетодСигнатураКонтракт
__init____init__(self)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
lengthlength(self)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
handle_datahandle_data(self, data)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
handle_starttaghandle_starttag(self, tag, attrs)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
handle_endtaghandle_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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · ui.alert

Ui.Alert

Подробная справка по модулю ui.alert, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

AlertDialogBuilder

Публичный класс DevGram SDK.

Методы

МетодСигнатураКонтракт
__init____init__(self, context=None, progress_style=0, resources_provider=None)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
set_titleset_title(self, value)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
set_messageset_message(self, value)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
set_positive_buttonset_positive_button(self, text, listener=None)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
set_negative_buttonset_negative_button(self, text, listener=None)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
set_neutral_buttonset_neutral_button(self, text, listener=None)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
createcreate(self)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
showshow(self)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
dismissdismiss(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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · ui.bulletin

Ui.Bulletin

Подробная справка по модулю ui.bulletin, сверенная с исходниками текущего DevGram runtime.

Совместимость

Публичными считаются перечисленные ниже символы. Перед публикацией проверяйте наличие API в целевой версии DevGram и обрабатывайте ошибки Java-моста.

BulletinHelper

Публичный класс DevGram SDK.

Методы

МетодСигнатураКонтракт
_show_show(kind, message, duration, button, callback)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
show_infoshow_info(cls, message, fragment=None, duration=DURATION_LONG, button=None, callback=None)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
show_successshow_success(cls, message, fragment=None, duration=DURATION_SHORT, button=None, callback=None)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
show_errorshow_error(cls, message, fragment=None, duration=DURATION_LONG, button=None, callback=None)Проверяйте типы аргументов и освобождайте созданные ресурсы при выгрузке плагина.
showshow(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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и RPC.
Verified API · ui.settings

Ui.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_rowas_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, которые меняются между версиями.

Проверка

  1. Default и явный account.
  2. Пустые, длинные и Unicode-значения.
  3. Повторный вызов после reload.
  4. Вызов после закрытия экрана.
  5. Ошибка сети, файла и 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 доступна, сторонние пакеты — нет.

Установка и распространение

  1. Отправь файл .plugin в чат или канал.
  2. Тап по файлу → карточка установки (иконка, имя, версия, автор, значок проверки) → «Установить».
  3. Управление: Настройки → 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, токены и временные файлы. Большие изображения оптимизируйте заранее.

Проверка архива

  1. Распакуйте пакет во временный каталог.
  2. Проверьте manifest и импорт entrypoint.
  3. Убедитесь, что wheels подходят встроенной версии Python и ABI.
  4. Проверьте отсутствие абсолютных путей и traversal entries.
  5. Установите пакет поверх предыдущей версии и отдельно на чистую установку.
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 guide

TL 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 installManifest, defaults, первый запуск, permissions.
UpdateМиграции, сохранение настроек, замена ресурсов.
Disable/enableObservers не дублируются, UI исчезает и возвращается.
Hot reloadPython 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 и требуемую минимальную версию клиента.

Проверка

  1. Соберите пакет из чистого checkout.
  2. Просмотрите список файлов архива.
  3. Просканируйте токены, ключи и локальные пути.
  4. Установите release package на чистую копию DevGram.
  5. Проверьте подпись или 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 index

Deep 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 updateJava-объект 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)switchBoolean хранится как 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.

Публикационный чек-лист

  1. Manifest валиден, ID стабилен, версия увеличена.
  2. Нет bot tokens, Firebase keys, signing keys и debug logs.
  3. Проверены Android 7+ и минимум два аккаунта.
  4. Проверены install, update, disable, enable, reload и uninstall.
  5. После краша safe mode восстанавливает приложение.
  6. 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Кэш, пользователи, чаты и сообщения.
СетьConnectionsManagerTL-запросы и состояние соединения.

Как исследовать класс и метод

  1. Найдите пользовательское действие, которое вызывает нужное поведение.
  2. Найдите соответствующий Java class в исходниках текущей версии.
  3. Проверьте overload, parameter types и return type.
  4. Установите временный диагностический hook без изменения поведения.
  5. Удалите подробный 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

  1. Низкоуровневые функции имеют feature detection.
  2. Каждый hook и listener снимается.
  3. View работает в обеих темах и не перекрывает input.
  4. Видео и shaders имеют fallback.
  5. Два аккаунта не смешивают controllers.
  6. Логи не содержат сообщения и токены.
  7. Плагин переживает reload и safe mode.