Consistency and sessions
Validation checks the values a session is about to send. The consistency checks ask the server how it sees a live session: whether the requests an official client makes succeed, whether the session counts as an official app, and whether the layer and the lang pack fit. The same client then lists the account’s sessions, prints them as a table and ends the ones you no longer want.
Every step here talks to Telegram as the logged-in account. Run it on accounts you own, and double-check a hash before terminating: an ended session cannot be restored, and the device using it is logged out.
Connect with the session’s profile
The checks compare the session with the profile it is used with, so connect exactly as the session
normally runs: the opentele2 TelegramClient with its api= in Python
(tdata to Telethon), an mtcute client with initConnectionOptions in
Node (tdata to mtcute), a grammers client with connection_params in
Rust (tdata to grammers, feature client).
Run the seven checks
They run in a fixed order and never stop at a failure:
| Check | Passes when |
|---|---|
get_config | help.getConfig succeeds |
nearest_dc | help.getNearestDc succeeds |
current_session | the server marks the current session official_app |
layer_match | the library’s layer is within 15 of the advertised 230 |
lang_pack | langpack.getLanguages accepts the session’s lang_pack; an empty pack passes without a request |
app_update | help.getAppUpdate succeeds |
terms_of_service | help. succeeds |
import asyncio
from opentele2.api import API
from opentele2.tl import TelegramClient
async def main():
client = TelegramClient("telethon.session", api=API.TelegramDesktop)
await client.connect()
report = await client.RunConsistencyChecks() # auto_warn=True warns when a check fails
print(report.all_passed)
print(report.summary)
for check in report.checks:
print(check.name, check.passed, check.detail)
await client.disconnect()
asyncio.run(main())import { consistencySummary, profile } from 'opentele2'
import { runConsistencyChecks } from 'opentele2/mtcute'
// client: a connected mtcute TelegramClient that uses the Telegram Desktop profile
const checks = await runConsistencyChecks(client, profile('TelegramDesktop'), null, { autoWarn: true })
console.log(checks.every((check) => check.passed))
console.log(consistencySummary(checks))The fourth argument takes layer, the library layer layer_match compares (mtcute’s tl.LAYER
by default), and autoWarn.
// cargo add opentele2-grammers --features client
use grammers_client::Client;
use opentele2_core::api::{self, Client as ApiClient, Versions};
use opentele2_core::auth::ConsistencyExpectations;
use opentele2_grammers::client;
async fn checks(grammers: &Client) {
let versions = Versions::offline();
let desktop = api::profile(ApiClient::Desktop, &versions);
let expectations = ConsistencyExpectations::new(&desktop, &versions);
let report = client::run_consistency_checks(grammers, &expectations).await;
println!("{}", report.all_passed());
println!("{}", report.summary());
if let Some(warning) = report.warning() {
eprintln!("{warning}"); // the crate emits no warnings itself
}
}Read the report
The summary is the same text in every package: a count line, then one line per check.
Consistency: 7/7 checks passed
[OK] get_config: ...
[OK] nearest_dc: ...
[OK] current_session: ...current_session fails for any session whose api_id is not official: the server reports such
sessions without official_app. With grammers, lang_pack asks about the profile’s pack while
grammers 0.11 sends an empty one. A failing check is a signal to look at the profile, not an
error of the library.
List the sessions
account.getAuthorizations returns every session of the account; the current one is flagged
current.
# client: a connected opentele2 TelegramClient, inside a coroutine
sessions = await client.GetSessions()
for auth in sessions.authorizations:
print(auth.hash, auth.current, auth.device_model, auth.app_name, auth.official_app)
current = await client.GetCurrentSession()
print(await client.is_official_app())import { getCurrentSession, getSessions, isOfficialApp } from 'opentele2/mtcute'
// client: a connected mtcute TelegramClient
const { authorizations } = await getSessions(client)
for (const auth of authorizations) {
console.log(auth.hash, auth.current, auth.deviceModel, auth.appName, auth.officialApp)
}
const current = await getCurrentSession(client) // null when none is flagged current
console.log(await isOfficialApp(client))getSessions rejects with OT_SESSIONS_UNAVAILABLE when the answer holds no list.
use grammers_client::Client;
use opentele2_grammers::client::{self, ClientError};
async fn list(grammers: &Client) -> Result<(), ClientError> {
for auth in client::sessions(grammers).await? {
println!("{} {} {} {}", auth.hash, auth.current, auth.device_model, auth.app_name);
}
let _current = client::current_session(grammers).await?;
println!("{}", client::is_official_app(grammers).await?);
Ok(())
}Print the table
await client.PrintSessions() # fetches the sessions, or pass the GetSessions() resultimport { formatSessions } from 'opentele2/mtcute'
console.log(await formatSessions(client)) // a client, an account.authorizations answer or a listuse grammers_client::Client;
use opentele2_grammers::client::{self, ClientError};
async fn print(grammers: &Client) -> Result<(), ClientError> {
println!("{}", client::sessions_table(grammers).await?);
Ok(())
}|---------+---------+----------+----------------+--------+----------------------------+--------------|
| | Device | Platform | System | API_ID | App name | Official App |
|---------+---------+----------+----------------+--------+----------------------------+--------------|
| Current | Desktop | Windows | Windows 11 x64 | 2040 | Telegram Desktop 7.3.1 x64 | ✔ |
|---------+---------+----------+----------------+--------+----------------------------+--------------|Terminate sessions
End one session by its hash, or every session except the current one. The examples end every
unofficial session first, then show the call that ends all the others.
# client: a connected opentele2 TelegramClient, inside a coroutine
sessions = await client.GetSessions()
old = [auth for auth in sessions.authorizations if not auth.current and not auth.official_app]
for auth in old:
await client.TerminateSession(auth.hash)
# Or end every session but the current one:
# await client.TerminateAllSessions()TerminateSession raises Telethon’s FreshResetAuthorisationForbiddenError or HashInvalidError
with opentele2’s message.
import { getSessions, terminateAllSessions, terminateSession } from 'opentele2/mtcute'
const { authorizations } = await getSessions(client)
for (const auth of authorizations.filter((a) => !a.current && !a.officialApp)) {
await terminateSession(client, auth.hash)
}
// Or end every session but the current one:
// console.log(await terminateAllSessions(client)) // how many were endedterminateSession rejects with OT_FRESH_RESET_FORBIDDEN or OT_HASH_INVALID, the RPC error as
cause.
use grammers_client::Client;
use opentele2_grammers::client::{self, ClientError};
async fn end_old(grammers: &Client) -> Result<(), ClientError> {
for auth in client::sessions(grammers).await? {
if !auth.current && !auth.official_app {
client::terminate_session(grammers, auth.hash).await?;
}
}
Ok(())
}
async fn end_all_others(grammers: &Client) -> Result<usize, ClientError> {
client::terminate_all_sessions(grammers).await // how many were ended
}ClientError::FreshResetAuthorisationForbidden and ClientError::HashInvalid report the two
refusals; in terminate_all_sessions the first failure stops the loop.
A session that logged in less than 24 hours ago cannot end other sessions: the server answers
FRESH_RESET_AUTHORISATION_FORBIDDEN. A session just created by QR login has to wait before it
cleans up.
Next
- initConnection fingerprints: what each check looks at.
- Client session to tdata: the QR login that adds a session.
- Reference: Python · Node · Rust.