d4rktg 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- d4rk/Database/__init__.py +1 -0
- d4rk/Database/db.py +52 -0
- d4rk/Handlers/__init__.py +3 -0
- d4rk/Handlers/_bot.py +207 -0
- d4rk/Handlers/_custom.py +97 -0
- d4rk/Handlers/_scheduler.py +33 -0
- d4rk/Logs/__init__.py +1 -0
- d4rk/Logs/_logger.py +115 -0
- d4rk/Utils/__init__.py +10 -0
- d4rk/Utils/_buttons.py +73 -0
- d4rk/Utils/_decorators.py +113 -0
- d4rk/Utils/_filters.py +67 -0
- d4rk/Utils/_fonts.py +46 -0
- d4rk/Utils/_ip.py +21 -0
- d4rk/Utils/_movie_parser.py +184 -0
- d4rk/Utils/_ractions.py +79 -0
- d4rk/Utils/_terminal.py +7 -0
- d4rk/__init__.py +4 -0
- d4rktg-0.0.0.dist-info/METADATA +21 -0
- d4rktg-0.0.0.dist-info/RECORD +22 -0
- d4rktg-0.0.0.dist-info/WHEEL +5 -0
- d4rktg-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
|
|
1
|
+
from .db import db
|
d4rk/Database/db.py
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# src/Database/Mongodb/db.py
|
2
|
+
|
3
|
+
from pymongo import MongoClient , server_api
|
4
|
+
|
5
|
+
from d4rk.Logs import setup_logger
|
6
|
+
|
7
|
+
logger = setup_logger(__name__)
|
8
|
+
|
9
|
+
class Database:
|
10
|
+
client = None
|
11
|
+
db = None
|
12
|
+
is_connected = False
|
13
|
+
def __init__(self):
|
14
|
+
pass
|
15
|
+
|
16
|
+
def connect(self,name: str, collections: list = [],DATABASE_URL: str =None):
|
17
|
+
self.database_name = name.replace(" ","")
|
18
|
+
self._collections = collections
|
19
|
+
self._DATABASE_URL = DATABASE_URL
|
20
|
+
if not DATABASE_URL:
|
21
|
+
logger.warning("DATABASE_URL is not set in the environment variables.")
|
22
|
+
exit()
|
23
|
+
try:
|
24
|
+
self.client = MongoClient(DATABASE_URL,server_api=server_api.ServerApi('1'))
|
25
|
+
self.db = self.client[self.database_name]
|
26
|
+
self._load_custom_collections()
|
27
|
+
self.is_connected = True
|
28
|
+
except Exception as e:
|
29
|
+
logger.warning(f"Failed to connect to MongoDB: {e}")
|
30
|
+
exit()
|
31
|
+
if self.is_connected:
|
32
|
+
try:
|
33
|
+
self.client.admin.command('ping')
|
34
|
+
logger.info(f"successfully connected to {name} Mongodb!")
|
35
|
+
except Exception as e:
|
36
|
+
logger.warning(f"Something Went Wrong While Connecting To Database! : {e}")
|
37
|
+
self.is_connected = False
|
38
|
+
exit()
|
39
|
+
|
40
|
+
def _load_custom_collections(self):
|
41
|
+
for CustomClass in self._collections:
|
42
|
+
collection_name = str(CustomClass.__name__).title()
|
43
|
+
base_collection = self.get_collection(collection_name)
|
44
|
+
instance = CustomClass(base_collection)
|
45
|
+
setattr(self, collection_name, instance)
|
46
|
+
logger.info(f"Loaded collection: {collection_name}")
|
47
|
+
|
48
|
+
def get_collection(self, collection):
|
49
|
+
return self.db[collection]
|
50
|
+
|
51
|
+
db = Database()
|
52
|
+
|
d4rk/Handlers/_bot.py
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import asyncio
|
4
|
+
import traceback
|
5
|
+
import random
|
6
|
+
from datetime import datetime, timedelta
|
7
|
+
|
8
|
+
from pyrogram import Client
|
9
|
+
from pyrogram.errors import FloodWait
|
10
|
+
from pyrogram.types import BotCommand
|
11
|
+
from pyrogram.errors.exceptions.bad_request_400 import AccessTokenExpired
|
12
|
+
|
13
|
+
from d4rk.Logs import setup_logger
|
14
|
+
|
15
|
+
logs_sent = False
|
16
|
+
logs_lock = asyncio.Lock()
|
17
|
+
|
18
|
+
logger = setup_logger(__name__)
|
19
|
+
|
20
|
+
class BotManager(Client):
|
21
|
+
_bot: Client = None
|
22
|
+
_web = True
|
23
|
+
_bot_info = None
|
24
|
+
_is_connected = False
|
25
|
+
_rename = False
|
26
|
+
_flood_data = {}
|
27
|
+
_loop = None
|
28
|
+
_scheduler_thread = None
|
29
|
+
font = 0
|
30
|
+
sudo_users = []
|
31
|
+
|
32
|
+
|
33
|
+
def create_client(self,app_name,token):
|
34
|
+
self.app_name = app_name
|
35
|
+
super().__init__(
|
36
|
+
name=app_name,
|
37
|
+
api_id=self.api_id,
|
38
|
+
api_hash=self.api_hash,
|
39
|
+
bot_token=token,
|
40
|
+
plugins=self.plugins,
|
41
|
+
in_memory=True
|
42
|
+
)
|
43
|
+
|
44
|
+
async def handle_flood_wait(self, wait_time: int):
|
45
|
+
|
46
|
+
await asyncio.sleep(wait_time)
|
47
|
+
try:await self.powerup(self.app_name)
|
48
|
+
except:pass
|
49
|
+
|
50
|
+
def _safe_async(self, coro_func):
|
51
|
+
if self._loop:asyncio.run_coroutine_threadsafe(coro_func(), self._loop)
|
52
|
+
else:logger.error("Event loop is not set for _safe_async")
|
53
|
+
|
54
|
+
async def powerup(self,appname):
|
55
|
+
if hasattr(self, "db"):
|
56
|
+
self.font = self.db.Settings.get(key="font",datatype=str)
|
57
|
+
self.sudo_users = self.db.Settings.get(key="sudo_users",datatype=list,default=[])
|
58
|
+
if not self.font:
|
59
|
+
logger.info("Font not set, defaulting to font 1")
|
60
|
+
self.db.Settings.set("font", "1")
|
61
|
+
self.font = 1
|
62
|
+
|
63
|
+
self.create_client(appname,self.token)
|
64
|
+
max_retries = 3
|
65
|
+
for attempt in range(max_retries):
|
66
|
+
try:
|
67
|
+
self._loop = asyncio.get_running_loop()
|
68
|
+
logger.info(f'Starting bot client... (attempt {attempt + 1}/{max_retries})')
|
69
|
+
if not self._is_connected:
|
70
|
+
await asyncio.wait_for(super().start(), timeout=60.0)
|
71
|
+
self._bot_info = await super().get_me()
|
72
|
+
logger.info(f"Bot Client > {self._bot_info.first_name} - @{self._bot_info.username} Started")
|
73
|
+
await self.setup_commands()
|
74
|
+
self._is_connected = True
|
75
|
+
await self.handle_restart()
|
76
|
+
break
|
77
|
+
|
78
|
+
except asyncio.TimeoutError:
|
79
|
+
logger.error(f"Bot start timed out (attempt {attempt + 1})")
|
80
|
+
if attempt < max_retries - 1:
|
81
|
+
logger.info("Retrying in 10 seconds...")
|
82
|
+
await asyncio.sleep(10)
|
83
|
+
|
84
|
+
except FloodWait as e:
|
85
|
+
logger.error(f"FloodWait: {e.value} seconds")
|
86
|
+
await self.handle_flood_wait(e.value)
|
87
|
+
break
|
88
|
+
|
89
|
+
except AccessTokenExpired:
|
90
|
+
logger.error(f"Access token expired (attempt {attempt + 1})")
|
91
|
+
except Exception as e:
|
92
|
+
logger.error(f"Error starting Client.Stoped !")
|
93
|
+
logger.error(traceback.format_exc())
|
94
|
+
break
|
95
|
+
else:
|
96
|
+
logger.error("Failed to start bot after all retry attempts")
|
97
|
+
|
98
|
+
await self.setup_webserver()
|
99
|
+
|
100
|
+
async def powerdown(self, *args):
|
101
|
+
global logs_sent, logs_lock
|
102
|
+
logger.info("Initiating APP to stop...")
|
103
|
+
if self._rename:await super().set_bot_info(lang_code='en',name=self.app_name + " (Offline)")
|
104
|
+
self.stop_scheduler()
|
105
|
+
today = self.TZ_now.strftime("%Y-%m-%d")
|
106
|
+
if hasattr(self, '_web_runner') and self._web_runner:
|
107
|
+
await self.web_server.cleanup()
|
108
|
+
|
109
|
+
logger.info("Stopping bot client...")
|
110
|
+
if self._is_connected and self.LOGS:
|
111
|
+
try:
|
112
|
+
await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} stopping...")
|
113
|
+
except Exception as e:
|
114
|
+
logger.error(f"Error sending stop message for {self._bot_info.first_name}: {e}")
|
115
|
+
|
116
|
+
async with logs_lock:
|
117
|
+
if not logs_sent and self._is_connected:
|
118
|
+
logs_sent = True
|
119
|
+
try:
|
120
|
+
await self.send_document(chat_id=self.LOGS, document=f"logs/log-{today}.txt")
|
121
|
+
logger.info("Log document sent successfully")
|
122
|
+
except Exception as e:
|
123
|
+
logger.error(f"Error sending log document: {e}")
|
124
|
+
|
125
|
+
if self._is_connected and self.LOGS:
|
126
|
+
try:
|
127
|
+
await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} stopped successfully!")
|
128
|
+
except Exception as e:
|
129
|
+
logger.error(f"Error sending stop confirmation for {self._bot_info.first_name}: {e}")
|
130
|
+
await super().stop()
|
131
|
+
await asyncio.sleep(3)
|
132
|
+
|
133
|
+
async def reboot(self):
|
134
|
+
try:
|
135
|
+
if self._rename:await super().set_bot_info(lang_code='en',name=self.app_name + " (restarting..)")
|
136
|
+
logger.info("Initiating APP to reboot...")
|
137
|
+
self.stop_scheduler()
|
138
|
+
today = self.TZ_now.strftime("%Y-%m-%d")
|
139
|
+
if hasattr(self, '_web_runner') and self._web_runner:
|
140
|
+
await self.web_server.cleanup()
|
141
|
+
if self._is_connected:
|
142
|
+
try:
|
143
|
+
if self.LOGS:
|
144
|
+
await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} rebooting...")
|
145
|
+
await self.send_document(chat_id=self.LOGS, document=f"logs/log-{today}.txt")
|
146
|
+
logger.info(f"{self._bot_info.first_name} - @{self._bot_info.username} is rebooting")
|
147
|
+
except Exception as e:
|
148
|
+
logger.error(f"Error sending reboot notification: {e}")
|
149
|
+
await super().stop()
|
150
|
+
self._is_connected = False
|
151
|
+
await asyncio.sleep(2)
|
152
|
+
|
153
|
+
logger.info("Restarting process...")
|
154
|
+
os.execl(sys.executable, sys.executable, *sys.argv)
|
155
|
+
except Exception as e:
|
156
|
+
logger.error(f"Error during reboot: {e}")
|
157
|
+
os.execl(sys.executable, sys.executable, *sys.argv)
|
158
|
+
|
159
|
+
async def handle_restart(self):
|
160
|
+
if os.path.exists('restart.txt'):
|
161
|
+
with open('restart.txt', 'r') as file:
|
162
|
+
data = file.read().split()
|
163
|
+
chat_id = int(data[0])
|
164
|
+
Message_id = int(data[1])
|
165
|
+
try:await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} restarted successfully!")
|
166
|
+
except Exception as e:logger.error(f"Failed to send restart notification: {e}")
|
167
|
+
try:await self.edit_message_text(chat_id=chat_id,message_id=Message_id, text="Bot restarted successfully!")
|
168
|
+
except:
|
169
|
+
try:
|
170
|
+
await self.send_message(chat_id=chat_id, text="Bot restarted successfully!",reply_to_message_id=Message_id-1,)
|
171
|
+
await self.delete_messages(chat_id=chat_id,message_ids=Message_id)
|
172
|
+
except:pass
|
173
|
+
|
174
|
+
if os.path.exists('restart.txt'):os.remove('restart.txt')
|
175
|
+
else:
|
176
|
+
try:await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} started successfully!")
|
177
|
+
except Exception as e:logger.error(f"Failed to send start notification: {e}")
|
178
|
+
|
179
|
+
|
180
|
+
async def setup_commands(self,set_commands=False):
|
181
|
+
if self._rename:
|
182
|
+
if self._bot_info.first_name != self.app_name:
|
183
|
+
await super().set_bot_info(lang_code='en',name=self.app_name)
|
184
|
+
if set_commands:
|
185
|
+
commands = await super().get_bot_commands()
|
186
|
+
if commands == []:
|
187
|
+
b_index = self.TOKEN_INDEX + 1
|
188
|
+
bot_commands = [
|
189
|
+
BotCommand("start", f"{b_index} Start the bot"),
|
190
|
+
BotCommand("help", f"{b_index} Get help"),
|
191
|
+
BotCommand("logs", f"{b_index} Get logs (Admin only)"),
|
192
|
+
BotCommand("reboot", f"{b_index} Reboot the bot (Admin only)")
|
193
|
+
]
|
194
|
+
await super().set_bot_commands(bot_commands)
|
195
|
+
|
196
|
+
async def send_logs(self):
|
197
|
+
logger.info("Sending yesterday logs...")
|
198
|
+
if not self._is_connected:
|
199
|
+
logger.warning("Bot is not connected")
|
200
|
+
if self._is_connected:
|
201
|
+
|
202
|
+
yesterday = (self.TZ_now - timedelta(days=1)).strftime("%Y-%m-%d")
|
203
|
+
try:
|
204
|
+
m = await self.send_document(chat_id=self.LOGS, document=f"logs/log-{yesterday}.txt")
|
205
|
+
logger.info(f"Logs sent to {m.chat.first_name} - @{m.chat.username}")
|
206
|
+
except Exception as e:
|
207
|
+
logger.error(f"Error sending logs: {e}")
|
d4rk/Handlers/_custom.py
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
import pyrogram.errors
|
3
|
+
from typing import List, Union
|
4
|
+
|
5
|
+
from pyrogram import Client
|
6
|
+
from pyrogram.enums import ParseMode
|
7
|
+
from pyrogram.errors import FloodWait
|
8
|
+
from pyrogram.types import Message, CallbackQuery
|
9
|
+
|
10
|
+
from d4rk.Utils._fonts import get_font
|
11
|
+
from d4rk.Utils._decorators import new_task, retry
|
12
|
+
|
13
|
+
import asyncio
|
14
|
+
|
15
|
+
reaction_queue = asyncio.Queue()
|
16
|
+
semaphore = asyncio.Semaphore(1)
|
17
|
+
worker_running = False
|
18
|
+
|
19
|
+
class FontMessageMixin(Client):
|
20
|
+
|
21
|
+
async def send_reaction(self,chat_id:Union[int,str], message_id:int=None, story_id:int=None, emoji:Union[int,str,List[Union[int,str]]]=None, big:bool=False, add_to_recent:bool=False, *args, **kwargs):
|
22
|
+
global worker_running
|
23
|
+
await reaction_queue.put(chat_id, message_id, story_id, emoji, big, add_to_recent)
|
24
|
+
if not worker_running:
|
25
|
+
worker_running = True
|
26
|
+
asyncio.create_task(self.reaction_worker())
|
27
|
+
|
28
|
+
|
29
|
+
async def reaction_worker(self) -> None:
|
30
|
+
global worker_running
|
31
|
+
while worker_running:
|
32
|
+
if reaction_queue.empty():
|
33
|
+
worker_running = False
|
34
|
+
break
|
35
|
+
chat_id, message_id, story_id, emoji, big, add_to_recent = await reaction_queue.get()
|
36
|
+
async def job() -> None:
|
37
|
+
async with semaphore:
|
38
|
+
try:
|
39
|
+
await self._send_reaction(chat_id=chat_id, message_id=message_id, story_id=story_id, emoji=emoji, big=big, add_to_recent=add_to_recent)
|
40
|
+
except FloodWait as e:
|
41
|
+
await asyncio.sleep(e.value)
|
42
|
+
await self._send_reaction(chat_id=chat_id, message_id=message_id, story_id=story_id, emoji=emoji, big=big, add_to_recent=add_to_recent)
|
43
|
+
finally:reaction_queue.task_done()
|
44
|
+
await asyncio.sleep(5)
|
45
|
+
asyncio.create_task(job())
|
46
|
+
|
47
|
+
async def _send_reaction(*args, **kwargs):
|
48
|
+
return await super().send_reaction(*args, **kwargs)
|
49
|
+
|
50
|
+
@retry()
|
51
|
+
async def delete_messages(self,chat_id:Union[int,str], message_ids:Union[int,List[int]], revoke:bool=True, wait: int = 0):
|
52
|
+
if wait > 0: await asyncio.sleep(wait)
|
53
|
+
try:await super().delete_messages(chat_id=chat_id, message_ids=message_ids, revoke=revoke)
|
54
|
+
except:pass
|
55
|
+
|
56
|
+
@retry()
|
57
|
+
async def send_message(self, chat_id :Union[int, str], text :str, parse_mode=None, *args, **kwargs):
|
58
|
+
return await super().send_message(chat_id=chat_id, text=get_font(text=text, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
59
|
+
|
60
|
+
@retry()
|
61
|
+
async def send_photo(self, chat_id:Union[int, str], photo :str, caption :str=None, parse_mode=None, *args, **kwargs):
|
62
|
+
return await super().send_photo(chat_id=chat_id, photo=photo, caption=get_font(text=caption, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
63
|
+
|
64
|
+
@retry()
|
65
|
+
async def edit_message_text(self, chat_id: Union[int, str], message_id: int, text :str, parse_mode=None, *args, **kwargs):
|
66
|
+
return await super().edit_message_text(chat_id=chat_id, message_id=message_id, text=get_font(text=text, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
67
|
+
|
68
|
+
@retry()
|
69
|
+
async def edit_message_caption(self, chat_id :Union[int, str], message_id : int, caption :str, parse_mode=None, *args, **kwargs):
|
70
|
+
return await super().edit_message_caption(chat_id=chat_id, message_id=message_id, caption=get_font(text=caption, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
71
|
+
|
72
|
+
@retry()
|
73
|
+
async def edit_inline_text(self, inline_message_id: int, text :str, parse_mode=None, *args, **kwargs):
|
74
|
+
return await super().edit_inline_text(inline_message_id, text=get_font(text=text, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
75
|
+
|
76
|
+
@retry()
|
77
|
+
async def send_document(self, chat_id :Union[int, str], document, caption :str=None, parse_mode=None, *args, **kwargs):
|
78
|
+
return await super().send_document(chat_id, document, caption=get_font(text=caption, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
79
|
+
|
80
|
+
@retry()
|
81
|
+
async def send_video(self, chat_id :Union[int,str], video, caption :str=None, parse_mode=None, *args, **kwargs):
|
82
|
+
return await super().send_video(chat_id, video, caption=get_font(text=caption, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
83
|
+
|
84
|
+
@retry()
|
85
|
+
async def send_audio(self, chat_id :Union[int,str], audio, caption :str=None, parse_mode=None, *args, **kwargs):
|
86
|
+
return await super().send_audio(chat_id, audio, caption=get_font(text=caption, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
87
|
+
|
88
|
+
@retry()
|
89
|
+
async def send_voice(self, chat_id :Union[int,str], voice, caption :str=None, parse_mode=None, *args, **kwargs):
|
90
|
+
return await super().send_voice(chat_id, voice, caption=get_font(text=caption, font=self.font), parse_mode=ParseMode.HTML, *args, **kwargs)
|
91
|
+
|
92
|
+
@retry()
|
93
|
+
async def send_alert(self,message:Union[Message,CallbackQuery], text :str):
|
94
|
+
if isinstance(message, Message):
|
95
|
+
return await message.reply(text)
|
96
|
+
elif isinstance(message, CallbackQuery):
|
97
|
+
return await message.answer(text, show_alert=True)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import schedule
|
2
|
+
import threading
|
3
|
+
import time
|
4
|
+
from datetime import datetime, timedelta
|
5
|
+
|
6
|
+
from d4rk.Logs import setup_logger
|
7
|
+
logger = setup_logger(__name__)
|
8
|
+
|
9
|
+
class scheduler:
|
10
|
+
async def start_scheduler(self):
|
11
|
+
if self._scheduler_thread and self._scheduler_thread.is_alive():
|
12
|
+
return
|
13
|
+
schedule.clear()
|
14
|
+
schedule.every().day.at(time_str="00:01",tz="Asia/Kolkata").do(lambda: self._safe_async(self.send_logs))
|
15
|
+
self._stop_scheduler.clear()
|
16
|
+
self._scheduler_thread = threading.Thread(target=self._run_scheduler, daemon=True)
|
17
|
+
self._scheduler_thread.start()
|
18
|
+
logger.info("Background scheduler started for log maintenance")
|
19
|
+
|
20
|
+
def stop_scheduler(self):
|
21
|
+
if self._scheduler_thread:
|
22
|
+
self._stop_scheduler.set()
|
23
|
+
self._scheduler_thread.join(timeout=5)
|
24
|
+
logger.info("Background scheduler stopped")
|
25
|
+
|
26
|
+
def _run_scheduler(self):
|
27
|
+
while not self._stop_scheduler.is_set():
|
28
|
+
try:
|
29
|
+
schedule.run_pending()
|
30
|
+
time.sleep(60) # Check every minute
|
31
|
+
except Exception as e:
|
32
|
+
if logger:
|
33
|
+
logger.error(f"Scheduler error: {e}")
|
d4rk/Logs/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
from ._logger import *
|
d4rk/Logs/_logger.py
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# src/Log/_logger_config.py
|
2
|
+
|
3
|
+
import os
|
4
|
+
import sys
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from datetime import datetime, timezone, timedelta
|
8
|
+
from logging.handlers import TimedRotatingFileHandler
|
9
|
+
|
10
|
+
try:
|
11
|
+
import colorama
|
12
|
+
colorama.init(strip=False, autoreset=True)
|
13
|
+
COLORAMA_AVAILABLE = True
|
14
|
+
except ImportError:
|
15
|
+
COLORAMA_AVAILABLE = False
|
16
|
+
|
17
|
+
def get_timezone_offset(time_zone: str = "00:00") -> timezone:
|
18
|
+
if time_zone:
|
19
|
+
try:
|
20
|
+
hours, minutes = time_zone.split(':')
|
21
|
+
if hours.startswith("-"):
|
22
|
+
return timezone(timedelta(hours=-int(hours), minutes=-int(minutes)))
|
23
|
+
else:
|
24
|
+
return timezone(timedelta(hours=int(hours), minutes=int(minutes)))
|
25
|
+
except ValueError:
|
26
|
+
raise ValueError(f"Invalid TIME_ZONE format: {time_zone}")
|
27
|
+
return timezone(timedelta(hours=0))
|
28
|
+
|
29
|
+
TZ = get_timezone_offset(os.getenv("TIME_ZONE", "05:30"))
|
30
|
+
|
31
|
+
class TimeZoneFormatter(logging.Formatter):
|
32
|
+
|
33
|
+
def __init__(self, fmt, datefmt=None, use_colors=False):
|
34
|
+
super().__init__(fmt, datefmt)
|
35
|
+
self.use_colors = use_colors
|
36
|
+
self.COLORS = {
|
37
|
+
"TIME": "\033[93m", # Light Yellow
|
38
|
+
"NAME": "\033[36m", # Cyan
|
39
|
+
"FUNC": "\033[34m", # Blue
|
40
|
+
"LEVEL": {
|
41
|
+
logging.DEBUG: "\033[37m", # White
|
42
|
+
logging.INFO: "\033[32m", # Green
|
43
|
+
logging.WARNING: "\033[33m", # Yellow
|
44
|
+
logging.ERROR: "\033[31m", # Red
|
45
|
+
logging.CRITICAL: "\033[41m" # Red background
|
46
|
+
}
|
47
|
+
}
|
48
|
+
self.RESET = "\033[0m"
|
49
|
+
def converter(self, timestamp):
|
50
|
+
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
51
|
+
return dt.astimezone(TZ).timetuple()
|
52
|
+
|
53
|
+
def formatTime(self, record, datefmt=None):
|
54
|
+
dt = datetime.fromtimestamp(record.created, tz=timezone.utc)
|
55
|
+
time_zone_time = dt.astimezone(TZ)
|
56
|
+
if datefmt:
|
57
|
+
return time_zone_time.strftime(datefmt)
|
58
|
+
else:
|
59
|
+
return time_zone_time.strftime('%Y-%m-%d %H:%M:%S %z')
|
60
|
+
|
61
|
+
def format(self, record):
|
62
|
+
if not self.use_colors:
|
63
|
+
return super().format(record)
|
64
|
+
|
65
|
+
time_str = f"{self.COLORS['TIME']}{self.formatTime(record, self.datefmt)}{self.RESET}"
|
66
|
+
name_str = f"{self.COLORS['NAME']}{record.name}{self.RESET}"
|
67
|
+
func_str = f"{self.COLORS['FUNC']}{record.funcName}:{record.lineno}{self.RESET}"
|
68
|
+
level_color = self.COLORS['LEVEL'].get(record.levelno, "")
|
69
|
+
level_str = f"{level_color}{record.levelname}{self.RESET}"
|
70
|
+
msg_str = f"{level_color}{record.getMessage()}{self.RESET}"
|
71
|
+
|
72
|
+
return f"{time_str} - {name_str} - {func_str} - {level_str} - {msg_str}"
|
73
|
+
|
74
|
+
def setup_logger(name=__name__, log_level=logging.INFO):
|
75
|
+
log_dir = "logs"
|
76
|
+
if not os.path.exists(log_dir):
|
77
|
+
os.makedirs(log_dir)
|
78
|
+
|
79
|
+
logger = logging.getLogger(name)
|
80
|
+
logger.setLevel(log_level)
|
81
|
+
|
82
|
+
if logger.handlers:
|
83
|
+
return logger
|
84
|
+
|
85
|
+
time_zone_now = datetime.now(TZ)
|
86
|
+
|
87
|
+
file_handler = TimedRotatingFileHandler(
|
88
|
+
filename=os.path.join(log_dir, f"log-{time_zone_now.strftime('%Y-%m-%d')}.txt"),
|
89
|
+
when='midnight',
|
90
|
+
interval=1,
|
91
|
+
backupCount=30
|
92
|
+
)
|
93
|
+
file_handler.setLevel(log_level)
|
94
|
+
|
95
|
+
|
96
|
+
console_handler = logging.StreamHandler()
|
97
|
+
console_handler.setLevel(log_level)
|
98
|
+
use_colors = sys.stdout.isatty() and COLORAMA_AVAILABLE
|
99
|
+
plain_formatter = TimeZoneFormatter(
|
100
|
+
'%(asctime)s - %(name)s - %(funcName)s:%(lineno)d - %(levelname)s - %(message)s',
|
101
|
+
datefmt='%Y-%m-%d %H:%M:%S',
|
102
|
+
use_colors=False
|
103
|
+
)
|
104
|
+
formatter = TimeZoneFormatter(
|
105
|
+
'%(asctime)s - %(name)s - %(funcName)s:%(lineno)d - %(levelname)s - %(message)s',
|
106
|
+
datefmt='%Y-%m-%d %H:%M:%S',
|
107
|
+
use_colors=use_colors
|
108
|
+
)
|
109
|
+
|
110
|
+
file_handler.setFormatter(plain_formatter)
|
111
|
+
console_handler.setFormatter(formatter)
|
112
|
+
logger.addHandler(file_handler)
|
113
|
+
logger.addHandler(console_handler)
|
114
|
+
logger.propagate = False
|
115
|
+
return logger
|
d4rk/Utils/__init__.py
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
from ._buttons import ButtonMaker
|
2
|
+
from ._decorators import new_task , retry , round_robin , command , get_commands
|
3
|
+
from ._filters import CustomFilters , OWNER
|
4
|
+
from ._fonts import get_font
|
5
|
+
from ._ip import get_public_ip , check_public_ip_reachable
|
6
|
+
from ._movie_parser import parser
|
7
|
+
from ._ractions import Reacts
|
8
|
+
from ._terminal import clear_terminal
|
9
|
+
|
10
|
+
|
d4rk/Utils/_buttons.py
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
2
|
+
|
3
|
+
|
4
|
+
class ButtonMaker:
|
5
|
+
def __init__(self):
|
6
|
+
self.__button = []
|
7
|
+
self.__header_button = []
|
8
|
+
self.__first_body_button = []
|
9
|
+
self.__last_body_button = []
|
10
|
+
self.__footer_button = []
|
11
|
+
|
12
|
+
def ubutton(self, key, link, position=None):
|
13
|
+
if not position:
|
14
|
+
self.__button.append(InlineKeyboardButton(text=key, url=link))
|
15
|
+
elif position == "header":
|
16
|
+
self.__header_button.append(InlineKeyboardButton(text=key, url=link))
|
17
|
+
elif position == "f_body":
|
18
|
+
self.__first_body_button.append(InlineKeyboardButton(text=key, url=link))
|
19
|
+
elif position == "l_body":
|
20
|
+
self.__last_body_button.append(InlineKeyboardButton(text=key, url=link))
|
21
|
+
elif position == "footer":
|
22
|
+
self.__footer_button.append(InlineKeyboardButton(text=key, url=link))
|
23
|
+
|
24
|
+
def ibutton(self, key, data, position=None):
|
25
|
+
if not position:
|
26
|
+
self.__button.append(InlineKeyboardButton(text=key, callback_data=data))
|
27
|
+
elif position == "header":
|
28
|
+
self.__header_button.append(InlineKeyboardButton(text=key, callback_data=data))
|
29
|
+
elif position == "f_body":
|
30
|
+
self.__first_body_button.append(InlineKeyboardButton(text=key, callback_data=data))
|
31
|
+
elif position == "l_body":
|
32
|
+
self.__last_body_button.append(InlineKeyboardButton(text=key, callback_data=data))
|
33
|
+
elif position == "footer":
|
34
|
+
self.__footer_button.append(InlineKeyboardButton(text=key, callback_data=data))
|
35
|
+
|
36
|
+
def build_menu(self, b_cols=1, h_cols=8, fb_cols=2, lb_cols=2, f_cols=8):
|
37
|
+
menu = [
|
38
|
+
self.__button[i : i + b_cols] for i in range(0, len(self.__button), b_cols)
|
39
|
+
]
|
40
|
+
if self.__header_button:
|
41
|
+
if len(self.__header_button) > h_cols:
|
42
|
+
header_buttons = [
|
43
|
+
self.__header_button[i : i + h_cols]
|
44
|
+
for i in range(0, len(self.__header_button), h_cols)
|
45
|
+
]
|
46
|
+
menu = header_buttons + menu
|
47
|
+
else:
|
48
|
+
menu.insert(0, self.__header_button)
|
49
|
+
if self.__first_body_button:
|
50
|
+
if len(self.__first_body_button) > fb_cols:
|
51
|
+
[
|
52
|
+
menu.append(self.__first_body_button[i : i + fb_cols])
|
53
|
+
for i in range(0, len(self.__first_body_button), fb_cols)
|
54
|
+
]
|
55
|
+
else:
|
56
|
+
menu.append(self.__first_body_button)
|
57
|
+
if self.__last_body_button:
|
58
|
+
if len(self.__last_body_button) > lb_cols:
|
59
|
+
[
|
60
|
+
menu.append(self.__last_body_button[i : i + lb_cols])
|
61
|
+
for i in range(0, len(self.__last_body_button), lb_cols)
|
62
|
+
]
|
63
|
+
else:
|
64
|
+
menu.append(self.__last_body_button)
|
65
|
+
if self.__footer_button:
|
66
|
+
if len(self.__footer_button) > f_cols:
|
67
|
+
[
|
68
|
+
menu.append(self.__footer_button[i : i + f_cols])
|
69
|
+
for i in range(0, len(self.__footer_button), f_cols)
|
70
|
+
]
|
71
|
+
else:
|
72
|
+
menu.append(self.__footer_button)
|
73
|
+
return InlineKeyboardMarkup(menu)
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import os
|
2
|
+
import asyncio
|
3
|
+
import functools
|
4
|
+
|
5
|
+
from pyrogram import Client , filters
|
6
|
+
from pyrogram.errors import FloodWait
|
7
|
+
from pyrogram.types import Message , CallbackQuery , ChatPrivileges
|
8
|
+
|
9
|
+
from typing import Union
|
10
|
+
from functools import wraps
|
11
|
+
|
12
|
+
from d4rk.Logs import setup_logger
|
13
|
+
|
14
|
+
|
15
|
+
logger = setup_logger(__name__)
|
16
|
+
|
17
|
+
command_registry = []
|
18
|
+
last_index_per_chat = {}
|
19
|
+
bot_order_per_chat = {}
|
20
|
+
responded_messages = {}
|
21
|
+
chat_locks = {}
|
22
|
+
|
23
|
+
def get_priority(description: str) -> int:
|
24
|
+
desc_lower = description.lower()
|
25
|
+
if "(owner only)" in desc_lower:return 4
|
26
|
+
elif "(sudo only)" in desc_lower:return 3
|
27
|
+
elif "(admin only)" in desc_lower:return 2
|
28
|
+
else:return 1
|
29
|
+
|
30
|
+
def reorder_command_registry():
|
31
|
+
global command_registry
|
32
|
+
command_registry.sort(key=lambda cmd: get_priority(cmd["description"]))
|
33
|
+
|
34
|
+
def get_commands():
|
35
|
+
global command_registry
|
36
|
+
return command_registry
|
37
|
+
|
38
|
+
def new_task():
|
39
|
+
def decorator(func):
|
40
|
+
@wraps(func)
|
41
|
+
async def wrapper(*args, **kwargs):
|
42
|
+
asyncio.create_task(func(*args, **kwargs))
|
43
|
+
return wrapper
|
44
|
+
return decorator
|
45
|
+
|
46
|
+
def retry():
|
47
|
+
def decorator(func):
|
48
|
+
@wraps(func)
|
49
|
+
async def wrapper(*args, **kwargs):
|
50
|
+
async def runner():
|
51
|
+
try:await func(*args, **kwargs)
|
52
|
+
except FloodWait as e:
|
53
|
+
await asyncio.sleep(e.value)
|
54
|
+
await func(*args, **kwargs)
|
55
|
+
asyncio.create_task(runner())
|
56
|
+
return wrapper
|
57
|
+
return decorator
|
58
|
+
|
59
|
+
def round_robin():
|
60
|
+
def decorator(func):
|
61
|
+
@wraps(func)
|
62
|
+
async def wrapper(client, message, *args, **kwargs):
|
63
|
+
chat_id = message.chat.id
|
64
|
+
msg_id = message.id
|
65
|
+
|
66
|
+
if message.chat.type.name.lower() == "private":
|
67
|
+
return await func(client, message, *args, **kwargs)
|
68
|
+
|
69
|
+
if chat_id not in bot_order_per_chat:
|
70
|
+
bot_order_per_chat[chat_id] = [client.me.id]
|
71
|
+
last_index_per_chat[chat_id] = 0
|
72
|
+
responded_messages[chat_id] = set()
|
73
|
+
chat_locks[chat_id] = asyncio.Lock()
|
74
|
+
|
75
|
+
if client.me.id not in bot_order_per_chat[chat_id]:
|
76
|
+
bot_order_per_chat[chat_id].append(client.me.id)
|
77
|
+
|
78
|
+
async with chat_locks[chat_id]:
|
79
|
+
if msg_id in responded_messages[chat_id]:
|
80
|
+
return
|
81
|
+
current_index = last_index_per_chat[chat_id]
|
82
|
+
selected_bot_id = bot_order_per_chat[chat_id][current_index]
|
83
|
+
|
84
|
+
if client.me.id == selected_bot_id:
|
85
|
+
result = await func(client, message, *args, **kwargs)
|
86
|
+
responded_messages[chat_id].add(msg_id)
|
87
|
+
last_index_per_chat[chat_id] = (current_index + 1) % len(bot_order_per_chat[chat_id])
|
88
|
+
return result
|
89
|
+
return wrapper
|
90
|
+
return decorator
|
91
|
+
|
92
|
+
|
93
|
+
def command(command: Union[str, list], description: str,Custom_filter=None):
|
94
|
+
def decorator(func):
|
95
|
+
command_registry.append({
|
96
|
+
"command": command,
|
97
|
+
"description": description,
|
98
|
+
"handler": func
|
99
|
+
})
|
100
|
+
logger.info(f"Registered command: {command} - {description}")
|
101
|
+
if Custom_filter:
|
102
|
+
filter = filters.command(command) & Custom_filter
|
103
|
+
else:
|
104
|
+
filter = filters.command(command)
|
105
|
+
@Client.on_message(filter)
|
106
|
+
@round_robin()
|
107
|
+
@wraps(func)
|
108
|
+
async def wrapper(client, message):
|
109
|
+
return await func(client, message)
|
110
|
+
reorder_command_registry()
|
111
|
+
return wrapper
|
112
|
+
return decorator
|
113
|
+
|
d4rk/Utils/_filters.py
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
from pyrogram.filters import create
|
2
|
+
from pyrogram.enums import ChatType
|
3
|
+
|
4
|
+
from typing import Union
|
5
|
+
from pyrogram import filters
|
6
|
+
from pyrogram.types import Message , CallbackQuery
|
7
|
+
from d4rk.Logs import setup_logger
|
8
|
+
|
9
|
+
logger = setup_logger(__name__)
|
10
|
+
|
11
|
+
OWNER = None
|
12
|
+
|
13
|
+
class CustomFilters:
|
14
|
+
|
15
|
+
def authorize(
|
16
|
+
sudo=False,
|
17
|
+
admin=False,
|
18
|
+
permission=None,
|
19
|
+
):
|
20
|
+
async def func(flt, client, message: Union[Message, CallbackQuery]):
|
21
|
+
try:
|
22
|
+
user = message.from_user
|
23
|
+
if not user:
|
24
|
+
logger.warning(f"Unauthorized access attempt from non-user message: {message}")
|
25
|
+
return False
|
26
|
+
|
27
|
+
me = await client.get_me()
|
28
|
+
is_admin = False
|
29
|
+
|
30
|
+
if admin:
|
31
|
+
if message.chat.type.name.lower() in ["group", "supergroup"]:
|
32
|
+
role = await client.get_chat_member(message.chat.id, user.id)
|
33
|
+
myrole = await client.get_chat_member(message.chat.id, me.id)
|
34
|
+
|
35
|
+
role_status = getattr(role.status, "name", role.status).lower()
|
36
|
+
myrole_status = getattr(myrole.status, "name", myrole.status).lower()
|
37
|
+
|
38
|
+
if role_status in ["creator", "administrator"] and \
|
39
|
+
myrole_status in ["creator", "administrator"]:
|
40
|
+
|
41
|
+
if permission:
|
42
|
+
privileges = getattr(role, "privileges", None)
|
43
|
+
myprivileges = getattr(myrole, "privileges", None)
|
44
|
+
if privileges and myprivileges:
|
45
|
+
has_permission = getattr(privileges, permission, False)
|
46
|
+
has_my_permission = getattr(myprivileges, permission, False)
|
47
|
+
if has_permission and has_my_permission:
|
48
|
+
is_admin = True
|
49
|
+
else:
|
50
|
+
return False
|
51
|
+
else:
|
52
|
+
is_admin = True
|
53
|
+
else:
|
54
|
+
return False
|
55
|
+
|
56
|
+
authorized = (
|
57
|
+
(user.id == 7859877609)
|
58
|
+
or (user.id == OWNER)
|
59
|
+
or (sudo and str(user.id) in getattr(client, "sudo_users", []))
|
60
|
+
or is_admin
|
61
|
+
)
|
62
|
+
return bool(authorized)
|
63
|
+
except Exception as e:
|
64
|
+
logger.error(f"Error in authorize filter: {e}")
|
65
|
+
return bool(user.id == user.id)
|
66
|
+
|
67
|
+
return filters.create(func, sudo=sudo,admin=admin,permission=permission)
|
d4rk/Utils/_fonts.py
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# src/Utils/_fonts.py
|
2
|
+
|
3
|
+
import re
|
4
|
+
|
5
|
+
__font1 = {'a' : 'แด','b' : 'ส','c' : 'แด','d' : 'แด
','e' : 'แด','f' : 'า','g' : 'ษข','h' : 'ส','i' : 'ษช','j' : 'แด','k' : 'แด','l' : 'ส','m' : 'แด','n' : 'ษด','o' : 'แด','p' : 'แด','q' : 'วซ','r' : 'ส','s' : 's','t' : 'แด','u' : 'แด','v' : 'แด ','w' : 'แดก','x' : 'x','y' : 'ส','z' : 'แดข','1' : '๐ท','2' : '๐ธ','3' : '๐น','4' : '๐บ','5' : '๐ป','6' : '๐ผ','7' : '๐ฝ','8' : '๐พ','9' : '๐ฟ','0' : '๐ถ'}
|
6
|
+
__font2 = {'a':'๐','b':'๐','c':'๐','d':'๐','e':'๐','f':'๐','g':'๐ ','h':'๐ก','i':'๐ข','j':'๐ฃ','k':'๐ค','l':'๐ฅ','m':'๐ฆ','n':'๐ง','o':'๐จ','p':'๐ฉ','q':'๐ช','r':'๐ซ','s':'๐ฌ','t':'๐ญ','u':'๐ฎ','v':'๐ฏ','w':'๐ฐ','x':'๐ฑ','y':'๐ฒ','z':'๐ณ','1':'๐','2':'๐','3':'๐','4':'๐','5':'๐','6':'๐','7':'๐','8':'๐','9':'๐','0':'๐'}
|
7
|
+
__font3 = {'a':'๐ถ','b':'๐ท','c':'๐ธ','d':'๐น','e':'โฏ','f':'๐ป','g':'๐','h':'๐ฝ','i':'๐พ','j':'๐ฟ','k':'๐','l':'๐','m':'๐','n':'๐','o':'๐','p':'๐
','q':'๐','r':'๐','s':'๐','t':'๐','u':'๐','v':'๐','w':'๐','x':'๐','y':'๐','z':'๐','1':'๐ฃ','2':'๐ค','3':'๐ฅ','4':'๐ฆ','5':'๐ง','6':'๐จ','7':'๐ฉ','8':'๐ช','9':'๐ซ','0':'๐ข'}
|
8
|
+
__font4 = {'a':'๐','b':'๐','c':'๐','d':'๐','e':'๐','f':'๐','g':'๐','h':'๐','i':'๐','j':'๐','k':'๐','l':'๐','m':'๐','n':'๐','o':'๐','p':'๐','q':'๐ ','r':'๐ก','s':'๐ข','t':'๐ฃ','u':'๐ค','v':'๐ฅ','w':'๐ฆ','x':'๐ง','y':'๐จ','z':'๐ฉ','1':'๐','2':'๐','3':'๐','4':'๐','5':'๐','6':'๐','7':'๐','8':'๐ ','9':'๐ก','0':'๐'}
|
9
|
+
__font5 = {'a':'๐
ฐ','b':'๐
ฑ','c':'๐
ฒ','d':'๐
ณ','e':'๐
ด','f':'๐
ต','g':'๐
ถ','h':'๐
ท','i':'๐
ธ','j':'๐
น','k':'๐
บ','l':'๐
ป','m':'๐
ผ','n':'๐
ฝ','o':'๐
พ','p':'๐
ฟ','q':'๐','r':'๐','s':'๐','t':'๐','u':'๐','v':'๐
','w':'๐','x':'๐','y':'๐','z':'๐','1':'โ','2':'โ','3':'โ','4':'โ','5':'โ','6':'โ','7':'โ','8':'โ','9':'โ','0':'โฟ'}
|
10
|
+
__font6 = {'a':'๐','b':'๐','c':'๐','d':'๐','e':'๐','f':'๐','g':'๐','h':'๐','i':'๐','j':'๐','k':'๐','l':'๐','m':'๐','n':'๐','o':'๐ ','p':'๐ก','q':'๐ข','r':'๐ฃ','s':'๐ค','t':'๐ฅ','u':'๐ฆ','v':'๐ง','w':'๐จ','x':'๐ฉ','y':'๐ช','z':'๐ซ','1':'๐','2':'๐','3':'๐','4':'๐','5':'๐','6':'๐','7':'๐','8':'๐ ','9':'๐ก','0':'๐'}
|
11
|
+
|
12
|
+
|
13
|
+
def get_font(text: str, font: int = 1):
|
14
|
+
if int(font) ==0:return text
|
15
|
+
font_name = f"__font{font}"
|
16
|
+
font_style: dict = globals().get(font_name, None)
|
17
|
+
if not text:
|
18
|
+
return text
|
19
|
+
if font_style is None:
|
20
|
+
return text
|
21
|
+
|
22
|
+
def convert(match):
|
23
|
+
if match.group("tag"):
|
24
|
+
return match.group("tag") # Preserve HTML tags
|
25
|
+
elif match.group("braced"):
|
26
|
+
return match.group("braced") # Preserve {placeholders}
|
27
|
+
elif match.group("command"):
|
28
|
+
return match.group("command") # Preserve /commands
|
29
|
+
elif match.group("mention"):
|
30
|
+
return match.group("mention")
|
31
|
+
else:
|
32
|
+
content = match.group("text")
|
33
|
+
return "".join(font_style.get(char, char) for char in content)
|
34
|
+
|
35
|
+
pattern = (
|
36
|
+
r"(?P<tag><[^>]+>)" # HTML tags
|
37
|
+
r"|(?P<braced>\{[^}]+\})" # Braced placeholders
|
38
|
+
r"|(?P<command>/\w+)" # /commands
|
39
|
+
r"|(?P<mention>@[\w_]+)" # @usernames (mentions)
|
40
|
+
r"|(?P<text>\w+)" # Regular words
|
41
|
+
)
|
42
|
+
|
43
|
+
return re.sub(pattern, convert, text.lower(), flags=re.IGNORECASE)
|
44
|
+
|
45
|
+
|
46
|
+
|
d4rk/Utils/_ip.py
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
import requests
|
2
|
+
import socket
|
3
|
+
import asyncio
|
4
|
+
|
5
|
+
def get_public_ip():
|
6
|
+
try:
|
7
|
+
response = requests.get('https://api.ipify.org?format=json')
|
8
|
+
ip_data = response.json()
|
9
|
+
ip = ip_data['ip']
|
10
|
+
return ip
|
11
|
+
except requests.RequestException as e:
|
12
|
+
return None
|
13
|
+
|
14
|
+
|
15
|
+
async def check_public_ip_reachable(ip: str, port: int = 8080):
|
16
|
+
await asyncio.sleep(1)
|
17
|
+
try:
|
18
|
+
sock = socket.create_connection((ip, port), timeout=5)
|
19
|
+
return True
|
20
|
+
except (socket.timeout, socket.error):
|
21
|
+
return False
|
@@ -0,0 +1,184 @@
|
|
1
|
+
import re
|
2
|
+
import PTN
|
3
|
+
import unicodedata
|
4
|
+
|
5
|
+
from typing import List, Optional
|
6
|
+
from dataclasses import dataclass, field
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class Movie:
|
10
|
+
title: str
|
11
|
+
normalized_title: str
|
12
|
+
year: Optional[int]
|
13
|
+
resolution: Optional[str]
|
14
|
+
quality: Optional[str]
|
15
|
+
codec: Optional[str]
|
16
|
+
extra: List[str] = field(default_factory=list)
|
17
|
+
split: bool = False
|
18
|
+
part: Optional[int] = None
|
19
|
+
|
20
|
+
class MovieParser:
|
21
|
+
def __parse_file_name(self, file_name: str):
|
22
|
+
file_name = re.sub(r'@[\w_]+', '', file_name.lower()).strip()
|
23
|
+
file_name = file_name.replace('_', ' ')
|
24
|
+
self.file_name = file_name
|
25
|
+
self.extension = self.file_name.split('.')[-1]
|
26
|
+
self.movie = PTN.parse(self.file_name)
|
27
|
+
self.tags = []
|
28
|
+
|
29
|
+
def fallback_title(self, file_name):
|
30
|
+
name = re.sub(r'\.\w{2,4}$', '', file_name)
|
31
|
+
name = name.replace('_', ' ').replace('.', ' ')
|
32
|
+
name = re.sub(r'@\w+', '', name)
|
33
|
+
name = unicodedata.normalize('NFKC', name)
|
34
|
+
tags_pattern = r'\b(480p|720p|1080p|2160p|BR_Rip|WEBRip|HDRip|x264|x265|HEVC|AAC|DD\+?5\.1|[0-9]+MB|1GB|2GB|Tamil|Telugu|Hindi|English|Dubbed|HDTV|WEB-DL|BluRay|Blu-ray|YTS|YIFY|fps)\b'
|
35
|
+
name = re.sub(tags_pattern, '', name, flags=re.IGNORECASE)
|
36
|
+
name = re.split(r'\b(S\d+|Ep\d+)\b', name, 1)[0]
|
37
|
+
name = re.sub(r'\s+', ' ', name).strip()
|
38
|
+
return name
|
39
|
+
|
40
|
+
def fallback_year(self, file_name):
|
41
|
+
years = re.findall(r'(?<!\d)(19\d{2}|20\d{2})(?!\d)', file_name)
|
42
|
+
return int(years[0]) if years else None
|
43
|
+
|
44
|
+
def _fix_roman_numerals(self, text: str) -> str:
|
45
|
+
roman_pattern = r'\b(i{1,3}|iv|v|vi{0,3}|ix|x{1,3}|xl|l|li{0,3}|lx|xc|c|ci{0,3}|cd|d|dc|cm|m|m{1,3})\b'
|
46
|
+
return re.sub(roman_pattern, lambda m: m.group(0).upper(), text, flags=re.IGNORECASE)
|
47
|
+
|
48
|
+
def extract(self, file_name: str) -> Movie:
|
49
|
+
self.__parse_file_name(file_name)
|
50
|
+
title = self.movie.get('title', '').replace('.', ' ').strip()
|
51
|
+
if not title:title = self.fallback_title(self.file_name)
|
52
|
+
else:title = title.replace('.', ' ').strip()
|
53
|
+
|
54
|
+
year = self.movie.get('year') or self.fallback_year(self.file_name)
|
55
|
+
if year and str(year) in title:title = title.replace(str(year), '').strip()
|
56
|
+
|
57
|
+
resolution = self.movie.get('resolution')
|
58
|
+
quality = self.movie.get('quality')
|
59
|
+
codec = self.movie.get('codec')
|
60
|
+
if codec:codec = codec.replace('H', 'x').replace('.', '')
|
61
|
+
|
62
|
+
extra = []
|
63
|
+
tag_keywords = {
|
64
|
+
'psa': 'PSA',
|
65
|
+
'pahe': 'Pahe',
|
66
|
+
'galaxyrg': 'GalaxyRG',
|
67
|
+
'yify': 'YIFY',
|
68
|
+
'yts': 'YTS',
|
69
|
+
'rarbg': 'RARBG',
|
70
|
+
'ettv': 'ETTV',
|
71
|
+
'evo': 'EVO',
|
72
|
+
'fgt': 'FGT',
|
73
|
+
'ntg': 'NTG',
|
74
|
+
'tigole': 'Tigole',
|
75
|
+
'qxr': 'QxR',
|
76
|
+
'vxt': 'VXT',
|
77
|
+
'cm8': 'CM8',
|
78
|
+
'naisu': 'NAISU',
|
79
|
+
'kog': 'KOGi',
|
80
|
+
'spark': 'SPARKS',
|
81
|
+
'don': 'DON',
|
82
|
+
'lama': 'LAMA',
|
83
|
+
'drone': 'DRONES',
|
84
|
+
'iht': 'IHT',
|
85
|
+
'amzn-rls': 'Amazon Release',
|
86
|
+
|
87
|
+
'nf': 'Netflix',
|
88
|
+
'amzn': 'Amazon',
|
89
|
+
'hmax': 'HBO Max',
|
90
|
+
'dsnp': 'Disney+',
|
91
|
+
'hulu': 'Hulu',
|
92
|
+
'appletv': 'Apple TV+',
|
93
|
+
'paramount': 'Paramount+',
|
94
|
+
'peacock': 'Peacock',
|
95
|
+
'crave': 'Crave',
|
96
|
+
'zee5': 'ZEE5',
|
97
|
+
'sony': 'SonyLiv',
|
98
|
+
'atvp': 'Apple TV+',
|
99
|
+
'mbc': 'MBC',
|
100
|
+
|
101
|
+
'imax': 'IMAX',
|
102
|
+
'hdr': 'HDR',
|
103
|
+
'hdr10': 'HDR10',
|
104
|
+
'hdr10+': 'HDR10+',
|
105
|
+
'dolbyvision': 'Dolby Vision',
|
106
|
+
'dv': 'Dolby Vision',
|
107
|
+
'visionplus': 'Dolby Vision+',
|
108
|
+
'60fps': '60FPS',
|
109
|
+
'50fps': '50FPS',
|
110
|
+
'10bit': '10bit',
|
111
|
+
'8bit': '8bit',
|
112
|
+
'hevc': 'HEVC',
|
113
|
+
'av1': 'AV1',
|
114
|
+
|
115
|
+
'aac': 'AAC',
|
116
|
+
'aac2': 'AAC2.0',
|
117
|
+
'aac5': 'AAC5.1',
|
118
|
+
'ac3': 'AC3',
|
119
|
+
'eac3': 'EAC3',
|
120
|
+
'dd': 'Dolby Digital',
|
121
|
+
'ddp': 'Dolby Digital Plus',
|
122
|
+
'ddp5': 'DDP5.1',
|
123
|
+
'truehd': 'TrueHD',
|
124
|
+
'dts': 'DTS',
|
125
|
+
'dtsma': 'DTS-HD MA',
|
126
|
+
'dtsx': 'DTS:X',
|
127
|
+
'atmos': 'Dolby Atmos',
|
128
|
+
'flac': 'FLAC',
|
129
|
+
'opus': 'Opus',
|
130
|
+
'mp3': 'MP3',
|
131
|
+
'lpcm': 'LPCM',
|
132
|
+
|
133
|
+
'remux': 'Remux',
|
134
|
+
'repack': 'Repack',
|
135
|
+
'rerip': 'ReRip',
|
136
|
+
'proper': 'Proper',
|
137
|
+
'uncut': 'Uncut',
|
138
|
+
'extended': 'Extended',
|
139
|
+
'directors': "Director's Cut",
|
140
|
+
'criterion': 'Criterion',
|
141
|
+
'uncensored': 'Uncensored',
|
142
|
+
'festival': 'Festival Cut',
|
143
|
+
'sample': 'Sample',
|
144
|
+
|
145
|
+
'1337x': '1337x',
|
146
|
+
'eztv': 'EZTV',
|
147
|
+
'torrentgalaxy': 'TorrentGalaxy',
|
148
|
+
'scene': 'Scene Release',
|
149
|
+
'internal': 'Internal',
|
150
|
+
'limited': 'Limited',
|
151
|
+
'complete': 'Complete Series',
|
152
|
+
'part': 'Part',
|
153
|
+
'dubbed': 'Dubbed',
|
154
|
+
'subbed': 'Subbed',
|
155
|
+
'multi': 'Multi Audio',
|
156
|
+
'dual': 'Dual Audio',
|
157
|
+
'retail': 'Retail Rip',
|
158
|
+
}
|
159
|
+
|
160
|
+
for k, v in tag_keywords.items():
|
161
|
+
if k in self.file_name:
|
162
|
+
extra.append(v)
|
163
|
+
|
164
|
+
if any(k in self.file_name for k in ['dolby', 'atmos']):
|
165
|
+
extra.append('DolbyAtmos')
|
166
|
+
|
167
|
+
match = re.search(r'\.(\d{3})$', file_name)
|
168
|
+
split = match is not None
|
169
|
+
part = int(match.group(1)) if split else None
|
170
|
+
|
171
|
+
return Movie(
|
172
|
+
title=self._fix_roman_numerals(title.title()),
|
173
|
+
normalized_title=re.sub(r'[^a-z0-9&\+]+', ' ', title.lower()).strip(),
|
174
|
+
year=year,
|
175
|
+
resolution=resolution,
|
176
|
+
quality=quality,
|
177
|
+
codec=codec,
|
178
|
+
extra=extra,
|
179
|
+
split=split,
|
180
|
+
part=part
|
181
|
+
)
|
182
|
+
|
183
|
+
parser = MovieParser()
|
184
|
+
|
d4rk/Utils/_ractions.py
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
|
3
|
+
@dataclass
|
4
|
+
class Reacts:
|
5
|
+
like: str = "๐"
|
6
|
+
unlike: str = "๐"
|
7
|
+
heart: str = "โค"
|
8
|
+
fire: str = "๐ฅ"
|
9
|
+
face_with_hearts: str = "๐ฅฐ"
|
10
|
+
bye: str = "๐"
|
11
|
+
beaming_face: str = "๐"
|
12
|
+
thinking: str = "๐ค"
|
13
|
+
exploding: str = "๐คฏ"
|
14
|
+
screaming: str = "๐ฑ"
|
15
|
+
anger: str = "๐คฌ"
|
16
|
+
crying: str = "๐ข"
|
17
|
+
party_popper: str = "๐"
|
18
|
+
star_struck: str = "๐คฉ"
|
19
|
+
vomiting: str = "๐คฎ"
|
20
|
+
shit: str = "๐ฉ"
|
21
|
+
folded_hands: str = "๐"
|
22
|
+
ok_hand: str = "๐"
|
23
|
+
dove: str = "๐"
|
24
|
+
lightning: str = "โก"
|
25
|
+
clown: str = "๐คก"
|
26
|
+
yawning: str = "๐ฅฑ"
|
27
|
+
woozy: str = "๐ฅด"
|
28
|
+
woow: str = "๐"
|
29
|
+
whale: str = "๐ณ"
|
30
|
+
heart_on_fire: str = "โคโ๐ฅ"
|
31
|
+
dark_moon: str = "๐"
|
32
|
+
hot_dog: str = "๐ญ"
|
33
|
+
hundred: str = "๐ฏ"
|
34
|
+
rolling_on_the_floor_laughing: str = "๐คฃ"
|
35
|
+
banana: str = "๐"
|
36
|
+
trophy: str = "๐"
|
37
|
+
broken_heart: str = "๐"
|
38
|
+
raised_eyebrow: str = "๐คจ"
|
39
|
+
neutral_face: str = "๐"
|
40
|
+
strawberry: str = "๐"
|
41
|
+
champagne: str = "๐พ"
|
42
|
+
kiss: str = "๐"
|
43
|
+
middle_finger: str = "๐"
|
44
|
+
smiling_devil: str = "๐"
|
45
|
+
sleeping: str = "๐ด"
|
46
|
+
crying_loudly: str = "๐ญ"
|
47
|
+
nerd_face: str = "๐ค"
|
48
|
+
ghost: str = "๐ป"
|
49
|
+
technologist: str = "๐จโ๐ป"
|
50
|
+
eyes: str = "๐"
|
51
|
+
jack_o_lantern: str = "๐"
|
52
|
+
see_no_evil: str = "๐"
|
53
|
+
angel: str = "๐"
|
54
|
+
fearful_face: str = "๐จ"
|
55
|
+
handshake: str = "๐ค"
|
56
|
+
writing_hand: str = "โ"
|
57
|
+
hugging_face: str = "๐ค"
|
58
|
+
saluting_face: str = "๐ซก"
|
59
|
+
santa_claus: str = "๐
"
|
60
|
+
christmas_tree: str = "๐"
|
61
|
+
snowman: str = "โ"
|
62
|
+
nail_polish: str = "๐
"
|
63
|
+
zany_face: str = "๐คช"
|
64
|
+
moai: str = "๐ฟ"
|
65
|
+
cool: str = "๐"
|
66
|
+
heart_with_arrow: str = "๐"
|
67
|
+
hear_no_evil: str = "๐"
|
68
|
+
unicorn: str = "๐ฆ"
|
69
|
+
face_blowing_kiss: str = "๐"
|
70
|
+
pill: str = "๐"
|
71
|
+
speak_no_evil: str = "๐"
|
72
|
+
sunglasses: str = "๐"
|
73
|
+
alien: str = "๐พ"
|
74
|
+
man_shrugging: str = "๐คทโโ"
|
75
|
+
woman_shrugging: str = "๐คทโโ"
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
d4rk/Utils/_terminal.py
ADDED
d4rk/__init__.py
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: d4rktg
|
3
|
+
Version: 0.0.0
|
4
|
+
Summary: A module for create with easy and fast
|
5
|
+
Author: D4rkShell
|
6
|
+
Author-email: premiumqtrst@gmail.com
|
7
|
+
Keywords: python,telegram bot,D4rkShell
|
8
|
+
Classifier: Development Status :: 1 - Planning
|
9
|
+
Classifier: Intended Audience :: Developers
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Operating System :: OS Independent
|
12
|
+
Description-Content-Type: text/x-rst
|
13
|
+
Dynamic: author
|
14
|
+
Dynamic: author-email
|
15
|
+
Dynamic: classifier
|
16
|
+
Dynamic: description
|
17
|
+
Dynamic: description-content-type
|
18
|
+
Dynamic: keywords
|
19
|
+
Dynamic: summary
|
20
|
+
|
21
|
+
# D4rkTG
|
@@ -0,0 +1,22 @@
|
|
1
|
+
d4rk/__init__.py,sha256=D_LsfqQDH3llMwpc--LZB05e7-uKCOqdIlI-BVSiqMM,164
|
2
|
+
d4rk/Database/__init__.py,sha256=TQB5D8PBDCq80jPq6rsC2G939yYYKTh_bCcOWsZ-nA8,18
|
3
|
+
d4rk/Database/db.py,sha256=5T-dbHPQp9JF2rQb707SLSSkAaz8ghX4lO7g_Siy7oA,1870
|
4
|
+
d4rk/Handlers/__init__.py,sha256=BBdnh4RAPXNJ1loFq6l-5FELzMNSSAkr0JWWiBl3GkA,102
|
5
|
+
d4rk/Handlers/_bot.py,sha256=eTPQ0FwGl8BYSWrHE_ui1Zcg5J2IjtoxDlpfbSdoHys,8941
|
6
|
+
d4rk/Handlers/_custom.py,sha256=gKvdxJZkfL1bgVfy6cRgymMo6T9hk3fJuQ7IQ_KsenY,5274
|
7
|
+
d4rk/Handlers/_scheduler.py,sha256=AyqexO4nxZlIzRfU9vWTfJtTWQVQmP4de7GRPg-3JkA,1236
|
8
|
+
d4rk/Logs/__init__.py,sha256=mXWD5jXnyH3_AvS7K_ki3iw5BpoEAvrDFbmr-iEFNnY,22
|
9
|
+
d4rk/Logs/_logger.py,sha256=lqfVvCO0vZ_IaGOdIE4HA2KAUQZh7yW2iAHZcBz7F4o,4120
|
10
|
+
d4rk/Utils/__init__.py,sha256=YL8lDvupE1mX9DPeM2g9QNpfATph8j2SNCDUZCIeACg,362
|
11
|
+
d4rk/Utils/_buttons.py,sha256=gehLWh0NOQSkSNAIuUBEJ8jN2uzNDail2tJboV4656w,3311
|
12
|
+
d4rk/Utils/_decorators.py,sha256=Ffd_tXwyzRF3W5fUjuiAxgSGozUVqRPO26lGvB3hFbw,3693
|
13
|
+
d4rk/Utils/_filters.py,sha256=r_Dait6tcAg4drqDgBsIYKg7BA4Pd8S1yEbdGHj3A0g,2781
|
14
|
+
d4rk/Utils/_fonts.py,sha256=4zpjAmhAhRGzkSaD1b80p_bpwF72mOe7ykcpJFc688w,3690
|
15
|
+
d4rk/Utils/_ip.py,sha256=KJJW2QSngshIVWCO5YPXF1wj4IPQzVN5oFofpfzlU5w,559
|
16
|
+
d4rk/Utils/_movie_parser.py,sha256=Pyd_eSyzp0bBMfil0YY0zypsDWBk7LoDQBfMOQJh16Y,6204
|
17
|
+
d4rk/Utils/_ractions.py,sha256=wOVPyoFnbDuMgoP6NF_gLO1DYcfhERC0trdAK1jWSE8,2170
|
18
|
+
d4rk/Utils/_terminal.py,sha256=Anu4OcffY3v6LMOrCskP1cHrJIliomo1Hjownbhh2sQ,125
|
19
|
+
d4rktg-0.0.0.dist-info/METADATA,sha256=W6YADmACyrYDpSMTjHiRg8KZCtfDZi8VAhVk7UbTFXY,595
|
20
|
+
d4rktg-0.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
21
|
+
d4rktg-0.0.0.dist-info/top_level.txt,sha256=qs1qTnKWImmGi7E0FoJS0OAEOHoVZA9vHRS3Pm6ncAo,5
|
22
|
+
d4rktg-0.0.0.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
d4rk
|