/nearby, subscriptions check, geocoding #2

Merged
profitroll merged 30 commits from dev into master 2023-01-02 12:16:38 +02:00
40 changed files with 922 additions and 338 deletions

View File

@ -104,6 +104,9 @@ After all of that you're good to go! Happy using :)
## To-Do ## To-Do
* [x] Complete messenger between user and admins
* [ ] Check sponsorship on Holo girls * [ ] Check sponsorship on Holo girls
* [ ] Get application by id and user_id * [ ] 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

9
app.py
View File

@ -1,5 +1,7 @@
from os import path, sep
from ujson import JSONDecodeError
from modules.logging import logWrite from modules.logging import logWrite
from modules.utils import configGet from modules.utils import configGet, jsonLoad
from pyrogram.client import Client from pyrogram.client import Client
from pyrogram.errors import bad_request_400 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): async def isAnAdmin(admin_id):
if (admin_id == configGet("owner")) or (admin_id in configGet("admins")): if (admin_id == configGet("owner")) or (admin_id in configGet("admins")):
return True 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: try:
async for member in app.get_chat_members(configGet("admin_group")): async for member in app.get_chat_members(configGet("admin_group")):
if member.user.id == admin_id: if member.user.id == admin_id:

5
classes/errors/geo.py Normal file
View File

@ -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}'")

View File

@ -1,4 +1,6 @@
from datetime import datetime from datetime import datetime
from os import sep
from uuid import uuid1
from requests import get from requests import get
from traceback import print_exc from traceback import print_exc
from app import app, isAnAdmin 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.types import User, ChatMember, ChatPrivileges, Chat, Message, Photo, Video, Document, Animation, Voice, ForceReply, ReplyKeyboardMarkup
from pyrogram.errors import bad_request_400 from pyrogram.errors import bad_request_400
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from classes.errors.geo import PlaceNotFoundError
from modules.database import col_tmp, col_users, col_context, col_warnings, col_applications, col_sponsorships, col_messages from modules.database import col_tmp, col_users, col_context, col_warnings, col_applications, col_sponsorships, col_messages
from modules.logging import logWrite 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): class DefaultApplicationTemp(dict):
def __init__(self, user: int): def __init__(self, user: int, reapply: bool = False):
super().__init__({}) super().__init__({})
self.dict = { self.dict = {
"user": user, "user": user,
@ -19,7 +22,7 @@ class DefaultApplicationTemp(dict):
"complete": False, "complete": False,
"sent": False, "sent": False,
"state": "fill", "state": "fill",
"reapply": False, "reapply": reapply,
"stage": 1, "stage": 1,
"application": { "application": {
"1": None, "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): class UserNotFoundError(Exception):
"""HoloUser could not find user with such an ID in database""" """HoloUser could not find user with such an ID in database"""
def __init__(self, user, user_id): def __init__(self, user, user_id):
@ -48,6 +69,11 @@ class UserInvalidError(Exception):
self.user = user self.user = user
super().__init__(f"Could not find HoloUser by using {type(self.user)} as an input type") 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(): class HoloUser():
"""This object represents a user of HoloChecker bot. """This object represents a user of HoloChecker bot.
It is primarily used to interact with a database in a more python-friendly way, 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.locale = holo_user["tg_locale"]
self.username = holo_user["tg_username"] self.username = holo_user["tg_username"]
if isinstance(user, User) and ((self.name != user.first_name) and (user.first_name is not None)): if isinstance(user, User):
self.set("tg_name", user.first_name)
if isinstance(user, User) and ((self.phone != user.phone_number) and (user.phone_number is not None)): if (self.name != user.first_name) and hasattr(user, "first_name") and (user.first_name is not None):
self.set("tg_phone", user.phone_number) 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)): if (self.phone != user.phone_number) and hasattr(user, "phone") and (user.phone_number is not None):
self.set("tg_locale", user.language_code) self.set("phone", user.phone_number, db_key="tg_phone")
if isinstance(user, User) and (self.username != user.username): if (self.locale != user.language_code) and hasattr(user, "locale") and (user.language_code is not None):
self.set("tg_username", user.username) self.set("locale", user.language_code, db_key="tg_locale")
def set(self, key: str, value: Any) -> None: 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, db_key: Union[str, None] = None) -> None:
"""Set attribute data and save it into database """Set attribute data and save it into database
### Args: ### Args:
@ -122,7 +150,8 @@ class HoloUser():
if not hasattr(self, key): if not hasattr(self, key):
raise AttributeError() raise AttributeError()
setattr(self, key, value) 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}") logWrite(f"Set attribute {key} of user {self.id} to {value}")
async def message(self, async def message(self,
@ -226,11 +255,11 @@ class HoloUser():
elif animation is not None: elif animation is not None:
if isinstance(animation, Animation): if isinstance(animation, Animation):
animation = animation.file_id 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: elif voice is not None:
if isinstance(voice, Voice): if isinstance(voice, Voice):
voice = voice.file_id 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: else:
new_message = await app.send_message(self.id, text) 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!") 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)) 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 """Set label in destination group
### Args: ### Args:
* chat (`Chat`): Telegram chat * chat (`Chat`): Telegram chat
* label (`str`): Label you want to set * label (`str`): Label you want to set
""" """
if len(label) > 16:
raise LabelTooLongError(label)
self.label = label self.label = label
self.set("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): if not await isAnAdmin(self.id):
await app.set_administrator_title(configGet("destination_group"), self.id, label) 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 """Reset label in destination group
### Args: ### Args:
@ -271,7 +302,9 @@ class HoloUser():
await app.set_administrator_title(configGet("destination_group"), self.id, "") await app.set_administrator_title(configGet("destination_group"), self.id, "")
if not await isAnAdmin(self.id): if not await isAnAdmin(self.id):
await app.promote_chat_member(configGet("destination_group"), self.id, privileges=ChatPrivileges( 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]: 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 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 """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: if col_tmp.find_one({"user": self.id, "type": "application"}) is None:
col_tmp.insert_one(document=DefaultApplicationTemp(self.id).dict) col_tmp.insert_one(document=DefaultApplicationTemp(self.id).dict)
else: else:
col_tmp.delete_one({"user": self.id, "type": "application"}) 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: async def application_next(self, query: str, msg: Message) -> None:
"""Move on filling application of user """Move on filling application of user
@ -318,9 +351,17 @@ class HoloUser():
) )
progress = col_tmp.find_one({"user": self.id, "type": "application"}) progress = col_tmp.find_one({"user": self.id, "type": "application"})
if progress is None:
return
stage = progress["stage"] stage = progress["stage"]
if progress["state"] == "fill": 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: if stage == 2:
@ -349,13 +390,16 @@ class HoloUser():
elif stage == 3: elif stage == 3:
try: 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)] = find_location(query)
progress["application"][str(stage)] = result["geonames"][0] 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}}) 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)))) 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): except PlaceNotFoundError:
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)))) 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 return
except Exception as exp: 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)))) 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))))
@ -390,3 +434,106 @@ class HoloUser():
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)))) 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") 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

View File

@ -9,6 +9,7 @@
"admin_group": 0, "admin_group": 0,
"destination_group": 0, "destination_group": 0,
"remove_application_time": -1, "remove_application_time": -1,
"search_radius": 50,
"admins": [], "admins": [],
"bot": { "bot": {
"api_id": 0, "api_id": 0,
@ -41,6 +42,14 @@
"cache_avatars": { "cache_avatars": {
"interval": 6, "interval": 6,
"enabled": true "enabled": true
},
"cache_members": {
"interval": 30,
"enabled": true
},
"cache_admins": {
"interval": 120,
"enabled": true
} }
}, },
"locations": { "locations": {

View File

@ -12,6 +12,7 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True)
# Importing # Importing
from modules.commands.application import * from modules.commands.application import *
from modules.commands.applications import * from modules.commands.applications import *
from modules.commands.cancel import *
from modules.commands.label import * from modules.commands.label import *
from modules.commands.message import * from modules.commands.message import *
from modules.commands.nearby import * from modules.commands.nearby import *
@ -26,12 +27,14 @@ from modules.commands.warnings import *
from modules.callbacks.nothing import * from modules.callbacks.nothing import *
from modules.callbacks.reapply import * from modules.callbacks.reapply import *
from modules.callbacks.rules import * from modules.callbacks.rules import *
from modules.callbacks.sponsorship import *
from modules.callbacks.sub import * from modules.callbacks.sub import *
from modules.callbacks.sus import * from modules.callbacks.sus import *
from modules.handlers.confirmation import * from modules.handlers.confirmation import *
from modules.handlers.contact import * from modules.handlers.contact import *
from modules.handlers.group_join import * from modules.handlers.group_join import *
from modules.handlers.sponsorship import *
from modules.handlers.voice import * from modules.handlers.voice import *
from modules.handlers.welcome import * from modules.handlers.welcome import *
from modules.handlers.everything import * from modules.handlers.everything import *

View File

@ -22,8 +22,8 @@
"question3_traceback": "⚠️ **Error occurred**\nError retrieving geocoding for `{0}`\nError: `{1}`\n\nTraceback:\n```\n{2}\n```", "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?", "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_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}", "application_got": "Received an application from `{0}`\n\nName in tg: `{1}`\nUsername: @{2}\n\n**Application data:**\n{3}",
"reapply_got": "Received profile change from `{0}`\n\nUsername: `{1}`, `{2}`\nUsername: @{3}\n\n**Application data:**\n{4}", "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}`", "shutdown": "Shutting down the bot with PID `{0}`",
"startup": "Starting the bot with PID `{0}`", "startup": "Starting the bot with PID `{0}`",
"startup_downtime": "Starting bot with PID `{0}` (was down for {1})", "startup_downtime": "Starting bot with PID `{0}` (was down for {1})",

View File

@ -20,10 +20,22 @@
"question3_found": "Використовую наступний результат:\n• {0} ({1})", "question3_found": "Використовую наступний результат:\n• {0} ({1})",
"question3_error": "⚠️ **Сталась помилка**\nНе вдалось отримати географічну мітку. Розробника повідомлено про цю помилку. Будь ласка, спробуйте ще раз.", "question3_error": "⚠️ **Сталась помилка**\nНе вдалось отримати географічну мітку. Розробника повідомлено про цю помилку. Будь ласка, спробуйте ще раз.",
"question3_traceback": "⚠️ **Сталась помилка**\nПомилка отримання геокодингу для `{0}`\nПомилка: `{1}`\n\nTraceback:\n```\n{2}\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Все правильно?", "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?",
"sponsor_confirm": "Здається, це все. Перевір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.",
"application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується. Гарного дня! :)", "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується. Гарного дня! :)",
"application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`, `{2}`\nЮзернейм: @{3}\n\n**Дані анкети:**\n{4}", "sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. Гарного дня! :)",
"reapply_got": "Отримано змінити анкети від `{0}`\n\nІм'я тг: `{1}`, `{2}`\nЮзернейм: @{3}\n\n**Дані анкети:**\n{4}", "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}`", "shutdown": "Вимкнення бота з підом `{0}`",
"startup": "Запуск бота з підом `{0}`", "startup": "Запуск бота з підом `{0}`",
"startup_downtime": "Запуск бота з підом `{0}` (лежав {1})", "startup_downtime": "Запуск бота з підом `{0}` (лежав {1})",
@ -37,6 +49,10 @@
"rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.", "rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.",
"rejected_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.", "rejected_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.",
"rejected_by_rus": "❌ **Анкету відхилено**\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}", "contact": "Анкета `{0}`\n\n**Дані анкети:**\n{1}\n\n{2}",
"application_status_accepted": "Прийнята `{0}` від {1}", "application_status_accepted": "Прийнята `{0}` від {1}",
"application_status_rejected": "Відхилена `{0}` від {1}", "application_status_rejected": "Відхилена `{0}` від {1}",
@ -71,6 +87,15 @@
"no_user_application": "Не знайдено користувачів за запитом **{0}**", "no_user_application": "Не знайдено користувачів за запитом **{0}**",
"user_invalid": "Надісланий користувач не має завершеної анкети.", "user_invalid": "Надісланий користувач не має завершеної анкети.",
"joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням", "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": [ "voice_message": [
"why are u gae", "why are u gae",
"руки відірвало? пиши як людина", "руки відірвало? пиши як людина",
@ -87,6 +112,11 @@
"question8": "Дивлюсь стріми:", "question8": "Дивлюсь стріми:",
"question9": "Подобаються пісні:", "question9": "Подобаються пісні:",
"question10": "Про себе:" "question10": "Про себе:"
},
"sponsor_titles": {
"question_streamer": "Стрімер:",
"question_expires": "Підписка до:",
"question_label": "Хоче роль:"
} }
}, },
"keyboard": { "keyboard": {
@ -122,13 +152,19 @@
"question7": "П'ять японських холодівчат", "question7": "П'ять японських холодівчат",
"question8": "Так або ні", "question8": "Так або ні",
"question9": "Ім'я дівчини або дівчин", "question9": "Ім'я дівчини або дівчин",
"question10": "Трошки про себе" "question10": "Трошки про себе",
"sponsor1": "Ім'я дівчини",
"sponsor2": "Дата до якої підписка",
"sponsor3": "Фото-підтвердження",
"sponsor4": "Бажана роль"
}, },
"button": { "button": {
"sub_yes": "✅ Прийняти", "sub_yes": "✅ Прийняти",
"sub_no": "❌ Відхилити", "sub_no": "❌ Відхилити",
"sub_aggressive": "🤡 Відхилити (Токс)", "sub_aggressive": "🤡 Відхилити (Токс)",
"sub_russian": "🇷🇺 Відхилити (Русак)", "sub_russian": "🇷🇺 Відхилити (Русак)",
"sponsor_yes": "✅ Прийняти",
"sponsor_no": "❌ Відхилити",
"accepted": "✅ Прийнято", "accepted": "✅ Прийнято",
"declined": "❌ Відхилено", "declined": "❌ Відхилено",
"join": "Приєднатись", "join": "Приєднатись",
@ -145,7 +181,9 @@
"rules_next": "Далі ➡️", "rules_next": "Далі ➡️",
"rules_prev": "⬅️ Назад", "rules_prev": "⬅️ Назад",
"applying_stop": "🛑 Перервати заповнення", "applying_stop": "🛑 Перервати заповнення",
"done": "✅ Готово" "done": "✅ Готово",
"sponsor_apply": "Заповнити форму",
"sponsor_started": "Форму розпочато"
}, },
"callback": { "callback": {
"sub_accepted": "✅ Анкету {0} схвалено", "sub_accepted": "✅ Анкету {0} схвалено",
@ -158,7 +196,10 @@
"rules_page": " Показано правило {0}", "rules_page": " Показано правило {0}",
"rules_home": " Показано головну правил", "rules_home": " Показано головну правил",
"rules_additional": " Показано додаткові правила", "rules_additional": " Показано додаткові правила",
"reapply_stopped": " Перервано заповнення анкети" "reapply_stopped": " Перервано заповнення анкети",
"sponsor_started": " Заповнення форми розпочато",
"sponsor_accepted": "✅ Форму {0} схвалено",
"sponsor_rejected": "❌ Форму {0} відхилено"
}, },
"inline": { "inline": {
"forbidden": { "forbidden": {

View File

@ -1,9 +1,11 @@
from app import app from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import CallbackQuery
from pyrogram.client import Client
from modules.utils import locale from modules.utils import locale
# Callback empty =============================================================================================================== # Callback empty ===============================================================================================================
@app.on_callback_query(filters.regex("nothing")) @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)) await clb.answer(text=locale("nothing", "callback", locale=clb.from_user))
# ============================================================================================================================== # ==============================================================================================================================

View File

@ -1,16 +1,17 @@
from datetime import datetime from datetime import datetime
from app import app 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 pyrogram import filters
from classes.holo_user import HoloUser 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.confirmation import confirm_yes
from modules.handlers.welcome import welcome_pass from modules.handlers.welcome import welcome_pass
from modules.database import col_tmp, col_applications from modules.database import col_tmp, col_applications
# Callbacks reapply ============================================================================================================ # Callbacks reapply ============================================================================================================
@app.on_callback_query(filters.regex("reapply_yes_[\s\S]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2])) 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.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_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")]] 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)) await app.send_message(holo_user.id, locale("approved_joined", "message", locale=holo_user))
@app.on_callback_query(filters.regex("reapply_no_[\s\S]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2])) 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 # Use old application when user reapplies after leaving the chat
@app.on_callback_query(filters.regex("reapply_old_[\s\S]*")) @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("_") 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])) 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")]])) 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 # Start a new application when user reapplies after leaving the chat
@app.on_callback_query(filters.regex("reapply_new_[\s\S]*")) @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("_") 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)) await clb.answer(locale("reapply_stopped", "callback", locale=clb.from_user))
message = await app.get_messages(clb.from_user.id, int(fullclb[2])) 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}}) 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 # Abort application fill in progress and restart it
@app.on_callback_query(filters.regex("reapply_stop_[\s\S]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(clb.from_user) holo_user = HoloUser(clb.from_user)

View File

@ -1,5 +1,6 @@
from app import app 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.errors import bad_request_400
from pyrogram import filters from pyrogram import filters
from modules.utils import locale, logWrite from modules.utils import locale, logWrite
@ -7,7 +8,7 @@ from modules.commands.rules import DefaultRulesMarkup
# Callback rule ================================================================================================================ # Callback rule ================================================================================================================
@app.on_callback_query(filters.regex("rule_[\s\S]*")) @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("_") 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])) await clb.answer(text=locale("rules_page", "callback", locale=clb.from_user).format(fullclb[1]))
@app.on_callback_query(filters.regex("rules_home")) @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") 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)) await clb.answer(text=locale("rules_home", "callback", locale=clb.from_user))
@app.on_callback_query(filters.regex("rules_additional")) @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") logWrite(f"User {clb.from_user.id} requested to check out additional rules")

View File

@ -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)

View File

@ -1,7 +1,8 @@
from datetime import datetime from datetime import datetime
from app import app from app import app
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from pyrogram import filters from pyrogram import filters
from pyrogram.client import Client
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import configGet, locale, logWrite from modules.utils import configGet, locale, logWrite
from modules.database import col_tmp, col_applications from modules.database import col_tmp, col_applications
@ -9,7 +10,7 @@ from modules.commands.rules import DefaultRulesMarkup
# Callbacks application ======================================================================================================== # Callbacks application ========================================================================================================
@app.on_callback_query(filters.regex("sub_yes_[\s\S]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2])) 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) 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]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2])) 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) 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]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2])) 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) 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]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2])) holo_user = HoloUser(int(fullclb[2]))

View File

@ -1,5 +1,6 @@
from app import app 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 pyrogram import filters
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import configGet, locale, logWrite from modules.utils import configGet, locale, logWrite
@ -7,7 +8,7 @@ from modules.database import col_tmp
# Callbacks sus users ========================================================================================================== # Callbacks sus users ==========================================================================================================
@app.on_callback_query(filters.regex("sus_allow_[\s\S]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2])) 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]*")) @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("_") fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2])) holo_user = HoloUser(int(fullclb[2]))

View File

@ -1,18 +1,19 @@
from datetime import datetime from datetime import datetime
from app import app, isAnAdmin from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.enums.parse_mode import ParseMode from pyrogram.enums.parse_mode import ParseMode
from pyrogram.types import Message
from pyrogram.errors import bad_request_400 from pyrogram.errors import bad_request_400
from pyrogram.client import Client
from classes.holo_user import HoloUser, UserNotFoundError from classes.holo_user import HoloUser, UserNotFoundError
from modules.utils import logWrite, locale, should_quote from modules.utils import logWrite, locale, should_quote
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from modules.database import col_applications from modules.database import col_applications
from modules import custom_filters
# Applications command ========================================================================================================= # Applications command =========================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["application"], prefixes=["/"])) @app.on_message(~ filters.scheduled & filters.command(["application"], prefixes=["/"]) & custom_filters.admin)
async def cmd_application(app, msg): async def cmd_application(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
try: try:

View File

@ -1,17 +1,19 @@
from os import sep, makedirs, remove from os import sep, makedirs, remove
from uuid import uuid1 from uuid import uuid1
from app import app, isAnAdmin from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from pyrogram.enums.chat_action import ChatAction from pyrogram.enums.chat_action import ChatAction
from modules.logging import logWrite from modules.logging import logWrite
from modules.utils import should_quote, jsonSave from modules.utils import should_quote, jsonSave
from modules.database import col_applications from modules.database import col_applications
from modules import custom_filters
# Applications command ========================================================================================================= # Applications command =========================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["applications"], prefixes=["/"])) @app.on_message(~ filters.scheduled & filters.command(["applications"], prefixes=["/"]) & custom_filters.admin)
async def cmd_applications(app, msg): 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") logWrite(f"Admin {msg.from_user.id} requested export of a database")
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT) await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
filename = uuid1() filename = uuid1()

View File

@ -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))

View File

@ -1,12 +1,13 @@
from app import app, isAnAdmin from app import app
from pyrogram import filters from pyrogram import filters
from modules.utils import should_quote, find_user from pyrogram.types import Message
from classes.holo_user import HoloUser 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=["/"])) @app.on_message(~ filters.scheduled & filters.private & filters.command(["label"], prefixes=["/"]) & custom_filters.admin)
async def cmd_label(app, msg): async def cmd_label(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
if len(msg.command) < 3: if len(msg.command) < 3:
await msg.reply_text("Invalid syntax:\n`/label USER LABEL`") await msg.reply_text("Invalid syntax:\n`/label USER LABEL`")
@ -21,11 +22,15 @@ async def cmd_label(app, msg):
label = " ".join(msg.command[2:]) label = " ".join(msg.command[2:])
if label.lower() == "reset": if label.lower() == "reset":
await target.reset_label(msg.chat) await target.label_reset(msg.chat)
await msg.reply_text(f"Resetting **{target.id}**'s label...", quote=should_quote(msg)) await msg.reply_text(f"Resetting **{target.id}**'s label...", quote=should_quote(msg))
else: else:
await target.set_label(msg.chat, label) 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)) await msg.reply_text(f"Setting **{target.id}**'s label to **{label}**...", quote=should_quote(msg))
else: else:

View File

@ -1,13 +1,14 @@
from app import app, isAnAdmin from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import logWrite, locale, should_quote from modules.utils import logWrite, locale, should_quote
from modules import custom_filters
# Message command ============================================================================================================== # Message command ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["message"], prefixes=["/"])) @app.on_message(~ filters.scheduled & filters.command(["message"], prefixes=["/"]) & custom_filters.admin)
async def cmd_message(app, msg): async def cmd_message(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
try: try:

View File

@ -1,13 +1,62 @@
from app import app, isAnAdmin from traceback import print_exc
from app import app
from pyrogram import filters from pyrogram import filters
from modules.utils import configGet, should_quote from pyrogram.types import Message
from modules.database import col_applications 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 =============================================================================================================== # Nearby command ===============================================================================================================
@app.on_message(~ filters.scheduled & (filters.private | (filters.chat(configGet("admin_group")) | filters.chat(configGet("destination_group")))) & filters.command(["nearby"], prefixes=["/"])) @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, msg): async def cmd_nearby(app: Client, msg: Message):
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)) 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"): # 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") # 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") # sponsor = jsonLoad(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json")

View File

@ -1,14 +1,15 @@
from app import app from app import app
from pyrogram import filters 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 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.handlers.welcome import welcome_pass
from modules.database import col_tmp from modules.database import col_tmp
# Reapply command ============================================================================================================== # Reapply command ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.private & filters.command(["reapply"], prefixes=["/"])) @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) holo_user = HoloUser(msg.from_user)
@ -19,7 +20,10 @@ async def cmd_reapply(app, msg):
if member.user.id == msg.from_user.id: if member.user.id == msg.from_user.id:
left_chat = False left_chat = False
if not left_chat: 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) await welcome_pass(app, msg, once_again=True)
else: else:
await msg.reply_text(locale("reapply_left_chat", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup([ await msg.reply_text(locale("reapply_left_chat", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup([

View File

@ -1,17 +1,19 @@
from app import app, isAnAdmin from app import app
from os import getpid from os import getpid
from sys import exit from sys import exit
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from modules.utils import locale, logWrite, should_quote from modules.utils import locale, logWrite, should_quote
from modules.scheduled import scheduler from modules.scheduled import scheduler
from modules import custom_filters
pid = getpid() pid = getpid()
# Shutdown command ============================================================================================================= # Shutdown command =============================================================================================================
@app.on_message(~ filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["/"])) @app.on_message(~ filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["/"]) & custom_filters.admin)
async def cmd_kill(app, msg): 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}") 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)) await msg.reply_text(locale("shutdown", "message", locale=msg.from_user).format(pid), quote=should_quote(msg))
scheduler.shutdown() scheduler.shutdown()

View File

@ -1,7 +1,8 @@
from typing import Union from typing import Union
from app import app from app import app
from pyrogram import filters 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 modules.utils import locale
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
@ -35,6 +36,6 @@ class DefaultRulesMarkup(list):
# Rules command ============================================================================================================= # Rules command =============================================================================================================
@app.on_message(~ filters.scheduled & filters.private & filters.command(["rules"], prefixes=["/"])) @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) await msg.reply_text(locale("rules_msg", locale=msg.from_user), disable_web_page_preview=True, reply_markup=DefaultRulesMarkup(msg.from_user).keyboard)
# ============================================================================================================================== # ==============================================================================================================================

View File

@ -1,25 +1,19 @@
from datetime import datetime from app import app
from app import app, isAnAdmin
from pyrogram import filters 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 from modules.database import col_applications
# Sponsorship command ========================================================================================================== # Sponsorship command ==========================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["sponsorship"], prefixes=["/"])) @app.on_message(~ filters.scheduled & filters.command(["sponsorship"], prefixes=["/"]) & (custom_filters.allowed | custom_filters.admin))
async def cmd_sponsorship(app, msg): async def cmd_sponsorship(app: Client, msg: Message):
if (await isAnAdmin(msg) is True) or (col_applications.find_one({"user": msg.from_user.id}) is not None): if HoloUser(msg.from_user).application_state()[0] == "fill":
await msg.reply_text("Yes, I exist.", quote=should_quote(msg)) await msg.reply_text(locale("finish_application", "message", locale=msg.from_user), quote=should_quote(msg))
# if not path.exists(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json"): return
# jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}sponsor_default.json"), f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json") 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))
# 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: # else:
# await msg.reply_text(f"Your sub expired {int((datetime.now()-datetime.strptime(sponsor['expires'], '%d.%m.%Y')).days)} days ago.") # await msg.reply_text(locale("sponsorship_application_empty", "message"))
# elif sponsor["approved"]:
# await msg.reply_text(f"Your sub expiration date is not valid.")
# else:
# await msg.reply_text(f"You have no active subscription.")
# ============================================================================================================================== # ==============================================================================================================================

View File

@ -1,12 +1,13 @@
from app import app from app import app
from pyrogram import filters 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.utils import locale, logWrite
from modules.database import col_users from modules.database import col_users
# Start command ================================================================================================================ # Start command ================================================================================================================
@app.on_message(~ filters.scheduled & filters.private & filters.command(["start"], prefixes=["/"])) @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}) user = col_users.find_one({"user": msg.from_user.id})

View File

@ -1,16 +1,18 @@
from datetime import datetime from datetime import datetime
from app import app, isAnAdmin from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from modules.utils import configGet, locale from modules.utils import configGet, locale
from modules.database import col_warnings from modules.database import col_warnings
from modules import custom_filters
# Warn command ================================================================================================================= # Warn command =================================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["warn"], prefixes=["/"])) @app.on_message(~ filters.scheduled & filters.command(["warn"], prefixes=["/"]) & custom_filters.admin)
async def cmd_warn(app, msg): async def cmd_warn(app: Client, msg: Message):
if msg.chat.id == configGet("destination_group"): if msg.chat.id == configGet("destination_group"):
if msg.reply_to_message_id != None: 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 "" 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}) col_warnings.insert_one({"user": msg.reply_to_message.from_user.id, "admin": msg.from_user.id, "date": datetime.now(), "reason": message})
if message == "": if message == "":

View File

@ -1,14 +1,15 @@
from app import app, isAnAdmin from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from pyrogram.enums.chat_members_filter import ChatMembersFilter from pyrogram.enums.chat_members_filter import ChatMembersFilter
from modules.utils import configGet, locale, should_quote from modules.utils import configGet, locale, should_quote
from modules.database import col_users, col_warnings from modules.database import col_users, col_warnings
from modules import custom_filters
# Warnings command ============================================================================================================= # Warnings command =============================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["warnings"], prefixes=["/"])) @app.on_message(~ filters.scheduled & filters.command(["warnings"], prefixes=["/"]) & custom_filters.admin)
async def cmd_warnings(app, msg): async def cmd_warnings(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
if len(msg.command) <= 1: if len(msg.command) <= 1:
await msg.reply_text(locale("syntax_warnings", "message", locale=msg.from_user), quote=should_quote(msg)) await msg.reply_text(locale("syntax_warnings", "message", locale=msg.from_user), quote=should_quote(msg))

12
modules/custom_filters.py Normal file
View File

@ -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)

View File

@ -1,4 +1,4 @@
from pymongo import MongoClient from pymongo import MongoClient, GEOSPHERE
from ujson import loads from ujson import loads
with open("config.json", "r", encoding="utf-8") as f: with open("config.json", "r", encoding="utf-8") as f:
@ -36,3 +36,5 @@ col_messages = db.get_collection("messages")
col_warnings = db.get_collection("warnings") col_warnings = db.get_collection("warnings")
col_applications = db.get_collection("applications") col_applications = db.get_collection("applications")
col_sponsorships = db.get_collection("sponsorships") col_sponsorships = db.get_collection("sponsorships")
col_applications.create_index([("application.3.location", GEOSPHERE)])

View File

@ -1,8 +1,12 @@
from os import remove, sep
from typing import Literal
from uuid import uuid1
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from datetime import datetime from datetime import datetime
from app import app from app import app
from pyrogram import filters 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 pyrogram.enums.parse_mode import ParseMode
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import all_locales, configGet, locale, logWrite from modules.utils import all_locales, configGet, locale, logWrite
@ -14,13 +18,11 @@ confirmation_1 = []
for pattern in all_locales("confirm", "keyboard"): for pattern in all_locales("confirm", "keyboard"):
confirmation_1.append(pattern[0][0]) confirmation_1.append(pattern[0][0])
@app.on_message(~ filters.scheduled & filters.private & filters.command(confirmation_1, prefixes=[""])) @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) 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)):
await msg.reply_text(locale("application_sent", "message"), reply_markup=ReplyKeyboardRemove())
tmp_application = col_tmp.find_one({"user": holo_user.id, "type": "application"}) 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.") logWrite(f"Application of {holo_user.id} is nowhere to be found.")
return return
if tmp_application["sent"] is True:
return
await msg.reply_text(locale("application_sent", "message"), reply_markup=ReplyKeyboardRemove())
application_content = [] application_content = []
i = 1 i = 1
@ -47,7 +54,7 @@ async def confirm_yes(app, msg):
i += 1 i += 1
if tmp_application["reapply"]: 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}") 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: 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}") 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}}) col_tmp.update_one({"user": holo_user.id, "type": "application"}, {"$set": {"sent": True}})
return
# configSet(["sent"], True, file=str(holo_user.id)) # configSet(["sent"], True, file=str(holo_user.id))
# configSet(["confirmed"], 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 = [] confirmation_2 = []
for pattern in all_locales("confirm", "keyboard"): for pattern in all_locales("confirm", "keyboard"):
confirmation_2.append(pattern[1][0]) confirmation_2.append(pattern[1][0])
@app.on_message(~ filters.scheduled & filters.private & filters.command(confirmation_2, prefixes=[""])) @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) 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() holo_user.application_restart()
await welcome_pass(app, msg, once_again=True) await welcome_pass(app, msg, once_again=True)
logWrite(f"User {msg.from_user.id} restarted the application due to typo in it") 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
# ============================================================================================================================== # ==============================================================================================================================

View File

@ -1,19 +1,20 @@
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from datetime import datetime from datetime import datetime
from app import app, isAnAdmin from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from modules.utils import locale, logWrite from modules.utils import locale, logWrite
from modules.database import col_applications from modules.database import col_applications
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules import custom_filters
# Contact getting ============================================================================================================== # Contact getting ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.contact & filters.private) @app.on_message(~ filters.scheduled & filters.contact & filters.private & (custom_filters.allowed | custom_filters.admin))
async def get_contact(app, msg): async def get_contact(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user) 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})

View File

@ -2,6 +2,7 @@ from app import app, isAnAdmin
import asyncio import asyncio
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message from pyrogram.types import Message
from pyrogram.client import Client
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import configGet, logWrite from modules.utils import configGet, logWrite
from modules.database import col_messages from modules.database import col_messages
@ -20,7 +21,7 @@ async def message_context(msg: Message) -> tuple:
# Any other input ============================================================================================================== # Any other input ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.private) @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: if msg.via_bot is None:
@ -50,7 +51,9 @@ async def any_stage(app, msg):
return return
if msg.text is not None:
await holo_user.application_next(msg.text, msg=msg) 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)) # 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")) # await msg.reply_text(locale("already_sent", "message"))
@app.on_message(~ filters.scheduled & filters.group) @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.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 (msg.via_bot.id == configGet("bot_id")) and (msg.chat.id == configGet("destination_group")):
if configGet("remove_application_time") > 0: if configGet("remove_application_time") > 0:

View File

@ -1,5 +1,6 @@
from app import app, isAnAdmin 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.utils import configGet, locale
from modules.logging import logWrite from modules.logging import logWrite
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
@ -7,7 +8,7 @@ from classes.holo_user import HoloUser
# Filter users on join ========================================================================================================= # Filter users on join =========================================================================================================
@app.on_chat_member_updated(group=configGet("destination_group")) @app.on_chat_member_updated(group=configGet("destination_group"))
#@app.on_message(filters.new_chat_members, 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: if member.invite_link != None:

View File

@ -1 +1,15 @@
from app import app 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)

View File

@ -1,10 +1,12 @@
from random import choice from random import choice
from app import app from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from modules.logging import logWrite from modules.logging import logWrite
from modules.utils import configGet, locale from modules.utils import configGet, locale
@app.on_message(~ filters.scheduled & filters.voice & filters.chat(configGet("destination_group"))) @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") logWrite(f"User {msg.from_user.id} sent voice message in destination group")
await msg.reply_text(choice(locale("voice_message", "message"))) await msg.reply_text(choice(locale("voice_message", "message")))

View File

@ -1,6 +1,7 @@
from app import app from app import app
from pyrogram import filters 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 from modules.utils import all_locales, locale, logWrite
# Welcome check ================================================================================================================ # Welcome check ================================================================================================================
@ -31,7 +32,7 @@ welcome_2 = []
for pattern in all_locales("welcome", "keyboard"): for pattern in all_locales("welcome", "keyboard"):
welcome_2.append(pattern[1][0]) welcome_2.append(pattern[1][0])
@app.on_message(~ filters.scheduled & filters.private & filters.command(welcome_2, prefixes=[""])) @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") 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)) await msg.reply_text(locale("goodbye", "message", locale=msg.from_user), reply_markup=ReplyKeyboardMarkup(locale("return", "keyboard", locale=msg.from_user), resize_keyboard=True))

View File

@ -1,7 +1,8 @@
from datetime import datetime from datetime import datetime
from os import path, sep from os import path, sep
from app import app, isAnAdmin 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_type import ChatType
from pyrogram.enums.chat_members_filter import ChatMembersFilter from pyrogram.enums.chat_members_filter import ChatMembersFilter
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
@ -10,7 +11,7 @@ from modules.utils import configGet, locale
from modules.database import col_applications from modules.database import col_applications
@app.on_inline_query() @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]: if inline_query.chat_type in [ChatType.CHANNEL]:
await inline_query.answer( await inline_query.answer(

View File

@ -1,16 +1,35 @@
from os import listdir, path, sep from os import listdir, makedirs, path, sep
from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.asyncio import AsyncIOScheduler
from datetime import datetime, timedelta from datetime import datetime, timedelta
from app import app from app import app
from pyrogram.types import BotCommand, BotCommandScopeChat from pyrogram.types import BotCommand, BotCommandScopeChat
from pyrogram.errors import bad_request_400 from pyrogram.errors import bad_request_400
from pyrogram.enums.chat_members_filter import ChatMembersFilter 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 dateutil.relativedelta import relativedelta
from modules.database import col_applications from modules.database import col_applications, col_sponsorships
scheduler = AsyncIOScheduler() 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 # Cache the avatars of group members
if configGet("enabled", "scheduler", "cache_avatars"): if configGet("enabled", "scheduler", "cache_avatars"):
@scheduler.scheduled_job(trigger="date", run_date=datetime.now()+timedelta(seconds=10)) @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"): if configGet("enabled", "scheduler", "sponsorships"):
@scheduler.scheduled_job(trigger="cron", hour=configGet("time", "scheduler", "sponsorships")) @scheduler.scheduled_job(trigger="cron", hour=configGet("time", "scheduler", "sponsorships"))
async def check_sponsors(): 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") logWrite("Sponsorships check performed")

View File

@ -1,4 +1,5 @@
from typing import Any, Union from typing import Any, Union
from requests import get
from pyrogram.enums.chat_type import ChatType from pyrogram.enums.chat_type import ChatType
from pyrogram.types import User from pyrogram.types import User
from pyrogram.client import Client from pyrogram.client import Client
@ -10,6 +11,7 @@ from sys import exit
from os import kill, listdir, sep from os import kill, listdir, sep
from os import name as osname from os import name as osname
from traceback import print_exc from traceback import print_exc
from classes.errors.geo import PlaceNotFoundError
from modules.logging import logWrite from modules.logging import logWrite
@ -171,6 +173,24 @@ def all_locales(key: str, *args: str) -> list:
return output 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: try:
from psutil import Process from psutil import Process
except ModuleNotFoundError: except ModuleNotFoundError:

View File

@ -1,6 +1,28 @@
{ {
"$jsonSchema": { "$jsonSchema": {
"required": [], "required": [
"properties": {} "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"
}
}
} }
} }