From 85e0c3b692b88665024bf93f1afe5202a3bbe224 Mon Sep 17 00:00:00 2001 From: kanewi11 Date: Thu, 10 Nov 2022 05:46:35 +0300 Subject: [PATCH] Working convert Telethon sessions to Pyrogram --- .gitignore | 1 + README.md | 9 ++--- convertot.py => convertor.py | 34 ++++++++++------- reactionbot.py | 72 +++++++++++++++++++++++++----------- requirements.txt | 3 ++ 5 files changed, 80 insertions(+), 39 deletions(-) rename convertot.py => convertor.py (63%) diff --git a/.gitignore b/.gitignore index f5b4169..13eba26 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ .idea/**/dictionaries .idea/**/shelf +test_sessions/* sessions/* test.py *.log diff --git a/README.md b/README.md index 6a30a82..ada0774 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,10 @@

-**This script sends reactions to a new post or message in selected open groups and channels, as well as automatically subscribes to them.** +**Automatically converts Telethon sessions to Pyrogram (may not be stable).** +**As long as the configuration file has the same name as the session file (see below). If you do not comply, it will not work at all 🙃** + +_This script sends reactions to a new post or message in selected open groups and channels, as well as automatically subscribes to them._ ## Launch Instructions 1. Create an empty directory @@ -80,7 +83,3 @@ You can add more parameters that [pyrogram](https://github.com/pyrogram/pyrogram ... } ``` - -### TODO: - - If there will be time to add session definition and conversion from tdata, telethon. -But I don't think the time will come 🙃. \ No newline at end of file diff --git a/convertot.py b/convertor.py similarity index 63% rename from convertot.py rename to convertor.py index 46147c6..4873806 100644 --- a/convertot.py +++ b/convertor.py @@ -14,29 +14,40 @@ class SessionConvertor: def __init__(self, session_path: Path, config: Dict, work_dir: Path): self.session_path = session_path + self.inappropriate_sessions_path = work_dir.joinpath('unnecessary_sessions') self.app_id = config['app_id'] self.app_hash = config['app_hash'] self.work_dir = work_dir - async def convert(self): - user_data, session_data = self.__get_data_telethon_session() - converted_sting_session = self.__get_converted_sting_session(session_data, user_data) - await self.__delete_telethon_session() + async def convert(self) -> None: + """Main func""" + user_data, session_data = await self.__get_data_telethon_session() + converted_sting_session = await self.__get_converted_sting_session(session_data, user_data) + await self.move_file_to_unnecessary(self.session_path) await self.__save_pyrogram_session_file(converted_sting_session, session_data) + async def move_file_to_unnecessary(self, file_path: Path): + """Move the unnecessary Telethon session file to the directory with the unnecessary sessions""" + if file_path.exists(): + file_path.rename(self.inappropriate_sessions_path.joinpath(file_path.name)) + async def __get_data_telethon_session(self) -> Tuple[User, StringSession]: - async with TelegramClient(self.session_path.__str__(), self.app_id, self.app_hash) as client: + """Get User and StringSession""" + async with TelegramClient(self.session_path.with_suffix('').__str__(), self.app_id, self.app_hash) as client: user_data = await client.get_me() string_session = StringSession.save(client.session) session_data = StringSession(string_session) - return user_data, session_data + return user_data, session_data async def __save_pyrogram_session_file(self, session_string: Union[str, Coroutine[Any, Any, str]], session_data: StringSession): - async with Client(session_string, api_id=self.app_id, api_hash=self.app_id, - workdir=self.work_dir.__str__()) as client: + """Create session file for pyrogram""" + async with Client(self.session_path.stem, session_string=session_string, api_id=self.app_id, + api_hash=self.app_hash, workdir=self.work_dir.__str__()) as client: user_data = await client.get_me() - client.storage = FileStorage(self.session_path.stem, Path(self.work_dir)) + client.storage = FileStorage(self.session_path.stem, self.work_dir) + client.storage.conn = sqlite3.Connection(self.session_path) + client.storage.create() await client.storage.dc_id(session_data.dc_id) await client.storage.test_mode(False) await client.storage.auth_key(session_data.auth_key.key) @@ -45,12 +56,9 @@ async def __save_pyrogram_session_file(self, session_string: Union[str, Coroutin await client.storage.is_bot(False) await client.storage.save() - async def __delete_telethon_session(self): - if self.session_path.exists(): - self.session_path.unlink() - @staticmethod async def __get_converted_sting_session(session_data: StringSession, user_data: User) -> str: + """Convert to sting session""" pack = [ Storage.SESSION_STRING_FORMAT, session_data.dc_id, diff --git a/reactionbot.py b/reactionbot.py index 5c26a63..fcf9c96 100644 --- a/reactionbot.py +++ b/reactionbot.py @@ -8,7 +8,7 @@ import configparser from pathlib import Path from sqlite3 import OperationalError -from typing import List, Dict +from typing import List, Dict, Tuple import uvloop from pyrogram.errors import ReactionInvalid @@ -17,12 +17,15 @@ from pyrogram.errors.exceptions.unauthorized_401 import UserDeactivatedBan from config import CHANNELS, POSSIBLE_KEY_NAMES, EMOJIS +from convertor import SessionConvertor TRY_AGAIN_SLEEP = 20 BASE_DIR = Path(sys.argv[0]).parent WORK_DIR = BASE_DIR.joinpath('sessions') +BANNED_SESSIONS_DIR = WORK_DIR.joinpath('banned_sessions') +UNNECESSARY_SESSIONS_DIR = WORK_DIR.joinpath('unnecessary_sessions') CONFIG_FILE_SUFFIXES = ('.ini', '.json') @@ -35,7 +38,7 @@ async def send_reaction(client: Client, message: types.Message) -> None: emoji = random.choice(EMOJIS) try: await client.send_reaction(chat_id=message.chat.id, message_id=message.id, emoji=emoji) - + except ReactionInvalid: logging.warning(f'{emoji} - INVALID REACTION') except UserDeactivatedBan: @@ -46,9 +49,9 @@ async def send_reaction(client: Client, message: types.Message) -> None: async def make_work_dir() -> None: """Create the sessions directory if it does not exist""" - if WORK_DIR.exists(): - return - WORK_DIR.mkdir() + WORK_DIR.mkdir(exist_ok=True) + UNNECESSARY_SESSIONS_DIR.mkdir(exist_ok=True) + BANNED_SESSIONS_DIR.mkdir(exist_ok=True) async def get_config_files_path() -> List[Path]: @@ -72,13 +75,13 @@ async def config_from_json_file(file_path: Path) -> Dict: async def get_config(file_path: Path) -> Dict: """Return the config file to the path""" - config = { - 'ini': config_from_ini_file, - 'json': config_from_json_file, + config_suffixes = { + '.ini': config_from_ini_file, + '.json': config_from_json_file, } - extension = file_path.suffix.lower()[1:] - config = await config[extension](file_path) - normalized_confing = {'name': file_path.name.split('.')[0]} + suffix = file_path.suffix.lower() + config = await config_suffixes[suffix](file_path) + normalized_confing = {'name': file_path.stem} for key, values in POSSIBLE_KEY_NAMES.items(): for value in values: if not config.get(value): @@ -88,19 +91,45 @@ async def get_config(file_path: Path) -> Dict: return normalized_confing -async def create_clients(config_files: List[Path]) -> List[Client]: +async def create_apps(config_files_paths: List[Path]) -> List[Tuple[Client, Dict, Path]]: """ Create 'Client' instances from config files. **If there is no name key in the config file, then the config file has the same name as the session!** """ - clients = [] - for config_file in config_files: + apps = [] + for config_file_path in config_files_paths: try: - config_dict = await get_config(config_file) - clients.append(Client(workdir=WORK_DIR.__str__(), **config_dict)) + config_dict = await get_config(config_file_path) + session_file_path = WORK_DIR.joinpath(config_file_path.with_suffix('.session')) + apps.append((Client(workdir=WORK_DIR.__str__(), **config_dict), config_dict, session_file_path)) except Exception: logging.warning(traceback.format_exc()) - return clients + return apps + + +async def try_convert(session_path: Path, config: Dict): + """Try to convert the session if the session failed to start in Pyrogram""" + convertor = SessionConvertor(session_path, config, BASE_DIR) + try: + await convertor.convert() + except OperationalError: + await convertor.move_file_to_unnecessary(session_path) + for suffix in CONFIG_FILE_SUFFIXES: + config_file_path = session_path.with_suffix(suffix) + await convertor.move_file_to_unnecessary(config_file_path) + logging.warning('Preservation of the session failed ' + session_path.stem) + + +async def move_session_to_ban_dir(session_path: Path): + """Move file to ban dir""" + if session_path.exists(): + session_path.rename(BANNED_SESSIONS_DIR.joinpath(session_path.name)) + + for suffix in CONFIG_FILE_SUFFIXES: + config_file_path = session_path.with_suffix(suffix) + if not session_path.exists(): + continue + config_file_path.rename(BANNED_SESSIONS_DIR.joinpath(config_file_path.name)) async def main(): @@ -116,20 +145,21 @@ async def main(): await make_work_dir() config_files = await get_config_files_path() - apps = await create_clients(config_files) + apps = await create_apps(config_files) if not apps: raise ValueError('No apps!') - for app in apps: + for app, config_dict, session_file_path in apps: message_handler = MessageHandler(send_reaction, filters=filters.chat(CHANNELS)) app.add_handler(message_handler) try: await app.start() except OperationalError: - logging.warning('Error in ' + app.name) + await try_convert(session_file_path, config_dict) continue except UserDeactivatedBan: + await move_session_to_ban_dir(session_file_path) logging.warning('Session banned - ' + app.name) continue except Exception: @@ -141,7 +171,7 @@ async def main(): await idle() - for app in apps: + for app, _, _ in apps: await app.stop() diff --git a/requirements.txt b/requirements.txt index 51dbbd1..68fea9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ pyaes==1.6.1 +pyasn1==0.4.8 Pyrogram==2.0.57 PySocks==1.7.1 +rsa==4.9 +Telethon==1.25.4 TgCrypto==1.2.4 uvloop==0.17.0