Добавлены функции для сохранения и обновления данных пользователей в классе OracleDatabase и обновлены обработчики в main.py:
- Реализован метод save_user для сохранения или обновления информации о пользователе. - Добавлены вызовы save_user в обработчики команд и событий для отслеживания взаимодействий пользователей. - Добавлен обработчик admin_stats для получения статистики пользователей по запросу администратора.
This commit is contained in:
parent
b1ba974b44
commit
3e175ff913
200
README_USER_TRACKING.md
Normal file
200
README_USER_TRACKING.md
Normal file
@ -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. Логи приложения для ошибок базы данных
|
||||||
|
|
||||||
|
Все операции с базой данных логируются для отладки.
|
||||||
72
create_users_table.sql
Normal file
72
create_users_table.sql
Normal file
@ -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;
|
||||||
193
db.py
193
db.py
@ -1,7 +1,8 @@
|
|||||||
# db.py
|
# db.py
|
||||||
import oracledb
|
import oracledb
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
import logging
|
# import logging
|
||||||
|
from aiogram.types import User
|
||||||
|
|
||||||
|
|
||||||
class OracleDatabase:
|
class OracleDatabase:
|
||||||
@ -122,3 +123,193 @@ class OracleDatabase:
|
|||||||
return detailed_info
|
return detailed_info
|
||||||
import asyncio
|
import asyncio
|
||||||
return await asyncio.to_thread(_query)
|
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 {}
|
||||||
|
|||||||
76
main.py
76
main.py
@ -1,7 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from os import getenv
|
from os import getenv
|
||||||
import logging
|
import logging
|
||||||
# import json
|
from datetime import datetime
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher
|
from aiogram import Bot, Dispatcher
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, LabeledPrice, PreCheckoutQuery
|
from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, LabeledPrice, PreCheckoutQuery
|
||||||
@ -37,7 +38,10 @@ class VinStates(StatesGroup):
|
|||||||
|
|
||||||
# Command handler
|
# Command handler
|
||||||
@dp.message(Command("start"))
|
@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_text = (
|
||||||
"Welcome to SalvagedbBot — your trusted assistant for vehicle history checks via VIN!\n\n"
|
"Welcome to SalvagedbBot — your trusted assistant for vehicle history checks via VIN!\n\n"
|
||||||
"🔍 What You Can Discover:\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")
|
@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 callback.message.answer("Please enter the vehicle VIN.")
|
||||||
await state.set_state(VinStates.waiting_for_vin)
|
await state.set_state(VinStates.waiting_for_vin)
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query(lambda c: c.data == "main_menu")
|
@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 state.clear()
|
||||||
await command_start_handler(callback.message)
|
await command_start_handler(callback.message, db)
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query(lambda c: c.data and c.data.startswith("pay_detailed_info:"))
|
@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
|
# Extract VIN from callback data
|
||||||
vin = callback.data.split(":")[1]
|
vin = callback.data.split(":")[1]
|
||||||
|
|
||||||
prices = [LabeledPrice(label="Detailed VIN Report", amount=1)]
|
prices = [LabeledPrice(label="Detailed VIN Report", amount=1)]
|
||||||
|
logging.info(f"Sending invoice for VIN: {vin}")
|
||||||
await callback.bot.send_invoice(
|
await callback.bot.send_invoice(
|
||||||
chat_id=callback.message.chat.id,
|
chat_id=callback.message.chat.id,
|
||||||
title="Detailed VIN Information",
|
title="Detailed VIN Information",
|
||||||
@ -94,6 +106,9 @@ async def pay_detailed_info_callback(callback: CallbackQuery):
|
|||||||
|
|
||||||
@dp.message(VinStates.waiting_for_vin)
|
@dp.message(VinStates.waiting_for_vin)
|
||||||
async def process_vin(message: Message, state: FSMContext, db: OracleDatabase):
|
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()
|
vin = message.text.strip().upper()
|
||||||
if len(vin) == 17 and vin.isalnum() and all(c not in vin for c in ["I", "O", "Q"]):
|
if len(vin) == 17 and vin.isalnum() and all(c not in vin for c in ["I", "O", "Q"]):
|
||||||
try:
|
try:
|
||||||
@ -129,8 +144,51 @@ async def pre_checkout_handler(pre_checkout_query: PreCheckoutQuery):
|
|||||||
await pre_checkout_query.answer(ok=True)
|
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)
|
@dp.message(lambda message: message.successful_payment)
|
||||||
async def successful_payment_handler(message: Message, db: OracleDatabase):
|
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
|
payload = message.successful_payment.invoice_payload
|
||||||
|
|
||||||
if payload.startswith("detailed_vin_info:"):
|
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"
|
report += "⚠️ **TECHNICAL INFORMATION AND ERRORS**\n"
|
||||||
for key, data in detailed_info['technical_information_and_errors'].items():
|
for key, data in detailed_info['technical_information_and_errors'].items():
|
||||||
if data['value'] == "0":
|
if data['value'] == "0":
|
||||||
report += f"✅ **No errors found**\n"
|
report += "✅ **No errors found**\n"
|
||||||
else:
|
else:
|
||||||
report += f"• **{data['param_name']}:** {data['value']}\n"
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
||||||
report += "\n"
|
report += "\n"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user