Quick start
Point opentele2 at a Telegram Desktop tdata folder and use its account from your client
library: Telethon in Python, mtcute in Node, grammers in Rust.
Work on a copy of the folder, and never share the folder or the sessions made from it: each one is a logged-in account.
Install
pip install opentele2Python 3.8+ · abi3 wheels · works over Telethon
npm install opentele2Node 18+ · subpaths opentele2/mtcute and opentele2/telethon
cargo add opentele2-coreAlso opentele2-browser, opentele2-versions and opentele2-grammers
Find the folder
Copy the tdata folder of a logged-in Telegram Desktop. The files opentele2 reads are the key
file and, per account, its MTP data file and map:
- key_datas
- D877F783D5D3EF8Cs
If Telegram Desktop has a local passcode, pass it when loading. See Telegram Desktop tdata for the layout.
Load it and get a session
import asyncio
from opentele2.api import UseCurrentSession
from opentele2.td import TDesktop
async def main():
tdesk = TDesktop("tdata") # TDesktop("tdata", passcode="...") for a locked folder
client = await tdesk.ToTelethon("telethon.session", UseCurrentSession)
await client.connect()
print(await client.get_me())
asyncio.run(main())ToTelethon returns an opentele2 TelegramClient, a telethon.TelegramClient subclass that
sends the official Telegram Desktop profile in initConnection. UseCurrentSession reuses the
folder’s own auth key and writes it into telethon.session; nothing is sent until connect().
import { TelegramClient } from '@mtcute/node'
import { loadTdata, profile } from 'opentele2'
import { createCryptoProvider, initConnectionOptions, toStringSession } from 'opentele2/mtcute'
import { saveSessionPair } from 'opentele2/telethon'
const tdata = await loadTdata('tdata') // loadTdata('tdata', passcode) for a locked folder
const desktop = profile('TelegramDesktop')
const client = new TelegramClient({
apiId: desktop.apiId,
apiHash: desktop.apiHash,
initConnectionOptions: initConnectionOptions(desktop),
crypto: await createCryptoProvider(), // every ICryptoProvider member on the core
})
await client.importSession(toStringSession(tdata.mainAccount))
// Telethon: a .session file and its .json companion (the core's bundled SQLite).
const { session, json } = await saveSessionPair('telethon', tdata.mainAccount, desktop)toStringSession turns the main account into an mtcute v3 string session, and
initConnectionOptions makes mtcute announce the Telegram Desktop profile, tz_offset included.
The same account also becomes a Telethon .session + .json pair.
use opentele2_core::api::{self, Versions};
use opentele2_core::session::{mtcute, telethon};
use opentele2_core::tdata::TData;
fn main() -> opentele2_core::Result<()> {
let tdata = TData::load("tdata", b"", None)?; // passcode bytes, key file name
let account = tdata.main_account().expect("folder has an account");
println!("{}", telethon::StringSession::from_account(account, false)?.encode());
println!("{}", mtcute::StringSession::from_account(account, false)?.encode());
// grammers 0.11 session data and initConnection params.
let desktop = api::profile(api::Client::Desktop, &Versions::offline());
let _session = opentele2_grammers::session_data(account);
let _params = opentele2_grammers::connection_params(&desktop);
Ok(())
}session_data gives a grammers_session::SessionData for grammers’ MemorySession::from, and
connection_params copies the profile’s device, system, app and language fields into grammers’
ConnectionParams. Pass desktop.api_id to grammers separately.
Save a session to disk
import asyncio
from opentele2.td import TDesktop
async def main():
tdesk = TDesktop("tdata")
session_file, json_file = await tdesk.SaveSessionJson("telethon.session")
print(session_file, json_file)
asyncio.run(main())SaveSessionJson writes a Telethon .session file and its .json companion holding the API
profile, so the pair can be loaded later with TelegramClient.FromSessionJson.
import { loadTdata, profile } from 'opentele2'
import { loadSessionPair, saveSessionPair } from 'opentele2/telethon'
const tdata = await loadTdata('tdata')
const paths = await saveSessionPair('telethon', tdata.mainAccount, profile('TelegramDesktop'))
const pair = await loadSessionPair('telethon')
console.log(paths.session, pair.rows.dcId, pair.profile?.deviceModel)// cargo add opentele2-core --features telethon-sqlite
use opentele2_core::api::{self, Versions};
use opentele2_core::session::telethon_file::{self, SessionRows};
use opentele2_core::tdata::TData;
fn main() -> opentele2_core::Result<()> {
let tdata = TData::load("tdata", b"", None)?;
let account = tdata.main_account().expect("folder has an account");
let rows = SessionRows::from_account(account, false)?;
telethon_file::write_session_file("telethon.session", &rows)?;
let desktop = api::profile(api::Client::Desktop, &Versions::offline());
std::fs::write("telethon.json", telethon_file::companion_text(&desktop, None))?;
Ok(())
}Go back to tdata
import asyncio
from opentele2.api import API, UseCurrentSession
from opentele2.td import TDesktop
async def main():
tdesk = TDesktop("tdata")
client = await tdesk.ToTelethon("telethon.session", UseCurrentSession)
# A new session, logged in by QR code from the folder's session, on a generated device.
tdesk2 = await client.ToTDesktop(api=API.TelegramDesktop.Generate("windows", "my-id"))
tdesk2.SaveTData("tdata_new")
asyncio.run(main())Without a flag, ToTDesktop (like ToTelethon) uses CreateNewSession: it logs a new session in
by QR code through the existing one, which needs the network and, with 2FA on, password=.
ToTDesktop(flag=UseCurrentSession) stores the client’s own session instead, offline.
import { newTdata, saveTdata } from 'opentele2'
import { loadSessionPair } from 'opentele2/telethon'
const { rows, json } = await loadSessionPair('telethon')
const tdata = await newTdata([
{ index: 0, userId: BigInt(json.id), mainDc: rows.dcId, authKey: rows.authKey },
])
await saveTdata(tdata, 'tdata_new')Over a live mtcute client, tdataFromClient(client) from opentele2/mtcute builds the folder from
the client’s own session.
use grammers_session::SessionData;
use opentele2_core::tdata::{DrawSource, NEW_FOLDER_ACTIVE_INDEX, TData};
fn to_tdata(data: &SessionData) -> opentele2_core::Result<()> {
let account = opentele2_grammers::account_from_session(data, 0)?;
// No passcode, performance mode on, keys from the OS random source.
let tdata =
TData::new_with_draws(vec![account], b"", true, NEW_FOLDER_ACTIVE_INDEX, DrawSource::os())?;
tdata.save_random("tdata_new")
}With the client feature, client::tdata_from_client does the same from a live grammers client.
Next
- Sessions: every format and the round trips between them.
- API profiles and devices: the profile your client announces.
- Crypto core: what runs on the Rust core in each client.