Data export, warnings' improvements, bug fixes #35
@ -213,16 +213,16 @@
|
|||||||
"general"
|
"general"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"application": {
|
"export": {
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"admins",
|
"admins",
|
||||||
"group_admins"
|
"group_admins"
|
||||||
],
|
],
|
||||||
"modules": [
|
"modules": [
|
||||||
"applications"
|
"general"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"applications": {
|
"application": {
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"admins",
|
"admins",
|
||||||
"group_admins"
|
"group_admins"
|
||||||
|
@ -12,8 +12,8 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True)
|
|||||||
|
|
||||||
# Importing
|
# Importing
|
||||||
from modules.commands.application import *
|
from modules.commands.application import *
|
||||||
from modules.commands.applications import *
|
|
||||||
from modules.commands.cancel import *
|
from modules.commands.cancel import *
|
||||||
|
from modules.commands.export import *
|
||||||
from modules.commands.identify import *
|
from modules.commands.identify import *
|
||||||
from modules.commands.issue import *
|
from modules.commands.issue import *
|
||||||
from modules.commands.label import *
|
from modules.commands.label import *
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
"no_warnings": "Користувач **{0}** (`{1}`) не має попереджень",
|
"no_warnings": "Користувач **{0}** (`{1}`) не має попереджень",
|
||||||
"no_user_warnings": "Не знайдено користувачів за запитом **{0}**",
|
"no_user_warnings": "Не знайдено користувачів за запитом **{0}**",
|
||||||
"syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`",
|
"syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`",
|
||||||
|
"syntax_export": "Неправильний синтаксис!\nТреба: `/export applications/warnings/sponsorships/bans`",
|
||||||
"message_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.",
|
"message_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.",
|
||||||
"message_sent": "Повідомлення надіслано",
|
"message_sent": "Повідомлення надіслано",
|
||||||
"message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.",
|
"message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.",
|
||||||
@ -294,13 +295,13 @@
|
|||||||
"rules_additional": "Додаткові правила, які несуть рекомендаційний характер та не мають явних покарань за порушення:\n1️⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2️⃣) У чаті немає заборони на російський контент. Але майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3️⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4️⃣) Поважайте авторські права контент-мейкерів. Якщо ви знаходите арт, анімацію, музику тощо на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом /search на повідомлення із артом.\n5️⃣) В особливо критичних ситуаціях порушник може отримати бан або бути повністю видаленим із чату, без попереджень.\n6️⃣) Якщо з кимось із учасників у вас трапиться якесь непорозуміння і вам неприємно буде перебувати в чаті один з одним (навіть, якщо конфлікт стався з кимось із адміністраторів) - напишіть мені в особисті повідомлення @Chirkopol. Я, як засновник чату та головний адміністратор, найбільше зацікавлений у збереженні цілісності чату та розвитку нашого ком'юніті, і я зроблю все, що в моїх силах, щоб допомогти вирішити вашу ситуацію.",
|
"rules_additional": "Додаткові правила, які несуть рекомендаційний характер та не мають явних покарань за порушення:\n1️⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2️⃣) У чаті немає заборони на російський контент. Але майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3️⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4️⃣) Поважайте авторські права контент-мейкерів. Якщо ви знаходите арт, анімацію, музику тощо на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом /search на повідомлення із артом.\n5️⃣) В особливо критичних ситуаціях порушник може отримати бан або бути повністю видаленим із чату, без попереджень.\n6️⃣) Якщо з кимось із учасників у вас трапиться якесь непорозуміння і вам неприємно буде перебувати в чаті один з одним (навіть, якщо конфлікт стався з кимось із адміністраторів) - напишіть мені в особисті повідомлення @Chirkopol. Я, як засновник чату та головний адміністратор, найбільше зацікавлений у збереженні цілісності чату та розвитку нашого ком'юніті, і я зроблю все, що в моїх силах, щоб допомогти вирішити вашу ситуацію.",
|
||||||
"commands": {
|
"commands": {
|
||||||
"application": "Переглянути анкету користувача",
|
"application": "Переглянути анкету користувача",
|
||||||
"applications": "Отримати всі анкети як JSON",
|
|
||||||
"cancel": "Відмінити актуальну дію",
|
"cancel": "Відмінити актуальну дію",
|
||||||
"identify": "Дізнатись дані про користувача за айді",
|
"identify": "Дізнатись дані про користувача за айді",
|
||||||
"issue": "Задачі для покращення бота",
|
"issue": "Задачі для покращення бота",
|
||||||
"label": "Встановити нікнейм користувачу",
|
"label": "Встановити нікнейм користувачу",
|
||||||
"message": "Надіслати користувачу повідомлення",
|
"message": "Надіслати користувачу повідомлення",
|
||||||
"nearby": "Показати користувачів поблизу",
|
"nearby": "Показати користувачів поблизу",
|
||||||
|
"export": "Експортувати дані як CSV та JSON",
|
||||||
"reapply": "Повторно заповнити анкету",
|
"reapply": "Повторно заповнити анкету",
|
||||||
"reboot": "Перезапустити бота",
|
"reboot": "Перезапустити бота",
|
||||||
"resetcommands": "Відреєструвати всі команди",
|
"resetcommands": "Відреєструвати всі команди",
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
from os import sep, makedirs, remove
|
|
||||||
from uuid import uuid1
|
|
||||||
from app import app
|
|
||||||
from pyrogram import filters
|
|
||||||
from pyrogram.types import Message
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram.enums.chat_action import ChatAction
|
|
||||||
from modules.logging import logWrite
|
|
||||||
from modules.utils import should_quote, jsonSave
|
|
||||||
from modules.database import col_applications
|
|
||||||
from modules import custom_filters
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(
|
|
||||||
custom_filters.enabled_applications
|
|
||||||
& ~filters.scheduled
|
|
||||||
& filters.command(["applications"], prefixes=["/"])
|
|
||||||
& custom_filters.admin
|
|
||||||
)
|
|
||||||
async def cmd_applications(app: Client, msg: Message):
|
|
||||||
logWrite(f"Admin {msg.from_user.id} requested export of a database")
|
|
||||||
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
|
|
||||||
filename = uuid1()
|
|
||||||
output = []
|
|
||||||
for entry in col_applications.find():
|
|
||||||
del entry["_id"]
|
|
||||||
entry["date"] = entry["date"].strftime("%d.%m.%Y, %H:%M")
|
|
||||||
entry["application"]["2"] = entry["application"]["2"].strftime(
|
|
||||||
"%d.%m.%Y, %H:%M"
|
|
||||||
)
|
|
||||||
output.append(entry)
|
|
||||||
makedirs("tmp", exist_ok=True)
|
|
||||||
jsonSave(output, f"tmp{sep}{filename}.json")
|
|
||||||
await msg.reply_document(
|
|
||||||
document=f"tmp{sep}{filename}.json",
|
|
||||||
file_name="applications",
|
|
||||||
quote=should_quote(msg),
|
|
||||||
)
|
|
||||||
remove(f"tmp{sep}{filename}.json")
|
|
182
modules/commands/export.py
Normal file
182
modules/commands/export.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
from csv import QUOTE_ALL
|
||||||
|
from os import makedirs, path, remove
|
||||||
|
from uuid import uuid1
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
from aiocsv.writers import AsyncDictWriter
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.client import Client
|
||||||
|
from pyrogram.enums.chat_action import ChatAction
|
||||||
|
from pyrogram.types import Message
|
||||||
|
from ujson import dumps
|
||||||
|
|
||||||
|
from app import app
|
||||||
|
from modules import custom_filters
|
||||||
|
from modules.database import col_applications, col_sponsorships, col_warnings
|
||||||
|
from modules.logging import logWrite
|
||||||
|
from modules.utils import locale, should_quote
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(
|
||||||
|
custom_filters.enabled_general
|
||||||
|
& ~filters.scheduled
|
||||||
|
& filters.command(["export"], prefixes=["/"])
|
||||||
|
& custom_filters.admin
|
||||||
|
)
|
||||||
|
async def cmd_export(app: Client, msg: Message):
|
||||||
|
if len(msg.command) <= 1:
|
||||||
|
await msg.reply_text(
|
||||||
|
locale("syntax_export", "message", locale=msg.from_user),
|
||||||
|
quote=should_quote(msg),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
selection = msg.command[1].lower()
|
||||||
|
|
||||||
|
if selection not in ["applications", "warnings", "sponsorships", "bans"]:
|
||||||
|
await msg.reply_text(
|
||||||
|
locale("syntax_export", "message", locale=msg.from_user),
|
||||||
|
quote=should_quote(msg),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logWrite(f"Admin {msg.from_user.id} requested export of {selection}")
|
||||||
|
|
||||||
|
makedirs("tmp", exist_ok=True)
|
||||||
|
temp_file = path.join("tmp", str(uuid1()))
|
||||||
|
|
||||||
|
await app.send_chat_action(msg.chat.id, ChatAction.TYPING)
|
||||||
|
|
||||||
|
output_csv = []
|
||||||
|
output_json = []
|
||||||
|
|
||||||
|
if selection == "applications":
|
||||||
|
header_csv = [
|
||||||
|
"user",
|
||||||
|
"date",
|
||||||
|
"admin",
|
||||||
|
"question_1",
|
||||||
|
"question_2",
|
||||||
|
"question_3",
|
||||||
|
"question_4",
|
||||||
|
"question_5",
|
||||||
|
"question_6",
|
||||||
|
"question_7",
|
||||||
|
"question_8",
|
||||||
|
"question_9",
|
||||||
|
"question_10",
|
||||||
|
]
|
||||||
|
|
||||||
|
for entry in list(col_applications.find()):
|
||||||
|
del entry["_id"]
|
||||||
|
entry["date"] = entry["date"].isoformat()
|
||||||
|
entry["application"]["2"] = entry["application"]["2"].isoformat()
|
||||||
|
output_json.append(entry)
|
||||||
|
|
||||||
|
for entry in list(col_applications.find()):
|
||||||
|
del entry["_id"]
|
||||||
|
entry["date"] = entry["date"].isoformat()
|
||||||
|
entry["application"]["2"] = entry["application"]["2"].isoformat()
|
||||||
|
for index, value in enumerate(entry["application"]):
|
||||||
|
entry[f"question_{index+1}"] = entry["application"][value]
|
||||||
|
entry[
|
||||||
|
"question_3"
|
||||||
|
] = f"{entry['application']['3']['name']} ({entry['application']['3']['adminName1']}, {entry['application']['3']['countryName']})"
|
||||||
|
del entry["application"]
|
||||||
|
output_csv.append(entry)
|
||||||
|
|
||||||
|
elif selection == "warnings":
|
||||||
|
header_csv = [
|
||||||
|
"id",
|
||||||
|
"user",
|
||||||
|
"admin",
|
||||||
|
"date",
|
||||||
|
"reason",
|
||||||
|
"active",
|
||||||
|
"revoke_date",
|
||||||
|
]
|
||||||
|
|
||||||
|
for entry in list(col_warnings.find()):
|
||||||
|
for k, v in list(entry.items()):
|
||||||
|
entry[{"_id": "id"}.get(k, k)] = entry.pop(k)
|
||||||
|
entry["id"] = str(entry["id"])
|
||||||
|
entry["date"] = entry["date"].isoformat()
|
||||||
|
if entry["revoke_date"] is not None:
|
||||||
|
entry["revoke_date"] = entry["revoke_date"].isoformat()
|
||||||
|
output_json.append(entry)
|
||||||
|
output_csv.append(entry)
|
||||||
|
|
||||||
|
elif selection == "sponsorships":
|
||||||
|
header_csv = [
|
||||||
|
"user",
|
||||||
|
"date",
|
||||||
|
"admin",
|
||||||
|
"streamer",
|
||||||
|
"expires",
|
||||||
|
"proof",
|
||||||
|
"label",
|
||||||
|
]
|
||||||
|
|
||||||
|
for entry in list(col_sponsorships.find()):
|
||||||
|
del entry["_id"]
|
||||||
|
entry["date"] = entry["date"].isoformat()
|
||||||
|
entry["sponsorship"]["expires"] = entry["sponsorship"][
|
||||||
|
"expires"
|
||||||
|
].isoformat()
|
||||||
|
output_json.append(entry)
|
||||||
|
|
||||||
|
for entry in list(col_sponsorships.find()):
|
||||||
|
del entry["_id"]
|
||||||
|
entry["date"] = entry["date"].isoformat()
|
||||||
|
entry["sponsorship"]["expires"] = entry["sponsorship"][
|
||||||
|
"expires"
|
||||||
|
].isoformat()
|
||||||
|
for index, value in enumerate(entry["sponsorship"]):
|
||||||
|
entry[value] = entry["sponsorship"][value]
|
||||||
|
del entry["sponsorship"]
|
||||||
|
output_csv.append(entry)
|
||||||
|
|
||||||
|
elif selection == "bans":
|
||||||
|
header_csv = ["user", "admin", "date"]
|
||||||
|
|
||||||
|
for entry in list(col_warnings.find()):
|
||||||
|
del entry["id"]
|
||||||
|
entry["date"] = entry["date"].isoformat()
|
||||||
|
output_json.append(entry)
|
||||||
|
output_csv.append(entry)
|
||||||
|
|
||||||
|
# Saving CSV
|
||||||
|
async with aiofiles.open(temp_file + ".csv", mode="w", encoding="utf-8") as file:
|
||||||
|
writer = AsyncDictWriter(file, header_csv, restval="NULL", quoting=QUOTE_ALL)
|
||||||
|
await writer.writeheader()
|
||||||
|
await writer.writerows(output_csv)
|
||||||
|
|
||||||
|
# Saving JSON
|
||||||
|
async with aiofiles.open(temp_file + ".json", mode="w", encoding="utf-8") as file:
|
||||||
|
await file.write(
|
||||||
|
dumps(
|
||||||
|
output_json, ensure_ascii=False, escape_forward_slashes=False, indent=4
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sending CSV
|
||||||
|
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
|
||||||
|
await msg.reply_document(
|
||||||
|
document=temp_file + ".csv",
|
||||||
|
file_name=f"{selection}.csv",
|
||||||
|
quote=should_quote(msg),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sending JSON
|
||||||
|
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
|
||||||
|
await msg.reply_document(
|
||||||
|
document=temp_file + ".json",
|
||||||
|
file_name=f"{selection}.json",
|
||||||
|
quote=should_quote(msg),
|
||||||
|
)
|
||||||
|
|
||||||
|
del output_csv, output_json
|
||||||
|
|
||||||
|
# Removing temp files
|
||||||
|
remove(temp_file + ".csv")
|
||||||
|
remove(temp_file + ".json")
|
@ -11,3 +11,5 @@ ujson~=5.7.0
|
|||||||
ftfy~=6.1.1
|
ftfy~=6.1.1
|
||||||
xmltodict==0.13.0
|
xmltodict==0.13.0
|
||||||
convopyro==0.5
|
convopyro==0.5
|
||||||
|
aiocsv==1.2.3
|
||||||
|
aiofiles~=23.1.0
|
Reference in New Issue
Block a user