diff --git a/README.md b/README.md index 23dce77..d462fee 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,9 @@ After all of that you're good to go! Happy using :) ## To-Do -* [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 +* [ ] Stats and infographic +* [ ] /nearby command +* [ ] Check group members without completed application +* [x] Complete messenger between user and admins +* [x] Get application by id and user_id \ No newline at end of file diff --git a/app.py b/app.py index 833c744..a30216e 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,7 @@ +from os import path, sep +from ujson import JSONDecodeError from modules.logging import logWrite -from modules.utils import configGet +from modules.utils import configGet, jsonLoad from pyrogram.client import Client from pyrogram.errors import bad_request_400 @@ -8,6 +10,11 @@ app = Client("holochecker", bot_token=configGet("bot_token", "bot"), api_id=conf async def isAnAdmin(admin_id): if (admin_id == configGet("owner")) or (admin_id in configGet("admins")): return True + if path.exists(f"cache{sep}admins") is True: + try: + return True if admin_id in jsonLoad(f"cache{sep}admins") else False + except (FileNotFoundError, JSONDecodeError): + pass try: async for member in app.get_chat_members(configGet("admin_group")): if member.user.id == admin_id: diff --git a/classes/errors/geo.py b/classes/errors/geo.py new file mode 100644 index 0000000..b4b8b02 --- /dev/null +++ b/classes/errors/geo.py @@ -0,0 +1,5 @@ +class PlaceNotFoundError(Exception): + """Query provided did not lead to any city or populated area""" + def __init__(self, query): + self.query = query + super().__init__(f"Could not find any place on geonames.org of feature classes A and P by query '{self.query}'") \ No newline at end of file diff --git a/classes/holo_user.py b/classes/holo_user.py index 4c4c03c..257e4ec 100644 --- a/classes/holo_user.py +++ b/classes/holo_user.py @@ -1,4 +1,6 @@ from datetime import datetime +from os import sep +from uuid import uuid1 from requests import get from traceback import print_exc from app import app, isAnAdmin @@ -6,12 +8,13 @@ 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 dateutil.relativedelta import relativedelta +from classes.errors.geo import PlaceNotFoundError from modules.database import col_tmp, col_users, col_context, col_warnings, col_applications, col_sponsorships, col_messages from modules.logging import logWrite -from modules.utils import configGet, locale, should_quote +from modules.utils import configGet, find_location, locale, should_quote class DefaultApplicationTemp(dict): - def __init__(self, user: int): + def __init__(self, user: int, reapply: bool = False): super().__init__({}) self.dict = { "user": user, @@ -19,7 +22,7 @@ class DefaultApplicationTemp(dict): "complete": False, "sent": False, "state": "fill", - "reapply": False, + "reapply": reapply, "stage": 1, "application": { "1": None, @@ -35,6 +38,24 @@ class DefaultApplicationTemp(dict): } } +class DefaultSponsorshipTemp(dict): + def __init__(self, user: int): + super().__init__({}) + self.dict = { + "user": user, + "type": "sponsorship", + "complete": False, + "sent": False, + "state": "fill", + "stage": 1, + "sponsorship": { + "streamer": None, + "expires": datetime.fromtimestamp(0), + "proof": None, + "label": "" + } + } + class UserNotFoundError(Exception): """HoloUser could not find user with such an ID in database""" def __init__(self, user, user_id): @@ -48,6 +69,11 @@ class UserInvalidError(Exception): self.user = user super().__init__(f"Could not find HoloUser by using {type(self.user)} as an input type") +class LabelTooLongError(Exception): + def __init__(self, label: str) -> None: + self.label = label + super().__init__(f"Could not set label to '{label}' because it is {len(label)} characters long (16 is maximum)") + 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, @@ -100,19 +126,21 @@ class HoloUser(): self.locale = holo_user["tg_locale"] self.username = holo_user["tg_username"] - if isinstance(user, User) and ((self.name != user.first_name) and (user.first_name is not None)): - self.set("tg_name", user.first_name) + if isinstance(user, User): - if isinstance(user, User) and ((self.phone != user.phone_number) and (user.phone_number is not None)): - self.set("tg_phone", user.phone_number) + if (self.name != user.first_name) and hasattr(user, "first_name") and (user.first_name is not None): + self.set("name", user.first_name, db_key="tg_name") - if isinstance(user, User) and ((self.locale != user.language_code) and (user.language_code is not None)): - self.set("tg_locale", user.language_code) + if (self.phone != user.phone_number) and hasattr(user, "phone") and (user.phone_number is not None): + self.set("phone", user.phone_number, db_key="tg_phone") - if isinstance(user, User) and (self.username != user.username): - self.set("tg_username", user.username) + if (self.locale != user.language_code) and hasattr(user, "locale") and (user.language_code is not None): + self.set("locale", user.language_code, db_key="tg_locale") + + if (self.username != user.username) and hasattr(user, "username") and (user.username is not None): + self.set("username", user.username, db_key="tg_username") - def set(self, key: str, value: Any) -> None: + def set(self, key: str, value: Any, db_key: Union[str, None] = None) -> None: """Set attribute data and save it into database ### Args: @@ -122,7 +150,8 @@ class HoloUser(): if not hasattr(self, key): raise AttributeError() setattr(self, key, value) - col_users.update_one(filter={"_id": self.db_id}, update={ "$set": { key: value } }, upsert=True) + db_key = key if db_key is None else db_key + col_users.update_one(filter={"_id": self.db_id}, update={ "$set": { db_key: value } }, upsert=True) logWrite(f"Set attribute {key} of user {self.id} to {value}") async def message(self, @@ -226,11 +255,11 @@ class HoloUser(): elif animation is not None: if isinstance(animation, Animation): animation = animation.file_id - new_message = await app.send_animation(animation, caption=caption, quote=True) + new_message = await app.send_animation(self.id, animation, caption=caption) elif voice is not None: if isinstance(voice, Voice): voice = voice.file_id - new_message = await app.send_voice(voice, caption=caption, quote=True) + new_message = await app.send_voice(self.id, voice, caption=caption) else: new_message = await app.send_message(self.id, text) @@ -247,20 +276,22 @@ class HoloUser(): 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: + async def label_set(self, chat: Chat, label: str) -> None: """Set label in destination group ### Args: * chat (`Chat`): Telegram chat * label (`str`): Label you want to set """ + if len(label) > 16: + raise LabelTooLongError(label) self.label = label self.set("label", label) - await app.promote_chat_member(configGet("destination_group"), self.id) + await app.promote_chat_member(configGet("destination_group"), self.id, privileges=ChatPrivileges(can_pin_messages=True, can_manage_video_chats=True)) if not await isAnAdmin(self.id): await app.set_administrator_title(configGet("destination_group"), self.id, label) - async def reset_label(self, chat: Chat) -> None: + async def label_reset(self, chat: Chat) -> None: """Reset label in destination group ### Args: @@ -271,7 +302,9 @@ class HoloUser(): await app.set_administrator_title(configGet("destination_group"), self.id, "") if not await isAnAdmin(self.id): await app.promote_chat_member(configGet("destination_group"), self.id, privileges=ChatPrivileges( - can_manage_chat=False + can_manage_chat=False, + can_pin_messages=False, + can_manage_video_chats=False )) def application_state(self) -> tuple[Literal["none", "fill", "approved", "rejected"], bool]: @@ -294,14 +327,14 @@ class HoloUser(): """ return True if col_applications.find_one({"user": self.id}) is not None else False - def application_restart(self) -> None: + def application_restart(self, reapply: bool = False) -> 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) + col_tmp.insert_one(document=DefaultApplicationTemp(self.id, reapply=reapply).dict) async def application_next(self, query: str, msg: Message) -> None: """Move on filling application of user @@ -318,9 +351,17 @@ class HoloUser(): ) progress = col_tmp.find_one({"user": self.id, "type": "application"}) - stage = progress["stage"] - if progress["state"] == "fill": + if progress is None: + return + + stage = progress["stage"] + + if self.sponsorship_state()[0] == "fill": + await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg)) + return + + if progress["state"] == "fill" and progress["sent"] is False: if stage == 2: @@ -349,13 +390,16 @@ class HoloUser(): 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] + progress["application"][str(stage)] = find_location(query) + if ("lat" in progress["application"][str(stage)] and "lng" in progress["application"][str(stage)]): + progress["application"][str(stage)]["location"] = [float(progress["application"][str(stage)]["lng"]), float(progress["application"][str(stage)]["lat"])] + del progress["application"][str(stage)]["lat"] + del progress["application"][str(stage)]["lng"] 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", locale=self.locale).format(result["geonames"][0]["name"], result["geonames"][0]["adminName1"])) + await msg.reply_text(locale("question3_found", "message", locale=self.locale).format(progress["application"][str(stage)]["name"], progress["application"][str(stage)]["adminName1"])) await msg.reply_text(locale(f"question{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage+1}", "force_reply", locale=self.locale)))) - except (ValueError, KeyError, IndexError): - await msg.reply_text(locale(f"question3_invalid", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply", locale=self.locale)))) + except PlaceNotFoundError: + await msg.reply_text(locale("question3_invalid", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply", locale=self.locale)))) return except Exception as exp: await msg.reply_text(locale("question3_error", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply", locale=self.locale)))) @@ -389,4 +433,107 @@ class HoloUser(): 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", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage+1}", "force_reply", locale=self.locale)))) - logWrite(f"User {self.id} completed stage {stage} of application") \ No newline at end of file + logWrite(f"User {self.id} completed stage {stage} of application") + + else: + return + + def sponsorship_state(self) -> tuple[Literal["none", "fill", "approved", "rejected"], bool]: + """Check the current state of sponsorship 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 sponsorship application is complete. + """ + tmp_sponsorship = col_tmp.find_one({"user": self.id, "type": "sponsorship"}) + if tmp_sponsorship is None: + return "none", False + else: + return tmp_sponsorship["state"], tmp_sponsorship["complete"] + + def sponsorship_valid(self) -> bool: + """Check whether user has a valid sponsorship + + ### Returns: + * `bool`: `True` if yes and `False` if no + """ + return True if col_sponsorships.find_one({"user": self.id, "expires": {"$gt": datetime.now()}}) is not None else False + + def sponsorship_restart(self) -> None: + """Reset sponsorship of a user in tmp collection and replace it with an empty one + """ + if col_tmp.find_one({"user": self.id, "type": "sponsorship"}) is None: + col_tmp.insert_one(document=DefaultSponsorshipTemp(self.id).dict) + else: + col_tmp.delete_one({"user": self.id, "type": "sponsorship"}) + col_tmp.insert_one(document=DefaultSponsorshipTemp(self.id).dict) + + async def sponsorship_next(self, query: str, msg: Message, photo: Union[Photo, None] = None) -> None: + """Move on filling sponsorship 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": "sponsorship"}) is not None: + + progress = col_tmp.find_one({"user": self.id, "type": "sponsorship"}) + + if progress is None: + return + + stage = progress["stage"] + + if progress["state"] == "fill" and progress["sent"] is False: + + if stage == 1: + + progress["sponsorship"]["streamer"] = query + col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "stage": progress["stage"]+1}}) + await msg.reply_text(locale(f"sponsor{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"sponsor{stage+1}", "force_reply", locale=self.locale)))) + + elif 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"sponsor2_invalid", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"sponsor{stage}", "force_reply", locale=self.locale)))) + return + + if datetime.now() >= input_dt: + logWrite(f"User {msg.from_user.id} failed stage {stage} due to sending date in the past") + await msg.reply_text(locale("sponsor2_past", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale("sponsor2", "force_reply", locale=self.locale)))) + return + + else: + progress["sponsorship"]["expires"] = input_dt + col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "stage": progress["stage"]+1}}) + await msg.reply_text(locale(f"sponsor{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"sponsor{stage+1}", "force_reply", locale=self.locale)))) + + elif stage == 3: + + if photo is not None: + filename = uuid1() + await app.download_media(photo.file_id, f"tmp{sep}{filename}") + with open(f"tmp{sep}{filename}", "rb") as f: + photo_bytes = f.read() + progress["sponsorship"]["proof"] = photo_bytes + col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "stage": progress["stage"]+1}}) + await msg.reply_text(locale(f"sponsor{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"sponsor{stage+1}", "force_reply", locale=self.locale)))) + + elif stage == 4: + if len(query) > 16: + await msg.reply_text(locale("label_too_long", "message")) + return + progress["sponsorship"]["label"] = query + col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "complete": True}}) + await msg.reply_text(locale("sponsor_confirm", "message", locale=self.locale), reply_markup=ReplyKeyboardMarkup(locale("confirm", "keyboard", locale=self.locale), resize_keyboard=True)) + + else: + return + + logWrite(f"User {self.id} completed stage {stage} of sponsorship") + + else: + return \ No newline at end of file diff --git a/config_example.json b/config_example.json index d228852..55bbd79 100644 --- a/config_example.json +++ b/config_example.json @@ -9,6 +9,7 @@ "admin_group": 0, "destination_group": 0, "remove_application_time": -1, + "search_radius": 50, "admins": [], "bot": { "api_id": 0, @@ -41,6 +42,14 @@ "cache_avatars": { "interval": 6, "enabled": true + }, + "cache_members": { + "interval": 30, + "enabled": true + }, + "cache_admins": { + "interval": 120, + "enabled": true } }, "locations": { diff --git a/holochecker.py b/holochecker.py index 849b88c..bb70530 100644 --- a/holochecker.py +++ b/holochecker.py @@ -12,6 +12,7 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True) # Importing from modules.commands.application import * from modules.commands.applications import * +from modules.commands.cancel import * from modules.commands.label import * from modules.commands.message import * from modules.commands.nearby import * @@ -26,12 +27,14 @@ from modules.commands.warnings import * from modules.callbacks.nothing import * from modules.callbacks.reapply import * from modules.callbacks.rules import * +from modules.callbacks.sponsorship import * from modules.callbacks.sub import * from modules.callbacks.sus import * from modules.handlers.confirmation import * from modules.handlers.contact import * from modules.handlers.group_join import * +from modules.handlers.sponsorship import * from modules.handlers.voice import * from modules.handlers.welcome import * from modules.handlers.everything import * diff --git a/locale/en.json b/locale/en.disabled similarity index 98% rename from locale/en.json rename to locale/en.disabled index 714d44b..a93f3b5 100644 --- a/locale/en.json +++ b/locale/en.disabled @@ -22,8 +22,8 @@ "question3_traceback": "⚠️ **Error occurred**\nError retrieving geocoding for `{0}`\nError: `{1}`\n\nTraceback:\n```\n{2}\n```", "confirm": "Great, thanks!\n\nPlease check the data is correct:\n{0}\n\nEverything correct?", "application_sent": "Thank you! We have sent your application for verification. You will receive a message as soon as it is checked and a decision is made. Until then, nothing more is required from you. Have a nice day :)", - "application_got": "Received an application from `{0}`\n\nName in tg: `{1}`, `{2}`\nUsername: @{3}\n\n**Application data:**\n{4}", - "reapply_got": "Received profile change from `{0}`\n\nUsername: `{1}`, `{2}`\nUsername: @{3}\n\n**Application data:**\n{4}", + "application_got": "Received an application from `{0}`\n\nName in tg: `{1}`\nUsername: @{2}\n\n**Application data:**\n{3}", + "reapply_got": "Received application change from `{0}`\n\nUsername: `{1}`\nUsername: @{2}\n\n**Application data:**\n{3}", "shutdown": "Shutting down the bot with PID `{0}`", "startup": "Starting the bot with PID `{0}`", "startup_downtime": "Starting bot with PID `{0}` (was down for {1})", diff --git a/locale/uk.json b/locale/uk.json index 8076558..12c5bed 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -20,10 +20,22 @@ "question3_found": "Використовую наступний результат:\n• {0} ({1})", "question3_error": "⚠️ **Сталась помилка**\nНе вдалось отримати географічну мітку. Розробника повідомлено про цю помилку. Будь ласка, спробуйте ще раз.", "question3_traceback": "⚠️ **Сталась помилка**\nПомилка отримання геокодингу для `{0}`\nПомилка: `{1}`\n\nTraceback:\n```\n{2}\n```", + "sponsorship_apply": "ℹ️ Оформіть платну підписку на когось з Холо, заповніть форму та отримайте особливу роль в якості винагороди!", + "sponsorship_applying": "ℹ️ Розпочато заповнення форми на отримання бонусів за платну підписку на холодівчат.", + "sponsor1": "На яку саме дівчину платна підписка?", + "sponsor2": "До якої дати (`ДД.ММ.РРРР`) підписка?", + "sponsor2_invalid": "Будь ласка, введи дату формату `ДД.ММ.РРРР`", + "sponsor2_past": "Вказана дата знаходиться в минулому. Будь ласка, вкажіть правильний термін дії підписки", + "sponsor3": "Будь ласка, надішли одне фото для підтвердження дійсності підписки", + "sponsor4": "Яку роль ти бажаєш отримати?", + "sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?", + "sponsor_confirm": "Здається, це все. Перевір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.", "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується. Гарного дня! :)", - "application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`, `{2}`\nЮзернейм: @{3}\n\n**Дані анкети:**\n{4}", - "reapply_got": "Отримано змінити анкети від `{0}`\n\nІм'я тг: `{1}`, `{2}`\nЮзернейм: @{3}\n\n**Дані анкети:**\n{4}", + "sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. Гарного дня! :)", + "application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", + "reapply_got": "Отримано оновлення анкети від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", + "sponsor_got": "Отримано форму на спонсорство від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані форми:**\n{3}", "shutdown": "Вимкнення бота з підом `{0}`", "startup": "Запуск бота з підом `{0}`", "startup_downtime": "Запуск бота з підом `{0}` (лежав {1})", @@ -37,6 +49,10 @@ "rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.", "rejected_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.", "rejected_by_rus": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: русня.", + "sponsor_approved": "Вітаємо! Твою форму переглянули та підтвердили її правильність. Коли термін дії наданої підписки буде добігати кінця - ми нагадаємо, що треба оновити дані аби й надалі отримувати плюшки в боті. Гарного дня!", + "sponsor_rejected": "Ой лишенько! Твою форму переглянули, однак не підтвердили її. Можливо, щось не так з датами, або ж бажана роль не може бути надана.\n\nТи можеш спробувати повторно заповнити форму командою /sponsorship", + "sponsor_approved_by": "✅ **Підписку схвалено**\nАдмін **{0}** переглянув та схвалив форму `{1}`.", + "sponsor_rejected_by": "❌ **Підписку відхилено**\nАдмін **{0}** переглянув та відхилив форму `{1}`.", "contact": "Анкета `{0}`\n\n**Дані анкети:**\n{1}\n\n{2}", "application_status_accepted": "Прийнята `{0}` від {1}", "application_status_rejected": "Відхилена `{0}` від {1}", @@ -71,6 +87,15 @@ "no_user_application": "Не знайдено користувачів за запитом **{0}**", "user_invalid": "Надісланий користувач не має завершеної анкети.", "joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням", + "sponsorships_expires": "⚠️ **Нагадування**\nНадана платна підписка припинить діяти **за {0} д**. Будь ласка, оновіть дані про неї командою /sponsorship інакше роль буде втрачено!", + "sponsorships_expired": "⚠️ **Нагадування**\nТермін дії вказаної підписки сплив. Для повторного отримання ролі користуйся командою /sponsorship.", + "label_too_long": "Довжина назви ролі не повинна перевищувати 16 символів", + "finish_sponsorship": "❌ **Дія неможлива**\nПерш ніж заповнювати анкету, треба завершити заповнення форми спонсора.", + "finish_application": "❌ **Дія неможлива**\nПерш ніж заповнювати форму спонсора, треба завершити заповнення анкети.", + "nearby_invalid": "ℹ️ **Місце не знайдено**\nЗа наданим запитом не знайдено місце з координатами. Спробуйте ще раз формулючи запит в стилі \"Чернівці\" або \"Київська область\".", + "nearby_error": "⚠️ **Сталась помилка**\n\nПомилка: `{0}`\n\nTraceback:\n```\n{1}\n```", + "nearby_result": "Результати пошуку:\n\n{0}", + "nearby_empty": "Здається, нікого поблизу немає.", "voice_message": [ "why are u gae", "руки відірвало? пиши як людина", @@ -87,6 +112,11 @@ "question8": "Дивлюсь стріми:", "question9": "Подобаються пісні:", "question10": "Про себе:" + }, + "sponsor_titles": { + "question_streamer": "Стрімер:", + "question_expires": "Підписка до:", + "question_label": "Хоче роль:" } }, "keyboard": { @@ -122,13 +152,19 @@ "question7": "П'ять японських холодівчат", "question8": "Так або ні", "question9": "Ім'я дівчини або дівчин", - "question10": "Трошки про себе" + "question10": "Трошки про себе", + "sponsor1": "Ім'я дівчини", + "sponsor2": "Дата до якої підписка", + "sponsor3": "Фото-підтвердження", + "sponsor4": "Бажана роль" }, "button": { "sub_yes": "✅ Прийняти", "sub_no": "❌ Відхилити", "sub_aggressive": "🤡 Відхилити (Токс)", "sub_russian": "🇷🇺 Відхилити (Русак)", + "sponsor_yes": "✅ Прийняти", + "sponsor_no": "❌ Відхилити", "accepted": "✅ Прийнято", "declined": "❌ Відхилено", "join": "Приєднатись", @@ -145,7 +181,9 @@ "rules_next": "Далі ➡️", "rules_prev": "⬅️ Назад", "applying_stop": "🛑 Перервати заповнення", - "done": "✅ Готово" + "done": "✅ Готово", + "sponsor_apply": "Заповнити форму", + "sponsor_started": "Форму розпочато" }, "callback": { "sub_accepted": "✅ Анкету {0} схвалено", @@ -158,7 +196,10 @@ "rules_page": "ℹ️ Показано правило {0}", "rules_home": "ℹ️ Показано головну правил", "rules_additional": "ℹ️ Показано додаткові правила", - "reapply_stopped": "ℹ️ Перервано заповнення анкети" + "reapply_stopped": "ℹ️ Перервано заповнення анкети", + "sponsor_started": "ℹ️ Заповнення форми розпочато", + "sponsor_accepted": "✅ Форму {0} схвалено", + "sponsor_rejected": "❌ Форму {0} відхилено" }, "inline": { "forbidden": { diff --git a/modules/callbacks/nothing.py b/modules/callbacks/nothing.py index 8f4a719..538ed27 100644 --- a/modules/callbacks/nothing.py +++ b/modules/callbacks/nothing.py @@ -1,9 +1,11 @@ from app import app from pyrogram import filters +from pyrogram.types import CallbackQuery +from pyrogram.client import Client from modules.utils import locale # Callback empty =============================================================================================================== @app.on_callback_query(filters.regex("nothing")) -async def callback_query_nothing(app, clb): +async def callback_query_nothing(app: Client, clb: CallbackQuery): await clb.answer(text=locale("nothing", "callback", locale=clb.from_user)) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/callbacks/reapply.py b/modules/callbacks/reapply.py index fe693ac..5496b5c 100644 --- a/modules/callbacks/reapply.py +++ b/modules/callbacks/reapply.py @@ -1,16 +1,17 @@ from datetime import datetime from app import app -from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardRemove +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardRemove, CallbackQuery +from pyrogram.client import Client from pyrogram import filters from classes.holo_user import HoloUser -from modules.utils import configGet, locale, logWrite +from modules.utils import configGet, locale, logWrite, should_quote 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): +async def callback_reapply_query_accept(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(int(fullclb[2])) @@ -22,7 +23,7 @@ async def callback_reapply_query_accept(app, clb): col_applications.delete_one({"user": 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}}) + col_tmp.update_one({"user": holo_user.id, "type": "application"}, {"$set": {"state": "approved", "sent": False}}) edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] @@ -56,7 +57,7 @@ async def callback_reapply_query_accept(app, clb): await app.send_message(holo_user.id, locale("approved_joined", "message", locale=holo_user)) @app.on_callback_query(filters.regex("reapply_no_[\s\S]*")) -async def callback_query_reapply_reject(app, clb): +async def callback_query_reapply_reject(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(int(fullclb[2])) @@ -74,18 +75,27 @@ async def callback_query_reapply_reject(app, clb): # 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): +async def callback_query_reapply_old(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") + + if HoloUser(clb.from_user).sponsorship_state()[0] == "fill": + await clb.message.reply_text(locale("finish_sponsorship", "message"), quote=False) + return + message = await app.get_messages(clb.from_user.id, int(fullclb[2])) - await confirm_yes(app, message) + await confirm_yes(app, message, kind="application") await clb.message.edit(clb.message.text, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("done", "button", locale=clb.from_user), "nothing")]])) # Start a new application when user reapplies after leaving the chat @app.on_callback_query(filters.regex("reapply_new_[\s\S]*")) -async def callback_query_reapply_new(app, clb): +async def callback_query_reapply_new(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") + if HoloUser(clb.from_user).sponsorship_state()[0] == "fill": + await clb.message.reply_text(locale("finish_sponsorship", "message"), quote=False) + return + await clb.answer(locale("reapply_stopped", "callback", locale=clb.from_user)) message = await app.get_messages(clb.from_user.id, int(fullclb[2])) col_tmp.update_one({"user": clb.from_user.id}, {"$set": {"state": "fill", "completed": False, "stage": 1}}) @@ -95,7 +105,7 @@ async def callback_query_reapply_new(app, clb): # Abort application fill in progress and restart it @app.on_callback_query(filters.regex("reapply_stop_[\s\S]*")) -async def callback_query_reapply_stop(app, clb): +async def callback_query_reapply_stop(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(clb.from_user) diff --git a/modules/callbacks/rules.py b/modules/callbacks/rules.py index 0a5a0ba..8cba64c 100644 --- a/modules/callbacks/rules.py +++ b/modules/callbacks/rules.py @@ -1,5 +1,6 @@ from app import app -from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery +from pyrogram.client import Client from pyrogram.errors import bad_request_400 from pyrogram import filters from modules.utils import locale, logWrite @@ -7,7 +8,7 @@ from modules.commands.rules import DefaultRulesMarkup # Callback rule ================================================================================================================ @app.on_callback_query(filters.regex("rule_[\s\S]*")) -async def callback_query_rule(app, clb): +async def callback_query_rule(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") @@ -46,7 +47,7 @@ async def callback_query_rule(app, clb): await clb.answer(text=locale("rules_page", "callback", locale=clb.from_user).format(fullclb[1])) @app.on_callback_query(filters.regex("rules_home")) -async def callback_query_rules_home(app, clb): +async def callback_query_rules_home(app: Client, clb: CallbackQuery): logWrite(f"User {clb.from_user.id} requested to check out homepage rules") @@ -58,7 +59,7 @@ async def callback_query_rules_home(app, clb): await clb.answer(text=locale("rules_home", "callback", locale=clb.from_user)) @app.on_callback_query(filters.regex("rules_additional")) -async def callback_query_rules_additional(app, clb): +async def callback_query_rules_additional(app: Client, clb: CallbackQuery): logWrite(f"User {clb.from_user.id} requested to check out additional rules") diff --git a/modules/callbacks/sponsorship.py b/modules/callbacks/sponsorship.py new file mode 100644 index 0000000..cc1c0d4 --- /dev/null +++ b/modules/callbacks/sponsorship.py @@ -0,0 +1,99 @@ +from datetime import datetime +from app import app +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ForceReply, CallbackQuery +from pyrogram.client import Client +from pyrogram import filters +from classes.holo_user import HoloUser +from modules.utils import configGet, locale, logWrite, should_quote +from modules.database import col_tmp, col_sponsorships + +# Callbacks sponsorship ======================================================================================================== +@app.on_callback_query(filters.regex("sponsor_apply_[\s\S]*")) +async def callback_query_sponsor_apply(app: Client, clb: CallbackQuery): + + fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) + + if holo_user.application_state()[0] == "fill": + await clb.message.reply_text(locale("finish_application", "message"), quote=should_quote(clb.message)) + return + + logWrite(f"User {holo_user.id} applied for sponsorship") + + holo_user.sponsorship_restart() + + edited_markup = [[InlineKeyboardButton(text=str(locale("sponsor_started", "button")), callback_data="nothing")]] + + await clb.message.edit(text=locale("sponsorship_applying", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup(edited_markup)) + await app.send_message(holo_user.id, locale(f"sponsor1", "message", locale=holo_user), reply_markup=ForceReply(placeholder=str(locale(f"sponsor1", "force_reply", locale=holo_user.locale)))) + await clb.answer(text=locale("sponsor_started", "callback", locale=holo_user).format(holo_user.id), show_alert=False) + +@app.on_callback_query(filters.regex("sponsor_yes_[\s\S]*")) +async def callback_query_sponsor_yes(app: Client, clb: CallbackQuery): + + fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) + + await app.send_message(configGet("admin_group"), locale("sponsor_approved_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + await app.send_message(holo_user.id, locale("sponsor_approved", "message", locale=holo_user)) + logWrite(f"User {holo_user.id} got approved by {clb.from_user.id}") + + if col_sponsorships.find_one({"user": holo_user.id}) is not None: + col_sponsorships.update_one({"user": holo_user.id}, + { + "$set": { + "date": datetime.now(), + "admin": clb.from_user.id, + "sponsorship": col_tmp.find_one({"user": holo_user.id, "type": "sponsorship"})["sponsorship"] + } + } + ) + else: + col_sponsorships.insert_one( + { + "user": holo_user.id, + "date": datetime.now(), + "admin": clb.from_user.id, + "sponsorship": col_tmp.find_one({"user": holo_user.id, "type": "sponsorship"})["sponsorship"] + } + ) + + col_tmp.update_one({"user": holo_user.id, "type":"sponsorship"}, + { + "$set": { + "state": "approved", + "sent": False + } + } + ) + + await holo_user.label_set(configGet("destination_group"), col_tmp.find_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "sponsorship"}})["sponsorship"]["label"]) + + edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] + + await app.edit_message_caption(clb.message.chat.id, clb.message.id, caption=clb.message.caption, reply_markup=InlineKeyboardMarkup(edited_markup)) + await clb.answer(text=locale("sponsor_accepted", "callback").format(fullclb[2]), show_alert=False) + +@app.on_callback_query(filters.regex("sponsor_no_[\s\S]*")) +async def callback_query_sponsor_no(app: Client, clb: CallbackQuery): + + fullclb = clb.data.split("_") + holo_user = HoloUser(int(fullclb[2])) + + await app.send_message(configGet("admin_group"), locale("sponsor_rejected_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True) + await app.send_message(holo_user.id, locale("sponsor_rejected", "message", locale=holo_user)) + logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id}") + + col_tmp.update_one({"user": holo_user.id, "type": "sponsorship"}, + { + "$set": { + "state": "rejected", + "sent": False + } + } + ) + + edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] + + await app.edit_message_caption(clb.message.chat.id, clb.message.id, caption=clb.message.caption, reply_markup=InlineKeyboardMarkup(edited_markup)) + await clb.answer(text=locale("sponsor_rejected", "callback").format(fullclb[2]), show_alert=False) \ No newline at end of file diff --git a/modules/callbacks/sub.py b/modules/callbacks/sub.py index 5eebb09..9347798 100644 --- a/modules/callbacks/sub.py +++ b/modules/callbacks/sub.py @@ -1,7 +1,8 @@ from datetime import datetime from app import app -from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery from pyrogram import filters +from pyrogram.client import Client from classes.holo_user import HoloUser from modules.utils import configGet, locale, logWrite from modules.database import col_tmp, col_applications @@ -9,7 +10,7 @@ from modules.commands.rules import DefaultRulesMarkup # Callbacks application ======================================================================================================== @app.on_callback_query(filters.regex("sub_yes_[\s\S]*")) -async def callback_query_accept(app, clb): +async def callback_query_accept(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(int(fullclb[2])) @@ -51,7 +52,7 @@ async def callback_query_accept(app, clb): await clb.answer(text=locale("sub_accepted", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True) @app.on_callback_query(filters.regex("sub_no_[\s\S]*")) -async def callback_query_reject(app, clb): +async def callback_query_reject(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(int(fullclb[2])) @@ -68,7 +69,7 @@ async def callback_query_reject(app, clb): await clb.answer(text=locale("sub_rejected", "callback", locale=clb.from_user).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): +async def callback_query_reject_aggressive(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(int(fullclb[2])) @@ -85,7 +86,7 @@ async def callback_query_reject_aggressive(app, clb): await clb.answer(text=locale("sub_aggressive", "callback", locale=clb.from_user).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): +async def callback_query_reject_russian(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(int(fullclb[2])) diff --git a/modules/callbacks/sus.py b/modules/callbacks/sus.py index 50c6d52..ae37ca1 100644 --- a/modules/callbacks/sus.py +++ b/modules/callbacks/sus.py @@ -1,5 +1,6 @@ from app import app -from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ChatPermissions +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ChatPermissions, CallbackQuery +from pyrogram.client import Client from pyrogram import filters from classes.holo_user import HoloUser from modules.utils import configGet, locale, logWrite @@ -7,7 +8,7 @@ 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): +async def callback_query_sus_allow(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(int(fullclb[2])) @@ -29,7 +30,7 @@ async def callback_query_sus_allow(app, clb): ) @app.on_callback_query(filters.regex("sus_reject_[\s\S]*")) -async def callback_query_sus_reject(app, clb): +async def callback_query_sus_reject(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") holo_user = HoloUser(int(fullclb[2])) diff --git a/modules/commands/application.py b/modules/commands/application.py index 62d1e28..abc758d 100644 --- a/modules/commands/application.py +++ b/modules/commands/application.py @@ -1,96 +1,97 @@ from datetime import datetime -from app import app, isAnAdmin +from app import app from pyrogram import filters from pyrogram.enums.parse_mode import ParseMode +from pyrogram.types import Message from pyrogram.errors import bad_request_400 +from pyrogram.client import Client 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 +from modules import custom_filters # Applications command ========================================================================================================= -@app.on_message(~ filters.scheduled & filters.command(["application"], prefixes=["/"])) -async def cmd_application(app, msg): +@app.on_message(~ filters.scheduled & filters.command(["application"], prefixes=["/"]) & custom_filters.admin) +async def cmd_application(app: Client, msg: Message): - if await isAnAdmin(msg.from_user.id) is True: + try: try: - + holo_user = HoloUser(int(msg.command[1])) + except (ValueError, UserNotFoundError): try: - 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", locale=msg.from_user).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", locale=msg.from_user), quote=should_quote(msg)) + 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", locale=msg.from_user).format(msg.command[1]), quote=should_quote(msg)) return - application_content = [] - i = 1 + application = col_applications.find_one({"user": holo_user.id}) - for question in application['application']: + 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", locale=msg.from_user), quote=should_quote(msg)) + return - if i == 2: - age = relativedelta(datetime.now(), application['application']['2']) - application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {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', locale=msg.from_user)} {application['application']['3']['name']}") - else: - application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})") + 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', locale=msg.from_user)} {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', locale=msg.from_user)} {application['application']['3']['name']}") else: - application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application'][question]}") - - i += 1 + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})") + else: + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application'][question]}") + + i += 1 - application_status = locale("application_status_accepted", "message", locale=msg.from_user).format((await app.get_users(application["admin"])).first_name, application["date"].strftime("%d.%m.%Y, %H:%M")) + application_status = locale("application_status_accepted", "message", locale=msg.from_user).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", locale=msg.from_user).format(holo_user.id, "\n".join(application_content), application_status), parse_mode=ParseMode.MARKDOWN, quote=should_quote(msg)) + logWrite(f"User {msg.from_user.id} requested application of {holo_user.id}") + await msg.reply_text(locale("contact", "message", locale=msg.from_user).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", locale=msg.from_user), 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", locale=msg.from_user), quote=should_quote(msg)) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/applications.py b/modules/commands/applications.py index b0c29b8..ba409e9 100644 --- a/modules/commands/applications.py +++ b/modules/commands/applications.py @@ -1,28 +1,30 @@ from os import sep, makedirs, remove from uuid import uuid1 -from app import app, isAnAdmin +from app import app from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client from pyrogram.enums.chat_action import ChatAction from modules.logging import logWrite from modules.utils import should_quote, jsonSave from modules.database import col_applications +from modules import custom_filters # Applications command ========================================================================================================= -@app.on_message(~ filters.scheduled & filters.command(["applications"], prefixes=["/"])) -async def cmd_applications(app, msg): +@app.on_message(~ filters.scheduled & filters.command(["applications"], prefixes=["/"]) & custom_filters.admin) +async def cmd_applications(app: Client, msg: Message): - 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") - await msg.reply_document(document=f"tmp{sep}{filename}.json", file_name="applications", quote=should_quote(msg)) - remove(f"tmp{sep}{filename}.json") + logWrite(f"Admin {msg.from_user.id} requested export of a database") + await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT) + filename = uuid1() + output = [] + for entry in col_applications.find(): + del entry["_id"] + entry["date"] = entry["date"].strftime("%d.%m.%Y, %H:%M") + entry["application"]["2"] = entry["application"]["2"].strftime("%d.%m.%Y, %H:%M") + output.append(entry) + makedirs("tmp", exist_ok=True) + jsonSave(output, f"tmp{sep}{filename}.json") + await msg.reply_document(document=f"tmp{sep}{filename}.json", file_name="applications", quote=should_quote(msg)) + remove(f"tmp{sep}{filename}.json") # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/cancel.py b/modules/commands/cancel.py new file mode 100644 index 0000000..5e06a44 --- /dev/null +++ b/modules/commands/cancel.py @@ -0,0 +1,9 @@ +from app import app +from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client +from modules.utils import should_quote + +@app.on_message(~ filters.scheduled & filters.command("cancel", prefixes=["/"])) +async def command_cancel(app: Client, msg: Message): + await msg.reply_text("Command exists.", quote=should_quote(msg)) \ No newline at end of file diff --git a/modules/commands/label.py b/modules/commands/label.py index 5a3edcd..60763b0 100644 --- a/modules/commands/label.py +++ b/modules/commands/label.py @@ -1,32 +1,37 @@ -from app import app, isAnAdmin +from app import app from pyrogram import filters -from modules.utils import should_quote, find_user -from classes.holo_user import HoloUser +from pyrogram.types import Message +from pyrogram.client import Client +from modules.utils import locale, should_quote, find_user +from classes.holo_user import HoloUser, LabelTooLongError +from modules import custom_filters -@app.on_message(~ filters.scheduled & filters.private & filters.command(["label"], prefixes=["/"])) -async def cmd_label(app, msg): +@app.on_message(~ filters.scheduled & filters.private & filters.command(["label"], prefixes=["/"]) & custom_filters.admin) +async def cmd_label(app: Client, msg: Message): - if await isAnAdmin(msg.from_user.id) is True: + if len(msg.command) < 3: + await msg.reply_text("Invalid syntax:\n`/label USER LABEL`") + return - if len(msg.command) < 3: - await msg.reply_text("Invalid syntax:\n`/label USER LABEL`") - return + target = await find_user(app, msg.command[1]) - target = await find_user(app, msg.command[1]) + if target is not None: + + target = HoloUser(target) - if target is not None: + label = " ".join(msg.command[2:]) + + if label.lower() == "reset": + await target.label_reset(msg.chat) + await msg.reply_text(f"Resetting **{target.id}**'s label...", quote=should_quote(msg)) - target = HoloUser(target) - - label = " ".join(msg.command[2:]) - - if label.lower() == "reset": - await target.reset_label(msg.chat) - await msg.reply_text(f"Resetting **{target.id}**'s label...", quote=should_quote(msg)) - - else: - await target.set_label(msg.chat, label) - await msg.reply_text(f"Setting **{target.id}**'s label to **{label}**...", quote=should_quote(msg)) - else: - await msg.reply_text(f"User not found") \ No newline at end of file + try: + await target.label_set(msg.chat, label) + except LabelTooLongError: + await msg.reply_text(locale("label_too_long", "message")) + return + await msg.reply_text(f"Setting **{target.id}**'s label to **{label}**...", quote=should_quote(msg)) + + else: + await msg.reply_text(f"User not found") \ No newline at end of file diff --git a/modules/commands/message.py b/modules/commands/message.py index 74ceeb4..60ce874 100644 --- a/modules/commands/message.py +++ b/modules/commands/message.py @@ -1,32 +1,33 @@ -from app import app, isAnAdmin +from app import app from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client from classes.holo_user import HoloUser from modules.utils import logWrite, locale, should_quote +from modules import custom_filters # Message command ============================================================================================================== -@app.on_message(~ filters.scheduled & filters.command(["message"], prefixes=["/"])) -async def cmd_message(app, msg): +@app.on_message(~ filters.scheduled & filters.command(["message"], prefixes=["/"]) & custom_filters.admin) +async def cmd_message(app: Client, msg: Message): - if await isAnAdmin(msg.from_user.id) is True: + try: try: - - try: - destination = HoloUser(int(msg.command[1])) - except ValueError: - destination = HoloUser(msg.command[1]) - - 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", locale=msg.from_user), quote=should_quote(msg)) - logWrite(f"Admin {msg.from_user.id} tried to send message but 'IndexError'") + destination = HoloUser(int(msg.command[1])) except ValueError: - await msg.reply_text(locale("message_invalid_syntax", "message", locale=msg.from_user), quote=should_quote(msg)) - logWrite(f"Admin {msg.from_user.id} tried to send message but 'ValueError'") + destination = HoloUser(msg.command[1]) + + 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", locale=msg.from_user), quote=should_quote(msg)) + logWrite(f"Admin {msg.from_user.id} tried to send message but 'IndexError'") + except ValueError: + await msg.reply_text(locale("message_invalid_syntax", "message", locale=msg.from_user), quote=should_quote(msg)) + logWrite(f"Admin {msg.from_user.id} tried to send message but 'ValueError'") # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/nearby.py b/modules/commands/nearby.py index d7a41ec..890a314 100644 --- a/modules/commands/nearby.py +++ b/modules/commands/nearby.py @@ -1,13 +1,62 @@ -from app import app, isAnAdmin +from traceback import print_exc +from app import app from pyrogram import filters -from modules.utils import configGet, should_quote -from modules.database import col_applications +from pyrogram.types import Message +from pyrogram.client import Client +from classes.holo_user import HoloUser +from modules import custom_filters +from modules.logging import logWrite +from modules.utils import configGet, locale, should_quote, find_location +from modules.database import col_applications, col_users +from classes.errors.geo import PlaceNotFoundError # 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)) +@app.on_message(~ filters.scheduled & (filters.private | (filters.chat(configGet("admin_group")) | filters.chat(configGet("destination_group")))) & filters.command(["nearby"], prefixes=["/"]) & (custom_filters.allowed | custom_filters.admin)) +async def cmd_nearby(app: Client, msg: Message): + + holo_user = HoloUser(msg.from_user) + + # Check if any place provided + if len(msg.command) == 1: # Action if no place provided + application = col_applications.find_one({"user": msg.from_user.id}) + if application is None: + await msg.reply_text(locale("nearby_user_empty", "message", locale=holo_user)) + return + location = application["application"]["3"]["location"][0], application["application"]["3"]["location"][1] + else: # Find a place from input query + logWrite(f"Looking for the location by query '{' '.join(msg.command[1:])}'") + try: + location_coordinates = find_location(" ".join(msg.command[1:])) + location = float(location_coordinates["lng"]), float(location_coordinates["lat"]) + except PlaceNotFoundError: # Place is not found + await msg.reply_text(locale("nearby_invalid", "message", locale=holo_user), quote=should_quote(msg)) + return + except Exception as exp: # Error occurred while finding the place + await msg.reply_text(locale("nearby_error", "message", locale=holo_user).format(exp, print_exc()), quote=should_quote(msg)) + return + + # Find all users registered in the area provided + output = [] + applications_nearby = col_applications.find( {"application.3.location": { "$nearSphere": {"$geometry": {"type": "Point", "coordinates": [location[0], location[1]]}, "$maxDistance": configGet("search_radius")*1000} } } ) + # {"application": {"3": {"location": {"$near": { "$geometry": { "type": "Point", "coordinates": location }, "$maxDistance": 30000 }} } } } ) + + for entry in applications_nearby: + if not entry["user"] == msg.from_user.id: + user = col_users.find_one( {"user": entry["user"]} ) + if user is not None: + if user["tg_username"] not in [None, "None", ""]: # Check if user has any name + output.append(f'• {user["tg_name"]} (@{user["tg_username"]}):\n {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}') + else: + output.append(f'• {user["tg_name"]}:\n {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}') + + logWrite(f"{holo_user.id} tried to find someone nearby {location[1]} {location[0]} in the radius of {configGet('search_radius')} kilometers") + + # Check if any users found + if len(output) > 0: + await msg.reply_text(locale("nearby_result", "message", locale=holo_user).format("\n".join(output)), quote=should_quote(msg)) + else: + await msg.reply_text(locale("nearby_empty", "message", locale=holo_user), 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") diff --git a/modules/commands/reapply.py b/modules/commands/reapply.py index 0936bf8..bcb7ac5 100644 --- a/modules/commands/reapply.py +++ b/modules/commands/reapply.py @@ -1,14 +1,15 @@ from app import app from pyrogram import filters -from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message +from pyrogram.client import Client from classes.holo_user import HoloUser -from modules.utils import configGet, locale +from modules.utils import configGet, locale, should_quote 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): +async def cmd_reapply(app: Client, msg: Message): holo_user = HoloUser(msg.from_user) @@ -19,7 +20,10 @@ async def cmd_reapply(app, msg): if member.user.id == msg.from_user.id: left_chat = False if not left_chat: - holo_user.application_restart() + if holo_user.sponsorship_state()[0] == "fill": + await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg)) + return + holo_user.application_restart(reapply=True) await welcome_pass(app, msg, once_again=True) else: await msg.reply_text(locale("reapply_left_chat", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup([ diff --git a/modules/commands/reboot.py b/modules/commands/reboot.py index 876d163..0a5faf9 100644 --- a/modules/commands/reboot.py +++ b/modules/commands/reboot.py @@ -1,19 +1,21 @@ -from app import app, isAnAdmin +from app import app from os import getpid from sys import exit from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client from modules.utils import locale, logWrite, should_quote from modules.scheduled import scheduler +from modules import custom_filters pid = getpid() # Shutdown command ============================================================================================================= -@app.on_message(~ filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["/"])) -async def cmd_kill(app, msg): +@app.on_message(~ filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["/"]) & custom_filters.admin) +async def cmd_kill(app: Client, msg: Message): - if await isAnAdmin(msg.from_user.id) is True: - logWrite(f"Shutting down bot with pid {pid}") - await msg.reply_text(locale("shutdown", "message", locale=msg.from_user).format(pid), quote=should_quote(msg)) - scheduler.shutdown() - exit() + logWrite(f"Shutting down bot with pid {pid}") + await msg.reply_text(locale("shutdown", "message", locale=msg.from_user).format(pid), quote=should_quote(msg)) + scheduler.shutdown() + exit() # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/rules.py b/modules/commands/rules.py index b75102f..98b6c39 100644 --- a/modules/commands/rules.py +++ b/modules/commands/rules.py @@ -1,7 +1,8 @@ from typing import Union from app import app from pyrogram import filters -from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, User +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, User, Message +from pyrogram.client import Client from modules.utils import locale from classes.holo_user import HoloUser @@ -35,6 +36,6 @@ class DefaultRulesMarkup(list): # Rules command ============================================================================================================= @app.on_message(~ filters.scheduled & filters.private & filters.command(["rules"], prefixes=["/"])) -async def cmd_rules(app, msg): +async def cmd_rules(app: Client, msg: Message): await msg.reply_text(locale("rules_msg", locale=msg.from_user), disable_web_page_preview=True, reply_markup=DefaultRulesMarkup(msg.from_user).keyboard) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/sponsorship.py b/modules/commands/sponsorship.py index d5234fb..32e14dc 100644 --- a/modules/commands/sponsorship.py +++ b/modules/commands/sponsorship.py @@ -1,25 +1,19 @@ -from datetime import datetime -from app import app, isAnAdmin +from app import app from pyrogram import filters -from modules.utils import should_quote +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message +from pyrogram.client import Client +from classes.holo_user import HoloUser +from modules import custom_filters +from modules.utils import locale, 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 (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.") +@app.on_message(~ filters.scheduled & filters.command(["sponsorship"], prefixes=["/"]) & (custom_filters.allowed | custom_filters.admin)) +async def cmd_sponsorship(app: Client, msg: Message): + if HoloUser(msg.from_user).application_state()[0] == "fill": + await msg.reply_text(locale("finish_application", "message", locale=msg.from_user), quote=should_quote(msg)) + return + await msg.reply_text(locale("sponsorship_apply", "message", locale=msg.from_user), reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text=str(locale("sponsor_apply", "button", locale=msg.from_user)), callback_data=f"sponsor_apply_{msg.from_user.id}")]]), quote=should_quote(msg)) # else: - # await msg.reply_text(f"You have no active subscription.") + # await msg.reply_text(locale("sponsorship_application_empty", "message")) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/commands/start.py b/modules/commands/start.py index 6c89afe..ac26461 100644 --- a/modules/commands/start.py +++ b/modules/commands/start.py @@ -1,12 +1,13 @@ from app import app from pyrogram import filters -from pyrogram.types import ReplyKeyboardMarkup +from pyrogram.types import ReplyKeyboardMarkup, Message +from pyrogram.client import Client from modules.utils import locale, logWrite from modules.database import col_users # Start command ================================================================================================================ @app.on_message(~ filters.scheduled & filters.private & filters.command(["start"], prefixes=["/"])) -async def cmd_start(app, msg): +async def cmd_start(app: Client, msg: Message): user = col_users.find_one({"user": msg.from_user.id}) diff --git a/modules/commands/warn.py b/modules/commands/warn.py index 50f948c..e974de6 100644 --- a/modules/commands/warn.py +++ b/modules/commands/warn.py @@ -1,20 +1,22 @@ from datetime import datetime -from app import app, isAnAdmin +from app import app from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client from modules.utils import configGet, locale from modules.database import col_warnings +from modules import custom_filters # Warn command ================================================================================================================= -@app.on_message(~ filters.scheduled & filters.command(["warn"], prefixes=["/"])) -async def cmd_warn(app, msg): +@app.on_message(~ filters.scheduled & filters.command(["warn"], prefixes=["/"]) & custom_filters.admin) +async def cmd_warn(app: Client, msg: Message): if msg.chat.id == configGet("destination_group"): if msg.reply_to_message_id != None: - 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: - 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)) + 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: + 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 fe43186..d2a9c7e 100644 --- a/modules/commands/warnings.py +++ b/modules/commands/warnings.py @@ -1,43 +1,44 @@ -from app import app, isAnAdmin +from app import app from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client from pyrogram.enums.chat_members_filter import ChatMembersFilter from modules.utils import configGet, locale, should_quote from modules.database import col_users, col_warnings +from modules import custom_filters # Warnings command ============================================================================================================= -@app.on_message(~ filters.scheduled & filters.command(["warnings"], prefixes=["/"])) -async def cmd_warnings(app, msg): +@app.on_message(~ filters.scheduled & filters.command(["warnings"], prefixes=["/"]) & custom_filters.admin) +async def cmd_warnings(app: Client, msg: Message): - if await isAnAdmin(msg.from_user.id) is True: + if len(msg.command) <= 1: + await msg.reply_text(locale("syntax_warnings", "message", locale=msg.from_user), quote=should_quote(msg)) + return - if len(msg.command) <= 1: - await msg.reply_text(locale("syntax_warnings", "message", locale=msg.from_user), quote=should_quote(msg)) + 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) + + if len(list_of_users) != 0: + target = list_of_users[0].user + target_name = target.first_name + target_id = str(target.id) + else: + await msg.reply_text(locale("no_user_warnings", "message", locale=msg.from_user).format(msg.command[1])) return - 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) + warns = len(list(col_warnings.find({"user": target_id}))) - if len(list_of_users) != 0: - target = list_of_users[0].user - target_name = target.first_name - target_id = str(target.id) - else: - await msg.reply_text(locale("no_user_warnings", "message", locale=msg.from_user).format(msg.command[1])) - return - - warns = len(list(col_warnings.find({"user": target_id}))) - - if warns == 0: - await msg.reply_text(locale("no_warnings", "message", locale=msg.from_user).format(target_name, target_id), quote=should_quote(msg)) + if warns == 0: + await msg.reply_text(locale("no_warnings", "message", locale=msg.from_user).format(target_name, target_id), quote=should_quote(msg)) + else: + if warns <= 5: + await msg.reply_text(locale("warnings_1", "message", locale=msg.from_user).format(target_name, target_id, warns), quote=should_quote(msg)) else: - if warns <= 5: - await msg.reply_text(locale("warnings_1", "message", locale=msg.from_user).format(target_name, target_id, warns), quote=should_quote(msg)) - else: - await msg.reply_text(locale("warnings_2", "message", locale=msg.from_user).format(target_name, target_id, warns), quote=should_quote(msg)) + await msg.reply_text(locale("warnings_2", "message", locale=msg.from_user).format(target_name, target_id, warns), quote=should_quote(msg)) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/custom_filters.py b/modules/custom_filters.py new file mode 100644 index 0000000..8686fb8 --- /dev/null +++ b/modules/custom_filters.py @@ -0,0 +1,12 @@ +from app import isAnAdmin +from modules.database import col_applications +from pyrogram import filters + +async def admin_func(_, __, msg): + return await isAnAdmin(msg) + +async def allowed_func(_, __, msg): + return True if (col_applications.find_one({"user": msg.from_user.id}) is not None) else False + +admin = filters.create(admin_func) +allowed = filters.create(allowed_func) \ No newline at end of file diff --git a/modules/database.py b/modules/database.py index 3e368d0..9a4ba1d 100644 --- a/modules/database.py +++ b/modules/database.py @@ -1,4 +1,4 @@ -from pymongo import MongoClient +from pymongo import MongoClient, GEOSPHERE from ujson import loads with open("config.json", "r", encoding="utf-8") as f: @@ -35,4 +35,6 @@ col_context = db.get_collection("context") col_messages = db.get_collection("messages") col_warnings = db.get_collection("warnings") col_applications = db.get_collection("applications") -col_sponsorships = db.get_collection("sponsorships") \ No newline at end of file +col_sponsorships = db.get_collection("sponsorships") + +col_applications.create_index([("application.3.location", GEOSPHERE)]) \ No newline at end of file diff --git a/modules/handlers/confirmation.py b/modules/handlers/confirmation.py index 207c6be..0878d05 100644 --- a/modules/handlers/confirmation.py +++ b/modules/handlers/confirmation.py @@ -1,8 +1,12 @@ +from os import remove, sep +from typing import Literal +from uuid import uuid1 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.types import ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton, ForceReply, Message +from pyrogram.client import Client from pyrogram.enums.parse_mode import ParseMode from classes.holo_user import HoloUser from modules.utils import all_locales, configGet, locale, logWrite @@ -14,13 +18,11 @@ confirmation_1 = [] for pattern in all_locales("confirm", "keyboard"): confirmation_1.append(pattern[0][0]) @app.on_message(~ filters.scheduled & filters.private & filters.command(confirmation_1, prefixes=[""])) -async def confirm_yes(app, msg): +async def confirm_yes(app: Client, msg: Message, kind: Literal["application", "sponsorship"] = "unknown"): holo_user = HoloUser(msg.from_user) - if (holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True): - - await msg.reply_text(locale("application_sent", "message"), reply_markup=ReplyKeyboardRemove()) + if (kind == "application") or ((holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True)): tmp_application = col_tmp.find_one({"user": holo_user.id, "type": "application"}) @@ -28,6 +30,11 @@ async def confirm_yes(app, msg): logWrite(f"Application of {holo_user.id} is nowhere to be found.") return + if tmp_application["sent"] is True: + return + + await msg.reply_text(locale("application_sent", "message"), reply_markup=ReplyKeyboardRemove()) + application_content = [] i = 1 @@ -47,7 +54,7 @@ async def confirm_yes(app, msg): 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( + 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.username, "\n".join(application_content)), parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup( [ [ InlineKeyboardButton(text=str(locale("reapply_yes", "button")), callback_data=f"reapply_yes_{holo_user.id}") @@ -59,7 +66,7 @@ async def confirm_yes(app, msg): ) ) 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( + 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.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}") @@ -81,19 +88,74 @@ async def confirm_yes(app, msg): col_tmp.update_one({"user": holo_user.id, "type": "application"}, {"$set": {"sent": True}}) + return + # configSet(["sent"], True, file=str(holo_user.id)) # configSet(["confirmed"], True, file=str(holo_user.id)) + if (kind == "sponsorship") or ((holo_user.sponsorship_state()[0] == "fill") and (holo_user.sponsorship_state()[1] is True)): + + tmp_sponsorship = col_tmp.find_one({"user": holo_user.id, "type": "sponsorship"}) + + if tmp_sponsorship is None: + logWrite(f"Sponsorship of {holo_user.id} is nowhere to be found.") + return + + if tmp_sponsorship["sent"] is True: + return + + await msg.reply_text(locale("sponsorship_sent", "message"), reply_markup=ReplyKeyboardRemove()) + + sponsorship_content = [] + + for question in tmp_sponsorship['sponsorship']: + + if question == "expires": + sponsorship_content.append(f"{locale(f'question_{question}', 'message', 'sponsor_titles')} {tmp_sponsorship['sponsorship'][question].strftime('%d.%m.%Y')}") + elif question == "proof": + filename = uuid1() + with open(f"tmp{sep}{filename}.jpg", "wb") as f: + f.write(tmp_sponsorship['sponsorship']['proof']) + else: + sponsorship_content.append(f"{locale(f'question_{question}', 'message', 'sponsor_titles')} {tmp_sponsorship['sponsorship'][question]}") + + await app.send_photo(chat_id=configGet("admin_group"), photo=f"tmp{sep}{filename}.jpg", caption=(locale("sponsor_got", "message")).format(str(holo_user.id), msg.from_user.first_name, msg.from_user.username, "\n".join(sponsorship_content)), parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text=str(locale("sponsor_yes", "button")), callback_data=f"sponsor_yes_{holo_user.id}") + ], + [ + InlineKeyboardButton(text=str(locale("sponsor_no", "button")), callback_data=f"sponsor_no_{holo_user.id}") + ] + ] + ) + ) + + remove(f"tmp{sep}{filename}.jpg") + + logWrite(f"User {holo_user.id} sent his sponsorship application and it will now be reviewed") + + col_tmp.update_one({"user": holo_user.id, "type": "sponsorship"}, {"$set": {"sent": True}}) + + return + confirmation_2 = [] for pattern in all_locales("confirm", "keyboard"): confirmation_2.append(pattern[1][0]) @app.on_message(~ filters.scheduled & filters.private & filters.command(confirmation_2, prefixes=[""])) -async def confirm_no(app, msg): +async def confirm_no(app: Client, msg: Message, kind: Literal["application", "sponsorship"] = "unknown"): holo_user = HoloUser(msg.from_user) - if (holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True): + if (kind == "application") or ((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") + return + + if (kind == "sponsorship") or ((holo_user.sponsorship_state()[0] == "fill") and (holo_user.sponsorship_state()[1] is True)): + holo_user.sponsorship_restart() + await app.send_message(holo_user.id, locale(f"sponsor1", "message", locale=holo_user.locale), reply_markup=ForceReply(placeholder=str(locale(f"sponsor1", "force_reply", locale=holo_user.locale)))) + logWrite(f"User {msg.from_user.id} restarted the sponsorship application due to typo in it") + return # ============================================================================================================================== \ No newline at end of file diff --git a/modules/handlers/contact.py b/modules/handlers/contact.py index 745e549..b6459a5 100644 --- a/modules/handlers/contact.py +++ b/modules/handlers/contact.py @@ -1,53 +1,54 @@ from dateutil.relativedelta import relativedelta from datetime import datetime -from app import app, isAnAdmin +from app import app from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client from modules.utils import locale, logWrite from modules.database import col_applications from classes.holo_user import HoloUser +from modules import custom_filters # Contact getting ============================================================================================================== -@app.on_message(~ filters.scheduled & filters.contact & filters.private) -async def get_contact(app, msg): +@app.on_message(~ filters.scheduled & filters.contact & filters.private & (custom_filters.allowed | custom_filters.admin)) +async def get_contact(app: Client, msg: Message): 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: - if msg.contact.user_id != None: + application = col_applications.find_one({"user": msg.contact.user_id}) - 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", locale=holo_user.locale)) + return - 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", locale=holo_user.locale)) - return + application_content = [] + i = 1 - application_content = [] - i = 1 + for question in application['application']: - 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', locale=holo_user.locale)} {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', locale=holo_user.locale)} {application['application']['3']['name']}") - else: - application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})") + if i == 2: + age = relativedelta(datetime.now(), application['application']['2']) + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {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', locale=holo_user.locale)} {application['application']['3']['name']}") else: - application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application'][question]}") - - i += 1 + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})") + else: + application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application'][question]}") + + i += 1 - application_status = locale("application_status_accepted", "message", locale=holo_user.locale).format((await app.get_users(application["admin"])).first_name, application["date"].strftime("%d.%m.%Y, %H:%M")) + application_status = locale("application_status_accepted", "message", locale=holo_user.locale).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", locale=holo_user.locale).format(str(msg.contact.user_id), "\n".join(application_content), application_status)) - + logWrite(f"User {holo_user.id} requested application of {msg.contact.user_id}") + await msg.reply_text(locale("contact", "message", locale=holo_user.locale).format(str(msg.contact.user_id), "\n".join(application_content), application_status)) + - else: - 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", locale=holo_user.locale)) + else: + 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", locale=holo_user.locale)) # ============================================================================================================================== \ No newline at end of file diff --git a/modules/handlers/everything.py b/modules/handlers/everything.py index 47be800..d9de5c7 100644 --- a/modules/handlers/everything.py +++ b/modules/handlers/everything.py @@ -2,6 +2,7 @@ from app import app, isAnAdmin import asyncio from pyrogram import filters from pyrogram.types import Message +from pyrogram.client import Client from classes.holo_user import HoloUser from modules.utils import configGet, logWrite from modules.database import col_messages @@ -20,7 +21,7 @@ async def message_context(msg: Message) -> tuple: # Any other input ============================================================================================================== @app.on_message(~ filters.scheduled & filters.private) -async def any_stage(app, msg): +async def any_stage(app: Client, msg: Message): if msg.via_bot is None: @@ -50,7 +51,9 @@ async def any_stage(app, msg): return - await holo_user.application_next(msg.text, msg=msg) + if msg.text is not None: + await holo_user.application_next(msg.text, msg=msg) + await holo_user.sponsorship_next(msg.text, msg=msg) # user_stage = configGet("stage", file=str(msg.from_user.id)) @@ -111,7 +114,7 @@ async def any_stage(app, msg): # await msg.reply_text(locale("already_sent", "message")) @app.on_message(~ filters.scheduled & filters.group) -async def message_in_group(app, msg): +async def message_in_group(app: Client, msg: Message): if (msg.chat is not None) and (msg.via_bot is not None): if (msg.via_bot.id == configGet("bot_id")) and (msg.chat.id == configGet("destination_group")): if configGet("remove_application_time") > 0: diff --git a/modules/handlers/group_join.py b/modules/handlers/group_join.py index 4ea326e..d110573 100644 --- a/modules/handlers/group_join.py +++ b/modules/handlers/group_join.py @@ -1,5 +1,6 @@ from app import app, isAnAdmin -from pyrogram.types import ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton +from pyrogram.types import ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton, ChatMemberUpdated +from pyrogram.client import Client from modules.utils import configGet, locale from modules.logging import logWrite from classes.holo_user import HoloUser @@ -7,7 +8,7 @@ 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): +async def filter_join(app: Client, member: ChatMemberUpdated): if member.invite_link != None: diff --git a/modules/handlers/sponsorship.py b/modules/handlers/sponsorship.py index e524e69..f78a6de 100644 --- a/modules/handlers/sponsorship.py +++ b/modules/handlers/sponsorship.py @@ -1 +1,15 @@ -from app import app \ No newline at end of file +from app import app +from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client + +from classes.holo_user import HoloUser + +@app.on_message(~ filters.scheduled & filters.photo & filters.private) +async def sponsor_proof(app: Client, msg: Message): + + if msg.via_bot is None: + + holo_user = HoloUser(msg.from_user) + + await holo_user.sponsorship_next(msg.text, msg=msg, photo=msg.photo) \ No newline at end of file diff --git a/modules/handlers/voice.py b/modules/handlers/voice.py index 47fdb1c..a6bb361 100644 --- a/modules/handlers/voice.py +++ b/modules/handlers/voice.py @@ -1,10 +1,12 @@ from random import choice from app import app from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client from modules.logging import logWrite from modules.utils import configGet, locale @app.on_message(~ filters.scheduled & filters.voice & filters.chat(configGet("destination_group"))) -async def voice_message(app, msg): +async def voice_message(app: Client, msg: Message): logWrite(f"User {msg.from_user.id} sent voice message in destination group") await msg.reply_text(choice(locale("voice_message", "message"))) \ No newline at end of file diff --git a/modules/handlers/welcome.py b/modules/handlers/welcome.py index ca04c7f..bcd516a 100644 --- a/modules/handlers/welcome.py +++ b/modules/handlers/welcome.py @@ -1,6 +1,7 @@ from app import app from pyrogram import filters -from pyrogram.types import ForceReply, ReplyKeyboardMarkup +from pyrogram.types import ForceReply, ReplyKeyboardMarkup, Message +from pyrogram.client import Client from modules.utils import all_locales, locale, logWrite # Welcome check ================================================================================================================ @@ -31,7 +32,7 @@ welcome_2 = [] for pattern in all_locales("welcome", "keyboard"): welcome_2.append(pattern[1][0]) @app.on_message(~ filters.scheduled & filters.private & filters.command(welcome_2, prefixes=[""])) -async def welcome_reject(app, msg): +async def welcome_reject(app: Client, msg: Message): logWrite(f"User {msg.from_user.id} rejected to start the application") await msg.reply_text(locale("goodbye", "message", locale=msg.from_user), reply_markup=ReplyKeyboardMarkup(locale("return", "keyboard", locale=msg.from_user), resize_keyboard=True)) diff --git a/modules/inline.py b/modules/inline.py index 851b42d..d0f1a8e 100644 --- a/modules/inline.py +++ b/modules/inline.py @@ -1,7 +1,8 @@ from datetime import datetime from os import path, sep from app import app, isAnAdmin -from pyrogram.types import InlineQueryResultArticle, InputTextMessageContent +from pyrogram.types import InlineQueryResultArticle, InputTextMessageContent, InlineQuery +from pyrogram.client import Client from pyrogram.enums.chat_type import ChatType from pyrogram.enums.chat_members_filter import ChatMembersFilter from dateutil.relativedelta import relativedelta @@ -10,7 +11,7 @@ from modules.utils import configGet, locale from modules.database import col_applications @app.on_inline_query() -async def inline_answer(client, inline_query): +async def inline_answer(client: Client, inline_query: InlineQuery): if inline_query.chat_type in [ChatType.CHANNEL]: await inline_query.answer( diff --git a/modules/scheduled.py b/modules/scheduled.py index 81c5ce6..979c964 100644 --- a/modules/scheduled.py +++ b/modules/scheduled.py @@ -1,16 +1,35 @@ -from os import listdir, path, sep +from os import listdir, makedirs, path, sep from apscheduler.schedulers.asyncio import AsyncIOScheduler from datetime import datetime, timedelta from app import app from pyrogram.types import BotCommand, BotCommandScopeChat from pyrogram.errors import bad_request_400 from pyrogram.enums.chat_members_filter import ChatMembersFilter -from modules.utils import configGet, locale, logWrite +from classes.holo_user import HoloUser +from modules.utils import configGet, jsonSave, locale, logWrite from dateutil.relativedelta import relativedelta -from modules.database import col_applications +from modules.database import col_applications, col_sponsorships scheduler = AsyncIOScheduler() +if configGet("enabled", "scheduler", "cache_members"): + @scheduler.scheduled_job(trigger="interval", seconds=configGet("interval", "scheduler", "cache_members")) + async def cache_group_members(): + list_of_users = [] + async for member in app.get_chat_members(configGet("destination_group")): + list_of_users.append(member.user.id) + makedirs("cache", exist_ok=True) + jsonSave(list_of_users, f"cache{sep}group_members") + +if configGet("enabled", "scheduler", "cache_admins"): + @scheduler.scheduled_job(trigger="interval", seconds=configGet("interval", "scheduler", "cache_admins")) + async def cache_admins(): + list_of_users = [] + async for member in app.get_chat_members(configGet("admin_group")): + list_of_users.append(member.user.id) + makedirs("cache", exist_ok=True) + jsonSave(list_of_users, f"cache{sep}admins") + # Cache the avatars of group members if configGet("enabled", "scheduler", "cache_avatars"): @scheduler.scheduled_job(trigger="date", run_date=datetime.now()+timedelta(seconds=10)) @@ -47,6 +66,29 @@ if configGet("enabled", "scheduler", "birthdays"): if configGet("enabled", "scheduler", "sponsorships"): @scheduler.scheduled_job(trigger="cron", hour=configGet("time", "scheduler", "sponsorships")) async def check_sponsors(): + for entry in col_sponsorships.find({"sponsorship.expires": {"$lt": datetime.now()+timedelta(days=2)}}): + try: + tg_user = await app.get_users(entry["user"]) + until_expiry = relativedelta(datetime.now(), entry["sponsorship"]["expires"]).days + await app.send_message( tg_user, locale("sponsorships_expires", "message").format(until_expiry) ) # type: ignore + logWrite(f"Notified user that sponsorship expires in {until_expiry} days") + except Exception as exp: + logWrite(f"Could not find user {entry['user']} notify about sponsorship expiry due to '{exp}'") + continue + for entry in col_sponsorships.find({"sponsorship.expires": {"$lt": datetime.now()}}): + try: + holo_user = HoloUser(entry["user"]) + await app.send_message( entry["user"], locale("sponsorships_expired", "message") ) # type: ignore + await holo_user.label_reset(configGet("destination_group")) + col_sponsorships.find_one_and_delete({"user": holo_user.id}) + try: + tg_user = await app.get_users(entry["user"]) + logWrite(f"Notified user that sponsorship expired") + except Exception as exp: + logWrite(f"Could not find user {entry['user']} notify about sponsorship expired due to '{exp}'") + except Exception as exp: + logWrite(f"Could not reset label of user {entry['user']} due to '{exp}'") + continue logWrite("Sponsorships check performed") diff --git a/modules/utils.py b/modules/utils.py index 317bb97..68c1e98 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,4 +1,5 @@ from typing import Any, Union +from requests import get from pyrogram.enums.chat_type import ChatType from pyrogram.types import User from pyrogram.client import Client @@ -10,6 +11,7 @@ from sys import exit from os import kill, listdir, sep from os import name as osname from traceback import print_exc +from classes.errors.geo import PlaceNotFoundError from modules.logging import logWrite @@ -171,6 +173,24 @@ def all_locales(key: str, *args: str) -> list: return output +def find_location(query: str) -> dict: + """Find location on geonames.org by query. Search is made with feature classes A and P. + + ### Args: + * query (`str`): Some city/village/state name + + ### Raises: + * PlaceNotFoundError: Exception is raised when API result is empty + + ### Returns: + * `dict`: One instance of geonames response + """ + 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() + return result["geonames"][0] + except (ValueError, KeyError, IndexError): + raise PlaceNotFoundError(query) + try: from psutil import Process except ModuleNotFoundError: diff --git a/validation/warnings.json b/validation/warnings.json index 026bd47..6712a4a 100644 --- a/validation/warnings.json +++ b/validation/warnings.json @@ -1,6 +1,28 @@ { "$jsonSchema": { - "required": [], - "properties": {} + "required": [ + "user", + "admin", + "date", + "reason" + ], + "properties": { + "user": { + "bsonType": ["int", "long"], + "description": "Telegram ID of user" + }, + "admin": { + "bsonType": ["int", "long"], + "description": "Telegram ID of admin" + }, + "date": { + "bsonType": "date", + "description": "Date and time of getting" + }, + "reason": { + "bsonType": "string", + "description": "Broken rule or admin's comment" + } + } } } \ No newline at end of file