Skip to content

Repository files navigation

Auto-Pricing Bot (GGSEL + DigiSeller)

Telegram-бот для мониторинга цен конкурентов и автообновления цены товара через API продавца.

Engineering focus Reliability controls Verification
Independent GGSEL and DigiSeller profiles, competitor parsing and price automation Price floors, rate limits, cooldowns, idempotent updates and safe parser fallbacks Docker deployment, GitHub Actions and a pytest suite covering core behavior

Start here: customer guide · deployment · tests · source

Простая инструкция для заказчика (без тех. терминов):

Поддерживаются независимые профили:

  • ggsel
  • digiseller

Для каждого профиля хранятся отдельно:

  • API-ключи/токен
  • основной ID товара
  • список отслеживаемых товаров (tracked_products)
  • список конкурентов
  • runtime-настройки
  • state/история/алерты

Обычный путь использования (для заказчика)

  1. В боте нажмите 🧩 Профиль и выберите площадку (GGSEL или DIGISELLER).
  2. Нажмите 📦 Товары.
  3. Добавьте товар и конкурента одной строкой:
    • <product_id> <url_конкурента>
    • пример: 4697439 https://ggsel.net/catalog/product/102124601
  4. Нажмите ⚙ Настройки и проверьте, что показан нужный Активный товар.
  5. Включите 🔔 Автоцена.
  6. Выберите режим:
    • Следование — цена как у конкурента.
    • Демпинг — чуть ниже конкурента.
    • Повышение — чуть выше конкурента.
  7. При необходимости задайте основные лимиты:
    • 📉 Мин — минимальная цена.
    • 📈 Макс — максимальная цена.
    • ↘️ Шаг- и ↗️ Шаг+ — шаг изменения цены.
    • 🔘 Округление — шаг округления (например 0.01 или 0.0001).
  8. Откройте 📊 Статус и убедитесь, что:
    • URL конкурента правильный.
    • Цена конкурента парсится.
    • Режим и лимиты те, которые вы выставили.

Важно:

  • Все настройки применяются только к активному товару (текущей паре товар↔конкурент).
  • Переключение товара (⬅/➡) меняет только товар для редактирования, а не профиль.
  • Авто-инструкции держите выключенными, пока не начнётся отдельное тестирование.

Ключевая логика

Базовое правило цены:

  • my_price = competitor_min - UNDERCUT_VALUE
  • по умолчанию UNDERCUT_VALUE=0.0051
  • пример: конкурент 0.3400 -> мы 0.3349

Ограничения и защита:

  • MIN_PRICE, MAX_PRICE
  • MODE=FOLLOW|DUMPING|RAISE
  • FOLLOW: ставить ровно цену конкурента (4 знака)
  • DUMPING: round(конкурент, 2) - 0.0051
  • RAISE: round(конкурент, 2) + 0.0049
  • MAX_DOWN_STEP для ограничения резкого падения
  • FAST_REBOUND_DELTA + bypass cooldown для быстрого отката вверх
  • при POSITION_FILTER_ENABLED=true и rank=N/A включается эвристика WEAK_UNKNOWN_RANK_* (абсолютный/относительный gap между 1-й и 2-й ценой), чтобы не демпинговать за "слабым" конкурентом

Идемпотентность апдейтов:

  • при неизменной цене конкурента повторный API update не выполняется
  • если целевая цена уже была применена, бот делает skip (без лишнего шума)
  • если у профиля пустой список конкурентов, цикл делает безопасный skip без отправки error-алертов
  • профиль может работать и без COMPETITOR_URLS (ручные операции и API smoke)

Точность цен:

  • расчёт/сохранение/отображение в боте: 4 знака после запятой
  • в GGSEL update payload цена отправляется в формате 0.0000
  • API чтение у площадки может возвращать округлённое значение, это учитывается

Как парсится конкурент

Pipeline:

  1. stealth_requests + HTML (BeautifulSoup)
  2. извлечение unit-price (unitsToPay / unitsToGet) если доступно
  3. fallback по CSS-селекторам цены
  4. fallback на публичный endpoint: https://api4.ggsel.com/goods/<id> (используется только для доменов ggsel.*)

