Commit Graph
114 Commits
Author SHA1 Message Date
admin a01195a1c0 Merge dev into main (subscription: MSK time, ceil days, extend by timestamp, menu return) 2026-10-30 13:46:48 +02:00
admin a9e8c2e3ba chore(subscription): reword expiry to 'Закончится <дата> в <время> (МСК)' 2026-10-30 13:44:54 +02:00
admin 6f52599342 fix(subscription): extend by absolute expiry timestamp, not floored days
- activate_subscription_days now adds timedelta to the real expires_at
  (max(now, current_expires)) instead of _days_until() floor value;
  prevents losing fractional time on each renewal (10d + 1d -> was 10d)
- return ceil day count for display consistency
- keep _days_until only for is_subscribed gate and support stats
2026-10-30 13:37:17 +02:00
admin 261b2b2d02 fix(subscription): ceil day count so bought N days shows 'N дн.'
- seconds // 86400 floored to N-1 (bought 10 -> showed 9); use ceil instead
- concrete MSK expiry time stays only for the last day (< 24h remaining)
2026-10-30 13:30:27 +02:00
admin 9400050ade feat(subscription): return to main menu after payment/renewal
- handle_successful_payment: after status, reset FSM to lobby and send main
  menu keyboard (state injected by dispatcher)
- confirm_development_payment (dev path): same — edit status then reply with
  main menu; add state param
- tests: assert second message carries main-menu markup; _StubState clear/set_state
2026-10-30 13:24:53 +02:00
admin b1bc011306 Merge dev into main (subscription expiry clock time) 2026-10-30 13:05:14 +02:00
admin 87f22fb249 fix(subscription): show expiry clock time (MSK) when under a day
- subscription_time_left: replace floor-div '23 ч.'/'мин.' branches with the
  concrete expiry timestamp when less than a full day remains
- add MSK_TIMEZONE (UTC+3) as display default; label time as '(МСК)'
- add shared subscription_status_text helper, use it in status menu,
  post-payment reply and dev confirmation instead of floor-rounded days
