Skip to content

Commit

Permalink
added convertor sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
kanewi11 committed Nov 10, 2022
2 parents d922720 + 85e0c3b commit 999b741
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 26 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 🙃.
79 changes: 79 additions & 0 deletions convertor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import struct
import base64
import sqlite3
from pathlib import Path
from typing import Union, Dict, Tuple, Coroutine, Any

from pyrogram import Client
from pyrogram.storage import FileStorage, Storage
from telethon import TelegramClient
from telethon.sessions import StringSession
from telethon.tl.types import User


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) -> 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]:
"""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

async def __save_pyrogram_session_file(self, session_string: Union[str, Coroutine[Any, Any, str]],
session_data: StringSession):
"""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, 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)
await client.storage.user_id(user_data.id)
await client.storage.date(0)
await client.storage.is_bot(False)
await client.storage.save()

@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,
None,
session_data.auth_key.key,
user_data.id,
user_data.bot
]
try:
bytes_pack = struct.pack(*pack)
except struct.error:
pack[0] = Storage.OLD_SESSION_STRING_FORMAT_64
bytes_pack = struct.pack(*pack)

encode_pack = base64.urlsafe_b64encode(bytes_pack)
decode_pack = encode_pack.decode()
sting_session = decode_pack.rstrip("=")
return sting_session
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 999b741

Please sign in to comment.