К содержанию
Общее · Разработчикам

Платформа плагинов Lovsel SDK 3

Единый SDK 3 для GGsel, FunPay и Playerok: шаблоны, блоки, четыре компоновки, мастер и события.

Содержание

Готовые сценарии — первый шаг

Плагины работают на площадках GGsel, FunPay и Playerok. Плагин описывает данные, действия и независимые блоки. Lovsel формирует навигацию, размещение, стандартные состояния, главное действие и настройку. GGsel, FunPay и Playerok используют один контракт. Плагин по-прежнему объявляет одну площадку в marketplace; это область его данных, а не отдельный UI SDK.

СценарийШаблонГотовые блоки
АвтоматизацияautomationОбзор, автоматизация, журнал, инструкция
Поставщик и закупкаsupplierОбзор, товары, заказы, связки, автоматизация, журнал, инструкция
АвтовыдачаdeliveryОбзор, заказы, связки, автоматизация, журнал, инструкция
Цены и остаткиpricingОбзор, товары, автоматизация, журнал, инструкция
Работа с заказамиordersОбзор, заказы, журнал, инструкция
Сообщения и отзывыmessagesОбзор, заказы, настройки, журнал, инструкция
Аналитика и отчётыanalyticsОбзор, статистика, журнал, инструкция
Синхронизация сервисовsyncОбзор, товары, связки, автоматизация, журнал, инструкция
python
app = WebPlugin(MANIFEST, ui_version=3, layout="hybrid", settings_panel=True)
app.settings([ui.field.switch("enabled", "Включить обработку", default=False)])
app.use_template("supplier", handlers={"bindings": bindings, "automation": automation})
app.setup_marketplace()

bindings(ctx, params) и automation(ctx, params) — ваши функции, возвращающие ui.* блоки. Шаблон даёт работающий каркас, читает товары/заказы через SDK и показывает сохранённые данные. Неуказанные бизнес-обработчики получают понятное пустое состояние. Шаблон не выполняет закупку, выдачу, изменение цен или отправку сообщений сам: эти операции явно реализуются в действиях и событиях. Для новых шаблонов автоматически появляется главное действие «Обновить». Его можно заменить через primary_action.

Полные примеры: GGsel, FunPay, Playerok. У всех трёх одинаковая структура и четыре варианта компоновки.

Выбор компоновки

РежимКак выглядитКогда выбирать
singleВсе нескрытые блоки и обычные страницы в одном рабочем экранеОдин сценарий, мало настроек, быстрый обзор
tabsСтраницы и разделы во вкладках сверхуНесколько равноправных разделов без глубокой вложенности
sidebarОтдельное внутреннее меню внутри страницы плагинаМного разделов: товары, заказы, связки, журнал, инструкция
hybridКрупные разделы сверху; внутри выбранного раздела — собственное боковое менюСложный плагин с несколькими рабочими областями

Левое меню Lovsel не заполняется десятками страниц плагина. В нём остаётся сам плагин. Внутренняя панель находится внутри его экрана. На телефоне она становится горизонтальным списком. Цвет берётся из площадки: GGsel — зелёный, FunPay — голубой, Playerok — синий. Не задавайте свой CSS.

python
app.section("work", "Рабочее пространство", icon="package")

@app.block("orders", kind="orders", section="work", width=8, order=1,
           requires_connection=True)
def orders(ctx, params):
    return [ui.market_table("orders", ctx.market.orders(), marketplace=ctx.marketplace)]

@app.block("stats", kind="statistics", section="work", width=4, order=2)
def statistics(ctx, params):
    return [ui.stats([ui.stat("Связок", len(ctx.storage.get("bindings", [])))])]

Теперь поменяем представление без изменения этих функций:

python
app.compose("single")
app.compose("tabs", {"orders": {"page": "orders"}, "stats": {"page": "statistics"}})
app.compose("sidebar")
app.compose("hybrid", {"orders": {"section": "work"}, "stats": {"section": "work"}})

compose(layout, placements) изменяет только page, section, width и order существующих блоков. Функции, настройки, события, данные и действия не перерегистрируются. Неверная ширина и неизвестный блок вызывают ошибку до применения размещения. Скрытые страницы с app.page(hidden=True) остаются отдельными карточками/редакторами, а не добавляются в общий экран single.

Независимые блоки и конструктор страницы

app.block(id, title=None, kind="overview", page=None, section=None, width=12, order=0, requires_connection=False) регистрирует функцию (ctx, params) → список блоков.

Стандартные виды: overview, statistics, products, orders, bindings, automation, settings, logs, help. У products/orders подписи автоматически меняются на лоты/продажи/сделки. Свой title переопределяет подпись, но не меняет поведение данных.

  • Сетка — 12 колонок. width от 1 до 12; order — порядок чтения и размещения.
  • По умолчанию каждый блок получает страницу со своим ID. Одинаковый page объединяет блоки на одной странице.
  • section ссылается на app.section; разделы могут вкладываться по прежнему контракту parent.
  • Мобильная версия автоматически размещает панели в одну колонку с тем же порядком.
  • Ошибка отдельного блока показывает стандартное состояние и ссылку на журнал; другие блоки продолжают строиться.
  • requires_connection=True показывает подключение правильной площадки вместо вызова обработчика без аккаунта.

Для динамических страниц можно вернуть сетку напрямую:

python
return [ui.canvas([
    ui.panel("overview", overview_blocks, title="Обзор", width=8, order=0),
    ui.panel("statistics", statistics_blocks, title="Статистика", width=4, order=1),
])]

ui.grid() сохраняет старый контракт 1–4 колонок. Его не нужно переписывать.

Состояния, кнопки и настройки

python
ui.state("loading", title="Обновляем данные")
ui.state("empty", description="Добавьте первую связку", action=ui.button("Добавить", page="bindings"))
ui.state("error", description="Повторите проверку", action=ui.button("Проверить", "check"))
ui.state("disconnected", marketplace=ctx.marketplace)

Все состояния отображаются одинаковыми компонентами Lovsel. Страница также автоматически имеет загрузку, повтор после ошибки и пустое состояние. Не заменяйте их собственными HTML-экранами.

python
app = WebPlugin(MANIFEST, ui_version=3, layout="sidebar", settings_panel=True,
                primary_action=ui.button("Проверить данные", "check", variant="primary"))

Главное действие закрепляется в шапке. Это обычное действие SDK: сохраняются confirm, индикация ожидания, ошибки, обновление данных и права доступа. У выключенного или ошибочного плагина оно не предлагается; у плагина, ожидающего подключения, главное действие ведёт в доступный мастер настройки. primary_action также может быть функцией (ctx) → ui.button(...), например «Исправить подключение» или «Включить автоматизацию» по текущему состоянию.

settings_panel=True открывает встроенные настройки справа, включая переход из события к __settings. Формат app.settings и серверная проверка значений не меняются. Секретные поля не возвращаются в браузер. Для отдельных групп используйте ui.settings_panel(label, groups, action=..., values=...) или ui.button(dialog=ui.dialog(..., side=True)).

Готовые компоненты и совместимость

КомпонентGGselFunPayPlayerok
ui.product_pickerТоварыЛотыТовары
ui.market_table("products", ...)ТоварыЛотыТовары
ui.market_table("orders", ...)ЗаказыПродажиСделки
ui.bindings_editorДаДаДа
ui.automation_statusДаДаДа
app.setup_marketplace / app.onboarding_stepДаДаДа
ui.logs / ui.operations / ui.progressДаДаДа
ui.chart / ui.statsДаДаДа
ui.message_editor / ui.message_previewДаДаДа
ui.settings_panel / ui.settingsДаДаДа
ui.canvas / ui.panel / ui.stateДаДаДа
single / tabs / sidebar / hybridДаДаДа
python
ui.product_picker("product_id", marketplace=ctx.marketplace, required=True)
ui.bindings_editor("binding", rows, marketplace=ctx.marketplace, action="save_binding")
ui.message_editor("message", action="save_message", value=current_message,
                  variables=[ui.var("order_id", "Номер заказа", "101")])
ui.automation_status(enabled, pending=waiting_count, error=last_error)
ui.operations()  # операции, созданные через ctx.operations

Выбор товара использует встроенный серверный поиск __market_products и права products.read. ID из формы всегда перепроверяется в бизнес-действии: сам список выбора не заменяет проверку принадлежности. Редактор связок передаёт product_id и external_id в data (поля data["product_id"] и data["external_id"]); сохранение делает ваше действие. ui.market_table принимает действия строк. У действия можно указать requires="raise_lots" или другую возможность из ctx.market.info["actions"]: неподходящие площадке кнопки не показываются. Серверные разрешения всё равно проверяются адаптером при выполнении действия.

Данные и особенности площадок

ctx.market.id, ctx.market.info, ctx.market.connected, ctx.market.products(), ctx.market.orders(), ctx.market.verify() дают единый read-only контракт. Нормализованные объекты имеют id, title, status и raw с исходными данными. Остальные поля сохраняются; единицы цены не преобразуются автоматически.

Метод / действиеGGselFunPayPlayerok
ctx.market.products()ctx.ggsel.products()ctx.funpay.lots()ctx.playerok.items()
ctx.market.orders()ctx.ggsel.orders()ctx.funpay.orders()ctx.playerok.orders()
Проверить подключение чтениемctx.market.verify()ctx.market.verify()ctx.market.verify()
Ценаctx.ggsel.set_pricectx.funpay.set_lot_pricectx.playerok.set_item_price
Изменить товар/лотctx.ggsel.update_productctx.funpay.save_lotctx.playerok.update_item
ОстаткиПоля товара / Seller APIПоля лота по возможностям категорииДанные товара по возможностям площадки
Перепискаctx.ggsel.messages/send_messagectx.funpay.messages/send_messagectx.playerok.messages/send_message
Отзывыctx.ggsel.reviewsctx.funpay.reply_review (ответ на отзыв продажи)ctx.playerok.reviews
Поднятие лотов—ctx.funpay.raise_lots—
ПубликацияВозможности Seller APIСохранение/активность лотаpublish_options/publish_item
Выдача / проблема сделкиСобытия заказов и бизнес-обработчикПродажа и бизнес-обработчикdeliver/delivery, событие playerok.deal_problem
Низкоуровневый APIctx.ggsel.requestctx.funpay.requestctx.playerok.graphql

Не имитируйте отсутствующее действие универсальной кнопкой. Перед специфичной кнопкой проверьте ctx.market.supports("raise_lots"), "publish", "delivery" и т. п. Для записи используйте реальный адаптер площадки и его контракт. Права (products.write, orders.read, chats.write, marketplace.api и др.) по-прежнему обязательны. API поставщика подключается отдельно через ctx.http; никакой конкретный поставщик не встроен в архитектуру интерфейса.

Первоначальная настройка

python
app.setup_marketplace(version="1")

@app.onboarding_step("binding", "Создайте связку",
                     complete=lambda ctx: bool(ctx.storage.get("bindings", [])))
def first_binding(ctx, params):
    return bindings(ctx, params)

Готовые шаги одинаковы для трёх площадок: подключить аккаунт, затем проверить чтение объявленных данных. Мастер SDK 3 доступен ещё до подключения аккаунта. В этом режиме Lovsel создаёт только контекст настройки: on_start, старый register и обработчики событий не запускаются. Бизнес-страницы и действия закрыты до подключения; после подключения плагин загружается обычным способом. Это не меняет запуск старых плагинов. Для этого объявите storage; результат проверки хранится в пространстве данного продавца и плагина. После смены аккаунта нужна новая проверка. version позволяет повторить проверку после изменения требований. Проверка не выполняет тестовых закупок, не отправляет сообщения и не публикует товары.

Дополнительный шаг состоит из id, title, серверной функции complete(ctx) и функции интерфейса. required=False делает шаг необязательным. Lovsel показывает число выполненных шагов и следующее обязательное действие. Завершение определяется данными на сервере, а не параметром запроса из браузера. После завершения обязательных шагов мастер автоматически заменяется обычным интерфейсом. Ссылка на отдельную страницу остаётся доступной: с неё можно продолжить настройку.

Центр событий: причина, последствия, решение

python
ctx.notify(
    "Не удалось обновить остатки", level="error", needs_action=True,
    marketplace=ctx.marketplace, plugin_id=ctx.plugin_key,
    cause="Сервис поставщика временно не ответил.",
    consequence="В плагине остаются ранее сохранённые остатки.",
    solution="Проверьте подключение и повторите синхронизацию.",
    page="bindings", section="connection", params={"id": "101"},
    primary_action={"label": "Проверить настройки", "entity": "settings"},
    secondary_action={"label": "Открыть связку", "entity": "binding", "id": "101"},
    group_key="supplier:connection",
)

Новые поля: plugin_id, page, section, params, primary_action, secondary_action, group_key, cause, consequence, solution. plugin_id может быть ID из манифеста либо текущим ctx.plugin_key; сохранённая принадлежность всегда определяется настоящим контекстом плагина.

Цели действия: page, settings, binding, order, deal, product, lot. Для объекта нужен id. Страница и параметры открываются внутри плагина; заказ/товар/лот/сделка — в правильном разделе площадки. section прокручивает страницу к соответствующему ui.panel(id=...); на странице __settings можно указать ключ поля, например section="enabled": панель откроется справа и поле получит фокус. Для произвольной связки укажите свою page; по умолчанию используется стандартная страница bindings. Действия уведомления — навигация, не скрытые запросы на покупку/выдачу/изменение данных.

Одинаковый group_key обновляет одну карточку, увеличивает счётчик и сохраняет последнее состояние. Область объединения: пользователь + установленный плагин + площадка + ключ. Другие продавцы и плагины не смешиваются. Обработанное событие может снова стать требующим действия. Для разных заказов используйте разные ключи, например order:<id>:delivery. Telegram использует тот же ключ для подавления повторов. Без group_key сохраняется прежнее поведение. Старый ctx.notify(title, body, ...) тоже работает: Lovsel добавляет стандартное объяснение и кнопку перехода, если автор не передал новые поля.

От простого плагина к сложному

  1. Начните с orders или automation и одного single-экрана.
  2. Замените обработчик обзора в handlers; добавьте реальные показатели и ui.state для отсутствующих данных.
  3. Добавьте app.settings, app.setup_marketplace и один обязательный бизнес-шаг.
  4. Подключите ui.bindings_editor, серверную проверку ID и сохранение связок в ctx.storage.
  5. Вынесите чтение товаров и заказов в отдельные блоки; переключите compose("tabs").
  6. При росте разделов используйте sidebar, а при появлении рабочих областей — hybrid с app.section.
  7. Вынесите длительные задачи в ctx.operations, показывайте ui.operations/ui.progress и журнал.
  8. Добавьте события с объяснением, конкретными переходами и group_key.
  9. Проверьте все четыре компоновки на трёх наборах данных через lovsel_sdk.testing.

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

Обратная совместимость и переход

  • SDK 3 экспортирует прежние WebPlugin, ui, PluginContext, ошибки, формы, операции и адаптеры площадок.
  • Без ui_version: 3 старый плагин сохраняет прежнюю навигацию и API страниц. Он автоматически оборачивается стандартной шапкой Lovsel и общими состояниями. Старые sidebar/auto остаются совместимыми вкладками.
  • Для новой компоновки достаточно ui_version: 3 в манифесте или WebPlugin(..., ui_version=3). Новые layout="single"/"hybrid", app.block, app.compose, use_template и шаги мастера включают SDK 3 явно.
  • app.page не удалён: в SDK 3 страницы участвуют в выбранной навигации, а в single их содержимое автоматически собирается в общий экран. Скрытые карточки по-прежнему открываются отдельно.
  • Настройки и данные продавца не переименовываются; ctx.ggsel, ctx.funpay, ctx.playerok не заменены.
  • Миграция центра событий добавляет только колонки с пустыми значениями и индекс, не удаляя историю.
  • Файл сложного плагина стороннего сервиса — пример требований, а не зависимость платформы.

Проверка перед публикацией

Проверьте пустой магазин, отсутствие подключения, отказ прав, ошибку блока, повтор события и несколько продавцов с одинаковыми ID товаров. Затем — светлую/тёмную темы, ширину 390 px, все четыре компоновки, доступность основного действия, сохранение настроек справа и переходы из событий. Полные примеры ниже можно проверять без аккаунтов площадок: стенд SDK принимает отдельные наборы products/orders, funpay={lots,orders} и playerok={items,orders}.

Частые вопросы

Нужен ли отдельный интерфейс для каждой площадки?

Нет. GGsel, FunPay и Playerok используют один SDK и одни компоненты. Площадка определяет данные, доступные действия, названия объектов и цвет, а не несовместимый набор элементов.

Можно ли сменить компоновку без переписывания логики?

Да. Выберите single, tabs, sidebar или hybrid через layout или app.compose. Функции блоков и действий остаются прежними; меняются только страницы, разделы, ширина и порядок размещения.

Старый плагин продолжит работать после обновления?

Да. Прежние app.page, формы, настройки и адаптеры сохранены. Новый интерфейс включается через ui_version=3 или новые методы композиции. Данные продавца не нужно переносить в новый формат.