Пишем плагины для AyuGram / ExteraGram.

Пишем плагины для AyuGram / ExteraGram.
Здравствуйте, дорогие читатели!
73dc06ae-2fa4-499d-937b-0216431277e8.webp

В данной статье мы разберём, как работают плагины, а так-же напишем пару собственных.​​

ExteraGram - неофициальный Telegram клиент, который добавляет многие возможности, среди которых имеется собственный движок для плагинов.
AyuGram построен на базе ExteraGram, и сохраняет функциональность данного клиента.

Каждый плагин пишется на языке Python, а так-же придерживается определённого синтаксиса. Подробнее об этом расписано ниже.

Метаданные - это глобальные переменные, которые принято писать в начале файла (после импортов).

⁡
⁡__id__
⁡ - Уникальный идентификатор плагина. Используется, чтобы клиент мог различать плагины между собой. Должен быть от 2 до 32 символов в длину, и содержать только символы a-z, 0-9, а так-же - и _.

⁡
⁡__name__
⁡ - Имя плагина, отображается при установке.

⁡
⁡__description__
⁡ - Описание плагина, отображается при установке.

⁡
⁡__author__
⁡ - Имя автора, отображается при установке.

⁡__version__
⁡ - Версия плагина. Для корректного обновления плагина необходимо увеличивать её, чтобы клиент знал, что это новая версия.
Рекомендуется указывать версию в формате SemVer, то есть: 1.0.0, 1.0.1 и тп.

⁡
⁡__icon__
⁡ - Иконка плагина. ExteraGram позволяет использовать стикеры в качестве кастомных иконок.

Для примера я взял стикерпак с лисичками. Ссылка на пак:

⁡t.me/addstickers/Foxfox8
⁡
Нас отсюда интересует только строка Foxfox8.⁡

Выбираем понравившийся стикер, в моём случае четвёртый по счёту:

Если отсчитывать с нуля, он будет иметь номер 3.

Подставляем эти данные и получаем такое значение:

⁡__icon__ = 'Foxfox8/3'
⁡


⁡__min_version__
⁡ - Минимальная версия клиента, так-же придерживается формата SemVer. Необязательное поле!
Пример:
11.12.0 - Плагин установится только на версию клиента выше или равную 11.12.0.

Зачем это нужно?
Новые версии Telegram могут отличаться, где-то меняется интерфейс, где-то функционал и прочее, разумеется это может ломать логику новых плагинов, и они банально могут не запускаться на более старых версиях клиента.

Плагин не будет установлен, а вы увидите ошибку:
Plugin requires app version 12.12.12 or higher. Current is <текущая_версия_клиента>

Пример заполненных метаданных:

__id__ = 'my_plugin'
__name__ = 'Plugin Name'
__description__ = 'Amazing plugin!'
__version__ = '1.2.3'
__icon__ = 'Foxfox8/3'
__min_version__ = '11.12.0'

python

Код:
__id__ = 'my_plugin'
__name__ = 'Plugin Name'
__description__ = 'Amazing plugin!'
__version__ = '1.2.3'
__icon__ = 'Foxfox8/3'
__min_version__ = '11.12.0'

Сами по себе эти данные ничего не делают, но даже такой плагин уже можно установить!

Для того, чтобы придать нашему плагину функционал, нужно установить библиотеку exteragram-utils:

⁡python -m pip install exteragram-utils
⁡

Данная библиотека добавляет модули base_plugin, hook_utils и android_utils, о них поговорим позже.

Из модуля base_plugin необходимо импортировать класс BasePlugin.
Это основа, от которой наследуется класс нашего плагина:

from base_plugin import BasePlugin

__name__ = 'custom icon!'
__description__ = 'Plugin with custom icon'
__id__ = 'test_custom_icon'
__author__ = 'reassurance'
__version__ = '1.0.0'
__icon__ = 'Foxfox8/3'
__min_version__ = '11.12.0'

class MyPlugin(BasePlugin):
def __init__(self):
super().__init__()

def on_plugin_load(self):
pass

def on_plugin_unload(self):
pass

python

Код:
from base_plugin import BasePlugin

__name__ = 'custom icon!'
__description__ = 'Plugin with custom icon'
__id__ = 'test_custom_icon'
__author__ = 'reassurance'
__version__ = '1.0.0'
__icon__ = 'Foxfox8/3'
__min_version__ = '11.12.0'

class MyPlugin(BasePlugin):
    def __init__(self):
        super().__init__()

    def on_plugin_load(self):
        pass

    def on_plugin_unload(self):
        pass

Метод __init__ это конструктор, в котором вы добавляете хуки, кнопки в интерфейс, вешаете на них обработчики и так далее.
Методы on_plugin_load и on_plugin_unload будут вызваны каждый раз, когда плагин будет загружен/выгружен.

УСТАНОВКА ПЛАГИНА
После того, как вы собрали каркас и указали все необходимые метаданные, плагин готов к установке.
Для этого переименуйте расширение файла в .plugin, затем загрузите файл в избранное (или любой другой чат), после чего кликните по нему, клиент сам предложит установить плагин.

Если вы хотите обновить существующий плагин, просто установите его поверх старого (клиент будет ориентироваться на его версию).

УДАЛЕНИЕ ПЛАГИНА
Для того, чтобы удалить плагин, откройте Settings > ExteraGram Preferences > Plugins, выберите ваш плагин и нажмите кнопку Удалить.


Для отладки плагина можно воспользоваться методом BasePlugin.log.
Данный метод принимает единственный аргумент - строку, в последствии можно прочитать историю логов при помощи adb.

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

Кастомные кнопки регистрируются через метод BasePlugin.add_menu_item.
Он принимает единственный аргумент MenuItemData (импортируется из base_plugin), который содержит поля:
- menu_type - Тип меню (см. ниже), относится к MenuItemType (импортируется из base_plugin)
- text - Текст, отображаемый на кнопке
- on_click - Функция-обработчик, вызывается при клике на кнопку
- icon - Иконка кнопки (см. спойлер "Иконки")

Кнопка добавляется в нижнюю часть бокового меню.


from typing import Any, Dict
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'my_menu'
__name__ = 'test menu'

class MyMenuPlugin(BasePlugin):
def on_plugin_load(self):
self.add_menu_item(
MenuItemData(
menu_type=MenuItemType.DRAWER_MENU,
text="Drawer",
on_click=self.handler,
icon="msg_addcontacts"
)
)

def on_plugin_unload(self):
pass

def handler(self, context: Dict[str, Any]):
pass

python

Код:
from typing import Any, Dict
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'my_menu'
__name__ = 'test menu'

class MyMenuPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_menu_item(
            MenuItemData(
                menu_type=MenuItemType.DRAWER_MENU,
                text="Drawer",
                on_click=self.handler,
                icon="msg_addcontacts"
            )
        )
 
    def on_plugin_unload(self):
        pass
 
    def handler(self, context: Dict[str, Any]):
        pass

Кнопка добавляется в попап-меню, которое можно увидеть при нажатии на сообщение.

​

from typing import Any, Dict
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'my_menu'
__name__ = 'test menu'

class MyMenuPlugin(BasePlugin):
def on_plugin_load(self):
self.add_menu_item(
MenuItemData(
menu_type=MenuItemType.MESSAGE_CONTEXT_MENU,
text="Message Context",
on_click=self.handler,
icon="msg_addcontacts"
)
)

def on_plugin_unload(self):
pass

def handler(self, context: Dict[str, Any]):
pass

python

Код:
from typing import Any, Dict
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'my_menu'
__name__ = 'test menu'

class MyMenuPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_menu_item(
            MenuItemData(
                menu_type=MenuItemType.MESSAGE_CONTEXT_MENU,
                text="Message Context",
                on_click=self.handler,
                icon="msg_addcontacts"
            )
        )

    def on_plugin_unload(self):
        pass
 
    def handler(self, context: Dict[str, Any]):
        pass

Кнопка добавляется в меню открытого профиля.


from typing import Any, Dict
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'my_menu'
__name__ = 'test menu'

class MyMenuPlugin(BasePlugin):
def on_plugin_load(self):
self.add_menu_item(
MenuItemData(
menu_type=MenuItemType.PROFILE_ACTION_MENU,
text="Profile Action",
on_click=self.handler,
icon="msg_addcontacts"
)
)

def on_plugin_unload(self):
pass

def handler(self, context: Dict[str, Any]):
pass

python

Код:
from typing import Any, Dict
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'my_menu'
__name__ = 'test menu'

class MyMenuPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_menu_item(
            MenuItemData(
                menu_type=MenuItemType.PROFILE_ACTION_MENU,
                text="Profile Action",
                on_click=self.handler,
                icon="msg_addcontacts"
            )
        )

    def on_plugin_unload(self):
        pass
 
    def handler(self, context: Dict[str, Any]):
        pass

Кнопка добавляется в меню открытого чата.


from typing import Any, Dict
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'my_menu'
__name__ = 'test menu'

class MyMenuPlugin(BasePlugin):
def on_plugin_load(self):
self.add_menu_item(
MenuItemData(
menu_type=MenuItemType.CHAT_ACTION_MENU,
text="Chat Action",
on_click=self.handler,
icon="msg_addcontacts"
)
)

def on_plugin_unload(self):
pass

def handler(self, context: Dict[str, Any]):
pass

python

Код:
from typing import Any, Dict
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'my_menu'
__name__ = 'test menu'

class MyMenuPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_menu_item(
            MenuItemData(
                menu_type=MenuItemType.CHAT_ACTION_MENU,
                text="Chat Action",
                on_click=self.handler,
                icon="msg_addcontacts"
            )
        )

    def on_plugin_unload(self):
        pass
 
    def handler(self, context: Dict[str, Any]):
        pass

Как вы могли заметить по коду, у каждого обработчика есть аргумент context.
Это переменная, которая хранит информацию о контексте, например для MenuItemType.MESSAGE_CONTEXT_MENU там будет объект message с информацией о сообщении, с которым мы работаем:

message = context.get("message")
# message.getId() - айди сообщения
# message.getSenderId() - айди отправителя
# message.messageText - текст сообщения

python

Код:
message = context.get("message")
# message.getId() - айди сообщения
# message.getSenderId() - айди отправителя
# message.messageText - текст сообщения

Более подробная информация по контексту:

⁡fragment
⁡ - <org.telegram.ui.ChatActivity>

⁡context
⁡ - <org.telegram.ui.LaunchActivity>

⁡dialog_id
⁡ - Идентификатор текущего диалога

⁡message
⁡ - <org.telegram.messenger.MessageObject>

⁡user
⁡ - <org.telegram.tgnet.TLRPC$TL_user>

⁡userId
⁡ - Идентификатор собеседника (в текущем диалоге)

⁡account
⁡ - Внутренний идентификатор аккаунта в клиенте

⁡fragment
⁡ - <org.telegram.ui.ChatActivity>

⁡context
⁡ - <org.telegram.ui.LaunchActivity>

⁡dialog_id
⁡ - Идентификатор текущего диалога

⁡user
⁡ - <org.telegram.tgnet.TLRPC$TL_user>

⁡userId
⁡ - Идентификатор собеседника (в текущем диалоге)

⁡account
⁡ - Внутренний идентификатор аккаунта в клиенте

⁡fragment
⁡ - <org.telegram.ui.ProfileActivity>

⁡context
⁡ - <org.telegram.ui.LaunchActivity>

⁡user
⁡ - <org.telegram.tgnet.TLRPC$TL_user>

⁡userId
⁡ - Идентификатор собеседника (в текущем диалоге)

⁡userFull
⁡ - <org.telegram.tgnet.TLRPC$TL_userFull>

⁡account
⁡ - Внутренний идентификатор аккаунта в клиенте

⁡context
⁡ - <org.telegram.ui.LaunchActivity>

⁡user
⁡ - <org.telegram.tgnet.TLRPC$TL_user>

⁡userId
⁡ - Идентификатор собеседника (в текущем диалоге)

⁡account
⁡ - Внутренний идентификатор аккаунта в клиенте

Данные классы полностью повторяют оригинальные на Java из официального репозитория Telegram.
Пример, Python класс <org.telegram.messenger.MessageObject> имеет все поля и методы оригинального класса.

Это хук на стороне клиента, ДО отправки сообщения.
Для его использования в классе BasePlugin реализованы методы on_send_message_hook и add_on_send_message_hook, а так-же вспомогательные HookResult и HookStrategy.

Для того, чтобы активировать такой хук - необходимо вызвать add_on_send_message_hook при инициализации плагина:

def on_plugin_load(self):
self.add_on_send_message_hook()

python

Код:
def on_plugin_load(self):
        self.add_on_send_message_hook()

Так-же необходимо переопределить метод on_send_message_hook (он будет вызываться при каждой отправке сообщения), данный метод должен возвращать HookResult:

def on_send_message_hook(self, account, params):
return HookResult()

python

Код:
def on_send_message_hook(self, account, params):
        return HookResult()

Данный метод принимает аргументы account и params.
Изменяя params.message, мы можем влиять на содержимое сообщения, которое в конечном счёте дойдёт до серверов Telegram:

def on_send_message_hook(self, account, params):
if not isinstance(params.message, str):
return HookResult()

params.message = 'hooked message'
return HookResult(strategy=HookStrategy.MODIFY, params=params)

python

Код:
def on_send_message_hook(self, account, params):
        if not isinstance(params.message, str):
            return HookResult()

        params.message = 'hooked message'
        return HookResult(strategy=HookStrategy.MODIFY, params=params)

HookStrategy.DEFAULT - Никаких изменений не вносится.
HookStrategy.MODIFY - Сообщение модифицируется тем, что мы передаём в HookResult (params).
HookStrategy.MODIFY_FINAL - Работает аналогично MODIFY, только имеет наивысший приоритет (по сравнению с другими плагинами), т.е другой плагин, использующий MODIFY для этого сообщения, не сможет как-либо изменить данные.
HookStrategy.CANCEL - Сообщение не отправляется. Полезно для юзер ботов, когда мы хотим выполнить какое-то действие, но не хотим, чтобы собеседник увидел об этом.

В целом, здесь может быть любая логика - парсинг каких-либо данных, ping / pong и так далее, всё ограничивается лишь вашей фантазией.

Речь идёт о методах send_text, send_audio, send_media и тп.

Они импортируется из модуля client_utils и позволяет отправлять сообщения прямо из вашего плагина.
Метод send_text имеет всего два аргумента:
peer - Идентификатор получателя
text - Текст сообщения

У методов send_audio, send_document и send_video их немного больше:
peer - Идентификатор получателя
file_path - Путь к медиа файлу
caption - Подпись под медиа (необязательно)

Метод send_photo похож на предыдущие, но имеет ещё один необязательный аргумент:
high_quality - Отправлять медиа в высоком качестве, по умолчанию false

Для примера я написал небольшой плагин, который отправляет получателю сообщение о том, что плагин был запущен:

from base_plugin import BasePlugin
from client_utils import send_text

__id__ = 'tg_hook'
__name__ = 'hook'

class HookPlugin(BasePlugin):
def on_plugin_load(self):
send_text(<ТУТ_БЫЛ_АЙДИ>, "plugin loaded")

python

Код:
from base_plugin import BasePlugin
from client_utils import send_text

__id__ = 'tg_hook'
__name__ = 'hook'

class HookPlugin(BasePlugin):
    def on_plugin_load(self):
        send_text(<ТУТ_БЫЛ_АЙДИ>, "plugin loaded")

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

Я решил хукнуть метод
⁡private void onGiftPermiumClicked()
⁡ у класса <org.telegram.ui.ProfileActivity> и отключить его, чтобы по нажатию на "Отправить подарок" ничего не происходило.

Начнём с того, что нам нужно найти нужный нам класс при помощи метода find_class:

profileActivity = find_class("org.telegram.ui.ProfileActivity")

python

Код:
profileActivity = find_class("org.telegram.ui.ProfileActivity")

Затем необходимо хукнуть сам метод при помощи BasePlugin.hook_method:

method = profileActivity.getClass().getDeclaredMethod("onGiftPermiumClicked")
self.hook_method(method, GiftHook(self))

python

Код:
method = profileActivity.getClass().getDeclaredMethod("onGiftPermiumClicked")
self.hook_method(method, GiftHook(self))

Напишем вспомогательный класс GiftHook, который будет реализовывать функцию before_hooked_method:

class GiftHook(MethodHook):
def __init__(self, plg):
self.plg = plg

def before_hooked_method(self, param):
param.setResult(None)

python

Код:
class GiftHook(MethodHook):
    def __init__(self, plg):
        self.plg = plg

    def before_hooked_method(self, param):
        param.setResult(None)

param.setResult(None) моментально завершает выполнение функции, возвращая результат - None (по сути то же самое, что и void).

Помимо этого, можно изменять аргументы метода (рекомендую убедиться, что у метода они вообще имеются):

param.args[1] = 123
param.args[2] = "custom text"

python

Код:
param.args[1] = 123
param.args[2] = "custom text"

А так-же подменять значение после выполнения метода (допустим, оригинальная функция возвращает число 150, а мы хотим чтобы было больше):

def after_hooked_method(self, param):
orig = param.getResult()
param.setResult(orig + 10000)

python

Код:
def after_hooked_method(self, param):
        orig = param.getResult()
        param.setResult(orig + 10000)

from base_plugin import BasePlugin, MenuItemData, MenuItemType, MethodHook
from typing import Dict, Any
from hook_utils import find_class

__id__ = 'hook'
__name__ = 'hook ProfileActivity'

class GiftHook(MethodHook):
def __init__(self, plg):
self.plg = plg

def before_hooked_method(self, param):
param.setResult(None)

class HookPlugin(BasePlugin):
def on_plugin_load(self):
self.add_menu_item(
MenuItemData(
menu_type=MenuItemType.PROFILE_ACTION_MENU,
text="Profile Action",
on_click=self.handler,
icon="msg_addcontacts"
)
)

def handler(self, context: Dict[str, Any]):
try:
profileActivity = find_class("org.telegram.ui.ProfileActivity")
if profileActivity:
method = profileActivity.getClass().getDeclaredMethod("onGiftPermiumClicked")
self.hook_method(method, GiftHook(self))
except Exception as e:
pass

python

Код:
from base_plugin import BasePlugin, MenuItemData, MenuItemType, MethodHook
from typing import Dict, Any
from hook_utils import find_class

__id__ = 'hook'
__name__ = 'hook ProfileActivity'

class GiftHook(MethodHook):
    def __init__(self, plg):
        self.plg = plg

    def before_hooked_method(self, param):
        param.setResult(None)

class HookPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_menu_item(
            MenuItemData(
                menu_type=MenuItemType.PROFILE_ACTION_MENU,
                text="Profile Action",
                on_click=self.handler,
                icon="msg_addcontacts"
            )
        )

    def handler(self, context: Dict[str, Any]):
        try:
            profileActivity = find_class("org.telegram.ui.ProfileActivity")
            if profileActivity:
                method = profileActivity.getClass().getDeclaredMethod("onGiftPermiumClicked")
                self.hook_method(method, GiftHook(self))
        except Exception as e:
            pass

Бинго! Теперь после активации хука, каждый раз при нажатии на кнопку "Отправить подарок" ничего не происходит, потому что мы перехватили этот метод и отключили его)

Таким образом можно перехватить практически любые методы у любых Java классов и поменять их логику / возвращаемое значение.

При помощи BulletinHelper можно выводить сообщения, своего рода
⁡alert
⁡ из JavaScript.

BulletinHelper.show_info принимает два аргумента:

⁡message
⁡ - Отображаемый текст

⁡fragment
⁡ - Объект Activity (например ProfileActivity или ChatActivity), указывать необязательно!

from ui.bulletin import BulletinHelper
# ...
BulletinHelper.show_info('some important info!', fragment)

python

Код:
from ui.bulletin import BulletinHelper
# ...
BulletinHelper.show_info('some important info!', fragment)

В целом, fragment указывать необязательно, но если вы очень хотите получить текущий Activity, это можно сделать так:

from client_utils import get_last_fragment
# ...
fragment = get_last_fragment()

python

Код:
from client_utils import get_last_fragment
# ...
fragment = get_last_fragment()

Помимо метода show_info так-же существуют show_success и show_error, они различаются только визуально.

Вы можете создавать, читать и удалять файлы в директории клиента:

⁡/data/data/<PACKAGE>/files/plugins/
⁡

Получить имя пакета можно при помощи метода ApplicationLoader.applicationContext.getPackageName.

from org.telegram.messenger import ApplicationLoader

package = ApplicationLoader.applicationContext.getPackageName()

python

Код:
from org.telegram.messenger import ApplicationLoader

package = ApplicationLoader.applicationContext.getPackageName()

Это можно сделать, переопределив метод create_settings в основном классе:

def create_settings(self):
return [
Header(text="Заголовок"),
Input(key="param", text="Поле ввода", default="", subtext="Описание", icon="msg_link"),
Divider()
]

python

Код:
def create_settings(self):
    return [
        Header(text="Заголовок"),
        Input(key="param", text="Поле ввода", default="", subtext="Описание", icon="msg_link"),
        Divider()
    ]

Данный метод возвращает массив из частей интерфейса:

Заголовок, необходимо указать аргумент
⁡text
⁡ .

Поле ввода, необходимо указать:

⁡key
⁡ - Название переменной (для get_setting)

⁡text
⁡ - Отображаемый текст (основной)

⁡default
⁡ - Значение по умолчанию

⁡subtext
⁡ - Описание

⁡icon
⁡ - Название иконки
⁡
⁡on_change
⁡ - Функция-обработчик, вызывается каждый раз при изменении значения

Поле с текстом, используется для вывода какой-либо дополнительной информации о вашем плагине:

⁡text
⁡ - Отображаемый текст

⁡icon
⁡ - Название иконки

⁡on_click
⁡ - Функция-обработчик, она будет вызвана при нажатии на текст

Разделитель:

⁡text
⁡ - Отображаемый текст (необязательно)

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

⁡key
⁡ - Название переменной (для get_setting)

⁡text
⁡ - Отображаемый текст

⁡default
⁡ - Индекс варианта по умолчанию

⁡items
⁡ - Массив из вариантов. Пример: ["one", "two", "three"]

⁡icon
⁡ - Название иконки
⁡
⁡on_change
⁡ - Функция-обработчик, вызывается каждый раз при изменении значения

Элемент переключатель (вкл/выкл), создан для типа данных Boolean. Аргументы:

⁡key
⁡ - Название переменной (для get_setting)

⁡text
⁡ - Отображаемый текст

⁡default
⁡ - Значение по умолчанию. False либо True

⁡subtext
⁡ - Описание

⁡icon
⁡ - Название иконки
⁡
⁡on_change
⁡ - Функция-обработчик, вызывается каждый раз при изменении значения
Получить значение того или иного параметра можно через метод get_setting:

⁡self.get_setting('param', 'default_value')
⁡

Так-же принудительно устанавливать свои параметры можно через метод set_setting:

⁡self.set_setting('param', 'value')
⁡

А теперь напишем собственные плагины!

Данный юзер-бот при отправке
⁡.id
⁡ выдаст информацию о текущем диалоге (его идентификатор).

Напишем заглушку с on_send_message_hook для того, чтобы посмотреть, что это вообще за объект params:

import json
from base_plugin import BasePlugin, HookResult

__id__ = 'chat_id'
__name__ = 'chat id viewer'

class ChatIdPlugin(BasePlugin):
def on_plugin_load(self):
self.add_on_send_message_hook()

def on_send_message_hook(self, account, params):
self.log(json.dumps(params, indent=2, default=str))
return HookResult()

python

Код:
import json
from base_plugin import BasePlugin, HookResult

__id__ = 'chat_id'
__name__ = 'chat id viewer'

class ChatIdPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_on_send_message_hook()

    def on_send_message_hook(self, account, params):
        self.log(json.dumps(params, indent=2, default=str))
        return HookResult()

Оказалось, что это <org.telegram.messenger.SendMessagesHelper$SendMessageParams>.
Давайте откроем код Telegram и посмотрим, какие поля у него имеются:

​

Скорее всего,
⁡public long peer;
⁡ и будет содержать идентификатор текущего диалога.
Добавим пару строк, чтобы наше сообщение заменялось на peer:

params.message = f'peer: {params.peer}'
return HookResult(strategy=HookStrategy.MODIFY, params=params)

python

Код:
params.message = f'peer: {params.peer}'
return HookResult(strategy=HookStrategy.MODIFY, params=params)

Устанавливаем новый плагин, отправляем
⁡.id
⁡и видим, что содержимое сообщения действительно поменялось:


А вот, что у нас получилось:

from base_plugin import BasePlugin, HookResult, HookStrategy

__id__ = 'chat_id'
__name__ = 'chat id viewer'

class ChatIdPlugin(BasePlugin):
def on_plugin_load(self):
self.add_on_send_message_hook()

def on_send_message_hook(self, account, params):
if not isinstance(params.message, str) or not params.message.startswith(".id"):
return HookResult()

params.message = f'peer: {params.peer}'
return HookResult(strategy=HookStrategy.MODIFY, params=params)

python

Код:
from base_plugin import BasePlugin, HookResult, HookStrategy

__id__ = 'chat_id'
__name__ = 'chat id viewer'

class ChatIdPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_on_send_message_hook()

    def on_send_message_hook(self, account, params):
        if not isinstance(params.message, str) or not params.message.startswith(".id"):
            return HookResult()

        params.message = f'peer: {params.peer}'
        return HookResult(strategy=HookStrategy.MODIFY, params=params)

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

Для начала рекомендуется ознакомиться с официальной документацией и получить токен тут.

Напишем простой код для поиска пользователя по привязанному Telegram:

import requests

lolz_token = 'ВАШ_ТОКЕН_ТУТ'
api_url = 'https://prod-api.lolz.live'

def get_profile_id(username: str):
try:
resp = requests.get(f'{api_url}/users/find?custom_fields[telegram]={username}', headers={
'accept': 'application/json',
'authorization': f'Bearer {lolz_token}'
})
users = resp.json().get('users')
if len(users) > 0:
return users[0].get('user_id')
return 0
except Exception as e:
return 0

python

Код:
import requests

lolz_token = 'ВАШ_ТОКЕН_ТУТ'
api_url = 'https://prod-api.lolz.live'

def get_profile_id(username: str):
    try:
        resp = requests.get(f'{api_url}/users/find?custom_fields[telegram]={username}', headers={
            'accept': 'application/json',
            'authorization': f'Bearer {lolz_token}'
        })
        users = resp.json().get('users')
        if len(users) > 0:
            return users[0].get('user_id')
        return 0
    except Exception as e:
        return 0

Затем добавим кнопку в профиль:

from typing import Dict, Any
from base_plugin import BasePlugin, MenuItemData, MenuItemType

class LolzIdPlugin(BasePlugin):
def on_plugin_load(self):
self.add_menu_item(
MenuItemData(
menu_type=MenuItemType.PROFILE_ACTION_MENU,
text="View Lolzteam",
on_click=self.on_click,
icon="msg_addcontacts"
)
)

def on_click(self, context: Dict[str, Any]):
pass

python

Код:
from typing import Dict, Any
from base_plugin import BasePlugin, MenuItemData, MenuItemType

class LolzIdPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_menu_item(
            MenuItemData(
                menu_type=MenuItemType.PROFILE_ACTION_MENU,
                text="View Lolzteam",
                on_click=self.on_click,
                icon="msg_addcontacts"
            )
        )

    def on_click(self, context: Dict[str, Any]):
        pass

При клике на кнопку "View Lolzteam" будет вызываться метод on_click. Давайте сделаем так, чтобы при каждом его вызове происходил запрос на API форума:

def on_click(self, context: Dict[str, Any]):
user = context.get("user")
if user and user.username:
lolz_id = get_profile(user.username)
if lolz_id == 0: return

python

Код:
    def on_click(self, context: Dict[str, Any]):
        user = context.get("user")
        if user and user.username:
            lolz_id = get_profile(user.username)
            if lolz_id == 0: return

Теперь давайте воспользуемся полученными выше знаниями и используем fragment для того, чтобы вывести информацию на экран:

fragment = context.get("fragment")
BulletinHelper.show_info(f'https://lolz.live/members/{lolz_id}', fragment)

python

Код:
fragment = context.get("fragment")
BulletinHelper.show_info(f'https://lolz.live/members/{lolz_id}', fragment)

Что получилось?
Зайдя в профиль к другу и нажав на кнопку, я увидел ссылку на его профиль внизу экрана:


import requests
from ui.bulletin import BulletinHelper
from typing import Dict, Any
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'lolz_id'
__name__ = 'Lolz By Telegram'
__author__ = 'reassurance'

lolz_token = 'ВАШ_ТОКЕН_ТУТ'
api_url = 'https://prod-api.lolz.live'

def get_profile(username):
try:
resp = requests.get(f'{api_url}/users/find?custom_fields[telegram]={username}', headers={
'accept': 'application/json',
'authorization': f'Bearer {lolz_token}'
})
users = resp.json().get('users')
if len(users) > 0:
return users[0].get('user_id')
return 0
except Exception as e:
return e

class LolzIdPlugin(BasePlugin):
def on_plugin_load(self):
self.add_menu_item(
MenuItemData(
menu_type=MenuItemType.PROFILE_ACTION_MENU,
text="View Lolzteam",
on_click=self.get_lolz,
icon="msg_addcontacts"
)
)

def on_click(self, context: Dict[str, Any]):
user = context.get("user")
if user and user.username:
lolz_id = get_profile(user.username)
if lolz_id == 0: return

fragment = context.get("fragment")
BulletinHelper.show_info(f'https://lolz.live/members/{lolz_id}', fragment)

python

Код:
import requests
from ui.bulletin import BulletinHelper
from typing import Dict, Any
from base_plugin import BasePlugin, MenuItemData, MenuItemType

__id__ = 'lolz_id'
__name__ = 'Lolz By Telegram'
__author__ = 'reassurance'

lolz_token = 'ВАШ_ТОКЕН_ТУТ'
api_url = 'https://prod-api.lolz.live'

def get_profile(username):
    try:
        resp = requests.get(f'{api_url}/users/find?custom_fields[telegram]={username}', headers={
            'accept': 'application/json',
            'authorization': f'Bearer {lolz_token}'
        })
        users = resp.json().get('users')
        if len(users) > 0:
            return users[0].get('user_id')
        return 0
    except Exception as e:
        return e

class LolzIdPlugin(BasePlugin):
    def on_plugin_load(self):
        self.add_menu_item(
            MenuItemData(
                menu_type=MenuItemType.PROFILE_ACTION_MENU,
                text="View Lolzteam",
                on_click=self.get_lolz,
                icon="msg_addcontacts"
            )
        )

    def on_click(self, context: Dict[str, Any]):
        user = context.get("user")
        if user and user.username:
            lolz_id = get_profile(user.username)
            if lolz_id == 0: return

            fragment = context.get("fragment")
            BulletinHelper.show_info(f'https://lolz.live/members/{lolz_id}', fragment)

Благодарю за прочтение моей статьи
3979d6df-ef84-4b9b-ab3d-85ceff94a852.webp

Комментарии 0

Пока нет комментариев. Начните обсуждение.
Войдите, чтобы оставить комментарий.
Назад
Верх