Cluster · разработчикам
Image Generator
Модуль создаёт карточки PNG из текста, показателей и необязательного фона. Его можно использовать для Ping, состояния задачи или небольшого отчёта. Это отрисовка заданных данных через Pillow, без внешнего сервиса генерации изображений.
Исходник и описание пакета доступны для просмотра и скачивания. Зависимость Pillow==13.0.0 устанавливается при сборке. Права генератора: files.read и files.write. Сеть, Telegram и полный доступ к файлам ему не нужны.
Карточка
Размер результата 1200 × 700. Фон тёмный, акцент фиолетовый. Можно передать заголовок, подзаголовок, строку состояния, до шести показателей и нижнюю подпись.
document = {
"title": "@nickname",
"subtitle": "Telegram userbot",
"status": "Cluster is running",
"metrics": [
{"label": "Ping", "value": "38 ms"},
{"label": "Core", "value": "0.1.0"},
{"label": "Uptime", "value": "01:24:10"},
],
"footer": "cluster.ninepeaks.dev",
}Текст передаётся строками до 256 символов. HTML и Markdown не разбираются. Переносы превращаются в пробелы, длинный текст обрезается по ширине с многоточием. Числа показателей нужно преобразовать в строки. Цвет состояния в этом шаблоне зелёный.
В пакете находится DejaVu Sans Mono с кириллицей. Лицензия шрифта входит в пакет. Отдельного цветного шрифта для эмодзи нет; для ников с эмодзи можно задать текстовое имя.
Для собственной публикации положи main.py, manifest.json, font.ttf и FONT-LICENSE.txt в корень архива. Один исходник без шрифта не является полным пакетом. После публикации используй назначенный каталогом ID в зависимости вызывающего модуля; ID установленного генератора можно получить на его странице.
Вызов
Добавь ID установленного генератора в dependencies и в modules.call.modules вызывающего модуля. Для сохранения своей копии изображения вызывающему также нужны files.read и files.write: права проверяются для всей цепочки вызова.
generator = app.modules("GENERATOR_MODULE_ID")
result = await generator.call("render", {"document": document})render возвращает id, size, sha256, width и height. ID указывает на временный файл генератора. Он не даёт вызывающему прямое право читать этот файл.
Обычный результат функции ограничен 32 КиБ, поэтому передавать целую картинку как base64 нельзя. Для этого есть chunk: он возвращает до 16 КиБ исходных байтов, закодированных в base64, следующую позицию offset и общий размер size.
import base64
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from resources import File, Module as Dependency
class Module:
async def image(self, generator: Dependency, document: dict) -> File:
result = await generator.call("render", {"document": document})
if result.get("error"): raise RuntimeError(result["error"])
try:
async with app.files.writer("card.png", result["size"], result["sha256"], ttl=120) as writer:
while writer.offset < result["size"]:
chunk = await generator.call("chunk", {"id": result["id"], "offset": writer.offset})
if chunk.get("error"): raise RuntimeError(chunk["error"])
raw = base64.b64decode(chunk["data"], validate=True)
if not raw or chunk["offset"] != writer.offset + len(raw): raise ValueError("Invalid image chunk")
await writer.write(raw)
return writer.file
finally:
await generator.call("release", {"id": result["id"]})В полном примере Ping дополнительно проверяются размер, длина частей и ошибки очистки. Writer проверяет SHA-256 перед завершением. При ошибке загрузки он удаляет незавершённую копию.
Свой фон
Фон передаётся через upload частями до 16 КиБ. Первый вызов содержит data и offset=0, ID можно не указывать. Последующие передают выданный id и точную позицию. Ответ содержит id и следующую позицию offset.
asset = ""
offset = 0
async for raw in app.files(background_file_id).chunks(16384):
result = await generator.call("upload", {
"id": asset,
"offset": offset,
"data": base64.b64encode(raw).decode(),
})
if result.get("error"): raise RuntimeError(result["error"])
asset, offset = result["id"], result["offset"]
document["background"] = assetПоддерживаются PNG, JPEG и WebP до 2 MiB и восьми миллионов пикселей. Изображение вписывается с обрезкой, затем затемняется для читаемости текста. Берётся первый кадр. Метаданные исходника не переносятся в новую карточку.
После использования вызови release и для карточки, и для фона. Фон можно использовать повторно до его освобождения или истечения срока.
Ограничения и ошибки
- Фон и результат принадлежат вызвавшему экземпляру модуля. Другой экземпляр не может получить их через
chunk, подставить фон или освободить результат. - Одновременно хранятся до четырёх загружаемых фонов и восьми результатов. Эти пределы общие для экземпляра генератора.
- Фон хранится в памяти, карточка в файловом хранилище юзербота. Срок обоих 120 секунд, размер результата до 2 MiB.
- После перезапуска генератора прежние временные ID недоступны через его функции. Оставшиеся файлы удаляются ядром по сроку.
- Неверные параметры возвращают
{"error": "invalid_input"}. Отказ ядра возвращает его код ошибки, когда он доступен. Такая ошибка не отключает генератор. - Потеря ответа на
renderможет оставить уже созданный результат. Не создавай бесконечные повторные вызовы; резервный срок ограничивает время хранения.
Ping
Ping 1.2.1 использует Image Generator как зависимость. Первая карточка отправляется с временным значением пинга, затем заменяется карточкой с измеренным временем отправки. Время подготовки картинки не входит в этот показатель. Это время операции через ядро и Telegram, а не чистое время прохождения сетевого пакета.
В исходящем сообщении Telegram меняется само сообщение с командой. Для входящей команды и команды личного бота отправляется новое изображение. Подпись остаётся пустой, все сведения находятся внутри PNG.
Настройка «Ответ картинкой» включает этот режим. «Свой фон» принимает изображение из файлового конфигуратора. Ник, отображение числа модулей и префикса настраиваются отдельно.
Если зависимость недоступна, право отозвано или Telegram запрещает вложение, остаётся текстовый ответ. Неизвестная ошибка отправки не вызывает повторную отправку: первое сообщение уже могло уйти. Если первая картинка отправилась, а её обновление не удалось с ожидаемым отказом, дополнительное сообщение не создаётся.
Установленным версиям новый доступ к зависимости не выдаётся самовольно: при обычном обновлении действуют настройки обновлений и подтверждения прав. Для проверки исходников смотри составной пример SDK.
Ошибка Telegram с неизвестным результатом записывается в журнал предупреждением. Ping остаётся включённым, а исходное действие не повторяется. Для записи предупреждения ему нужно logs.write. Файлы незавершённого ответа очищаются; при потере доступа действует срок хранения.