Плагин для Playerok — дополнительные возможности аккаунта продавца на Playerok. Всё, что продавец может сделать на сайте Playerok, можно сделать в плагине: готовыми методами ctx.playerok (сделки, чаты, выдача товара, возврат, товары, публикация, отзывы) или прямым запросом GraphQL сайта — ctx.playerok.graphql(). Плагин не получает cookies, токен и прокси продавца: запросы к Playerok выполняет Lovsel через подключённый аккаунт.
Общие правила создания плагина — структура файла, страницы, формы, операции, публикация — в руководстве «Как создать плагин». Здесь — только то, что относится к Playerok.
Манифест
MANIFEST = {
"id": "studio.playerok-helper",
"name": "Помощник Playerok",
"version": "1.0.0",
"author": "Studio",
"description": "Пишет покупателям Playerok после оплаты и подтверждения.",
"permissions": ["orders.read", "chats.read", "chats.write", "storage"],
"marketplace": "playerok", # одна площадка: плагин стоит в каталоге Playerok
}- Каждый плагин работает с одной площадкой: GGsel, FunPay или Playerok. Каталог плагинов у каждой площадки свой — плагин Playerok покупатель видит, когда выбрал Playerok вверху кабинета.
- Плагин Playerok не запускается, пока у пользователя не подключён Playerok, и включается сам после подключения.
- Плагины GGsel и FunPay с Playerok не работают: для Playerok делается отдельный плагин.
- Плагин Playerok получает только события
playerok.*(иinit) и не может обратиться кctx.ggselилиctx.funpay.
Разрешения
| Разрешение | Методы ctx.playerok |
|---|---|
orders.read | orders(), order(), delivery() |
orders.write | mark_sent(), refund() |
chats.read | chats(), messages() |
chats.write | send_message(), send_image(), deliver() |
products.read | items(), publish_options() |
products.write | set_item_price(), update_item(), remove_item(), publish_item() |
reviews.read | reviews() |
balance.read | balance() |
marketplace.api | graphql() — любой запрос и действие сайта Playerok, и все методы выше |
Каждый метод проверяет разрешение, объявленную площадку, принадлежность объекта подключённому аккаунту и активную подписку пользователя. Без них — PermissionDenied или KeyError с понятным текстом. Разрешения — объявление для покупателя, а не предел возможностей: нужно всё, что продавец делает на сайте, — объявите marketplace.api («Полный доступ к API площадки»).
API ctx.playerok
| Метод или свойство | Что возвращает | Обращается к Playerok |
|---|---|---|
ctx.playerok.connected | Подключён ли Playerok и объявлена ли площадка. | нет |
ctx.playerok.username | Ник продавца. | нет |
ctx.playerok.orders(limit=500, status="") | Продажи текущего аккаунта, новые первыми. | нет |
ctx.playerok.order(order_id, refresh=False) | Сделка; refresh=True — перечитать с Playerok. | при refresh |
ctx.playerok.chats(limit=100) | Чаты: kind — buyer, support или system. | нет |
ctx.playerok.messages(chat_id, limit=50) | Свежая переписка чата. | да |
ctx.playerok.send_message(chat_id, text) | Сообщение в чат (до 2000 символов). | да |
ctx.playerok.send_image(chat_id, content, filename, mime, text="") | Сообщение с изображением и подписью. | да |
ctx.playerok.deliver(order_id, text, mark_sent=True) | Выдача товара сообщением в чат сделки. | да |
ctx.playerok.delivery(order_id) | Состояние выдачи по сделке или None. | нет |
ctx.playerok.mark_sent(order_id) | «Товар отправлен» по оплаченной продаже; возвращает сделку. | да |
ctx.playerok.refund(order_id) | Полный возврат покупателю по оплаченной и ещё не подтверждённой сделке. Записывается в журнал действий Lovsel. | да |
ctx.playerok.items(status="") | Свои товары со статусами. | нет |
ctx.playerok.set_item_price(item_id, price) | Новая цена в рублях (целое). Остальные поля не меняются. | да |
ctx.playerok.update_item(item_id, name=, price=, description=) | Название, цена или описание товара. | да |
ctx.playerok.remove_item(item_id) | Удаляет товар с Playerok. | да |
ctx.playerok.publish_options(item_id) | Варианты публикации: id, name, type, price (₽), period. | да |
ctx.playerok.publish_item(item_id, priority_status_id, price) | Публикация с выбранным вариантом по подтверждённой цене. | да |
ctx.playerok.reviews(limit=100) | Отзывы к сделкам. | нет |
ctx.playerok.balance() | Баланс аккаунта на Playerok (не Lovsel). | нет |
ctx.playerok.graphql(query, variables, operation_name) | Любой запрос GraphQL сайта от имени аккаунта — подробно. | да |
Поля сделки: id (строка), title, amount, currency, status (paid, sent, success, refunded, waiting), raw_status (как на Playerok: PAID, SENT, CONFIRMED…), has_problem, is_automated, item_id, buyer, buyer_id, chat_id, obtaining_fields (данные покупателя), buyer_comment, created_at, marketplace. ID сделок, чатов и товаров Playerok — строки, а не числа.
Выдача товара плагином
deliver() использует ту же операцию выдачи, что и встроенная автовыдача: одна сделка — одна выдача.
@app.on("playerok.new_order")
def on_order(ctx, event):
order = event["order"]
if order["raw_status"] != "PAID":
return
result = ctx.playerok.deliver(order["id"], "Ваш код: " + take_code(ctx))
if result["status"] == "busy":
return # сделку выдаёт встроенная автовыдача или другой плагин
if result["status"] == "unknown":
ctx.log("Playerok не ответил — Lovsel сверит чат сам, повторять не нужно", "warning")busy— сделку уже обслуживает другая выдача. Если продавец выбрал схему «Автовыдача Lovsel», плагин может выдать только сделку, к которой не подходит ни одно правило; при схеме «Плагины» встроенные правила не срабатывают.bad_state— сделка не ждёт выдачи (уже отправлена, возврат, Playerok выдал товар сам).unknown— Playerok не ответил на отправку: сообщение могло дойти. Не отправляйте товар повторно — выдача ждёт сверки и решения продавца.
Действия со сделками и товарами
Всё, что продавец делает с продажами и товарами в кабинете Lovsel, плагин делает готовыми методами. Перед каждым действием Lovsel заново читает сделку или товар с Playerok и проверяет, что он принадлежит подключённому аккаунту.
order = ctx.playerok.mark_sent("deal-1") # «Товар отправлен» (orders.write)
order = ctx.playerok.refund("deal-1") # полный возврат покупателю (orders.write)
ctx.playerok.update_item("item-1", price=249, description="Ключ придёт сразу.") # products.write
options = ctx.playerok.publish_options("item-1") # [{"id": "...", "name": "Обычный", "price": 0.0, ...}]
ctx.playerok.publish_item("item-1", options[0]["id"], options[0]["price"])
ctx.playerok.remove_item("item-1")- Возврат необратим, частичного возврата у Playerok нет: вызывайте
refund()только после подтверждения продавца. Возвраты плагина записываются в журнал действий Lovsel. - Публикация с платным вариантом списывает деньги с баланса Playerok продавца. Плагин передаёт в
publish_item()цену, которую подтвердил продавец: если стоимость варианта изменилась, публикации не будет —MarketplaceErrorс кодомprice_changed. Платные публикации записываются в журнал действий Lovsel. - Если Playerok не ответил на действие, приходит
MarketplaceErrorс кодомunknown_result: Lovsel уже сверил состояние, где это возможно; не повторяйте действие вслепую.
Прямой доступ к GraphQL Playerok
Сайт playerok.com работает запросами GraphQL. ctx.playerok.graphql() отправляет любой такой запрос от имени аккаунта через сессию в Lovsel — с теми же cookies, заголовками браузера и прокси продавца, что и встроенная синхронизация. Нужно разрешение marketplace.api.
ctx.playerok.graphql(query="", variables=None, operation_name="", persisted_hash="")query— текст запроса (query …) или действия (mutation …), отправляется так же, как с сайта.- Без текста — сохранённый запрос сайта:
operation_nameиpersisted_hash(sha256 запроса, как его отправляет сайт), или толькоoperation_nameзапроса, который уже знает Lovsel:viewer,user,deals,deal,userChats,chat,chatMessages,items,item,itemPriorityStatuses,testimonials,gameCategoryDataFields,gameCategoryObtainingTypes,updateDeal,markChatAsRead,createChatMessage,updateItem,removeItem,publishItem,increaseItemPriorityStatus. - Возвращает поле
dataответа. Ошибка Playerok —MarketplaceErrorс кодомgraphqlи текстом площадки. - Действие (
mutation) при обрыве связи не повторяется:MarketplaceErrorс кодомunknown_result— сначала проверьте результат запросом, потом решайте. - Запросы идут с той же паузой, что и синхронизация Lovsel, и требуют активной подписки Lovsel.
me = ctx.playerok.graphql("query viewer { viewer { id username } }")["viewer"]
deals = ctx.playerok.graphql(operation_name="deals", variables={
"pagination": {"first": 24, "after": None},
"filter": {"userId": me["id"], "direction": "OUT", "status": ["PAID"]},
"showForbiddenImage": True,
})["deals"]
ctx.playerok.graphql(operation_name="markChatAsRead", variables={"input": {"chatId": "chat-1"}})Это запросы сайта, а не партнёрский API: Playerok может их менять. Проверяйте ответ и показывайте продавцу понятную ошибку, если поля стали другими. Платные действия (повышение приоритета, платная публикация) выполняйте только после подтверждения продавца с суммой.
События
| Событие | Когда | Данные |
|---|---|---|
playerok.new_order | Новая продажа после подключения | order |
playerok.order_status | Статус сделки изменился | order, old_status, old_raw_status |
playerok.deal_problem | Проблема открыта или решена | order, opened |
playerok.new_chat | Первое сообщение в новом чате | chat_id, buyer, buyer_id, chat_type, message, order |
playerok.new_message | Сообщение покупателя или поддержки | то же, плюс from (buyer или support) |
playerok.new_review | Новый отзыв | order, chat_id, review (id, stars, text) |
playerok.delivery | Товар по сделке отправлен | order, mechanism, status |
Во всех событиях event["marketplace"] == "playerok". Свои сообщения продавца, служебные уведомления площадки и старые события до подключения плагинам не приходят. Одно и то же событие не доставляется дважды при сверке и перезапуске, но обработчик всё равно должен быть идемпотентным: храните в ctx.storage, что уже сделано.
Единый баланс
ctx.wallet работает и для сделок Playerok: ctx.wallet.reserve(order_id, amount, marketplace="playerok"). Сделка должна существовать в Lovsel, а площадка плагина — быть Playerok (marketplace можно не указывать). Идемпотентность и изоляция операций — по пользователю, плагину, площадке, сделке и ключу, как для других площадок. Баланс Playerok и баланс Lovsel — разные деньги: закупки плагина оплачиваются только с баланса Lovsel. Подробнее — «Единый баланс в плагинах».
Проверка без сервера
from lovsel_sdk.testing import load_plugin
plugin = load_plugin("my_plugin.py", playerok={
"orders": [{"id": "deal-1", "title": "Ключ", "amount": 199, "status": "PAID", "buyer": "anna",
"chat_id": "chat-1", "item_id": "item-1"}],
"chats": [{"id": "chat-1", "buyer": "anna"}],
})
plugin.emit("playerok.new_order", {"order": plugin.ctx.playerok.order("deal-1")})
assert plugin.playerok_sent[0]["chat_id"] == "chat-1"Прямые запросы на стенде отвечают из параметра api (ключ — имя операции GraphQL), а возвраты, отметки и публикации складываются в plugin.market_actions:
plugin = load_plugin("my_plugin.py", playerok={"orders": [{"id": "deal-1", "status": "PAID"}]},
api={"viewer": {"viewer": {"id": "user-1", "username": "TestSeller"}}})
assert plugin.ctx.playerok.graphql("query viewer { viewer { id username } }")["viewer"]["id"] == "user-1"
plugin.ctx.playerok.mark_sent("deal-1")
assert plugin.market_actions[-1]["action"] == "mark_sent"Полный пример — docs/examples/playerok_greetings.py: приветствие после оплаты, благодарность после подтверждения и уведомление о плохом отзыве. Он ничего не создаёт на Playerok и не тратит деньги.