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

DevGramBuilder

Официальный инструмент для создания проекта, проверки исходников и сборки устанавливаемого .dgplugin.

Установка

python -m pip install --upgrade "git+https://github.com/DevGramOfficial/DevGramBuilder.git"
dgb --version

На Windows можно использовать py -3. Если Linux запрещает системную установку, создайте virtualenv или используйте pipx.

Первая сборка

mkdir my-plugin
cd my-plugin
dgb new
dgb build -a -v -nf

Готовый файл появится в builds/<id>-<version>.dgplugin. Его можно открыть в DevGram или загрузить через Dev Server.

Создание

dgb new создаёт рабочую структуру и пример.

Проверка

-a проверяет Python и JSON локалей.

Сборка

Исходный или скомпилированный пакет.

Устройство

dgb upload устанавливает пакет через Dev Server.

Репозиторий DevGramBuilder · Последний релиз

DevGram Builder

Быстрый старт

От пустой папки до готового файла за несколько команд.

1. Установите Builder

# Windows
py -3 -m pip install --upgrade "git+https://github.com/DevGramOfficial/DevGramBuilder.git"

# Linux / macOS
python3 -m pip install --upgrade "git+https://github.com/DevGramOfficial/DevGramBuilder.git"

2. Создайте проект

mkdir hello-devgram
cd hello-devgram
dgb new

Builder спросит название, автора, ID, версию, описание и ссылку на иконку. Для CI доступен неинтерактивный режим:

dgb new . --gen   --name "Hello DevGram"   --author "@username"   --id "username.hello"   --plugin-version "1.0.0"   --description "Первый плагин"

3. Соберите

dgb build -a -v -nf

-a включает проверку синтаксиса, -v — подробный вывод, -nf исключает служебную папку Builder из релиза.

4. Установите

Отправьте файл из builds/ в Telegram, откройте его в DevGram и нажмите «Установить». Для быстрой разработки используйте dgb upload.

DevGram Builder

Структура проекта

Исходный проект и готовый архив имеют предсказуемую структуру.

Проект

my-plugin/
├── devgram-builder.json
├── .devgrambuilder/config.json
├── src/
│   ├── main.py
│   └── helpers.py
├── assets/
├── locales/
│   ├── en.json
│   └── ru.json
├── wheels/
└── builds/

Готовый .dgplugin

manifest.json
main.py
helpers.py
assets/...
locales/en.json
locales/ru.json
wheels/...

Файлы из src/ попадают в корень архива. Builder создаёт manifest из devgram-builder.json. Git, IDE-файлы, builds/, кэш Python и логи исключаются автоматически.

.devgrambuilder/config.json

{
  "source": "src",
  "ignoreAll": [".git/**", "builds/**", "**/__pycache__/**"],
  "optionalAssets": [],
  "compilationIgnore": []
}
ПолеНазначение
sourceКаталог Python-исходников.
ignoreAllНикогда не включаемые файлы.
optionalAssetsИсключаются флагом --no-assets.
compilationIgnoreОстаются исходниками при сборке -c.
DevGram Builder

Справочник CLI

Команды DevGramBuilder 1.0.

КомандаНазначение
dgb new [directory]Создать проект.
dgb buildСобрать .dgplugin.
dgb watch secondsПересобирать при изменениях.
dgb cachedСравнить файлы с кэшем.
dgb add-ignore / del-ignoreНастроить исключения.
dgb statsСтатистика сборок, файлов, строк и размера.
dgb uploadУстановить через Dev Server.

Флаги build

ФлагЗначение
-a, --astПроверить Python и локали.
-c [0|1|2]Скомпилировать Python 3.11.
-vПодробный вывод.
-nfНе включать служебный config.
-rСбросить кэш.
-niНе записывать сведения о Builder.
--no-assetsИсключить optionalAssets.
-o PATHВыходной файл.

Ignore

dgb add-ignore "assets/source.psd" --all
dgb add-ignore "assets/large/**" --no-assets
dgb add-ignore "src/debug.py" --compile
dgb del-ignore 0 --all

Статистика

dgb stats builds
dgb stats files
dgb stats lines
dgb stats size

Полная справка: dgb --help и dgb build --help.

DevGram Builder

Метаданные проекта

devgram-builder.json преобразуется в пакетный manifest.json.

{
  "id": "username.my_plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "author": "@username",
  "description": "Что делает плагин",
  "icon": "https://example.com/icon.png",
  "main": "main.py",
  "min_app_version": "12.10.3",
  "min_devgram": "3",
  "requirements": []
}
ПолеПравило
idСтабильный ID до 64 символов: буквы, цифры, ., _, -.
name, version, authorОбязательны для Builder.
mainТочка входа относительно src/; обычно main.py.
description, iconОписание и прямая HTTPS-ссылка на изображение.
min_app_versionМинимальная версия DevGram.
min_devgramМинимальный уровень Plugin API; текущий — 3.
requirementsДо 32 имён зависимостей из wheels/.
Не меняйте ID после публикации

Другой ID воспринимается как новый плагин и получает отдельные настройки.

DevGram Builder

Модули и импорты

Проект делится обычными Python-файлами внутри src/.

# src/helpers.py
def greeting(name):
    return f"Привет, {name}!"

# src/main.py
from devgram import BasePlugin
from .helpers import greeting

class HelloPlugin(BasePlugin):
    id = "username.hello"
    name = "Hello"
    version = "1.0.0"
    author = "@username"

    def on_load(self):
        self.bulletin(greeting(self.name), kind="success")

    def on_unload(self):
        self.unregister_pills()
        self.unhook_all()

Загрузчик сам найдёт класс-наследник BasePlugin и создаст экземпляр. Строка plugin = HelloPlugin() не требуется. Для подпапок добавляйте __init__.py и используйте относительные импорты.

Lifecycle

Listeners, hooks, observers и pills создавайте в on_load(), а закрывайте в on_unload().

DevGram Builder

Ресурсы и локализация

Builder переносит assets/ и locales/ с сохранением структуры.

Assets

# assets/icons/cat.png
path = self.asset_path("icons/cat.png")

asset_path() возвращает безопасный абсолютный путь только к существующему файлу внутри assets/. Регистр имени важен.

Локализация

# locales/ru.json
{"welcome": "Привет, {name}!"}

text = self.string("welcome", "Hello!", name="FireDragoq")

Английский используется как fallback, затем применяются строки языка приложения. Builder проверяет, что locale-файл является JSON-объектом строк.

Необязательные ресурсы

Добавьте шаблоны в optionalAssets и используйте dgb build --no-assets.

DevGram Builder

Настройки плагина

Builder упаковывает код, а экран настроек создаётся публичным DevGram Plugin SDK.

from devgram import BasePlugin
from devgram.ui import Header, Switch, Input, Selector, Button, Text

class Plugin(BasePlugin):
    id = "username.settings"

    def settings(self):
        return [
            Header(text="Основное"),
            Switch(key="enabled", text="Включено", default=True),
            Input(key="name", text="Имя", default="DevGram"),
            Selector(key="style", text="Стиль", items=["system", "compact"]),
            Button(key="test", text="Проверить"),
            Text(text="Версия 1.0.0"),
        ]

    def on_setting_changed(self, key, value):
        self.log(f"{key}={value}")

    def on_setting_click(self, key):
        if key == "test":
            self.bulletin("Работает", kind="success")

Отдельного runtime API Builder нет: компоненты и callbacks берутся из devgram.ui.

DevGram Builder

Зависимости

Зависимости поставляются внутри пакета: DevGram ничего не скачивает при установке.

  1. Скачайте совместимый .whl.
  2. Положите его в wheels/.
  3. Добавьте имя distribution в requirements.
# devgram-builder.json
"requirements": ["requests"]

# файл
wheels/requests-2.32.3-py3-none-any.whl

Builder проверяет соответствие имён и файлов. Допускается максимум 32 зависимости.

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

Предпочитайте py3-none-any. Wheel с нативным кодом должен быть собран для Android, ABI устройства и Python 3.11.

Стандартная библиотека Python и devgram.* в requirements не добавляются.

DevGram Builder

Сборка и разработка

Проверка, компиляция, watch-режим и кэш.

Исходная сборка

dgb build -a -v -nf

-a проверяет Python через AST и JSON локалей. Исходники остаются в пакете.

Компилированная сборка

dgb build -c 2 -v -nf

-c создаёт .pyc и меняет точку входа на main.pyc. Нужен Python 3.11; уровни оптимизации — 0, 1 или 2.

Watch

dgb watch 2 --args "-a -v -nf"

Изменения проверяются каждые две секунды.

Кэш

dgb cached
dgb build -c 2 -r -v -nf

cached показывает изменённые файлы, -r очищает кэш компиляции.

Свой выходной путь

dgb build -a -o dist/plugin.dgplugin
DevGram Builder

Решение проблем Builder

Установка CLI, конфигурация, компиляция и загрузка.

dgb не найден

Проверьте python -m devgram_builder --version. Если модуль работает, добавьте Python Scripts в PATH. На Windows: py -3 -m devgram_builder.

externally-managed-environment

python3 -m venv .venv
source .venv/bin/activate
python -m pip install "git+https://github.com/DevGramOfficial/DevGramBuilder.git"

devgram-builder.json не найден

Запускайте команду внутри проекта. Для нового проекта выполните dgb new.

Точка входа не найдена

main указывается относительно src/. Проверьте регистр и ignore-правила.

Для -c нужен Python 3.11

Установите Python 3.11. На Windows Builder также ищет py -3.11.

Нет wheel для зависимости

Имя из requirements должно соответствовать файлу в wheels/.

Dev Server вернул ошибку

Проверьте ADB, режим разработчика, порт и актуальный токен. invalid package означает ошибку структуры пакета.

Getting started

Подготовка

Среда для разработки, сборки и быстрой установки плагинов.

Нужно установить

  • актуальный DevGram;
  • Python 3.10 или новее для Builder;
  • Python 3.11 для компилированной сборки -c;
  • ADB для Dev Server;
  • редактор с поддержкой Python.

DevGramBuilder

python -m pip install --upgrade "git+https://github.com/DevGramOfficial/DevGramBuilder.git"
dgb --version

Режим разработчика

В DevGram откройте Плагины → Система плагинов, включите режим разработчика и скопируйте порт и токен Dev Server.

adb devices
adb forward tcp:42690 tcp:42690
Безопасность

Токен даёт локальный доступ к установке плагина. Не публикуйте его; при утечке используйте кнопку сброса токена.

Getting started

Первый .dgplugin

Создайте проект, измените пример и получите устанавливаемый пакет.

mkdir hello-devgram
cd hello-devgram
dgb new

Откройте src/main.py. Минимальная точка входа:

from devgram import BasePlugin

class Hello(BasePlugin):
    id = "username.hello"
    name = "Hello DevGram"
    version = "1.0.0"
    author = "@username"
    description = "Мой первый плагин"

    def on_load(self):
        self.bulletin("Плагин загружен", kind="success")

    def on_unload(self):
        pass

Те же значения укажите в devgram-builder.json. Для пакетного плагина manifest имеет приоритет над атрибутами класса.

Сборка

dgb build -a -v -nf

Откройте готовый файл из builds/ в DevGram. После установки проверьте включение, выключение, повторную загрузку и удаление.

Пакет .dgplugin

manifest.json

Manifest находится строго в корне архива и описывает пакет до запуска кода.

{
  "id": "username.my_plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "author": "@username",
  "description": "Описание плагина",
  "icon": "https://example.com/icon.png",
  "main": "main.py",
  "min_app_version": "12.10.3",
  "min_devgram": "3",
  "requirements": []
}
ПолеНазначение
idОбязательный ID до 64 символов. Разрешены буквы, цифры, точки, дефисы и подчёркивания.
mainСуществующая точка входа внутри архива; по умолчанию main.py.
name, version, authorМетаданные карточки и каталога.
description, iconОписание и HTTPS-иконка.
min_app_versionМинимальная версия приложения.
min_devgram / min_sdkМинимальный уровень Plugin API.
requirementsИмена зависимостей, присутствующих в wheels/.
Правильное имя поля

Используется main, а не entrypoint. DevGramBuilder формирует manifest автоматически.

Приоритет

В пакетном плагине поля id, name, version, author, description и icon из manifest заменяют атрибуты класса.

Пакет .dgplugin

Иконка плагина

Иконка отображается в карточке установки, менеджере и каталоге.

В проекте Builder

{
  "id": "username.my_plugin",
  "name": "My Plugin",
  "icon": "https://example.com/plugin-icon.png"
}

Добавьте icon в devgram-builder.json; Builder перенесёт ссылку в manifest.

Требования

  • прямая HTTPS-ссылка на изображение, а не HTML-страницу;
  • PNG или JPG, желательно квадратный;
  • стабильный URL без временного токена;
  • контраст на светлой и тёмной теме.

При отсутствии или ошибке загрузки DevGram показывает стандартную иконку.

Пакет .dgplugin

Assets и локализация

Ресурсы извлекаются вместе с пакетом в приватный каталог плагина.

Assets

assets/background.png
assets/icons/action.png

background = self.asset_path("background.png")
action_icon = self.asset_path("icons/action.png")

Метод возвращает пустую строку, если файл отсутствует или путь выходит за пределы assets/.

Локали

# locales/en.json
{"welcome": "Hello, {name}!"}

# locales/ru.json
{"welcome": "Привет, {name}!"}

text = self.string("welcome", "Hello!", name="FireDragoq")

Английский используется как fallback, после него применяются строки текущего языка. Ключи и значения должны быть строками.

Изменяемые данные

assets/ предназначен для файлов поставки. Пользовательские данные храните через plugin_files_dir(), write_file() и read_file().

Пакет .dgplugin

Wheel-зависимости

Внешние библиотеки поставляются внутри пакета.

my-plugin.dgplugin/
├── manifest.json
├── main.py
└── wheels/
    └── requests-2.32.3-py3-none-any.whl
"requirements": ["requests"]

DevGram добавляет все .whl из wheels/ в Python path. Установщик не обращается к PyPI и не запускает pip.

Правила

  • не более 32 простых имён в requirements;
  • URL, локальные пути и pip-флаги запрещены;
  • каждому имени должен соответствовать wheel;
  • _ и - нормализуются одинаково.
Нативные библиотеки

Desktop wheel не подходит Android. Используйте py3-none-any либо сборку для нужного Android ABI и Python 3.11.

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

Локальная установка .dgplugin по ADB без пересылки через Telegram.

Подготовка

  1. Включите режим разработчика DevGram.
  2. Скопируйте порт и токен.
  3. Подключите телефон и подтвердите ADB.

Загрузка через Builder

# Windows PowerShell
$env:DEVGRAM_TOKEN="ваш_токен"
dgb upload

# Linux / macOS
export DEVGRAM_TOKEN="ваш_токен"
dgb upload

Без аргумента Builder сначала собирает проект. Готовый архив можно указать явно:

dgb upload builds/my.plugin-1.0.0.dgplugin --token "ваш_токен"

Как это работает

Builder создаёт ADB forwarding на порт 42690, отправляет multipart-запрос на локальный /upload и передаёт токен в заголовке. Сервер проверяет пакет и устанавливает его включённым.

Диагностика

adb devices
adb forward tcp:42690 tcp:42690
curl -H "X-DevGram-Token: ваш_токен" http://127.0.0.1:42690/status
Токен приватный

Не публикуйте его. При утечке сбросьте токен в приложении.

Публикация

Безопасность

Код плагина выполняется внутри процесса клиента, поэтому доверяйте источнику пакета.

  • не храните секреты в архиве;
  • не блокируйте 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 в исходниках или архиве. Перед публикацией сканируйте весь пакет и историю сборки.

Публикация

Проблемы .dgplugin

Ошибки сборки, проверки, установки и загрузки.

Пакет не открывается

  • проверьте расширение .dgplugin;
  • убедитесь, что это ZIP;
  • проверьте manifest.json и файл из поля main в корне;
  • соберите заново через dgb build -a -v -nf.

invalid package manifest

