randydev commited on
Commit
93807cf
·
verified ·
1 Parent(s): 7eb4b78

Upload 2 files

Browse files
Files changed (2) hide show
  1. Akeno/utils/handler.py +9 -0
  2. Akeno/utils/helps.py +466 -0
Akeno/utils/handler.py CHANGED
@@ -3,7 +3,16 @@ from pyrogram import Client, filters
3
  from pyrogram.enums import ChatType
4
  from pyrogram.handlers import MessageHandler
5
  from pyrogram.types import Message
 
 
 
 
6
 
7
  group_only = [ChatType.GROUP, ChatType.SUPERGROUP]
8
  Akeno = Client.on_message
9
  Akeno_chat_member_updated = Client.on_chat_member_updated()
 
 
 
 
 
 
3
  from pyrogram.enums import ChatType
4
  from pyrogram.handlers import MessageHandler
5
  from pyrogram.types import Message
6
+ import pathlib
7
+ from time import perf_counter
8
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
9
+ from Akeno.utils.helps import ModuleHelp
10
 
11
  group_only = [ChatType.GROUP, ChatType.SUPERGROUP]
12
  Akeno = Client.on_message
13
  Akeno_chat_member_updated = Client.on_chat_member_updated()
14
+ script_path = pathlib.Path(__file__).parent.parent
15
+ modules_help = ModuleHelp()
16
+ scheduler_jobs = []
17
+ scheduler = AsyncIOScheduler()
18
+ bot_uptime = perf_counter()
Akeno/utils/helps.py ADDED
@@ -0,0 +1,466 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import shlex
5
+ import sys
6
+ from typing import Dict, List, Optional, Tuple, Union
7
+
8
+ import aiohttp
9
+ import arrow
10
+ from apscheduler.triggers.cron import CronTrigger
11
+ from apscheduler.triggers.interval import IntervalTrigger
12
+ from pyrogram import Client, enums, errors
13
+ from pyrogram.enums import ChatType
14
+ from pyrogram.session import Session
15
+ from pyrogram.storage import Storage
16
+ from pyrogram.types import Chat, Message, User
17
+
18
+
19
+ class CustomFormatter(logging.Formatter):
20
+ # Colors
21
+ black = "\x1b[30m"
22
+ red = "\x1b[31m"
23
+ green = "\x1b[32m"
24
+ yellow = "\x1b[33m"
25
+ blue = "\x1b[34m"
26
+ gray = "\x1b[38m"
27
+ # Styles
28
+ reset = "\x1b[0m"
29
+ bold = "\x1b[1m"
30
+
31
+ COLORS = {
32
+ logging.DEBUG: gray + bold,
33
+ logging.INFO: blue + bold,
34
+ logging.WARNING: yellow + bold,
35
+ logging.ERROR: red,
36
+ logging.CRITICAL: red + bold,
37
+ }
38
+
39
+ def format(self, record):
40
+ log_color = self.COLORS[record.levelno]
41
+ fmt = "(black){asctime}(reset) (levelcolor){levelname:<8}(reset) (green){name}(reset) {message}"
42
+ fmt = fmt.replace("(black)", self.black + self.bold)
43
+ fmt = fmt.replace("(reset)", self.reset)
44
+ fmt = fmt.replace("(levelcolor)", log_color)
45
+ fmt = fmt.replace("(green)", self.green + self.bold)
46
+ formatter = logging.Formatter(fmt, "%Y-%m-%d %H:%M:%S", style="{")
47
+ return formatter.format(record)
48
+
49
+ def get_full_name(obj: Union[User, Chat]) -> str:
50
+ if isinstance(obj, Chat):
51
+ if obj.type == ChatType.PRIVATE:
52
+ return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name
53
+ return obj.title
54
+ elif isinstance(obj, User):
55
+ return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name
56
+ else:
57
+ raise TypeError("obj must be User or Chat")
58
+
59
+ def format_exc(e: Exception, suffix="") -> str:
60
+ if isinstance(e, errors.RPCError):
61
+ return (
62
+ f"<b>Telegram API error!</b>\n"
63
+ f"<code>[{e.CODE} {e.ID or e.NAME}] — {e.MESSAGE.format(value=e.value)}</code>\n\n"
64
+ f"<b>{suffix}</b>"
65
+ )
66
+ return f"<code>{e.__class__.__name__}: {e}</code>\n\n<b>{suffix}</b>"
67
+
68
+ def with_reply(func):
69
+ async def wrapped(client: Client, message: Message):
70
+ if not message.reply_to_message:
71
+ await message.edit("<b>Reply to message is required</b>")
72
+ else:
73
+ return await func(client, message)
74
+
75
+ return wrapped
76
+
77
+ def with_args(text: str):
78
+ def decorator(func):
79
+ async def wrapped(client: Client, message: Message):
80
+ if message.text and len(message.text.split()) == 1:
81
+ await message.edit(text)
82
+ else:
83
+ return await func(client, message)
84
+
85
+ return wrapped
86
+
87
+ return decorator
88
+
89
+ def with_premium(func):
90
+ async def wrapped(client: Client, message: Message):
91
+ if not (await client.get_me()).is_premium:
92
+ await message.edit("<b>Premium account is required</b>")
93
+ else:
94
+ return await func(client, message)
95
+
96
+ return wrapped
97
+
98
+ async def dpaste(code: str):
99
+ async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
100
+ data = {"content": code, "lexer": "python", "expires": "never"}
101
+ async with session.post("https://dpaste.org/api/", data=data) as resp:
102
+ if resp.status != 200:
103
+ return "Pasting failed!"
104
+ else:
105
+ return (await resp.text()).replace('"', "")
106
+
107
+ async def paste_neko(code: str):
108
+ try:
109
+ async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
110
+ async with session.post(
111
+ "https://nekobin.com/api/documents",
112
+ json={"content": code},
113
+ ) as paste:
114
+ paste.raise_for_status()
115
+ result = await paste.json()
116
+ except Exception:
117
+ return await dpaste(code=code)
118
+ else:
119
+ return f"nekobin.com/{result['result']['key']}.py"
120
+
121
+ def get_args_raw(message: Union[Message, str], use_reply: bool = None) -> str:
122
+ """Returns text after command.
123
+
124
+ Args:
125
+ message (Union[Message, str]): Message or text.
126
+
127
+ use_reply (bool, optional): Try to get args from reply message if no args in message. Defaults to None.
128
+
129
+ Returns:
130
+ str: Text after command or empty string.
131
+ """
132
+ if isinstance(message, Message):
133
+ text = message.text or message.caption
134
+ args = text.split(maxsplit=1)[1] if len(text.split()) > 1 else ""
135
+
136
+ if use_reply and not args:
137
+ args = message.reply_to_message.text or message.reply_to_message.caption
138
+
139
+ elif not isinstance(message, str):
140
+ return ""
141
+
142
+ return args or ""
143
+
144
+
145
+ def get_args(
146
+ message: Union[Message, str], use_reply: bool = None
147
+ ) -> Tuple[List[str], Dict[str, str]]:
148
+ """Returns list of common args and a dictionary with named args.
149
+
150
+ Args:
151
+ message (Union[Message, str]): Message or text.
152
+
153
+ use_reply (bool, optional): Try to get args from reply message if no args in message. Defaults to None.
154
+
155
+ Returns:
156
+ List[str]: List of args.
157
+ """
158
+ raw_args = get_args_raw(message, use_reply)
159
+
160
+ try:
161
+ args = list(filter(lambda x: len(x) > 0, shlex.split(raw_args)))
162
+ except ValueError:
163
+ return [raw_args], {}
164
+
165
+ common_args = []
166
+ named_args = {}
167
+
168
+ i = 0
169
+ while i < len(args):
170
+ arg = args[i]
171
+ if arg.startswith("-"):
172
+ if i + 1 < len(args) and (
173
+ not args[i + 1].startswith("-") or len(args[i + 1].split()) > 1
174
+ ):
175
+ named_args[arg] = args[i + 1]
176
+ i += 2
177
+ else:
178
+ i += 1
179
+ else:
180
+ i += 1
181
+ common_args.append(arg)
182
+ return common_args, named_args
183
+
184
+
185
+ class ScheduleJob:
186
+ def __init__(
187
+ self,
188
+ func: callable,
189
+ trigger: Optional[Union[CronTrigger, IntervalTrigger]] = IntervalTrigger(seconds=3600),
190
+ *args,
191
+ **kwargs,
192
+ ):
193
+ self.func = func
194
+ self.args = args or []
195
+ self.kwargs = kwargs or {}
196
+ self.id = func.__name__
197
+ self.trigger = trigger
198
+
199
+
200
+ def get_ram_usage() -> float:
201
+ """Returns current process tree memory usage in MB"""
202
+ try:
203
+ import psutil
204
+
205
+ current_process = psutil.Process(os.getpid())
206
+ mem = current_process.memory_info()[0] / 2.0**20
207
+ for child in current_process.children(recursive=True):
208
+ mem += child.memory_info()[0] / 2.0**20
209
+
210
+ return round(mem, 1)
211
+ except Exception:
212
+ return 0
213
+
214
+
215
+ def get_cpu_usage() -> float:
216
+ """Returns current process tree CPU usage in %"""
217
+ try:
218
+ import psutil
219
+
220
+ current_process = psutil.Process(os.getpid())
221
+ cpu = current_process.cpu_percent()
222
+ for child in current_process.children(recursive=True):
223
+ cpu += child.cpu_percent()
224
+
225
+ return round(cpu, 1)
226
+ except Exception:
227
+ return 0
228
+
229
+
230
+ def humanize_seconds(seconds: Union[int, float]) -> str:
231
+ """Returns humanized time delta from seconds"""
232
+ current_time = arrow.get()
233
+ target_time = current_time.shift(seconds=-seconds)
234
+ return target_time.humanize(current_time, only_distance=True)
235
+
236
+ class Command:
237
+ def __init__(
238
+ self,
239
+ name: str,
240
+ description: Optional[str] = None,
241
+ args: Optional[str] = None,
242
+ aliases: Optional[List[str]] = None,
243
+ ):
244
+ self.name = name
245
+ self.description = description
246
+ self.args = args
247
+ self.aliases = aliases
248
+ self.hidden = False
249
+
250
+
251
+ class Module:
252
+ def __init__(self, name: str, path: str):
253
+ self.name = name
254
+ self.path = path
255
+ self.commands = {}
256
+ self.hidden = False
257
+
258
+ def add_command(
259
+ self,
260
+ command: str,
261
+ description: Optional[str] = None,
262
+ args: Optional[str] = None,
263
+ aliases: Optional[List[str]] = None,
264
+ ) -> Command:
265
+ if command in self.commands:
266
+ raise ValueError(f"Command {command} already exists")
267
+
268
+ self.commands[command] = Command(command, description, args, aliases)
269
+
270
+ return self.commands[command]
271
+
272
+ def delete_command(self, command: str) -> None:
273
+ if command not in self.commands:
274
+ raise ValueError(f"Command {command} not found")
275
+
276
+ del self.commands[command]
277
+
278
+ def hide_command(self, command: str) -> None:
279
+ if command not in self.commands:
280
+ raise ValueError(f"Command {command} not found")
281
+
282
+ self.commands[command].hidden = True
283
+
284
+ def show_command(self, command: str) -> None:
285
+ if command not in self.commands:
286
+ raise ValueError(f"Command {command} not found")
287
+
288
+ self.commands[command].hidden = False
289
+
290
+
291
+ class ModuleHelp:
292
+ def __init__(self) -> None:
293
+ self.modules = {}
294
+
295
+ def add_module(self, name: str, path: str) -> Module:
296
+ self.modules[name] = Module(name, path)
297
+
298
+ return self.modules[name]
299
+
300
+ def delete_module(self, name: str) -> None:
301
+ del self.modules[name]
302
+
303
+ def hide_module(self, name: str) -> None:
304
+ if name not in self.modules:
305
+ raise ValueError(f"Module {name} not found")
306
+
307
+ self.modules[name].hidden = True
308
+
309
+ def show_module(self, name: str) -> None:
310
+ if name not in self.modules:
311
+ raise ValueError(f"Module {name} not found")
312
+
313
+ self.modules[name].hidden = False
314
+
315
+ def get_module(self, name: str) -> Module:
316
+ if name not in self.modules:
317
+ raise ValueError(f"Module {name} not found")
318
+
319
+ return self.modules[name]
320
+
321
+ def get_module_by_path(self, path: str) -> Module:
322
+ for module in self.modules.values():
323
+ if module.path == path:
324
+ return module
325
+
326
+ raise ValueError(f"Module with path {path} not found")
327
+
328
+ def help(self) -> List[str]:
329
+ prefix = "."
330
+ result = []
331
+
332
+ help_text = f"For more help on how to use a command, type <code>{prefix}help [module]</code>\n\nAvailable Modules:\n"
333
+
334
+ for module_name, module in sorted(self.modules.items(), key=lambda x: x[0]):
335
+ help_text += f'• {module_name.title()}: {" ".join([f"<code>{prefix + cmd_name}</code>" for cmd_name in module.commands.keys()])}\n'
336
+
337
+ if len(help_text) >= 2048:
338
+ result.append(help_text)
339
+ help_text = ""
340
+
341
+ help_text += f"\nThe number of modules in the userbot: {self.modules_count}\n"
342
+ help_text += f"The number of commands in the userbot: {self.commands_count}"
343
+
344
+ result.append(help_text)
345
+
346
+ return result
347
+
348
+ def module_help(self, module: str, full: bool = True) -> str:
349
+ if module not in self.modules:
350
+ raise ValueError(f"Module {module} not found")
351
+
352
+ prefix = "."
353
+ help_text = ""
354
+
355
+ if full:
356
+ help_text += f"<b>Help for |<code>{module}</code>|</b>\n\n"
357
+
358
+ help_text += "<b>Usage:</b>\n"
359
+ for command in self.modules[module].commands.values():
360
+ help_text += f"<code>{prefix}{command.name}"
361
+ if command.args:
362
+ help_text += f" {command.args}"
363
+ if command.description:
364
+ help_text += f"</code> — <i>{command.description}</i>\n"
365
+
366
+ return help_text
367
+
368
+ def command_help(self, command: str) -> str:
369
+ for module in self.modules.values():
370
+ for cmd in module.commands.values():
371
+ if cmd.name == command or (cmd.aliases and command in cmd.aliases):
372
+ command = cmd
373
+ break
374
+ else:
375
+ continue
376
+ break
377
+ else:
378
+ raise ValueError(f"Command {command} not found")
379
+
380
+ prefix = "."
381
+
382
+ help_text = f"<b>Help for command</b> <code>{prefix}{command.name}</code>\n"
383
+ if command.aliases:
384
+ help_text += "<b>Aliases:</b> "
385
+ help_text += (
386
+ f"{' '.join([f'<code>{prefix}{alias}</code>' for alias in command.aliases])}\n"
387
+ )
388
+
389
+ help_text += (
390
+ f"\n<b>Module: {module.name}</b> (<code>{prefix}help {module.name}</code>)\n\n"
391
+ )
392
+ help_text += f"<code>{prefix}{command.name}"
393
+
394
+ if command.args:
395
+ help_text += f" {command.args}"
396
+ help_text += "</code>"
397
+ if command.description:
398
+ help_text += f" — <i>{command.description}</i>"
399
+
400
+ return help_text
401
+
402
+ @property
403
+ def modules_count(self) -> int:
404
+ return len(self.modules)
405
+
406
+ @property
407
+ def commands_count(self) -> int:
408
+ return sum(len(module.commands) for module in self.modules.values())
409
+
410
+
411
+ def get_entity_url(
412
+ entity: Union[User, Chat],
413
+ openmessage: bool = False,
414
+ ) -> str:
415
+ """
416
+ Get link to object, if available
417
+ :param entity: Entity to get url of
418
+ :param openmessage: Use tg://openmessage link for users
419
+ :return: Link to object or empty string
420
+ """
421
+ return (
422
+ (f"tg://openmessage?user_id={entity.id}" if openmessage else f"tg://user?id={entity.id}")
423
+ if isinstance(entity, User)
424
+ else (
425
+ f"tg://resolve?domain={entity.username}" if getattr(entity, "username", None) else ""
426
+ )
427
+ )
428
+
429
+ def get_message_link(
430
+ message: Message,
431
+ chat: Optional[Chat] = None,
432
+ ) -> str:
433
+ """
434
+ Get link to message
435
+ :param message: Message to get link of
436
+ :param chat: Chat, where message was sent
437
+ :return: Link to message
438
+ """
439
+ if message.chat.type == ChatType.PRIVATE:
440
+ return f"tg://openmessage?user_id={message.chat.id}&message_id={message.id}"
441
+
442
+ return (
443
+ f"https://t.me/{chat.username}/{message.id}"
444
+ if getattr(chat, "username", False)
445
+ else f"https://t.me/c/{chat.id}/{message.id}"
446
+ )
447
+
448
+ async def shell_exec(
449
+ command: str,
450
+ executable: Optional[str] = None,
451
+ timeout: Optional[Union[int, float]] = None,
452
+ stdout=asyncio.subprocess.PIPE,
453
+ stderr=asyncio.subprocess.PIPE,
454
+ ) -> Tuple[int, str, str]:
455
+ """Executes shell command and returns tuple with return code, decoded stdout and stderr"""
456
+ process = await asyncio.create_subprocess_shell(
457
+ cmd=command, stdout=stdout, stderr=stderr, shell=True, executable=executable
458
+ )
459
+
460
+ try:
461
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout)
462
+ except asyncio.exceptions.TimeoutError as e:
463
+ process.kill()
464
+ raise e
465
+
466
+ return process.returncode, stdout.decode(), stderr.decode()