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
|
||||
|
||||
# Exports
|
||||
exports/
|
||||
backups/
|
||||
|
||||
# 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.info(f"!!! [STARTUP] main.py execution started. PID: {os.getpid()}")
|
||||
|
||||
async def on_startup(bot: Bot):
|
||||
"""Действия при запуске"""
|
||||
logger.info(f"!!! [STARTUP] on_startup triggered. PID: {os.getpid()}")
|
||||
logger.info('🚀 LKM37 CORE v4.0 запускается...')
|
||||
await init_db()
|
||||
|
||||
scheduler = Scheduler(bot)
|
||||
scheduler.start()
|
||||
|
||||
web_server = WebServerService(port=8000)
|
||||
web_server.start()
|
||||
# Веб-сервер теперь запускается отдельным сервисом (domovoy-web.service)
|
||||
# web_server = WebServerService(port=8000)
|
||||
# web_server.start()
|
||||
|
||||
try:
|
||||
await bot.send_message(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"""
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Dict, List, Any
|
||||
from pathlib import Path
|
||||
from sqlalchemy import select, func, distinct
|
||||
|
|
@ -21,9 +21,10 @@ class ChatAnalytics:
|
|||
self.session = session
|
||||
self.export_dir = Path(EXPORT_DIR)
|
||||
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]:
|
||||
"""Общая статистика чата"""
|
||||
"""Общая статистика чата (Оригинальная логика из Gitea)"""
|
||||
# Всего пользователей
|
||||
stmt = select(func.count(User.user_id))
|
||||
result = await self.session.execute(stmt)
|
||||
|
|
@ -41,56 +42,34 @@ class ChatAnalytics:
|
|||
|
||||
# Сообщения за неделю
|
||||
week_ago = datetime.utcnow() - timedelta(days=7)
|
||||
stmt = (
|
||||
select(func.count(Message.id))
|
||||
.where(Message.timestamp >= week_ago)
|
||||
)
|
||||
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)
|
||||
)
|
||||
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_susp = select(func.count(User.user_id)).where(User.spy_score >= 31).where(User.spy_score <= 60)
|
||||
suspicious_count = (await self.session.execute(stmt_susp)).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
|
||||
stmt_spies = select(func.count(User.user_id)).where(User.spy_score > 60)
|
||||
spies_count = (await self.session.execute(stmt_spies)).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,
|
||||
'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)
|
||||
|
|
@ -101,117 +80,49 @@ class ChatAnalytics:
|
|||
)
|
||||
result = await self.session.execute(stmt)
|
||||
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:
|
||||
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:
|
||||
async def export_to_json(self, full_history: bool = False) -> str:
|
||||
"""
|
||||
Экспорт данных чата в JSON
|
||||
Возвращает путь к файлу
|
||||
Экспорт в JSON.
|
||||
full_history=False -> за неделю (для планировщика)
|
||||
full_history=True -> всё (для кнопки в меню)
|
||||
"""
|
||||
timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
||||
filename = f'chat_export_{timestamp}.json'
|
||||
now_local = datetime.now(self.uly_tz)
|
||||
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
|
||||
|
||||
# Получаем всех пользователей
|
||||
stmt = select(User).order_by(User.join_date.desc())
|
||||
result = await self.session.execute(stmt)
|
||||
users = list(result.scalars().all())
|
||||
# Пользователи
|
||||
users = list((await self.session.execute(select(User).order_by(User.user_id))).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())
|
||||
# Сообщения
|
||||
stmt = select(Message).order_by(Message.timestamp.asc())
|
||||
if not full_history:
|
||||
week_ago = datetime.utcnow() - timedelta(days=7)
|
||||
stmt = stmt.where(Message.timestamp >= week_ago)
|
||||
|
||||
messages = list((await self.session.execute(stmt)).scalars().all())
|
||||
|
||||
# Формируем экспорт
|
||||
export_data = {
|
||||
'export_date': datetime.utcnow().isoformat(),
|
||||
'chat_id': None, # Заполнится при использовании
|
||||
'period': {
|
||||
'from': week_ago.isoformat(),
|
||||
'to': datetime.utcnow().isoformat(),
|
||||
},
|
||||
'export_date': now_local.isoformat(),
|
||||
'is_full_history': full_history,
|
||||
'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
|
||||
]
|
||||
'users': [{
|
||||
'user_id': u.user_id, 'username': u.username, 'first_name': u.first_name,
|
||||
'apartment': u.apartment, 'verified': u.verified, 'rating': u.rating
|
||||
} 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]
|
||||
}
|
||||
|
||||
# Сохраняем
|
||||
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,
|
||||
}
|
||||
|
|
|
|||
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('🛑 Планировщик остановлен')
|
||||
|
||||
async def weekly_export(self):
|
||||
"""Еженедельный экспорт данных"""
|
||||
"""Еженедельный экспорт данных (Новая логика v2026)"""
|
||||
try:
|
||||
from database.db import AsyncSessionLocal
|
||||
from services.chat_exporter import ChatExporter
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
analytics = ChatAnalytics(session)
|
||||
filepath = await analytics.export_to_json()
|
||||
exporter = ChatExporter(session)
|
||||
result = await exporter.run_full_export()
|
||||
|
||||
status_git = "✅ В Gitea" if result.get('git') else "❌ Ошибка Gitea"
|
||||
|
||||
await self.bot.send_message(
|
||||
ADMIN_USER_ID,
|
||||
f'📥 <b>Еженедельный экспорт выполнен</b>\n\n'
|
||||
f'Файл сохранён: {filepath}\n'
|
||||
f'Время: {datetime.utcnow().strftime("%d.%m.%Y %H:%M")}'
|
||||
f'📥 <b>Еженедельный экспорт завершён</b>\n\n'
|
||||
f'📄 Файл: <code>{result.get("filename")}</code>\n'
|
||||
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:
|
||||
logger.error(f'Ошибка экспорта: {e}')
|
||||
await self.bot.send_message(
|
||||
ADMIN_USER_ID,
|
||||
f'❌ <b>Ошибка экспорта</b>\n\n{str(e)}'
|
||||
)
|
||||
await self.bot.send_message(ADMIN_USER_ID, f'❌ <b>Ошибка экспорта</b>\n\n{str(e)}')
|
||||
|
||||
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 json
|
||||
import aiohttp
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import secrets
|
||||
import subprocess
|
||||
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.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
|
@ -15,34 +32,36 @@ from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, File
|
|||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
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
|
||||
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) =====
|
||||
ULY_TZ = timezone(timedelta(hours=4))
|
||||
|
||||
def to_local(dt: datetime) -> datetime:
|
||||
def to_local(dt) -> datetime:
|
||||
if dt is None: return None
|
||||
# Если время без часового пояса (naive), считаем что оно в UTC (как в БД)
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
if isinstance(dt, str):
|
||||
try:
|
||||
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)
|
||||
|
||||
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)
|
||||
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__)
|
||||
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(
|
||||
CORSMiddleware,
|
||||
|
|
@ -52,27 +71,26 @@ app.add_middleware(
|
|||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def debug_exception_handler(request: Request, exc: Exception):
|
||||
logger.error(f"❌ WEB ERROR: {exc}", exc_info=True)
|
||||
return JSONResponse(status_code=500, content={"message": f"Internal Server Error: {str(exc)}"})
|
||||
templates = Jinja2Templates(directory=str(BASE_DIR / "web" / "templates"))
|
||||
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "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")
|
||||
|
||||
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()
|
||||
|
||||
def get_current_admin(credentials: HTTPBasicCredentials = Depends(security)):
|
||||
correct_username = secrets.compare_digest(credentials.username, config.WEB_ADMIN_LOGIN)
|
||||
correct_password = secrets.compare_digest(credentials.password, config.WEB_ADMIN_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
|
||||
|
||||
# ============================================================================
|
||||
# ГЛАВНАЯ
|
||||
# ГЛАВНАЯ (DASHBOARD)
|
||||
# ============================================================================
|
||||
|
||||
@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:
|
||||
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()
|
||||
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_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()
|
||||
pending_count = (await session.execute(select(func.count(VerificationRequest.id)).where(VerificationRequest.status == 'pending'))).scalar()
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request=request, name="dashboard.html",
|
||||
context={
|
||||
"username": username, "users_count": users_count, "verified_count": verified_count,
|
||||
"messages_count": messages_count, "active_ads": active_ads, "active_polls": active_polls,
|
||||
"upcoming_events": upcoming_events, "pending_count": pending_count
|
||||
}
|
||||
)
|
||||
return templates.TemplateResponse("dashboard.html", {
|
||||
"request": request, "username": username,
|
||||
"stats": {
|
||||
"users": users_count, "verified": verified_count,
|
||||
"messages": messages_count, "ads": active_ads,
|
||||
"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)
|
||||
if filter == 'verified': stmt = stmt.where(User.verified == True)
|
||||
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':
|
||||
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()]
|
||||
stmt = stmt.where(User.user_id.in_(ig_user_ids))
|
||||
|
||||
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())
|
||||
total_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()
|
||||
stmt = stmt.order_by(User.user_id.desc()).limit(100)
|
||||
users = list((await session.execute(stmt)).scalars().all())
|
||||
|
||||
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())
|
||||
|
||||
pending_stmt = select(VerificationRequest).where(VerificationRequest.status == 'pending')
|
||||
pending_requests = list((await session.execute(pending_stmt)).scalars().all())
|
||||
pending_stmt = select(VerificationRequest).where(VerificationRequest.status == 'pending').order_by(VerificationRequest.created_at.desc())
|
||||
pending_requests = (await session.execute(pending_stmt)).scalars().all()
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request=request, name="users.html",
|
||||
context={
|
||||
"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
|
||||
}
|
||||
)
|
||||
total_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()
|
||||
active_unverified = (await session.execute(select(func.count(User.user_id)).where(User.verified == False, User.message_count >= 50))).scalar()
|
||||
|
||||
# ============================================================================
|
||||
# РАССЫЛКИ
|
||||
# ============================================================================
|
||||
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)
|
||||
async def broadcast_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
@app.get("/api/user/{user_id}")
|
||||
async def api_get_user(user_id: int, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
broadcasts = list((await session.execute(select(Broadcast).order_by(Broadcast.created_at.desc()).limit(50))).scalars().all())
|
||||
return templates.TemplateResponse(request=request, name="broadcast.html", context={"username": username, "broadcasts": broadcasts})
|
||||
user = await session.get(User, user_id)
|
||||
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")
|
||||
async def api_create_broadcast(
|
||||
text: str = Form(...),
|
||||
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)):
|
||||
@app.post("/api/user/{user_id}/update")
|
||||
async def api_update_user(user_id: int, request: Request, username: str = Depends(get_current_admin)):
|
||||
data = await request.json()
|
||||
async with AsyncSessionLocal() as session:
|
||||
result = await session.execute(text("SELECT id, filename, file_id, file_type FROM assets ORDER BY created_at DESC"))
|
||||
files = [{"id": r[0], "filename": r[1], "file_id": r[2], "file_type": r[3]} for r in result.all()]
|
||||
return JSONResponse({"success": True, "files": files})
|
||||
update_data = {}
|
||||
if 'apartment' in data: update_data['apartment'] = data['apartment'].upper()
|
||||
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")
|
||||
async def api_upload_file(file: UploadFile = File(...), username: str = Depends(get_current_admin)):
|
||||
temp_path = Path("data") / file.filename
|
||||
with open(temp_path, "wb") as f: f.write(await file.read())
|
||||
file_ext = Path(file.filename).suffix.lower()
|
||||
is_photo = file_ext in ['.jpg', '.jpeg', '.png', '.webp']
|
||||
tg_method = "sendPhoto" if is_photo else "sendDocument"
|
||||
@app.post("/api/user/{user_id}/verify")
|
||||
async def api_verify_user(user_id: int, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
await session.execute(update(User).where(User.user_id == user_id).values(verified=True, verification_date=datetime.utcnow()))
|
||||
await session.commit()
|
||||
return JSONResponse({"success": True})
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/{tg_method}"
|
||||
data = aiohttp.FormData()
|
||||
data.add_field('chat_id', str(config.ADMIN_USER_ID))
|
||||
data.add_field('photo' if is_photo else 'document', open(temp_path, 'rb'))
|
||||
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.post("/api/user/{user_id}/unverify")
|
||||
async def api_unverify_user(user_id: int, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
await session.execute(update(User).where(User.user_id == user_id).values(verified=False))
|
||||
await session.commit()
|
||||
return JSONResponse({"success": True})
|
||||
|
||||
# ============================================================================
|
||||
# ОСТАЛЬНЫЕ РОУТЫ (ЗАГЛУШКИ / БАЗОВЫЕ)
|
||||
# ВЕРИФИКАЦИЯ
|
||||
# ============================================================================
|
||||
|
||||
@app.get("/verification", response_class=HTMLResponse)
|
||||
async def verification_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
pending = list((await session.execute(select(VerificationRequest).where(VerificationRequest.status == 'pending'))).scalars().all())
|
||||
unverified = list((await session.execute(select(User).where(User.verified == False).limit(50))).scalars().all())
|
||||
return templates.TemplateResponse(request=request, name="verification.html", context={"username": username, "pending_requests": pending, "unverified_users": unverified})
|
||||
pending = (await session.execute(select(VerificationRequest).where(VerificationRequest.status == 'pending').order_by(VerificationRequest.created_at.desc()))).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("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)
|
||||
async def phones_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
services = list((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})
|
||||
|
||||
@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})
|
||||
services = (await session.execute(select(Service).order_by(Service.category, Service.name))).scalars().all()
|
||||
return templates.TemplateResponse("phones.html", {"request": request, "username": username, "services": services})
|
||||
|
||||
@app.get("/ads", response_class=HTMLResponse)
|
||||
async def ads_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
ads = list((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})
|
||||
ads = (await session.execute(select(Ad).order_by(Ad.created_at.desc()).limit(100))).scalars().all()
|
||||
return templates.TemplateResponse("ads.html", {"request": request, "username": username, "ads": ads})
|
||||
|
||||
@app.get("/polls", response_class=HTMLResponse)
|
||||
async def polls_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
polls = list((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})
|
||||
polls = (await session.execute(select(Poll).order_by(Poll.created_at.desc()).limit(50))).scalars().all()
|
||||
return templates.TemplateResponse("polls.html", {"request": request, "username": username, "polls": polls})
|
||||
|
||||
@app.get("/events", response_class=HTMLResponse)
|
||||
async def events_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
events = list((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})
|
||||
events = (await session.execute(select(Event).order_by(Event.event_date.desc()).limit(50))).scalars().all()
|
||||
return templates.TemplateResponse("events.html", {"request": request, "username": username, "events": events})
|
||||
|
||||
@app.get("/digests", response_class=HTMLResponse)
|
||||
async def digests_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
return templates.TemplateResponse(request=request, name="digests.html", context={"username": username, "pending_digests": [], "archive_digests": []})
|
||||
@app.get("/schedules", response_class=HTMLResponse)
|
||||
async def schedules_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
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)):
|
||||
return templates.TemplateResponse(request=request, name="export.html", context={"username": username})
|
||||
# ============================================================================
|
||||
# РАССЫЛКИ (SMART BROADCAST)
|
||||
# ============================================================================
|
||||
|
||||
@app.get("/api/export/json")
|
||||
async def api_export_json(username: str = Depends(get_current_admin)):
|
||||
"""Экспорт данных в JSON с прямой отдачей файла"""
|
||||
import json
|
||||
from database.db import AsyncSessionLocal
|
||||
@app.get("/broadcast", response_class=HTMLResponse)
|
||||
async def broadcast_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
broadcasts = (await session.execute(select(Broadcast).order_by(Broadcast.created_at.desc()).limit(50))).scalars().all()
|
||||
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)
|
||||
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)
|
||||
async def emails_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Получаем статистику по доменам
|
||||
stmt = select(EmailAudit).order_by(EmailAudit.last_email_date.desc())
|
||||
audit_data = list((await session.execute(stmt)).scalars().all())
|
||||
audit_data = (await session.execute(select(EmailAudit).order_by(EmailAudit.last_email_date.desc()))).scalars().all()
|
||||
last_log = (await session.execute(select(EmailExportLog).order_by(EmailExportLog.start_time.desc()).limit(1))).scalar_one_or_none()
|
||||
|
||||
# Получаем логи последнего экспорта
|
||||
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 = []
|
||||
all_folder = base_dir / "services" / "email_auditor" / "exported_emails" / "all"
|
||||
all_folder = BASE_DIR / "services" / "email_auditor" / "exported_emails" / "all"
|
||||
if all_folder.exists():
|
||||
files = sorted(all_folder.glob("*.pdf"), key=lambda x: x.stat().st_mtime, reverse=True)[:10]
|
||||
for f in files:
|
||||
name = f.stem
|
||||
# Парсим: YYYY-MM-DD_domain_subject
|
||||
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})
|
||||
match = re.match(r'(\d{4}-\d{2}-\d{2})_([^_]+)_(.*)', f.stem)
|
||||
if match: latest_emails.append({"date": match.group(1), "domain": match.group(2), "subject": match.group(3)})
|
||||
|
||||
# Проверяем наличие мастер-файла
|
||||
master_pdf_path = Path(__file__).parent.parent / "services" / "email_auditor" / "exported_emails" / "TOTAL_ARCHIVE_2025-2026.pdf"
|
||||
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)
|
||||
master_pdf_path = BASE_DIR / "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
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request=request, name="emails.html",
|
||||
context={
|
||||
"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
|
||||
}
|
||||
)
|
||||
return templates.TemplateResponse("emails.html", {
|
||||
"request": request, "username": username, "audit_data": audit_data, "last_log": last_log,
|
||||
"latest_emails": latest_emails, "has_master_pdf": master_pdf_path.exists(), "last_update": last_update, "fmt_local": fmt_local
|
||||
})
|
||||
|
||||
@app.post("/api/emails/run")
|
||||
async def api_run_email_audit(username: str = Depends(get_current_admin)):
|
||||
"""Запуск аудита почты вручную (фоновым процессом)"""
|
||||
import subprocess
|
||||
import sys
|
||||
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}")
|
||||
|
||||
script_path = BASE_DIR / "services" / "email_auditor" / "export_emails_v2.py"
|
||||
venv_python = BASE_DIR / "venv" / "bin" / "python3"
|
||||
if not script_path.exists(): return JSONResponse(status_code=404, content={"message": "Script not found"})
|
||||
try:
|
||||
# Устанавливаем PYTHONPATH
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = str(base_dir)
|
||||
|
||||
# Открываем лог-файл для записи вывода скрипта
|
||||
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."})
|
||||
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})"})
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Subprocess error: {e}", exc_info=True)
|
||||
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)
|
||||
return JSONResponse(status_code=500, content={"message": str(e)})
|
||||
|
||||
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 %}
|
||||
{% for digest in pending_digests %}
|
||||
<div class="pending-card">
|
||||
<h3>📰 Дайджест #{{ digest.week }}/{{ digest.year }}</h3>
|
||||
<h3>📰 Дайджест #{{ digest.week_number }}/{{ digest.year }}</h3>
|
||||
<div class="meta">
|
||||
Период: {{ digest.period_label }} | Создан: {{ digest.created_at }} | Статус: <span class="badge badge-pending">{{ digest.status_emoji }} {{ digest.status }}</span>
|
||||
</div>
|
||||
<div class="text">{{ digest.text }}</div>
|
||||
<div class="text">{{ digest.digest_text }}</div>
|
||||
<div style="margin-top: 15px;">
|
||||
<button class="btn btn-approve" onclick="approveDigest({{ digest.id }})">✅ Утвердить</button>
|
||||
<button class="btn btn-edit" onclick="editDigest({{ digest.id }})">✏️ Редактировать</button>
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
<tbody>
|
||||
{% for digest in archive_digests %}
|
||||
<tr>
|
||||
<td>#{{ digest.week }}/{{ digest.year }}</td>
|
||||
<td>#{{ digest.week_number }}/{{ digest.year }}</td>
|
||||
<td>{{ digest.period_label }}</td>
|
||||
<td><span class="badge badge-{{ digest.status }}">{{ digest.status_emoji }} {{ digest.status }}</span></td>
|
||||
<td>{{ digest.sent_at }}</td>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@
|
|||
<div class="col-md-6">
|
||||
<div class="card 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 class="card-body">
|
||||
<p>Экспорт всех пользователей и сообщений в JSON формат.</p>
|
||||
<p>Экспорт ВСЕХ пользователей и сообщений. Файл будет автоматически отправлен в Gitea.</p>
|
||||
<button class="btn btn-matrix w-100" onclick="exportJSON()">
|
||||
<i class="bi bi-download"></i> ЭКСПОРТИРОВАТЬ
|
||||
<i class="bi bi-cloud-arrow-up"></i> ЗАПУСТИТЬ ПОЛНЫЙ ЭКСПОРТ
|
||||
</button>
|
||||
<div id="exportResult" class="mt-3"></div>
|
||||
</div>
|
||||
|
|
@ -25,18 +25,17 @@
|
|||
<div class="col-md-6">
|
||||
<div class="card border-success">
|
||||
<div class="card-header border-success">
|
||||
<i class="bi bi-info-circle"></i> ИНФОРМАЦИЯ
|
||||
<i class="bi bi-graph-up"></i> СТАТИСТИКА ВЫГРУЗОК
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Экспорт включает:</p>
|
||||
<ul class="text-success opacity-75">
|
||||
<li>Всех пользователей</li>
|
||||
<li>Все сообщения</li>
|
||||
<li>Даты и время</li>
|
||||
<li>Квартиры и телефоны</li>
|
||||
</ul>
|
||||
<div class="h4 text-matrix mb-3">
|
||||
Всего выгрузок: <span class="badge bg-success">{{ export_stats.total }}</span>
|
||||
</div>
|
||||
<p>Последняя выгрузка:<br>
|
||||
<span class="text-success">{{ fmt_local(export_stats.last_date) if export_stats.last_date else 'Никогда' }}</span></p>
|
||||
<hr class="border-success">
|
||||
<p class="text-muted small">
|
||||
Файл сохраняется в директории <code>exports/</code>
|
||||
Автоматический экспорт выполняется раз в неделю (вс, 03:00) и пушится в репозиторий.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,44 +47,37 @@
|
|||
<script>
|
||||
async function exportJSON() {
|
||||
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 {
|
||||
const response = await fetch('/api/export/json');
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
// Получаем файл как 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();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="alert alert-success">
|
||||
✅ Файл успешно сгенерирован и отправлен в загрузки!<br>
|
||||
<small>Имя файла: ${fileName}</small>
|
||||
✅ <b>Успех!</b><br>
|
||||
Файл: <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>
|
||||
`;
|
||||
setTimeout(() => location.reload(), 10000);
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка: ${errorData.error || 'Неизвестная ошибка'}</div>`;
|
||||
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка: ${data.error || 'Неизвестно'}</div>`;
|
||||
}
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка сети: ${error.message}</div>`;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue