瀏覽代碼

Merge pull request #3 from gram-js/tl

TL methods
painor 5 年之前
父節點
當前提交
da224768fb
共有 4 個文件被更改,包括 214 次插入2 次删除
  1. 45 0
      tl/mtprotoRequest.js
  2. 52 0
      tl/session.js
  3. 112 0
      tl/telegramClient.js
  4. 5 2
      utils/Helpers.js

+ 45 - 0
tl/mtprotoRequest.js

@@ -0,0 +1,45 @@
+class MTProtoRequest {
+
+    constructor() {
+        this.sent = false;
+        this.msgId = 0; //long
+        this.sequence = 0;
+
+        this.dirty = false;
+        this.sendTime = 0;
+        this.confirmReceived = false;
+
+        // These should be overrode
+
+        this.constructorId = 0;
+        this.confirmed = false;
+        this.responded = false;
+    }
+
+    // these should not be overrode
+    onSendSuccess() {
+        this.sendTime = new Date().getTime();
+        this.sent = true;
+    }
+
+    onConfirm() {
+        this.confirmReceived = true;
+    }
+
+    needResend(){
+        return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000);
+    }
+
+    // These should be overrode
+    onSend(buffer){
+
+    }
+
+    onResponse(buffer){
+
+    }
+
+    onException(exception){
+
+    }
+}

+ 52 - 0
tl/session.js

@@ -0,0 +1,52 @@
+const Helpers = require("../utils/Helpers");
+const fs = require("fs");
+
+class Session {
+    constructor(sessionUserId) {
+        this.sessionUserId = sessionUserId;
+        this.serverAddress = "149.154.167.40";
+        this.port = 80;
+        this.authKey = undefined;
+        this.id = Helpers.generateRandomLong(false);
+        this.sequence = 0;
+        this.salt = 0; // Unsigned long
+        this.timeOffset = 0;
+        this.lastMessageId = 0;
+        this.user = undefined;
+    }
+
+    /**
+     * Saves the current session object as session_user_id.session
+     */
+    async save() {
+        if (this.sessionUserId) {
+            await fs.writeFile(`${this.sessionUserId}.session`, JSON.stringify(this));
+        }
+    }
+
+    static tryLoadOrCreateNew(sessionUserId) {
+        if (sessionUserId === undefined) {
+            return new Session();
+        }
+        let filepath = `${sessionUserId}.session`;
+        if (fs.existsSync(filepath)) {
+            return JSON.parse(fs.readFileSync(filepath, "utf-8"));
+        } else {
+            return Session(sessionUserId);
+        }
+    }
+
+    getNewMsgId() {
+        let msTime = new Date().getTime();
+        let newMessageId = (BigInt(Math.floor(msTime / 1000) + this.timeOffset) << BigInt(32)) |
+            ((msTime % 1000) << 22) |
+            (Helpers.getRandomInt(0, 524288) << 2); // 2^19
+
+        if (this.lastMessageId >= newMessageId) {
+            newMessageId = this.lastMessageId + 4;
+        }
+        this.lastMessageId = newMessageId;
+        return newMessageId;
+    }
+}
+

+ 112 - 0
tl/telegramClient.js

@@ -0,0 +1,112 @@
+const Session = require("./Session");
+
+class TelegramClient {
+
+    async constructor(sessionUserId, layer, apiId, apiHash) {
+        if (apiId === undefined || apiHash === undefined) {
+            throw Error("Your API ID or Hash are invalid. Please read \"Requirements\" on README.md");
+        }
+        this.apiId = apiId;
+        this.apiHash = apiHash;
+
+        this.layer = layer;
+
+        this.session = Session.tryLoadOrCreateNew(sessionUserId);
+        this.transport = TcpTransport(this.session.serverAddress, this.session.port);
+
+        //These will be set later
+        this.dcOptions = undefined;
+        this.sender = undefined;
+        this.phoneCodeHashes = Array();
+
+    }
+
+    /**
+     * Connects to the Telegram servers, executing authentication if required.
+     * Note that authenticating to the Telegram servers is not the same as authenticating
+     * the app, which requires to send a code first.
+     * @param reconnect {Boolean}
+     * @returns {Promise<Boolean>}
+     */
+    async connect(reconnect = false) {
+        try {
+            if (!this.session.authKey || reconnect) {
+                let res = network.authenticator.doAuthentication(this.transport);
+                this.session.authKey = res.authKey;
+                this.session.timeOffset = res.timeOffset;
+                this.session.save();
+            }
+            this.sender = MtProtoSender(this.transport, this.session);
+
+            // Now it's time to send an InitConnectionRequest
+            // This must always be invoked with the layer we'll be using
+            let query = InitConnectionRequest({
+                apiId: apiId,
+                deviceModel: "PlaceHolder",
+                systemVersion: "PlaceHolder",
+                appVersion: "0.0.1",
+                langCode: "en",
+                query: GetConfigRequest()
+            });
+            let result = await this.invoke(InvokeWithLayerRequest({
+                layer: this.layer,
+                query: query
+            }));
+
+            // We're only interested in the DC options
+            // although many other options are available!
+            this.dcOptions = result.dcOptions;
+            return true;
+        } catch (error) {
+            console.log('Could not stabilise initial connection: {}'.replace("{}", error));
+            return false;
+        }
+    }
+
+    /**
+     * Reconnects to the specified DC ID. This is automatically called after an InvalidDCError is raised
+     * @param dc_id {number}
+     */
+    async reconnect_to_dc(dc_id) {
+
+        if (this.dcOptions === undefined || this.dcOptions.length === 0) {
+            throw new Error("Can't reconnect. Stabilise an initial connection first.");
+        }
+        let dc;
+        for (dc of this.dcOptions) {
+            if (dc.id === dc_id) {
+                break;
+            }
+        }
+        this.transport.close();
+        this.transport = new TcpTransport(dc.ipAddress, dc.port);
+        this.session.server_address = dc.ipAddress;
+        this.session.port = dc.port;
+        this.session.save();
+        await this.connect(true);
+    }
+
+    /**
+     * Disconnects from the Telegram server
+     * @returns {Promise<void>}
+     */
+    async disconnect() {
+        if (this.sender) {
+            await this.sender.disconnect();
+        }
+    }
+
+    /**
+     * Invokes a MTProtoRequest (sends and receives it) and returns its result
+     * @param request
+     * @returns {Promise}
+     */
+    async invoke(request) {
+        if (!MTProtoRequest.prototype.isPrototypeOf(Object.getPrototypeOf(request).prototype)) {
+            throw new Error("You can only invoke MtProtoRequests");
+        }
+        await this.sender.send(request);
+        await this.sender.receive(request);
+        return request.result;
+    }
+}

+ 5 - 2
utils/Helpers.js

@@ -8,9 +8,12 @@ class Helpers {
      * Generates a random long integer (8 bytes), which is optionally signed
      * @returns {number}
      */
-    static generateRandomLong() {
+    static generateRandomLong(signed) {
         let buf = Buffer.from(this.generateRandomBytes(8)); // 0x12345678 = 305419896
-        return buf.readUInt32BE(0);
+        if (signed)
+            return buf.readInt32BE(0);
+        else
+            return buf.readUInt32LE(0);
     }
 
     /**