From 3e175ff913330b3ee3e6c69b0bcdb4e6ab8dcd65 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sun, 25 May 2025 11:03:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=D0=B5=20OracleDatabase=20=D0=B8=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D1=87=D0=B8=D0=BA=D0=B8=20=D0=B2=20main.py:?= =?UTF-8?q?=20-=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20save=5Fuser=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B8=D0=BB=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=BD=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BE=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5.?= =?UTF-8?q?=20-=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=D1=8B=20save=5Fuser=20=D0=B2?= =?UTF-8?q?=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=87=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=20=D0=B8=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D0=B9=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BE=D1=82=D1=81=D0=BB=D0=B5=D0=B6=D0=B8=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D0=B9=20=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=B9.=20-=20?= =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=87=D0=B8=D0=BA=20admin=5Fstat?= =?UTF-8?q?s=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=BF=D0=BE=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=83=20=D0=B0=D0=B4=D0=BC=D0=B8?= =?UTF-8?q?=D0=BD=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README_USER_TRACKING.md | 200 ++++++++++++++++++++++++++++++++++++++++ create_users_table.sql | 72 +++++++++++++++ db.py | 193 +++++++++++++++++++++++++++++++++++++- main.py | 76 +++++++++++++-- 4 files changed, 531 insertions(+), 10 deletions(-) create mode 100644 README_USER_TRACKING.md create mode 100644 create_users_table.sql diff --git a/README_USER_TRACKING.md b/README_USER_TRACKING.md new file mode 100644 index 0000000..059b243 --- /dev/null +++ b/README_USER_TRACKING.md @@ -0,0 +1,200 @@ +# 📊 Система учета пользователей SalvageDB Bot + +## 🎯 Обзор + +Данная система реализует полный учет пользователей Telegram бота в соответствии с требованиями KYC (Know Your Customer) для сервисов, принимающих платежи. + +## 📝 Что отслеживается + +### Данные пользователя Telegram: +- **ID пользователя** (уникальный 64-bit идентификатор) +- **Имя и фамилия** пользователя +- **Username** (если есть) +- **Код языка** (IETF language tag) +- **Статус Premium** в Telegram +- **Флаг бота** (для различения ботов и пользователей) + +### Данные взаимодействий: +- **Дата первого взаимодействия** +- **Дата последнего взаимодействия** +- **Количество взаимодействий** +- **Источник регистрации** (какая команда/кнопка) + +### Финансовые данные: +- **Общая сумма платежей** в Telegram Stars +- **Количество успешных платежей** +- **История транзакций** + +## 🗄️ Структура базы данных + +### Таблица `bot_users` + +```sql +CREATE TABLE bot_users ( + id NUMBER(19) PRIMARY KEY, -- Telegram user ID + first_name VARCHAR2(256) NOT NULL, -- Имя пользователя + last_name VARCHAR2(256), -- Фамилия (опционально) + username VARCHAR2(128), -- Username (опционально) + language_code VARCHAR2(10), -- Код языка + is_bot NUMBER(1) DEFAULT 0 NOT NULL, -- Флаг бота + is_premium NUMBER(1) DEFAULT 0, -- Telegram Premium + added_to_attachment_menu NUMBER(1) DEFAULT 0, -- Добавлен в меню вложений + first_interaction_date DATE DEFAULT SYSDATE, -- Первое взаимодействие + last_interaction_date DATE DEFAULT SYSDATE, -- Последнее взаимодействие + interaction_count NUMBER(10) DEFAULT 1, -- Количество взаимодействий + is_active NUMBER(1) DEFAULT 1, -- Активен ли пользователь + is_blocked NUMBER(1) DEFAULT 0, -- Заблокирован ли + registration_source VARCHAR2(50) DEFAULT 'bot', -- Источник регистрации + total_payments NUMBER(10,2) DEFAULT 0, -- Сумма платежей + successful_payments_count NUMBER(8) DEFAULT 0, -- Количество платежей + created_at DATE DEFAULT SYSDATE, -- Дата создания записи + updated_at DATE DEFAULT SYSDATE -- Дата обновления +); +``` + +## 🚀 Развертывание + +### 1. Создание таблицы в Oracle + +```bash +# Выполните SQL скрипт для создания таблицы +sqlplus username/password@database @create_users_table.sql +``` + +### 2. Настройка переменных окружения + +Добавьте в ваш `.env` файл: + +```env +# Настройки базы данных (уже должны быть) +db_user=your_oracle_user +db_password=your_oracle_password +db_dsn=your_oracle_dsn + +# ID администратора для команды /admin_stats +ADMIN_USER_ID=123456789 +``` + +### 3. Обновление кода + +Код уже интегрирован в основные файлы: +- `main.py` - обработчики с сохранением данных пользователей +- `db.py` - методы для работы с пользователями +- `create_users_table.sql` - SQL скрипт для создания таблицы + +## 📈 Использование + +### Автоматическое отслеживание + +Система автоматически сохраняет данные пользователя при: +- Использовании команды `/start` +- Нажатии любых кнопок +- Обработке VIN +- Совершении платежей + +### Команды администратора + +``` +/admin_stats - Просмотр статистики пользователей +``` + +Пример вывода: +``` +📊 Bot Users Statistics + +👥 Users Overview: +• Total users: 1,234 +• Premium users: 156 + +💰 Revenue: +• Total revenue: 567 ⭐️ +• Total transactions: 445 + +📈 Activity: +• Active last 24h: 89 +• Active last week: 234 +``` + +## 🔧 API методы + +### Сохранение пользователя +```python +await db.save_user(user: User, interaction_source: str = "bot") -> bool +``` + +### Обновление платежных данных +```python +await db.update_user_payment(user_id: int, payment_amount: float) -> bool +``` + +### Получение статистики пользователя +```python +await db.get_user_stats(user_id: int) -> Optional[dict] +``` + +### Общая статистика +```python +await db.get_users_summary() -> dict +``` + +## 🛡️ Безопасность и соответствие + +### GDPR/Персональные данные: +- Сохраняются только данные, предоставляемые Telegram API +- Данные используются только для предоставления сервиса +- Пользователи могут быть помечены как неактивные + +### Финансовая отчетность: +- Все платежи логируются с timestamp +- Связь платежей с пользователями для аудита +- Возможность генерации отчетов + +### Мониторинг: +- Отслеживание активности пользователей +- Выявление подозрительной активности +- Статистика для бизнес-аналитики + +## 📊 Полезные запросы + +### Топ пользователей по платежам: +```sql +SELECT first_name, last_name, username, total_payments, successful_payments_count +FROM bot_users +WHERE total_payments > 0 +ORDER BY total_payments DESC +FETCH FIRST 10 ROWS ONLY; +``` + +### Активные пользователи за месяц: +```sql +SELECT COUNT(*) as active_users +FROM bot_users +WHERE last_interaction_date >= SYSDATE - 30 +AND is_active = 1; +``` + +### Конверсия в платящих пользователей: +```sql +SELECT + COUNT(*) as total_users, + COUNT(CASE WHEN successful_payments_count > 0 THEN 1 END) as paying_users, + ROUND(COUNT(CASE WHEN successful_payments_count > 0 THEN 1 END) * 100.0 / COUNT(*), 2) as conversion_rate +FROM bot_users +WHERE is_active = 1; +``` + +## 🔍 Мониторинг и алерты + +Рекомендуется настроить мониторинг для: +- Необычно высокой активности от одного пользователя +- Большого количества возвратов платежей +- Резкого роста/падения количества новых пользователей + +## 📞 Поддержка + +При возникновении проблем проверьте: +1. Права доступа к таблице `bot_users` +2. Корректность переменных окружения +3. Логи приложения для ошибок базы данных + +Все операции с базой данных логируются для отладки. \ No newline at end of file diff --git a/create_users_table.sql b/create_users_table.sql new file mode 100644 index 0000000..7a2f8cd --- /dev/null +++ b/create_users_table.sql @@ -0,0 +1,72 @@ +-- SQL скрипт для создания таблицы пользователей +-- Сохраняет все данные о пользователе, передаваемые Telegram через aiogram + +CREATE TABLE bot_users ( + -- Основные данные пользователя Telegram + id NUMBER(19) PRIMARY KEY, -- Telegram user ID (64-bit integer) + first_name VARCHAR2(256) NOT NULL, -- Имя пользователя + last_name VARCHAR2(256), -- Фамилия пользователя (опционально) + username VARCHAR2(128), -- Username (опционально) + language_code VARCHAR2(10), -- Код языка (IETF language tag) + + -- Флаги статуса пользователя + is_bot NUMBER(1) DEFAULT 0 NOT NULL, -- Является ли пользователь ботом + is_premium NUMBER(1) DEFAULT 0, -- Telegram Premium пользователь + added_to_attachment_menu NUMBER(1) DEFAULT 0, -- Добавил ли бота в меню вложений + + -- Системные поля для аудита + first_interaction_date DATE DEFAULT SYSDATE NOT NULL, -- Дата первого взаимодействия + last_interaction_date DATE DEFAULT SYSDATE NOT NULL, -- Дата последнего взаимодействия + interaction_count NUMBER(10) DEFAULT 1 NOT NULL, -- Количество взаимодействий + + -- Дополнительные бизнес-поля + is_active NUMBER(1) DEFAULT 1 NOT NULL, -- Активен ли пользователь + is_blocked NUMBER(1) DEFAULT 0 NOT NULL, -- Заблокирован ли пользователь + registration_source VARCHAR2(50) DEFAULT 'bot', -- Источник регистрации + + -- Финансовые данные + total_payments NUMBER(10,2) DEFAULT 0, -- Общая сумма платежей в звездах + successful_payments_count NUMBER(8) DEFAULT 0, -- Количество успешных платежей + + -- Системные поля + created_at DATE DEFAULT SYSDATE NOT NULL, + updated_at DATE DEFAULT SYSDATE NOT NULL +); + +-- Создание индексов для оптимизации поиска +CREATE INDEX idx_bot_users_username ON bot_users(username); +CREATE INDEX idx_bot_users_language ON bot_users(language_code); +CREATE INDEX idx_bot_users_last_interaction ON bot_users(last_interaction_date); +CREATE INDEX idx_bot_users_is_premium ON bot_users(is_premium); +CREATE INDEX idx_bot_users_created_at ON bot_users(created_at); + +-- Создание комментариев к таблице и полям +COMMENT ON TABLE bot_users IS 'Таблица пользователей Telegram бота с полными данными KYC'; +COMMENT ON COLUMN bot_users.id IS 'Уникальный идентификатор пользователя Telegram (64-bit)'; +COMMENT ON COLUMN bot_users.first_name IS 'Имя пользователя в Telegram'; +COMMENT ON COLUMN bot_users.last_name IS 'Фамилия пользователя в Telegram (опционально)'; +COMMENT ON COLUMN bot_users.username IS 'Username пользователя в Telegram (опционально)'; +COMMENT ON COLUMN bot_users.language_code IS 'Код языка пользователя (IETF language tag)'; +COMMENT ON COLUMN bot_users.is_bot IS 'Флаг: является ли пользователь ботом (0/1)'; +COMMENT ON COLUMN bot_users.is_premium IS 'Флаг: Telegram Premium пользователь (0/1)'; +COMMENT ON COLUMN bot_users.added_to_attachment_menu IS 'Флаг: добавил ли бота в меню вложений (0/1)'; +COMMENT ON COLUMN bot_users.first_interaction_date IS 'Дата первого взаимодействия с ботом'; +COMMENT ON COLUMN bot_users.last_interaction_date IS 'Дата последнего взаимодействия с ботом'; +COMMENT ON COLUMN bot_users.interaction_count IS 'Общее количество взаимодействий с ботом'; +COMMENT ON COLUMN bot_users.is_active IS 'Флаг: активен ли пользователь (0/1)'; +COMMENT ON COLUMN bot_users.is_blocked IS 'Флаг: заблокирован ли пользователь (0/1)'; +COMMENT ON COLUMN bot_users.registration_source IS 'Источник регистрации пользователя'; +COMMENT ON COLUMN bot_users.total_payments IS 'Общая сумма платежей пользователя в Telegram Stars'; +COMMENT ON COLUMN bot_users.successful_payments_count IS 'Количество успешных платежей'; + +-- Создание триггера для автоматического обновления updated_at +CREATE OR REPLACE TRIGGER trg_bot_users_updated_at + BEFORE UPDATE ON bot_users + FOR EACH ROW +BEGIN + :NEW.updated_at := SYSDATE; +END; +/ + +-- Создание последовательности для системных нужд (если потребуется) +CREATE SEQUENCE seq_bot_users_internal START WITH 1 INCREMENT BY 1 NOCACHE; \ No newline at end of file diff --git a/db.py b/db.py index aafa68c..4c448f8 100644 --- a/db.py +++ b/db.py @@ -1,7 +1,8 @@ # db.py import oracledb from typing import Optional, Tuple -import logging +# import logging +from aiogram.types import User class OracleDatabase: @@ -122,3 +123,193 @@ class OracleDatabase: return detailed_info import asyncio return await asyncio.to_thread(_query) + + async def save_user(self, user: User, interaction_source: str = "bot") -> bool: + """ + Сохраняет или обновляет данные пользователя в базе данных + При первом взаимодействии создает запись, при последующих - обновляет + """ + def _save_user(): + with self._pool.acquire() as conn: + with conn.cursor() as cur: + # Проверяем, существует ли пользователь + cur.execute("SELECT id FROM bot_users WHERE id = :user_id", {"user_id": user.id}) + existing_user = cur.fetchone() + + if existing_user: + # Обновляем существующего пользователя + update_query = """ + UPDATE bot_users SET + first_name = :first_name, + last_name = :last_name, + username = :username, + language_code = :language_code, + is_premium = :is_premium, + added_to_attachment_menu = :added_to_attachment_menu, + last_interaction_date = SYSDATE, + interaction_count = interaction_count + 1 + WHERE id = :user_id + """ + + params = { + "user_id": user.id, + "first_name": user.first_name, + "last_name": user.last_name, + "username": user.username, + "language_code": user.language_code, + "is_premium": 1 if user.is_premium else 0, + "added_to_attachment_menu": 1 if user.added_to_attachment_menu else 0 + } + else: + # Создаем нового пользователя + insert_query = """ + INSERT INTO bot_users ( + id, first_name, last_name, username, language_code, + is_bot, is_premium, added_to_attachment_menu, + registration_source, first_interaction_date, + last_interaction_date, interaction_count + ) VALUES ( + :user_id, :first_name, :last_name, :username, :language_code, + :is_bot, :is_premium, :added_to_attachment_menu, + :registration_source, SYSDATE, SYSDATE, 1 + ) + """ + + params = { + "user_id": user.id, + "first_name": user.first_name, + "last_name": user.last_name, + "username": user.username, + "language_code": user.language_code, + "is_bot": 1 if user.is_bot else 0, + "is_premium": 1 if user.is_premium else 0, + "added_to_attachment_menu": 1 if user.added_to_attachment_menu else 0, + "registration_source": interaction_source + } + cur.execute(insert_query, params) + conn.commit() + return True + + cur.execute(update_query, params) + conn.commit() + return True + + try: + import asyncio + loop = asyncio.get_event_loop() + return await loop.run_in_executor(None, _save_user) + except Exception as e: + print(f"Error saving user {user.id}: {e}") + return False + + async def update_user_payment(self, user_id: int, payment_amount: float) -> bool: + """ + Обновляет данные о платежах пользователя + """ + def _update_payment(): + with self._pool.acquire() as conn: + with conn.cursor() as cur: + update_query = """ + UPDATE bot_users SET + total_payments = total_payments + :amount, + successful_payments_count = successful_payments_count + 1, + last_interaction_date = SYSDATE + WHERE id = :user_id + """ + + cur.execute(update_query, { + "user_id": user_id, + "amount": payment_amount + }) + conn.commit() + return cur.rowcount > 0 + + try: + import asyncio + loop = asyncio.get_event_loop() + return await loop.run_in_executor(None, _update_payment) + except Exception as e: + print(f"Error updating payment for user {user_id}: {e}") + return False + + async def get_user_stats(self, user_id: int) -> Optional[dict]: + """ + Получает статистику пользователя из базы данных + """ + def _get_stats(): + with self._pool.acquire() as conn: + with conn.cursor() as cur: + query = """ + SELECT + first_name, last_name, username, language_code, + is_premium, interaction_count, total_payments, + successful_payments_count, first_interaction_date, + last_interaction_date + FROM bot_users + WHERE id = :user_id + """ + + cur.execute(query, {"user_id": user_id}) + result = cur.fetchone() + + if result: + return { + "first_name": result[0], + "last_name": result[1], + "username": result[2], + "language_code": result[3], + "is_premium": bool(result[4]), + "interaction_count": result[5], + "total_payments": float(result[6]) if result[6] else 0.0, + "successful_payments_count": result[7], + "first_interaction_date": result[8], + "last_interaction_date": result[9] + } + return None + + try: + import asyncio + loop = asyncio.get_event_loop() + return await loop.run_in_executor(None, _get_stats) + except Exception as e: + print(f"Error getting stats for user {user_id}: {e}") + return None + + async def get_users_summary(self) -> dict: + """ + Получает общую статистику по пользователям + """ + def _get_summary(): + with self._pool.acquire() as conn: + with conn.cursor() as cur: + summary_query = """ + SELECT + COUNT(*) as total_users, + COUNT(CASE WHEN is_premium = 1 THEN 1 END) as premium_users, + SUM(total_payments) as total_revenue, + SUM(successful_payments_count) as total_transactions, + COUNT(CASE WHEN last_interaction_date >= SYSDATE - 1 THEN 1 END) as active_last_24h, + COUNT(CASE WHEN last_interaction_date >= SYSDATE - 7 THEN 1 END) as active_last_week + FROM bot_users + WHERE is_active = 1 + """ + + cur.execute(summary_query) + result = cur.fetchone() + + return { + "total_users": result[0] or 0, + "premium_users": result[1] or 0, + "total_revenue": float(result[2]) if result[2] else 0.0, + "total_transactions": result[3] or 0, + "active_last_24h": result[4] or 0, + "active_last_week": result[5] or 0 + } + + try: + import asyncio + loop = asyncio.get_event_loop() + return await loop.run_in_executor(None, _get_summary) + except Exception as e: + print(f"Error getting users summary: {e}") + return {} diff --git a/main.py b/main.py index 2cb9786..f18bd65 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,8 @@ import asyncio from os import getenv import logging -# import json +from datetime import datetime + from aiogram import Bot, Dispatcher from aiogram.filters import Command from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, LabeledPrice, PreCheckoutQuery @@ -37,7 +38,10 @@ class VinStates(StatesGroup): # Command handler @dp.message(Command("start")) -async def command_start_handler(message: Message) -> None: +async def command_start_handler(message: Message, db: OracleDatabase) -> None: + # Сохраняем данные пользователя при каждом взаимодействии + await db.save_user(message.from_user, "start_command") + welcome_text = ( "Welcome to SalvagedbBot — your trusted assistant for vehicle history checks via VIN!\n\n" "🔍 What You Can Discover:\n\n" @@ -60,26 +64,34 @@ async def command_start_handler(message: Message) -> None: @dp.callback_query(lambda c: c.data == "decode_vin") -async def decode_vin_callback(callback: CallbackQuery, state: FSMContext): +async def decode_vin_callback(callback: CallbackQuery, state: FSMContext, db: OracleDatabase): + # Сохраняем данные пользователя при нажатии кнопки + await db.save_user(callback.from_user, "decode_vin_button") + await callback.message.answer("Please enter the vehicle VIN.") await state.set_state(VinStates.waiting_for_vin) await callback.answer() @dp.callback_query(lambda c: c.data == "main_menu") -async def main_menu_callback(callback: CallbackQuery, state: FSMContext): +async def main_menu_callback(callback: CallbackQuery, state: FSMContext, db: OracleDatabase): + # Сохраняем данные пользователя при возврате в главное меню + await db.save_user(callback.from_user, "main_menu_button") + await state.clear() - await command_start_handler(callback.message) + await command_start_handler(callback.message, db) await callback.answer() @dp.callback_query(lambda c: c.data and c.data.startswith("pay_detailed_info:")) -async def pay_detailed_info_callback(callback: CallbackQuery): +async def pay_detailed_info_callback(callback: CallbackQuery, db: OracleDatabase): + # Сохраняем данные пользователя при инициации платежа + await db.save_user(callback.from_user, "payment_initiation") + # Extract VIN from callback data vin = callback.data.split(":")[1] - prices = [LabeledPrice(label="Detailed VIN Report", amount=1)] - + logging.info(f"Sending invoice for VIN: {vin}") await callback.bot.send_invoice( chat_id=callback.message.chat.id, title="Detailed VIN Information", @@ -94,6 +106,9 @@ async def pay_detailed_info_callback(callback: CallbackQuery): @dp.message(VinStates.waiting_for_vin) async def process_vin(message: Message, state: FSMContext, db: OracleDatabase): + # Сохраняем данные пользователя при обработке VIN + await db.save_user(message.from_user, "vin_processing") + vin = message.text.strip().upper() if len(vin) == 17 and vin.isalnum() and all(c not in vin for c in ["I", "O", "Q"]): try: @@ -129,8 +144,51 @@ async def pre_checkout_handler(pre_checkout_query: PreCheckoutQuery): await pre_checkout_query.answer(ok=True) +ADMIN_USER_ID = int(getenv("ADMIN_USER_ID", "0")) # ID администратора из переменных окружения + +@dp.message(Command("admin_stats")) +async def admin_stats_handler(message: Message, db: OracleDatabase): + # Проверяем, является ли пользователь администратором + if message.from_user.id != ADMIN_USER_ID: + await message.answer("❌ Access denied. This command is for administrators only.") + return + + try: + # Получаем общую статистику + stats = await db.get_users_summary() + + # Формируем отчет + report = f""" +📊 **Bot Users Statistics** + +👥 **Users Overview:** +• Total users: {stats.get('total_users', 0)} +• Premium users: {stats.get('premium_users', 0)} + +💰 **Revenue:** +• Total revenue: {stats.get('total_revenue', 0)} ⭐️ +• Total transactions: {stats.get('total_transactions', 0)} + +📈 **Activity:** +• Active last 24h: {stats.get('active_last_24h', 0)} +• Active last week: {stats.get('active_last_week', 0)} + +📅 **Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + """ + + await message.answer(report, parse_mode="Markdown") + + except Exception as e: + logging.error(f"Error generating admin stats: {e}") + await message.answer("❌ Error generating statistics. Please try again later.") + + @dp.message(lambda message: message.successful_payment) async def successful_payment_handler(message: Message, db: OracleDatabase): + # Сохраняем данные о платеже пользователя + await db.save_user(message.from_user, "successful_payment") + await db.update_user_payment(message.from_user.id, 1.0) # 1 Telegram Star + payload = message.successful_payment.invoice_payload if payload.startswith("detailed_vin_info:"): @@ -231,7 +289,7 @@ async def successful_payment_handler(message: Message, db: OracleDatabase): report += "⚠️ **TECHNICAL INFORMATION AND ERRORS**\n" for key, data in detailed_info['technical_information_and_errors'].items(): if data['value'] == "0": - report += f"✅ **No errors found**\n" + report += "✅ **No errors found**\n" else: report += f"• **{data['param_name']}:** {data['value']}\n" report += "\n"