Содержание
- Что такое плагин
- Быстрый старт
- Структура файла
- Манифест
- Контекст ctx
- Площадки
- Страницы и навигация
- Действия и кнопки
- Формы и пользовательский ввод
- Настройки
- Длительные операции и прогресс
- Ошибки и результаты
- События площадок
- Файлы
- Стандарт интерфейса Lovsel
- Проверка плагина
- Изображения
- Публикация
- Обновление плагина
- Совместимость со старыми плагинами
- Полные примеры
- Справочник
Это руководство для разработчика, который впервые видит Lovsel. Из него вы узнаете, как устроен плагин, какие возможности даёт ядро, как построить интерфейс, который выглядит частью Lovsel, как проверить плагин и выпустить его в магазин.
Руководство общее для всех площадок. Отдельные статьи:
- «Компоненты интерфейса SDK» — все блоки: навигация, таблицы, фильтры, формы, мастера, предпросмотр сообщений;
- «Разработка, публикация и проверка Lovsel» — тестовая копия, сборки, публикация, отметка «Проверен Lovsel»;
- «Плагины для GGsel» и «Плагины для FunPay» — API, события, ограничения и полные примеры для каждой площадки.
Главное правило. Плагин описывает данные и действия, а Lovsel превращает их в веб-интерфейс: карточки, таблицы, формы, прогресс. Не стройте плагин вокруг Telegram-кнопок и текстовых меню.
Что такое плагин
Плагин — один файл .py, который расширяет кабинет продавца Lovsel: работает с заказами и товарами на площадках GGsel и FunPay, подключает поставщика, меняет цены, выдаёт товары, пишет покупателям, строит отчёты. У каждого продавца своя копия плагина со своими настройками и данными.
Плагин может:
- показывать собственный раздел в кабинете Lovsel — со своим меню, таблицами, формами и мастерами;
- выполнять действия по кнопкам и формам;
- запускать длительные операции с живым прогрессом;
- реагировать на события площадок: новый заказ, новое сообщение, смена статуса, отзыв;
- читать и менять данные подключённых площадок — GGsel и FunPay (в пределах объявленных площадок и разрешений);
- хранить настройки и собственные данные;
- принимать файлы от пользователя и отдавать файлы на скачивание;
- отправлять уведомления в центр событий Lovsel и в Telegram продавца.
Плагин работает на сервере Lovsel, пока у продавца активна подписка. Ключи площадок плагин не получает: запросы к GGsel и FunPay выполняет Lovsel.
Быстрый старт
Минимальный рабочий плагин для обеих площадок — 30 строк:
from lovsel_sdk import WebPlugin, ui
MANIFEST = {
"id": "acme.hello",
"name": "Привет, Lovsel",
"version": "1.0.0",
"author": "ACME",
"description": "Показывает число заказов на подключённых площадках.",
"icon": "👋",
"category": "tools",
"permissions": ["orders.read"],
"marketplaces": ["ggsel", "funpay"],
}
app = WebPlugin(MANIFEST)
@app.page("overview", "Обзор", icon="layout-dashboard")
def overview(ctx, params):
stats = []
if ctx.ggsel.connected:
stats.append(ui.stat("Заказов GGsel", len(ctx.ggsel.orders())))
if ctx.funpay.connected:
stats.append(ui.stat("Заказов FunPay", len(ctx.funpay.orders())))
if not stats:
return [ui.empty("Площадка не подключена", "Подключите GGsel или FunPay в разделе «Подключение».")]
return [ui.stats(stats), ui.card("Приветствие", [ui.text("Здравствуйте! Магазин: **" + ctx.shop_title + "**")])]Как попробовать:
- Сохраните код в файл
hello.py. - В Lovsel откройте Плагины → Разработчикам → Новый плагин, выберите файл и нажмите «Создать и тестировать у себя».
- Плагин сразу появится в меню слева и откроется на отдельной странице с вкладкой «Обзор». Модерация для этого не нужна.
Полные примеры с интерфейсом, как у встроенных разделов Lovsel: «Благодарности покупателям» для GGsel и «Отзывы и напоминания» для FunPay. Пример со всеми возможностями ядра — настройками, операцией и файлом — docs/examples/example_web_plugin.py.
Структура файла
from lovsel_sdk import WebPlugin, ui, ValidationError
MANIFEST = {...} # 1. описание плагина
app = WebPlugin(MANIFEST, layout="sidebar") # 2. объект плагина (обязательно с именем app)
app.settings([...]) # 3. настройки (необязательно)
@app.page("overview", "Обзор", group="Работа") # 4. страницы — порядок объявления = порядок в меню
def overview(ctx, params): ...
@app.action("sync") # 5. действия — вызываются кнопками и формами
def sync(ctx, data): ...
@app.on("new_order") # 6. события магазина (необязательно)
def on_order(ctx, event): ...
@app.on_start # 7. запуск фоновых задач (необязательно)
def start(ctx): ...Первый аргумент каждого обработчика — ctx, контекст конкретного магазина. Никогда не храните данные магазина в глобальных переменных модуля: у каждого продавца своя копия плагина, но глобальные объекты удобно перепутать между потоками.
Сервер читает MANIFEST без выполнения кода (через разбор Python-файла), поэтому манифест должен быть обычным словарём из строк, чисел и списков — без вызовов функций и переменных.
Манифест
| Поле | Обязательно | Описание |
|---|---|---|
id | да | Уникальный идентификатор, например studio.supplier-sync. Не меняйте между версиями. |
name | да | Название в каталоге и в меню (до 120 символов). |
version | да | Версия в формате 1.2.3. Каждая новая выпущенная версия — больше предыдущей. |
author | нет | Подпись для плагина вне магазина. В магазине автором всегда показывается ник вашего аккаунта Lovsel — его меняют в «Настройки → Профиль». |
description | да | Одно-два предложения: что делает плагин. |
icon | нет | В магазине не используется: иконку-картинку загружают в карточке плагина. Без неё показывается логотип площадки плагина — GGsel или FunPay. |
category | нет | Одна из категорий ниже. |
permissions | да | Разрешения, которые нужны плагину. Пустой список — если плагин не трогает магазин. |
marketplaces | нет | Площадки плагина: ["ggsel"] (по умолчанию), ["funpay"] или ["ggsel", "funpay"]. См. «Площадки». |
capabilities | нет | Что умеет плагин (для карточки каталога). |
homepage | нет | Ссылка на сайт или поддержку (https://…). |
Категории: automation (автоматизация), pricing (цены), delivery (выдача товаров), suppliers (поставщики), messages (сообщения), analytics (аналитика), tools (инструменты), other.
Разрешения:
| Разрешение | Что открывает |
|---|---|
orders.read | история продаж и подробности заказа |
chats.read | переписка с покупателем |
chats.write | отправка сообщений покупателям |
products.read | список и карточки товаров |
products.write | изменение цены, остатка, описания, публикация |
reviews.read | отзывы покупателей |
balance.read | баланс продавца на площадке |
storage | собственное хранилище (объявляйте, если используете ctx.storage) |
network | запросы во внешние сервисы |
files | приём и выдача файлов |
notifications | уведомления в центр событий |
Разрешения показываются покупателю до покупки и сверяются с кодом при проверке Lovsel. Методы ctx.ggsel.* и ctx.funpay.* работают только с объявленными разрешениями — иначе выбрасывается PermissionDenied с понятным текстом. Разрешения общие для площадок: orders.read открывает и заказы GGsel, и заказы FunPay — если плагин объявил обе площадки.
Возможности (capabilities): web_ui, settings, background, events, operations, telegram. web_ui и settings Lovsel определяет сам.
Контекст ctx
| Свойство или метод | Назначение |
|---|---|
ctx.settings | Словарь значений настроек (с учётом значений по умолчанию). |
ctx.save_settings(values) | Сохранить настройки программно. |
ctx.storage | Хранилище плагина: get(key, default), set(key, value), delete(key), all(), incr(key). Значения — любые JSON-данные до 1 МБ. |
ctx.ggsel | Данные подключённого магазина GGsel — API для GGsel. |
ctx.funpay | Данные подключённого аккаунта FunPay — API для FunPay. |
ctx.marketplaces | Площадки плагина: ["ggsel"], ["funpay"] или обе. |
ctx.operations | Длительные операции: start(title, total), get(id), active(). |
ctx.notify(title, body, ...) | Событие в центр событий и Telegram продавца. |
ctx.log(message, level) | Запись в журнал плагина (info, success, warning, error). |
ctx.log_error(message, exc) | Ошибка в журнал вместе с трассировкой до строки вашего файла. |
ctx.download(name, content, mime) | Подготовить файл к скачиванию. |
ctx.user_id, ctx.shop_title | Идентификатор аккаунта и название магазина. |
ctx.core | Внутреннее ядро магазина. Только для совместимости со старыми плагинами: API не гарантируется, а проверка Lovsel отмечает такие обращения. |
Хранилище изолировано: разные магазины не видят данные друг друга, а при обновлении версии плагина данные сохраняются.
Площадки
Lovsel работает с несколькими площадками: GGsel и FunPay. Каждый плагин объявляет, для какой площадки он сделан:
MANIFEST = {
"id": "studio.funpay-helper",
"name": "Помощник FunPay",
"version": "1.0.0",
"author": "Studio",
"description": "Отвечает покупателям FunPay и следит за лотами.",
"permissions": ["orders.read", "chats.read", "chats.write", "products.read"],
"marketplaces": ["funpay"], # ["ggsel"] — по умолчанию; ["ggsel", "funpay"] — обе площадки
}- Без поля
marketplacesплагин считается плагином GGsel — старые плагины работают как раньше. - Площадку можно поменять и в карточке плагина: карточка каталога получит отметку площадки, а покупатели смогут отфильтровать каталог по ней.
- Плагин только для FunPay у покупателя не запускается, пока FunPay не подключён: в кабинете он отмечен «ждёт подключения» и включается сам сразу после подключения аккаунта. Плагин для обеих площадок работает, если подключена любая из них — проверяйте
ctx.ggsel.connectedиctx.funpay.connected. - Неизвестные площадки в
marketplaces— ошибка манифеста: форма создания плагина покажет её сразу. - Плагин работает только со своими площадками.
ctx.ggselдоступен плагинам с"ggsel"вmarketplaces(иначеPermissionDenied), события GGsel (new_order,new_message…) приходят только им, а событияfunpay.*— только плагинам с"funpay". Событиеinitполучают все. - В меню кабинета покупатель видит плагины выбранной площадки; в режиме «Все площадки» — все.
- Интерфейс плагина окрашивается в цвет площадки: GGsel — зелёный, FunPay — голубой. Подробнее — в статье «Компоненты интерфейса SDK».
Что даёт каждая площадка:
| GGsel | FunPay | |
|---|---|---|
| Доступ из плагина | ctx.ggsel | ctx.funpay |
| Заказы | orders(), order(), currency() | orders(), order() |
| Переписка | messages(), send_message() — диалог заказа | chats(), messages(), send_message() — диалог с покупателем |
| Товары | products(), product(), set_price(), update_product(), set_active() | lots(), refresh_lots(), set_lot_price(), set_lot_active(), raise_lots() |
| Прочее | reviews(), balance() | balance(), username |
| События | new_order, order_status, new_chat, new_message | funpay.new_order, funpay.order_status, funpay.new_chat, funpay.new_message, funpay.new_review |
| Подробно | Плагины для GGsel | Плагины для FunPay |
Разрешения общие для площадок: orders.read открывает и заказы GGsel, и заказы FunPay — если плагин объявил обе площадки.
Страницы и навигация
У плагина свой раздел в кабинете с собственным меню. Страница — функция, которая возвращает список блоков. Lovsel отрисовывает их своими компонентами, поэтому интерфейс выглядит единообразно, поддерживает тёмную тему и телефоны.
app = WebPlugin(MANIFEST, layout="sidebar") # меню слева внутри плагина
@app.page("products", "Товары", icon="package", group="Рабочее пространство",
description="Слоты, созданные плагином", badge=lambda ctx: len(ctx.storage.get("slots", [])) or None)
def products(ctx, params):
rows = ctx.storage.get("slots", [])
return [
ui.header("Товары", "Слоты, созданные плагином",
actions=[ui.button("Создать слоты", action="create_slots", variant="primary", icon="plus")]),
ui.table(
[ui.column("title", "Игра", subtitle="region"),
ui.column("price", "Цена", align="right", format="money",
currency=ctx.ggsel.currency()), # или поле "currency" в каждой строке
ui.column("state", "Статус", format="status")],
[dict(r, state=ui.badge("активен", "success", value="on")) for r in rows],
filters=[ui.filter("state", "Статус", [("on", "Активные"), ("off", "Выключенные")], style="tabs")],
searchable=True,
row_click=ui.button("Открыть", page="slot"),
row_menu=[ui.button("Удалить", "delete_slot", icon="trash", danger=True, confirm="Удалить слот?")],
empty_text="Слотов ещё нет",
),
]
@app.page("slot", "Слот", hidden=True) # карточка записи: открывается из строки таблицы
def slot(ctx, params):
...layout:"sidebar"— меню слева,"tabs"— вкладки сверху,"auto"— меню слева, если страниц больше трёх.group— группа в меню,badge— счётчик,hidden=True— страница без пункта меню,bottom=True— пункт внизу меню.- Второй аргумент страницы —
params: параметры из кнопки (ui.button(page=..., params=...)), строки таблицы (params["id"]), поиска и фильтров панелиui.toolbar, вкладок. Они хранятся в адресе страницы. - Функция страницы должна отработать быстро (до 15 секунд). Всё долгое — в операции.
Основные блоки:
| Блок | Назначение |
|---|---|
ui.header, ui.card, ui.grid, ui.section, ui.tabs, ui.details | Шапка страницы и раскладка. |
ui.stats, ui.chart, ui.keyvalue, ui.list_view, ui.timeline, ui.steps | Показатели, графики, списки, ленты и этапы. |
ui.table, ui.toolbar, ui.search, ui.filter | Таблицы с поиском, фильтрами, выбором строк и меню действий. |
ui.form, ui.wizard, ui.field.* | Формы и пошаговые мастера. |
ui.field.template, ui.message_preview, ui.chat | Текст сообщения с переменными и предпросмотром в чате площадки. |
ui.button, ui.menu, ui.actions, ui.toggle | Кнопки, меню и переключатели. |
ui.text, ui.alert, ui.code, ui.empty, ui.badge, ui.money | Текст, сообщения, значения. |
ui.asset, ui.image, ui.banner, ui.gallery | Ваши картинки: баннеры, превью, скриншоты и шаги инструкций. |
ui.progress, ui.operations, ui.logs | Прогресс, операции и журнал. |
Все параметры блоков, форматы колонок, варианты кнопок и готовые образцы страниц — в статье «Компоненты интерфейса SDK».
Действия и кнопки
@app.action("reprice", confirm="Пересчитать цены всех слотов?")
def reprice(ctx, data):
row = data.get("row") # строка таблицы, если кнопка в row_actions или row_menu
...
return ui.result("Цена обновлена")data— словарь:payloadкнопки, значения формы, строка таблицы (row), выбранные строки (ids,rows) и загруженные файлы.- Действие должно завершиться за 20 секунд. Если нужно дольше — запустите операцию и сразу верните результат.
confirmв кнопке или действии показывает диалог подтверждения. Используйте его для всего, что меняет магазин или удаляет данные.
Что можно вернуть:
| Результат | Эффект |
|---|---|
None | Страница обновится. |
"Текст" | Уведомление «успешно» и обновление страницы. |
ui.result("Текст", tone="info") | Уведомление: success, info, warning, danger. |
ui.result(operation=op) | Показать прогресс запущенной операции. |
ui.result(page="orders", params={"id": 5}) | Перейти на страницу плагина. |
ui.result(dialog=ui.dialog("Итог", [blocks], side=True)) | Окно с результатом; side=True — панель справа. |
ui.result(download=ctx.download(...)) | Скачать файл. |
ui.result(values={...}) | Новые значения полей формы. |
ui.result(refresh=False) | Не перезагружать страницу. |
Ошибку ввода покажите у поля:
from lovsel_sdk import ValidationError
if price <= 0:
raise ValidationError({"price": "Цена должна быть больше нуля"})Любое другое исключение Lovsel покажет пользователю как «Ошибка плагина: …» и запишет в журнал с трассировкой. Текст исключения должен быть понятен продавцу.
Формы и пользовательский ввод
ui.form("add_slot", [
ui.field.text("title", "Название игры", required=True, width="half"),
ui.field.select("region", "Регион", [("ar", "Аргентина"), ("tr", "Турция")], default="ar", width="half"),
ui.field.number("markup", "Наценка", min=0, max=200, default=15, suffix="%"),
ui.field.switch("auto", "Обновлять цену автоматически", default=True),
ui.field.textarea("note", "Комментарий", show_if={"field": "auto", "equals": False}),
ui.field.text("token", "Ключ поставщика", secret=True),
ui.field.date("since", "Начиная с даты"),
ui.field.file("keys", "Файл с ключами", accept=".txt", max_mb=5),
], submit="Добавить", action="add_slot")Lovsel проверяет обязательные поля, числа и списки ещё до вызова действия и приводит значения к типам: number → число, switch → True/False. Для длинных форм есть ui.wizard — пошаговый мастер с проверкой каждого шага. Все поля — radio, checkboxes, tags, template, список с поиском — описаны в статье «Компоненты интерфейса SDK».
Файл из формы приходит объектом UploadedFile: name, size, mime, content (байты), text(), lines().
@app.action("add_slot")
def add_slot(ctx, data):
keys = data.get("keys")
lines = keys.lines() if keys else []
...Не просите пользователя вводить данные по одному сообщению («введите название», «теперь введите цену»). Одна форма — одно действие.
Настройки
def check(ctx, values):
if values["markup"] > 100 and not values["confirm_high"]:
raise ValidationError({"markup": "Больше 100% — включите подтверждение"})
return values # можно вернуть исправленные значения
app.settings([
ui.field.text("api_key", "API-ключ поставщика", secret=True, required=True),
ui.field.number("markup", "Наценка", min=0, max=500, default=15, suffix="%"),
ui.field.switch("confirm_high", "Разрешить наценку больше 100%"),
], description="Ключ хранится только на сервере Lovsel.", validate=check)Lovsel создаст страницу «Настройки» в группе «Сервис» меню плагина. Поля secret не отдаются в браузер: пустое поле при сохранении означает «оставить прежнее значение». Читайте настройки через ctx.settings["markup"].
Длительные операции и прогресс
Всё, что дольше пары секунд, оформляйте операцией. Пользователь видит, что происходит, сколько готово и сколько осталось, и может остановить операцию.
@app.action("create_slots", confirm="Создать слоты для выбранных игр?")
def create_slots(ctx, data):
games = load_games(ctx)
op = ctx.operations.start("Создание слотов", total=len(games))
def work(op):
for game in games:
if op.cancelled:
return "Остановлено пользователем"
op.update(stage="Создание", current=game["title"] + " / " + game["region"])
try:
create_slot(ctx, game)
op.step(ok=True)
except Exception as exc:
op.step(ok=False, note=game["title"] + ": " + str(exc))
op.finish("Создано: %d, ошибок: %d" % (op.success, op.errors))
op.run_in_background(work)
return ui.result("Создание запущено", operation=op)| Метод | Назначение |
|---|---|
op.update(done=, total=, current=, stage=, success=, errors=, message=) | Обновить прогресс. |
op.step(ok=True, current=, note=) | Отметить один обработанный элемент. |
op.log(message, level) | Запись в журнал операции. |
op.cancelled | Пользователь нажал «Остановить» — завершите работу. |
op.finish(message, result={"download": ...}) | Успешное завершение. |
op.fail(message) | Завершение с ошибкой. |
op.run_in_background(fn) | Выполнить fn(op) в фоне; исключение превратится в fail. |
Пользователь увидит карточку:
Создание слотов выполняется
14 / 50 28% · осталось ≈ 2 мин
[██████░░░░░░░░░░░░░░]
Создание: Сейчас Steam / Argentina
Успешно: 13 Ошибок: 1 Осталось: 36Прогресс отображается на странице плагина, в шапке кабинета и на обзоре. По завершении приходит уведомление в центр событий. Чтобы показать прогресс внутри своей карточки, используйте ui.progress(operation=op.id).
Ошибки и результаты
- Ошибка ввода —
ValidationErrorу конкретного поля. - Ошибка действия — исключение с понятным текстом или
ui.result("…", tone="danger"). - Проблема, требующая внимания продавца —
ctx.notify(title, body, level="error")илиneeds_action=True: событие попадёт в раздел «Требуют внимания». - Технические подробности —
ctx.log(..., "error")илиctx.log_error(message, exc), они видны во вкладке «Журнал». - Итог большой операции —
op.finish(message, result=...)и, при необходимости, диалогui.dialogс таблицей.
ctx.notify(
"Поставщик не ответил",
"Цены не обновлены 30 минут. Проверьте ключ API.",
level="error",
product="Steam Gift Card",
)Ошибки страниц, действий и обработчиков событий Lovsel записывает в журнал сам — с трассировкой до строки вашего файла. Их видно в пункте «Журнал» меню плагина; ошибка загрузки плагина видна и во вкладке «Разработка» кабинета разработчика.
События площадок
Плагин подписывается на события декоратором @app.on(...). Первый аргумент обработчика — ctx, второй — словарь события:
@app.on("new_order") # новый заказ GGsel
def on_new_order(ctx, event):
order = event["order"]
...
@app.on("funpay.new_message") # сообщение покупателя FunPay
def on_message(ctx, event):
text = event["message"]["text"]
...| Площадка | События | Данные и примеры |
|---|---|---|
| Все | init — магазин запущен | — |
| GGsel | new_order, order_status, new_chat, new_message | События GGsel |
| FunPay | funpay.new_order, funpay.order_status, funpay.new_chat, funpay.new_message, funpay.new_review | События FunPay |
В каждом событии есть поле marketplace ("ggsel" или "funpay"). События площадки приходят только плагинам, объявившим эту площадку, и только при подключённом аккаунте.
Обработчик должен работать быстро. Долгую реакцию запускайте операцией или своим потоком из @app.on_start. Если вы запускаете поток, проверяйте флаг остановки и не держите блокировки.
Файлы
Приём — поле ui.field.file. Выдача:
file = ctx.download("prices.csv", csv_text, "text/csv")
return ui.result("Отчёт готов", download=file)Файл хранится в памяти 30 минут. Для больших выгрузок формируйте файл в операции и передавайте его через op.finish(result={"download": file}) — на карточке операции появится кнопка «Скачать».
Стандарт интерфейса Lovsel
Проверка Lovsel учитывает эти правила, а покупатели ждут от плагина интерфейса, как у встроенных разделов.
- Меню по задачам. Типичный набор: «Обзор», рабочие страницы («Правила», «Товары», «Заказы», «История»), внизу — «Инструкция». Lovsel сам добавляет «Настройки» (если они объявлены), «Журнал» и «О плагине».
- Первая страница — обзор. Состояние плагина, ключевые показатели, «Требуют внимания» и одно главное действие.
- Список → карточка записи. Таблица с поиском и фильтрами, запись открывается на скрытой странице с кнопкой «назад».
- Пустые состояния. Если данных нет, объясните, что сделать:
ui.empty("Слотов нет", "Нажмите «Создать слоты»", action=...). - Одна основная кнопка на экран (
variant="primary"), остальные — обычные; опасные действия — в меню «⋯» илиui.details. - Подтверждение для всего, что меняет магазин, списывает деньги или удаляет данные.
- Прогресс для любой работы дольше двух секунд.
- Таблицы вместо текстовых списков, формы и мастера вместо пошагового ввода, бейджи для статусов.
- Сообщения покупателю — с предпросмотром. Шаблоны редактируются полем
ui.field.template: продавец видит сообщение глазами покупателя. - Понятные тексты. Русский язык, без технических кодов, числа с единицами («15 %», «3 шт.»), суммы — через
ui.money(value, currency)или колонкуformat="money". Никогда не пишите знак валюты вручную: у площадок и заказов разная валюта — магазин GGsel может работать в долларах. - Без имитации Telegram. Не рисуйте меню из эмодзи-кнопок и не отправляйте экраны текстом.
- Секреты — только поля
secret; не выводите ключи на страницах и в журнале. - Бережно к площадкам. Не опрашивайте GGsel и FunPay в цикле без паузы: заказы, диалоги и лоты уже есть в
orders(),chats()иlots(). - Площадка на виду. Если плагин работает с обеими площадками, показывайте, к какой из них относятся данные, и проверяйте
connectedперед обращением к площадке.
Проверка плагина
Без сервера. В SDK есть тестовый стенд. Он отрисовывает каждую страницу и проверяет блоки: опечатка в типе блока, кнопка без действия или повторяющийся ключ поля будут видны сразу.
python -m lovsel_sdk.testing my_plugin.py # манифест, навигация и отрисовка страниц
python -m lovsel_sdk.testing my_plugin.py --action exportВ автотестах:
from lovsel_sdk.testing import load_plugin
def test_overview():
plugin = load_plugin("my_plugin.py", orders=[{"id": "1", "title": "Ключ", "amount": 100}])
blocks = plugin.page("overview")
assert blocks[0]["type"] == "stats"
result = plugin.action("export", {})
assert result["operation"]["status"] == "success"Товары и сообщения GGsel — параметр products: сообщения покупателям складываются в plugin.ggsel_sent. События вызываются методом plugin.emit:
plugin = load_plugin("my_plugin.py", products=[{"id": 101, "title": "Ключ Steam", "price": 199}])
plugin.emit("new_order", {"order": {"id": "9001", "product_id": 101, "chat_id": 9001}})
assert plugin.ggsel_sent[0]["chat_id"] == "9001"Плагин для FunPay проверяется с тестовым аккаунтом — сообщения не уходят на FunPay, а складываются в plugin.funpay_sent:
plugin = load_plugin("my_plugin.py", funpay={
"orders": [{"id": "AB12CD34", "title": "Ключ", "amount": 199, "status": "paid", "buyer": "anna", "chat_id": "700001"}],
"chats": [{"id": "700001", "buyer": "anna", "last_text": "Здравствуйте"}],
"lots": [{"id": "1001", "title": "Ключ", "price": 199}],
})
plugin.ctx.funpay.send_message("700001", "Спасибо!")
assert plugin.funpay_sent == [{"chat_id": "700001", "text": "Спасибо!"}]В Lovsel. Создайте плагин в «Плагины → Разработчикам → Новый плагин» — он сразу заработает у вас как тестовая копия. Во вкладке «Разработка» кабинета разработчика: «Открыть плагин», загрузка новой версии перетаскиванием файла, «Перезапустить», ошибка загрузки со строкой кода и картинки плагина. Нашли проблему — загрузите исправленный файл и сразу проверьте снова. Проверьте работу на телефоне и в тёмной теме.
Изображения
Баннеры, превью и картинки для инструкций загрузите во вкладке «Разработка → Изображения» кабинета разработчика (PNG, JPEG, WebP или GIF до 2 МБ). В коде ссылайтесь на них по имени файла:
ui.banner(ui.asset("banner.png"), "Автовыдача за 3 шага", "Настройка займёт пару минут.")
ui.gallery([ui.asset("step-1.png"), ui.asset("step-2.png")], columns=2)
ui.image(ui.asset("result.png"), caption="Так покупатель увидит сообщение")Lovsel сам подставит адрес картинки, поэтому она одинаково открывается у вас и у покупателей, на компьютере и на телефоне. Подробно — в разделе «Изображения» справочника компонентов.
Публикация
Модерация, чтобы начать, не нужна: плагин работает у вас сразу после загрузки. Когда он готов:
- Нажмите «Опубликовать» в кабинете разработчика — бесплатно или по своей цене.
- Плагин появится в магазине, а выпущенная сборка уйдёт на проверку Lovsel.
- После проверки на карточке появится отметка «Проверен Lovsel».
Покупатели видят статус проверки, автора и разрешения до покупки. Как проходит проверка, что будет при замечаниях, как устроены деньги и что делать перед публикацией — в статье «Разработка, публикация и проверка Lovsel».
Обновление плагина
- Увеличьте
versionвMANIFEST(например,1.0.0→1.1.0). - Откройте плагин в кабинете разработчика → Загрузить сборку. Новая сборка сразу работает у вас, покупатели пользуются выпущенной версией.
- Проверьте изменения и нажмите «Выпустить», описав, что нового. Покупатели получат обновление автоматически, а сборка уйдёт на проверку Lovsel.
Lovsel хранит файлы только последней сборки и версии у покупателей: загруженная сборка заменяет предыдущую, а после выпуска файл прошлой версии удаляется. Во вкладке «Версии» остаётся история — номер обновления, дата и «что нового». Исходники храните у себя.
Совместимость данных:
- не переименовывайте ключи
ctx.storageи настроек без миграции; - при изменении формата храните версию данных и переводите старые значения при первом запуске:
@app.on_start
def migrate(ctx):
if ctx.storage.get("schema", 1) < 2:
slots = ctx.storage.get("slots", [])
for s in slots:
s.setdefault("region", "ar")
ctx.storage.set("slots", slots)
ctx.storage.set("schema", 2)Совместимость со старыми плагинами
Плагины прежнего формата (register(cardinal), меню в Telegram) продолжают работать. В кабинете они открываются в совместимом режиме: экран плагина и его кнопки отображаются карточкой с плитками действий. Для публикации в каталоге рекомендуем перейти на WebPlugin:
| Было | Стало |
|---|---|
def register(cardinal) + cardinal.bus.register(...) | @app.on("new_order") |
settings(cardinal, chat_id) с кнопками | app.settings([...]) |
panel.register_callback(...) | @app.action("...") + ui.button(action=...) |
| Ожидание текстового ввода | ui.form(...) или ui.wizard(...) |
| Сообщения с прогрессом «14/50» | ctx.operations.start(...) |
panel.notify_admins(text) | ctx.notify(title, body) |
Свои JSON-файлы в configs/ | ctx.storage |
Оба способа можно совмещать в одном файле: register(cardinal) для Telegram-меню и app = WebPlugin(...) для кабинета.
Полные примеры
- Для обеих площадок — «Быстрый старт» в начале этого руководства.
- GGsel — «Благодарности покупателям»: меню слева, правила с мастером, шаблон с предпросмотром, история с поиском. Разбор, файл
docs/examples/ggsel_thanks.py. - FunPay — «Отзывы и напоминания»: сценарии во вкладках, заказы с этапами, отзывы с фильтрами, переписка. Разбор, файл
docs/examples/funpay_reviews.py. - Короткие примеры площадок — отчёт по продажам для GGsel и благодарность за оплату для FunPay.
- Всё ядро сразу — страницы, настройки, операция с прогрессом, файлы:
docs/examples/example_web_plugin.py.
Все примеры можно скачать кнопкой «Пример плагина» в документации.
Справочник
WebPlugin(manifest, layout=)—page(),action(),settings(),on(),on_start.ctx.ggsel,ctx.funpay— данные площадок;ctx.marketplaces— площадки плагина.ui— блоки интерфейса,ui.field.*,ui.wizard(),ui.result(),ui.dialog()— «Компоненты интерфейса SDK».ValidationError({field: text})— ошибка формы.PermissionDenied— не объявлено разрешение.UploadedFile— файл из формы.lovsel_sdk.testing.load_plugin(),check_blocks()— тестовый стенд.
Вопросы и предложения по SDK — через поддержку Lovsel.