Session conversion #20
50 changed files with 1537 additions and 1 deletions
|
@ -2,6 +2,7 @@
|
||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"../**/*.test.ts",
|
"../**/*.test.ts",
|
||||||
"../**/*.test-utils.ts"
|
"../**/*.test-utils.ts",
|
||||||
|
"../**/__fixtures__/**",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
22
packages/convert/README.md
Normal file
22
packages/convert/README.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# @mtcute/file-id
|
||||||
|
|
||||||
|
📖 [API Reference](https://ref.mtcute.dev/modules/_mtcute_file_id.html)
|
||||||
|
|
||||||
|
This package is used internally by `@mtcute/core` to parse, serialize
|
||||||
|
and manipulate TDLib and Bot API compatible File IDs, but can also be used
|
||||||
|
for any other purposes.
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
This is basically a port of a portion of TDLib APIs, but greatly
|
||||||
|
simplified in usage and made to work seamlessly with the rest of the
|
||||||
|
mtcute APIs.
|
||||||
|
|
||||||
|
This is a list of files from TDLib repository, from which most of the code was taken:
|
||||||
|
- [td/telegram/files/FileManager.cpp](https://github.com/tdlib/td/blob/master/td/telegram/files/FileManager.cpp)
|
||||||
|
- [td/telegram/files/FileLocation.hpp](https://github.com/tdlib/td/blob/master/td/telegram/files/FileLocation.hpp)
|
||||||
|
- [td/telegram/PhotoSizeSource.h](https://github.com/tdlib/td/blob/master/td/telegram/PhotoSizeSource.h)
|
||||||
|
- [td/telegram/PhotoSizeSource.hpp](https://github.com/tdlib/td/blob/master/td/telegram/PhotoSizeSource.hpp)
|
||||||
|
- [td/telegram/Version.h](https://github.com/tdlib/td/blob/master/td/telegram/Version.h)
|
||||||
|
|
||||||
|
Additionally, some of the test cases were taken from a similar Python
|
||||||
|
library, [luckydonald/telegram_file_id](https://github.com/luckydonald/telegram_file_id)
|
29
packages/convert/package.json
Normal file
29
packages/convert/package.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "@mtcute/convert",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.7.0",
|
||||||
|
"description": "Cross-library session conversion utilities",
|
||||||
|
"author": "Alina Sireneva <alina@tei.su>",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": false,
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm run -w build-package convert"
|
||||||
|
},
|
||||||
|
"distOnlyFields": {
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./esm/index.js",
|
||||||
|
"require": "./cjs/index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mtcute/core": "workspace:^",
|
||||||
|
"@mtcute/tl": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@mtcute/test": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
113
packages/convert/src/dcs.ts
Normal file
113
packages/convert/src/dcs.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import { DcOptions } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
// some libraries only store the DCs in the source code, so we need to map them to the correct DCs
|
||||||
|
// this may not be very accurate, but it's better than nothing
|
||||||
|
// we *could* always map those to the primary dc (the client should handle that gracefully),
|
||||||
|
// but imo it's better to be as accurate as possible
|
||||||
|
// we'll also only map to ipv4 since that's more portable
|
||||||
|
|
||||||
|
export const DC_MAPPING_PROD: Record<number, DcOptions> = {
|
||||||
|
'1': {
|
||||||
|
main: {
|
||||||
|
id: 1,
|
||||||
|
ipAddress: '149.154.175.56',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 1,
|
||||||
|
ipAddress: '149.154.175.211',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.41',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.35',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
main: {
|
||||||
|
id: 3,
|
||||||
|
ipAddress: '149.154.175.100',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 3,
|
||||||
|
ipAddress: '149.154.175.100',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'4': {
|
||||||
|
main: {
|
||||||
|
id: 4,
|
||||||
|
ipAddress: '149.154.167.91',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 4,
|
||||||
|
ipAddress: '149.154.167.255',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'5': {
|
||||||
|
main: {
|
||||||
|
id: 5,
|
||||||
|
ipAddress: '91.108.56.179',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 5,
|
||||||
|
ipAddress: '149.154.171.255',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DC_MAPPING_TEST: Record<number, DcOptions> = {
|
||||||
|
'1': {
|
||||||
|
main: {
|
||||||
|
id: 1,
|
||||||
|
ipAddress: '149.154.175.10',
|
||||||
|
port: 80,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 1,
|
||||||
|
ipAddress: '149.154.175.10',
|
||||||
|
port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
main: {
|
||||||
|
id: 3,
|
||||||
|
ipAddress: '149.154.175.117',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 3,
|
||||||
|
ipAddress: '149.154.175.117',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTestDc(ip: string): boolean {
|
||||||
|
return Object.values(DC_MAPPING_TEST).some((dc) => dc.main.ipAddress === ip || dc.media.ipAddress === ip)
|
||||||
|
}
|
46
packages/convert/src/gramjs/__fixtures__/generate.cjs
Normal file
46
packages/convert/src/gramjs/__fixtures__/generate.cjs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
const { execSync } = require('child_process')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
const VERSION = '2.19.20'
|
||||||
|
const TMP_DIR = '/tmp/gramjs'
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
if (!fs.existsSync(TMP_DIR)) {
|
||||||
|
execSync(`mkdir -p ${TMP_DIR}`)
|
||||||
|
execSync(`npm install telegram@${VERSION}`, {
|
||||||
|
cwd: TMP_DIR,
|
||||||
|
stdio: 'inherit',
|
||||||
|
})
|
||||||
|
console.log('Installed gramjs')
|
||||||
|
}
|
||||||
|
|
||||||
|
// crutches for webpack
|
||||||
|
const gramjs = require(`${TMP_DIR}/node_modules/telegram`)
|
||||||
|
|
||||||
|
const apiId = Number(process.env.API_ID)
|
||||||
|
const apiHash = process.env.API_HASH
|
||||||
|
const stringSession = new gramjs.sessions.StringSession('')
|
||||||
|
stringSession.setDC(2, '149.154.167.40', 443)
|
||||||
|
|
||||||
|
const client = new gramjs.TelegramClient(stringSession, apiId, apiHash, {
|
||||||
|
connectionRetries: 5,
|
||||||
|
})
|
||||||
|
|
||||||
|
await client.start({
|
||||||
|
phoneNumber: async () => '9996621234',
|
||||||
|
phoneCode: async () => '22222',
|
||||||
|
onError: console.error,
|
||||||
|
})
|
||||||
|
|
||||||
|
const session = stringSession.save()
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
`${__dirname}/session.ts`,
|
||||||
|
`export const GRAMJS_SESSION = '${session}'\n`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await client.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
2
packages/convert/src/gramjs/__fixtures__/session.ts
Normal file
2
packages/convert/src/gramjs/__fixtures__/session.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const GRAMJS_SESSION =
|
||||||
|
'1AgAOMTQ5LjE1NC4xNjcuNDABu60obcEYS8Yb/I7YlCwaLvW84dXCX2oGnBYG+zuMciJhHP99c8ZJvwxJgH8yU1QrqI+Gh0kK0JAuQucIpDfq/jJVLZ1ZRimq5yy1XbeEs65gtZA1+SUwZRXahh+NzGbPmOVUMCnCtRONo9GNvcx/QxSXRrh7T/K0YYN1iHsK1vJDk8/SUnthvTNmRycC+JLn4fMtctqP4Le2WPOH/deYbUF0BlwmR77M7fv1GZSInqCgWReaIl5nvn0IqA4mOCTkdOgcvwOiB2UmXwiyInxRuLdBIyLbBUDCuTlmL1m3FJqbuEpZEUJnoJf2YDFZ1wR6TfL0MUS1VwnjOcy3WIIFwwg='
|
72
packages/convert/src/gramjs/convert.test.ts
Normal file
72
packages/convert/src/gramjs/convert.test.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { GRAMJS_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { convertFromGramjsSession, convertToGramjsSession } from './convert.js'
|
||||||
|
|
||||||
|
describe('gramjs/convert', () => {
|
||||||
|
it('should correctly convert from gramjs sessions', () => {
|
||||||
|
expect(convertFromGramjsSession(GRAMJS_SESSION)).toEqual({
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'ad286dc1184bc61bfc8ed8942c1a2ef5bce1d5c25f6a069c1606fb3b8c722261' +
|
||||||
|
'1cff7d73c649bf0c49807f3253542ba88f8687490ad0902e42e708a437eafe32' +
|
||||||
|
'552d9d594629aae72cb55db784b3ae60b59035f925306515da861f8dcc66cf98' +
|
||||||
|
'e5543029c2b5138da3d18dbdcc7f43149746b87b4ff2b4618375887b0ad6f243' +
|
||||||
|
'93cfd2527b61bd3366472702f892e7e1f32d72da8fe0b7b658f387fdd7986d41' +
|
||||||
|
'74065c2647beccedfbf51994889ea0a059179a225e67be7d08a80e263824e474' +
|
||||||
|
'e81cbf03a20765265f08b2227c51b8b7412322db0540c2b939662f59b7149a9b' +
|
||||||
|
'b84a59114267a097f6603159d7047a4df2f43144b55709e339ccb7588205c308',
|
||||||
|
),
|
||||||
|
primaryDcs: {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testMode: true,
|
||||||
|
version: 3,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly convert to gramjs sessions', () => {
|
||||||
|
expect(
|
||||||
|
convertToGramjsSession({
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'ad286dc1184bc61bfc8ed8942c1a2ef5bce1d5c25f6a069c1606fb3b8c722261' +
|
||||||
|
'1cff7d73c649bf0c49807f3253542ba88f8687490ad0902e42e708a437eafe32' +
|
||||||
|
'552d9d594629aae72cb55db784b3ae60b59035f925306515da861f8dcc66cf98' +
|
||||||
|
'e5543029c2b5138da3d18dbdcc7f43149746b87b4ff2b4618375887b0ad6f243' +
|
||||||
|
'93cfd2527b61bd3366472702f892e7e1f32d72da8fe0b7b658f387fdd7986d41' +
|
||||||
|
'74065c2647beccedfbf51994889ea0a059179a225e67be7d08a80e263824e474' +
|
||||||
|
'e81cbf03a20765265f08b2227c51b8b7412322db0540c2b939662f59b7149a9b' +
|
||||||
|
'b84a59114267a097f6603159d7047a4df2f43144b55709e339ccb7588205c308',
|
||||||
|
),
|
||||||
|
primaryDcs: {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testMode: true,
|
||||||
|
version: 3,
|
||||||
|
}),
|
||||||
|
).toEqual(GRAMJS_SESSION)
|
||||||
|
})
|
||||||
|
})
|
29
packages/convert/src/gramjs/convert.ts
Normal file
29
packages/convert/src/gramjs/convert.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { readStringSession, StringSessionData } from '@mtcute/core/utils.js'
|
||||||
|
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||||
|
|
||||||
|
import { convertFromTelethonSession } from '../telethon/convert.js'
|
||||||
|
import { TelethonSession } from '../telethon/types.js'
|
||||||
|
import { parseGramjsSession } from './parse.js'
|
||||||
|
import { serializeGramjsSession } from './serialize.js'
|
||||||
|
|
||||||
|
export function convertFromGramjsSession(session: TelethonSession | string): StringSessionData {
|
||||||
|
if (typeof session === 'string') {
|
||||||
|
session = parseGramjsSession(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertFromTelethonSession(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertToGramjsSession(session: StringSessionData | string): string {
|
||||||
|
if (typeof session === 'string') {
|
||||||
|
session = readStringSession(__tlReaderMap, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializeGramjsSession({
|
||||||
|
dcId: session.primaryDcs.main.id,
|
||||||
|
ipAddress: session.primaryDcs.main.ipAddress,
|
||||||
|
port: session.primaryDcs.main.port,
|
||||||
|
ipv6: session.primaryDcs.main.ipv6 ?? false,
|
||||||
|
authKey: session.authKey,
|
||||||
|
})
|
||||||
|
}
|
27
packages/convert/src/gramjs/parse.test.ts
Normal file
27
packages/convert/src/gramjs/parse.test.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { GRAMJS_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { parseGramjsSession } from './parse.js'
|
||||||
|
|
||||||
|
describe('gramjs/parse', () => {
|
||||||
|
it('should correctly parse sessions', () => {
|
||||||
|
expect(parseGramjsSession(GRAMJS_SESSION)).toEqual({
|
||||||
|
dcId: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
ipv6: false,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'ad286dc1184bc61bfc8ed8942c1a2ef5bce1d5c25f6a069c1606fb3b8c722261' +
|
||||||
|
'1cff7d73c649bf0c49807f3253542ba88f8687490ad0902e42e708a437eafe32' +
|
||||||
|
'552d9d594629aae72cb55db784b3ae60b59035f925306515da861f8dcc66cf98' +
|
||||||
|
'e5543029c2b5138da3d18dbdcc7f43149746b87b4ff2b4618375887b0ad6f243' +
|
||||||
|
'93cfd2527b61bd3366472702f892e7e1f32d72da8fe0b7b658f387fdd7986d41' +
|
||||||
|
'74065c2647beccedfbf51994889ea0a059179a225e67be7d08a80e263824e474' +
|
||||||
|
'e81cbf03a20765265f08b2227c51b8b7412322db0540c2b939662f59b7149a9b' +
|
||||||
|
'b84a59114267a097f6603159d7047a4df2f43144b55709e339ccb7588205c308',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
35
packages/convert/src/gramjs/parse.ts
Normal file
35
packages/convert/src/gramjs/parse.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
import { TelethonSession } from '../telethon/types.js'
|
||||||
|
|
||||||
|
export function parseGramjsSession(session: string): TelethonSession {
|
||||||
|
if (session[0] !== '1') {
|
||||||
|
// version
|
||||||
|
throw new MtArgumentError(`Invalid session string (version = ${session[0]})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
session = session.slice(1)
|
||||||
|
|
||||||
|
const data = getPlatform().base64Decode(session)
|
||||||
|
const dv = dataViewFromBuffer(data)
|
||||||
|
|
||||||
|
const dcId = dv.getUint8(0)
|
||||||
|
|
||||||
|
const ipSize = dv.getUint16(1)
|
||||||
|
let pos = 3 + ipSize
|
||||||
|
|
||||||
|
const ip = getPlatform().utf8Decode(data.subarray(3, pos))
|
||||||
|
const port = dv.getUint16(pos)
|
||||||
|
pos += 2
|
||||||
|
const authKey = data.subarray(pos, pos + 256)
|
||||||
|
|
||||||
|
return {
|
||||||
|
dcId,
|
||||||
|
ipAddress: ip,
|
||||||
|
ipv6: ip.includes(':'), // dumb check but gramjs does this
|
||||||
|
port,
|
||||||
|
authKey,
|
||||||
|
}
|
||||||
|
}
|
29
packages/convert/src/gramjs/serialize.test.ts
Normal file
29
packages/convert/src/gramjs/serialize.test.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { GRAMJS_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { serializeGramjsSession } from './serialize.js'
|
||||||
|
|
||||||
|
describe('gramjs/serialize', () => {
|
||||||
|
it('should correctly serialize sessions', () => {
|
||||||
|
expect(
|
||||||
|
serializeGramjsSession({
|
||||||
|
dcId: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
ipv6: false,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'ad286dc1184bc61bfc8ed8942c1a2ef5bce1d5c25f6a069c1606fb3b8c722261' +
|
||||||
|
'1cff7d73c649bf0c49807f3253542ba88f8687490ad0902e42e708a437eafe32' +
|
||||||
|
'552d9d594629aae72cb55db784b3ae60b59035f925306515da861f8dcc66cf98' +
|
||||||
|
'e5543029c2b5138da3d18dbdcc7f43149746b87b4ff2b4618375887b0ad6f243' +
|
||||||
|
'93cfd2527b61bd3366472702f892e7e1f32d72da8fe0b7b658f387fdd7986d41' +
|
||||||
|
'74065c2647beccedfbf51994889ea0a059179a225e67be7d08a80e263824e474' +
|
||||||
|
'e81cbf03a20765265f08b2227c51b8b7412322db0540c2b939662f59b7149a9b' +
|
||||||
|
'b84a59114267a097f6603159d7047a4df2f43144b55709e339ccb7588205c308',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
).toEqual(GRAMJS_SESSION)
|
||||||
|
})
|
||||||
|
})
|
28
packages/convert/src/gramjs/serialize.ts
Normal file
28
packages/convert/src/gramjs/serialize.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
import { TelethonSession } from '../telethon/types.js'
|
||||||
|
|
||||||
|
export function serializeGramjsSession(session: TelethonSession) {
|
||||||
|
if (session.authKey.length !== 256) {
|
||||||
|
throw new MtArgumentError('authKey must be 256 bytes long')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipEncoded = getPlatform().utf8Encode(session.ipAddress)
|
||||||
|
|
||||||
|
const u8 = new Uint8Array(261 + ipEncoded.length)
|
||||||
|
const dv = dataViewFromBuffer(u8)
|
||||||
|
|
||||||
|
dv.setUint8(0, session.dcId)
|
||||||
|
dv.setUint16(1, ipEncoded.length)
|
||||||
|
u8.set(ipEncoded, 3)
|
||||||
|
|
||||||
|
let pos = 3 + ipEncoded.length
|
||||||
|
|
||||||
|
dv.setUint16(pos, session.port)
|
||||||
|
pos += 2
|
||||||
|
u8.set(session.authKey, pos)
|
||||||
|
|
||||||
|
return '1' + getPlatform().base64Encode(u8)
|
||||||
|
}
|
7
packages/convert/src/gramjs/types.ts
Normal file
7
packages/convert/src/gramjs/types.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export interface GramjsSession {
|
||||||
|
dcId: number
|
||||||
|
ipAddress: string
|
||||||
|
ipv6: boolean
|
||||||
|
port: number
|
||||||
|
authKey: Uint8Array
|
||||||
|
}
|
15
packages/convert/src/mtkruto/__fixtures__/generate.js
Normal file
15
packages/convert/src/mtkruto/__fixtures__/generate.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/* eslint-disable import/no-unresolved, no-undef, no-console */
|
||||||
|
|
||||||
|
import { Client, StorageMemory } from 'https://deno.land/x/mtkruto@0.1.157/mod.ts'
|
||||||
|
|
||||||
|
const client = new Client(new StorageMemory(), Number(Deno.env.get('API_ID')), Deno.env.get('API_HASH'), {
|
||||||
|
initialDc: '2-test',
|
||||||
|
})
|
||||||
|
|
||||||
|
await client.start({
|
||||||
|
phone: () => '9996621234',
|
||||||
|
code: () => '22222',
|
||||||
|
})
|
||||||
|
|
||||||
|
const authString = await client.exportAuthString()
|
||||||
|
console.log('The auth string is', authString)
|
2
packages/convert/src/mtkruto/__fixtures__/session.ts
Normal file
2
packages/convert/src/mtkruto/__fixtures__/session.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const MTKRUTO_SESSION =
|
||||||
|
'BjItdGVzdAAB_gABAQABWEIKa07Ch-9zoA024mDOpsv20TW4YwuoRRROqSi41YQCbD3c4nKnz7BcFIu1mfn6f6Xm3OTVqoT0zib4p_AuZD9H-t8j5AagecRg-oSpQlmjoiUazKQSxnxWotGWf1mPNntAeOvDNa5t1NjXUxmqdB3e2AjYLF_E2jDESVgUuDBQUMBHIDc_xFBAlz6kVxCZ6iINJHbnyJ2F19tbEPFJvSM999RKaFj5lUUVs0qKNXEUmsFYUuIdPBzjWilY8Uvf9nYU_xXd9CUAAXS5_i4aaWlHoTIf3zn8ZEINhDIU1DMauh5vhSWt7F0fkxODjtou-7PdIunuDtqyQm4steuNJc8'
|
70
packages/convert/src/mtkruto/convert.test.ts
Normal file
70
packages/convert/src/mtkruto/convert.test.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { u8HexDecode } from '@mtcute/test'
|
||||||
|
|
||||||
|
import { MTKRUTO_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { convertFromMtkrutoSession, convertToMtkrutoSession } from './convert.js'
|
||||||
|
|
||||||
|
describe('mtkruto/convert', () => {
|
||||||
|
it('should correctly convert from mtkruto sessions', () => {
|
||||||
|
expect(convertFromMtkrutoSession(MTKRUTO_SESSION)).toEqual({
|
||||||
|
authKey: u8HexDecode(
|
||||||
|
'58420a6b4ec287ef73a00d36e260cea6cbf6d135b8630ba845144ea928b8d584' +
|
||||||
|
'026c3ddce272a7cfb05c148bb599f9fa7fa5e6dce4d5aa84f4ce26f8a7f02e64' +
|
||||||
|
'3f47fadf23e406a079c460fa84a94259a3a2251acca412c67c56a2d1967f598f' +
|
||||||
|
'367b4078ebc335ae6dd4d8d75319aa741dded808d82c5fc4da30c4495814b830' +
|
||||||
|
'5050c04720373fc45040973ea4571099ea220d2476e7c89d85d7db5b10f149bd' +
|
||||||
|
'233df7d44a6858f9954515b34a8a3571149ac15852e21d3c1ce35a2958f14bdf' +
|
||||||
|
'f67614ff15ddf4250074b9fe2e1a696947a1321fdf39fc64420d843214d4331a' +
|
||||||
|
'ba1e6f8525adec5d1f9313838eda2efbb3dd22e9ee0edab2426e2cb5eb8d25cf',
|
||||||
|
),
|
||||||
|
primaryDcs: {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testMode: true,
|
||||||
|
version: 3,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly convert to mtkruto sessions', () => {
|
||||||
|
expect(
|
||||||
|
convertToMtkrutoSession({
|
||||||
|
authKey: u8HexDecode(
|
||||||
|
'58420a6b4ec287ef73a00d36e260cea6cbf6d135b8630ba845144ea928b8d584' +
|
||||||
|
'026c3ddce272a7cfb05c148bb599f9fa7fa5e6dce4d5aa84f4ce26f8a7f02e64' +
|
||||||
|
'3f47fadf23e406a079c460fa84a94259a3a2251acca412c67c56a2d1967f598f' +
|
||||||
|
'367b4078ebc335ae6dd4d8d75319aa741dded808d82c5fc4da30c4495814b830' +
|
||||||
|
'5050c04720373fc45040973ea4571099ea220d2476e7c89d85d7db5b10f149bd' +
|
||||||
|
'233df7d44a6858f9954515b34a8a3571149ac15852e21d3c1ce35a2958f14bdf' +
|
||||||
|
'f67614ff15ddf4250074b9fe2e1a696947a1321fdf39fc64420d843214d4331a' +
|
||||||
|
'ba1e6f8525adec5d1f9313838eda2efbb3dd22e9ee0edab2426e2cb5eb8d25cf',
|
||||||
|
),
|
||||||
|
primaryDcs: {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testMode: true,
|
||||||
|
version: 3,
|
||||||
|
}),
|
||||||
|
).toEqual(MTKRUTO_SESSION)
|
||||||
|
})
|
||||||
|
})
|
32
packages/convert/src/mtkruto/convert.ts
Normal file
32
packages/convert/src/mtkruto/convert.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { readStringSession, StringSessionData } from '@mtcute/core/utils.js'
|
||||||
|
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||||
|
|
||||||
|
import { DC_MAPPING_PROD, DC_MAPPING_TEST } from '../dcs.js'
|
||||||
|
import { parseMtkrutoSession } from './parse.js'
|
||||||
|
import { serializeMtkrutoSession } from './serialize.js'
|
||||||
|
import { MtkrutoSession } from './types.js'
|
||||||
|
|
||||||
|
export function convertFromMtkrutoSession(session: MtkrutoSession | string): StringSessionData {
|
||||||
|
if (typeof session === 'string') {
|
||||||
|
session = parseMtkrutoSession(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 3,
|
||||||
|
testMode: session.isTest,
|
||||||
|
primaryDcs: (session.isTest ? DC_MAPPING_TEST : DC_MAPPING_PROD)[session.dcId],
|
||||||
|
authKey: session.authKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertToMtkrutoSession(session: StringSessionData | string): string {
|
||||||
|
if (typeof session === 'string') {
|
||||||
|
session = readStringSession(__tlReaderMap, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializeMtkrutoSession({
|
||||||
|
dcId: session.primaryDcs.main.id,
|
||||||
|
isTest: session.testMode,
|
||||||
|
authKey: session.authKey,
|
||||||
|
})
|
||||||
|
}
|
25
packages/convert/src/mtkruto/parse.test.ts
Normal file
25
packages/convert/src/mtkruto/parse.test.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { u8HexDecode } from '@mtcute/test'
|
||||||
|
|
||||||
|
import { MTKRUTO_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { parseMtkrutoSession } from './parse.js'
|
||||||
|
|
||||||
|
describe('mtkruto/parse', () => {
|
||||||
|
it('should correctly parse sessions', () => {
|
||||||
|
expect(parseMtkrutoSession(MTKRUTO_SESSION)).toEqual({
|
||||||
|
dcId: 2,
|
||||||
|
isTest: true,
|
||||||
|
authKey: u8HexDecode(
|
||||||
|
'58420a6b4ec287ef73a00d36e260cea6cbf6d135b8630ba845144ea928b8d584' +
|
||||||
|
'026c3ddce272a7cfb05c148bb599f9fa7fa5e6dce4d5aa84f4ce26f8a7f02e64' +
|
||||||
|
'3f47fadf23e406a079c460fa84a94259a3a2251acca412c67c56a2d1967f598f' +
|
||||||
|
'367b4078ebc335ae6dd4d8d75319aa741dded808d82c5fc4da30c4495814b830' +
|
||||||
|
'5050c04720373fc45040973ea4571099ea220d2476e7c89d85d7db5b10f149bd' +
|
||||||
|
'233df7d44a6858f9954515b34a8a3571149ac15852e21d3c1ce35a2958f14bdf' +
|
||||||
|
'f67614ff15ddf4250074b9fe2e1a696947a1321fdf39fc64420d843214d4331a' +
|
||||||
|
'ba1e6f8525adec5d1f9313838eda2efbb3dd22e9ee0edab2426e2cb5eb8d25cf',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
31
packages/convert/src/mtkruto/parse.ts
Normal file
31
packages/convert/src/mtkruto/parse.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { TlBinaryReader } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
import { telegramRleDecode } from '../utils/rle.js'
|
||||||
|
import { MtkrutoSession } from './types.js'
|
||||||
|
|
||||||
|
export function parseMtkrutoSession(session: string): MtkrutoSession {
|
||||||
|
const data = telegramRleDecode(getPlatform().base64Decode(session, true))
|
||||||
|
const reader = TlBinaryReader.manual(data)
|
||||||
|
|
||||||
|
let dcIdStr = reader.string()
|
||||||
|
const authKey = reader.bytes()
|
||||||
|
|
||||||
|
const isTest = dcIdStr.endsWith('-test')
|
||||||
|
|
||||||
|
if (isTest) {
|
||||||
|
dcIdStr = dcIdStr.slice(0, -5)
|
||||||
|
}
|
||||||
|
const dcId = Number(dcIdStr)
|
||||||
|
|
||||||
|
if (isNaN(dcId)) {
|
||||||
|
throw new MtArgumentError(`Invalid DC ID: ${dcIdStr}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dcId,
|
||||||
|
isTest,
|
||||||
|
authKey,
|
||||||
|
}
|
||||||
|
}
|
27
packages/convert/src/mtkruto/serialize.test.ts
Normal file
27
packages/convert/src/mtkruto/serialize.test.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { u8HexDecode } from '@mtcute/test'
|
||||||
|
|
||||||
|
import { MTKRUTO_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { serializeMtkrutoSession } from './serialize.js'
|
||||||
|
|
||||||
|
describe('mtkruto/serialize', () => {
|
||||||
|
it('should correctly serialize sessions', () => {
|
||||||
|
expect(
|
||||||
|
serializeMtkrutoSession({
|
||||||
|
dcId: 2,
|
||||||
|
isTest: true,
|
||||||
|
authKey: u8HexDecode(
|
||||||
|
'58420a6b4ec287ef73a00d36e260cea6cbf6d135b8630ba845144ea928b8d584' +
|
||||||
|
'026c3ddce272a7cfb05c148bb599f9fa7fa5e6dce4d5aa84f4ce26f8a7f02e64' +
|
||||||
|
'3f47fadf23e406a079c460fa84a94259a3a2251acca412c67c56a2d1967f598f' +
|
||||||
|
'367b4078ebc335ae6dd4d8d75319aa741dded808d82c5fc4da30c4495814b830' +
|
||||||
|
'5050c04720373fc45040973ea4571099ea220d2476e7c89d85d7db5b10f149bd' +
|
||||||
|
'233df7d44a6858f9954515b34a8a3571149ac15852e21d3c1ce35a2958f14bdf' +
|
||||||
|
'f67614ff15ddf4250074b9fe2e1a696947a1321fdf39fc64420d843214d4331a' +
|
||||||
|
'ba1e6f8525adec5d1f9313838eda2efbb3dd22e9ee0edab2426e2cb5eb8d25cf',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
).toEqual(MTKRUTO_SESSION)
|
||||||
|
})
|
||||||
|
})
|
16
packages/convert/src/mtkruto/serialize.ts
Normal file
16
packages/convert/src/mtkruto/serialize.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { TlBinaryWriter } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
import { telegramRleEncode } from '../utils/rle.js'
|
||||||
|
import { MtkrutoSession } from './types.js'
|
||||||
|
|
||||||
|
export function serializeMtkrutoSession(session: MtkrutoSession): string {
|
||||||
|
const dcIdStr = `${session.dcId}${session.isTest ? '-test' : ''}`
|
||||||
|
|
||||||
|
const writer = TlBinaryWriter.manual(session.authKey.length + dcIdStr.length + 8)
|
||||||
|
|
||||||
|
writer.string(dcIdStr)
|
||||||
|
writer.bytes(session.authKey)
|
||||||
|
|
||||||
|
return getPlatform().base64Encode(telegramRleEncode(writer.result()), true)
|
||||||
|
}
|
5
packages/convert/src/mtkruto/types.ts
Normal file
5
packages/convert/src/mtkruto/types.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export interface MtkrutoSession {
|
||||||
|
dcId: number
|
||||||
|
isTest: boolean
|
||||||
|
authKey: Uint8Array
|
||||||
|
}
|
20
packages/convert/src/pyrogram/__fixtures__/generate.py
Normal file
20
packages/convert/src/pyrogram/__fixtures__/generate.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from pyrogram import Client, filters
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with Client(
|
||||||
|
"my_account_test",
|
||||||
|
api_id=int(os.environ["API_ID"]),
|
||||||
|
api_hash=os.environ["API_HASH"],
|
||||||
|
test_mode=True,
|
||||||
|
in_memory=True,
|
||||||
|
phone_number="9996621234",
|
||||||
|
phone_code="22222"
|
||||||
|
) as app:
|
||||||
|
session = await app.export_session_string()
|
||||||
|
open("session.ts", "w").write(
|
||||||
|
'export const PYROGRAM_TEST_SESSION = \'' + session + '\'\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.get_event_loop().run_until_complete(main())
|
24
packages/convert/src/pyrogram/__fixtures__/generate_old.py
Normal file
24
packages/convert/src/pyrogram/__fixtures__/generate_old.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# pip install pyrogram==1.4.16
|
||||||
|
# also it needs to be patched a bit because its broken smh
|
||||||
|
from pyrogram import Client, filters
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with Client(
|
||||||
|
":memory:",
|
||||||
|
api_id=int(os.environ["API_ID"]),
|
||||||
|
api_hash=os.environ["API_HASH"],
|
||||||
|
test_mode=True,
|
||||||
|
phone_number="9996621234",
|
||||||
|
phone_code="22222"
|
||||||
|
) as app:
|
||||||
|
session = await app.export_session_string()
|
||||||
|
open("session_old.ts", "w").write(
|
||||||
|
'export const PYROGRAM_TEST_SESSION_OLD = \'' + session + '\'\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.get_event_loop().run_until_complete(main())
|
2
packages/convert/src/pyrogram/__fixtures__/session.ts
Normal file
2
packages/convert/src/pyrogram/__fixtures__/session.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const PYROGRAM_TEST_SESSION =
|
||||||
|
'AgAyyvcBTk6KssqikKPxEhxfXJpkoFIgQ_o8VpCk_4g0tcHe0rVCXx34AaDKvaNOlbkJOZ4jA3AI8iDYkI2opuifbM_7S2u9MMdnrjfg5jpfkXfI9-wF8DK_UBGIe1zk_Ibn0IHLRz-lkb-QqZNhh8O8Ggb8cieamatEYwLrkjkZR7JG53q76F0ktUd22L6_bUlp9p_qgXqBg8vZdkIIs9T1OiShw2X6TNO0lYqfJVaczMVQcT9Zt0FiyrAMpovFuT7-96OFKWcQ9gzrs_SHfz9HrQgBwvNSdkVziXTtxLJXsaNz3smGeyh-CEuEgdF3enIECnzftlvvUClLN_ylcPir1bi4_wAAAAEqEi1JAA'
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const PYROGRAM_TEST_SESSION_OLD =
|
||||||
|
'AgEWdHMtuA1pC01YkNiHpL1bC0yBC3wzGZCwSRWKlA_a69RhePUN3M51NpnwSXrW3pZV9FS8WjAwUkA23uT_49t8c7Umw3ihhKD6-hTpZ5wXC2MuC0EsF0-Z6WshYhT3gmN6QhEt0jlXo5cW1BJ3MYmXtsTWNf_hJfd3_wF_ZFa58ntVV-3qd08wQRhiL_IxM7L5YazjPw0dg2z92CqRARku_oq5D29V6W6bo8T-SLzF_ujj5ZcAQL25mJtCcXfhhjp9atxcrqnKzEs05xyrehnlJZKoGmnX0mF2P_6wUHqZC9tcTBUV4AmFcbuy7m_4SYLnJ8MbftNs7aWHHNcB1R4fAAAAASoSLUkA'
|
80
packages/convert/src/pyrogram/convert.test.ts
Normal file
80
packages/convert/src/pyrogram/convert.test.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { PYROGRAM_TEST_SESSION_OLD } from './__fixtures__/session_old.js'
|
||||||
|
import { convertFromPyrogramSession, convertToPyrogramSession } from './convert.js'
|
||||||
|
|
||||||
|
describe('pyrogram/convert', () => {
|
||||||
|
it('should correctly convert from pyrogram sessions', () => {
|
||||||
|
expect(convertFromPyrogramSession(PYROGRAM_TEST_SESSION_OLD)).toEqual({
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'1674732db80d690b4d5890d887a4bd5b0b4c810b7c331990b049158a940fdaeb' +
|
||||||
|
'd46178f50ddcce753699f0497ad6de9655f454bc5a3030524036dee4ffe3db7c' +
|
||||||
|
'73b526c378a184a0fafa14e9679c170b632e0b412c174f99e96b216214f78263' +
|
||||||
|
'7a42112dd23957a39716d41277318997b6c4d635ffe125f777ff017f6456b9f2' +
|
||||||
|
'7b5557edea774f304118622ff23133b2f961ace33f0d1d836cfdd82a9101192e' +
|
||||||
|
'fe8ab90f6f55e96e9ba3c4fe48bcc5fee8e3e5970040bdb9989b427177e1863a' +
|
||||||
|
'7d6adc5caea9cacc4b34e71cab7a19e52592a81a69d7d261763ffeb0507a990b' +
|
||||||
|
'db5c4c1515e0098571bbb2ee6ff84982e727c31b7ed36ceda5871cd701d51e1f',
|
||||||
|
),
|
||||||
|
primaryDcs: {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
isBot: false,
|
||||||
|
isPremium: false,
|
||||||
|
userId: 5000801609,
|
||||||
|
usernames: [],
|
||||||
|
},
|
||||||
|
testMode: true,
|
||||||
|
version: 3,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly convert to pyrogram sessions', () => {
|
||||||
|
expect(
|
||||||
|
convertToPyrogramSession({
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'1674732db80d690b4d5890d887a4bd5b0b4c810b7c331990b049158a940fdaeb' +
|
||||||
|
'd46178f50ddcce753699f0497ad6de9655f454bc5a3030524036dee4ffe3db7c' +
|
||||||
|
'73b526c378a184a0fafa14e9679c170b632e0b412c174f99e96b216214f78263' +
|
||||||
|
'7a42112dd23957a39716d41277318997b6c4d635ffe125f777ff017f6456b9f2' +
|
||||||
|
'7b5557edea774f304118622ff23133b2f961ace33f0d1d836cfdd82a9101192e' +
|
||||||
|
'fe8ab90f6f55e96e9ba3c4fe48bcc5fee8e3e5970040bdb9989b427177e1863a' +
|
||||||
|
'7d6adc5caea9cacc4b34e71cab7a19e52592a81a69d7d261763ffeb0507a990b' +
|
||||||
|
'db5c4c1515e0098571bbb2ee6ff84982e727c31b7ed36ceda5871cd701d51e1f',
|
||||||
|
),
|
||||||
|
primaryDcs: {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
isBot: false,
|
||||||
|
isPremium: false,
|
||||||
|
userId: 5000801609,
|
||||||
|
usernames: [],
|
||||||
|
},
|
||||||
|
testMode: true,
|
||||||
|
version: 3,
|
||||||
|
}),
|
||||||
|
).toEqual(PYROGRAM_TEST_SESSION_OLD)
|
||||||
|
})
|
||||||
|
})
|
46
packages/convert/src/pyrogram/convert.ts
Normal file
46
packages/convert/src/pyrogram/convert.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { readStringSession, StringSessionData } from '@mtcute/core/utils.js'
|
||||||
|
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||||
|
|
||||||
|
import { DC_MAPPING_PROD, DC_MAPPING_TEST } from '../dcs.js'
|
||||||
|
import { parsePyrogramSession } from './parse.js'
|
||||||
|
import { serializePyrogramSession } from './serialize.js'
|
||||||
|
import { PyrogramSession } from './types.js'
|
||||||
|
|
||||||
|
export function convertFromPyrogramSession(session: PyrogramSession | string): StringSessionData {
|
||||||
|
if (typeof session === 'string') {
|
||||||
|
session = parsePyrogramSession(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 3,
|
||||||
|
testMode: session.isTest,
|
||||||
|
primaryDcs: (session.isTest ? DC_MAPPING_TEST : DC_MAPPING_PROD)[session.dcId],
|
||||||
|
authKey: session.authKey,
|
||||||
|
self: {
|
||||||
|
userId: session.userId,
|
||||||
|
isBot: session.isBot,
|
||||||
|
isPremium: false,
|
||||||
|
usernames: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertToPyrogramSession(
|
||||||
|
session: StringSessionData | string,
|
||||||
|
params?: {
|
||||||
|
apiId?: number
|
||||||
|
},
|
||||||
|
): string {
|
||||||
|
if (typeof session === 'string') {
|
||||||
|
session = readStringSession(__tlReaderMap, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializePyrogramSession({
|
||||||
|
apiId: params?.apiId,
|
||||||
|
isBot: session.self?.isBot ?? false,
|
||||||
|
isTest: session.testMode,
|
||||||
|
userId: session.self?.userId ?? 0,
|
||||||
|
dcId: session.primaryDcs.main.id,
|
||||||
|
authKey: session.authKey,
|
||||||
|
})
|
||||||
|
}
|
48
packages/convert/src/pyrogram/parse.test.ts
Normal file
48
packages/convert/src/pyrogram/parse.test.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { PYROGRAM_TEST_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { PYROGRAM_TEST_SESSION_OLD } from './__fixtures__/session_old.js'
|
||||||
|
import { parsePyrogramSession } from './parse.js'
|
||||||
|
|
||||||
|
describe('pyrogram/parse', () => {
|
||||||
|
it('should correctly parse old sessions', () => {
|
||||||
|
expect(parsePyrogramSession(PYROGRAM_TEST_SESSION_OLD)).toEqual({
|
||||||
|
isBot: false,
|
||||||
|
isTest: true,
|
||||||
|
userId: 5000801609,
|
||||||
|
dcId: 2,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'1674732db80d690b4d5890d887a4bd5b0b4c810b7c331990b049158a940fdaeb' +
|
||||||
|
'd46178f50ddcce753699f0497ad6de9655f454bc5a3030524036dee4ffe3db7c' +
|
||||||
|
'73b526c378a184a0fafa14e9679c170b632e0b412c174f99e96b216214f78263' +
|
||||||
|
'7a42112dd23957a39716d41277318997b6c4d635ffe125f777ff017f6456b9f2' +
|
||||||
|
'7b5557edea774f304118622ff23133b2f961ace33f0d1d836cfdd82a9101192e' +
|
||||||
|
'fe8ab90f6f55e96e9ba3c4fe48bcc5fee8e3e5970040bdb9989b427177e1863a' +
|
||||||
|
'7d6adc5caea9cacc4b34e71cab7a19e52592a81a69d7d261763ffeb0507a990b' +
|
||||||
|
'db5c4c1515e0098571bbb2ee6ff84982e727c31b7ed36ceda5871cd701d51e1f',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly parse new sessions', () => {
|
||||||
|
expect(parsePyrogramSession(PYROGRAM_TEST_SESSION)).toEqual({
|
||||||
|
apiId: 3328759,
|
||||||
|
isBot: false,
|
||||||
|
isTest: true,
|
||||||
|
userId: 5000801609,
|
||||||
|
dcId: 2,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'4e4e8ab2caa290a3f1121c5f5c9a64a0522043fa3c5690a4ff8834b5c1ded2b5' +
|
||||||
|
'425f1df801a0cabda34e95b909399e23037008f220d8908da8a6e89f6ccffb4b' +
|
||||||
|
'6bbd30c767ae37e0e63a5f9177c8f7ec05f032bf5011887b5ce4fc86e7d081cb' +
|
||||||
|
'473fa591bf90a9936187c3bc1a06fc72279a99ab446302eb92391947b246e77a' +
|
||||||
|
'bbe85d24b54776d8bebf6d4969f69fea817a8183cbd9764208b3d4f53a24a1c3' +
|
||||||
|
'65fa4cd3b4958a9f25569cccc550713f59b74162cab00ca68bc5b93efef7a385' +
|
||||||
|
'296710f60cebb3f4877f3f47ad0801c2f3527645738974edc4b257b1a373dec9' +
|
||||||
|
'867b287e084b8481d1777a72040a7cdfb65bef50294b37fca570f8abd5b8b8ff',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
62
packages/convert/src/pyrogram/parse.ts
Normal file
62
packages/convert/src/pyrogram/parse.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// source: https://github.com/pyrogram/pyrogram/blob/master/pyrogram/storage/storage.py
|
||||||
|
|
||||||
|
import { Long } from '@mtcute/core'
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { dataViewFromBuffer, longFromBuffer } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
import { PyrogramSession } from './types.js'
|
||||||
|
|
||||||
|
const SESSION_STRING_SIZE = 351
|
||||||
|
const SESSION_STRING_SIZE_64 = 356
|
||||||
|
|
||||||
|
export function parsePyrogramSession(session: string): PyrogramSession {
|
||||||
|
const data = getPlatform().base64Decode(session, true)
|
||||||
|
const dv = dataViewFromBuffer(data)
|
||||||
|
|
||||||
|
if (session.length === SESSION_STRING_SIZE || session.length === SESSION_STRING_SIZE_64) {
|
||||||
|
// old format
|
||||||
|
// const OLD_SESSION_STRING_FORMAT = '>B?256sI?'
|
||||||
|
// const OLD_SESSION_STRING_FORMAT_64 = '>B?256sQ?'
|
||||||
|
const dcId = dv.getUint8(0)
|
||||||
|
const isTest = dv.getUint8(1) !== 0
|
||||||
|
const authKey = data.subarray(2, 258)
|
||||||
|
|
||||||
|
let userId
|
||||||
|
|
||||||
|
if (data.length === SESSION_STRING_SIZE) {
|
||||||
|
userId = dv.getUint32(258)
|
||||||
|
} else {
|
||||||
|
const high = dv.getUint32(258)
|
||||||
|
const low = dv.getUint32(262)
|
||||||
|
userId = Long.fromBits(low, high).toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBot = dv.getUint8(data.length - 1) !== 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
dcId,
|
||||||
|
isTest,
|
||||||
|
authKey,
|
||||||
|
userId,
|
||||||
|
isBot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// new format
|
||||||
|
// const SESSION_STRING_FORMAT = '>BI?256sQ?'
|
||||||
|
const dcId = dv.getUint8(0)
|
||||||
|
const apiId = dv.getUint32(1)
|
||||||
|
const isTest = dv.getUint8(5) !== 0
|
||||||
|
const authKey = data.subarray(6, 262)
|
||||||
|
const userId = longFromBuffer(data.subarray(262, 270), true, false).toNumber()
|
||||||
|
const isBot = dv.getUint8(270) !== 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
dcId,
|
||||||
|
apiId,
|
||||||
|
isTest,
|
||||||
|
authKey,
|
||||||
|
userId,
|
||||||
|
isBot,
|
||||||
|
}
|
||||||
|
}
|
52
packages/convert/src/pyrogram/serialize.test.ts
Normal file
52
packages/convert/src/pyrogram/serialize.test.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { PYROGRAM_TEST_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { PYROGRAM_TEST_SESSION_OLD } from './__fixtures__/session_old.js'
|
||||||
|
import { serializePyrogramSession } from './serialize.js'
|
||||||
|
|
||||||
|
describe('pyrogram/serialize', () => {
|
||||||
|
it('should correctly serialize old sessions', () => {
|
||||||
|
expect(
|
||||||
|
serializePyrogramSession({
|
||||||
|
isBot: false,
|
||||||
|
isTest: true,
|
||||||
|
userId: 5000801609,
|
||||||
|
dcId: 2,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'1674732db80d690b4d5890d887a4bd5b0b4c810b7c331990b049158a940fdaeb' +
|
||||||
|
'd46178f50ddcce753699f0497ad6de9655f454bc5a3030524036dee4ffe3db7c' +
|
||||||
|
'73b526c378a184a0fafa14e9679c170b632e0b412c174f99e96b216214f78263' +
|
||||||
|
'7a42112dd23957a39716d41277318997b6c4d635ffe125f777ff017f6456b9f2' +
|
||||||
|
'7b5557edea774f304118622ff23133b2f961ace33f0d1d836cfdd82a9101192e' +
|
||||||
|
'fe8ab90f6f55e96e9ba3c4fe48bcc5fee8e3e5970040bdb9989b427177e1863a' +
|
||||||
|
'7d6adc5caea9cacc4b34e71cab7a19e52592a81a69d7d261763ffeb0507a990b' +
|
||||||
|
'db5c4c1515e0098571bbb2ee6ff84982e727c31b7ed36ceda5871cd701d51e1f',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
).toEqual(PYROGRAM_TEST_SESSION_OLD)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly parse new sessions', () => {
|
||||||
|
expect(
|
||||||
|
serializePyrogramSession({
|
||||||
|
apiId: 3328759,
|
||||||
|
isBot: false,
|
||||||
|
isTest: true,
|
||||||
|
userId: 5000801609,
|
||||||
|
dcId: 2,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'4e4e8ab2caa290a3f1121c5f5c9a64a0522043fa3c5690a4ff8834b5c1ded2b5' +
|
||||||
|
'425f1df801a0cabda34e95b909399e23037008f220d8908da8a6e89f6ccffb4b' +
|
||||||
|
'6bbd30c767ae37e0e63a5f9177c8f7ec05f032bf5011887b5ce4fc86e7d081cb' +
|
||||||
|
'473fa591bf90a9936187c3bc1a06fc72279a99ab446302eb92391947b246e77a' +
|
||||||
|
'bbe85d24b54776d8bebf6d4969f69fea817a8183cbd9764208b3d4f53a24a1c3' +
|
||||||
|
'65fa4cd3b4958a9f25569cccc550713f59b74162cab00ca68bc5b93efef7a385' +
|
||||||
|
'296710f60cebb3f4877f3f47ad0801c2f3527645738974edc4b257b1a373dec9' +
|
||||||
|
'867b287e084b8481d1777a72040a7cdfb65bef50294b37fca570f8abd5b8b8ff',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
).toEqual(PYROGRAM_TEST_SESSION)
|
||||||
|
})
|
||||||
|
})
|
45
packages/convert/src/pyrogram/serialize.ts
Normal file
45
packages/convert/src/pyrogram/serialize.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { Long, MtArgumentError } from '@mtcute/core'
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
import { PyrogramSession } from './types.js'
|
||||||
|
|
||||||
|
const SESSION_STRING_SIZE_OLD = 267
|
||||||
|
const SESSION_STRING_SIZE = 271
|
||||||
|
|
||||||
|
export function serializePyrogramSession(session: PyrogramSession): string {
|
||||||
|
if (session.authKey.length !== 256) {
|
||||||
|
throw new MtArgumentError('authKey must be 256 bytes long')
|
||||||
|
}
|
||||||
|
|
||||||
|
const userIdLong = Long.fromNumber(session.userId, true)
|
||||||
|
|
||||||
|
let u8: Uint8Array
|
||||||
|
|
||||||
|
if (session.apiId === undefined) {
|
||||||
|
// old format
|
||||||
|
u8 = new Uint8Array(SESSION_STRING_SIZE_OLD)
|
||||||
|
const dv = dataViewFromBuffer(u8)
|
||||||
|
|
||||||
|
dv.setUint8(0, session.dcId)
|
||||||
|
dv.setUint8(1, session.isTest ? 1 : 0)
|
||||||
|
u8.set(session.authKey, 2)
|
||||||
|
dv.setUint32(258, userIdLong.high)
|
||||||
|
dv.setUint32(262, userIdLong.low)
|
||||||
|
dv.setUint8(266, session.isBot ? 1 : 0)
|
||||||
|
} else {
|
||||||
|
u8 = new Uint8Array(SESSION_STRING_SIZE)
|
||||||
|
const dv = dataViewFromBuffer(u8)
|
||||||
|
|
||||||
|
dv.setUint8(0, session.dcId)
|
||||||
|
dv.setUint32(1, session.apiId)
|
||||||
|
dv.setUint8(5, session.isTest ? 1 : 0)
|
||||||
|
u8.set(session.authKey, 6)
|
||||||
|
|
||||||
|
dv.setUint32(262, userIdLong.high)
|
||||||
|
dv.setUint32(266, userIdLong.low)
|
||||||
|
dv.setUint8(270, session.isBot ? 1 : 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPlatform().base64Encode(u8, true)
|
||||||
|
}
|
8
packages/convert/src/pyrogram/types.ts
Normal file
8
packages/convert/src/pyrogram/types.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export interface PyrogramSession {
|
||||||
|
apiId?: number
|
||||||
|
dcId: number
|
||||||
|
isTest: boolean
|
||||||
|
authKey: Uint8Array
|
||||||
|
userId: number
|
||||||
|
isBot: boolean
|
||||||
|
}
|
22
packages/convert/src/telethon/__fixtures__/generate.py
Normal file
22
packages/convert/src/telethon/__fixtures__/generate.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
from telethon.sync import TelegramClient
|
||||||
|
from telethon.sessions import StringSession
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
client = TelegramClient(
|
||||||
|
StringSession(),
|
||||||
|
api_id=int(os.environ["API_ID"]),
|
||||||
|
api_hash=os.environ["API_HASH"],
|
||||||
|
)
|
||||||
|
client.session.set_dc(2, '149.154.167.40', 80)
|
||||||
|
await client.start(
|
||||||
|
phone='9996621234',
|
||||||
|
code_callback=lambda: '22222'
|
||||||
|
)
|
||||||
|
session = client.session.save()
|
||||||
|
open("session.ts", "w").write(
|
||||||
|
'export const TELETHON_TEST_SESSION = \'' + session + '\'\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.get_event_loop().run_until_complete(main())
|
26
packages/convert/src/telethon/__fixtures__/generate_v6.py
Normal file
26
packages/convert/src/telethon/__fixtures__/generate_v6.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from telethon.sync import TelegramClient
|
||||||
|
from telethon.sessions import StringSession
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
client = TelegramClient(
|
||||||
|
StringSession(),
|
||||||
|
api_id=int(os.environ["API_ID"]),
|
||||||
|
api_hash=os.environ["API_HASH"],
|
||||||
|
use_ipv6=True
|
||||||
|
)
|
||||||
|
client.session.set_dc(1, '2001:0b28:f23d:f001:0000:0000:0000:000e', 443)
|
||||||
|
await client.start(
|
||||||
|
phone='9996611234',
|
||||||
|
code_callback=lambda: '11111'
|
||||||
|
)
|
||||||
|
session = client.session.save()
|
||||||
|
open("session_v6.ts", "w").write(
|
||||||
|
'export const TELETHON_TEST_SESSION_V6 = \'' + session + '\'\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.get_event_loop().run_until_complete(main())
|
2
packages/convert/src/telethon/__fixtures__/session.ts
Normal file
2
packages/convert/src/telethon/__fixtures__/session.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const TELETHON_TEST_SESSION =
|
||||||
|
'1ApWapygAUChJS1_xwUK01Is4cOvQa1JKTn1POabdMUCfLmXNYFUyvG3v9Z_qbFNFp3zYP--3aVpTYI2DpB2Ib46p_bwSC0j1QEjvdQxJj26cVj8NfslrCkYrdV3glOhdczSq08kp31eqBGXMPhA7wy7DOcSLLAoy-Jf3Q_V_Q3y2a8_64ArFJe8PFfSqkdO56VQutajNLscFUtTQXUQFLJ7ft6vIl__UOc9tpQZEiFW7jWmID79WkfYLHFjuChTVKGMLDa8YcZj6z5Sq-pXPE9VbAbJ5L1JRqXOey3QGtZgJeIEww_WWD5nMMUfhLIydD2i7eDmVoUE5EIZPpsevJmjiGLw4vJk='
|
2
packages/convert/src/telethon/__fixtures__/session_v6.ts
Normal file
2
packages/convert/src/telethon/__fixtures__/session_v6.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const TELETHON_TEST_SESSION_V6 =
|
||||||
|
'1ASABCyjyPfABAAAAAAAAAA4Bu4pveAFWSE51_trKsrRQeMvGXMl8fI6NsGaWqdrXXeqyaXne9qNthqnrBmH56kHfOhFUCPSoVzNNrGgnQr67AYQbkhpP_Yml2EDd8epdc6Gywh4q2NBgYyW6VBT8UKg89-FebYTO6n47I1cJMGsSZ1ddxEOpIpHXsSmPdGBSTz6uaHbLYo0jnxd59PQn4H4dKb8FxuOQsUVa3vY_o79HMVMQRVT1IksUKFg5gAe5ZJ0yx6W4pMviVbC-TYZC0HInmv2fFMv-S3rQyg1C7qpU-Gbo1P6UZC4KZGmu2pMJooFNyfRbFgl3BI5Z-FNx9TKu4UFrF9G6Q0l8PjPXOZm4j-c='
|
72
packages/convert/src/telethon/convert.test.ts
Normal file
72
packages/convert/src/telethon/convert.test.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { TELETHON_TEST_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { convertFromTelethonSession, convertToTelethonSession } from './convert.js'
|
||||||
|
|
||||||
|
describe('telethon/convert', () => {
|
||||||
|
it('should correctly convert from telethon sessions', () => {
|
||||||
|
expect(convertFromTelethonSession(TELETHON_TEST_SESSION)).toEqual({
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'28494b5ff1c142b4d48b3870ebd06b524a4e7d4f39a6dd31409f2e65cd605532' +
|
||||||
|
'bc6deff59fea6c5345a77cd83fefb7695a53608d83a41d886f8ea9fdbc120b48' +
|
||||||
|
'f54048ef750c498f6e9c563f0d7ec96b0a462b755de094e85d7334aad3c929df' +
|
||||||
|
'57aa0465cc3e103bc32ec339c48b2c0a32f897f743f57f437cb66bcffae00ac5' +
|
||||||
|
'25ef0f15f4aa91d3b9e9542eb5a8cd2ec70552d4d05d44052c9edfb7abc897ff' +
|
||||||
|
'd439cf6da506448855bb8d69880fbf5691f60b1c58ee0a14d528630b0daf1871' +
|
||||||
|
'98facf94aafa95cf13d55b01b2792f5251a9739ecb7406b59809788130c3f596' +
|
||||||
|
'0f99cc3147e12c8c9d0f68bb783995a1413910864fa6c7af2668e218bc38bc99',
|
||||||
|
),
|
||||||
|
primaryDcs: {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 80,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testMode: true,
|
||||||
|
version: 3,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly convert to telethon sessions', () => {
|
||||||
|
expect(
|
||||||
|
convertToTelethonSession({
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'28494b5ff1c142b4d48b3870ebd06b524a4e7d4f39a6dd31409f2e65cd605532' +
|
||||||
|
'bc6deff59fea6c5345a77cd83fefb7695a53608d83a41d886f8ea9fdbc120b48' +
|
||||||
|
'f54048ef750c498f6e9c563f0d7ec96b0a462b755de094e85d7334aad3c929df' +
|
||||||
|
'57aa0465cc3e103bc32ec339c48b2c0a32f897f743f57f437cb66bcffae00ac5' +
|
||||||
|
'25ef0f15f4aa91d3b9e9542eb5a8cd2ec70552d4d05d44052c9edfb7abc897ff' +
|
||||||
|
'd439cf6da506448855bb8d69880fbf5691f60b1c58ee0a14d528630b0daf1871' +
|
||||||
|
'98facf94aafa95cf13d55b01b2792f5251a9739ecb7406b59809788130c3f596' +
|
||||||
|
'0f99cc3147e12c8c9d0f68bb783995a1413910864fa6c7af2668e218bc38bc99',
|
||||||
|
),
|
||||||
|
primaryDcs: {
|
||||||
|
main: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 80,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
id: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
ipv6: false,
|
||||||
|
port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testMode: true,
|
||||||
|
version: 3,
|
||||||
|
}),
|
||||||
|
).toEqual(TELETHON_TEST_SESSION)
|
||||||
|
})
|
||||||
|
})
|
46
packages/convert/src/telethon/convert.ts
Normal file
46
packages/convert/src/telethon/convert.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { BasicDcOption, readStringSession, StringSessionData } from '@mtcute/core/utils.js'
|
||||||
|
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||||
|
|
||||||
|
import { isTestDc } from '../dcs.js'
|
||||||
|
import { parseTelethonSession } from './parse.js'
|
||||||
|
import { serializeTelethonSession } from './serialize.js'
|
||||||
|
import { TelethonSession } from './types.js'
|
||||||
|
|
||||||
|
export function convertFromTelethonSession(session: TelethonSession | string): StringSessionData {
|
||||||
|
if (typeof session === 'string') {
|
||||||
|
session = parseTelethonSession(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dc: BasicDcOption = {
|
||||||
|
id: session.dcId,
|
||||||
|
ipAddress: session.ipAddress,
|
||||||
|
port: session.port,
|
||||||
|
ipv6: session.ipv6,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 3,
|
||||||
|
// we don't exactly have that information. try to deduce it from DC_MAPPING_TEST
|
||||||
|
// todo: we should maybe check this at connect?
|
||||||
|
testMode: isTestDc(session.ipAddress),
|
||||||
|
primaryDcs: {
|
||||||
|
main: dc,
|
||||||
|
media: dc,
|
||||||
|
},
|
||||||
|
authKey: session.authKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertToTelethonSession(session: StringSessionData | string): string {
|
||||||
|
if (typeof session === 'string') {
|
||||||
|
session = readStringSession(__tlReaderMap, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializeTelethonSession({
|
||||||
|
dcId: session.primaryDcs.main.id,
|
||||||
|
ipAddress: session.primaryDcs.main.ipAddress,
|
||||||
|
port: session.primaryDcs.main.port,
|
||||||
|
ipv6: session.primaryDcs.main.ipv6 ?? false,
|
||||||
|
authKey: session.authKey,
|
||||||
|
})
|
||||||
|
}
|
47
packages/convert/src/telethon/parse.test.ts
Normal file
47
packages/convert/src/telethon/parse.test.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { TELETHON_TEST_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { TELETHON_TEST_SESSION_V6 } from './__fixtures__/session_v6.js'
|
||||||
|
import { parseTelethonSession } from './parse.js'
|
||||||
|
|
||||||
|
describe('telethon/parse', () => {
|
||||||
|
it('should correctly parse ipv4 sessions', () => {
|
||||||
|
expect(parseTelethonSession(TELETHON_TEST_SESSION)).toEqual({
|
||||||
|
dcId: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 80,
|
||||||
|
ipv6: false,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'28494b5ff1c142b4d48b3870ebd06b524a4e7d4f39a6dd31409f2e65cd605532' +
|
||||||
|
'bc6deff59fea6c5345a77cd83fefb7695a53608d83a41d886f8ea9fdbc120b48' +
|
||||||
|
'f54048ef750c498f6e9c563f0d7ec96b0a462b755de094e85d7334aad3c929df' +
|
||||||
|
'57aa0465cc3e103bc32ec339c48b2c0a32f897f743f57f437cb66bcffae00ac5' +
|
||||||
|
'25ef0f15f4aa91d3b9e9542eb5a8cd2ec70552d4d05d44052c9edfb7abc897ff' +
|
||||||
|
'd439cf6da506448855bb8d69880fbf5691f60b1c58ee0a14d528630b0daf1871' +
|
||||||
|
'98facf94aafa95cf13d55b01b2792f5251a9739ecb7406b59809788130c3f596' +
|
||||||
|
'0f99cc3147e12c8c9d0f68bb783995a1413910864fa6c7af2668e218bc38bc99',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly parse ipv6 sessions', () => {
|
||||||
|
expect(parseTelethonSession(TELETHON_TEST_SESSION_V6)).toEqual({
|
||||||
|
dcId: 1,
|
||||||
|
ipAddress: '2001:0b28:f23d:f001:0000:0000:0000:000e',
|
||||||
|
port: 443,
|
||||||
|
ipv6: true,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'8a6f780156484e75fedacab2b45078cbc65cc97c7c8e8db06696a9dad75deab2' +
|
||||||
|
'6979def6a36d86a9eb0661f9ea41df3a115408f4a857334dac682742bebb0184' +
|
||||||
|
'1b921a4ffd89a5d840ddf1ea5d73a1b2c21e2ad8d0606325ba5414fc50a83cf7' +
|
||||||
|
'e15e6d84ceea7e3b235709306b1267575dc443a92291d7b1298f7460524f3eae' +
|
||||||
|
'6876cb628d239f1779f4f427e07e1d29bf05c6e390b1455adef63fa3bf473153' +
|
||||||
|
'104554f5224b142858398007b9649d32c7a5b8a4cbe255b0be4d8642d072279a' +
|
||||||
|
'fd9f14cbfe4b7ad0ca0d42eeaa54f866e8d4fe94642e0a6469aeda9309a2814d' +
|
||||||
|
'c9f45b160977048e59f85371f532aee1416b17d1ba43497c3e33d73999b88fe7',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
38
packages/convert/src/telethon/parse.ts
Normal file
38
packages/convert/src/telethon/parse.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
import { parseIpFromBytes } from '../utils/ip.js'
|
||||||
|
import { TelethonSession } from './types.js'
|
||||||
|
|
||||||
|
export function parseTelethonSession(session: string): TelethonSession {
|
||||||
|
if (session[0] !== '1') {
|
||||||
|
// version
|
||||||
|
throw new MtArgumentError(`Invalid session string (version = ${session[0]})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
session = session.slice(1)
|
||||||
|
|
||||||
|
const data = getPlatform().base64Decode(session, true)
|
||||||
|
const dv = dataViewFromBuffer(data)
|
||||||
|
|
||||||
|
const dcId = dv.getUint8(0)
|
||||||
|
|
||||||
|
const ipSize = session.length === 352 ? 4 : 16
|
||||||
|
let pos = 1 + ipSize
|
||||||
|
|
||||||
|
const ipBytes = data.subarray(1, pos)
|
||||||
|
const port = dv.getUint16(pos)
|
||||||
|
pos += 2
|
||||||
|
const authKey = data.subarray(pos, pos + 256)
|
||||||
|
|
||||||
|
const ip = parseIpFromBytes(ipBytes)
|
||||||
|
|
||||||
|
return {
|
||||||
|
dcId,
|
||||||
|
ipAddress: ip,
|
||||||
|
ipv6: ipSize === 16,
|
||||||
|
port,
|
||||||
|
authKey,
|
||||||
|
}
|
||||||
|
}
|
51
packages/convert/src/telethon/serialize.test.ts
Normal file
51
packages/convert/src/telethon/serialize.test.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
import { TELETHON_TEST_SESSION } from './__fixtures__/session.js'
|
||||||
|
import { TELETHON_TEST_SESSION_V6 } from './__fixtures__/session_v6.js'
|
||||||
|
import { serializeTelethonSession } from './serialize.js'
|
||||||
|
|
||||||
|
describe('telethon/serialize', () => {
|
||||||
|
it('should correctly serialize ipv4 sessions', () => {
|
||||||
|
expect(
|
||||||
|
serializeTelethonSession({
|
||||||
|
dcId: 2,
|
||||||
|
ipAddress: '149.154.167.40',
|
||||||
|
port: 80,
|
||||||
|
ipv6: false,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'28494b5ff1c142b4d48b3870ebd06b524a4e7d4f39a6dd31409f2e65cd605532' +
|
||||||
|
'bc6deff59fea6c5345a77cd83fefb7695a53608d83a41d886f8ea9fdbc120b48' +
|
||||||
|
'f54048ef750c498f6e9c563f0d7ec96b0a462b755de094e85d7334aad3c929df' +
|
||||||
|
'57aa0465cc3e103bc32ec339c48b2c0a32f897f743f57f437cb66bcffae00ac5' +
|
||||||
|
'25ef0f15f4aa91d3b9e9542eb5a8cd2ec70552d4d05d44052c9edfb7abc897ff' +
|
||||||
|
'd439cf6da506448855bb8d69880fbf5691f60b1c58ee0a14d528630b0daf1871' +
|
||||||
|
'98facf94aafa95cf13d55b01b2792f5251a9739ecb7406b59809788130c3f596' +
|
||||||
|
'0f99cc3147e12c8c9d0f68bb783995a1413910864fa6c7af2668e218bc38bc99',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
).toEqual(TELETHON_TEST_SESSION)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly serialize ipv6 sessions', () => {
|
||||||
|
expect(
|
||||||
|
serializeTelethonSession({
|
||||||
|
dcId: 1,
|
||||||
|
ipAddress: '2001:0b28:f23d:f001:0000:0000:0000:000e',
|
||||||
|
port: 443,
|
||||||
|
ipv6: true,
|
||||||
|
authKey: getPlatform().hexDecode(
|
||||||
|
'8a6f780156484e75fedacab2b45078cbc65cc97c7c8e8db06696a9dad75deab2' +
|
||||||
|
'6979def6a36d86a9eb0661f9ea41df3a115408f4a857334dac682742bebb0184' +
|
||||||
|
'1b921a4ffd89a5d840ddf1ea5d73a1b2c21e2ad8d0606325ba5414fc50a83cf7' +
|
||||||
|
'e15e6d84ceea7e3b235709306b1267575dc443a92291d7b1298f7460524f3eae' +
|
||||||
|
'6876cb628d239f1779f4f427e07e1d29bf05c6e390b1455adef63fa3bf473153' +
|
||||||
|
'104554f5224b142858398007b9649d32c7a5b8a4cbe255b0be4d8642d072279a' +
|
||||||
|
'fd9f14cbfe4b7ad0ca0d42eeaa54f866e8d4fe94642e0a6469aeda9309a2814d' +
|
||||||
|
'c9f45b160977048e59f85371f532aee1416b17d1ba43497c3e33d73999b88fe7',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
).toEqual(TELETHON_TEST_SESSION_V6)
|
||||||
|
})
|
||||||
|
})
|
37
packages/convert/src/telethon/serialize.ts
Normal file
37
packages/convert/src/telethon/serialize.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
|
import { serializeIpv4ToBytes, serializeIpv6ToBytes } from '../utils/ip.js'
|
||||||
|
import { TelethonSession } from './types.js'
|
||||||
|
|
||||||
|
export function serializeTelethonSession(session: TelethonSession) {
|
||||||
|
if (session.authKey.length !== 256) {
|
||||||
|
throw new MtArgumentError('authKey must be 256 bytes long')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipSize = session.ipv6 ? 16 : 4
|
||||||
|
const u8 = new Uint8Array(259 + ipSize)
|
||||||
|
const dv = dataViewFromBuffer(u8)
|
||||||
|
|
||||||
|
dv.setUint8(0, session.dcId)
|
||||||
|
|
||||||
|
let pos
|
||||||
|
|
||||||
|
if (session.ipv6) {
|
||||||
|
serializeIpv6ToBytes(session.ipAddress, u8.subarray(1, 17))
|
||||||
|
pos = 17
|
||||||
|
} else {
|
||||||
|
serializeIpv4ToBytes(session.ipAddress, u8.subarray(1, 5))
|
||||||
|
pos = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
dv.setUint16(pos, session.port)
|
||||||
|
pos += 2
|
||||||
|
u8.set(session.authKey, pos)
|
||||||
|
|
||||||
|
let b64 = getPlatform().base64Encode(u8, true)
|
||||||
|
while (b64.length % 4 !== 0) b64 += '=' // for some reason telethon uses padding
|
||||||
|
|
||||||
|
return '1' + b64
|
||||||
|
}
|
7
packages/convert/src/telethon/types.ts
Normal file
7
packages/convert/src/telethon/types.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export interface TelethonSession {
|
||||||
|
dcId: number
|
||||||
|
ipAddress: string
|
||||||
|
ipv6: boolean
|
||||||
|
port: number
|
||||||
|
authKey: Uint8Array
|
||||||
|
}
|
0
packages/convert/src/types.ts
Normal file
0
packages/convert/src/types.ts
Normal file
48
packages/convert/src/utils/ip.ts
Normal file
48
packages/convert/src/utils/ip.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
|
|
||||||
|
export function parseIpFromBytes(data: Uint8Array): string {
|
||||||
|
if (data.length === 4) {
|
||||||
|
return `${data[0]}.${data[1]}.${data[2]}.${data[3]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length === 16) {
|
||||||
|
let res = ''
|
||||||
|
|
||||||
|
for (let i = 0; i < 16; i += 2) {
|
||||||
|
res += data[i].toString(16).padStart(2, '0')
|
||||||
|
res += data[i + 1].toString(16).padStart(2, '0')
|
||||||
|
if (i < 14) res += ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MtArgumentError('Invalid IP address length')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeIpv4ToBytes(ip: string, buf: Uint8Array) {
|
||||||
|
const parts = ip.split('.')
|
||||||
|
|
||||||
|
if (parts.length !== 4) {
|
||||||
|
throw new MtArgumentError('Invalid IPv4 address')
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[0] = Number(parts[0])
|
||||||
|
buf[1] = Number(parts[1])
|
||||||
|
buf[2] = Number(parts[2])
|
||||||
|
buf[3] = Number(parts[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeIpv6ToBytes(ip: string, buf: Uint8Array) {
|
||||||
|
const parts = ip.split(':')
|
||||||
|
|
||||||
|
if (parts.length !== 8) {
|
||||||
|
throw new MtArgumentError('Invalid IPv6 address')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
const val = parseInt(parts[i], 16)
|
||||||
|
buf[i * 2] = val >> 8
|
||||||
|
buf[i * 2 + 1] = val & 0xff
|
||||||
|
}
|
||||||
|
}
|
56
packages/convert/src/utils/rle.ts
Normal file
56
packages/convert/src/utils/rle.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// tdlib's RLE only encodes consecutive \x00
|
||||||
|
|
||||||
|
export function telegramRleEncode(buf: Uint8Array): Uint8Array {
|
||||||
|
const len = buf.length
|
||||||
|
const ret: number[] = []
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const cur = buf[i]
|
||||||
|
|
||||||
|
if (cur === 0) {
|
||||||
|
count += 1
|
||||||
|
} else {
|
||||||
|
if (count > 0) {
|
||||||
|
ret.push(0, count)
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.push(cur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
ret.push(0, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function telegramRleDecode(buf: Uint8Array): Uint8Array {
|
||||||
|
const len = buf.length
|
||||||
|
const ret: number[] = []
|
||||||
|
let prev = -1
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const cur = buf[i]
|
||||||
|
|
||||||
|
if (prev === 0) {
|
||||||
|
for (let j = 0; j < cur; j++) {
|
||||||
|
ret.push(prev)
|
||||||
|
}
|
||||||
|
prev = -1
|
||||||
|
} else {
|
||||||
|
if (prev !== -1) ret.push(prev)
|
||||||
|
prev = cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev !== -1) ret.push(prev)
|
||||||
|
|
||||||
|
return new Uint8Array(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertNever(_: never): never {
|
||||||
|
throw new Error('unreachable')
|
||||||
|
}
|
13
packages/convert/tsconfig.json
Normal file
13
packages/convert/tsconfig.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/esm",
|
||||||
|
"rootDir": "./src",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src",
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../core" },
|
||||||
|
],
|
||||||
|
}
|
4
packages/convert/typedoc.cjs
Normal file
4
packages/convert/typedoc.cjs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ['../../.config/typedoc/config.base.cjs'],
|
||||||
|
entryPoints: ['./src/index.ts'],
|
||||||
|
}
|
|
@ -115,6 +115,19 @@ importers:
|
||||||
specifier: 0.34.6
|
specifier: 0.34.6
|
||||||
version: 0.34.6(@vitest/browser@0.34.6)(@vitest/ui@0.34.6)(playwright@1.40.1)
|
version: 0.34.6(@vitest/browser@0.34.6)(@vitest/ui@0.34.6)(playwright@1.40.1)
|
||||||
|
|
||||||
|
packages/convert:
|
||||||
|
dependencies:
|
||||||
|
'@mtcute/core':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../core
|
||||||
|
'@mtcute/tl':
|
||||||
|
specifier: '*'
|
||||||
|
version: link:../tl
|
||||||
|
devDependencies:
|
||||||
|
'@mtcute/test':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../test
|
||||||
|
|
||||||
packages/core:
|
packages/core:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mtcute/file-id':
|
'@mtcute/file-id':
|
||||||
|
|
Loading…
Reference in a new issue