- tests: cover under-a-day clock format; fix stale to_thread mock
2026-10-30 13:04:38 +02:00
admin 4ea1fc2737 Merge branch 'dev' into promote-main3 2026-10-30 00:33:26 +02:00
admin 140f2f7263 fix(apinex): browser UA — Cloudflare 1010 blocks bot-like User-Agent 2026-10-30 00:33:07 +02:00
admin cdff37a183 Merge dev into main: model slug free/gemini-3.8-flash
# Conflicts:
#	IDEA.MD
2026-10-30 00:19:28 +02:00
admin 376f26b2eb fix(llm): default model slug free/gemini-3.8-flash (billing) 2026-10-30 00:12:01 +02:00
admin e9123274a6 docs(support): require Manage Topics right for the support group 2026-10-29 23:58:20 +02:00
admin 2b78351707 docs(support): require Manage Topics right for the support group 2026-10-29 23:57:49 +02:00
admin b35188eacc docs(groups): pin support group id 2026-10-29 23:24:03 +02:00
admin 48a82bd0b3 docs(groups): pin support group id 2026-10-29 23:22:44 +02:00
admin 9d1f7fa3dd fix(admin): accept /stats with bot mention 2026-10-29 23:10:38 +02:00
admin 741a27eb27 fix(admin): accept /stats with bot mention 2026-10-29 23:10:38 +02:00
admin 67b4cd0c70 docs(groups): pin admin group and topic ids 2026-10-29 22:55:37 +02:00
admin f1712d6db0 docs(groups): pin admin group and topic ids 2026-10-29 22:54:48 +02:00
admin d212e60eab fix(logging): write raw updates in production too 2026-10-29 22:23:59 +02:00
admin 089c59bc68 fix(logging): write raw updates in production too 2026-10-29 22:19:40 +02:00
admin de638bb4d0 docs: describe analytics storage and production raw update log 2026-10-29 22:19:40 +02:00
admin 57f4c6cb4f fix(subscription): show precise remaining time 2026-10-29 21:35:58 +02:00
admin 2fd41d82b9 feat(training): drop info heading and show last training time 2026-10-29 20:20:32 +02:00
admin 60f41dd675 fix(training): show progress while building info summary 2026-10-29 20:13:54 +02:00
admin 61c66def3f feat(training): summarize learned profile and add info button 2026-10-29 20:05:42 +02:00
admin 26b533716a fix(subscription): grant one day for one-star plan 2026-10-29 20:00:26 +02:00
admin 60dc55a9b3 fix(subscription): allow zero-day dev confirmation 2026-10-29 13:31:24 +02:00
admin dc2ec6344b fix(subscription): process zero-day one-star payment 2026-10-29 13:03:55 +02:00
admin 0dc54a26b9 fix: use one-star subscription probe 2026-10-21 14:27:23 +02:00
admin a5389e9927 fix(subscription): restore subscription:plan callback decorator
Коммит 3819230 случайно удалил декоратор @router.callback_query(F.data.startswith("subscription:plan:"))
у subscription_plan — хендлер перестал регистрироваться, и все кнопки выбора
тарифа (0/1/10/30 дней) отвечали молча: callback ничем не обрабатывался,
а catch-all @router.message() его не видит.
2026-10-21 13:54:50 +02:00
admin 3819230b3e test: add zero-star invoice probe 2026-10-21 12:56:50 +02:00
admin 13230e3fe2 fix: simplify subscription payment messages 2026-10-20 21:29:44 +02:00
admin c32702c402 fix: simplify subscription payment messages 2026-10-20 21:13:51 +02:00
admin 1ff4012e2f feat: show analytics across periods in tables 2026-10-20 18:35:39 +02:00
admin e4b1b1e510 feat: render full analytics as tables 2026-10-20 17:05:52 +02:00
admin 112c3247e0 feat: add per-user analytics and stats report 2026-10-20 16:42:30 +02:00
admin aa59e30776 docs: update onboarding documentation 2026-10-19 18:47:33 +02:00
admin c1f49ededa feat: update welcome onboarding message 2026-10-19 17:53:39 +02:00
admin 9c37a05139 fix: simplify user-facing bot messages 2026-10-19 15:15:53 +02:00
admin 073c7d2c81 fix: use reply keyboard for subscription purchase 2026-10-12 21:14:58 +02:00
hermes aba1eb4ced fix: allow help navigation outside help mode 2026-10-12 20:40:59 +02:00
hermes a11b5aac64 feat: перевести навигацию справки на inline-клавиатуру 2026-10-11 22:55:17 +02:00
hermes da0da91479 feat: уплотнить кнопки справки 2026-10-11 22:11:57 +02:00
hermes a73d8f403d docs: синхронизация версии 0.1.0+59 в README и IDEA.MD 2026-10-11 21:11:29 +02:00
hermes 21f4adf0b1 feat: кнопка «Ответить» на сообщении оператора
Оператор мог ответить, когда пользователь уже вышел из режима поддержки.
Сообщение приходило, но ответить было нечем: снова идти в «Помощь →
Поддержка», а про прежний контекст уже ничего не напоминало.

- К сообщению оператора добавляется инлайн-кнопка «↩️ Ответить», только
  если обращение закрыто (при активном пользователь и так пишет оператору).
- Кнопка ставит is_active=1, убирает себя с сообщения и показывает
  клавиатуру поддержки (только «Главное меню»).
- Callback проверяет владельца: support:reply:<user_id> сработает только
  для того пользователя, кому пришло сообщение.
- Роутер support.router не затронут фильтром основного роутера
  (F.message.chat.type == "private") — он на него не распространяется.

Тесты: 251 OK (7 новых). Проверено, что aiogram вызывает bot(SendMessage(...)),
а не bot.send_message — asserts адаптированы, иначе тест проходил вхолостую.

VERSION: 0.1.0+58 -> 0.1.0+59
2026-10-11 20:40:06 +02:00
hermes 175d618d62 feat: подпись имени пользователя на сообщении в топике
В топике было непонятно, от кого сообщение: текст приходил без автора.

- Текст уходит в топик как «<Имя>:\n<текст>»; имени нет — «<id>:\n<текст>»
  (user_signature()).
- Caption медиа уходит текстом с той же подписью, само медиа копируется.
- Всё остальное (фото, документы, голосовые без подписи) копируется как есть,
  без подписи — как и просили «если не текст, то пофиг».

Симметрично подписи оператора «Оператор поддержки:» на ответах.

Тесты: 244 OK (8 новых — подпись имени, fallback на id, медиа с caption,
медиа без текста копируется без подписи).

VERSION: 0.1.0+57 -> 0.1.0+58
2026-10-11 20:22:34 +02:00
hermes 6938c1d0f3 feat: подпись «Оператор поддержки» на ответе оператора
Ответ из топика приходил пользователю без подписи, и было непонятно, кто
пишет — бот или человек.

- Текст уходит одним сообщением: «Оператор поддержки:\n<текст>».
- У медиа copy_message не позволяет подменить caption, поэтому подпись
  отправляется отдельным сообщением перед копией; caption медиа, если был,
  попадает в то же сообщение.
- Подпись задаётся SUPPORT_OPERATOR_SIGNATURE (default «Оператор поддержки»).
- Заметки с префиксом // по-прежнему не уходят пользователю.

Тесты: 236 OK (6 новых — текст, медиа с caption, медиа без caption,
переопределение подписи через env).

VERSION: 0.1.0+56 -> 0.1.0+57
2026-10-11 19:59:34 +02:00
hermes 3dd2924d5e fix: вход в любой режим закрывает обращение поддержки
Деактивация была только в /start и «Главное меню». После перехода в
«Обучение» флаг is_active оставался, и текст уезжал оператору, а не в
обучение — сообщение уходило не туда.

Правило теперь: ушёл из контекста поддержки — обращение неактивно.
_close_support_case() вызывается из enter_mode() (любой режим),
show_main_menu() и start(). «🆘 Поддержка» — не режим, флаг не снимает.
Топик и история сохраняются, следующее обращение продолжает переписку.

Три точки вызова сведены в один helper вместо трёх копий try/except.

Тесты: 230 OK (3 новых). Регрессия проверена на старом коде: после входа
в «Обучение» is_active=True и relay=1 (текст ушёл оператору); с фиксом
is_active=False, relay=0 (текст обработал бот).

VERSION: 0.1.0+55 -> 0.1.0+56
2026-10-11 19:48:57 +02:00
hermes 12ef2c6b44 fix: /start закрывает обращение в поддержку
/start возвращает в лобби, но не снимал is_active в support_topics: FSM
сбрасывался, а обращение в БД оставалось открытым. Пользователь оказывался
в лобби, а следующее сообщение всё равно уходило оператору. Дополнительно
enter_mode() блокировал вход в режим сообщением «Идёт обращение в поддержку»
— после /start это выглядело как необъяснимый отказ.

- start() вызывает support.deactivate() перед сбросом FSM.
- Блокировка из enter_mode() удалена: выход в любой режим из открытого
  обращения разрешён — пользователя нельзя запирать. Проверка дублировала
  middleware иConflictовала с /start.

Тесты: 227 OK (3 новых). Регрессия проверена на старом коде: /start оставляет
is_active=True (баг), с фиксом — False. Топик при этом сохраняется, поэтому
следующее обращение продолжает ту же переписку.

VERSION: 0.1.0+54 -> 0.1.0+55
2026-10-11 18:34:14 +02:00