From fe6d2514c703338cd5143356b717c43125d01837 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 2 Jan 2023 11:17:17 +0100 Subject: [PATCH 01/19] Updated to-do --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d462fee..42d5a35 100644 --- a/README.md +++ b/README.md @@ -104,9 +104,9 @@ After all of that you're good to go! Happy using :) ## To-Do -* [ ] Check sponsorship on Holo girls +* [x] Check sponsorship on Holo girls * [ ] Stats and infographic -* [ ] /nearby command +* [x] /nearby command * [ ] Check group members without completed application * [x] Complete messenger between user and admins * [x] Get application by id and user_id \ No newline at end of file From a82adc4d1fe12b089e6f8dbb1404e6cb8426ef93 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 2 Jan 2023 11:18:40 +0100 Subject: [PATCH 02/19] Changed command output formatting --- modules/commands/nearby.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/commands/nearby.py b/modules/commands/nearby.py index 890a314..c325564 100644 --- a/modules/commands/nearby.py +++ b/modules/commands/nearby.py @@ -45,9 +45,9 @@ async def cmd_nearby(app: Client, msg: Message): user = col_users.find_one( {"user": entry["user"]} ) if user is not None: if user["tg_username"] not in [None, "None", ""]: # Check if user has any name - output.append(f'• {user["tg_name"]} (@{user["tg_username"]}):\n {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}') + output.append(f'• **{user["tg_name"]}** (@{user["tg_username"]}):\n - {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}') else: - output.append(f'• {user["tg_name"]}:\n {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}') + output.append(f'• **{user["tg_name"]}**:\n - {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}') logWrite(f"{holo_user.id} tried to find someone nearby {location[1]} {location[0]} in the radius of {configGet('search_radius')} kilometers") From ba13f36769a70a93c317eb249e4d0aa2b98d8ed9 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 2 Jan 2023 14:19:35 +0100 Subject: [PATCH 03/19] Updated locale for sponsorship --- classes/holo_user.py | 18 +++++++++++++----- locale/uk.json | 6 +++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/classes/holo_user.py b/classes/holo_user.py index 257e4ec..b36dc65 100644 --- a/classes/holo_user.py +++ b/classes/holo_user.py @@ -286,10 +286,15 @@ class HoloUser(): if len(label) > 16: raise LabelTooLongError(label) self.label = label - self.set("label", label) - await app.promote_chat_member(configGet("destination_group"), self.id, privileges=ChatPrivileges(can_pin_messages=True, can_manage_video_chats=True)) - if not await isAnAdmin(self.id): - await app.set_administrator_title(configGet("destination_group"), self.id, label) + try: + await app.promote_chat_member(configGet("destination_group"), self.id, privileges=ChatPrivileges(can_pin_messages=True, can_manage_video_chats=True)) + if not await isAnAdmin(self.id): + await app.set_administrator_title(configGet("destination_group"), self.id, label) + self.set("label", label) + except bad_request_400.UserCreator: + logWrite(f"Could not set {self.id}'s title to '{self.label}' because of bad_request_400.UserCreator") + except bad_request_400.ChatAdminRequired: + logWrite(f"Could not set {self.id}'s title to '{self.label}' because of bad_request_400.ChatAdminRequired") async def label_reset(self, chat: Chat) -> None: """Reset label in destination group @@ -346,6 +351,9 @@ class HoloUser(): if col_tmp.find_one({"user": self.id, "type": "application"}) is None: + if self.sponsorship_state()[0] == "fill": + return + col_tmp.insert_one( document=DefaultApplicationTemp(self.id).dict ) @@ -524,7 +532,7 @@ class HoloUser(): elif stage == 4: if len(query) > 16: - await msg.reply_text(locale("label_too_long", "message")) + await msg.reply_text(locale("label_too_long", "message"), reply_markup=ForceReply(placeholder=str(locale("sponsor4", "force_reply", locale=self.locale)))) return progress["sponsorship"]["label"] = query col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "complete": True}}) diff --git a/locale/uk.json b/locale/uk.json index 12c5bed..9b8f591 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -26,8 +26,8 @@ "sponsor2": "До якої дати (`ДД.ММ.РРРР`) підписка?", "sponsor2_invalid": "Будь ласка, введи дату формату `ДД.ММ.РРРР`", "sponsor2_past": "Вказана дата знаходиться в минулому. Будь ласка, вкажіть правильний термін дії підписки", - "sponsor3": "Будь ласка, надішли одне фото для підтвердження дійсності підписки", - "sponsor4": "Яку роль ти бажаєш отримати?", + "sponsor3": "Будь ласка, надішли одне фото для підтвердження дійсності підписки\n\nℹ️ **Підказка**\nПрочитай як правильно скрінити легітимне підтвердження підписки: https://telegra.ph/Pіdpiska-na-holo-dіvchinu-01-02", + "sponsor4": "Яку роль ти бажаєш отримати?\n\nℹ️ **Підказка**\nНазва ролі повинна бути якось пов'язана зі вказаною дівчиною, не повинна порушувати правила спільноти а також має бути не довше за 16 символів (обмеження Telegram).", "sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?", "sponsor_confirm": "Здається, це все. Перевір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.", @@ -49,7 +49,7 @@ "rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.", "rejected_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.", "rejected_by_rus": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: русня.", - "sponsor_approved": "Вітаємо! Твою форму переглянули та підтвердили її правильність. Коли термін дії наданої підписки буде добігати кінця - ми нагадаємо, що треба оновити дані аби й надалі отримувати плюшки в боті. Гарного дня!", + "sponsor_approved": "Вітаємо! Твою форму переглянули та підтвердили її правильність. Коли термін дії наданої підписки буде добігати кінця - ми нагадаємо, що треба оновити дані аби й надалі отримувати плюшки в чаті. Також можна повторно заповнити форму, якщо хочеться змінити бажане ім'я ролі або подовжити термін дії підписки завчасно, за допомогою команди /sponsorship. Гарного дня!", "sponsor_rejected": "Ой лишенько! Твою форму переглянули, однак не підтвердили її. Можливо, щось не так з датами, або ж бажана роль не може бути надана.\n\nТи можеш спробувати повторно заповнити форму командою /sponsorship", "sponsor_approved_by": "✅ **Підписку схвалено**\nАдмін **{0}** переглянув та схвалив форму `{1}`.", "sponsor_rejected_by": "❌ **Підписку відхилено**\nАдмін **{0}** переглянув та відхилив форму `{1}`.", From 1aed7bff7bbff6cedf9ce7bdcfdcdc4bd3e63397 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 2 Jan 2023 14:20:41 +0100 Subject: [PATCH 04/19] Removed "have a nice day" where not needed --- locale/uk.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/uk.json b/locale/uk.json index 9b8f591..5b164f6 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -31,8 +31,8 @@ "sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?", "sponsor_confirm": "Здається, це все. Перевір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.", - "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується. Гарного дня! :)", - "sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. Гарного дня! :)", + "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується :)", + "sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення :)", "application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", "reapply_got": "Отримано оновлення анкети від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", "sponsor_got": "Отримано форму на спонсорство від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані форми:**\n{3}", From 626492fb3ce43c036ebbae5db695bc8970decd21 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 2 Jan 2023 14:24:29 +0100 Subject: [PATCH 05/19] Improved logging --- modules/callbacks/sponsorship.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/callbacks/sponsorship.py b/modules/callbacks/sponsorship.py index cc1c0d4..910a5f6 100644 --- a/modules/callbacks/sponsorship.py +++ b/modules/callbacks/sponsorship.py @@ -36,7 +36,7 @@ async def callback_query_sponsor_yes(app: Client, clb: CallbackQuery): await app.send_message(configGet("admin_group"), locale("sponsor_approved_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) await app.send_message(holo_user.id, locale("sponsor_approved", "message", locale=holo_user)) - logWrite(f"User {holo_user.id} got approved by {clb.from_user.id}") + logWrite(f"User {holo_user.id} got sponsorship approved by {clb.from_user.id}") if col_sponsorships.find_one({"user": holo_user.id}) is not None: col_sponsorships.update_one({"user": holo_user.id}, @@ -82,7 +82,7 @@ async def callback_query_sponsor_no(app: Client, clb: CallbackQuery): await app.send_message(configGet("admin_group"), locale("sponsor_rejected_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) await app.send_message(holo_user.id, locale("sponsor_rejected", "message", locale=holo_user)) - logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id}") + logWrite(f"User {holo_user.id} got sponsorship rejected by {clb.from_user.id}") col_tmp.update_one({"user": holo_user.id, "type": "sponsorship"}, { From a59a7b738ca20a0bb1bf48d3b126e7bbc00f0524 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Tue, 3 Jan 2023 10:13:38 +0100 Subject: [PATCH 06/19] /cancel added to commands to register --- locale/uk.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locale/uk.json b/locale/uk.json index 5b164f6..ce5e4c3 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -233,6 +233,7 @@ "rules_additional": "Додаткові правила, які несуть рекомендаційний характер, та не мають явних покарань за порушення:\n1️⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2️⃣) У чаті немає заборони на російський контент. Але, майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3️⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4️⃣) Поважайте авторські права контентмейкерів. Якщо ви знаходите арт, анімацію, музику тощо, на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом `/search` на повідомлення із артом.", "commands": { "rules": "Правила спільноти", + "cancel": "Відмінити актуальну дію", "nearby": "Показати користувачів поблизу", "reapply": "Повторно заповнити анкету", "sponsorship": "Отримати роль за спонсорство" From a7038e9d8f152b7b76a4013882cf2aa5954c2b7c Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 13:01:46 +0100 Subject: [PATCH 07/19] Improved tmp files system --- classes/holo_user.py | 35 +++++++++++++++++++---------------- modules/utils.py | 24 ++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/classes/holo_user.py b/classes/holo_user.py index b36dc65..663746a 100644 --- a/classes/holo_user.py +++ b/classes/holo_user.py @@ -11,7 +11,7 @@ from dateutil.relativedelta import relativedelta from classes.errors.geo import PlaceNotFoundError from modules.database import col_tmp, col_users, col_context, col_warnings, col_applications, col_sponsorships, col_messages from modules.logging import logWrite -from modules.utils import configGet, find_location, locale, should_quote +from modules.utils import configGet, create_tmp, download_tmp, find_location, locale, should_quote class DefaultApplicationTemp(dict): def __init__(self, user: int, reapply: bool = False): @@ -349,14 +349,14 @@ class HoloUser(): * msg (`Message`): Message that should receive replies """ - if col_tmp.find_one({"user": self.id, "type": "application"}) is None: + # if col_tmp.find_one({"user": self.id, "type": "application"}) is None: - if self.sponsorship_state()[0] == "fill": - return + if self.sponsorship_state()[0] == "fill": + return - col_tmp.insert_one( - document=DefaultApplicationTemp(self.id).dict - ) + # col_tmp.insert_one( + # document=DefaultApplicationTemp(self.id).dict + # ) progress = col_tmp.find_one({"user": self.id, "type": "application"}) @@ -365,9 +365,9 @@ class HoloUser(): stage = progress["stage"] - if self.sponsorship_state()[0] == "fill": - await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg)) - return + # if self.sponsorship_state()[0] == "fill": + # await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg)) + # return if progress["state"] == "fill" and progress["sent"] is False: @@ -522,11 +522,7 @@ class HoloUser(): elif stage == 3: if photo is not None: - filename = uuid1() - await app.download_media(photo.file_id, f"tmp{sep}{filename}") - with open(f"tmp{sep}{filename}", "rb") as f: - photo_bytes = f.read() - progress["sponsorship"]["proof"] = photo_bytes + progress["sponsorship"]["proof"] = await download_tmp(app, photo.file_id) col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "stage": progress["stage"]+1}}) await msg.reply_text(locale(f"sponsor{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"sponsor{stage+1}", "force_reply", locale=self.locale)))) @@ -536,7 +532,14 @@ class HoloUser(): return progress["sponsorship"]["label"] = query col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "complete": True}}) - await msg.reply_text(locale("sponsor_confirm", "message", locale=self.locale), reply_markup=ReplyKeyboardMarkup(locale("confirm", "keyboard", locale=self.locale), resize_keyboard=True)) + await msg.reply_photo( + photo=create_tmp(progress["sponsorship"]["proof"], kind="image"), + caption=locale("sponsor_confirm", "message", locale=self.locale).format( + progress["sponsorship"]["streamer"], + progress["sponsorship"]["expires"].strftime("%d.%m.%Y"), + progress["sponsorship"]["label"] + ), + reply_markup=ReplyKeyboardMarkup(locale("confirm", "keyboard", locale=self.locale), resize_keyboard=True)) else: return diff --git a/modules/utils.py b/modules/utils.py index 68c1e98..bfdf21e 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,4 +1,5 @@ -from typing import Any, Union +from typing import Any, Literal, Union +from uuid import uuid1 from requests import get from pyrogram.enums.chat_type import ChatType from pyrogram.types import User @@ -8,7 +9,7 @@ from ujson import JSONDecodeError as JSONDecodeError from ujson import loads, dumps from sys import exit -from os import kill, listdir, sep +from os import kill, listdir, makedirs, path, sep from os import name as osname from traceback import print_exc from classes.errors.geo import PlaceNotFoundError @@ -191,6 +192,25 @@ def find_location(query: str) -> dict: except (ValueError, KeyError, IndexError): raise PlaceNotFoundError(query) +def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", "video"], None]) -> str: + filename = str(uuid1()) + if kind == "image": + filename += ".jpg" + elif kind == "video": + filename += ".mp4" + makedirs("tmp", exist_ok=True) + with open(path.join("tmp", filename), "wb") as file: + file.write(bytedata) + return path.join("tmp", filename) + +async def download_tmp(app: Client, file_id: str) -> bytes: + filename = str(uuid1()) + makedirs("tmp", exist_ok=True) + await app.download_media(file_id, path.join("tmp", filename)) + with open(path.join("tmp", filename), "rb") as f: + bytedata = f.read() + return bytedata + try: from psutil import Process except ModuleNotFoundError: From b2613c25a4c5fc8afd8c718d5c20515cf2c1a04d Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 13:02:27 +0100 Subject: [PATCH 08/19] /label can now be used in admin group --- modules/commands/label.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/commands/label.py b/modules/commands/label.py index 60763b0..4382fe2 100644 --- a/modules/commands/label.py +++ b/modules/commands/label.py @@ -6,7 +6,7 @@ from modules.utils import locale, should_quote, find_user from classes.holo_user import HoloUser, LabelTooLongError from modules import custom_filters -@app.on_message(~ filters.scheduled & filters.private & filters.command(["label"], prefixes=["/"]) & custom_filters.admin) +@app.on_message(~ filters.scheduled & filters.command(["label"], prefixes=["/"]) & custom_filters.admin) async def cmd_label(app: Client, msg: Message): if len(msg.command) < 3: From 642c23dd6159a64a25770a3c6e857e929b4ddd1e Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 13:02:39 +0100 Subject: [PATCH 09/19] Added docstring --- modules/custom_filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/custom_filters.py b/modules/custom_filters.py index 8686fb8..e840a62 100644 --- a/modules/custom_filters.py +++ b/modules/custom_filters.py @@ -1,3 +1,6 @@ +"""Custom message filters made to improve commands +usage in context of Holo Users.""" + from app import isAnAdmin from modules.database import col_applications from pyrogram import filters From b401028dd1a92f0ef5bfabb75e94157acece6a1f Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 13:03:48 +0100 Subject: [PATCH 10/19] application_state()[0] "none" is also handled now --- modules/commands/reapply.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/commands/reapply.py b/modules/commands/reapply.py index bcb7ac5..35192c7 100644 --- a/modules/commands/reapply.py +++ b/modules/commands/reapply.py @@ -13,18 +13,25 @@ async def cmd_reapply(app: Client, msg: Message): holo_user = HoloUser(msg.from_user) - if holo_user.application_state()[0] in ["approved", "rejected"]: - if (holo_user.application_state()[1] is True) and (not col_tmp.find_one({"user": holo_user.id, "type": "application"})["sent"]): + # Check if user has approved/rejected tmp application + if (holo_user.application_state()[0] in ["approved", "rejected"]) or (holo_user.application_state()[0] == "none"): + + # Check if user's tmp application is already completed or even sent + if ((holo_user.application_state()[1] is True) and (not col_tmp.find_one({"user": holo_user.id, "type": "application"})["sent"])) or (holo_user.application_state()[0] == "none"): + left_chat = True + async for member in app.get_chat_members(configGet("destination_group")): if member.user.id == msg.from_user.id: left_chat = False + if not left_chat: if holo_user.sponsorship_state()[0] == "fill": await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg)) return holo_user.application_restart(reapply=True) await welcome_pass(app, msg, once_again=True) + else: await msg.reply_text(locale("reapply_left_chat", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup([ [ @@ -34,15 +41,20 @@ async def cmd_reapply(app: Client, msg: Message): InlineKeyboardButton(locale("reapply_new_one", "button", locale=holo_user), f"reapply_new_{msg.id}") ] ])) + else: + await msg.reply_text(locale("reapply_in_progress", "message", locale=holo_user).format(locale("confirm", "keyboard", locale=holo_user)[1][0]), reply_markup=InlineKeyboardMarkup([ [ InlineKeyboardButton(locale("applying_stop", "button", locale=holo_user), f"reapply_stop_{msg.id}") ] ])) + else: + if (holo_user.application_state()[0] == "fill") and (col_tmp.find_one({"user": holo_user.id, "type": "application"})["sent"] is True): await msg.reply_text(locale("reapply_forbidden", "message", locale=holo_user)) + else: await msg.reply_text(locale("reapply_in_progress", "message", locale=holo_user).format(locale("confirm", "keyboard", locale=holo_user)[1][0]), reply_markup=InlineKeyboardMarkup([ [ From 7854f8821753aee5e11c36b490962d4be2aeac0e Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 13:04:10 +0100 Subject: [PATCH 11/19] Cancel command implemented --- locale/uk.json | 6 +++++- modules/commands/cancel.py | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/locale/uk.json b/locale/uk.json index ce5e4c3..d8dc1ba 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -30,7 +30,7 @@ "sponsor4": "Яку роль ти бажаєш отримати?\n\nℹ️ **Підказка**\nНазва ролі повинна бути якось пов'язана зі вказаною дівчиною, не повинна порушувати правила спільноти а також має бути не довше за 16 символів (обмеження Telegram).", "sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?", - "sponsor_confirm": "Здається, це все. Перевір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.", + "sponsor_confirm": "**Дані форми:**\nСтрімер: {0}\nПідписка до: {1}\nХочу роль: {2}\n\nПеревір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.", "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується :)", "sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення :)", "application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", @@ -96,6 +96,8 @@ "nearby_error": "⚠️ **Сталась помилка**\n\nПомилка: `{0}`\n\nTraceback:\n```\n{1}\n```", "nearby_result": "Результати пошуку:\n\n{0}", "nearby_empty": "Здається, нікого поблизу немає.", + "cancel": "Всі поточні операції скасовано.", + "identify_success": "Користувач `{0}`\n\nІм'я: {1}\nЮзернейм: {2}\nЄ в чаті: {3}\nЄ адміном: {4}\nРоль: {5}\nНаявна анкета: {6}\nНаявне спонсорство: {7}", "voice_message": [ "why are u gae", "руки відірвало? пиши як людина", @@ -242,6 +244,7 @@ "reboot": "Перезапустити бота", "label": "Встановити нікнейм користувачу", "message": "Надіслати користувачу повідомлення", + "identify": "Дізнатись дані про користувача за айді", "warnings": "Переглянути попередження користувача", "application": "Переглянути анкету користувача", "applications": "Отримати всі анкети як JSON" @@ -251,6 +254,7 @@ "label": "Встановити нікнейм користувачу", "nearby": "Показати користувачів поблизу", "message": "Надіслати користувачу повідомлення", + "identify": "Дізнатись дані про користувача за айді", "warnings": "Переглянути попередження користувача", "application": "Переглянути анкету користувача", "applications": "Отримати всі анкети як JSON" diff --git a/modules/commands/cancel.py b/modules/commands/cancel.py index 5e06a44..eca4630 100644 --- a/modules/commands/cancel.py +++ b/modules/commands/cancel.py @@ -2,8 +2,11 @@ from app import app from pyrogram import filters from pyrogram.types import Message from pyrogram.client import Client -from modules.utils import should_quote +from modules.utils import should_quote, logWrite, locale +from modules.database import col_tmp @app.on_message(~ filters.scheduled & filters.command("cancel", prefixes=["/"])) async def command_cancel(app: Client, msg: Message): - await msg.reply_text("Command exists.", quote=should_quote(msg)) \ No newline at end of file + col_tmp.delete_many( {"user": msg.from_user.id} ) + await msg.reply_text(locale("cancel", "message", locale=msg.from_user), quote=should_quote(msg)) + logWrite(f"Cancelling all ongoing tmp operations for {msg.from_user.id}") \ No newline at end of file From 6aa8128fc6dbd5edea4284c2a99f921edc9c03b4 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 13:04:38 +0100 Subject: [PATCH 12/19] Using application_restart() now --- modules/handlers/welcome.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/handlers/welcome.py b/modules/handlers/welcome.py index bcd516a..e902617 100644 --- a/modules/handlers/welcome.py +++ b/modules/handlers/welcome.py @@ -2,6 +2,7 @@ from app import app from pyrogram import filters from pyrogram.types import ForceReply, ReplyKeyboardMarkup, Message from pyrogram.client import Client +from classes.holo_user import HoloUser from modules.utils import all_locales, locale, logWrite # Welcome check ================================================================================================================ @@ -11,7 +12,7 @@ for pattern in all_locales("welcome", "keyboard"): for pattern in all_locales("return", "keyboard"): welcome_1.append(pattern[0][0]) @app.on_message(~ filters.scheduled & filters.private & filters.command(welcome_1, prefixes=[""])) -async def welcome_pass(app, msg, once_again: bool = True) -> None: +async def welcome_pass(app: Client, msg: Message, once_again: bool = True) -> None: """Set user's stage to 1 and start a fresh application ### Args: @@ -22,6 +23,10 @@ async def welcome_pass(app, msg, once_again: bool = True) -> None: if not once_again: await msg.reply_text(locale("privacy_notice", "message")) + + holo_user = HoloUser(msg.from_user) + + holo_user.application_restart() logWrite(f"User {msg.from_user.id} confirmed starting the application") await msg.reply_text(locale("question1", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("question1", "force_reply", locale=msg.from_user))) From ea1dc542a37d5b16292d15b16d99888f4334bbe6 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 13:16:57 +0100 Subject: [PATCH 13/19] Improved docstrings --- modules/utils.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/modules/utils.py b/modules/utils.py index bfdf21e..c90d473 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -193,6 +193,15 @@ def find_location(query: str) -> dict: raise PlaceNotFoundError(query) def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", "video"], None]) -> str: + """Create temporary file to help uploading it + + ### Args: + * bytedata (`Union[bytes, bytearray]`): Some bytes to be written + * kind (`Union[Literal["image", "video"], None]`): Kind of upload. Will add `.jpg` or `.mp4` if needed + + ### Returns: + * `str`: Path to temporary file + """ filename = str(uuid1()) if kind == "image": filename += ".jpg" @@ -204,6 +213,15 @@ def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", " return path.join("tmp", filename) async def download_tmp(app: Client, file_id: str) -> bytes: + """Download file by its ID and return its bytes + + ### Args: + * app (`Client`): App that will download the file + * file_id (`str`): File's unique id + + ### Returns: + * `bytes`: Bytes of downloaded file + """ filename = str(uuid1()) makedirs("tmp", exist_ok=True) await app.download_media(file_id, path.join("tmp", filename)) From c8f89a7447f28c18d14d7c2a4bed256147476193 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 13:17:59 +0100 Subject: [PATCH 14/19] Fixed attribute error --- app.py | 7 +++++++ modules/custom_filters.py | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index a30216e..0c1e383 100644 --- a/app.py +++ b/app.py @@ -8,13 +8,19 @@ from pyrogram.errors import bad_request_400 app = Client("holochecker", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot")) async def isAnAdmin(admin_id): + + # Check if user is mentioned in config if (admin_id == configGet("owner")) or (admin_id in configGet("admins")): return True + + # Check if user is probably in cache if path.exists(f"cache{sep}admins") is True: try: return True if admin_id in jsonLoad(f"cache{sep}admins") else False except (FileNotFoundError, JSONDecodeError): pass + + # Check if user is in admin group try: async for member in app.get_chat_members(configGet("admin_group")): if member.user.id == admin_id: @@ -22,4 +28,5 @@ async def isAnAdmin(admin_id): except bad_request_400.ChannelInvalid: logWrite(f"Could not get users in admin group to answer isAnAdmin(). Bot is likely not in the group.") return False + return False \ No newline at end of file diff --git a/modules/custom_filters.py b/modules/custom_filters.py index e840a62..148b01c 100644 --- a/modules/custom_filters.py +++ b/modules/custom_filters.py @@ -4,11 +4,12 @@ usage in context of Holo Users.""" from app import isAnAdmin from modules.database import col_applications from pyrogram import filters +from pyrogram.types import Message -async def admin_func(_, __, msg): - return await isAnAdmin(msg) +async def admin_func(_, __, msg: Message): + return await isAnAdmin(msg.from_user.id) -async def allowed_func(_, __, msg): +async def allowed_func(_, __, msg: Message): return True if (col_applications.find_one({"user": msg.from_user.id}) is not None) else False admin = filters.create(admin_func) From 7edffd0b40c815d3c1d367438f9fd01713baead4 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 14:30:16 +0100 Subject: [PATCH 15/19] /identify command added --- holochecker.py | 1 + locale/uk.json | 4 +++ modules/commands/identify.py | 63 ++++++++++++++++++++++++++++++++++++ modules/utils.py | 2 +- 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 modules/commands/identify.py diff --git a/holochecker.py b/holochecker.py index bb70530..c2ad999 100644 --- a/holochecker.py +++ b/holochecker.py @@ -13,6 +13,7 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True) from modules.commands.application import * from modules.commands.applications import * from modules.commands.cancel import * +from modules.commands.identify import * from modules.commands.label import * from modules.commands.message import * from modules.commands.nearby import * diff --git a/locale/uk.json b/locale/uk.json index d8dc1ba..2ead0de 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -97,7 +97,11 @@ "nearby_result": "Результати пошуку:\n\n{0}", "nearby_empty": "Здається, нікого поблизу немає.", "cancel": "Всі поточні операції скасовано.", + "identify_invalid_syntax": "Неправильний синтаксис!\nТреба: `/identify ID/NAME/USERNAME`", + "identify_not_found": "Не знайдено користувачів за запитом **{0}**", "identify_success": "Користувач `{0}`\n\nІм'я: {1}\nЮзернейм: {2}\nЄ в чаті: {3}\nЄ адміном: {4}\nРоль: {5}\nНаявна анкета: {6}\nНаявне спонсорство: {7}", + "yes": "Так", + "no": "Ні", "voice_message": [ "why are u gae", "руки відірвало? пиши як людина", diff --git a/modules/commands/identify.py b/modules/commands/identify.py new file mode 100644 index 0000000..ad696c4 --- /dev/null +++ b/modules/commands/identify.py @@ -0,0 +1,63 @@ +from os import path +from app import app, isAnAdmin +from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client +from pyrogram.errors import bad_request_400 +from pyrogram.enums.chat_action import ChatAction +from classes.holo_user import HoloUser, UserNotFoundError, UserInvalidError +from modules.utils import jsonLoad, should_quote, logWrite, locale, download_tmp, create_tmp +from modules import custom_filters + +@app.on_message(~ filters.scheduled & filters.command("identify", prefixes=["/"]) & custom_filters.admin) +async def command_identify(app: Client, msg: Message): + + if len(msg.command) != 2: + await msg.reply_text(locale("identify_invalid_syntax", "message", locale=msg.from_user)) + return + + try: + try: + holo_user = HoloUser(int(msg.command[1])) + except ValueError: + holo_user = HoloUser(await app.get_users(msg.command[1])) + except (UserInvalidError, UserNotFoundError, bad_request_400.UsernameInvalid, bad_request_400.PeerIdInvalid, bad_request_400.UsernameNotOccupied): + await msg.reply_text(locale("identify_not_found", "message", locale=msg.from_user).format(msg.command[1])) + return + + role = holo_user.label + has_application = locale("yes", "message", locale=msg.from_user) if holo_user.application_approved() is True else locale("no", "message", locale=msg.from_user) + has_sponsorship = locale("yes", "message", locale=msg.from_user) if holo_user.sponsorship_valid() is True else locale("no", "message", locale=msg.from_user) + + username = holo_user.username if holo_user.username is not None else "N/A" + in_chat = locale("yes", "message", locale=msg.from_user) if (holo_user.id in jsonLoad(path.join("cache", "group_members"))) else locale("no", "message", locale=msg.from_user) + is_admin = locale("yes", "message", locale=msg.from_user) if (await isAnAdmin(holo_user.id)) else locale("no", "message", locale=msg.from_user) + + output = locale("identify_success", "message", locale=msg.from_user).format( + holo_user.id, + holo_user.name, + username, + in_chat, + is_admin, + role, + has_application, + has_sponsorship + ) + + user = await app.get_users(holo_user.id) + + if user.photo is not None: + await app.send_chat_action(msg.chat.id, action=ChatAction.UPLOAD_PHOTO) + await msg.reply_photo( + create_tmp(await download_tmp(app, user.photo.big_file_id), kind="image"), + quote=should_quote(msg), + caption=output + ) + else: + await app.send_chat_action(msg.chat.id, action=ChatAction.TYPING) + await msg.reply_text( + output, + quote=should_quote(msg) + ) + + logWrite(f"User {msg.from_user.id} identified user {holo_user.id}") \ No newline at end of file diff --git a/modules/utils.py b/modules/utils.py index c90d473..880d087 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -192,7 +192,7 @@ def find_location(query: str) -> dict: except (ValueError, KeyError, IndexError): raise PlaceNotFoundError(query) -def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", "video"], None]) -> str: +def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", "video"], None] = None) -> str: """Create temporary file to help uploading it ### Args: From e79edf1dff745ec4cc42bd00c584679ea8ad914d Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 14:30:29 +0100 Subject: [PATCH 16/19] Added some more exceptions to handle --- modules/commands/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/commands/application.py b/modules/commands/application.py index abc758d..2e8521b 100644 --- a/modules/commands/application.py +++ b/modules/commands/application.py @@ -22,7 +22,7 @@ async def cmd_application(app: Client, msg: Message): except (ValueError, UserNotFoundError): try: holo_user = HoloUser((await app.get_users(msg.command[1])).id) - except (bad_request_400.UsernameInvalid, bad_request_400.PeerIdInvalid): + except (bad_request_400.UsernameInvalid, bad_request_400.PeerIdInvalid, bad_request_400.UsernameNotOccupied): await msg.reply_text(locale("no_user_application", "message", locale=msg.from_user).format(msg.command[1]), quote=should_quote(msg)) return From db60a538b2d8051dc7a6db7add2eb9c2c81dd665 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 15:12:46 +0100 Subject: [PATCH 17/19] Improved docstrings --- modules/database.py | 3 +++ modules/inline.py | 3 +++ modules/scheduled.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/modules/database.py b/modules/database.py index 9a4ba1d..f68db38 100644 --- a/modules/database.py +++ b/modules/database.py @@ -1,3 +1,6 @@ +"""Module that provides all database columns and +creates geospatial index for col_applications""" + from pymongo import MongoClient, GEOSPHERE from ujson import loads diff --git a/modules/inline.py b/modules/inline.py index d0f1a8e..0637c53 100644 --- a/modules/inline.py +++ b/modules/inline.py @@ -1,3 +1,6 @@ +"""Module responsible for providing answers to +all inline queries that bot receives""" + from datetime import datetime from os import path, sep from app import app, isAnAdmin diff --git a/modules/scheduled.py b/modules/scheduled.py index 979c964..ce986ef 100644 --- a/modules/scheduled.py +++ b/modules/scheduled.py @@ -1,3 +1,6 @@ +"""Automatically register commands and execute +some scheduled tasks is the main idea of this module""" + from os import listdir, makedirs, path, sep from apscheduler.schedulers.asyncio import AsyncIOScheduler from datetime import datetime, timedelta From b383ab6001ef07f9300e739a1fd1dae7f5a2d72e Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 15:15:15 +0100 Subject: [PATCH 18/19] Optimized imports --- classes/holo_user.py | 5 +---- modules/callbacks/reapply.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/classes/holo_user.py b/classes/holo_user.py index 663746a..1757a31 100644 --- a/classes/holo_user.py +++ b/classes/holo_user.py @@ -1,7 +1,4 @@ from datetime import datetime -from os import sep -from uuid import uuid1 -from requests import get from traceback import print_exc from app import app, isAnAdmin from typing import Any, List, Literal, Union @@ -9,7 +6,7 @@ from pyrogram.types import User, ChatMember, ChatPrivileges, Chat, Message, Phot from pyrogram.errors import bad_request_400 from dateutil.relativedelta import relativedelta from classes.errors.geo import PlaceNotFoundError -from modules.database import col_tmp, col_users, col_context, col_warnings, col_applications, col_sponsorships, col_messages +from modules.database import col_tmp, col_users, col_applications, col_sponsorships, col_messages from modules.logging import logWrite from modules.utils import configGet, create_tmp, download_tmp, find_location, locale, should_quote diff --git a/modules/callbacks/reapply.py b/modules/callbacks/reapply.py index 5496b5c..b3c8141 100644 --- a/modules/callbacks/reapply.py +++ b/modules/callbacks/reapply.py @@ -4,7 +4,7 @@ from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyb from pyrogram.client import Client from pyrogram import filters from classes.holo_user import HoloUser -from modules.utils import configGet, locale, logWrite, should_quote +from modules.utils import configGet, locale, logWrite from modules.handlers.confirmation import confirm_yes from modules.handlers.welcome import welcome_pass from modules.database import col_tmp, col_applications From ccebccf0866ece731796274d9e468f21416ccf91 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 3 Jan 2023 15:43:48 +0100 Subject: [PATCH 19/19] Updated validation rules --- validation/applications.json | 85 +++++++++++++++++++++++++++++++++++- validation/sponsorships.json | 41 ++++++++++++++++- validation/tmp.json | 6 +++ 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 validation/tmp.json diff --git a/validation/applications.json b/validation/applications.json index 026bd47..2bd0192 100644 --- a/validation/applications.json +++ b/validation/applications.json @@ -1,6 +1,87 @@ { "$jsonSchema": { - "required": [], - "properties": {} + "required": [ + "user", + "date", + "admin", + "application", + "application.1", + "application.2", + "application.3", + "application.3.name", + "application.3.adminName1", + "application.3.countryCode", + "application.3.countryName", + "application.3.location", + "application.4", + "application.5", + "application.6", + "application.7", + "application.8", + "application.9", + "application.10" + ], + "properties": { + "user": { + "bsonType": ["int", "long"], + "description": "Telegram ID of user" + }, + "date": { + "bsonType": "date", + "description": "Date when application was accepted" + }, + "admin": { + "bsonType": ["int", "long"], + "description": "Telegram ID of admin that accepted the application" + }, + "application": { + "bsonType": "object" + }, + "application.1": { + "bsonType": "string" + }, + "application.2": { + "bsonType": "date" + }, + "application.3": { + "bsonType": "object" + }, + "application.3.name": { + "bsonType": "string" + }, + "application.3.adminName1": { + "bsonType": "string" + }, + "application.3.countryCode": { + "bsonType": "string" + }, + "application.3.countryName": { + "bsonType": "string" + }, + "application.3.location": { + "bsonType": "array" + }, + "application.4": { + "bsonType": "string" + }, + "application.5": { + "bsonType": "string" + }, + "application.6": { + "bsonType": "string" + }, + "application.7": { + "bsonType": "string" + }, + "application.8": { + "bsonType": "string" + }, + "application.9": { + "bsonType": "string" + }, + "application.10": { + "bsonType": "string" + } + } } } \ No newline at end of file diff --git a/validation/sponsorships.json b/validation/sponsorships.json index 026bd47..3610e48 100644 --- a/validation/sponsorships.json +++ b/validation/sponsorships.json @@ -1,6 +1,43 @@ { "$jsonSchema": { - "required": [], - "properties": {} + "required": [ + "user", + "date", + "admin", + "sponsorship", + "sponsorship.streamer", + "sponsorship.expires", + "sponsorship.proof", + "sponsorship.label" + ], + "properties": { + "user": { + "bsonType": ["int", "long"], + "description": "Telegram ID of user" + }, + "date": { + "bsonType": "date", + "description": "Date when sponsorship was accepted" + }, + "admin": { + "bsonType": ["int", "long"], + "description": "Telegram ID of admin that accepted the sponsorship" + }, + "sponsorship": { + "bsonType": "object" + }, + "sponsorship.streamer": { + "bsonType": "string" + }, + "sponsorship.expires": { + "bsonType": "date" + }, + "sponsorship.proof": { + "bsonType": "binData" + }, + "sponsorship.label": { + "bsonType": "string" + } + } } } \ No newline at end of file diff --git a/validation/tmp.json b/validation/tmp.json new file mode 100644 index 0000000..026bd47 --- /dev/null +++ b/validation/tmp.json @@ -0,0 +1,6 @@ +{ + "$jsonSchema": { + "required": [], + "properties": {} + } +} \ No newline at end of file