Отсутствует ID, ID длиннее 64 символов, содержит запрещённый знак либо main указывает на отсутствующий или небезопасный путь.

missing bundled wheel

Для имени из requirements нет подходящего файла в wheels/.

Установился, но не загрузился

Найдите первую ошибку package load или load в логах плагинов. Обычно это импорт, несовместимый wheel или исключение на уровне модуля.

Обновление откатилось

Новая версия не загрузилась, поэтому DevGram восстановил предыдущий архив. Исправьте первую ошибку и увеличьте version.

HTTP 401

Скопируйте актуальный токен. При необходимости сбросьте его и обновите DEVGRAM_TOKEN.

HTTP 500

Проверьте ADB forwarding, формат multipart и актуальность Dev Server.

После reload событие вызывается дважды

on_unload() должен закрывать observers, listeners, hooks, таймеры, panel tabs и pills.

Публикация

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 клиента.

Разделение ответственности

DevGramBuilder создаёт архив, а модули devgram.* предоставляют runtime API внутри приложения.

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-зависимости (см. следующий раздел).
Пакет .dgplugin

Формат .dgplugin

Точная структура, проверка, установка и обновление пакетного плагина.

Что это

.dgplugin — ZIP-архив с обязательным manifest.json и Python-точкой входа. Расширение менять нельзя.

plugin.dgplugin
├── manifest.json
├── main.py или main.pyc
├── helpers.py
├── assets/
├── locales/
└── wheels/

Проверка загрузчиком

  • корректный ZIP и JSON-object в manifest.json;
  • валидный id и существующий путь main;
  • нет абсолютных путей, компонентов .. и повторяющихся имён;
  • не более 4096 файлов;
  • каждой записи requirements соответствует wheel;
  • минимальные версии совместимы с установленным DevGram.

Установка и обновление

DevGram валидирует пакет, копирует его во временный файл и только затем заменяет установленную версию. Если новая версия не загрузилась, восстанавливается предыдущий архив. Файлы извлекаются в приватную папку .devgram/<id>.

Правильная сборка

dgb build -a -v -nf
# builds/username.my_plugin-1.0.0.dgplugin

Не архивируйте проект вручную: Builder создаёт правильный корень, manifest и воспроизводимый порядок файлов.

Исходник или pyc

Обычная сборка хранит .py. dgb build -c 2 -v -nf создаёт .pyc для Python 3.11. Компиляция не защищает секреты: токены нельзя помещать в пакет.

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

Публикация .dgplugin

Подготовка версии для пользователей и каталога DevGram.

Release-сборка

dgb build -a -v -nf

Используйте -c 2 только при необходимости; исходная сборка проще для проверки и модерации.

Перед публикацией

  1. Увеличьте version, не меняя id.
  2. Проверьте описание, автора, HTTPS-иконку и минимальные версии.
  3. Удалите токены, приватные URL, тестовые данные и логи.
  4. Проверьте список файлов архива.
  5. Установите начисто и поверх предыдущей версии.
  6. Проверьте enable, disable, reload, удаление и safe mode.
  7. Отправьте файл именно из builds/.

Обновления

Одинаковый ID обновляет существующий плагин. Замена атомарная; при ошибке загрузки восстанавливается предыдущий архив.

Changelog

Опишите пользовательские изменения, новые разрешения или сетевые обращения, минимальную версию и несовместимые изменения.

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

Официальный CLI для создания проекта, проверки исходников и сборки .dgplugin. Runtime API предоставляют обычные модули devgram.*.

Reference index

Deep reference

Навигационная страница полного справочника DevGram SDK.

Стабильный Python API

Начните с SDK contracts, затем откройте страницу нужного verified module. Там перечислены фактические классы, функции, сигнатуры и методы текущей сборки.

Практические руководства

Для большого плагина используйте страницы об архитектуре, потоках, миграциях, тестировании и безопасности.

Низкоуровневый runtime

Java hooks, reflection, class proxy и TL API зависят от Telegram core. Проверяйте наличие API и предоставляйте fallback.

DevGramBuilder

Builder отвечает за структуру и упаковку проекта. Исполняемый код использует публичные модули devgram.*.

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.