diff --git a/config_example.json b/config_example.json index ba435f8..0b3e093 100644 --- a/config_example.json +++ b/config_example.json @@ -213,16 +213,16 @@ "general" ] }, - "application": { + "export": { "permissions": [ "admins", "group_admins" ], "modules": [ - "applications" + "general" ] }, - "applications": { + "application": { "permissions": [ "admins", "group_admins" diff --git a/holochecker.py b/holochecker.py index c7b0ad0..c9c65e4 100644 --- a/holochecker.py +++ b/holochecker.py @@ -12,8 +12,8 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True) # Importing from modules.commands.application import * -from modules.commands.applications import * from modules.commands.cancel import * +from modules.commands.export import * from modules.commands.identify import * from modules.commands.issue import * from modules.commands.label import * diff --git a/locale/uk.json b/locale/uk.json index b6aaecd..9bdfcb5 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -81,6 +81,7 @@ "no_warnings": "Користувач **{0}** (`{1}`) не має попереджень", "no_user_warnings": "Не знайдено користувачів за запитом **{0}**", "syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`", + "syntax_export": "Неправильний синтаксис!\nТреба: `/export applications/warnings/sponsorships/bans`", "message_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.", "message_sent": "Повідомлення надіслано", "message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.", @@ -294,13 +295,13 @@ "rules_additional": "Додаткові правила, які несуть рекомендаційний характер та не мають явних покарань за порушення:\n1️⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2️⃣) У чаті немає заборони на російський контент. Але майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3️⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4️⃣) Поважайте авторські права контент-мейкерів. Якщо ви знаходите арт, анімацію, музику тощо на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом /search на повідомлення із артом.\n5️⃣) В особливо критичних ситуаціях порушник може отримати бан або бути повністю видаленим із чату, без попереджень.\n6️⃣) Якщо з кимось із учасників у вас трапиться якесь непорозуміння і вам неприємно буде перебувати в чаті один з одним (навіть, якщо конфлікт стався з кимось із адміністраторів) - напишіть мені в особисті повідомлення @Chirkopol. Я, як засновник чату та головний адміністратор, найбільше зацікавлений у збереженні цілісності чату та розвитку нашого ком'юніті, і я зроблю все, що в моїх силах, щоб допомогти вирішити вашу ситуацію.", "commands": { "application": "Переглянути анкету користувача", - "applications": "Отримати всі анкети як JSON", "cancel": "Відмінити актуальну дію", "identify": "Дізнатись дані про користувача за айді", "issue": "Задачі для покращення бота", "label": "Встановити нікнейм користувачу", "message": "Надіслати користувачу повідомлення", "nearby": "Показати користувачів поблизу", + "export": "Експортувати дані як CSV та JSON", "reapply": "Повторно заповнити анкету", "reboot": "Перезапустити бота", "resetcommands": "Відреєструвати всі команди", diff --git a/modules/commands/applications.py b/modules/commands/applications.py deleted file mode 100644 index 7e2edbf..0000000 --- a/modules/commands/applications.py +++ /dev/null @@ -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") diff --git a/modules/commands/export.py b/modules/commands/export.py new file mode 100644 index 0000000..9037f2d --- /dev/null +++ b/modules/commands/export.py @@ -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") diff --git a/requirements.txt b/requirements.txt index 5146bd7..7e6b1c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,6 @@ starlette==0.26.1 ujson~=5.7.0 ftfy~=6.1.1 xmltodict==0.13.0 -convopyro==0.5 \ No newline at end of file +convopyro==0.5 +aiocsv==1.2.3 +aiofiles~=23.1.0 \ No newline at end of file