initConnection fingerprints
The first request of every Telegram connection is invokeWithLayer(initConnection(...)). What a
client puts there, together with the transport it speaks and the way it numbers its messages, is
its fingerprint on the wire. opentele2 fills those values from an API profile,
validates them, and checks a live session against them.
The parameters
initConnection field | From the profile | Notes |
|---|---|---|
api_id | api_id | must be the official id of the lang_pack in strict mode |
device_model, system_version | the device | never empty |
app_version | the app version | never empty; its major must match the known version in strict mode |
lang_code | lang_code | at least two characters |
system_lang_code | system_lang_code | needs a region (en-US) for Android and iOS |
lang_pack | lang_pack | must be an official pack |
params | — | Telegram Desktop sends {"tz_ |
The layer opentele2 advertises is 230, the schema of Telegram Desktop 7.3.1 (LAYER in all
three packages).
Per client library. The opentele2 Telethon TelegramClient sends every field, lang_pack
included (Telethon itself leaves it empty), and params with tz_offset for the tdesktop pack.
initConnectionOptions gives mtcute the same fields and params. grammers 0.11 builds
initConnection itself and always sends an empty lang_pack and no params, so
connection_params can set every field but those two.
Validation
from opentele2.fingerprint import validate_init_connection_params
issues = validate_init_connection_params(
1, "Desktop", "Windows 11 x64", "7.3.1 x64", "en-US", "tdesktop", "en", strict=True
)
print(issues) # ["api_id 1 does not match lang_pack 'tdesktop' (expected 2040)"]The opentele2 TelegramClient validates its own parameters on construction with its
fingerprint_config (default DEFAULT_CONFIG, warn mode). In warn mode a bad profile shows up as
a warning. The client never raises for a fingerprint issue: in strict mode it catches the
ValueError and only logs it at debug level. To stop before connecting, call validate_params
in strict mode yourself:
from opentele2.fingerprint import FingerprintConfig, StrictMode
strict = FingerprintConfig(strict_mode=StrictMode.STRICT)
strict.validate_params( # raises ValueError on any issue
api_id=1, device_model="Desktop", system_version="Windows 11 x64", app_version="7.3.1 x64",
system_lang_code="en-US", lang_pack="tdesktop", lang_code="en",
)import { checkVersions, issuesMessage, profile, validateInitParams } from 'opentele2'
const desktop = { ...profile('TelegramDesktop'), apiId: 1 }
const issues = validateInitParams(desktop, null, 'strict')
console.log(issues) // [{ kind: 'ApiIdMismatch', severity: 'error', message: ... }]
console.log(issuesMessage(issues))
console.log(checkVersions(profile('TelegramAndroid'))) // [] when the app version is currentuse opentele2_core::api::{self, Client, Versions};
use opentele2_core::fingerprint::{self, StrictMode};
fn main() {
let versions = Versions::offline();
let mut desktop = api::profile(Client::Desktop, &versions);
desktop.api_id = 1;
for issue in fingerprint::validate_init_params(&desktop, &versions, StrictMode::Strict) {
println!("{:?} {:?} {}", issue.kind, issue.severity, issue.message);
}
}Every finding is an issue with a kind, a severity and a message worded the same in every package.
| Kind | Checked |
|---|---|
UnknownLangPack | always: lang_pack is not an official pack |
ShortLangCode | always: lang_code has fewer than two characters |
MissingRegion | always: an Android or iOS system_lang_code without a region |
EmptyDeviceModel, EmptySystemVersion, EmptyAppVersion | always |
ApiIdMismatch | strict: api_id is not the official id of the lang_pack |
AppVersionMajor | strict: the app version major of android, ios, tdesktop and macos profiles |
AndroidSdkRange | strict: the Android SDK level is outside the known range |
The mode decides what happens with them:
| Mode | Severity | Python validate_params | Python TelegramClient |
|---|---|---|---|
off | note | never warns or raises | stays silent |
warn (default) | warning | warnings.warn with the message | warns on construction |
strict | error | raises ValueError | catches the error and logs it at debug level |
tz_offset
Telegram Desktop sends the local UTC offset in seconds as tz_offset, wrapped by whole days into
−12 h … +14 h and rounded to a multiple of 900 seconds. The core computes it from the offset you
pass, so the result never depends on the machine running the code.
from opentele2.fingerprint import tdesktop_tz_offset
print(tdesktop_tz_offset()) # this machine's local offset, for example 10800.0The opentele2 TelegramClient recomputes it on every connect(), so a daylight saving change
between connections shows up as it does in Telegram Desktop.
import { tzOffset } from 'opentele2'
console.log(tzOffset(-new Date().getTimezoneOffset() * 60)) // the local offsetfn main() {
println!("{}", opentele2_core::fingerprint::tz_offset(3 * 3600 + 1000)); // 11700
}msg_id offset
Official clients add a small random offset to their message ids. FingerprintConfig draws one in
0..=0xFFFF from the operating system’s random source (randomize_msg_id_offset, on by default);
random.seed and other seeded generators do not reproduce it. is_valid_msg_id /
isValidMsgId tells whether an id is plausible: positive, odd from the client and even from the
server, with its upper 32 bits within 300 seconds of now.
| Python | Node | Rust | |
|---|---|---|---|
| Draw an offset | generate_ | msgIdOffset() | fingerprint:: |
| Check an id | is_ | is | fingerprint:: |
Layer
get_advertised_layer() (Python), LAYER (Node) and api::LAYER (Rust) are 230. In Python,
get_recommended_layer() returns the layer of the installed Telethon and warns when it differs from
230 by more than 15 layers (LAYER_TOLERANCE); FingerprintConfig(layer_override=...) replaces the
layer get_effective_layer() reports. A mismatched layer breaks deserialisation, so override it
only with a library built for that layer.
Transport recommendation
Every official client speaks the obfuscated transport. TransportRecommendation.get_connection_class() returns Telethon's ConnectionTcpObfuscated, and the opentele2 TelegramClient uses it unless you pass connection=. TransportRecommendation.get_available_transports() lists the Telethon transports (full, abridged, intermediate, obfuscated) that import.
Part of the Python package only: it picks a Telethon connection class. In Node and Rust, configure the transport of mtcute or grammers directly; fingerprintConfig() and FingerprintConfig carry preferredTransport / preferred_transport, 'obfuscated' by default.
Part of the Python package only: it picks a Telethon connection class. In Node and Rust, configure the transport of mtcute or grammers directly; fingerprintConfig() and FingerprintConfig carry preferredTransport / preferred_transport, 'obfuscated' by default.
Consistency checks
Validation looks at the values you are about to send; the consistency checks ask the server how it sees a live session. They run seven requests and checks in a fixed order:
| 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 |
# client: a connected opentele2 TelegramClient, inside a coroutine
report = await client.RunConsistencyChecks() # auto_warn=True warns when a check fails
print(report.all_passed)
print(report.summary) # one [OK] or [FAIL] line per checkimport { 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'))
console.log(consistencySummary(checks))// 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.summary());
}current_session fails for any session whose api_id is not official: the server reports such
sessions without official_app. A failing check is a signal to look at the profile, not an error
of the library.