Session conversion #20
55 changed files with 1581 additions and 1 deletions
|
@ -2,6 +2,7 @@
|
|||
"extends": "../tsconfig.json",
|
||||
"exclude": [
|
||||
"../**/*.test.ts",
|
||||
"../**/*.test-utils.ts"
|
||||
"../**/*.test-utils.ts",
|
||||
"../**/__fixtures__/**",
|
||||
]
|
||||
}
|
||||
|
|
46
packages/convert/README.md
Normal file
46
packages/convert/README.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# @mtcute/convert
|
||||
|
||||
📖 [API Reference](https://ref.mtcute.dev/modules/_mtcute_convert.html)
|
||||
|
||||
This package can be used to convert other libraries sessions to/from mtcute sessions
|
||||
|
||||
Currently only the libraries that support exporting sessions to strings are supported, namely:
|
||||
|
||||
## [Telethon](https://github.com/LonamiWebs/Telethon)
|
||||
|
||||
> Telethon v2 seems to have removed the ability to export sessions,
|
||||
> so it's currently not supported
|
||||
|
||||
```ts
|
||||
import { convertFromTelethonSession } from '@mtcute/convert'
|
||||
|
||||
const client = new TelegramClient({ ... })
|
||||
await client.importSession(convertFromTelethonSession("..."))
|
||||
```
|
||||
|
||||
## [Pyrogram](https://github.com/gram-js/gramjs)
|
||||
|
||||
```ts
|
||||
import { convertFromPyrogramSession } from '@mtcute/convert'
|
||||
|
||||
const client = new TelegramClient({ ... })
|
||||
await client.importSession(convertFromPyrogramSession("..."))
|
||||
```
|
||||
|
||||
## GramJS
|
||||
|
||||
```ts
|
||||
import { convertFromGramjsSession } from '@mtcute/convert'
|
||||
|
||||
const client = new TelegramClient({ ... })
|
||||
await client.importSession(convertFromGramjsSession("..."))
|
||||
```
|
||||
|
||||
## [MTKruto](https://github.com/MTKruto/MTKruto)
|
||||
|
||||
```ts
|
||||
import { convertFromMtkrutoSession } from '@mtcute/convert'
|
||||
|
||||
const client = new TelegramClient({ ... })
|
||||
await client.importSession(convertFromMtkrutoSession("..."))
|
||||
```
|
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,
|
||||
})
|
||||
}
|
4
packages/convert/src/gramjs/index.ts
Normal file
4
packages/convert/src/gramjs/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './convert.js'
|
||||
export * from './parse.js'
|
||||
export * from './serialize.js'
|
||||
export * from './types.js'
|
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
|
||||
}
|
4
packages/convert/src/index.ts
Normal file
4
packages/convert/src/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './gramjs/index.js'
|
||||
export * from './mtkruto/index.js'
|
||||
export * from './pyrogram/index.js'
|
||||
export * from './telethon/index.js'
|
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,
|
||||
})
|
||||
}
|
4
packages/convert/src/mtkruto/index.ts
Normal file
4
packages/convert/src/mtkruto/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './convert.js'
|
||||
export * from './parse.js'
|
||||
export * from './serialize.js'
|
||||
export * from './types.js'
|
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,
|
||||
})
|
||||
}
|
4
packages/convert/src/pyrogram/index.ts
Normal file
4
packages/convert/src/pyrogram/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './convert.js'
|
||||
export * from './parse.js'
|
||||
export * from './serialize.js'
|
||||
export * from './types.js'
|
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,
|
||||
})
|
||||
}
|
4
packages/convert/src/telethon/index.ts
Normal file
4
packages/convert/src/telethon/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './convert.js'
|
||||
export * from './parse.js'
|
||||
export * from './serialize.js'
|
||||
export * from './types.js'
|
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
|
||||
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:
|
||||
dependencies:
|
||||
'@mtcute/file-id':
|
||||
|
|
Loading…
Reference in a new issue