DevGram docs
Сайт

Плагины DevGram

Пиши расширения на Python, которые меняют поведение и внешний вид клиента.

Введение

Плагин — это файл .plugin (или .py) с классом-наследником BasePlugin. Внутри клиента работает встроенный Python 3.11 — код плагина выполняется прямо в приложении, без root и без внешнего Xposed-фреймворка.

Что умеет плагин:

  • менять текст исходящих и входящих сообщений;
  • добавлять пункты в меню сообщения и свою страницу настроек;
  • показывать тосты, плашки и диалоги, читать/писать буфер и файлы;
  • через хуки методов — переопределять поведение любого Java-метода клиента.

Требования

  • DevGram для Android (Android 7.0+, arm64).
  • Знание Python. Стандартная библиотека доступна; сторонние пакеты — нет.
  • Любой текстовый редактор. Готовый файл сохраняй с расширением .plugin.

Первый плагин

from devgram import BasePlugin

class Hello(BasePlugin):
    id = "hello"
    name = "Привет DevGram"
    version = "1.0"
    author = "@you"

    def on_send_message(self, text):
        return text.replace(":love:", "❤️")

Сохрани как hello.plugin, отправь файл в чат и тапни по нему — появится карточка установки.

Метаданные

Атрибуты класса. Читаются без выполнения кода (через ast) — карточка установки безопасна.

ПолеОписание
idУникальный идентификатор (латиница). По нему обновляется/удаляется.
nameНазвание в списке.
versionВерсия.
authorАвтор.
descriptionКороткое описание.
iconURL картинки-аватарки (png/jpg).

Жизненный цикл

def on_load(self):     # один раз при загрузке — тут ставь хуки
    self.log("загружен")

def on_unload(self):   # при выгрузке / отключении
    pass

self.enabledвыключенный плагин остаётся загруженным, но события и хуки ему не приходят.

Страница настроек плагина

Верни строки из settings() — у плагина появится своя страница настроек. Значения храни через get_setting/set_setting.

def settings(self):
    return [
        ("header", "",      "Приветствие"),
        ("switch", "on",    "Включить"),
        ("text",   "greet", "Текст"),
        ("button", "reset", "Сбросить"),
    ]

def on_setting_click(self, key):
    if key == "reset":
        self.set_setting("greet", "")
        self.toast("Сброшено")
ТипЧто это
headerЗаголовок раздела.
switchПереключатель (хранит 1/0).
textТекстовое поле.
buttonКнопка — зовёт on_setting_click(key).

Сообщения

def on_send_message(self, text):
    # перед отправкой. return: str — новый текст | None — без изменений | False — отменить
    if text.strip() == "/id":
        return "Мой id: " + str(self.me())
    return None

def on_receive_message(self, text):
    # входящий текст — только чтение
    pass

Апдейты (TL)

Переопредели on_update, чтобы получать сырые TL-апдейты (Java-объект TLRPC.Update). Читай поля через рефлексию. Диспетчер включается сам, только если метод переопределён.

def on_update(self, update):
    if type(update).__name__ == "TL_updateUserTyping":
        self.log("печатает: " + str(update.user_id))

client_utils

МетодЧто делает
send_message(dialog_id, text)Отправить сообщение (id>0 — юзер, <0 — чат/канал).
me()ID текущего аккаунта.
user_name(uid)Имя пользователя по id.
chat_name(cid)Имя чата/канала по id.

android_utils

МетодЧто делает
toast(text)Короткое всплывающее уведомление.
copy(text)Скопировать в буфер обмена.
clipboard()Прочитать буфер обмена.
log(msg)Запись в логкат (для отладки).

Диалоги и плашки

self.toast("Готово")                 # короткий тост
self.bulletin("Скопировано")          # плашка в стиле Telegram
self.alert("Заголовок", "Текст")     # диалог с кнопкой OK

Настройки и файлы

# key-value, переживает перезапуск
n = int(self.get_setting("count", "0"))
self.set_setting("count", str(n + 1))

# файлы в личной папке плагина
self.write_file("data.txt", "hello")
data = self.read_file("data.txt")

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

Задай icon — URL картинки. DevGram скачает её и покажет как аватарку плагина в списке и в карточке установки.

    icon = "https://devgram.space/favicon.png"

Хуки методов — основы

Самое мощное: перехватывай любой Java-метод клиента и меняй его аргументы или результат. Хуки ставь в on_load, логику пиши в before_hook / after_hook.

def on_load(self):
    self.hook(
        "org.telegram.messenger.MessagesController",  # класс (FQN)
        "isPremiumUser",                             # метод, или "<init>" для конструктора
        "org.telegram.tgnet.TLRPC$User",             # типы аргументов…
    )

def after_hook(self, frame):
    frame.setResult(True)   # у всех «премиум» → True

self.hook(class, method, *param_types) возвращает True при успехе. Один плагин может ставить несколько хуков — все они зовут общие before_hook/after_hook (различай по frame.method).

Объект frame (CallFrame)

Описание
frame.argsМассив аргументов. Пиши: frame.args[0] = … — уйдёт в оригинал.
frame.thisObjectОбъект, на котором вызван метод.
frame.methodКакой метод сработал (java.lang.reflect.Member).
frame.getResult()Результат — в after_hook.
frame.setResult(x)Подменить результат. В before_hook — ПРОПУСКАЕТ оригинал.

Типы аргументов

Передавай типы параметров строками, точно как в сигнатуре Java-метода.

  • Примитивы: int, long, boolean, byte, char, short, float, double.
  • Классы — по полному имени: java.lang.String, java.lang.CharSequence.
  • Вложенные классы — через $: org.telegram.tgnet.TLRPC$User.
Числа приводятся автоматически

Присваивай обычные Python-числа: frame.args[0] = 0xFF6C2BD9 — рантайм сам приведёт значение к int/long/float нужного метода.

Не хукай «горячие» методы

Методы, вызываемые покадрово (отрисовка, Theme.getColor, onDraw), при хуке вешают UI. Хукай «холодные» — те, что срабатывают при открытии экрана или событии.

Примеры хуков

Менять аргумент (заголовок экрана)

def on_load(self):
    self.hook("org.telegram.ui.ActionBar.ActionBar", "setTitle",
              "java.lang.CharSequence", "android.graphics.drawable.Drawable")

def before_hook(self, frame):
    s = str(frame.args[0])
    if s and not s.startswith("✨"):
        frame.args[0] = "✨ " + s

Подменять результат (премиум)

def on_load(self):
    self.hook("org.telegram.messenger.MessagesController",
              "isPremiumUser", "org.telegram.tgnet.TLRPC$User")

def after_hook(self, frame):
    frame.setResult(True)

Полезные классы для хуков

Клиент основан на Telegram для Android — имена классов оттуда. Несколько удобных точек:

Класс · методДля чего
MessagesController.isPremiumUser(User)Разблокировать премиум-функции (визуально).
ActionBar.setTitle(CharSequence, Drawable)Заголовки экранов (настройки, списки).
ChatAvatarContainer.setTitle(…)Шапка чата — имя собеседника вверху.
ActionBar.setBackgroundColor(int)Цвет верхней панели (холодный вызов при настройке экрана).
SendMessagesHelperОтправка сообщений.
TLRPC$*Классы TL-объектов (User, Chat, Message…) как типы аргументов.

Совет: чтобы узнать точную сигнатуру метода, смотри исходники Telegram для Android (класс с тем же именем). Один плагин может держать несколько хуков и различать их по frame.method.getName().

Визуальные эффекты

Хелперы для «стеклянных» и размытых панелей. Обычно зовутся из before_hook/after_hook, где view = frame.thisObject.

МетодЧто делает
tint(view, argb)Полупрозрачная заливка (стекло), напр. 0x66121018.
blur(view, radius=40)Размыть содержимое View. Android 12+ (иначе False).
unblur(view)Снять размытие.
round_corners(view, radius_dp=18)Скруглить углы (обрезка по контуру).

Пример: стеклянная шапка

def on_load(self):
    self.hook("org.telegram.ui.ActionBar.ActionBar", "setBackgroundColor", "int")

def before_hook(self, frame):
    frame.args[0] = 0x66121018          # полупрозрачная стеклянная тонировка
    self.blur(frame.thisObject, 18)   # матовость (Android 12+)
Про «жидкое стекло» как в iOS

blur размывает содержимое самого View, а не фон за ним. Настоящее «размытие того, что позади» плавающей панели во время скролла Android из коробки не даёт, а плагины не могут добавлять нативные библиотеки. Надёжно работают: стеклянная тонировка (tint), размытие фоновых View и скругления. Полный live-эффект iOS — только приближение.

Визуальные эффекты

Хелперы для «стеклянных» и размытых панелей. Обычно зовутся из before_hook/after_hook, где view = frame.thisObject.

МетодЧто делает
tint(view, argb)Полупрозрачная заливка фона (стекло), напр. 0x66121018.
blur(view, radius=40)Размыть содержимое View (Android 12+). True при успехе.
unblur(view)Снять размытие.
round_corners(view, radius_dp=18)Скруглить углы (обрезка по контуру).

Пример: стеклянная шапка

def on_load(self):
    self.hook("org.telegram.ui.ActionBar.ActionBar", "setBackgroundColor", "int")

def before_hook(self, frame):
    frame.args[0] = 0x66121018          # полупрозрачная тонировка
    self.blur(frame.thisObject, 18)    # матовое стекло (Android 12+)
Про «жидкое стекло» как в iOS

blur размывает содержимое самого View, а не фон за ним. Настоящее «размытие того, что позади» на скролле недоступно — плагины не могут добавлять нативные библиотеки. Стеклянная тонировка (tint) + blur надёжно работают на панелях, диалогах и фоновых картинках.

Прямой доступ к Android (jclass)

Плагин НЕ ограничен хуками и хелперами выше. Через Chaquopy доступен весь Java/Android-рантайм — по возможностям плагин равен Xposed-модулю, просто на Python.

from java import jclass   # любой класс Android SDK / Telegram

Доступны View, Canvas, Paint, Shader, RenderEffect, RuntimeShader (AGSL-шейдеры, Android 13+), WindowManager, Window. Хелперы blur/tint/round_corners — лишь обёртки над этим.

GPU-шейдер (AGSL) на View

from java import jclass

def before_hook(self, frame):
    view = frame.thisObject
    if jclass("android.os.Build$VERSION").SDK_INT >= 33:
        RS = jclass("android.graphics.RuntimeShader")
        RE = jclass("android.graphics.RenderEffect")
        agsl = ("uniform shader content;"
                "half4 main(float2 p){ half4 c = content.eval(p);"
                " return half4(c.rgb*0.85 + 0.06, c.a); }")
        view.setRenderEffect(RE.createRuntimeShaderEffect(RS(agsl), "content"))

Навешивается один раз — дальше шейдер крутится на GPU каждый кадр, Python не участвует.

Границы

RenderEffect/шейдер работают над содержимым САМОЙ View, а не над фоном за ней — публичного backdrop-API нет (iOS-рефракция «преломить всё под панелью» недостижима из плагина). Покадровую собственную отрисовку в Python делать нельзя (хук onDraw = ANR). Навешивай эффект один раз — дальше работает GPU. Это уровень нативного мода.

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

  • Отправь файл .plugin в чат или канал.
  • Тап по файлу → карточка установки (имя, автор, значок проверки) → «Установить».
  • Проверенные плагины из реестра DevGram помечаются зелёным значком «Проверено».
  • Управление — «Настройки → DevGram → Плагины»: тумблеры, настройки, удаление, «Система плагинов» по кнопке i.

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

  • Код плагина выполняется в приложении. Ставь плагины только из проверенных источников.
  • «Безопасный режим» (Плагины → i) отключает все хуки, не удаляя плагины.
  • Если плагин уронил клиент — при следующем запуске безопасный режим включается сам, а полный отчёт о сбое копируется в буфер и сохраняется в файл.

Справочник хуков

МетодКогда
on_load / on_unloadзагрузка / выгрузка
on_send_message(text)перед отправкой; return str/None/False
on_receive_message(text)входящий текст
on_update(update)сырой TL-апдейт
menu_items / on_menu_clickменю сообщения
settings / on_setting_clickстраница настроек
before_hook / after_hookхук Java-метода

Полный пример

from devgram import BasePlugin

class Toolbox(BasePlugin):
    id = "toolbox"
    name = "Инструменты"
    version = "1.0"
    author = "@you"
    icon = "https://devgram.space/favicon.png"

    def on_send_message(self, text):
        t = text.strip()
        if t == "/id":
            return "Мой id: " + str(self.me())
        if t.startswith("/upper "):
            return t[7:].upper()
        return None

    def menu_items(self):
        return ["Скопировать id чата"]

    def on_menu_click(self, label, message_text, dialog_id):
        self.copy(str(dialog_id))
        self.bulletin("Скопировано")
Опубликовать плагин в канале →