Плагины 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 | Короткое описание. |
icon | URL картинки-аватарки (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+)
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+)
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("Скопировано")
Опубликовать плагин в канале →