111 Commits

Author SHA1 Message Date
234b73add0 Merge pull request 'Bug fixes and small structural changes' (#7) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#7
2023-01-06 17:01:20 +02:00
742b529c33 Moved templates classes into another module 2023-01-06 15:59:09 +01:00
78a37ca186 Placed exceptions to errors module 2023-01-06 15:49:51 +01:00
374effd5d7 Fixed bugs of /message and now using cached media 2023-01-06 13:21:16 +01:00
Profitroll
1195df894c Added documents support for spoilers 2023-01-05 21:15:34 +01:00
Profitroll
67f1ce535f Small fixes 2023-01-05 20:47:30 +01:00
Profitroll
b6d45545fc User search improved 2023-01-05 20:47:02 +01:00
Profitroll
59dafc004d LabelSettingError added 2023-01-05 20:46:47 +01:00
Profitroll
57518399a1 Sponsorship handler removed 2023-01-05 20:46:04 +01:00
Profitroll
ee288a1983 Photo ID used instead of files for sponsorships 2023-01-05 20:45:35 +01:00
373f2332a0 Updated requirements 2023-01-05 15:51:25 +01:00
f4fb85f7a4 Merge pull request 'Removed legacy, fixed some bugs, improved spoilers' (#6) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#6
2023-01-05 16:49:35 +02:00
02a6960391 Fixed forwarding spoilers to admin group 2023-01-05 15:48:34 +01:00
7939f8b65e Minor spoiler category fixes 2023-01-05 14:52:57 +01:00
5b28f9f588 Caption and category added for spoilers 2023-01-05 14:34:02 +01:00
6e44177907 Shutdown time is now added on KeyboardInterrupt 2023-01-05 14:33:23 +01:00
4f0b9f8e3a Fixed dict key for startup message 2023-01-05 13:16:11 +01:00
78dee0591c /cancel now removes reply markup 2023-01-05 13:12:22 +01:00
b0b0f04a9b Added down time messages 2023-01-05 13:08:08 +01:00
9e009aea43 Minor naming improvements 2023-01-05 13:01:57 +01:00
42d00383bf Removed legacy 2023-01-05 13:01:18 +01:00
4fba305b05 Merge pull request 'Small fix for spoiler with an empty description' (#5) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#5
2023-01-05 13:54:19 +02:00
47e62946ab Small fix for spoiler with an empty description 2023-01-05 12:53:55 +01:00
68c7cc0ada Merge pull request 'Spoilers, major command system improvements' (#4) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#4
2023-01-05 13:45:14 +02:00
646db667d9 Updated config in README 2023-01-05 12:44:13 +01:00
2e8277d6d2 Added "owner" as an additional command permission 2023-01-05 12:42:15 +01:00
19fc9308e4 Added /resetcommands command 2023-01-05 12:41:55 +01:00
c90495eb1c Added spoiler's validation rule 2023-01-05 10:49:22 +01:00
Profitroll
bdb2338ab9 Still debugging 2023-01-04 22:31:58 +01:00
Profitroll
496bb7d4a6 Improved logging 2023-01-04 21:58:44 +01:00
Profitroll
b437092fe7 Trying to find commands registration issue 2023-01-04 21:58:20 +01:00
Profitroll
9431763e6b Added filling_sponsorship filter 2023-01-04 21:57:11 +01:00
Profitroll
4977b8f31a Updated to-do list 2023-01-04 21:56:54 +01:00
Profitroll
0739eeb87d Updated config 2023-01-04 21:56:37 +01:00
Profitroll
0214a29a2e Fixed handlers 2023-01-04 21:55:50 +01:00
Profitroll
6b80b7d0fa Improved handlers 2023-01-04 20:13:29 +01:00
Profitroll
3fd56e8b41 Fixed a typo in config keys 2023-01-04 19:59:09 +01:00
Profitroll
3eef04794a Integration of spoilers [WIP] 2023-01-04 19:58:54 +01:00
Profitroll
d59a1671b3 Changed config keys 2023-01-04 19:14:02 +01:00
Profitroll
ccbc135ee4 No need to save bot's ID anymore 2023-01-04 17:26:27 +01:00
083281e784 Fixed some paths 2023-01-04 13:31:42 +01:00
8cb3ef283b Now using find_user() 2023-01-04 13:15:08 +01:00
Profitroll
1268c33830 Small fix 2023-01-03 21:54:01 +01:00
Profitroll
aad5c4f9f2 Added support for "general" module 2023-01-03 21:46:16 +01:00
Profitroll
3d4dd29205 Changed the way how commands work 2023-01-03 21:36:26 +01:00
Profitroll
ff95d9556d Fixed some minor issues 2023-01-03 20:48:35 +01:00
Profitroll
b3c5f060a1 Now features can be turned off 2023-01-03 20:34:44 +01:00
Profitroll
096a0498f8 Now using custom filters 2023-01-03 20:34:13 +01:00
Profitroll
d7936fa600 Added more custom filters 2023-01-03 20:33:53 +01:00
3442a478d4 Updated default config in README 2023-01-03 15:48:33 +01:00
ee7f9712c8 Updated to-do 2023-01-03 15:47:44 +01:00
79304816b0 Merge pull request '/cancel, /identify, sponsorships improvements and fixes' (#3) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#3
2023-01-03 16:45:20 +02:00
ccebccf086 Updated validation rules 2023-01-03 15:43:48 +01:00
b383ab6001 Optimized imports 2023-01-03 15:15:15 +01:00
db60a538b2 Improved docstrings 2023-01-03 15:12:46 +01:00
e79edf1dff Added some more exceptions to handle 2023-01-03 14:30:29 +01:00
7edffd0b40 /identify command added 2023-01-03 14:30:16 +01:00
c8f89a7447 Fixed attribute error 2023-01-03 13:17:59 +01:00
ea1dc542a3 Improved docstrings 2023-01-03 13:16:57 +01:00
6aa8128fc6 Using application_restart() now 2023-01-03 13:04:38 +01:00
7854f88217 Cancel command implemented 2023-01-03 13:04:10 +01:00
b401028dd1 application_state()[0] "none" is also handled now 2023-01-03 13:03:48 +01:00
642c23dd61 Added docstring 2023-01-03 13:02:39 +01:00
b2613c25a4 /label can now be used in admin group 2023-01-03 13:02:27 +01:00
a7038e9d8f Improved tmp files system 2023-01-03 13:01:46 +01:00
Profitroll
a59a7b738c /cancel added to commands to register 2023-01-03 10:13:38 +01:00
626492fb3c Improved logging 2023-01-02 14:24:29 +01:00
1aed7bff7b Removed "have a nice day" where not needed 2023-01-02 14:20:41 +01:00
ba13f36769 Updated locale for sponsorship 2023-01-02 14:19:35 +01:00
a82adc4d1f Changed command output formatting 2023-01-02 11:18:40 +01:00
fe6d2514c7 Updated to-do 2023-01-02 11:17:17 +01:00
2cfa5a8f8d Merge pull request '/nearby, subscriptions check, geocoding' (#2) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#2
2023-01-02 12:16:38 +02:00
950666e4ad Changed "loc" to "location" in application 2023-01-02 11:13:41 +01:00
ace71fd6be Members/admins caching is now configurable 2023-01-02 10:48:55 +01:00
Profitroll
25be843cd8 Improved /nearby 2022-12-30 23:29:17 +01:00
Profitroll
e6589fc3e5 Improved admin group filter 2022-12-30 20:36:06 +01:00
Profitroll
8b2abc2cfa Updated /nearby command 2022-12-29 15:04:00 +01:00
Profitroll
c763fc537b Locale usage fixes 2022-12-28 19:01:21 +01:00
Profitroll
22011829a5 Geo update WIP 2022-12-28 18:56:13 +01:00
Profitroll
e59aa98fd5 Working on /nearby 2022-12-27 18:49:02 +01:00
Profitroll
082acc85cf Added custom filters 2022-12-27 18:46:17 +01:00
Profitroll
87d9afe74a Small fix of message method 2022-12-27 13:40:58 +01:00
Profitroll
5e06859b56 Improved typing and linting 2022-12-27 13:36:54 +01:00
Profitroll
47896faf06 Application and sponsorship crosscheck 2022-12-27 13:23:24 +01:00
Profitroll
85112dc653 Fixed some bugs 2022-12-23 12:54:51 +01:00
Profitroll
af862a3454 Added messages to sponsor's locale 2022-12-23 01:49:38 +01:00
Profitroll
1eb98750a7 Changed sponsorship locale 2022-12-23 01:44:21 +01:00
Profitroll
eecb71a91e Updated label_set 2022-12-23 01:44:07 +01:00
Profitroll
426b1550f6 Initialized /cancel command 2022-12-23 01:40:23 +01:00
Profitroll
0302d8c1ae Removed unused 2022-12-23 01:40:07 +01:00
Profitroll
5a6a96d3f9 Delete sponsorships on expiry 2022-12-22 22:14:35 +01:00
Profitroll
12da1b2376 Added LabelTooLongError() and changed names 2022-12-22 22:11:36 +01:00
Profitroll
7db8c9ac5c Fixed label length on set 2022-12-22 22:11:15 +01:00
95e9fdf460 Added sponsorship support 2022-12-22 15:05:27 +01:00
ac5a0d112f Disabled EN locale 2022-12-22 15:05:07 +01:00
2db19acf6c Disabled EN locale 2022-12-22 15:02:28 +01:00
6874268154 Added one more None check 2022-12-21 15:23:36 +01:00
a906c0a1cc Trying to fix AttributeError() on init 2022-12-21 15:22:14 +01:00
bc54abcc97 Fixed imports 2022-12-21 12:51:39 +01:00
1b6f429be9 Updated to-do 2022-12-21 12:26:50 +01:00
be55f8a3b1 Added DB validation for warnings 2022-12-21 12:26:05 +01:00
d3945eea0c Started working on sponsorships 2022-12-21 12:25:47 +01:00
Profitroll
b345a5d776 Added voice message logging 2022-12-18 17:54:58 +01:00
Profitroll
f905a5c4df Bot is now aggressive to voice messages 2022-12-18 17:51:21 +01:00
Profitroll
f4a2e655a6 Setting commands is now scheduled 2022-12-17 23:36:39 +01:00
Profitroll
741a01cff6 Fixed literal 2022-12-17 23:26:07 +01:00
Profitroll
6b00f181f6 Avatars caching implemented 2022-12-17 23:25:53 +01:00
Profitroll
41eb6e46ee Added one more type check for HoloUser init 2022-12-17 23:14:12 +01:00
Profitroll
1d5ebd02fe Changed max number of results 2022-12-17 23:12:15 +01:00
Profitroll
43ce2d73b6 Fixed application removal 2022-12-17 23:10:56 +01:00
Profitroll
797d9de7e4 Also handling HEAD requests now 2022-12-17 22:31:30 +01:00
53 changed files with 2296 additions and 722 deletions

201
README.md
View File

@@ -32,14 +32,16 @@ You can see config file with all the comments below:
"locale": "uk",
"debug": false,
"owner": 0,
"bot_id": 0,
"age_allowed": 0,
"api": "http://example.com",
"inline_preview_count": 7,
"admin_group": 0,
"destination_group": 0,
"remove_application_time": -1,
"search_radius": 50,
"admins": [],
"groups": {
"admin": 0,
"users": 0
},
"bot": {
"api_id": 0,
"api_hash": "",
@@ -59,6 +61,29 @@ You can see config file with all the comments below:
"size": 512,
"location": "logs"
},
"features": {
"general": {
"enabled": true
},
"applications": {
"enabled": true
},
"sponsorships": {
"enabled": true
},
"warnings": {
"enabled": true
},
"invites_check": {
"enabled": true
},
"dinovoice": {
"enabled": false
},
"spoilers": {
"enabled": true
}
},
"scheduler": {
"birthdays": {
"time": 9,
@@ -67,6 +92,18 @@ You can see config file with all the comments below:
"sponsorships": {
"time": 9,
"enabled": true
},
"cache_avatars": {
"interval": 6,
"enabled": true
},
"cache_members": {
"interval": 30,
"enabled": true
},
"cache_admins": {
"interval": 120,
"enabled": true
}
},
"locations": {
@@ -74,28 +111,134 @@ You can see config file with all the comments below:
"locale": "locale"
},
"commands": {
"rules": "Check out the rules",
"reapply": "Resubmit the application",
"sponsorship": "Apply for sponsor role"
},
"commands_admin": {
"reboot": "Restart the bot",
"message": "Send a message",
"label": "Set user's nickname",
"warnings": "Check user's warnings",
"application": "Check user's application",
"applications": "Retrieve all applications as a JSON"
},
"commands_group_admin": {
"reboot": "Restart the bot",
"message": "Send a message",
"label": "Set user's nickname",
"warnings": "Check user's warnings",
"application": "Check user's application",
"applications": "Retrieve all applications as a JSON"
},
"commands_group_destination": {
"warn": "Warn a user"
"rules": {
"permissions": [
"users",
"admins"
],
"modules": [
"general"
]
},
"spoiler": {
"permissions": [
"users",
"admins"
],
"modules": [
"spoilers"
]
},
"cancel": {
"permissions": [
"users",
"admins"
],
"modules": [
"spoilers",
"applications",
"sponsorships"
]
},
"nearby": {
"permissions": [
"users",
"admins",
"group_users",
"group_admins"
],
"modules": [
"applications"
]
},
"warn": {
"permissions": [
"group_users"
],
"modules": [
"warnings"
]
},
"reapply": {
"permissions": [
"users",
"admins"
],
"modules": [
"applications"
]
},
"sponsorship": {
"permissions": [
"users",
"admins"
],
"modules": [
"sponsorships"
]
},
"reboot": {
"permissions": [
"owner"
],
"modules": [
"general"
]
},
"label": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"message": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"general"
]
},
"identify": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications",
"sponsorships"
]
},
"application": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"applications": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"resetcommands": {
"permissions": [
"owner"
],
"modules": [
"general"
]
}
}
}
```
@@ -104,6 +247,10 @@ After all of that you're good to go! Happy using :)
## To-Do
* [ ] Stats and infographics
* [ ] Check group members without completed application
* [x] Replicate some functions of @spoilerobot
* [x] Check sponsorship on Holo girls
* [x] /nearby command
* [x] Complete messenger between user and admins
* [ ] Check sponsorship on Holo girls
* [ ] Get application by id and user_id
* [x] Get application by id and user_id

View File

@@ -1,6 +1,6 @@
from os import makedirs, path, sep
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse, JSONResponse
from fastapi.responses import FileResponse, JSONResponse, Response
from starlette.status import HTTP_404_NOT_FOUND
from modules.utils import configGet
@@ -9,12 +9,20 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True)
app = FastAPI(title="HoloUA Avatars API", docs_url=None, redoc_url=None, version="1.0")
@app.get("/check", response_class=JSONResponse, include_in_schema=False)
@app.head("/check", response_class=JSONResponse, include_in_schema=False)
async def check():
return JSONResponse({"detail": "I'm alright, thank you"})
@app.get("/", response_class=FileResponse, include_in_schema=False)
async def favicon(avatar_id: str):
async def avatar_get(avatar_id: str):
if path.exists(f'{configGet("cache", "locations")}{sep}avatars{sep}{avatar_id}'):
return FileResponse(f'{configGet("cache", "locations")}{sep}avatars{sep}{avatar_id}', media_type="image/jpg")
else:
raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail="File not found")
@app.head("/", response_class=Response, include_in_schema=False)
async def avatar_head(avatar_id: str):
if path.exists(f'{configGet("cache", "locations")}{sep}avatars{sep}{avatar_id}'):
return Response(headers={"Content-Length": path.getsize(f'{configGet("cache", "locations")}{sep}avatars{sep}{avatar_id}')})
else:
raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail="File not found")

18
app.py
View File

@@ -1,18 +1,32 @@
from os import path, sep
from ujson import JSONDecodeError
from modules.logging import logWrite
from modules.utils import configGet
from modules.utils import configGet, jsonLoad
from pyrogram.client import Client
from pyrogram.errors import bad_request_400
app = Client("holochecker", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot"))
async def isAnAdmin(admin_id):
# Check if user is mentioned in config
if (admin_id == configGet("owner")) or (admin_id in configGet("admins")):
return True
# Check if user is probably in cache
if path.exists(f"cache{sep}admins") is True:
try:
return True if admin_id in jsonLoad(f"cache{sep}admins") else False
except (FileNotFoundError, JSONDecodeError):
pass
# Check if user is in admin group
try:
async for member in app.get_chat_members(configGet("admin_group")):
async for member in app.get_chat_members(configGet("admin", "groups")):
if member.user.id == admin_id:
return True
except bad_request_400.ChannelInvalid:
logWrite(f"Could not get users in admin group to answer isAnAdmin(). Bot is likely not in the group.")
return False
return False

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

@@ -0,0 +1,24 @@
"""Exceptions that are meant to be used by HoloUser class
and other modules that handle those exceptions"""
class UserNotFoundError(Exception):
"""HoloUser could not find user with such an ID in database"""
def __init__(self, user, user_id):
self.user = user
self.user_id = user_id
super().__init__(f"User of type {type(self.user)} with id {self.user_id} was not found")
class UserInvalidError(Exception):
"""Provided to HoloUser object is not supported"""
def __init__(self, user):
self.user = user
super().__init__(f"Could not find HoloUser by using {type(self.user)} as an input type")
class LabelTooLongError(Exception):
def __init__(self, label: str) -> None:
self.label = label
super().__init__(f"Could not set label to '{label}' because it is {len(label)} characters long (16 is maximum)")
class LabelSettingError(Exception):
def __init__(self, exp: Exception, trace: str) -> None:
super().__init__(f"❌ **Could not set label**\n\nException: `{exp}`\n\n**Traceback:**\n```\n{trace}\n```")

View File

@@ -1,52 +1,16 @@
from datetime import datetime
from requests import get
from traceback import print_exc
from traceback import format_exc
from app import app, isAnAdmin
from typing import Any, List, Literal, Union
from pyrogram.types import User, ChatMember, ChatPrivileges, Chat, Message, Photo, Video, Document, Animation, Voice, ForceReply, ReplyKeyboardMarkup
from pyrogram.errors import bad_request_400
from dateutil.relativedelta import relativedelta
from modules.database import col_tmp, col_users, col_context, col_warnings, col_applications, col_sponsorships, col_messages
from classes.errors.geo import PlaceNotFoundError
from classes.errors.holo_user import UserInvalidError, UserNotFoundError, LabelTooLongError, LabelSettingError
from classes.templates import DefaultApplicationTemp, DefaultSponsorshipTemp
from modules.database import col_tmp, col_users, col_applications, col_sponsorships, col_messages, col_spoilers
from modules.logging import logWrite
from modules.utils import configGet, locale, should_quote
class DefaultApplicationTemp(dict):
def __init__(self, user: int):
super().__init__({})
self.dict = {
"user": user,
"type": "application",
"complete": False,
"sent": False,
"state": "fill",
"reapply": False,
"stage": 1,
"application": {
"1": None,
"2": None,
"3": None,
"4": None,
"5": None,
"6": None,
"7": None,
"8": None,
"9": None,
"10": None
}
}
class UserNotFoundError(Exception):
"""HoloUser could not find user with such an ID in database"""
def __init__(self, user, user_id):
self.user = user
self.user_id = user_id
super().__init__(f"User of type {type(self.user)} with id {self.user_id} was not found")
class UserInvalidError(Exception):
"""Provided to HoloUser object is not supported"""
def __init__(self, user):
self.user = user
super().__init__(f"Could not find HoloUser by using {type(self.user)} as an input type")
from modules.utils import configGet, find_location, locale, should_quote
class HoloUser():
"""This object represents a user of HoloChecker bot.
@@ -100,19 +64,21 @@ class HoloUser():
self.locale = holo_user["tg_locale"]
self.username = holo_user["tg_username"]
if isinstance(user, User) and ((self.name != user.first_name) and (user.first_name is None)):
self.set("tg_name", user.first_name)
if isinstance(user, User):
if isinstance(user, User) and (self.phone != user.phone_number):
self.set("tg_phone", user.phone_number)
if (self.name != user.first_name) and hasattr(user, "first_name") and (user.first_name is not None):
self.set("name", user.first_name, db_key="tg_name")
if isinstance(user, User) and ((self.locale != user.language_code) and (user.language_code is not None)):
self.set("tg_locale", user.language_code)
if (self.phone != user.phone_number) and hasattr(user, "phone") and (user.phone_number is not None):
self.set("phone", user.phone_number, db_key="tg_phone")
if isinstance(user, User) and (self.username != user.username):
self.set("tg_username", user.username)
if (self.locale != user.language_code) and hasattr(user, "locale") and (user.language_code is not None):
self.set("locale", user.language_code, db_key="tg_locale")
if (self.username != user.username) and hasattr(user, "username") and (user.username is not None):
self.set("username", user.username, db_key="tg_username")
def set(self, key: str, value: Any) -> None:
def set(self, key: str, value: Any, db_key: Union[str, None] = None) -> None:
"""Set attribute data and save it into database
### Args:
@@ -122,7 +88,8 @@ class HoloUser():
if not hasattr(self, key):
raise AttributeError()
setattr(self, key, value)
col_users.update_one(filter={"_id": self.db_id}, update={ "$set": { key: value } }, upsert=True)
db_key = key if db_key is None else db_key
col_users.update_one(filter={"_id": self.db_id}, update={ "$set": { db_key: value } }, upsert=True)
logWrite(f"Set attribute {key} of user {self.id} to {value}")
async def message(self,
@@ -189,11 +156,11 @@ class HoloUser():
if photo is not None:
if isinstance(photo, Photo):
photo = photo.file_id
new_message = await origin.reply_photo(photo, caption=caption, quote=True)
new_message = await origin.reply_cached_media(photo, caption=caption, quote=True)
elif video is not None:
if isinstance(video, Video):
video = video.file_id
new_message = await origin.reply_video(video, caption=caption, quote=True)
new_message = await origin.reply_cached_media(video, caption=caption, quote=True)
elif file is not None:
if isinstance(file, Document):
file = file.file_id
@@ -201,7 +168,7 @@ class HoloUser():
elif animation is not None:
if isinstance(animation, Animation):
animation = animation.file_id
new_message = await origin.reply_animation(animation, caption=caption, quote=True)
new_message = await origin.reply_cached_media(animation, caption=caption, quote=True)
elif voice is not None:
if isinstance(voice, Voice):
voice = voice.file_id
@@ -214,11 +181,11 @@ class HoloUser():
if photo is not None:
if isinstance(photo, Photo):
photo = photo.file_id
new_message = await app.send_photo(self.id, photo, caption=caption)
new_message = await app.send_cached_media(self.id, photo, caption=caption)
elif video is not None:
if isinstance(video, Video):
video = video.file_id
new_message = await app.send_video(self.id, video, caption=caption)
new_message = await app.send_cached_media(self.id, video, caption=caption)
elif file is not None:
if isinstance(file, Document):
file = file.file_id
@@ -226,11 +193,11 @@ class HoloUser():
elif animation is not None:
if isinstance(animation, Animation):
animation = animation.file_id
new_message = await app.send_animation(animation, caption=caption, quote=True)
new_message = await app.send_cached_media(self.id, animation, caption=caption)
elif voice is not None:
if isinstance(voice, Voice):
voice = voice.file_id
new_message = await app.send_voice(voice, caption=caption, quote=True)
new_message = await app.send_cached_media(self.id, voice, caption=caption)
else:
new_message = await app.send_message(self.id, text)
@@ -240,27 +207,33 @@ class HoloUser():
# Report to admin and to sender about message sending failure
except Exception as exp:
logWrite(f"Exception {exp} happened as {context.from_user.id} tried to send message to {self.id}. Traceback:\n{print_exc()}")
logWrite(f"Exception {exp} happened as {context.from_user.id} tried to send message to {self.id}. Traceback:\n{format_exc()}")
try:
await app.send_message(configGet("owner"), locale("message_traceback", "message").format(context.from_user.id, self.id, exp, print_exc()))
await app.send_message(configGet("owner"), locale("message_traceback", "message").format(context.from_user.id, self.id, exp, format_exc()))
except bad_request_400.PeerIdInvalid:
logWrite(f"Could not notify admin about failure when sending message! Admin has never interacted with bot!")
await context.reply_text(locale("message_error", "message"), quote=should_quote(context))
async def set_label(self, chat: Chat, label: str) -> None:
async def label_set(self, chat: Chat, label: str) -> None:
"""Set label in destination group
### Args:
* chat (`Chat`): Telegram chat
* label (`str`): Label you want to set
"""
if len(label) > 16:
raise LabelTooLongError(label)
self.label = label
self.set("label", label)
await app.promote_chat_member(configGet("destination_group"), self.id)
if not await isAnAdmin(self.id):
await app.set_administrator_title(configGet("destination_group"), self.id, label)
try:
await app.promote_chat_member(configGet("users", "groups"), self.id, privileges=ChatPrivileges(can_pin_messages=True, can_manage_video_chats=True))
if not await isAnAdmin(self.id):
await app.set_administrator_title(configGet("users", "groups"), self.id, label)
self.set("label", label)
except Exception as exp:
logWrite(f"Could not set {self.id}'s title to '{self.label}' due to {exp}")
raise LabelSettingError(exp, format_exc())
async def reset_label(self, chat: Chat) -> None:
async def label_reset(self, chat: Chat) -> None:
"""Reset label in destination group
### Args:
@@ -268,10 +241,12 @@ class HoloUser():
"""
self.label = ""
self.set("label", "")
await app.set_administrator_title(configGet("destination_group"), self.id, "")
await app.set_administrator_title(configGet("users", "groups"), self.id, "")
if not await isAnAdmin(self.id):
await app.promote_chat_member(configGet("destination_group"), self.id, privileges=ChatPrivileges(
can_manage_chat=False
await app.promote_chat_member(configGet("users", "groups"), self.id, privileges=ChatPrivileges(
can_manage_chat=False,
can_pin_messages=False,
can_manage_video_chats=False
))
def application_state(self) -> tuple[Literal["none", "fill", "approved", "rejected"], bool]:
@@ -294,14 +269,14 @@ class HoloUser():
"""
return True if col_applications.find_one({"user": self.id}) is not None else False
def application_restart(self) -> None:
def application_restart(self, reapply: bool = False) -> None:
"""Reset application of a user in tmp collection and replace it with an empty one
"""
if col_tmp.find_one({"user": self.id, "type": "application"}) is None:
col_tmp.insert_one(document=DefaultApplicationTemp(self.id).dict)
else:
col_tmp.delete_one({"user": self.id, "type": "application"})
col_tmp.insert_one(document=DefaultApplicationTemp(self.id).dict)
col_tmp.insert_one(document=DefaultApplicationTemp(self.id, reapply=reapply).dict)
async def application_next(self, query: str, msg: Message) -> None:
"""Move on filling application of user
@@ -311,16 +286,30 @@ class HoloUser():
* msg (`Message`): Message that should receive replies
"""
if col_tmp.find_one({"user": self.id, "type": "application"}) is None:
# if col_tmp.find_one({"user": self.id, "type": "application"}) is None:
col_tmp.insert_one(
document=DefaultApplicationTemp(self.id).dict
)
if self.sponsorship_state()[0] == "fill":
return
if self.spoiler_state() is True:
return
# col_tmp.insert_one(
# document=DefaultApplicationTemp(self.id).dict
# )
progress = col_tmp.find_one({"user": self.id, "type": "application"})
stage = progress["stage"]
if progress["state"] == "fill":
if progress is None:
return
stage = progress["stage"]
# if self.sponsorship_state()[0] == "fill":
# await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg))
# return
if progress["state"] == "fill" and progress["sent"] is False:
if stage == 2:
@@ -342,25 +331,27 @@ class HoloUser():
return
else:
print(f'Look: {((datetime.now() - input_dt).days)} > {(datetime.now() - datetime.now().replace(year=datetime.now().year - configGet("age_allowed"))).days}')
progress["application"][str(stage)] = input_dt
col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "application"}}, {"$set": {"application": progress["application"], "stage": progress["stage"]+1}})
await msg.reply_text(locale(f"question{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage+1}", "force_reply", locale=self.locale))))
elif stage == 3:
try:
result = (get(f"http://api.geonames.org/searchJSON?q={query}&maxRows=1&countryBias=UA&lang=uk&orderby=relevance&featureClass=P&featureClass=A&username={configGet('username', 'geocoding')}")).json()
progress["application"][str(stage)] = result["geonames"][0]
progress["application"][str(stage)] = find_location(query)
if ("lat" in progress["application"][str(stage)] and "lng" in progress["application"][str(stage)]):
progress["application"][str(stage)]["location"] = [float(progress["application"][str(stage)]["lng"]), float(progress["application"][str(stage)]["lat"])]
del progress["application"][str(stage)]["lat"]
del progress["application"][str(stage)]["lng"]
col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "application"}}, {"$set": {"application": progress["application"], "stage": progress["stage"]+1}})
await msg.reply_text(locale(f"question3_found", "message", locale=self.locale).format(result["geonames"][0]["name"], result["geonames"][0]["adminName1"]))
await msg.reply_text(locale("question3_found", "message", locale=self.locale).format(progress["application"][str(stage)]["name"], progress["application"][str(stage)]["adminName1"]))
await msg.reply_text(locale(f"question{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage+1}", "force_reply", locale=self.locale))))
except (ValueError, KeyError, IndexError):
await msg.reply_text(locale(f"question3_invalid", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply", locale=self.locale))))
except PlaceNotFoundError:
await msg.reply_text(locale("question3_invalid", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply", locale=self.locale))))
return
except Exception as exp:
await msg.reply_text(locale("question3_error", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply", locale=self.locale))))
try:
await app.send_message(configGet("owner"), locale("question3_traceback", "message", locale=self.locale).format(query, exp, print_exc()))
await app.send_message(configGet("owner"), locale("question3_traceback", "message", locale=self.locale).format(query, exp, format_exc()))
except bad_request_400.PeerIdInvalid:
logWrite(f"Could not notify admin about failure when sending message! Admin has never interacted with bot!")
return
@@ -389,4 +380,115 @@ class HoloUser():
col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "application"}}, {"$set": {"application": progress["application"], "stage": progress["stage"]+1}})
await msg.reply_text(locale(f"question{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage+1}", "force_reply", locale=self.locale))))
logWrite(f"User {self.id} completed stage {stage} of application")
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
"""
progress = col_tmp.find_one({"user": self.id, "type": "sponsorship"})
if progress is not None:
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:
progress["sponsorship"]["proof"] = photo.file_id
col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "stage": progress["stage"]+1}})
await msg.reply_text(locale(f"sponsor{stage+1}", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"sponsor{stage+1}", "force_reply", locale=self.locale))))
elif stage == 4:
if len(query) > 16:
await msg.reply_text(locale("label_too_long", "message"), reply_markup=ForceReply(placeholder=str(locale("sponsor4", "force_reply", locale=self.locale))))
return
progress["sponsorship"]["label"] = query
col_tmp.update_one({"user": {"$eq": self.id}, "type": {"$eq": "sponsorship"}}, {"$set": {"sponsorship": progress["sponsorship"], "complete": True}})
await msg.reply_cached_media(
progress["sponsorship"]["proof"],
caption=locale("sponsor_confirm", "message", locale=self.locale).format(
progress["sponsorship"]["streamer"],
progress["sponsorship"]["expires"].strftime("%d.%m.%Y"),
progress["sponsorship"]["label"]
),
reply_markup=ReplyKeyboardMarkup(locale("confirm", "keyboard", locale=self.locale), resize_keyboard=True))
else:
return
logWrite(f"User {self.id} completed stage {stage} of sponsorship")
else:
return
def spoiler_state(self) -> bool:
"""Check if user has any started but not finished spoilers
### Returns:
* `bool`: `True` if any not finished spoilers available and `False` if none.
"""
return False if col_spoilers.find_one({"user": self.id, "completed": False}) is None else True

46
classes/templates.py Normal file
View File

@@ -0,0 +1,46 @@
"""Templates for temporary application/sponsorship records"""
from datetime import datetime
class DefaultApplicationTemp(dict):
def __init__(self, user: int, reapply: bool = False):
super().__init__({})
self.dict = {
"user": user,
"type": "application",
"complete": False,
"sent": False,
"state": "fill",
"reapply": reapply,
"stage": 1,
"application": {
"1": None,
"2": None,
"3": None,
"4": None,
"5": None,
"6": None,
"7": None,
"8": None,
"9": None,
"10": None
}
}
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": ""
}
}

View File

@@ -2,14 +2,16 @@
"locale": "uk",
"debug": false,
"owner": 0,
"bot_id": 0,
"age_allowed": 0,
"api": "http://example.com",
"inline_preview_count": 7,
"admin_group": 0,
"destination_group": 0,
"remove_application_time": -1,
"search_radius": 50,
"admins": [],
"groups": {
"admin": 0,
"users": 0
},
"bot": {
"api_id": 0,
"api_hash": "",
@@ -29,6 +31,29 @@
"size": 512,
"location": "logs"
},
"features": {
"general": {
"enabled": true
},
"applications": {
"enabled": true
},
"sponsorships": {
"enabled": true
},
"warnings": {
"enabled": true
},
"invites_check": {
"enabled": true
},
"dinovoice": {
"enabled": false
},
"spoilers": {
"enabled": true
}
},
"scheduler": {
"birthdays": {
"time": 9,
@@ -37,10 +62,152 @@
"sponsorships": {
"time": 9,
"enabled": true
},
"cache_avatars": {
"interval": 6,
"enabled": true
},
"cache_members": {
"interval": 30,
"enabled": true
},
"cache_admins": {
"interval": 120,
"enabled": true
}
},
"locations": {
"cache": "cache",
"locale": "locale"
},
"commands": {
"rules": {
"permissions": [
"users",
"admins"
],
"modules": [
"general"
]
},
"spoiler": {
"permissions": [
"users",
"admins"
],
"modules": [
"spoilers"
]
},
"cancel": {
"permissions": [
"users",
"admins"
],
"modules": [
"spoilers",
"applications",
"sponsorships"
]
},
"nearby": {
"permissions": [
"users",
"admins",
"group_users",
"group_admins"
],
"modules": [
"applications"
]
},
"warn": {
"permissions": [
"group_users"
],
"modules": [
"warnings"
]
},
"reapply": {
"permissions": [
"users",
"admins"
],
"modules": [
"applications"
]
},
"sponsorship": {
"permissions": [
"users",
"admins"
],
"modules": [
"sponsorships"
]
},
"reboot": {
"permissions": [
"owner"
],
"modules": [
"general"
]
},
"label": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"message": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"general"
]
},
"identify": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications",
"sponsorships"
]
},
"application": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"applications": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"resetcommands": {
"permissions": [
"owner"
],
"modules": [
"general"
]
}
}
}

View File

@@ -1,9 +1,9 @@
from os import getpid, makedirs
from time import time
from modules.utils import *
from modules.inline import *
from app import app
from modules.commands_register import commands_register
from pyrogram import idle
pid = getpid()
@@ -13,12 +13,16 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True)
# Importing
from modules.commands.application import *
from modules.commands.applications import *
from modules.commands.cancel import *
from modules.commands.identify import *
from modules.commands.label import *
from modules.commands.message import *
from modules.commands.nearby import *
from modules.commands.reapply import *
from modules.commands.reboot import *
from modules.commands.resetcommands import *
from modules.commands.rules import *
from modules.commands.spoiler import *
from modules.commands.sponsorship import *
from modules.commands.start import *
from modules.commands.warn import *
@@ -27,12 +31,15 @@ from modules.commands.warnings import *
from modules.callbacks.nothing import *
from modules.callbacks.reapply import *
from modules.callbacks.rules import *
from modules.callbacks.sid import *
from modules.callbacks.sponsorship import *
from modules.callbacks.sub import *
from modules.callbacks.sus import *
from modules.handlers.confirmation import *
from modules.handlers.contact import *
from modules.handlers.group_join import *
from modules.handlers.voice import *
from modules.handlers.welcome import *
from modules.handlers.everything import *
@@ -46,42 +53,32 @@ if __name__ == "__main__":
# I did compare performance, almost no difference and it's much more useful this way. Change my mind.
app.start()
# if configGet("birthdays_notify"):
# every().day.at(configGet("birthdays_time")).do(check_birthdays, app)
# # Background tasks checker
# def background_task():
# try:
# while True:
# try:
# run_pending()
# #print('Checked')
# time.sleep(1)
# except:
# pass
# except KeyboardInterrupt:
# print('\nShutting down')
# killProc(pid)
# t = Thread(target=background_task)
# t.start()
try:
app.send_message(configGet("owner"), f"Starting up with pid `{pid}`")
if path.exists(path.join(configGet("cache", "locations"), "shutdown_time")):
downtime = relativedelta(datetime.now(), datetime.fromtimestamp(jsonLoad(path.join(configGet("cache", "locations"), "shutdown_time"))["timestamp"]))
if downtime.days >= 1:
app.send_message(configGet("owner"), locale("startup_downtime_days", "message").format(pid, downtime.days))
elif downtime.hours >= 1:
app.send_message(configGet("owner"), locale("startup_downtime_hours", "message").format(pid, downtime.hours))
else:
app.send_message(configGet("owner"), locale("startup_downtime_minutes", "message").format(pid, downtime.minutes))
else:
app.send_message(configGet("owner"), locale("startup", "message").format(pid))
except bad_request_400.PeerIdInvalid:
logWrite(f"Could not send startup message to bot owner. Perhaps user has not started the bot yet.")
commands_register(app)
scheduler.start()
idle()
try:
app.send_message(configGet("owner"), f"Shutting with pid `{pid}`")
app.send_message(configGet("owner"), locale("shutdown", "message").format(pid))
except bad_request_400.PeerIdInvalid:
logWrite(f"Could not send shutdown message to bot owner. Perhaps user has not started the bot yet.")
app.stop()
makedirs(configGet("cache", "locations"), exist_ok=True)
jsonSave({"timestamp": time()}, path.join(configGet("cache", "locations"), "shutdown_time"))
killProc(pid)

View File

@@ -22,8 +22,8 @@
"question3_traceback": "⚠️ **Error occurred**\nError retrieving geocoding for `{0}`\nError: `{1}`\n\nTraceback:\n```\n{2}\n```",
"confirm": "Great, thanks!\n\nPlease check the data is correct:\n{0}\n\nEverything correct?",
"application_sent": "Thank you! We have sent your application for verification. You will receive a message as soon as it is checked and a decision is made. Until then, nothing more is required from you. Have a nice day :)",
"application_got": "Received an application from `{0}`\n\nName in tg: `{1}`, `{2}`\nUsername: @{3}\n\n**Application data:**\n{4}",
"reapply_got": "Received profile change from `{0}`\n\nUsername: `{1}`, `{2}`\nUsername: @{3}\n\n**Application data:**\n{4}",
"application_got": "Received an application from `{0}`\n\nName in tg: `{1}`\nUsername: @{2}\n\n**Application data:**\n{3}",
"reapply_got": "Received application change from `{0}`\n\nUsername: `{1}`\nUsername: @{2}\n\n**Application data:**\n{3}",
"shutdown": "Shutting down the bot with PID `{0}`",
"startup": "Starting the bot with PID `{0}`",
"startup_downtime": "Starting bot with PID `{0}` (was down for {1})",

View File

@@ -20,13 +20,27 @@
"question3_found": "Використовую наступний результат:\n• {0} ({1})",
"question3_error": "⚠️ **Сталась помилка**\nНе вдалось отримати географічну мітку. Розробника повідомлено про цю помилку. Будь ласка, спробуйте ще раз.",
"question3_traceback": "⚠️ **Сталась помилка**\nПомилка отримання геокодингу для `{0}`\nПомилка: `{1}`\n\nTraceback:\n```\n{2}\n```",
"sponsorship_apply": " Оформіть платну підписку на когось з Холо, заповніть форму та отримайте особливу роль в якості винагороди!",
"sponsorship_applying": " Розпочато заповнення форми на отримання бонусів за платну підписку на холодівчат.",
"sponsor1": "На яку саме дівчину платна підписка?",
"sponsor2": "До якої дати (`ДД.ММ.РРРР`) підписка?",
"sponsor2_invalid": "Будь ласка, введи дату формату `ДД.ММ.РРРР`",
"sponsor2_past": "Вказана дата знаходиться в минулому. Будь ласка, вкажіть правильний термін дії підписки",
"sponsor3": "Будь ласка, надішли одне фото для підтвердження дійсності підписки\n\n **Підказка**\nПрочитай як правильно скрінити легітимне підтвердження підписки: https://telegra.ph/Pіdpiska-na-holo-dіvchinu-01-02",
"sponsor4": "Яку роль ти бажаєш отримати?\n\n **Підказка**\nНазва ролі повинна бути якось пов'язана зі вказаною дівчиною, не повинна порушувати правила спільноти а також має бути не довше за 16 символів (обмеження Telegram).",
"sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.",
"confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?",
"application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується. Гарного дня! :)",
"application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`, `{2}`\nЮзернейм: @{3}\n\n**Дані анкети:**\n{4}",
"reapply_got": "Отримано змінити анкети від `{0}`\n\nІм'я тг: `{1}`, `{2}`\nЮзернейм: @{3}\n\n**Дані анкети:**\n{4}",
"sponsor_confirm": "**Дані форми:**\nСтрімер: {0}\nПідписка до: {1}\nХочу роль: {2}\n\nПеревір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.",
"application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується :)",
"sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення :)",
"application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}",
"reapply_got": "Отримано оновлення анкети від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}",
"sponsor_got": "Отримано форму на спонсорство від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані форми:**\n{3}",
"shutdown": "Вимкнення бота з підом `{0}`",
"startup": "Запуск бота з підом `{0}`",
"startup_downtime": "Запуск бота з підом `{0}` (лежав {1})",
"startup_downtime_minutes": "Запуск бота з підом `{0}` (лежав {1} хв.)",
"startup_downtime_hours": "Запуск бота з підом `{0}` (лежав {1} год.)",
"startup_downtime_days": "Запуск бота з підом `{0}` (лежав {1} дн.)",
"approved": "Вітаємо! Твою анкету переглянули та підтвердили твоє право на вступ. Скористайся кнопкою під повідомленням щоб вступити до нашої лампової спільноти!",
"approved_joined": "Вітаємо! Твою анкету переглянули та підтвердили її правильність. Дякуємо за витрачений на заповнення час та гарного дня!",
"read_rules": "Будь ласка, прочитай ці правила перш ніж натискати на кнопку та приєднуватись до чату.",
@@ -37,6 +51,10 @@
"rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.",
"rejected_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.",
"rejected_by_rus": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: русня.",
"sponsor_approved": "Вітаємо! Твою форму переглянули та підтвердили її правильність. Коли термін дії наданої підписки буде добігати кінця - ми нагадаємо, що треба оновити дані аби й надалі отримувати плюшки в чаті. Також можна повторно заповнити форму, якщо хочеться змінити бажане ім'я ролі або подовжити термін дії підписки завчасно, за допомогою команди /sponsorship. Гарного дня!",
"sponsor_rejected": "Ой лишенько! Твою форму переглянули, однак не підтвердили її. Можливо, щось не так з датами, або ж бажана роль не може бути надана.\n\nТи можеш спробувати повторно заповнити форму командою /sponsorship",
"sponsor_approved_by": "✅ **Підписку схвалено**\nАдмін **{0}** переглянув та схвалив форму `{1}`.",
"sponsor_rejected_by": "❌ **Підписку відхилено**\nАдмін **{0}** переглянув та відхилив форму `{1}`.",
"contact": "Анкета `{0}`\n\n**Дані анкети:**\n{1}\n\n{2}",
"application_status_accepted": "Прийнята `{0}` від {1}",
"application_status_rejected": "Відхилена `{0}` від {1}",
@@ -71,6 +89,38 @@
"no_user_application": "Не знайдено користувачів за запитом **{0}**",
"user_invalid": "Надісланий користувач не має завершеної анкети.",
"joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням",
"sponsorships_expires": "⚠️ **Нагадування**\nНадана платна підписка припинить діяти **за {0} д**. Будь ласка, оновіть дані про неї командою /sponsorship інакше роль буде втрачено!",
"sponsorships_expired": "⚠️ **Нагадування**\nТермін дії вказаної підписки сплив. Для повторного отримання ролі користуйся командою /sponsorship.",
"label_too_long": "Довжина назви ролі не повинна перевищувати 16 символів",
"finish_sponsorship": "❌ **Дія неможлива**\nПерш ніж заповнювати анкету, треба завершити заповнення форми спонсора або перервати його командою /cancel.",
"finish_application": "❌ **Дія неможлива**\nПерш ніж заповнювати форму спонсора, треба завершити заповнення анкети.",
"nearby_invalid": " **Місце не знайдено**\nЗа наданим запитом не знайдено місце з координатами. Спробуйте ще раз формулючи запит в стилі \"Чернівці\" або \"Київська область\".",
"nearby_error": "⚠️ **Сталась помилка**\n\nПомилка: `{0}`\n\nTraceback:\n```\n{1}\n```",
"nearby_result": "Результати пошуку:\n\n{0}",
"nearby_empty": "Здається, нікого поблизу немає.",
"cancel": "Всі поточні операції скасовано.",
"identify_invalid_syntax": "Неправильний синтаксис!\nТреба: `/identify ID/NAME/USERNAME`",
"identify_not_found": "Не знайдено користувачів за запитом **{0}**",
"identify_success": "Користувач `{0}`\n\nІм'я: {1}\nЮзернейм: {2}\nЄ в чаті: {3}\nЄ адміном: {4}\nРоль: {5}\nНаявна анкета: {6}\nНаявне спонсорство: {7}",
"spoiler_started": "Розпочато створення спойлера. Будь ласка, оберіть категорію спойлера за допомогою клавіатури бота.",
"spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення",
"spoiler_cancel": "Створення спойлера було припинено",
"spoiler_empty": "Спойлер категорії \"{0}\" без опису",
"spoiler_described": "Спойлер категорії \"{0}\": {1}",
"spoiler_description_enter": "Добре, введіть бажаний опис спойлера",
"spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)",
"spoiler_send_description": "Тепер треба надіслати коротенький опис спойлера, щоб люди розуміли що під ним варто очкувати. Надішли мінус (-) щоб пропустити цей крок.",
"spoiler_ready": "Успіх! Спойлер створено. Користуйтесь кнопкою нижче щоб надіслати його.",
"spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).",
"spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.",
"spoiler_in_progress": "❌ **Дія неможлива**\nПерш ніж починати нову дію, треба завершити створення спойлера або перервати його командою /cancel.",
"yes": "Так",
"no": "Ні",
"voice_message": [
"why are u gae",
"руки відірвало? пиши як людина",
"унга-бунга, не маєш клавіатури?"
],
"question_titles": {
"question1": "Ім'я/звертання:",
"question2": "День народження:",
@@ -82,6 +132,16 @@
"question8": "Дивлюсь стріми:",
"question9": "Подобаються пісні:",
"question10": "Про себе:"
},
"sponsor_titles": {
"question_streamer": "Стрімер:",
"question_expires": "Підписка до:",
"question_label": "Хоче роль:"
},
"spoiler_categories": {
"nsfw": "NSFW контент",
"deanon": "Деанон холо-учасників",
"other": "Інше"
}
},
"keyboard": {
@@ -105,6 +165,17 @@
[
"Ні, повторно заповнити"
]
],
"spoiler_categories": [
[
"NSFW контент"
],
[
"Деанон холо-учасників"
],
[
"Інше"
]
]
},
"force_reply": {
@@ -117,13 +188,21 @@
"question7": "П'ять японських холодівчат",
"question8": "Так або ні",
"question9": "Ім'я дівчини або дівчин",
"question10": "Трошки про себе"
"question10": "Трошки про себе",
"sponsor1": "Ім'я дівчини",
"sponsor2": "Дата до якої підписка",
"sponsor3": "Фото-підтвердження",
"sponsor4": "Бажана роль",
"spoiler_content": "Вміст спойлера",
"spoiler_description": "Опис спойлера"
},
"button": {
"sub_yes": "✅ Прийняти",
"sub_no": "❌ Відхилити",
"sub_aggressive": "🤡 Відхилити (Токс)",
"sub_russian": "🇷🇺 Відхилити (Русак)",
"sponsor_yes": "✅ Прийняти",
"sponsor_no": "❌ Відхилити",
"accepted": "✅ Прийнято",
"declined": "❌ Відхилено",
"join": "Приєднатись",
@@ -140,7 +219,11 @@
"rules_next": "Далі ➡️",
"rules_prev": "⬅️ Назад",
"applying_stop": "🛑 Перервати заповнення",
"done": "✅ Готово"
"done": "✅ Готово",
"sponsor_apply": "Заповнити форму",
"sponsor_started": "Форму розпочато",
"spoiler_send": "Надіслати",
"spoiler_view": "Переглянути"
},
"callback": {
"sub_accepted": "✅ Анкету {0} схвалено",
@@ -153,7 +236,10 @@
"rules_page": " Показано правило {0}",
"rules_home": " Показано головну правил",
"rules_additional": " Показано додаткові правила",
"reapply_stopped": " Перервано заповнення анкети"
"reapply_stopped": " Перервано заповнення анкети",
"sponsor_started": " Заповнення форми розпочато",
"sponsor_accepted": "✅ Форму {0} схвалено",
"sponsor_rejected": "❌ Форму {0} відхилено"
},
"inline": {
"forbidden": {
@@ -170,6 +256,10 @@
"title": "",
"description": "Переглянути анкету {0} (@{1})",
"message_content": "{0} (@{1})\n\n**Дані анкети:**\n{2}"
},
"spoiler": {
"title": "Відправити",
"description": "Надіслати цей спойлер до чату"
}
},
"rules_msg": "📢Правила можуть доповнюватись та змінюватись, залежно від потреби. У такому разі, порушення, які були вчинені до введення (змінення) правила, порушеннями вважатися не будуть. Про всі зміни в правилах, ви будете проінформовані за допомогою закріплених повідомлень. Але вони не будуть закріплені на постійній основі, тому, час від часу, перевіряйте актуальність правил у боті.\n\n🔔Якщо ви бачите, як хтось із учасників порушив правила, тегніть одного із адмінів, у відповідь на повідомлення, яке, на вашу думку, є порушенням. У дописі до тегу, вкажіть, по якому пункту ви побачили порушення. Або перешліть повідомлення до будь кого із адміністраторів у особисті повідомлення, та коротко опишіть ситуацію.\nСписок адміністраторів: @Chirkopol @Za_NerZula @Denialvapr\nЗ питань функціонування бота звертайтесь до @Profitroll2281337\n\n❗Будь-який заборонений контент, може бути відправлений до чату за допомогою бота - https://t.me/spoilerobot з повним описом контенту, що міститься під спойлером. За неправильний або некоректний опис, може бути видане попередження.\n\n‼Видалені або змінені повідомлення, все ще є повідомленнями від вашого імені, які могли побачити учасники чату, і які можуть бути відстежені через адмінську панель.\n\n🔨 За порушення - ви отримаєте попередження. За наявності 3-х попереджень - мут на добу. За повторні порушення, ви одразу отримаєте покарання, без додаткових попереджень.",
@@ -186,30 +276,20 @@
],
"rules_additional": "Додаткові правила, які несуть рекомендаційний характер, та не мають явних покарань за порушення:\n1⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2⃣) У чаті немає заборони на російський контент. Але, майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4⃣) Поважайте авторські права контентмейкерів. Якщо ви знаходите арт, анімацію, музику тощо, на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом `/search` на повідомлення із артом.",
"commands": {
"rules": "Правила спільноти",
"application": "Переглянути анкету користувача",
"applications": "Отримати всі анкети як JSON",
"cancel": "Відмінити актуальну дію",
"identify": "Дізнатись дані про користувача за айді",
"label": "Встановити нікнейм користувачу",
"message": "Надіслати користувачу повідомлення",
"nearby": "Показати користувачів поблизу",
"reapply": "Повторно заповнити анкету",
"sponsorship": "Отримати роль за спонсорство"
},
"commands_admin": {
"reboot": "Перезапустити бота",
"label": "Встановити нікнейм користувачу",
"message": "Надіслати користувачу повідомлення",
"warnings": ереглянути попередження користувача",
"application": "Переглянути анкету користувача",
"applications": "Отримати всі анкети як JSON"
},
"commands_group_admin": {
"reboot": "Перезапустити бота",
"label": "Встановити нікнейм користувачу",
"nearby": "Показати користувачів поблизу",
"message": "Надіслати користувачу повідомлення",
"warnings": "Переглянути попередження користувача",
"application": "Переглянути анкету користувача",
"applications": "Отримати всі анкети як JSON"
},
"commands_group_destination": {
"resetcommands": "Відреєструвати всі команди",
"rules": "Правила спільноти",
"spoiler": очати створювати спойлер",
"sponsorship": "Отримати роль за спонсорство",
"warn": "Попередити користувача",
"nearby": оказати користувачів поблизу"
"warnings": ереглянути попередження користувача"
}
}

View File

@@ -1,9 +1,11 @@
from app import app
from pyrogram import filters
from pyrogram.types import CallbackQuery
from pyrogram.client import Client
from modules.utils import locale
# Callback empty ===============================================================================================================
@app.on_callback_query(filters.regex("nothing"))
async def callback_query_nothing(app, clb):
async def callback_query_nothing(app: Client, clb: CallbackQuery):
await clb.answer(text=locale("nothing", "callback", locale=clb.from_user))
# ==============================================================================================================================

View File

@@ -1,6 +1,7 @@
from datetime import datetime
from app import app
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardRemove
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardRemove, CallbackQuery
from pyrogram.client import Client
from pyrogram import filters
from classes.holo_user import HoloUser
from modules.utils import configGet, locale, logWrite
@@ -10,19 +11,19 @@ from modules.database import col_tmp, col_applications
# Callbacks reapply ============================================================================================================
@app.on_callback_query(filters.regex("reapply_yes_[\s\S]*"))
async def callback_reapply_query_accept(app, clb):
async def callback_reapply_query_accept(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin_group"), locale("approved_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(configGet("admin", "groups"), locale("approved_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
logWrite(f"User {holo_user.id} got their reapplication approved by {clb.from_user.id}")
await app.send_message(holo_user.id, locale("approved_joined", "message", locale=holo_user))
col_applications.delete_one({"user": {"$eq": 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_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")]]
@@ -31,12 +32,12 @@ async def callback_reapply_query_accept(app, clb):
need_link = True
async for member in app.get_chat_members(configGet("destination_group")):
async for member in app.get_chat_members(configGet("users", "groups")):
if member.user.id == holo_user.id:
need_link = False
if need_link:
link = await app.create_chat_invite_link(configGet("destination_group"), name=f"Invite for {holo_user.id}", member_limit=1) #, expire_date=datetime.now()+timedelta(days=1))
link = await app.create_chat_invite_link(configGet("users", "groups"), name=f"Invite for {holo_user.id}", member_limit=1) #, expire_date=datetime.now()+timedelta(days=1))
await app.send_message(holo_user.id, locale("read_rules", "message", locale=holo_user))
@@ -56,12 +57,12 @@ async def callback_reapply_query_accept(app, clb):
await app.send_message(holo_user.id, locale("approved_joined", "message", locale=holo_user))
@app.on_callback_query(filters.regex("reapply_no_[\s\S]*"))
async def callback_query_reapply_reject(app, clb):
async def callback_query_reapply_reject(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin_group"), locale("rejected_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True)
await app.send_message(configGet("admin", "groups"), locale("rejected_by", "message").format(clb.from_user.first_name, fullclb[2]), disable_notification=True)
await app.send_message(holo_user.id, locale("rejected", "message", locale=holo_user))
logWrite(f"User {fullclb[2]} got their reapplication rejected by {clb.from_user.id}")
@@ -74,18 +75,27 @@ async def callback_query_reapply_reject(app, clb):
# Use old application when user reapplies after leaving the chat
@app.on_callback_query(filters.regex("reapply_old_[\s\S]*"))
async def callback_query_reapply_old(app, clb):
async def callback_query_reapply_old(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
if HoloUser(clb.from_user).sponsorship_state()[0] == "fill":
await clb.message.reply_text(locale("finish_sponsorship", "message"), quote=False)
return
message = await app.get_messages(clb.from_user.id, int(fullclb[2]))
await confirm_yes(app, message)
await confirm_yes(app, message, kind="application")
await clb.message.edit(clb.message.text, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("done", "button", locale=clb.from_user), "nothing")]]))
# Start a new application when user reapplies after leaving the chat
@app.on_callback_query(filters.regex("reapply_new_[\s\S]*"))
async def callback_query_reapply_new(app, clb):
async def callback_query_reapply_new(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
if HoloUser(clb.from_user).sponsorship_state()[0] == "fill":
await clb.message.reply_text(locale("finish_sponsorship", "message"), quote=False)
return
await clb.answer(locale("reapply_stopped", "callback", locale=clb.from_user))
message = await app.get_messages(clb.from_user.id, int(fullclb[2]))
col_tmp.update_one({"user": clb.from_user.id}, {"$set": {"state": "fill", "completed": False, "stage": 1}})
@@ -95,7 +105,7 @@ async def callback_query_reapply_new(app, clb):
# Abort application fill in progress and restart it
@app.on_callback_query(filters.regex("reapply_stop_[\s\S]*"))
async def callback_query_reapply_stop(app, clb):
async def callback_query_reapply_stop(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(clb.from_user)

View File

@@ -1,5 +1,6 @@
from app import app
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from pyrogram.client import Client
from pyrogram.errors import bad_request_400
from pyrogram import filters
from modules.utils import locale, logWrite
@@ -7,7 +8,7 @@ from modules.commands.rules import DefaultRulesMarkup
# Callback rule ================================================================================================================
@app.on_callback_query(filters.regex("rule_[\s\S]*"))
async def callback_query_rule(app, clb):
async def callback_query_rule(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
@@ -46,7 +47,7 @@ async def callback_query_rule(app, clb):
await clb.answer(text=locale("rules_page", "callback", locale=clb.from_user).format(fullclb[1]))
@app.on_callback_query(filters.regex("rules_home"))
async def callback_query_rules_home(app, clb):
async def callback_query_rules_home(app: Client, clb: CallbackQuery):
logWrite(f"User {clb.from_user.id} requested to check out homepage rules")
@@ -58,7 +59,7 @@ async def callback_query_rules_home(app, clb):
await clb.answer(text=locale("rules_home", "callback", locale=clb.from_user))
@app.on_callback_query(filters.regex("rules_additional"))
async def callback_query_rules_additional(app, clb):
async def callback_query_rules_additional(app: Client, clb: CallbackQuery):
logWrite(f"User {clb.from_user.id} requested to check out additional rules")

10
modules/callbacks/sid.py Normal file
View File

@@ -0,0 +1,10 @@
from app import app
from pyrogram.types import CallbackQuery
from pyrogram.client import Client
from pyrogram import filters
# Callback rule ================================================================================================================
@app.on_callback_query(filters.regex("sid_[\s\S]*"))
async def callback_query_rule(app: Client, clb: CallbackQuery):
await clb.answer(url=f'https://t.me/{(await app.get_me()).username}?start={clb.data.split("_")[1]}')
# ==============================================================================================================================

View File

@@ -0,0 +1,103 @@
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.errors.holo_user import LabelSettingError
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", "groups"), 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 sponsorship approved by {clb.from_user.id}")
if col_sponsorships.find_one({"user": holo_user.id}) is not None:
col_sponsorships.update_one({"user": holo_user.id},
{
"$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
}
}
)
try:
await holo_user.label_set(configGet("users", "groups"), col_tmp.find_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "sponsorship"}})["sponsorship"]["label"])
except LabelSettingError as exp:
await app.send_message(configGet("admin", "groups"), exp.__str__(), disable_notification=True)
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", "groups"), 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 sponsorship 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 app import app
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from pyrogram import filters
from pyrogram.client import Client
from classes.holo_user import HoloUser
from modules.utils import configGet, locale, logWrite
from modules.database import col_tmp, col_applications
@@ -9,22 +10,22 @@ from modules.commands.rules import DefaultRulesMarkup
# Callbacks application ========================================================================================================
@app.on_callback_query(filters.regex("sub_yes_[\s\S]*"))
async def callback_query_accept(app, clb):
async def callback_query_accept(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin_group"), locale("approved_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(configGet("admin", "groups"), locale("approved_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
logWrite(f"User {holo_user.id} got approved by {clb.from_user.id}")
need_link = True
async for member in app.get_chat_members(configGet("destination_group")):
async for member in app.get_chat_members(configGet("users", "groups")):
if member.user.id == holo_user.id:
need_link = False
if need_link:
link = await app.create_chat_invite_link(configGet("destination_group"), name=f"Invite for {holo_user.id}", member_limit=1) #, expire_date=datetime.now()+timedelta(days=1))
link = await app.create_chat_invite_link(configGet("users", "groups"), name=f"Invite for {holo_user.id}", member_limit=1) #, expire_date=datetime.now()+timedelta(days=1))
await app.send_message(holo_user.id, locale("read_rules", "message", locale=holo_user))
@@ -51,12 +52,12 @@ async def callback_query_accept(app, clb):
await clb.answer(text=locale("sub_accepted", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True)
@app.on_callback_query(filters.regex("sub_no_[\s\S]*"))
async def callback_query_reject(app, clb):
async def callback_query_reject(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin_group"), locale("rejected_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(configGet("admin", "groups"), locale("rejected_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(holo_user.id, locale("rejected", "message", locale=holo_user))
logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id}")
@@ -68,12 +69,12 @@ async def callback_query_reject(app, clb):
await clb.answer(text=locale("sub_rejected", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True)
@app.on_callback_query(filters.regex("sub_aggressive_[\s\S]*"))
async def callback_query_reject_aggressive(app, clb):
async def callback_query_reject_aggressive(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin_group"), locale("rejected_by_agr", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(configGet("admin", "groups"), locale("rejected_by_agr", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(holo_user.id, locale("rejected_aggressive", "message", locale=holo_user))
logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id} due to being aggressive")
@@ -85,12 +86,12 @@ async def callback_query_reject_aggressive(app, clb):
await clb.answer(text=locale("sub_aggressive", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True)
@app.on_callback_query(filters.regex("sub_russian_[\s\S]*"))
async def callback_query_reject_russian(app, clb):
async def callback_query_reject_russian(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin_group"), locale("rejected_by_rus", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(configGet("admin", "groups"), locale("rejected_by_rus", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(holo_user.id, locale("rejected_russian", "message", locale=holo_user))
logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id} due to being russian")

View File

@@ -1,5 +1,6 @@
from app import app
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ChatPermissions
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ChatPermissions, CallbackQuery
from pyrogram.client import Client
from pyrogram import filters
from classes.holo_user import HoloUser
from modules.utils import configGet, locale, logWrite
@@ -7,12 +8,12 @@ from modules.database import col_tmp
# Callbacks sus users ==========================================================================================================
@app.on_callback_query(filters.regex("sus_allow_[\s\S]*"))
async def callback_query_sus_allow(app, clb):
async def callback_query_sus_allow(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin_group"), locale("sus_allowed_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(configGet("admin", "groups"), locale("sus_allowed_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
logWrite(f"User {holo_user.id} was allowed to join with another link by {clb.from_user.id}")
edited_markup = [[InlineKeyboardButton(text=str(locale("sus_allowed", "button")), callback_data="nothing")]]
@@ -20,7 +21,7 @@ async def callback_query_sus_allow(app, clb):
await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup))
await clb.answer(text=locale("sus_allowed", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True)
await app.restrict_chat_member(configGet("destination_group"), holo_user.id, permissions=ChatPermissions(
await app.restrict_chat_member(configGet("users", "groups"), holo_user.id, permissions=ChatPermissions(
can_send_messages=True,
can_send_media_messages=True,
can_send_other_messages=True,
@@ -29,12 +30,12 @@ async def callback_query_sus_allow(app, clb):
)
@app.on_callback_query(filters.regex("sus_reject_[\s\S]*"))
async def callback_query_sus_reject(app, clb):
async def callback_query_sus_reject(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin_group"), locale("sus_rejected_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(configGet("admin", "groups"), locale("sus_rejected_by", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
logWrite(f"User {holo_user.id} was rejected to join with another link by {clb.from_user.id}")
edited_markup = [[InlineKeyboardButton(text=str(locale("sus_rejected", "button")), callback_data="nothing")]]
@@ -42,7 +43,7 @@ async def callback_query_sus_reject(app, clb):
await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup))
await clb.answer(text=locale("sus_rejected", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True)
await app.ban_chat_member(configGet("destination_group"), holo_user.id)
await app.ban_chat_member(configGet("users", "groups"), holo_user.id)
col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}})
# ==============================================================================================================================

View File

@@ -1,96 +1,62 @@
from datetime import datetime
from app import app, isAnAdmin
from app import app
from pyrogram import filters
from pyrogram.enums.parse_mode import ParseMode
from pyrogram.types import Message
from pyrogram.errors import bad_request_400
from classes.holo_user import HoloUser, UserNotFoundError
from pyrogram.client import Client
from classes.errors.holo_user import UserNotFoundError
from classes.holo_user import HoloUser
from modules.utils import logWrite, locale, should_quote
from dateutil.relativedelta import relativedelta
from modules.database import col_applications
from modules import custom_filters
# Applications command =========================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["application"], prefixes=["/"]))
async def cmd_application(app, msg):
# Application command ==========================================================================================================
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.command(["application"], prefixes=["/"]) & custom_filters.admin)
async def cmd_application(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
try:
try:
holo_user = HoloUser(int(msg.command[1]))
except (ValueError, UserNotFoundError):
try:
holo_user = HoloUser(int(msg.command[1]))
except (ValueError, UserNotFoundError):
try:
holo_user = HoloUser((await app.get_users(msg.command[1])).id)
except (bad_request_400.UsernameInvalid, bad_request_400.PeerIdInvalid):
await msg.reply_text(locale("no_user_application", "message", locale=msg.from_user).format(msg.command[1]), quote=should_quote(msg))
return
application = col_applications.find_one({"user": holo_user.id})
if application is None:
logWrite(f"User {msg.from_user.id} requested application of {holo_user.id} but user does not exists")
await msg.reply_text(locale("user_invalid", "message", locale=msg.from_user), quote=should_quote(msg))
holo_user = HoloUser((await app.get_users(msg.command[1])).id)
except (bad_request_400.UsernameInvalid, bad_request_400.PeerIdInvalid, bad_request_400.UsernameNotOccupied):
await msg.reply_text(locale("no_user_application", "message", locale=msg.from_user).format(msg.command[1]), quote=should_quote(msg))
return
application_content = []
i = 1
application = col_applications.find_one({"user": holo_user.id})
for question in application['application']:
if application is None:
logWrite(f"User {msg.from_user.id} requested application of {holo_user.id} but user does not exists")
await msg.reply_text(locale("user_invalid", "message", locale=msg.from_user), quote=should_quote(msg))
return
if i == 2:
age = relativedelta(datetime.now(), application['application']['2'])
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)")
elif i == 3:
if application['application']['3']['countryCode'] == "UA":
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application']['3']['name']}")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})")
application_content = []
i = 1
for question in application['application']:
if i == 2:
age = relativedelta(datetime.now(), application['application']['2'])
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)")
elif i == 3:
if application['application']['3']['countryCode'] == "UA":
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application']['3']['name']}")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application'][question]}")
i += 1
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=msg.from_user)} {application['application'][question]}")
i += 1
application_status = locale("application_status_accepted", "message", locale=msg.from_user).format((await app.get_users(application["admin"])).first_name, application["date"].strftime("%d.%m.%Y, %H:%M"))
application_status = locale("application_status_accepted", "message", locale=msg.from_user).format((await app.get_users(application["admin"])).first_name, application["date"].strftime("%d.%m.%Y, %H:%M"))
logWrite(f"User {msg.from_user.id} requested application of {holo_user.id}")
await msg.reply_text(locale("contact", "message", locale=msg.from_user).format(holo_user.id, "\n".join(application_content), application_status), parse_mode=ParseMode.MARKDOWN, quote=should_quote(msg))
# if (path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json") and jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json")["approved"]):
# user_id = int(msg.command[1])
# else:
# list_of_users = []
# async for m in app.get_chat_members(configGet("destination_group"), filter=ChatMembersFilter.SEARCH, query=msg.command[1]):
# list_of_users.append(m)
# user_id = list_of_users[0].user.id
# try:
# user_data = jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{user_id}.json")
# application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json")[str(user_id)]
# application_content = []
# i = 1
# for question in configGet("application", file=str(msg.from_user.id)):
# if i == 2:
# age = relativedelta(datetime.now(), datetime.strptime(application['application']['2'], '%d.%m.%Y'))
# application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {application['application']['2']} ({age.years} р.)")
# else:
# application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {application['application'][question]}")
# i += 1
# if user_data["sent"]:
# if user_data["approved"]:
# application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M"))
# elif application["rejected"]:
# application_status = locale("application_status_rejected", "message").format((await app.get_users(application["rejected_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M"))
# else:
# application_status = locale("application_status_on_hold", "message")
# else:
# if user_data["approved"]:
# application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M"))
# elif application["rejected"]:
# application_status = locale("application_status_rejected", "message").format((await app.get_users(application["rejected_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M"))
# else:
# application_status = locale("application_status_not_send", "message")
# logWrite(f"User {msg.from_user.id} requested application of {user_id}")
# await msg.reply_text(locale("contact", "message").format(str(user_id), "\n".join(application_content), application_status), quote=should_quote(msg))
except IndexError:
await msg.reply_text(locale("application_invalid_syntax", "message", locale=msg.from_user), quote=should_quote(msg))
logWrite(f"User {msg.from_user.id} requested application of {holo_user.id}")
await msg.reply_text(locale("contact", "message", locale=msg.from_user).format(holo_user.id, "\n".join(application_content), application_status), parse_mode=ParseMode.MARKDOWN, quote=should_quote(msg))
except IndexError:
await msg.reply_text(locale("application_invalid_syntax", "message", locale=msg.from_user), quote=should_quote(msg))
# ==============================================================================================================================

View File

@@ -1,28 +1,30 @@
from os import sep, makedirs, remove
from uuid import uuid1
from app import app, isAnAdmin
from app import app
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from pyrogram.enums.chat_action import ChatAction
from modules.logging import logWrite
from modules.utils import should_quote, jsonSave
from modules.database import col_applications
from modules import custom_filters
# Applications command =========================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["applications"], prefixes=["/"]))
async def cmd_applications(app, msg):
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.command(["applications"], prefixes=["/"]) & custom_filters.admin)
async def cmd_applications(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
logWrite(f"Admin {msg.from_user.id} requested export of a database")
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
filename = uuid1()
output = []
for entry in col_applications.find():
del entry["_id"]
entry["date"] = entry["date"].strftime("%d.%m.%Y, %H:%M")
entry["application"]["2"] = entry["application"]["2"].strftime("%d.%m.%Y, %H:%M")
output.append(entry)
makedirs("tmp", exist_ok=True)
jsonSave(output, f"tmp{sep}{filename}.json")
await msg.reply_document(document=f"tmp{sep}{filename}.json", file_name="applications", quote=should_quote(msg))
remove(f"tmp{sep}{filename}.json")
logWrite(f"Admin {msg.from_user.id} requested export of a database")
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
filename = uuid1()
output = []
for entry in col_applications.find():
del entry["_id"]
entry["date"] = entry["date"].strftime("%d.%m.%Y, %H:%M")
entry["application"]["2"] = entry["application"]["2"].strftime("%d.%m.%Y, %H:%M")
output.append(entry)
makedirs("tmp", exist_ok=True)
jsonSave(output, f"tmp{sep}{filename}.json")
await msg.reply_document(document=f"tmp{sep}{filename}.json", file_name="applications", quote=should_quote(msg))
remove(f"tmp{sep}{filename}.json")
# ==============================================================================================================================

View File

@@ -0,0 +1,16 @@
from app import app
from pyrogram import filters
from pyrogram.types import Message, ReplyKeyboardRemove
from pyrogram.client import Client
from modules.utils import should_quote, logWrite, locale
from modules.database import col_tmp, col_spoilers
from modules import custom_filters
# Cancel command ===============================================================================================================
@app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.command("cancel", prefixes=["/"]))
async def command_cancel(app: Client, msg: Message):
col_tmp.delete_many( {"user": msg.from_user.id} )
col_spoilers.delete_many( {"user": msg.from_user.id, "completed": False} )
await msg.reply_text(locale("cancel", "message", locale=msg.from_user), quote=should_quote(msg), reply_markup=ReplyKeyboardRemove())
logWrite(f"Cancelling all ongoing tmp operations for {msg.from_user.id}")
# ==============================================================================================================================

View File

@@ -0,0 +1,66 @@
from os import path
from app import app, isAnAdmin
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from pyrogram.errors import bad_request_400
from pyrogram.enums.chat_action import ChatAction
from classes.errors.holo_user import UserNotFoundError, UserInvalidError
from classes.holo_user import HoloUser
from modules.utils import jsonLoad, should_quote, logWrite, locale, download_tmp, create_tmp, find_user
from modules import custom_filters
# Identify command =============================================================================================================
@app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.command("identify", prefixes=["/"]) & custom_filters.admin)
async def cmd_identify(app: Client, msg: Message):
if len(msg.command) != 2:
await msg.reply_text(locale("identify_invalid_syntax", "message", locale=msg.from_user))
return
try:
try:
holo_user = HoloUser(int(msg.command[1]))
except ValueError:
holo_user = HoloUser(await find_user(app, msg.command[1]))
except (UserInvalidError, UserNotFoundError, bad_request_400.UsernameInvalid, bad_request_400.PeerIdInvalid, bad_request_400.UsernameNotOccupied, TypeError):
await msg.reply_text(locale("identify_not_found", "message", locale=msg.from_user).format(msg.command[1]))
return
role = holo_user.label
has_application = locale("yes", "message", locale=msg.from_user) if holo_user.application_approved() is True else locale("no", "message", locale=msg.from_user)
has_sponsorship = locale("yes", "message", locale=msg.from_user) if holo_user.sponsorship_valid() is True else locale("no", "message", locale=msg.from_user)
username = holo_user.username if holo_user.username is not None else "N/A"
in_chat = locale("yes", "message", locale=msg.from_user) if (holo_user.id in jsonLoad(path.join("cache", "group_members"))) else locale("no", "message", locale=msg.from_user)
is_admin = locale("yes", "message", locale=msg.from_user) if (await isAnAdmin(holo_user.id)) else locale("no", "message", locale=msg.from_user)
output = locale("identify_success", "message", locale=msg.from_user).format(
holo_user.id,
holo_user.name,
username,
in_chat,
is_admin,
role,
has_application,
has_sponsorship
)
user = await app.get_users(holo_user.id)
if user.photo is not None:
await app.send_chat_action(msg.chat.id, action=ChatAction.UPLOAD_PHOTO)
await msg.reply_photo(
create_tmp(await download_tmp(app, user.photo.big_file_id), kind="image"),
quote=should_quote(msg),
caption=output
)
else:
await app.send_chat_action(msg.chat.id, action=ChatAction.TYPING)
await msg.reply_text(
output,
quote=should_quote(msg)
)
logWrite(f"User {msg.from_user.id} identified user {holo_user.id}")
# ==============================================================================================================================

View File

@@ -1,32 +1,43 @@
from app import app, isAnAdmin
from app import app
from pyrogram import filters
from modules.utils import should_quote, find_user
from pyrogram.types import Message
from pyrogram.client import Client
from modules.utils import configGet, locale, should_quote, find_user
from classes.errors.holo_user import LabelTooLongError, LabelSettingError
from classes.holo_user import HoloUser
from modules import custom_filters
@app.on_message(~ filters.scheduled & filters.private & filters.command(["label"], prefixes=["/"]))
async def cmd_label(app, msg):
# Label command ================================================================================================================
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.command(["label"], prefixes=["/"]) & custom_filters.admin)
async def cmd_label(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
if len(msg.command) < 3:
await msg.reply_text("Invalid syntax:\n`/label USER LABEL`")
return
if len(msg.command) < 3:
await msg.reply_text("Invalid syntax:\n`/label USER LABEL`")
return
target = await find_user(app, msg.command[1])
target = await find_user(app, msg.command[1])
if target is not None:
target = HoloUser(target)
if target is not None:
label = " ".join(msg.command[2:])
if label.lower() == "reset":
await target.label_reset(msg.chat)
await msg.reply_text(f"Resetting **{target.id}**'s label...", quote=should_quote(msg))
target = HoloUser(target)
label = " ".join(msg.command[2:])
if label.lower() == "reset":
await target.reset_label(msg.chat)
await msg.reply_text(f"Resetting **{target.id}**'s label...", quote=should_quote(msg))
else:
await target.set_label(msg.chat, label)
await msg.reply_text(f"Setting **{target.id}**'s label to **{label}**...", quote=should_quote(msg))
else:
await msg.reply_text(f"User not found")
try:
await target.label_set(msg.chat, label)
except LabelTooLongError:
await msg.reply_text(locale("label_too_long", "message"))
return
except LabelSettingError as exp:
await app.send_message(configGet("admin", "groups"), exp.__str__(), disable_notification=True)
return
await msg.reply_text(f"Setting **{target.id}**'s label to **{label}**...", quote=should_quote(msg))
else:
await msg.reply_text(f"User not found")
# ==============================================================================================================================

View File

@@ -1,32 +1,34 @@
from app import app, isAnAdmin
from app import app
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from classes.errors.holo_user import UserInvalidError
from classes.holo_user import HoloUser
from modules.utils import logWrite, locale, should_quote
from modules.utils import logWrite, locale, should_quote, find_user
from modules import custom_filters
# Message command ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["message"], prefixes=["/"]))
async def cmd_message(app, msg):
@app.on_message(custom_filters.enabled_general & ~filters.scheduled & filters.command(["message"], prefixes=["/"]) & custom_filters.admin)
async def cmd_message(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
try:
try:
destination = HoloUser(int(msg.command[1]))
except (ValueError, UserInvalidError):
destination = HoloUser(await find_user(app, query=msg.command[1]))
try:
destination = HoloUser(int(msg.command[1]))
except ValueError:
destination = HoloUser(msg.command[1])
if ((msg.text is not None) and (len(msg.text.split()) > 2)):
await destination.message(context=msg, text=" ".join(msg.text.split()[2:]), caption=msg.caption, photo=msg.photo, video=msg.video, file=msg.document, adm_context=True)
elif ((msg.caption is not None) and (len(msg.caption.split()) > 2)):
await destination.message(context=msg, text=msg.text, caption=" ".join(msg.caption.split()[2:]), photo=msg.photo, video=msg.video, file=msg.document, adm_context=True)
else:
await destination.message(context=msg, text=None, caption=None, photo=msg.photo, video=msg.video, file=msg.document, adm_context=True)
except IndexError:
await msg.reply_text(locale("message_invalid_syntax", "message", locale=msg.from_user), quote=should_quote(msg))
logWrite(f"Admin {msg.from_user.id} tried to send message but 'IndexError'")
except ValueError:
await msg.reply_text(locale("message_invalid_syntax", "message", locale=msg.from_user), quote=should_quote(msg))
logWrite(f"Admin {msg.from_user.id} tried to send message but 'ValueError'")
if ((msg.text is not None) and (len(msg.text.split()) > 2)):
await destination.message(context=msg, text=" ".join(msg.text.split()[2:]), caption=msg.caption, photo=msg.photo, video=msg.video, file=msg.document, voice=msg.voice, animation=msg.animation, adm_context=True)
elif ((msg.caption is not None) and (len(msg.caption.split()) > 2)):
await destination.message(context=msg, text=msg.text, caption=" ".join(msg.caption.split()[2:]), photo=msg.photo, video=msg.video, file=msg.document, voice=msg.voice, animation=msg.animation, adm_context=True)
else:
await destination.message(context=msg, text=None, caption=None, photo=msg.photo, video=msg.video, file=msg.document, voice=msg.voice, animation=msg.animation, adm_context=True)
except IndexError:
await msg.reply_text(locale("message_invalid_syntax", "message", locale=msg.from_user), quote=should_quote(msg))
logWrite(f"Admin {msg.from_user.id} tried to send message but 'IndexError'")
except ValueError:
await msg.reply_text(locale("message_invalid_syntax", "message", locale=msg.from_user), quote=should_quote(msg))
logWrite(f"Admin {msg.from_user.id} tried to send message but 'ValueError'")
# ==============================================================================================================================

View File

@@ -1,24 +1,58 @@
from app import app, isAnAdmin
from traceback import print_exc
from app import app
from pyrogram import filters
from modules.utils import configGet, should_quote
from modules.database import col_applications
from pyrogram.types import Message
from pyrogram.client import Client
from classes.holo_user import HoloUser
from modules import custom_filters
from modules.logging import logWrite
from modules.utils import configGet, locale, should_quote, find_location
from modules.database import col_applications, col_users
from classes.errors.geo import PlaceNotFoundError
# Nearby command ===============================================================================================================
@app.on_message(~ filters.scheduled & (filters.private | (filters.chat(configGet("admin_group")) | filters.chat(configGet("destination_group")))) & filters.command(["nearby"], prefixes=["/"]))
async def cmd_nearby(app, msg):
if (await isAnAdmin(msg) is True) or (col_applications.find_one({"user": msg.from_user.id}) is not None):
await msg.reply_text("Yes, I exist.", quote=should_quote(msg))
# if not path.exists(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json"):
# jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}sponsor_default.json"), f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json")
# sponsor = jsonLoad(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json")
# if sponsor["approved"]:
# if sponsor["expires"] is not None:
# if datetime.strptime(sponsor["expires"], "%d.%m.%Y") > datetime.now():
# await msg.reply_text(f"You have an active sub til **{sponsor['expires']}**.")
# else:
# await msg.reply_text(f"Your sub expired {int((datetime.now()-datetime.strptime(sponsor['expires'], '%d.%m.%Y')).days)} days ago.")
# elif sponsor["approved"]:
# await msg.reply_text(f"Your sub expiration date is not valid.")
# else:
# await msg.reply_text(f"You have no active subscription.")
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & (filters.private | (filters.chat(configGet("admin", "groups")) | filters.chat(configGet("users", "groups")))) & filters.command(["nearby"], prefixes=["/"]) & (custom_filters.allowed | custom_filters.admin))
async def cmd_nearby(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user)
# Check if any place provided
if len(msg.command) == 1: # Action if no place provided
application = col_applications.find_one({"user": msg.from_user.id})
if application is None:
await msg.reply_text(locale("nearby_user_empty", "message", locale=holo_user))
return
location = application["application"]["3"]["location"][0], application["application"]["3"]["location"][1]
else: # Find a place from input query
logWrite(f"Looking for the location by query '{' '.join(msg.command[1:])}'")
try:
location_coordinates = find_location(" ".join(msg.command[1:]))
location = float(location_coordinates["lng"]), float(location_coordinates["lat"])
except PlaceNotFoundError: # Place is not found
await msg.reply_text(locale("nearby_invalid", "message", locale=holo_user), quote=should_quote(msg))
return
except Exception as exp: # Error occurred while finding the place
await msg.reply_text(locale("nearby_error", "message", locale=holo_user).format(exp, print_exc()), quote=should_quote(msg))
return
# Find all users registered in the area provided
output = []
applications_nearby = col_applications.find( {"application.3.location": { "$nearSphere": {"$geometry": {"type": "Point", "coordinates": [location[0], location[1]]}, "$maxDistance": configGet("search_radius")*1000} } } )
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))
# ==============================================================================================================================

View File

@@ -1,26 +1,38 @@
from app import app
from pyrogram import filters
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message
from pyrogram.client import Client
from classes.holo_user import HoloUser
from modules.utils import configGet, locale
from modules.utils import configGet, locale, should_quote
from modules.handlers.welcome import welcome_pass
from modules.database import col_tmp
from modules import custom_filters
# Reapply command ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.private & filters.command(["reapply"], prefixes=["/"]))
async def cmd_reapply(app, msg):
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(["reapply"], prefixes=["/"]))
async def cmd_reapply(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user)
if holo_user.application_state()[0] in ["approved", "rejected"]:
if (holo_user.application_state()[1] is True) and (not col_tmp.find_one({"user": holo_user.id, "type": "application"})["sent"]):
# Check if user has approved/rejected tmp application
if ((holo_user.application_state()[0] in ["approved", "rejected"]) or (holo_user.application_state()[0] == "none")) and holo_user.spoiler_state() is False:
# Check if user's tmp application is already completed or even sent
if ((holo_user.application_state()[1] is True) and (not col_tmp.find_one({"user": holo_user.id, "type": "application"})["sent"])) or (holo_user.application_state()[0] == "none"):
left_chat = True
async for member in app.get_chat_members(configGet("destination_group")):
async for member in app.get_chat_members(configGet("users", "groups")):
if member.user.id == msg.from_user.id:
left_chat = False
if not left_chat:
holo_user.application_restart()
if holo_user.sponsorship_state()[0] == "fill":
await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg))
return
holo_user.application_restart(reapply=True)
await welcome_pass(app, msg, once_again=True)
else:
await msg.reply_text(locale("reapply_left_chat", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup([
[
@@ -30,15 +42,24 @@ async def cmd_reapply(app, msg):
InlineKeyboardButton(locale("reapply_new_one", "button", locale=holo_user), f"reapply_new_{msg.id}")
]
]))
else:
await msg.reply_text(locale("reapply_in_progress", "message", locale=holo_user).format(locale("confirm", "keyboard", locale=holo_user)[1][0]), reply_markup=InlineKeyboardMarkup([
[
InlineKeyboardButton(locale("applying_stop", "button", locale=holo_user), f"reapply_stop_{msg.id}")
]
]))
elif holo_user.spoiler_state() is True:
await msg.reply_text(locale("spoiler_in_progress", "message", locale=holo_user))
else:
if (holo_user.application_state()[0] == "fill") and (col_tmp.find_one({"user": holo_user.id, "type": "application"})["sent"] is True):
await msg.reply_text(locale("reapply_forbidden", "message", locale=holo_user))
else:
await msg.reply_text(locale("reapply_in_progress", "message", locale=holo_user).format(locale("confirm", "keyboard", locale=holo_user)[1][0]), reply_markup=InlineKeyboardMarkup([
[

View File

@@ -1,19 +1,24 @@
from app import app, isAnAdmin
from os import getpid
from app import app
from os import getpid, makedirs, path
from sys import exit
from time import time
from pyrogram import filters
from modules.utils import locale, logWrite, should_quote
from pyrogram.types import Message
from pyrogram.client import Client
from modules.utils import configGet, jsonSave, locale, logWrite, should_quote
from modules.scheduled import scheduler
from modules import custom_filters
pid = getpid()
# Shutdown command =============================================================================================================
@app.on_message(~ filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["/"]))
async def cmd_kill(app, msg):
# Reboot command ===============================================================================================================
@app.on_message(custom_filters.enabled_general & ~filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["/"]) & custom_filters.admin)
async def cmd_kill(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
logWrite(f"Shutting down bot with pid {pid}")
await msg.reply_text(locale("shutdown", "message", locale=msg.from_user).format(pid), quote=should_quote(msg))
scheduler.shutdown()
exit()
logWrite(f"Shutting down bot with pid {pid}")
await msg.reply_text(locale("shutdown", "message", locale=msg.from_user).format(pid), quote=should_quote(msg))
scheduler.shutdown()
makedirs(configGet("cache", "locations"), exist_ok=True)
jsonSave({"timestamp": time()}, path.join(configGet("cache", "locations"), "shutdown_time"))
exit()
# ==============================================================================================================================

View File

@@ -0,0 +1,65 @@
from app import app
from os import getpid, listdir
from pyrogram import filters
from pyrogram.types import Message, BotCommandScopeDefault, BotCommandScopeChat
from pyrogram.errors import bad_request_400
from pyrogram.client import Client
from modules.utils import logWrite, should_quote, configGet
from modules import custom_filters
pid = getpid()
# Reset commands command =======================================================================================================
@app.on_message(custom_filters.enabled_general & ~filters.scheduled & filters.private & filters.command(["resetcommands"], prefixes=["/"]) & custom_filters.admin)
async def cmd_resetcommands(app: Client, msg: Message):
if msg.from_user.id == configGet("owner"):
logWrite(f"Resetting all commands on owner's request")
valid_locales = []
files_locales = listdir(f'{configGet("locale", "locations")}')
for entry in files_locales:
if entry.endswith(".json"):
valid_locales.append(".".join(entry.split(".")[:-1]))
if configGet("debug") is True:
logWrite(f'Resetting commands in groups {configGet("admin", "groups")} and {configGet("users", "groups")}')
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("admin", "groups")))
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("users", "groups")))
for admin in configGet("admins"):
try:
if configGet("debug") is True:
logWrite(f'Resetting commands for admin {admin}')
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=admin))
except bad_request_400.PeerIdInvalid:
pass
try:
if configGet("debug") is True:
logWrite(f'Resetting commands for owner {configGet("owner")}')
for lc in valid_locales:
if configGet("debug") is True:
logWrite(f'Resetting commands for owner {configGet("owner")} [{lc}]')
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("owner")), language_code=lc)
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("owner")))
except bad_request_400.PeerIdInvalid:
pass
for lc in valid_locales:
if configGet("debug") is True:
logWrite(f'Resetting commands for locale {lc}')
await app.delete_bot_commands(scope=BotCommandScopeDefault(), language_code=lc)
if configGet("debug") is True:
logWrite(f'Resetting default commands')
await app.delete_bot_commands()
await msg.reply_text("OK", quote=should_quote(msg))
if configGet("debug") is True:
logWrite(str(await app.get_bot_commands()))
logWrite(str(await app.get_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("owner")))))
# ==============================================================================================================================

View File

@@ -1,8 +1,10 @@
from typing import Union
from app import app
from pyrogram import filters
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, User
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, User, Message
from pyrogram.client import Client
from modules.utils import locale
from modules import custom_filters
from classes.holo_user import HoloUser
class DefaultRulesMarkup(list):
@@ -34,7 +36,7 @@ class DefaultRulesMarkup(list):
# Rules command =============================================================================================================
@app.on_message(~ filters.scheduled & filters.private & filters.command(["rules"], prefixes=["/"]))
async def cmd_rules(app, msg):
@app.on_message(custom_filters.enabled_general & ~filters.scheduled & filters.private & filters.command(["rules"], prefixes=["/"]))
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)
# ==============================================================================================================================

View File

@@ -0,0 +1,45 @@
from app import app
from pyrogram import filters
from pyrogram.types import Message, ReplyKeyboardMarkup
from pyrogram.client import Client
from classes.errors.holo_user import UserNotFoundError, UserInvalidError
from classes.holo_user import HoloUser
from modules.logging import logWrite
from modules.utils import locale
from modules.database import col_spoilers
from modules import custom_filters
# Spoiler command ==============================================================================================================
@app.on_message(custom_filters.member & ~filters.scheduled & filters.private & filters.command(["spoiler"], prefixes=["/"]))
async def cmd_spoiler(app: Client, msg: Message):
try:
holo_user = HoloUser(msg.from_user)
except (UserInvalidError, UserNotFoundError):
return
if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill":
if col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} ) is None:
col_spoilers.insert_one(
{
"user": msg.from_user.id,
"completed": False,
"category": None,
"description": None,
"photo": None,
"video": None,
"animation": None,
"document": None,
"caption": None,
"text": None
}
)
await msg.reply_text(locale("spoiler_started", "message", locale=msg.from_user), reply_markup=ReplyKeyboardMarkup(locale("spoiler_categories", "keyboard"), resize_keyboard=True, one_time_keyboard=True))
logWrite(f"User {msg.from_user.id} started creating new spoiler")
else:
await msg.reply_text(locale("spoiler_unfinished", "message", locale=msg.from_user))
# ==============================================================================================================================

View File

@@ -1,25 +1,23 @@
from datetime import datetime
from app import app, isAnAdmin
from app import app
from pyrogram import filters
from modules.utils import should_quote
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message
from pyrogram.client import Client
from classes.holo_user import HoloUser
from modules import custom_filters
from modules.utils import locale, should_quote
from modules.database import col_applications
# Sponsorship command ==========================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["sponsorship"], prefixes=["/"]))
async def cmd_sponsorship(app, msg):
if (await isAnAdmin(msg) is True) or (col_applications.find_one({"user": msg.from_user.id}) is not None):
await msg.reply_text("Yes, I exist.", quote=should_quote(msg))
# if not path.exists(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json"):
# jsonSave(jsonLoad(f"{configGet('data', 'locations')}{sep}sponsor_default.json"), f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json")
# sponsor = jsonLoad(f"{configGet('data', 'locations')}{sep}sponsors{sep}{msg.from_user.id}.json")
# if sponsor["approved"]:
# if sponsor["expires"] is not None:
# if datetime.strptime(sponsor["expires"], "%d.%m.%Y") > datetime.now():
# await msg.reply_text(f"You have an active sub til **{sponsor['expires']}**.")
# else:
# await msg.reply_text(f"Your sub expired {int((datetime.now()-datetime.strptime(sponsor['expires'], '%d.%m.%Y')).days)} days ago.")
# elif sponsor["approved"]:
# await msg.reply_text(f"Your sub expiration date is not valid.")
@app.on_message(custom_filters.enabled_sponsorships & ~filters.scheduled & filters.command(["sponsorship"], prefixes=["/"]) & (custom_filters.allowed | custom_filters.admin))
async def cmd_sponsorship(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user)
if holo_user.application_state()[0] == "fill":
await msg.reply_text(locale("finish_application", "message", locale=msg.from_user), quote=should_quote(msg))
return
if holo_user.spoiler_state() is True:
await msg.reply_text(locale("spoiler_in_progress", "message", locale=holo_user))
return
await msg.reply_text(locale("sponsorship_apply", "message", locale=msg.from_user), reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text=str(locale("sponsor_apply", "button", locale=msg.from_user)), callback_data=f"sponsor_apply_{msg.from_user.id}")]]), quote=should_quote(msg))
# else:
# await msg.reply_text(f"You have no active subscription.")
# await msg.reply_text(locale("sponsorship_application_empty", "message"))
# ==============================================================================================================================

View File

@@ -1,12 +1,16 @@
from app import app
from pyrogram import filters
from pyrogram.types import ReplyKeyboardMarkup
from pyrogram.types import ReplyKeyboardMarkup, Message
from pyrogram.client import Client
from modules.utils import locale, logWrite
from modules.database import col_users
from modules.database import col_users, col_spoilers
from modules import custom_filters
from bson.objectid import ObjectId
from bson.errors import InvalidId
# Start command ================================================================================================================
@app.on_message(~ filters.scheduled & filters.private & filters.command(["start"], prefixes=["/"]))
async def cmd_start(app, msg):
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(["start"], prefixes=["/"]))
async def cmd_start(app: Client, msg: Message):
user = col_users.find_one({"user": msg.from_user.id})
@@ -24,4 +28,20 @@ async def cmd_start(app, msg):
logWrite(f"User {msg.from_user.id} started bot interaction")
await msg.reply_text(locale("start", "message", locale=msg.from_user), reply_markup=ReplyKeyboardMarkup(locale("welcome", "keyboard", locale=msg.from_user), resize_keyboard=True))
if len(msg.command) > 1:
try:
spoiler = col_spoilers.find_one( {"_id": ObjectId(msg.command[1])} )
if spoiler["photo"] is not None:
await msg.reply_document(spoiler["photo"], caption=spoiler["caption"])
if spoiler["video"] is not None:
await msg.reply_cached_media(spoiler["video"], caption=spoiler["caption"])
if spoiler["animation"] is not None:
await msg.reply_cached_media(spoiler["animation"], caption=spoiler["caption"])
if spoiler["document"] is not None:
await msg.reply_document(spoiler["document"], caption=spoiler["caption"])
if spoiler["text"] is not None:
await msg.reply_text(spoiler["text"])
except InvalidId:
await msg.reply_text(f"Got an invalid ID {msg.command[1]}")
# ==============================================================================================================================

View File

@@ -1,20 +1,22 @@
from datetime import datetime
from app import app, isAnAdmin
from app import app
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from modules.utils import configGet, locale
from modules.database import col_warnings
from modules import custom_filters
# Warn command =================================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["warn"], prefixes=["/"]))
async def cmd_warn(app, msg):
@app.on_message(custom_filters.enabled_warnings & ~filters.scheduled & filters.command(["warn"], prefixes=["/"]) & custom_filters.admin)
async def cmd_warn(app: Client, msg: Message):
if msg.chat.id == configGet("destination_group"):
if msg.chat.id == configGet("users", "groups"):
if msg.reply_to_message_id != None:
if await isAnAdmin(msg.from_user.id) is True:
message = " ".join(msg.command[1:]) if len(msg.command) > 1 else ""
col_warnings.insert_one({"user": msg.reply_to_message.from_user.id, "admin": msg.from_user.id, "date": datetime.now(), "reason": message})
if message == "":
await msg.reply_text(locale("warned", "message").format(msg.reply_to_message.from_user.first_name, msg.reply_to_message.from_user.id))
else:
await msg.reply_text(locale("warned_reason", "message").format(msg.reply_to_message.from_user.first_name, msg.reply_to_message.from_user.id, message))
message = " ".join(msg.command[1:]) if len(msg.command) > 1 else ""
col_warnings.insert_one({"user": msg.reply_to_message.from_user.id, "admin": msg.from_user.id, "date": datetime.now(), "reason": message})
if message == "":
await msg.reply_text(locale("warned", "message").format(msg.reply_to_message.from_user.first_name, msg.reply_to_message.from_user.id))
else:
await msg.reply_text(locale("warned_reason", "message").format(msg.reply_to_message.from_user.first_name, msg.reply_to_message.from_user.id, message))
# ==============================================================================================================================

View File

@@ -1,43 +1,44 @@
from app import app, isAnAdmin
from app import app
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from pyrogram.enums.chat_members_filter import ChatMembersFilter
from modules.utils import configGet, locale, should_quote
from modules.database import col_users, col_warnings
from modules import custom_filters
# Warnings command =============================================================================================================
@app.on_message(~ filters.scheduled & filters.command(["warnings"], prefixes=["/"]))
async def cmd_warnings(app, msg):
@app.on_message(custom_filters.enabled_warnings & ~filters.scheduled & filters.command(["warnings"], prefixes=["/"]) & custom_filters.admin)
async def cmd_warnings(app: Client, msg: Message):
if await isAnAdmin(msg.from_user.id) is True:
if len(msg.command) <= 1:
await msg.reply_text(locale("syntax_warnings", "message", locale=msg.from_user), quote=should_quote(msg))
return
if len(msg.command) <= 1:
await msg.reply_text(locale("syntax_warnings", "message", locale=msg.from_user), quote=should_quote(msg))
try:
user_db = col_users.find_one({"user": int(msg.command[1])})
target_id = user_db["user"]
target_name = user_db["tg_name"]
except:
list_of_users = []
async for m in app.get_chat_members(configGet("users", "groups"), filter=ChatMembersFilter.SEARCH, query=msg.command[1]):
list_of_users.append(m)
if len(list_of_users) != 0:
target = list_of_users[0].user
target_name = target.first_name
target_id = str(target.id)
else:
await msg.reply_text(locale("no_user_warnings", "message", locale=msg.from_user).format(msg.command[1]))
return
try:
user_db = col_users.find_one({"user": int(msg.command[1])})
target_id = user_db["user"]
target_name = user_db["tg_name"]
except:
list_of_users = []
async for m in app.get_chat_members(configGet("destination_group"), filter=ChatMembersFilter.SEARCH, query=msg.command[1]):
list_of_users.append(m)
warns = len(list(col_warnings.find({"user": target_id})))
if len(list_of_users) != 0:
target = list_of_users[0].user
target_name = target.first_name
target_id = str(target.id)
else:
await msg.reply_text(locale("no_user_warnings", "message", locale=msg.from_user).format(msg.command[1]))
return
warns = len(list(col_warnings.find({"user": target_id})))
if warns == 0:
await msg.reply_text(locale("no_warnings", "message", locale=msg.from_user).format(target_name, target_id), quote=should_quote(msg))
if warns == 0:
await msg.reply_text(locale("no_warnings", "message", locale=msg.from_user).format(target_name, target_id), quote=should_quote(msg))
else:
if warns <= 5:
await msg.reply_text(locale("warnings_1", "message", locale=msg.from_user).format(target_name, target_id, warns), quote=should_quote(msg))
else:
if warns <= 5:
await msg.reply_text(locale("warnings_1", "message", locale=msg.from_user).format(target_name, target_id, warns), quote=should_quote(msg))
else:
await msg.reply_text(locale("warnings_2", "message", locale=msg.from_user).format(target_name, target_id, warns), quote=should_quote(msg))
await msg.reply_text(locale("warnings_2", "message", locale=msg.from_user).format(target_name, target_id, warns), quote=should_quote(msg))
# ==============================================================================================================================

View File

@@ -1,62 +0,0 @@
from os import listdir
from modules.logging import logWrite
from modules.utils import configGet, locale
from pyrogram.types import BotCommand, BotCommandScopeChat
from pyrogram.errors import bad_request_400
def commands_register(app):
valid_locales = []
files_locales = listdir(f'{configGet("locale", "locations")}')
for entry in files_locales:
valid_locales.append(".".join(entry.split(".")[:-1]))
# Registering user commands
commands_list = []
for command in locale("commands"):
commands_list.append(BotCommand(command, locale("commands")[command]))
app.set_bot_commands(commands_list)
# Registering user commands for each locale
for lc in valid_locales:
commands_list = []
for command in locale("commands", locale=lc):
commands_list.append(BotCommand(command, locale("commands",locale=lc)[command]))
app.set_bot_commands(commands_list, language_code=lc)
# Registering admin commands
commands_admin_list = []
for command in locale("commands"):
commands_admin_list.append(BotCommand(command, locale("commands")[command]))
for command in locale("commands_admin"):
commands_admin_list.append(BotCommand(command, locale("commands_admin")[command]))
for admin in configGet("admins"):
try:
app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=admin))
except bad_request_400.PeerIdInvalid:
pass
try:
app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=configGet("owner")))
except bad_request_400.PeerIdInvalid:
logWrite(f"Could not register commands for bot owner. Perhaps user has not started the bot yet.")
# Registering admin group commands
commands_group_admin_list = []
for command in locale("commands_group_admin"):
commands_group_admin_list.append(BotCommand(command, locale("commands_group_admin")[command]))
try:
app.set_bot_commands(commands_group_admin_list, scope=BotCommandScopeChat(chat_id=configGet("admin_group")))
except bad_request_400.ChannelInvalid:
logWrite(f"Could not register commands for admin group. Bot is likely not in the group.")
# Registering destination group commands
commands_group_destination_list = []
for command in locale("commands_group_destination"):
commands_group_destination_list.append(BotCommand(command, locale("commands_group_destination")[command]))
try:
app.set_bot_commands(commands_group_destination_list, scope=BotCommandScopeChat(chat_id=configGet("destination_group")))
except bad_request_400.ChannelInvalid:
logWrite(f"Could not register commands for destination group. Bot is likely not in the group.")

52
modules/custom_filters.py Normal file
View File

@@ -0,0 +1,52 @@
"""Custom message filters made to improve commands
usage in context of Holo Users."""
from os import path
from app import isAnAdmin
from modules.utils import configGet, jsonLoad
from modules.database import col_applications, col_tmp
from pyrogram import filters
from pyrogram.types import Message
async def admin_func(_, __, msg: Message):
return await isAnAdmin(msg.from_user.id)
async def member_func(_, __, msg: Message):
return True if (msg.from_user.id in jsonLoad(path.join(configGet("cache", "locations"), "group_members"))) else False
async def allowed_func(_, __, msg: Message):
return True if (col_applications.find_one({"user": msg.from_user.id}) is not None) else False
async def enabled_general_func(_, __, msg: Message):
return configGet("enabled", "features", "general")
async def enabled_applications_func(_, __, msg: Message):
return configGet("enabled", "features", "applications")
async def enabled_sponsorships_func(_, __, msg: Message):
return configGet("enabled", "features", "sponsorships")
async def enabled_warnings_func(_, __, msg: Message):
return configGet("enabled", "features", "warnings")
async def enabled_invites_check_func(_, __, msg: Message):
return configGet("enabled", "features", "invites_check")
async def enabled_dinovoice_func(_, __, msg: Message):
return configGet("enabled", "features", "dinovoice")
async def filling_sponsorship_func(_, __, msg: Message):
return True if col_tmp.find_one({"user": msg.from_user.id, "type": "sponsorship"}) is not None else False
admin = filters.create(admin_func)
member = filters.create(member_func)
allowed = filters.create(allowed_func)
enabled_general = filters.create(enabled_general_func)
enabled_applications = filters.create(enabled_applications_func)
enabled_sponsorships = filters.create(enabled_sponsorships_func)
enabled_warnings = filters.create(enabled_warnings_func)
enabled_invites_check = filters.create(enabled_invites_check_func)
enabled_dinovoice = filters.create(enabled_dinovoice_func)
filling_sponsorship = filters.create(filling_sponsorship_func)

View File

@@ -1,4 +1,7 @@
from pymongo import MongoClient
"""Module that provides all database columns and
creates geospatial index for col_applications"""
from pymongo import MongoClient, GEOSPHERE
from ujson import loads
with open("config.json", "r", encoding="utf-8") as f:
@@ -25,14 +28,17 @@ db = db_client.get_database(name=db_config["name"])
collections = db.list_collection_names()
for collection in ["tmp", "users", "context", "messages", "warnings", "applications", "sponsorships"]:
for collection in ["tmp", "users", "context", "spoilers", "messages", "warnings", "applications", "sponsorships"]:
if not collection in collections:
db.create_collection(collection)
col_tmp = db.get_collection("tmp")
col_users = db.get_collection("users")
col_context = db.get_collection("context")
col_spoilers = db.get_collection("spoilers")
col_messages = db.get_collection("messages")
col_warnings = db.get_collection("warnings")
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,99 +1,161 @@
from typing import Literal
from dateutil.relativedelta import relativedelta
from datetime import datetime
from app import app
from pyrogram import filters
from pyrogram.types import ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.types import ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton, ForceReply, Message
from pyrogram.client import Client
from pyrogram.enums.parse_mode import ParseMode
from classes.holo_user import HoloUser
from modules.utils import all_locales, configGet, locale, logWrite
from modules.handlers.welcome import welcome_pass
from modules.database import col_tmp
from modules import custom_filters
# Confirmation =================================================================================================================
confirmation_1 = []
for pattern in all_locales("confirm", "keyboard"):
confirmation_1.append(pattern[0][0])
@app.on_message(~ filters.scheduled & filters.private & filters.command(confirmation_1, prefixes=[""]))
async def confirm_yes(app, msg):
@app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.private & filters.command(confirmation_1, prefixes=[""]))
async def confirm_yes(app: Client, msg: Message, kind: Literal["application", "sponsorship", "unknown"] = "unknown"):
holo_user = HoloUser(msg.from_user)
if (holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True):
if configGet("enabled", "features", "applications") is True:
await msg.reply_text(locale("application_sent", "message"), reply_markup=ReplyKeyboardRemove())
if (kind == "application") or ((holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True)):
tmp_application = col_tmp.find_one({"user": holo_user.id, "type": "application"})
tmp_application = col_tmp.find_one({"user": holo_user.id, "type": "application"})
if tmp_application is None:
logWrite(f"Application of {holo_user.id} is nowhere to be found.")
return
if tmp_application["sent"] is True:
return
await msg.reply_text(locale("application_sent", "message"), reply_markup=ReplyKeyboardRemove())
application_content = []
i = 1
for question in tmp_application['application']:
if i == 2:
age = relativedelta(datetime.now(), tmp_application['application']['2'])
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)")
elif i == 3:
if tmp_application['application']['3']['countryCode'] == "UA":
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['3']['name']}")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['3']['name']} ({tmp_application['application']['3']['adminName1']}, {tmp_application['application']['3']['countryName']})")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application'][question]}")
i += 1
if tmp_application["reapply"]:
await app.send_message(chat_id=configGet("admin", "groups"), 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_no", "button")), callback_data=f"reapply_no_{holo_user.id}")
]
]
)
)
else:
await app.send_message(chat_id=configGet("admin", "groups"), 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_no", "button")), callback_data=f"sub_no_{holo_user.id}")
],
[
InlineKeyboardButton(text=str(locale("sub_aggressive", "button")), callback_data=f"sub_aggressive_{holo_user.id}")
],
[
InlineKeyboardButton(text=str(locale("sub_russian", "button")), callback_data=f"sub_russian_{holo_user.id}")
]
]
)
)
logWrite(f"User {holo_user.id} sent his application and it will now be reviewed")
col_tmp.update_one({"user": holo_user.id, "type": "application"}, {"$set": {"sent": True}})
if tmp_application is None:
logWrite(f"Application of {holo_user.id} is nowhere to be found.")
return
application_content = []
i = 1
if configGet("enabled", "features", "sponsorships") is True:
for question in tmp_application['application']:
if (kind == "sponsorship") or ((holo_user.sponsorship_state()[0] == "fill") and (holo_user.sponsorship_state()[1] is True)):
if i == 2:
age = relativedelta(datetime.now(), tmp_application['application']['2'])
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)")
elif i == 3:
if tmp_application['application']['3']['countryCode'] == "UA":
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['3']['name']}")
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":
continue
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application']['3']['name']} ({tmp_application['application']['3']['adminName1']}, {tmp_application['application']['3']['countryName']})")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {tmp_application['application'][question]}")
sponsorship_content.append(f"{locale(f'question_{question}', 'message', 'sponsor_titles')} {tmp_sponsorship['sponsorship'][question]}")
i += 1
if tmp_application["reapply"]:
await app.send_message(chat_id=configGet("admin_group"), text=(locale("reapply_got", "message")).format(str(holo_user.id), msg.from_user.first_name, msg.from_user.last_name, msg.from_user.username, "\n".join(application_content)), parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup(
await app.send_cached_media(chat_id=configGet("admin", "groups"), photo=tmp_sponsorship["sponsorship"]["proof"], 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("reapply_yes", "button")), callback_data=f"reapply_yes_{holo_user.id}")
InlineKeyboardButton(text=str(locale("sponsor_yes", "button")), callback_data=f"sponsor_yes_{holo_user.id}")
],
[
InlineKeyboardButton(text=str(locale("reapply_no", "button")), callback_data=f"reapply_no_{holo_user.id}")
]
]
)
)
else:
await app.send_message(chat_id=configGet("admin_group"), text=(locale("application_got", "message")).format(str(holo_user.id), msg.from_user.first_name, msg.from_user.last_name, msg.from_user.username, "\n".join(application_content)), parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(text=str(locale("sub_yes", "button")), callback_data=f"sub_yes_{holo_user.id}")
],
[
InlineKeyboardButton(text=str(locale("sub_no", "button")), callback_data=f"sub_no_{holo_user.id}")
],
[
InlineKeyboardButton(text=str(locale("sub_aggressive", "button")), callback_data=f"sub_aggressive_{holo_user.id}")
],
[
InlineKeyboardButton(text=str(locale("sub_russian", "button")), callback_data=f"sub_russian_{holo_user.id}")
InlineKeyboardButton(text=str(locale("sponsor_no", "button")), callback_data=f"sponsor_no_{holo_user.id}")
]
]
)
)
logWrite(f"User {holo_user.id} sent his application and it will now be reviewed")
# remove(f"tmp{sep}{filename}.jpg")
col_tmp.update_one({"user": holo_user.id, "type": "application"}, {"$set": {"sent": True}})
logWrite(f"User {holo_user.id} sent his sponsorship application and it will now be reviewed")
# configSet(["sent"], True, file=str(holo_user.id))
# configSet(["confirmed"], True, file=str(holo_user.id))
col_tmp.update_one({"user": holo_user.id, "type": "sponsorship"}, {"$set": {"sent": True}})
return
confirmation_2 = []
for pattern in all_locales("confirm", "keyboard"):
confirmation_2.append(pattern[1][0])
@app.on_message(~ filters.scheduled & filters.private & filters.command(confirmation_2, prefixes=[""]))
async def confirm_no(app, msg):
@app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.private & filters.command(confirmation_2, prefixes=[""]))
async def confirm_no(app: Client, msg: Message, kind: Literal["application", "sponsorship", "unknown"] = "unknown"):
holo_user = HoloUser(msg.from_user)
if (holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True):
holo_user.application_restart()
await welcome_pass(app, msg, once_again=True)
logWrite(f"User {msg.from_user.id} restarted the application due to typo in it")
if configGet("enabled", "features", "applications") is True:
if (kind == "application") or ((holo_user.application_state()[0] == "fill") and (holo_user.application_state()[1] is True)):
holo_user.application_restart()
await welcome_pass(app, msg, once_again=True)
logWrite(f"User {msg.from_user.id} restarted the application due to typo in it")
return
if configGet("enabled", "features", "sponsorships") is True:
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,53 +1,54 @@
from dateutil.relativedelta import relativedelta
from datetime import datetime
from app import app, isAnAdmin
from app import app
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from modules.utils import locale, logWrite
from modules.database import col_applications
from classes.holo_user import HoloUser
from modules import custom_filters
# Contact getting ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.contact & filters.private)
async def get_contact(app, msg):
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.contact & filters.private & (custom_filters.allowed | custom_filters.admin))
async def get_contact(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user)
if holo_user.application_approved() or (await isAnAdmin(holo_user.id) is True):
if msg.contact.user_id != None:
if msg.contact.user_id != None:
application = col_applications.find_one({"user": msg.contact.user_id})
application = col_applications.find_one({"user": msg.contact.user_id})
if application is None:
logWrite(f"User {holo_user.id} requested application of {msg.contact.user_id} but user does not exists")
await msg.reply_text(locale("contact_invalid", "message", locale=holo_user.locale))
return
if application is None:
logWrite(f"User {holo_user.id} requested application of {msg.contact.user_id} but user does not exists")
await msg.reply_text(locale("contact_invalid", "message", locale=holo_user.locale))
return
application_content = []
i = 1
application_content = []
i = 1
for question in application['application']:
for question in application['application']:
if i == 2:
age = relativedelta(datetime.now(), application['application']['2'])
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)")
elif i == 3:
if application['application']['3']['countryCode'] == "UA":
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application']['3']['name']}")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})")
if i == 2:
age = relativedelta(datetime.now(), application['application']['2'])
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)")
elif i == 3:
if application['application']['3']['countryCode'] == "UA":
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application']['3']['name']}")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application'][question]}")
i += 1
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles', locale=holo_user.locale)} {application['application'][question]}")
i += 1
application_status = locale("application_status_accepted", "message", locale=holo_user.locale).format((await app.get_users(application["admin"])).first_name, application["date"].strftime("%d.%m.%Y, %H:%M"))
application_status = locale("application_status_accepted", "message", locale=holo_user.locale).format((await app.get_users(application["admin"])).first_name, application["date"].strftime("%d.%m.%Y, %H:%M"))
logWrite(f"User {holo_user.id} requested application of {msg.contact.user_id}")
await msg.reply_text(locale("contact", "message", locale=holo_user.locale).format(str(msg.contact.user_id), "\n".join(application_content), application_status))
logWrite(f"User {holo_user.id} requested application of {msg.contact.user_id}")
await msg.reply_text(locale("contact", "message", locale=holo_user.locale).format(str(msg.contact.user_id), "\n".join(application_content), application_status))
else:
logWrite(f"User {holo_user.id} requested application of someone but user is not telegram user")
await msg.reply_text(locale("contact_not_member", "message", locale=holo_user.locale))
else:
logWrite(f"User {holo_user.id} requested application of someone but user is not telegram user")
await msg.reply_text(locale("contact_not_member", "message", locale=holo_user.locale))
# ==============================================================================================================================

View File

@@ -1,10 +1,12 @@
from traceback import print_exc
from app import app, isAnAdmin
import asyncio
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.types import Message, ForceReply, InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.client import Client
from classes.holo_user import HoloUser
from modules.utils import configGet, logWrite
from modules.database import col_messages
from modules.utils import configGet, logWrite, locale, all_locales
from modules.database import col_messages, col_spoilers
async def message_involved(msg: Message) -> bool:
message = col_messages.find_one({"destination.id": msg.reply_to_message.id, "destination.chat": msg.reply_to_message.chat.id})
@@ -20,7 +22,7 @@ async def message_context(msg: Message) -> tuple:
# Any other input ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.private)
async def any_stage(app, msg):
async def any_stage(app: Client, msg: Message):
if msg.via_bot is None:
@@ -41,79 +43,115 @@ async def any_stage(app, msg):
photo=msg.photo,
video=msg.video,
file=msg.document,
animation=msg.animation,
voice=msg.voice,
adm_origin=await isAnAdmin(context_message.from_user.id),
adm_context=await isAnAdmin(msg.from_user.id)
)
# await msg.reply_text(locale("message_sent", "message"), quote=should_quote(msg))
# col_messages.insert_one({"origin": {"chat": msg.chat.id, "id": msg.id}, "destination": {"chat": new_message.chat.id, "id": new_message.id}})
return
await holo_user.application_next(msg.text, msg=msg)
if msg.text is not None:
# user_stage = configGet("stage", file=str(msg.from_user.id))
if configGet("enabled", "features", "applications") is True:
await holo_user.application_next(msg.text, msg=msg)
if configGet("enabled", "features", "sponsorships") is True:
await holo_user.sponsorship_next(msg.text, msg)
if msg.photo is not None:
await holo_user.sponsorship_next(msg.text, msg=msg, photo=msg.photo)
# if user_stage == 1:
# await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply"))))
# logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application")
# configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id))
# configSet(["stage"], user_stage+1, file=str(msg.from_user.id))
if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill":
# elif user_stage == 2:
spoiler = col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} )
# try:
if spoiler is None:
return
# configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id))
if spoiler["category"] is None:
# input_dt = datetime.strptime(msg.text, "%d.%m.%Y")
found = False
# if datetime.now() <= input_dt:
# logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to joking")
# await msg.reply_text(locale("question2_joke", "message"), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply"))))
# Find category in all locales
for lc in all_locales("spoiler_categories", "message"):
for key in lc:
if lc[key] == msg.text:
found = True
category = key
# elif ((datetime.now() - input_dt).days) < ((datetime.now() - datetime.now().replace(year=datetime.now().year - configGet("age_allowed"))).days):
# logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to being underage")
# await msg.reply_text(locale("question2_underage", "message").format(str(configGet("age_allowed"))), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply"))))
if found is False:
await msg.reply_text(locale("spoiler_incorrect_category", "message", locale=msg.from_user))
return
# else:
# logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application")
# await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply"))))
# configSet(["stage"], user_stage+1, file=str(msg.from_user.id))
col_spoilers.find_one_and_update( {"_id": spoiler["_id"]}, {"$set": {"category": category}} )
await msg.reply_text(locale("spoiler_send_description", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("spoiler_description", "force_reply", locale=msg.from_user)))
return
# except ValueError:
# logWrite(f"User {msg.from_user.id} failed stage {user_stage} due to sending invalid date format")
# await msg.reply_text(locale(f"question2_invalid", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage}", "force_reply"))))
if spoiler["description"] is None and (spoiler["photo"] is None and spoiler["video"] is None and spoiler["animation"] is None and spoiler["text"] is None):
# for lc in all_locales("spoiler_description", "keyboard"):
# if msg.text == lc[-1][0]:
# await msg.reply_text(locale("spoiler_description_enter", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("spoiler_description", "force_reply", locale=msg.from_user)))
# return
if msg.text != "-":
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"description": msg.text}} )
else:
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"description": ""}} )
logWrite(f"Adding description '{msg.text}' to {msg.from_user.id}'s spoiler")
await msg.reply_text(locale("spoiler_using_description", "message", locale=msg.from_user).format(msg.text), reply_markup=ForceReply(placeholder=locale("spoiler_content", "force_reply", locale=msg.from_user)))
return
ready = False
if msg.photo is not None:
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"photo": msg.photo.file_id, "caption": msg.caption, "completed": True}} )
logWrite(f"Adding photo with id {msg.photo.file_id} to {msg.from_user.id}'s spoiler")
ready = True
if msg.video is not None:
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"video": msg.video.file_id, "caption": msg.caption, "completed": True}} )
logWrite(f"Adding video with id {msg.video.file_id} to {msg.from_user.id}'s spoiler")
ready = True
if msg.animation is not None:
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"animation": msg.animation.file_id, "caption": msg.caption, "completed": True}} )
logWrite(f"Adding animation with id {msg.animation.file_id} to {msg.from_user.id}'s spoiler")
ready = True
if msg.document is not None:
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"document": msg.document.file_id, "caption": msg.caption, "completed": True}} )
logWrite(f"Adding document with id {msg.document.file_id} to {msg.from_user.id}'s spoiler")
ready = True
if spoiler["photo"] is None and spoiler["video"] is None and spoiler["animation"] is None and spoiler["document"] is None and spoiler["text"] is None:
if msg.text is not None:
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"text": msg.text, "completed": True}} )
logWrite(f"Adding text '{msg.text}' to {msg.from_user.id}'s spoiler")
ready = True
if ready is True:
await msg.reply_text(locale("spoiler_ready", "message", locale=msg.from_user), reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_send", "button", locale=msg.from_user), switch_inline_query=f"spoiler:{spoiler['_id'].__str__()}")]]))
else:
await msg.reply_text(locale("spoiler_incorrect_content", "message", locale=msg.from_user))
# else:
# if user_stage <= 9:
# logWrite(f"User {msg.from_user.id} completed stage {user_stage} of application")
# await msg.reply_text(locale(f"question{user_stage+1}", "message"), reply_markup=ForceReply(placeholder=str(locale(f"question{user_stage+1}", "force_reply"))))
# configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id))
# configSet(["stage"], user_stage+1, file=str(msg.from_user.id))
# else:
# if not configGet("sent", file=str(msg.from_user.id)):
# if not configGet("confirmed", file=str(msg.from_user.id)):
# configSet(["application", str(user_stage)], str(msg.text), file=str(msg.from_user.id))
# application_content = []
# i = 1
# for question in configGet("application", file=str(msg.from_user.id)):
# application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(msg.from_user.id))[question]}")
# i += 1
# await msg.reply_text(locale("confirm", "message").format("\n".join(application_content)), reply_markup=ReplyKeyboardMarkup(locale("confirm", "keyboard"), resize_keyboard=True))
# #configSet("sent", True, file=str(msg.from_user.id))
# #configSet("application_date", int(time()), file=str(msg.from_user.id))
# else:
# if not configGet("approved", file=str(msg.from_user.id)) and not configGet("rejected", file=str(msg.from_user.id)):
# await msg.reply_text(locale("already_sent", "message"))
# else:
# if not configGet("approved", file=str(msg.from_user.id)) and not configGet("rejected", file=str(msg.from_user.id)):
# await msg.reply_text(locale("already_sent", "message"))
@app.on_message(~ filters.scheduled & filters.group)
async def message_in_group(app, msg):
async def message_in_group(app: Client, msg: Message):
if (msg.chat is not None) and (msg.via_bot is not None):
if (msg.via_bot.id == configGet("bot_id")) and (msg.chat.id == configGet("destination_group")):
if (msg.via_bot.id == (await app.get_me()).id) and (msg.chat.id == configGet("users", "groups")):
if msg.text.startswith(locale("spoiler_described", "message").split()[0]) or msg.text.startswith(locale("spoiler_empty", "message").split()[0]):
logWrite(f"User {msg.from_user.id} sent spoiler to user's group")
try:
logWrite("Forwarding spoiler to admin's group")
await msg.copy(configGet("admin", "groups"), disable_notification=True)
except Exception as exp:
logWrite(f"Could not forward spoiler to admin's group due to '{exp}': {print_exc()}")
return
if configGet("remove_application_time") > 0:
logWrite(f"User {msg.from_user.id} requested application in destination group, removing in {configGet('remove_application_time')} minutes")
await asyncio.sleep(configGet("remove_application_time")*60)

View File

@@ -1,13 +1,15 @@
from app import app, isAnAdmin
from pyrogram.types import ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.types import ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton, ChatMemberUpdated
from pyrogram.client import Client
from modules.utils import configGet, locale
from modules.logging import logWrite
from classes.holo_user import HoloUser
from modules import custom_filters
# Filter users on join =========================================================================================================
@app.on_chat_member_updated(group=configGet("destination_group"))
#@app.on_message(filters.new_chat_members, group=configGet("destination_group"))
async def filter_join(app, member):
@app.on_chat_member_updated(custom_filters.enabled_invites_check, group=configGet("users", "groups"))
#@app.on_message(filters.new_chat_members, group=configGet("users", "groups"))
async def filter_join(app: Client, member: ChatMemberUpdated):
if member.invite_link != None:
@@ -23,7 +25,7 @@ async def filter_join(app, member):
logWrite(f"User {holo_user.id} joined destination group with stolen/unapproved link {holo_user.link}")
await app.send_message(configGet("admin_group"), locale("joined_false_link", "message").format(member.from_user.first_name, member.from_user.id), reply_markup=InlineKeyboardMarkup(
await app.send_message(configGet("admin", "groups"), locale("joined_false_link", "message").format(member.from_user.first_name, member.from_user.id), reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(text=str(locale("sus_allow", "button")), callback_data=f"sus_allow_{member.from_user.id}")

View File

@@ -1 +0,0 @@
from app import app

13
modules/handlers/voice.py Normal file
View File

@@ -0,0 +1,13 @@
from random import choice
from app import app
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from modules.logging import logWrite
from modules.utils import configGet, locale
from modules import custom_filters
@app.on_message(custom_filters.enabled_dinovoice & ~filters.scheduled & filters.voice & filters.chat(configGet("users", "groups")))
async def voice_message(app: Client, msg: Message):
logWrite(f"User {msg.from_user.id} sent voice message in destination group")
await msg.reply_text(choice(locale("voice_message", "message")))

View File

@@ -1,7 +1,10 @@
from app import app
from pyrogram import filters
from pyrogram.types import ForceReply, ReplyKeyboardMarkup
from pyrogram.types import ForceReply, ReplyKeyboardMarkup, Message
from pyrogram.client import Client
from classes.holo_user import HoloUser
from modules.utils import all_locales, locale, logWrite
from modules import custom_filters
# Welcome check ================================================================================================================
welcome_1 = []
@@ -9,8 +12,8 @@ for pattern in all_locales("welcome", "keyboard"):
welcome_1.append(pattern[0][0])
for pattern in all_locales("return", "keyboard"):
welcome_1.append(pattern[0][0])
@app.on_message(~ filters.scheduled & filters.private & filters.command(welcome_1, prefixes=[""]))
async def welcome_pass(app, msg, once_again: bool = True) -> None:
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(welcome_1, prefixes=[""]))
async def welcome_pass(app: Client, msg: Message, once_again: bool = True) -> None:
"""Set user's stage to 1 and start a fresh application
### Args:
@@ -21,17 +24,19 @@ async def welcome_pass(app, msg, once_again: bool = True) -> None:
if not once_again:
await msg.reply_text(locale("privacy_notice", "message"))
holo_user = HoloUser(msg.from_user)
holo_user.application_restart()
logWrite(f"User {msg.from_user.id} confirmed starting the application")
await msg.reply_text(locale("question1", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("question1", "force_reply", locale=msg.from_user)))
# configSet(["stage"], 1, file=str(msg.from_user.id))
# configSet(["sent"], False, file=str(msg.from_user.id))
welcome_2 = []
for pattern in all_locales("welcome", "keyboard"):
welcome_2.append(pattern[1][0])
@app.on_message(~ filters.scheduled & filters.private & filters.command(welcome_2, prefixes=[""]))
async def welcome_reject(app, msg):
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(welcome_2, prefixes=[""]))
async def welcome_reject(app: Client, msg: Message):
logWrite(f"User {msg.from_user.id} rejected to start the application")
await msg.reply_text(locale("goodbye", "message", locale=msg.from_user), reply_markup=ReplyKeyboardMarkup(locale("return", "keyboard", locale=msg.from_user), resize_keyboard=True))

View File

@@ -1,16 +1,54 @@
"""Module responsible for providing answers to
all inline queries that bot receives"""
from datetime import datetime
from os import path, sep
from app import app, isAnAdmin
from pyrogram.types import InlineQueryResultArticle, InputTextMessageContent
from pyrogram.types import InlineQueryResultArticle, InputTextMessageContent, InlineQuery, InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.client import Client
from pyrogram.enums.chat_type import ChatType
from pyrogram.enums.chat_members_filter import ChatMembersFilter
from dateutil.relativedelta import relativedelta
from classes.holo_user import HoloUser, UserInvalidError, UserNotFoundError
from classes.errors.holo_user import UserNotFoundError, UserInvalidError
from classes.holo_user import HoloUser
from modules.utils import configGet, locale
from modules.database import col_applications
from modules.database import col_applications, col_spoilers
from bson.objectid import ObjectId
from bson.errors import InvalidId
@app.on_inline_query()
async def inline_answer(client, inline_query):
async def inline_answer(client: Client, inline_query: InlineQuery):
results = []
if inline_query.query.startswith("spoiler:"):
try:
spoil = col_spoilers.find_one( {"_id": ObjectId(inline_query.query.removeprefix("spoiler:"))} )
if spoil is not None:
desc = locale("spoiler_empty", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories")) if spoil["description"] == "" else locale("spoiler_described", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), spoil["description"])
results = [
InlineQueryResultArticle(
title=locale("title", "inline", "spoiler", locale=inline_query.from_user),
description=locale("description", "inline", "spoiler", locale=inline_query.from_user),
input_message_content=InputTextMessageContent(desc, disable_web_page_preview=True),
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=inline_query.from_user), callback_data=f'sid_{inline_query.query.removeprefix("spoiler:")}')]])
)
]
except InvalidId:
results = []
await inline_query.answer(
results=results
)
return
if inline_query.chat_type in [ChatType.CHANNEL]:
await inline_query.answer(
@@ -44,11 +82,11 @@ async def inline_answer(client, inline_query):
if holo_user.application_approved() or (await isAnAdmin(holo_user.id) is True):
list_of_users = []
async for m in app.get_chat_members(configGet("destination_group"), limit=configGet("inline_preview_count"), filter=ChatMembersFilter.SEARCH, query=inline_query.query):
list_of_users.append(m)
max_results = configGet("inline_preview_count") if inline_query.query != "" else 200
results = []
list_of_users = []
async for m in app.get_chat_members(configGet("users", "groups"), limit=max_results, filter=ChatMembersFilter.SEARCH, query=inline_query.query):
list_of_users.append(m)
for match in list_of_users:

View File

@@ -1,39 +1,222 @@
"""Automatically register commands and execute
some scheduled tasks is the main idea of this module"""
from os import listdir, makedirs, path, sep
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from datetime import datetime
from datetime import datetime, timedelta
from ujson import dumps
from app import app
from modules.utils import configGet, locale, logWrite
from pyrogram.types import BotCommand, BotCommandScopeChat
from pyrogram.errors import bad_request_400
from pyrogram.enums.chat_members_filter import ChatMembersFilter
from classes.holo_user import HoloUser
from modules.utils import configGet, jsonSave, locale, logWrite
from dateutil.relativedelta import relativedelta
from modules.database import col_applications
from modules.database import col_applications, col_sponsorships
scheduler = AsyncIOScheduler()
# for user_file in listdir(f"{configGet('data', 'locations')}{sep}users{sep}"):
# filename = fsdecode(f"{configGet('data', 'locations')}{sep}users{sep}{user_file}")
# if filename.endswith(".json"):
# user = jsonLoad(filename)
# if isinstance(user["application"]["2"], str):
# try:
# if ".".join([((user["application"]["2"]).split("."))[0], ((user["application"]["2"]).split("."))[1]]) == datetime.now().strftime("%d.%m"):
# tg_user = await app.get_users(int(user_file.replace(".json", "")))
# await app.send_message( configGet("admin_group"), locale("birthday", "message").format(str(tg_user.first_name), str(tg_user.username), str(relativedelta(datetime.now(), datetime.strptime(user["application"]["2"], '%d.%m.%Y')).years)) )
# except AttributeError:
# continue
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("users", "groups")):
list_of_users.append(member.user.id)
makedirs("cache", exist_ok=True)
jsonSave(list_of_users, path.join(configGet("cache", "locations"), "group_members"))
if configGet("enabled", "scheduler", "birthdays"):
@scheduler.scheduled_job(trigger="cron", hour=configGet("time", "scheduler", "birthdays"))
async def check_birthdays():
for entry in col_applications.find():
if entry["application"]["2"].strftime("%d.%m") == datetime.now().strftime("%d.%m"):
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", "groups")):
list_of_users.append(member.user.id)
makedirs("cache", exist_ok=True)
jsonSave(list_of_users, path.join(configGet("cache", "locations"), "admins"))
# Cache the avatars of group members
if configGet("enabled", "scheduler", "cache_avatars"):
@scheduler.scheduled_job(trigger="date", run_date=datetime.now()+timedelta(seconds=10))
@scheduler.scheduled_job(trigger="interval", hours=configGet("interval", "scheduler", "cache_avatars"))
async def cache_avatars():
list_of_users = []
async for member in app.get_chat_members(configGet("users", "groups"), filter=ChatMembersFilter.SEARCH, query=""):
list_of_users.append(member.user)
for user in list_of_users:
if user.photo != None:
if not path.exists(f'{configGet("cache", "locations")}{sep}avatars{sep}{user.photo.big_file_id}'):
print(f'Pre-cached avatar {user.photo.big_file_id} of {user.id}', flush=True)
await app.download_media(user.photo.big_file_id, file_name=path.join(configGet("cache", "locations"), "avatars", user.photo.big_file_id))
logWrite("Avatars caching performed")
# Check for birthdays
if configGet("enabled", "features", "applications") is True:
if configGet("enabled", "scheduler", "birthdays") is True:
@scheduler.scheduled_job(trigger="cron", hour=configGet("time", "scheduler", "birthdays"))
async def check_birthdays():
for entry in col_applications.find():
if entry["application"]["2"].strftime("%d.%m") == datetime.now().strftime("%d.%m"):
try:
tg_user = await app.get_users(entry["user"])
await app.send_message( configGet("admin", "groups"), locale("birthday", "message").format(str(tg_user.first_name), str(tg_user.username), str(relativedelta(datetime.now(), entry["application"]["2"], '%d.%m.%Y').years)) ) # type: ignore
logWrite(f"Notified admins about {entry['user']}'s birthday")
except Exception as exp:
logWrite(f"Could not find user {entry['user']} to send a message about birthday due to '{exp}'")
continue
logWrite("Birthdays check performed")
# Check for expired sponsorships
if configGet("enabled", "features", "sponsorships") is True:
if configGet("enabled", "scheduler", "sponsorships") is True:
@scheduler.scheduled_job(trigger="cron", hour=configGet("time", "scheduler", "sponsorships"))
async def check_sponsors():
for entry in col_sponsorships.find({"sponsorship.expires": {"$lt": datetime.now()+timedelta(days=2)}}):
try:
tg_user = await app.get_users(entry["user"])
await app.send_message( configGet("admin_group"), locale("birthday", "message").format(str(tg_user.first_name), str(tg_user.username), str(relativedelta(datetime.now(), entry["application"]["2"], '%d.%m.%Y').years)) ) # type: ignore
logWrite(f"Notified admins about {entry['user']}'s birthday")
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']} to send a message about birthday due to '{exp}'")
logWrite(f"Could not find user {entry['user']} notify about sponsorship expiry due to '{exp}'")
continue
logWrite("Birthdays check performed")
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("users", "groups"))
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")
if configGet("enabled", "scheduler", "sponsorships"):
@scheduler.scheduled_job(trigger="cron", hour=configGet("time", "scheduler", "sponsorships"))
async def check_sponsors():
logWrite("Sponsorships check performed")
# Register all bot commands
@scheduler.scheduled_job(trigger="date", run_date=datetime.now()+timedelta(seconds=3))
async def commands_register():
commands = {
"users": [],
"admins": [],
"owner": [],
"group_users": [],
"group_admins": [],
"locales": {}
}
commands_raw = {
"users": [],
"admins": [],
"owner": [],
"group_users": [],
"group_admins": [],
"locales": {}
}
valid_locales = []
files_locales = listdir(f'{configGet("locale", "locations")}')
for entry in files_locales:
if entry.endswith(".json"):
valid_locales.append(".".join(entry.split(".")[:-1]))
commands["locales"][".".join(entry.split(".")[:-1])] = {
"users": [],
"admins": [],
"owner": [],
"group_users": [],
"group_admins": []
}
if configGet("debug") is True:
commands_raw["locales"][".".join(entry.split(".")[:-1])] = {
"users": [],
"admins": [],
"owner": [],
"group_users": [],
"group_admins": []
}
config_modules = configGet("features")
config_commands = configGet("commands")
for command in config_commands:
enabled = False
for module in config_commands[command]["modules"]:
if config_modules[module]["enabled"] is True:
enabled = True
if enabled is False:
if configGet("debug") is True:
logWrite(f"Not registering {command} at all")
continue
for permission in config_commands[command]["permissions"]:
commands[permission].append(BotCommand(command, locale("commands")[command]))
if configGet("debug") is True:
commands_raw[permission].append({f"{command}": locale("commands")[command]})
logWrite(f"Registering {command} for {permission}")
for lc in valid_locales:
commands["locales"][lc][permission].append(BotCommand(command, locale("commands", locale=lc)[command]))
if configGet("debug") is True:
commands_raw["locales"][lc][permission].append({f"{command}": locale("commands", locale=lc)[command]})
logWrite(f"Registering {command} for {permission} [{lc}]")
# Registering user commands
await app.set_bot_commands(commands["users"])
logWrite("Registered user commands for default locale")
# Registering user commands for each locale
for lc in valid_locales:
await app.set_bot_commands(commands["locales"][lc]["users"], language_code=lc)
logWrite(f"Registered user commands for locale {lc}")
# Registering admin commands
for admin in configGet("admins"):
try:
await app.set_bot_commands(commands["admins"]+commands["users"], scope=BotCommandScopeChat(chat_id=admin))
logWrite(f"Registered admin commands for admin {admin}")
except bad_request_400.PeerIdInvalid:
pass
# Registering owner commands
try:
await app.set_bot_commands(commands["admins"]+commands["owner"]+commands["users"], scope=BotCommandScopeChat(chat_id=configGet("owner")))
for lc in valid_locales:
await app.set_bot_commands(commands["locales"][lc]["admins"]+commands["locales"][lc]["owner"]+commands["locales"][lc]["users"], scope=BotCommandScopeChat(chat_id=configGet("owner")))
logWrite(f"Registered admin commands for owner {configGet('owner')}")
except bad_request_400.PeerIdInvalid:
pass
# Registering admin group commands
try:
await app.set_bot_commands(commands["group_admins"], scope=BotCommandScopeChat(chat_id=configGet("admin", "groups")))
logWrite("Registered admin group commands for default locale")
except bad_request_400.ChannelInvalid:
logWrite(f"Could not register commands for admin group. Bot is likely not in the group.")
# Registering destination group commands
try:
await app.set_bot_commands(commands["group_users"], scope=BotCommandScopeChat(chat_id=configGet("users", "groups")))
logWrite("Registered destination group commands")
except bad_request_400.ChannelInvalid:
logWrite(f"Could not register commands for destination group. Bot is likely not in the group.")
if configGet("debug") is True:
print(commands, flush=True)
logWrite(f"Complete commands registration:\n{dumps(commands_raw, indent=4, ensure_ascii=False, encode_html_chars=False)}")

View File

@@ -1,4 +1,6 @@
from typing import Any, Union
from typing import Any, Literal, Union
from uuid import uuid1
from requests import get
from pyrogram.enums.chat_type import ChatType
from pyrogram.types import User
from pyrogram.client import Client
@@ -7,9 +9,10 @@ from ujson import JSONDecodeError as JSONDecodeError
from ujson import loads, dumps
from sys import exit
from os import kill, listdir, sep
from os import kill, listdir, makedirs, path, sep
from os import name as osname
from traceback import print_exc
from classes.errors.geo import PlaceNotFoundError
from modules.logging import logWrite
@@ -171,6 +174,61 @@ def all_locales(key: str, *args: str) -> list:
return output
def find_location(query: str) -> dict:
"""Find location on geonames.org by query. Search is made with feature classes A and P.
### Args:
* query (`str`): Some city/village/state name
### Raises:
* PlaceNotFoundError: Exception is raised when API result is empty
### Returns:
* `dict`: One instance of geonames response
"""
try:
result = (get(f"http://api.geonames.org/searchJSON?q={query}&maxRows=1&countryBias=UA&lang=uk&orderby=relevance&featureClass=P&featureClass=A&username={configGet('username', 'geocoding')}")).json()
return result["geonames"][0]
except (ValueError, KeyError, IndexError):
raise PlaceNotFoundError(query)
def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", "video"], None] = None) -> str:
"""Create temporary file to help uploading it
### Args:
* bytedata (`Union[bytes, bytearray]`): Some bytes to be written
* kind (`Union[Literal["image", "video"], None]`): Kind of upload. Will add `.jpg` or `.mp4` if needed
### Returns:
* `str`: Path to temporary file
"""
filename = str(uuid1())
if kind == "image":
filename += ".jpg"
elif kind == "video":
filename += ".mp4"
makedirs("tmp", exist_ok=True)
with open(path.join("tmp", filename), "wb") as file:
file.write(bytedata)
return path.join("tmp", filename)
async def download_tmp(app: Client, file_id: str) -> bytes:
"""Download file by its ID and return its bytes
### Args:
* app (`Client`): App that will download the file
* file_id (`str`): File's unique id
### Returns:
* `bytes`: Bytes of downloaded file
"""
filename = str(uuid1())
makedirs("tmp", exist_ok=True)
await app.download_media(file_id, path.join("tmp", filename))
with open(path.join("tmp", filename), "rb") as f:
bytedata = f.read()
return bytedata
try:
from psutil import Process
except ModuleNotFoundError:

View File

@@ -2,9 +2,9 @@ APScheduler==3.9.1.post1
fastapi==0.88.0
psutil==5.9.4
pymongo==4.3.3
Pyrogram==2.0.69
Pyrogram~=2.0.93
requests==2.28.1
tgcrypto==1.2.5
python_dateutil==2.8.2
starlette==0.22.0
starlette==0.23.0
ujson==5.6.0

View File

@@ -1,6 +1,87 @@
{
"$jsonSchema": {
"required": [],
"properties": {}
"required": [
"user",
"date",
"admin",
"application",
"application.1",
"application.2",
"application.3",
"application.3.name",
"application.3.adminName1",
"application.3.countryCode",
"application.3.countryName",
"application.3.location",
"application.4",
"application.5",
"application.6",
"application.7",
"application.8",
"application.9",
"application.10"
],
"properties": {
"user": {
"bsonType": ["int", "long"],
"description": "Telegram ID of user"
},
"date": {
"bsonType": "date",
"description": "Date when application was accepted"
},
"admin": {
"bsonType": ["int", "long"],
"description": "Telegram ID of admin that accepted the application"
},
"application": {
"bsonType": "object"
},
"application.1": {
"bsonType": "string"
},
"application.2": {
"bsonType": "date"
},
"application.3": {
"bsonType": "object"
},
"application.3.name": {
"bsonType": "string"
},
"application.3.adminName1": {
"bsonType": "string"
},
"application.3.countryCode": {
"bsonType": "string"
},
"application.3.countryName": {
"bsonType": "string"
},
"application.3.location": {
"bsonType": "array"
},
"application.4": {
"bsonType": "string"
},
"application.5": {
"bsonType": "string"
},
"application.6": {
"bsonType": "string"
},
"application.7": {
"bsonType": "string"
},
"application.8": {
"bsonType": "string"
},
"application.9": {
"bsonType": "string"
},
"application.10": {
"bsonType": "string"
}
}
}
}

59
validation/spoilers.json Normal file
View File

@@ -0,0 +1,59 @@
{
"$jsonSchema": {
"required": [
"user",
"completed",
"category",
"description",
"photo",
"video",
"animation",
"document",
"caption",
"text"
],
"properties": {
"user": {
"bsonType": ["int", "long"],
"description": "Telegram ID of user"
},
"completed": {
"bsonType": "bool",
"description": "Whether spoiler is a completed one"
},
"category": {
"bsonType": ["string", "null"],
"enum": ["nsfw", "deanon", "other"],
"description": "Spoiler's category"
},
"description": {
"bsonType": ["string", "null"],
"description": "Spoiler's description"
},
"photo": {
"bsonType": ["string", "null"],
"description": "Spoilered photo"
},
"video": {
"bsonType": ["string", "null"],
"description": "Spoilered video"
},
"animation": {
"bsonType": ["string", "null"],
"description": "Spoilered animation/GIF"
},
"document": {
"bsonType": ["string", "null"],
"description": "Spoilered document/file"
},
"caption": {
"bsonType": ["string", "null"],
"description": "Spoilered caption for media"
},
"text": {
"bsonType": ["string", "null"],
"description": "Spoilered text"
}
}
}
}

View File

@@ -1,6 +1,43 @@
{
"$jsonSchema": {
"required": [],
"properties": {}
"required": [
"user",
"date",
"admin",
"sponsorship",
"sponsorship.streamer",
"sponsorship.expires",
"sponsorship.proof",
"sponsorship.label"
],
"properties": {
"user": {
"bsonType": ["int", "long"],
"description": "Telegram ID of user"
},
"date": {
"bsonType": "date",
"description": "Date when sponsorship was accepted"
},
"admin": {
"bsonType": ["int", "long"],
"description": "Telegram ID of admin that accepted the sponsorship"
},
"sponsorship": {
"bsonType": "object"
},
"sponsorship.streamer": {
"bsonType": "string"
},
"sponsorship.expires": {
"bsonType": "date"
},
"sponsorship.proof": {
"bsonType": "string"
},
"sponsorship.label": {
"bsonType": "string"
}
}
}
}

6
validation/tmp.json Normal file
View File

@@ -0,0 +1,6 @@
{
"$jsonSchema": {
"required": [],
"properties": {}
}
}

View File

@@ -1,6 +1,28 @@
{
"$jsonSchema": {
"required": [],
"properties": {}
"required": [
"user",
"admin",
"date",
"reason"
],
"properties": {
"user": {
"bsonType": ["int", "long"],
"description": "Telegram ID of user"
},
"admin": {
"bsonType": ["int", "long"],
"description": "Telegram ID of admin"
},
"date": {
"bsonType": "date",
"description": "Date and time of getting"
},
"reason": {
"bsonType": "string",
"description": "Broken rule or admin's comment"
}
}
}
}