Selaa lähdekoodia

stop using mixins

painor 4 vuotta sitten
vanhempi
commit
c0033c6faf
68 muutettua tiedostoa jossa 2456 lisäystä ja 1871 poistoa
  1. 3 4
      .gitignore
  2. 34 14
      examples/main.ts
  3. 1 4
      examples/mainjs.js
  4. 21 1
      gramjs/Helpers.ts
  5. 21 13
      gramjs/Utils.ts
  6. 392 54
      gramjs/client/TelegramClient.ts
  7. 0 15
      gramjs/client/account.ts
  8. 255 269
      gramjs/client/auth.ts
  9. 17 26
      gramjs/client/bots.ts
  10. 66 61
      gramjs/client/buttons.ts
  11. 20 41
      gramjs/client/chats.ts
  12. 0 15
      gramjs/client/dialogs.ts
  13. 3 3
      gramjs/client/downloads.ts
  14. 136 131
      gramjs/client/messageParse.ts
  15. 125 148
      gramjs/client/messages.ts
  16. 9 187
      gramjs/client/telegramBaseClient.ts
  17. 104 110
      gramjs/client/updates.ts
  18. 124 2
      gramjs/client/uploads.ts
  19. 324 336
      gramjs/client/users.ts
  20. 4 4
      gramjs/define.d.ts
  21. 1 1
      gramjs/entityCache.ts
  22. 1 1
      gramjs/errors/Common.ts
  23. 147 0
      gramjs/events/NewMessage.ts
  24. 45 0
      gramjs/events/Raw.ts
  25. 112 9
      gramjs/events/common.ts
  26. 2 0
      gramjs/events/index.ts
  27. 1 1
      gramjs/extensions/BinaryReader.ts
  28. 2 3
      gramjs/extensions/MessagePacker.ts
  29. 65 72
      gramjs/extensions/markdown.ts
  30. 2 2
      gramjs/network/Authenticator.ts
  31. 1 2
      gramjs/network/MTProtoPlainSender.ts
  32. 5 4
      gramjs/network/MTProtoSender.ts
  33. 4 3
      gramjs/network/MTProtoState.ts
  34. 0 2
      gramjs/network/RequestState.ts
  35. 2 2
      gramjs/network/connection/Connection.ts
  36. 1 1
      gramjs/network/connection/TCPAbridged.ts
  37. 1 1
      gramjs/network/connection/TCPFull.ts
  38. 2 2
      gramjs/requestIter.ts
  39. 36 3
      gramjs/sessions/Abstract.ts
  40. 7 5
      gramjs/sessions/Memory.ts
  41. 2 2
      gramjs/sessions/StoreSession.ts
  42. 1 1
      gramjs/tl/AllTLObjects.ts
  43. 201 186
      gramjs/tl/api.d.ts
  44. 1 1
      gramjs/tl/api.js
  45. 2 2
      gramjs/tl/core/GZIPPacked.ts
  46. 1 1
      gramjs/tl/core/MessageContainer.ts
  47. 2 2
      gramjs/tl/core/RPCResult.ts
  48. 1 0
      gramjs/tl/core/TLMessage.ts
  49. 3 3
      gramjs/tl/custom/button.ts
  50. 8 11
      gramjs/tl/custom/chatGetter.ts
  51. 6 5
      gramjs/tl/custom/dialog.ts
  52. 6 6
      gramjs/tl/custom/draft.ts
  53. 1 1
      gramjs/tl/custom/file.ts
  54. 4 5
      gramjs/tl/custom/forward.ts
  55. 1 0
      gramjs/tl/custom/index.ts
  56. 9 12
      gramjs/tl/custom/inlineResult.ts
  57. 5 6
      gramjs/tl/custom/inlineResults.ts
  58. 50 53
      gramjs/tl/custom/message.ts
  59. 5 7
      gramjs/tl/custom/messageButton.ts
  60. 7 3
      gramjs/tl/custom/senderGetter.ts
  61. 1 1
      gramjs/tl/generationHelpers.ts
  62. 4 2
      gramjs/tl/index.ts
  63. 11 5
      gramjs/tl/patched/index.ts
  64. 19 0
      gramjs/tl/types-generator/generate.js
  65. 3 1
      gramjs/tl/types-generator/template.js
  66. 3 0
      npmpublish.bat
  67. 2 2
      package-lock.json
  68. 1 1
      package.json

+ 3 - 4
.gitignore

@@ -3,9 +3,6 @@
 /.idea/
 # Generated code
 /docs/
-/gramjs/tl/functions/
-/gramjs/tl/types/
-/gramjs/tl/patched/
 /dist/
 /coverage/
 
@@ -13,8 +10,10 @@
 *.session
 usermedia/
 
-# Quick tests should live in this file
+# Quick tests should live in these files
 example.js
+example.ts
+example.d.ts
 
 # dotenv
 .env

+ 34 - 14
examples/main.ts

@@ -1,22 +1,42 @@
-import {TelegramClient} from "../gramjs";
-import {StringSession} from "../gramjs/sessions";
+import {Logger} from "telegram/extensions";
+import {TelegramClient} from "telegram";
+import {StringSession} from "telegram/sessions";
+import {NewMessage} from "telegram/events";
+import {NewMessageEvent} from "telegram/events/NewMessage";
 
+const apiId = ;
+const apiHash = '';
+const stringSession = '';
+
+async function eventPrint(event: NewMessageEvent) {
+    const message = event.message;
+
+    // Checks if it's a private message (from user or bot)
+    if (event.isPrivate){
+        // prints sender id
+        console.log(message.senderId);
+        // read message
+        if (message.text=="hello"){
+            const sender = await message.getSender();
+            console.log("sender is",sender);
+            await client.sendMessage(sender,{
+                message:`hi your id is ${message.senderId}`
+            });
+        }
+    }
+}
+const client = new TelegramClient(new StringSession(stringSession), apiId, apiHash, {connectionRetries: 5});
 
 (async () => {
+    Logger.setLevel("debug");
     console.log('Loading interactive example...');
-    const apiId = -1; // put your api id here [for example 123456789]
-    const apiHash = ""; // put your api hash here [for example '123456abcfghe']
-    const client = new TelegramClient(new StringSession(''), apiId, apiHash, {
-        connectionRetries: 3,
-    });
     await client.start({
-        botAuthToken:'YOUR BOT TOKEN'
-
+        botAuthToken: ""
     });
-    console.log('You should now be connected.')
-    console.log(await client.getMe());
-    // USE THIS STRING TO AVOID RELOGGING EACH TIME
-    console.log(await client.session.save());
-
 
+    console.log(await client.getEntity("me"));
+    console.log(client.session.save())
 })();
+
+// adds an event handler for new messages
+client.addEventHandler(eventPrint, new NewMessage({}));

+ 1 - 4
examples/mainjs.js

@@ -1,7 +1,4 @@
-// if you're using TS import from here
-//const {TelegramClient} = require("telegram/gramjs");
-//const {StringSession} = require("telegram/gramjs/sessions");
-// if you're not then import from here
+
 const { TelegramClient } = require('telegram/dist')
 const { StringSession } = require('telegram/dist/sessions');
 

+ 21 - 1
gramjs/Helpers.ts

@@ -1,6 +1,6 @@
 import {isNode} from 'browser-or-node';
 import bigInt from "big-integer";
-import {EntityLike} from "./define";
+import type {EntityLike} from "./define";
 
 export const IS_NODE = isNode;
 const crypto = require(isNode ? 'crypto' : './crypto/crypto');
@@ -286,6 +286,26 @@ export function getByteArray(integer: bigInt.BigInteger | number, signed = false
     return readBufferFromBigInt(typeof integer == "number" ? bigInt(integer) : integer, byteLength, false, signed);
 }
 
+/**
+ * Helper function to return the smaller big int in an array
+ * @param arrayOfBigInts
+ */
+export function getMinBigInt(arrayOfBigInts: bigInt.BigInteger[]) {
+    if (arrayOfBigInts.length == 0) {
+        return bigInt.zero;
+    }
+    if (arrayOfBigInts.length==1){
+        return arrayOfBigInts[0];
+    }
+    let smallest = arrayOfBigInts[0];
+    for (let i=1;i<arrayOfBigInts.length;i++){
+        if (arrayOfBigInts[i]<smallest){
+            smallest = arrayOfBigInts[i];
+        }
+    }
+    return smallest;
+}
+
 /**
  * returns a random int from min (inclusive) and max (inclusive)
  * @param min

+ 21 - 13
gramjs/Utils.ts

@@ -1,11 +1,11 @@
-import {Entity, EntityLike, FileLike} from "./define";
+import type {Entity, EntityLike, FileLike} from "./define";
 import {Api} from "./tl";
 import bigInt from "big-integer";
-import TypeInputPeer = Api.TypeInputPeer;
-import TypeMessageEntity = Api.TypeMessageEntity;
 import * as markdown from "./extensions/markdown"
 import {EntityCache} from "./entityCache";
 import mime from 'mime-types';
+import type {ParseInterface} from "./client/messageParse";
+import  {MarkdownParser} from "./extensions/markdown";
 
 
 const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' +
@@ -44,7 +44,7 @@ function _raiseCastFail(entity: EntityLike, target: any): never {
  * @param allowSelf
  * @param checkHash
  */
-export function getInputPeer(entity: any, allowSelf = true, checkHash = true): TypeInputPeer {
+export function getInputPeer(entity: any, allowSelf = true, checkHash = true): Api.TypeInputPeer {
     if (entity.SUBCLASS_OF_ID === undefined) {
         // e.g. custom.Dialog (can't cyclic import).
         if (allowSelf && 'inputEntity' in entity) {
@@ -147,7 +147,7 @@ export function _photoSizeByteCount(size: FileLike) {
 
 export function _getEntityPair(entityId: number, entities: Map<number, Entity>,
                                cache: EntityCache,
-                               getInputPeerFunction: any = getInputPeer): [Entity?, EntityLike?] {
+                               getInputPeerFunction: any = getInputPeer): [Entity?, Api.TypeInputPeer?] {
 
     const entity = entities.get(entityId);
     let inputEntity = cache.get(entityId);
@@ -158,7 +158,7 @@ export function _getEntityPair(entityId: number, entities: Map<number, Entity>,
 
 }
 
-export function getInnerText(text: string, entities: Map<number, TypeMessageEntity>) {
+export function getInnerText(text: string, entities: Map<number, Api.TypeMessageEntity>) {
 
     const result: string[] = [];
     entities.forEach(function (value, key) {
@@ -182,6 +182,9 @@ export function getInnerText(text: string, entities: Map<number, TypeMessageEnti
  * @returns {InputChannel|*}
  */
 export function getInputChannel(entity: EntityLike) {
+    if (typeof entity==="string" || typeof entity=="number"){
+        _raiseCastFail(entity, 'InputChannel')
+    }
     if (entity.SUBCLASS_OF_ID === undefined) {
         _raiseCastFail(entity, 'InputChannel')
     }
@@ -218,6 +221,10 @@ export function getInputChannel(entity: EntityLike) {
  * @param entity
  */
 export function getInputUser(entity: EntityLike): Api.InputPeerSelf {
+    if (typeof entity==="string" || typeof entity=="number"){
+        _raiseCastFail(entity, 'InputUser')
+    }
+
     if (entity.SUBCLASS_OF_ID === undefined) {
         _raiseCastFail(entity, 'InputUser')
     }
@@ -911,12 +918,14 @@ export function getPeer(peer: EntityLike) {
 
 export const isArrayLike = (<T>(x: any): x is ArrayLike<T> => x && typeof x.length === 'number' && typeof x !== 'function');
 
-export function sanitizeParseMode(mode: string) {
-    if (!mode) {
-        return null;
-    }
+export function sanitizeParseMode(mode: string | ParseInterface): ParseInterface {
     if (mode === "md" || mode === "markdown") {
-        return markdown;
+        return MarkdownParser;
+    }
+    if (typeof mode == "object") {
+        if ("parse" in mode && "unparse" in mode) {
+            return mode;
+        }
     }
     throw new Error(`Invalid parse mode type ${mode}`);
 }
@@ -942,7 +951,6 @@ export function getPeerId(peer: EntityLike, addMark = true): number {
     if (typeof peer == 'number') {
         return addMark ? peer : resolveId(peer)[0]
     }
-
     // Tell the user to use their client to resolve InputPeerSelf if we got one
     if (peer instanceof Api.InputPeerSelf) {
         _raiseCastFail(peer, 'int (you might want to use client.get_peer_id)')
@@ -962,7 +970,7 @@ export function getPeerId(peer: EntityLike, addMark = true): number {
         }
 
         return addMark ? -(peer.chatId) : peer.chatId
-    } else if ("channelId" in peer) { // if (peer instanceof Api.PeerChannel)
+    } else if (typeof peer=="object" && "channelId" in peer) { // if (peer instanceof Api.PeerChannel)
         // Check in case the user mixed things up to avoid blowing up
         if (!(0 < peer.channelId && peer.channelId <= 0x7fffffff)) {
             peer.channelId = resolveId(peer.channelId)[0]

+ 392 - 54
gramjs/client/TelegramClient.ts

@@ -1,60 +1,398 @@
-import {DownloadMethods} from "./downloads";
-import {DialogMethods} from "./dialogs";
-import {TelegramBaseClient, TelegramClientParams} from "./telegramBaseClient";
-import {ButtonMethods} from "./buttons";
-import {UpdateMethods} from "./updates";
-import {MessageMethods} from "./messages";
-import {MessageParseMethods} from "./messageParse";
-import {AuthMethods} from "./auth";
-import {UserMethods} from "./users";
-import {ChatMethods} from "./chats";
-import {Mixin} from "ts-mixer";
-import {Session} from "../sessions/Abstract";
+import {TelegramBaseClient} from "./telegramBaseClient";
+
+import * as authMethods from "./auth";
+import * as botMethods from "./bots";
+import * as buttonsMethods from "./buttons";
+import * as downloadMethods from "./downloads";
+import * as parseMethods from "./messageParse";
+import * as messageMethods from "./messages";
+import * as updateMethods from "./updates";
+import * as uploadMethods from "./uploads";
+import * as userMethods from "./users";
+import type {ButtonLike, EntityLike, MarkupLike} from "../define";
+import {Api} from "../tl";
+import  {sanitizeParseMode} from "../Utils";
+import  {MarkdownParser} from "../extensions/markdown";
+import type  {EventBuilder} from "../events/common";
+import  {MTProtoSender, UpdateConnectionState} from "../network";
+
+import {LAYER} from "../tl/AllTLObjects";
 import {IS_NODE} from "../Helpers";
-import {ConnectionTCPFull, ConnectionTCPObfuscated} from "../network/connection";
-
-// TODO add uploads
-
-export class TelegramClient extends Mixin(AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
-    MessageMethods, ButtonMethods, UpdateMethods,
-    MessageParseMethods, UserMethods, TelegramBaseClient) {
-    constructor(session: string | Session, apiId: number, apiHash: string, {
-        connection = IS_NODE ? ConnectionTCPFull : ConnectionTCPObfuscated,
-        useIPV6 = false,
-        timeout = 10,
-        requestRetries = 5,
-        connectionRetries = 5,
-        retryDelay = 1000,
-        autoReconnect = true,
-        sequentialUpdates = false,
-        floodSleepThreshold = 60,
-        deviceModel = '',
-        systemVersion = '',
-        appVersion = '',
-        langCode = 'en',
-        systemLangCode = 'en',
-        baseLogger = 'gramjs',
-        useWSS = false,
-    }: TelegramClientParams) {
-        super(session, apiId, apiHash, {
-            connection,
-            useIPV6,
-            timeout,
-            requestRetries,
-            connectionRetries,
-            retryDelay,
-            autoReconnect,
-            sequentialUpdates ,
-            floodSleepThreshold,
-            deviceModel,
-            systemVersion,
-            appVersion,
-            langCode,
-            systemLangCode,
-            baseLogger,
-            useWSS,
+
+export class TelegramClient extends TelegramBaseClient {
+
+    // region auth
+
+    start(authParams: authMethods.UserAuthParams | authMethods.BotAuthParams) {
+        return authMethods.start(this, authParams);
+    }
+
+    checkAuthorization() {
+        return authMethods.checkAuthorization(this);
+    }
+
+    signInUser(
+        apiCredentials: authMethods.ApiCredentials,
+        authParams: authMethods.UserAuthParams,
+    ) {
+        return authMethods.signInUser(this, apiCredentials, authParams);
+    }
+
+    signInUserWithQrCode(
+        apiCredentials: authMethods.ApiCredentials,
+        authParams: authMethods.UserAuthParams,
+    ) {
+        return authMethods.signInUserWithQrCode(this, apiCredentials, authParams);
+    }
+
+    sendCode(apiCredentials: authMethods.ApiCredentials, phoneNumber: string, forceSMS = false) {
+        return authMethods.sendCode(this, apiCredentials, phoneNumber, forceSMS);
+    }
+
+    signInWithPassword(apiCredentials: authMethods.ApiCredentials, authParams: authMethods.UserAuthParams) {
+        return authMethods.signInWithPassword(this, apiCredentials, authParams);
+    }
+
+    signInBot(apiCredentials: authMethods.ApiCredentials, authParams: authMethods.BotAuthParams) {
+        return authMethods.signInBot(this, apiCredentials, authParams);
+    }
+
+    authFlow(
+        apiCredentials: authMethods.ApiCredentials,
+        authParams: authMethods.UserAuthParams | authMethods.BotAuthParams,
+    ) {
+        return authMethods.authFlow(this, apiCredentials, authParams);
+    }
+
+    //endregion auth
+
+    //region bot
+    inlineQuery(bot: EntityLike, query: string,
+                entity?: Api.InputPeerSelf | null,
+                offset?: string, geoPoint?: Api.GeoPoint) {
+        return botMethods.inlineQuery(this, bot, query, entity, offset, geoPoint);
+    }
+
+    //endregion
+
+    //region buttons
+    buildReplyMarkup(buttons: Api.TypeReplyMarkup | undefined | ButtonLike | ButtonLike[] | ButtonLike[][], inlineOnly: boolean = false) {
+        return buttonsMethods.buildReplyMarkup(buttons, inlineOnly);
+    }
+
+    //endregion
+
+    //region download
+    downloadFile(
+        inputLocation: Api.InputFileLocation,
+        fileParams: downloadMethods.DownloadFileParams,
+    ) {
+        return downloadMethods.downloads(this,
+            inputLocation,
+            fileParams,
+        )
+    }
+
+    //endregion
+
+    //region message parse
+    get parseMode() {
+        return this._parseMode || MarkdownParser;
+    }
+
+    setParseMode(mode:string | parseMethods.ParseInterface) {
+        this._parseMode = sanitizeParseMode(mode);
+    }
+
+    // private methods
+    _replaceWithMention(entities: Api.TypeMessageEntity[], i: number, user: EntityLike) {
+        return parseMethods._replaceWithMention(this, entities, i, user);
+    }
+
+    _parseMessageText(message: string, parseMode: any) {
+        return parseMethods._parseMessageText(this, message, parseMode);
+    }
+
+    //endregion
+    // region messages
+    iterMessages(entity: EntityLike, params: messageMethods.IterMessagesParams) {
+        return messageMethods.iterMessages(this, entity, params)
+    }
+
+    getMessages(entity: EntityLike, params: messageMethods.IterMessagesParams) {
+        return messageMethods.getMessages(this, entity, params);
+    }
+
+    sendMessage(entity: EntityLike, params: messageMethods.SendMessageParams) {
+        return messageMethods.sendMessage(this, entity, params)
+    }
+
+    //endregion
+    //region updates
+    on(event: any) {
+        return updateMethods.on(this, event);
+    }
+
+    addEventHandler(callback: CallableFunction, event?: EventBuilder) {
+        console.log("adding handler");
+        return updateMethods.addEventHandler(this, callback, event);
+
+    }
+
+    removeEventHandler(callback: CallableFunction, event: EventBuilder) {
+        return updateMethods.removeEventHandler(this, callback, event);
+
+    }
+
+    listEventHandlers() {
+        return updateMethods.listEventHandlers(this);
+
+    }
+
+    // private methods
+    _handleUpdate(update: Api.TypeUpdate | number) {
+        return updateMethods._handleUpdate(this, update);
+
+    }
+
+    _processUpdate(update: any, others: any, entities?: any) {
+        return updateMethods._processUpdate(this, update, others, entities);
+
+    }
+
+    _dispatchUpdate(args: { update: UpdateConnectionState | any }) {
+        return updateMethods._dispatchUpdate(this, args);
+
+    }
+
+    _updateLoop() {
+        return updateMethods._updateLoop(this);
+
+    }
+
+    //endregion
+
+    // region uploads
+    uploadFile(fileParams: uploadMethods.UploadFileParams) {
+        return uploadMethods.uploadFile(this, fileParams);
+    }
+
+    // endregion
+
+    //region user methods
+    invoke<R extends Api.AnyRequest>(request: R): Promise<R['__response']> {
+        return userMethods.invoke(this, request);
+    }
+
+    getMe(inputPeer = false) {
+        return userMethods.getMe(this, inputPeer);
+    }
+
+    isBot() {
+        return userMethods.isBot(this);
+    }
+
+    isUserAuthorized() {
+        return userMethods.isBot(this);
+    }
+
+    getEntity(entity: any) {
+        return userMethods.getEntity(this, entity);
+    }
+
+    getInputEntity(peer: EntityLike) {
+        return userMethods.getInputEntity(this, peer);
+    }
+
+    getPeerId(peer: EntityLike, addMark = false) {
+        return userMethods.getPeerId(this, peer, addMark);
+    }
+
+    // private methods
+    _getEntityFromString(string: string) {
+        return userMethods._getEntityFromString(this, string);
+    }
+
+
+    _getPeer(peer: EntityLike) {
+        return userMethods._getPeer(this, peer);
+    }
+
+    _getInputDialog(dialog: any) {
+        return userMethods._getInputDialog(this, dialog);
+    }
+
+    _getInputNotify(notify: any) {
+        return userMethods._getInputNotify(this, notify);
+    }
+    //endregion
+
+    //region base methods
+
+    async connect() {
+        await this._initSession();
+
+        this._sender = new MTProtoSender(this.session.getAuthKey(), {
+            logger: this._log,
+            dcId: this.session.dcId || 4,
+            retries: this._connectionRetries,
+            delay: this._retryDelay,
+            autoReconnect: this._autoReconnect,
+            connectTimeout: this._timeout,
+            authKeyCallback: this._authKeyCallback.bind(this),
+            updateCallback: this._handleUpdate.bind(this),
+            isMainSender: true,
         });
+
+        const connection = new this._connection(this.session.serverAddress
+            , this.session.port, this.session.dcId, this._log);
+        if (!await this._sender.connect(connection, this._dispatchUpdate.bind(this))) {
+            return
+        }
+        this.session.setAuthKey(this._sender.authKey);
+        await this.session.save();
+        this._initRequest.query = new Api.help.GetConfig();
+        await this._sender.send(new Api.InvokeWithLayer(
+            {
+                layer: LAYER,
+                query: this._initRequest
+            }
+        ));
+
+        this._dispatchUpdate({update: new UpdateConnectionState(1)});
+        this._updateLoop()
     }
 
+    //endregion
+    // region Working with different connections/Data Centers
+
+    async _switchDC(newDc: number) {
+        this._log.info(`Reconnecting to new data center ${newDc}`);
+        const DC = await this.getDC(newDc);
+        this.session.setDC(newDc, DC.ipAddress, DC.port);
+        // authKey's are associated with a server, which has now changed
+        // so it's not valid anymore. Set to None to force recreating it.
+        await this._sender.authKey.setKey();
+        this.session.setAuthKey();
+        await this.disconnect();
+        return this.connect()
+    }
+
+    async _createExportedSender(dcId: number, retries: number) {
+        const dc = await this.getDC(dcId);
+        const sender = new MTProtoSender(this.session.getAuthKey(dcId),
+            {
+                logger: this._log,
+                dcId: dcId,
+                retries: this._connectionRetries,
+                delay: this._retryDelay,
+                autoReconnect: this._autoReconnect,
+                connectTimeout: this._timeout,
+                authKeyCallback: this._authKeyCallback.bind(this),
+                isMainSender: dcId === this.session.dcId,
+                senderCallback: this.removeSender.bind(this),
+            });
+        for (let i = 0; i < retries; i++) {
+            try {
+                await sender.connect(new this._connection(
+                    dc.ipAddress,
+                    dc.port,
+                    dcId,
+                    this._log,
+                ));
+                if (this.session.dcId !== dcId) {
+                    this._log.info(`Exporting authorization for data center ${dc.ipAddress}`);
+                    const auth = await this.invoke(new Api.auth.ExportAuthorization({dcId: dcId}));
+                    this._initRequest.query = new Api.auth.ImportAuthorization({
+                            id: auth.id,
+                            bytes: auth.bytes,
+                        },
+                    )
+                    const req = new Api.InvokeWithLayer({
+                        layer: LAYER,
+                        query: this._initRequest
+                    });
+                    await sender.send(req)
+                }
+                sender.dcId = dcId;
+                return sender
+            } catch (e) {
+                console.log(e);
+                await sender.disconnect()
+            }
+        }
+        return null
+    }
+
+    async getDC(dcId: number): Promise<{ id: number, ipAddress: string, port: number }> {
+        if (!IS_NODE) {
+            switch (dcId) {
+                case 1:
+                    return {
+                        id: 1,
+                        ipAddress: 'pluto.web.telegram.org',
+                        port: 443,
+                    };
+                case 2:
+                    return {
+                        id: 2,
+                        ipAddress: 'venus.web.telegram.org',
+                        port: 443,
+                    };
+                case 3:
+                    return {
+                        id: 3,
+                        ipAddress: 'aurora.web.telegram.org',
+                        port: 443,
+                    };
+                case 4:
+                    return {
+                        id: 4,
+                        ipAddress: 'vesta.web.telegram.org',
+                        port: 443,
+                    };
+                case 5:
+                    return {
+                        id: 5,
+                        ipAddress: 'flora.web.telegram.org',
+                        port: 443,
+                    };
+                default:
+                    throw new Error(`Cannot find the DC with the ID of ${dcId}`)
+            }
+        }
+        if (!this._config) {
+            this._config = await this.invoke(new Api.help.GetConfig())
+        }
+        for (const DC of this._config.dcOptions) {
+            if (DC.id === dcId) {
+                return {
+                    id: DC.id,
+                    ipAddress: DC.ipAddress,
+                    port: 443,
+                }
+            }
+        }
+        throw new Error(`Cannot find the DC with the ID of ${dcId}`)
+    }
+    removeSender(dcId: number) {
+        delete this._borrowedSenderPromises[dcId]
+    }
+
+    async _borrowExportedSender(dcId: number, retries = 5) {
+        let senderPromise = this._borrowedSenderPromises[dcId];
+        if (!senderPromise) {
+            senderPromise = this._createExportedSender(dcId, retries);
+            this._borrowedSenderPromises[dcId] = senderPromise;
+
+            senderPromise.then((sender: any) => {
+                if (!sender) {
+                    delete this._borrowedSenderPromises[dcId]
+                }
+            })
+        }
+        return senderPromise
+    }
 
+    // endregion
 }

+ 0 - 15
gramjs/client/account.ts

@@ -1,15 +0,0 @@
-import {AuthMethods} from "./auth";
-import {DownloadMethods} from "./downloads";
-import {DialogMethods} from "./dialogs";
-import {BotMethods} from "./bots";
-import {MessageMethods} from "./messages";
-import {ButtonMethods} from "./buttons";
-import {UpdateMethods} from "./updates";
-import {MessageParseMethods} from "./messageParse";
-import {UserMethods} from "./users";
-import {TelegramBaseClient} from "./telegramBaseClient";
-import {ChatMethods} from "./chats";
-
-export class AccountMethods {
-
-}

+ 255 - 269
gramjs/client/auth.ts

@@ -2,18 +2,7 @@ import {Api} from '../tl';
 import * as utils from '../Utils';
 import {sleep} from '../Helpers';
 import {computeCheck as computePasswordSrpCheck} from '../Password';
-import {TelegramClient} from "../index";
-import {AccountMethods} from "./account";
-import {DownloadMethods} from "./downloads";
-import {DialogMethods} from "./dialogs";
-import {ChatMethods} from "./chats";
-import {BotMethods} from "./bots";
-import {MessageMethods} from "./messages";
-import {ButtonMethods} from "./buttons";
-import {UpdateMethods} from "./updates";
-import {MessageParseMethods} from "./messageParse";
-import {UserMethods} from "./users";
-import {TelegramBaseClient} from "./telegramBaseClient";
+import type {TelegramClient} from "./TelegramClient";
 
 export interface UserAuthParams {
     phoneNumber: string | (() => Promise<string>);
@@ -33,7 +22,7 @@ export interface BotAuthParams {
     botAuthToken: string | ReturnString;
 }
 
-interface ApiCredentials {
+export interface ApiCredentials {
     apiId: number,
     apiHash: string,
 }
@@ -41,332 +30,329 @@ interface ApiCredentials {
 const QR_CODE_TIMEOUT = 30000;
 
 
-export class AuthMethods {
+// region public methods
 
+export async function start(client: TelegramClient, authParams: UserAuthParams | BotAuthParams) {
+    if (!client.connected) {
+        await client.connect();
+    }
 
-    // region public methods
-
-    async start(authParams: UserAuthParams | BotAuthParams) {
-        if (!this.connected) {
-            await this.connect();
-        }
+    if (await client.checkAuthorization()) {
+        return;
+    }
 
-        if (await this.checkAuthorization()) {
-            return;
-        }
+    const apiCredentials = {
+        apiId: client.apiId,
+        apiHash: client.apiHash,
+    };
 
-        const apiCredentials = {
-            apiId: this.apiId,
-            apiHash: this.apiHash,
-        };
+    await client.authFlow(apiCredentials, authParams);
+}
 
-        await this.authFlow(apiCredentials, authParams);
+export async function checkAuthorization(client: TelegramClient) {
+    try {
+        await client.invoke(new Api.updates.GetState());
+        return true;
+    } catch (e) {
+        return false;
     }
+}
+
+export async function signInUser(
+    client: TelegramClient,
+    apiCredentials: ApiCredentials,
+    authParams: UserAuthParams,
+): Promise<Api.TypeUser> {
+    let phoneNumber;
+    let phoneCodeHash;
+    let isCodeViaApp = false;
 
-    async checkAuthorization() {
+    while (1) {
         try {
-            await this.invoke(new Api.updates.GetState());
-            return true;
-        } catch (e) {
-            return false;
+            if (typeof authParams.phoneNumber === 'function') {
+                try {
+                    phoneNumber = await authParams.phoneNumber();
+                } catch (err) {
+                    if (err.message === 'RESTART_AUTH_WITH_QR') {
+                        return client.signInUserWithQrCode(apiCredentials, authParams);
+                    }
+
+                    throw err;
+                }
+            } else {
+                phoneNumber = authParams.phoneNumber;
+            }
+            const sendCodeResult = await client.sendCode(apiCredentials, phoneNumber, authParams.forceSMS);
+            phoneCodeHash = sendCodeResult.phoneCodeHash;
+            isCodeViaApp = sendCodeResult.isCodeViaApp;
+
+            if (typeof phoneCodeHash !== 'string') {
+                throw new Error('Failed to retrieve phone code hash');
+            }
+
+            break;
+        } catch (err) {
+            if (typeof authParams.phoneNumber !== 'function') {
+                throw err;
+            }
+
+            authParams.onError(err);
         }
     }
 
-    async signInUser(
-        apiCredentials: ApiCredentials, authParams: UserAuthParams,
-    ): Promise<Api.TypeUser> {
-        let phoneNumber;
-        let phoneCodeHash;
-        let isCodeViaApp = false;
+    let phoneCode;
+    let isRegistrationRequired = false;
+    let termsOfService;
 
-        while (1) {
+    while (1) {
+        try {
             try {
-                if (typeof authParams.phoneNumber === 'function') {
-                    try {
-                        phoneNumber = await authParams.phoneNumber();
-                    } catch (err) {
-                        if (err.message === 'RESTART_AUTH_WITH_QR') {
-                            return this.signInUserWithQrCode(apiCredentials, authParams);
-                        }
-
-                        throw err;
-                    }
-                } else {
-                    phoneNumber = authParams.phoneNumber;
+                phoneCode = await authParams.phoneCode(isCodeViaApp);
+            } catch (err) {
+                // This is the support for changing phone number from the phone code screen.
+                if (err.message === 'RESTART_AUTH') {
+                    return client.signInUser(apiCredentials, authParams);
                 }
-                const sendCodeResult = await this.sendCode(apiCredentials, phoneNumber, authParams.forceSMS);
-                phoneCodeHash = sendCodeResult.phoneCodeHash;
-                isCodeViaApp = sendCodeResult.isCodeViaApp;
+            }
 
-                if (typeof phoneCodeHash !== 'string') {
-                    throw new Error('Failed to retrieve phone code hash');
-                }
+            if (!phoneCode) {
+                throw new Error('Code is empty');
+            }
 
+            // May raise PhoneCodeEmptyError, PhoneCodeExpiredError,
+            // PhoneCodeHashEmptyError or PhoneCodeInvalidError.
+            const result = await client.invoke(new Api.auth.SignIn({
+                phoneNumber,
+                phoneCodeHash,
+                phoneCode,
+            }));
+
+            if (result instanceof Api.auth.AuthorizationSignUpRequired) {
+                isRegistrationRequired = true;
+                termsOfService = result.termsOfService;
                 break;
-            } catch (err) {
-                if (typeof authParams.phoneNumber !== 'function') {
-                    throw err;
-                }
+            }
 
+            return result.user;
+        } catch (err) {
+            if (err.message === 'SESSION_PASSWORD_NEEDED') {
+                return client.signInWithPassword(apiCredentials, authParams);
+            } else {
                 authParams.onError(err);
             }
         }
+    }
 
-        let phoneCode;
-        let isRegistrationRequired = false;
-        let termsOfService;
-
+    if (isRegistrationRequired) {
         while (1) {
             try {
-                try {
-                    phoneCode = await authParams.phoneCode(isCodeViaApp);
-                } catch (err) {
-                    // This is the support for changing phone number from the phone code screen.
-                    if (err.message === 'RESTART_AUTH') {
-                        return this.signInUser(apiCredentials, authParams);
-                    }
+                let lastName;
+                let firstName = "first name";
+                if (authParams.firstAndLastNames) {
+                    const result = await authParams.firstAndLastNames();
+                    firstName = result[0];
+                    lastName = result[1];
                 }
-
-                if (!phoneCode) {
-                    throw new Error('Code is empty');
+                if (!firstName) {
+                    throw new Error('First name is required');
                 }
 
-                // May raise PhoneCodeEmptyError, PhoneCodeExpiredError,
-                // PhoneCodeHashEmptyError or PhoneCodeInvalidError.
-                const result = await this.invoke(new Api.auth.SignIn({
+                const {user} = await client.invoke(new Api.auth.SignUp({
                     phoneNumber,
                     phoneCodeHash,
-                    phoneCode,
-                }));
+                    firstName,
+                    lastName,
+                })) as Api.auth.Authorization;
 
-                if (result instanceof Api.auth.AuthorizationSignUpRequired) {
-                    isRegistrationRequired = true;
-                    termsOfService = result.termsOfService;
-                    break;
+                if (termsOfService) {
+                    // This is a violation of Telegram rules: the user should be presented with and accept TOS.
+                    await client.invoke(new Api.help.AcceptTermsOfService({id: termsOfService.id}));
                 }
 
-                return result.user;
+                return user;
             } catch (err) {
-                if (err.message === 'SESSION_PASSWORD_NEEDED') {
-                    return this.signInWithPassword(apiCredentials, authParams);
-                } else {
-                    authParams.onError(err);
-                }
-            }
-        }
-
-        if (isRegistrationRequired) {
-            while (1) {
-                try {
-                    let lastName;
-                    let  firstName = "first name";
-                    if (authParams.firstAndLastNames){
-                        const result = await authParams.firstAndLastNames();
-                        firstName = result[0];
-                        lastName = result[1];
-                    }
-                    if (!firstName) {
-                        throw new Error('First name is required');
-                    }
-
-                    const {user} = await this.invoke(new Api.auth.SignUp({
-                        phoneNumber,
-                        phoneCodeHash,
-                        firstName,
-                        lastName,
-                    })) as Api.auth.Authorization;
-
-                    if (termsOfService) {
-                        // This is a violation of Telegram rules: the user should be presented with and accept TOS.
-                        await this.invoke(new Api.help.AcceptTermsOfService({id: termsOfService.id}));
-                    }
-
-                    return user;
-                } catch (err) {
-                    authParams.onError(err);
-                }
+                authParams.onError(err);
             }
         }
-
-        authParams.onError(new Error('Auth failed'));
-        return this.signInUser(apiCredentials, authParams);
     }
 
-    async signInUserWithQrCode(
-        apiCredentials: ApiCredentials, authParams: UserAuthParams,
-    ): Promise<Api.TypeUser> {
-        const inputPromise = (async () => {
-            while (1) {
-                const result = await this.invoke(new Api.auth.ExportLoginToken({
-                    apiId: Number(process.env.TELEGRAM_T_API_ID),
-                    apiHash: process.env.TELEGRAM_T_API_HASH,
-                    exceptIds: [],
-                }));
-
-                if (!(result instanceof Api.auth.LoginToken)) {
-                    throw new Error('Unexpected');
-                }
-
-                const {token, expires} = result;
-                if (authParams.qrCode){
-                    await Promise.race([
-                        authParams.qrCode({token, expires}),
-                        sleep(QR_CODE_TIMEOUT),
-                    ]);
-                }
-            }
-        })();
-
-        const updatePromise = new Promise((resolve) => {
-            this.addEventHandler((update: Api.TypeUpdate) => {
-                if (update instanceof Api.UpdateLoginToken) {
-                    resolve(undefined);
-                }
-            });
-        });
-
-        try {
-            await Promise.race([updatePromise, inputPromise]);
-        } catch (err) {
-            if (err.message === 'RESTART_AUTH') {
-                return this.signInUser(apiCredentials, authParams);
-            }
-
-            throw err;
-        }
+    authParams.onError(new Error('Auth failed'));
+    return client.signInUser(apiCredentials, authParams);
+}
 
-        try {
-            const result2 = await this.invoke(new Api.auth.ExportLoginToken({
+export async function signInUserWithQrCode(
+    client: TelegramClient,
+    apiCredentials: ApiCredentials,
+    authParams: UserAuthParams,
+): Promise<Api.TypeUser> {
+    const inputPromise = (async () => {
+        while (1) {
+            const result = await client.invoke(new Api.auth.ExportLoginToken({
                 apiId: Number(process.env.TELEGRAM_T_API_ID),
                 apiHash: process.env.TELEGRAM_T_API_HASH,
                 exceptIds: [],
             }));
 
-            if (result2 instanceof Api.auth.LoginTokenSuccess && result2.authorization instanceof Api.auth.Authorization) {
-                return result2.authorization.user;
-            } else if (result2 instanceof Api.auth.LoginTokenMigrateTo) {
-                await this._switchDC(result2.dcId);
-                const migratedResult = await this.invoke(new Api.auth.ImportLoginToken({
-                    token: result2.token,
-                }));
+            if (!(result instanceof Api.auth.LoginToken)) {
+                throw new Error('Unexpected');
+            }
 
-                if (migratedResult instanceof Api.auth.LoginTokenSuccess && migratedResult.authorization instanceof Api.auth.Authorization) {
-                    return migratedResult.authorization.user;
-                }
+            const {token, expires} = result;
+            if (authParams.qrCode) {
+                await Promise.race([
+                    authParams.qrCode({token, expires}),
+                    sleep(QR_CODE_TIMEOUT),
+                ]);
             }
-        } catch (err) {
-            if (err.message === 'SESSION_PASSWORD_NEEDED') {
-                return this.signInWithPassword(apiCredentials, authParams);
+        }
+    })();
+
+    const updatePromise = new Promise((resolve) => {
+        client.addEventHandler((update: Api.TypeUpdate) => {
+            if (update instanceof Api.UpdateLoginToken) {
+                resolve(undefined);
             }
+        });
+    });
+
+    try {
+        await Promise.race([updatePromise, inputPromise]);
+    } catch (err) {
+        if (err.message === 'RESTART_AUTH') {
+            return client.signInUser(apiCredentials, authParams);
         }
 
-        authParams.onError(new Error('QR auth failed'));
-        return this.signInUser(apiCredentials, authParams);
+        throw err;
     }
 
-    async sendCode(apiCredentials: ApiCredentials, phoneNumber: string, forceSMS = false,
-    ): Promise<{
-        phoneCodeHash: string;
-        isCodeViaApp: boolean;
-    }> {
-        try {
-            const {apiId, apiHash} = apiCredentials;
-            const sendResult = await this.invoke(new Api.auth.SendCode({
-                phoneNumber,
-                apiId,
-                apiHash,
-                settings: new Api.CodeSettings(),
+    try {
+        const result2 = await client.invoke(new Api.auth.ExportLoginToken({
+            apiId: Number(process.env.TELEGRAM_T_API_ID),
+            apiHash: process.env.TELEGRAM_T_API_HASH,
+            exceptIds: [],
+        }));
+
+        if (result2 instanceof Api.auth.LoginTokenSuccess && result2.authorization instanceof Api.auth.Authorization) {
+            return result2.authorization.user;
+        } else if (result2 instanceof Api.auth.LoginTokenMigrateTo) {
+            await client._switchDC(result2.dcId);
+            const migratedResult = await client.invoke(new Api.auth.ImportLoginToken({
+                token: result2.token,
             }));
 
-            // If we already sent a SMS, do not resend the phoneCode (hash may be empty)
-            if (!forceSMS || (sendResult.type instanceof Api.auth.SentCodeTypeSms)) {
-                return {
-                    phoneCodeHash: sendResult.phoneCodeHash,
-                    isCodeViaApp: sendResult.type instanceof Api.auth.SentCodeTypeApp,
-                };
+            if (migratedResult instanceof Api.auth.LoginTokenSuccess && migratedResult.authorization instanceof Api.auth.Authorization) {
+                return migratedResult.authorization.user;
             }
+        }
+    } catch (err) {
+        if (err.message === 'SESSION_PASSWORD_NEEDED') {
+            return client.signInWithPassword(apiCredentials, authParams);
+        }
+    }
 
-            const resendResult = await this.invoke(new Api.auth.ResendCode({
-                phoneNumber,
-                phoneCodeHash: sendResult.phoneCodeHash,
-            }));
+    authParams.onError(new Error('QR auth failed'));
+    return client.signInUser(apiCredentials, authParams);
+}
+
+export async function sendCode(client: TelegramClient, apiCredentials: ApiCredentials, phoneNumber: string, forceSMS = false,
+): Promise<{
+    phoneCodeHash: string;
+    isCodeViaApp: boolean;
+}> {
+    try {
+        const {apiId, apiHash} = apiCredentials;
+        const sendResult = await client.invoke(new Api.auth.SendCode({
+            phoneNumber,
+            apiId,
+            apiHash,
+            settings: new Api.CodeSettings(),
+        }));
 
+        // If we already sent a SMS, do not resend the phoneCode (hash may be empty)
+        if (!forceSMS || (sendResult.type instanceof Api.auth.SentCodeTypeSms)) {
             return {
-                phoneCodeHash: resendResult.phoneCodeHash,
-                isCodeViaApp: resendResult.type instanceof Api.auth.SentCodeTypeApp,
+                phoneCodeHash: sendResult.phoneCodeHash,
+                isCodeViaApp: sendResult.type instanceof Api.auth.SentCodeTypeApp,
             };
-        } catch (err) {
-            if (err.message === 'AUTH_RESTART') {
-                return this.sendCode(apiCredentials, phoneNumber, forceSMS);
-            } else {
-                throw err;
-            }
         }
-    }
 
-    async signInWithPassword(apiCredentials: ApiCredentials, authParams: UserAuthParams,
-    ): Promise<Api.TypeUser> {
-        while (1) {
-            try {
-                const passwordSrpResult = await this.invoke(new Api.account.GetPassword());
-                const password = await authParams.password(passwordSrpResult.hint);
-                if (!password) {
-                    throw new Error('Password is empty');
-                }
+        const resendResult = await client.invoke(new Api.auth.ResendCode({
+            phoneNumber,
+            phoneCodeHash: sendResult.phoneCodeHash,
+        }));
 
-                const passwordSrpCheck = await computePasswordSrpCheck(passwordSrpResult, password);
-                const {user} = await this.invoke(new Api.auth.CheckPassword({
-                    password: passwordSrpCheck,
-                })) as Api.auth.Authorization;
-
-                return user;
-            } catch (err) {
-                authParams.onError(err);
-            }
+        return {
+            phoneCodeHash: resendResult.phoneCodeHash,
+            isCodeViaApp: resendResult.type instanceof Api.auth.SentCodeTypeApp,
+        };
+    } catch (err) {
+        if (err.message === 'AUTH_RESTART') {
+            return client.sendCode(apiCredentials, phoneNumber, forceSMS);
+        } else {
+            throw err;
         }
-
-        return undefined!; // Never reached (TypeScript fix)
     }
+}
 
-    async signInBot(apiCredentials: ApiCredentials, authParams: BotAuthParams) {
-        const {apiId, apiHash} = apiCredentials;
-        let {botAuthToken} = authParams;
-        if (!botAuthToken) {
-            throw new Error('a valid BotToken is required');
-        }
-        if (typeof botAuthToken === "function") {
-            let token;
-            while (true) {
-                token = await botAuthToken();
-                if (token) {
-                    botAuthToken = token;
-                    break;
-                }
+export async function signInWithPassword(client: TelegramClient, apiCredentials: ApiCredentials, authParams: UserAuthParams,
+): Promise<Api.TypeUser> {
+    while (1) {
+        try {
+            const passwordSrpResult = await client.invoke(new Api.account.GetPassword());
+            const password = await authParams.password(passwordSrpResult.hint);
+            if (!password) {
+                throw new Error('Password is empty');
             }
-        }
 
-        console.dir(botAuthToken);
-        const {user} = await this.invoke(new Api.auth.ImportBotAuthorization({
-            apiId,
-            apiHash,
-            botAuthToken,
-        })) as Api.auth.Authorization;
-        return user;
+            const passwordSrpCheck = await computePasswordSrpCheck(passwordSrpResult, password);
+            const {user} = await client.invoke(new Api.auth.CheckPassword({
+                password: passwordSrpCheck,
+            })) as Api.auth.Authorization;
+
+            return user;
+        } catch (err) {
+            authParams.onError(err);
+        }
     }
 
-    async authFlow(
-        apiCredentials: ApiCredentials,
-        authParams: UserAuthParams | BotAuthParams,
-    ) {
-        const me = 'phoneNumber' in authParams
-            ? await this.signInUser(apiCredentials, authParams)
-            : await this.signInBot(apiCredentials, authParams);
+    return undefined!; // Never reached (TypeScript fix)
+}
 
-        // TODO @logger
-        this._log.info('Signed in successfully as ' + utils.getDisplayName(me));
+export async function signInBot(client: TelegramClient, apiCredentials: ApiCredentials, authParams: BotAuthParams) {
+    const {apiId, apiHash} = apiCredentials;
+    let {botAuthToken} = authParams;
+    if (!botAuthToken) {
+        throw new Error('a valid BotToken is required');
+    }
+    if (typeof botAuthToken === "function") {
+        let token;
+        while (true) {
+            token = await botAuthToken();
+            if (token) {
+                botAuthToken = token;
+                break;
+            }
+        }
     }
+
+    const {user} = await client.invoke(new Api.auth.ImportBotAuthorization({
+        apiId,
+        apiHash,
+        botAuthToken,
+    })) as Api.auth.Authorization;
+    return user;
 }
 
-export interface AuthMethods extends UserMethods, UpdateMethods {
+export async function authFlow(
+    client: TelegramClient,
+    apiCredentials: ApiCredentials,
+    authParams: UserAuthParams | BotAuthParams,
+) {
+    const me = 'phoneNumber' in authParams
+        ? await client.signInUser(apiCredentials, authParams)
+        : await client.signInBot(apiCredentials, authParams);
+
+    // TODO @logger
+    client._log.info('Signed in successfully as ' + utils.getDisplayName(me));
 }
 

+ 17 - 26
gramjs/client/bots.ts

@@ -1,33 +1,24 @@
-import {EntityLike} from "../define";
+import type {EntityLike} from "../define";
 import {Api} from "../tl";
 import {InlineResults} from "../tl/custom/inlineResults";
 import GetInlineBotResults = Api.messages.GetInlineBotResults;
-import {UserMethods} from "./users";
+import type {TelegramClient} from "./TelegramClient";
 
-export class BotMethods {
-    async inlineQuery(bot: EntityLike, query: string, entity?: Api.InputPeerSelf | null,
-                      offset?: string, geoPoint?: Api.GeoPoint): Promise<InlineResults<Api.messages.BotResults>> {
-        bot = await this.getInputEntity(bot);
-        let peer = new Api.InputPeerSelf();
-        if (entity) {
-            peer = await this.getInputEntity(entity);
-        }
-        const result = await this.invoke(new GetInlineBotResults({
-            bot: bot,
-            peer: peer,
-            query: query,
-            offset: offset || '',
-            geoPoint: geoPoint
-        }));
-        // @ts-ignore
-        return new InlineResults(this, result, entity = entity ? peer : undefined);
+// BotMethods
+export async function inlineQuery(client: TelegramClient, bot: EntityLike, query: string, entity?: Api.InputPeerSelf | null,
+                                  offset?: string, geoPoint?: Api.GeoPoint): Promise<InlineResults<Api.messages.BotResults>> {
+    bot = await client.getInputEntity(bot);
+    let peer = new Api.InputPeerSelf();
+    if (entity) {
+        peer = await client.getInputEntity(entity);
     }
+    const result = await client.invoke(new GetInlineBotResults({
+        bot: bot,
+        peer: peer,
+        query: query,
+        offset: offset || '',
+        geoPoint: geoPoint
+    }));
+    return new InlineResults(client, result, entity = entity ? peer : undefined);
 }
 
-export interface BotMethods extends UserMethods {
-
-}
-
-{
-
-}

+ 66 - 61
gramjs/client/buttons.ts

@@ -1,78 +1,83 @@
-import {Api} from "../tl/api";
-import {MarkupLike} from "../define";
+import {Api} from "../tl";
+import type {ButtonLike, MarkupLike} from "../define";
 import {Button} from "../tl/custom/button";
 import {MessageButton} from "../tl/custom/messageButton";
 import {isArrayLike} from "../Utils";
 
-export class ButtonMethods {
-     buildReplyMarkup(buttons: MarkupLike, inlineOnly: boolean = false): Api.TypeReplyMarkup | undefined {
-        if (buttons == undefined) {
-            return undefined;
-        }
+// ButtonMethods
+export function buildReplyMarkup(buttons: Api.TypeReplyMarkup | undefined | ButtonLike | ButtonLike[] | ButtonLike[][], inlineOnly: boolean = false): Api.TypeReplyMarkup | undefined {
+    if (buttons == undefined) {
+        return undefined;
+    }
+    if ("SUBCLASS_OF_ID" in buttons) {
         if (buttons.SUBCLASS_OF_ID == 0xe2e10ef2) {
             return buttons;
         }
-        if (!isArrayLike(buttons)) {
-            buttons = [[buttons]];
-        } else if (!buttons || !isArrayLike(buttons[0])) {
-            buttons = [buttons];
-        }
-        let isInline = false;
-        let isNormal = false;
-        let resize = undefined;
-        const singleUse = false;
-        const selective = false;
+    }
+    if (!isArrayLike(buttons)) {
+        // @ts-ignore
+        buttons = [[buttons]];
+    } else if (!buttons || !isArrayLike(buttons[0])) {
+        // @ts-ignore
+        buttons = [buttons];
+    }
+    let isInline = false;
+    let isNormal = false;
+    let resize = undefined;
+    const singleUse = false;
+    const selective = false;
 
-        const rows = [];
-        for (const row of buttons) {
-            const current = [];
-            for (let button of row) {
-                if (button instanceof Button) {
-                    if (button.resize != undefined) {
-                        resize = button.resize;
-                    }
-                    if (button.singleUse != undefined) {
-                        resize = button.singleUse;
-                    }
-                    if (button.selective != undefined) {
-                        resize = button.selective;
-                    }
-                    button = button.button;
-                } else if (button instanceof MessageButton) {
-                    button = button.button;
+    const rows = [];
+    // @ts-ignore
+    for (const row of buttons) {
+        const current = [];
+        for (let button of row) {
+            if (button instanceof Button) {
+                if (button.resize != undefined) {
+                    resize = button.resize;
                 }
-                const inline = Button._isInline(button);
-                if (!isInline && inline) {
-                    isInline = true;
+                if (button.singleUse != undefined) {
+                    resize = button.singleUse;
                 }
-                if (!isNormal && inline) {
-                    isNormal = false;
-                }
-                if (button.SUBCLASS_OF_ID == 0xbad74a3) {
-                    // 0xbad74a3 == crc32(b'KeyboardButton')
-                    current.push(button);
+                if (button.selective != undefined) {
+                    resize = button.selective;
                 }
+                button = button.button;
+            } else if (button instanceof MessageButton) {
+                button = button.button;
+            }
+            const inline = Button._isInline(button);
+            if (!isInline && inline) {
+                isInline = true;
+            }
+            if (!isNormal && inline) {
+                isNormal = false;
             }
-            if (current) {
-                rows.push(new Api.KeyboardButtonRow({
-                    buttons: current,
-                }))
+            if (button.SUBCLASS_OF_ID == 0xbad74a3) {
+                // 0xbad74a3 == crc32(b'KeyboardButton')
+                current.push(button);
             }
         }
-        if (inlineOnly && isNormal) {
-            throw new Error('You cannot use non-inline buttons here');
-        } else if (isInline === isNormal && isNormal) {
-            throw new Error('You cannot mix inline with normal buttons');
-        } else if (isInline) {
-            return new Api.ReplyInlineMarkup({
-                rows: rows
-            })
+        if (current) {
+            rows.push(new Api.KeyboardButtonRow({
+                buttons: current,
+            }))
         }
-        return new Api.ReplyKeyboardMarkup(({
-            rows: rows,
-            resize: resize,
-            singleUse: singleUse,
-            selective: selective
-        }))
     }
+    if (inlineOnly && isNormal) {
+        throw new Error('You cannot use non-inline buttons here');
+    } else if (isInline === isNormal && isNormal) {
+        throw new Error('You cannot mix inline with normal buttons');
+    } else if (isInline) {
+        return new Api.ReplyInlineMarkup({
+            rows: rows
+        })
+    }
+    return new Api.ReplyKeyboardMarkup(({
+        rows: rows,
+        resize: resize,
+        singleUse: singleUse,
+        selective: selective
+    }))
 }
+

+ 20 - 41
gramjs/client/chats.ts

@@ -1,33 +1,10 @@
-import {TelegramClient} from "./TelegramClient";
-import {EntitiesLike, Entity, EntityLike, ValueOf} from "../define";
-// @ts-ignore
+import type{TelegramClient} from "./TelegramClient";
+import type{EntitiesLike, Entity, EntityLike, ValueOf} from "../define";
 import {sleep, getMinBigInt} from '../Helpers';
 import {RequestIter} from "../requestIter";
-import {helpers, utils} from "../index";
-import {Api} from "../tl/api";
-import GetFullChannel = Api.channels.GetFullChannel;
-import AnyRequest = Api.AnyRequest;
-import SetTyping = Api.messages.SetTyping;
-import GetParticipants = Api.channels.GetParticipants;
-import ChannelParticipantsSearch = Api.ChannelParticipantsSearch;
-import GetFullChat = Api.messages.GetFullChat;
-import ChatParticipants = Api.ChatParticipants;
-import ChannelParticipantsNotModified = Api.channels.ChannelParticipantsNotModified;
-import ChannelAdminLogEventsFilter = Api.ChannelAdminLogEventsFilter;
-import GetAdminLog = Api.channels.GetAdminLog;
+import {helpers, utils} from "../";
+import {Api} from "../tl";
 import bigInt, {BigInteger} from "big-integer";
-import ChannelAdminLogEventActionEditMessage = Api.ChannelAdminLogEventActionEditMessage;
-import {AccountMethods} from "./account";
-import {AuthMethods} from "./auth";
-import {DownloadMethods} from "./downloads";
-import {DialogMethods} from "./dialogs";
-import {BotMethods} from "./bots";
-import {MessageMethods} from "./messages";
-import {ButtonMethods} from "./buttons";
-import {UpdateMethods} from "./updates";
-import {MessageParseMethods} from "./messageParse";
-import {UserMethods} from "./users";
-import {TelegramBaseClient} from "./telegramBaseClient";
 
 const _MAX_PARTICIPANTS_CHUNK_SIZE = 200;
 const _MAX_ADMIN_LOG_CHUNK_SIZE = 100;
@@ -68,7 +45,7 @@ class _ChatAction {
     private _action: ValueOf<typeof _ChatAction._str_mapping>;
     private _delay: number;
     private autoCancel: boolean;
-    private _request?: AnyRequest;
+    private _request?: Api.AnyRequest;
     private _task: null;
     private _running: boolean;
 
@@ -87,7 +64,7 @@ class _ChatAction {
     }
 
     async start() {
-        this._request = new SetTyping({
+        this._request = new Api.messages.SetTyping({
             peer: this._chat,
             action: this._action
         });
@@ -98,7 +75,7 @@ class _ChatAction {
     async stop() {
         this._running = false;
         if (this.autoCancel) {
-            await this._client.invoke(new SetTyping({
+            await this._client.invoke(new  Api.messages.SetTyping({
                 peer: this._chat,
                 action: new Api.SendMessageCancelAction()
             }));
@@ -125,7 +102,7 @@ class _ChatAction {
 
 class _ParticipantsIter extends RequestIter {
     private filterEntity: ((entity: Entity) => boolean) | undefined;
-    private requests?: GetParticipants[];
+    private requests?: Api.channels.GetParticipants[];
 
     async _init(entity: EntityLike, filter: any, search?: string): Promise<boolean | void> {
         if (filter.constructor === Function) {
@@ -152,7 +129,7 @@ class _ParticipantsIter extends RequestIter {
         // Only used for channels, but we should always set the attribute
         this.requests = [];
         if (ty == helpers._EntityType.CHANNEL) {
-            const channel = (await this.client.invoke(new GetFullChannel({
+            const channel = (await this.client.invoke(new Api.channels.GetFullChannel({
                 channel: entity
             })));
             if (!(channel.fullChat instanceof Api.ChatFull)) {
@@ -161,9 +138,9 @@ class _ParticipantsIter extends RequestIter {
             if (this.total && this.total <= 0) {
                 return false;
             }
-            this.requests.push(new GetParticipants({
+            this.requests.push(new Api.channels.GetParticipants({
                 channel: entity,
-                filter: filter || new ChannelParticipantsSearch({
+                filter: filter || new Api.ChannelParticipantsSearch({
                     q: search || '',
                 }),
                 offset: 0,
@@ -171,7 +148,10 @@ class _ParticipantsIter extends RequestIter {
                 hash: 0,
             }))
         } else if (ty == helpers._EntityType.CHAT) {
-            const full = await this.client.invoke(new GetFullChat({
+            if (!("chatId" in entity)){
+                throw new Error("Found chat without id "+JSON.stringify(entity));
+            }
+            const full = await this.client.invoke(new Api.messages.GetFullChat({
                 chatId: entity.chatId
             }));
 
@@ -230,7 +210,7 @@ class _ParticipantsIter extends RequestIter {
         }
         for (let i = this.requests.length; i > 0; i--) {
             const participants = results[i];
-            if (participants instanceof ChannelParticipantsNotModified || !participants.users) {
+            if (participants instanceof Api.channels.ChannelParticipantsNotModified || !participants.users) {
                 this.requests.splice(i, 1);
                 continue;
             }
@@ -285,7 +265,7 @@ class _AdminLogIter extends RequestIter {
         let eventsFilter = undefined;
 
         if (filterArgs && Object.values(filterArgs).find(element => element === true)) {
-            eventsFilter = new ChannelAdminLogEventsFilter({
+            eventsFilter = new Api.ChannelAdminLogEventsFilter({
                 ...filterArgs
             });
         }
@@ -296,7 +276,7 @@ class _AdminLogIter extends RequestIter {
                 adminList.push(await this.client.getInputEntity(admin))
             }
         }
-        this.request = new GetAdminLog({
+        this.request = new Api.channels.GetAdminLog({
                 channel: this.entity,
                 q: searchArgs?.search || '',
                 minId: searchArgs?.minId,
@@ -324,7 +304,7 @@ class _AdminLogIter extends RequestIter {
         }
         this.request.maxId = getMinBigInt([bigInt.zero, ...eventIds]);
         for (const ev of r.events) {
-            if (ev.action instanceof ChannelAdminLogEventActionEditMessage) {
+            if (ev.action instanceof Api.ChannelAdminLogEventActionEditMessage) {
                 // @ts-ignore
                 // TODO ev.action.prevMessage._finishInit(this.client, entities, this.entity);
                 // @ts-ignore
@@ -335,6 +315,5 @@ class _AdminLogIter extends RequestIter {
     }
 }
 
-export class ChatMethods {
     // TODO implement
-}
+

+ 0 - 15
gramjs/client/dialogs.ts

@@ -1,15 +0,0 @@
-import {AccountMethods} from "./account";
-import {AuthMethods} from "./auth";
-import {DownloadMethods} from "./downloads";
-import {ChatMethods} from "./chats";
-import {BotMethods} from "./bots";
-import {MessageMethods} from "./messages";
-import {ButtonMethods} from "./buttons";
-import {UpdateMethods} from "./updates";
-import {MessageParseMethods} from "./messageParse";
-import {UserMethods} from "./users";
-import {TelegramBaseClient} from "./telegramBaseClient";
-
-export class DialogMethods {
-    // TODO
-}

+ 3 - 3
gramjs/client/downloads.ts

@@ -1,5 +1,5 @@
-import { default as Api } from '../tl/api';
-import {TelegramClient} from './TelegramClient';
+import { Api } from '../tl';
+import type {TelegramClient} from './TelegramClient';
 import { getAppropriatedPartSize } from '../Utils';
 import { sleep } from '../Helpers';
 
@@ -34,7 +34,7 @@ const DEFAULT_CHUNK_SIZE = 64; // kb
 const ONE_MB = 1024 * 1024;
 const REQUEST_TIMEOUT = 15000;
 
-export async function downloadFile(
+export async function downloads(
     client: TelegramClient,
     inputLocation: Api.InputFileLocation,
     fileParams: DownloadFileParams,

+ 136 - 131
gramjs/client/messageParse.ts

@@ -1,156 +1,161 @@
-import {getPeer, getPeerId, isArrayLike, sanitizeParseMode} from "../Utils";
-import {Api} from "../tl/api";
-import {EntityLike} from "../define";
-import {_EntityType, _entityType} from "../Helpers";
-import {UserMethods} from "./users";
+import {sanitizeParseMode} from "../Utils";
+import {Api} from "../tl";
+import type {EntityLike, ValueOf} from "../define";
+import type {TelegramClient} from "./TelegramClient";
 
-export class MessageParseMethods {
-    _parseMode: any;
-    get parseMode(): any {
-        return this._parseMode;
-    }
+export type messageEntities = typeof Api.MessageEntityBold | typeof Api.MessageEntityItalic |
+    typeof Api.MessageEntityStrike | typeof Api.MessageEntityCode | typeof Api.MessageEntityPre;
+export const DEFAULT_DELIMITERS: {
+    [key: string]: messageEntities
+} = {
+    '**': Api.MessageEntityBold,
+    '__': Api.MessageEntityItalic,
+    '~~': Api.MessageEntityStrike,
+    '`': Api.MessageEntityCode,
+    '```': Api.MessageEntityPre
+};
+
+// export class MessageParseMethods {
 
-    set parseMode(mode: any) {
-        this._parseMode = sanitizeParseMode(mode);
+export interface ParseInterface {
+    parse: (message: string, delimiters?: typeof DEFAULT_DELIMITERS) => [string, ValueOf<typeof DEFAULT_DELIMITERS>[]],
+    unparse: (text: string, entities: Api.TypeMessageEntity[] | undefined, delimiters?: typeof DEFAULT_DELIMITERS) => string
+}
+
+export async function _replaceWithMention(client: TelegramClient, entities: Api.TypeMessageEntity[], i: number, user: EntityLike) {
+    try {
+        entities[i] = new Api.InputMessageEntityMentionName(
+            {
+                offset: entities[i].offset,
+                length: entities[i].length,
+                userId: await client.getInputEntity(user)
+            }
+        )
+        return true;
+    } catch (e) {
+        return false;
     }
+}
 
-    async _replaceWithMention(entities: Api.TypeMessageEntity[], i: number, user: EntityLike) {
-        try {
-            entities[i] = new Api.InputMessageEntityMentionName(
-                {
-                    offset: entities[i].offset,
-                    length: entities[i].length,
-                    userId: await this.getInputEntity(user)
-                }
-            )
-            return true;
-        } catch (e) {
-            return false;
-        }
+export function _parseMessageText(client: TelegramClient, message: string, parseMode: any) {
+    if (!parseMode) {
+        parseMode = client.parseMode;
+    } else if (typeof parseMode === "string") {
+        parseMode = sanitizeParseMode(parseMode);
+    }
+    if (!parseMode) {
+        return [message, []]
     }
+    return parseMode.parse(message);
+}
 
-    _parseMessageText(message: string, parseMode: any) {
-        if (!parseMode) {
-            parseMode = this._parseMode;
-        } else if (typeof parseMode === "string") {
-            parseMode = sanitizeParseMode(parseMode);
-        }
-        if (!parseMode) {
-            return [message, []]
+/* TODO make your own smh
+export function _getResponseMessage(request: Api.AnyRequest, result: Api.TypeUpdates, inputChat: Api.TypeInputPeer) {
+    let updates = [];
+    let entities = new Map();
+    if (result instanceof Api.UpdateShort) {
+        updates = [result.update]
+    } else if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
+        updates = result.updates;
+        for (const x of [...result.users, ...result.chats]) {
+            entities.set(getPeerId(x), x);
         }
-        return parseMode.parse(message);
+    } else {
+        return;
     }
-
-    _getResponseMessage(request: Api.AnyRequest, result: Api.TypeUpdates, inputChat: Api.TypeInputPeer) {
-        let updates = [];
-        let entities = new Map();
-        if (result instanceof Api.UpdateShort) {
-            updates = [result.update]
-        } else if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
-            updates = result.updates;
-            for (const x of [...result.users, ...result.chats]) {
-                entities.set(getPeerId(x), x);
+    const randomToId = new Map();
+    const idToMessage = new Map();
+    const schedToMessage = new Map();
+    for (const update of updates) {
+        if (update instanceof Api.UpdateMessageID) {
+            randomToId.set(update.randomId, update.id);
+        } else if (update instanceof Api.UpdateNewChannelMessage || update instanceof Api.UpdateNewMessage) {
+            // @ts-ignore
+            // TODO update.message._finishInit(this, entities, inputChat);
+            if ('randomId' in request || isArrayLike(request)) {
+                idToMessage.set(update.message.id, update.message);
+            } else {
+                return update.message;
             }
-        } else {
-            return;
-        }
-        const randomToId = new Map();
-        const idToMessage = new Map();
-        const schedToMessage = new Map();
-        for (const update of updates) {
-            if (update instanceof Api.UpdateMessageID) {
-                randomToId.set(update.randomId, update.id);
-            } else if (update instanceof Api.UpdateNewChannelMessage || update instanceof Api.UpdateNewMessage) {
-                // @ts-ignore
-                // TODO update.message._finishInit(this, entities, inputChat);
-                if ('randomId' in request || isArrayLike(request)) {
-                    idToMessage.set(update.message.id, update.message);
-                } else {
-                    return update.message;
-                }
-            } else if (update instanceof Api.UpdateEditMessage && 'peer' in request && _entityType(request.peer) != _EntityType.CHANNEL) {
-                // @ts-ignore
-                // TODO update.message._finishInit(this, entities, inputChat);
-                if ('randomId' in request) {
-                    idToMessage.set(update.message.id, update.message);
-                } else if ('id' in request && request.id === update.message.id) {
-                    return update.message;
-                }
-            } else if (update instanceof Api.UpdateEditChannelMessage &&
-                update.message instanceof Api.Message && 'peer' in request && getPeerId(request.peer) == getPeerId(update.message.peerId)) {
-                schedToMessage.set(update.message.id, update.message);
-            } else if (update instanceof Api.UpdateMessagePoll) {
-                if ('media' in request && request.media && "poll" in request.media && request?.media.poll.id == update.pollId) {
-                    if ('peer' in request) {
-                        const peerId = getPeer(request.peer) as Api.TypePeer;
-                        const poll = update.poll;
-                        if (poll && 'id' in request) {
-                            const m = new Api.Message({
-                                id: request.id,
-                                peerId: peerId,
-                                media: new Api.MessageMediaPoll({
-                                    poll: poll,
-                                    results: update.results
-                                }),
-                                message: '',
-                                date: 0,
-                            });
-                            // @ts-ignore
-                            m._finishInit(this, entities, inputChat);
-                            return m;
-                        }
+        } else if (update instanceof Api.UpdateEditMessage && 'peer' in request && _entityType(request.peer) != _EntityType.CHANNEL) {
+            // @ts-ignore
+            // TODO update.message._finishInit(this, entities, inputChat);
+            if ('randomId' in request) {
+                idToMessage.set(update.message.id, update.message);
+            } else if ('id' in request && request.id === update.message.id) {
+                return update.message;
+            }
+        } else if (update instanceof Api.UpdateEditChannelMessage &&
+            update.message instanceof Api.Message && 'peer' in request && getPeerId(request.peer) == getPeerId(update.message.peerId)) {
+            schedToMessage.set(update.message.id, update.message);
+        } else if (update instanceof Api.UpdateMessagePoll) {
+            if ('media' in request && request.media && "poll" in request.media && request?.media.poll.id == update.pollId) {
+                if ('peer' in request) {
+                    const peerId = getPeer(request.peer) as Api.TypePeer;
+                    const poll = update.poll;
+                    if (poll && 'id' in request) {
+                        const m = new Api.Message({
+                            id: request.id,
+                            peerId: peerId,
+                            media: new Api.MessageMediaPoll({
+                                poll: poll,
+                                results: update.results
+                            }),
+                            message: '',
+                            date: 0,
+                        });
+                        // @ts-ignore
+                        m._finishInit(this, entities, inputChat);
+                        return m;
                     }
                 }
             }
-
-        }
-        if (!request) {
-            return idToMessage;
         }
-        let mapping;
-        let opposite = new Map();
 
-        if (!("scheduleDate" in request)) {
-            mapping = idToMessage;
-        } else {
-            mapping = schedToMessage;
-            opposite = idToMessage;
-        }
-        let randomId: any = (typeof request == 'number' || isArrayLike(request)) ? request : 'randomId' in request ? request.randomId : undefined;
+    }
+    if (!request) {
+        return idToMessage;
+    }
+    let mapping;
+    let opposite = new Map();
 
-        if (randomId === undefined) {
-            // TODO add logging
-            return null;
+    if (!("scheduleDate" in request)) {
+        mapping = idToMessage;
+    } else {
+        mapping = schedToMessage;
+        opposite = idToMessage;
+    }
+    let randomId: any = (typeof request == 'number' || isArrayLike(request)) ? request : 'randomId' in request ? request.randomId : undefined;
+
+    if (randomId === undefined) {
+        // TODO add logging
+        return null;
+    }
+    if (!isArrayLike(request)) {
+        let msg = mapping.get(randomToId.get(randomId));
+        if (!msg) {
+            msg = opposite.get(randomToId.get(randomId));
         }
-        if (!isArrayLike(request)) {
-            let msg = mapping.get(randomToId.get(randomId));
-            if (!msg) {
-                msg = opposite.get(randomToId.get(randomId));
-            }
-            if (!msg) {
-                throw new Error(`Request ${request.className} had missing message mapping`)
-                // TODO add logging
-            }
-            return msg;
+        if (!msg) {
+            throw new Error(`Request ${request.className} had missing message mapping`)
+            // TODO add logging
         }
-        if (isArrayLike((randomId))) {
+        return msg;
+    }
+    if (isArrayLike((randomId))) {
 
-            const maps = [];
-            // @ts-ignore
-            for (const rnd of randomId) {
-                const d = mapping.get(randomToId.get(rnd));
-                const o = opposite.get(randomToId.get(rnd));
+        const maps = [];
+        // @ts-ignore
+        for (const rnd of randomId) {
+            const d = mapping.get(randomToId.get(rnd));
+            const o = opposite.get(randomToId.get(rnd));
 
-                maps.push(d ?? o)
+            maps.push(d ?? o)
 
-            }
-            return maps;
         }
+        return maps;
     }
-
 }
 
-export interface MessageParseMethods extends UserMethods {
-
-}
 
+*/

+ 125 - 148
gramjs/client/messages.ts

@@ -1,28 +1,14 @@
 import {Api} from "../tl";
-import {Message} from '../tl/custom/message';
-import {DateLike, EntityLike, FileLike, MarkupLike, MessageLike} from "../define";
+import type {Message} from '../tl/custom/message';
+import type {DateLike, EntityLike, FileLike, MarkupLike, MessageIDLike, MessageLike} from "../define";
 import {RequestIter} from "../requestIter";
 import {_EntityType, _entityType} from "../Helpers";
 import {getMessageId, getPeerId, isArrayLike} from "../Utils";
-import {TelegramClient, utils} from "../index";
-import {MessageParseMethods} from "./messageParse";
-import {ButtonMethods} from "./buttons";
+import type {TelegramClient} from "../";
+import {utils} from "../";
 
 const _MAX_CHUNK_SIZE = 100;
 
-export interface SendMessageInterface {
-    replyTo?: number | Api.Message,
-    parseMode?: any,
-    formattingEntities?: Api.TypeMessageEntity,
-    linkPreview?: boolean,
-    file?: FileLike | FileLike[],
-    forceDocument?: boolean,
-    clearDraft?: boolean,
-    buttons?: MarkupLike,
-    silent?: boolean,
-    schedule?: DateLike
-
-}
 
 interface MessageIterParams {
     offsetId: number;
@@ -33,10 +19,10 @@ interface MessageIterParams {
     addOffset: number;
     filter: any;
     search: string;
-    replyTo: EntityLike;
+    replyTo: MessageIDLike;
 }
 
-class _MessagesIter extends RequestIter {
+export class _MessagesIter extends RequestIter {
     private entity?: Api.TypeInputPeer;
 
     async _init(entity: EntityLike, {offsetId, minId, maxId, fromUser, offsetDate, addOffset, filter, search, replyTo}: MessageIterParams) {
@@ -239,7 +225,7 @@ class _MessagesIter extends RequestIter {
     }
 }
 
-class _IDsIter extends RequestIter {
+export class _IDsIter extends RequestIter {
     async _init(entity: EntityLike, ids: MessageLike[]) {
         this.total = ids.length;
         this._ids = this.reverse ? ids.reverse() : ids;
@@ -298,15 +284,13 @@ class _IDsIter extends RequestIter {
             if (message instanceof Api.MessageEmpty || fromId && message.peerId != fromId) {
                 this.buffer?.push(undefined)
             } else {
-                // @ts-ignore
-                // TODO message._finishInit(this.client, entities, this._entity);
                 this.buffer?.push(message);
             }
         }
     }
 }
 
-interface IterMessagesParams {
+export interface IterMessagesParams {
     limit?: number;
     offsetDate?: DateLike;
     offsetId?: number;
@@ -322,7 +306,7 @@ interface IterMessagesParams {
     replyTo?: number;
 }
 
-interface SendMessageParams {
+export interface SendMessageParams {
     message: MessageLike;
     replyTo?: number | Api.Message;
     parseMode?: any;
@@ -336,145 +320,138 @@ interface SendMessageParams {
     schedule?: DateLike;
 }
 
-export class MessageMethods {
+//  MessageMethods {
 
-    iterMessages(entity: EntityLike, {limit, offsetDate, offsetId, maxId, minId, addOffset, search, filter, fromUser, waitTime, ids, reverse = false, replyTo}: IterMessagesParams) {
-        if (ids) {
-            if (typeof ids == 'number') {
-                ids = [ids]
-            }
-            // @ts-ignore
-            return new _IDsIter(this, ids.length, {
-                reverse: reverse,
-                waitTime: waitTime
-            }, {
-                entity: entity
-            });
+export function iterMessages(client: TelegramClient, entity: EntityLike, {limit, offsetDate, offsetId, maxId, minId, addOffset, search, filter, fromUser, waitTime, ids, reverse = false, replyTo}: IterMessagesParams) {
+    if (ids) {
+        if (typeof ids == 'number') {
+            ids = [ids]
         }
         // @ts-ignore
-        return new _MessagesIter(this, limit, {
-            waitTime: waitTime,
-            reverse: reverse
+        return new _IDsIter(this, ids.length, {
+            reverse: reverse,
+            waitTime: waitTime
         }, {
-            entity: entity,
-            offsetId: offsetId,
-            minId: minId,
-            maxId: maxId,
-            fromUser: fromUser,
-            offsetDate: offsetDate,
-            addOffset: addOffset,
-            filter: filter,
-            search: search,
-            replyTo: replyTo
-        })
+            entity: entity
+        });
     }
+    // @ts-ignore
+    return new _MessagesIter(client, limit, {
+        waitTime: waitTime,
+        reverse: reverse
+    }, {
+        entity: entity,
+        offsetId: offsetId,
+        minId: minId,
+        maxId: maxId,
+        fromUser: fromUser,
+        offsetDate: offsetDate,
+        addOffset: addOffset,
+        filter: filter,
+        search: search,
+        replyTo: replyTo
+    })
+}
 
-    async getMessages(entity: EntityLike, params: IterMessagesParams) {
-        if (Object.keys(params).length == 1 && params.limit === undefined) {
-            if (params.minId === undefined && params.maxId === undefined) {
-                params.limit = undefined;
-            } else {
-                params.limit = 1;
-            }
+export async function getMessages(client: TelegramClient, entity: EntityLike, params: IterMessagesParams) {
+    if (Object.keys(params).length == 1 && params.limit === undefined) {
+        if (params.minId === undefined && params.maxId === undefined) {
+            params.limit = undefined;
+        } else {
+            params.limit = 1;
         }
+    }
 
-        const it = this.iterMessages(entity, params);
-        const ids = params.ids;
-        if (ids && !isArrayLike(ids)) {
-            for await (const message of it) {
-                return message;
-            }
-            return;
-
+    const it = client.iterMessages(entity, params);
+    const ids = params.ids;
+    if (ids && !isArrayLike(ids)) {
+        for await (const message of it) {
+            return message;
         }
-        return await it.collect();
+        return;
+
     }
+    return await it.collect();
+}
 
-    // region Message
+// region Message
 
-    async sendMessage(entity: EntityLike, {message, replyTo, parseMode, formattingEntities, linkPreview = true, file, forceDocument, clearDraft, buttons, silent, schedule}: SendMessageParams) {
-        if (file) {
-            throw new Error("Not Supported Yet");
-            //return this.sendFile();
+export async function sendMessage(client: TelegramClient,
+                                  entity: EntityLike,
+                                  {
+                                      message,
+                                      replyTo,
+                                      parseMode, formattingEntities,
+                                      linkPreview = true,
+                                      file, forceDocument,
+                                      clearDraft,
+                                      buttons,
+                                      silent,
+                                      schedule
+                                  }: SendMessageParams) {
+    if (file) {
+        throw new Error("Not Supported Yet");
+        //return this.sendFile();
+    }
+    entity = await client.getInputEntity(entity);
+    let markup, request;
+    if (message instanceof Api.Message) {
+        if (buttons == undefined) {
+            markup = message.replyMarkup;
+        } else {
+            markup = client.buildReplyMarkup(buttons);
         }
-        entity = await this.getInputEntity(entity);
-        let markup, request;
-        if (message instanceof Api.Message) {
-            if (buttons == undefined) {
-                markup = message.replyMarkup;
-            } else {
-                markup = this.buildReplyMarkup(buttons);
-            }
-            if (silent == undefined) {
-                silent = message.silent;
-            }
-            if (message.media && !(message.media instanceof Api.MessageMediaWebPage)) {
-                throw new Error("Not Supported Yet");
-                /*
-                                return this.sendFile(entity, message.media, {
-                                    caption: message.message,
-                                    silent: silent,
-                                    replyTo: replyTo,
-                                    buttons: markup,
-                                    formattingEntities: message.entities,
-                                    schedule: schedule
-                                })
+        if (silent == undefined) {
+            silent = message.silent;
+        }
+        if (message.media && !(message.media instanceof Api.MessageMediaWebPage)) {
+            throw new Error("Not Supported Yet");
+            /*
+                            return this.sendFile(entity, message.media, {
+                                caption: message.message,
+                                silent: silent,
+                                replyTo: replyTo,
+                                buttons: markup,
+                                formattingEntities: message.entities,
+                                schedule: schedule
+                            })
 
-                 */
-            }
-            request = new Api.messages.SendMessage({
-                peer: entity,
-                message: message.message || '',
-                silent: silent,
-                replyToMsgId: getMessageId(replyTo),
-                replyMarkup: markup,
-                entities: message.entities,
-                clearDraft: clearDraft,
-                noWebpage: !(message.media instanceof Api.MessageMediaWebPage),
-                scheduleDate: schedule
-            })
-            message = message.message;
-        } else {
-            if (formattingEntities == undefined) {
-                [message, formattingEntities] = await this._parseMessageText(message, parseMode);
-            }
-            if (!message) {
-                throw new Error("The message cannot be empty unless a file is provided");
-            }
-            request = new Api.messages.SendMessage({
-                peer: entity,
-                message: message.toString(),
-                entities: formattingEntities,
-                noWebpage: !linkPreview,
-                replyToMsgId: getMessageId(replyTo),
-                clearDraft: clearDraft,
-                silent: silent,
-                replyMarkup: this.buildReplyMarkup(buttons),
-                scheduleDate: schedule
-            })
+             */
         }
-        const result = await this.invoke(request);
-        if (result instanceof Api.UpdateShortSentMessage) {
-            const newMessage = new Message({
-                id: result.id,
-                peerId: await this._getPeer(entity),
-                message: message,
-                date: result.date,
-                out: result.out,
-                media: result.media,
-                entities: result.entities,
-                replyMarkup: request.replyMarkup,
-            })
-            // @ts-ignore
-            //newMessage._finishInit(this, new Map<>(), entity);
-            return newMessage;
+        request = new Api.messages.SendMessage({
+            peer: entity,
+            message: message.message || '',
+            silent: silent,
+            replyToMsgId: getMessageId(replyTo),
+            replyMarkup: markup,
+            entities: message.entities,
+            clearDraft: clearDraft,
+            noWebpage: !(message.media instanceof Api.MessageMediaWebPage),
+            scheduleDate: schedule
+        })
+        message = message.message;
+    } else {
+        if (formattingEntities == undefined) {
+            [message, formattingEntities] = await client._parseMessageText(message, parseMode);
+        }
+        if (!message) {
+            throw new Error("The message cannot be empty unless a file is provided");
         }
-        return this._getResponseMessage(request, result, entity);
+        request = new Api.messages.SendMessage({
+            peer: entity,
+            message: message.toString(),
+            entities: formattingEntities,
+            noWebpage: !linkPreview,
+            replyToMsgId: getMessageId(replyTo),
+            clearDraft: clearDraft,
+            silent: silent,
+            replyMarkup: client.buildReplyMarkup(buttons),
+            scheduleDate: schedule
+        })
     }
-
-    // TODO do the rest
+    const result = await client.invoke(request);
+    return result;
+    //return client._getResponseMessage(request, result, entity);
 }
 
-export interface MessageMethods extends MessageParseMethods, ButtonMethods {
-
-}
+// TODO do the rest

+ 9 - 187
gramjs/client/telegramBaseClient.ts

@@ -1,19 +1,17 @@
-import {version} from "../index";
+import {version} from "../";
 import {IS_NODE} from "../Helpers";
 import {ConnectionTCPFull, ConnectionTCPObfuscated} from "../network/connection";
-import {Session} from "../sessions/Abstract";
+import type {Session} from "../sessions/Abstract";
 import {Logger} from "../extensions";
 import {StoreSession, StringSession} from "../sessions";
 import {Api} from "../tl";
 
 
 import os from 'os';
-import {LAYER} from "../tl/AllTLObjects";
-import {AuthKey} from "../crypto/AuthKey";
+import type {AuthKey} from "../crypto/AuthKey";
 import {EntityCache} from "../entityCache";
-import {UpdateMethods} from "./updates";
-import {MTProtoSender, UpdateConnectionState} from "../network";
-import {UserMethods} from "./users";
+import type {ParseInterface} from "./messageParse";
+import type {EventBuilder} from "../events/common";
 
 const DEFAULT_DC_ID = 1;
 const DEFAULT_IPV4_IP = IS_NODE ? '149.154.167.51' : 'pluto.web.telegram.org';
@@ -45,7 +43,7 @@ export class TelegramBaseClient {
     _config ?: Api.Config;
     public _log: Logger;
     public _floodSleepThreshold: number;
-    public session: StringSession|StoreSession;
+    public session: Session;
     public apiHash: string;
     public apiId: number;
     public _requestRetries: number;
@@ -61,12 +59,10 @@ export class TelegramBaseClient {
     public _bot?: boolean;
     public _selfInputPeer?: Api.InputPeerUser;
     public useWSS: boolean;
-    public _eventBuilders: any[];
+    public _eventBuilders: [EventBuilder,CallableFunction][];
     public _entityCache: EntityCache;
-
-    downloadMedia(...args: any) {
-        return undefined;
-    }
+    public _lastRequest?: number;
+    public _parseMode?: ParseInterface;
 
     constructor(session: string | Session, apiId: number, apiHash: string, {
         connection = IS_NODE ? ConnectionTCPFull : ConnectionTCPObfuscated,
@@ -152,39 +148,6 @@ export class TelegramBaseClient {
     }
 
 
-    async connect() {
-        await this._initSession();
-
-        this._sender = new MTProtoSender(this.session.getAuthKey(), {
-            logger: this._log,
-            dcId: this.session.dcId,
-            retries: this._connectionRetries,
-            delay: this._retryDelay,
-            autoReconnect: this._autoReconnect,
-            connectTimeout: this._timeout,
-            authKeyCallback: this._authKeyCallback.bind(this),
-            updateCallback: this._handleUpdate.bind(this),
-            isMainSender: true,
-        });
-
-        const connection = new this._connection(this.session.serverAddress
-            , this.session.port, this.session.dcId, this._log);
-        if (!await this._sender.connect(connection, this._dispatchUpdate.bind(this))) {
-            return
-        }
-        this.session.setAuthKey(this._sender.authKey);
-        await this.session.save();
-        this._initRequest.query = new Api.help.GetConfig();
-        await this._sender.send(new Api.InvokeWithLayer(
-            {
-                layer: LAYER,
-                query: this._initRequest
-            }
-        ));
-
-        this._dispatchUpdate({update: new UpdateConnectionState(1)});
-        this._updateLoop()
-    }
 
     get connected() {
         return this._sender && this._sender.isConnected();
@@ -213,17 +176,6 @@ export class TelegramBaseClient {
         this._eventBuilders = []
     }
 
-    async _switchDC(newDc: number) {
-        this._log.info(`Reconnecting to new data center ${newDc}`);
-        const DC = await this.getDC(newDc);
-        this.session.setDC(newDc, DC.ipAddress, DC.port);
-        // authKey's are associated with a server, which has now changed
-        // so it's not valid anymore. Set to None to force recreating it.
-        await this._sender.authKey.setKey();
-        this.session.setAuthKey();
-        await this.disconnect();
-        return this.connect()
-    }
 
     async _authKeyCallback(authKey: AuthKey, dcId: number) {
         this.session.setAuthKey(authKey, dcId);
@@ -232,136 +184,6 @@ export class TelegramBaseClient {
 
     // endregion
 
-    // region Working with different connections/Data Centers
-
-    removeSender(dcId: number) {
-        delete this._borrowedSenderPromises[dcId]
-    }
-
-    async _borrowExportedSender(dcId: number, retries = 5) {
-        let senderPromise = this._borrowedSenderPromises[dcId];
-        if (!senderPromise) {
-            senderPromise = this._createExportedSender(dcId, retries);
-            this._borrowedSenderPromises[dcId] = senderPromise;
-
-            senderPromise.then((sender: any) => {
-                if (!sender) {
-                    delete this._borrowedSenderPromises[dcId]
-                }
-            })
-        }
-        return senderPromise
-    }
-
-    async _createExportedSender(dcId: number, retries: number) {
-        const dc = await this.getDC(dcId);
-        const sender = new MTProtoSender(this.session.getAuthKey(dcId),
-            {
-                logger: this._log,
-                dcId: dcId,
-                retries: this._connectionRetries,
-                delay: this._retryDelay,
-                autoReconnect: this._autoReconnect,
-                connectTimeout: this._timeout,
-                authKeyCallback: this._authKeyCallback.bind(this),
-                isMainSender: dcId === this.session.dcId,
-                senderCallback: this.removeSender.bind(this),
-            });
-        for (let i = 0; i < retries; i++) {
-            try {
-                await sender.connect(new this._connection(
-                    dc.ipAddress,
-                    dc.port,
-                    dcId,
-                    this._log,
-                ));
-                if (this.session.dcId !== dcId) {
-                    this._log.info(`Exporting authorization for data center ${dc.ipAddress}`);
-                    const auth = await this.invoke(new Api.auth.ExportAuthorization({dcId: dcId}));
-                    this._initRequest.query = new Api.auth.ImportAuthorization({
-                            id: auth.id,
-                            bytes: auth.bytes,
-                        },
-                    )
-                    const req = new Api.InvokeWithLayer({
-                        layer: LAYER,
-                        query: this._initRequest
-                    });
-                    await sender.send(req)
-                }
-                sender.dcId = dcId;
-                return sender
-            } catch (e) {
-                console.log(e);
-                await sender.disconnect()
-            }
-        }
-        return null
-    }
-
-    async getDC(dcId: number): Promise<{ id: number, ipAddress: string, port: number }> {
-        if (!IS_NODE) {
-            switch (dcId) {
-                case 1:
-                    return {
-                        id: 1,
-                        ipAddress: 'pluto.web.telegram.org',
-                        port: 443,
-                    };
-                case 2:
-                    return {
-                        id: 2,
-                        ipAddress: 'venus.web.telegram.org',
-                        port: 443,
-                    };
-                case 3:
-                    return {
-                        id: 3,
-                        ipAddress: 'aurora.web.telegram.org',
-                        port: 443,
-                    };
-                case 4:
-                    return {
-                        id: 4,
-                        ipAddress: 'vesta.web.telegram.org',
-                        port: 443,
-                    };
-                case 5:
-                    return {
-                        id: 5,
-                        ipAddress: 'flora.web.telegram.org',
-                        port: 443,
-                    };
-                default:
-                    throw new Error(`Cannot find the DC with the ID of ${dcId}`)
-            }
-        }
-        if (!this._config) {
-            this._config = await this.invoke(new Api.help.GetConfig())
-        }
-        for (const DC of this._config.dcOptions) {
-            if (DC.id === dcId) {
-                return {
-                    id: DC.id,
-                    ipAddress: DC.ipAddress,
-                    port: 443,
-                }
-            }
-        }
-        throw new Error(`Cannot find the DC with the ID of ${dcId}`)
-    }
-
-    // endregion
-
-
-}
-
-export interface TelegramBaseClient {
-    invoke<R extends Api.AnyRequest>(request: R): Promise<R['__response']>;
-
-    _dispatchUpdate(args: { update: UpdateConnectionState | any }): Promise<void>;
 
-    _updateLoop(): Promise<void>;
 
-    _handleUpdate(update: Api.TypeUpdate | number): void;
 }

+ 104 - 110
gramjs/client/updates.ts

@@ -1,143 +1,137 @@
-import {EventBuilder} from "../events/common";
+import type {EventBuilder,EventCommon} from "../events/common";
 import {Api} from "../tl";
-import {UserMethods} from "./users";
-import {TelegramBaseClient} from "./telegramBaseClient";
-import {helpers} from "../index";
+import {helpers} from "../";
+import type {TelegramClient} from "../";
 import bigInt from 'big-integer';
 import {UpdateConnectionState} from "../network";
-import {Message} from "../tl/custom/message";
-import {getMessageId, getPeerId} from "../Utils";
-
-export class UpdateMethods {
-    on(event: any) {
-        return (f: CallableFunction) => {
-            this.addEventHandler(f, event);
-            return f;
-        }
-    }
+import type {Raw} from "../events";
 
-    addEventHandler(callback: CallableFunction, event?: EventBuilder) {
-        // TODO; add actual handling
-        this._eventBuilders.push([event, callback])
+// export class UpdateMethods {
+export function on(client: TelegramClient, event: any) {
+    return (f: CallableFunction) => {
+        client.addEventHandler(f, event);
+        return f;
     }
+}
 
-    removeEventHandler(callback: CallableFunction, event: EventBuilder) {
-        this._eventBuilders = this._eventBuilders.filter(function (item) {
-            return item !== [event, callback]
-        })
+export function addEventHandler(client: TelegramClient, callback: CallableFunction, event?: EventBuilder) {
+    if (event == undefined) {
+        // recursive imports :(
+        const raw = require("../events/Raw");
+        event = new raw({}) as Raw;
     }
+    console.log("event tpo add is", event);
+    client._eventBuilders.push([event, callback])
+}
 
-    listEventHandlers() {
-        return this._eventBuilders;
-    }
+export function removeEventHandler(client: TelegramClient, callback: CallableFunction, event: EventBuilder) {
+    client._eventBuilders = client._eventBuilders.filter(function (item) {
+        return item !== [event, callback]
+    })
+}
 
-    catchUp() {
-        // TODO
-    }
+export function listEventHandlers(client: TelegramClient) {
+    return client._eventBuilders;
+}
 
-    _handleUpdate(update: Api.TypeUpdate | number):void {
-        if (typeof update === 'number') {
-            if ([-1, 0, 1].includes(update)) {
-                this._dispatchUpdate({update: new UpdateConnectionState(update)})
-                return
-            }
+export function catchUp() {
+    // TODO
+}
+
+export function _handleUpdate(client: TelegramClient, update: Api.TypeUpdate | number): void {
+    if (typeof update === 'number') {
+        if ([-1, 0, 1].includes(update)) {
+            client._dispatchUpdate({update: new UpdateConnectionState(update)})
+            return
         }
+    }
 
-        //this.session.processEntities(update)
-        this._entityCache.add(update);
-        this.session.processEntities(update);
+    //this.session.processEntities(update)
+    client._entityCache.add(update);
+    client.session.processEntities(update);
 
-        if (update instanceof Api.Updates || update instanceof Api.UpdatesCombined) {
-            // TODO deal with entities
-            const entities = []
-            for (const x of [...update.users, ...update.chats]) {
-                entities.push(x)
-            }
-            for (const u of update.updates) {
-                this._processUpdate(u, update.updates, entities)
-            }
-        } else if (update instanceof Api.UpdateShort) {
-            this._processUpdate(update.update, null)
-        } else {
-            this._processUpdate(update, null)
+    if (update instanceof Api.Updates || update instanceof Api.UpdatesCombined) {
+        // TODO deal with entities
+        const entities = []
+        for (const x of [...update.users, ...update.chats]) {
+            entities.push(x)
         }
-        // TODO add caching
-        // this._stateCache.update(update)
+        for (const u of update.updates) {
+            client._processUpdate(u, update.updates, entities)
+        }
+    } else if (update instanceof Api.UpdateShort) {
+        client._processUpdate(update.update, null)
+    } else {
+        client._processUpdate(update, null)
     }
+}
 
-    _processUpdate(update: any, others: any, entities?: any) {
-        update._entities = entities || {};
-        const args = {
-            update: update,
-            others: others,
-        }
-        this._dispatchUpdate(args)
+export function _processUpdate(client: TelegramClient, update: any, others: any, entities?: any) {
+    update._entities = entities || {};
+    const args = {
+        update: update,
+        others: others,
     }
 
-    async _dispatchUpdate(args: { update: UpdateConnectionState | any }):Promise<void> {
-        for (const [builder, callback] of this._eventBuilders) {
-            let event = args.update;
+    client._dispatchUpdate(args)
+}
+
+export async function _dispatchUpdate(client: TelegramClient, args: { update: UpdateConnectionState | any }): Promise<void> {
+    for (const [builder, callback] of client._eventBuilders) {
+        if (!builder.resolved){
+            await builder.resolve(client);
+        }
+        console.log("builder is", builder);
+        let event = args.update;
+        if (event) {
+            if (!client._selfInputPeer) {
+                await client.getMe(true)
+            }
+            if (!(event instanceof UpdateConnectionState)) {
+                // TODO fix me
+            }
+            event = builder.build(event);
             if (event) {
-                if (!this._selfInputPeer) {
-                    await this.getMe(true)
+                if ("_eventName" in event) {
+                    event._setClient(client);
+                    event.originalUpdate = args.update;
+                    event._entities = args.update._entities
                 }
-                if (!(event instanceof UpdateConnectionState)) {
-                    if ('message' in event) {
-                        event = new Message({
-                            id: event.message.id,
-                            peerId: await this._getPeer(event.message.peerId),
-                            message: event.message,
-                            date: event.message.date,
-                            out: event.message.out,
-                            media: event.message.media,
-                            entities: event.message.entities,
-                            replyMarkup: event.message.replyMarkup,
-                            _entities: event._entities,
-                        });
-                        const entities = new Map();
-                        for (const entity of event._entities) {
-                            entities.set(getPeerId(entity), entity)
-                        }
-                        event._finishInit(this, entities);
-                    }
+                const filter = await builder.filter(event);
+                if (!filter) {
+                    continue
                 }
-                await callback(event)
+                await callback(event);
             }
         }
     }
+}
 
-    async _updateLoop():Promise<void> {
-        while (this.connected) {
-            const rnd = helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)
-            await helpers.sleep(1000 * 60)
-            // We don't care about the result we just want to send it every
-            // 60 seconds so telegram doesn't stop the connection
-            try {
-                this._sender.send(new Api.Ping({
-                    pingId: bigInt(rnd),
-                }))
-            } catch (e) {
+export async function _updateLoop(client: TelegramClient): Promise<void> {
+    while (client.connected) {
+        const rnd = helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)
+        await helpers.sleep(1000 * 60)
+        // We don't care about the result we just want to send it every
+        // 60 seconds so telegram doesn't stop the connection
+        try {
+            client._sender.send(new Api.Ping({
+                pingId: bigInt(rnd),
+            }))
+        } catch (e) {
 
-            }
+        }
 
-            // We need to send some content-related request at least hourly
-            // for Telegram to keep delivering updates, otherwise they will
-            // just stop even if we're connected. Do so every 30 minutes.
+        // We need to send some content-related request at least hourly
+        // for Telegram to keep delivering updates, otherwise they will
+        // just stop even if we're connected. Do so every 30 minutes.
 
-            // TODO Call getDifference instead since it's more relevant
-            if (!this._lastRequest  || new Date().getTime() - this._lastRequest > 30 * 60 * 1000) {
-                try {
-                    await this.invoke(new Api.updates.GetState())
-                } catch (e) {
+        // TODO Call getDifference instead since it's more relevant
+        if (!client._lastRequest || new Date().getTime() - client._lastRequest > 30 * 60 * 1000) {
+            try {
+                await client.invoke(new Api.updates.GetState())
+            } catch (e) {
 
-                }
             }
         }
     }
-
-
-}
-
-export interface UpdateMethods extends UserMethods, TelegramBaseClient {
 }
-

+ 124 - 2
gramjs/client/uploads.ts

@@ -1,3 +1,125 @@
-export class UploadMethods {
-    //TODO imepleent
+import { Api } from '../tl';
+
+import type {TelegramClient} from './TelegramClient';
+// @ts-ignore
+import { generateRandomBytes, readBigIntFromBuffer, sleep } from '../Helpers';
+// @ts-ignore
+import { getAppropriatedPartSize } from '../Utils';
+
+interface OnProgress {
+    // Float between 0 and 1.
+    (progress: number): void;
+
+    isCanceled?: boolean;
+}
+
+export interface UploadFileParams {
+    file: File;
+    workers: number;
+    onProgress?: OnProgress;
+}
+
+const KB_TO_BYTES = 1024;
+const LARGE_FILE_THRESHOLD = 10 * 1024 * 1024;
+const UPLOAD_TIMEOUT = 15 * 1000;
+
+export async function uploadFile(
+    client: TelegramClient,
+    fileParams: UploadFileParams,
+): Promise<Api.InputFile | Api.InputFileBig> {
+    const { file, onProgress } = fileParams;
+    let { workers } = fileParams;
+
+    const { name, size } = file;
+    const fileId = readBigIntFromBuffer(generateRandomBytes(8), true, true);
+    const isLarge = size > LARGE_FILE_THRESHOLD;
+
+    const partSize = getAppropriatedPartSize(size) * KB_TO_BYTES;
+    const partCount = Math.floor((size + partSize - 1) / partSize);
+    const buffer = Buffer.from(await fileToBuffer(file));
+
+    // We always upload from the DC we are in.
+    const sender = await client._borrowExportedSender(client.session.dcId);
+
+    if (!workers || !size) {
+        workers = 1;
+    }
+    if (workers >= partCount) {
+        workers = partCount;
+    }
+
+    let progress = 0;
+    if (onProgress) {
+        onProgress(progress);
+    }
+
+    for (let i = 0; i < partCount; i += workers) {
+        let sendingParts = [];
+        let end = i + workers;
+        if (end > partCount) {
+            end = partCount;
+        }
+
+        for (let j = i; j < end; j++) {
+            const bytes = buffer.slice(j * partSize, (j + 1) * partSize);
+
+            sendingParts.push((async () => {
+                await sender.send(
+                    isLarge
+                        ? new Api.upload.SaveBigFilePart({
+                            fileId,
+                            filePart: j,
+                            fileTotalParts: partCount,
+                            bytes,
+                        })
+                        : new Api.upload.SaveFilePart({
+                            fileId,
+                            filePart: j,
+                            bytes,
+                        }),
+                );
+
+                if (onProgress) {
+                    if (onProgress.isCanceled) {
+                        throw new Error('USER_CANCELED');
+                    }
+
+                    progress += (1 / partCount);
+                    onProgress(progress);
+                }
+            })());
+
+        }
+        try {
+            await Promise.race([
+                await Promise.all(sendingParts),
+                sleep(UPLOAD_TIMEOUT * workers).then(() => Promise.reject(new Error('TIMEOUT'))),
+            ]);
+        } catch (err) {
+            if (err.message === 'TIMEOUT') {
+                console.warn('Upload timeout. Retrying...');
+                i -= workers;
+                continue;
+            }
+
+            throw err;
+        }
+    }
+
+    return isLarge
+        ? new Api.InputFileBig({
+            id: fileId,
+            parts: partCount,
+            name,
+        })
+        : new Api.InputFile({
+            id: fileId,
+            parts: partCount,
+            name,
+            md5Checksum: '', // This is not a "flag", so not sure if we can make it optional.
+        });
+}
+
+function fileToBuffer(file: File) {
+    return new Response(file).arrayBuffer();
 }

+ 324 - 336
gramjs/client/users.ts

@@ -1,409 +1,397 @@
 import {Api} from "../tl";
-import {Entity, EntityLike} from "../define";
-import {getPeerId, isArrayLike} from "../Utils";
+import type {Entity, EntityLike} from "../define";
+import {getPeerId as peerUtils, isArrayLike} from "../Utils";
 import {_entityType, _EntityType, sleep} from "../Helpers";
-import {errors, utils} from "../index";
-import {TelegramBaseClient} from "./telegramBaseClient";
+import {errors, utils} from "../";
+import type {TelegramClient} from "../";
 import bigInt from 'big-integer';
+// UserMethods {
+// region Invoking Telegram request
 
-export class UserMethods {
-    // region Invoking Telegram request
-    public _lastRequest?: number;
-
-    /**
-     * Invokes a MTProtoRequest (sends and receives it) and returns its result
-     * @param request
-     * @returns {Promise}
-     */
-    async invoke<R extends Api.AnyRequest>(request: R): Promise<R['__response']> {
-        if (request.classType !== 'request') {
-            throw new Error('You can only invoke MTProtoRequests')
-        }
-        // This causes issues for now because not enough utils
-        // await request.resolve(this, utils)
-
-        await request.resolve(this, utils);
-        this._lastRequest = new Date().getTime();
-        let attempt: number;
-        for (attempt = 0; attempt < this._requestRetries; attempt++) {
-            try {
-                const promise = this._sender.send(request);
-                const result = await promise;
-                this.session.processEntities(result)
-                this._entityCache.add(result);
-                return result
-            } catch (e) {
-                if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL' ||
-                    e.message === 'RPC_MCGET_FAIL') {
-                    this._log.warn(`Telegram is having internal issues ${e.constructor.name}`);
-                    await sleep(2000)
-                } else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) {
-                    if (e.seconds <= this.floodSleepThreshold) {
-                        this._log.info(`Sleeping for ${e.seconds}s on flood wait`);
-                        await sleep(e.seconds * 1000)
-                    } else {
-                        throw e
-                    }
-                } else if (e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError ||
-                    e instanceof errors.UserMigrateError) {
-                    this._log.info(`Phone migrated to ${e.newDc}`);
-                    const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError;
-                    if (shouldRaise && await this.isUserAuthorized()) {
-                        throw e
-                    }
-                    await this._switchDC(e.newDc)
+/**
+ * Invokes a MTProtoRequest (sends and receives it) and returns its result
+ * @param client
+ * @param request
+ * @returns {Promise}
+ */
+export async function invoke<R extends Api.AnyRequest>(client: TelegramClient, request: R): Promise<R['__response']> {
+    if (request.classType !== 'request') {
+        throw new Error('You can only invoke MTProtoRequests')
+    }
+    // This causes issues for now because not enough utils
+    // await request.resolve(this, utils)
+    await request.resolve(client, utils);
+    client._lastRequest = new Date().getTime();
+    let attempt: number;
+    for (attempt = 0; attempt < client._requestRetries; attempt++) {
+        try {
+            const promise = client._sender.send(request);
+            const result = await promise;
+            client.session.processEntities(result)
+            client._entityCache.add(result);
+            return result
+        } catch (e) {
+            if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL' ||
+                e.message === 'RPC_MCGET_FAIL') {
+                client._log.warn(`Telegram is having internal issues ${e.constructor.name}`);
+                await sleep(2000)
+            } else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) {
+                if (e.seconds <= client.floodSleepThreshold) {
+                    client._log.info(`Sleeping for ${e.seconds}s on flood wait`);
+                    await sleep(e.seconds * 1000)
                 } else {
                     throw e
                 }
+            } else if (e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError ||
+                e instanceof errors.UserMigrateError) {
+                client._log.info(`Phone migrated to ${e.newDc}`);
+                const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError;
+                if (shouldRaise && await client.isUserAuthorized()) {
+                    throw e
+                }
+                await client._switchDC(e.newDc)
+            } else {
+                throw e
             }
         }
-        throw new Error(`Request was unsuccessful ${attempt} time(s)`)
     }
+    throw new Error(`Request was unsuccessful ${attempt} time(s)`)
+}
 
-    async getMe(inputPeer = false): Promise<Api.InputPeerUser | Api.User | undefined> {
-        if (inputPeer && this._selfInputPeer) {
-            return this._selfInputPeer;
-        }
-        try {
-            const me = (await this.invoke(new Api.users
-                .GetUsers({id: [new Api.InputUserSelf()]})))[0] as Api.User;
-            this._bot = me.bot;
+export async function getMe(client: TelegramClient, inputPeer = false): Promise<Api.InputPeerUser | Api.User> {
+    if (inputPeer && client._selfInputPeer) {
+        return client._selfInputPeer;
+    }
+    try {
+        const me = (await client.invoke(new Api.users
+            .GetUsers({id: [new Api.InputUserSelf()]})))[0] as Api.User;
+        client._bot = me.bot;
 
-            if (!this._selfInputPeer) {
-                this._selfInputPeer = utils.getInputPeer(me, true) as Api.InputPeerUser;
-            }
-            return inputPeer ? this._selfInputPeer : me;
-        } catch (e) {
+        if (!client._selfInputPeer) {
+            client._selfInputPeer = utils.getInputPeer(me, true) as Api.InputPeerUser;
         }
+        return inputPeer ? client._selfInputPeer : me;
+    } catch (e) {
+        throw new Error("Could not get me");
     }
+}
 
-    async isBot() {
-        if (this._bot === undefined) {
-            const me = await this.getMe();
-            if (me) {
-                return !(me instanceof Api.InputPeerUser) ? me.bot : undefined;
-            }
+export async function isBot(client: TelegramClient) {
+    if (client._bot === undefined) {
+        const me = await client.getMe();
+        if (me) {
+            return !(me instanceof Api.InputPeerUser) ? me.bot : undefined;
         }
-        return this._bot;
+    }
+    return client._bot;
+}
+
+export async function isUserAuthorized(client: TelegramClient) {
+    try {
+        await client.invoke(new Api.updates.GetState());
+        return true;
+    } catch (e) {
+        return false;
+    }
+}
+
+export async function getEntity(client: TelegramClient, entity: any): Promise<Entity> {
+    const single = !isArrayLike(entity);
+
+    if (single) {
+        entity = [entity];
     }
 
-    async isUserAuthorized() {
+    const inputs = [];
+    for (const x of entity) {
+        if (typeof x === 'string') {
+            inputs.push(x);
+        } else {
+            inputs.push(await client.getInputEntity(x));
+        }
+    }
+    const lists = new Map<number, any[]>([
+        [_EntityType.USER, []],
+        [_EntityType.CHAT, []],
+        [_EntityType.CHANNEL, []],
+    ]);
+    for (const x of inputs) {
         try {
-            await this.invoke(new Api.updates.GetState());
-            return true;
+            lists.get(_entityType(x))!.push(x);
         } catch (e) {
-            return false;
+
         }
     }
+    let users = lists.get(_EntityType.USER)!;
+    let chats = lists.get(_EntityType.CHAT)!;
+    let channels = lists.get(_EntityType.CHANNEL)!;
+    console.log("getting users", users);
+    console.log("getting chats", chats);
+    console.log("getting channels", channels);
 
-    async getEntity(entity: any): Promise<Entity> {
-        const single = !isArrayLike(entity);
+    if (users.length) {
+        users = await client.invoke(new Api.users.GetUsers({
+            id: users
+        }));
+    }
+    if (chats.length) {
+        const chatIds = chats.map((x) => x.chatId);
+        chats = (await client.invoke(new Api.messages.GetChats({id: chatIds}))).chats;
+    }
+    if (channels.length) {
+        channels = (await client.invoke(new Api.channels.GetChannels(({id: channels})))).chats;
+    }
+    const idEntity = new Map<number, any>();
 
-        if (single) {
-            // TODO fix this
-            // @ts-ignore
-            entity = [entity];
-        }
-        const inputs = [];
-        for (const x of entity) {
-            if (typeof x === 'string') {
-                inputs.push(x);
-            } else {
-                inputs.push(await this.getInputEntity(x));
-            }
-        }
-        const lists = new Map<number, any[]>([
-            [_EntityType.USER, []],
-            [_EntityType.CHAT, []],
-            [_EntityType.CHANNEL, []],
-        ]);
-        for (const x of inputs) {
-            try {
-                lists.get(_entityType(x))?.push(x);
-            } catch (e) {
 
-            }
-        }
-        let users = lists.get(_EntityType.USER);
-        let chats = lists.get(_EntityType.CHAT);
-        let channels = lists.get(_EntityType.CHANNEL);
-        if (users) {
-            const tmp = [];
-            while (users) {
-                let curr;
-                [curr, users] = [users.slice(0, 200), users.slice(200)];
-                tmp.push([...await this.invoke(new Api.users.GetUsers({
-                    id: curr
-                }))])
-            }
-            users = tmp;
-        }
-        if (chats) {
-            const chatIds = chats.map((x) => x.chatId);
-            chats = (await this.invoke(new Api.messages.GetChats({id: chatIds}))).chats;
-        }
-        if (channels) {
-            channels = (await this.invoke(new Api.channels.GetChannels(({id: channels})))).chats;
-        }
-        const idEntity = new Map<number, any>();
+    for (const user of users) {
+        idEntity.set(peerUtils(user), user);
+    }
 
-        const res = [];
-        if (users) {
-            for (const user of users) {
-                res.push([getPeerId(user), user])
-            }
-        }
-        if (channels) {
-            for (const channel of channels) {
-                res.push([getPeerId(channel), channel])
-            }
-        }
-        if (chats) {
-            for (const chat of chats) {
-                res.push([getPeerId(chat), chat])
-            }
-        }
-        for (const x of res) {
-            idEntity.set(x[0], x[1]);
-        }
-        const result = [];
-        for (const x of inputs) {
-            if (typeof x === 'string') {
-                result.push(await this._getEntityFromString(x));
-            } else if (x instanceof Api.InputPeerSelf) {
-                result.push(idEntity.get(getPeerId(x)))
-            } else {
-                for (const [key, u] of idEntity.entries()) {
-                    if (u instanceof Api.User && u.self) {
-                        result.push(u);
-                        break
-                    }
+    for (const channel of channels) {
+        idEntity.set(peerUtils(channel), channel);
+    }
+
+    for (const chat of chats) {
+        idEntity.set(peerUtils(chat), chat);
+    }
+
+    const result = [];
+    for (const x of inputs) {
+        if (typeof x === 'string') {
+            result.push(await client._getEntityFromString(x));
+        } else if (!(x instanceof Api.InputPeerSelf)) {
+            result.push(idEntity.get(peerUtils(x)))
+        } else {
+            for (const [key, u] of idEntity.entries()) {
+                if (u instanceof Api.User && u.self) {
+                    result.push(u);
+                    break
                 }
             }
         }
-        return single ? result[0] : result;
     }
+    return single ? result[0] : result;
+}
 
-    async getInputEntity(peer: EntityLike): Promise<Api.TypeInputPeer> {
-        // Short-circuit if the input parameter directly maps to an InputPeer
+export async function getInputEntity(client: TelegramClient, peer: EntityLike): Promise<Api.TypeInputPeer> {
+    // Short-circuit if the input parameter directly maps to an InputPeer
 
-        try {
-            return utils.getInputPeer(peer)
-            // eslint-disable-next-line no-empty
-        } catch (e) {
-        }
-        // Next in priority is having a peer (or its ID) cached in-memory
-        try {
-            // 0x2d45687 == crc32(b'Peer')
-            if (typeof peer !== "string" && (typeof peer === 'number' || peer.SUBCLASS_OF_ID === 0x2d45687)) {
-                const res = this._entityCache.get(peer);
-                if (res) {
-                    return res;
-                }
+    try {
+        return utils.getInputPeer(peer)
+        // eslint-disable-next-line no-empty
+    } catch (e) {
+    }
+    // Next in priority is having a peer (or its ID) cached in-memory
+    try {
+        // 0x2d45687 == crc32(b'Peer')
+        if (typeof peer !== "string" && (typeof peer === 'number' || peer.SUBCLASS_OF_ID === 0x2d45687)) {
+            const res = client._entityCache.get(peer);
+            if (res) {
+                return res;
             }
-            // eslint-disable-next-line no-empty
-        } catch (e) {
         }
-        // Then come known strings that take precedence
-        if (typeof peer == 'string') {
-            if (['me', 'this', 'self'].includes(peer)) {
-                return new Api.InputPeerSelf();
-            }
+        // eslint-disable-next-line no-empty
+    } catch (e) {
+    }
+    // Then come known strings that take precedence
+    if (typeof peer == 'string') {
+        if (['me', 'this', 'self'].includes(peer)) {
+            return new Api.InputPeerSelf();
         }
+    }
 
-        // No InputPeer, cached peer, or known string. Fetch from disk cache
-        try {
-            return this.session.getInputEntity(peer)
-            // eslint-disable-next-line no-empty
-        } catch (e) {
+    // No InputPeer, cached peer, or known string. Fetch from disk cache
+    try {
+        if (peer != undefined) {
+            return client.session.getInputEntity(peer)
         }
-        // Only network left to try
-        if (typeof peer === 'string') {
-            return utils.getInputPeer(await this._getEntityFromString(peer))
+        // eslint-disable-next-line no-empty
+    } catch (e) {
+    }
+    // Only network left to try
+    if (typeof peer === 'string') {
+        return utils.getInputPeer(await client._getEntityFromString(peer))
+    }
+    // If we're a bot and the user has messaged us privately users.getUsers
+    // will work with accessHash = 0. Similar for channels.getChannels.
+    // If we're not a bot but the user is in our contacts, it seems to work
+    // regardless. These are the only two special-cased requests.
+    peer = utils.getPeer(peer);
+    if (peer instanceof Api.PeerUser) {
+        const users = await client.invoke(new Api.users.GetUsers({
+            id: [new Api.InputUser({
+                userId: peer.userId,
+                accessHash: bigInt.zero,
+            })],
+        }));
+        if (users.length && !(users[0] instanceof Api.UserEmpty)) {
+            // If the user passed a valid ID they expect to work for
+            // channels but would be valid for users, we get UserEmpty.
+            // Avoid returning the invalid empty input peer for that.
+            //
+            // We *could* try to guess if it's a channel first, and if
+            // it's not, work as a chat and try to validate it through
+            // another request, but that becomes too much work.
+            return utils.getInputPeer(users[0])
         }
-        // If we're a bot and the user has messaged us privately users.getUsers
-        // will work with accessHash = 0. Similar for channels.getChannels.
-        // If we're not a bot but the user is in our contacts, it seems to work
-        // regardless. These are the only two special-cased requests.
-        peer = utils.getPeer(peer);
-        if (peer instanceof Api.PeerUser) {
-            const users = await this.invoke(new Api.users.GetUsers({
-                id: [new Api.InputUser({
-                    userId: peer.userId,
+    } else if (peer instanceof Api.PeerChat) {
+        return new Api.InputPeerChat({
+            chatId: peer.chatId,
+        })
+    } else if (peer instanceof Api.PeerChannel) {
+        try {
+            const channels = await client.invoke(new Api.channels.GetChannels({
+                id: [new Api.InputChannel({
+                    channelId: peer.channelId,
                     accessHash: bigInt.zero,
                 })],
             }));
-            if (users.length && !(users[0] instanceof Api.UserEmpty)) {
-                // If the user passed a valid ID they expect to work for
-                // channels but would be valid for users, we get UserEmpty.
-                // Avoid returning the invalid empty input peer for that.
-                //
-                // We *could* try to guess if it's a channel first, and if
-                // it's not, work as a chat and try to validate it through
-                // another request, but that becomes too much work.
-                return utils.getInputPeer(users[0])
-            }
-        } else if (peer instanceof Api.PeerChat) {
-            return new Api.InputPeerChat({
-                chatId: peer.chatId,
-            })
-        } else if (peer instanceof Api.PeerChannel) {
-            try {
-                const channels = await this.invoke(new Api.channels.GetChannels({
-                    id: [new Api.InputChannel({
-                        channelId: peer.channelId,
-                        accessHash: bigInt.zero,
-                    })],
-                }));
 
-                return utils.getInputPeer(channels.chats[0])
-                // eslint-disable-next-line no-empty
-            } catch (e) {
-                console.log(e)
-            }
+            return utils.getInputPeer(channels.chats[0])
+            // eslint-disable-next-line no-empty
+        } catch (e) {
+            console.log(e)
         }
-        throw new Error(`Could not find the input entity for ${peer}.
-         Please read https://` +
-            'docs.telethon.dev/en/latest/concepts/entities.html to' +
-            ' find out more details.',
-        )
     }
+    throw new Error(`Could not find the input entity for ${peer}.
+         Please read https://` +
+        'docs.telethon.dev/en/latest/concepts/entities.html to' +
+        ' find out more details.',
+    )
+}
 
-    async _getEntityFromString(string: string) {
-        const phone = utils.parsePhone(string);
-        if (phone) {
-            try {
-                const result = await this.invoke(
-                    new Api.contacts.GetContacts({
-                        hash: 0
-                    }));
-                if (!(result instanceof Api.contacts.ContactsNotModified)) {
-                    for (const user of result.users) {
-                        if (!(user instanceof Api.User) || user.phone === phone) {
-                            return user
-                        }
+export async function _getEntityFromString(client: TelegramClient, string: string) {
+    const phone = utils.parsePhone(string);
+    if (phone) {
+        try {
+            const result = await client.invoke(
+                new Api.contacts.GetContacts({
+                    hash: 0
+                }));
+            if (!(result instanceof Api.contacts.ContactsNotModified)) {
+                for (const user of result.users) {
+                    if (!(user instanceof Api.User) || user.phone === phone) {
+                        return user
                     }
                 }
+            }
 
-            } catch (e) {
-                if (e.message === 'BOT_METHOD_INVALID') {
-                    throw new Error('Cannot get entity by phone number as a ' +
-                        'bot (try using integer IDs, not strings)')
-                }
-                throw e
+        } catch (e) {
+            if (e.message === 'BOT_METHOD_INVALID') {
+                throw new Error('Cannot get entity by phone number as a ' +
+                    'bot (try using integer IDs, not strings)')
             }
-        } else if (['me', 'this'].includes(string.toLowerCase())) {
-            return this.getMe()
-        } else {
-            const {username, isInvite} = utils.parseUsername(string);
-            if (isInvite) {
-                const invite = await this.invoke(new Api.messages.CheckChatInvite({
-                    'hash': username,
-                }));
-                if (invite instanceof Api.ChatInvite) {
-                    throw new Error('Cannot get entity from a channel (or group) ' +
-                        'that you are not part of. Join the group and retry',
-                    )
-                } else if (invite instanceof Api.ChatInviteAlready) {
-                    return invite.chat
-                }
-            } else if (username) {
-                try {
-                    const result = await this.invoke(
-                        new Api.contacts.ResolveUsername({username: username}));
-                    const pid = utils.getPeerId(result.peer, false);
-                    if (result.peer instanceof Api.PeerUser) {
-                        for (const x of result.users) {
-                            if (x.id === pid) {
-                                return x
-                            }
-                        }
-                    } else {
-                        for (const x of result.chats) {
-                            if (x.id === pid) {
-                                return x
-                            }
+            throw e
+        }
+    } else if (['me', 'this'].includes(string.toLowerCase())) {
+        return client.getMe()
+    } else {
+        const {username, isInvite} = utils.parseUsername(string);
+        if (isInvite) {
+            const invite = await client.invoke(new Api.messages.CheckChatInvite({
+                'hash': username,
+            }));
+            if (invite instanceof Api.ChatInvite) {
+                throw new Error('Cannot get entity from a channel (or group) ' +
+                    'that you are not part of. Join the group and retry',
+                )
+            } else if (invite instanceof Api.ChatInviteAlready) {
+                return invite.chat
+            }
+        } else if (username) {
+            try {
+                const result = await client.invoke(
+                    new Api.contacts.ResolveUsername({username: username}));
+                const pid = utils.getPeerId(result.peer, false);
+                if (result.peer instanceof Api.PeerUser) {
+                    for (const x of result.users) {
+                        if (x.id === pid) {
+                            return x
                         }
                     }
-                } catch (e) {
-                    if (e.message === 'USERNAME_NOT_OCCUPIED') {
-                        throw new Error(`No user has "${username}" as username`)
+                } else {
+                    for (const x of result.chats) {
+                        if (x.id === pid) {
+                            return x
+                        }
                     }
-                    throw e
                 }
+            } catch (e) {
+                if (e.message === 'USERNAME_NOT_OCCUPIED') {
+                    throw new Error(`No user has "${username}" as username`)
+                }
+                throw e
             }
         }
-        throw new Error(`Cannot find any entity corresponding to "${string}"`)
     }
+    throw new Error(`Cannot find any entity corresponding to "${string}"`)
+}
 
-    async getPeerId(peer: EntityLike, addMark = false) {
-        if (typeof peer == 'number') {
-            return utils.getPeerId(peer, addMark);
-        }
-        if (typeof peer == 'string') {
-            peer = await this.getInputEntity(peer);
-        }
-
-        if (peer.SUBCLASS_OF_ID == 0x2d45687 || peer.SUBCLASS_OF_ID == 0xc91c90b6) {
-            peer = await this.getInputEntity(peer);
-        }
-        if (peer instanceof Api.InputPeerSelf) {
-            peer = await this.getMe(true);
-        }
+export async function getPeerId(client: TelegramClient, peer: EntityLike, addMark = false) {
+    if (typeof peer == 'number') {
         return utils.getPeerId(peer, addMark);
-
-
+    }
+    if (typeof peer == 'string') {
+        peer = await client.getInputEntity(peer);
     }
 
-    async _getPeer(peer: EntityLike) {
-        if (!peer) {
-            return undefined;
-        }
-        const [i, cls] = utils.resolveId(await this.getPeerId(peer));
-        return new cls({
-            userId: i,
-            channelId: i,
-            chatId: i
-        });
+    if (peer.SUBCLASS_OF_ID == 0x2d45687 || peer.SUBCLASS_OF_ID == 0xc91c90b6) {
+        peer = await client.getInputEntity(peer);
+    }
+    if (peer instanceof Api.InputPeerSelf) {
+        peer = await client.getMe(true);
     }
+    return utils.getPeerId(peer, addMark);
 
-    async _getInputDialog(dialog: any) {
-        try {
-            if (dialog.SUBCLASS_OF_ID == 0xa21c9795) { // crc32(b'InputDialogPeer')
-                dialog.peer = await this.getInputEntity(dialog.peer);
-                return dialog
-            } else if (dialog.SUBCLASS_OF_ID == 0xc91c90b6) { //crc32(b'InputPeer')
-                return new Api.InputDialogPeer({
-                    peer: dialog,
-                });
-            }
 
-        } catch (e) {
+}
 
-        }
-        return new Api.InputDialogPeer({
-            peer: dialog
-        });
+export async function _getPeer(client: TelegramClient, peer: EntityLike) {
+    if (!peer) {
+        return undefined;
     }
+    const [i, cls] = utils.resolveId(await client.getPeerId(peer));
+    return new cls({
+        userId: i,
+        channelId: i,
+        chatId: i
+    });
+}
 
-    async _getInputNotify(notify: any) {
-        try {
-            if (notify.SUBCLASS_OF_ID == 0x58981615) {
-                if (notify instanceof Api.InputNotifyPeer) {
-                    notify.peer = await this.getInputEntity(notify.peer)
-                }
-                return notify;
-            }
-        } catch (e) {
-
+export async function _getInputDialog(client: TelegramClient, dialog: any) {
+    try {
+        if (dialog.SUBCLASS_OF_ID == 0xa21c9795) { // crc32(b'InputDialogPeer')
+            dialog.peer = await client.getInputEntity(dialog.peer);
+            return dialog
+        } else if (dialog.SUBCLASS_OF_ID == 0xc91c90b6) { //crc32(b'InputPeer')
+            return new Api.InputDialogPeer({
+                peer: dialog,
+            });
         }
-        return new Api.InputNotifyPeer({
-            peer: await this.getInputEntity(notify)
-        })
+
+    } catch (e) {
 
     }
+    return new Api.InputDialogPeer({
+        peer: dialog
+    });
 }
 
-export interface UserMethods extends TelegramBaseClient {
+export async function _getInputNotify(client: TelegramClient, notify: any) {
+    try {
+        if (notify.SUBCLASS_OF_ID == 0x58981615) {
+            if (notify instanceof Api.InputNotifyPeer) {
+                notify.peer = await client.getInputEntity(notify.peer)
+            }
+            return notify;
+        }
+    } catch (e) {
+
+    }
+    return new Api.InputNotifyPeer({
+        peer: await client.getInputEntity(notify)
+    })
 
 }
+

+ 4 - 4
gramjs/define.d.ts

@@ -1,5 +1,5 @@
-import {Button} from "./tl/custom/button";
-import {Api} from "./tl/api";
+import type {Button} from "./tl/custom/button";
+import {Api} from "./tl";
 
 type ValueOf<T> = T[keyof T];
 type Phone = string;
@@ -11,7 +11,7 @@ type PeerLike = Api.TypePeer | Api.TypeInputPeer | Entity | FullEntity
 type EntityLike = Phone | Username | PeerID | Api.TypePeer | Api.TypeInputPeer | Entity | FullEntity ;
 
 type EntitiesLike = EntityLike[];
-type MessageIDLike = number | types.Message | types.TypeInputMessage;
+type MessageIDLike = number | Api.Message | Api.TypeInputMessage;
 type MessageLike = string | Api.Message;
 
 type LocalPath = string;
@@ -29,7 +29,7 @@ type FileLike =
 type ProgressCallback = (total: number, downloaded: number) => void;
 type ButtonLike = Api.TypeKeyboardButton | Button;
 
-type MarkupLike = types.TypeReplyMarkup |
+type MarkupLike = Api.TypeReplyMarkup |
     ButtonLike |
     ButtonLike[] |
     ButtonLike[][];

+ 1 - 1
gramjs/entityCache.ts

@@ -1,7 +1,7 @@
 // Which updates have the following fields?
 
 import {getInputPeer, getPeerId, isArrayLike} from "./Utils";
-import {Api} from "./tl/api";
+import {Api} from "./tl";
 
 export class EntityCache {
     private cacheMap: Map<number, any>;

+ 1 - 1
gramjs/errors/Common.ts

@@ -3,7 +3,7 @@
  */
 
 
-import {Api} from "../tl/api";
+import {Api} from "../tl";
 
 /**
  * Occurs when a read operation was cancelled.

+ 147 - 0
gramjs/events/NewMessage.ts

@@ -0,0 +1,147 @@
+import {_intoIdSet, EventBuilder, EventCommon} from "./common";
+import type {EntityLike} from "../define";
+import type {TelegramClient} from "../client/TelegramClient";
+import {Api} from "../tl";
+import {Message} from "../tl/patched";
+import {Mixin} from 'ts-mixer';
+
+interface NewMessageInterface {
+    chats?: EntityLike[],
+    func?: CallableFunction,
+    incoming?: boolean,
+    outgoing?: boolean,
+    fromUsers?: EntityLike[],
+    forwards?: boolean,
+    pattern?: RegExp,
+    blacklistChats?: boolean
+}
+
+export class NewMessage extends EventBuilder {
+    chats?: EntityLike[];
+    func?: CallableFunction;
+    incoming?: boolean;
+    outgoing?: boolean;
+    fromUsers?: EntityLike[];
+    forwards?: boolean;
+    pattern?: RegExp;
+    private _noCheck: boolean;
+
+    constructor({chats, func, incoming, outgoing, fromUsers, forwards, pattern, blacklistChats = true}: NewMessageInterface) {
+        if (incoming && outgoing) {
+            incoming = outgoing = undefined;
+        } else if (incoming != undefined && outgoing == undefined) {
+            outgoing = !incoming;
+        } else if (outgoing != undefined && incoming == undefined) {
+            incoming = !outgoing;
+        } else if (outgoing == false && incoming == false) {
+            throw new Error("Don't create an event handler if you don't want neither incoming nor outgoing!")
+        }
+        super({chats, blacklistChats, func});
+        this.incoming = incoming;
+        this.outgoing = outgoing;
+        this.fromUsers = fromUsers;
+        this.forwards = forwards;
+        this.pattern = pattern;
+        this._noCheck = [incoming, outgoing, fromUsers, forwards, pattern].every(v => v == undefined);
+
+    }
+
+    async _resolve(client: TelegramClient) {
+        await super._resolve(client);
+        this.fromUsers = await _intoIdSet(client, this.fromUsers);
+    }
+
+    build(update: Api.TypeUpdate, others: any = null) {
+        if (update instanceof Api.UpdateNewMessage || update instanceof Api.UpdateNewChannelMessage) {
+            if (!(update.message instanceof Api.Message) && !(update.message instanceof Message)) {
+                return undefined;
+            }
+            const event = new NewMessageEvent(update.message as Message);
+            this.addAttributes(event);
+            return event;
+        } else if (update instanceof Api.UpdateShortMessage) {
+            return new NewMessageEvent(new Message({
+                out: update.out,
+                mentioned: update.mentioned,
+                mediaUnread: update.mediaUnread,
+                silent: update.silent,
+                id: update.id,
+                peerId: new Api.PeerUser({userId: update.userId}),
+                fromId: new Api.PeerUser({userId: update.userId}),
+                message: update.message,
+                date: update.date,
+                fwdFrom: update.fwdFrom,
+                viaBotId: update.viaBotId,
+                replyTo: update.replyTo,
+                entities: update.entities,
+                // ttlPeriod:update.ttlPeriod
+            }))
+        } else if (update instanceof Api.UpdateShortChatMessage) {
+            return new NewMessageEvent(new Message({
+                out: update.out,
+                mentioned: update.mentioned,
+                mediaUnread: update.mediaUnread,
+                silent: update.silent,
+                id: update.id,
+                peerId: new Api.PeerChat({chatId: update.chatId}),
+                fromId: new Api.PeerUser({userId: update.fromId}),
+                message: update.message,
+                date: update.date,
+                fwdFrom: update.fwdFrom,
+                viaBotId: update.viaBotId,
+                replyTo: update.replyTo,
+                entities: update.entities,
+                // ttlPeriod:update.ttlPeriod
+            }))
+        }
+    }
+
+    filter(event: Message): any {
+        if (this._noCheck) {
+            return event;
+        }
+        if (this.incoming && event.out) {
+            return
+        }
+        if (this.outgoing && !event.out) {
+            return;
+        }
+        if (this.forwards != undefined) {
+            if (this.forwards != !!event.fwdFrom) {
+            }
+        }
+        if (this.pattern) {
+            const match = event.message.match(this.pattern);
+            if (!match) {
+                return
+            }
+            event.patternMatch = match;
+        }
+        return super.filter(event);
+    }
+
+
+    addAttributes(update: any) {
+        //update.patternMatch =
+    }
+
+}
+
+export class NewMessageEvent extends EventCommon {
+    message: Message;
+
+    constructor(message: Message) {
+        super({
+            msgId: message.id,
+            chatPeer: message.peerId,
+            broadcast: message.post,
+        });
+        this.message = message;
+    }
+
+    _setClient(client: TelegramClient) {
+        super._setClient(client);
+        const m = this.message;
+        m._finishInit(client, this.message._entities, undefined);
+    }
+}

+ 45 - 0
gramjs/events/Raw.ts

@@ -0,0 +1,45 @@
+import {EventBuilder, EventCommon} from "./common";
+import type {TelegramClient} from "../client/TelegramClient";
+import {Api} from "../tl";
+
+interface RawInterface {
+    types?: Function[],
+    func?: CallableFunction
+}
+
+export class Raw extends EventBuilder {
+    private types?: Function[];
+
+    constructor({types = undefined, func = undefined}: RawInterface) {
+        super({func: func});
+        this.types = types;
+    }
+
+    async resolve(client: TelegramClient) {
+        this.resolved = true;
+    }
+
+    build(update: Api.TypeUpdate, others: any = null): Api.TypeUpdate {
+        return update
+    }
+
+    filter(event: EventCommon) {
+
+        if (this.types) {
+            let correct = false;
+            for (const type of this.types) {
+                if (event instanceof type) {
+                    correct = true;
+                    break
+                }
+            }
+            if (!correct) {
+                return;
+            }
+        }
+        if (this.func) {
+            return this.func(event);
+        }
+        return event;
+    }
+}

+ 112 - 9
gramjs/events/common.ts

@@ -1,27 +1,130 @@
 import {Api} from "../tl";
+import type {EntityLike} from "../define";
+import {ChatGetter} from "../tl/custom/chatGetter";
+import type {TelegramClient} from "../client/TelegramClient";
+
+import bigInt from "big-integer";
+import {isArrayLike} from "../Utils";
+import {utils} from "../";
+import {Message} from "../tl/patched";
+
+export async function _intoIdSet(client: TelegramClient, chats: EntityLike[] | EntityLike | undefined): Promise<number[] | undefined> {
+    if (chats == undefined) {
+        return undefined;
+    }
+    if (!isArrayLike(chats)) {
+        chats = [chats]
+    }
+    const result: Set<number> = new Set<number>();
+    for (let chat of chats) {
+        if (typeof chat == "number") {
+            if (chat < 0) {
+                result.add(chat);
+            } else {
+                result.add(utils.getPeerId(new Api.PeerUser({
+                    userId: chat,
+                })));
+                result.add(utils.getPeerId(new Api.PeerChat({
+                    chatId: chat,
+                })));
+                result.add(utils.getPeerId(new Api.PeerChannel({
+                    channelId: chat,
+                })));
+
+            }
+        } else if (typeof chat == "object" && chat.SUBCLASS_OF_ID == 0x2d45687) {
+            result.add(utils.getPeerId(chat));
+        } else {
+            chat = await client.getInputEntity(chat);
+            if (chat instanceof Api.InputPeerSelf) {
+                chat = await client.getMe(true);
+            }
+            result.add(utils.getPeerId(chat));
+        }
+    }
+    return Array.from(result);
+}
+
+interface DefaultEventInterface {
+    chats?: EntityLike[],
+    blacklistChats?: boolean,
+    func?: CallableFunction,
+
+}
 
 export class EventBuilder {
-    private chats: undefined;
+    chats?: EntityLike[];
     private blacklistChats: boolean;
-    private resolved: boolean;
-    private func: undefined;
+    resolved: boolean;
+    func?: CallableFunction;
 
-    constructor({
-                    chats = undefined, blacklistChats = false, func = undefined,
-                },
-    ) {
+    constructor({chats, blacklistChats = true, func}: DefaultEventInterface) {
         this.chats = chats;
         this.blacklistChats = blacklistChats;
         this.resolved = false;
         this.func = func
     }
 
-    build(update: Api.Updates, others = null) {
+    build(update: Api.TypeUpdate, others = null): any {
+        if (update)
+            return update;
+    }
+
+    async resolve(client: TelegramClient) {
+        if (this.resolved) {
+            return
+        }
+        await this._resolve(client);
+        this.resolved = true;
+    }
+
+    async _resolve(client: TelegramClient) {
+        this.chats = await _intoIdSet(client, this.chats);
+    }
 
+    filter(event: any) {
+        if (!this.resolved) {
+            return
+        }
+        if (this.chats != undefined && event.chatId != undefined) {
+            const inside = this.chats.includes(event.chatId);
+            if (inside == this.blacklistChats) {
+                // If this chat matches but it's a blacklist ignore.
+                // If it doesn't match but it's a whitelist ignore.
+                return undefined;
+
+            }
+        }
     }
 }
 
-export class EventCommon {
+interface EventCommonInterface {
+    chatPeer?: EntityLike,
+    msgId?: bigInt.BigInteger,
+    broadcast?: boolean,
+}
+
+export class EventCommon extends ChatGetter {
+    _eventName = "Event";
+    _entities: any;
+    _messageId?: bigInt.BigInteger;
+    originalUpdate: undefined;
+
+    constructor({chatPeer = undefined, msgId = undefined, broadcast = undefined}: EventCommonInterface) {
+        super({chatPeer, broadcast});
+        this._entities = {};
+        this._client = undefined;
+        this._messageId = msgId;
+        this.originalUpdate = undefined;
+    }
+
+    _setClient(client: TelegramClient) {
+        this._client = client;
+    }
+
+    get client() {
+        return this._client;
+    }
 
 }
 

+ 2 - 0
gramjs/events/index.ts

@@ -0,0 +1,2 @@
+export {Raw} from "./Raw";
+export {NewMessage} from "./NewMessage";

+ 1 - 1
gramjs/extensions/BinaryReader.ts

@@ -2,7 +2,6 @@ import {TypeNotFoundError} from '../errors';
 import {coreObjects} from '../tl/core';
 
 import {tlobjects} from '../tl/AllTLObjects';
-import {Api} from "../tl";
 import {readBigIntFromBuffer} from "../Helpers";
 
 
@@ -179,6 +178,7 @@ export class BinaryReader {
      */
     tgReadObject(): any {
         const constructorId = this.readInt(false);
+
         let clazz = tlobjects[constructorId];
         if (clazz === undefined) {
             /**

+ 2 - 3
gramjs/extensions/MessagePacker.ts

@@ -1,9 +1,8 @@
 import {MessageContainer} from '../tl/core';
 import {TLMessage} from '../tl/core';
 import {BinaryWriter} from './BinaryWriter';
-import {Api} from "../tl";
-import {MTProtoState} from "../network/MTProtoState";
-import {RequestState} from "../network/RequestState";
+import type{MTProtoState} from "../network/MTProtoState";
+import type{RequestState} from "../network/RequestState";
 
 export class MessagePacker {
     private _state: any;

+ 65 - 72
gramjs/extensions/markdown.ts

@@ -1,87 +1,80 @@
 import {Api} from "../tl";
-import {ValueOf} from "../define";
+import type {ValueOf} from "../define";
+import {DEFAULT_DELIMITERS, messageEntities} from "../client/messageParse";
 
-const DEFAULT_DELIMITERS: {
-    [key: string]: typeof Api.MessageEntityBold | typeof Api.MessageEntityItalic |
-        typeof Api.MessageEntityStrike | typeof Api.MessageEntityCode | typeof Api.MessageEntityPre
-} = {
-    '**': Api.MessageEntityBold,
-    '__': Api.MessageEntityItalic,
-    '~~': Api.MessageEntityStrike,
-    '`': Api.MessageEntityCode,
-    '```': Api.MessageEntityPre
-};
+export class MarkdownParser {
 
+    // TODO maybe there is a better way :shrug:
+    static parse(message: string, delimiters = DEFAULT_DELIMITERS): [string, ValueOf<typeof DEFAULT_DELIMITERS>[]] {
+        let i = 0;
+        const keys: { [key: string]: boolean } = {};
+        for (const k in DEFAULT_DELIMITERS) {
+            keys[k] = false;
+        }
+        const entities = [];
+        const tempEntities: { [key: string]: any } = {};
+        while (i < message.length) {
+            let foundIndex = -1;
+            let foundDelim = undefined;
+            for (const key of Object.keys(DEFAULT_DELIMITERS)) {
+                const index = message.indexOf(key, i);
+                if (index > -1 && (foundIndex === -1 || index < foundIndex)) {
+                    foundIndex = index;
+                    foundDelim = key;
+                }
 
-// TODO maybe there is a better way :shrug:
-export function parse(message: string, delimiters = DEFAULT_DELIMITERS): [string, ValueOf<typeof DEFAULT_DELIMITERS>[]] {
-    let i = 0;
-    const keys: { [key: string]: boolean } = {};
-    for (const k in DEFAULT_DELIMITERS) {
-        keys[k] = false;
-    }
-    const entities = [];
-    const tempEntities: { [key: string]: any } = {};
-    while (i < message.length) {
-        let foundIndex = -1;
-        let foundDelim = undefined;
-        for (const key of Object.keys(DEFAULT_DELIMITERS)) {
-            const index = message.indexOf(key, i);
-            if (index > -1 && (foundIndex === -1 || index < foundIndex)) {
-                foundIndex = index;
-                foundDelim = key;
             }
 
-        }
+            if (foundIndex === -1 || foundDelim == undefined) {
+                break;
+            }
+            if (!keys[foundDelim]) {
+                tempEntities[foundDelim] = new DEFAULT_DELIMITERS[foundDelim]({
+                    offset: foundIndex,
+                    length: -1,
+                    language: ""
+                });
+                keys[foundDelim] = true;
+            } else {
+                keys[foundDelim] = false;
+                tempEntities[foundDelim].length = foundIndex - tempEntities[foundDelim].offset;
+                entities.push(tempEntities[foundDelim])
+            }
+            message = message.replace(foundDelim, "");
+            i = foundIndex;
 
-        if (foundIndex === -1 || foundDelim == undefined) {
-            break;
-        }
-        if (!keys[foundDelim]) {
-            tempEntities[foundDelim] = new DEFAULT_DELIMITERS[foundDelim]({
-                offset: foundIndex,
-                length: -1,
-                language: ""
-            });
-            keys[foundDelim] = true;
-        } else {
-            keys[foundDelim] = false;
-            tempEntities[foundDelim].length = foundIndex - tempEntities[foundDelim].offset;
-            entities.push(tempEntities[foundDelim])
         }
-        message = message.replace(foundDelim, "");
-        i = foundIndex;
-
+        return [message, entities];
     }
-    return [message, entities];
-}
 
-export function unparse(text: string, entities: Api.TypeMessageEntity[] | undefined, delimiters = DEFAULT_DELIMITERS) {
-    if (!text || !entities) {
-        return text;
-    }
-    let insertAt: [number, string][] = [];
+    static unparse(text: string, entities: Api.TypeMessageEntity[] | undefined, delimiters = DEFAULT_DELIMITERS) {
+        if (!text || !entities) {
+            return text;
+        }
+        let insertAt: [number, string][] = [];
 
-    const tempDelimiters: Map<string, string> = new Map();
-    Object.keys(delimiters).forEach(key => {
-        tempDelimiters.set(delimiters[key].className, key);
-    });
-    for (const entity of entities) {
+        const tempDelimiters: Map<string, string> = new Map();
+        Object.keys(delimiters).forEach(key => {
+            tempDelimiters.set(delimiters[key].className, key);
+        });
+        for (const entity of entities) {
 
-        const s = entity.offset;
-        const e = entity.offset + entity.length;
-        const delimiter = tempDelimiters.get(entity.className);
-        if (delimiter) {
-            insertAt.push([s, delimiter]);
-            insertAt.push([e, delimiter]);
+            const s = entity.offset;
+            const e = entity.offset + entity.length;
+            const delimiter = tempDelimiters.get(entity.className);
+            if (delimiter) {
+                insertAt.push([s, delimiter]);
+                insertAt.push([e, delimiter]);
+            }
         }
+        insertAt = insertAt.sort((a: [number, string], b: [number, string]) => {
+            return a[0] - b[0];
+        });
+        while (insertAt.length) {
+            const [at, what] = insertAt.pop() as [number, string];
+            text = text.slice(0, at) + what + text.slice(at);
+        }
+        return text;
     }
-    insertAt = insertAt.sort((a: [number, string], b: [number, string]) => {
-        return a[0] - b[0];
-    });
-    while (insertAt.length) {
-        const [at, what] = insertAt.pop() as [number, string];
-        text = text.slice(0, at) + what + text.slice(at);
-    }
-    return text;
+
 }

+ 2 - 2
gramjs/network/Authenticator.ts

@@ -10,10 +10,10 @@ import {Factorizator} from "../crypto/Factorizator";
 import {IGE} from "../crypto/IGE";
 import {BinaryReader} from "../extensions";
 import {AuthKey} from "../crypto/AuthKey";
-import {helpers} from "../index";
+import {helpers} from "../";
 import {encrypt} from "../crypto/RSA";
 import bigInt from 'big-integer';
-import {MTProtoPlainSender} from "./MTProtoPlainSender";
+import type {MTProtoPlainSender} from "./MTProtoPlainSender";
 
 export async function doAuthentication(sender: MTProtoPlainSender, log: any) {
     // Step 1 sending: PQ Request, endianness doesn't matter since it's random

+ 1 - 2
gramjs/network/MTProtoPlainSender.ts

@@ -2,13 +2,12 @@
  *  This module contains the class used to communicate with Telegram's servers
  *  in plain text, when no authorization key has been created yet.
  */
-import bigInt from 'big-integer';
 import {MTProtoState} from "./MTProtoState";
 import {Api} from "../tl";
 import {toSignedLittleBuffer} from "../Helpers";
 import {InvalidBufferError} from "../errors";
 import {BinaryReader} from "../extensions";
-import {Connection} from "./connection";
+import type {Connection} from "./connection";
 
 /**
  * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)

+ 5 - 4
gramjs/network/MTProtoSender.ts

@@ -13,16 +13,17 @@
  */
 import {AuthKey} from "../crypto/AuthKey";
 import {MTProtoState} from "./MTProtoState";
-import {BinaryReader, MessagePacker} from "../extensions";
+import {BinaryReader } from "../extensions";
+import  {MessagePacker } from "../extensions";
 import {GZIPPacked, MessageContainer, RPCResult, TLMessage} from "../tl/core";
 import {Api} from "../tl";
 import bigInt from 'big-integer'
 import {sleep} from "../Helpers";
 import {RequestState} from "./RequestState";
 import {doAuthentication} from "./Authenticator";
-import {MTProtoPlainSender} from "./MTProtoPlainSender";
-import {BadMessageError, InvalidBufferError, RPCMessageToError, SecurityError, TypeNotFoundError} from "../errors";
-import {UpdateConnectionState} from "./index";
+import  {MTProtoPlainSender} from "./MTProtoPlainSender";
+import {BadMessageError, TypeNotFoundError,InvalidBufferError,SecurityError,RPCMessageToError} from "../errors";
+import  {UpdateConnectionState} from "./";
 
 interface DEFAULT_OPTIONS {
     logger: any,

+ 4 - 3
gramjs/network/MTProtoState.ts

@@ -1,10 +1,11 @@
 import bigInt from 'big-integer';
-import {AuthKey} from "../crypto/AuthKey";
-import {helpers} from "../index";
+import type {AuthKey} from "../crypto/AuthKey";
+import {helpers} from "../";
 import {Api} from '../tl';
 import {sha256, toSignedLittleBuffer} from "../Helpers";
 import {GZIPPacked, TLMessage} from "../tl/core";
-import {BinaryReader, BinaryWriter} from "../extensions";
+import {BinaryReader} from "../extensions";
+import type {BinaryWriter} from "../extensions";
 import {IGE} from "../crypto/IGE";
 import {InvalidBufferError, SecurityError} from "../errors";
 

+ 0 - 2
gramjs/network/RequestState.ts

@@ -1,5 +1,3 @@
-import {Api} from "../tl";
-
 export class RequestState {
     public containerId: undefined;
     public msgId?: bigInt.BigInteger;

+ 2 - 2
gramjs/network/connection/Connection.ts

@@ -1,5 +1,5 @@
-import {BinaryReader, PromisedWebSockets} from '../../extensions'
-import {PromisedNetSockets} from '../../extensions'
+import type{BinaryReader} from '../../extensions'
+import {PromisedNetSockets,PromisedWebSockets} from '../../extensions'
 import {AsyncQueue} from '../../extensions'
 import {IS_NODE} from '../../Helpers'
 

+ 1 - 1
gramjs/network/connection/TCPAbridged.ts

@@ -1,6 +1,6 @@
 import {readBufferFromBigInt} from '../../Helpers';
 import {Connection, PacketCodec} from './Connection';
-import {BinaryReader} from "../../extensions";
+import type {BinaryReader} from "../../extensions";
 
 import bigInt from "big-integer";
 

+ 1 - 1
gramjs/network/connection/TCPFull.ts

@@ -1,7 +1,7 @@
 import {Connection, PacketCodec} from './Connection';
 import {crc32} from '../../Helpers';
 import {InvalidChecksumError} from '../../errors';
-import {BinaryReader} from "../../extensions";
+import type {BinaryReader} from "../../extensions";
 
 class FullPacketCodec extends PacketCodec {
     private _sendCounter: number;

+ 2 - 2
gramjs/requestIter.ts

@@ -1,6 +1,6 @@
-import {TelegramClient} from "./client/TelegramClient";
+import type {TelegramClient} from "./client/TelegramClient";
 import {sleep} from './Helpers';
-import {helpers} from "./index";
+import {helpers} from "./";
 
 interface BaseRequestIterInterface {
     reverse?: boolean,

+ 36 - 3
gramjs/sessions/Abstract.ts

@@ -1,4 +1,6 @@
-import {AuthKey} from "../crypto/AuthKey";
+import type {AuthKey} from "../crypto/AuthKey";
+import type {EntityLike} from "../define";
+import {Api} from "../tl";
 
 export class Session {
     constructor() {
@@ -31,7 +33,7 @@ export class Session {
     /**
      * Returns the currently-used data center ID.
      */
-    get dcId(): number | undefined {
+    get dcId(): number {
         throw new Error('Not Implemented')
     }
 
@@ -65,6 +67,36 @@ export class Session {
         throw new Error('Not Implemented')
     }
 
+    /**
+     * Called before using the session
+     */
+    async load() {
+        throw new Error('Not Implemented')
+
+    }
+
+    /**
+     *  sets auth key for a dc
+     */
+    setAuthKey(authKey?: AuthKey, dcId?: number) {
+        throw new Error('Not Implemented')
+    }
+
+    /**
+     *  gets auth key for a dc
+     */
+
+    getAuthKey(dcId?: number): AuthKey | undefined {
+        throw new Error('Not Implemented')
+    }
+
+    /**
+     *
+     * @param key
+     */
+    getInputEntity(key: EntityLike) : Api.TypeInputPeer{
+        throw new Error('Not Implemented')
+    }
     /**
      * Returns an ID of the takeout process initialized for this session,
      * or `None` if there's no were any unfinished takeout requests.
@@ -143,6 +175,7 @@ export class Session {
     /**
      * Lists available sessions. Not used by the library itself.
      */
+
     /*CONTEST
     listSessions() {
         throw new Error('Not Implemented')
@@ -155,7 +188,7 @@ export class Session {
      * whatever information is relevant (e.g., ID or access hash).
      * @param tlo
      */
-    processEntities(tlo:any) {
+    processEntities(tlo: any) {
         throw new Error('Not Implemented')
     }
 

+ 7 - 5
gramjs/sessions/Memory.ts

@@ -1,11 +1,11 @@
-import {Session} from './Abstract';
-import {AuthKey} from "../crypto/AuthKey";
+import  {Session} from './Abstract';
+import type {AuthKey} from "../crypto/AuthKey";
 import {Api} from "../tl";
 import bigInt from "big-integer";
 
 import {getDisplayName, getInputPeer, getPeerId, isArrayLike} from "../Utils";
-import {utils} from "../index";
-import {EntityLike} from "../define";
+import {utils} from "../";
+import type {EntityLike} from "../define";
 
 export class MemorySession extends Session {
     protected _serverAddress?: string;
@@ -200,7 +200,7 @@ export class MemorySession extends Session {
         }
     }
 
-    getInputEntity(key: EntityLike) {
+    getInputEntity(key: EntityLike):Api.TypeInputPeer {
         let exact;
             if (typeof key === 'object' && key.SUBCLASS_OF_ID) {
                 if ([0xc91c90b6, 0xe669bf46, 0x40f202fd].includes(key.SUBCLASS_OF_ID)) {
@@ -261,6 +261,8 @@ export class MemorySession extends Session {
         } else {
             throw new Error('Could not find input entity with key ' + key)
         }
+        throw new Error('Could not find input entity with key ' + key)
+
     }
 
 }

+ 2 - 2
gramjs/sessions/StoreSession.ts

@@ -60,7 +60,7 @@ export class StoreSession extends MemorySession {
     getAuthKey(dcId?: number) {
         if (dcId && dcId !== this.dcId) {
             // Not supported.
-            return undefined
+            throw  new Error("not supported");
         }
 
         return this.authKey
@@ -69,7 +69,7 @@ export class StoreSession extends MemorySession {
     setAuthKey(authKey?: AuthKey, dcId?: number) {
         if (dcId && dcId !== this.dcId) {
             // Not supported.
-            return undefined
+            throw  new Error("not supported");
         }
 
         this.authKey = authKey

+ 1 - 1
gramjs/tl/AllTLObjects.ts

@@ -1,4 +1,4 @@
-import {Api} from './api'
+import {Api} from './'
 
 export const LAYER = 122;
 const tlobjects: any = {};

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 201 - 186
gramjs/tl/api.d.ts


+ 1 - 1
gramjs/tl/api.js

@@ -263,7 +263,7 @@ function createClasses(classesType, params) {
                         }
                     }
                 }
-                return new VirtualClass(args)
+                return new this(args)
             }
 
             getBytes() {

+ 2 - 2
gramjs/tl/core/GZIPPacked.ts

@@ -1,6 +1,6 @@
-import {serializeBytes} from '../index';
+import {serializeBytes} from '../';
 import {inflate} from 'pako';
-import {BinaryReader} from "../../extensions/BinaryReader";
+import type {BinaryReader} from "../../extensions";
 
 export class GZIPPacked {
     static CONSTRUCTOR_ID = 0x3072cfa1;

+ 1 - 1
gramjs/tl/core/MessageContainer.ts

@@ -1,5 +1,5 @@
 import {TLMessage} from './TLMessage';
-import {BinaryReader} from "../../extensions";
+import type {BinaryReader} from "../../extensions";
 
 export class MessageContainer {
     static CONSTRUCTOR_ID = 0x73f1f8dc;

+ 2 - 2
gramjs/tl/core/RPCResult.ts

@@ -1,6 +1,6 @@
 import {Api} from '../api';
-import {BinaryReader} from "../../extensions";
-import {GZIPPacked} from "./index";
+import type {BinaryReader} from "../../extensions";
+import {GZIPPacked} from "./";
 
 
 

+ 1 - 0
gramjs/tl/core/TLMessage.ts

@@ -1,3 +1,4 @@
+import bigInt from "big-integer"
 export class TLMessage {
     static SIZE_OVERHEAD = 12;
     static classType = 'constructor';

+ 3 - 3
gramjs/tl/custom/button.ts

@@ -1,6 +1,6 @@
-import {ButtonLike, EntityLike} from "../../define";
+import type {ButtonLike, EntityLike} from "../../define";
 import {Api} from "../api";
-import {utils} from "../../index";
+import {utils} from "../../";
 
 export class Button  {
     public button: ButtonLike;
@@ -8,7 +8,7 @@ export class Button  {
     public selective: boolean | undefined;
     public singleUse: boolean | undefined;
 
-    constructor(button: ButtonLike, resize?: boolean, singleUse?: boolean, selective?: boolean) {
+    constructor(button: Api.TypeKeyboardButton, resize?: boolean, singleUse?: boolean, selective?: boolean) {
         this.button = button;
         this.resize = resize;
         this.singleUse = singleUse;

+ 8 - 11
gramjs/tl/custom/chatGetter.ts

@@ -1,10 +1,7 @@
-import {Entity, EntityLike} from "../../define";
-import {TelegramClient} from "../../client/TelegramClient";
-import {utils} from "../../index";
+import type {Entity, EntityLike} from "../../define";
+import type {TelegramClient} from "../../client/TelegramClient";
+import {utils} from "../../";
 import {Api} from "../api";
-import PeerUser = Api.PeerUser;
-import PeerChannel = Api.PeerChannel;
-import PeerChat = Api.PeerChat;
 
 export interface ChatGetterConstructorParams {
     chatPeer?: EntityLike;
@@ -80,26 +77,26 @@ export class ChatGetter {
         return this._chatPeer ? utils.getPeerId(this._chatPeer) : undefined;
     }
 
-    get is() {
-        return this._chatPeer ? this._chatPeer instanceof PeerUser : undefined;
+    get isPrivate() {
+        return this._chatPeer ? this._chatPeer instanceof Api.PeerUser : undefined;
     }
 
     get isGroup() {
         if (!this._broadcast && this.chat && 'broadcast' in this.chat) {
             this._broadcast = Boolean(this.chat.broadcast);
         }
-        if (this._chatPeer instanceof PeerChannel) {
+        if (this._chatPeer instanceof Api.PeerChannel) {
             if (this._broadcast === undefined) {
                 return undefined;
             } else {
                 return !this._broadcast;
             }
         }
-        return this._chatPeer instanceof PeerChat;
+        return this._chatPeer instanceof Api.PeerChat;
     }
 
     get isChannel() {
-        return this._chatPeer instanceof PeerChannel;
+        return this._chatPeer instanceof Api.PeerChannel;
     }
 
     async _refetchChat() {

+ 6 - 5
gramjs/tl/custom/dialog.ts

@@ -1,7 +1,6 @@
-import {TelegramClient} from "../../client/TelegramClient";
+import type {TelegramClient} from "../../client/TelegramClient";
 import {Api} from "../api";
-import {Entity} from "../../define";
-import Message = Api.Message;
+import type {Entity} from "../../define";
 import {getDisplayName, getInputPeer, getPeerId} from "../../Utils";
 import {Draft} from "./draft";
 
@@ -25,7 +24,7 @@ export class Dialog {
     private isGroup: boolean;
     private isChannel: boolean;
 
-    constructor(client: TelegramClient, dialog: Api.Dialog, entities: Map<number, Entity>, message: Message) {
+    constructor(client: TelegramClient, dialog: Api.Dialog, entities: Map<number, Entity>, message: Api.Message) {
         this._client = client;
         this.dialog = dialog;
         this.pinned = !!(dialog.pinned);
@@ -44,7 +43,9 @@ export class Dialog {
 
         this.unreadCount = dialog.unreadCount;
         this.unreadMentionsCount = dialog.unreadMentionsCount;
-
+        if (!this.entity){
+            throw new Error("Entity not found for dialog");
+        }
         this.draft = new Draft(client, this.entity, this.dialog.draft);
 
         this.isUser = this.entity instanceof Api.User;

+ 6 - 6
gramjs/tl/custom/draft.ts

@@ -1,9 +1,9 @@
-import {Entity} from "../../define";
-import {TelegramClient} from "../../client/TelegramClient";
+import type {Entity} from "../../define";
+import type {TelegramClient} from "../../client/TelegramClient";
 import {getInputPeer, getPeer} from "../../Utils";
 import {Api} from "../api";
-import DraftMessage = Api.DraftMessage;
-import {unparse} from "../../extensions/markdown";
+import {MarkdownParser} from "../../extensions/markdown";
+
 
 export class Draft {
     private _client: TelegramClient;
@@ -16,7 +16,7 @@ export class Draft {
     private linkPreview?: boolean;
     private replyToMsgId?: Api.int;
 
-    constructor(client: TelegramClient, entity: Entity | undefined, draft: Api.TypeDraftMessage | undefined) {
+    constructor(client: TelegramClient, entity: Entity, draft: Api.TypeDraftMessage | undefined) {
         this._client = client;
         this._peer = getPeer(entity);
         this._entity = entity;
@@ -29,7 +29,7 @@ export class Draft {
         }
         if (!(draft instanceof Api.DraftMessageEmpty)) {
             this.linkPreview = !draft.noWebpage;
-            this._text = unparse(draft.message,draft.entities);
+            this._text = MarkdownParser.unparse(draft.message,draft.entities);
             this._rawText = draft.message;
             this.date = draft.date;
             this.replyToMsgId = draft.replyToMsgId;

+ 1 - 1
gramjs/tl/custom/file.ts

@@ -1,4 +1,4 @@
-import {FileLike} from "../../define";
+import type {FileLike} from "../../define";
 import {Api} from "../api";
 import {_photoSizeByteCount} from "../../Utils";
 

+ 4 - 5
gramjs/tl/custom/forward.ts

@@ -1,17 +1,16 @@
 import {ChatGetter} from "./chatGetter";
 import {SenderGetter} from "./senderGetter";
 import {Api} from "../api";
-import Message = Api.Message;
-import {TelegramClient} from "../../client/TelegramClient";
-import {Entity} from "../../define";
+import type {TelegramClient} from "../../client/TelegramClient";
+import type {Entity} from "../../define";
 import {_EntityType, _entityType} from "../../Helpers";
 import {_getEntityPair, getPeerId} from "../../Utils";
 
 
 export class Forward {
-    private originalFwd: Api.Message;
+    private originalFwd: Api.MessageFwdHeader;
 
-    constructor(client: TelegramClient, original: Message, entities: Map<number, Entity>) {
+    constructor(client: TelegramClient, original: Api.MessageFwdHeader, entities: Map<number, Entity>) {
         // Copy all objects here. probably need a better way tho. PRs are welcome
         Object.assign(this, original);
         this.originalFwd = original;

+ 1 - 0
gramjs/tl/custom/index.ts

@@ -0,0 +1 @@
+export {ChatGetter} from "./chatGetter";

+ 9 - 12
gramjs/tl/custom/inlineResult.ts

@@ -1,8 +1,7 @@
-import {TelegramClient} from "../../client/TelegramClient";
-import {EntityLike, FileLike, MessageIDLike, ProgressCallback} from "../../define";
+import type {TelegramClient} from "../../client/TelegramClient";
+import type {EntityLike, MessageIDLike} from "../../define";
 import {Api} from "../api";
-import {utils} from "../../index";
-import SendInlineBotResult = Api.messages.SendInlineBotResult;
+import {utils} from "../../";
 
 export class InlineResult {
     ARTICLE = 'article';
@@ -18,10 +17,10 @@ export class InlineResult {
     GAME = 'game';
     private _entity: EntityLike | undefined;
     private _queryId: Api.long | undefined;
-    private result: Api.TypeInputPeer;
+    private result: Api.TypeBotInlineResult;
     private _client: TelegramClient;
 
-    constructor(client: TelegramClient, original: Api.TypeInputBotInlineMessage, queryId?: Api.long, entity?: EntityLike) {
+    constructor(client: TelegramClient, original: Api.TypeBotInlineResult, queryId?: Api.long, entity?: EntityLike) {
         this._client = client;
         this.result = original;
         this._queryId = queryId;
@@ -49,7 +48,7 @@ export class InlineResult {
     get photo() {
         if (this.result instanceof Api.BotInlineResult) {
             return this.result.thumb;
-        } else if (this.result instanceof Api.BotInlineMediaResult) {
+        } else {
             return this.result.photo;
         }
     }
@@ -57,7 +56,7 @@ export class InlineResult {
     get document() {
         if (this.result instanceof Api.BotInlineResult) {
             return this.result.content;
-        } else if (this.result instanceof Api.BotInlineMediaResult) {
+        } else {
             return this.result.document;
         }
     }
@@ -71,7 +70,7 @@ export class InlineResult {
             throw new Error("You must provide the entity where the result should be sent to");
         }
         const replyId = replyTo ? utils.getMessageId(replyTo) : undefined;
-        const request = new SendInlineBotResult({
+        const request = new Api.messages.SendInlineBotResult({
             peer: entity,
             queryId: this._queryId,
             id: this.result.id,
@@ -80,9 +79,7 @@ export class InlineResult {
             hideVia: hideVia,
             replyToMsgId: replyId,
         });
-        return this._client._getResponseMessage(
-            request, await this._client.invoke(request), entity
-        )
+        return await this._client.invoke(request);
     }
 
     /*

+ 5 - 6
gramjs/tl/custom/inlineResults.ts

@@ -1,11 +1,10 @@
-import {TelegramClient} from "../../client/TelegramClient";
-import {EntityLike} from "../../define";
+import type {TelegramClient} from "../../client/TelegramClient";
+import type {EntityLike} from "../../define";
 import {Api} from "../api";
-import TypeBotResults = Api.messages.TypeBotResults;
 
 
 export class InlineResults<InlineResult> extends Array<InlineResult> {
-    private result: TypeBotResults;
+    private result: Api.messages.TypeBotResults;
     private queryId: Api.long;
     private cacheTime: Api.int;
     private _validUntil: number;
@@ -14,13 +13,13 @@ export class InlineResults<InlineResult> extends Array<InlineResult> {
     private nextOffset: string | undefined;
     private switchPm: Api.TypeInlineBotSwitchPM | undefined;
 
-    constructor(client: TelegramClient, original: TypeBotResults, entity?: EntityLike) {
+    constructor(client: TelegramClient, original: Api.messages.TypeBotResults, entity?: EntityLike) {
         super();
         this.result = original;
         this.queryId = original.queryId;
         this.cacheTime = original.cacheTime;
         this._validUntil = ((new Date()).getTime() / 1000) + this.cacheTime;
-        this.users = original.users;
+        this.users = <Api.TypeUser[]>original.users;
         this.gallery = Boolean(original.gallery);
         this.nextOffset = original.nextOffset;
         this.switchPm = original.switchPm;

+ 50 - 53
gramjs/tl/custom/message.ts

@@ -1,15 +1,13 @@
 import {SenderGetter} from "./senderGetter";
-import {DateLike, EntitiesLike, Entity, EntityLike} from "../../define";
+import type {Entity, EntityLike} from "../../define";
 import {Api} from "../api";
-import MessageMediaEmpty = Api.MessageMediaEmpty;
-import PeerUser = Api.PeerUser;
-import {TelegramClient} from "../../client/TelegramClient";
+import type {TelegramClient} from "../../client/TelegramClient";
 import {ChatGetter} from "./chatGetter";
 import * as utils from "../../Utils";
 import {Forward} from "./forward";
-import {File} from "./file";
-import {getInnerText} from "../../Utils";
+import type {File} from "./file";
 import {Mixin} from "ts-mixer";
+import bigInt from "big-integer";
 
 interface MessageBaseInterface {
     id: any;
@@ -40,7 +38,7 @@ interface MessageBaseInterface {
     forwards?: any;
     replies?: any;
     action?: any;
-    _entities?: any;
+    _entities?: Map<number, Entity>;
 }
 
 
@@ -53,14 +51,14 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
     fromScheduled: any | undefined;
     legacy: any | undefined;
     editHide: any | undefined;
-    id: number;
+    id: bigInt.BigInteger;
     fromId?: EntityLike;
     peerId: any;
-    fwdFrom: any;
+    fwdFrom: Api.TypeMessageFwdHeader;
     viaBotId: any;
     replyTo: Api.MessageReplyHeader;
     date: any | undefined;
-    message: any | undefined;
+    message: string;
     media: any;
     replyMarkup: any | undefined;
     entities: any | undefined;
@@ -77,50 +75,50 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
     public _client?: TelegramClient;
     _text?: string;
     _file?: File;
-    _replyMessage: null;
-    _buttons: null;
-    _buttonsFlat: null;
+    _replyMessage: undefined;
+    _buttons: undefined;
+    _buttonsFlat: undefined;
     _buttonsCount: number;
     _viaBot?: EntityLike;
     _viaInputBot?: EntityLike;
     _inputSender: any;
     _forward?: Forward;
     _sender: any;
-    _entities: any[]
+    _entities: Map<number, Entity>
+    patternMatch?: RegExpMatchArray;
 
     constructor(
         {
             id,
-            peerId = undefined, date = null,
+            peerId = undefined, date = undefined,
 
-            out = null, mentioned = null, mediaUnread = null, silent = null,
-            post = null, fromId = null, replyTo = null,
+            out = undefined, mentioned = undefined, mediaUnread = undefined, silent = undefined,
+            post = undefined, fromId = undefined, replyTo = undefined,
 
-            message = null,
+            message = undefined,
 
 
-            fwdFrom = null, viaBotId = null, media = null, replyMarkup = null,
-            entities = null, views = null, editDate = null, postAuthor = null,
-            groupedId = null, fromScheduled = null, legacy = null,
-            editHide = null, pinned = null, restrictionReason = null, forwards = null, replies = null,
+            fwdFrom = undefined, viaBotId = undefined, media = undefined, replyMarkup = undefined,
+            entities = undefined, views = undefined, editDate = undefined, postAuthor = undefined,
+            groupedId = undefined, fromScheduled = undefined, legacy = undefined,
+            editHide = undefined, pinned = undefined, restrictionReason = undefined, forwards = undefined, replies = undefined,
 
 
-            action = null,
+            action = undefined,
 
 
-            _entities = [],
+            _entities = new Map<number, Entity>(),
         }: MessageBaseInterface) {
-        if (!id) throw new TypeError('id is a required attribute for Message');
+        if (!id) throw new Error('id is a required attribute for Message');
         let senderId = undefined;
         if (fromId) {
             senderId = utils.getPeerId(fromId);
         } else if (peerId) {
-            if (post || (!out && peerId instanceof PeerUser)) {
+            if (post || (!out && peerId instanceof Api.PeerUser)) {
                 senderId = utils.getPeerId(peerId);
             }
         }
-        // @ts-ignore
-        super({chatPeer: peerId, broadcast: post, senderId: senderId});
+        super({});
         // Common properties to all messages
         this._entities = _entities;
         this.out = out;
@@ -139,7 +137,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
         this.replyTo = replyTo;
         this.date = date;
         this.message = message;
-        this.media = media instanceof MessageMediaEmpty ? media : null;
+        this.media = media instanceof Api.MessageMediaEmpty ? media : undefined;
         this.replyMarkup = replyMarkup;
         this.entities = entities;
         this.views = views;
@@ -155,14 +153,13 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
         this._client = undefined;
         this._text = undefined;
         this._file = undefined;
-        this._replyMessage = null;
-        this._buttons = null;
-        this._buttonsFlat = null;
+        this._replyMessage = undefined;
+        this._buttons = undefined;
+        this._buttonsFlat = undefined;
         this._buttonsCount = 0;
         this._viaBot = undefined;
         this._viaInputBot = undefined;
-        this._actionEntities = null;
-
+        this._actionEntities = undefined;
 
         // Note: these calls would reset the client
         ChatGetter.initClass(this, {chatPeer: peerId, broadcast: post});
@@ -172,7 +169,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
     }
 
 
-    _finishInit(client: TelegramClient, entities: Map<number, Entity>, inputChat: EntityLike) {
+    _finishInit(client: TelegramClient, entities: Map<number, Entity>, inputChat?: EntityLike) {
         this._client = client;
         const cache = client._entityCache;
 
@@ -266,7 +263,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
         get buttons() {
             if (!this._buttons && this.replyMarkup) {
                 if (!this.inputChat) {
-                    return null
+                    return undefined
                 }
 
                 const bot = this._neededMarkupBot();
@@ -323,9 +320,9 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
             } else {
                 return this.webPreview && this.webPreview instanceof Api.Photo
                     ? this.webPreview.photo
-                    : null
+                    : undefined
             }
-            return null
+            return undefined
         }
 
         get document() {
@@ -337,9 +334,9 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
 
                 return web && web.document instanceof Api.Document
                     ? web.document
-                    : null
+                    : undefined
             }
-            return null
+            return undefined
         }
 
         get webPreview() {
@@ -444,7 +441,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
                 return this.peerId;
             }
 
-            getEntitiesText(cls: any = null) {
+            getEntitiesText(cls: any = undefined) {
                 let ent = this.entities;
                 if (!ent || ent.length == 0) return;
 
@@ -459,12 +456,12 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
 
             async getReplyMessage() {
                 if (!this._replyMessage && this._client) {
-                    if (!this.replyTo) return null;
+                    if (!this.replyTo) return undefined;
 
                     // Bots cannot access other bots' messages by their ID.
                     // However they can access them through replies...
                     this._replyMessage = await this._client.getMessages(
-                        this.isChannel ? await this.getInputChat() : null, {
+                        this.isChannel ? await this.getInputChat() : undefined, {
                             ids: Api.InputMessageReplyTo({id: this.id})
                         });
 
@@ -474,7 +471,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
                         // If that's the case, give it a second chance accessing
                         // directly by its ID.
                         this._replyMessage = await this._client.getMessages(
-                            this.isChannel ? this._inputChat : null, {
+                            this.isChannel ? this._inputChat : undefined, {
                                 ids: this.replyToMsgId
                             })
                     }
@@ -506,7 +503,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
             }
 
             async edit(args) {
-                if (this.fwdFrom || !this.out || !this._client) return null;
+                if (this.fwdFrom || !this.out || !this._client) return undefined;
                 args.entity = await this.getInputChat();
                 args.message = this.id;
 
@@ -531,12 +528,12 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
                 if (this._client)
                     return this._client.downloadMedia(args)
             }
-            async click({i = null, j = null, text = null, filter = null, data = null}) {
+            async click({i = undefined, j = undefined, text = undefined, filter = undefined, data = undefined}) {
                 if (!this._client) return;
 
                 if (data) {
                     if (!(await this._getInputChat()))
-                        return null;
+                        return undefined;
 
                     try {
                         return await this._client.invoke(functions.messages.GetBotCallbackAnswerRequest({
@@ -546,7 +543,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
                         }))
                     } catch (e) {
                         if (e instanceof errors.BotTimeout)
-                            return null
+                            return undefined
                     }
                 }
 
@@ -578,7 +575,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
                             return button.click()
                         }
                     }
-                    return null
+                    return undefined
                 }
 
                 i = !i ? 0 : i;
@@ -608,7 +605,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
             async _reloadMessage() {
                 if (!this._client) return;
 
-                const chat = this.isChannel ? this.getInputChat() : null;
+                const chat = this.isChannel ? this.getInputChat() : undefined;
                 const msg = this._client.getMessages({chat, ids: this.id});
 
                 if (!msg) return;
@@ -640,7 +637,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
             _neededMarkupBot() {
                 if (this._client && !(this.replyMarkup instanceof types.ReplyInlineMarkup ||
                     this.replyMarkup instanceof types.ReplyKeyboardMarkup)) {
-                    return null
+                    return undefined
                 }
 
                 for (const row of this.replyMarkup.rows) {
@@ -661,7 +658,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
             }
             // TODO fix this
 
-            _documentByAttribute(kind, condition = null) {
+            _documentByAttribute(kind, condition = undefined) {
                 const doc = this.document;
                 if (doc) {
                     for (const attr of doc.attributes) {
@@ -669,7 +666,7 @@ export class Message extends Mixin(SenderGetter, ChatGetter) {
                             if (!condition || (callable(condition) && condition(attr))) {
                                 return doc
                             }
-                            return null
+                            return undefined
                         }
                     }
                 }

+ 5 - 7
gramjs/tl/custom/messageButton.ts

@@ -1,8 +1,6 @@
-import {TelegramClient} from "../../client/TelegramClient";
-import {ButtonLike, EntityLike, MessageIDLike} from "../../define";
+import type {TelegramClient} from "../../client/TelegramClient";
+import type {ButtonLike, EntityLike, MessageIDLike} from "../../define";
 import {Api} from "../api";
-import GetBotCallbackAnswer = Api.messages.GetBotCallbackAnswer;
-import StartBot = Api.messages.StartBot;
 import {Button} from "./button";
 
 export class MessageButton {
@@ -53,7 +51,7 @@ export class MessageButton {
                 parseMode: undefined,
             });
         } else if (this.button instanceof Api.KeyboardButtonCallback) {
-            const request = new GetBotCallbackAnswer({
+            const request = new Api.messages.GetBotCallbackAnswer({
                 peer: this._chat,
                 msgId: this._msgId,
                 data: this.button.data,
@@ -67,7 +65,7 @@ export class MessageButton {
                 throw e;
             }
         } else if (this.button instanceof Api.KeyboardButtonSwitchInline) {
-            return this._client.invoke(new StartBot({
+            return this._client.invoke(new Api.messages.StartBot({
                 bot: this._bot,
                 peer: this._chat,
                 startParam: this.button.query
@@ -75,7 +73,7 @@ export class MessageButton {
         } else if (this.button instanceof Api.KeyboardButtonUrl) {
             return this.button.url;
         } else if (this.button instanceof Api.KeyboardButtonGame) {
-            const request = new GetBotCallbackAnswer({
+            const request = new Api.messages.GetBotCallbackAnswer({
                 peer: this._chat,
                 msgId: this._msgId,
                 game: true,

+ 7 - 3
gramjs/tl/custom/senderGetter.ts

@@ -1,6 +1,6 @@
-import {Entity, EntityLike} from "../../define";
-import {TelegramClient} from "../../client/TelegramClient";
-import Api from "../api";
+import type {Entity} from "../../define";
+import type {TelegramClient} from "../../client/TelegramClient";
+import {Api} from "../api";
 
 interface SenderGetterConstructorInterface {
     senderId?: number;
@@ -24,6 +24,7 @@ export class SenderGetter {
         c._sender = sender;
         c._inputSender = inputSender;
         c._client = undefined;
+
     }
 
     get sender() {
@@ -38,6 +39,9 @@ export class SenderGetter {
                 await this._refetchSender();
             }
         }
+        if (!this._sender){
+            throw new Error("Could not find sender");
+        }
         return this._sender;
     }
 

+ 1 - 1
gramjs/tl/generationHelpers.ts

@@ -1,5 +1,5 @@
 import {crc32} from '../Helpers';
-import {DateLike} from "../define";
+import type {DateLike} from "../define";
 
 const snakeToCamelCase = (name: string) => {
     const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase());

+ 4 - 2
gramjs/tl/index.ts

@@ -1,3 +1,5 @@
-export {Api} from "./api";
+import {Api} from "./api";
+import {patchAll} from "./patched"
+patchAll();
+export {Api}
 export {serializeBytes, serializeDate} from './generationHelpers';
-export const patched = null;

+ 11 - 5
gramjs/tl/patched/index.ts

@@ -11,11 +11,17 @@ class MessageService extends Mixin(_Message, Api.MessageService) {
 
 }
 
-class Message extends Mixin(_Message, Api.MessageEmpty) {
+class Message extends Mixin(_Message, Api.Message) {
 
 }
+export function patchAll(){
 
-tlobjects[MessageEmpty.CONSTRUCTOR_ID] = MessageEmpty;
-tlobjects[MessageService.CONSTRUCTOR_ID] = MessageService;
-tlobjects[Message.CONSTRUCTOR_ID] = Message;
-console.log("called");
+    tlobjects[MessageEmpty.CONSTRUCTOR_ID.toString()] = MessageEmpty;
+    tlobjects[MessageService.CONSTRUCTOR_ID.toString()] = MessageService;
+    tlobjects[Message.CONSTRUCTOR_ID.toString()] = Message;
+}
+export {
+    Message,
+    MessageService,
+    MessageEmpty
+}

+ 19 - 0
gramjs/tl/types-generator/generate.js

@@ -8,6 +8,22 @@ const INPUT_FILE = path.resolve(__dirname, '../static/api.tl')
 const SCHEMA_FILE = path.resolve(__dirname, '../static/schema.tl')
 
 const OUTPUT_FILE = path.resolve(__dirname, '../api.d.ts')
+const peersToPatch = ['InputPeer', 'Peer', 'InputUser', 'User', 'UserFull', 'Chat', 'ChatFull', 'InputChannel']
+
+function patchMethods(methods) {
+    for (const method of methods) {
+        for (const arg in method['argsConfig']) {
+            if (peersToPatch.includes(method['argsConfig'][arg]['type'])) {
+                method['argsConfig'][arg]['type'] = 'EntityLike'
+            } else if (method['argsConfig'][arg]['type'] && arg.toLowerCase().includes('msgid')) {
+                if (method['argsConfig'][arg]['type'] !== 'long') {
+                    method['argsConfig'][arg]['type'] = 'MessageIDLike'
+                }
+            }
+        }
+    }
+
+}
 
 function main() {
     const tlContent = fs.readFileSync(INPUT_FILE, 'utf-8')
@@ -17,6 +33,9 @@ function main() {
     const types = [...apiConfig.types, ...schemeConfig.types]
     const functions = [...apiConfig.functions, ...schemeConfig.functions]
     const constructors = [...apiConfig.constructors, ...schemeConfig.constructors]
+    // patching custom types
+
+    patchMethods(functions)
     const generated = templateFn({ types: types, functions: functions, constructors: constructors })
 
     fs.writeFileSync(OUTPUT_FILE, generated)

+ 3 - 1
gramjs/tl/types-generator/template.js

@@ -137,6 +137,7 @@ ${indent}};`.trim()
 // This file is autogenerated. All changes will be overwritten.
 
 import { BigInteger } from 'big-integer';
+import {EntityLike,MessageIDLike} from "../define";
 
 
 export namespace Api {
@@ -181,7 +182,7 @@ export namespace Api {
 
   class Request<Args, Response> extends VirtualClass<Partial<Args>> {
     static readResult(reader: Reader): Buffer;
-    static resolve(client: Client, utils: Utils): Promise<void>;
+    resolve(client: Client, utils: Utils): Promise<void>;
 
     __response: Response;
   }
@@ -213,6 +214,7 @@ export namespace Api {
         .join('\n')}
 
 // Types
+  export type TypeEntityLike = EntityLike;
   ${renderTypes(typesByNs._, '  ')}
 // All requests
   export type AnyRequest = ${requestsByNs._.map(({ name }) => upperFirst(name))

+ 3 - 0
npmpublish.bat

@@ -1,9 +1,12 @@
+tsc
 TYPE package.json > dist\package.json
 TYPE README.md > dist\README.md
 TYPE LICENSE > dist\LICENSE
 mkdir dist\tl\static\
 TYPE gramjs\tl\static\api.tl > dist\tl\static\api.tl
 TYPE gramjs\tl\static\schema.tl > dist\tl\static\schema.tl
+TYPE gramjs\tl\api.d.ts > dist\tl\api.d.ts
+TYPE gramjs\define.d.ts > dist\define.d.ts
 cd dist
 npm publish
 cd ..

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "telegram",
-  "version": "1.2.5",
+  "version": "1.2.7",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "telegram",
-      "version": "1.2.5",
+      "version": "1.2.7",
       "license": "MIT",
       "dependencies": {
         "@cryptography/aes": "^0.1.1",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "telegram",
-  "version": "1.2.7",
+  "version": "1.3.2",
   "description": "NodeJS MTProto API Telegram client library,",
   "main": "index.js",
   "types": "index.d.ts",

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä