Skip to content

Commit

Permalink
Working convert Telethon sessions to Pyrogram
Browse files Browse the repository at this point in the history
  • Loading branch information
kanewi11 committed Nov 10, 2022
1 parent d7efc10 commit 85e0c3b
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
.idea/**/dictionaries
.idea/**/shelf

test_sessions/*
sessions/*
test.py
*.log
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
</a>
</p>

**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
Expand Down Expand Up @@ -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 🙃.
34 changes: 21 additions & 13 deletions convertot.py → convertor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand Down
72 changes: 51 additions & 21 deletions reactionbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')

Expand All @@ -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:
Expand All @@ -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]:
Expand All @@ -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):
Expand All @@ -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():
Expand All @@ -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:
Expand All @@ -141,7 +171,7 @@ async def main():

await idle()

for app in apps:
for app, _, _ in apps:
await app.stop()


Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 85e0c3b

Please sign in to comment.