├── AndroidTelePorter ├── models │ ├── __init__.py │ ├── salt.py │ ├── ip.py │ ├── auth.py │ ├── tgnet_session.py │ ├── datacenter.py │ └── headers.py ├── utils │ ├── __init__.py │ ├── auth_key.py │ ├── filesmanager.py │ └── nativebytebuffer.py ├── constants │ ├── __init__.py │ └── datacenters.py ├── __init__.py ├── managers │ ├── __init__.py │ ├── userconfigmanager.py │ └── tgnetmanager.py └── androidsession.py ├── .gitignore ├── requirements.txt ├── LICENSE ├── setup.py ├── .github └── workflows │ └── python-publish.yml ├── main.py └── README.md /AndroidTelePorter/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AndroidTelePorter/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AndroidTelePorter/constants/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | venv 3 | .venv 4 | tgnets 5 | -------------------------------------------------------------------------------- /AndroidTelePorter/__init__.py: -------------------------------------------------------------------------------- 1 | from .androidsession import AndroidSession 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | telethon 2 | lxml~=5.2.2 3 | opentele~=1.15.1 4 | setuptools~=72.1.0 5 | Pyrogram~=2.0.106 6 | -------------------------------------------------------------------------------- /AndroidTelePorter/managers/__init__.py: -------------------------------------------------------------------------------- 1 | from .tgnetmanager import TgnetManager 2 | from .userconfigmanager import UserConfigManager 3 | -------------------------------------------------------------------------------- /AndroidTelePorter/models/salt.py: -------------------------------------------------------------------------------- 1 | class Salt: 2 | salt_valid_since: int 3 | salt_valid_until: int 4 | salt: int 5 | 6 | def __str__(self): 7 | return str(self.salt) 8 | -------------------------------------------------------------------------------- /AndroidTelePorter/utils/auth_key.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | def calculate_id(auth_key: bytes | bytearray) -> int: 5 | return int.from_bytes(hashlib.sha1(auth_key).digest()[12:12 + 8], "little", signed=True) 6 | -------------------------------------------------------------------------------- /AndroidTelePorter/models/ip.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Literal 3 | 4 | 5 | @dataclass 6 | class IP: 7 | type_: Literal['addressesIpv4', 'addressesIpv6', 'addressesIpv4Download', 'addressesIpv6Download'] 8 | address: str 9 | port: int 10 | flags: int = field(default_factory=lambda: None) 11 | secret: str | None = field(default_factory=lambda: None) # added in 9 version 12 | 13 | def __str__(self): 14 | return f'{self.address}:{self.port}' 15 | -------------------------------------------------------------------------------- /AndroidTelePorter/models/auth.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | 4 | @dataclass 5 | class AuthCredentials: 6 | auth_key_perm: bytearray | bytes | None = field(default_factory=lambda: None) # will be None in empty session 7 | auth_key_perm_id: int | None = field(default_factory=lambda: None) # will be None in empty session 8 | auth_key_temp: bytearray | bytes | None = field(default_factory=lambda: None) # added in 8 version 9 | auth_key_temp_id: int | None = field(default_factory=lambda: None) # added in 8 version 10 | auth_key_media_temp: bytearray | bytes | None = field(default_factory=lambda: None) # added in 12 version 11 | auth_key_media_temp_id: int | None = field(default_factory=lambda: None) # added in 12 version 12 | authorized: int = field(default_factory=lambda: None) 13 | 14 | def __str__(self): 15 | return str(self.auth_key_perm) 16 | -------------------------------------------------------------------------------- /AndroidTelePorter/models/tgnet_session.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from AndroidTelePorter.models.datacenter import Datacenter 4 | from AndroidTelePorter.models.headers import Headers 5 | 6 | 7 | @dataclass 8 | class TgnetSession: 9 | headers: Headers 10 | datacenters: list[Datacenter] 11 | 12 | def __post_init__(self): 13 | self.__dcs = {} 14 | for datacenter in self.datacenters: 15 | self.__dcs[datacenter.dc_id] = datacenter 16 | 17 | @property 18 | def current_dc(self) -> Datacenter: 19 | return self.get_dc(self.headers.current_dc_id) 20 | 21 | @property 22 | def auth_key(self) -> bytes: 23 | """ 24 | Returns: bytes (auth key for current dc) 25 | """ 26 | return self.current_dc.auth.auth_key_perm 27 | 28 | @property 29 | def dc_id(self) -> int: 30 | return self.headers.current_dc_id 31 | 32 | def get_dc(self, dc_id: int) -> Datacenter | None: 33 | return self.__dcs.get(dc_id) 34 | -------------------------------------------------------------------------------- /AndroidTelePorter/models/datacenter.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from AndroidTelePorter.models.auth import AuthCredentials 4 | from AndroidTelePorter.models.ip import IP 5 | from AndroidTelePorter.models.salt import Salt 6 | 7 | 8 | @dataclass 9 | class Datacenter: 10 | current_version: int 11 | dc_id: int 12 | last_init_version: int 13 | 14 | auth: AuthCredentials = field(default_factory=lambda: AuthCredentials) 15 | 16 | last_init_media_version: int | None = field(default_factory=lambda: None) # added in 10 version 17 | 18 | is_cdn_datacenter: bool | None = field(default_factory=lambda: None) # added in 6 version 19 | 20 | ips: dict[str, list[IP]] = field(default_factory=lambda: { 21 | 'addressesIpv4': [], 22 | 'addressesIpv6': [], 23 | 'addressesIpv4Download': [], 24 | 'addressesIpv6Download': [], 25 | }) 26 | 27 | salt: list[Salt] = field(default_factory=lambda: []) 28 | 29 | def __str__(self): 30 | return f'DC ID: {self.dc_id} | Auth Key: {len(self.auth.auth_key_perm)} bytes' 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 batreller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import codecs 3 | import os 4 | 5 | here = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as fh: 8 | long_description = "\n" + fh.read() 9 | 10 | VERSION = '1.1.1' 11 | DESCRIPTION = 'Serializer and deserializer for mobile telegram session' 12 | 13 | setup( 14 | name='AndroidTelePorter', 15 | version=VERSION, 16 | author='batreller', 17 | author_email='', 18 | description=DESCRIPTION, 19 | long_description_content_type='text/markdown', 20 | long_description=long_description, 21 | url='https://github.com/batreller/AndroidTelePorter', 22 | packages=find_packages(), 23 | license='MIT', 24 | install_requires=['telethon', 'lxml~=5.2.2', 'opentele~=1.15.1', 'setuptools~=72.1.0', 'Pyrogram~=2.0.106'], 25 | classifiers=[ 26 | 'Development Status :: 5 - Production/Stable', 27 | 'Intended Audience :: Developers', 28 | 'Programming Language :: Python :: 3', 29 | 'Operating System :: OS Independent' 30 | ], 31 | python_requires='>=3.9' 32 | ) 33 | -------------------------------------------------------------------------------- /AndroidTelePorter/models/headers.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import List 3 | 4 | 5 | @dataclass 6 | class Headers: 7 | version: int 8 | test_backend: bool 9 | client_blocked: bool | None = field(default_factory=lambda: None) # added in 3 version 10 | last_init_system_langcode: str | None = field(default_factory=lambda: None) # added in 4 version 11 | current_dc_id: int | None = field(default_factory=lambda: None) # will be None in empty session 12 | time_difference: int | None = field(default_factory=lambda: None) # will be None in empty session 13 | last_dc_update_time: int | None = field(default_factory=lambda: None) # will be None in empty session 14 | push_session_id: int | None = field(default_factory=lambda: None) # will be None in empty session 15 | registered_for_internal_push: bool | None = field(default_factory=lambda: None) # added in 2 version 16 | last_server_time: int | None = field(default_factory=lambda: None) # added in 5 version 17 | current_time: int | None = field(default_factory=lambda: None) # added in 5 version 18 | sessions_to_destroy: List[int] = field(default_factory=lambda: []) 19 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up Python 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: ${{ secrets.PYPI_USERNAME }} 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /AndroidTelePorter/utils/filesmanager.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import os 3 | from lxml import etree 4 | 5 | from AndroidTelePorter.managers import TgnetManager, UserConfigManager 6 | import xml.etree.ElementTree as ET 7 | 8 | 9 | def read_tgnet(path: str) -> TgnetManager: 10 | with open(path, 'rb') as f: 11 | buffer = f.read() 12 | 13 | return TgnetManager.from_buffer(buffer) 14 | 15 | 16 | def read_userconfig(path: str) -> UserConfigManager: 17 | try: 18 | tree = ET.parse(path, parser=ET.XMLParser(encoding='utf8')) 19 | except ET.ParseError: 20 | raise ValueError('Invalid file passed as userconfig, make sure you are using shared_prefs/userconfing.xml file') 21 | user_info_element = tree.find(".//string[@name='user']") 22 | if user_info_element is None or not user_info_element.text: 23 | raise ValueError(f"{path} does not contain user id. This user config file is invalid.") 24 | 25 | return UserConfigManager.from_base64(user_info_element.text) 26 | 27 | 28 | def write_tgnet(tgnet_manager: TgnetManager, path: str) -> None: 29 | os.makedirs(path, exist_ok=True) 30 | with open(os.path.join(path, 'tgnet.dat'), 'wb') as f: 31 | f.write(tgnet_manager.to_buffer().get_value()) 32 | 33 | 34 | def write_userconfig(userconfig_manager: UserConfigManager, path: str) -> None: 35 | os.makedirs(path, exist_ok=True) 36 | root = etree.Element("map") 37 | user_element = etree.SubElement(root, "string") 38 | user_element.set("name", "user") 39 | user_element.text = base64.b64encode(userconfig_manager.userconfig._bytes()).decode('utf-8') 40 | tree = etree.ElementTree(root) 41 | tree.write(os.path.join(path, 'userconfing.xml'), pretty_print=True, xml_declaration=True, 42 | encoding="utf-8", standalone="yes") 43 | -------------------------------------------------------------------------------- /AndroidTelePorter/managers/userconfigmanager.py: -------------------------------------------------------------------------------- 1 | """ 2 | UserConfigManager is a class used to work with user config files 3 | it is based on NativeByteBuffer 4 | 5 | Creator https://github.com/batreller/ 6 | Code https://github.com/batreller/AndroidTelePorter 7 | """ 8 | 9 | import base64 10 | 11 | from telethon.errors import TypeNotFoundError 12 | from telethon.extensions import BinaryReader 13 | from telethon.tl.types import UserFull, User, UserEmpty 14 | 15 | 16 | def clean_base64(data: str) -> str: 17 | """Clean a base64 encoded string, so it can be read by from_bytes method. 18 | 19 | Args: 20 | data (str): The base64 encoded string. 21 | 22 | Returns: 23 | str: The cleaned base64 encoded string with correct padding and without useless symbols. 24 | 25 | """ 26 | data = data.replace(" ", "") 27 | while len(data) % 4 != 0: 28 | data += '=' 29 | return data 30 | 31 | 32 | class UserConfigManager: 33 | def __init__(self, userconfig: User | UserEmpty | UserFull) -> None: 34 | self.userconfig: User | UserEmpty | UserFull = userconfig 35 | 36 | @classmethod 37 | def from_base64(cls, data: str) -> 'UserConfigManager': 38 | return cls.from_bytes(base64.b64decode(clean_base64(data))) 39 | 40 | @classmethod 41 | def from_bytes(cls, data: bytes | bytearray) -> 'UserConfigManager': 42 | bdata = BinaryReader(data) 43 | try: 44 | user = bdata.tgread_object() 45 | except TypeNotFoundError: 46 | raise ValueError(f'Constructor ID {hex(bdata.read_int(signed=False))} not found, run pip install --upgrade git+https://github.com/LonamiWebs/Telethon.git') 47 | if not isinstance(user, (User, UserEmpty, UserFull)): 48 | raise ValueError('Invalid bytes') 49 | return cls(userconfig=user) 50 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """Telegram android session converter or AndroidTelePorter 2 | 3 | Is a code used to scrap all needed session info 4 | from original telegram's client on Android 5 | and convert it into other formats or to mobile format. 6 | 7 | There are 4 locations of tgnet.dat file: 8 | /data/data/org.telegram.messenger.web/files/tgnet.dat 9 | /data/data/org.telegram.messenger.web/files/account1/tgnet.dat 10 | /data/data/org.telegram.messenger.web/files/account2/tgnet.dat 11 | /data/data/org.telegram.messenger.web/files/account3/tgnet.dat 12 | 13 | and 4 locations of userconfing.xml file: 14 | /data/data/org.telegram.messenger.web/shared_prefs/userconfing.xml 15 | /data/data/org.telegram.messenger.web/shared_prefs/userconfig1.xml 16 | /data/data/org.telegram.messenger.web/shared_prefs/userconfig2.xml 17 | /data/data/org.telegram.messenger.web/shared_prefs/userconfig3.xml 18 | 19 | But in real-life scenario if you only have 1 account on your phone 20 | you will only need /files/tgnet.dat and userconfing.xml 21 | 22 | tgnet.dat contains auth key and dc id 23 | userconfing.xml contains user id 24 | 25 | Creator https://github.com/batreller/ 26 | Code https://github.com/batreller/AndroidTelePorter 27 | """ 28 | 29 | from AndroidTelePorter import AndroidSession 30 | 31 | if __name__ == '__main__': 32 | session = AndroidSession.from_manual( 33 | auth_key=bytes.fromhex( 34 | 'hex auth key' 35 | ), 36 | dc_id=0, # datacenter id (from 1 to 5) 37 | user_id=12345678 # telegram user id 38 | ) # can be used to create any session (tgnet / tdata / telethon) from auth key, dc id and user id 39 | 40 | session = AndroidSession.from_tgnet( 41 | tgnet_path=r'files\tgnet.dat', # contains auth key and dc id 42 | userconfig_path=r'shared_prefs\userconfing.xml' # contains user id 43 | ) # can be used to convert session from tgnet.dat and userconfing.xml into any other format 44 | 45 | session.to_tgnet('converted/tgnet') # will create all needed files right in directory that you specified 46 | session.to_tdata('converted/pc') # will create another folder "tdata" inside directory that you specified 47 | session.to_telethon('converted/telethon.session') # must end with .session 48 | session.to_pyrogram('converted/pyrogram.session') # must end with .session 49 | -------------------------------------------------------------------------------- /AndroidTelePorter/constants/datacenters.py: -------------------------------------------------------------------------------- 1 | from AndroidTelePorter.models.ip import IP 2 | 3 | DATACENTERS = { 4 | 1: { 5 | 'addressesIpv4': [ 6 | IP(type_='addressesIpv4', address='149.154.175.52', port=443, flags=0, secret=''), 7 | IP(type_='addressesIpv4', address='149.154.175.54', port=443, flags=16, secret='') 8 | ], 9 | 'addressesIpv6': [ 10 | IP(type_='addressesIpv6', address='2001:0b28:f23d:f001:0000:0000:0000:000a', port=443, flags=1, secret='')], 11 | 'addressesIpv4Download': [], 12 | 'addressesIpv6Download': [], 13 | }, 14 | 2: { 15 | 'addressesIpv4': [IP(type_='addressesIpv4', address='149.154.167.41', port=443, flags=0, secret='')], 16 | 'addressesIpv6': [ 17 | IP(type_='addressesIpv6', address='2001:067c:04e8:f002:0000:0000:0000:000a', port=443, flags=1, secret='')], 18 | 'addressesIpv4Download': [ 19 | IP(type_='addressesIpv4Download', address='149.154.167.151', port=443, flags=2, secret='')], 20 | 'addressesIpv6Download': [ 21 | IP(type_='addressesIpv6Download', address='2001:067c:04e8:f002:0000:0000:0000:000b', port=443, flags=3, 22 | secret='')], 23 | }, 24 | 3: { 25 | 'addressesIpv4': [IP(type_='addressesIpv4', address='149.154.175.100', port=443, flags=0, secret='')], 26 | 'addressesIpv6': [ 27 | IP(type_='addressesIpv6', address='2001:0b28:f23d:f003:0000:0000:0000:000a', port=443, flags=1, secret='')], 28 | 'addressesIpv4Download': [], 29 | 'addressesIpv6Download': [], 30 | }, 31 | 4: { 32 | 'addressesIpv4': [IP(type_='addressesIpv4', address='149.154.167.92', port=443, flags=0, secret='')], 33 | 'addressesIpv6': [ 34 | IP(type_='addressesIpv6', address='2001:067c:04e8:f004:0000:0000:0000:000a', port=443, flags=1, secret='')], 35 | 'addressesIpv4Download': [ 36 | IP(type_='addressesIpv4Download', address='149.154.165.96', port=443, flags=2, secret='')], 37 | 'addressesIpv6Download': [ 38 | IP(type_='addressesIpv6Download', address='2001:067c:04e8:f004:0000:0000:0000:000b', port=443, flags=3, 39 | secret='')], 40 | }, 41 | 5: { 42 | 'addressesIpv4': [IP(type_='addressesIpv4', address='91.108.56.197', port=443, flags=0, secret='')], 43 | 'addressesIpv6': [ 44 | IP(type_='addressesIpv6', address='2001:0b28:f23f:f005:0000:0000:0000:000a', port=443, flags=1, secret='')], 45 | 'addressesIpv4Download': [], 46 | 'addressesIpv6Download': [], 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidTelePorter 2 | 3 | [![pypi package](https://img.shields.io/pypi/v/AndroidTelePorter.svg)](https://pypi.python.org/pypi/AndroidTelePorter/) 4 | 5 | 6 | Serializer and deserializer for mobile telegram session 7 | 8 | ## Table of Contents 9 | 10 | - [Project Name](#project-name) 11 | - [Table of Contents](#table-of-contents) 12 | - [Description](#description) 13 | - [Installation](#installation) 14 | - [Usage](#usage) 15 | 16 | ## Description 17 | 18 | This tool can be used to serialize and deserialize session on original Telegram client for Android phones. 19 | 20 | It can extract any information stored in files/tgnet.dat and all needed information from shared_prefs/userconfing.xml 21 | 22 | It also can deserialize existing session into object and convert it into other session formats (currently supported tdata, telethon and tgnet) 23 | 24 | And you can serialize session manually into mobile tgnet format, all you need is just auth key, datacenter id and user id, it is minimum information needed for almost any session format 25 | 26 | ## Installation 27 | 28 | You can easily set up this package as it is available on pypi by running the following command 29 | ```bash 30 | pip install AndroidTelePorter 31 | ``` 32 | 33 | ## Usage 34 | 35 | #### Converting existing mobile session into other format 36 | ```python 37 | from AndroidTelePorter import AndroidSession 38 | 39 | # both tgnet.dat and userconfing.xml are stored in /data/data/org.telegram.messenger directory 40 | # if you have more than 1 account you would need to use tgnet.dat from /files/account(account_number)/tgnet.dat 41 | # and corresponding userconfig.xml file from /shared_prefs/userconfig(account_number).xml 42 | session = AndroidSession.from_tgnet( 43 | tgnet_path=r'files\tgnet.dat', # contains auth key and dc id 44 | userconfig_path=r'shared_prefs\userconfing.xml' # contains user id 45 | ) 46 | 47 | session.to_tgnet('converted/tgnet') # will create all needed files right in directory that you specified 48 | # or 49 | session.to_tdata('converted/pc') # will create another folder "tdata" inside directory that you specified 50 | # or 51 | session.to_telethon('converted/telethon.session') # must end with .session 52 | ``` 53 | 54 | #### Creating mobile session from auth key, dc id and user id 55 | ```python 56 | from AndroidTelePorter import AndroidSession 57 | 58 | session = AndroidSession.from_manual( 59 | auth_key=bytes.fromhex('hex auth key'), 60 | dc_id=0, # datacenter id (from 1 to 5) 61 | user_id=12345678 # telegram user id 62 | ) # can be used to create any session (tgnet / tdata / telethon) from auth key, dc id and user id 63 | 64 | session.to_tgnet('converted/tgnet') # will create all needed files right in directory that you specified 65 | ``` 66 | -------------------------------------------------------------------------------- /AndroidTelePorter/utils/nativebytebuffer.py: -------------------------------------------------------------------------------- 1 | """ 2 | NativeByteBuffer class is a full copy of telegram's class NativeByteBuffer 3 | https://github.com/DrKLO/Telegram/blob/master/TMessagesProj/jni/tgnet/NativeByteBuffer.cpp 4 | 5 | Creator https://github.com/batreller/ 6 | Code https://github.com/batreller/AndroidTelePorter 7 | """ 8 | 9 | from io import BytesIO 10 | 11 | 12 | class NativeByteBuffer: 13 | def __init__(self, data: bytes | bytearray = None): 14 | self.stream = BytesIO(data) 15 | 16 | def __len__(self): 17 | return len(self.stream.getvalue()) 18 | 19 | def get_value(self): 20 | return self.stream.getvalue() 21 | 22 | def read_bytes(self, length: int) -> bytes: 23 | return self.stream.read(length) 24 | 25 | def read_byte(self): 26 | return self.read_bytes(1)[0] 27 | 28 | def read_number(self, length: int, signed=True): 29 | return int.from_bytes(self.read_bytes(length), byteorder='little', signed=signed) 30 | 31 | def read_int(self, signed=True): 32 | return self.read_number(4, signed=signed) 33 | 34 | def read_long(self, signed=True): 35 | return self.read_number(8, signed=signed) 36 | 37 | def read_bool(self): 38 | value = self.read_int(signed=False) 39 | if value == 0x997275b5: 40 | return True 41 | elif value == 0xbc799737: 42 | return False 43 | raise BufferError('Unexpected byte value') 44 | 45 | def read_byte_array(self): 46 | sl = 1 47 | length = self.read_number(1, signed=False) 48 | if length >= 254: 49 | length = self.read_number(3, signed=False) 50 | sl = 4 51 | 52 | addition = (length + sl) % 4 53 | if addition != 0: 54 | addition = 4 - addition 55 | 56 | result = self.read_bytes(length) 57 | self.read_bytes(addition) 58 | return result 59 | 60 | def read_string(self): 61 | return str(self.read_byte_array(), encoding='utf-8', errors='replace') 62 | 63 | def write_bytes(self, data: bytearray | bytes): 64 | self.stream.write(data) 65 | 66 | def write_number(self, number: int, length: int, signed=True): 67 | self.write_bytes(number.to_bytes(length, byteorder='little', signed=signed)) 68 | 69 | def write_int(self, number: int, signed=True): 70 | self.write_number(number=number, length=4, signed=signed) 71 | 72 | def write_long(self, number: int, signed=True): 73 | self.write_number(number=number, length=8, signed=signed) 74 | 75 | def write_bool(self, value: bool): 76 | if value is True: 77 | self.write_int(0x997275b5, signed=False) 78 | else: 79 | self.write_int(0xbc799737, signed=False) 80 | 81 | def write_byte_array(self, data: bytearray | bytes): 82 | length = len(data) 83 | if length < 254: # 1byte(len) + data 84 | self.write_number(length, 1, signed=False) 85 | else: # 1byte(len) + 3bytes(lenMore254) + data 86 | self.write_number(254, 1, signed=False) 87 | self.write_number(length, 3, signed=False) 88 | 89 | self.write_bytes(data) 90 | 91 | # calculate padding 92 | sl = 1 if length < 254 else 4 93 | total_length = length + sl 94 | padding = (4 - (total_length % 4)) % 4 95 | 96 | # write padding bytes 97 | if padding: 98 | self.write_bytes(b'\x00' * padding) 99 | 100 | def write_string(self, value: str): 101 | self.write_byte_array(value.encode('utf-8')) 102 | -------------------------------------------------------------------------------- /AndroidTelePorter/managers/tgnetmanager.py: -------------------------------------------------------------------------------- 1 | """ 2 | TgnetManager is a class used to work with tgnet.dat files 3 | it is based on NativeByteBuffer 4 | 5 | Creator https://github.com/batreller/ 6 | Code https://github.com/batreller/AndroidTelePorter 7 | """ 8 | 9 | import time 10 | from typing import Literal 11 | 12 | from AndroidTelePorter.utils.nativebytebuffer import NativeByteBuffer 13 | from AndroidTelePorter.models.auth import AuthCredentials 14 | from AndroidTelePorter.models.datacenter import Datacenter 15 | from AndroidTelePorter.models.headers import Headers 16 | from AndroidTelePorter.models.ip import IP 17 | from AndroidTelePorter.models.salt import Salt 18 | from AndroidTelePorter.models.tgnet_session import TgnetSession 19 | 20 | 21 | class TgnetManager: 22 | def __init__(self, session: TgnetSession) -> None: 23 | self.session = session 24 | 25 | def to_buffer(self) -> NativeByteBuffer: 26 | buffer = NativeByteBuffer() 27 | self._write_front_headers(buffer=buffer, headers=self.session.headers) 28 | self._write_datacenters(buffer=buffer, datacenters=self.session.datacenters) 29 | self._write_buffer_length(buffer=buffer) 30 | return buffer 31 | 32 | @classmethod 33 | def from_buffer(cls, data: bytes | bytearray) -> 'TgnetManager': 34 | buffer = NativeByteBuffer(data) 35 | session = TgnetSession( 36 | headers=cls._read_headers(buffer), 37 | datacenters=cls._read_datacenters(buffer) 38 | ) 39 | return cls(session=session) 40 | 41 | @staticmethod 42 | def _write_front_headers(buffer: NativeByteBuffer, headers: Headers): 43 | buffer.write_int(headers.version) 44 | buffer.write_bool(headers.test_backend) 45 | 46 | if headers.version >= 3: 47 | buffer.write_bool(headers.client_blocked) 48 | if headers.version >= 4: 49 | buffer.write_string(headers.last_init_system_langcode) 50 | buffer.write_bool(True) 51 | buffer.write_int(headers.current_dc_id) 52 | buffer.write_int(headers.time_difference) 53 | buffer.write_int(headers.last_dc_update_time) 54 | buffer.write_long(headers.push_session_id) 55 | 56 | if headers.version >= 2: 57 | buffer.write_bool(headers.registered_for_internal_push) 58 | if headers.version >= 5: 59 | buffer.write_int(headers.last_server_time) 60 | 61 | buffer.write_int(0) # writing sessions_to_destroy is not implemented 62 | 63 | @staticmethod 64 | def _write_datacenters(buffer: NativeByteBuffer, datacenters: list[Datacenter]): 65 | buffer.write_int(len(datacenters)) 66 | for datacenter in datacenters: 67 | buffer.write_int(datacenter.current_version) 68 | buffer.write_int(datacenter.dc_id) 69 | buffer.write_int(datacenter.last_init_version) 70 | 71 | if datacenter.current_version > 10: 72 | buffer.write_int(datacenter.last_init_media_version) 73 | 74 | # writing ips 75 | for address_group in datacenter.ips: 76 | buffer.write_int(len(datacenter.ips[address_group])) 77 | 78 | for ip in datacenter.ips[address_group]: 79 | buffer.write_string(ip.address) 80 | buffer.write_int(ip.port) 81 | 82 | if datacenter.current_version >= 7: 83 | buffer.write_int(ip.flags) 84 | 85 | if datacenter.current_version >= 11: 86 | buffer.write_string(ip.secret) 87 | elif datacenter.current_version >= 9: 88 | raise NotImplementedError( 89 | 'Writing sessions with Datacenter\'s versions 9 and 10 is not supported, please use another version') 90 | 91 | if datacenter.current_version >= 6: 92 | buffer.write_bool(datacenter.is_cdn_datacenter) 93 | 94 | # writing auth credentials 95 | if datacenter.auth.auth_key_perm: 96 | buffer.write_int(len(datacenter.auth.auth_key_perm), signed=False) 97 | buffer.write_bytes(datacenter.auth.auth_key_perm) 98 | else: 99 | buffer.write_int(0) 100 | 101 | if datacenter.current_version >= 4: 102 | buffer.write_long(datacenter.auth.auth_key_perm_id) 103 | else: 104 | raise NotImplementedError('Datacenters below version 4 are not supported') 105 | 106 | if datacenter.current_version >= 8: 107 | if datacenter.auth.auth_key_temp: 108 | buffer.write_int(len(datacenter.auth.auth_key_temp), signed=False) 109 | buffer.write_bytes(datacenter.auth.auth_key_temp) 110 | buffer.write_long(datacenter.auth.auth_key_temp_id) 111 | else: 112 | buffer.write_int(0) 113 | buffer.write_long(0) 114 | 115 | if datacenter.current_version >= 12: 116 | if datacenter.auth.auth_key_media_temp: 117 | buffer.write_int(len(datacenter.auth.auth_key_media_temp), signed=False) 118 | buffer.write_bytes(datacenter.auth.auth_key_media_temp) 119 | buffer.write_long(datacenter.auth.auth_key_media_temp_id) 120 | else: 121 | buffer.write_int(0) 122 | buffer.write_long(0) 123 | 124 | buffer.write_int(datacenter.auth.authorized) 125 | 126 | # writing salt info 127 | buffer.write_int(0) 128 | if datacenter.current_version >= 13: 129 | buffer.write_int(0) # writing salts in session is not implemented 130 | 131 | @staticmethod 132 | def _write_buffer_length(buffer: NativeByteBuffer): 133 | buffer_with_length = NativeByteBuffer() 134 | buffer_with_length.write_int(len(buffer)) 135 | buffer_with_length.write_bytes(buffer.get_value()) 136 | buffer.stream.seek(0) 137 | buffer.write_bytes(buffer_with_length.get_value()) 138 | 139 | @classmethod 140 | def _read_headers(cls, buffer: NativeByteBuffer) -> Headers: 141 | buffer.read_int() 142 | headers = Headers( 143 | version=buffer.read_int(), 144 | test_backend=buffer.read_bool() 145 | ) 146 | if headers.version >= 3: 147 | client_blocked = buffer.read_bool() 148 | headers.client_blocked = client_blocked 149 | if headers.version >= 4: 150 | last_init_system_langcode = buffer.read_string() 151 | headers.last_init_system_langcode = last_init_system_langcode 152 | 153 | if buffer.read_bool(): # will be False if session is empty 154 | headers.current_dc_id = buffer.read_int(signed=False) 155 | headers.time_difference = buffer.read_int() 156 | headers.last_dc_update_time = buffer.read_int() 157 | headers.push_session_id = buffer.read_long() 158 | 159 | if headers.version >= 2: 160 | headers.registered_for_internal_push = buffer.read_bool() 161 | if headers.version >= 5: 162 | headers.last_server_time = buffer.read_int() 163 | headers.current_time = int(time.time()) 164 | 165 | if headers.time_difference < headers.current_time < headers.last_server_time: 166 | headers.time_difference += (headers.last_server_time - headers.current_time) 167 | 168 | count = buffer.read_int(signed=False) 169 | for _ in range(count): 170 | headers.sessions_to_destroy.append(buffer.read_long()) 171 | return headers 172 | 173 | @classmethod 174 | def _get_ip(cls, buffer: NativeByteBuffer, current_version: int, ip_type: Literal[ 175 | 'addressesIpv4', 'addressesIpv6', 'addressesIpv4Download', 'addressesIpv6Download']) -> IP: 176 | ip = IP( 177 | type_=ip_type, 178 | address=buffer.read_string(), 179 | port=buffer.read_int() 180 | ) 181 | 182 | if current_version >= 7: 183 | flags = buffer.read_int() 184 | else: 185 | flags = 0 186 | ip.flags = flags 187 | 188 | if current_version >= 11: 189 | secret = buffer.read_string() 190 | ip.secret = secret 191 | 192 | elif current_version >= 9: 193 | secret = buffer.read_string() 194 | if secret: 195 | size = len(secret) // 2 196 | result = bytearray(size) 197 | for i in range(size): 198 | result[i] = int(secret[i * 2:i * 2 + 2], 16) 199 | secret = result.decode('utf-8') 200 | ip.secret = secret 201 | 202 | return ip 203 | 204 | @classmethod 205 | def _read_datacenters(cls, buffer: NativeByteBuffer) -> list[Datacenter]: 206 | datacenters = [] 207 | num_of_datacenters = buffer.read_int() 208 | 209 | for i in range(num_of_datacenters): 210 | datacenter = Datacenter( 211 | current_version=buffer.read_int(), 212 | dc_id=buffer.read_int(), 213 | last_init_version=buffer.read_int() 214 | ) 215 | 216 | if datacenter.current_version > 10: 217 | datacenter.last_init_media_version = buffer.read_int() 218 | 219 | count = 4 if datacenter.current_version >= 5 else 1 220 | 221 | for b in range(count): 222 | array = None 223 | if b == 0: 224 | array = 'addressesIpv4' 225 | elif b == 1: 226 | array = 'addressesIpv6' 227 | elif b == 2: 228 | array = 'addressesIpv4Download' 229 | elif b == 3: 230 | array = 'addressesIpv6Download' 231 | 232 | if array is None: 233 | continue 234 | 235 | ips_amount = buffer.read_int() 236 | for ip_index in range(ips_amount): 237 | ip = cls._get_ip(buffer, datacenter.current_version, array) 238 | datacenter.ips[array].append(ip) 239 | 240 | if datacenter.current_version >= 6: 241 | datacenter.is_cdn_datacenter = buffer.read_bool() 242 | 243 | auth_credentials = cls._auth_credentials(buffer, datacenter.current_version) 244 | datacenter.auth = auth_credentials 245 | 246 | datacenter.salt = cls._salt_info(buffer, datacenter.current_version) 247 | datacenters.append(datacenter) 248 | 249 | return datacenters 250 | 251 | @classmethod 252 | def _auth_credentials(cls, buffer: NativeByteBuffer, current_version: int) -> AuthCredentials: 253 | auth = AuthCredentials() 254 | len_of_bytes = buffer.read_int(signed=False) 255 | if len_of_bytes != 0: 256 | auth.auth_key_perm = buffer.read_bytes(len_of_bytes) 257 | 258 | if current_version >= 4: 259 | auth.auth_key_perm_id = buffer.read_long() 260 | 261 | else: 262 | len_of_bytes = buffer.read_int(signed=False) 263 | if len_of_bytes != 0: 264 | auth.auth_key_perm_id = buffer.read_long() 265 | 266 | if current_version >= 8: 267 | len_of_bytes = buffer.read_int(signed=False) 268 | if len_of_bytes != 0: 269 | auth.auth_key_temp = buffer.read_bytes(len_of_bytes) 270 | auth.auth_key_temp_id = buffer.read_long() 271 | 272 | if current_version >= 12: 273 | len_of_bytes = buffer.read_int(signed=False) 274 | if len_of_bytes != 0: 275 | auth.auth_key_media_temp = buffer.read_bytes(len_of_bytes) 276 | auth.auth_key_media_temp_id = buffer.read_long() 277 | auth.authorized = buffer.read_int() 278 | return auth 279 | 280 | @classmethod 281 | def _salt_info(cls, buffer: NativeByteBuffer, current_version: int) -> list[Salt]: 282 | salts = [] 283 | bytes_len = buffer.read_int() 284 | for x in range(bytes_len): 285 | salt = Salt() 286 | salt.salt_valid_since = buffer.read_int() 287 | salt.salt_valid_until = buffer.read_int() 288 | salt.salt = buffer.read_long() 289 | salts.append(salt) 290 | 291 | if current_version >= 13: 292 | bytes_len = buffer.read_int() 293 | for x in range(bytes_len): 294 | salt = Salt() 295 | salt.salt_valid_since = buffer.read_int() 296 | salt.salt_valid_until = buffer.read_int() 297 | salt.salt = buffer.read_long() 298 | salts.append(salt) 299 | 300 | return salts 301 | -------------------------------------------------------------------------------- /AndroidTelePorter/androidsession.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | import time 4 | 5 | from opentele.api import API 6 | from opentele.td import TDesktop, Account, AuthKeyType 7 | from opentele.td import AuthKey as AuthKeyOpentele 8 | from opentele.td.configs import DcId 9 | from pyrogram.storage.sqlite_storage import SCHEMA 10 | from telethon.crypto import AuthKey as AuthKeyTelethon 11 | from telethon.sessions import SQLiteSession 12 | 13 | from AndroidTelePorter.managers import TgnetManager, UserConfigManager 14 | from AndroidTelePorter.constants.datacenters import DATACENTERS 15 | from AndroidTelePorter.models.auth import AuthCredentials 16 | from AndroidTelePorter.models.datacenter import Datacenter 17 | from AndroidTelePorter.models.headers import Headers 18 | from AndroidTelePorter.models.tgnet_session import TgnetSession 19 | from telethon.tl.types import UserEmpty, User 20 | from AndroidTelePorter.utils.filesmanager import read_tgnet, read_userconfig, write_tgnet, write_userconfig 21 | from AndroidTelePorter.utils.auth_key import calculate_id 22 | 23 | 24 | class AndroidSession: 25 | def __init__(self, tgnet_manager: TgnetManager, userconfig_manager: UserConfigManager): 26 | self._tgnet_manager = tgnet_manager 27 | self._userconfig_manager = userconfig_manager 28 | 29 | @classmethod 30 | def from_tgnet(cls, tgnet_path: str, userconfig_path: str) -> 'AndroidSession': 31 | """Read tgnet.dat and userconfing.xml file and return instance of current class 32 | that in future can be used for conversion between other formats 33 | 34 | Args: 35 | tgnet_path: path to tgnet.dat file 36 | userconfig_path: path to userconfing.xml file 37 | """ 38 | return cls( 39 | tgnet_manager=read_tgnet(path=tgnet_path), 40 | userconfig_manager=read_userconfig(path=userconfig_path) 41 | ) 42 | 43 | @classmethod 44 | def from_manual(cls, auth_key: bytes, dc_id: int, user_id: int, 45 | config_version: int = 5, 46 | test_backend: bool = False, 47 | client_blocked: bool = False, 48 | last_init_system_langcode: str = 'en-us', 49 | time_difference: int = 0, 50 | last_dc_update_time: int = 0, 51 | push_session_id: int = 0, 52 | registered_for_internal_push: bool = True, 53 | last_server_time: int = 0, 54 | current_time: int = int(time.time()), 55 | sessions_to_destroy: list[int] | None = None, 56 | 57 | current_dc_version: int = 13, 58 | last_dc_init_version: int = 48502, 59 | last_dc_media_init_version: int = 48502, 60 | is_cdn_datacenter: bool = False, 61 | dc_salt: list[int] | None = None, 62 | auth_key_temp: bytes = 0, 63 | auth_key_media_temp: bytes = 0, 64 | authorized: int = 1 65 | ) -> 'AndroidSession': 66 | """Create instance of current object with all needed fields 67 | In future can be used to convert to any possible format. 68 | 69 | Args: 70 | auth_key: 256 bytes auth key used to log into account 71 | dc_id: datacenter id of current account 72 | user_id: user id of current account 73 | config_version: version of tgnet.dat file (default 5) 74 | test_backend: whether this session is used to test backend (default False) 75 | client_blocked: whether current client is blocked (default False) 76 | last_init_system_langcode: last lang code on client's system (default 'en-us') 77 | time_difference: time difference between client and server (default 0) 78 | last_dc_update_time: last time when datacenter was updated (default 0) 79 | push_session_id: push session id (default 0) 80 | registered_for_internal_push: whether client is registered for internal push (default True) 81 | last_server_time: last server time (default 0) 82 | current_time: current unix timestamp (default time.time()) 83 | sessions_to_destroy: sessions to destroy (default []) 84 | current_dc_version: current dc version (default 13) 85 | last_dc_init_version: last dc init version (default 48502) 86 | last_dc_media_init_version: last dc media init version (default 48502) 87 | is_cdn_datacenter: whether current dc is a cdn datacenter (default False) 88 | dc_salt: dc salts, used somewhere deeply by MTProto 89 | you can read more detailed here https://core.telegram.org/schema/mtproto (default []) 90 | auth_key_temp: temporary auth key, used by telegram client while generating permanent auth key (default None) 91 | auth_key_media_temp: have no idea what is this for (default None) 92 | authorized: integer 1 or 0, does not really affect anything in the session (default 1) 93 | """ 94 | if not auth_key: 95 | raise ValueError('auth_key must be passed') 96 | if not dc_id: 97 | raise ValueError('dc_id must be passed') 98 | if not user_id: 99 | raise ValueError('user_id must be passed') 100 | 101 | if sessions_to_destroy is None: 102 | sessions_to_destroy = [] 103 | if dc_salt is None: 104 | dc_salt = [] 105 | 106 | tgnet_manager = TgnetManager( 107 | session=TgnetSession( 108 | headers=Headers( 109 | version=config_version, 110 | test_backend=test_backend, 111 | client_blocked=client_blocked, 112 | last_init_system_langcode=last_init_system_langcode, 113 | current_dc_id=dc_id, 114 | time_difference=time_difference, 115 | last_dc_update_time=last_dc_update_time, 116 | push_session_id=push_session_id, 117 | registered_for_internal_push=registered_for_internal_push, 118 | last_server_time=last_server_time, 119 | current_time=current_time, 120 | sessions_to_destroy=sessions_to_destroy 121 | ), 122 | datacenters=[ 123 | Datacenter( 124 | current_version=current_dc_version, 125 | dc_id=dc_id, 126 | last_init_version=last_dc_init_version, 127 | last_init_media_version=last_dc_media_init_version, 128 | is_cdn_datacenter=is_cdn_datacenter, 129 | auth=AuthCredentials( 130 | auth_key_perm=auth_key, 131 | auth_key_perm_id=calculate_id(auth_key), 132 | auth_key_temp=auth_key_temp, 133 | auth_key_temp_id=calculate_id(auth_key_temp) if auth_key_temp else 0, 134 | auth_key_media_temp=auth_key_media_temp, 135 | auth_key_media_temp_id=calculate_id(auth_key_media_temp) if auth_key_media_temp else 0, 136 | authorized=authorized 137 | ), 138 | ips=DATACENTERS[dc_id], 139 | salt=dc_salt, 140 | ) 141 | ] 142 | ) 143 | ) 144 | userconfig = UserConfigManager(UserEmpty(id=user_id)) 145 | return cls(tgnet_manager=tgnet_manager, userconfig_manager=userconfig) 146 | 147 | def to_telethon(self, filename: str, force: bool = True) -> None: 148 | """Create telethon .session file and save it. 149 | 150 | Args: 151 | filename: Filename with full path where .session file will be saved 152 | force: if True, will overwrite existing .session file, otherwise will raise an error 153 | """ 154 | if not filename.endswith('.session'): 155 | raise ValueError('filename must end with .session') 156 | if not force and os.path.exists(filename): 157 | raise FileExistsError(f"{filename} already exists") 158 | if not force and not os.path.exists(os.path.dirname(filename)): 159 | raise FileExistsError(f"The folder where you are trying to write .session file does not exist, you can set force=True to avoid this error") 160 | os.makedirs(os.path.dirname(filename), exist_ok=True) 161 | 162 | sqlite_session = SQLiteSession(filename) 163 | 164 | sqlite_session.auth_key = AuthKeyTelethon(data=self._tgnet_manager.session.auth_key) 165 | sqlite_session.set_dc( 166 | dc_id=self._tgnet_manager.session.current_dc.dc_id, 167 | server_address=self._tgnet_manager.session.current_dc.ips['addressesIpv4'][0].address, 168 | port=self._tgnet_manager.session.current_dc.ips['addressesIpv4'][0].port 169 | ) 170 | 171 | # telethon always adds first entity with user_id=0 and access_hash=user_id for some reason 172 | user_id_entity = User( 173 | id=0, 174 | access_hash=self._userconfig_manager.userconfig.id, 175 | username=self._userconfig_manager.userconfig.username if hasattr(self._userconfig_manager.userconfig, 176 | 'username') else None, 177 | phone=self._userconfig_manager.userconfig.phone if hasattr(self._userconfig_manager.userconfig, 178 | 'phone') else None, 179 | first_name=self._userconfig_manager.userconfig.first_name if hasattr(self._userconfig_manager.userconfig, 180 | 'first_name') else None, 181 | ) 182 | sqlite_session.process_entities([user_id_entity, self._userconfig_manager.userconfig]) 183 | sqlite_session.save() 184 | 185 | def to_tdata(self, path: str, force: bool = True) -> None: 186 | """Create tdata session. 187 | 188 | Args: 189 | path: Path to folder where /tdata folder will be created and saved 190 | force: if True, will overwrite existing /tdata folder, otherwise will raise an error 191 | """ 192 | tdata_path = os.path.join(path, 'tdata') 193 | if not force and os.path.exists(tdata_path): 194 | raise FileExistsError(f"{tdata_path} already exist") 195 | if not force and not os.path.exists(path): 196 | raise FileExistsError(f"The folder {path} does not exist") 197 | os.makedirs(os.path.dirname(tdata_path), exist_ok=True) 198 | 199 | if self._tgnet_manager.session.headers.last_init_system_langcode: 200 | system_lang_code = self._tgnet_manager.session.headers.last_init_system_langcode 201 | else: 202 | system_lang_code = 'en-us' 203 | 204 | if self._tgnet_manager.session.headers.last_init_system_langcode: 205 | lang_code = self._tgnet_manager.session.headers.last_init_system_langcode.split('-')[0] 206 | else: 207 | lang_code = 'en' 208 | 209 | api = API.TelegramAndroid( 210 | app_version='10.13.2 (4850)', 211 | lang_code=lang_code, 212 | system_lang_code=system_lang_code, 213 | lang_pack='android' 214 | ) 215 | client = TDesktop() 216 | client._TDesktop__generateLocalKey() 217 | account = Account(owner=client, api=api) 218 | dc_id = DcId(self._tgnet_manager.session.dc_id) 219 | auth_key = AuthKeyOpentele(self._tgnet_manager.session.auth_key, AuthKeyType.ReadFromFile, dc_id) 220 | account._setMtpAuthorizationCustom(dc_id, self._userconfig_manager.userconfig.id, [auth_key]) 221 | client._addSingleAccount(account) 222 | client.SaveTData(tdata_path) 223 | 224 | def to_pyrogram(self, filename: str, force: bool = True) -> None: 225 | """Create pyrogram .session file and save it. 226 | 227 | Args: 228 | filename: Filename with full path where .session file will be saved 229 | force: if True, will overwrite existing .session file, otherwise will raise an error 230 | """ 231 | 232 | if not filename.endswith('.session'): 233 | raise ValueError('filename must end with .session') 234 | if os.path.exists(filename): 235 | if not force: 236 | raise FileExistsError(f"{filename} already exists") 237 | else: 238 | os.remove(filename) 239 | if not force and os.path.exists(os.path.dirname(filename)): 240 | raise FileExistsError(f"The folder where you are trying to write .session file does not exist, you can set force=True to avoid this error") 241 | os.makedirs(os.path.dirname(filename), exist_ok=True) 242 | 243 | with sqlite3.connect(filename) as db: 244 | db.executescript(SCHEMA) 245 | db.commit() 246 | db.execute("INSERT INTO version VALUES (?)", (3,)) 247 | params = ( 248 | self._tgnet_manager.session.dc_id, # dc id 249 | 6, # api id 250 | self._tgnet_manager.session.headers.test_backend, # test mode 251 | self._tgnet_manager.session.auth_key, # auth key 252 | 0, # timestamp 253 | self._userconfig_manager.userconfig.id or 9999, # user id 254 | self._userconfig_manager.userconfig.bot if hasattr(self._userconfig_manager.userconfig, 'bot') else False # is bot 255 | ) 256 | db.execute("INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?, ?)", params) 257 | db.commit() 258 | 259 | def to_tgnet(self, path: str, force: bool = True) -> None: 260 | """Create mobile telegram tgnet session for original telegram client (not Telegram X). 261 | 262 | Args: 263 | path: Path to folder where /files and /shared_prefs folders will be created 264 | force: if True, will overwrite existing content in folders, otherwise will raise an error 265 | """ 266 | 267 | files_path = os.path.join(path, 'files') 268 | shared_prefs_path = os.path.join(path, 'shared_prefs') 269 | 270 | if not force and os.path.exists(path): 271 | raise FileExistsError(f"{path} already exist") 272 | 273 | write_tgnet(self._tgnet_manager, files_path) 274 | write_userconfig(self._userconfig_manager, shared_prefs_path) 275 | --------------------------------------------------------------------------------