Что пишется в state:

  • last_competitor_min
  • last_competitor_url
  • last_competitor_method
  • last_competitor_parse_at
  • ошибки/причины блокировок парсера

Поведение cookies:

  • бот синхронизирует cookies из .env на каждом цикле без рестарта
  • если cookies протухли и парсинг без cookies успешен, runtime cookies очищаются автоматически (чтобы не повторять битый запрос)
  • если retry без cookies тоже неуспешен, stale runtime cookies сбрасываются, чтобы в следующем цикле не застревать на 401/403 с тем же значением
  • путь к env-файлу можно переопределить через ENV_FILE_PATH

Быстрый старт (локально)

python3 -m venv .venv
source .venv/bin/activate
# runtime deps
pip install -r requirements.txt
# для запуска тестов/линтеров
pip install -r requirements-dev.txt
cp .env.example .env

Минимум в .env:

  • TELEGRAM_BOT_TOKEN
  • TELEGRAM_ADMIN_IDS
  • для включённого профиля: *_API_KEY/*_ACCESS_TOKEN, *_SELLER_ID, *_PRODUCT_ID
  • если *_API_KEY это JWT access token, задайте *_API_SECRET для ApiLogin (автообновление токена)
  • GGSEL_COMPETITOR_URLS и/или DIGISELLER_COMPETITOR_URLS (для GGSEL есть fallback на COMPETITOR_URLS для обратной совместимости)
  • cookies конкурента: GGSEL_COMPETITOR_COOKIES / DIGISELLER_COMPETITOR_COOKIES (если не заданы, используется общий COMPETITOR_COOKIES)
  • при нестандартном запуске можно явно задать ENV_FILE_PATH

Если у включённого профиля не задан *_PRODUCT_ID, такой профиль не запускается (fail-safe защита от шумных циклов и пустых API-обновлений).

Профильные дефолты DigiSeller (опционально):

  • DIGISELLER_MIN_PRICE
  • DIGISELLER_MAX_PRICE
  • DIGISELLER_DESIRED_PRICE
  • DIGISELLER_UNDERCUT_VALUE
  • DIGISELLER_MODE
  • DIGISELLER_WEAK_PRICE_CEIL_LIMIT
  • DIGISELLER_POSITION_FILTER_ENABLED
  • DIGISELLER_WEAK_POSITION_THRESHOLD
  • DIGISELLER_WEAK_UNKNOWN_RANK_ENABLED
  • DIGISELLER_WEAK_UNKNOWN_RANK_ABS_GAP
  • DIGISELLER_WEAK_UNKNOWN_RANK_REL_GAP
  • DIGISELLER_CHECK_INTERVAL
  • DIGISELLER_FAST_CHECK_INTERVAL_MIN
  • DIGISELLER_FAST_CHECK_INTERVAL_MAX
  • DIGISELLER_COOLDOWN_SECONDS
  • DIGISELLER_IGNORE_DELTA
  • DIGISELLER_NOTIFY_SKIP
  • DIGISELLER_NOTIFY_SKIP_COOLDOWN_SECONDS
  • DIGISELLER_NOTIFY_COMPETITOR_CHANGE
  • DIGISELLER_COMPETITOR_CHANGE_DELTA
  • DIGISELLER_COMPETITOR_CHANGE_COOLDOWN_SECONDS
  • DIGISELLER_UPDATE_ONLY_ON_COMPETITOR_CHANGE
  • DIGISELLER_NOTIFY_PARSER_ISSUES
  • DIGISELLER_PARSER_ISSUE_COOLDOWN_SECONDS
  • DIGISELLER_HARD_FLOOR_ENABLED
  • DIGISELLER_MAX_DOWN_STEP
  • DIGISELLER_FAST_REBOUND_DELTA
  • DIGISELLER_FAST_REBOUND_BYPASS_COOLDOWN

Эти значения применяются только если соответствующий runtime-ключ ещё не был задан ранее в БД (runtime_settings).

Общий флаг уведомлений об ошибках в Telegram:

  • NOTIFY_ERRORS=false — не отправлять ❌ Ошибка в Telegram, писать только в серверные логи.

Включение DigiSeller профиля

Минимальный набор переменных:

  • DIGISELLER_ENABLED=true
  • DIGISELLER_API_KEY (или DIGISELLER_ACCESS_TOKEN)
  • DIGISELLER_API_SECRET (если DIGISELLER_API_KEY хранится как JWT)
  • DIGISELLER_SELLER_ID
  • DIGISELLER_PRODUCT_ID

Рекомендуемо сразу указать:

  • DIGISELLER_COMPETITOR_URLS (если нужен авто-режим мониторинга)
  • DIGISELLER_REQUIRE_API_ON_START=true (чтобы процесс не стартовал с битым API)

Опционально для авто-инструкций в переписке заказа (DigiSeller):

  • DIGISELLER_CHAT_AUTOREPLY_ENABLED=true
  • DIGISELLER_CHAT_AUTOREPLY_PRODUCT_IDS=5077639,5104800
  • DIGISELLER_CHAT_AUTOREPLY_INTERVAL_SECONDS=30
  • DIGISELLER_CHAT_AUTOREPLY_DEDUPE_BY_MESSAGES=true
  • DIGISELLER_CHAT_AUTOREPLY_ONLY_EMPTY_CHAT=true
  • DIGISELLER_CHAT_AUTOREPLY_REQUIRE_RULES=true
  • DIGISELLER_CHAT_AUTOREPLY_ALLOW_CUSTOM_TEXT=false
  • DIGISELLER_CHAT_AUTOREPLY_ALLOW_TEMPLATE_FALLBACK=false
  • DIGISELLER_CHAT_AUTOREPLY_LOOKBACK_MESSAGES=30
  • DIGISELLER_CHAT_AUTOREPLY_SENT_TTL_DAYS=30
  • DIGISELLER_CHAT_AUTOREPLY_CLEANUP_EVERY_HOURS=24
  • DIGISELLER_CHAT_TEMPLATE_RU_ALREADY, DIGISELLER_CHAT_TEMPLATE_RU_ADD
  • DIGISELLER_CHAT_TEMPLATE_EN_ALREADY, DIGISELLER_CHAT_TEMPLATE_EN_ADD

Если шаблоны не заданы, бот берёт текст из полей товара:

  • для RU: info_ru/instruction_ru/add_info_ru с fallback на info/instruction/add_info
  • для EN: info_en/instruction_en/add_info_en с fallback на info/instruction/add_info

Для режима добавит приоритет у add_info*, иначе у info*. Инструкция отправляется для каждого нового заказа (order_id) отдельно. Для одного и того же заказа бот отправляет инструкцию только один раз (антидубль по order_id + dedupe по тексту в истории сообщений). Если включён *_CHAT_AUTOREPLY_ONLY_EMPTY_CHAT=true, бот отправляет инструкцию только в пустой чат заказа (без предыдущих сообщений). Перед отправкой бот проверяет права chat API; при нехватке прав отправка не выполняется, причина пишется в /diag (Chat perms). При *_CHAT_AUTOREPLY_ALLOW_CUSTOM_TEXT=false кастомные тексты правил игнорируются (в чат уйдёт только спарсенная инструкция товара по выбранному параметру). При *_CHAT_AUTOREPLY_ALLOW_TEMPLATE_FALLBACK=false шаблонные fallback-сообщения не используются. Если в заказе у выбранного параметра (option/variant) есть свой текст инструкции, бот отправляет именно его (приоритет над общим info/add_info). Если по выбранному параметру текста нет — отправка пропускается, причина пишется в лог. Логика одинакова для DigiSeller и GGSEL при включённом *_CHAT_AUTOREPLY_ENABLED.

Аналогичные параметры есть и для GGSEL:

  • GGSEL_CHAT_AUTOREPLY_ENABLED
  • GGSEL_CHAT_AUTOREPLY_PRODUCT_IDS
  • GGSEL_CHAT_AUTOREPLY_INTERVAL_SECONDS
  • GGSEL_CHAT_AUTOREPLY_DEDUPE_BY_MESSAGES
  • GGSEL_CHAT_AUTOREPLY_ONLY_EMPTY_CHAT
  • GGSEL_CHAT_AUTOREPLY_REQUIRE_RULES
  • GGSEL_CHAT_AUTOREPLY_ALLOW_CUSTOM_TEXT
  • GGSEL_CHAT_AUTOREPLY_ALLOW_TEMPLATE_FALLBACK
  • GGSEL_CHAT_AUTOREPLY_LOOKBACK_MESSAGES
  • GGSEL_CHAT_AUTOREPLY_SENT_TTL_DAYS
  • GGSEL_CHAT_AUTOREPLY_CLEANUP_EVERY_HOURS
  • GGSEL_CHAT_TEMPLATE_RU_ALREADY, GGSEL_CHAT_TEMPLATE_RU_ADD
  • GGSEL_CHAT_TEMPLATE_EN_ALREADY, GGSEL_CHAT_TEMPLATE_EN_ADD

Быстрая проверка только DigiSeller:

python3 scripts/smoke_profiles_api.py --profile digiseller --verify-read

Запуск:

python3 -m src

Запуск в Docker

docker compose up -d --build
docker compose logs -f

Telegram управление (reply-клавиатура)

Команды:

  • /start
  • /status — статус активного профиля
  • /status <profile> — статус выбранного профиля
  • /diag — диагностика активного профиля
  • /diag <profile> — диагностика выбранного профиля
  • /smoke — безопасный API smoke для активного профиля (read + noop write + verify) для DigiSeller дополнительно показывает token/perms. Можно указать профиль аргументом: /smoke ggsel или /smoke digiseller.

Алиасы профилей в аргументах команд:

  • GGSEL: gg, ggsel
  • DigiSeller: digi, dg, digiseller, plati

Главное меню (актуально)

  • 📊 Статус
  • 📦 Товары
  • ⬅ Пред. товар / ➡ След. товар
  • 🧩 Профиль
  • ⚙ Настройки

Важно по UX:

  • кнопки ⬅/➡ только переключают активный товар для управления и оставляют пользователя в главном меню (без автоперехода в настройки)
  • это защита от случайных мискликов и лишних ручных изменений цены
  • при смене профиля незавершённый ввод (pending action) сбрасывается автоматически

Как управлять товарами

Кнопка 📦 Товары (из главного меню) открывает ввод:

  • отправьте product_id — добавить/выбрать товар
  • отправьте list — список товаров профиля
  • отправьте сразу пару: <product_id> <url_конкурента>
  • задайте понятное имя: name <product_id|active> <название>
  • сбросьте имя: clearname <product_id|active>

Примеры:

  • 4697439
  • 4697439 https://ggsel.net/catalog/product/102124601
  • name active Скины подарком

Как работают несколько товаров

  • один профиль может мониторить несколько товаров одновременно
  • у каждого товара свой список URL конкурентов
  • бот мониторит все товары из списка
  • в списке показывается ID + имя (если имя найдено в карточке или задано вручную)
  • активный товар нужен для редактирования его настроек: режим, автоцена и лимиты
  • в 📊 Статус показываются:
    • активный товар
    • позиция активного товара в списке (1/N)
    • ключевые цены (моя/выставленная/конкурента)
    • текущий URL/метод/время последнего парса

Настройки (кнопка ⚙ Настройки)

Доступные кнопки:

  • 🔔 Авто: ВКЛ/ВЫКЛ — для активного товара
  • 🎯 Цена
  • 🔀 Режим
  • 📦 Товары — добавить/выбрать товар и сразу привязать URL конкурента
  • 🗑 Удалить товар — удалить один товар (active/id) или сразу все (all)
  • 💬 Инструкции: ВКЛ/ВЫКЛ — если профиль поддерживает chat API
  • 📭/📨 Только пустой чат — отправлять авто-инструкцию только в пустой чат
  • 📝 Правила инстр. — правила отправки по конкретным вариантам параметров

Режимы цены (простыми словами)

  • Следование: ставим ровно цену конкурента (4 знака), например 0.3560 -> 0.3560
  • Демпинг: считаем от витринной цены конкурента (2 знака): round(конкурент, 2) - 0.0051, например витрина 0.35 -> 0.3449
  • Повышение: также от витринной цены (2 знака): round(конкурент, 2) + 0.0049, например витрина 0.35 -> 0.3549

Важно:

  • стратегии, авто-режим и лимиты изолированы по товару внутри профиля
  • настройки одного товара не переносятся на другой товар автоматически
  • при добавлении/удалении товара через Telegram новый список scheduler’ов применяется после перезапуска процесса/контейнера

Правила авто-инструкций по параметрам

  • Откройте ⚙ Настройки -> 📝 Правила инстр.
  • Бот покажет список вариантов параметров выбранного товара.
  • Если включено хотя бы одно правило, инструкция уходит только по совпавшим правилам.
  • Вкл/выкл, сброс и выход делаются кнопками под сообщением со списком правил.
  • Кастомный текст опционален: если не задан, бот берет текст из карточки товара.
  • Текстовые команды (по желанию):
    • text <N> <текст> — задать свой текст для варианта
    • clear <N> — убрать свой текст (оставить текст из карточки товара)
    • done — выйти из редактора

Полезные скрипты

Проверка GGSEL apilogin (использует GGSEL_API_SECRET или fallback на GGSEL_API_KEY):

python3 scripts/check_apilogin.py

Если GGSEL_API_KEY у вас JWT access token, обязательно задайте GGSEL_API_SECRET, иначе apilogin недоступен.

Выпуск access token через apilogin:

python3 scripts/issue_access_token.py

Smoke API активных профилей:

python3 scripts/smoke_profiles_api.py

Read-only smoke (без write probe):

python3 scripts/smoke_profiles_api.py --profile all --verify-read

Проверить только DigiSeller:

python3 scripts/smoke_profiles_api.py --profile digiseller

Если профиль запрошен явно (--profile ggsel|digiseller) и выключен в .env, скрипт завершится с ошибкой (код 1).

С реальным тестовым изменением и rollback:

python3 scripts/smoke_profiles_api.py --profile digiseller --mutate --delta 0.0001 --verify-read

Smoke прав чатов/переписки (без отправки сообщений):

python3 scripts/smoke_chat_api.py --profile all

С безопасной POST-пробой chat.send (id_i=0):

python3 scripts/smoke_chat_api.py --profile digiseller --send-probe

Smoke доступности текстов инструкций (без отправки сообщений):

python3 scripts/smoke_instruction_data.py --profile all

Тесты и проверки

pytest -q
python3 -m compileall src scripts healthcheck.py

Если у вас Python 3.14+, используйте версии из requirements-dev.txt (pytest==8.4.2, pytest-asyncio==1.2.0), чтобы избежать deprecated warning от старого pytest-asyncio.

Структура кода

  • src/main.py — запуск профилей и orchestration
  • src/scheduler.py — цикл парсинг -> расчёт -> update/skip
  • src/logic.py — бизнес-формулы цены
  • src/rsc_parser.py — парсер конкурента
  • src/api_client.py — GGSEL API клиент
  • src/digiseller_client.py — DigiSeller API клиент
  • src/telegram_bot.py — Telegram reply UI + handlers
  • src/storage.py — SQLite state/runtime/history/alerts

Источники API

  • GGSEL Seller API: https://seller.ggsel.com/docs/seller-api-v-1
  • DigiSeller API: https://my.digiseller.com/inside/api.asp

About

Python auto-pricing and chat automation bot for GGSEL and DigiSeller, with competitor monitoring, tests and Docker deployment.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages