From 3f72c968401a9e5a8e11949e0280ec56b9a8bc47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D8=B1=D9=88=D8=AC=D8=B1?= <104784913+rogerpq@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:41:29 +0300 Subject: [PATCH] Add files via upload --- repthon/core/__init__.py | 7 + repthon/core/client.py | 352 ++++++++++++++++++++++++ repthon/core/cmdinfo.py | 94 +++++++ repthon/core/data.py | 36 +++ repthon/core/decorators.py | 32 +++ repthon/core/events.py | 500 ++++++++++++++++++++++++++++++++++ repthon/core/fasttelethon.py | 411 ++++++++++++++++++++++++++++ repthon/core/helpers.py | 48 ++++ repthon/core/inlinebot.py | 227 +++++++++++++++ repthon/core/logger.py | 7 + repthon/core/managers.py | 85 ++++++ repthon/core/pluginManager.py | 88 ++++++ repthon/core/pool.py | 41 +++ repthon/core/route.py | 8 + repthon/core/server.py | 9 + repthon/core/session.py | 44 +++ repthon/core/zllm.txt | 1 + 17 files changed, 1990 insertions(+) create mode 100644 repthon/core/__init__.py create mode 100644 repthon/core/client.py create mode 100644 repthon/core/cmdinfo.py create mode 100644 repthon/core/data.py create mode 100644 repthon/core/decorators.py create mode 100644 repthon/core/events.py create mode 100644 repthon/core/fasttelethon.py create mode 100644 repthon/core/helpers.py create mode 100644 repthon/core/inlinebot.py create mode 100644 repthon/core/logger.py create mode 100644 repthon/core/managers.py create mode 100644 repthon/core/pluginManager.py create mode 100644 repthon/core/pool.py create mode 100644 repthon/core/route.py create mode 100644 repthon/core/server.py create mode 100644 repthon/core/session.py create mode 100644 repthon/core/zllm.txt diff --git a/repthon/core/__init__.py b/repthon/core/__init__.py new file mode 100644 index 0000000..1299e9d --- /dev/null +++ b/repthon/core/__init__.py @@ -0,0 +1,7 @@ +from .decorators import check_owner + +CMD_INFO = {} +PLG_INFO = {} +GRP_INFO = {} +BOT_INFO = [] +LOADED_CMDS = {} diff --git a/repthon/core/client.py b/repthon/core/client.py new file mode 100644 index 0000000..2baa30a --- /dev/null +++ b/repthon/core/client.py @@ -0,0 +1,352 @@ +import asyncio +import datetime +import inspect +import re +import sys +import traceback +from pathlib import Path +from typing import Dict, List, Union + +from telethon import TelegramClient, events +from telethon.errors import ( + AlreadyInConversationError, + BotInlineDisabledError, + BotResponseTimeoutError, + ChatSendInlineForbiddenError, + ChatSendMediaForbiddenError, + ChatSendStickersForbiddenError, + FloodWaitError, + MessageIdInvalidError, + MessageNotModifiedError, +) + +from ..Config import Config +from ..helpers.utils.events import checking +from ..helpers.utils.format import paste_message +from ..helpers.utils.utils import runcmd +from ..sql_helper.globals import gvarstatus +from . import BOT_INFO, CMD_INFO, GRP_INFO, LOADED_CMDS, PLG_INFO +from .cmdinfo import _format_about +from .data import _sudousers_list, blacklist_chats_list, sudo_enabled_cmds +from .events import * +from .fasttelethon import download_file, upload_file +from .logger import logging +from .managers import edit_delete +from .pluginManager import get_message_link, restart_script + +LOGS = logging.getLogger(__name__) + + +class REGEX: + def __init__(self): + self.regex = "" + self.regex1 = "" + self.regex2 = "" + + +REGEX_ = REGEX() +sudo_enabledcmds = sudo_enabled_cmds() + + +class ZedUserBotClient(TelegramClient): + def zed_cmd( + self: TelegramClient, + pattern: str or tuple = None, + info: Union[str, Dict[str, Union[str, List[str], Dict[str, str]]]] + or tuple = None, + groups_only: bool = False, + private_only: bool = False, + allow_sudo: bool = True, + edited: bool = True, + forword=False, + disable_errors: bool = False, + command: str or tuple = None, + **kwargs, + ) -> callable: # sourcery no-metrics + kwargs["func"] = kwargs.get("func", lambda e: e.via_bot_id is None) + kwargs.setdefault("forwards", forword) + if gvarstatus("blacklist_chats") is not None: + kwargs["blacklist_chats"] = True + kwargs["chats"] = blacklist_chats_list() + stack = inspect.stack() + previous_stack_frame = stack[1] + file_test = Path(previous_stack_frame.filename) + file_test = file_test.stem.replace(".py", "") + if command is not None: + command = list(command) + if not command[1] in BOT_INFO: + BOT_INFO.append(command[1]) + try: + if file_test not in GRP_INFO[command[1]]: + GRP_INFO[command[1]].append(file_test) + except BaseException: + GRP_INFO.update({command[1]: [file_test]}) + try: + if command[0] not in PLG_INFO[file_test]: + PLG_INFO[file_test].append(command[0]) + except BaseException: + PLG_INFO.update({file_test: [command[0]]}) + if not command[0] in CMD_INFO: + CMD_INFO[command[0]] = [_format_about(info)] + if pattern is not None: + if ( + pattern.startswith(r"\#") + or not pattern.startswith(r"\#") + and pattern.startswith(r"^") + ): + REGEX_.regex1 = REGEX_.regex2 = re.compile(pattern) + else: + reg1 = "\\" + Config.COMMAND_HAND_LER + reg2 = "\\" + Config.SUDO_COMMAND_HAND_LER + REGEX_.regex1 = re.compile(reg1 + pattern) + REGEX_.regex2 = re.compile(reg2 + pattern) + + def decorator(func): # sourcery no-metrics + async def wrapper(check): # sourcery no-metrics + if groups_only and not check.is_group: + return await edit_delete( + check, "**⪼ عذرا هذا الامر يستخدم في المجموعات فقط 𓆰،**", 10 + ) + if private_only and not check.is_private: + return await edit_delete( + check, "**⪼ هذا الامر يستخدم فقط في الدردشات الخاصه 𓆰،**", 10 + ) + try: + await func(check) + except events.StopPropagation as e: + raise events.StopPropagation from e + except KeyboardInterrupt: + pass + except MessageNotModifiedError: + LOGS.error("كانت الرسالة مماثلة للرسالة السابقة") + except MessageIdInvalidError: + LOGS.error("الرسالة تم حذفها او لم يتم العثور عليها") + except BotInlineDisabledError: + await edit_delete(check, "**⌔∮ يجب عليك تفعيل وضع الانلاين اولاً**", 10) + except ChatSendStickersForbiddenError: + await edit_delete( + check, "**- هـذه المجمـوعـه لا تسمح بارسـال الملصقـات هنا**", 10 + ) + except BotResponseTimeoutError: + await edit_delete( + check, "⪼ استخدم الميزه بعد وقت قليل لا يمكن الاستجابه الان", 10 + ) + except ChatSendMediaForbiddenError: + await edit_delete(check, "**⪼ هذه المجموعه تمنع ارسال الميديا هنا 𓆰،**", 10) + except AlreadyInConversationError: + await edit_delete( + check, + "**- المحادثه تجري بالفعل مع الدردشة المحددة .. حاول مرة أخرى بعد قليل**", + 10, + ) + except ChatSendInlineForbiddenError: + await edit_delete( + check, "**- عـذراً .. الانـلايـن فـي هـذه المجمـوعـة مغـلق**", 10 + ) + except FloodWaitError as e: + LOGS.error( + f"ايقاف مؤقت بسبب التكرار {e.seconds} حدث. انتظر {e.seconds} ثانيه و حاول مجددا" + ) + await check.delete() + await asyncio.sleep(e.seconds + 5) + except BaseException as e: + LOGS.exception(e) + if not disable_errors: + if Config.PRIVATE_GROUP_BOT_API_ID == 0: + return + date = (datetime.datetime.now()).strftime("%m/%d/%Y, %H:%M:%S") + ftext = f"\nيتم تحميل هذا الملف فقط هنا ،\ + \n\nنسجل فقـط تقريـر الخطـأ وتـاريخـه ،\ + \n\nنحن نحترم خصوصيتك.\ + \n\nفقـط قـم بإعـادة توجيـه هـذه الرسـالة إلى مطـور السـورس @E_7_V\ + \n\n--------بـدء تتبـع سجـل ريبـــثون 𝗥𝗲𝗽𝘁𝗵𝗼𝗻--------\ + \n- التـاريـخ : {date}\n- ايـدي الكـروب : {str(check.chat_id)}\ + \n- ايـدي الشخـص : {str(check.sender_id)}\ + \n- رابـط الرسـالـه : {await check.client.get_msg_link(check)}\ + \n\n- التقـريـر :\n{str(check.text)}\ + \n\n- التفـاصـيل :\n{str(traceback.format_exc())}\ + \n\n- نـص الخطـأ :\n{str(sys.exc_info()[1])}" + new = { + "error": str(sys.exc_info()[1]), + "date": datetime.datetime.now(), + } + ftext += "\n\n--------نهـاية سجـل تتبـع ريبـــثون 𝗥𝗲𝗽𝘁𝗵𝗼𝗻--------" + ftext += "\n\n\n- آخـر 5 ملفـات تم تحديثهـا :\n" + command = 'git log --pretty=format:"%an: %s" -5' + output = (await runcmd(command))[:2] + result = output[0] + output[1] + ftext += result + pastelink = await paste_message( + ftext, pastetype="s", markdown=False + ) + link = "[𐇮 ✗ ¦ ↱𝐺𝑜𝑙 𝐷. 𝑅𝑜𝑔𝑒𝑟↲ ¦ ✗ 𐇮](https://t.me/E_7_V)" + text = ( + "**✘ تقـريـر خطـأ ريبـــثون 𝗥𝗲𝗽𝘁𝗵𝗼𝗻 ✘**\n\n" + + "- يمكنك الإبـلاغ عن هـذا الخطـأ .. " + ) + text += f"- فقط قم بإعـادة توجيـه هـذه الرسـالة إلى مطـور السـورس {link}.\n\n" + text += ( + "- لـ اعـلام المطـور بالخطـأ .. حتـى يتـم اصـلاحـه\n\n" + ) + text += f"**- رسـالة الخطـأ :** [{new['error']}]({pastelink})" + await check.client.send_message( + Config.PRIVATE_GROUP_BOT_API_ID, text, link_preview=False + ) + + from .session import zedub + + if not func.__doc__ is None: + CMD_INFO[command[0]].append((func.__doc__).strip()) + if pattern is not None: + if command is not None: + if command[0] in LOADED_CMDS and wrapper in LOADED_CMDS[command[0]]: + return None + try: + LOADED_CMDS[command[0]].append(wrapper) + except BaseException: + LOADED_CMDS.update({command[0]: [wrapper]}) + if edited: + zedub.add_event_handler( + wrapper, + MessageEdited(pattern=REGEX_.regex1, outgoing=True, **kwargs), + ) + zedub.add_event_handler( + wrapper, + NewMessage(pattern=REGEX_.regex1, outgoing=True, **kwargs), + ) + if allow_sudo and gvarstatus("sudoenable") is not None: + if command is None or command[0] in sudo_enabledcmds: + if edited: + zedub.add_event_handler( + wrapper, + MessageEdited( + pattern=REGEX_.regex2, + from_users=_sudousers_list(), + **kwargs, + ), + ) + zedub.add_event_handler( + wrapper, + NewMessage( + pattern=REGEX_.regex2, + from_users=_sudousers_list(), + **kwargs, + ), + ) + else: + if file_test in LOADED_CMDS and func in LOADED_CMDS[file_test]: + return None + try: + LOADED_CMDS[file_test].append(func) + except BaseException: + LOADED_CMDS.update({file_test: [func]}) + if edited: + zedub.add_event_handler(func, events.MessageEdited(**kwargs)) + zedub.add_event_handler(func, events.NewMessage(**kwargs)) + return wrapper + + return decorator + + def bot_cmd( + self: TelegramClient, + disable_errors: bool = False, + edited: bool = False, + forword=False, + **kwargs, + ) -> callable: # sourcery no-metrics + kwargs["func"] = kwargs.get("func", lambda e: e.via_bot_id is None) + kwargs.setdefault("forwards", forword) + + def decorator(func): + async def wrapper(check): + try: + await func(check) + except events.StopPropagation as e: + raise events.StopPropagation from e + except KeyboardInterrupt: + pass + except MessageNotModifiedError: + LOGS.error("Message was same as previous message") + except MessageIdInvalidError: + LOGS.error("Message was deleted or cant be found") + except BaseException as e: + # Check if we have to disable error logging. + LOGS.exception(e) # Log the error in console + if not disable_errors: + if Config.PRIVATE_GROUP_BOT_API_ID == 0: + return + date = (datetime.datetime.now()).strftime("%m/%d/%Y, %H:%M:%S") + ftext = f"\nيتم تحميل هذا الملف فقط هنا ،\ + \n\nنسجل فقـط تقريـر الخطـأ وتـاريخـه ،\ + \n\nنحن نحترم خصوصيتك.\ + \n\nفقـط قـم بإعـادة توجيـه هـذه الرسـالة إلى مطـور السـورس @ZQ_LO\ + \n\n--------بـدء تتبـع سجـل ريبـــثون 𝗥𝗲𝗽𝘁𝗵𝗼𝗻--------\ + \n- التـاريـخ : {date}\n- ايـدي الكـروب : {str(check.chat_id)}\ + \n- ايـدي الشخـص : {str(check.sender_id)}\ + \n- رابـط الرسـالـه : {await check.client.get_msg_link(check)}\ + \n\n- التقـريـر :\n{str(check.text)}\ + \n\n- التفـاصـيل :\n{str(traceback.format_exc())}\ + \n\n- نـص الخطـأ :\n{str(sys.exc_info()[1])}" + new = { + "error": str(sys.exc_info()[1]), + "date": datetime.datetime.now(), + } + ftext += "\n\n--------نهـاية سجـل تتبـع ريبـــثون 𝗥𝗲𝗽𝘁𝗵𝗼𝗻--------" + command = 'git log --pretty=format:"%an: %s" -5' + ftext += "\n\n\n- آخـر 5 ملفـات تم تحديثهـا :\n" + output = (await runcmd(command))[:2] + result = output[0] + output[1] + ftext += result + pastelink = await paste_message( + ftext, pastetype="s", markdown=False + ) + text = "**✘ تقـريـر خطـأ ريبـــثون 𝗥𝗲𝗽𝘁𝗵𝗼𝗻✘**\n\n" + link = "[𐇮 ✗ ¦ ↱𝐺𝑜𝑙 𝐷. 𝑅𝑜𝑔𝑒𝑟↲ ¦ ✗ 𐇮](https://t.me/E_7_V)" + text += "- يمكنك الإبـلاغ عن هـذا الخطـأ .. " + text += f"- فقط قم بإعـادة توجيـه هـذه الرسـالة إلى مطـور السـورس {link}.\n" + text += ( + "- لـ اعـلام المطـور بالخطـأ .. حتـى يتـم اصـلاحـه\n\n" + ) + text += f"**- رسـالة الخطـأ :** [{new['error']}]({pastelink})" + await check.client.send_message( + Config.PRIVATE_GROUP_BOT_API_ID, text, link_preview=False + ) + + from .session import zedub + + if edited is True: + zedub.tgbot.add_event_handler(func, events.MessageEdited(**kwargs)) + else: + zedub.tgbot.add_event_handler(func, events.NewMessage(**kwargs)) + + return wrapper + + return decorator + + async def get_traceback(self, exc: Exception) -> str: + return "".join( + traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__) + ) + + def _kill_running_processes(self) -> None: + """Kill all the running asyncio subprocessess""" + for _, process in self.running_processes.items(): + try: + process.kill() + LOGS.debug("Killed %d which was still running.", process.pid) + except Exception as e: + LOGS.debug(e) + self.running_processes.clear() + + +ZedUserBotClient.fast_download_file = download_file +ZedUserBotClient.fast_upload_file = upload_file +ZedUserBotClient.reload = restart_script +ZedUserBotClient.get_msg_link = get_message_link +ZedUserBotClient.check_testcases = checking +try: + send_message_check = TelegramClient.send_message +except AttributeError: + ZedUserBotClient.send_message = send_message + ZedUserBotClient.send_file = send_file + ZedUserBotClient.edit_message = edit_message diff --git a/repthon/core/cmdinfo.py b/repthon/core/cmdinfo.py new file mode 100644 index 0000000..4a59c01 --- /dev/null +++ b/repthon/core/cmdinfo.py @@ -0,0 +1,94 @@ +from typing import Dict, List, Union + +from ..helpers.utils.extdl import install_pip + +try: + from urlextract import URLExtract +except ModuleNotFoundError: + install_pip("urlextract") + from urlextract import URLExtract + +from ..Config import Config + +extractor = URLExtract() + + +def get_data(about, ktype): + data = about[ktype] + urls = extractor.find_urls(data) + if len(urls) > 0: + return data + return data.capitalize() + + +def _format_about( + about: Union[str, Dict[str, Union[str, List[str], Dict[str, str]]]] +) -> str: # sourcery no-metrics + if not isinstance(about, dict): + return about + tmp_chelp = "" + if "header" in about and isinstance(about["header"], str): + tmp_chelp += f"__{about['header'].title()}__" + del about["header"] + if "description" in about and isinstance(about["description"], str): + tmp_chelp += ( + "\n\n✘ **Description :**\n" f"__{get_data(about , 'description')}__" + ) + del about["description"] + if "flags" in about: + tmp_chelp += "\n\n✘ **Available Flags :**" + if isinstance(about["flags"], dict): + for f_n, f_d in about["flags"].items(): + tmp_chelp += f"\n ▫ `{f_n}` : __{f_d.lower()}__" + else: + tmp_chelp += f"\n {about['flags']}" + del about["flags"] + if "options" in about: + tmp_chelp += "\n\n✘ **Available Options :**" + if isinstance(about["options"], dict): + for o_n, o_d in about["options"].items(): + tmp_chelp += f"\n ▫ `{o_n}` : __{o_d.lower()}__" + else: + tmp_chelp += f"\n __{about['options']}__" + del about["options"] + if "types" in about: + tmp_chelp += "\n\n✘ **Supported Types :**" + if isinstance(about["types"], list): + for _opt in about["types"]: + tmp_chelp += f"\n `{_opt}` ," + else: + tmp_chelp += f"\n __{about['types']}__" + del about["types"] + if "usage" in about: + tmp_chelp += "\n\n✘ **Usage :**" + if isinstance(about["usage"], list): + for ex_ in about["usage"]: + tmp_chelp += f"\n `{ex_}`" + else: + tmp_chelp += f"\n `{about['usage']}`" + del about["usage"] + if "examples" in about: + tmp_chelp += "\n\n✘ **Examples :**" + if isinstance(about["examples"], list): + for ex_ in about["examples"]: + tmp_chelp += f"\n `{ex_}`" + else: + tmp_chelp += f"\n `{about['examples']}`" + del about["examples"] + if "others" in about: + tmp_chelp += f"\n\n✘ **Others :**\n__{get_data(about , 'others')}__" + del about["others"] + if about: + for t_n, t_d in about.items(): + tmp_chelp += f"\n\n✘ **{t_n.title()} :**\n" + if isinstance(t_d, dict): + for o_n, o_d in t_d.items(): + tmp_chelp += f" ▫ `{o_n}` : __{get_data(t_d , o_n)}__\n" + elif isinstance(t_d, list): + for _opt in t_d: + tmp_chelp += f" `{_opt}` ," + tmp_chelp += "\n" + else: + tmp_chelp += f"__{get_data(about ,t_n)}__" + tmp_chelp += "\n" + return tmp_chelp.replace("{tr}", Config.COMMAND_HAND_LER) diff --git a/repthon/core/data.py b/repthon/core/data.py new file mode 100644 index 0000000..a4522e1 --- /dev/null +++ b/repthon/core/data.py @@ -0,0 +1,36 @@ +from ..sql_helper.global_collectionjson import get_collection +from ..sql_helper.global_list import get_collection_list + + +def _sudousers_list(): + try: + sudousers = get_collection("sudousers_list").json + except AttributeError: + sudousers = {} + ulist = sudousers.keys() + return [int(chat) for chat in ulist] + + +def _users_list(): + try: + sudousers = get_collection("sudousers_list").json + except AttributeError: + sudousers = {} + ulist = sudousers.keys() + ulist = [int(chat) for chat in ulist] + ulist.append("me") + return list(ulist) + + +def blacklist_chats_list(): + try: + blacklistchats = get_collection("blacklist_chats_list").json + except AttributeError: + blacklistchats = {} + blacklist = blacklistchats.keys() + return [int(chat) for chat in blacklist] + + +def sudo_enabled_cmds(): + listcmds = get_collection_list("sudo_enabled_cmds") + return list(listcmds) diff --git a/repthon/core/decorators.py b/repthon/core/decorators.py new file mode 100644 index 0000000..c6be807 --- /dev/null +++ b/repthon/core/decorators.py @@ -0,0 +1,32 @@ +import asyncio + +from telethon.errors import FloodWaitError, MessageNotModifiedError +from telethon.events import CallbackQuery + +from ..Config import Config +from ..sql_helper.globals import gvarstatus + + +def check_owner(func): + async def wrapper(c_q: CallbackQuery): + if c_q.query.user_id and ( + c_q.query.user_id == Config.OWNER_ID + or c_q.query.user_id in Config.SUDO_USERS + ): + try: + await func(c_q) + except FloodWaitError as e: + await asyncio.sleep(e.seconds + 5) + except MessageNotModifiedError: + pass + else: + HELP_TEXT = ( + gvarstatus("HELP_TEXT") + or "- عـذراً .. هـذه اللوحـه خاصـه بـ مـالك البـوت\n\n- قم بتنصيب بوت خاص بك من القناة @Repthon" + ) + await c_q.answer( + HELP_TEXT, + alert=True, + ) + + return wrapper diff --git a/repthon/core/events.py b/repthon/core/events.py new file mode 100644 index 0000000..2808db6 --- /dev/null +++ b/repthon/core/events.py @@ -0,0 +1,500 @@ +import typing + +from telethon import events, hints, types +from telethon.tl.types import ( + InputPeerChannel, + InputPeerChat, + InputPeerUser, + MessageMediaWebPage, +) + +from ..Config import Config +from .managers import edit_or_reply + + +@events.common.name_inner_event +class NewMessage(events.NewMessage): + def __init__(self, require_admin: bool = None, inline: bool = False, **kwargs): + super().__init__(**kwargs) + + self.require_admin = require_admin + self.inline = inline + + def filter(self, event): + _event = super().filter(event) + if not _event: + return + + if self.inline is not None and bool(self.inline) != bool( + event.message.via_bot_id + ): + return + + if self.require_admin and not isinstance(event._chat_peer, types.PeerUser): + is_creator = False + is_admin = False + creator = hasattr(event.chat, "creator") + admin_rights = hasattr(event.chat, "admin_rights") + flag = None + if not creator and not admin_rights: + try: + event.chat = event._client.loop.create_task(event.get_chat()) + except AttributeError: + flag = "Null" + + if self.incoming: + try: + p = event._client.loop.create_task( + event._client.get_permissions(event.chat_id, event.sender_id) + ) + participant = p.participant + except Exception: + participant = None + if isinstance(participant, types.ChannelParticipantCreator): + is_creator = True + if isinstance(participant, types.ChannelParticipantAdmin): + is_admin = True + elif flag: + is_admin = True + is_creator = False + else: + is_creator = event.chat.creator + is_admin = event.chat.admin_rights + + if not is_creator and not is_admin: + text = "- لاستخدام هذا الامر تحتاج الى صلاحيات المشرف اولاً " + + event._client.loop.create_task(edit_or_reply(event, text)) + return + return event + + +@events.common.name_inner_event +class MessageEdited(NewMessage): + @classmethod + def build(cls, update, others=None, self_id=None): + if isinstance(update, types.UpdateEditMessage): + return cls.Event(update.message) + if isinstance(update, types.UpdateEditChannelMessage): + if ( + update.message.edit_date + and update.message.is_channel + and not update.message.is_group + ): + return + return cls.Event(update.message) + + class Event(NewMessage.Event): + pass + + +async def safe_check_text(msg): # sourcery no-metrics + # sourcery skip: low-code-quality + if not msg: + return False + msg = str(msg) + from .session import zedub + + return bool( + ( + (Config.STRING_SESSION in msg) + or (Config.API_HASH in msg) + or (Config.TG_BOT_TOKEN in msg) + or (Config.HEROKU_API_KEY and Config.HEROKU_API_KEY in msg) + or (Config.OPEN_WEATHER_MAP_APPID and Config.OPEN_WEATHER_MAP_APPID in msg) + or (Config.IBM_WATSON_CRED_URL and Config.IBM_WATSON_CRED_URL in msg) + or (Config.OCR_SPACE_API_KEY and Config.OCR_SPACE_API_KEY in msg) + or (Config.GENIUS_API_TOKEN and Config.GENIUS_API_TOKEN in msg) + or (Config.REM_BG_API_KEY and Config.REM_BG_API_KEY in msg) + or (Config.CURRENCY_API and Config.CURRENCY_API in msg) + or (Config.G_DRIVE_CLIENT_ID and Config.G_DRIVE_CLIENT_ID in msg) + or (Config.G_DRIVE_CLIENT_SECRET and Config.G_DRIVE_CLIENT_SECRET in msg) + or (Config.G_DRIVE_DATA and Config.G_DRIVE_DATA in msg) + or (Config.LASTFM_API and Config.LASTFM_API in msg) + or (Config.LASTFM_SECRET and Config.LASTFM_SECRET in msg) + or (Config.LASTFM_PASSWORD_PLAIN and Config.LASTFM_PASSWORD_PLAIN in msg) + or (Config.SPAMWATCH_API and Config.SPAMWATCH_API in msg) + or (Config.SPOTIFY_CLIENT_ID and Config.SPOTIFY_CLIENT_ID in msg) + or (Config.SPOTIFY_CLIENT_SECRET and Config.SPOTIFY_CLIENT_SECRET in msg) + or (Config.GITHUB_ACCESS_TOKEN and Config.GITHUB_ACCESS_TOKEN in msg) + or (Config.DEEP_AI and Config.DEEP_AI in msg) + or ( + Config.SCREEN_SHOT_LAYER_ACCESS_KEY + and Config.SCREEN_SHOT_LAYER_ACCESS_KEY in msg + ) + or ( + Config.IBM_WATSON_CRED_PASSWORD + and Config.IBM_WATSON_CRED_PASSWORD in msg + ) + or ( + Config.TG_2STEP_VERIFICATION_CODE + and Config.TG_2STEP_VERIFICATION_CODE in msg + ) + ) + ) + + +async def send_message( + client, + entity: "hints.EntityLike", + message: "hints.MessageLike" = "", + *, + send_as: "hints.EntityLike" = None, + reply_to: "typing.Union[int, types.Message]" = None, + attributes: "typing.Sequence[types.TypeDocumentAttribute]" = None, + parse_mode: typing.Optional[str] = (), + formatting_entities: typing.Optional[typing.List[types.TypeMessageEntity]] = None, + link_preview: bool = False, + file: "typing.Union[hints.FileLike, typing.Sequence[hints.FileLike]]" = None, + thumb: "hints.FileLike" = None, + force_document: bool = False, + clear_draft: bool = False, + buttons: "hints.MarkupLike" = None, + silent: bool = None, + album: bool = False, + allow_cache: bool = False, + background: bool = None, + noforwards: bool = None, + supports_streaming: bool = False, + schedule: "hints.DateLike" = None, + comment_to: "typing.Union[int, types.Message]" = None, +): + chatid = entity + if str(chatid) in [ + str(Config.BOTLOG_CHATID), + str(Config.PM_LOGGER_GROUP_ID), + ]: + return await client.sendmessage( + entity=chatid, + message=message, + send_as=send_as, + reply_to=reply_to, + attributes=attributes, + parse_mode=parse_mode, + formatting_entities=formatting_entities, + link_preview=link_preview, + file=file, + thumb=thumb, + force_document=force_document, + clear_draft=clear_draft, + buttons=buttons, + silent=silent, + album=album, + allow_cache=allow_cache, + background=background, + noforwards=noforwards, + supports_streaming=supports_streaming, + schedule=schedule, + comment_to=comment_to, + ) + msg = message + safecheck = await safe_check_text(msg) + if safecheck: + if Config.BOTLOG: + response = await client.sendmessage( + entity=Config.BOTLOG_CHATID, + message=msg, + send_as=send_as, + reply_to=reply_to, + attributes=attributes, + parse_mode=parse_mode, + formatting_entities=formatting_entities, + link_preview=link_preview, + file=file, + thumb=thumb, + force_document=force_document, + clear_draft=clear_draft, + buttons=buttons, + silent=silent, + album=album, + allow_cache=allow_cache, + background=background, + noforwards=noforwards, + supports_streaming=supports_streaming, + schedule=schedule, + comment_to=comment_to, + ) + msglink = await client.get_msg_link(response) + msg = f"**- عذراً لا يمكنني ارسال هذه الرسالة في المجموعات العامة لانها تحتوي بيانات حساسه اقرئها في** [مجموعة التخزين]({msglink})." + return await client.sendmessage( + entity=chatid, + message=msg, + send_as=send_as, + reply_to=reply_to, + attributes=attributes, + parse_mode=parse_mode, + formatting_entities=formatting_entities, + link_preview=link_preview, + file=file, + thumb=thumb, + force_document=force_document, + clear_draft=clear_draft, + buttons=buttons, + silent=silent, + album=album, + allow_cache=allow_cache, + background=background, + noforwards=noforwards, + supports_streaming=supports_streaming, + schedule=schedule, + comment_to=comment_to, + ) + return await client.sendmessage( + entity=chatid, + message=msg, + send_as=send_as, + reply_to=reply_to, + attributes=attributes, + parse_mode=parse_mode, + formatting_entities=formatting_entities, + link_preview=link_preview, + file=file, + thumb=thumb, + force_document=force_document, + clear_draft=clear_draft, + buttons=buttons, + silent=silent, + album=album, + allow_cache=allow_cache, + background=background, + noforwards=noforwards, + supports_streaming=supports_streaming, + schedule=schedule, + comment_to=comment_to, + ) + + +async def send_file( + client, + entity: "hints.EntityLike", + file: "typing.Union[hints.FileLike, typing.Sequence[hints.FileLike]]", + *, + caption: typing.Union[str, typing.Sequence[str]] = None, + force_document: bool = False, + file_size: int = None, + clear_draft: bool = False, + progress_callback: "hints.ProgressCallback" = None, + reply_to: "hints.MessageIDLike" = None, + attributes: "typing.Sequence[types.TypeDocumentAttribute]" = None, + thumb: "hints.FileLike" = None, + allow_cache: bool = True, + parse_mode: str = (), + formatting_entities: typing.Optional[typing.List[types.TypeMessageEntity]] = None, + voice_note: bool = False, + video_note: bool = False, + buttons: "hints.MarkupLike" = None, + silent: bool = None, + background: bool = None, + supports_streaming: bool = False, + schedule: "hints.DateLike" = None, + comment_to: "typing.Union[int, types.Message]" = None, + ttl: int = None, + **kwargs, +): + if isinstance(file, MessageMediaWebPage): + return await client.send_message( + entity=entity, + message=caption, + reply_to=reply_to, + parse_mode=parse_mode, + formatting_entities=formatting_entities, + link_preview=True, + buttons=buttons, + silent=silent, + schedule=schedule, + comment_to=comment_to, + ) + chatid = entity + if str(chatid) == str(Config.BOTLOG_CHATID): + return await client.sendfile( + entity=Config.BOTLOG_CHATID, + file=file, + caption=caption, + force_document=force_document, + file_size=file_size, + clear_draft=clear_draft, + progress_callback=progress_callback, + reply_to=reply_to, + attributes=attributes, + thumb=thumb, + allow_cache=allow_cache, + parse_mode=parse_mode, + formatting_entities=formatting_entities, + voice_note=voice_note, + video_note=video_note, + buttons=buttons, + silent=silent, + background=background, + supports_streaming=supports_streaming, + schedule=schedule, + comment_to=comment_to, + ttl=ttl, + **kwargs, + ) + + msg = caption + safecheck = await safe_check_text(msg) + try: + with open(file) as f: + filemsg = f.read() + except Exception: + filemsg = "" + safe_file_check = await safe_check_text(filemsg) + if safecheck or safe_file_check: + if Config.BOTLOG: + response = await client.sendfile( + entity=Config.BOTLOG_CHATID, + file=file, + caption=msg, + force_document=force_document, + file_size=file_size, + clear_draft=clear_draft, + progress_callback=progress_callback, + reply_to=reply_to, + attributes=attributes, + thumb=thumb, + allow_cache=allow_cache, + parse_mode=parse_mode, + formatting_entities=formatting_entities, + voice_note=voice_note, + video_note=video_note, + buttons=buttons, + silent=silent, + background=background, + supports_streaming=supports_streaming, + schedule=schedule, + comment_to=comment_to, + ttl=ttl, + **kwargs, + ) + msglink = await client.get_msg_link(response) + msg = f"**- عذراً لا يمكنني ارسال هذه الرسالة في المجموعات العامة لانها تحتوي بيانات حساسه اقرئها في** [مجموعة التخزين]({msglink})." + return await client.sendmessage( + entity=chatid, + message=msg, + reply_to=reply_to, + link_preview=False, + silent=silent, + schedule=schedule, + comment_to=comment_to, + ) + return await client.sendfile( + entity=chatid, + file=file, + caption=msg, + force_document=force_document, + file_size=file_size, + clear_draft=clear_draft, + progress_callback=progress_callback, + reply_to=reply_to, + attributes=attributes, + thumb=thumb, + allow_cache=allow_cache, + parse_mode=parse_mode, + formatting_entities=formatting_entities, + voice_note=voice_note, + video_note=video_note, + buttons=buttons, + silent=silent, + background=background, + supports_streaming=supports_streaming, + schedule=schedule, + comment_to=comment_to, + ttl=ttl, + **kwargs, + ) + + +async def edit_message( + client, + entity: "typing.Union[hints.EntityLike, types.Message]", + message: "hints.MessageLike" = None, + text: str = None, + *, + parse_mode: str = (), + attributes: "typing.Sequence[types.TypeDocumentAttribute]" = None, + formatting_entities: typing.Optional[typing.List[types.TypeMessageEntity]] = None, + link_preview: bool = True, + file: "hints.FileLike" = None, + thumb: "hints.FileLike" = None, + force_document: bool = False, + buttons: "hints.MarkupLike" = None, + supports_streaming: bool = False, + schedule: "hints.DateLike" = None, +): + chatid = entity + if isinstance(chatid, InputPeerChannel): + chat_id = int(f"-100{str(chatid.channel_id)}") + elif isinstance(chatid, InputPeerChat): + chat_id = int(f"-{str(chatid.chat_id)}") + elif isinstance(chatid, InputPeerUser): + chat_id = int(chatid.user_id) + else: + chat_id = chatid + if str(chat_id) == str(Config.BOTLOG_CHATID): + return await client.editmessage( + entity=chatid, + message=message, + text=text, + parse_mode=parse_mode, + attributes=attributes, + formatting_entities=formatting_entities, + link_preview=link_preview, + file=file, + thumb=thumb, + force_document=force_document, + buttons=buttons, + supports_streaming=supports_streaming, + schedule=schedule, + ) + + main_msg = text + safecheck = await safe_check_text(main_msg) + if safecheck: + if Config.BOTLOG: + response = await client.sendmessage( + entity=Config.BOTLOG_CHATID, + message=main_msg, + parse_mode=parse_mode, + attributes=attributes, + formatting_entities=formatting_entities, + link_preview=link_preview, + file=file, + thumb=thumb, + force_document=force_document, + buttons=buttons, + supports_streaming=supports_streaming, + schedule=schedule, + ) + msglink = await client.get_msg_link(response) + msg = f"**- عذراً لا يمكنني ارسال هذه الرسالة في المجموعات العامة لانها تحتوي بيانات حساسه اقرئها في** [مجموعة التخزين]({msglink})." + return await client.editmessage( + entity=chatid, + message=message, + text=msg, + parse_mode=parse_mode, + attributes=attributes, + formatting_entities=formatting_entities, + link_preview=link_preview, + file=file, + thumb=thumb, + force_document=force_document, + buttons=buttons, + supports_streaming=supports_streaming, + schedule=schedule, + ) + return await client.editmessage( + entity=chatid, + message=message, + text=main_msg, + parse_mode=parse_mode, + attributes=attributes, + formatting_entities=formatting_entities, + link_preview=link_preview, + file=file, + thumb=thumb, + force_document=force_document, + buttons=buttons, + supports_streaming=supports_streaming, + schedule=schedule, + ) diff --git a/repthon/core/fasttelethon.py b/repthon/core/fasttelethon.py new file mode 100644 index 0000000..676a518 --- /dev/null +++ b/repthon/core/fasttelethon.py @@ -0,0 +1,411 @@ +# copied from https://github.com/tulir/mautrix-telegram/blob/master/mautrix_telegram/util/parallel_file_transfer.py +# Copyright (C) 2021 Tulir Asokan +import asyncio +import hashlib +import inspect +import logging +import math +import os +from collections import defaultdict +from typing import ( + AsyncGenerator, + Awaitable, + BinaryIO, + DefaultDict, + List, + Optional, + Tuple, + Union, +) + +from telethon import TelegramClient, helpers, utils +from telethon.crypto import AuthKey +from telethon.errors import FloodWaitError +from telethon.network import MTProtoSender +from telethon.tl.alltlobjects import LAYER +from telethon.tl.functions import InvokeWithLayerRequest +from telethon.tl.functions.auth import ( + ExportAuthorizationRequest, + ImportAuthorizationRequest, +) +from telethon.tl.functions.upload import ( + GetFileRequest, + SaveBigFilePartRequest, + SaveFilePartRequest, +) +from telethon.tl.types import ( + Document, + InputDocumentFileLocation, + InputFile, + InputFileBig, + InputFileLocation, + InputPeerPhotoFileLocation, + InputPhotoFileLocation, + TypeInputFile, +) + +try: + from mautrix.crypto.attachments import async_encrypt_attachment +except ImportError: + async_encrypt_attachment = None + +log: logging.Logger = logging.getLogger("fasttelethon") + +TypeLocation = Union[ + Document, + InputDocumentFileLocation, + InputPeerPhotoFileLocation, + InputFileLocation, + InputPhotoFileLocation, +] + + +class DownloadSender: + client: TelegramClient + sender: MTProtoSender + request: GetFileRequest + remaining: int + stride: int + + def __init__( + self, + client: TelegramClient, + sender: MTProtoSender, + file: TypeLocation, + offset: int, + limit: int, + stride: int, + count: int, + ) -> None: + self.sender = sender + self.client = client + self.request = GetFileRequest(file, offset=offset, limit=limit) + self.stride = stride + self.remaining = count + + async def next(self) -> Optional[bytes]: + if not self.remaining: + return None + while True: + try: + result = await self.client._call(self.sender, self.request) + except FloodWaitError as e: + await asyncio.sleep(e.seconds) + else: + break + self.remaining -= 1 + self.request.offset += self.stride + return result.bytes + + def disconnect(self) -> Awaitable[None]: + return self.sender.disconnect() + + +class UploadSender: + client: TelegramClient + sender: MTProtoSender + request: Union[SaveFilePartRequest, SaveBigFilePartRequest] + part_count: int + stride: int + previous: Optional[asyncio.Task] + loop: asyncio.AbstractEventLoop + + def __init__( + self, + client: TelegramClient, + sender: MTProtoSender, + file_id: int, + part_count: int, + big: bool, + index: int, + stride: int, + loop: asyncio.AbstractEventLoop, + ) -> None: + self.client = client + self.sender = sender + self.part_count = part_count + if big: + self.request = SaveBigFilePartRequest(file_id, index, part_count, b"") + else: + self.request = SaveFilePartRequest(file_id, index, b"") + self.stride = stride + self.previous = None + self.loop = loop + + async def next(self, data: bytes) -> None: + if self.previous: + await self.previous + self.previous = self.loop.create_task(self._next(data)) + + async def _next(self, data: bytes) -> None: + self.request.bytes = data + log.debug( + f"Sending file part {self.request.file_part}/{self.part_count}" + f" with {len(data)} bytes" + ) + await self.client._call(self.sender, self.request) + self.request.file_part += self.stride + + async def disconnect(self) -> None: + if self.previous: + await self.previous + return await self.sender.disconnect() + + +class ParallelTransferrer: + client: TelegramClient + loop: asyncio.AbstractEventLoop + dc_id: int + senders: Optional[List[Union[DownloadSender, UploadSender]]] + auth_key: AuthKey + upload_ticker: int + + def __init__(self, client: TelegramClient, dc_id: Optional[int] = None) -> None: + self.client = client + self.loop = self.client.loop + self.dc_id = dc_id or self.client.session.dc_id + self.auth_key = ( + None + if dc_id and self.client.session.dc_id != dc_id + else self.client.session.auth_key + ) + self.senders = None + self.upload_ticker = 0 + + async def _cleanup(self) -> None: + await asyncio.gather(*[sender.disconnect() for sender in self.senders]) + self.senders = None + + @staticmethod + def _get_connection_count( + file_size: int, max_count: int = 20, full_size: int = 100 * 1024 * 1024 + ) -> int: + if file_size > full_size: + return max_count + return math.ceil((file_size / full_size) * max_count) + + async def _init_download( + self, connections: int, file: TypeLocation, part_count: int, part_size: int + ) -> None: + minimum, remainder = divmod(part_count, connections) + + def get_part_count() -> int: + nonlocal remainder + if remainder > 0: + remainder -= 1 + return minimum + 1 + return minimum + + # The first cross-DC sender will export+import the authorization, so we always create it + # before creating any other senders. + self.senders = [ + await self._create_download_sender( + file, 0, part_size, connections * part_size, get_part_count() + ), + *await asyncio.gather( + *[ + self._create_download_sender( + file, i, part_size, connections * part_size, get_part_count() + ) + for i in range(1, connections) + ] + ), + ] + + async def _create_download_sender( + self, + file: TypeLocation, + index: int, + part_size: int, + stride: int, + part_count: int, + ) -> DownloadSender: + return DownloadSender( + self.client, + await self._create_sender(), + file, + index * part_size, + part_size, + stride, + part_count, + ) + + async def _init_upload( + self, connections: int, file_id: int, part_count: int, big: bool + ) -> None: + self.senders = [ + await self._create_upload_sender(file_id, part_count, big, 0, connections), + *await asyncio.gather( + *[ + self._create_upload_sender(file_id, part_count, big, i, connections) + for i in range(1, connections) + ] + ), + ] + + async def _create_upload_sender( + self, file_id: int, part_count: int, big: bool, index: int, stride: int + ) -> UploadSender: + return UploadSender( + self.client, + await self._create_sender(), + file_id, + part_count, + big, + index, + stride, + loop=self.loop, + ) + + async def _create_sender(self) -> MTProtoSender: + dc = await self.client._get_dc(self.dc_id) + sender = MTProtoSender(self.auth_key, loggers=self.client._log) + await sender.connect( + self.client._connection( + dc.ip_address, + dc.port, + dc.id, + loggers=self.client._log, + proxy=self.client._proxy, + ) + ) + if not self.auth_key: + log.debug(f"Exporting auth to DC {self.dc_id}") + auth = await self.client(ExportAuthorizationRequest(self.dc_id)) + self.client._init_request.query = ImportAuthorizationRequest( + id=auth.id, bytes=auth.bytes + ) + req = InvokeWithLayerRequest(LAYER, self.client._init_request) + await sender.send(req) + self.auth_key = sender.auth_key + return sender + + async def init_upload( + self, + file_id: int, + file_size: int, + part_size_kb: Optional[float] = None, + connection_count: Optional[int] = None, + ) -> Tuple[int, int, bool]: + connection_count = connection_count or self._get_connection_count(file_size) + part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024 + part_count = (file_size + part_size - 1) // part_size + is_large = file_size > 10 * 1024 * 1024 + await self._init_upload(connection_count, file_id, part_count, is_large) + return part_size, part_count, is_large + + async def upload(self, part: bytes) -> None: + await self.senders[self.upload_ticker].next(part) + self.upload_ticker = (self.upload_ticker + 1) % len(self.senders) + + async def finish_upload(self) -> None: + await self._cleanup() + + async def download( + self, + file: TypeLocation, + file_size: int, + part_size_kb: Optional[float] = None, + connection_count: Optional[int] = None, + ) -> AsyncGenerator[bytes, None]: + connection_count = connection_count or self._get_connection_count(file_size) + part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024 + part_count = math.ceil(file_size / part_size) + log.debug( + "Starting parallel download: " + f"{connection_count} {part_size} {part_count} {file!s}" + ) + await self._init_download(connection_count, file, part_count, part_size) + + part = 0 + while part < part_count: + tasks = [self.loop.create_task(sender.next()) for sender in self.senders] + for task in tasks: + data = await task + if not data: + break + yield data + part += 1 + log.debug(f"Part {part} downloaded") + + log.debug("Parallel download finished, cleaning up connections") + await self._cleanup() + + +parallel_transfer_locks: DefaultDict[int, asyncio.Lock] = defaultdict( + lambda: asyncio.Lock() +) + + +def stream_file(file_to_stream: BinaryIO, chunk_size=1024): + while True: + data_read = file_to_stream.read(chunk_size) + if not data_read: + break + yield data_read + + +async def _internal_transfer_to_telegram( + client: TelegramClient, response: BinaryIO, progress_callback: callable +) -> Tuple[TypeInputFile, int]: + file_id = helpers.generate_random_long() + file_size = os.path.getsize(response.name) + + hash_md5 = hashlib.md5() + uploader = ParallelTransferrer(client) + part_size, part_count, is_large = await uploader.init_upload(file_id, file_size) + buffer = bytearray() + for data in stream_file(response): + if progress_callback: + r = progress_callback(response.tell(), file_size) + if inspect.isawaitable(r): + await r + if not is_large: + hash_md5.update(data) + if len(buffer) == 0 and len(data) == part_size: + await uploader.upload(data) + continue + new_len = len(buffer) + len(data) + if new_len >= part_size: + cutoff = part_size - len(buffer) + buffer.extend(data[:cutoff]) + await uploader.upload(bytes(buffer)) + buffer.clear() + buffer.extend(data[cutoff:]) + else: + buffer.extend(data) + if len(buffer) > 0: + await uploader.upload(bytes(buffer)) + await uploader.finish_upload() + if is_large: + return InputFileBig(file_id, part_count, "upload"), file_size + return InputFile(file_id, part_count, "upload", hash_md5.hexdigest()), file_size + + +async def download_file( + client: TelegramClient, + location: TypeLocation, + out: BinaryIO, + progress_callback: callable = None, +) -> BinaryIO: + size = location.size + dc_id, location = utils.get_input_location(location) + # We lock the transfers because telegram has connection count limits + downloader = ParallelTransferrer(client, dc_id) + downloaded = downloader.download(location, size) + async for x in downloaded: + out.write(x) + if progress_callback: + r = progress_callback(out.tell(), size) + if inspect.isawaitable(r): + await r + + return out + + +async def upload_file( + client: TelegramClient, + file: BinaryIO, + progress_callback: callable = None, +) -> TypeInputFile: + return (await _internal_transfer_to_telegram(client, file, progress_callback))[0] diff --git a/repthon/core/helpers.py b/repthon/core/helpers.py new file mode 100644 index 0000000..49d2519 --- /dev/null +++ b/repthon/core/helpers.py @@ -0,0 +1,48 @@ +import logging +from typing import Union + +from telethon.tl import types +from telethon.utils import get_display_name + +from .events import NewMessage + +LOGGER = logging.getLogger("zthon") + + +def printUser(entity: types.User) -> None: + """Print the user's first name + last name upon start""" + user = get_display_name(entity) + LOGGER.warning("تم بنجـاح تسجيل الدخول {0}".format(user)) + + +async def get_chat_link( + arg: Union[types.User, types.Chat, types.Channel, NewMessage.Event], reply=None +) -> str: + if isinstance(arg, (types.User, types.Chat, types.Channel)): + entity = arg + else: + entity = await arg.get_chat() + + if isinstance(entity, types.User): + if entity.is_self: + name = 'your "Saved Messages"' + else: + name = get_display_name(entity) or "Deleted Account?" + extra = f"[{name}](tg://user?id={entity.id})" + else: + if hasattr(entity, "username") and entity.username is not None: + username = f"@{entity.username}" + else: + username = entity.id + if reply is not None: + if isinstance(username, str) and username.startswith("@"): + username = username[1:] + else: + username = f"c/{username}" + extra = f"[{entity.title}](https://t.me/{username}/{reply})" + elif isinstance(username, int): + username = f"`{username}`" + extra = f"{entity.title} ( {username} )" + else: + extra = f"[{entity.title}](tg://resolve?domain={username})" + return extra diff --git a/repthon/core/inlinebot.py b/repthon/core/inlinebot.py new file mode 100644 index 0000000..0d66bed --- /dev/null +++ b/repthon/core/inlinebot.py @@ -0,0 +1,227 @@ +import json +import math +import os +import random +import re +import time +from uuid import uuid4 +from platform import python_version +from telethon import Button, types, version +from telethon.errors import QueryIdInvalidError +from telethon.events import CallbackQuery, InlineQuery +from youtubesearchpython import VideosSearch +from zthon import zedub, repversion, StartTime +from ..Config import Config +from ..helpers.functions import rand_key, zedalive, check_data_base_heal_th, get_readable_time +from ..helpers.functions.utube import download_button, get_yt_video_id, get_ytthumb, result_formatter, ytsearch_data +from ..plugins import mention +from ..sql_helper.globals import gvarstatus +from . import CMD_INFO, GRP_INFO, PLG_INFO, check_owner +from .logger import logging + +LOGS = logging.getLogger(__name__) + +BTN_URL_REGEX = re.compile(r"(\[([^\[]+?)\]\)") +MEDIA_PATH_REGEX = re.compile(r"(:?\<\bmedia:(:?(?:.*?)+)\>)") +tr = Config.COMMAND_HAND_LER + +def getkey(val): + for key, value in GRP_INFO.items(): + for plugin in value: + if val == plugin: + return key + return None + +def get_thumb(name): + url = f"https://github.com/TgCatUB/CatUserbot-Resources/blob/master/Resources/Inline/{name}?raw=true" + return types.InputWebDocument(url=url, size=0, mime_type="image/png", attributes=[]) + +def ibuild_keyboard(buttons): + keyb = [] + for btn in buttons: + if btn[2] and keyb: + keyb[-1].append(Button.url(btn[0], btn[1])) + else: + keyb.append([Button.url(btn[0], btn[1])]) + return keyb + +@zedub.tgbot.on(InlineQuery) +async def inline_handler(event): # sourcery no-metrics + builder = event.builder + result = None + query = event.text + string = query.lower() + query.split(" ", 2) + str_y = query.split(" ", 1) + string.split() + query_user_id = event.query.user_id + if query_user_id == Config.OWNER_ID or query_user_id in Config.SUDO_USERS: + hmm = re.compile("troll (.*) (.*)") + match = re.findall(hmm, query) + inf = re.compile("secret (.*) (.*)") + match2 = re.findall(inf, query) + hid = re.compile("hide (.*)") + match3 = re.findall(hid, query) + if match or match2 or match3: + user_list = [] + if match3: + sandy = "Chat" + query = query[5:] + info_type = ["hide", "can't", "Read Message "] + else: + sandy = "" + if match: + query = query[6:] + info_type = ["troll", "can't", "show message 🔐"] + elif match2: + query = query[7:] + info_type = ["secret", "can", "show message 🔐"] + if "|" in query: + iris, query = query.replace(" |", "|").replace("| ", "|").split("|") + users = iris.split(" ") + else: + user, query = query.split(" ", 1) + users = [user] + for user in users: + usr = int(user) if user.isdigit() else user + try: + u = await event.client.get_entity(usr) + except ValueError: + return + if u.username: + sandy += f"@{u.username}" + else: + sandy += f"[{u.first_name}](tg://user?id={u.id})" + user_list.append(u.id) + sandy += " " + sandy = sandy[:-1] + old_msg = os.path.join("./Zara", f"{info_type[0]}.txt") + try: + jsondata = json.load(open(old_msg)) + except Exception: + jsondata = False + timestamp = int(time.time() * 2) + new_msg = { + str(timestamp): {"text": query} + if match3 + else {"userid": user_list, "text": query} + } + buttons = [Button.inline(info_type[2], data=f"{info_type[0]}_{timestamp}")] + result = builder.article( + title=f"{info_type[0].title()} message to {sandy}.", + description="Send hidden text in chat." + if match3 + else f"Only he/she/they {info_type[1]} open it.", + thumb=get_thumb(f"{info_type[0]}.png"), + text="✖✖✖" + if match3 + else f"🔒 A whisper message to {sandy}, Only he/she can open it.", + buttons=buttons, + ) + await event.answer([result] if result else None) + if jsondata: + jsondata.update(new_msg) + json.dump(jsondata, open(old_msg, "w")) + else: + json.dump(new_msg, open(old_msg, "w")) + elif str_y[0].lower() == "ytdl" and len(str_y) == 2: + link = get_yt_video_id(str_y[1].strip()) + found_ = True + if link is None: + search = VideosSearch(str_y[1].strip(), limit=15) + resp = (search.result()).get("result") + if len(resp) == 0: + found_ = False + else: + outdata = await result_formatter(resp) + key_ = rand_key() + ytsearch_data.store_(key_, outdata) + buttons = [ + Button.inline( + f"1 / {len(outdata)}", + data=f"ytdl_next_{key_}_1", + ), + Button.inline( + "القائمـة 📜", + data=f"ytdl_listall_{key_}_1", + ), + Button.inline( + "⬇️ تحميـل", + data=f'ytdl_download_{outdata[1]["video_id"]}_0', + ), + ] + caption = outdata[1]["message"] + photo = await get_ytthumb(outdata[1]["video_id"]) + else: + caption, buttons = await download_button(link, body=True) + photo = await get_ytthumb(link) + if found_: + markup = event.client.build_reply_markup(buttons) + photo = types.InputWebDocument( + url=photo, size=0, mime_type="image/jpeg", attributes=[] + ) + text, msg_entities = await event.client._parse_message_text( + caption, "html" + ) + result = types.InputBotInlineResult( + id=str(uuid4()), + type="photo", + title=link, + description="⬇️ اضغـط للتحميـل", + thumb=photo, + content=photo, + send_message=types.InputBotInlineMessageMediaAuto( + reply_markup=markup, message=text, entities=msg_entities + ), + ) + else: + result = builder.article( + title="Not Found", + text=f"No Results found for `{str_y[1]}`", + description="INVALID", + ) + try: + await event.answer([result] if result else None) + except QueryIdInvalidError: + await event.answer( + [ + builder.article( + title="Not Found", + text=f"No Results found for `{str_y[1]}`", + description="INVALID", + ) + ] + ) + elif string == "pmpermit": + buttons = [ + Button.inline(text="عـرض الخيـارات", data="show_pmpermit_options"), + ] + PM_PIC = gvarstatus("pmpermit_pic") + if PM_PIC: + CAT = [x for x in PM_PIC.split()] + PIC = list(CAT) + CAT_IMG = random.choice(PIC) + else: + CAT_IMG = None + query = gvarstatus("pmpermit_text") + if CAT_IMG and CAT_IMG.endswith((".jpg", ".jpeg", ".png")): + result = builder.photo( + CAT_IMG, + # title="Alive zed", + text=query, + buttons=buttons, + ) + elif CAT_IMG: + result = builder.document( + CAT_IMG, + title="Alive cat", + text=query, + buttons=buttons, + ) + else: + result = builder.article( + title="Alive cat", + text=query, + buttons=buttons, + ) + await event.answer([result] if result else None) diff --git a/repthon/core/logger.py b/repthon/core/logger.py new file mode 100644 index 0000000..b19e32d --- /dev/null +++ b/repthon/core/logger.py @@ -0,0 +1,7 @@ +import logging + +logging.basicConfig( + format="[%(levelname)s- %(asctime)s]- %(name)s- %(message)s", + level=logging.INFO, + datefmt="%H:%M:%S", +) diff --git a/repthon/core/managers.py b/repthon/core/managers.py new file mode 100644 index 0000000..137c68e --- /dev/null +++ b/repthon/core/managers.py @@ -0,0 +1,85 @@ +import asyncio +import os + +from ..helpers.utils.format import md_to_text, paste_message +from .data import _sudousers_list + + +# https://t.me/c/1220993104/623253 +# https://docs.telethon.dev/en/latest/misc/changelog.html#breaking-changes +async def edit_or_reply( + event, + text, + parse_mode=None, + link_preview=None, + file_name=None, + aslink=False, + deflink=False, + noformat=False, + linktext=None, + caption=None, +): # sourcery no-metrics + sudo_users = _sudousers_list() + link_preview = link_preview or False + reply_to = await event.get_reply_message() + if len(text) < 4096 and not deflink: + parse_mode = parse_mode or "md" + if event.sender_id in sudo_users: + if reply_to: + return await reply_to.reply( + text, parse_mode=parse_mode, link_preview=link_preview + ) + return await event.reply( + text, parse_mode=parse_mode, link_preview=link_preview + ) + await event.edit(text, parse_mode=parse_mode, link_preview=link_preview) + return event + if not noformat: + text = md_to_text(text) + if aslink or deflink: + linktext = linktext or "Message was to big so pasted to bin" + response = await paste_message(text, pastetype="s") + text = f"{linktext} [here]({response})" + if event.sender_id in sudo_users: + if reply_to: + return await reply_to.reply(text, link_preview=link_preview) + return await event.reply(text, link_preview=link_preview) + await event.edit(text, link_preview=link_preview) + return event + file_name = file_name or "output.txt" + caption = caption or None + with open(file_name, "w+") as output: + output.write(text) + if reply_to: + await reply_to.reply(caption, file=file_name) + await event.delete() + return os.remove(file_name) + if event.sender_id in sudo_users: + await event.reply(caption, file=file_name) + await event.delete() + return os.remove(file_name) + await event.client.send_file(event.chat_id, file_name, caption=caption) + await event.delete() + os.remove(file_name) + + +async def edit_delete(event, text, time=None, parse_mode=None, link_preview=None): + sudo_users = _sudousers_list() + parse_mode = parse_mode or "md" + link_preview = link_preview or False + time = time or 5 + if event.sender_id in sudo_users: + reply_to = await event.get_reply_message() + zedevent = ( + await reply_to.reply(text, link_preview=link_preview, parse_mode=parse_mode) + if reply_to + else await event.reply( + text, link_preview=link_preview, parse_mode=parse_mode + ) + ) + else: + zedevent = await event.edit( + text, link_preview=link_preview, parse_mode=parse_mode + ) + await asyncio.sleep(time) + return await zedevent.delete() diff --git a/repthon/core/pluginManager.py b/repthon/core/pluginManager.py new file mode 100644 index 0000000..ffb314c --- /dev/null +++ b/repthon/core/pluginManager.py @@ -0,0 +1,88 @@ +import asyncio +import os +import re +import sys + +from telethon import TelegramClient + +from ..core.logger import logging +from ..sql_helper.global_collection import ( + add_to_collectionlist, + del_keyword_collectionlist, + get_collectionlist_items, +) + +package_patern = re.compile(r"([\w-]+)(?:=|<|>|!)") +github_patern = re.compile(r"(?:https?)?(?:www.)?(?:github.com/)?([\w\-.]+/[\w\-.]+)/?") +github_raw_pattern = re.compile( + r"(?:https?)?(?:raw.)?(?:githubusercontent.com/)?([\w\-.]+/[\w\-.]+)/?" +) +trees_pattern = "https://api.github.com/repos/{}/git/trees/master" +raw_pattern = "https://raw.githubusercontent.com/{}/master/{}" + +LOGS = logging.getLogger(__name__) + + +async def get_pip_packages(requirements): + """Get a list of all the pacakage's names.""" + if requirements: + packages = requirements + else: + cmd = await asyncio.create_subprocess_exec( + sys.executable.replace(" ", "\\ "), + "-m", + "pip", + "freeze", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, _ = await cmd.communicate() + packages = stdout.decode("utf-8") + tmp = package_patern.findall(packages) + return [package.lower() for package in tmp] + + +async def install_pip_packages(packages): + """Install pip packages.""" + args = ["-m", "pip", "install", "--upgrade", "--user"] + cmd = await asyncio.create_subprocess_exec( + sys.executable.replace(" ", "\\ "), + *args, + *packages, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + await cmd.communicate() + return cmd.returncode == 0 + + +def run_async(func: callable): + """Run async functions with the right event loop.""" + asyncio.get_event_loop() + return loop.run_until_complete(func) + + +async def restart_script(client: TelegramClient, sandy): + """Restart the current script.""" + try: + ulist = get_collectionlist_items() + for i in ulist: + if i == "restart_update": + del_keyword_collectionlist("restart_update") + except Exception as e: + LOGS.error(e) + try: + add_to_collectionlist("restart_update", [sandy.chat_id, sandy.id]) + except Exception as e: + LOGS.error(e) + executable = sys.executable.replace(" ", "\\ ") + args = [executable, "-m", "zthon"] + os.execle(executable, *args, os.environ) + os._exit(143) + + +async def get_message_link(client, event): + chat = await event.get_chat() + if event.is_private: + return f"tg://openmessage?user_id={chat.id}&message_id={event.id}" + return f"https://t.me/c/{chat.id}/{event.id}" diff --git a/repthon/core/pool.py b/repthon/core/pool.py new file mode 100644 index 0000000..3e089c8 --- /dev/null +++ b/repthon/core/pool.py @@ -0,0 +1,41 @@ +import asyncio +from concurrent.futures import Future, ThreadPoolExecutor +from functools import partial, wraps +from typing import Any, Callable + +from motor.frameworks.asyncio import _EXECUTOR + +from .logger import logging + +_LOG = logging.getLogger(__name__) +_LOG_STR = "<<>>" + + +def submit_thread(func: Callable[[Any], Any], *args: Any, **kwargs: Any) -> Future: + """إرسال الموضوع إلى تجمع""" + return _EXECUTOR.submit(func, *args, **kwargs) + + +def run_in_thread(func: Callable[[Any], Any]) -> Callable[[Any], Any]: + """تشغيل في موضوع""" + + @wraps(func) + async def wrapper(*args: Any, **kwargs: Any) -> Any: + loop = asyncio.get_running_loop() + return await loop.run_in_executor(_EXECUTOR, partial(func, *args, **kwargs)) + + return wrapper + + +def _get() -> ThreadPoolExecutor: + return _EXECUTOR + + +def _stop(): + _EXECUTOR.shutdown() + # pylint: disable=protected-access + _LOG.info(_LOG_STR, f"اووبس لم يتم تنصيب جميع مكاتب ريبـــثون : {_EXECUTOR._max_workers} ") + + +# pylint: disable=protected-access +_LOG.info(_LOG_STR, f" تم بنجـاح تنصيب جميع مكاتب ريبـــثون : {_EXECUTOR._max_workers} ") diff --git a/repthon/core/route.py b/repthon/core/route.py new file mode 100644 index 0000000..9efd636 --- /dev/null +++ b/repthon/core/route.py @@ -0,0 +1,8 @@ +from aiohttp import web + +routes = web.RouteTableDef() + + +@routes.get("/", allow_head=True) +async def root_route_handler(request): + return web.json_response("Rep-Thon") diff --git a/repthon/core/server.py b/repthon/core/server.py new file mode 100644 index 0000000..e5e9f84 --- /dev/null +++ b/repthon/core/server.py @@ -0,0 +1,9 @@ +from aiohttp import web + +from .route import routes + + +async def web_server(): + web_app = web.Application(client_max_size=30000000) + web_app.add_routes(routes) + return web_app diff --git a/repthon/core/session.py b/repthon/core/session.py new file mode 100644 index 0000000..09ba6dd --- /dev/null +++ b/repthon/core/session.py @@ -0,0 +1,44 @@ +import os +import sys + +from telethon.network.connection.tcpabridged import ConnectionTcpAbridged +from telethon.sessions import StringSession + +from ..Config import Config +from .client import ZedUserBotClient + +__version__ = "2.10.6" + +loop = None + +if Config.STRING_SESSION: + session = StringSession(str(Config.STRING_SESSION)) +else: + session = "zelzal" + +try: + zedub = ZedUserBotClient( + session=session, + api_id=Config.APP_ID, + api_hash=Config.API_HASH, + loop=loop, + app_version=__version__, + connection=ConnectionTcpAbridged, + auto_reconnect=True, + connection_retries=None, + ) +except Exception as e: + print(f"STRING_SESSION - {e}") + sys.exit() + + +zedub.tgbot = tgbot = ZedUserBotClient( + session="ZedTgbot", + api_id=Config.APP_ID, + api_hash=Config.API_HASH, + loop=loop, + app_version=__version__, + connection=ConnectionTcpAbridged, + auto_reconnect=True, + connection_retries=None, +).start(bot_token=Config.TG_BOT_TOKEN) diff --git a/repthon/core/zllm.txt b/repthon/core/zllm.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/repthon/core/zllm.txt @@ -0,0 +1 @@ +