Telethon .session + .json
Many tools store a Telethon account as two files with one base name: a Telethon SQLiteSession
file and a .json companion with the API profile and account fields. opentele2 writes both byte
for byte the same in every ecosystem, over the SQLite bundled in the core, and reads pairs that
Telethon, the Python package or another tool wrote.
- account.session
- account.json
The .session file holds the auth key: it is the logged-in account. Copy pairs only between
machines you control, never commit them, and never paste the file or its key into a chat or an
issue. The .json holds the phone number and names when they were saved.
Name the pair
A base path gives both file names: one trailing .session is removed, then one trailing .json,
and each suffix is added back. account, account.session and account.json all name the same
pair.
from opentele2 import _native
print(_native.session_pair_paths("account.session")) # ('account.session', 'account.json')import { sessionPairPaths } from 'opentele2'
console.log(sessionPairPaths('account.session')) // { session: 'account.session', json: 'account.json' }use opentele2_core::session::telethon_file;
fn main() {
let (session, json) = telethon_file::pair_paths("account.session");
println!("{} {}", session.display(), json.display()); // account.session account.json
}Write the pair
The .session file is a Telethon database of version 8, the layout Telethon 1.45 reads, with one
sessions row: DC id, server address, port and auth key. The companion is written as Python’s
json.dump writes it, with two-space indentation.
import asyncio
from opentele2.td import TDesktop
async def from_tdata():
# The folder's own session and profile.
return await TDesktop("tdata").SaveSessionJson("account.session")
async def from_client(client):
# client: a connected opentele2 TelegramClient; fills in the account fields.
return await client.SaveSessionJson("account.session", fetch_user_info=True)
print(asyncio.run(from_tdata())) # ('account.session', 'account.json')SaveSessionJson(path, api=None, fetch_user_info=False) returns the two paths. With
fetch_user_info=True on a connected client it fills id, phone, username, first_name,
last_name and is_premium from get_me(); id is written whenever the client knows its user.
import { loadTdata, profile } from 'opentele2'
import { saveSessionPair } from 'opentele2/telethon'
const { mainAccount } = await loadTdata('tdata')
const paths = await saveSessionPair('account', mainAccount, profile('TelegramDesktop'), {
extra: { phone: '+10000000000', username: 'example' },
})
console.log(paths.session, paths.json)extra adds or overrides companion keys after session_file; id defaults to the account’s user
id. writeSessionFile(path, rows) writes the .session file alone, sessionCompanionText(profile, extra) gives the companion text.
// cargo add opentele2-core --features telethon-sqlite
// cargo add serde_json
use opentele2_core::api::{self, Client, 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 (session_path, json_path) = telethon_file::pair_paths("account");
telethon_file::write_session_file(&session_path, &SessionRows::from_account(account, false)?)?;
let mut extra = serde_json::Map::new();
extra.insert("session_file".into(), "account".into());
extra.insert("id".into(), account.user_id.into());
let desktop = api::profile(Client::Desktop, &Versions::offline());
std::fs::write(json_path, telethon_file::companion_text(&desktop, Some(&extra)))?;
Ok(())
}write_session_file and read_session_file need the telethon-sqlite feature; pair_paths,
companion_json, companion_text and read_companion do not.
Know the companion’s keys
The companion starts with the nine profile keys, in this order. They become the API profile when the pair is loaded, so a session keeps announcing the device it was saved with.
| Key | Profile field | When absent |
|---|---|---|
app_id | api_id | required |
app_hash | api_hash | required |
device | device_model | — |
sdk | system_version | — |
app_version | app_version | — |
system_lang_pack | system_lang_code | en |
system_lang_code | system_lang_code | read from system_lang_pack |
lang_pack | lang_pack | empty |
lang_code | lang_code | en |
The account keys follow with their defaults: twoFA (null), role (""), id, phone,
username, date_of_birth, date_of_birth_integrity (all null), is_premium and
has_profile_pic (false), spamblock, register_time, last_check_time, avatar (null),
first_name and last_name (""), sex and proxy (null), ipv6 (false) and
session_file, the base name of the pair. Keys you add go after them.
{
"app_id": 2040,
"app_hash": "b18441a1ff607e10a989891a5462e627",
"device": "Desktop",
"sdk": "Windows 11 x64",
"app_version": "7.3.1 x64",
"system_lang_pack": "en-US",
"system_lang_code": "en-US",
"lang_pack": "tdesktop",
"lang_code": "en",
"twoFA": null,
"role": "",
"id": null,
"phone": null,
"username": null,
"date_of_birth": null,
"date_of_birth_integrity": null,
"is_premium": false,
"has_profile_pic": false,
"spamblock": null,
"register_time": null,
"last_check_time": null,
"avatar": null,
"first_name": "",
"last_name": "",
"sex": null,
"proxy": null,
"ipv6": false,
"session_file": "account"
}from opentele2.api import API
print(API.TelegramDesktop().to_json({"session_file": "account"})) # the dict aboveAPIData.to_json(extra) and APIData.from_json(data) are the profile half of the companion.
import { defaultExtraFields, profile, sessionCompanionText, sessionJsonFields } from 'opentele2'
console.log(sessionJsonFields().map((field) => field.key)) // the nine profile keys
console.log(defaultExtraFields()) // [['twoFA', null], ['role', ''], ...]
console.log(sessionCompanionText(profile('TelegramDesktop'), { session_file: 'account' }))use opentele2_core::api::{self, Client, Versions};
use opentele2_core::session::telethon_file;
fn main() {
let desktop = api::profile(Client::Desktop, &Versions::offline());
println!("{}", telethon_file::companion_text(&desktop, None));
}Read the pair back
import asyncio
from opentele2.tl import TelegramClient
async def main():
# The profile comes from the .json; the session from the .session file.
client = await TelegramClient.FromSessionJson("account.session")
await client.connect()
print(await client.get_me())
asyncio.run(main())FromSessionJson(session_path, json_path=None, flag=UseCurrentSession, password=None) reuses the
stored session. With CreateNewSession it logs a new session in by QR code through the stored one,
on the stored profile; pass the 2FA password as password=.
import { loadSessionPair } from 'opentele2/telethon'
const { paths, rows, profile, json } = await loadSessionPair('account')
console.log(rows.dcId, rows.serverAddress, rows.port, profile?.deviceModel, json.id)profile is null when the companion’s profile keys are not in strict form; json still holds
every key as parsed.
// cargo add opentele2-core --features telethon-sqlite
use opentele2_core::session::telethon_file;
fn main() -> opentele2_core::Result<()> {
let (session_path, json_path) = telethon_file::pair_paths("account");
let rows = telethon_file::read_session_file(&session_path)?;
let text = std::fs::read_to_string(json_path)?;
let (profile, extra) = telethon_file::read_companion(&text)?;
println!("{} {} {}", rows.dc_id, profile.device_model, extra["session_file"]);
Ok(())
}Turn the pair into tdata
import asyncio
from opentele2.td import TDesktop
async def main():
tdesk = await TDesktop.FromSessionJson("account.session") # the pair's own key, offline
tdesk.SaveTData("tdata_from_pair")
asyncio.run(main())import { newTdata, saveTdata } from 'opentele2'
import { loadSessionPair } from 'opentele2/telethon'
const { rows, json } = await loadSessionPair('account')
const tdata = await newTdata([
{ index: 0, userId: BigInt(json.id), mainDc: rows.dcId, authKey: rows.authKey },
])
await saveTdata(tdata, 'tdata_from_pair')A tdata account needs the user id: save the pair with id set, as both writers do when they know it.
The client to tdata guide builds a folder from an account; from a pair,
build it with Account::new(0, user_id, rows.dc_id, vec![(rows.dc_id, rows.auth_key)]) and
TData::new_with_draws.
Next
- Sessions: the pair next to the other formats.
- Client session to tdata: a new session by QR login, saved as tdata.
- Reference: Python · Node · Rust.