Browse Source

major rework

painor 4 years ago
parent
commit
bf7313fd08

+ 0 - 907
gramjs/client/TelegramClient--old.js

@@ -1,907 +0,0 @@
-const Logger = require('../extensions/Logger')
-const { sleep,IS_NODE } = require('../Helpers')
-const errors = require('../errors')
-const MemorySession = require('../sessions/Memory')
-const Helpers = require('../Helpers')
-const { BinaryWriter } = require('../extensions')
-const utils = require('../Utils')
-const Session = require('../sessions/Abstract')
-const os = require('os')
-const { LAYER } = require('../tl/AllTLObjects')
-const { constructors, requests } = require('../tl')
-const MTProtoSender = require('../network/MTProtoSender')
-const { UpdateConnectionState } = require('../network')
-const { ConnectionTCPObfuscated } = require('../network/connection/TCPObfuscated')
-const { ConnectionTCPFull } = require('../network/connection/TCPFull')
-const { authFlow, checkAuthorization } = require('./auth')
-const { downloadFile } = require('./downloadFile')
-const { uploadFile } = require('./uploadFile')
-
-const DEFAULT_DC_ID = 1
-const DEFAULT_IPV4_IP = IS_NODE?'149.154.167.51':'pluto.web.telegram.org'
-const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]'
-
-// All types
-const sizeTypes = ['w', 'y', 'd', 'x', 'c', 'm', 'b', 'a', 's']
-
-
-class TelegramClient {
-    static DEFAULT_OPTIONS = {
-        connection:IS_NODE? ConnectionTCPFull: ConnectionTCPObfuscated,
-        useIPV6: false,
-        proxy: null,
-        timeout: 10,
-        requestRetries: 5,
-        connectionRetries: Infinity,
-        retryDelay: 1000,
-        autoReconnect: true,
-        sequentialUpdates: false,
-        floodSleepThreshold: 60,
-        deviceModel: null,
-        systemVersion: null,
-        appVersion: null,
-        langCode: 'en',
-        systemLangCode: 'en',
-        baseLogger: 'gramjs',
-        useWSS: false,
-    }
-
-    /**
-     *
-     * @param session {StringSession|LocalStorageSession}
-     * @param apiId
-     * @param apiHash
-     * @param opts
-     */
-    constructor(session, apiId, apiHash, opts = TelegramClient.DEFAULT_OPTIONS) {
-        if (apiId === undefined || apiHash === undefined) {
-            throw Error('Your API ID or Hash are invalid. Please read "Requirements" on README.md')
-        }
-        const args = { ...TelegramClient.DEFAULT_OPTIONS, ...opts }
-        this.apiId = apiId
-        this.apiHash = apiHash
-        this._useIPV6 = args.useIPV6
-        this._entityCache = new Set()
-        if (typeof args.baseLogger == 'string') {
-            this._log = new Logger()
-        } else {
-            this._log = args.baseLogger
-        }
-        // Determine what session we will use
-        if (typeof session === 'string' || !session) {
-            throw new Error('not implemented')
-        } else if (!(session instanceof Session)) {
-            throw new Error('The given session must be str or a session instance')
-        }
-
-        this.floodSleepThreshold = args.floodSleepThreshold
-        this._eventBuilders = []
-
-        this._phoneCodeHash = {}
-        this.session = session
-        // this._entityCache = EntityCache();
-        this.apiId = parseInt(apiId)
-        this.apiHash = apiHash
-
-        this._requestRetries = args.requestRetries
-        this._connectionRetries = args.connectionRetries
-        this._retryDelay = args.retryDelay || 0
-        if (args.proxy) {
-            this._log.warn('proxies are not supported')
-        }
-        this._proxy = args.proxy
-        this._timeout = args.timeout
-        this._autoReconnect = args.autoReconnect
-
-        this._connection = args.connection
-        // TODO add proxy support
-
-        this._floodWaitedRequests = {}
-
-        this._initWith = x => {
-            return new requests.InvokeWithLayer({
-                layer: LAYER,
-                query: new requests.InitConnection({
-                    apiId: this.apiId,
-                    deviceModel: args.deviceModel || os.type()
-                        .toString() || 'Unknown',
-                    systemVersion: args.systemVersion || os.release()
-                        .toString() || '1.0',
-                    appVersion: args.appVersion || '1.0',
-                    langCode: args.langCode,
-                    langPack: '', // this should be left empty.
-                    systemLangCode: args.systemLangCode,
-                    query: x,
-                    proxy: null, // no proxies yet.
-                }),
-            })
-        }
-
-        this._args = args
-        // These will be set later
-        this._config = null
-        this.phoneCodeHashes = []
-        this._borrowedSenderPromises = {}
-    }
-
-
-    // region Connecting
-
-    /**
-     * Connects to the Telegram servers, executing authentication if required.
-     * Note that authenticating to the Telegram servers is not the same as authenticating
-     * the app, which requires to send a code first.
-     * @returns {Promise<void>}
-     */
-    async connect() {
-        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._sender.send(this._initWith(
-            new requests.help.GetConfig({}),
-        ))
-
-        this._dispatchUpdate({ update: new UpdateConnectionState(1) })
-        this._updateLoop()
-    }
-
-    async _initSession() {
-        await this.session.load()
-
-        if (!this.session.serverAddress || (this.session.serverAddress.includes(':') !== this._useIPV6)) {
-            this.session.setDC(DEFAULT_DC_ID, this._useIPV6 ? DEFAULT_IPV6_IP : DEFAULT_IPV4_IP, this._args.useWSS ? 443 : 80)
-        }
-    }
-
-    async _updateLoop() {
-        while (this.isConnected()) {
-            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 requests.Ping({
-                    pingId: 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.
-
-            // TODO Call getDifference instead since it's more relevant
-            if (new Date().getTime() - this._lastRequest > 30 * 60 * 1000) {
-                try {
-                    await this.invoke(new requests.updates.GetState())
-                } catch (e) {
-
-                }
-            }
-        }
-    }
-
-    /**
-     * Disconnects from the Telegram server
-     * @returns {Promise<void>}
-     */
-    async disconnect() {
-        if (this._sender) {
-            await this._sender.disconnect()
-        }
-    }
-
-    /**
-     * Disconnects all senders and removes all handlers
-     * @returns {Promise<void>}
-     */
-    async destroy() {
-        await Promise.all([
-            this.disconnect(),
-            this.session.delete(),
-            ...Object.values(this._borrowedSenderPromises).map(promise => {
-                return promise
-                    .then(sender => sender.disconnect())
-            }),
-        ])
-
-        this._eventBuilders = []
-    }
-
-    async _switchDC(newDc) {
-        this._log.info(`Reconnecting to new data center ${newDc}`)
-        const DC = await utils.getDC(newDc,this)
-        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(null)
-        this.session.setAuthKey(null)
-        await this.disconnect()
-        return this.connect()
-    }
-
-    async _authKeyCallback(authKey, dcId) {
-        this.session.setAuthKey(authKey, dcId)
-    }
-
-    // endregion
-    // export region
-
-    removeSender(dcId) {
-        delete this._borrowedSenderPromises[dcId]
-    }
-
-    async _borrowExportedSender(dcId, retries = 5) {
-        let senderPromise = this._borrowedSenderPromises[dcId]
-        if (!senderPromise) {
-            senderPromise = this._createExportedSender(dcId, retries)
-            this._borrowedSenderPromises[dcId] = senderPromise
-
-            senderPromise.then(sender => {
-                if (!sender) {
-                    delete this._borrowedSenderPromises[dcId]
-                }
-            })
-        }
-        return senderPromise
-    }
-
-    async _createExportedSender(dcId, retries) {
-        const dc = await utils.getDC(dcId,this)
-        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 requests.auth.ExportAuthorization({ dcId: dcId }))
-                    const req = this._initWith(new requests.auth.ImportAuthorization({
-                        id: auth.id,
-                        bytes: auth.bytes,
-                    },
-                    ))
-                    await sender.send(req)
-                }
-                sender.dcId = dcId
-                return sender
-            } catch (e) {
-                console.log(e)
-                await sender.disconnect()
-            }
-        }
-        return null
-    }
-
-    // end region
-
-    // download region
-
-    /**
-     * Complete flow to download a file.
-     * @param inputLocation {constructors.InputFileLocation}
-     * @param [args[partSizeKb] {number}]
-     * @param [args[fileSize] {number}]
-     * @param [args[progressCallback] {Function}]
-     * @param [args[start] {number}]
-     * @param [args[end] {number}]
-     * @param [args[dcId] {number}]
-     * @param [args[workers] {number}]
-     * @returns {Promise<Buffer>}
-     */
-    async downloadFile(inputLocation, args = {}) {
-        return downloadFile(this, inputLocation, args)
-    }
-
-    async downloadMedia(messageOrMedia, args) {
-        let date
-        let media
-        if (messageOrMedia instanceof constructors.Message) {
-            date = messageOrMedia.date
-            media = messageOrMedia.media
-        } else {
-            date = new Date().getTime()
-            media = messageOrMedia
-        }
-        if (typeof media == 'string') {
-            throw new Error('not implemented')
-        }
-
-        if (media instanceof constructors.MessageMediaWebPage) {
-            if (media.webpage instanceof constructors.WebPage) {
-                media = media.webpage.document || media.webpage.photo
-            }
-        }
-        if (media instanceof constructors.MessageMediaPhoto || media instanceof constructors.Photo) {
-            return this._downloadPhoto(media, args)
-        } else if (media instanceof constructors.MessageMediaDocument || media instanceof constructors.Document) {
-            return this._downloadDocument(media, args)
-        } else if (media instanceof constructors.MessageMediaContact) {
-            return this._downloadContact(media, args)
-        } else if (media instanceof constructors.WebDocument || media instanceof constructors.WebDocumentNoProxy) {
-            return this._downloadWebDocument(media, args)
-        }
-    }
-
-    async downloadProfilePhoto(entity, isBig = false) {
-        // ('User', 'Chat', 'UserFull', 'ChatFull')
-        const ENTITIES = [0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697]
-        // ('InputPeer', 'InputUser', 'InputChannel')
-        // const INPUTS = [0xc91c90b6, 0xe669bf46, 0x40f202fd]
-        // Todo account for input methods
-        const sizeType = isBig ? 'x' : 'm'
-        let photo
-        if (!(ENTITIES.includes(entity.SUBCLASS_OF_ID))) {
-            photo = entity
-        } else {
-            if (!entity.photo) {
-                // Special case: may be a ChatFull with photo:Photo
-                if (!entity.chatPhoto) {
-                    return null
-                }
-
-                return this._downloadPhoto(
-                    entity.chatPhoto, { sizeType },
-                )
-            }
-            photo = entity.photo
-        }
-
-        let dcId
-        let loc
-        if (photo instanceof constructors.UserProfilePhoto || photo instanceof constructors.ChatPhoto) {
-            dcId = photo.dcId
-            const size = isBig ? photo.photoBig : photo.photoSmall
-            loc = new constructors.InputPeerPhotoFileLocation({
-                peer: utils.getInputPeer(entity),
-                localId: size.localId,
-                volumeId: size.volumeId,
-                big: isBig,
-            })
-        } else {
-            // It doesn't make any sense to check if `photo` can be used
-            // as input location, because then this method would be able
-            // to "download the profile photo of a message", i.e. its
-            // media which should be done with `download_media` instead.
-            return null
-        }
-        try {
-            return this.downloadFile(loc, {
-                dcId: dcId,
-            })
-        } catch (e) {
-            // TODO this should never raise
-            throw e
-            /*if (e.message === 'LOCATION_INVALID') {
-                const ie = await this.getInputEntity(entity)
-                if (ie instanceof constructors.InputPeerChannel) {
-                    const full = await this.invoke(new requests.channels.GetFullChannel({
-                        channel: ie,
-                    }))
-                    return this._downloadPhoto(full.fullChat.chatPhoto, { sizeType })
-                } else {
-                    return null
-                }
-            } else {
-                throw e
-            }*/
-        }
-    }
-
-    async downloadStickerSetThumb(stickerSet) {
-        if (!stickerSet.thumb || !stickerSet.thumb.location) {
-            return undefined
-        }
-
-        const { location } = stickerSet.thumb
-
-        return this.downloadFile(
-            new constructors.InputStickerSetThumb({
-                stickerset: new constructors.InputStickerSetID({
-                    id: stickerSet.id,
-                    accessHash: stickerSet.accessHash,
-                }),
-                localId: location.localId,
-                volumeId: location.volumeId,
-            }),
-            { dcId: stickerSet.thumbDcId },
-        )
-    }
-
-    _pickFileSize(sizes, sizeType) {
-        if (!sizeType || !sizes || !sizes.length) {
-            return null
-        }
-        const indexOfSize = sizeTypes.indexOf(sizeType)
-        let size
-        for (let i = indexOfSize; i < sizeTypes.length; i++) {
-            size = sizes.find(s => s.type === sizeTypes[i])
-            if (size) {
-                return size
-            }
-        }
-        return null
-    }
-
-
-    _downloadCachedPhotoSize(size) {
-        // No need to download anything, simply write the bytes
-        let data
-        if (size instanceof constructors.PhotoStrippedSize) {
-            data = utils.strippedPhotoToJpg(size.bytes)
-        } else {
-            data = size.bytes
-        }
-        return data
-    }
-
-    async _downloadPhoto(photo, args) {
-        if (photo instanceof constructors.MessageMediaPhoto) {
-            photo = photo.photo
-        }
-        if (!(photo instanceof constructors.Photo)) {
-            return
-        }
-        const size = this._pickFileSize(photo.sizes, args.sizeType)
-        if (!size || (size instanceof constructors.PhotoSizeEmpty)) {
-            return
-        }
-
-        if (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize) {
-            return this._downloadCachedPhotoSize(size)
-        }
-        return this.downloadFile(
-            new constructors.InputPhotoFileLocation({
-                id: photo.id,
-                accessHash: photo.accessHash,
-                fileReference: photo.fileReference,
-                thumbSize: size.type,
-            }),
-            {
-                dcId: photo.dcId,
-                fileSize: size.size,
-                progressCallback: args.progressCallback,
-            },
-        )
-    }
-
-    async _downloadDocument(doc, args) {
-        if (doc instanceof constructors.MessageMediaDocument) {
-            doc = doc.document
-        }
-        if (!(doc instanceof constructors.Document)) {
-            return
-        }
-
-        let size = null
-        if (args.sizeType) {
-            size = doc.thumbs ? this._pickFileSize(doc.thumbs, args.sizeType) : null
-            if (!size && doc.mimeType.startsWith('video/')) {
-                return
-            }
-
-            if (size && (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize)) {
-                return this._downloadCachedPhotoSize(size)
-            }
-        }
-
-        return this.downloadFile(
-            new constructors.InputDocumentFileLocation({
-                id: doc.id,
-                accessHash: doc.accessHash,
-                fileReference: doc.fileReference,
-                thumbSize: size ? size.type : '',
-            }),
-            {
-                fileSize: size ? size.size : doc.size,
-                progressCallback: args.progressCallback,
-                start: args.start,
-                end: args.end,
-                dcId: doc.dcId,
-                workers: args.workers,
-            },
-        )
-    }
-
-    _downloadContact(media, args) {
-        throw new Error('not implemented')
-    }
-
-    _downloadWebDocument(media, args) {
-        throw new Error('not implemented')
-    }
-
-    // region Invoking Telegram request
-    /**
-     * Invokes a MTProtoRequest (sends and receives it) and returns its result
-     * @param request
-     * @returns {Promise}
-     */
-    async invoke(request) {
-        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)
-
-
-        this._lastRequest = new Date().getTime()
-        let attempt = 0
-        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 checkAuthorization(this)) {
-                        throw e
-                    }
-                    await this._switchDC(e.newDc)
-                } else {
-                    throw e
-                }
-            }
-        }
-        throw new Error(`Request was unsuccessful ${attempt} time(s)`)
-    }
-
-    async getMe() {
-        try {
-            return (await this.invoke(new requests.users
-                .GetUsers({ id: [new constructors.InputUserSelf()] })))[0]
-        } catch (e) {
-        }
-    }
-
-
-
-    uploadFile(fileParams) {
-        return uploadFile(this, fileParams)
-    }
-
-    // event region
-    addEventHandler(callback, event) {
-        this._eventBuilders.push([event, callback])
-    }
-
-    _handleUpdate(update) {
-        if ([-1, 0, 1].includes(update)){
-            this._dispatchUpdate({ update: new UpdateConnectionState(update) })
-            return
-        }
-        //this.session.processEntities(update)
-        this._entityCache.add(update)
-
-        if (update instanceof constructors.Updates || update instanceof constructors.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 constructors.UpdateShort) {
-            this._processUpdate(update.update, null)
-        } else {
-            this._processUpdate(update, null)
-        }
-        // TODO add caching
-        // this._stateCache.update(update)
-    }
-
-    _processUpdate(update, others, entities) {
-        update._entities = entities || {}
-        const args = {
-            update: update,
-            others: others,
-        }
-        this._dispatchUpdate(args)
-    }
-
-
-    // endregion
-
-    // region private methods
-
-    /**
-     Gets a full entity from the given string, which may be a phone or
-     a username, and processes all the found entities on the session.
-     The string may also be a user link, or a channel/chat invite link.
-
-     This method has the side effect of adding the found users to the
-     session database, so it can be queried later without API calls,
-     if this option is enabled on the session.
-
-     Returns the found entity, or raises TypeError if not found.
-     * @param string {string}
-     * @returns {Promise<void>}
-     * @private
-     */
-    /*CONTEST
-    async _getEntityFromString(string) {
-        const phone = utils.parsePhone(string)
-        if (phone) {
-            try {
-                for (const user of (await this.invoke(
-                    new requests.contacts.GetContacts(0))).users) {
-                    if (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
-            }
-        } else if (['me', 'this'].includes(string.toLowerCase())) {
-            return this.getMe()
-        } else {
-            const { username, isJoinChat } = utils.parseUsername(string)
-            if (isJoinChat) {
-                const invite = await this.invoke(new requests.messages.CheckChatInvite({
-                    'hash': username,
-                }))
-                if (invite instanceof constructors.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 constructors.ChatInviteAlready) {
-                    return invite.chat
-                }
-            } else if (username) {
-                try {
-                    const result = await this.invoke(
-                        new requests.contacts.ResolveUsername(username))
-                    const pid = utils.getPeerId(result.peer, false)
-                    if (result.peer instanceof constructors.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
-                            }
-                        }
-                    }
-                } 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}"`)
-    }
-    */
-    // endregion
-
-
-    // users region
-    /**
-     Turns the given entity into its input entity version.
-
-     Most requests use this kind of :tl:`InputPeer`, so this is the most
-     suitable call to make for those cases. **Generally you should let the
-     library do its job** and don't worry about getting the input entity
-     first, but if you're going to use an entity often, consider making the
-     call:
-
-     Arguments
-     entity (`str` | `int` | :tl:`Peer` | :tl:`InputPeer`):
-     If a username or invite link is given, **the library will
-     use the cache**. This means that it's possible to be using
-     a username that *changed* or an old invite link (this only
-     happens if an invite link for a small group chat is used
-     after it was upgraded to a mega-group).
-
-     If the username or ID from the invite link is not found in
-     the cache, it will be fetched. The same rules apply to phone
-     numbers (``'+34 123456789'``) from people in your contact list.
-
-     If an exact name is given, it must be in the cache too. This
-     is not reliable as different people can share the same name
-     and which entity is returned is arbitrary, and should be used
-     only for quick tests.
-
-     If a positive integer ID is given, the entity will be searched
-     in cached users, chats or channels, without making any call.
-
-     If a negative integer ID is given, the entity will be searched
-     exactly as either a chat (prefixed with ``-``) or as a channel
-     (prefixed with ``-100``).
-
-     If a :tl:`Peer` is given, it will be searched exactly in the
-     cache as either a user, chat or channel.
-
-     If the given object can be turned into an input entity directly,
-     said operation will be done.
-
-     Unsupported types will raise ``TypeError``.
-
-     If the entity can't be found, ``ValueError`` will be raised.
-
-     Returns
-     :tl:`InputPeerUser`, :tl:`InputPeerChat` or :tl:`InputPeerChannel`
-     or :tl:`InputPeerSelf` if the parameter is ``'me'`` or ``'self'``.
-
-     If you need to get the ID of yourself, you should use
-     `get_me` with ``input_peer=True``) instead.
-
-     Example
-     .. code-block:: python
-
-     // If you're going to use "username" often in your code
-     // (make a lot of calls), consider getting its input entity
-     // once, and then using the "user" everywhere instead.
-     user = await client.get_input_entity('username')
-
-     // The same applies to IDs, chats or channels.
-     chat = await client.get_input_entity(-123456789)
-
-     * @param peer
-     * @returns {Promise<>}
-     */
-    /*CONTEST
-    async getInputEntity(peer) {
-        // 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 === 'number' || peer.SUBCLASS_OF_ID === 0x2d45687) {
-                if (this._entityCache.has(peer)) {
-                    return this._entityCache[peer]
-                }
-            }
-            // eslint-disable-next-line no-empty
-        } catch (e) {
-        }
-        // Then come known strings that take precedence
-        if (['me', 'this'].includes(peer)) {
-            return new constructors.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) {
-        }
-        // Only network left to try
-        if (typeof peer === 'string') {
-            return utils.getInputPeer(await this._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 constructors.PeerUser) {
-            const users = await this.invoke(new requests.users.GetUsers({
-                id: [new constructors.InputUser({
-                    userId: peer.userId,
-                    accessHash: 0,
-                })],
-            }))
-            if (users && !(users[0] instanceof constructors.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 constructors.PeerChat) {
-            return new constructors.InputPeerChat({
-                chatId: peer.chatId,
-            })
-        } else if (peer instanceof constructors.PeerChannel) {
-            try {
-                const channels = await this.invoke(new requests.channels.GetChannels({
-                    id: [new constructors.InputChannel({
-                        channelId: peer.channelId,
-                        accessHash: 0,
-                    })],
-                }))
-
-                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.id || peer.channelId || peer.chatId || peer.userId}.
-         Please read https://` +
-            'docs.telethon.dev/en/latest/concepts/entities.html to' +
-            ' find out more details.',
-        )
-    }
-    */
-    async _dispatchUpdate(args = {
-        update: null,
-        others: null,
-        channelId: null,
-        ptsDate: null,
-    }) {
-        for (const [builder, callback] of this._eventBuilders) {
-            const event = builder.build(args.update)
-            if (event) {
-                await callback(event)
-            }
-        }
-    }
-
-    isConnected() {
-        if (this._sender) {
-            if (this._sender.isConnected()) {
-                return true
-            }
-        }
-        return false
-    }
-}
-
-module.exports = TelegramClient

+ 0 - 192
gramjs/client/downloadFile.ts

@@ -1,192 +0,0 @@
-import { default as Api } from '../tl/api';
-import {TelegramClient} from './TelegramClient';
-// @ts-ignore
-import { getAppropriatedPartSize } from '../Utils';
-// @ts-ignore
-import { sleep } from '../Helpers';
-
-export interface progressCallback {
-    (
-        progress: number, // Float between 0 and 1.
-        ...args: any[]
-    ): void;
-
-    isCanceled?: boolean;
-    acceptsBuffer?: boolean;
-}
-
-export interface DownloadFileParams {
-    dcId: number;
-    fileSize: number;
-    workers?: number;
-    partSizeKb?: number;
-    start?: number;
-    end?: number;
-    progressCallback?: progressCallback;
-}
-
-interface Deferred {
-    promise: Promise<any>;
-    resolve: (value?: any) => void;
-}
-
-// Chunk sizes for `upload.getFile` must be multiple of the smallest size
-const MIN_CHUNK_SIZE = 4096;
-const DEFAULT_CHUNK_SIZE = 64; // kb
-const ONE_MB = 1024 * 1024;
-const REQUEST_TIMEOUT = 15000;
-
-export async function downloadFile(
-    client: TelegramClient,
-    inputLocation: Api.InputFileLocation,
-    fileParams: DownloadFileParams,
-) {
-    let { partSizeKb, fileSize, workers = 1, end } = fileParams;
-    const { dcId, progressCallback, start = 0 } = fileParams;
-
-    end = end && end < fileSize ? end : fileSize - 1;
-
-    if (!partSizeKb) {
-        partSizeKb = fileSize ? getAppropriatedPartSize(fileSize) : DEFAULT_CHUNK_SIZE;
-    }
-
-    // @ts-ignore
-    const partSize = partSizeKb * 1024;
-    const partsCount = end ? Math.ceil((end - start) / partSize) : 1;
-
-    if (partSize % MIN_CHUNK_SIZE !== 0) {
-        throw new Error(`The part size must be evenly divisible by ${MIN_CHUNK_SIZE}`);
-    }
-
-    let sender: any;
-    if (dcId) {
-        try {
-            sender = await client._borrowExportedSender(dcId);
-        } catch (e) {
-            // This should never raise
-            client._log.error(e);
-            if (e.message === 'DC_ID_INVALID') {
-                // Can't export a sender for the ID we are currently in
-                sender = client._sender;
-            } else {
-                throw e;
-            }
-        }
-    } else {
-        sender = client._sender;
-    }
-
-    client._log.info(`Downloading file in chunks of ${partSize} bytes`);
-
-    const foreman = new Foreman(workers);
-    const promises: Promise<any>[] = [];
-    let offset = start;
-    // Used for files with unknown size and for manual cancellations
-    let hasEnded = false;
-
-    let progress = 0;
-    if (progressCallback) {
-        progressCallback(progress);
-    }
-
-    while (true) {
-        let limit = partSize;
-        let isPrecise = false;
-
-        if (Math.floor(offset / ONE_MB) !== Math.floor((offset + limit - 1) / ONE_MB)) {
-            limit = ONE_MB - offset % ONE_MB;
-            isPrecise = true;
-        }
-
-        await foreman.requestWorker();
-
-        if (hasEnded) {
-            await foreman.releaseWorker();
-            break;
-        }
-
-        promises.push((async () => {
-            try {
-                const result = await Promise.race([
-                    await sender.send(new Api.upload.GetFile({
-                        location: inputLocation,
-                        offset,
-                        limit,
-                        precise: isPrecise || undefined,
-                    })),
-                    sleep(REQUEST_TIMEOUT).then(() => Promise.reject(new Error('REQUEST_TIMEOUT'))),
-                ]);
-
-                if (progressCallback) {
-                    if (progressCallback.isCanceled) {
-                        throw new Error('USER_CANCELED');
-                    }
-
-                    progress += (1 / partsCount);
-                    progressCallback(progress);
-                }
-
-                if (!end && (result.bytes.length < limit)) {
-                    hasEnded = true;
-                }
-
-                return result.bytes;
-            } catch (err) {
-                hasEnded = true;
-                throw err;
-            } finally {
-                foreman.releaseWorker();
-            }
-        })());
-
-        offset += limit;
-
-        if (end && (offset > end)) {
-            break;
-        }
-    }
-
-    const results = await Promise.all(promises);
-    const buffers = results.filter(Boolean);
-    const totalLength = end ? (end + 1) - start : undefined;
-    return Buffer.concat(buffers, totalLength);
-}
-
-class Foreman {
-    private deferred: Deferred | undefined;
-    private activeWorkers = 0;
-
-    constructor(private maxWorkers: number) {
-    }
-
-    requestWorker() {
-        this.activeWorkers++;
-
-        if (this.activeWorkers > this.maxWorkers) {
-            this.deferred = createDeferred();
-            return this.deferred.promise;
-        }
-
-        return Promise.resolve();
-    }
-
-    releaseWorker() {
-        this.activeWorkers--;
-
-        if (this.deferred && (this.activeWorkers <= this.maxWorkers)) {
-            this.deferred.resolve();
-        }
-    }
-}
-
-function createDeferred(): Deferred {
-    let resolve: Deferred['resolve'];
-    const promise = new Promise((_resolve) => {
-        resolve = _resolve;
-    });
-
-    return {
-        promise,
-        resolve: resolve!,
-    };
-}

+ 189 - 2
gramjs/client/downloads.ts

@@ -1,3 +1,190 @@
-export class DownloadMethods {
-    // TODO
+import { default as Api } from '../tl/api';
+import {TelegramClient} from './TelegramClient';
+import { getAppropriatedPartSize } from '../Utils';
+import { sleep } from '../Helpers';
+
+export interface progressCallback {
+    (
+        progress: number, // Float between 0 and 1.
+        ...args: any[]
+    ): void;
+
+    isCanceled?: boolean;
+    acceptsBuffer?: boolean;
+}
+
+export interface DownloadFileParams {
+    dcId: number;
+    fileSize: number;
+    workers?: number;
+    partSizeKb?: number;
+    start?: number;
+    end?: number;
+    progressCallback?: progressCallback;
+}
+
+interface Deferred {
+    promise: Promise<any>;
+    resolve: (value?: any) => void;
+}
+
+// Chunk sizes for `upload.getFile` must be multiple of the smallest size
+const MIN_CHUNK_SIZE = 4096;
+const DEFAULT_CHUNK_SIZE = 64; // kb
+const ONE_MB = 1024 * 1024;
+const REQUEST_TIMEOUT = 15000;
+
+export async function downloadFile(
+    client: TelegramClient,
+    inputLocation: Api.InputFileLocation,
+    fileParams: DownloadFileParams,
+) {
+    let { partSizeKb, fileSize, workers = 1, end } = fileParams;
+    const { dcId, progressCallback, start = 0 } = fileParams;
+
+    end = end && end < fileSize ? end : fileSize - 1;
+
+    if (!partSizeKb) {
+        partSizeKb = fileSize ? getAppropriatedPartSize(fileSize) : DEFAULT_CHUNK_SIZE;
+    }
+
+    // @ts-ignore
+    const partSize = partSizeKb * 1024;
+    const partsCount = end ? Math.ceil((end - start) / partSize) : 1;
+
+    if (partSize % MIN_CHUNK_SIZE !== 0) {
+        throw new Error(`The part size must be evenly divisible by ${MIN_CHUNK_SIZE}`);
+    }
+
+    let sender: any;
+    if (dcId) {
+        try {
+            sender = await client._borrowExportedSender(dcId);
+        } catch (e) {
+            // This should never raise
+            client._log.error(e);
+            if (e.message === 'DC_ID_INVALID') {
+                // Can't export a sender for the ID we are currently in
+                sender = client._sender;
+            } else {
+                throw e;
+            }
+        }
+    } else {
+        sender = client._sender;
+    }
+
+    client._log.info(`Downloading file in chunks of ${partSize} bytes`);
+
+    const foreman = new Foreman(workers);
+    const promises: Promise<any>[] = [];
+    let offset = start;
+    // Used for files with unknown size and for manual cancellations
+    let hasEnded = false;
+
+    let progress = 0;
+    if (progressCallback) {
+        progressCallback(progress);
+    }
+
+    while (true) {
+        let limit = partSize;
+        let isPrecise = false;
+
+        if (Math.floor(offset / ONE_MB) !== Math.floor((offset + limit - 1) / ONE_MB)) {
+            limit = ONE_MB - offset % ONE_MB;
+            isPrecise = true;
+        }
+
+        await foreman.requestWorker();
+
+        if (hasEnded) {
+            await foreman.releaseWorker();
+            break;
+        }
+
+        promises.push((async () => {
+            try {
+                const result = await Promise.race([
+                    await sender.send(new Api.upload.GetFile({
+                        location: inputLocation,
+                        offset,
+                        limit,
+                        precise: isPrecise || undefined,
+                    })),
+                    sleep(REQUEST_TIMEOUT).then(() => Promise.reject(new Error('REQUEST_TIMEOUT'))),
+                ]);
+
+                if (progressCallback) {
+                    if (progressCallback.isCanceled) {
+                        throw new Error('USER_CANCELED');
+                    }
+
+                    progress += (1 / partsCount);
+                    progressCallback(progress);
+                }
+
+                if (!end && (result.bytes.length < limit)) {
+                    hasEnded = true;
+                }
+
+                return result.bytes;
+            } catch (err) {
+                hasEnded = true;
+                throw err;
+            } finally {
+                foreman.releaseWorker();
+            }
+        })());
+
+        offset += limit;
+
+        if (end && (offset > end)) {
+            break;
+        }
+    }
+
+    const results = await Promise.all(promises);
+    const buffers = results.filter(Boolean);
+    const totalLength = end ? (end + 1) - start : undefined;
+    return Buffer.concat(buffers, totalLength);
+}
+
+class Foreman {
+    private deferred: Deferred | undefined;
+    private activeWorkers = 0;
+
+    constructor(private maxWorkers: number) {
+    }
+
+    requestWorker() {
+        this.activeWorkers++;
+
+        if (this.activeWorkers > this.maxWorkers) {
+            this.deferred = createDeferred();
+            return this.deferred.promise;
+        }
+
+        return Promise.resolve();
+    }
+
+    releaseWorker() {
+        this.activeWorkers--;
+
+        if (this.deferred && (this.activeWorkers <= this.maxWorkers)) {
+            this.deferred.resolve();
+        }
+    }
+}
+
+function createDeferred(): Deferred {
+    let resolve: Deferred['resolve'];
+    const promise = new Promise((_resolve) => {
+        resolve = _resolve;
+    });
+
+    return {
+        promise,
+        resolve: resolve!,
+    };
 }

+ 0 - 125
gramjs/client/uploadFile.ts

@@ -1,125 +0,0 @@
-import { default as Api } from '../tl/api';
-
-import {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();
-}

+ 0 - 94
gramjs/events/NewMessage.js

@@ -1,94 +0,0 @@
-/*CONTEST
-const { EventBuilder, EventCommon } = require('./common')
-const { constructors } = require('../tl')
-
-class NewMessage extends EventBuilder {
-    constructor(args = {
-        chats: null,
-        func: null,
-    }) {
-        super(args)
-
-        this.chats = args.chats
-        this.func = args.func
-        this._noCheck = true
-    }
-
-    async _resolve(client) {
-        await super._resolve(client)
-        // this.fromUsers = await _intoIdSet(client, this.fromUsers)
-    }
-
-    build(update, others = null, thisId = null) {
-        let event
-        if (update instanceof constructors.UpdateNewMessage || update instanceof constructors.UpdateNewChannelMessage) {
-            if (!(update.message instanceof constructors.Message)) {
-                return
-            }
-            event = new Event(update.message)
-        } else if (update instanceof constructors.UpdateShortMessage) {
-            event = new Event(new constructors.Message({
-                out: update.out,
-                mentioned: update.mentioned,
-                mediaUnread: update.mediaUnread,
-                silent: update.silent,
-                id: update.id,
-                // Note that to_id/from_id complement each other in private
-                // messages, depending on whether the message was outgoing.
-                toId: new constructors.PeerUser(update.out ? update.userId : thisId),
-                fromId: update.out ? thisId : update.userId,
-                message: update.message,
-                date: update.date,
-                fwdFrom: update.fwdFrom,
-                viaBotId: update.viaBotId,
-                replyToMsgId: update.replyToMsgId,
-                entities: update.entities,
-            }))
-        } else if (update instanceof constructors.UpdateShortChatMessage) {
-            event = new this.Event(new constructors.Message({
-                out: update.out,
-                mentioned: update.mentioned,
-                mediaUnread: update.mediaUnread,
-                silent: update.silent,
-                id: update.id,
-                toId: new constructors.PeerChat(update.chatId),
-                fromId: update.fromId,
-                message: update.message,
-                date: update.date,
-                fwdFrom: update.fwdFrom,
-                viaBotId: update.viaBotId,
-                replyToMsgId: update.replyToMsgId,
-                entities: update.entities,
-            }))
-        } else {
-            return
-        }
-
-        // Make messages sent to ourselves outgoing unless they're forwarded.
-        // This makes it consistent with official client's appearance.
-        const ori = event.message
-        if (ori.toId instanceof constructors.PeerUser) {
-            if (ori.fromId === ori.toId.userId && !ori.fwdFrom) {
-                event.message.out = true
-            }
-        }
-        return event
-    }
-
-    filter(event) {
-        if (this._noCheck) {
-            return event
-        }
-        return event
-    }
-}
-
-class Event extends EventCommon {
-    constructor(message) {
-        super()
-        this.message = message
-    }
-}
-
-module.exports = NewMessage
-*/

+ 0 - 0
gramjs/events/NewMessage.ts


+ 0 - 21
gramjs/events/Raw.js

@@ -1,21 +0,0 @@
-const { EventBuilder } = require('./common')
-
-class Raw extends EventBuilder {
-    constructor(args = {
-        types: null,
-        func: null,
-    }) {
-        super()
-        if (!args.types) {
-            this.types = true
-        } else {
-            this.types = args.types
-        }
-    }
-
-    build(update, others = null) {
-        return update
-    }
-}
-
-module.exports = Raw

+ 0 - 0
gramjs/events/Raw.ts


+ 0 - 8
gramjs/events/index.js

@@ -1,8 +0,0 @@
-const NewMessage = require('./NewMessage')
-const Raw = require('./Raw')
-
-class StopPropagation extends Error {
-
-}
-
-module.exports = { NewMessage, StopPropagation, Raw }

+ 0 - 0
gramjs/events/index.ts


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


+ 21 - 0
gramjs/tl/patched/index.ts

@@ -0,0 +1,21 @@
+import {Api} from "../api";
+import {Message as _Message} from "../custom/message"
+import {Mixin} from 'ts-mixer';
+import {tlobjects} from "../AllTLObjects";
+
+class MessageEmpty extends Mixin(_Message, Api.MessageEmpty) {
+
+}
+
+class MessageService extends Mixin(_Message, Api.MessageService) {
+
+}
+
+class Message extends Mixin(_Message, Api.MessageEmpty) {
+
+}
+
+tlobjects[MessageEmpty.CONSTRUCTOR_ID] = MessageEmpty;
+tlobjects[MessageService.CONSTRUCTOR_ID] = MessageService;
+tlobjects[Message.CONSTRUCTOR_ID] = Message;
+console.log("called");