Skip to content

Instantly share code, notes, and snippets.

@painor
Last active November 17, 2024 13:25
Show Gist options
  • Save painor/7ef6c68a1d5bdea01a2f58b1de147104 to your computer and use it in GitHub Desktop.
Save painor/7ef6c68a1d5bdea01a2f58b1de147104 to your computer and use it in GitHub Desktop.
Convert TDesktop tdata folders to GramJS sessions.

tdata converter

Converts tdata folder to a GramJS session for usage in a userbot.

Usage

Usage is simple. the main() function accepts a folder path (absolute or relative) and will return and print the session string if it found one.

Limitation

Currently, this will only work if your tdata folder has 1 account and is not password protected and is also using the latest version of tdesktop (2.7+) Works with tdesktop 3.0+

/**
* Converts a TDATA (tdesktop) folder to a GramJS session.
* Needs GramJS installed (npm i telegram)
*
* This is all based on https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Conversion.php from the madeline library
* and as such is distributed in the same license and with the same header
*
* onversion module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
import * as crypto from "crypto";
import * as fs from "fs";
import { BinaryReader } from "telegram/extensions";
import { IGE } from "telegram/crypto/IGE";
import { AuthKey } from "telegram/crypto/AuthKey";
import { StringSession } from "telegram/sessions";
function tdesktop_md5(data: string) {
let result = '';
const hash = crypto.createHash('md5').update(data).digest("hex");
for (let i = 0; i < hash.length; i += 2) {
result += hash[i + 1] + hash[i];
}
return result.toUpperCase();
}
function tdesktop_readBuffer(file: BinaryReader) {
let length = file.read(4).reverse().readInt32LE();
return length > 0 ? file.read(length, false) : Buffer.alloc(0);
}
function sha1(buf: Buffer) {
return crypto.createHash('sha1').update(buf).digest()
}
/**
* Old way of calculating aes keys
*/
function _calcKey(authKey: Buffer, msgKey: Buffer, client: boolean) {
const x = client ? 0 : 8;
const sha1_a = sha1(Buffer.concat([msgKey, authKey.slice(x, x + 32)]));
const sha1_b = sha1(Buffer.concat([authKey.slice(32 + x, 32 + x + 16), msgKey, authKey.slice(48 + x, 48 + x + 16)]));
const sha1_c = sha1(Buffer.concat([authKey.slice(64 + x, 64 + x + 32), msgKey]));
const sha1_d = sha1(Buffer.concat([msgKey, authKey.slice(96 + x, 96 + x + 32)]));
const aes_key = Buffer.concat([sha1_a.slice(0, 8), sha1_b.slice(8, 8 + 12), sha1_c.slice(4, 4 + 12)]);
const aes_iv = Buffer.concat([sha1_a.slice(8, 8 + 12), sha1_b.slice(0, 8), sha1_c.slice(16, 16 + 4), sha1_d.slice(0, 8)]);
return [aes_key, aes_iv];
}
function tdesktop_decrypt(data: BinaryReader, auth_key: Buffer): BinaryReader {
const message_key = data.read(16);
const encrypted_data = data.read();
const [aes_key, aes_iv] = _calcKey(auth_key, message_key, false);
const ige = new IGE(aes_key, aes_iv);
const decrypted_data = ige.decryptIge(encrypted_data) as Buffer;
if (message_key.toString("hex") != sha1(decrypted_data).slice(0, 16).toString("hex")) {
throw new Error('msg_key mismatch');
}
return new BinaryReader(decrypted_data);
}
function tdesktop_open_encrypted(fileName: string, tdesktop_key: Buffer) {
const f = tdesktop_open(fileName);
const data = tdesktop_readBuffer(f);
const res = tdesktop_decrypt(new BinaryReader(data), tdesktop_key);
const length = res.readInt(false);
if (length > res.getBuffer().length || length < 4) {
throw new Error('Wrong length');
}
return res;
}
function tdesktop_open(name: string): BinaryReader {
const filesToTry: BinaryReader[] = [];
for (const i of ['0', '1', 's']) {
if (fs.existsSync(name + i)) {
filesToTry.push(new BinaryReader(fs.readFileSync(name + i)));
}
}
for (const fileToTry of filesToTry) {
const magic = fileToTry.read(4).toString("utf-8");
if (magic != "TDF$") {
console.error("WRONG MAGIC");
continue;
}
const versionBytes = fileToTry.read(4);
const version = versionBytes.readInt32LE(0);
console.error(`TDesktop version: ${version}`);
let data = fileToTry.read();
const md5 = data.slice(-16).toString("hex");
data = data.slice(0, -16);
const length = Buffer.alloc(4);
length.writeInt32LE(data.length, 0);
const toCompare = Buffer.concat([data, length, versionBytes, Buffer.from("TDF$", "utf-8")]);
const hash = crypto.createHash('md5').update(toCompare).digest("hex");
if (hash != md5) {
throw new Error("Wrong MD5");
}
return new BinaryReader(data)
}
throw new Error("Could not open " + name)
}
function getServerAddress(dcId: number) {
switch (dcId) {
case 1:
return "149.154.175.55";
case 2:
return "149.154.167.50";
case 3:
return "149.154.175.100";
case 4:
return "149.154.167.91";
case 5:
return "91.108.56.170";
default:
throw new Error("Invalid DC");
}
}
async function main(TDATA_PATH: string) {
const old_session_key = 'data';
const part_one_md5 = tdesktop_md5(old_session_key).slice(0, 16);
const tdesktop_user_base_path = TDATA_PATH + "/" + part_one_md5;
const path_key = 'key_' + old_session_key;
const data = tdesktop_open(TDATA_PATH + "/" + path_key);
const salt = tdesktop_readBuffer(data);
if (salt.length !== 32) {
throw new Error("Length of salt is wrong!");
}
const encryptedKey = tdesktop_readBuffer(data);
const encryptedInfo = tdesktop_readBuffer(data);
const hash = crypto.createHash('sha512').update(salt).update('').update(salt).digest();
const passKey = crypto.pbkdf2Sync(hash, salt, 1, 256, "sha512");
const key = tdesktop_readBuffer(tdesktop_decrypt(new BinaryReader(encryptedKey), passKey));
const info = tdesktop_readBuffer(tdesktop_decrypt(new BinaryReader(encryptedInfo), key));
const count = info.readUInt32BE();
console.log("Accounts count", count);
if (count !== 1) {
throw new Error("Currently only supporting one account at a time");
}
let main = tdesktop_open_encrypted(tdesktop_user_base_path, key);
const magic = main.read(4).reverse().readUInt32LE();
if (magic != 75) {
throw new Error("Unsupported magic version");
}
const final = new BinaryReader(tdesktop_readBuffer(main));
final.read(12);
const userId = final.read(4).reverse().readUInt32LE();
console.log("User ID is ", userId);
const mainDc = final.read(4).reverse().readUInt32LE();
console.log("Main DC is ", mainDc);
const length = final.read(4).reverse().readUInt32LE();
const mainAuthKey = new AuthKey();
for (let i = 0; i < length; i++) {
const dc = final.read(4).reverse().readUInt32LE();
const authKey = final.read(256);
if (dc == mainDc) {
await mainAuthKey.setKey(authKey);
const session = new StringSession("");
session.setDC(
mainDc,
getServerAddress(mainDc),
443,
);
session.setAuthKey(mainAuthKey);
console.log("Session is");
const sessionString = session.save();
console.log(sessionString);
return sessionString;
}
}
}
// uncomment this line if you want to run it here.
// use your own path
//main("C:/Users/user/AppData/Roaming/Telegram Desktop/tdata");
@ncsft
Copy link

ncsft commented Feb 23, 2024

How can i get apiId and apiHash ?

@encloinc
Copy link

encloinc commented Mar 4, 2024

bro i love you

@encloinc
Copy link

encloinc commented Mar 4, 2024

(⌒∇⌒)ノ””    
(⌒∇⌒)ノ””    
(⌒∇⌒)ノ””    
(⌒∇⌒)ノ””    
(⌒∇⌒)ノ””    
(⌒∇⌒)ノ””    
(⌒∇⌒)ノ””    
(⌒∇⌒)ノ””    

@encloinc
Copy link

encloinc commented Mar 4, 2024

<3

@nosphe12345
Copy link

good work bro
can you help me to use ?
i cant use it :/

@xzone911
Copy link

xzone911 commented Jul 3, 2024

if account has 2fa password,how to converte?

@arifintajul4
Copy link

any sugestion if singgle tdata have multiple account?

@ofarukcaki
Copy link

any sugestion if singgle tdata have multiple account?

Did you find a solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment