Написал скилл
This commit is contained in:
parent
2fb00ed2bc
commit
cd736d5a38
9 changed files with 5577 additions and 0 deletions
1
.python-version
Normal file
1
.python-version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3.12
|
||||||
44
README.md
44
README.md
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Secure SBP Bridge
|
||||||
|
|
||||||
|
Инструментарий для обеспечения безопасности финансовых транзакций в рамках работы ИИ-агентов. Проект реализует механизм передачи платежной сессии пользователю через протокол СБП (Система Быстрых Платежей).
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
|
||||||
|
Навык предназначен для интеграции в ИИ-агенты на базе фреймворка OpenClaw. Основная задача — исключить прямой доступ модели к платежным картам и автоматизировать процесс извлечения данных для ручной оплаты человеком (Human-in-the-Loop).
|
||||||
|
|
||||||
|
## Принципы безопасности
|
||||||
|
|
||||||
|
* Изоляция данных: Агент не имеет доступа к реквизитам банковских карт.
|
||||||
|
* Программная экстракция: Платежные ссылки извлекаются напрямую из контекста браузера, минуя текстовую обработку моделью, что предотвращает подмену URL при инъекциях промпта.
|
||||||
|
* Внеполосная верификация: Оплата происходит во внешней защищенной среде (банковское приложение на устройстве пользователя).
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
* `src/sbp_bridge/extractor.py`: Логика взаимодействия с Playwright, поиск ссылок и создание скриншотов.
|
||||||
|
* `src/sbp_bridge/formatter.py`: Формирование структурированного отчета для пользователя.
|
||||||
|
* `src/sbp_bridge/tool.py`: Интеграционный слой для подключения функции к ИИ-агенту.
|
||||||
|
* `src/sbp_bridge/SKILL.md`: Системные инструкции, определяющие правила поведения модели.
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
Для управления зависимостями используется менеджер пакетов `uv`.
|
||||||
|
|
||||||
|
1. Установка зависимостей:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установка браузеров Playwright:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run playwright install chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
## Алгоритм использования
|
||||||
|
|
||||||
|
1. Агент доходит до этапа выбора оплаты в интернет-магазине.
|
||||||
|
2. При обнаружении интерфейса СБП вызывается инструмент `secure_sbp_handover`.
|
||||||
|
3. Пользователь получает в мессенджер ссылку на корзину и прямую ссылку на оплату СБП.
|
||||||
|
4. Агент переходит в режим ожидания.
|
||||||
|
5. После совершения платежа пользователь отправляет сигнал подтверждения («Готово»), после чего агент завершает задачу.
|
||||||
12
pyproject.toml
Normal file
12
pyproject.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[project]
|
||||||
|
name = "secure-transactions"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"browser-use>=0.12.5",
|
||||||
|
"langchain-core>=1.2.23",
|
||||||
|
"loguru>=0.7.3",
|
||||||
|
"playwright>=1.58.0",
|
||||||
|
]
|
||||||
35
src/sbp_bridge/SKILL.md
Normal file
35
src/sbp_bridge/SKILL.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Skill: Secure SBP Payment Bridge (v1.0.0)
|
||||||
|
|
||||||
|
## 1. Назначение
|
||||||
|
Навык предназначен для безопасного завершения процесса покупки в интернет-магазинах и маркетплейсах через Систему Быстрых Платежей (СБП). Реализует протокол "Human-in-the-Loop", исключая прямой доступ ИИ к платежным средствам (картам).
|
||||||
|
|
||||||
|
## 2. Глобальные ограничения (Security Policy)
|
||||||
|
1. **ZERO_CARD_POLICY:** Тебе КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО запрашивать данные банковских карт у пользователя, сохранять их или пытаться ввести их в формы на сайтах.
|
||||||
|
2. **NO_AUTO_PAY:** Тебе запрещено нажимать кнопки типа "Оплатить привязанной картой" или использовать любые методы автоплатежа без подтверждения через СБП.
|
||||||
|
3. **ONLY_SBP:** Единственный разрешенный метод оплаты — СБП (QR-код или платежная ссылка).
|
||||||
|
|
||||||
|
## 3. Условия вызова (Triggers)
|
||||||
|
Вызывай инструмент `secure_sbp_handover` немедленно, если на текущей странице браузера обнаружен один из следующих признаков:
|
||||||
|
- Кнопка или ссылка с текстом "СБП" или "Система быстрых платежей".
|
||||||
|
- Графический QR-код в контексте оплаты.
|
||||||
|
- Переход на домен платежного шлюза (например, `qr.nspk.ru`, `sbp.nspk.ru`).
|
||||||
|
- Финальная страница оформления заказа (Checkout), требующая выбора метода оплаты.
|
||||||
|
|
||||||
|
## 4. Алгоритм действий
|
||||||
|
1. Дойди до этапа выбора оплаты в магазине.
|
||||||
|
2. Если доступен выбор методов, выбери "СБП".
|
||||||
|
3. Как только на экране появилась информация для оплаты (ссылка/QR), вызови инструмент `secure_sbp_handover`.
|
||||||
|
4. Полученный от инструмента текст (отчет) отправь пользователю БЕЗ изменений и дополнений.
|
||||||
|
5. **SUSPEND (Остановка):** После отправки отчета прекрати любые активные действия. Не закрывай вкладку с оплатой и не переходи на другие страницы.
|
||||||
|
|
||||||
|
## 5. Возобновление и завершение
|
||||||
|
- Перейди в режим ожидания (Waiting state).
|
||||||
|
- Твоя задача считается активной, пока пользователь не пришлет сигнал подтверждения (например: "Готово", "Оплатил", "Ок").
|
||||||
|
- После получения сигнала:
|
||||||
|
1. Обнови текущую страницу в браузере.
|
||||||
|
2. Проверь наличие подтверждения заказа (текст "Заказ оформлен", "Спасибо за покупку" или номер заказа).
|
||||||
|
3. Сообщи пользователю финальный статус задачи и заверши сессию.
|
||||||
|
- Если пользователь прислал сигнал "Отмена": закрой вкладку оплаты и прекрати выполнение задачи.
|
||||||
|
|
||||||
|
## 6. Инструменты
|
||||||
|
- `secure_sbp_handover`: Программный захват платежных ссылок и создание скриншота верификации.
|
||||||
0
src/sbp_bridge/__init__.py
Normal file
0
src/sbp_bridge/__init__.py
Normal file
88
src/sbp_bridge/extractor.py
Normal file
88
src/sbp_bridge/extractor.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
async def capture_payment_data(page):
|
||||||
|
"""
|
||||||
|
Основная логика извлечения платежных данных из текущей страницы браузера.
|
||||||
|
"""
|
||||||
|
logger.info("Начинаю поиск платежных данных СБП...")
|
||||||
|
|
||||||
|
# 1. Даем странице немного времени на подгрузку динамических элементов (QR, кнопки)
|
||||||
|
await page.wait_for_timeout(2000)
|
||||||
|
|
||||||
|
# 2. Пытаемся найти ссылку на оплату СБП
|
||||||
|
# Мы используем каскадный поиск (от самого точного к общему)
|
||||||
|
sbp_link = await _find_sbp_link(page)
|
||||||
|
|
||||||
|
# 3. Определяем ссылку на корзину (для проверки пользователем)
|
||||||
|
# Универсальный метод: берем домен текущего сайта
|
||||||
|
cart_url = _generate_cart_url(page.url)
|
||||||
|
|
||||||
|
# 4. Делаем скриншот страницы
|
||||||
|
# Сохраняем в папку воркспейса бота, чтобы он мог отправить файл
|
||||||
|
screenshot_path = await _take_screenshot(page)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"sbp_link": sbp_link,
|
||||||
|
"cart_url": cart_url,
|
||||||
|
"screenshot_path": screenshot_path,
|
||||||
|
"domain": cart_url.split("//")[1].split("/")[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def _find_sbp_link(page):
|
||||||
|
"""Внутренняя функция для поиска URL СБП."""
|
||||||
|
# Случай А: Бот уже перенаправлен на шлюз (например, qr.nspk.ru)
|
||||||
|
current_url = page.url
|
||||||
|
if "qr.nspk.ru" in current_url or "sbp.nspk.ru" in current_url:
|
||||||
|
logger.info("Найден прямой URL платежного шлюза.")
|
||||||
|
return current_url
|
||||||
|
|
||||||
|
# Случай Б: Ссылка спрятана в кнопке на странице магазина
|
||||||
|
# Ищем любые ссылки или кнопки, содержащие домен СБП или протокол sbp://
|
||||||
|
selectors = [
|
||||||
|
'a[href*="qr.nspk.ru"]',
|
||||||
|
'a[href^="sbp://"]',
|
||||||
|
'button[data-url*="qr.nspk.ru"]',
|
||||||
|
]
|
||||||
|
|
||||||
|
for selector in selectors:
|
||||||
|
element = await page.query_selector(selector)
|
||||||
|
if element:
|
||||||
|
link = await element.get_attribute("href") or await element.get_attribute(
|
||||||
|
"data-url"
|
||||||
|
)
|
||||||
|
if link:
|
||||||
|
logger.info(f"Ссылка найдена через селектор: {selector}")
|
||||||
|
return link
|
||||||
|
|
||||||
|
logger.warning("Текстовая ссылка СБП не найдена (возможно, только графический QR).")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_cart_url(current_url):
|
||||||
|
"""Формирует стандартную ссылку на корзину для текущего домена."""
|
||||||
|
try:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
parsed = urlparse(current_url)
|
||||||
|
return f"{parsed.scheme}://{parsed.netloc}/cart"
|
||||||
|
except Exception:
|
||||||
|
return current_url
|
||||||
|
|
||||||
|
|
||||||
|
async def _take_screenshot(page):
|
||||||
|
"""Делает скриншот и возвращает абсолютный путь к файлу."""
|
||||||
|
# Создаем папку для скриншотов, если её нет
|
||||||
|
output_dir = os.path.join(os.getcwd(), "screenshots")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
file_name = f"sbp_payment_{int(time.time())}.png"
|
||||||
|
file_path = os.path.join(output_dir, file_name)
|
||||||
|
|
||||||
|
await page.screenshot(path=file_path, full_page=False)
|
||||||
|
logger.info(f"Скриншот сохранен: {file_path}")
|
||||||
|
return file_path
|
||||||
34
src/sbp_bridge/formatter.py
Normal file
34
src/sbp_bridge/formatter.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
def format_payment_report(data):
|
||||||
|
"""
|
||||||
|
Формирует нейтральный и структурированный отчет для пользователя.
|
||||||
|
"""
|
||||||
|
sbp_link = data.get("sbp_link")
|
||||||
|
cart_url = data.get("cart_url")
|
||||||
|
domain = data.get("domain", "не определен")
|
||||||
|
screenshot_path = data.get("screenshot_path")
|
||||||
|
|
||||||
|
# Формируем блок со ссылками
|
||||||
|
# Если ссылка СБП не найдена, пишем об этом прямо
|
||||||
|
if sbp_link:
|
||||||
|
payment_info = f"[Нажмите для оплаты через СБП]({sbp_link})"
|
||||||
|
else:
|
||||||
|
payment_info = (
|
||||||
|
"Текстовая ссылка СБП не обнаружена. Используйте QR-код со скриншота."
|
||||||
|
)
|
||||||
|
|
||||||
|
report = [
|
||||||
|
"### Запрос на подтверждение транзакции",
|
||||||
|
f"**Источник:** {domain}",
|
||||||
|
"",
|
||||||
|
"Для завершения покупки необходимо выполнить проверку:",
|
||||||
|
f"1. **Состав заказа:** [Открыть корзину]({cart_url})",
|
||||||
|
f"2. **Оплата:** {payment_info}",
|
||||||
|
"",
|
||||||
|
"**Визуальное подтверждение:** Скриншот страницы оплаты сохранен.",
|
||||||
|
f"Путь: `{screenshot_path}`",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
"**Инструкция:** Проверьте данные. После оплаты в приложении банка напишите 'Готово' для финализации задачи. Для отмены напишите 'Отмена'.",
|
||||||
|
]
|
||||||
|
|
||||||
|
return "\n".join(report)
|
||||||
40
src/sbp_bridge/tool.py
Normal file
40
src/sbp_bridge/tool.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from langchain_core.tools import tool
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# Мы убираем проблемный импорт BrowserContext
|
||||||
|
# и импортируем только логику
|
||||||
|
from .extractor import capture_payment_data
|
||||||
|
from .formatter import format_payment_report
|
||||||
|
|
||||||
|
|
||||||
|
@tool
|
||||||
|
async def secure_sbp_handover(context: Any):
|
||||||
|
"""
|
||||||
|
Инструмент для безопасной передачи платежной сессии СБП человеку.
|
||||||
|
Используй этот инструмент ТОЛЬКО когда на экране появился выбор оплаты СБП,
|
||||||
|
QR-код или ссылка nspk.ru.
|
||||||
|
Инструмент извлечет ссылки, сделает скриншот и передаст управление пользователю.
|
||||||
|
"""
|
||||||
|
logger.info("Вызван инструмент secure_sbp_handover")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Получаем активную страницу.
|
||||||
|
# В browser-use это стандартный метод для объекта контекста.
|
||||||
|
page = await context.get_current_page()
|
||||||
|
|
||||||
|
# 2. Извлекаем технические данные
|
||||||
|
data = await capture_payment_data(page)
|
||||||
|
|
||||||
|
# 3. Формируем текст отчета
|
||||||
|
report_text = format_payment_report(data)
|
||||||
|
|
||||||
|
logger.success("Данные подготовлены. Ждем действий пользователя.")
|
||||||
|
|
||||||
|
return report_text
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Ошибка в sbp_bridge: {str(e)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return error_msg
|
||||||
Reference in a new issue