FIX: Полное восстановление веб-панели, фикс экспорта JSON, исправление путей PATH в системных юнитах и обновление шаблонов
This commit is contained in:
parent
434cd71505
commit
d068e8137f
26 changed files with 8861 additions and 484 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -31,7 +31,6 @@ logs/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Exports
|
# Exports
|
||||||
exports/
|
|
||||||
backups/
|
backups/
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
|
|
|
||||||
25
EXPORTS_FIX_LOG.md
Normal file
25
EXPORTS_FIX_LOG.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# 📜 История исправлений REBORN (LKM37) - ЭКСПОРТ И ПРОЦЕССЫ
|
||||||
|
|
||||||
|
## 18 апреля 2026 г. (Финал)
|
||||||
|
### 🛑 Что было сломано:
|
||||||
|
1. **Экспорт JSON**: Старая логика была завязана на 7 дней, а ручной экспорт падал из-за отсутствия моделей и таймаутов Git Push (объем данных > 260Мб).
|
||||||
|
2. **Дубликаты процессов**: Бот сам запускал веб-сервер внутри `on_startup`, что приводило к конфликтам порта 8000 при перезапусках.
|
||||||
|
3. **Git/Gitea**: `exports/` был в `.gitignore`, что блокировало автоматическую отправку. Прокси мешал локальному доступу к Synology.
|
||||||
|
|
||||||
|
### ✅ Что сделано (РЕЗУЛЬТАТ):
|
||||||
|
1. **Новый сервис `ChatExporter`**:
|
||||||
|
- Полный экспорт всей истории без лимитов.
|
||||||
|
- Прямой Push в Gitea (использует `git add -f`).
|
||||||
|
- Учет всех выгрузок в новой таблице `chat_exports`.
|
||||||
|
2. **Разделение сервисов**:
|
||||||
|
- `domovoy-bot.service`: Только Telegram-бот и планировщик.
|
||||||
|
- `domovoy-web.service`: Отдельный сервис для админки (порт 8000).
|
||||||
|
- Это исключило конфликты портов раз и навсегда.
|
||||||
|
3. **Исправление путей и времени**: Везде используется Ульяновск (UTC+4) и динамические пути проекта.
|
||||||
|
|
||||||
|
### 💡 Как пользоваться:
|
||||||
|
- **Кнопка в меню**: Собирает всё и пушит в Гит. Если файл большой — подождите 30 секунд.
|
||||||
|
- **Статистика**: На странице экспорта теперь видно общее количество выгрузок и дату последней.
|
||||||
|
- **Автоматика**: Планировщик раз в неделю делает то же самое.
|
||||||
|
|
||||||
|
**ВАЖНО**: Для работы с Gitea прокси не нужен. Скрипт сам сбрасывает переменные окружения при пуше.
|
||||||
20
database/migrate_exports.py
Normal file
20
database/migrate_exports.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
|
||||||
|
db_path = "database/domovoy.db"
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS chat_exports (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
export_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
filename TEXT NOT NULL,
|
||||||
|
total_messages INTEGER,
|
||||||
|
git_pushed BOOLEAN DEFAULT 0
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print("✅ Таблица chat_exports создана")
|
||||||
40
exports/chat_export_20260228_124720.json
Normal file
40
exports/chat_export_20260228_124720.json
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-02-28T12:47:20.562331",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-02-21T12:47:20.558312",
|
||||||
|
"to": "2026-02-28T12:47:20.562352"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 2,
|
||||||
|
"verified_users": 2,
|
||||||
|
"total_messages": 0
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": []
|
||||||
|
}
|
||||||
40
exports/chat_export_20260302_030000.json
Normal file
40
exports/chat_export_20260302_030000.json
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-03-02T03:00:00.039339",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-02-23T03:00:00.034574",
|
||||||
|
"to": "2026-03-02T03:00:00.039354"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 2,
|
||||||
|
"verified_users": 2,
|
||||||
|
"total_messages": 0
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": []
|
||||||
|
}
|
||||||
100
exports/chat_export_20260306_192022.json
Normal file
100
exports/chat_export_20260306_192022.json
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-03-06T19:20:22.868011",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-02-27T19:20:22.864014",
|
||||||
|
"to": "2026-03-06T19:20:22.868033"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 7,
|
||||||
|
"verified_users": 7,
|
||||||
|
"total_messages": 0
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": []
|
||||||
|
}
|
||||||
100
exports/chat_export_20260306_192032.json
Normal file
100
exports/chat_export_20260306_192032.json
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-03-06T19:20:32.719080",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-02-27T19:20:32.717289",
|
||||||
|
"to": "2026-03-06T19:20:32.719100"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 7,
|
||||||
|
"verified_users": 7,
|
||||||
|
"total_messages": 0
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": []
|
||||||
|
}
|
||||||
100
exports/chat_export_20260309_030000.json
Normal file
100
exports/chat_export_20260309_030000.json
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-03-09T03:00:00.032102",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-03-02T03:00:00.028836",
|
||||||
|
"to": "2026-03-09T03:00:00.032120"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 7,
|
||||||
|
"verified_users": 7,
|
||||||
|
"total_messages": 0
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "186",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": []
|
||||||
|
}
|
||||||
185
exports/chat_export_20260311_184038.json
Normal file
185
exports/chat_export_20260311_184038.json
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-03-11T18:40:38.970537",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-03-04T18:40:38.966350",
|
||||||
|
"to": "2026-03-11T18:40:38.970559"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 12,
|
||||||
|
"verified_users": 12,
|
||||||
|
"total_messages": 3
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 259925158,
|
||||||
|
"username": "Izuma_net",
|
||||||
|
"first_name": "izuma_net",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-11T10:34:58.022583",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 462572897,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алекс",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "48",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:30:06.663941",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 576801733,
|
||||||
|
"username": "VolkovaElena73",
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": "Волкова Ульяновск",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.293427",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1136316705,
|
||||||
|
"username": "IrinaTs15",
|
||||||
|
"first_name": "Irina",
|
||||||
|
"last_name": "Tsyganova",
|
||||||
|
"apartment": "319",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.292007",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1970561536,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Окся",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "255 КВ",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.289231",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "186",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"message_id": 6693,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "162 квартира, 5й подьезд - топят всех \n\nкто нибудь знает кто живет там в 2шке ?",
|
||||||
|
"timestamp": "2026-03-11T18:27:49.681833",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6690,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "Такая куча ссылок на какие то акты - да с номерами !\nФаина явно заинтересует вам что то из актов !",
|
||||||
|
"timestamp": "2026-03-11T17:16:35.705412",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6689,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Это отчет УО за 2025г.\nТатьяна Мих. расклеила",
|
||||||
|
"timestamp": "2026-03-11T17:06:13.912964",
|
||||||
|
"reply_to": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
206
exports/chat_export_20260316_182048.json
Normal file
206
exports/chat_export_20260316_182048.json
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-03-16T18:20:48.915055",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-03-09T18:20:48.911322",
|
||||||
|
"to": "2026-03-16T18:20:48.915072"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 12,
|
||||||
|
"verified_users": 12,
|
||||||
|
"total_messages": 6
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 259925158,
|
||||||
|
"username": "Izuma_net",
|
||||||
|
"first_name": "izuma_net",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-11T10:34:58.022583",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 462572897,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алекс",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "48",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:30:06.663941",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 576801733,
|
||||||
|
"username": "VolkovaElena73",
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": "Волкова Ульяновск",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.293427",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1136316705,
|
||||||
|
"username": "IrinaTs15",
|
||||||
|
"first_name": "Irina",
|
||||||
|
"last_name": "Tsyganova",
|
||||||
|
"apartment": "319",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.292007",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1970561536,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Окся",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "255 КВ",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.289231",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "205",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "186",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 5,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"message_id": 6699,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "Вот смеху то будет, если в апреле такая платежка прийдет ! 😃\n\nКак только придет первая платежка с тарифом выше 37,50 руб., подаем массовые требования о выплате штрафа 50% от суммы превышения на основании ч. 11 ст. 156 ЖК РФ\n\nНу, кто хочет платить по выдуманному тарифу может конечно при своем остаться, у нас же свобода воли полная (но не по мнению ЖСС 😁)",
|
||||||
|
"timestamp": "2026-03-12T17:04:16.699206",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6698,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "❔Кто нибудь в нашем доме понимает - каким таким образом ЖСС считает, что бумажка без подписи и печати на подьезде позволяет им навязать нам, собственникам, какой то выдуманный тариф с марта ? \n\nУже сколько они тарифов предлагали нам ? со счета сбился я😆\nВсе разные\nНи один не обоснован ничем\n\nИ вот - теперь вообще спустя рукава, бумажку из принтера повесили - вот вам новый тариф! \n\nЧто то нету таких вариантов в Жилищном Кодексе РФ, где четко написано, что этот вопрос решается только на Общем Соб",
|
||||||
|
"timestamp": "2026-03-12T17:02:51.184428",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6695,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "аварийку вызвали, перекрыли воду и уехали \nдверь никто не открывает, никого нету",
|
||||||
|
"timestamp": "2026-03-11T18:49:43.652398",
|
||||||
|
"reply_to": 6694
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6693,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "162 квартира, 5й подьезд - топят всех \n\nкто нибудь знает кто живет там в 2шке ?",
|
||||||
|
"timestamp": "2026-03-11T18:27:49.681833",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6690,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "Такая куча ссылок на какие то акты - да с номерами !\nФаина явно заинтересует вам что то из актов !",
|
||||||
|
"timestamp": "2026-03-11T17:16:35.705412",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6689,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Это отчет УО за 2025г.\nТатьяна Мих. расклеила",
|
||||||
|
"timestamp": "2026-03-11T17:06:13.912964",
|
||||||
|
"reply_to": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
725
exports/chat_export_20260322_230000.json
Normal file
725
exports/chat_export_20260322_230000.json
Normal file
|
|
@ -0,0 +1,725 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-03-22T23:00:00.045259",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-03-15T23:00:00.032902",
|
||||||
|
"to": "2026-03-22T23:00:00.045277"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 27,
|
||||||
|
"verified_users": 27,
|
||||||
|
"total_messages": 51
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 5183810334,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Леонид",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-21T21:27:30.494437",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5167606723,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Tatiyana",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "297",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-21T09:19:51.165950",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Ольга",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-20T11:47:26.400692",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5166131650,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Николай",
|
||||||
|
"last_name": "Петрович",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-20T11:35:37.951913",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 6344554911,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Наташа 🌹",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T12:26:06.709415",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 368569899,
|
||||||
|
"username": "Countesss",
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": "Лютерова",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T06:07:27.815681",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7339698511,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T05:28:20.549649",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7193750921,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T14:10:20.804365",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5246728667,
|
||||||
|
"username": "valeriya_nagini",
|
||||||
|
"first_name": "Валерия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.288584",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"username": "KhaykinaAnastasia",
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.284381",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 2139751123,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Евгений",
|
||||||
|
"last_name": "Петрович",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.265579",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1861008160,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.262810",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"username": "st5ksu",
|
||||||
|
"first_name": "Kseniya",
|
||||||
|
"last_name": "Stepanova",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.259098",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1432214116,
|
||||||
|
"username": "pro100beze",
|
||||||
|
"first_name": "Алёна",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.256800",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 861072224,
|
||||||
|
"username": "AFadeeva",
|
||||||
|
"first_name": "Александра🌤",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.228806",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 259925158,
|
||||||
|
"username": "Izuma_net",
|
||||||
|
"first_name": "izuma_net",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-11T10:34:58.022583",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 462572897,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алекс",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "48",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:30:06.663941",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 576801733,
|
||||||
|
"username": "VolkovaElena73",
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": "Волкова Ульяновск",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.293427",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1136316705,
|
||||||
|
"username": "IrinaTs15",
|
||||||
|
"first_name": "Irina",
|
||||||
|
"last_name": "Tsyganova",
|
||||||
|
"apartment": "319",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.292007",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1970561536,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Окся",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "255 КВ",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.289231",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "205",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "171",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "186",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 10,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 16,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"message_id": 6811,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-22T11:38:52.034326",
|
||||||
|
"reply_to": 6260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6810,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-22T11:38:52.025450",
|
||||||
|
"reply_to": 6260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6809,
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"text": "А вы наверное юморист?",
|
||||||
|
"timestamp": "2026-03-22T04:46:48.764565",
|
||||||
|
"reply_to": 6808
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6808,
|
||||||
|
"user_id": 5183810334,
|
||||||
|
"text": "Вы что уличный сторож?",
|
||||||
|
"timestamp": "2026-03-21T21:27:31.050046",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6807,
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-21T19:25:04.858395",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6806,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "А за уборкой подьезда и заменой лампочек в подьезде - видимо тоже куда то надо обращаться ? \n\nУ меня ощущение у одного, что ЖСС как бы и не при чем ? \n\nНу старается показать что не при чем 😅",
|
||||||
|
"timestamp": "2026-03-21T12:10:14.825495",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6805,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Уважаемые жители дома!\nКто в янв- февр- марте обращался за доступом к видеоархиву и получил его? А кто не получил?",
|
||||||
|
"timestamp": "2026-03-21T12:07:50.673653",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6804,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Я пишу: \"если с января...\" так как в чате жители писали, что не могут получить доступ к видео.\nИ в январе исполнитель с Видеосервис говорил, что с января 26г.доступ к архиву с разрешения Погодина.",
|
||||||
|
"timestamp": "2026-03-21T12:04:13.443501",
|
||||||
|
"reply_to": 6803
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6803,
|
||||||
|
"user_id": 462572897,
|
||||||
|
"text": "Фаина, а как понять, что с января 26 г. услуга не оказывается?",
|
||||||
|
"timestamp": "2026-03-21T11:50:50.112523",
|
||||||
|
"reply_to": 6799
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6802,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Там приложено Доп.согл.11 от 01.02.24г, которым наш дом присоединился",
|
||||||
|
"timestamp": "2026-03-21T11:00:57.340571",
|
||||||
|
"reply_to": 6798
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6801,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "да ПСД дома подпишет все, что ей ЖСС даст, не глядя и забыв про бухгалтерию 😀😂",
|
||||||
|
"timestamp": "2026-03-21T10:59:18.563614",
|
||||||
|
"reply_to": 6799
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6800,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "Какой же нормальный человек будет в магазине покупая диван, заключать договор не между собой и магазином - а подписывать какой то договор между Магазином и другим человеком ? \nОн потом как в суд пойдет если что случится ? \nВ суде пальцем у виска покрутят и скажут \"Это ж не ваш договор, чего вы на него ссылаетесь ? \"",
|
||||||
|
"timestamp": "2026-03-21T10:58:50.056137",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6799,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Если с января 26г услуга не оказывается, почему Акты ПСДома подписывает. \nЯ лично за янв- февр.за камеры не плачу.",
|
||||||
|
"timestamp": "2026-03-21T10:58:35.386487",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6798,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-21T10:57:54.609831",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6797,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Жители дома при инцидентах обращались к Т.Мих, так как договора никто ранее и не видел, кроме ПСдома.",
|
||||||
|
"timestamp": "2026-03-21T10:54:32.497882",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6796,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Вот договор, который нам дали на руки в УО ЖСС 27 февраля. В ГИС ЖКХ его нет.\nПо п.4.1.3. сказано, что запись из архива предоставляется по письменным запросам собственников МКД непосредственно в ООО \"ВидеоСервис\".\nПо п.2.2. записано, что Акты выполнкнеых работ подписывает Исполнитель, Заквзчик и ПСДома, на основании которых ежемесячно производится оплата.",
|
||||||
|
"timestamp": "2026-03-21T10:50:22.689484",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6794,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-21T10:40:53.162184",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6795,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-21T10:40:53.117680",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6793,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-21T10:40:53.080101",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6792,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-21T09:36:37.219173",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6791,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-21T07:47:26.209479",
|
||||||
|
"reply_to": 6260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6790,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-21T07:45:12.249018",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6787,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-20T14:14:46.161576",
|
||||||
|
"reply_to": 6260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6789,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-20T14:14:46.136436",
|
||||||
|
"reply_to": 6260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6786,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-20T14:14:46.113065",
|
||||||
|
"reply_to": 6260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6788,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-20T14:14:46.089582",
|
||||||
|
"reply_to": 6260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6785,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-20T14:14:46.056946",
|
||||||
|
"reply_to": 6260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6784,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "Вы о чём?",
|
||||||
|
"timestamp": "2026-03-20T11:47:26.526343",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6783,
|
||||||
|
"user_id": 5166131650,
|
||||||
|
"text": "Напротив 6 подъезда?",
|
||||||
|
"timestamp": "2026-03-20T11:37:13.711268",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6782,
|
||||||
|
"user_id": 5166131650,
|
||||||
|
"text": "Напротив 5 подъезда?",
|
||||||
|
"timestamp": "2026-03-20T11:35:38.550863",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6781,
|
||||||
|
"user_id": 6344554911,
|
||||||
|
"text": "Его Алексей зовут",
|
||||||
|
"timestamp": "2026-03-19T15:32:23.718845",
|
||||||
|
"reply_to": 6780
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6780,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "А как его зовут ? Может его в бота в раздел Мастера записать ?",
|
||||||
|
"timestamp": "2026-03-19T15:28:58.258357",
|
||||||
|
"reply_to": 6778
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6779,
|
||||||
|
"user_id": 6344554911,
|
||||||
|
"text": "Спасибо",
|
||||||
|
"timestamp": "2026-03-19T12:48:10.923728",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6778,
|
||||||
|
"user_id": 259925158,
|
||||||
|
"text": "Попробуйте этот 8 (996) 219-95-67",
|
||||||
|
"timestamp": "2026-03-19T12:45:37.451726",
|
||||||
|
"reply_to": 6777
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6777,
|
||||||
|
"user_id": 6344554911,
|
||||||
|
"text": "Здравствуйте. Кто знает телефон нашего сантехника, дайте пожалуйста",
|
||||||
|
"timestamp": "2026-03-19T12:26:07.174734",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6776,
|
||||||
|
"user_id": 7339698511,
|
||||||
|
"text": "Спасибо большое",
|
||||||
|
"timestamp": "2026-03-19T06:17:47.232121",
|
||||||
|
"reply_to": 6775
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6775,
|
||||||
|
"user_id": 368569899,
|
||||||
|
"text": "Елена, я вам написала в личные сообщения",
|
||||||
|
"timestamp": "2026-03-19T06:07:28.399172",
|
||||||
|
"reply_to": 6773
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6774,
|
||||||
|
"user_id": 7339698511,
|
||||||
|
"text": "Или напишите адреса куда можно обратиться по поводу камер и домофона, пожалуйста",
|
||||||
|
"timestamp": "2026-03-19T05:29:12.037940",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6773,
|
||||||
|
"user_id": 7339698511,
|
||||||
|
"text": "Люди!!! Дайте ссылки на домофон и на камеры???",
|
||||||
|
"timestamp": "2026-03-19T05:28:21.047935",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6772,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-17T17:59:33.059721",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6770,
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"text": "Через госуслуги попробуйте связаться",
|
||||||
|
"timestamp": "2026-03-17T11:44:29.175932",
|
||||||
|
"reply_to": 6769
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6767,
|
||||||
|
"user_id": 2139751123,
|
||||||
|
"text": "Отписка пять баллов 👍",
|
||||||
|
"timestamp": "2026-03-17T11:44:28.974797",
|
||||||
|
"reply_to": 6766
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6771,
|
||||||
|
"user_id": 5246728667,
|
||||||
|
"text": "Забрали уже.",
|
||||||
|
"timestamp": "2026-03-17T11:44:28.952646",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6766,
|
||||||
|
"user_id": 1861008160,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-17T11:44:28.923567",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6761,
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"text": "Понятно, спасибо",
|
||||||
|
"timestamp": "2026-03-17T11:44:28.903963",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6762,
|
||||||
|
"user_id": 576801733,
|
||||||
|
"text": "Он выдаёт мне на андроид опять vdome, а оно не пашет((((",
|
||||||
|
"timestamp": "2026-03-17T11:44:28.863145",
|
||||||
|
"reply_to": 6756
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6769,
|
||||||
|
"user_id": 1432214116,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-17T11:44:28.763231",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6763,
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"text": "Значит вам нужно на Киевский,они установят вам приложение",
|
||||||
|
"timestamp": "2026-03-17T11:44:28.742319",
|
||||||
|
"reply_to": 6762
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6764,
|
||||||
|
"user_id": 576801733,
|
||||||
|
"text": "Спасибки, завтра доеду)))",
|
||||||
|
"timestamp": "2026-03-17T11:44:28.723239",
|
||||||
|
"reply_to": 6763
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6768,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "Смешной штраф🙈",
|
||||||
|
"timestamp": "2026-03-17T11:44:28.638147",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6765,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-17T11:44:28.416187",
|
||||||
|
"reply_to": 6764
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
668
exports/chat_export_20260329_230000.json
Normal file
668
exports/chat_export_20260329_230000.json
Normal file
|
|
@ -0,0 +1,668 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-03-29T23:00:00.120135",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-03-22T23:00:00.118218",
|
||||||
|
"to": "2026-03-29T23:00:00.120151"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 32,
|
||||||
|
"verified_users": 27,
|
||||||
|
"total_messages": 33
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 1476737008,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": false,
|
||||||
|
"join_date": "2026-03-28T17:29:19.435202",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5036016769,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алсу",
|
||||||
|
"last_name": "Юсупова",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": false,
|
||||||
|
"join_date": "2026-03-27T16:54:14.815446",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 864147545,
|
||||||
|
"username": "roman_garipov",
|
||||||
|
"first_name": "Roman",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": false,
|
||||||
|
"join_date": "2026-03-25T16:59:41.548812",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 6654418281,
|
||||||
|
"username": "snez_frolova99",
|
||||||
|
"first_name": "Твоя ногтевая фея💅🏼",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": false,
|
||||||
|
"join_date": "2026-03-24T15:02:19.685046",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 2044370304,
|
||||||
|
"username": "Brasletvz73",
|
||||||
|
"first_name": "Браслет",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": false,
|
||||||
|
"join_date": "2026-03-24T14:50:37.799662",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5183810334,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Леонид",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-21T21:27:30.494437",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5167606723,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Tatiyana",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "297",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-21T09:19:51.165950",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Ольга",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-20T11:47:26.400692",
|
||||||
|
"message_count": 6,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5166131650,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Николай",
|
||||||
|
"last_name": "Петрович",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-20T11:35:37.951913",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 6344554911,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Наташа 🌹",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T12:26:06.709415",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 368569899,
|
||||||
|
"username": "Countesss",
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": "Лютерова",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T06:07:27.815681",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7339698511,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T05:28:20.549649",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7193750921,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T14:10:20.804365",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5246728667,
|
||||||
|
"username": "valeriya_nagini",
|
||||||
|
"first_name": "Валерия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.288584",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"username": "KhaykinaAnastasia",
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.284381",
|
||||||
|
"message_count": 5,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 2139751123,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Евгений",
|
||||||
|
"last_name": "Петрович",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.265579",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1861008160,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.262810",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"username": "st5ksu",
|
||||||
|
"first_name": "Kseniya",
|
||||||
|
"last_name": "Stepanova",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.259098",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1432214116,
|
||||||
|
"username": "pro100beze",
|
||||||
|
"first_name": "Алёна",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.256800",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 861072224,
|
||||||
|
"username": "AFadeeva",
|
||||||
|
"first_name": "Александра🌤",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.228806",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 259925158,
|
||||||
|
"username": "Izuma_net",
|
||||||
|
"first_name": "izuma_net",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-11T10:34:58.022583",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 462572897,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алекс",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "48",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:30:06.663941",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 576801733,
|
||||||
|
"username": "VolkovaElena73",
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": "Волкова Ульяновск",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.293427",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1136316705,
|
||||||
|
"username": "IrinaTs15",
|
||||||
|
"first_name": "Irina",
|
||||||
|
"last_name": "Tsyganova",
|
||||||
|
"apartment": "319",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.292007",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1970561536,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Окся",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "255 КВ",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.289231",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "205",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "171",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "186",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 10,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 21,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"message_id": 6845,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "https://ulpressa.ru/2026/03/28/upravdom-provajdery-v-mkd-2-dlya-kogo-zakony-pisany/",
|
||||||
|
"timestamp": "2026-03-29T18:21:13.686290",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6844,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-29T18:03:20.251284",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6843,
|
||||||
|
"user_id": 1476737008,
|
||||||
|
"text": "Спасибо большое)",
|
||||||
|
"timestamp": "2026-03-29T06:46:04.110315",
|
||||||
|
"reply_to": 6840
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6842,
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"text": ")))",
|
||||||
|
"timestamp": "2026-03-29T06:12:19.450560",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6841,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-29T06:10:46.673382",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6840,
|
||||||
|
"user_id": 368569899,
|
||||||
|
"text": "Добрый вечер. Мы пару раз делали в Лидере на 1 этаже, там где ключи делают.",
|
||||||
|
"timestamp": "2026-03-28T17:37:29.003831",
|
||||||
|
"reply_to": 6839
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6839,
|
||||||
|
"user_id": 1476737008,
|
||||||
|
"text": "Добрый вечер.Соседи,подскажите пожалуйста где сделать ключ от домофона",
|
||||||
|
"timestamp": "2026-03-28T17:29:19.880276",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6838,
|
||||||
|
"user_id": 5036016769,
|
||||||
|
"text": "Где же вы все были когда снег там лежал",
|
||||||
|
"timestamp": "2026-03-27T16:54:45.860697",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6836,
|
||||||
|
"user_id": 5036016769,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-27T16:54:15.376935",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6835,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-27T15:01:39.815229",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6834,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "Да да\nИнета нет\nА платим исправно....",
|
||||||
|
"timestamp": "2026-03-26T08:32:56.795527",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6833,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-26T08:25:52.691712",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6832,
|
||||||
|
"user_id": 864147545,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-25T17:59:17.637774",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6831,
|
||||||
|
"user_id": 864147545,
|
||||||
|
"text": "У718АС 73 хендай элантра не нужен о заезжать на «газон»(в нашем случае грязь)",
|
||||||
|
"timestamp": "2026-03-25T16:59:42.057708",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6830,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "Добрый вечер\nУ всех сегодня билайн домашний инет не работал?\n\nА теперь всё с ограничениями?",
|
||||||
|
"timestamp": "2026-03-25T15:47:14.973788",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6829,
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-25T14:24:10.506225",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6828,
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"text": "Ой как здорово",
|
||||||
|
"timestamp": "2026-03-25T09:37:18.521901",
|
||||||
|
"reply_to": 6825
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6827,
|
||||||
|
"user_id": 7193750921,
|
||||||
|
"text": "Цены старые, конечно.",
|
||||||
|
"timestamp": "2026-03-25T09:25:22.994905",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6826,
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"text": "300 р",
|
||||||
|
"timestamp": "2026-03-25T09:25:22.294027",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6825,
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"text": "Делаешь им фотку счетчика они присылают акт проверки моментально",
|
||||||
|
"timestamp": "2026-03-25T09:25:12.232124",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6824,
|
||||||
|
"user_id": 7193750921,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-25T09:24:33.141258",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6823,
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"text": "Большое спасибо!",
|
||||||
|
"timestamp": "2026-03-25T09:24:23.524322",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6822,
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"text": "Мне вот они понравились )",
|
||||||
|
"timestamp": "2026-03-25T09:24:06.325785",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6821,
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"text": "+7 927 270-21-70 добрый день",
|
||||||
|
"timestamp": "2026-03-25T09:23:59.518450",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6820,
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"text": "Добрый день! Подскажите пожалуйста, кто-нибудь знает номер телефона организации по поверке счетчиков",
|
||||||
|
"timestamp": "2026-03-25T09:22:49.504050",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6819,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-25T08:25:50.145257",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6818,
|
||||||
|
"user_id": 2044370304,
|
||||||
|
"text": "Как в их чат написать ?",
|
||||||
|
"timestamp": "2026-03-24T15:15:45.859199",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6817,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "Пишите в чат 5 дома. Может там хозяин",
|
||||||
|
"timestamp": "2026-03-24T15:05:27.186789",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6816,
|
||||||
|
"user_id": 6654418281,
|
||||||
|
"text": "Эвакуатор может вызвать ?",
|
||||||
|
"timestamp": "2026-03-24T15:02:20.242206",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6815,
|
||||||
|
"user_id": 2044370304,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-24T14:53:40.014838",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6814,
|
||||||
|
"user_id": 2044370304,
|
||||||
|
"text": "Товарищи, кто знает хозяина этой машины ?",
|
||||||
|
"timestamp": "2026-03-24T14:53:36.062481",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6813,
|
||||||
|
"user_id": 2044370304,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-24T14:50:38.299476",
|
||||||
|
"reply_to": 6266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6812,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-03-23T10:04:38.017104",
|
||||||
|
"reply_to": 6262
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
623
exports/chat_export_20260405_230000.json
Normal file
623
exports/chat_export_20260405_230000.json
Normal file
|
|
@ -0,0 +1,623 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-04-05T23:00:00.045504",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-03-29T23:00:00.035069",
|
||||||
|
"to": "2026-04-05T23:00:00.045519"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 33,
|
||||||
|
"verified_users": 33,
|
||||||
|
"total_messages": 24
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 772234481,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-04-05T14:01:56.745028",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1476737008,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-28T17:29:19.435202",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5036016769,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алсу",
|
||||||
|
"last_name": "Юсупова",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-27T16:54:14.815446",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 864147545,
|
||||||
|
"username": "roman_garipov",
|
||||||
|
"first_name": "Roman",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-25T16:59:41.548812",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 6654418281,
|
||||||
|
"username": "snez_frolova99",
|
||||||
|
"first_name": "Твоя ногтевая фея💅🏼",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-24T15:02:19.685046",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 2044370304,
|
||||||
|
"username": "Brasletvz73",
|
||||||
|
"first_name": "Браслет",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-24T14:50:37.799662",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5183810334,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Леонид",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-21T21:27:30.494437",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5167606723,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Tatiyana",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "297",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-21T09:19:51.165950",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Ольга",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-20T11:47:26.400692",
|
||||||
|
"message_count": 10,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5166131650,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Николай",
|
||||||
|
"last_name": "Петрович",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-20T11:35:37.951913",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 6344554911,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Наташа 🌹",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T12:26:06.709415",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 368569899,
|
||||||
|
"username": "Countesss",
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": "Лютерова",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T06:07:27.815681",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7339698511,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T05:28:20.549649",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7193750921,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T14:10:20.804365",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5246728667,
|
||||||
|
"username": "valeriya_nagini",
|
||||||
|
"first_name": "Валерия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.288584",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"username": "KhaykinaAnastasia",
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.284381",
|
||||||
|
"message_count": 5,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 2139751123,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Евгений",
|
||||||
|
"last_name": "Петрович",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.265579",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1861008160,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.262810",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"username": "st5ksu",
|
||||||
|
"first_name": "Kseniya",
|
||||||
|
"last_name": "Stepanova",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.259098",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1432214116,
|
||||||
|
"username": "pro100beze",
|
||||||
|
"first_name": "Алёна",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.256800",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 861072224,
|
||||||
|
"username": "AFadeeva",
|
||||||
|
"first_name": "Александра🌤",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.228806",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 259925158,
|
||||||
|
"username": "Izuma_net",
|
||||||
|
"first_name": "izuma_net",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-11T10:34:58.022583",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 462572897,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алекс",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "48",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:30:06.663941",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 576801733,
|
||||||
|
"username": "VolkovaElena73",
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": "Волкова Ульяновск",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.293427",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1136316705,
|
||||||
|
"username": "IrinaTs15",
|
||||||
|
"first_name": "Irina",
|
||||||
|
"last_name": "Tsyganova",
|
||||||
|
"apartment": "319",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.292007",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1970561536,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Окся",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "255 КВ",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.289231",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "205",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "171",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "186",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 10,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 30,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"message_id": 6886,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "🔥🔥🔥",
|
||||||
|
"timestamp": "2026-04-05T16:32:19.532647",
|
||||||
|
"reply_to": 6885
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6885,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "🔥Для жильцов нашего дома поясню\nНа прошлой неделе, благодаря инициативной группе, я побывал в отделении ОБЭП\nКоторое начало расследовать \"А что тут происходит в доме вообще?\"\nВсе эти таинственные пропадающие договора, все эти документы которых нигде нет, вся эта кутерьма с видеокамерами, вся эта ерунда с оплатой домофона самовольной по метражу, по обрезкам, по ремонтам, по уборке тракторами и метлами. \n\nУ нас за года СТОЛЬКО претензий к УО ЖСС накопилось!\n\nИ вот как то на встречу они совершенно ",
|
||||||
|
"timestamp": "2026-04-05T16:13:58.099823",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6884,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "а вот и первое сообщение о платежке в почтовом ящике - и там тариф, который не мы с вами НЕ ВЫБИРАЛИ",
|
||||||
|
"timestamp": "2026-04-05T16:13:55.637380",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6883,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "💯",
|
||||||
|
"timestamp": "2026-04-05T15:40:06.767521",
|
||||||
|
"reply_to": 6879
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6882,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "Или тоже, как с камерами. Все не при чем, молиться в сторону ульяновского 2, и только по троекратному письменному разрешению неизвестно кого ? 🤣",
|
||||||
|
"timestamp": "2026-04-05T15:33:54.688085",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6881,
|
||||||
|
"user_id": 576801733,
|
||||||
|
"text": "Уважаемые соседи, можно вопрос не про мусор, видео кто нибудь оплачивает? У меня в этом году какой то просто адский тариф и за 2 месяца 900 с лишним рублей((( подскажите плиз тариф у нас какой? А Домофон у кого нибудь вообще работает не с ключа?",
|
||||||
|
"timestamp": "2026-04-05T15:33:40.638091",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6880,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "А у нас разве вывоз такого мусора - никак не регламентирован ? \nВот просто не верю, по простой причине - оплата то у нас за это идет не абы как, а регулярная и постоянная.\nТак что и график там должен быть регулярный и постоянный",
|
||||||
|
"timestamp": "2026-04-05T15:33:08.750641",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6879,
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"text": "Потому что тот кто определяет эту необходимость делает это так как считает нужным именно он (она)",
|
||||||
|
"timestamp": "2026-04-05T15:31:32.428514",
|
||||||
|
"reply_to": 6878
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6878,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "Это же и про подъезды можно сказать... Увозили бы раз в неделю и разговоров бы не было\n\nКак и с уборкой подъездов и двора.\nФормулировка - по необходимости - приводит к грязи..и хаосу...",
|
||||||
|
"timestamp": "2026-04-05T15:26:34.820124",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6877,
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"text": "Если бы увозили с такой периодичностью, чтобы не образовывалась СВАЛКА у подъезда, никто бы, я думаю, не возмущался",
|
||||||
|
"timestamp": "2026-04-05T15:24:23.779266",
|
||||||
|
"reply_to": 6875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6876,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-05T15:05:16.857463",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6875,
|
||||||
|
"user_id": 772234481,
|
||||||
|
"text": "У 7,8,9 подъезда есть места(выход к бакам)куда можно выкидывать крупногабаритный мусор,туда уборочные машины почему-то приезжают чаще и забирают,но опять таки,когда жильцы относили мусор туда,другие жильцы из этих подъездов ругались, и высказывали своё негодование,мол зачем весь мусор относят к их подъезду",
|
||||||
|
"timestamp": "2026-04-05T14:53:26.450162",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6874,
|
||||||
|
"user_id": 772234481,
|
||||||
|
"text": "Про бытовой мусор с кухни согласна,это дикость",
|
||||||
|
"timestamp": "2026-04-05T14:46:27.710436",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6873,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "у нас внутри двора рекреации под это имеются\nи это отродясь не вызывало проблем никому и никогда \n\nно вот с появлением новых жильцов такое бывает, например у 5го подьезда это тупо бытовой мусор с кухни! \nнадо быть очень диким и далеким от цивилизации, чтобы выбрасывать такой мусор - под дверь подьезда",
|
||||||
|
"timestamp": "2026-04-05T14:34:07.586221",
|
||||||
|
"reply_to": 6872
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6872,
|
||||||
|
"user_id": 772234481,
|
||||||
|
"text": "Не всем легко эти большие баулы с мусором относить за дом,потому что они тяжелые.Я не понимаю постоянные претензии по поводу этого.(К мешкам я не причастна,но эта тема из раза в раз оговаривается в чате,зачем-не знаю,мне кажется и так все всё видят и понимают,не могу сказать точно когда,но раз в неделю приезжает машина и забирает такой мусор,можно к этому и по спокойнее относиться)\nПусть тогда все салоны/магазины/пвз и тд со стороны проспекта,куда нужно относить мусор по вашему, закроют и уберут",
|
||||||
|
"timestamp": "2026-04-05T14:01:57.183265",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6871,
|
||||||
|
"user_id": 576801733,
|
||||||
|
"text": "Крыши... т9 зараза",
|
||||||
|
"timestamp": "2026-04-05T13:44:46.797929",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6870,
|
||||||
|
"user_id": 576801733,
|
||||||
|
"text": "Искренне верим что завтра вывезут... я так и не поняла зачем они сложили мешки и пропали.... с кры я имею ввиду",
|
||||||
|
"timestamp": "2026-04-05T13:44:27.580631",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6869,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "А как дела обстоят в ваших подъездах? \nЕсть такие \"дремучие\", которые никак не поймут, что с мусором делать? \n\nМожет у кого то из окна пакеты выбрасывают ? 😁",
|
||||||
|
"timestamp": "2026-04-05T13:38:59.369432",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6866,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-05T13:38:06.107622",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6867,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-05T13:38:06.092502",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6868,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-05T13:38:06.074054",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6855,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "К старшей и в ук \nНаверное \nСейчас концов не найдешь",
|
||||||
|
"timestamp": "2026-04-05T11:51:00.903741",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6863,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-05T11:51:00.877190",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6864,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-05T11:51:00.860105",
|
||||||
|
"reply_to": 6262
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
794
exports/chat_export_20260412_230000.json
Normal file
794
exports/chat_export_20260412_230000.json
Normal file
|
|
@ -0,0 +1,794 @@
|
||||||
|
{
|
||||||
|
"export_date": "2026-04-12T23:00:00.070866",
|
||||||
|
"chat_id": null,
|
||||||
|
"period": {
|
||||||
|
"from": "2026-04-05T23:00:00.063081",
|
||||||
|
"to": "2026-04-12T23:00:00.070885"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"total_users": 39,
|
||||||
|
"verified_users": 39,
|
||||||
|
"total_messages": 36
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": 7702466390,
|
||||||
|
"username": "Ulik8319",
|
||||||
|
"first_name": "Юлия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "287",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-04-12T16:31:55.828411",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5106504485,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Ирина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-04-12T10:36:28.448884",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1353946650,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "ГеннадийП",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-04-11T12:03:02.546979",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7346013939,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Любовь",
|
||||||
|
"last_name": "Землянская",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-04-11T08:03:52.447038",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 458413170,
|
||||||
|
"username": "MeLnikov_Alesha",
|
||||||
|
"first_name": "Алексей",
|
||||||
|
"last_name": "Мельников",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-04-09T14:09:47.597586",
|
||||||
|
"message_count": 5,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5235580033,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-04-07T15:59:11.741889",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 772234481,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-04-05T14:01:56.745028",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1476737008,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-28T17:29:19.435202",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5036016769,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алсу",
|
||||||
|
"last_name": "Юсупова",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-27T16:54:14.815446",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 864147545,
|
||||||
|
"username": "roman_garipov",
|
||||||
|
"first_name": "Roman",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-25T16:59:41.548812",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 6654418281,
|
||||||
|
"username": "snez_frolova99",
|
||||||
|
"first_name": "Твоя ногтевая фея💅🏼",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-24T15:02:19.685046",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 2044370304,
|
||||||
|
"username": "Brasletvz73",
|
||||||
|
"first_name": "Браслет",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-24T14:50:37.799662",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5183810334,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Леонид",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-21T21:27:30.494437",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5167606723,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Tatiyana",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "297",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-21T09:19:51.165950",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Ольга",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-20T11:47:26.400692",
|
||||||
|
"message_count": 12,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5166131650,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Николай",
|
||||||
|
"last_name": "Петрович",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-20T11:35:37.951913",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 6344554911,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Наташа 🌹",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T12:26:06.709415",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 368569899,
|
||||||
|
"username": "Countesss",
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": "Лютерова",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T06:07:27.815681",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7339698511,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-19T05:28:20.549649",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 7193750921,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T14:10:20.804365",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5246728667,
|
||||||
|
"username": "valeriya_nagini",
|
||||||
|
"first_name": "Валерия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.288584",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"username": "KhaykinaAnastasia",
|
||||||
|
"first_name": "Анастасия",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.284381",
|
||||||
|
"message_count": 6,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 2139751123,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Евгений",
|
||||||
|
"last_name": "Петрович",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.265579",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1861008160,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.262810",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"username": "st5ksu",
|
||||||
|
"first_name": "Kseniya",
|
||||||
|
"last_name": "Stepanova",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.259098",
|
||||||
|
"message_count": 5,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1432214116,
|
||||||
|
"username": "pro100beze",
|
||||||
|
"first_name": "Алёна",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.256800",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 861072224,
|
||||||
|
"username": "AFadeeva",
|
||||||
|
"first_name": "Александра🌤",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-17T11:44:28.228806",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 259925158,
|
||||||
|
"username": "Izuma_net",
|
||||||
|
"first_name": "izuma_net",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "295",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-11T10:34:58.022583",
|
||||||
|
"message_count": 2,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 462572897,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Алекс",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "48",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:30:06.663941",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 576801733,
|
||||||
|
"username": "VolkovaElena73",
|
||||||
|
"first_name": "Елена",
|
||||||
|
"last_name": "Волкова Ульяновск",
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.293427",
|
||||||
|
"message_count": 5,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1136316705,
|
||||||
|
"username": "IrinaTs15",
|
||||||
|
"first_name": "Irina",
|
||||||
|
"last_name": "Tsyganova",
|
||||||
|
"apartment": "319",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.292007",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1970561536,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Окся",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "255 КВ",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-10T16:06:40.289231",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1299551170,
|
||||||
|
"username": "katt_rin9",
|
||||||
|
"first_name": "Екатерина🐈",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "205",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.232963",
|
||||||
|
"message_count": 1,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "O4aPoBaHHblu_cTpaHHuK",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.231023",
|
||||||
|
"message_count": 3,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 1867403697,
|
||||||
|
"username": "cvetok_nura",
|
||||||
|
"first_name": "Светлана",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "171",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.228965",
|
||||||
|
"message_count": 4,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Фаина",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": "186",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.226876",
|
||||||
|
"message_count": 14,
|
||||||
|
"spy_score": 25,
|
||||||
|
"spy_flags": [
|
||||||
|
"no_avatar",
|
||||||
|
"generic_username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 8725618164,
|
||||||
|
"username": "lkmthreeseven_bot",
|
||||||
|
"first_name": "DomovoyKontrolBot",
|
||||||
|
"last_name": null,
|
||||||
|
"apartment": null,
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-03-05T03:59:57.221608",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 5475262081,
|
||||||
|
"username": null,
|
||||||
|
"first_name": "Надежда",
|
||||||
|
"last_name": "Тырина",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T16:46:55.844916",
|
||||||
|
"message_count": 0,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": 197957361,
|
||||||
|
"username": "ktybdsq",
|
||||||
|
"first_name": "Александр",
|
||||||
|
"last_name": "MatrixHasYou",
|
||||||
|
"apartment": "152",
|
||||||
|
"verified": true,
|
||||||
|
"join_date": "2026-02-23T13:28:08.090899",
|
||||||
|
"message_count": 35,
|
||||||
|
"spy_score": 0,
|
||||||
|
"spy_flags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"message_id": 6929,
|
||||||
|
"user_id": 259925158,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T17:50:38.219022",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6928,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "Погоды не позволяют пока что беседы неспешные во дворе проводить \nНо вот за неделю с несколькими жильцами я тут повстречался, минут по сорок-часу побеседовали\n\nГлаза квадратные у людей от происходящего, шашками хотят махать, писать и прочее\n\nВот уж где инициативная группа поработает весною теплою ! Вот где люди прозреют",
|
||||||
|
"timestamp": "2026-04-12T15:14:13.060243",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6927,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": "Спасибо Александр",
|
||||||
|
"timestamp": "2026-04-12T15:12:27.802314",
|
||||||
|
"reply_to": 6926
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6926,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "📌 Мы тут с инициативными жильцами обсуждали буквально на днях, что пора бы подавать в суд, иском прям, на нашу \"любимую\" управляющую организацию УО ЖСС, и ее \"опытного бухгалтера\" по совместительству председателя текущего нашего дома - гражданку Ерееву Т.М. \nИ знаете, таки иск в суд отправлен ! \nНу а раз формальные правила требуют от нас отправки копии иска - в адрес Ответчика (УО ЖСС), то им оно ушло первым делом. \n\nТеперь в понедельник и УО ЖСС, и суд заволжский, получат иски\nГде черным по бел",
|
||||||
|
"timestamp": "2026-04-12T14:20:35.418521",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6925,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T14:14:26.193642",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6924,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T14:14:15.163391",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6923,
|
||||||
|
"user_id": 197957361,
|
||||||
|
"text": "Не пойму никак, яйца то при чем - вроде день Гагарина сегодня ? не ? \nВпрочем это лирика ! \nА что у нас по гамбургскому счету ?",
|
||||||
|
"timestamp": "2026-04-12T14:13:52.405574",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6922,
|
||||||
|
"user_id": 462572897,
|
||||||
|
"text": "Не воспитанные, родители взрастили вседозволенность. Хоть праздник, хоть что ... Дикость.",
|
||||||
|
"timestamp": "2026-04-12T14:05:15.606018",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6918,
|
||||||
|
"user_id": 5106504485,
|
||||||
|
"text": "Это они не ели,а раскидали на три этажа",
|
||||||
|
"timestamp": "2026-04-12T10:39:51.117025",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6917,
|
||||||
|
"user_id": 1970561536,
|
||||||
|
"text": "Детки голодные,ели сразу",
|
||||||
|
"timestamp": "2026-04-12T10:38:08.866439",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6916,
|
||||||
|
"user_id": 5106504485,
|
||||||
|
"text": "1подъезд🫣",
|
||||||
|
"timestamp": "2026-04-12T10:36:53.349640",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6915,
|
||||||
|
"user_id": 5106504485,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T10:36:50.775138",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6914,
|
||||||
|
"user_id": 5106504485,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T10:36:28.845060",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6913,
|
||||||
|
"user_id": 1670557872,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T08:04:05.021389",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6912,
|
||||||
|
"user_id": 576801733,
|
||||||
|
"text": "ВОИСТИНУ ВОСКРЕСЕ!!!!!🙏🙏💐🌷🌹",
|
||||||
|
"timestamp": "2026-04-12T07:41:00.515836",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6911,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "🙏🙏🌞💐",
|
||||||
|
"timestamp": "2026-04-12T07:16:38.400835",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6911,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "🙏🙏🌞💐",
|
||||||
|
"timestamp": "2026-04-12T07:16:38.356136",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6910,
|
||||||
|
"user_id": 5183810334,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T06:57:10.820177",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6909,
|
||||||
|
"user_id": 1667052403,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T06:49:15.228600",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6908,
|
||||||
|
"user_id": 2139751123,
|
||||||
|
"text": "ВО ИСТИНУ ВОСКРЕСЕ!!!\nХРИСТОС ВОСКРЕСЕ!!!\nХРИСТОС ВОСКРЕСЕ!!!\nХРИСТОС ВОСКРЕСЕ!!!",
|
||||||
|
"timestamp": "2026-04-12T06:32:17.398539",
|
||||||
|
"reply_to": 6907
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6907,
|
||||||
|
"user_id": 5183810334,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-12T04:35:40.077555",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6906,
|
||||||
|
"user_id": 1353946650,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-11T12:03:02.956710",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6905,
|
||||||
|
"user_id": 7346013939,
|
||||||
|
"text": "Добрый день! Как наши успехи, деньги собрали на пошлину, теперь проблема с телегой. Информации мало.",
|
||||||
|
"timestamp": "2026-04-11T08:03:52.949041",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6904,
|
||||||
|
"user_id": 1335945859,
|
||||||
|
"text": "Раньше я боялся темноты, но вчера принесли квитанции. Теперь я боюсь света, воды и тепла. И еще немного — мусора.",
|
||||||
|
"timestamp": "2026-04-10T15:16:15.029712",
|
||||||
|
"reply_to": 6262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6903,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "В этой платежке указано что платеж за кап.ремонт за март 26г. (при цене 15,05 руб за кв.м и площади квартиры 66,8 м.кв). \nОплата проводится ежемесячно через РИЦ на спецсчет дома.\nВсе, больше ничего по кап.ремонту платить не нужно.\n\nНикаких прямых платежей в фонд кап.ремонта не должно быть. Счет на 4820 руб - возможно от мошеников, будьте аккуратнее, никаких действий не производите. \n\nОбратитесь в банк, от кого выставлен счет, там выявят, если от мошеников.",
|
||||||
|
"timestamp": "2026-04-09T19:32:58.749051",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6902,
|
||||||
|
"user_id": 8181748817,
|
||||||
|
"text": "В этой платежке указано что платеж за кап.ремонт за март 26г. (при цене 15,05 руб за кв.м и площади квартиры 66,8 м.кв). \nОплата проводится ежемесячно через РИЦ на спецсчет дома.\nВсе, больше ничего по кап.ремонту платить не нужно.\n\nНикаких прямых платежей в фонд кап.ремонта не должно быть. Счет на 4820 руб - возможно от мошеников, будьте аккуратнее, никаких действий не производите. \n\nОбратитесь в банк, от кого выставлен счет, там выявят, если от мошеников.",
|
||||||
|
"timestamp": "2026-04-09T19:27:30.222466",
|
||||||
|
"reply_to": 1093
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6901,
|
||||||
|
"user_id": 458413170,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-09T14:55:33.063509",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6900,
|
||||||
|
"user_id": 458413170,
|
||||||
|
"text": "Я так понимаю, оплачиваем все",
|
||||||
|
"timestamp": "2026-04-09T14:55:28.518224",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6899,
|
||||||
|
"user_id": 458413170,
|
||||||
|
"text": "Так за капремонт разве мы не оплачиваем ежемесячно в РИЦ?",
|
||||||
|
"timestamp": "2026-04-09T14:20:25.906081",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6898,
|
||||||
|
"user_id": 462572897,
|
||||||
|
"text": "Возможно это капремонт. За несколько месяцев, если не платили ...",
|
||||||
|
"timestamp": "2026-04-09T14:14:34.659087",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6897,
|
||||||
|
"user_id": 458413170,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-09T14:10:04.284659",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6896,
|
||||||
|
"user_id": 458413170,
|
||||||
|
"text": null,
|
||||||
|
"timestamp": "2026-04-09T14:10:04.260985",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6895,
|
||||||
|
"user_id": 458413170,
|
||||||
|
"text": "Добрый день. Пришел счёт в приложение банка. Скажите, это что вообще",
|
||||||
|
"timestamp": "2026-04-09T14:09:47.993980",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6894,
|
||||||
|
"user_id": 5246728667,
|
||||||
|
"text": "Здравствуйте, а в чем проблема вторую ночь подряд нет напора холодной воды? Днем всё хорошо, а ночью холодная вода еле еле течёт.",
|
||||||
|
"timestamp": "2026-04-07T21:13:03.405086",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6893,
|
||||||
|
"user_id": 5235580033,
|
||||||
|
"text": "Щенка никто не терял 6 подъезд, предположительно хаски",
|
||||||
|
"timestamp": "2026-04-07T15:59:12.269686",
|
||||||
|
"reply_to": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": 6892,
|
||||||
|
"user_id": 1179832004,
|
||||||
|
"text": "Доброе утро,9 подъезд 3-ка нет воды,что случилось?",
|
||||||
|
"timestamp": "2026-04-06T05:21:23.714970",
|
||||||
|
"reply_to": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1474
exports/chat_history_2026-04-18_22-31.json
Normal file
1474
exports/chat_history_2026-04-18_22-31.json
Normal file
File diff suppressed because it is too large
Load diff
1474
exports/chat_history_2026-04-18_23-21.json
Normal file
1474
exports/chat_history_2026-04-18_23-21.json
Normal file
File diff suppressed because it is too large
Load diff
9
main.py
9
main.py
|
|
@ -60,18 +60,23 @@ logging.basicConfig(
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import os
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
# МАРКЕР ЗАПУСКА БОТА
|
||||||
|
logger.info(f"!!! [STARTUP] main.py execution started. PID: {os.getpid()}")
|
||||||
|
|
||||||
async def on_startup(bot: Bot):
|
async def on_startup(bot: Bot):
|
||||||
"""Действия при запуске"""
|
"""Действия при запуске"""
|
||||||
|
logger.info(f"!!! [STARTUP] on_startup triggered. PID: {os.getpid()}")
|
||||||
logger.info('🚀 LKM37 CORE v4.0 запускается...')
|
logger.info('🚀 LKM37 CORE v4.0 запускается...')
|
||||||
await init_db()
|
await init_db()
|
||||||
|
|
||||||
scheduler = Scheduler(bot)
|
scheduler = Scheduler(bot)
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
|
|
||||||
web_server = WebServerService(port=8000)
|
# Веб-сервер теперь запускается отдельным сервисом (domovoy-web.service)
|
||||||
web_server.start()
|
# web_server = WebServerService(port=8000)
|
||||||
|
# web_server.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from sqlalchemy import select, func, distinct
|
from sqlalchemy import select, func, distinct
|
||||||
|
|
@ -21,9 +21,10 @@ class ChatAnalytics:
|
||||||
self.session = session
|
self.session = session
|
||||||
self.export_dir = Path(EXPORT_DIR)
|
self.export_dir = Path(EXPORT_DIR)
|
||||||
self.export_dir.mkdir(parents=True, exist_ok=True)
|
self.export_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.uly_tz = timezone(timedelta(hours=4))
|
||||||
|
|
||||||
async def get_general_stats(self) -> Dict[str, Any]:
|
async def get_general_stats(self) -> Dict[str, Any]:
|
||||||
"""Общая статистика чата"""
|
"""Общая статистика чата (Оригинальная логика из Gitea)"""
|
||||||
# Всего пользователей
|
# Всего пользователей
|
||||||
stmt = select(func.count(User.user_id))
|
stmt = select(func.count(User.user_id))
|
||||||
result = await self.session.execute(stmt)
|
result = await self.session.execute(stmt)
|
||||||
|
|
@ -41,56 +42,34 @@ class ChatAnalytics:
|
||||||
|
|
||||||
# Сообщения за неделю
|
# Сообщения за неделю
|
||||||
week_ago = datetime.utcnow() - timedelta(days=7)
|
week_ago = datetime.utcnow() - timedelta(days=7)
|
||||||
stmt = (
|
stmt = select(func.count(Message.id)).where(Message.timestamp >= week_ago)
|
||||||
select(func.count(Message.id))
|
|
||||||
.where(Message.timestamp >= week_ago)
|
|
||||||
)
|
|
||||||
result = await self.session.execute(stmt)
|
result = await self.session.execute(stmt)
|
||||||
messages_week = result.scalar() or 0
|
messages_week = result.scalar() or 0
|
||||||
|
|
||||||
# Сообщения за день
|
# Сообщения за день
|
||||||
day_ago = datetime.utcnow() - timedelta(days=1)
|
day_ago = datetime.utcnow() - timedelta(days=1)
|
||||||
stmt = (
|
stmt = select(func.count(Message.id)).where(Message.timestamp >= day_ago)
|
||||||
select(func.count(Message.id))
|
|
||||||
.where(Message.timestamp >= day_ago)
|
|
||||||
)
|
|
||||||
result = await self.session.execute(stmt)
|
result = await self.session.execute(stmt)
|
||||||
messages_day = result.scalar() or 0
|
messages_day = result.scalar() or 0
|
||||||
|
|
||||||
# Подозрительные
|
# Подозрительные и шпионы
|
||||||
stmt = (
|
stmt_susp = select(func.count(User.user_id)).where(User.spy_score >= 31).where(User.spy_score <= 60)
|
||||||
select(func.count(User.user_id))
|
suspicious_count = (await self.session.execute(stmt_susp)).scalar() or 0
|
||||||
.where(User.spy_score >= 31)
|
|
||||||
.where(User.spy_score <= 60)
|
|
||||||
)
|
|
||||||
result = await self.session.execute(stmt)
|
|
||||||
suspicious_count = result.scalar() or 0
|
|
||||||
|
|
||||||
# Шпионы
|
stmt_spies = select(func.count(User.user_id)).where(User.spy_score > 60)
|
||||||
stmt = (
|
spies_count = (await self.session.execute(stmt_spies)).scalar() or 0
|
||||||
select(func.count(User.user_id))
|
|
||||||
.where(User.spy_score > 60)
|
|
||||||
)
|
|
||||||
result = await self.session.execute(stmt)
|
|
||||||
spies_count = result.scalar() or 0
|
|
||||||
|
|
||||||
# Топ пользователей за неделю
|
# Топ пользователей за неделю
|
||||||
top_users = await self._get_top_users(week_ago, limit=5)
|
top_users = await self._get_top_users(week_ago, limit=5)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'total_users': total_users,
|
'total_users': total_users, 'verified_users': verified_users,
|
||||||
'verified_users': verified_users,
|
'unverified_users': total_users - verified_users, 'total_messages': total_messages,
|
||||||
'unverified_users': total_users - verified_users,
|
'messages_week': messages_week, 'messages_day': messages_day,
|
||||||
'total_messages': total_messages,
|
'suspicious_count': suspicious_count, 'spies_count': spies_count, 'top_users': top_users,
|
||||||
'messages_week': messages_week,
|
|
||||||
'messages_day': messages_day,
|
|
||||||
'suspicious_count': suspicious_count,
|
|
||||||
'spies_count': spies_count,
|
|
||||||
'top_users': top_users,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _get_top_users(self, since: datetime, limit: int = 5) -> str:
|
async def _get_top_users(self, since: datetime, limit: int = 5) -> str:
|
||||||
"""Топ пользователей по активности"""
|
|
||||||
stmt = (
|
stmt = (
|
||||||
select(User.user_id, User.first_name, func.count(Message.id).label('count'))
|
select(User.user_id, User.first_name, func.count(Message.id).label('count'))
|
||||||
.join(Message, User.user_id == Message.user_id)
|
.join(Message, User.user_id == Message.user_id)
|
||||||
|
|
@ -101,117 +80,49 @@ class ChatAnalytics:
|
||||||
)
|
)
|
||||||
result = await self.session.execute(stmt)
|
result = await self.session.execute(stmt)
|
||||||
rows = result.all()
|
rows = result.all()
|
||||||
|
if not rows: return 'нет данных'
|
||||||
|
return ', '.join([f'{i}. {r[1] or f"user{r[0]}"}: {r[2]}' for i, r in enumerate(rows, 1)])
|
||||||
|
|
||||||
if not rows:
|
async def export_to_json(self, full_history: bool = False) -> str:
|
||||||
return 'нет данных'
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
for i, (user_id, name, count) in enumerate(rows, 1):
|
|
||||||
display_name = name or f'user{user_id}'
|
|
||||||
lines.append(f'{i}. {display_name}: {count}')
|
|
||||||
|
|
||||||
return ', '.join(lines)
|
|
||||||
|
|
||||||
async def export_to_json(self) -> str:
|
|
||||||
"""
|
"""
|
||||||
Экспорт данных чата в JSON
|
Экспорт в JSON.
|
||||||
Возвращает путь к файлу
|
full_history=False -> за неделю (для планировщика)
|
||||||
|
full_history=True -> всё (для кнопки в меню)
|
||||||
"""
|
"""
|
||||||
timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
now_local = datetime.now(self.uly_tz)
|
||||||
filename = f'chat_export_{timestamp}.json'
|
prefix = "full_" if full_history else "weekly_"
|
||||||
|
filename = f'chat_export_{prefix}{now_local.strftime("%Y%m%d_%H%M%S")}.json'
|
||||||
filepath = self.export_dir / filename
|
filepath = self.export_dir / filename
|
||||||
|
|
||||||
# Получаем всех пользователей
|
# Пользователи
|
||||||
stmt = select(User).order_by(User.join_date.desc())
|
users = list((await self.session.execute(select(User).order_by(User.user_id))).scalars().all())
|
||||||
result = await self.session.execute(stmt)
|
|
||||||
users = list(result.scalars().all())
|
|
||||||
|
|
||||||
# Получаем сообщения за последнюю неделю
|
# Сообщения
|
||||||
week_ago = datetime.utcnow() - timedelta(days=7)
|
stmt = select(Message).order_by(Message.timestamp.asc())
|
||||||
stmt = (
|
if not full_history:
|
||||||
select(Message)
|
week_ago = datetime.utcnow() - timedelta(days=7)
|
||||||
.where(Message.timestamp >= week_ago)
|
stmt = stmt.where(Message.timestamp >= week_ago)
|
||||||
.order_by(Message.timestamp.desc())
|
|
||||||
.limit(10000) # Ограничение на количество
|
messages = list((await self.session.execute(stmt)).scalars().all())
|
||||||
)
|
|
||||||
result = await self.session.execute(stmt)
|
|
||||||
messages = list(result.scalars().all())
|
|
||||||
|
|
||||||
# Формируем экспорт
|
|
||||||
export_data = {
|
export_data = {
|
||||||
'export_date': datetime.utcnow().isoformat(),
|
'export_date': now_local.isoformat(),
|
||||||
'chat_id': None, # Заполнится при использовании
|
'is_full_history': full_history,
|
||||||
'period': {
|
|
||||||
'from': week_ago.isoformat(),
|
|
||||||
'to': datetime.utcnow().isoformat(),
|
|
||||||
},
|
|
||||||
'summary': {
|
'summary': {
|
||||||
'total_users': len(users),
|
'total_users': len(users),
|
||||||
'verified_users': sum(1 for u in users if u.verified),
|
|
||||||
'total_messages': len(messages),
|
'total_messages': len(messages),
|
||||||
},
|
},
|
||||||
'users': [
|
'users': [{
|
||||||
{
|
'user_id': u.user_id, 'username': u.username, 'first_name': u.first_name,
|
||||||
'user_id': u.user_id,
|
'apartment': u.apartment, 'verified': u.verified, 'rating': u.rating
|
||||||
'username': u.username,
|
} for u in users],
|
||||||
'first_name': u.first_name,
|
'messages': [{
|
||||||
'last_name': u.last_name,
|
'id': m.id, 'user_id': m.user_id, 'text': m.text,
|
||||||
'apartment': u.apartment,
|
'date': m.timestamp.isoformat() if m.timestamp else None, 'topic': m.topic
|
||||||
'verified': u.verified,
|
} for m in messages]
|
||||||
'join_date': u.join_date.isoformat() if u.join_date else None,
|
|
||||||
'message_count': u.message_count,
|
|
||||||
'spy_score': u.spy_score,
|
|
||||||
'spy_flags': json.loads(u.spy_flags) if u.spy_flags else [],
|
|
||||||
}
|
|
||||||
for u in users
|
|
||||||
],
|
|
||||||
'messages': [
|
|
||||||
{
|
|
||||||
'message_id': m.message_id,
|
|
||||||
'user_id': m.user_id,
|
|
||||||
'text': m.text[:500] if m.text else None, # Обрезаем длинные
|
|
||||||
'timestamp': m.timestamp.isoformat() if m.timestamp else None,
|
|
||||||
'reply_to': m.reply_to_message_id,
|
|
||||||
}
|
|
||||||
for m in messages
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Сохраняем
|
|
||||||
with open(filepath, 'w', encoding='utf-8') as f:
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
json.dump(export_data, f, ensure_ascii=False, indent=2)
|
json.dump(export_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
logger.info(f'Экспорт сохранён: {filepath}')
|
|
||||||
return str(filepath)
|
return str(filepath)
|
||||||
|
|
||||||
async def get_activity_report(self, days: int = 7) -> Dict[str, Any]:
|
|
||||||
"""Отчёт по активности за период"""
|
|
||||||
since = datetime.utcnow() - timedelta(days=days)
|
|
||||||
|
|
||||||
# Активные пользователи
|
|
||||||
stmt = (
|
|
||||||
select(distinct(Message.user_id))
|
|
||||||
.where(Message.timestamp >= since)
|
|
||||||
)
|
|
||||||
result = await self.session.execute(stmt)
|
|
||||||
active_users = len(list(result.scalars().all()))
|
|
||||||
|
|
||||||
# Самые активные часы
|
|
||||||
stmt = (
|
|
||||||
select(
|
|
||||||
func.strftime('%H', Message.timestamp).label('hour'),
|
|
||||||
func.count().label('count')
|
|
||||||
)
|
|
||||||
.where(Message.timestamp >= since)
|
|
||||||
.group_by(func.strftime('%H', Message.timestamp))
|
|
||||||
.order_by(func.count().desc())
|
|
||||||
.limit(5)
|
|
||||||
)
|
|
||||||
result = await self.session.execute(stmt)
|
|
||||||
peak_hours = [(row.hour, row.count) for row in result.all()]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'active_users': active_users,
|
|
||||||
'peak_hours': peak_hours,
|
|
||||||
'period_days': days,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
217
services/analytics_reference.py
Normal file
217
services/analytics_reference.py
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
"""
|
||||||
|
Аналитика чата и экспорт данных
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
from pathlib import Path
|
||||||
|
from sqlalchemy import select, func, distinct
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from database.models import User, Message
|
||||||
|
from config import EXPORT_DIR
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ChatAnalytics:
|
||||||
|
"""Аналитика чата"""
|
||||||
|
|
||||||
|
def __init__(self, session: AsyncSession):
|
||||||
|
self.session = session
|
||||||
|
self.export_dir = Path(EXPORT_DIR)
|
||||||
|
self.export_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
async def get_general_stats(self) -> Dict[str, Any]:
|
||||||
|
"""Общая статистика чата"""
|
||||||
|
# Всего пользователей
|
||||||
|
stmt = select(func.count(User.user_id))
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
total_users = result.scalar() or 0
|
||||||
|
|
||||||
|
# Верифицированы
|
||||||
|
stmt = select(func.count(User.user_id)).where(User.verified == True)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
verified_users = result.scalar() or 0
|
||||||
|
|
||||||
|
# Сообщения всего
|
||||||
|
stmt = select(func.count(Message.id))
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
total_messages = result.scalar() or 0
|
||||||
|
|
||||||
|
# Сообщения за неделю
|
||||||
|
week_ago = datetime.utcnow() - timedelta(days=7)
|
||||||
|
stmt = (
|
||||||
|
select(func.count(Message.id))
|
||||||
|
.where(Message.timestamp >= week_ago)
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
messages_week = result.scalar() or 0
|
||||||
|
|
||||||
|
# Сообщения за день
|
||||||
|
day_ago = datetime.utcnow() - timedelta(days=1)
|
||||||
|
stmt = (
|
||||||
|
select(func.count(Message.id))
|
||||||
|
.where(Message.timestamp >= day_ago)
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
messages_day = result.scalar() or 0
|
||||||
|
|
||||||
|
# Подозрительные
|
||||||
|
stmt = (
|
||||||
|
select(func.count(User.user_id))
|
||||||
|
.where(User.spy_score >= 31)
|
||||||
|
.where(User.spy_score <= 60)
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
suspicious_count = result.scalar() or 0
|
||||||
|
|
||||||
|
# Шпионы
|
||||||
|
stmt = (
|
||||||
|
select(func.count(User.user_id))
|
||||||
|
.where(User.spy_score > 60)
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
spies_count = result.scalar() or 0
|
||||||
|
|
||||||
|
# Топ пользователей за неделю
|
||||||
|
top_users = await self._get_top_users(week_ago, limit=5)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_users': total_users,
|
||||||
|
'verified_users': verified_users,
|
||||||
|
'unverified_users': total_users - verified_users,
|
||||||
|
'total_messages': total_messages,
|
||||||
|
'messages_week': messages_week,
|
||||||
|
'messages_day': messages_day,
|
||||||
|
'suspicious_count': suspicious_count,
|
||||||
|
'spies_count': spies_count,
|
||||||
|
'top_users': top_users,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _get_top_users(self, since: datetime, limit: int = 5) -> str:
|
||||||
|
"""Топ пользователей по активности"""
|
||||||
|
stmt = (
|
||||||
|
select(User.user_id, User.first_name, func.count(Message.id).label('count'))
|
||||||
|
.join(Message, User.user_id == Message.user_id)
|
||||||
|
.where(Message.timestamp >= since)
|
||||||
|
.group_by(User.user_id, User.first_name)
|
||||||
|
.order_by(func.count(Message.id).desc())
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
rows = result.all()
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
return 'нет данных'
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
for i, (user_id, name, count) in enumerate(rows, 1):
|
||||||
|
display_name = name or f'user{user_id}'
|
||||||
|
lines.append(f'{i}. {display_name}: {count}')
|
||||||
|
|
||||||
|
return ', '.join(lines)
|
||||||
|
|
||||||
|
async def export_to_json(self) -> str:
|
||||||
|
"""
|
||||||
|
Экспорт данных чата в JSON
|
||||||
|
Возвращает путь к файлу
|
||||||
|
"""
|
||||||
|
timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
||||||
|
filename = f'chat_export_{timestamp}.json'
|
||||||
|
filepath = self.export_dir / filename
|
||||||
|
|
||||||
|
# Получаем всех пользователей
|
||||||
|
stmt = select(User).order_by(User.join_date.desc())
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
users = list(result.scalars().all())
|
||||||
|
|
||||||
|
# Получаем сообщения за последнюю неделю
|
||||||
|
week_ago = datetime.utcnow() - timedelta(days=7)
|
||||||
|
stmt = (
|
||||||
|
select(Message)
|
||||||
|
.where(Message.timestamp >= week_ago)
|
||||||
|
.order_by(Message.timestamp.desc())
|
||||||
|
.limit(10000) # Ограничение на количество
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
messages = list(result.scalars().all())
|
||||||
|
|
||||||
|
# Формируем экспорт
|
||||||
|
export_data = {
|
||||||
|
'export_date': datetime.utcnow().isoformat(),
|
||||||
|
'chat_id': None, # Заполнится при использовании
|
||||||
|
'period': {
|
||||||
|
'from': week_ago.isoformat(),
|
||||||
|
'to': datetime.utcnow().isoformat(),
|
||||||
|
},
|
||||||
|
'summary': {
|
||||||
|
'total_users': len(users),
|
||||||
|
'verified_users': sum(1 for u in users if u.verified),
|
||||||
|
'total_messages': len(messages),
|
||||||
|
},
|
||||||
|
'users': [
|
||||||
|
{
|
||||||
|
'user_id': u.user_id,
|
||||||
|
'username': u.username,
|
||||||
|
'first_name': u.first_name,
|
||||||
|
'last_name': u.last_name,
|
||||||
|
'apartment': u.apartment,
|
||||||
|
'verified': u.verified,
|
||||||
|
'join_date': u.join_date.isoformat() if u.join_date else None,
|
||||||
|
'message_count': u.message_count,
|
||||||
|
'spy_score': u.spy_score,
|
||||||
|
'spy_flags': json.loads(u.spy_flags) if u.spy_flags else [],
|
||||||
|
}
|
||||||
|
for u in users
|
||||||
|
],
|
||||||
|
'messages': [
|
||||||
|
{
|
||||||
|
'message_id': m.message_id,
|
||||||
|
'user_id': m.user_id,
|
||||||
|
'text': m.text[:500] if m.text else None, # Обрезаем длинные
|
||||||
|
'timestamp': m.timestamp.isoformat() if m.timestamp else None,
|
||||||
|
'reply_to': m.reply_to_message_id,
|
||||||
|
}
|
||||||
|
for m in messages
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Сохраняем
|
||||||
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(export_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
logger.info(f'Экспорт сохранён: {filepath}')
|
||||||
|
return str(filepath)
|
||||||
|
|
||||||
|
async def get_activity_report(self, days: int = 7) -> Dict[str, Any]:
|
||||||
|
"""Отчёт по активности за период"""
|
||||||
|
since = datetime.utcnow() - timedelta(days=days)
|
||||||
|
|
||||||
|
# Активные пользователи
|
||||||
|
stmt = (
|
||||||
|
select(distinct(Message.user_id))
|
||||||
|
.where(Message.timestamp >= since)
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
active_users = len(list(result.scalars().all()))
|
||||||
|
|
||||||
|
# Самые активные часы
|
||||||
|
stmt = (
|
||||||
|
select(
|
||||||
|
func.strftime('%H', Message.timestamp).label('hour'),
|
||||||
|
func.count().label('count')
|
||||||
|
)
|
||||||
|
.where(Message.timestamp >= since)
|
||||||
|
.group_by(func.strftime('%H', Message.timestamp))
|
||||||
|
.order_by(func.count().desc())
|
||||||
|
.limit(5)
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
peak_hours = [(row.hour, row.count) for row in result.all()]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'active_users': active_users,
|
||||||
|
'peak_hours': peak_hours,
|
||||||
|
'period_days': days,
|
||||||
|
}
|
||||||
119
services/chat_exporter.py
Normal file
119
services/chat_exporter.py
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
"""
|
||||||
|
Надёжный сервис экспорта всей истории чата в JSON и Git.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from sqlalchemy import select, func
|
||||||
|
from database.models import User, Message as DBMessage
|
||||||
|
|
||||||
|
logger = logging.getLogger("ChatExporter")
|
||||||
|
|
||||||
|
class ChatExporter:
|
||||||
|
def __init__(self, session):
|
||||||
|
self.session = session
|
||||||
|
self.base_dir = Path(__file__).parent.parent
|
||||||
|
self.export_dir = self.base_dir / "exports"
|
||||||
|
self.export_dir.mkdir(exist_ok=True)
|
||||||
|
self.uly_tz = timezone(timedelta(hours=4))
|
||||||
|
|
||||||
|
async def run_full_export(self):
|
||||||
|
"""Полный цикл: Генерация -> Запись в БД -> Push в Git"""
|
||||||
|
try:
|
||||||
|
now_local = datetime.now(self.uly_tz)
|
||||||
|
timestamp = now_local.strftime("%Y-%m-%d_%H-%M")
|
||||||
|
filename = f"chat_history_{timestamp}.json"
|
||||||
|
filepath = self.export_dir / filename
|
||||||
|
|
||||||
|
# 1. Собираем данные
|
||||||
|
users_res = await self.session.execute(select(User))
|
||||||
|
users = users_res.scalars().all()
|
||||||
|
|
||||||
|
msg_res = await self.session.execute(select(DBMessage).order_by(DBMessage.timestamp.asc()))
|
||||||
|
messages = msg_res.scalars().all()
|
||||||
|
|
||||||
|
export_data = {
|
||||||
|
"info": {
|
||||||
|
"exported_at": now_local.isoformat(),
|
||||||
|
"total_users": len(list(users)),
|
||||||
|
"total_messages": len(list(messages))
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": u.user_id, "username": u.username, "name": u.full_name,
|
||||||
|
"apt": u.apartment, "verified": u.verified
|
||||||
|
} for u in users
|
||||||
|
],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": m.id, "user_id": m.user_id, "text": m.text,
|
||||||
|
"date": m.timestamp.isoformat() if m.timestamp else None,
|
||||||
|
"topic": m.topic
|
||||||
|
} for m in messages
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Сохраняем файл
|
||||||
|
with open(filepath, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(export_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# 3. Пушим в Gitea
|
||||||
|
git_success = self._push_to_gitea(filepath)
|
||||||
|
|
||||||
|
# 4. Логируем в БД
|
||||||
|
await self._log_to_db(filename, len(messages), git_success)
|
||||||
|
|
||||||
|
return {"success": True, "filename": filename, "messages": len(messages), "git": git_success}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Критическая ошибка экспорта: {e}", exc_info=True)
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
def _push_to_gitea(self, filepath):
|
||||||
|
"""Отправка файла в Gitea через системный git"""
|
||||||
|
try:
|
||||||
|
# Напрямую, без прокси
|
||||||
|
env = os.environ.copy()
|
||||||
|
for var in ['http_proxy', 'https_proxy', 'all_proxy', 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY']:
|
||||||
|
env.pop(var, None)
|
||||||
|
|
||||||
|
# Переходим в корень проекта для git команд
|
||||||
|
os.chdir(str(self.base_dir))
|
||||||
|
|
||||||
|
subprocess.run(["git", "add", "-f", str(filepath)], check=True, env=env)
|
||||||
|
subprocess.run(["git", "commit", "-m", f"📦 Авто-экспорт чата: {filepath.name}"], check=True, env=env)
|
||||||
|
subprocess.run(["git", "push", "origin", "main"], check=True, env=env)
|
||||||
|
|
||||||
|
logger.info(f"✅ Файл {filepath.name} успешно отправлен в Gitea")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Ошибка Git Push: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _log_to_db(self, filename, count, git_pushed):
|
||||||
|
from sqlalchemy import text
|
||||||
|
try:
|
||||||
|
# Используем текстовый запрос, так как у нас нет модели для этой таблицы
|
||||||
|
sql = text("INSERT INTO chat_exports (filename, total_messages, git_pushed) VALUES (:f, :c, :g)")
|
||||||
|
await self.session.execute(sql, {"f": filename, "c": count, "g": 1 if git_pushed else 0})
|
||||||
|
await self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Ошибка записи в таблицу экспортов: {e}")
|
||||||
|
|
||||||
|
async def get_stats(self):
|
||||||
|
"""Получить статистику для веб-панели"""
|
||||||
|
from sqlalchemy import text
|
||||||
|
try:
|
||||||
|
sql = text("SELECT COUNT(*), MAX(export_date) FROM chat_exports")
|
||||||
|
res = await self.session.execute(sql)
|
||||||
|
row = res.first()
|
||||||
|
if row:
|
||||||
|
count, last_date = row
|
||||||
|
return {"total": count or 0, "last_date": last_date}
|
||||||
|
return {"total": 0, "last_date": None}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting stats: {e}")
|
||||||
|
return {"total": 0, "last_date": None}
|
||||||
|
|
@ -127,29 +127,31 @@ class Scheduler:
|
||||||
logger.info('🛑 Планировщик остановлен')
|
logger.info('🛑 Планировщик остановлен')
|
||||||
|
|
||||||
async def weekly_export(self):
|
async def weekly_export(self):
|
||||||
"""Еженедельный экспорт данных"""
|
"""Еженедельный экспорт данных (Новая логика v2026)"""
|
||||||
try:
|
try:
|
||||||
from database.db import AsyncSessionLocal
|
from database.db import AsyncSessionLocal
|
||||||
|
from services.chat_exporter import ChatExporter
|
||||||
|
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
analytics = ChatAnalytics(session)
|
exporter = ChatExporter(session)
|
||||||
filepath = await analytics.export_to_json()
|
result = await exporter.run_full_export()
|
||||||
|
|
||||||
|
status_git = "✅ В Gitea" if result.get('git') else "❌ Ошибка Gitea"
|
||||||
|
|
||||||
await self.bot.send_message(
|
await self.bot.send_message(
|
||||||
ADMIN_USER_ID,
|
ADMIN_USER_ID,
|
||||||
f'📥 <b>Еженедельный экспорт выполнен</b>\n\n'
|
f'📥 <b>Еженедельный экспорт завершён</b>\n\n'
|
||||||
f'Файл сохранён: {filepath}\n'
|
f'📄 Файл: <code>{result.get("filename")}</code>\n'
|
||||||
f'Время: {datetime.utcnow().strftime("%d.%m.%Y %H:%M")}'
|
f'📊 Сообщений: {result.get("messages")}\n'
|
||||||
|
f'🌐 Статус: {status_git}\n'
|
||||||
|
f'🕒 Пояс: Ульяновск (UTC+4)'
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f'Еженедельный экспорт: {filepath}')
|
logger.info(f"✅ Weekly export finished: {result.get('filename')}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Ошибка экспорта: {e}')
|
logger.error(f'Ошибка экспорта: {e}')
|
||||||
await self.bot.send_message(
|
await self.bot.send_message(ADMIN_USER_ID, f'❌ <b>Ошибка экспорта</b>\n\n{str(e)}')
|
||||||
ADMIN_USER_ID,
|
|
||||||
f'❌ <b>Ошибка экспорта</b>\n\n{str(e)}'
|
|
||||||
)
|
|
||||||
|
|
||||||
async def daily_spy_check(self):
|
async def daily_spy_check(self):
|
||||||
"""Ежедневная проверка на шпионов"""
|
"""Ежедневная проверка на шпионов"""
|
||||||
|
|
|
||||||
6
test_web.py
Normal file
6
test_web.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from fastapi import FastAPI
|
||||||
|
import uvicorn
|
||||||
|
app = FastAPI()
|
||||||
|
@app.get("/")
|
||||||
|
def read_root(): return {"Hello": "World"}
|
||||||
|
if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8005)
|
||||||
595
web/app.py
595
web/app.py
|
|
@ -1,13 +1,30 @@
|
||||||
"""
|
"""
|
||||||
Web Admin Interface v4.2 REBORN - MEGA ADMIN PANEL
|
Web Admin Interface v4.5 REBORN - MEGA ADMIN PANEL
|
||||||
Полноценное управление ВСЕМ функционалом бота
|
Полноценное управление ВСЕМ функционалом бота:
|
||||||
|
- Dashboard, Users, Verification, Phones, Ads, Polls, Events, Schedules
|
||||||
|
- Smart Broadcasts (с отслеживанием прочтения)
|
||||||
|
- Scheduled Posts (с фото/документами)
|
||||||
|
- Weekly Digests (v3.1)
|
||||||
|
- Chat Exporter (JSON + Gitea Push)
|
||||||
|
- Email Auditor (аудит переписки)
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import secrets
|
||||||
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from fastapi import FastAPI, Request, Depends, HTTPException, status, Form, File, UploadFile
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
# ГАРАНТИРУЕМ, что корень проекта в пути поиска модулей
|
||||||
|
BASE_DIR = Path(__file__).parent.parent
|
||||||
|
if str(BASE_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(BASE_DIR))
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Request, Depends, HTTPException, status, Form, File, UploadFile, Query
|
||||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
@ -15,34 +32,36 @@ from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, File
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select, func, update, delete, desc, text
|
from sqlalchemy import select, func, update, delete, desc, text
|
||||||
from database.models import User, Message, EmailExportLog, EmailAudit
|
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
import secrets
|
|
||||||
import config
|
import config
|
||||||
|
from database.db import AsyncSessionLocal
|
||||||
|
from database.models import (
|
||||||
|
User, Message as DBMessage, Poll, Ad, PaymentReminder, Schedule,
|
||||||
|
VerificationRequest, Service, Announcement, Event, ScheduledPost,
|
||||||
|
Broadcast, BroadcastRead, Digest, InitiativeGroup, EmailAudit, EmailExportLog,
|
||||||
|
ProfileHistory, Achievement, Thank, SpyLog
|
||||||
|
)
|
||||||
|
|
||||||
# ===== ЧАСОВОЙ ПОЯС УЛЬЯНОВСК (UTC+4) =====
|
# ===== ЧАСОВОЙ ПОЯС УЛЬЯНОВСК (UTC+4) =====
|
||||||
ULY_TZ = timezone(timedelta(hours=4))
|
ULY_TZ = timezone(timedelta(hours=4))
|
||||||
|
|
||||||
def to_local(dt: datetime) -> datetime:
|
def to_local(dt) -> datetime:
|
||||||
if dt is None: return None
|
if dt is None: return None
|
||||||
# Если время без часового пояса (naive), считаем что оно в UTC (как в БД)
|
if isinstance(dt, str):
|
||||||
if dt.tzinfo is None:
|
try:
|
||||||
dt = dt.replace(tzinfo=timezone.utc)
|
dt = datetime.fromisoformat(dt.replace(' ', 'T'))
|
||||||
|
except: return None
|
||||||
|
if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc)
|
||||||
return dt.astimezone(ULY_TZ)
|
return dt.astimezone(ULY_TZ)
|
||||||
|
|
||||||
def fmt_local(dt: datetime, fmt: str = '%d.%m.%Y %H:%M') -> str:
|
def fmt_local(dt, fmt: str = '%d.%m.%Y %H:%M') -> str:
|
||||||
local_dt = to_local(dt)
|
local_dt = to_local(dt)
|
||||||
return local_dt.strftime(fmt) if local_dt else '-'
|
return local_dt.strftime(fmt) if local_dt else '-'
|
||||||
|
|
||||||
from database.db import AsyncSessionLocal
|
|
||||||
from database.models import (
|
|
||||||
User, Message, Poll, Ad, PaymentReminder, Schedule,
|
|
||||||
VerificationRequest, Service, Announcement, Event, ScheduledPost,
|
|
||||||
Broadcast, BroadcastRead, Digest, InitiativeGroup, EmailAudit, EmailExportLog
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
app = FastAPI(title="LKM37 MEGA Admin Panel", version="4.2.0")
|
logger.info(f"!!! [STARTUP] Unified Web App loaded. PID: {os.getpid()}")
|
||||||
|
|
||||||
|
app = FastAPI(title="LKM37 MEGA Admin Panel", version="4.5.0")
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
|
|
@ -52,27 +71,26 @@ app.add_middleware(
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.exception_handler(Exception)
|
templates = Jinja2Templates(directory=str(BASE_DIR / "web" / "templates"))
|
||||||
async def debug_exception_handler(request: Request, exc: Exception):
|
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "web" / "static")), name="static")
|
||||||
logger.error(f"❌ WEB ERROR: {exc}", exc_info=True)
|
app.mount("/static/service_images", StaticFiles(directory=str(BASE_DIR / "data" / "service_images")), name="service_images")
|
||||||
return JSONResponse(status_code=500, content={"message": f"Internal Server Error: {str(exc)}"})
|
app.mount("/static/email_archive", StaticFiles(directory=str(BASE_DIR / "services" / "email_auditor" / "exported_emails")), name="email_archive")
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="web/templates")
|
|
||||||
base_dir = Path(__file__).parent.parent
|
|
||||||
app.mount("/static", StaticFiles(directory="web/static"), name="static")
|
|
||||||
app.mount("/static/service_images", StaticFiles(directory=str(base_dir / "data" / "service_images")), name="service_images")
|
|
||||||
app.mount("/static/email_archive", StaticFiles(directory=str(base_dir / "services" / "email_auditor" / "exported_emails")), name="email_archive")
|
|
||||||
security = HTTPBasic()
|
security = HTTPBasic()
|
||||||
|
|
||||||
def get_current_admin(credentials: HTTPBasicCredentials = Depends(security)):
|
def get_current_admin(credentials: HTTPBasicCredentials = Depends(security)):
|
||||||
correct_username = secrets.compare_digest(credentials.username, config.WEB_ADMIN_LOGIN)
|
correct_username = secrets.compare_digest(credentials.username, config.WEB_ADMIN_LOGIN)
|
||||||
correct_password = secrets.compare_digest(credentials.password, config.WEB_ADMIN_PASSWORD)
|
correct_password = secrets.compare_digest(credentials.password, config.WEB_ADMIN_PASSWORD)
|
||||||
if not (correct_username and correct_password):
|
if not (correct_username and correct_password):
|
||||||
raise HTTPException(status_code=401, detail="Неверный логин или пароль", headers={"WWW-Authenticate": "Basic"})
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Incorrect login or password",
|
||||||
|
headers={"WWW-Authenticate": "Basic"},
|
||||||
|
)
|
||||||
return credentials.username
|
return credentials.username
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# ГЛАВНАЯ
|
# ГЛАВНАЯ (DASHBOARD)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
|
@ -80,20 +98,21 @@ async def dashboard(request: Request, username: str = Depends(get_current_admin)
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
users_count = (await session.execute(select(func.count(User.user_id)))).scalar()
|
users_count = (await session.execute(select(func.count(User.user_id)))).scalar()
|
||||||
verified_count = (await session.execute(select(func.count(User.user_id)).where(User.verified == True))).scalar()
|
verified_count = (await session.execute(select(func.count(User.user_id)).where(User.verified == True))).scalar()
|
||||||
messages_count = (await session.execute(select(func.count(Message.id)))).scalar()
|
messages_count = (await session.execute(select(func.count(DBMessage.id)))).scalar()
|
||||||
active_ads = (await session.execute(select(func.count(Ad.ad_id)).where(Ad.is_active == True))).scalar()
|
active_ads = (await session.execute(select(func.count(Ad.ad_id)).where(Ad.is_active == True))).scalar()
|
||||||
active_polls = (await session.execute(select(func.count(Poll.poll_id)).where(Poll.is_active == True))).scalar()
|
active_polls = (await session.execute(select(func.count(Poll.poll_id)).where(Poll.is_active == True))).scalar()
|
||||||
upcoming_events = (await session.execute(select(func.count(Event.id)).where(Event.is_active == True))).scalar()
|
upcoming_events = (await session.execute(select(func.count(Event.id)).where(Event.is_active == True))).scalar()
|
||||||
pending_count = (await session.execute(select(func.count(VerificationRequest.id)).where(VerificationRequest.status == 'pending'))).scalar()
|
pending_count = (await session.execute(select(func.count(VerificationRequest.id)).where(VerificationRequest.status == 'pending'))).scalar()
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse("dashboard.html", {
|
||||||
request=request, name="dashboard.html",
|
"request": request, "username": username,
|
||||||
context={
|
"stats": {
|
||||||
"username": username, "users_count": users_count, "verified_count": verified_count,
|
"users": users_count, "verified": verified_count,
|
||||||
"messages_count": messages_count, "active_ads": active_ads, "active_polls": active_polls,
|
"messages": messages_count, "ads": active_ads,
|
||||||
"upcoming_events": upcoming_events, "pending_count": pending_count
|
"polls": active_polls, "events": upcoming_events
|
||||||
}
|
},
|
||||||
)
|
"pending_count": pending_count
|
||||||
|
})
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# ПОЛЬЗОВАТЕЛИ
|
# ПОЛЬЗОВАТЕЛИ
|
||||||
|
|
@ -105,330 +124,316 @@ async def users_page(request: Request, search: str = "", filter: str = "", usern
|
||||||
stmt = select(User)
|
stmt = select(User)
|
||||||
if filter == 'verified': stmt = stmt.where(User.verified == True)
|
if filter == 'verified': stmt = stmt.where(User.verified == True)
|
||||||
elif filter == 'unverified': stmt = stmt.where(User.verified == False)
|
elif filter == 'unverified': stmt = stmt.where(User.verified == False)
|
||||||
|
elif filter == 'active_unverified': stmt = stmt.where(User.verified == False, User.message_count >= 50)
|
||||||
elif filter == 'ig':
|
elif filter == 'ig':
|
||||||
ig_stmt = select(InitiativeGroup.user_id).where(InitiativeGroup.is_active == True)
|
ig_stmt = select(InitiativeGroup.user_id).where(InitiativeGroup.is_active == True)
|
||||||
ig_user_ids = [row[0] for row in (await session.execute(ig_stmt)).all()]
|
ig_user_ids = [row[0] for row in (await session.execute(ig_stmt)).all()]
|
||||||
stmt = stmt.where(User.user_id.in_(ig_user_ids))
|
stmt = stmt.where(User.user_id.in_(ig_user_ids))
|
||||||
|
|
||||||
if search:
|
if search:
|
||||||
stmt = stmt.where((User.first_name.ilike(f"%{search}%")) | (User.username.ilike(f"%{search}%")) | (User.apartment.ilike(f"%{search}%")))
|
stmt = stmt.where(
|
||||||
|
(User.first_name.ilike(f"%{search}%")) |
|
||||||
|
(User.last_name.ilike(f"%{search}%")) |
|
||||||
|
(User.username.ilike(f"%{search}%")) |
|
||||||
|
(User.apartment.ilike(f"%{search}%"))
|
||||||
|
)
|
||||||
|
|
||||||
users = list((await session.execute(stmt.order_by(User.user_id.desc()).limit(100))).scalars().all())
|
stmt = stmt.order_by(User.user_id.desc()).limit(100)
|
||||||
total_count = (await session.execute(select(func.count(User.user_id)))).scalar()
|
users = list((await session.execute(stmt)).scalars().all())
|
||||||
verified_count = (await session.execute(select(func.count(User.user_id)).where(User.verified == True))).scalar()
|
|
||||||
|
|
||||||
ig_stmt = select(InitiativeGroup.user_id).where(InitiativeGroup.is_active == True)
|
ig_stmt = select(InitiativeGroup.user_id).where(InitiativeGroup.is_active == True)
|
||||||
ig_user_ids = set(row[0] for row in (await session.execute(ig_stmt)).all())
|
ig_user_ids = set(row[0] for row in (await session.execute(ig_stmt)).all())
|
||||||
|
|
||||||
pending_stmt = select(VerificationRequest).where(VerificationRequest.status == 'pending')
|
pending_stmt = select(VerificationRequest).where(VerificationRequest.status == 'pending').order_by(VerificationRequest.created_at.desc())
|
||||||
pending_requests = list((await session.execute(pending_stmt)).scalars().all())
|
pending_requests = (await session.execute(pending_stmt)).scalars().all()
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
total_count = (await session.execute(select(func.count(User.user_id)))).scalar()
|
||||||
request=request, name="users.html",
|
verified_count = (await session.execute(select(func.count(User.user_id)).where(User.verified == True))).scalar()
|
||||||
context={
|
active_unverified = (await session.execute(select(func.count(User.user_id)).where(User.verified == False, User.message_count >= 50))).scalar()
|
||||||
"username": username, "users": users, "search": search, "current_filter": filter,
|
|
||||||
"total_count": total_count, "verified_count": verified_count, "unverified_count": total_count - verified_count,
|
|
||||||
"ig_count": len(ig_user_ids), "ig_user_ids": ig_user_ids, "pending_requests": [], "pending_count": len(pending_requests),
|
|
||||||
"active_unverified": 0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# ============================================================================
|
return templates.TemplateResponse("users.html", {
|
||||||
# РАССЫЛКИ
|
"request": request, "username": username, "users": users, "search": search, "current_filter": filter,
|
||||||
# ============================================================================
|
"stats": {"total": total_count, "verified": verified_count, "unverified": total_count - verified_count, "active_unverified": active_unverified, "ig": len(ig_user_ids)},
|
||||||
|
"ig_user_ids": ig_user_ids, "pending_count": len(list(pending_requests))
|
||||||
|
})
|
||||||
|
|
||||||
@app.get("/broadcast", response_class=HTMLResponse)
|
@app.get("/api/user/{user_id}")
|
||||||
async def broadcast_page(request: Request, username: str = Depends(get_current_admin)):
|
async def api_get_user(user_id: int, username: str = Depends(get_current_admin)):
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
broadcasts = list((await session.execute(select(Broadcast).order_by(Broadcast.created_at.desc()).limit(50))).scalars().all())
|
user = await session.get(User, user_id)
|
||||||
return templates.TemplateResponse(request=request, name="broadcast.html", context={"username": username, "broadcasts": broadcasts})
|
if not user: return JSONResponse({"error": "Not found"}, status_code=404)
|
||||||
|
return JSONResponse({"user": {"id": user.user_id, "username": user.username, "first_name": user.first_name, "last_name": user.last_name, "apartment": user.apartment, "phone": user.phone, "verified": user.verified, "rating": user.rating, "message_count": user.message_count, "is_banned": user.is_banned}})
|
||||||
|
|
||||||
@app.post("/api/broadcast/create")
|
@app.post("/api/user/{user_id}/update")
|
||||||
async def api_create_broadcast(
|
async def api_update_user(user_id: int, request: Request, username: str = Depends(get_current_admin)):
|
||||||
text: str = Form(...),
|
data = await request.json()
|
||||||
photo: UploadFile = File(None),
|
|
||||||
recipients: str = Form("all_and_chat"),
|
|
||||||
staircase: str = Form("all"),
|
|
||||||
username: str = Depends(get_current_admin)
|
|
||||||
):
|
|
||||||
from handlers.smart_broadcast import send_smart_broadcast
|
|
||||||
from bot_instance import get_bot
|
|
||||||
|
|
||||||
photo_file_id = None
|
|
||||||
if photo and photo.filename:
|
|
||||||
temp_path = Path("data") / photo.filename
|
|
||||||
with open(temp_path, "wb") as f: f.write(await photo.read())
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/sendPhoto"
|
|
||||||
data = aiohttp.FormData()
|
|
||||||
data.add_field('chat_id', str(config.ADMIN_USER_ID))
|
|
||||||
data.add_field('photo', open(temp_path, 'rb'))
|
|
||||||
async with session.post(url, data=data, proxy=config.get_proxy_url()) as resp:
|
|
||||||
res = await resp.json()
|
|
||||||
if res.get('ok'): photo_file_id = res['result']['photo'][-1]['file_id']
|
|
||||||
if temp_path.exists(): temp_path.unlink()
|
|
||||||
|
|
||||||
bot = get_bot()
|
|
||||||
success = await send_smart_broadcast(bot, text, recipients, photo_file_id, staircase)
|
|
||||||
return JSONResponse({"success": success})
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# ФАЙЛЫ (МЕНЕДЖЕР)
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
@app.get("/files", response_class=HTMLResponse)
|
|
||||||
async def files_page(request: Request, username: str = Depends(get_current_admin)):
|
|
||||||
return templates.TemplateResponse(request=request, name="files.html", context={"username": username})
|
|
||||||
|
|
||||||
@app.get("/api/files/list")
|
|
||||||
async def api_list_files(username: str = Depends(get_current_admin)):
|
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
result = await session.execute(text("SELECT id, filename, file_id, file_type FROM assets ORDER BY created_at DESC"))
|
update_data = {}
|
||||||
files = [{"id": r[0], "filename": r[1], "file_id": r[2], "file_type": r[3]} for r in result.all()]
|
if 'apartment' in data: update_data['apartment'] = data['apartment'].upper()
|
||||||
return JSONResponse({"success": True, "files": files})
|
if 'phone' in data: update_data['phone'] = data['phone']
|
||||||
|
if 'verified' in data:
|
||||||
|
update_data['verified'] = bool(data['verified'])
|
||||||
|
if data['verified']: update_data['verification_date'] = datetime.utcnow()
|
||||||
|
if 'rating' in data: update_data['rating'] = int(data['rating'])
|
||||||
|
if update_data:
|
||||||
|
await session.execute(update(User).where(User.user_id == user_id).values(**update_data))
|
||||||
|
await session.commit()
|
||||||
|
return JSONResponse({"success": True})
|
||||||
|
|
||||||
@app.post("/api/files/upload")
|
@app.post("/api/user/{user_id}/verify")
|
||||||
async def api_upload_file(file: UploadFile = File(...), username: str = Depends(get_current_admin)):
|
async def api_verify_user(user_id: int, username: str = Depends(get_current_admin)):
|
||||||
temp_path = Path("data") / file.filename
|
async with AsyncSessionLocal() as session:
|
||||||
with open(temp_path, "wb") as f: f.write(await file.read())
|
await session.execute(update(User).where(User.user_id == user_id).values(verified=True, verification_date=datetime.utcnow()))
|
||||||
file_ext = Path(file.filename).suffix.lower()
|
await session.commit()
|
||||||
is_photo = file_ext in ['.jpg', '.jpeg', '.png', '.webp']
|
return JSONResponse({"success": True})
|
||||||
tg_method = "sendPhoto" if is_photo else "sendDocument"
|
|
||||||
|
|
||||||
try:
|
@app.post("/api/user/{user_id}/unverify")
|
||||||
async with aiohttp.ClientSession() as session:
|
async def api_unverify_user(user_id: int, username: str = Depends(get_current_admin)):
|
||||||
url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/{tg_method}"
|
async with AsyncSessionLocal() as session:
|
||||||
data = aiohttp.FormData()
|
await session.execute(update(User).where(User.user_id == user_id).values(verified=False))
|
||||||
data.add_field('chat_id', str(config.ADMIN_USER_ID))
|
await session.commit()
|
||||||
data.add_field('photo' if is_photo else 'document', open(temp_path, 'rb'))
|
return JSONResponse({"success": True})
|
||||||
async with session.post(url, data=data, proxy=config.get_proxy_url()) as resp:
|
|
||||||
res = await resp.json()
|
|
||||||
if not res.get('ok'): raise Exception(res.get('description'))
|
|
||||||
fid = res['result']['photo'][-1]['file_id'] if is_photo else res['result']['document']['file_id']
|
|
||||||
async with AsyncSessionLocal() as sdb:
|
|
||||||
await sdb.execute(text("INSERT INTO assets (filename, file_id, file_type) VALUES (:fn, :fid, :ft)"),
|
|
||||||
{"fn": file.filename, "fid": fid, "ft": "photo" if is_photo else "document"})
|
|
||||||
await sdb.commit()
|
|
||||||
return JSONResponse({"success": True, "file_id": fid})
|
|
||||||
except Exception as e: return JSONResponse({"success": False, "error": str(e)}, status_code=500)
|
|
||||||
finally:
|
|
||||||
if temp_path.exists(): temp_path.unlink()
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# ОСТАЛЬНЫЕ РОУТЫ (ЗАГЛУШКИ / БАЗОВЫЕ)
|
# ВЕРИФИКАЦИЯ
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
@app.get("/verification", response_class=HTMLResponse)
|
@app.get("/verification", response_class=HTMLResponse)
|
||||||
async def verification_page(request: Request, username: str = Depends(get_current_admin)):
|
async def verification_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
pending = list((await session.execute(select(VerificationRequest).where(VerificationRequest.status == 'pending'))).scalars().all())
|
pending = (await session.execute(select(VerificationRequest).where(VerificationRequest.status == 'pending').order_by(VerificationRequest.created_at.desc()))).scalars().all()
|
||||||
unverified = list((await session.execute(select(User).where(User.verified == False).limit(50))).scalars().all())
|
unverified = (await session.execute(select(User).where(User.verified == False).order_by(User.join_date.desc()).limit(50))).scalars().all()
|
||||||
return templates.TemplateResponse(request=request, name="verification.html", context={"username": username, "pending_requests": pending, "unverified_users": unverified})
|
return templates.TemplateResponse("verification.html", {"request": request, "username": username, "pending_requests": pending, "unverified_users": unverified})
|
||||||
|
|
||||||
|
@app.post("/api/verification/{req_id}/approve")
|
||||||
|
async def api_approve_verification(req_id: int, username: str = Depends(get_current_admin)):
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
req = (await session.execute(select(VerificationRequest).where(VerificationRequest.id == req_id))).scalar_one_or_none()
|
||||||
|
if req:
|
||||||
|
req.status = 'approved'
|
||||||
|
req.reviewed_at = datetime.utcnow()
|
||||||
|
await session.execute(update(User).where(User.user_id == req.user_id).values(verified=True, apartment=req.apartment, verification_date=datetime.utcnow()))
|
||||||
|
await session.commit()
|
||||||
|
return JSONResponse({"success": True})
|
||||||
|
|
||||||
|
@app.post("/api/verification/{req_id}/reject")
|
||||||
|
async def api_reject_verification(req_id: int, username: str = Depends(get_current_admin)):
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
req = (await session.execute(select(VerificationRequest).where(VerificationRequest.id == req_id))).scalar_one_or_none()
|
||||||
|
if req:
|
||||||
|
req.status = 'rejected'
|
||||||
|
req.reviewed_at = datetime.utcnow()
|
||||||
|
await session.commit()
|
||||||
|
return JSONResponse({"success": True})
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ТЕЛЕФОНЫ / ОБЪЯВЛЕНИЯ / ОПРОСЫ / СОБЫТИЯ / ГРАФИКИ
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
@app.get("/phones", response_class=HTMLResponse)
|
@app.get("/phones", response_class=HTMLResponse)
|
||||||
async def phones_page(request: Request, username: str = Depends(get_current_admin)):
|
async def phones_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
services = list((await session.execute(select(Service).order_by(Service.category, Service.name))).scalars().all())
|
services = (await session.execute(select(Service).order_by(Service.category, Service.name))).scalars().all()
|
||||||
return templates.TemplateResponse(request=request, name="phones.html", context={"username": username, "services": services})
|
return templates.TemplateResponse("phones.html", {"request": request, "username": username, "services": services})
|
||||||
|
|
||||||
@app.get("/schedules", response_class=HTMLResponse)
|
|
||||||
async def schedules_page(request: Request, username: str = Depends(get_current_admin)):
|
|
||||||
async with AsyncSessionLocal() as session:
|
|
||||||
schedules = list((await session.execute(select(Schedule).order_by(Schedule.start_time.desc()))).scalars().all())
|
|
||||||
return templates.TemplateResponse(request=request, name="schedules.html", context={"username": username, "schedules": schedules})
|
|
||||||
|
|
||||||
@app.get("/ads", response_class=HTMLResponse)
|
@app.get("/ads", response_class=HTMLResponse)
|
||||||
async def ads_page(request: Request, username: str = Depends(get_current_admin)):
|
async def ads_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
ads = list((await session.execute(select(Ad).order_by(Ad.created_at.desc()).limit(100))).scalars().all())
|
ads = (await session.execute(select(Ad).order_by(Ad.created_at.desc()).limit(100))).scalars().all()
|
||||||
return templates.TemplateResponse(request=request, name="ads.html", context={"username": username, "ads": ads})
|
return templates.TemplateResponse("ads.html", {"request": request, "username": username, "ads": ads})
|
||||||
|
|
||||||
@app.get("/polls", response_class=HTMLResponse)
|
@app.get("/polls", response_class=HTMLResponse)
|
||||||
async def polls_page(request: Request, username: str = Depends(get_current_admin)):
|
async def polls_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
polls = list((await session.execute(select(Poll).order_by(Poll.created_at.desc()).limit(50))).scalars().all())
|
polls = (await session.execute(select(Poll).order_by(Poll.created_at.desc()).limit(50))).scalars().all()
|
||||||
return templates.TemplateResponse(request=request, name="polls.html", context={"username": username, "polls": polls})
|
return templates.TemplateResponse("polls.html", {"request": request, "username": username, "polls": polls})
|
||||||
|
|
||||||
@app.get("/events", response_class=HTMLResponse)
|
@app.get("/events", response_class=HTMLResponse)
|
||||||
async def events_page(request: Request, username: str = Depends(get_current_admin)):
|
async def events_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
events = list((await session.execute(select(Event).order_by(Event.event_date.desc()).limit(50))).scalars().all())
|
events = (await session.execute(select(Event).order_by(Event.event_date.desc()).limit(50))).scalars().all()
|
||||||
return templates.TemplateResponse(request=request, name="events.html", context={"username": username, "events": events})
|
return templates.TemplateResponse("events.html", {"request": request, "username": username, "events": events})
|
||||||
|
|
||||||
@app.get("/digests", response_class=HTMLResponse)
|
@app.get("/schedules", response_class=HTMLResponse)
|
||||||
async def digests_page(request: Request, username: str = Depends(get_current_admin)):
|
async def schedules_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
return templates.TemplateResponse(request=request, name="digests.html", context={"username": username, "pending_digests": [], "archive_digests": []})
|
async with AsyncSessionLocal() as session:
|
||||||
|
schedules = (await session.execute(select(Schedule).order_by(Schedule.start_time.desc()))).scalars().all()
|
||||||
|
return templates.TemplateResponse("schedules.html", {"request": request, "username": username, "schedules": schedules})
|
||||||
|
|
||||||
@app.get("/export", response_class=HTMLResponse)
|
# ============================================================================
|
||||||
async def export_page(request: Request, username: str = Depends(get_current_admin)):
|
# РАССЫЛКИ (SMART BROADCAST)
|
||||||
return templates.TemplateResponse(request=request, name="export.html", context={"username": username})
|
# ============================================================================
|
||||||
|
|
||||||
@app.get("/api/export/json")
|
@app.get("/broadcast", response_class=HTMLResponse)
|
||||||
async def api_export_json(username: str = Depends(get_current_admin)):
|
async def broadcast_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
"""Экспорт данных в JSON с прямой отдачей файла"""
|
async with AsyncSessionLocal() as session:
|
||||||
import json
|
broadcasts = (await session.execute(select(Broadcast).order_by(Broadcast.created_at.desc()).limit(50))).scalars().all()
|
||||||
from database.db import AsyncSessionLocal
|
broadcasts_data = []
|
||||||
|
for b in broadcasts:
|
||||||
|
read_count = (await session.execute(select(func.count(BroadcastRead.id)).where(BroadcastRead.broadcast_id == b.id))).scalar() or 0
|
||||||
|
broadcasts_data.append({
|
||||||
|
'id': b.id, 'text': b.text[:100] + '...' if len(b.text) > 100 else b.text,
|
||||||
|
'sent_at': fmt_local(b.sent_at) if b.sent_at else '-',
|
||||||
|
'total_sent': b.total_sent, 'read_count': read_count,
|
||||||
|
'read_percent': round((read_count / b.total_sent * 100), 1) if b.total_sent > 0 else 0,
|
||||||
|
'broadcast_type': b.broadcast_type or 'regular',
|
||||||
|
})
|
||||||
|
return templates.TemplateResponse("broadcast.html", {"request": request, "username": username, "broadcasts": broadcasts_data})
|
||||||
|
|
||||||
export_dir = Path(__file__).parent.parent / "exports"
|
# ============================================================================
|
||||||
export_dir.mkdir(exist_ok=True)
|
# ЗАПЛАНИРОВАННЫЕ ПОСТЫ
|
||||||
|
# ============================================================================
|
||||||
# Время по Ульяновску для имени файла
|
|
||||||
now_uly = datetime.now(ULY_TZ)
|
|
||||||
timestamp = now_uly.strftime("%Y%m%d_%H%M%S")
|
|
||||||
filename = f"domovoy_export_{timestamp}.json"
|
|
||||||
filepath = export_dir / filename
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with AsyncSessionLocal() as session:
|
|
||||||
# Получаем ВСЕХ пользователей
|
|
||||||
users_res = await session.execute(select(User))
|
|
||||||
users = users_res.scalars().all()
|
|
||||||
|
|
||||||
# Получаем ВСЕ сообщения (без лимита 1000)
|
|
||||||
messages_res = await session.execute(select(Message).order_by(Message.timestamp.asc()))
|
|
||||||
messages = messages_res.scalars().all()
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"export_info": {
|
|
||||||
"date": fmt_local(now_uly),
|
|
||||||
"total_users": len(list(users)),
|
|
||||||
"total_messages": len(list(messages))
|
|
||||||
},
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"user_id": u.user_id, "username": u.username, "first_name": u.first_name,
|
|
||||||
"last_name": u.last_name, "apartment": u.apartment, "phone": u.phone,
|
|
||||||
"verified": u.verified, "rating": u.rating, "level": u.level
|
|
||||||
} for u in users
|
|
||||||
],
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"id": m.id, "user_id": m.user_id, "text": m.text,
|
|
||||||
"date": fmt_local(m.timestamp), "topic": m.topic
|
|
||||||
} for m in messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
with open(filepath, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
# Отдаем файл напрямую в браузер
|
|
||||||
return FileResponse(
|
|
||||||
path=filepath,
|
|
||||||
filename=filename,
|
|
||||||
media_type='application/json'
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Export error: {e}", exc_info=True)
|
|
||||||
return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
|
|
||||||
|
|
||||||
@app.get("/scheduled_posts", response_class=HTMLResponse)
|
@app.get("/scheduled_posts", response_class=HTMLResponse)
|
||||||
async def scheduled_posts_page(request: Request, username: str = Depends(get_current_admin)):
|
async def scheduled_posts_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
return templates.TemplateResponse(request=request, name="scheduled_posts.html", context={"username": username})
|
return templates.TemplateResponse("scheduled_posts.html", {"request": request, "username": username})
|
||||||
|
|
||||||
|
@app.get("/api/scheduled_posts/list")
|
||||||
|
async def api_list_scheduled_posts(username: str = Depends(get_current_admin)):
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
posts = (await session.execute(select(ScheduledPost).order_by(ScheduledPost.scheduled_time.desc()))).scalars().all()
|
||||||
|
posts_data = []
|
||||||
|
for post in posts:
|
||||||
|
posts_data.append({
|
||||||
|
"id": post.id, "text": post.text, "topic_name": post.topic_name,
|
||||||
|
"topic_emoji": post.get_topic_emoji(), "recipients": post.recipients,
|
||||||
|
"scheduled_time": fmt_local(post.scheduled_time), "status": post.status,
|
||||||
|
"status_emoji": post.get_status_emoji(), "has_photo": bool(post.photo_file_id)
|
||||||
|
})
|
||||||
|
return JSONResponse({"success": True, "posts": posts_data})
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# АУДИТ ПОЧТЫ
|
# ДАЙДЖЕСТЫ (V3.1)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
@app.get("/digests", response_class=HTMLResponse)
|
||||||
|
async def digests_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
# Получаем список дайджестов из БД
|
||||||
|
stmt_pending = select(Digest).where(Digest.status.in_(['draft', 'pending_approval'])).order_by(Digest.created_at.desc())
|
||||||
|
pending = (await session.execute(stmt_pending)).scalars().all()
|
||||||
|
|
||||||
|
stmt_archive = select(Digest).where(Digest.status.in_(['approved', 'sent', 'rejected'])).order_by(Digest.created_at.desc()).limit(20)
|
||||||
|
archive = (await session.execute(stmt_archive)).scalars().all()
|
||||||
|
|
||||||
|
return templates.TemplateResponse("digests.html", {
|
||||||
|
"request": request,
|
||||||
|
"username": username,
|
||||||
|
"pending_digests": pending,
|
||||||
|
"archive_digests": archive
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.post("/api/digest/generate")
|
||||||
|
async def api_generate_digest(username: str = Depends(get_current_admin)):
|
||||||
|
from services.digest_service import DigestService
|
||||||
|
try:
|
||||||
|
stats = await DigestService.collect_weekly_stats()
|
||||||
|
digest_text = DigestService.format_digest_text(stats)
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
week_num = now.isocalendar()[1]
|
||||||
|
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
new_digest = Digest(
|
||||||
|
year=now.year,
|
||||||
|
week_number=week_num,
|
||||||
|
period_start=now - timedelta(days=7),
|
||||||
|
period_end=now,
|
||||||
|
digest_text=digest_text,
|
||||||
|
status='draft'
|
||||||
|
)
|
||||||
|
session.add(new_digest)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
return JSONResponse({"success": True, "message": f"Дайджест за {week_num} неделю успешно сгенерирован (черновик)"})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error generating digest: {e}", exc_info=True)
|
||||||
|
return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
|
||||||
|
|
||||||
|
@app.get("/api/digest/{digest_id}")
|
||||||
|
async def api_get_digest(digest_id: int, username: str = Depends(get_current_admin)):
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
digest = await session.get(Digest, digest_id)
|
||||||
|
if not digest: return JSONResponse({"error": "Not found"}, status_code=404)
|
||||||
|
return JSONResponse({
|
||||||
|
"digest": {
|
||||||
|
"id": digest.id,
|
||||||
|
"week": digest.week_number,
|
||||||
|
"year": digest.year,
|
||||||
|
"text": digest.digest_text,
|
||||||
|
"status": digest.status
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ЭКСПОРТ (JSON + GITEA)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
@app.get("/export", response_class=HTMLResponse)
|
||||||
|
async def export_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
|
from services.chat_exporter import ChatExporter
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
exporter = ChatExporter(session)
|
||||||
|
stats = await exporter.get_stats()
|
||||||
|
return templates.TemplateResponse("export.html", {"request": request, "username": username, "export_stats": stats, "fmt_local": fmt_local})
|
||||||
|
|
||||||
|
@app.get("/api/export/json")
|
||||||
|
async def api_export_json(username: str = Depends(get_current_admin)):
|
||||||
|
from services.chat_exporter import ChatExporter
|
||||||
|
try:
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
exporter = ChatExporter(session)
|
||||||
|
result = await exporter.run_full_export()
|
||||||
|
if not result.get('success'): return JSONResponse(status_code=500, content=result)
|
||||||
|
return JSONResponse(result)
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
|
||||||
|
|
||||||
|
@app.get("/exports/{filename}")
|
||||||
|
async def download_export(filename: str, username: str = Depends(get_current_admin)):
|
||||||
|
filepath = BASE_DIR / "exports" / filename
|
||||||
|
if not filepath.exists(): raise HTTPException(status_code=404, detail="File not found")
|
||||||
|
return FileResponse(filepath, filename=filename, media_type='application/json')
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ПОЧТА (EMAIL AUDITOR)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
@app.get("/emails", response_class=HTMLResponse)
|
@app.get("/emails", response_class=HTMLResponse)
|
||||||
async def emails_page(request: Request, username: str = Depends(get_current_admin)):
|
async def emails_page(request: Request, username: str = Depends(get_current_admin)):
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
# Получаем статистику по доменам
|
audit_data = (await session.execute(select(EmailAudit).order_by(EmailAudit.last_email_date.desc()))).scalars().all()
|
||||||
stmt = select(EmailAudit).order_by(EmailAudit.last_email_date.desc())
|
last_log = (await session.execute(select(EmailExportLog).order_by(EmailExportLog.start_time.desc()).limit(1))).scalar_one_or_none()
|
||||||
audit_data = list((await session.execute(stmt)).scalars().all())
|
|
||||||
|
|
||||||
# Получаем логи последнего экспорта
|
|
||||||
stmt_log = select(EmailExportLog).order_by(EmailExportLog.start_time.desc()).limit(1)
|
|
||||||
last_log = (await session.execute(stmt_log)).scalar_one_or_none()
|
|
||||||
|
|
||||||
# Получаем список последних 10 файлов (писем)
|
|
||||||
latest_emails = []
|
latest_emails = []
|
||||||
all_folder = base_dir / "services" / "email_auditor" / "exported_emails" / "all"
|
all_folder = BASE_DIR / "services" / "email_auditor" / "exported_emails" / "all"
|
||||||
if all_folder.exists():
|
if all_folder.exists():
|
||||||
files = sorted(all_folder.glob("*.pdf"), key=lambda x: x.stat().st_mtime, reverse=True)[:10]
|
files = sorted(all_folder.glob("*.pdf"), key=lambda x: x.stat().st_mtime, reverse=True)[:10]
|
||||||
for f in files:
|
for f in files:
|
||||||
name = f.stem
|
match = re.match(r'(\d{4}-\d{2}-\d{2})_([^_]+)_(.*)', f.stem)
|
||||||
# Парсим: YYYY-MM-DD_domain_subject
|
if match: latest_emails.append({"date": match.group(1), "domain": match.group(2), "subject": match.group(3)})
|
||||||
match = re.match(r'(\d{4}-\d{2}-\d{2})_([^_]+)_(.*)', name)
|
|
||||||
if match:
|
|
||||||
date_str, domain, subject = match.groups()
|
|
||||||
latest_emails.append({"date": date_str, "domain": domain, "subject": subject})
|
|
||||||
else:
|
|
||||||
# Вариант 2: domain_date_sender_subject
|
|
||||||
match = re.match(r'([^_]+)_(\d{4}-\d{2}-\d{2})_(.*)', name)
|
|
||||||
if match:
|
|
||||||
domain, date_str, rest = match.groups()
|
|
||||||
latest_emails.append({"date": date_str, "domain": domain, "subject": rest})
|
|
||||||
|
|
||||||
# Проверяем наличие мастер-файла
|
master_pdf_path = BASE_DIR / "services" / "email_auditor" / "exported_emails" / "TOTAL_ARCHIVE_2025-2026.pdf"
|
||||||
master_pdf_path = Path(__file__).parent.parent / "services" / "email_auditor" / "exported_emails" / "TOTAL_ARCHIVE_2025-2026.pdf"
|
last_update = datetime.fromtimestamp(master_pdf_path.stat().st_mtime, tz=timezone.utc) if master_pdf_path.exists() else None
|
||||||
has_master_pdf = master_pdf_path.exists()
|
|
||||||
last_update = None
|
|
||||||
if has_master_pdf:
|
|
||||||
mtime = master_pdf_path.stat().st_mtime
|
|
||||||
# Берем время как UTC
|
|
||||||
last_update = datetime.fromtimestamp(mtime, tz=timezone.utc)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse("emails.html", {
|
||||||
request=request, name="emails.html",
|
"request": request, "username": username, "audit_data": audit_data, "last_log": last_log,
|
||||||
context={
|
"latest_emails": latest_emails, "has_master_pdf": master_pdf_path.exists(), "last_update": last_update, "fmt_local": fmt_local
|
||||||
"username": username, "audit_data": audit_data, "last_log": last_log,
|
})
|
||||||
"latest_emails": latest_emails,
|
|
||||||
"has_master_pdf": has_master_pdf, "last_update": last_update,
|
|
||||||
"fmt_local": fmt_local
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.post("/api/emails/run")
|
@app.post("/api/emails/run")
|
||||||
async def api_run_email_audit(username: str = Depends(get_current_admin)):
|
async def api_run_email_audit(username: str = Depends(get_current_admin)):
|
||||||
"""Запуск аудита почты вручную (фоновым процессом)"""
|
script_path = BASE_DIR / "services" / "email_auditor" / "export_emails_v2.py"
|
||||||
import subprocess
|
venv_python = BASE_DIR / "venv" / "bin" / "python3"
|
||||||
import sys
|
if not script_path.exists(): return JSONResponse(status_code=404, content={"message": "Script not found"})
|
||||||
import os
|
|
||||||
|
|
||||||
# Определяем пути
|
|
||||||
base_dir = Path(__file__).parent.parent
|
|
||||||
script_path = base_dir / "services" / "email_auditor" / "export_emails_v2.py"
|
|
||||||
cwd_path = base_dir / "services" / "email_auditor"
|
|
||||||
|
|
||||||
# Ищем питон в venv
|
|
||||||
venv_python = base_dir / "venv" / "bin" / "python3"
|
|
||||||
if not venv_python.exists():
|
|
||||||
# Попробуем просто python3 если venv не найден
|
|
||||||
venv_python = Path(sys.executable)
|
|
||||||
|
|
||||||
if not script_path.exists():
|
|
||||||
return JSONResponse(status_code=404, content={"message": f"Скрипт не найден: {script_path}"})
|
|
||||||
|
|
||||||
logger.info(f"🚀 Manual audit attempt. Python: {venv_python}, Script: {script_path}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Устанавливаем PYTHONPATH
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["PYTHONPATH"] = str(base_dir)
|
env["PYTHONPATH"] = str(BASE_DIR)
|
||||||
|
process = subprocess.Popen([str(venv_python), str(script_path)], cwd=str(BASE_DIR / "services" / "email_auditor"), env=env, start_new_session=True)
|
||||||
# Открываем лог-файл для записи вывода скрипта
|
return JSONResponse(content={"message": f"Audit started (PID: {process.pid})"})
|
||||||
audit_log_path = base_dir / "logs" / "manual_audit.log"
|
|
||||||
audit_log = open(audit_log_path, "a")
|
|
||||||
|
|
||||||
# Запускаем скрипт в фоне
|
|
||||||
process = subprocess.Popen(
|
|
||||||
[str(venv_python), str(script_path)],
|
|
||||||
cwd=str(cwd_path),
|
|
||||||
env=env,
|
|
||||||
stdout=audit_log,
|
|
||||||
stderr=audit_log,
|
|
||||||
start_new_session=True
|
|
||||||
)
|
|
||||||
return JSONResponse(content={"message": f"Процесс аудита успешно запущен (PID: {process.pid}). Результат придет в Telegram."})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Subprocess error: {e}", exc_info=True)
|
return JSONResponse(status_code=500, content={"message": str(e)})
|
||||||
return JSONResponse(status_code=500, content={"message": f"Ошибка запуска процесса: {str(e)}"})
|
|
||||||
|
|
||||||
def run_web_server(host: str = "0.0.0.0", port: int = 8000):
|
|
||||||
import uvicorn
|
|
||||||
uvicorn.run(app, host=host, port=port)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run_web_server()
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
|
|
|
||||||
1547
web/app_reference.py
Normal file
1547
web/app_reference.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -68,11 +68,11 @@
|
||||||
{% if pending_digests|length > 0 %}
|
{% if pending_digests|length > 0 %}
|
||||||
{% for digest in pending_digests %}
|
{% for digest in pending_digests %}
|
||||||
<div class="pending-card">
|
<div class="pending-card">
|
||||||
<h3>📰 Дайджест #{{ digest.week }}/{{ digest.year }}</h3>
|
<h3>📰 Дайджест #{{ digest.week_number }}/{{ digest.year }}</h3>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
Период: {{ digest.period_label }} | Создан: {{ digest.created_at }} | Статус: <span class="badge badge-pending">{{ digest.status_emoji }} {{ digest.status }}</span>
|
Период: {{ digest.period_label }} | Создан: {{ digest.created_at }} | Статус: <span class="badge badge-pending">{{ digest.status_emoji }} {{ digest.status }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text">{{ digest.text }}</div>
|
<div class="text">{{ digest.digest_text }}</div>
|
||||||
<div style="margin-top: 15px;">
|
<div style="margin-top: 15px;">
|
||||||
<button class="btn btn-approve" onclick="approveDigest({{ digest.id }})">✅ Утвердить</button>
|
<button class="btn btn-approve" onclick="approveDigest({{ digest.id }})">✅ Утвердить</button>
|
||||||
<button class="btn btn-edit" onclick="editDigest({{ digest.id }})">✏️ Редактировать</button>
|
<button class="btn btn-edit" onclick="editDigest({{ digest.id }})">✏️ Редактировать</button>
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for digest in archive_digests %}
|
{% for digest in archive_digests %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>#{{ digest.week }}/{{ digest.year }}</td>
|
<td>#{{ digest.week_number }}/{{ digest.year }}</td>
|
||||||
<td>{{ digest.period_label }}</td>
|
<td>{{ digest.period_label }}</td>
|
||||||
<td><span class="badge badge-{{ digest.status }}">{{ digest.status_emoji }} {{ digest.status }}</span></td>
|
<td><span class="badge badge-{{ digest.status }}">{{ digest.status_emoji }} {{ digest.status }}</span></td>
|
||||||
<td>{{ digest.sent_at }}</td>
|
<td>{{ digest.sent_at }}</td>
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-success">
|
<div class="card border-success">
|
||||||
<div class="card-header border-success">
|
<div class="card-header border-success">
|
||||||
<i class="bi bi-file-earmark-json"></i> ЭКСПОРТ В JSON
|
<i class="bi bi-file-earmark-json"></i> ЭКСПОРТ В JSON & GITEA
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>Экспорт всех пользователей и сообщений в JSON формат.</p>
|
<p>Экспорт ВСЕХ пользователей и сообщений. Файл будет автоматически отправлен в Gitea.</p>
|
||||||
<button class="btn btn-matrix w-100" onclick="exportJSON()">
|
<button class="btn btn-matrix w-100" onclick="exportJSON()">
|
||||||
<i class="bi bi-download"></i> ЭКСПОРТИРОВАТЬ
|
<i class="bi bi-cloud-arrow-up"></i> ЗАПУСТИТЬ ПОЛНЫЙ ЭКСПОРТ
|
||||||
</button>
|
</button>
|
||||||
<div id="exportResult" class="mt-3"></div>
|
<div id="exportResult" class="mt-3"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -25,18 +25,17 @@
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-success">
|
<div class="card border-success">
|
||||||
<div class="card-header border-success">
|
<div class="card-header border-success">
|
||||||
<i class="bi bi-info-circle"></i> ИНФОРМАЦИЯ
|
<i class="bi bi-graph-up"></i> СТАТИСТИКА ВЫГРУЗОК
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>Экспорт включает:</p>
|
<div class="h4 text-matrix mb-3">
|
||||||
<ul class="text-success opacity-75">
|
Всего выгрузок: <span class="badge bg-success">{{ export_stats.total }}</span>
|
||||||
<li>Всех пользователей</li>
|
</div>
|
||||||
<li>Все сообщения</li>
|
<p>Последняя выгрузка:<br>
|
||||||
<li>Даты и время</li>
|
<span class="text-success">{{ fmt_local(export_stats.last_date) if export_stats.last_date else 'Никогда' }}</span></p>
|
||||||
<li>Квартиры и телефоны</li>
|
<hr class="border-success">
|
||||||
</ul>
|
|
||||||
<p class="text-muted small">
|
<p class="text-muted small">
|
||||||
Файл сохраняется в директории <code>exports/</code>
|
Автоматический экспорт выполняется раз в неделю (вс, 03:00) и пушится в репозиторий.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -48,44 +47,37 @@
|
||||||
<script>
|
<script>
|
||||||
async function exportJSON() {
|
async function exportJSON() {
|
||||||
const resultDiv = document.getElementById('exportResult');
|
const resultDiv = document.getElementById('exportResult');
|
||||||
resultDiv.innerHTML = '<div class="alert alert-info"><div class="spinner-border spinner-border-sm me-2"></div>Генерация файла...</div>';
|
const btn = document.querySelector('button[onclick="exportJSON()"]');
|
||||||
|
|
||||||
|
resultDiv.innerHTML = '<div class="alert alert-info"><div class="spinner-border spinner-border-sm me-2"></div>Сбор данных и отправка в Gitea...</div>';
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/export/json');
|
const response = await fetch('/api/export/json');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok && data.success) {
|
||||||
// Получаем файл как blob
|
|
||||||
const blob = await response.blob();
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
// Извлекаем имя файла из заголовка или генерим сами
|
|
||||||
const contentDisposition = response.headers.get('Content-Disposition');
|
|
||||||
let fileName = 'domovoy_export.json';
|
|
||||||
if (contentDisposition && contentDisposition.indexOf('filename=') !== -1) {
|
|
||||||
fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем невидимую ссылку для скачивания
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = fileName;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
a.remove();
|
|
||||||
|
|
||||||
resultDiv.innerHTML = `
|
resultDiv.innerHTML = `
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
✅ Файл успешно сгенерирован и отправлен в загрузки!<br>
|
✅ <b>Успех!</b><br>
|
||||||
<small>Имя файла: ${fileName}</small>
|
Файл: <code>${data.filename}</code><br>
|
||||||
|
Сообщений: ${data.messages}<br>
|
||||||
|
Статус Git: ${data.git ? '🚀 Отправлено в Gitea' : '❌ Ошибка Git'}<br>
|
||||||
|
<br>
|
||||||
|
<a href="/exports/${data.filename}" class="btn btn-outline-success btn-sm w-100 mb-2">
|
||||||
|
<i class="bi bi-download"></i> СКАЧАТЬ ФАЙЛ ЛОКАЛЬНО
|
||||||
|
</a>
|
||||||
|
<small>Страница обновится через 10 сек...</small>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
setTimeout(() => location.reload(), 10000);
|
||||||
} else {
|
} else {
|
||||||
const errorData = await response.json();
|
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка: ${data.error || 'Неизвестно'}</div>`;
|
||||||
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка: ${errorData.error || 'Неизвестная ошибка'}</div>`;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка сети: ${error.message}</div>`;
|
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка сети: ${error.message}</div>`;
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue