chore(userbot): apply ruff check --fix and ruff format

- ruff check --fix: 210 auto-fixed errors (import sorting, trailing
  whitespace, unused imports, f-string fixups, deprecated annotations)
- ruff format: 104 files reformatted to consistent style
- 268 non-auto-fixable issues remain (S113 requests timeout, etc.)
This commit is contained in:
2026-06-19 12:19:01 +02:00
parent 95cec59263
commit 7ba6bc44f2
104 changed files with 4338 additions and 5319 deletions
+18 -19
View File
@@ -1,32 +1,31 @@
import os
import environs
env = environs.Env()
try:
env.read_env("./.env")
env.read_env('./.env')
except FileNotFoundError:
print("No .env file found, using os.environ.")
print('No .env file found, using os.environ.')
api_id = int(os.getenv("API_ID", env.int("API_ID")))
api_hash = os.getenv("API_HASH", env.str("API_HASH"))
api_id = int(os.getenv('API_ID', env.int('API_ID')))
api_hash = os.getenv('API_HASH', env.str('API_HASH'))
STRINGSESSION = os.getenv("STRINGSESSION", env.str("STRINGSESSION"))
STRINGSESSION = os.getenv('STRINGSESSION', env.str('STRINGSESSION'))
second_session = os.getenv("SECOND_SESSION", env.str("SECOND_SESSION", ""))
second_session = os.getenv('SECOND_SESSION', env.str('SECOND_SESSION', ''))
db_type = os.getenv("DATABASE_TYPE", env.str("DATABASE_TYPE"))
db_url = os.getenv("DATABASE_URL", env.str("DATABASE_URL", ""))
db_name = os.getenv("DATABASE_NAME", env.str("DATABASE_NAME"))
db_type = os.getenv('DATABASE_TYPE', env.str('DATABASE_TYPE'))
db_url = os.getenv('DATABASE_URL', env.str('DATABASE_URL', ''))
db_name = os.getenv('DATABASE_NAME', env.str('DATABASE_NAME'))
apiflash_key = os.getenv("APIFLASH_KEY", env.str("APIFLASH_KEY"))
rmbg_key = os.getenv("RMBG_KEY", env.str("RMBG_KEY", ""))
vt_key = os.getenv("VT_KEY", env.str("VT_KEY", ""))
gemini_key = os.getenv("GEMINI_KEY", env.str("GEMINI_KEY", ""))
cohere_key = os.getenv("COHERE_KEY", env.str("COHERE_KEY", ""))
apiflash_key = os.getenv('APIFLASH_KEY', env.str('APIFLASH_KEY'))
rmbg_key = os.getenv('RMBG_KEY', env.str('RMBG_KEY', ''))
vt_key = os.getenv('VT_KEY', env.str('VT_KEY', ''))
gemini_key = os.getenv('GEMINI_KEY', env.str('GEMINI_KEY', ''))
cohere_key = os.getenv('COHERE_KEY', env.str('COHERE_KEY', ''))
pm_limit = int(os.getenv("PM_LIMIT", env.int("PM_LIMIT", 4)))
pm_limit = int(os.getenv('PM_LIMIT', env.int('PM_LIMIT', 4)))
test_server = bool(os.getenv("TEST_SERVER", env.bool("TEST_SERVER", False)))
modules_repo_branch = os.getenv(
"MODULES_REPO_BRANCH", env.str("MODULES_REPO_BRANCH", "master")
)
test_server = bool(os.getenv('TEST_SERVER', env.bool('TEST_SERVER', False)))
modules_repo_branch = os.getenv('MODULES_REPO_BRANCH', env.str('MODULES_REPO_BRANCH', 'master'))
+14 -20
View File
@@ -14,14 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import asyncio
from collections import OrderedDict
from pyrogram import Client, filters, types
from pyrogram.handlers import MessageHandler
from pyrogram.enums.parse_mode import ParseMode
import asyncio
from typing import Union, List, Dict, Optional
from pyrogram.handlers import MessageHandler
class _TrueFilter(filters.Filter):
@@ -30,12 +28,12 @@ class _TrueFilter(filters.Filter):
class Conversation:
_locks: Dict[int, asyncio.Lock] = {}
_locks: dict[int, asyncio.Lock] = {}
def __init__(
self,
client: Client,
chat: Union[str, int],
chat: str | int,
timeout: float = 5,
delete_at_end=True,
exclusive=True,
@@ -49,10 +47,10 @@ class Conversation:
self._chat_id = 0
self._message_ids = []
self._handler_object = None
self._chat_unique_lock: Optional[asyncio.Lock] = None
self._waiters: Dict[asyncio.Event, filters.Filter] = {}
self._responses: Dict[asyncio.Event, types.Message] = {}
self._pending_updates: List[types.Message] = []
self._chat_unique_lock: asyncio.Lock | None = None
self._waiters: dict[asyncio.Event, filters.Filter] = {}
self._responses: dict[asyncio.Event, types.Message] = {}
self._pending_updates: list[types.Message] = []
async def __aenter__(self):
self._chat_id = (await self.client.get_chat(self.chat)).id
@@ -65,9 +63,7 @@ class Conversation:
if self.exclusive:
await self._chat_unique_lock.acquire()
self._handler_object = MessageHandler(
self._handler, filters.chat(self._chat_id)
)
self._handler_object = MessageHandler(self._handler, filters.chat(self._chat_id))
if -999 not in self.client.dispatcher.groups:
new_groups = OrderedDict(self.client.dispatcher.groups)
@@ -101,7 +97,7 @@ class Conversation:
async def get_response(
self,
message_filter: Optional[filters.Filter] = None,
message_filter: filters.Filter | None = None,
timeout: float = None,
) -> types.Message:
if timeout is None:
@@ -119,15 +115,13 @@ class Conversation:
self._message_ids.append(message.id)
return message
async def _wait_message(
self, message_filter: Optional[filters.Filter], timeout: float
) -> types.Message:
async def _wait_message(self, message_filter: filters.Filter | None, timeout: float) -> types.Message:
event = asyncio.Event()
self._waiters[event] = message_filter
try:
await asyncio.wait_for(event.wait(), timeout=timeout)
except asyncio.TimeoutError as e:
except TimeoutError as e:
raise TimeoutError from e
finally:
self._waiters.pop(event)
@@ -137,8 +131,8 @@ class Conversation:
async def send_message(
self,
text: str,
parse_mode: Optional[str] = ParseMode.HTML,
entities: List[types.MessageEntity] = None,
parse_mode: str | None = ParseMode.HTML,
entities: list[types.MessageEntity] = None,
disable_web_page_preview: bool = None,
disable_notification: bool = None,
reply_to_message_id: int = None,
+48 -48
View File
@@ -14,16 +14,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import re
import json
import threading
import re
import sqlite3
from dns import resolver
import threading
import pymongo
from dns import resolver
from utils import config
resolver.default_resolver = resolver.Resolver(configure=False)
resolver.default_resolver.nameservers = ["1.1.1.1"]
resolver.default_resolver.nameservers = ['1.1.1.1']
class Database:
@@ -55,26 +57,24 @@ class MongoDatabase(Database):
def set(self, module: str, variable: str, value):
if not isinstance(module, str) or not isinstance(variable, str):
raise ValueError("Module and variable must be strings")
self._database[module].replace_one(
{"var": variable}, {"var": variable, "val": value}, upsert=True
)
raise ValueError('Module and variable must be strings')
self._database[module].replace_one({'var': variable}, {'var': variable, 'val': value}, upsert=True)
def get(self, module: str, variable: str, default=None):
if not isinstance(module, str) or not isinstance(variable, str):
raise ValueError("Module and variable must be strings")
doc = self._database[module].find_one({"var": variable})
return default if doc is None else doc["val"]
raise ValueError('Module and variable must be strings')
doc = self._database[module].find_one({'var': variable})
return default if doc is None else doc['val']
def get_collection(self, module: str):
if not isinstance(module, str):
raise ValueError("Module must be a string")
return {item["var"]: item["val"] for item in self._database[module].find()}
raise ValueError('Module must be a string')
return {item['var']: item['val'] for item in self._database[module].find()}
def remove(self, module: str, variable: str):
if not isinstance(module, str) or not isinstance(variable, str):
raise ValueError("Module and variable must be strings")
self._database[module].delete_one({"var": variable})
raise ValueError('Module and variable must be strings')
self._database[module].delete_one({'var': variable})
def close(self):
self._client.close()
@@ -82,27 +82,27 @@ class MongoDatabase(Database):
def add_chat_history(self, user_id, message):
chat_history = self.get_chat_history(user_id, default=[])
chat_history.append(message)
self.set(f"core.cohere.user_{user_id}", "chat_history", chat_history)
self.set(f'core.cohere.user_{user_id}', 'chat_history', chat_history)
def get_chat_history(self, user_id, default=None):
if default is None:
default = []
return self.get(f"core.cohere.user_{user_id}", "chat_history", default=[])
return self.get(f'core.cohere.user_{user_id}', 'chat_history', default=[])
def addaiuser(self, user_id):
chatai_users = self.get("core.chatbot", "chatai_users", default=[])
chatai_users = self.get('core.chatbot', 'chatai_users', default=[])
if user_id not in chatai_users:
chatai_users.append(user_id)
self.set("core.chatbot", "chatai_users", chatai_users)
self.set('core.chatbot', 'chatai_users', chatai_users)
def remaiuser(self, user_id):
chatai_users = self.get("core.chatbot", "chatai_users", default=[])
chatai_users = self.get('core.chatbot', 'chatai_users', default=[])
if user_id in chatai_users:
chatai_users.remove(user_id)
self.set("core.chatbot", "chatai_users", chatai_users)
self.set('core.chatbot', 'chatai_users', chatai_users)
def getaiusers(self):
return self.get("core.chatbot", "chatai_users", default=[])
return self.get('core.chatbot', 'chatai_users', default=[])
class SqliteDatabase(Database):
@@ -114,25 +114,25 @@ class SqliteDatabase(Database):
@staticmethod
def _parse_row(row: sqlite3.Row):
if row["type"] == "bool":
return row["val"] == "1"
if row["type"] == "int":
return int(row["val"])
if row["type"] == "str":
return row["val"]
return json.loads(row["val"])
if row['type'] == 'bool':
return row['val'] == '1'
if row['type'] == 'int':
return int(row['val'])
if row['type'] == 'str':
return row['val']
return json.loads(row['val'])
def _execute(self, module: str, *args, **kwargs) -> sqlite3.Cursor:
pattern = r"^(core|custom)"
pattern = r'^(core|custom)'
if not re.match(pattern, module):
raise ValueError(f"Invalid module name format: {module}")
raise ValueError(f'Invalid module name format: {module}')
self._lock.acquire()
try:
cursor = self._conn.cursor()
return cursor.execute(*args, **kwargs)
except sqlite3.OperationalError as e:
if str(e).startswith("no such table"):
if str(e).startswith('no such table'):
sql = f"""
CREATE TABLE IF NOT EXISTS '{module}' (
var TEXT UNIQUE NOT NULL,
@@ -165,17 +165,17 @@ class SqliteDatabase(Database):
"""
if isinstance(value, bool):
val = "1" if value else "0"
typ = "bool"
val = '1' if value else '0'
typ = 'bool'
elif isinstance(value, str):
val = value
typ = "str"
typ = 'str'
elif isinstance(value, int):
val = str(value)
typ = "int"
typ = 'int'
else:
val = json.dumps(value)
typ = "json"
typ = 'json'
self._execute(module, sql, (variable, val, typ, val, typ, variable))
self._conn.commit()
@@ -188,16 +188,16 @@ class SqliteDatabase(Database):
self._conn.commit()
def get_collection(self, module: str) -> dict:
pattern = r"^(core|custom)"
pattern = r'^(core|custom)'
if not re.match(pattern, module):
raise ValueError(f"Invalid module name format: {module}")
raise ValueError(f'Invalid module name format: {module}')
sql = f"SELECT * FROM '{module}'"
cur = self._execute(module, sql)
collection = {}
for row in cur:
collection[row["var"]] = self._parse_row(row)
collection[row['var']] = self._parse_row(row)
return collection
@@ -208,30 +208,30 @@ class SqliteDatabase(Database):
def add_chat_history(self, user_id, message):
chat_history = self.get_chat_history(user_id, default=[])
chat_history.append(message)
self.set(f"core.cohere.user_{user_id}", "chat_history", chat_history)
self.set(f'core.cohere.user_{user_id}', 'chat_history', chat_history)
def get_chat_history(self, user_id, default=None):
if default is None:
default = []
return self.get(f"core.cohere.user_{user_id}", "chat_history", default=[])
return self.get(f'core.cohere.user_{user_id}', 'chat_history', default=[])
def addaiuser(self, user_id):
chatai_users = self.get("core.chatbot", "chatai_users", default=[])
chatai_users = self.get('core.chatbot', 'chatai_users', default=[])
if user_id not in chatai_users:
chatai_users.append(user_id)
self.set("core.chatbot", "chatai_users", chatai_users)
self.set('core.chatbot', 'chatai_users', chatai_users)
def remaiuser(self, user_id):
chatai_users = self.get("core.chatbot", "chatai_users", default=[])
chatai_users = self.get('core.chatbot', 'chatai_users', default=[])
if user_id in chatai_users:
chatai_users.remove(user_id)
self.set("core.chatbot", "chatai_users", chatai_users)
self.set('core.chatbot', 'chatai_users', chatai_users)
def getaiusers(self):
return self.get("core.chatbot", "chatai_users", default=[])
return self.get('core.chatbot', 'chatai_users', default=[])
if config.db_type in ["mongo", "mongodb"]:
if config.db_type in ['mongo', 'mongodb']:
db = MongoDatabase(config.db_url, config.db_name)
else:
db = SqliteDatabase(config.db_name)
+274 -353
View File
File diff suppressed because it is too large Load Diff
+15 -13
View File
@@ -1,31 +1,33 @@
from sys import version_info
from .db import db
import git
from .db import db
__all__ = [
"modules_help",
"requirements_list",
"python_version",
"prefix",
"gitrepo",
"userbot_version",
'modules_help',
'requirements_list',
'python_version',
'prefix',
'gitrepo',
'userbot_version',
]
modules_help = {}
requirements_list = []
python_version = f"{version_info[0]}.{version_info[1]}.{version_info[2]}"
python_version = f'{version_info[0]}.{version_info[1]}.{version_info[2]}'
prefix = db.get("core.main", "prefix", ".")
prefix = db.get('core.main', 'prefix', '.')
try:
gitrepo = git.Repo(".")
gitrepo = git.Repo('.')
except (git.exc.InvalidGitRepositoryError, git.exc.NoSuchPathError):
gitrepo = None
if gitrepo is not None and len(gitrepo.tags) > 0:
commits_since_tag = list(gitrepo.iter_commits(f"{gitrepo.tags[-1].name}..HEAD"))
userbot_version = f"2.5.{len(commits_since_tag)}"
commits_since_tag = list(gitrepo.iter_commits(f'{gitrepo.tags[-1].name}..HEAD'))
userbot_version = f'2.5.{len(commits_since_tag)}'
else:
userbot_version = "2.5.0"
userbot_version = '2.5.0'
+13 -15
View File
@@ -4,12 +4,12 @@ from typing import Optional
from pyrogram import Client
from utils.scripts import load_module
from utils.misc import modules_help
from utils.scripts import load_module
class ModuleManager:
_instance: Optional["ModuleManager"] = None
_instance: Optional['ModuleManager'] = None
def __init__(self):
self.success_modules = 0
@@ -17,27 +17,25 @@ class ModuleManager:
self.help_navigator = None
@classmethod
def get_instance(cls) -> "ModuleManager":
def get_instance(cls) -> 'ModuleManager':
if cls._instance is None:
cls._instance = ModuleManager()
return cls._instance
async def load_modules(self, app: Client):
"""Load all modules and initialize help navigator"""
for path in Path("modules").rglob("*.py"):
for path in Path('modules').rglob('*.py'):
try:
await load_module(
path.stem, app, core="custom_modules" not in path.parent.parts
)
await load_module(path.stem, app, core='custom_modules' not in path.parent.parts)
except Exception:
logging.warning("Can't import module %s", path.stem, exc_info=True)
self.failed_modules += 1
else:
self.success_modules += 1
logging.info("Imported %d modules", self.success_modules)
logging.info('Imported %d modules', self.success_modules)
if self.failed_modules:
logging.warning("Failed to import %d modules", self.failed_modules)
logging.warning('Failed to import %d modules', self.failed_modules)
self.help_navigator = HelpNavigator()
return self.help_navigator
@@ -48,7 +46,7 @@ class HelpNavigator:
self.current_page = 1
self.module_list = list(modules_help.keys())
self.total_pages = (len(modules_help) + 9) // 10
logging.info("Initialized HelpNavigator with %d modules", len(self.module_list))
logging.info('Initialized HelpNavigator with %d modules', len(self.module_list))
async def send_page(self, message):
from utils.misc import prefix
@@ -56,13 +54,13 @@ class HelpNavigator:
start_index = (self.current_page - 1) * 10
end_index = start_index + 10
page_modules = self.module_list[start_index:end_index]
text = "<b>Help</b>\n"
text += f"For more help on how to use a command, type <code>{prefix}help [module]</code>\n\n"
text += f"Help Page No: {self.current_page}/{self.total_pages}\n\n"
text = '<b>Help</b>\n'
text += f'For more help on how to use a command, type <code>{prefix}help [module]</code>\n\n'
text += f'Help Page No: {self.current_page}/{self.total_pages}\n\n'
for module_name in page_modules:
commands = modules_help[module_name]
text += f"<b>• {module_name.title()}:</b> {', '.join([f'<code>{prefix + cmd_name.split()[0]}</code>' for cmd_name in commands.keys()])}\n"
text += f"\n<b>The number of modules in the userbot: {len(modules_help)}</b>"
text += f'<b>• {module_name.title()}:</b> {", ".join([f"<code>{prefix + cmd_name.split()[0]}</code>" for cmd_name in commands.keys()])}\n'
text += f'\n<b>The number of modules in the userbot: {len(modules_help)}</b>'
await message.edit(text, disable_web_page_preview=True)
def next_page(self) -> bool:
+49 -62
View File
@@ -5,18 +5,17 @@ import asyncio
import http.cookiejar
import urllib.parse
import urllib.request
from uuid import uuid4
from datetime import datetime, timedelta
from http.cookies import SimpleCookie
from json import loads as json_loads
from uuid import uuid4
from utils.db import db
BASE_PROTOCOL = "https://"
BASE_URL = "rentry.co"
BASE_PROTOCOL = 'https://'
BASE_URL = 'rentry.co'
_headers = {"Referer": f"{BASE_PROTOCOL}{BASE_URL}"}
_headers = {'Referer': f'{BASE_PROTOCOL}{BASE_URL}'}
class UrllibClient:
@@ -24,9 +23,7 @@ class UrllibClient:
def __init__(self):
self.cookie_jar = http.cookiejar.CookieJar()
self.opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(self.cookie_jar)
)
self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cookie_jar))
urllib.request.install_opener(self.opener)
def get(self, url, headers=None):
@@ -45,58 +42,50 @@ class UrllibClient:
def _request(self, request):
response = self.opener.open(request)
response.status_code = response.getcode()
response.data = response.read().decode("utf-8")
response.data = response.read().decode('utf-8')
return response
def raw(url: str):
client = UrllibClient()
return json_loads(client.get(f"{BASE_PROTOCOL}{BASE_URL}/api/raw/{url}").data)
return json_loads(client.get(f'{BASE_PROTOCOL}{BASE_URL}/api/raw/{url}').data)
def new(text: str, edit_code: str = "", url: str = ""):
def new(text: str, edit_code: str = '', url: str = ''):
client, cookie = UrllibClient(), SimpleCookie()
cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"])
csrftoken = cookie["csrftoken"].value
cookie.load(vars(client.get(f'{BASE_PROTOCOL}{BASE_URL}'))['headers']['Set-Cookie'])
csrftoken = cookie['csrftoken'].value
payload = {
"csrfmiddlewaretoken": csrftoken,
"url": url,
"edit_code": edit_code,
"text": text,
'csrfmiddlewaretoken': csrftoken,
'url': url,
'edit_code': edit_code,
'text': text,
}
return json_loads(
client.post(
f"{BASE_PROTOCOL}{BASE_URL}" + "/api/new", payload, headers=_headers
).data
)
return json_loads(client.post(f'{BASE_PROTOCOL}{BASE_URL}' + '/api/new', payload, headers=_headers).data)
def edit(url_short: str, edit_code: str, text: str):
client, cookie = UrllibClient(), SimpleCookie()
cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"])
csrftoken = cookie["csrftoken"].value
cookie.load(vars(client.get(f'{BASE_PROTOCOL}{BASE_URL}'))['headers']['Set-Cookie'])
csrftoken = cookie['csrftoken'].value
payload = {"csrfmiddlewaretoken": csrftoken, "edit_code": edit_code, "text": text}
payload = {'csrfmiddlewaretoken': csrftoken, 'edit_code': edit_code, 'text': text}
return json_loads(
client.post(
f"{BASE_PROTOCOL}{BASE_URL}/api/edit/{url_short}", payload, headers=_headers
).data
)
return json_loads(client.post(f'{BASE_PROTOCOL}{BASE_URL}/api/edit/{url_short}', payload, headers=_headers).data)
def delete(url_short: str, edit_code: str):
client, cookie = UrllibClient(), SimpleCookie()
cookie.load(vars(client.get(f"{BASE_PROTOCOL}{BASE_URL}"))["headers"]["Set-Cookie"])
csrftoken = cookie["csrftoken"].value
payload = {"csrfmiddlewaretoken": csrftoken, "edit_code": edit_code}
cookie.load(vars(client.get(f'{BASE_PROTOCOL}{BASE_URL}'))['headers']['Set-Cookie'])
csrftoken = cookie['csrftoken'].value
payload = {'csrfmiddlewaretoken': csrftoken, 'edit_code': edit_code}
return json_loads(
client.post(
f"{BASE_PROTOCOL}{BASE_URL}/api/delete/{url_short}",
f'{BASE_PROTOCOL}{BASE_URL}/api/delete/{url_short}',
payload,
headers=_headers,
).data
@@ -128,35 +117,35 @@ async def paste(
if edit_bin:
if not (url and edit_code):
raise ValueError("Please provide both, url and edit code")
raise ValueError('Please provide both, url and edit code')
response = edit(url_short=url, edit_code=edit_code, text=text)
else:
response = new(text=text)
if response.get("status") != "200":
if response.get('status') != '200':
raise RuntimeError(
f"paste task terminated with status: {response.get('status')}\n"
f"Message: {response.get('content', 'No message provided')}"
f'paste task terminated with status: {response.get("status")}\n'
f'Message: {response.get("content", "No message provided")}'
)
url = response["url"]
edit_code = response["edit_code"]
url = response['url']
edit_code = response['edit_code']
if not permanent:
short_url = response["url_short"]
short_url = response['url_short']
time_now = datetime.now()
ftime = time_now.strftime("%d %I:%M:%S %p %Y")
ftime = time_now.strftime('%d %I:%M:%S %p %Y')
print(f"URL: {url} - Edit Code: {edit_code} - Time: {ftime}")
print(f'URL: {url} - Edit Code: {edit_code} - Time: {ftime}')
rallUrls = db.get("core.rentry", "urls", default={"allUrls": {}})
rallUrls = db.get('core.rentry', 'urls', default={'allUrls': {}})
entry_id = str(uuid4())
rallUrls["allUrls"][entry_id] = {
"url": short_url,
"edit_code": edit_code,
"time": ftime,
rallUrls['allUrls'][entry_id] = {
'url': short_url,
'edit_code': edit_code,
'time': ftime,
}
db.set("core.rentry", "urls", rallUrls)
db.set('core.rentry', 'urls', rallUrls)
if return_edit:
return (url, edit_code)
@@ -167,34 +156,32 @@ async def rentry_cleanup_job():
"""Periodically checks and deletes rentry pastes older than 24 hours"""
while True:
try:
rallUrls = db.get("core.rentry", "urls", default={"allUrls": {}})
rallUrls = db.get('core.rentry', 'urls', default={'allUrls': {}})
now = datetime.now()
deleted_count = 0
error_count = 0
for entry_id, entry in list(rallUrls["allUrls"].items()):
url = entry["url"]
entry_time = datetime.strptime(entry["time"], "%d %I:%M:%S %p %Y")
for entry_id, entry in list(rallUrls['allUrls'].items()):
url = entry['url']
entry_time = datetime.strptime(entry['time'], '%d %I:%M:%S %p %Y')
if now - entry_time > timedelta(days=1):
try:
delete(url, entry["edit_code"])
del rallUrls["allUrls"][entry_id]
delete(url, entry['edit_code'])
del rallUrls['allUrls'][entry_id]
deleted_count += 1
print(f"[#] Deleted expired rentry paste: {url}")
print(f'[#] Deleted expired rentry paste: {url}')
except Exception as e:
error_count += 1
print(f"[!] Failed to delete rentry paste {url}: {str(e)}")
print(f'[!] Failed to delete rentry paste {url}: {str(e)}')
if deleted_count or error_count:
print(
f"[*] Cleanup summary: {deleted_count} deleted, {error_count} failed"
)
print(f'[*] Cleanup summary: {deleted_count} deleted, {error_count} failed')
if deleted_count:
db.set("core.rentry", "urls", rallUrls)
db.set('core.rentry', 'urls', rallUrls)
except Exception as e:
print(f"[!] Error in rentry cleanup job: {str(e)}")
print(f'[!] Error in rentry cleanup job: {str(e)}')
await asyncio.sleep(12 * 60 * 60)
+98 -128
View File
@@ -25,22 +25,20 @@ import sys
import tempfile
import time
import traceback
from PIL import Image
from io import BytesIO
from types import ModuleType
from typing import Dict, Tuple
import psutil
from PIL import Image
from pyrogram import Client, errors, filters
from pyrogram.errors import FloodWait, MessageNotModified, UserNotParticipant
from pyrogram.types import Message
from pyrogram.enums import ChatMembersFilter
from utils.db import db
from .misc import modules_help, prefix, requirements_list
META_COMMENTS = re.compile(r"^ *# *meta +(\S+) *: *(.*?)\s*$", re.MULTILINE)
META_COMMENTS = re.compile(r'^ *# *meta +(\S+) *: *(.*?)\s*$', re.MULTILINE)
interact_with_to_delete = []
@@ -51,11 +49,11 @@ def time_formatter(milliseconds: int) -> str:
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
tmp = (
((str(days) + " day(s), ") if days else "")
+ ((str(hours) + " hour(s), ") if hours else "")
+ ((str(minutes) + " minute(s), ") if minutes else "")
+ ((str(seconds) + " second(s), ") if seconds else "")
+ ((str(milliseconds) + " millisecond(s), ") if milliseconds else "")
((str(days) + ' day(s), ') if days else '')
+ ((str(hours) + ' hour(s), ') if hours else '')
+ ((str(minutes) + ' minute(s), ') if minutes else '')
+ ((str(seconds) + ' second(s), ') if seconds else '')
+ ((str(milliseconds) + ' millisecond(s), ') if milliseconds else '')
)
return tmp[:-2]
@@ -63,32 +61,30 @@ def time_formatter(milliseconds: int) -> str:
def humanbytes(size):
"""Convert Bytes To Bytes So That Human Can Read It"""
if not size:
return ""
return ''
power = 2**10
raised_to_pow = 0
dict_power_n = {0: "", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"}
dict_power_n = {0: '', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'}
while size > power:
size /= power
raised_to_pow += 1
return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B"
return str(round(size, 2)) + ' ' + dict_power_n[raised_to_pow] + 'B'
async def edit_or_send_as_file(
tex: str,
message: Message,
client: Client,
caption: str = "<code>Result!</code>",
file_name: str = "result",
caption: str = '<code>Result!</code>',
file_name: str = 'result',
):
"""Send As File If Len Of Text Exceeds Tg Limit Else Edit Message"""
if not tex:
await message.edit("<code>Wait, What?</code>")
await message.edit('<code>Wait, What?</code>')
return
if len(tex) > 1024:
await message.edit("<code>OutPut is Too Large, Sending As File!</code>")
with tempfile.NamedTemporaryFile(
"w", delete=False, suffix=".txt", prefix=f"{file_name}_"
) as fn:
await message.edit('<code>OutPut is Too Large, Sending As File!</code>')
with tempfile.NamedTemporaryFile('w', delete=False, suffix='.txt', prefix=f'{file_name}_') as fn:
fn.write(tex)
temp_path = fn.name
try:
@@ -106,7 +102,7 @@ def get_text(message: Message) -> None | str:
text_to_return = message.text
if message.text is None:
return None
if " " in text_to_return:
if ' ' in text_to_return:
try:
return message.text.split(None, 1)[1]
except IndexError:
@@ -127,32 +123,28 @@ async def progress(current, total, message, start, type_of_ps, file_name=None):
return
time_to_completion = round((total - current) / speed) * 1000
estimated_total_time = elapsed_time + time_to_completion
progress_str = f"{''.join(['' for i in range(math.floor(percentage / 10))])}"
progress_str += (
f"{''.join(['' for i in range(10 - math.floor(percentage / 10))])}"
)
progress_str += f"{round(percentage, 2)}%\n"
tmp = f"{progress_str}{humanbytes(current)} of {humanbytes(total)}\n"
tmp += f"ETA: {time_formatter(estimated_total_time)}"
progress_str = f'{"".join(["" for i in range(math.floor(percentage / 10))])}'
progress_str += f'{"".join(["" for i in range(10 - math.floor(percentage / 10))])}'
progress_str += f'{round(percentage, 2)}%\n'
tmp = f'{progress_str}{humanbytes(current)} of {humanbytes(total)}\n'
tmp += f'ETA: {time_formatter(estimated_total_time)}'
if file_name:
try:
await message.edit(
f"{type_of_ps}\n<b>File Name:</b> <code>{file_name}</code>\n{tmp}"
)
await message.edit(f'{type_of_ps}\n<b>File Name:</b> <code>{file_name}</code>\n{tmp}')
except FloodWait as e:
await asyncio.sleep(e.x)
except MessageNotModified:
pass
else:
try:
await message.edit(f"{type_of_ps}\n{tmp}")
await message.edit(f'{type_of_ps}\n{tmp}')
except FloodWait as e:
await asyncio.sleep(e.x)
except MessageNotModified:
pass
async def run_cmd(prefix: str) -> Tuple[str, str, int, int]:
async def run_cmd(prefix: str) -> tuple[str, str, int, int]:
"""Run Commands"""
args = shlex.split(prefix)
process = await asyncio.create_subprocess_exec(
@@ -160,45 +152,45 @@ async def run_cmd(prefix: str) -> Tuple[str, str, int, int]:
)
stdout, stderr = await process.communicate()
return (
stdout.decode("utf-8", "replace").strip(),
stderr.decode("utf-8", "replace").strip(),
stdout.decode('utf-8', 'replace').strip(),
stderr.decode('utf-8', 'replace').strip(),
process.returncode,
process.pid,
)
def mediainfo(media):
xx = str((str(media)).split("(", maxsplit=1)[0])
m = ""
if xx == "MessageMediaDocument":
xx = str((str(media)).split('(', maxsplit=1)[0])
m = ''
if xx == 'MessageMediaDocument':
mim = media.document.mime_type
if mim == "application/x-tgsticker":
m = "sticker animated"
elif "image" in mim:
if mim == "image/webp":
m = "sticker"
elif mim == "image/gif":
m = "gif as doc"
if mim == 'application/x-tgsticker':
m = 'sticker animated'
elif 'image' in mim:
if mim == 'image/webp':
m = 'sticker'
elif mim == 'image/gif':
m = 'gif as doc'
else:
m = "pic as doc"
elif "video" in mim:
if "DocumentAttributeAnimated" in str(media):
m = "gif"
elif "DocumentAttributeVideo" in str(media):
m = 'pic as doc'
elif 'video' in mim:
if 'DocumentAttributeAnimated' in str(media):
m = 'gif'
elif 'DocumentAttributeVideo' in str(media):
i = str(media.document.attributes[0])
if "supports_streaming=True" in i:
m = "video"
m = "video as doc"
if 'supports_streaming=True' in i:
m = 'video'
m = 'video as doc'
else:
m = "video"
elif "audio" in mim:
m = "audio"
m = 'video'
elif 'audio' in mim:
m = 'audio'
else:
m = "document"
elif xx == "MessageMediaPhoto":
m = "pic"
elif xx == "MessageMediaWebPage":
m = "web"
m = 'document'
elif xx == 'MessageMediaPhoto':
m = 'pic'
elif xx == 'MessageMediaWebPage':
m = 'web'
return m
@@ -217,31 +209,31 @@ def text(message: Message) -> str:
def restart() -> None:
music_bot_pid = db.get("custom.musicbot", "music_bot_pid", None)
music_bot_pid = db.get('custom.musicbot', 'music_bot_pid', None)
if music_bot_pid is not None:
try:
music_bot_process = psutil.Process(music_bot_pid)
music_bot_process.terminate()
except psutil.NoSuchProcess:
print("Music bot is not running.")
os.execvp(sys.executable, [sys.executable, "main.py"]) # skipcq
print('Music bot is not running.')
os.execvp(sys.executable, [sys.executable, 'main.py']) # skipcq
def format_exc(e: Exception, suffix="") -> str:
def format_exc(e: Exception, suffix='') -> str:
traceback.print_exc()
err = traceback.format_exc()
if isinstance(e, errors.RPCError):
return (
f"<b>Telegram API error!</b>\n"
f"<code>[{e.CODE} {e.ID or e.NAME}] — {e.MESSAGE.format(value=e.value)}</code>\n\n<b>{suffix}</b>"
f'<b>Telegram API error!</b>\n'
f'<code>[{e.CODE} {e.ID or e.NAME}] — {e.MESSAGE.format(value=e.value)}</code>\n\n<b>{suffix}</b>'
)
return f"<b>Error!</b>\n" f"<code>{err}</code>"
return f'<b>Error!</b>\n<code>{err}</code>'
def with_reply(func):
async def wrapped(client: Client, message: Message):
if not message.reply_to_message:
await message.edit("<b>Reply to message is required</b>")
await message.edit('<b>Reply to message is required</b>')
else:
return await func(client, message)
@@ -251,16 +243,12 @@ def with_reply(func):
def is_admin(func):
async def wrapped(client: Client, message: Message):
try:
chat_member = await client.get_chat_member(
message.chat.id, message.from_user.id
)
if chat_member.status in ("administrator", "creator"):
chat_member = await client.get_chat_member(message.chat.id, message.from_user.id)
if chat_member.status in ('administrator', 'creator'):
return await func(client, message)
await message.edit("You need to be an admin to perform this action.")
await message.edit('You need to be an admin to perform this action.')
except UserNotParticipant:
await message.edit(
"You need to be a participant in the chat to perform this action."
)
await message.edit('You need to be a participant in the chat to perform this action.')
return wrapped
@@ -278,9 +266,7 @@ async def interact_with(message: Message) -> Message:
await asyncio.sleep(1)
# noinspection PyProtectedMember
response = [
msg async for msg in message._client.get_chat_history(message.chat.id, limit=1)
]
response = [msg async for msg in message._client.get_chat_history(message.chat.id, limit=1)]
seconds_waiting = 0
while response[0].from_user.is_self:
@@ -290,10 +276,7 @@ async def interact_with(message: Message) -> Message:
await asyncio.sleep(1)
# noinspection PyProtectedMember
response = [
msg
async for msg in message._client.get_chat_history(message.chat.id, limit=1)
]
response = [msg async for msg in message._client.get_chat_history(message.chat.id, limit=1)]
interact_with_to_delete.append(message.id)
interact_with_to_delete.append(response[0].id)
@@ -304,14 +287,12 @@ async def interact_with(message: Message) -> Message:
def format_module_help(module_name: str, full=True):
commands = modules_help[module_name]
help_text = (
f"<b>Help for |{module_name}|\n\nUsage:</b>\n" if full else "<b>Usage:</b>\n"
)
help_text = f'<b>Help for |{module_name}|\n\nUsage:</b>\n' if full else '<b>Usage:</b>\n'
for command, desc in commands.items():
cmd = command.split(maxsplit=1)
args = " <code>" + cmd[1] + "</code>" if len(cmd) > 1 else ""
help_text += f"<code>{prefix}{cmd[0]}</code>{args} — <i>{desc}</i>\n"
args = ' <code>' + cmd[1] + '</code>' if len(cmd) > 1 else ''
help_text += f'<code>{prefix}{cmd[0]}</code>{args} — <i>{desc}</i>\n'
return help_text
@@ -319,16 +300,12 @@ def format_module_help(module_name: str, full=True):
def format_small_module_help(module_name: str, full=True):
commands = modules_help[module_name]
help_text = (
f"<b>Help for |{module_name}|\n\nCommands list:\n"
if full
else "<b>Commands list:\n"
)
help_text = f'<b>Help for |{module_name}|\n\nCommands list:\n' if full else '<b>Commands list:\n'
for command, _desc in commands.items():
cmd = command.split(maxsplit=1)
args = " <code>" + cmd[1] + "</code>" if len(cmd) > 1 else ""
help_text += f"<code>{prefix}{cmd[0]}</code>{args}\n"
help_text += f"\nGet full usage: <code>{prefix}help {module_name}</code></b>"
args = ' <code>' + cmd[1] + '</code>' if len(cmd) > 1 else ''
help_text += f'<code>{prefix}{cmd[0]}</code>{args}\n'
help_text += f'\nGet full usage: <code>{prefix}help {module_name}</code></b>'
return help_text
@@ -348,12 +325,12 @@ def import_library(library_name: str, package_name: str = None):
return importlib.import_module(library_name)
except ImportError as exc:
completed = subprocess.run(
[sys.executable, "-m", "pip", "install", "--upgrade", package_name],
[sys.executable, '-m', 'pip', 'install', '--upgrade', package_name],
check=True,
)
if completed.returncode != 0:
raise AssertionError(
f"Failed to install library {package_name} (pip exited with code {completed.returncode})"
f'Failed to install library {package_name} (pip exited with code {completed.returncode})'
) from exc
return importlib.import_module(library_name)
@@ -363,21 +340,17 @@ def uninstall_library(package_name: str):
Uninstalls a library
:param package_name: package name in PyPi (pip uninstall example)
"""
completed = subprocess.run(
[sys.executable, "-m", "pip", "uninstall", "-y", package_name], check=True
)
completed = subprocess.run([sys.executable, '-m', 'pip', 'uninstall', '-y', package_name], check=True)
if completed.returncode != 0:
raise AssertionError(
f"Failed to uninstall library {package_name} (pip exited with code {completed.returncode})"
f'Failed to uninstall library {package_name} (pip exited with code {completed.returncode})'
)
def resize_image(
input_img, output=None, img_type="PNG", size: int = 512, size2: int = None
):
def resize_image(input_img, output=None, img_type='PNG', size: int = 512, size2: int = None):
if output is None:
output = BytesIO()
output.name = f"sticker.{img_type.lower()}"
output.name = f'sticker.{img_type.lower()}'
with Image.open(input_img) as img:
# We used to use thumbnail(size) here, but it returns with a *max* dimension of 512,512
@@ -435,13 +408,13 @@ async def load_module(
if module_name in modules_help and not core:
await unload_module(module_name, client)
path = f"modules.{'custom_modules.' if not core else ''}{module_name}"
path = f'modules.{"custom_modules." if not core else ""}{module_name}'
with open(f"{path.replace('.', '/')}.py", encoding="utf-8") as f:
with open(f'{path.replace(".", "/")}.py', encoding='utf-8') as f:
code = f.read()
meta = parse_meta_comments(code)
packages = meta.get("requires", "").split()
packages = meta.get('requires', '').split()
requirements_list.extend(packages)
try:
@@ -455,39 +428,36 @@ async def load_module(
raise
if message:
await message.edit(f"<b>Installing requirements: {' '.join(packages)}</b>")
await message.edit(f'<b>Installing requirements: {" ".join(packages)}</b>')
proc = await asyncio.create_subprocess_exec(
sys.executable,
"-m",
"pip",
"install",
"-U",
'-m',
'pip',
'install',
'-U',
*packages,
)
try:
await asyncio.wait_for(proc.wait(), timeout=120)
except asyncio.TimeoutError:
except TimeoutError:
if message:
await message.edit(
"<b>Timeout while installed requirements."
+ "Try to install them manually</b>"
)
raise TimeoutError("timeout while installing requirements") from e
await message.edit('<b>Timeout while installed requirements.' + 'Try to install them manually</b>')
raise TimeoutError('timeout while installing requirements') from e
if proc.returncode != 0:
if message:
await message.edit(
f"<b>Failed to install requirements (pip exited with code {proc.returncode}). "
f"Check logs for futher info</b>",
f'<b>Failed to install requirements (pip exited with code {proc.returncode}). '
f'Check logs for futher info</b>',
)
raise RuntimeError("failed to install requirements") from e
raise RuntimeError('failed to install requirements') from e
module = importlib.import_module(path)
for _name, obj in vars(module).items():
if isinstance(getattr(obj, "handlers", []), list):
for handler, group in getattr(obj, "handlers", []):
if isinstance(getattr(obj, 'handlers', []), list):
for handler, group in getattr(obj, 'handlers', []):
client.add_handler(handler, group)
module.__meta__ = meta
@@ -496,14 +466,14 @@ async def load_module(
async def unload_module(module_name: str, client: Client) -> bool:
path = "modules.custom_modules." + module_name
path = 'modules.custom_modules.' + module_name
if path not in sys.modules:
return False
module = importlib.import_module(path)
for _name, obj in vars(module).items():
for handler, group in getattr(obj, "handlers", []):
for handler, group in getattr(obj, 'handlers', []):
client.remove_handler(handler, group)
del modules_help[module_name]
@@ -521,7 +491,7 @@ def no_prefix(handler):
return filters.create(func)
def parse_meta_comments(code: str) -> Dict[str, str]:
def parse_meta_comments(code: str) -> dict[str, str]:
try:
groups = META_COMMENTS.search(code).groups()
except AttributeError: