Browse Source

Add async Queue
Add default args
Fix bugs
More code refactoring

painor 5 years ago
parent
commit
abe1b365bc

+ 39 - 0
examples/main.js

@@ -0,0 +1,39 @@
+const {TelegramClient} = require('../gramjs');
+const log4js = require('log4js');
+const {InputPeerChat} = require('../gramjs/tl/types');
+const {SendMessageRequest} = require('../gramjs/tl/functions/messages');
+const {ResolveUsernameRequest} = require('../gramjs/tl/functions/contacts');
+const logger = log4js.getLogger('gramjs');
+
+logger.level = 'debug';
+
+const inputPeer = new InputPeerChat({
+    chatId: 400319287,
+    accessHash: 4770003194588524965n,
+});
+const message = new SendMessageRequest({
+    peer: inputPeer,
+    message: 'hi',
+    randomId: 5,
+});
+console.log(message.bytes.toString('hex'));
+
+(async () => {
+    console.log('Loading interactive example...');
+    const sessionName = 'anon';
+    const apiId = 17349;
+    const apiHash = '344583e45741c457fe1862106095a5eb';
+    const client = new TelegramClient(sessionName, apiId, apiHash);
+    await client.connect();
+
+
+    client._authorized = true;
+
+    const message = new SendMessageRequest({
+        peer: inputPeer,
+        message: 'hi from GramJS',
+    });
+    const r = await client.invoke(message);
+    console.log(r);
+    console.log('You should now be connected.');
+})();

+ 145 - 0
gramjs/client/TelegramClient.js

@@ -0,0 +1,145 @@
+const log4js = require("log4js");
+const Session = require("../sessions/Session");
+const os = require('os');
+const {GetConfigRequest} = require("../tl/functions/help");
+const {LAYER} = require("../tl/alltlobjects");
+const {functions} = require("../tl");
+const MTProtoSender = require("../network/MTProtoSender");
+const {ConnectionTCPFull} = require("../network/connection/TCPFull");
+DEFAULT_DC_ID = 4;
+DEFAULT_IPV4_IP = '149.154.167.51';
+DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]';
+DEFAULT_PORT = 443;
+
+class TelegramClient {
+
+    static DEFAULT_OPTIONS = {
+        connection: ConnectionTCPFull,
+        useIPV6: false,
+        proxy: null,
+        timeout: 10,
+        requestRetries: 5,
+        connectionRetries: 5,
+        retryDelay: 1,
+        autoReconnect: true,
+        sequentialUpdates: false,
+        FloodSleepLimit: 60,
+        deviceModel: null,
+        systemVersion: null,
+        appVersion: null,
+        langCode: 'en',
+        systemLangCode: 'en',
+        baseLogger: null
+    };
+
+    constructor(sessionName, apiId, apiHash, opts = TelegramClient.DEFAULT_OPTIONS) {
+        if (apiId === undefined || apiHash === undefined) {
+            throw Error("Your API ID or Hash are invalid. Please read \"Requirements\" on README.md");
+        }
+        const args = {...TelegramClient.DEFAULT_OPTIONS, ...opts};
+        this.apiId = apiId;
+        this.apiHash = apiHash;
+        this._useIPV6 = args.useIPV6;
+
+        if (typeof args.baseLogger == 'string') {
+            this._log = log4js.getLogger(args.baseLogger);
+        } else {
+            this._log = args.baseLogger;
+        }
+        const session = Session.tryLoadOrCreateNew(sessionName);
+
+        if (!session.serverAddress || (session.serverAddress.includes(":") !== this._useIPV6)) {
+            session.setDc(DEFAULT_DC_ID, this._useIPV6 ? DEFAULT_IPV6_IP : DEFAULT_IPV4_IP, DEFAULT_PORT)
+        }
+        this.FloodSleepLimit = args.FloodSleepLimit;
+
+        this.session = session;
+        //this._entityCache = EntityCache();
+        this.apiId = parseInt(apiId);
+        this.apiHash = apiHash;
+
+        this._requestRetries = args.requestRetries;
+        this._connectionRetries = args.connectionRetries;
+        this._retryDelay = args.retryDelay || 0;
+        if (args.proxy) {
+            this._log.warn("proxies are not supported");
+        }
+        this._proxy = args.proxy;
+        this._timeout = args.timeout;
+        this._autoReconnect = args.autoReconnect;
+
+        this._connection = args.connection;
+        //TODO add proxy support
+
+
+        this._initWith = (x) => {
+            return new functions.InvokeWithLayerRequest({
+                layer: LAYER,
+                query: new functions.InitConnectionRequest({
+                    apiId: this.apiId,
+                    deviceModel: args.deviceModel | os.type() | "Unkown",
+                    systemVersion: args.systemVersion | os.release() | '1.0',
+                    appVersion: args.appVersion | this.__version__,
+                    langCode: args.langCode,
+                    langPack: "", //this should be left empty.
+                    systemLangCode: args.systemVersion,
+                    query: x,
+                    proxy: null, // no proxies yet.
+                })
+            })
+        };
+        //These will be set later
+        this._sender = new MTProtoSender(this.session.authKey, {
+            logger: this._log,
+        });
+        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.
+     * @returns {Promise<void>}
+     */
+    async connect() {
+        const connection = new this._connection(this.session.serverAddress, this.session.port, this.session.dcId, this._log);
+        if (!await this._sender.connect(connection)) {
+            return;
+        }
+        this.session.authKey = this._sender.authKey;
+        await this.session.save();
+        await this._sender.send(this._initWith(
+            new GetConfigRequest()
+        ));
+
+    }
+
+
+    /**
+     * 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 (!(request instanceof TLRequest)) {
+            throw new Error("You can only invoke MTProtoRequests");
+        }
+        return this._sender.send(request);
+    }
+
+
+}
+
+module.exports = TelegramClient;

+ 33 - 0
gramjs/extensions/AsyncQueue.js

@@ -0,0 +1,33 @@
+class AsyncQueue {
+
+    constructor() {
+        this._queue = [];
+        this.canGet = new Promise((resolve) => {
+            this.resolveGet = resolve
+        });
+        this.canPush = true
+    }
+
+    async push(value) {
+        await this.canPush;
+        this._queue.push(value);
+        this.resolveGet(true);
+        this.canPush = new Promise((resolve) => {
+            console.log(this);
+            this.resolvePush = resolve
+        })
+    }
+
+    async pop() {
+        await this.canGet;
+        const returned = this._queue.pop();
+        this.resolvePush(true);
+        this.canGet = new Promise((resolve) => {
+            this.resolveGet = resolve
+        });
+        return returned
+    }
+
+}
+
+module.exports = AsyncQueue;

+ 26 - 27
gramjs/extensions/MessagePacker.js

@@ -1,35 +1,37 @@
-const Helpers = require('../utils/Helpers');
-const MessageContainer = require('../tl/core/MessageContainer');
-const TLMessage = require('../tl/core/TLMessage');
-const { TLRequest } = require('../tl/tlobject');
-const BinaryWriter = require('../extensions/BinaryWriter');
+const MessageContainer = require("../tl/core/MessageContainer");
+const TLMessage = require("../tl/core/TLMessage");
+const {TLRequest} = require("../tl/tlobject");
+const BinaryWriter = require("../extensions/BinaryWriter");
+
 
 class MessagePacker {
     constructor(state, logger) {
         this._state = state;
         this._queue = [];
-        this._ready = false;
+        this._ready = new Promise((resolve => {
+            this.setReady = resolve;
+        }));
         this._log = logger;
     }
 
     append(state) {
         this._queue.push(state);
-        this._ready = true;
+        this.setReady(true);
     }
 
     extend(states) {
         for (const state of states) {
             this._queue.push(state);
         }
-        this._ready = true;
+        this.setReady(true);
     }
 
     async get() {
         if (!this._queue.length) {
-            this._ready = false;
-            while (!this._ready) {
-                await Helpers.sleep(100);
-            }
+            this._ready = new Promise((resolve => {
+                this.setReady = resolve;
+            }));
+            await this._ready;
         }
         let data;
         const buffer = new BinaryWriter(Buffer.alloc(0));
@@ -46,9 +48,7 @@ class MessagePacker {
                     afterId = state.after.msgId;
                 }
                 state.msgId = await this._state.writeDataAsMessage(
-                    buffer,
-                    state.data,
-                    state.request instanceof TLRequest,
+                    buffer, state.data, state.request instanceof TLRequest,
                     afterId
                 );
 
@@ -60,31 +60,30 @@ class MessagePacker {
                 this._queue.unshift(state);
                 break;
             }
-            this._log.warn(
-                `Message payload for ${state.request.constructor.name} is too long ${state.data.length} and cannot be sent`
-            );
-            state.promise.reject('Request Payload is too big');
+            this._log.warn(`Message payload for ${state.request.constructor.name} is too long ${state.data.length} and cannot be sent`);
+            state.promise.reject("Request Payload is too big");
             size = 0;
-            continue;
+            continue
         }
         if (!batch.length) {
             return null;
         }
         if (batch.length > 1) {
-            data = Buffer.concat([
-                struct.pack('<Ii', MessageContainer.CONSTRUCTOR_ID, batch.length),
-                buffer.getValue(),
-            ]);
+            data = Buffer.concat([struct.pack(
+                '<Ii', MessageContainer.CONSTRUCTOR_ID, batch.length
+            ), buffer.getValue()]);
 
-            const containerId = await this._state.writeDataAsMessage(buffer, data, false);
+            const containerId = await this._state.writeDataAsMessage(
+                buffer, data, false
+            );
             for (const s of batch) {
                 s.containerId = containerId;
             }
         }
 
         data = buffer.getValue();
-        return { batch, data };
+        return {batch, data}
     }
 }
 
-module.exports = MessagePacker;
+module.exports = MessagePacker;

+ 4 - 0
gramjs/index.js

@@ -0,0 +1,4 @@
+const TelegramClient = require("./client/TelegramClient");
+module.exports = {
+    TelegramClient
+};

+ 10 - 21
gramjs/network/connection/Connection.js

@@ -1,7 +1,7 @@
 const { PromiseSocket } = require('promise-socket');
 const { Socket } = require('net');
 const Helpers = require('../../utils/Helpers');
-
+const AsyncQueue = require("../../extensions/AsyncQueue");
 /**
  * The `Connection` class is a wrapper around ``asyncio.open_connection``.
  *
@@ -28,8 +28,8 @@ class Connection {
         this._recvTask = null;
         this._codec = null;
         this._obfuscation = null; // TcpObfuscated and MTProxy
-        this._sendArray = [];
-        this._recvArray = [];
+        this._sendArray = new AsyncQueue();
+        this._recvArray = new AsyncQueue();
         this.socket = new PromiseSocket(new Socket());
     }
 
@@ -50,25 +50,19 @@ class Connection {
 
     async disconnect() {
         this._connected = false;
-        this.socket.close();
+        await this.socket.end();
     }
 
     async send(data) {
         if (!this._connected) {
             throw new Error('Not connected');
         }
-        while (this._sendArray.length !== 0) {
-            await Helpers.sleep(1000);
-        }
-        this._sendArray.push(data);
+        await this._sendArray.push(data);
     }
 
     async recv() {
         while (this._connected) {
-            while (this._recvArray.length === 0) {
-                await Helpers.sleep(1000);
-            }
-            const result = this._recvArray.pop();
+            const result = await this._recvArray.pop();
 
             // null = sentinel value = keep trying
             if (result) {
@@ -82,10 +76,8 @@ class Connection {
         // TODO handle errors
         try {
             while (this._connected) {
-                while (this._sendArray.length === 0) {
-                    await Helpers.sleep(1000);
-                }
-                await this._send(this._sendArray.pop());
+
+                await this._send(await this._sendArray.pop());
             }
         } catch (e) {
             console.log(e);
@@ -102,11 +94,7 @@ class Connection {
                 console.log(e);
                 this._log.info('The server closed the connection');
             }
-            while (this._recvArray.length !== 0) {
-                await Helpers.sleep(1000);
-            }
-
-            this._recvArray.push(data);
+            await this._recvArray.push(data);
         }
     }
 
@@ -123,6 +111,7 @@ class Connection {
 
     async _recv() {
         return await this._codec.readPacket(this.socket);
+
     }
 
     toString() {

+ 0 - 0
gramjs/tl/Session.js → gramjs/sessions/Session.js


+ 0 - 117
gramjs/tl/TelegramClient.js

@@ -1,117 +0,0 @@
-const Session = require('./Session');
-const MtProtoSender = require('../network/mtprotoSender');
-const { ImportBotAuthorizationRequest } = require('./functions/auth');
-const { ConnectionTCPFull } = require('../network/connection/TCPFull');
-const { TLRequest } = require('./tlobject');
-const { InvokeWithLayerRequest, InitConnectionRequest } = require('./functions/index');
-const { GetConfigRequest } = require('./functions/help');
-const { LAYER } = require('../tl/alltlobjects');
-const log4js = require('log4js');
-
-class TelegramClient {
-    constructor(sessionUserId, apiId, apiHash, connection = ConnectionTCPFull) {
-        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._connection = ConnectionTCPFull;
-        this._log = log4js.getLogger('gramjs');
-        this._initWith = (x) => {
-            return new InvokeWithLayerRequest({
-                layer: LAYER,
-                query: new InitConnectionRequest({
-                    apiId: this.apiId,
-                    deviceModel: 'Windows',
-                    systemVersion: '1.8.3',
-                    appVersion: '1.8',
-                    langCode: 'en',
-                    langPack: '',
-                    systemLangCode: 'en',
-                    query: x,
-                    proxy: null,
-                }),
-            });
-        };
-        this.session = Session.tryLoadOrCreateNew(sessionUserId);
-        // These will be set later
-        this.dcOptions = null;
-        this._sender = new MtProtoSender(this.session.authKey, {
-            logger: this._log,
-        });
-        this.phoneCodeHashes = [];
-    }
-
-    /**
-     * 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.
-     * @returns {Promise<void>}
-     */
-    async connect() {
-        const connection = new this._connection(
-            this.session.serverAddress,
-            this.session.port,
-            this.session.dcId,
-            this._log
-        );
-        if (!(await this._sender.connect(connection))) {
-            return;
-        }
-        this.session.authKey = this._sender.authKey;
-        await this.session.save();
-        await this._sender.send(this._initWith(new GetConfigRequest()));
-    }
-
-    /**
-     * 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 (!(request instanceof TLRequest)) {
-            throw new Error('You can only invoke MTProtoRequests');
-        }
-        const res = await this._sender.send(request);
-        return res;
-    }
-
-    /**
-     * Logs in to Telegram to an existing user or bot account.
-
-     You should only use this if you are not authorized yet.
-
-     This method will send the code if it's not provided.
-
-     .. note::
-
-     In most cases, you should simply use `start()` and not this method.
-
-     * @param args {{botToken: string}}
-     * @returns {Promise<void>}
-     */
-    async signIn(args = { phone: null, code: null, password: null, botToken: null, phoneCodeHash: null }) {
-        const botToken = args.botToken;
-        const request = new ImportBotAuthorizationRequest({
-            flags: 0,
-            botAuthToken: botToken,
-            apiId: this.apiId,
-            apiHash: this.apiHash,
-        });
-        const result = await this.invoke(request);
-        return result;
-    }
-}
-
-module.exports = TelegramClient;

+ 0 - 49
main.js

@@ -1,49 +0,0 @@
-const TelegramClient = require('./gramjs/tl/TelegramClient');
-const log4js = require('log4js');
-const { InputPeerChannel } = require('./gramjs/tl/types');
-const { SendMessageRequest } = require('./gramjs/tl/functions/messages');
-const { ResolveUsernameRequest } = require('./gramjs/tl/functions/contacts');
-const logger = log4js.getLogger('gramjs');
-
-logger.level = 'debug';
-
-const inputPeer = new InputPeerChannel({
-    channelId: 1180212174,
-    accessHash: BigInt('548480552819456668'),
-});
-const message = new SendMessageRequest({
-    peer: inputPeer,
-    message: 'hi',
-    randomId: 5,
-});
-console.log(message.bytes.toString('hex'));
-
-(async () => {
-    console.log('Loading interactive example...');
-    const sessionName = 'anon';
-    const apiId = 0;
-    const apiHash = '';
-    const client = new TelegramClient(sessionName, apiId, apiHash);
-    await client.connect();
-    // let request = new GetConfigRequest();
-    // let res =         await client._sender.send(new GetConfigRequest());
-    // console.log(res)
-    const res = await client.signIn({ botToken: '' });
-    const user = res.user;
-    client._authorized = true;
-    const result = await client.invoke(
-        new ResolveUsernameRequest({
-            username: 'gramjschat',
-        })
-    );
-    console.log(result);
-
-    const message = new SendMessageRequest({
-        peer: inputPeer,
-        message: 'hi from GramJS',
-    });
-    console.log(message);
-    const r = await client.invoke(message);
-    console.log(r);
-    console.log('You should now be connected.', user);
-})();