UNPKG

@tgsnake/core

Version:

Pure Telegram MTProto library for nodejs

572 lines (571 loc) 26.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Session = exports.Results = void 0; const platform_node_js_1 = require("../platform.node.js"); const Logger_js_1 = require("../Logger.js"); const connection_js_1 = require("../connection/connection.js"); const index_js_1 = require("../raw/index.js"); const Mtproto = __importStar(require("../crypto/Mtproto.js")); const Errors = __importStar(require("../errors/index.js")); const MsgId_js_1 = require("./internals/MsgId.js"); const MsgFactory_js_1 = require("./internals/MsgFactory.js"); const helpers_js_1 = require("../helpers.js"); const Timeout_js_1 = require("../Timeout.js"); const Auth_js_1 = require("./Auth.js"); class Results { value; reject; resolve; constructor() { this.value = new Promise((resolve, reject) => { this.reject = reject; this.resolve = resolve; }); } } exports.Results = Results; class Session { START_TIMEOUT = 2000; WAIT_TIMEOUT = 15000; SLEEP_THRESHOLD = 10000; MAX_RETRIES; ACKS_THRESHOLD = 8; PING_INTERVAL = 5000; _dcId; _authKey; _testMode; _proxy; _isMedia; _isCdn; _authKeyId; _connection; _pingTask; _client; _sessionId = platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes(8)); _msgFactory = (0, MsgFactory_js_1.MsgFactory)(); _msgId = new MsgId_js_1.MsgId(); _salt = BigInt(0); _storedMsgId = []; _results = new Map(); _isConnected = false; _pendingAcks = new Set(); _task = new Timeout_js_1.Timeout(); _networkTask = true; _mutex = new platform_node_js_1.Mutex(); constructor(client, dcId, authKey, testMode, proxy, isMedia = false, isCdn = false) { this._client = client; this._dcId = dcId; this._authKey = authKey; this._testMode = testMode; this._proxy = proxy; this._isMedia = isMedia; this._isCdn = isCdn; this._authKeyId = platform_node_js_1.crypto.createHash('sha1').update(this._authKey).digest().subarray(-8); this.MAX_RETRIES = client._maxRetries ?? 5; } async _handlePacket(packet) { Logger_js_1.Logger.debug(`[33] Unpacking ${platform_node_js_1.Buffer.byteLength(packet)} bytes packet.`); try { const data = await Mtproto.unpack(new index_js_1.BytesIO(packet), this._sessionId, this._authKey, this._authKeyId, this._storedMsgId); const message = data.body instanceof index_js_1.MsgContainer ? data.body.messages : [data]; Logger_js_1.Logger.debug(`[34] Reveive ${message.length} data.`); for (const msg of message) { if (msg.seqNo % 2 === 0) { Logger_js_1.Logger.debug(`[35] Setting server time: ${msg.msgId / BigInt(2 ** 32)}.`); this._msgId.setServerTime(msg.msgId / BigInt(2 ** 32)); } else { if (this._pendingAcks.has(msg.msgId)) { Logger_js_1.Logger.debug(`[36] Skiping pending acks msg id: ${msg.msgId}.`); continue; } else { Logger_js_1.Logger.debug(`[37] Add msg id ${msg.msgId} to pending acks.`); this._pendingAcks.add(msg.msgId); } } if (msg.body instanceof index_js_1.Raw.MsgDetailedInfo || msg.body instanceof index_js_1.Raw.MsgNewDetailedInfo) { Logger_js_1.Logger.debug(`[38] Got ${msg.body.constructor.name} and adding to pending acks: ${msg.body.answerMsgId}.`); this._pendingAcks.add(msg.body.answerMsgId); continue; } if (msg.body instanceof index_js_1.Raw.NewSessionCreated) { Logger_js_1.Logger.debug(`[39] Got ${msg.body.constructor.name} and skiping.`); continue; } let msgId; if (msg.body instanceof index_js_1.Raw.BadMsgNotification || msg.body instanceof index_js_1.Raw.BadServerSalt) { Logger_js_1.Logger.debug(`[40] Got ${msg.body.constructor.name} and msg id is: ${msg.body.badMsgId}.`); msgId = msg.body.badMsgId; if (msg.body instanceof index_js_1.Raw.BadServerSalt) { this._salt = msg.body.newServerSalt; } } else if (msg.body instanceof index_js_1.Raw.FutureSalts || msg.body instanceof index_js_1.Raw.RpcResult) { Logger_js_1.Logger.debug(`[41] Got ${msg.body.constructor.name} and msg id is: ${msg.body.reqMsgId}.`); msgId = msg.body.reqMsgId; if (msg.body instanceof index_js_1.Raw.RpcResult) { msg.body; msg.body = msg.body.result; } } else if (msg.body instanceof index_js_1.Raw.Pong) { Logger_js_1.Logger.debug(`[42] Got ${msg.body.constructor.name} and msg id is: ${msg.body.msgId}.`); msgId = msg.body.msgId; } else { Logger_js_1.Logger.debug(`[43] Handling update ${msg.body.constructor.name}.`); this._client.handleUpdate(msg.body); } if (msgId !== undefined) { const promises = this._results.get(BigInt(msgId)); if (promises !== undefined) { Logger_js_1.Logger.debug(`[44] Setting results of msg id ${msgId} with ${msg.body.constructor.name}.`); promises.resolve(msg.body); } } } if (this._pendingAcks.size >= this.ACKS_THRESHOLD) { Logger_js_1.Logger.debug(`[45] Sending ${this._pendingAcks.size} pending aks.`); try { await this._send(new index_js_1.Raw.MsgsAck({ msgIds: Array.from(this._pendingAcks), }), false); Logger_js_1.Logger.debug(`[46] Clearing all pending acks`); this._pendingAcks.clear(); } catch (error) { if (!(error instanceof Errors.TimeoutError)) { Logger_js_1.Logger.debug(`[47] Clearing all pending acks`); this._pendingAcks.clear(); } Logger_js_1.Logger.error(`[48] Got error when sending pending acks:`, error); } } } catch (error) { if (error instanceof Errors.SecurityCheckMismatch) { Logger_js_1.Logger.error(`[49] Invalid to unpack ${platform_node_js_1.Buffer.byteLength(packet)} bytes packet cause: ${error.description ?? error.message}`); return await this.stop(); } throw error; } } async _send(data, waitResponse = true, timeout = this.WAIT_TIMEOUT) { const msg = await this._msgFactory(data, this._msgId); const msgId = msg.msgId; if (waitResponse) { this._results.set(BigInt(msgId), new Results()); } if (msgId === undefined) { Logger_js_1.Logger.error(`[107] Can't send request ${data.className} when msgId is undefined.`); return; } Logger_js_1.Logger.debug(`[50] Sending msg id ${msgId} (${data.className}), has ${platform_node_js_1.Buffer.byteLength(msg.write())} bytes message.`); const payload = Mtproto.pack(msg, this._salt, this._sessionId, this._authKey, this._authKeyId); try { Logger_js_1.Logger.debug(`[51] Sending ${platform_node_js_1.Buffer.byteLength(payload)} bytes payload.`); await this._connection.send(payload); } catch (error) { Logger_js_1.Logger.error(`[52] Got error when trying to send ${platform_node_js_1.Buffer.byteLength(payload)} bytes payload:`, error); if (error instanceof Errors.WSError.ReadClosed || error instanceof Errors.WSError.Disconnected || error instanceof Errors.ClientError.ClientDisconnected) { Logger_js_1.Logger.debug(`[108] Restarting client due to disconnected`); if (this._client._maxReconnectRetries) { return this.retriesReconnect(); } else { this.restart(); } return; } const promises = this._results.get(BigInt(msgId)); if (promises) { promises.reject(error); } } const promises = this._results.get(BigInt(msgId)); if (waitResponse && promises !== undefined) { let response; try { response = await this._task.run(promises.value, timeout); } catch (error) { Logger_js_1.Logger.error(`[53] Got error when waiting response:`, error); } if (response) { this._results.delete(BigInt(msgId)); Logger_js_1.Logger.debug(`[54] Got response from msg id ${msgId}: ${response.constructor.name}`); if (response instanceof index_js_1.Raw.RpcError) { if (data instanceof index_js_1.Raw.InvokeWithoutUpdates || data instanceof index_js_1.Raw.InvokeWithTakeout || data instanceof index_js_1.Raw.InvokeWithTakeout || data instanceof index_js_1.Raw.InvokeWithBusinessConnection || data instanceof index_js_1.Raw.InvokeWithGooglePlayIntegrity || data instanceof index_js_1.Raw.InvokeWithApnsSecret || data instanceof index_js_1.Raw.InvokeWithMessagesRange) { data = data.query; } await Errors.RPCError.raise(response, data); } else if (response instanceof index_js_1.Raw.BadMsgNotification) { throw new Errors.BadMsgNotification(response.errorCode); } else if (response instanceof index_js_1.Raw.BadServerSalt) { this._salt = response.newServerSalt; return await this._send(data, waitResponse, timeout); } else { return response; } } else { throw new Errors.TimeoutError(timeout); } } } _pingWorker() { const ping = async () => { try { if (!this._isConnected) return; Logger_js_1.Logger.debug(`[55] Ping to telegram server.`); await this._send(new index_js_1.Raw.PingDelayDisconnect({ pingId: BigInt(0), disconnectDelay: this.WAIT_TIMEOUT + 10000, }), false); } catch (error) { Logger_js_1.Logger.error(`[56] Get error when trying ping to telegram :`, error); } return this._pingWorker(); }; this._pingTask = setTimeout(ping, this.PING_INTERVAL); return this._pingTask; } async _networkWorker() { Logger_js_1.Logger.debug(`[57] Network worker started.`); let waiting = false; while (true) { if (!this._networkTask) { Logger_js_1.Logger.debug(`[58] Network worker ended`); return; } if (!waiting) { try { const packet = await this._connection.recv(); if (packet !== undefined && platform_node_js_1.Buffer.byteLength(packet) !== 4) { waiting = true; const release = await this._mutex.acquire(); try { await this._handlePacket(packet); } finally { release(); } waiting = false; } else { if (packet) { Logger_js_1.Logger.warning(`[59] Server sent "${packet.readInt32LE(0)}"`); } if (this._isConnected) { return this.restart(); } } } catch (error) { Logger_js_1.Logger.error('[139] Network worker error:', error); if (!this._isConnected) { break; } else if ((error instanceof Errors.WSError.ReadClosed || error instanceof Errors.WSError.Disconnected || error instanceof Errors.ClientError.ClientDisconnected) && this._client._maxReconnectRetries) { return this.retriesReconnect(); } else { throw error; } } } } } async retriesReconnect(retries = this._client._maxReconnectRetries) { try { Logger_js_1.Logger.info('[136] Reconnecting to Telegram Server.'); Logger_js_1.Logger.debug('[137] Stop ping task.'); clearTimeout(this._pingTask); this._isConnected = false; await this._connection.close().catch(() => { }); await this._connection.connect(); this._networkWorker(); const isInited = await this.initConnection(); if (isInited) { this._isConnected = true; this._pingWorker(); if (!this._client._storage.isBot && this._client._takeout) { const takeout = await this.invoke(new index_js_1.Raw.account.InitTakeoutSession({})); this._client._takeoutId = takeout.id; } await this.invoke(new index_js_1.Raw.updates.GetState()); const me = await this.invoke(new index_js_1.Raw.users.GetFullUser({ id: new index_js_1.Raw.InputUserSelf(), })); this._client._me = me; return me; } } catch (e) { Logger_js_1.Logger.error(`[138] Got error when trying to reconnecting to Telegram Server, retries ${retries}:`, e); if (!retries) { throw e; } } return this.retriesReconnect(retries - 1); } async stop() { const release = await this._mutex.acquire(); try { this._networkTask = false; this._isConnected = false; clearTimeout(this._pingTask); await this._connection.close().catch(() => { }); this._results.clear(); this._task.clear(); Logger_js_1.Logger.info(`[60] Session stopped.`); } finally { release(); } } restart() { try { Logger_js_1.Logger.debug(`[61] Restarting client`); this.stop(); this.start(); } catch (_error) { } } async invoke(data, retries = this.MAX_RETRIES, timeout = this.WAIT_TIMEOUT, sleepThreshold = this.SLEEP_THRESHOLD) { Logger_js_1.Logger.debug(`[62] Invoking ${data.className} with parameters: ${retries} retries, ${timeout}ms timeout, ${sleepThreshold}ms sleep threshold.`); if (!this._isConnected) { Logger_js_1.Logger.error(`[63] Can't sending request when client is unconnected.`); throw new Errors.ClientError.ClientDisconnected(); } if (data.classType !== 'functions') { throw new Errors.NotAFunctionClass(data.className); } let className = data.className; if (data instanceof index_js_1.Raw.InvokeWithLayer || data instanceof index_js_1.Raw.InvokeWithoutUpdates || data instanceof index_js_1.Raw.InvokeWithTakeout || data instanceof index_js_1.Raw.InvokeWithBusinessConnection || data instanceof index_js_1.Raw.InvokeWithGooglePlayIntegrity || data instanceof index_js_1.Raw.InvokeWithApnsSecret || data instanceof index_js_1.Raw.InvokeWithMessagesRange) { className = data.query.className; } while (true) { if (this._isConnected) { try { const response = await this._send(data, true, timeout); if (response !== undefined) { return response; } await (0, helpers_js_1.sleep)(1000); } catch (error) { Logger_js_1.Logger.error(`[64] Got error when trying invoking ${className}:`, error); if (error instanceof Errors.Exceptions.Flood.FloodWait) { error; const amount = Number(error.value ?? 2000); if (amount > sleepThreshold >= 0) { throw error; } Logger_js_1.Logger.info(`[65] Waiting for ${amount} seconds before continuing (caused by ${className})`); await (0, helpers_js_1.sleep)(amount); } else if ((error instanceof Errors.Exceptions.SeeOther.FileMigrate || error instanceof Errors.Exceptions.SeeOther.StatsMigrate || error instanceof Errors.Exceptions.SeeOther.NetworkMigrate) && typeof error.value !== 'undefined') { Logger_js_1.Logger.error(`[156] Got error when trying invoking ${className}: ${error.message}. Try to reconnecting.`); const exportedAuthKey = (await this.invoke(new index_js_1.Raw.auth.ExportAuthorization({ dcId: error.value }))); const newAuthKey = await new Auth_js_1.Auth(error.value, this._testMode, this._client._ipv6).create(); const newSession = new Session(this._client, error.value, newAuthKey, this._testMode, this._proxy, this._isMedia, this._isCdn); Logger_js_1.Logger.debug(`[157] Reconnecting to telegram server`); await newSession.start(); Logger_js_1.Logger.debug(`[158] Importing auth key`); await newSession.invoke(new index_js_1.Raw.auth.ImportAuthorization({ id: exportedAuthKey.id, bytes: exportedAuthKey.bytes, })); Logger_js_1.Logger.debug(`[159] Session imported, resend the query`); const result = await newSession.invoke(data, retries, timeout, sleepThreshold); Logger_js_1.Logger.debug(`[160] Closing session in DC${error.value}`); await newSession.stop(); return result; } else { if (!retries) { throw error; } if (retries < 2) { Logger_js_1.Logger.info(`[66] [${this.MAX_RETRIES - retries + 1}] Retrying "${className}" due to ${error.message}`); } else { Logger_js_1.Logger.info(`[67] [${this.MAX_RETRIES - retries + 1}] Retrying "${className}" due to ${error.message}`, error); } await (0, helpers_js_1.sleep)(500); return await this.invoke(data, retries - 1, timeout, sleepThreshold); } } } else { throw new Errors.ClientError.ClientDisconnected(); } } } async start() { while (true) { this._connection = new connection_js_1.Connection(this._dcId, this._testMode, this._client._ipv6, this._proxy, this._isMedia, this._client._connectionMode, this._client._local); this._networkTask = true; try { Logger_js_1.Logger.debug(`[68] Connecting to telegram server`); await this._connection.connect(); this._networkWorker(); await this.initConnection(); Logger_js_1.Logger.info(`[69] Session initialized: Layer ${index_js_1.Raw.Layer}`); Logger_js_1.Logger.info(`[70] Device: ${this._client._deviceModel} - ${this._client._appVersion}`); Logger_js_1.Logger.info(`[71] System: ${this._client._systemVersion} (${this._client._langCode.toUpperCase()})`); Logger_js_1.Logger.info(`[135] Getting Update State`); this._pingWorker(); this._isConnected = true; Logger_js_1.Logger.info('[72] Session Started'); break; } catch (error) { if (error instanceof Errors.Exceptions.NotAcceptable.AuthKeyDuplicated) { await this.stop(); throw error; } else if (error instanceof Errors.TimeoutError || error instanceof Errors.RPCError) { await (0, helpers_js_1.sleep)(1000); await this.stop(); } else { break; } } } } async initConnection() { const ping = await this._send(new index_js_1.Raw.Ping({ pingId: BigInt(0), }), true, this.START_TIMEOUT); if (!this._isCdn) { const initData = await this._send(new index_js_1.Raw.InvokeWithLayer({ layer: index_js_1.Raw.Layer, query: new index_js_1.Raw.InitConnection({ apiId: this._client._apiId, appVersion: this._client._appVersion, deviceModel: this._client._deviceModel, systemVersion: this._client._systemVersion, systemLangCode: this._client._systemLangCode, langCode: this._client._langCode, langPack: '', query: new index_js_1.Raw.help.GetConfig(), proxy: this._proxy && 'secret' in this._proxy && 'port' in this._proxy && 'server' in this._proxy ? new index_js_1.Raw.InputClientProxy({ address: this._proxy.server, port: this._proxy.port }) : undefined, }), }), true, this.START_TIMEOUT); return initData; } return ping; } [Symbol.for('nodejs.util.inspect.custom')]() { const toPrint = { _: this.constructor.name, }; for (const key in this) { if (Object.prototype.hasOwnProperty.call(this, key)) { const value = this[key]; if (!key.startsWith('_') && value !== undefined && value !== null) { toPrint[key] = value; } } } return toPrint; } [Symbol.for('Deno.customInspect')]() { return String((0, platform_node_js_1.inspect)(this[Symbol.for('nodejs.util.inspect.custom')](), { colors: true })); } toJSON() { const toPrint = { _: this.constructor.name, }; for (const key in this) { if (Object.prototype.hasOwnProperty.call(this, key)) { const value = this[key]; if (!key.startsWith('_') && value !== undefined && value !== null) { if (typeof value === 'bigint') { toPrint[key] = String(value); } else if (Array.isArray(value)) { toPrint[key] = value.map((v) => (typeof v === 'bigint' ? String(v) : v)); } else { toPrint[key] = value; } } } } return toPrint; } toString() { return `[constructor of ${this.constructor.name}] ${JSON.stringify(this, null, 2)}`; } } exports.Session = Session;