diff --git a/.gitignore b/.gitignore index bcc8d4c..daa0805 100644 --- a/.gitignore +++ b/.gitignore @@ -157,11 +157,8 @@ config.json config_debug.json *.session *.session-journal -users -!users/.gitkeep +data TASK.md inline_bot.py -data/applications.json -!data/cache/avatars/.gitkeep -data/cache/avatars/* -.vscode \ No newline at end of file +.vscode +migrate.py \ No newline at end of file diff --git a/README.md b/README.md index 98bdedc..23dce77 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Small Telegram bot made on Pyrogram * Give one-time links to join group * Track down users that were not allowed to join * Show applications to other users +* Manage YouTube sponsorships [WIP] +* Send and receive messages to users using bot [WIP] ## Installation @@ -32,8 +34,6 @@ You can see config file with all the comments below: "owner": 0, "bot_id": 0, "age_allowed": 0, - "birthdays_notify": true, - "birthdays_time": "09:00", "api": "http://example.com", "inline_preview_count": 7, "admin_group": 0, @@ -45,29 +45,51 @@ You can see config file with all the comments below: "api_hash": "", "bot_token": "" }, + "database": { + "user": null, + "password": null, + "host": "127.0.0.1", + "port": 27017, + "name": "holochecker" + }, + "geocoding": { + "username": "demo" + }, "logging": { "size": 512, "location": "logs" }, + "scheduler": { + "birthdays": { + "time": 9, + "enabled": true + }, + "sponsorships": { + "time": 9, + "enabled": true + } + }, "locations": { - "data": "data", "cache": "cache", "locale": "locale" }, "commands": { "rules": "Check out the rules", - "reapply": "Resubmit the application" + "reapply": "Resubmit the application", + "sponsorship": "Apply for sponsor role" }, "commands_admin": { "reboot": "Restart the bot", - "message": "Send a message", + "message": "Send a message", + "label": "Set user's nickname", "warnings": "Check user's warnings", "application": "Check user's application", "applications": "Retrieve all applications as a JSON" }, "commands_group_admin": { "reboot": "Restart the bot", - "message": "Send a message", + "message": "Send a message", + "label": "Set user's nickname", "warnings": "Check user's warnings", "application": "Check user's application", "applications": "Retrieve all applications as a JSON" @@ -82,6 +104,6 @@ After all of that you're good to go! Happy using :) ## To-Do -* [ ] Complete messenger between user and admins +* [x] Complete messenger between user and admins * [ ] Check sponsorship on Holo girls * [ ] Get application by id and user_id \ No newline at end of file diff --git a/app.py b/app.py index 6c40ca1..833c744 100644 --- a/app.py +++ b/app.py @@ -1,12 +1,18 @@ +from modules.logging import logWrite from modules.utils import configGet from pyrogram.client import Client +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): if (admin_id == configGet("owner")) or (admin_id in configGet("admins")): return True - async for member in app.get_chat_members(configGet("admin_group")): - if member.user.id == admin_id: - return True + try: + async for member in app.get_chat_members(configGet("admin_group")): + if member.user.id == admin_id: + return True + 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/classes/holo_user.py b/classes/holo_user.py index e07b2ca..c0879f0 100644 --- a/classes/holo_user.py +++ b/classes/holo_user.py @@ -1,12 +1,40 @@ +from datetime import datetime +from requests import get +from traceback import print_exc from app import app, isAnAdmin -from typing import Any, List, Union -from pyrogram.types import User, ChatMember, ChatPrivileges, Chat, Message -from pyrogram.client import Client +from typing import Any, List, Literal, Union +from pyrogram.types import User, ChatMember, ChatPrivileges, Chat, Message, Photo, Video, Document, Animation, Voice, ForceReply, ReplyKeyboardMarkup from pyrogram.errors import bad_request_400 -from modules.database import col_users, col_context, col_warnings, col_applications, col_sponsorships, col_messages +from dateutil.relativedelta import relativedelta +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, locale, should_quote +class DefaultApplicationTemp(dict): + def __init__(self, user: int): + super().__init__({}) + self.dict = { + "user": user, + "type": "application", + "complete": False, + "sent": False, + "state": "fill", + "reapply": False, + "stage": 1, + "application": { + "1": None, + "2": None, + "3": None, + "4": None, + "5": None, + "6": None, + "7": None, + "8": None, + "9": None, + "10": None + } + } + class UserNotFoundError(Exception): """HoloUser could not find user with such an ID in database""" def __init__(self, user, user_id): @@ -21,8 +49,21 @@ class UserInvalidError(Exception): super().__init__(f"Could not find HoloUser by using {type(self.user)} as an input type") class HoloUser(): + """This object represents a user of HoloChecker bot. + It is primarily used to interact with a database in a more python-friendly way, + as well as provide better programming experience in case of adding new features, etc. + """ - def __init__(self, user: Union[List[User], User, ChatMember, int, str]) -> None: + def __init__(self, user: Union[User, List[User], ChatMember, int]) -> None: + """A user of Holo bot. Used to simplify DB interaction. + + ### Args: + * user (`Union[User, List[User], ChatMember, int]`): Any possible way to identify the user. Pass `User` object, user's telegram ID, etc... + + ### Raises: + * UserInvalidError: Provided to `HoloUser` object is not supported + * UserNotFoundError: `HoloUser` could not find user with such an ID in database + """ # Determine input object class and extract id if isinstance(user, list) and len(user) != 0: @@ -33,13 +74,14 @@ class HoloUser(): self.id = user.user.id elif isinstance(user, int): self.id = user - elif isinstance(user, str): - try: - self.id = (app.get_users(user)).id # this line requires testing though - except bad_request_400.UsernameNotOccupied: - raise UserInvalidError(user) - except bad_request_400.PeerIdInvalid: - raise UserInvalidError(user) + # elif isinstance(user, str): + # try: + # get_users = async_to_sync(app.get_users) + # self.id = get_users(user).id # this line requires testing though + # except bad_request_400.UsernameNotOccupied: + # raise UserInvalidError(user) + # except bad_request_400.PeerIdInvalid: + # raise UserInvalidError(user) else: raise UserInvalidError(user) @@ -71,35 +113,268 @@ class HoloUser(): col_users.update_one(filter={"_id": self.db_id}, update={ "$set": { key: value } }, upsert=True) logWrite(f"Set attribute {key} of user {self.id} to {value}") - async def message(self, origin: Message, text: Union[str, None] = None, photo: Union[str, None] = None, video: Union[str, None] = None, file: Union[str, None] = None): - new_message = await app.send_message(self.id, text+locale("message_reply_notice", "message")) - await origin.reply_text(locale("message_sent", "message"), quote=should_quote(origin)) - logWrite(f"Admin {origin.from_user.id} sent message '{' '.join(origin.command[2:])}' to {self.id}") - col_messages.insert_one({"origin": {"chat": origin.chat.id, "id": origin.id}, "destination": {"chat": new_message.chat.id, "id": new_message.id}}) + async def message(self, + context: Message, + origin: Union[Message, None] = None, + text: Union[str, None] = None, + caption: Union[str, None] = None, + photo: Union[str, Photo, None] = None, + video: Union[str, Video, None] = None, + file: Union[str, Document, None] = None, + animation: Union[str, Animation, None] = None, + voice: Union[str, Voice, None] = None, + adm_origin: bool = False, + adm_context: bool = False + ) -> None: + """Send a message to user - async def set_label(self, chat: Chat, label: str): + ### Args: + * context (`Message`): Context (mostly the message where this method is called) + * origin (`Union[Message, None]`, *optional*): Origin message where to refer. None if called in a command. Defaults to None. + * text (`Union[str, None]`, *optional*): Text if this is a simple text message. Defaults to None. + * caption (`Union[str, None]`, *optional*): Text if this is a media message. Defaults to None. + * photo (`Union[str, Photo, None]`, *optional*): Photo as a photo object or file_id as a string. Defaults to None. + * video (`Union[str, Video, None]`, *optional*): Video as a video object or file_id as a string. Defaults to None. + * file (`Union[str, Document, None]`, *optional*): File as a document object or file_id as a string. Defaults to None. + * animation (`Union[str, Animation, None]`, *optional*): Animation as an animation object or file_id as a string. Defaults to None. + * voice (`Union[str, Voice, None]`, *optional*): Voice as a voice object or file_id as a string. Defaults to None. + * adm_origin (`bool`, *optional*): Whether origin sender is an admin. Defaults to False. + * adm_context (`bool`, *optional*): Whether context sender is an admin. Defaults to False. + """ + + # Check if any text available and log message sending + if text is not None: + logWrite(f"{context.from_user.id} sent message '{text}' to {self.id}") + elif caption is not None: + logWrite(f"{context.from_user.id} sent message '{caption}' to {self.id}") + else: + logWrite(f"{context.from_user.id} sent message to {self.id}") + + # Add notices for admin or user + if text is not None: + if adm_context: + text += locale("message_reply_notice", "message") + elif adm_origin: + text = locale("message_from", "message").format(context.from_user.first_name, context.from_user.id) + text + locale("message_reply_notice", "message") + else: + text = locale("message_reply_notice", "message") + + if caption is not None: + if adm_context: + caption += locale("message_reply_notice", "message") + elif adm_origin: + caption = locale("message_from", "message").format(context.from_user.first_name, context.from_user.id) + caption + locale("message_reply_notice", "message") + else: + caption = locale("message_reply_notice", "message") + + # Try sending the message + try: + + # Check if origin message exists + # This check decides whether we send_ a message or reply_ to one + if origin is not None: + + if photo is not None: + if isinstance(photo, Photo): + photo = photo.file_id + new_message = await origin.reply_photo(photo, caption=caption, quote=True) + elif video is not None: + if isinstance(video, Video): + video = video.file_id + new_message = await origin.reply_video(video, caption=caption, quote=True) + elif file is not None: + if isinstance(file, Document): + file = file.file_id + new_message = await origin.reply_document(file, caption=caption, quote=True) + elif animation is not None: + if isinstance(animation, Animation): + animation = animation.file_id + new_message = await origin.reply_animation(animation, caption=caption, quote=True) + elif voice is not None: + if isinstance(voice, Voice): + voice = voice.file_id + new_message = await origin.reply_voice(voice, caption=caption, quote=True) + else: + new_message = await origin.reply_text(text, quote=True) + + else: + + if photo is not None: + if isinstance(photo, Photo): + photo = photo.file_id + new_message = await app.send_photo(self.id, photo, caption=caption) + elif video is not None: + if isinstance(video, Video): + video = video.file_id + new_message = await app.send_video(self.id, video, caption=caption) + elif file is not None: + if isinstance(file, Document): + file = file.file_id + new_message = await app.send_document(self.id, file, caption=caption) + elif animation is not None: + if isinstance(animation, Animation): + animation = animation.file_id + new_message = await app.send_animation(animation, caption=caption, quote=True) + elif voice is not None: + if isinstance(voice, Voice): + voice = voice.file_id + new_message = await app.send_voice(voice, caption=caption, quote=True) + else: + new_message = await app.send_message(self.id, text) + + # Acknowledge sending a message and save entry into DB + await context.reply_text(locale("message_sent", "message"), quote=should_quote(context)) + col_messages.insert_one({"origin": {"chat": context.chat.id, "id": context.id}, "destination": {"chat": new_message.chat.id, "id": new_message.id}}) + + # Report to admin and to sender about message sending failure + except Exception as exp: + logWrite(f"Exception {exp} happened as {context.from_user.id} tried to send message to {self.id}. Traceback:\n{print_exc()}") + try: + await app.send_message(configGet("owner"), locale("message_traceback", "message").format(context.from_user.id, self.id, exp, print_exc())) + except bad_request_400.PeerIdInvalid: + logWrite(f"Could not notify admin about failure when sending message! Admin has never interacted with bot!") + await context.reply_text(locale("message_error", "message"), quote=should_quote(context)) + + async def set_label(self, chat: Chat, label: str) -> None: """Set label in destination group ### Args: - * app (`Client`): Pyrogram client + * chat (`Chat`): Telegram chat * label (`str`): Label you want to set """ self.label = label self.set("label", label) await app.promote_chat_member(configGet("destination_group"), self.id) - if (not await isAnAdmin(self.id)) and (chat.id == configGet("admin_group")): + if not await isAnAdmin(self.id): await app.set_administrator_title(configGet("destination_group"), self.id, label) - async def reset_label(self, chat: Chat): + async def reset_label(self, chat: Chat) -> None: """Reset label in destination group ### Args: - * app (`Client`): Pyrogram client + * chat (`Chat`): Telegram chat """ self.label = "" self.set("label", "") await app.set_administrator_title(configGet("destination_group"), self.id, "") - if (not await isAnAdmin(self.id)) and (chat.id == configGet("admin_group")): + if not await isAnAdmin(self.id): await app.promote_chat_member(configGet("destination_group"), self.id, privileges=ChatPrivileges( can_manage_chat=False - )) \ No newline at end of file + )) + + def application_state(self) -> tuple[Literal["none", "fill", "approved", "rejected"], bool]: + """Check the current state of application in tmp collection + + ### Returns: + * `tuple[Literal["none", "fill", "approved", "rejected"], bool]`: First element is an enum of a state and the second one is whether application is complete. + """ + tmp_application = col_tmp.find_one({"user": self.id, "type": "application"}) + if tmp_application is None: + return "none", False + else: + return tmp_application["state"], tmp_application["complete"] + + def application_approved(self) -> bool: + """Check whether user has a completed application and it got approved + + ### Returns: + * `bool`: `True` if yes and `False` if no + """ + return True if col_applications.find_one({"user": self.id}) is not None else False + + def application_restart(self) -> None: + """Reset application of a user in tmp collection and replace it with an empty one + """ + if col_tmp.find_one({"user": self.id, "type": "application"}) is None: + col_tmp.insert_one(document=DefaultApplicationTemp(self.id).dict) + else: + col_tmp.delete_one({"user": self.id, "type": "application"}) + col_tmp.insert_one(document=DefaultApplicationTemp(self.id).dict) + + async def application_next(self, query: str, msg: Message) -> None: + """Move on filling application of user + + ### Args: + * query (`str`): Some kind of input + * msg (`Message`): Message that should receive replies + """ + + if col_tmp.find_one({"user": self.id, "type": "application"}) is None: + + col_tmp.insert_one( + document=DefaultApplicationTemp(self.id).dict + ) + + progress = col_tmp.find_one({"user": self.id, "type": "application"}) + stage = progress["stage"] + + if progress["state"] == "fill": + + if stage == 2: + + try: + input_dt = datetime.strptime(query, "%d.%m.%Y") + except ValueError: + logWrite(f"User {msg.from_user.id} failed stage {stage} due to sending invalid date format") + await msg.reply_text(locale(f"question2_invalid", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply")))) + return + + if datetime.now() <= input_dt: + logWrite(f"User {msg.from_user.id} failed stage {stage} due to joking") + await msg.reply_text(locale("question2_joke", "message"), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply")))) + return + + elif ((datetime.now() - input_dt).days) < ((datetime.now() - datetime.now().replace(year=datetime.now().year - configGet("age_allowed"))).days): + logWrite(f"User {msg.from_user.id} failed stage {stage} due to being underage") + await msg.reply_text(locale("question2_underage", "message").format(str(configGet("age_allowed"))), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply")))) + return + + else: + print(f'Look: {((datetime.now() - input_dt).days)} > {(datetime.now() - datetime.now().replace(year=datetime.now().year - configGet("age_allowed"))).days}') + progress["application"][str(stage)] = input_dt + col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "application"}}, {"$set": {"application": progress["application"], "stage": progress["stage"]+1}}) + await msg.reply_text(locale(f"question{stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{stage+1}", "force_reply")))) + + elif stage == 3: + try: + result = (get(f"http://api.geonames.org/searchJSON?q={query}&maxRows=1&countryBias=UA&lang=uk&orderby=relevance&featureClass=P&featureClass=A&username={configGet('username', 'geocoding')}")).json() + progress["application"][str(stage)] = result["geonames"][0] + col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "application"}}, {"$set": {"application": progress["application"], "stage": progress["stage"]+1}}) + await msg.reply_text(locale(f"question3_found", "message").format(result["geonames"][0]["name"], result["geonames"][0]["adminName1"])) + await msg.reply_text(locale(f"question{stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{stage+1}", "force_reply")))) + except (ValueError, KeyError, IndexError): + await msg.reply_text(locale(f"question3_invalid", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply")))) + return + except Exception as exp: + await msg.reply_text(locale("question3_error", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply")))) + try: + await app.send_message(configGet("owner"), locale("question3_traceback", "message").format(query, exp, print_exc())) + except bad_request_400.PeerIdInvalid: + logWrite(f"Could not notify admin about failure when sending message! Admin has never interacted with bot!") + return + + elif stage == 10: + progress["application"][str(stage)] = query + col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "application"}}, {"$set": {"application": progress["application"], "complete": True}}) + application_content = [] + i = 1 + for question in progress["application"]: + if i == 2: + age = relativedelta(datetime.now(), progress['application']['2']) + application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {progress['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)") + elif i == 3: + if progress['application']['3']['countryCode'] == "UA": + application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {progress['application']['3']['name']} ({progress['application']['3']['adminName1']})") + else: + application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {progress['application']['3']['name']} ({progress['application']['3']['adminName1']}, {progress['application']['3']['countryName']})") + else: + application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {progress['application'][question]}") + i += 1 + await msg.reply_text(locale("confirm", "message").format("\n".join(application_content)), reply_markup=ReplyKeyboardMarkup(locale("confirm", "keyboard"), resize_keyboard=True)) + + else: + progress["application"][str(stage)] = query + col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "application"}}, {"$set": {"application": progress["application"], "stage": progress["stage"]+1}}) + await msg.reply_text(locale(f"question{stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{stage+1}", "force_reply")))) + + logWrite(f"User {self.id} completed stage {stage} of application") \ No newline at end of file diff --git a/config_example.json b/config_example.json index cfa538b..0cd6a8d 100644 --- a/config_example.json +++ b/config_example.json @@ -22,6 +22,9 @@ "port": 27017, "name": "holochecker" }, + "geocoding": { + "username": "demo" + }, "logging": { "size": 512, "location": "logs" @@ -42,6 +45,7 @@ }, "commands": { "rules": "Check out the rules", + "nearby": "Show users near the area", "reapply": "Resubmit the application", "sponsorship": "Apply for sponsor role" }, @@ -57,11 +61,13 @@ "reboot": "Restart the bot", "message": "Send a message", "label": "Set user's nickname", + "nearby": "Show users near the area", "warnings": "Check user's warnings", "application": "Check user's application", "applications": "Retrieve all applications as a JSON" }, "commands_group_destination": { - "warn": "Warn a user" + "warn": "Warn a user", + "nearby": "Show users near the area" } } \ No newline at end of file diff --git a/holochecker.py b/holochecker.py index 6cc31c9..6062371 100644 --- a/holochecker.py +++ b/holochecker.py @@ -15,6 +15,7 @@ from modules.commands.application import * from modules.commands.applications import * from modules.commands.label import * from modules.commands.message import * +from modules.commands.nearby import * from modules.commands.reapply import * from modules.commands.reboot import * from modules.commands.rules import * diff --git a/locale/uk.json b/locale/uk.json index b775900..57ec6d1 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -5,7 +5,7 @@ "privacy_notice": "Раді це чути!\n\nДля продовження треба буде заповнити невеличку анкетку. Будь ласка, віднесись до цього серйозно. Ми відповідально ставимось до персональних даних, тому ця анкета не буде передана третім особам, а буде використана лише для проходження до спільноти.", "question1": "Як до тебе можна звертатись?", "question2": "Коли в тебе день народження?", - "question3": "З якого ти міста та де проживаєш зараз?\n\n⚠️ Будь ласка, не вказуйте точних адрес! \"Київщина\" може бути достатньою конкретизацією.", + "question3": "З якого ти міста або де проживаєш зараз?\n\n⚠️ Будь ласка, не вказуйте точних адрес! \"Київ\" або \"Київська Область\" є достатньою конкретизацією.\n\nПриклади:\n• Київ\n• Одеська область\n• Макіївка (Луганська область)", "question4": "Коли вперше довелось дізнатись про Хололайв?", "question5": "Чим тебе зацікавив Хололайв?", "question6": "Контент якої дівчини тобі подобається найбільше?", @@ -16,6 +16,10 @@ "question2_underage": "Вибач, але треба досягти віку {0} років, щоб приєднатись до нас. Такі обмеження існують для того, щоб всім у спільноті було цікаво одне з одним.", "question2_invalid": "Будь ласка, введи дату формату `ДД.ММ.РРРР`", "question2_joke": "Шутнік, ми так і поняли. Але будь ласка, введи реальне значення.", + "question3_invalid": "Місто/населений пункт не знайдено. Користуйтесь прикладами нижче щоб вказати де ви проживаєте та спробуйте ще раз:\n\n• Київ\n• Одеська область\n• Макіївка (Луганська область)", + "question3_found": "Використовую наступний результат:\n• {0} ({1})", + "question3_error": "⚠️ **Сталась помилка**\nНе вдалось отримати географічну мітку. Розробника повідомлено про цю помилку. Будь ласка, спробуйте ще раз.", + "question3_traceback": "⚠️ **Сталась помилка**\nПомилка отримання геокодингу для `{0}`\nПомилка: `{1}`\n\nTraceback:\n```\n{2}\n```", "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?", "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується. Гарного дня! :)", "application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`, `{2}`\nЮзернейм: @{3}\n\n**Дані анкети:**\n{4}", @@ -26,30 +30,32 @@ "approved": "Вітаємо! Твою анкету переглянули та підтвердили твоє право на вступ. Скористайся кнопкою під повідомленням щоб вступити до нашої лампової спільноти!", "approved_joined": "Вітаємо! Твою анкету переглянули та підтвердили її правильність. Дякуємо за витрачений на заповнення час та гарного дня!", "read_rules": "Будь ласка, прочитай ці правила перш ніж натискати на кнопку та приєднуватись до чату.", - "refused": "Ой лишенько! Твою анкету переглянули, однак не підтвердили право на вступ до спільноти. Better luck next time!", - "refused_russian": "русский военньій корабль, иди нахуй!", + "rejected": "Ой лишенько! Твою анкету переглянули, однак не підтвердили право на вступ до спільноти. Better luck next time!\n\nТи можеш спробувати повторно заповнити анкету командою /reapply", + "rejected_aggressive": "Ой лишенько! Твою анкету переглянули, однак не підтвердили право на вступ до спільноти.", + "rejected_russian": "русский военньій корабль, иди нахуй!", "approved_by": "✅ **Анкету схвалено**\nАдмін **{0}** переглянув та схвалив анкету `{1}`.", - "refused_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.", - "refused_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.", - "refused_by_rus": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: русня.", + "rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.", + "rejected_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.", + "rejected_by_rus": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: русня.", "contact": "Анкета `{0}`\n\n**Дані анкети:**\n{1}\n\n{2}", "application_status_accepted": "Прийнята `{0}` від {1}", - "application_status_refused": "Відхилена `{0}` від {1}", + "application_status_rejected": "Відхилена `{0}` від {1}", "application_status_on_hold": "Анкета все ще на розгляді", "application_status_not_send": "Анкета ще не була відправлена", - "contact_invalid": "Надісланий контакт не має розпочатої анкети.", + "contact_invalid": "Надісланий контакт не має завершеної анкети.", "contact_not_member": "Надісланий контакт не є користувачем Telegram.", "already_sent": "Анкету вже надіслано, просто почекай. Тобі одразу повідомлять, яке рішення буде прийнято.", "sus_joined": "Користувач **{0}** (`{1}`) зайшов до групи не за своїм персональним запрошенням.", "sus_allowed_by": "✅ **Доступ дозволено**\nАдмін **{0}** дозволив `{1}` вступити до спільноти не за персональним посиланням.", - "sus_refused_by": "❌ **Доступ заборонено**\nАдмін **{0}** заборонив `{1}` доступ до спільноти не за персональним посиланням.", + "sus_rejected_by": "❌ **Доступ заборонено**\nАдмін **{0}** заборонив `{1}` доступ до спільноти не за персональним посиланням.", "reapply_forbidden": "❌ **Дія неможлива**\nТвоя минула анкета ще не була схвалена або відхилена.", "reapply_in_progress": "❌ **Дія неможлива**\nТи прямо зараз вже заповнюєш анкету. Якщо в ній є помилка - просто натисни кнопку нижче щоб почати заповнювати спочатку.", "reapply_restarted": "🔁 **Перезапущено**\nРозпочате заповнення анкети спочатку.", "reapply_left_chat": "⚠️ **Нагадування**\nЗдається, ти залишив чат у минулому, проте твоя анкета все ще доступна до використання. Подати запит на вступ користуючись старою анкетою?", "birthday": "У користувача **{0}** (@{1}) сьогодні день народження! Виповнилось {2} років", "application_invalid_syntax": "Неправильний синтаксис!\nТреба: `/application ID/NAME/USERNAME`", - "warned": "Попереджено користувача **{0}** (`{1}`) про порушення правил", + "warned": "Попереджено **{0}** (`{1}`) про порушення правил", + "warned_reason": "Попереджено **{0}** (`{1}`)\n\n**Причина:**\n{2}", "warnings_1": "Користувач **{0}** (`{1}`) має **{2}** попередження", "warnings_2": "Користувач **{0}** (`{1}`) має **{2}** попереджень", "no_warnings": "Користувач **{0}** (`{1}`) не має попереджень", @@ -60,6 +66,11 @@ "message_invalid_syntax": "Неправильний синтаксис!\nТреба: `/message ID ПОВІДОМЛЕННЯ`", "message_from": "Повідомлення від **{0}** (`{1}`):\n\n", "message_reply_notice": "\n\n**Щоб надіслати відповідь на це повідомлення, тегніть його.**", + "message_error": "⚠️ **Сталась помилка**\nНе вдалось надіслати ваше повідомлення. Розробника повідомлено про цю помилку.", + "message_traceback": "⚠️ **Сталась помилка**\nПомилка повідомлень: `{0}` -> `{1}`\nПомилка: `{2}`\n\nTraceback:\n```\n{3}\n```", + "no_user_application": "Не знайдено користувачів за запитом **{0}**", + "user_invalid": "Надісланий користувач не має завершеної анкети.", + "joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням", "question_titles": { "question1": "Ім'я/звертання:", "question2": "День народження:", @@ -111,15 +122,15 @@ "button": { "sub_yes": "✅ Прийняти", "sub_no": "❌ Відхилити", - "sub_no_aggressive": "🤡 Відхилити (Токс)", - "sub_no_russian": "🇷🇺 Відхилити (Русак)", + "sub_aggressive": "🤡 Відхилити (Токс)", + "sub_russian": "🇷🇺 Відхилити (Русак)", "accepted": "✅ Прийнято", "declined": "❌ Відхилено", "join": "Приєднатись", "sus_allow": "✅ Підтвердити дозвіл", - "sus_refuse": "❌ Перманентно заблокувати", + "sus_reject": "❌ Перманентно заблокувати", "sus_allowed": "✅ Дозвіл надано", - "sus_refused": "❌ Користувача заблоковано", + "sus_rejected": "❌ Користувача заблоковано", "reapply_yes": "✅ Прийняти", "reapply_no": "❌ Відхилити", "reapply_old_one": "✅ Надіслати стару", @@ -133,11 +144,11 @@ }, "callback": { "sub_accepted": "✅ Анкету {0} схвалено", - "sub_refused": "❌ Анкету {0} відхилено", - "sub_no_aggressive": "🤡 Анкету {0} відхилено", - "sub_no_russian": "🇷🇺 Анкету {0} відхилено", + "sub_rejected": "❌ Анкету {0} відхилено", + "sub_aggressive": "🤡 Анкету {0} відхилено", + "sub_russian": "🇷🇺 Анкету {0} відхилено", "sus_allowed": "✅ Доступ {0} дозволено", - "sus_refused": "❌ Доступ {0} заборонено", + "sus_rejected": "❌ Доступ {0} заборонено", "nothing": "🔔 Дія вже виконана", "rules_page": "ℹ️ Показано правило {0}", "rules_home": "ℹ️ Показано головну правил", diff --git a/modules/callbacks/reapply.py b/modules/callbacks/reapply.py index 0318c57..74c2fe8 100644 --- a/modules/callbacks/reapply.py +++ b/modules/callbacks/reapply.py @@ -1,93 +1,82 @@ -from os import sep -from time import time +from datetime import datetime from app import app from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardRemove from pyrogram import filters -from modules.utils import configGet, configSet, jsonLoad, jsonSave, locale, logWrite +from classes.holo_user import HoloUser +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 # Callbacks reapply ============================================================================================================ @app.on_callback_query(filters.regex("reapply_yes_[\s\S]*")) async def callback_reapply_query_accept(app, clb): fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) - await app.send_message(configGet("admin_group"), locale("approved_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True) - logWrite(f"User {fullclb[2]} got their reapplication approved by {clb.from_user.id}") + await app.send_message(configGet("admin_group"), locale("approved_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + logWrite(f"User {holo_user.id} got their reapplication approved by {clb.from_user.id}") - await app.send_message(int(fullclb[2]), locale("approved_joined", "message")) + await app.send_message(holo_user.id, locale("approved_joined", "message")) - configSet(["approved"], True, file=fullclb[2]) - configSet(["sent"], False, file=fullclb[2]) - - application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json") - application[fullclb[2]]["approved"] = True - application[fullclb[2]]["approved_by"] = clb.from_user.id - application[fullclb[2]]["approval_date"] = int(time()) - jsonSave(application, f"{configGet('data', 'locations')}{sep}applications.json") + col_applications.delete_one({"user": {"$eq": holo_user.id}}) + col_applications.insert_one({"user": holo_user.id, "date": datetime.now(), "admin": clb.from_user.id, "application": col_tmp.find_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}})["application"]}) + col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "approved", "sent": False}}) edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) - await clb.answer(text=locale("sub_accepted", "callback").format(fullclb[2]), show_alert=True) + await clb.answer(text=locale("sub_accepted", "callback").format(holo_user.id), show_alert=True) need_link = True async for member in app.get_chat_members(configGet("destination_group")): - if member.user.id == int(fullclb[2]): + if member.user.id == holo_user.id: need_link = False if need_link: - link = await app.create_chat_invite_link(configGet("destination_group"), name=f"Invite for {fullclb[2]}", member_limit=1) #, expire_date=datetime.now()+timedelta(days=1)) + link = await app.create_chat_invite_link(configGet("destination_group"), name=f"Invite for {holo_user.id}", member_limit=1) #, expire_date=datetime.now()+timedelta(days=1)) - await app.send_message(int(fullclb[2]), locale("read_rules", "message")) + await app.send_message(holo_user.id, locale("read_rules", "message")) for rule_msg in locale("rules"): - await app.send_message(int(fullclb[2]), rule_msg) + await app.send_message(holo_user.id, rule_msg) - await app.send_message(int(fullclb[2]), locale("approved", "message"), reply_markup=InlineKeyboardMarkup( + await app.send_message(holo_user.id, locale("approved", "message"), reply_markup=InlineKeyboardMarkup( [[ InlineKeyboardButton(str(locale("join", "button")), url=link.invite_link) ]] )) - configSet(["link"], link.invite_link, file=fullclb[2]) - logWrite(f"User {fullclb[2]} got an invite link {link.invite_link}") + holo_user.set("link", link.invite_link) + logWrite(f"User {holo_user.id} got an invite link {link.invite_link}") else: - await app.send_message(int(fullclb[2]), locale("approved_joined", "message")) + await app.send_message(holo_user.id, locale("approved_joined", "message")) @app.on_callback_query(filters.regex("reapply_no_[\s\S]*")) -async def callback_query_reapply_refuse(app, clb): +async def callback_query_reapply_reject(app, clb): fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) - await app.send_message(configGet("admin_group"), locale("refused_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True) - await app.send_message(int(fullclb[2]), locale("refused", "message")) - logWrite(f"User {fullclb[2]} got their reapplication refused by {clb.from_user.id}") + await app.send_message(configGet("admin_group"), locale("rejected_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True) + await app.send_message(holo_user.id, locale("rejected", "message")) + logWrite(f"User {fullclb[2]} got their reapplication rejected by {clb.from_user.id}") - configSet(["refused"], True, file=fullclb[2]) - configSet(["sent"], False, file=fullclb[2]) - - application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json") - application[fullclb[2]]["refused"] = True - application[fullclb[2]]["refused_by"] = clb.from_user.id - application[fullclb[2]]["refusal_date"] = int(time()) - jsonSave(application, f"{configGet('data', 'locations')}{sep}applications.json") + col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}}) edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) - await clb.answer(text=locale("sub_refused", "callback").format(fullclb[2]), show_alert=True) + await clb.answer(text=locale("sub_rejected", "callback").format(fullclb[2]), show_alert=True) # Use old application when user reapplies after leaving the chat @app.on_callback_query(filters.regex("reapply_old_[\s\S]*")) async def callback_query_reapply_old(app, clb): fullclb = clb.data.split("_") message = await app.get_messages(clb.from_user.id, int(fullclb[2])) - configSet(["approved"], False, file=str(clb.from_user.id)) - configSet(["refused"], False, file=str(clb.from_user.id)) await confirm_yes(app, message) await clb.message.edit(clb.message.text, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("done", "button"), "nothing")]])) @@ -97,11 +86,6 @@ async def callback_query_reapply_new(app, clb): fullclb = clb.data.split("_") - jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}user_default.json"), f"{configGet('data', 'locations')}{sep}users{sep}{clb.from_user.id}.json") - configSet(["telegram_id"], str(clb.from_user.username), file=str(clb.from_user.id)) - configSet(["telegram_name"], f"{clb.from_user.first_name} {clb.from_user.last_name}", file=str(clb.from_user.id)) - configSet(["telegram_phone"], str(clb.from_user.phone_number), file=str(clb.from_user.id)) - configSet(["telegram_locale"], str(clb.from_user.language_code), file=str(clb.from_user.id)) await clb.answer(locale("reapply_stopped", "callback")) message = await app.get_messages(clb.from_user.id, int(fullclb[2])) await welcome_pass(app, message, once_again=True) @@ -113,12 +97,9 @@ async def callback_query_reapply_new(app, clb): async def callback_query_reapply_stop(app, clb): fullclb = clb.data.split("_") + holo_user = HoloUser(clb.from_user) - jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}user_default.json"), f"{configGet('data', 'locations')}{sep}users{sep}{clb.from_user.id}.json") - configSet(["telegram_id"], str(clb.from_user.username), file=str(clb.from_user.id)) - configSet(["telegram_name"], f"{clb.from_user.first_name} {clb.from_user.last_name}", file=str(clb.from_user.id)) - configSet(["telegram_phone"], str(clb.from_user.phone_number), file=str(clb.from_user.id)) - configSet(["telegram_locale"], str(clb.from_user.language_code), file=str(clb.from_user.id)) + holo_user.application_restart() await clb.answer(locale("reapply_stopped", "callback")) message = await app.get_messages(clb.from_user.id, int(fullclb[2])) await welcome_pass(app, message, once_again=True) diff --git a/modules/callbacks/sub.py b/modules/callbacks/sub.py index c21d7d1..de9163a 100644 --- a/modules/callbacks/sub.py +++ b/modules/callbacks/sub.py @@ -1,126 +1,103 @@ -from os import sep -from time import time +from datetime import datetime from app import app from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton from pyrogram import filters -from modules.utils import configGet, configSet, jsonLoad, jsonSave, locale, logWrite +from classes.holo_user import HoloUser +from modules.utils import configGet, locale, logWrite +from modules.database import col_tmp, col_applications +from modules.commands.rules import default_rules_markup # Callbacks application ======================================================================================================== @app.on_callback_query(filters.regex("sub_yes_[\s\S]*")) async def callback_query_accept(app, clb): fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) - await app.send_message(configGet("admin_group"), locale("approved_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True) - logWrite(f"User {fullclb[2]} got approved by {clb.from_user.id}") + await app.send_message(configGet("admin_group"), locale("approved_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + logWrite(f"User {holo_user.id} got approved by {clb.from_user.id}") need_link = True async for member in app.get_chat_members(configGet("destination_group")): - if member.user.id == int(fullclb[2]): + if member.user.id == holo_user.id: need_link = False if need_link: - link = await app.create_chat_invite_link(configGet("destination_group"), name=f"Invite for {fullclb[2]}", member_limit=1) #, expire_date=datetime.now()+timedelta(days=1)) + link = await app.create_chat_invite_link(configGet("destination_group"), name=f"Invite for {holo_user.id}", member_limit=1) #, expire_date=datetime.now()+timedelta(days=1)) - await app.send_message(int(fullclb[2]), locale("read_rules", "message")) + await app.send_message(holo_user.id, locale("read_rules", "message")) - for rule_msg in locale("rules"): - await app.send_message(int(fullclb[2]), rule_msg) + await app.send_message(holo_user.id, locale("rules_msg"), disable_web_page_preview=True, reply_markup=default_rules_markup) - await app.send_message(int(fullclb[2]), locale("approved", "message"), reply_markup=InlineKeyboardMarkup( + await app.send_message(holo_user.id, locale("approved", "message"), reply_markup=InlineKeyboardMarkup( [[ InlineKeyboardButton(str(locale("join", "button")), url=link.invite_link) ]] )) - configSet(["link"], link.invite_link, file=fullclb[2]) - logWrite(f"User {fullclb[2]} got an invite link {link.invite_link}") + holo_user.set("link", link.invite_link) + logWrite(f"User {holo_user.id} got an invite link {link.invite_link}") else: - await app.send_message(int(fullclb[2]), locale("approved_joined", "message")) + await app.send_message(holo_user.id, locale("approved_joined", "message")) - configSet(["approved"], True, file=fullclb[2]) - configSet(["sent"], False, file=fullclb[2]) - - application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json") - application[fullclb[2]]["approved"] = True - application[fullclb[2]]["approved_by"] = clb.from_user.id - application[fullclb[2]]["approval_date"] = int(time()) - jsonSave(application, f"{configGet('data', 'locations')}{sep}applications.json") + col_applications.insert_one({"user": holo_user.id, "date": datetime.now(), "admin": clb.from_user.id, "application": col_tmp.find_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}})["application"]}) + col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "approved", "sent": False}}) edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) - await clb.answer(text=locale("sub_accepted", "callback").format(fullclb[2]), show_alert=True) - -@app.on_callback_query(filters.regex("sub_no_aggressive_[\s\S]*")) -async def callback_query_refuse_aggressive(app, clb): - - fullclb = clb.data.split("_") - - await app.send_message(configGet("admin_group"), locale("refused_by_agr", "message").format(clb.from_user.first_name, fullclb[3]), disable_notification=True) - await app.send_message(int(fullclb[3]), locale("refused", "message")) - logWrite(f"User {fullclb[3]} got refused by {clb.from_user.id} due to being aggressive") - - configSet(["refused"], True, file=fullclb[3]) - configSet(["sent"], False, file=fullclb[3]) - - application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json") - application[fullclb[3]]["refused"] = True - application[fullclb[3]]["refused_by"] = clb.from_user.id - application[fullclb[3]]["refusal_date"] = int(time()) - jsonSave(application, f"{configGet('data', 'locations')}{sep}applications.json") - - edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] - - await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) - await clb.answer(text=locale("sub_no_aggressive", "callback").format(fullclb[3]), show_alert=True) - -@app.on_callback_query(filters.regex("sub_no_russian_[\s\S]*")) -async def callback_query_refuse_russian(app, clb): - - fullclb = clb.data.split("_") - - await app.send_message(configGet("admin_group"), locale("refused_by_rus", "message").format(clb.from_user.first_name, fullclb[3]), disable_notification=True) - await app.send_message(int(fullclb[3]), locale("refused", "message")) - await app.send_message(int(fullclb[3]), locale("refused_russian", "message")) - logWrite(f"User {fullclb[3]} got refused by {clb.from_user.id} due to being russian") - - configSet(["refused"], True, file=fullclb[3]) - configSet(["sent"], False, file=fullclb[3]) - - application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json") - application[fullclb[3]]["refused"] = True - application[fullclb[3]]["refused_by"] = clb.from_user.id - application[fullclb[3]]["refusal_date"] = int(time()) - jsonSave(application, f"{configGet('data', 'locations')}{sep}applications.json") - - edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] - - await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) - await clb.answer(text=locale("sub_no_russian", "callback").format(fullclb[3]), show_alert=True) + await clb.answer(text=locale("sub_accepted", "callback").format(holo_user.id), show_alert=True) @app.on_callback_query(filters.regex("sub_no_[\s\S]*")) -async def callback_query_refuse(app, clb): +async def callback_query_reject(app, clb): fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) - await app.send_message(configGet("admin_group"), locale("refused_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True) - await app.send_message(int(fullclb[2]), locale("refused", "message")) - logWrite(f"User {fullclb[2]} got refused by {clb.from_user.id}") + await app.send_message(configGet("admin_group"), locale("rejected_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + await app.send_message(holo_user.id, locale("rejected", "message")) + logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id}") - configSet(["refused"], True, file=fullclb[2]) - configSet(["sent"], False, file=fullclb[2]) - - application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json") - application[fullclb[2]]["refused"] = True - application[fullclb[2]]["refused_by"] = clb.from_user.id - application[fullclb[2]]["refusal_date"] = int(time()) - jsonSave(application, f"{configGet('data', 'locations')}{sep}applications.json") + col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}}) edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) - await clb.answer(text=locale("sub_refused", "callback").format(fullclb[2]), show_alert=True) + await clb.answer(text=locale("sub_rejected", "callback").format(holo_user.id), show_alert=True) + +@app.on_callback_query(filters.regex("sub_aggressive_[\s\S]*")) +async def callback_query_reject_aggressive(app, clb): + + fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) + + await app.send_message(configGet("admin_group"), locale("rejected_by_agr", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + await app.send_message(holo_user.id, locale("rejected_aggressive", "message")) + logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id} due to being aggressive") + + col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}}) + + edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] + + await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) + await clb.answer(text=locale("sub_aggressive", "callback").format(holo_user.id), show_alert=True) + +@app.on_callback_query(filters.regex("sub_russian_[\s\S]*")) +async def callback_query_reject_russian(app, clb): + + fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) + + await app.send_message(configGet("admin_group"), locale("rejected_by_rus", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + await app.send_message(holo_user.id, locale("rejected_russian", "message")) + logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id} due to being russian") + + col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}}) + + edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] + + await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) + await clb.answer(text=locale("sub_russian", "callback").format(holo_user.id), show_alert=True) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/callbacks/sus.py b/modules/callbacks/sus.py index 62931b3..2f49542 100644 --- a/modules/callbacks/sus.py +++ b/modules/callbacks/sus.py @@ -1,24 +1,26 @@ -from os import sep from app import app from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ChatPermissions from pyrogram import filters -from modules.utils import configGet, configSet, jsonLoad, jsonSave, locale, logWrite +from classes.holo_user import HoloUser +from modules.utils import configGet, locale, logWrite +from modules.database import col_tmp # Callbacks sus users ========================================================================================================== @app.on_callback_query(filters.regex("sus_allow_[\s\S]*")) async def callback_query_sus_allow(app, clb): fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) - await app.send_message(configGet("admin_group"), locale("sus_allowed_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True) - logWrite(f"User {fullclb[2]} was allowed to join with another link by {clb.from_user.id}") + await app.send_message(configGet("admin_group"), locale("sus_allowed_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + logWrite(f"User {holo_user.id} was allowed to join with another link by {clb.from_user.id}") edited_markup = [[InlineKeyboardButton(text=str(locale("sus_allowed", "button")), callback_data="nothing")]] await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) - await clb.answer(text=locale("sus_allowed", "callback").format(fullclb[2]), show_alert=True) + await clb.answer(text=locale("sus_allowed", "callback").format(holo_user.id), show_alert=True) - await app.restrict_chat_member(configGet("destination_group"), int(fullclb[2]), permissions=ChatPermissions( + await app.restrict_chat_member(configGet("destination_group"), holo_user.id, permissions=ChatPermissions( can_send_messages=True, can_send_media_messages=True, can_send_other_messages=True, @@ -26,23 +28,21 @@ async def callback_query_sus_allow(app, clb): ) ) -@app.on_callback_query(filters.regex("sus_refuse_[\s\S]*")) -async def callback_query_sus_refuse(app, clb): +@app.on_callback_query(filters.regex("sus_reject_[\s\S]*")) +async def callback_query_sus_reject(app, clb): fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) - await app.send_message(configGet("admin_group"), locale("sus_refused_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True) - logWrite(f"User {fullclb[2]} was refused to join with another link by {clb.from_user.id}") + await app.send_message(configGet("admin_group"), locale("sus_rejected_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + logWrite(f"User {holo_user.id} was rejected to join with another link by {clb.from_user.id}") - edited_markup = [[InlineKeyboardButton(text=str(locale("sus_refused", "button")), callback_data="nothing")]] + edited_markup = [[InlineKeyboardButton(text=str(locale("sus_rejected", "button")), callback_data="nothing")]] await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) - await clb.answer(text=locale("sus_refused", "callback").format(fullclb[2]), show_alert=True) + await clb.answer(text=locale("sus_rejected", "callback").format(holo_user.id), show_alert=True) - await app.ban_chat_member(configGet("destination_group"), int(fullclb[2])) + await app.ban_chat_member(configGet("destination_group"), holo_user.id) - jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}user_default.json"), f"{configGet('data', 'locations')}{sep}users{sep}{fullclb[2]}.json") - configSet(["stage"], 10, file=fullclb[2]) - configSet(["refused"], True, file=fullclb[2]) - configSet(["refused_by"], clb.from_user.id, file=fullclb[2]) + col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}}) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/application.py b/modules/commands/application.py index 5bf3186..ad3d24f 100644 --- a/modules/commands/application.py +++ b/modules/commands/application.py @@ -1,55 +1,95 @@ -from os import sep, path from datetime import datetime from app import app, isAnAdmin from pyrogram import filters -from pyrogram.enums.chat_members_filter import ChatMembersFilter -from modules.utils import configGet, jsonLoad, logWrite, locale, should_quote +from pyrogram.enums.parse_mode import ParseMode +from pyrogram.errors import bad_request_400 +from classes.holo_user import HoloUser, UserNotFoundError +from modules.utils import logWrite, locale, should_quote from dateutil.relativedelta import relativedelta +from modules.database import col_applications # Applications command ========================================================================================================= @app.on_message(~ filters.scheduled & filters.command(["application"], prefixes=["/"])) async def cmd_application(app, msg): - if (await isAnAdmin(msg.from_user.id)) or (msg.chat.id == configGet("admin_group")): + if await isAnAdmin(msg.from_user.id) is True: + try: - if (path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json") and jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json")["approved"]): - user_id = int(msg.command[1]) - else: - list_of_users = [] - async for m in app.get_chat_members(configGet("destination_group"), filter=ChatMembersFilter.SEARCH, query=msg.command[1]): - list_of_users.append(m) - user_id = list_of_users[0].user.id + try: - user_data = jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{user_id}.json") - application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json")[str(user_id)] - application_content = [] - i = 1 - for question in configGet("application", file=str(msg.from_user.id)): - if i == 2: - age = relativedelta(datetime.now(), datetime.strptime(application['application']['2'], '%d.%m.%Y')) - application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {application['application']['2']} ({age.years} р.)") + holo_user = HoloUser(int(msg.command[1])) + except (ValueError, UserNotFoundError): + try: + holo_user = HoloUser((await app.get_users(msg.command[1])).id) + except (bad_request_400.UsernameInvalid, bad_request_400.PeerIdInvalid): + await msg.reply_text(locale("no_user_application", "message").format(msg.command[1]), quote=should_quote(msg)) + return + + application = col_applications.find_one({"user": holo_user.id}) + + if application is None: + logWrite(f"User {msg.from_user.id} requested application of {holo_user.id} but user does not exists") + await msg.reply_text(locale("user_invalid", "message"), quote=should_quote(msg)) + return + + application_content = [] + i = 1 + + for question in application['application']: + + if i == 2: + age = relativedelta(datetime.now(), application['application']['2']) + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)") + elif i == 3: + if application['application']['3']['countryCode'] == "UA": + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']}") else: - application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {application['application'][question]}") - i += 1 - if user_data["sent"]: - if user_data["approved"]: - application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M")) - elif application["refused"]: - application_status = locale("application_status_refused", "message").format((await app.get_users(application["refused_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M")) - else: - application_status = locale("application_status_on_hold", "message") + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})") else: - if user_data["approved"]: - application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M")) - elif application["refused"]: - application_status = locale("application_status_refused", "message").format((await app.get_users(application["refused_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M")) - else: - application_status = locale("application_status_not_send", "message") - logWrite(f"User {msg.from_user.id} requested application of {user_id}") - await msg.reply_text(locale("contact", "message").format(str(user_id), "\n".join(application_content), application_status), quote=should_quote(msg)) - except FileNotFoundError: - logWrite(f"User {msg.from_user.id} requested application of {user_id} but user does not exists") - await msg.reply_text(locale("contact_invalid", "message"), quote=should_quote(msg)) + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application'][question]}") + + i += 1 + + application_status = locale("application_status_accepted", "message").format((await app.get_users(application["admin"])).first_name, application["date"].strftime("%d.%m.%Y, %H:%M")) + + logWrite(f"User {msg.from_user.id} requested application of {holo_user.id}") + await msg.reply_text(locale("contact", "message").format(holo_user.id, "\n".join(application_content), application_status), parse_mode=ParseMode.MARKDOWN, quote=should_quote(msg)) + + # if (path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json") and jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json")["approved"]): + # user_id = int(msg.command[1]) + # else: + # list_of_users = [] + # async for m in app.get_chat_members(configGet("destination_group"), filter=ChatMembersFilter.SEARCH, query=msg.command[1]): + # list_of_users.append(m) + # user_id = list_of_users[0].user.id + # try: + # user_data = jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{user_id}.json") + # application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json")[str(user_id)] + # application_content = [] + # i = 1 + # for question in configGet("application", file=str(msg.from_user.id)): + # if i == 2: + # age = relativedelta(datetime.now(), datetime.strptime(application['application']['2'], '%d.%m.%Y')) + # application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {application['application']['2']} ({age.years} р.)") + # else: + # application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {application['application'][question]}") + # i += 1 + # if user_data["sent"]: + # if user_data["approved"]: + # application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M")) + # elif application["rejected"]: + # application_status = locale("application_status_rejected", "message").format((await app.get_users(application["rejected_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M")) + # else: + # application_status = locale("application_status_on_hold", "message") + # else: + # if user_data["approved"]: + # application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M")) + # elif application["rejected"]: + # application_status = locale("application_status_rejected", "message").format((await app.get_users(application["rejected_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M")) + # else: + # application_status = locale("application_status_not_send", "message") + # logWrite(f"User {msg.from_user.id} requested application of {user_id}") + # await msg.reply_text(locale("contact", "message").format(str(user_id), "\n".join(application_content), application_status), quote=should_quote(msg)) except IndexError: await msg.reply_text(locale("application_invalid_syntax", "message"), quote=should_quote(msg)) diff --git a/modules/commands/applications.py b/modules/commands/applications.py index 4b7578c..b0c29b8 100644 --- a/modules/commands/applications.py +++ b/modules/commands/applications.py @@ -3,18 +3,23 @@ from uuid import uuid1 from app import app, isAnAdmin from pyrogram import filters from pyrogram.enums.chat_action import ChatAction -from modules.utils import configGet, should_quote, jsonSave +from modules.logging import logWrite +from modules.utils import should_quote, jsonSave from modules.database import col_applications # Applications command ========================================================================================================= @app.on_message(~ filters.scheduled & filters.command(["applications"], prefixes=["/"])) async def cmd_applications(app, msg): - if (await isAnAdmin(msg.from_user.id)) or (msg.chat.id == configGet("admin_group")): + if await isAnAdmin(msg.from_user.id) is True: + 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") diff --git a/modules/commands/label.py b/modules/commands/label.py index 903edd6..5a3edcd 100644 --- a/modules/commands/label.py +++ b/modules/commands/label.py @@ -1,13 +1,12 @@ from app import app, isAnAdmin from pyrogram import filters -from pyrogram.types import ChatPrivileges -from modules.utils import should_quote, find_user, configGet +from modules.utils import should_quote, find_user from classes.holo_user import HoloUser @app.on_message(~ filters.scheduled & filters.private & filters.command(["label"], prefixes=["/"])) async def cmd_label(app, msg): - if msg.chat.id == configGet("admin_group") or await isAnAdmin(msg.from_user.id): + if await isAnAdmin(msg.from_user.id) is True: if len(msg.command) < 3: await msg.reply_text("Invalid syntax:\n`/label USER LABEL`") diff --git a/modules/commands/message.py b/modules/commands/message.py index f05c33c..fa6dd8a 100644 --- a/modules/commands/message.py +++ b/modules/commands/message.py @@ -1,16 +1,13 @@ -from os import sep from app import app, isAnAdmin from pyrogram import filters -from pyrogram.errors import bad_request_400 from classes.holo_user import HoloUser -from modules.utils import jsonLoad, jsonSave, logWrite, locale, configGet, should_quote -from modules.database import col_messages +from modules.utils import logWrite, locale, should_quote # Message command ============================================================================================================== @app.on_message(~ filters.scheduled & filters.command(["message"], prefixes=["/"])) async def cmd_message(app, msg): - if msg.chat.id == configGet("admin_group") or await isAnAdmin(msg.from_user.id): + if await isAnAdmin(msg.from_user.id) is True: try: @@ -19,19 +16,13 @@ async def cmd_message(app, msg): except ValueError: destination = HoloUser(msg.command[1]) - void = msg.command[2] - message = " ".join(msg.command[2:]) - - await destination.message(msg, msg.command[2:]) - - # try: - # new_message = await app.send_message(destination.id, message+locale("message_reply_notice", "message")) - # await msg.reply_text(locale("message_sent", "message"), quote=should_quote(msg)) - # logWrite(f"Admin {msg.from_user.id} sent message '{' '.join(msg.command[2:])}' to {destination.id}") - # col_messages.insert_one({"origin": {"chat": msg.chat.id, "id": msg.id}, "destination": {"chat": new_message.chat.id, "id": new_message.id}}) - # except bad_request_400.PeerIdInvalid: - # await msg.reply_text(locale("message_no_user", "message"), quote=should_quote(msg)) - # logWrite(f"Admin {msg.from_user.id} tried to send message '{' '.join(msg.command[2:])}' to {destination.id} but 'PeerIdInvalid'") + if ((msg.text is not None) and (len(msg.text.split()) > 2)): + await destination.message(context=msg, text=" ".join(msg.text.split()[2:]), caption=msg.caption, photo=msg.photo, video=msg.video, file=msg.document, adm_context=True) + elif ((msg.caption is not None) and (len(msg.caption.split()) > 2)): + await destination.message(context=msg, text=msg.text, caption=" ".join(msg.caption.split()[2:]), photo=msg.photo, video=msg.video, file=msg.document, adm_context=True) + else: + await destination.message(context=msg, text=None, caption=None, photo=msg.photo, video=msg.video, file=msg.document, adm_context=True) + except IndexError: await msg.reply_text(locale("message_invalid_syntax", "message"), quote=should_quote(msg)) logWrite(f"Admin {msg.from_user.id} tried to send message but 'IndexError'") diff --git a/modules/commands/nearby.py b/modules/commands/nearby.py new file mode 100644 index 0000000..d7a41ec --- /dev/null +++ b/modules/commands/nearby.py @@ -0,0 +1,24 @@ +from app import app, isAnAdmin +from pyrogram import filters +from modules.utils import configGet, should_quote +from modules.database import col_applications + +# Nearby command =============================================================================================================== +@app.on_message(~ filters.scheduled & (filters.private | (filters.chat(configGet("admin_group")) | filters.chat(configGet("destination_group")))) & filters.command(["nearby"], prefixes=["/"])) +async def cmd_nearby(app, msg): + if (await isAnAdmin(msg) is True) or (col_applications.find_one({"user": msg.from_user.id}) is not None): + await msg.reply_text("Yes, I exist.", quote=should_quote(msg)) + # if not path.exists(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json"): + # jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}sponsor_default.json"), f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json") + # sponsor = jsonLoad(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json") + # if sponsor["approved"]: + # if sponsor["expires"] is not None: + # if datetime.strptime(sponsor["expires"], "%d.%m.%Y") > datetime.now(): + # await msg.reply_text(f"You have an active sub til **{sponsor['expires']}**.") + # else: + # await msg.reply_text(f"Your sub expired {int((datetime.now()-datetime.strptime(sponsor['expires'], '%d.%m.%Y')).days)} days ago.") + # elif sponsor["approved"]: + # await msg.reply_text(f"Your sub expiration date is not valid.") + # else: + # await msg.reply_text(f"You have no active subscription.") +# ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/reapply.py b/modules/commands/reapply.py index b17fa4c..bb81eca 100644 --- a/modules/commands/reapply.py +++ b/modules/commands/reapply.py @@ -1,22 +1,25 @@ from app import app from pyrogram import filters from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from modules.utils import configGet, configSet, locale +from classes.holo_user import HoloUser +from modules.utils import configGet, locale from modules.handlers.welcome import welcome_pass +from modules.database import col_tmp # Reapply command ============================================================================================================== @app.on_message(~ filters.scheduled & filters.private & filters.command(["reapply"], prefixes=["/"])) async def cmd_reapply(app, msg): - if configGet("approved", file=str(msg.from_user.id)) or configGet("refused", file=str(msg.from_user.id)): - if (configGet("stage", file=str(msg.from_user.id)) == 10) and not (configGet("sent", file=str(msg.from_user.id))): + 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"]): 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: - configSet(["reapply"], True, file=str(msg.from_user.id)) - configSet(["confirmed"], False, file=str(msg.from_user.id)) + holo_user.application_restart() await welcome_pass(app, msg, once_again=True) else: await msg.reply_text(locale("reapply_left_chat", "message"), reply_markup=InlineKeyboardMarkup([ @@ -34,7 +37,7 @@ async def cmd_reapply(app, msg): ] ])) else: - if configGet("sent", file=str(msg.from_user.id)): + 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")) else: await msg.reply_text(locale("reapply_in_progress", "message").format(locale("confirm", "keyboard")[1][0]), reply_markup=InlineKeyboardMarkup([ diff --git a/modules/commands/reboot.py b/modules/commands/reboot.py index 1397f41..6945079 100644 --- a/modules/commands/reboot.py +++ b/modules/commands/reboot.py @@ -2,7 +2,7 @@ from app import app, isAnAdmin from os import getpid from sys import exit from pyrogram import filters -from modules.utils import configGet, logWrite, should_quote +from modules.utils import logWrite, should_quote from modules.scheduled import scheduler pid = getpid() @@ -11,7 +11,7 @@ pid = getpid() @app.on_message(~ filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["/"])) async def cmd_kill(app, msg): - if msg.chat.id == configGet("admin_group") or await isAnAdmin(msg.from_user.id): + if await isAnAdmin(msg.from_user.id) is True: logWrite(f"Shutting down bot with pid {pid}") await msg.reply_text(f"Вимкнення бота з підом `{pid}`", quote=should_quote(msg)) scheduler.shutdown() diff --git a/modules/commands/sponsorship.py b/modules/commands/sponsorship.py index bde9db1..d5234fb 100644 --- a/modules/commands/sponsorship.py +++ b/modules/commands/sponsorship.py @@ -1,23 +1,25 @@ from datetime import datetime -from os import path, sep -from app import app +from app import app, isAnAdmin from pyrogram import filters -from modules.utils import configGet, jsonLoad, jsonSave +from modules.utils import should_quote +from modules.database import col_applications # Sponsorship command ========================================================================================================== @app.on_message(~ filters.scheduled & filters.command(["sponsorship"], prefixes=["/"])) async def cmd_sponsorship(app, msg): - if not path.exists(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json"): - jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}sponsor_default.json"), f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json") - sponsor = jsonLoad(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json") - if sponsor["approved"]: - if sponsor["expires"] is not None: - if datetime.strptime(sponsor["expires"], "%d.%m.%Y") > datetime.now(): - await msg.reply_text(f"You have an active sub til **{sponsor['expires']}**.") - else: - await msg.reply_text(f"Your sub expired {int((datetime.now()-datetime.strptime(sponsor['expires'], '%d.%m.%Y')).days)} days ago.") - elif sponsor["approved"]: - await msg.reply_text(f"Your sub expiration date is not valid.") - else: - await msg.reply_text(f"You have no active subscription.") + if (await isAnAdmin(msg) is True) or (col_applications.find_one({"user": msg.from_user.id}) is not None): + await msg.reply_text("Yes, I exist.", quote=should_quote(msg)) + # if not path.exists(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json"): + # jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}sponsor_default.json"), f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json") + # sponsor = jsonLoad(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json") + # if sponsor["approved"]: + # if sponsor["expires"] is not None: + # if datetime.strptime(sponsor["expires"], "%d.%m.%Y") > datetime.now(): + # await msg.reply_text(f"You have an active sub til **{sponsor['expires']}**.") + # else: + # await msg.reply_text(f"Your sub expired {int((datetime.now()-datetime.strptime(sponsor['expires'], '%d.%m.%Y')).days)} days ago.") + # elif sponsor["approved"]: + # await msg.reply_text(f"Your sub expiration date is not valid.") + # else: + # await msg.reply_text(f"You have no active subscription.") # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/start.py b/modules/commands/start.py index da6eb29..e4260ba 100644 --- a/modules/commands/start.py +++ b/modules/commands/start.py @@ -1,5 +1,4 @@ from app import app -from os import sep from pyrogram import filters from pyrogram.types import ReplyKeyboardMarkup from modules.utils import locale, logWrite diff --git a/modules/commands/warn.py b/modules/commands/warn.py index 3a8419d..50f948c 100644 --- a/modules/commands/warn.py +++ b/modules/commands/warn.py @@ -1,7 +1,8 @@ -from os import sep +from datetime import datetime from app import app, isAnAdmin from pyrogram import filters -from modules.utils import jsonLoad, jsonSave, configGet, locale +from modules.utils import configGet, locale +from modules.database import col_warnings # Warn command ================================================================================================================= @app.on_message(~ filters.scheduled & filters.command(["warn"], prefixes=["/"])) @@ -9,12 +10,11 @@ async def cmd_warn(app, msg): if msg.chat.id == configGet("destination_group"): if msg.reply_to_message_id != None: - if isAnAdmin(msg.from_user.id): - warnings = jsonLoad(f"{configGet('data', 'locations')}{sep}warnings.json") - if str(msg.reply_to_message.from_user.id) not in warnings: - warnings[str(msg.reply_to_message.from_user.id)] = 1 + if await isAnAdmin(msg.from_user.id) is True: + message = " ".join(msg.command[1:]) if len(msg.command) > 1 else "" + col_warnings.insert_one({"user": msg.reply_to_message.from_user.id, "admin": msg.from_user.id, "date": datetime.now(), "reason": message}) + if message == "": + await msg.reply_text(locale("warned", "message").format(msg.reply_to_message.from_user.first_name, msg.reply_to_message.from_user.id)) else: - warnings[str(msg.reply_to_message.from_user.id)] += 1 - jsonSave(warnings, f"{configGet('data', 'locations')}{sep}warnings.json") - await msg.reply_text(locale("warned", "message").format(msg.reply_to_message.from_user.first_name, msg.reply_to_message.from_user.id)) + await msg.reply_text(locale("warned_reason", "message").format(msg.reply_to_message.from_user.first_name, msg.reply_to_message.from_user.id, message)) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/warnings.py b/modules/commands/warnings.py index d88b99b..843817e 100644 --- a/modules/commands/warnings.py +++ b/modules/commands/warnings.py @@ -1,24 +1,23 @@ -from os import path, sep from app import app, isAnAdmin from pyrogram import filters from pyrogram.enums.chat_members_filter import ChatMembersFilter -from modules.utils import configGet, jsonLoad, locale, should_quote +from modules.utils import configGet, locale, should_quote +from modules.database import col_users, col_warnings # Warnings command ============================================================================================================= @app.on_message(~ filters.scheduled & filters.command(["warnings"], prefixes=["/"])) async def cmd_warnings(app, msg): - if msg.chat.id == configGet("admin_group") or await isAnAdmin(msg.from_user.id): - - warnings = jsonLoad(f"{configGet('data', 'locations')}{sep}warnings.json") + if await isAnAdmin(msg.from_user.id) is True: if len(msg.command) <= 1: await msg.reply_text(locale("syntax_warnings", "message"), quote=should_quote(msg)) - if path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json"): - target_id = str(int(msg.command[1])) - target_name = "N/A" - else: + try: + user_db = col_users.find_one({"user": int(msg.command[1])}) + target_id = user_db["user"] + target_name = user_db["tg_name"] + except: list_of_users = [] async for m in app.get_chat_members(configGet("destination_group"), filter=ChatMembersFilter.SEARCH, query=msg.command[1]): list_of_users.append(m) @@ -31,11 +30,13 @@ async def cmd_warnings(app, msg): await msg.reply_text(locale("no_user_warnings", "message").format(msg.command[1])) return - if target_id not in warnings: + warns = len(list(col_warnings.find({"user": target_id}))) + + if warns == 0: await msg.reply_text(locale("no_warnings", "message").format(target_name, target_id), quote=should_quote(msg)) else: - if warnings[target_id] <= 5: - await msg.reply_text(locale("warnings_1", "message").format(target_name, target_id, warnings[target_id]), quote=should_quote(msg)) + if warns <= 5: + await msg.reply_text(locale("warnings_1", "message").format(target_name, target_id, warns), quote=should_quote(msg)) else: - await msg.reply_text(locale("warnings_2", "message").format(target_name, target_id, warnings[target_id]), quote=should_quote(msg)) + await msg.reply_text(locale("warnings_2", "message").format(target_name, target_id, warns), quote=should_quote(msg)) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/database.py b/modules/database.py index ec90cbe..3e368d0 100644 --- a/modules/database.py +++ b/modules/database.py @@ -25,10 +25,11 @@ db = db_client.get_database(name=db_config["name"]) collections = db.list_collection_names() -for collection in ["users", "context", "messages", "warnings", "applications", "sponsorships"]: +for collection in ["tmp", "users", "context", "messages", "warnings", "applications", "sponsorships"]: if not collection in collections: db.create_collection(collection) +col_tmp = db.get_collection("tmp") col_users = db.get_collection("users") col_context = db.get_collection("context") col_messages = db.get_collection("messages") diff --git a/modules/handlers/confirmation.py b/modules/handlers/confirmation.py index 95620f4..edd97a9 100644 --- a/modules/handlers/confirmation.py +++ b/modules/handlers/confirmation.py @@ -1,99 +1,93 @@ -from os import sep -from time import time from dateutil.relativedelta import relativedelta from datetime import datetime from app import app from pyrogram import filters from pyrogram.types import ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton from pyrogram.enums.parse_mode import ParseMode -from modules.utils import configGet, configSet, jsonLoad, jsonSave, locale, logWrite +from classes.holo_user import HoloUser +from modules.utils import configGet, locale, logWrite from modules.handlers.welcome import welcome_pass +from modules.database import col_tmp # Confirmation ================================================================================================================= @app.on_message(~ filters.scheduled & filters.private & (filters.regex(locale("confirm", "keyboard")[0][0]))) async def confirm_yes(app, msg): - user_stage = configGet("stage", file=str(msg.from_user.id)) + holo_user = HoloUser(msg.from_user) - if user_stage == 10: + if (holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True): - if not configGet("sent", file=str(msg.from_user.id)): + await msg.reply_text(locale("application_sent", "message"), reply_markup=ReplyKeyboardRemove()) - await msg.reply_text(locale("application_sent", "message"), reply_markup=ReplyKeyboardRemove()) + tmp_application = col_tmp.find_one({"user": holo_user.id, "type": "application"}) - applications = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json") + if tmp_application is None: + logWrite(f"Application of {holo_user.id} is nowhere to be found.") + return - applications[str(msg.from_user.id)] = { - "approved": False, - "approved_by": None, - "approval_date": None, - "refused": False, - "refused_by": False, - "refusal_date": False, - "application_date": int(time()), - "application": configGet("application", file=str(msg.from_user.id)) - } + application_content = [] + i = 1 - jsonSave(applications, f"{configGet('data', 'locations')}{sep}applications.json") + for question in tmp_application['application']: - application_content = [] - i = 1 - - for question in configGet("application", file=str(msg.from_user.id)): - if i == 2: - age = relativedelta(datetime.now(), datetime.strptime(configGet('application', file=str(msg.from_user.id))['2'], '%d.%m.%Y')) - application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(msg.from_user.id))['2']} ({age.years} р.)") + if i == 2: + age = relativedelta(datetime.now(), tmp_application['application']['2']) + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)") + elif i == 3: + if tmp_application['application']['3']['countryCode'] == "UA": + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['3']['name']}") else: - application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(msg.from_user.id))[question]}") - i += 1 - - if configGet("reapply", file=str(msg.from_user.id)): - await app.send_message(chat_id=configGet("admin_group"), text=(locale("reapply_got", "message")).format(str(msg.from_user.id), msg.from_user.first_name, msg.from_user.last_name, msg.from_user.username, "\n".join(application_content)), parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup( - [ - [ - InlineKeyboardButton(text=str(locale("reapply_yes", "button")), callback_data=f"reapply_yes_{msg.from_user.id}") - ], - [ - InlineKeyboardButton(text=str(locale("reapply_no", "button")), callback_data=f"reapply_no_{msg.from_user.id}") - ] - ] - ) - ) + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['3']['name']} ({tmp_application['application']['3']['adminName1']}, {tmp_application['application']['3']['countryName']})") else: - await app.send_message(chat_id=configGet("admin_group"), text=(locale("application_got", "message")).format(str(msg.from_user.id), msg.from_user.first_name, msg.from_user.last_name, msg.from_user.username, "\n".join(application_content)), parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup( + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application'][question]}") + + i += 1 + + if tmp_application["reapply"]: + await app.send_message(chat_id=configGet("admin_group"), text=(locale("reapply_got", "message")).format(str(holo_user.id), msg.from_user.first_name, msg.from_user.last_name, msg.from_user.username, "\n".join(application_content)), parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup( + [ [ - [ - InlineKeyboardButton(text=str(locale("sub_yes", "button")), callback_data=f"sub_yes_{msg.from_user.id}") - ], - [ - InlineKeyboardButton(text=str(locale("sub_no", "button")), callback_data=f"sub_no_{msg.from_user.id}") - ], - [ - InlineKeyboardButton(text=str(locale("sub_no_aggressive", "button")), callback_data=f"sub_no_aggresive_{msg.from_user.id}") - ], - [ - InlineKeyboardButton(text=str(locale("sub_no_russian", "button")), callback_data=f"sub_no_russian_{msg.from_user.id}") - ] + InlineKeyboardButton(text=str(locale("reapply_yes", "button")), callback_data=f"reapply_yes_{holo_user.id}") + ], + [ + InlineKeyboardButton(text=str(locale("reapply_no", "button")), callback_data=f"reapply_no_{holo_user.id}") ] - ) + ] ) + ) + else: + await app.send_message(chat_id=configGet("admin_group"), text=(locale("application_got", "message")).format(str(holo_user.id), msg.from_user.first_name, msg.from_user.last_name, msg.from_user.username, "\n".join(application_content)), parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text=str(locale("sub_yes", "button")), callback_data=f"sub_yes_{holo_user.id}") + ], + [ + InlineKeyboardButton(text=str(locale("sub_no", "button")), callback_data=f"sub_no_{holo_user.id}") + ], + [ + InlineKeyboardButton(text=str(locale("sub_aggressive", "button")), callback_data=f"sub_aggressive_{holo_user.id}") + ], + [ + InlineKeyboardButton(text=str(locale("sub_russian", "button")), callback_data=f"sub_russian_{holo_user.id}") + ] + ] + ) + ) - logWrite(f"User {msg.from_user.id} sent his application and it will now be reviewed") + logWrite(f"User {holo_user.id} sent his application and it will now be reviewed") - configSet(["sent"], True, file=str(msg.from_user.id)) - configSet(["confirmed"], True, file=str(msg.from_user.id)) + col_tmp.update_one({"user": holo_user.id, "type": "application"}, {"$set": {"sent": True}}) + + # configSet(["sent"], True, file=str(holo_user.id)) + # configSet(["confirmed"], True, file=str(holo_user.id)) @app.on_message(~ filters.scheduled & filters.private & (filters.regex(locale("confirm", "keyboard")[1][0]))) async def confirm_no(app, msg): - user_stage = configGet("stage", file=str(msg.from_user.id)) + holo_user = HoloUser(msg.from_user) - if user_stage == 10: - jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}user_default.json"), f"{configGet('data', 'locations')}{sep}users{sep}{msg.from_user.id}.json") - configSet(["telegram_id"], str(msg.from_user.username), file=str(msg.from_user.id)) - configSet(["telegram_name"], f"{msg.from_user.first_name} {msg.from_user.last_name}", file=str(msg.from_user.id)) - configSet(["telegram_phone"], str(msg.from_user.phone_number), file=str(msg.from_user.id)) - configSet(["telegram_locale"], str(msg.from_user.language_code), file=str(msg.from_user.id)) + if (holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True): + holo_user.application_restart() await welcome_pass(app, msg, once_again=True) logWrite(f"User {msg.from_user.id} restarted the application due to typo in it") # ============================================================================================================================== \ No newline at end of file diff --git a/modules/handlers/contact.py b/modules/handlers/contact.py index 6a8f5a7..787848b 100644 --- a/modules/handlers/contact.py +++ b/modules/handlers/contact.py @@ -1,47 +1,53 @@ -from os import sep, path from dateutil.relativedelta import relativedelta from datetime import datetime from app import app, isAnAdmin from pyrogram import filters -from modules.utils import configGet, jsonLoad, locale, logWrite +from modules.utils import locale, logWrite +from modules.database import col_applications +from classes.holo_user import HoloUser # Contact getting ============================================================================================================== @app.on_message(~ filters.scheduled & filters.contact & filters.private) async def get_contact(app, msg): - if (path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{msg.from_user.id}.json") and jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{msg.from_user.id}.json")["approved"]) or (await isAnAdmin(msg.from_user.id)): + + holo_user = HoloUser(msg.from_user) + + if holo_user.application_approved() or (await isAnAdmin(holo_user.id) is True): + if msg.contact.user_id != None: - try: - user_data = jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{msg.contact.user_id}.json") - application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json")[str(msg.contact.user_id)] - application_content = [] - i = 1 - for question in application["application"]: - if i == 2: - age = relativedelta(datetime.now(), datetime.strptime(application['application']['2'], '%d.%m.%Y')) - application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {application['application']['2']} ({age.years} р.)") - else: - application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {application['application'][question]}") - i += 1 - if user_data["sent"]: - if user_data["approved"]: - application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M")) - elif application["refused"]: - application_status = locale("application_status_refused", "message").format((await app.get_users(application["refused_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M")) - else: - application_status = locale("application_status_on_hold", "message") - else: - if user_data["approved"]: - application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M")) - elif application["refused"]: - application_status = locale("application_status_refused", "message").format((await app.get_users(application["refused_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M")) - else: - application_status = locale("application_status_not_send", "message") - logWrite(f"User {msg.from_user.id} requested application of {msg.contact.user_id}") - await msg.reply_text(locale("contact", "message").format(str(msg.contact.user_id), "\n".join(application_content), application_status)) - except FileNotFoundError: - logWrite(f"User {msg.from_user.id} requested application of {msg.contact.user_id} but user does not exists") + + application = col_applications.find_one({"user": msg.contact.user_id}) + + if application is None: + logWrite(f"User {holo_user.id} requested application of {msg.contact.user_id} but user does not exists") await msg.reply_text(locale("contact_invalid", "message")) + return + + application_content = [] + i = 1 + + for question in application['application']: + + if i == 2: + age = relativedelta(datetime.now(), application['application']['2']) + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)") + elif i == 3: + if application['application']['3']['countryCode'] == "UA": + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']}") + else: + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})") + else: + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application'][question]}") + + i += 1 + + application_status = locale("application_status_accepted", "message").format((await app.get_users(application["admin"])).first_name, application["date"].strftime("%d.%m.%Y, %H:%M")) + + logWrite(f"User {holo_user.id} requested application of {msg.contact.user_id}") + await msg.reply_text(locale("contact", "message").format(str(msg.contact.user_id), "\n".join(application_content), application_status)) + + else: - logWrite(f"User {msg.from_user.id} requested application of someone but user is not telegram user") + logWrite(f"User {holo_user.id} requested application of someone but user is not telegram user") await msg.reply_text(locale("contact_not_member", "message")) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/handlers/everything.py b/modules/handlers/everything.py index e4495a4..47be800 100644 --- a/modules/handlers/everything.py +++ b/modules/handlers/everything.py @@ -1,10 +1,9 @@ -from datetime import datetime -from os import sep from app import app, isAnAdmin import asyncio from pyrogram import filters -from pyrogram.types import ForceReply, ReplyKeyboardMarkup, Message -from modules.utils import configGet, configSet, jsonLoad, jsonSave, locale, logWrite, should_quote +from pyrogram.types import Message +from classes.holo_user import HoloUser +from modules.utils import configGet, logWrite from modules.database import col_messages async def message_involved(msg: Message) -> bool: @@ -20,78 +19,96 @@ async def message_context(msg: Message) -> tuple: return 0, 0 # Any other input ============================================================================================================== -# @app.on_message(~ filters.scheduled & filters.private) -# async def any_stage(app, msg): +@app.on_message(~ filters.scheduled & filters.private) +async def any_stage(app, msg): -# if msg.via_bot is None: + if msg.via_bot is None: -# if (msg.reply_to_message != None) and (await message_involved(msg)): -# context = await message_context(msg) -# if msg.chat.id == configGet("admin_group") or await isAnAdmin(msg.from_user.id): -# new_message = await (await app.get_messages(context[0], context[1])).reply_text(msg.text+locale("message_reply_notice", "message"), quote=True) -# else: -# new_message = await (await app.get_messages(context[0], context[1])).reply_text(locale("message_from", "message").format(msg.from_user.first_name, msg.from_user.id)+msg.text+locale("message_reply_notice", "message"), quote=True) -# await msg.reply_text(locale("message_sent", "message"), quote=should_quote(msg)) -# col_messages.insert_one({"origin": {"chat": msg.chat.id, "id": msg.id}, "destination": {"chat": new_message.chat.id, "id": new_message.id}}) -# return + holo_user = HoloUser(msg.from_user) -# user_stage = configGet("stage", file=str(msg.from_user.id)) + if (msg.reply_to_message is not None) and (await message_involved(msg)): + + context = await message_context(msg) + context_message = await app.get_messages(context[0], context[1]) + + destination_user = HoloUser(context_message.from_user) + + await destination_user.message( + origin=context_message, + context=msg, + text=msg.text, + caption=msg.caption, + photo=msg.photo, + video=msg.video, + file=msg.document, + adm_origin=await isAnAdmin(context_message.from_user.id), + adm_context=await isAnAdmin(msg.from_user.id) + ) + + # await msg.reply_text(locale("message_sent", "message"), quote=should_quote(msg)) + # col_messages.insert_one({"origin": {"chat": msg.chat.id, "id": msg.id}, "destination": {"chat": new_message.chat.id, "id": new_message.id}}) + + return + + await holo_user.application_next(msg.text, msg=msg) + + # user_stage = configGet("stage", file=str(msg.from_user.id)) -# if user_stage == 1: -# await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply")))) -# logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application") -# configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id)) -# configSet(["stage"], user_stage+1, file=str(msg.from_user.id)) + # if user_stage == 1: + # await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply")))) + # logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application") + # configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id)) + # configSet(["stage"], user_stage+1, file=str(msg.from_user.id)) -# elif user_stage == 2: + # elif user_stage == 2: -# try: + # try: -# configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id)) + # configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id)) -# input_dt = datetime.strptime(msg.text, "%d.%m.%Y") + # input_dt = datetime.strptime(msg.text, "%d.%m.%Y") -# if datetime.now() <= input_dt: -# logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to joking") -# await msg.reply_text(locale("question2_joke", "message"), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply")))) + # if datetime.now() <= input_dt: + # logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to joking") + # await msg.reply_text(locale("question2_joke", "message"), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply")))) -# elif ((datetime.now() - input_dt).days) < ((datetime.now() - datetime.now().replace(year=datetime.now().year - configGet("age_allowed"))).days): -# logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to being underage") -# await msg.reply_text(locale("question2_underage", "message").format(str(configGet("age_allowed"))), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply")))) + # elif ((datetime.now() - input_dt).days) < ((datetime.now() - datetime.now().replace(year=datetime.now().year - configGet("age_allowed"))).days): + # logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to being underage") + # await msg.reply_text(locale("question2_underage", "message").format(str(configGet("age_allowed"))), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply")))) -# else: -# logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application") -# await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply")))) -# configSet(["stage"], user_stage+1, file=str(msg.from_user.id)) + # else: + # logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application") + # await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply")))) + # configSet(["stage"], user_stage+1, file=str(msg.from_user.id)) -# except ValueError: -# logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to sending invalid date format") -# await msg.reply_text(locale(f"question2_invalid", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage}", "force_reply")))) + # except ValueError: + # logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to sending invalid date format") + # await msg.reply_text(locale(f"question2_invalid", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage}", "force_reply")))) -# else: -# if user_stage <= 9: -# logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application") -# await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply")))) -# configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id)) -# configSet(["stage"], user_stage+1, file=str(msg.from_user.id)) -# else: -# if not configGet("sent", file=str(msg.from_user.id)): -# if not configGet("confirmed", file=str(msg.from_user.id)): -# configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id)) -# application_content = [] -# i = 1 -# for question in configGet("application", file=str(msg.from_user.id)): -# application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(msg.from_user.id))[question]}") -# i += 1 -# await msg.reply_text(locale("confirm", "message").format("\n".join(application_content)), reply_markup=ReplyKeyboardMarkup(locale("confirm", "keyboard"), resize_keyboard=True)) -# #configSet("sent", True, file=str(msg.from_user.id)) -# #configSet("application_date", int(time()), file=str(msg.from_user.id)) -# else: -# if not configGet("approved", file=str(msg.from_user.id)) and not configGet("refused", file=str(msg.from_user.id)): -# await msg.reply_text(locale("already_sent", "message")) -# else: -# if not configGet("approved", file=str(msg.from_user.id)) and not configGet("refused", file=str(msg.from_user.id)): -# await msg.reply_text(locale("already_sent", "message")) + # else: + # if user_stage <= 9: + # logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application") + # await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply")))) + # configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id)) + # configSet(["stage"], user_stage+1, file=str(msg.from_user.id)) + # else: + # if not configGet("sent", file=str(msg.from_user.id)): + # if not configGet("confirmed", file=str(msg.from_user.id)): + # configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id)) + # application_content = [] + # i = 1 + # for question in configGet("application", file=str(msg.from_user.id)): + # application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(msg.from_user.id))[question]}") + # i += 1 + # await msg.reply_text(locale("confirm", "message").format("\n".join(application_content)), reply_markup=ReplyKeyboardMarkup(locale("confirm", "keyboard"), resize_keyboard=True)) + # #configSet("sent", True, file=str(msg.from_user.id)) + # #configSet("application_date", int(time()), file=str(msg.from_user.id)) + # else: + # if not configGet("approved", file=str(msg.from_user.id)) and not configGet("rejected", file=str(msg.from_user.id)): + # await msg.reply_text(locale("already_sent", "message")) + # else: + # if not configGet("approved", file=str(msg.from_user.id)) and not configGet("rejected", file=str(msg.from_user.id)): + # await msg.reply_text(locale("already_sent", "message")) @app.on_message(~ filters.scheduled & filters.group) async def message_in_group(app, msg): diff --git a/modules/handlers/group_join.py b/modules/handlers/group_join.py index d6b98de..4ea326e 100644 --- a/modules/handlers/group_join.py +++ b/modules/handlers/group_join.py @@ -1,25 +1,35 @@ -from os import sep, path from app import app, isAnAdmin from pyrogram.types import ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton -from modules.utils import configGet, jsonLoad, locale +from modules.utils import configGet, locale +from modules.logging import logWrite +from classes.holo_user import HoloUser # Filter users on join ========================================================================================================= @app.on_chat_member_updated(group=configGet("destination_group")) #@app.on_message(filters.new_chat_members, group=configGet("destination_group")) async def filter_join(app, member): + if member.invite_link != None: - if (path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{member.from_user.id}.json") and jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{member.from_user.id}.json")["approved"]) or (await isAnAdmin(member.from_user.id)): - if configGet("link", file=str(member.from_user.id)) == member.invite_link.invite_link: - return - if await isAnAdmin(member.invite_link.creator.id): + + holo_user = HoloUser(member.from_user) + + if (holo_user.link is not None) and (holo_user.link == member.invite_link.invite_link): + logWrite(f"User {holo_user.id} joined destination group with correct link {holo_user.link}") return - await app.send_message(configGet("admin_group"), f"User **{member.from_user.first_name}** (`{member.from_user.id}`) joined the chat not with his personal link", reply_markup=InlineKeyboardMarkup( + + if await isAnAdmin(member.invite_link.creator.id): + logWrite(f"User {holo_user.id} joined destination group with link {holo_user.link} of an admin {member.invite_link.creator.id}") + return + + logWrite(f"User {holo_user.id} joined destination group with stolen/unapproved link {holo_user.link}") + + await app.send_message(configGet("admin_group"), locale("joined_false_link", "message").format(member.from_user.first_name, member.from_user.id), reply_markup=InlineKeyboardMarkup( [ [ InlineKeyboardButton(text=str(locale("sus_allow", "button")), callback_data=f"sus_allow_{member.from_user.id}") ], [ - InlineKeyboardButton(text=str(locale("sus_refuse", "button")), callback_data=f"sus_refuse_{member.from_user.id}") + InlineKeyboardButton(text=str(locale("sus_reject", "button")), callback_data=f"sus_reject_{member.from_user.id}") ] ] )) diff --git a/modules/handlers/welcome.py b/modules/handlers/welcome.py index a484ac1..b0d8940 100644 --- a/modules/handlers/welcome.py +++ b/modules/handlers/welcome.py @@ -1,8 +1,7 @@ from app import app from pyrogram import filters from pyrogram.types import ForceReply, ReplyKeyboardMarkup -from classes.holo_user import HoloUser -from modules.utils import configGet, configSet, locale, logWrite +from modules.utils import locale, logWrite # Welcome check ================================================================================================================ @app.on_message(~ filters.scheduled & filters.private & (filters.regex(locale("welcome", "keyboard")[0][0]) | filters.regex(locale("return", "keyboard")[0][0]))) @@ -15,19 +14,17 @@ async def welcome_pass(app, msg, once_again: bool = True) -> None: * once_again (bool, optional): Set to False if it's the first time as user applies. Defaults to True. """ - holo_user = HoloUser(msg.from_user) - if not once_again: await msg.reply_text(locale("privacy_notice", "message")) logWrite(f"User {msg.from_user.id} confirmed starting the application") await msg.reply_text(locale("question1", "message"), reply_markup=ForceReply(placeholder=locale("question1", "force_reply"))) - configSet(["stage"], 1, file=str(msg.from_user.id)) - configSet(["sent"], False, file=str(msg.from_user.id)) + # configSet(["stage"], 1, file=str(msg.from_user.id)) + # configSet(["sent"], False, file=str(msg.from_user.id)) @app.on_message(~ filters.scheduled & filters.private & (filters.regex(locale("welcome", "keyboard")[1][0]))) async def welcome_reject(app, msg): - logWrite(f"User {msg.from_user.id} refused to start the application") + logWrite(f"User {msg.from_user.id} rejected to start the application") await msg.reply_text(locale("goodbye", "message"), reply_markup=ReplyKeyboardMarkup(locale("return", "keyboard"), resize_keyboard=True)) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/inline.py b/modules/inline.py index 4b8c6f9..229b988 100644 --- a/modules/inline.py +++ b/modules/inline.py @@ -1,12 +1,13 @@ from datetime import datetime from os import path, sep +from app import app, isAnAdmin from pyrogram.types import InlineQueryResultArticle, InputTextMessageContent from pyrogram.enums.chat_type import ChatType from pyrogram.enums.chat_members_filter import ChatMembersFilter from dateutil.relativedelta import relativedelta - -from app import app, isAnAdmin -from modules.utils import configGet, jsonLoad, locale +from classes.holo_user import HoloUser, UserInvalidError, UserNotFoundError +from modules.utils import configGet, locale +from modules.database import col_applications @app.on_inline_query() async def inline_answer(client, inline_query): @@ -25,7 +26,23 @@ async def inline_answer(client, inline_query): ) return - if (path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{inline_query.from_user.id}.json") and jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{inline_query.from_user.id}.json")["approved"]) or (await isAnAdmin(inline_query.from_user.id)): + try: + holo_user = HoloUser(inline_query.from_user) + except (UserNotFoundError, UserInvalidError): + await inline_query.answer( + results=[ + InlineQueryResultArticle( + title=locale("title", "inline", "forbidden"), + input_message_content=InputTextMessageContent( + locale("message_content", "inline", "forbidden") + ), + description=locale("description", "inline", "forbidden") + ) + ] + ) + return + + if holo_user.application_approved() or (await isAnAdmin(holo_user.id) is True): list_of_users = [] async for m in app.get_chat_members(configGet("destination_group"), limit=configGet("inline_preview_count"), filter=ChatMembersFilter.SEARCH, query=inline_query.query): @@ -33,26 +50,30 @@ async def inline_answer(client, inline_query): results = [] - applications = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json") - for match in list_of_users: - try: - application_content = [] - i = 1 - for question in applications[str(match.user.id)]["application"]: - if i == 2: - age = relativedelta(datetime.now(), datetime.strptime(applications[str(match.user.id)]['application']['2'], '%d.%m.%Y')) - application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {applications[str(match.user.id)]['application']['2']} ({age.years} р.)") + application = col_applications.find_one({"user": match.user.id}) + + if application is None: + continue + + application_content = [] + i = 1 + + for question in application['application']: + + if i == 2: + age = relativedelta(datetime.now(), application['application']['2']) + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)") + elif i == 3: + if application['application']['3']['countryCode'] == "UA": + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']}") else: - application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {applications[str(match.user.id)]['application'][question]}") - i += 1 - except KeyError: - continue - except FileNotFoundError: - continue - except TypeError: - continue + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})") + else: + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application'][question]}") + + i += 1 if match.user.photo != None: try: @@ -104,17 +125,3 @@ async def inline_answer(client, inline_query): results=results, cache_time=10 ) - - else: - await inline_query.answer( - results=[ - InlineQueryResultArticle( - title=locale("title", "inline", "forbidden"), - input_message_content=InputTextMessageContent( - locale("message_content", "inline", "forbidden") - ), - description=locale("description", "inline", "forbidden") - ) - ] - ) - return \ No newline at end of file diff --git a/modules/logging.py b/modules/logging.py index 62637a2..9314727 100644 --- a/modules/logging.py +++ b/modules/logging.py @@ -13,6 +13,11 @@ with open(getcwd()+path.sep+"config.json", "r", encoding='utf8') as file: # Check latest log size def checkSize(debug=False): + """Check size of latest.log file and rotate it if needed + + ### Args: + * debug (`bool`, *optional*): Whether this is a debug log. Defaults to `False`. + """ global log_folder @@ -35,7 +40,13 @@ def checkSize(debug=False): pass # Append string to log -def logAppend(message, debug=False): +def logAppend(message: str, debug=False): + """Write message to log file + + ### Args: + * message (`str`): Message to write + * debug (`bool`, *optional*): Whether this is a debug log. Defaults to `False`. + """ global log_folder @@ -52,7 +63,13 @@ def logAppend(message, debug=False): log.close() # Print to stdout and then to log -def logWrite(message, debug=False): +def logWrite(message: str, debug=False): + """Write message to stdout and log file + + ### Args: + * message (`str`): Message to print and write + * debug (`bool`, *optional*): Whether this is a debug log. Defaults to `False`. + """ # save to log file and rotation is to be done logAppend(f'{message}', debug=debug) print(f"{message}", flush=True) \ No newline at end of file diff --git a/modules/scheduled.py b/modules/scheduled.py index 19577ed..5b98aca 100644 --- a/modules/scheduled.py +++ b/modules/scheduled.py @@ -1,8 +1,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from datetime import datetime -from os import fsdecode, listdir, sep from app import app -from modules.utils import configGet, jsonLoad, locale, logWrite +from modules.utils import configGet, locale, logWrite from dateutil.relativedelta import relativedelta from modules.database import col_applications @@ -23,10 +22,15 @@ scheduler = AsyncIOScheduler() if configGet("enabled", "scheduler", "birthdays"): @scheduler.scheduled_job(trigger="cron", hour=configGet("time", "scheduler", "birthdays")) async def check_birthdays(): - for entry in col_applications.find({"2": datetime.now().strftime("%d.%m.%Y")}): - tg_user = await app.get_users(entry["user"]) - await app.send_message( configGet("admin_group"), locale("birthday", "message").format(str(tg_user.first_name), str(tg_user.username), str(relativedelta(datetime.now(), datetime.strptime(entry["2"], '%d.%m.%Y')).years)) ) # type: ignore - logWrite(f"Notified admins about {entry['user']}'s birthday") + for entry in col_applications.find(): + if entry["application"]["2"].strftime("%d.%m") == datetime.now().strftime("%d.%m"): + try: + tg_user = await app.get_users(entry["user"]) + await app.send_message( configGet("admin_group"), locale("birthday", "message").format(str(tg_user.first_name), str(tg_user.username), str(relativedelta(datetime.now(), entry["application"]["2"], '%d.%m.%Y').years)) ) # type: ignore + logWrite(f"Notified admins about {entry['user']}'s birthday") + except Exception as exp: + logWrite(f"Could not find user {entry['user']} to send a message about birthday due to '{exp}'") + continue logWrite("Birthdays check performed") if configGet("enabled", "scheduler", "sponsorships"): diff --git a/requirements.txt b/requirements.txt index 1409df8..1b571e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ fastapi==0.88.0 psutil==5.9.4 pymongo==4.3.3 Pyrogram==2.0.69 +requests==2.28.1 tgcrypto==1.2.5 python_dateutil==2.8.2 starlette==0.22.0