Răsfoiți Sursa

Overhaul the internal methods
Add multiple friendly methods

painor 4 ani în urmă
părinte
comite
54823e4a60

+ 1 - 1
gramjs/Helpers.ts

@@ -363,7 +363,7 @@ export function crc32(buf: Buffer | string) {
     return (crc ^ (-1)) >>> 0
 }
 
-export class TotalList extends Array {
+export class TotalList<T> extends Array<T> {
     public total?: number;
 
     constructor() {

+ 16 - 14
gramjs/Utils.ts

@@ -5,7 +5,9 @@ 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";
+import {MarkdownParser} from "./extensions/markdown";
+import {CustomFile} from "./client/uploads";
+import TypeInputFile = Api.TypeInputFile;
 
 
 const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' +
@@ -182,7 +184,7 @@ export function getInnerText(text: string, entities: Map<number, Api.TypeMessage
  * @returns {InputChannel|*}
  */
 export function getInputChannel(entity: EntityLike) {
-    if (typeof entity==="string" || typeof entity=="number"){
+    if (typeof entity === "string" || typeof entity == "number") {
         _raiseCastFail(entity, 'InputChannel')
     }
     if (entity.SUBCLASS_OF_ID === undefined) {
@@ -221,7 +223,7 @@ export function getInputChannel(entity: EntityLike) {
  * @param entity
  */
 export function getInputUser(entity: EntityLike): Api.InputPeerSelf {
-    if (typeof entity==="string" || typeof entity=="number"){
+    if (typeof entity === "string" || typeof entity == "number") {
         _raiseCastFail(entity, 'InputUser')
     }
 
@@ -294,7 +296,7 @@ function getInputDialog(dialog) {
  *  Similar to :meth:`get_input_peer`, but for input messages.
  */
 
-function getInputMessage(message: any): Api.InputMessageID {
+export function getInputMessage(message: any): Api.InputMessageID {
     if (typeof message === "number") {
         return new Api.InputMessageID({id: message});
     }
@@ -314,7 +316,7 @@ function getInputMessage(message: any): Api.InputMessageID {
  *  Similar to :meth:`get_input_peer`, but for input messages.
  */
 
-function getInputChatPhoto(photo: any): Api.TypeInputChatPhoto {
+export function getInputChatPhoto(photo: any): Api.TypeInputChatPhoto {
     if (photo === undefined || photo.SUBCLASS_OF_ID === undefined) {
         _raiseCastFail(photo, "InputChatPhoto");
     }
@@ -344,7 +346,7 @@ function getInputChatPhoto(photo: any): Api.TypeInputChatPhoto {
  * @param stripped{Buffer}
  * @returns {Buffer}
  */
-function strippedPhotoToJpg(stripped: Buffer) {
+export function strippedPhotoToJpg(stripped: Buffer) {
     // Note: Changes here should update _stripped_real_length
     if (stripped.length < 3 || stripped[0] !== 1) {
         return stripped
@@ -411,7 +413,7 @@ function getInputLocation(location) {
 /**
  *  Similar to :meth:`get_input_peer`, but for photos
  */
-function getInputPhoto(photo: any): Api.TypePhoto | Api.InputPhotoEmpty {
+export function getInputPhoto(photo: any): Api.TypePhoto | Api.InputPhotoEmpty {
     if (photo.SUBCLASS_OF_ID === undefined) {
         _raiseCastFail(photo, "InputPhoto");
     }
@@ -460,7 +462,7 @@ function getInputPhoto(photo: any): Api.TypePhoto | Api.InputPhotoEmpty {
  *  Similar to :meth:`get_input_peer`, but for documents
  */
 
-function getInputDocument(document: any): Api.InputDocument | Api.InputDocumentEmpty {
+export function getInputDocument(document: any): Api.InputDocument | Api.InputDocumentEmpty {
     if (document.SUBCLASS_OF_ID === undefined) {
         _raiseCastFail(document, "InputDocument");
     }
@@ -503,7 +505,7 @@ interface GetAttributesParams {
 /**
  *  Returns `True` if the file has an audio mime type.
  */
-function isAudio(file: any): boolean {
+export function isAudio(file: any): boolean {
     const ext = _getExtension(file);
     if (!ext) {
         const metadata = _getMetadata(file);
@@ -518,7 +520,7 @@ function isAudio(file: any): boolean {
     }
 }
 
-function getExtension(media: any): string {
+export function getExtension(media: any): string {
     // Photos are always compressed as .jpg by Telegram
 
     try {
@@ -588,9 +590,9 @@ function isVideo(file: any): boolean {
  Get a list of attributes for the given file and
  the mime type as a tuple ([attribute], mime_type).
  */
-function getAttributes(file: any, {attributes = null, mimeType = undefined, forceDocument = false, voiceNote = false, videoNote = false, supportsStreaming = false, thumb = null}: GetAttributesParams) {
+export function getAttributes(file: File | CustomFile | TypeInputFile, {attributes = null, mimeType = undefined, forceDocument = false, voiceNote = false, videoNote = false, supportsStreaming = false, thumb = null}: GetAttributesParams) {
 
-    const name: string = (typeof file === "string") ? file : file.name || "unnamed";
+    const name: string = file.name || "unnamed";
     if (mimeType === undefined) {
         mimeType = mime.lookup(name) || "application/octet-stream";
     }
@@ -667,7 +669,7 @@ function getAttributes(file: any, {attributes = null, mimeType = undefined, forc
     }
 
     return {
-        attrs: Array.from(attrObj.values()),
+        attrs: Array.from(attrObj.values()) as Api.TypeDocumentAttribute[],
         mimeType: mimeType,
     };
 }
@@ -970,7 +972,7 @@ export function getPeerId(peer: EntityLike, addMark = true): number {
         }
 
         return addMark ? -(peer.chatId) : peer.chatId
-    } else if (typeof peer=="object" && "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]

+ 39 - 7
gramjs/client/TelegramClient.ts

@@ -11,13 +11,14 @@ 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 {sanitizeParseMode} from "../Utils";
+import {MarkdownParser} from "../extensions/markdown";
 import type  {EventBuilder} from "../events/common";
-import  {MTProtoSender, UpdateConnectionState} from "../network";
+import {MTProtoSender, UpdateConnectionState} from "../network";
 
 import {LAYER} from "../tl/AllTLObjects";
 import {IS_NODE} from "../Helpers";
+import {DownloadMediaInterface} from "./downloads";
 
 export class TelegramClient extends TelegramBaseClient {
 
@@ -84,15 +85,39 @@ export class TelegramClient extends TelegramBaseClient {
 
     //region download
     downloadFile(
-        inputLocation: Api.InputFileLocation,
+        inputLocation: Api.TypeInputFileLocation,
         fileParams: downloadMethods.DownloadFileParams,
     ) {
-        return downloadMethods.downloads(this,
+        return downloadMethods.downloadFile(this,
             inputLocation,
             fileParams,
         )
     }
 
+    _downloadPhoto(photo: Api.MessageMediaPhoto | Api.Photo, args: DownloadMediaInterface) {
+        return downloadMethods._downloadPhoto(this, photo, args);
+    }
+
+    _downloadCachedPhotoSize(size: Api.PhotoCachedSize | Api.PhotoStrippedSize) {
+        return downloadMethods._downloadCachedPhotoSize(this, size);
+    }
+
+    _downloadDocument(media: Api.MessageMediaDocument | Api.Document, args: DownloadMediaInterface) {
+        return downloadMethods._downloadDocument(this, media, args);
+    }
+
+    _downloadContact(contact: Api.MessageMediaContact, args: DownloadMediaInterface) {
+        return downloadMethods._downloadContact(this, contact, args);
+    }
+
+    _downloadWebDocument(webDocument: Api.WebDocument | Api.WebDocumentNoProxy, args: DownloadMediaInterface) {
+        return downloadMethods._downloadWebDocument(this, webDocument, args);
+    }
+
+    downloadMedia(messageOrMedia: Api.Message | Api.TypeMessageMedia, args: DownloadMediaInterface) {
+        return downloadMethods.downloadMedia(this, messageOrMedia, args);
+    }
+
     //endregion
 
     //region message parse
@@ -100,7 +125,7 @@ export class TelegramClient extends TelegramBaseClient {
         return this._parseMode || MarkdownParser;
     }
 
-    setParseMode(mode:string | parseMethods.ParseInterface) {
+    setParseMode(mode: string | parseMethods.ParseInterface) {
         this._parseMode = sanitizeParseMode(mode);
     }
 
@@ -134,7 +159,6 @@ export class TelegramClient extends TelegramBaseClient {
     }
 
     addEventHandler(callback: CallableFunction, event?: EventBuilder) {
-        console.log("adding handler");
         return updateMethods.addEventHandler(this, callback, event);
 
     }
@@ -177,6 +201,10 @@ export class TelegramClient extends TelegramBaseClient {
         return uploadMethods.uploadFile(this, fileParams);
     }
 
+    sendFile(entity: EntityLike, params: uploadMethods.SendFileInterface) {
+        return uploadMethods.sendFile(this, entity, params);
+    }
+
     // endregion
 
     //region user methods
@@ -225,6 +253,7 @@ export class TelegramClient extends TelegramBaseClient {
     _getInputNotify(notify: any) {
         return userMethods._getInputNotify(this, notify);
     }
+
     //endregion
 
     //region base methods
@@ -375,6 +404,7 @@ export class TelegramClient extends TelegramBaseClient {
         }
         throw new Error(`Cannot find the DC with the ID of ${dcId}`)
     }
+
     removeSender(dcId: number) {
         delete this._borrowedSenderPromises[dcId]
     }
@@ -395,4 +425,6 @@ export class TelegramClient extends TelegramBaseClient {
     }
 
     // endregion
+
+
 }

+ 11 - 5
gramjs/client/chats.ts

@@ -75,7 +75,7 @@ class _ChatAction {
     async stop() {
         this._running = false;
         if (this.autoCancel) {
-            await this._client.invoke(new  Api.messages.SetTyping({
+            await this._client.invoke(new Api.messages.SetTyping({
                 peer: this._chat,
                 action: new Api.SendMessageCancelAction()
             }));
@@ -100,11 +100,17 @@ class _ChatAction {
     }
 }
 
+interface ParticipantsIterInterface {
+    entity: EntityLike,
+    filter: any,
+    search?: string
+}
+
 class _ParticipantsIter extends RequestIter {
     private filterEntity: ((entity: Entity) => boolean) | undefined;
     private requests?: Api.channels.GetParticipants[];
 
-    async _init(entity: EntityLike, filter: any, search?: string): Promise<boolean | void> {
+    async _init({entity, filter, search}: ParticipantsIterInterface): Promise<boolean | void> {
         if (filter.constructor === Function) {
             if ([Api.ChannelParticipantsBanned, Api.ChannelParticipantsKicked, Api.ChannelParticipantsSearch, Api.ChannelParticipantsContacts].includes(filter)) {
                 filter = new filter({
@@ -148,8 +154,8 @@ class _ParticipantsIter extends RequestIter {
                 hash: 0,
             }))
         } else if (ty == helpers._EntityType.CHAT) {
-            if (!("chatId" in entity)){
-                throw new Error("Found chat without id "+JSON.stringify(entity));
+            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
@@ -315,5 +321,5 @@ class _AdminLogIter extends RequestIter {
     }
 }
 
-    // TODO implement
+// TODO implement
 

+ 165 - 8
gramjs/client/downloads.ts

@@ -1,7 +1,8 @@
-import { Api } from '../tl';
+import {Api} from '../tl';
 import type {TelegramClient} from './TelegramClient';
-import { getAppropriatedPartSize } from '../Utils';
-import { sleep } from '../Helpers';
+import {getAppropriatedPartSize, strippedPhotoToJpg} from '../Utils';
+import {sleep} from '../Helpers';
+
 
 export interface progressCallback {
     (
@@ -28,19 +29,22 @@ interface Deferred {
     resolve: (value?: any) => void;
 }
 
+// All types
+const sizeTypes = ['w', 'y', 'd', 'x', 'c', 'm', 'b', 'a', 's'];
+
 // 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 downloads(
+export async function downloadFile(
     client: TelegramClient,
-    inputLocation: Api.InputFileLocation,
+    inputLocation: Api.TypeInputFileLocation,
     fileParams: DownloadFileParams,
 ) {
-    let { partSizeKb, fileSize, workers = 1, end } = fileParams;
-    const { dcId, progressCallback, start = 0 } = fileParams;
+    let {partSizeKb, fileSize, workers = 1, end} = fileParams;
+    const {dcId, progressCallback, start = 0} = fileParams;
 
     end = end && end < fileSize ? end : fileSize - 1;
 
@@ -102,7 +106,6 @@ export async function downloads(
             await foreman.releaseWorker();
             break;
         }
-
         promises.push((async () => {
             try {
                 const result = await Promise.race([
@@ -188,3 +191,157 @@ function createDeferred(): Deferred {
         resolve: resolve!,
     };
 }
+
+export interface DownloadMediaInterface {
+    sizeType?: string,
+    /** where to start downloading **/
+    start?: number,
+    /** where to stop downloading **/
+    end?: number,
+    progressCallback?: progressCallback,
+    workers?: number,
+
+}
+
+export async function downloadMedia(client: TelegramClient, messageOrMedia: Api.Message | Api.TypeMessageMedia, args: DownloadMediaInterface): Promise<Buffer> {
+    let date;
+    let media;
+    if (messageOrMedia instanceof Api.Message) {
+        media = messageOrMedia.media
+    } else {
+        media = messageOrMedia
+    }
+    if (typeof media == 'string') {
+        throw new Error('not implemented')
+    }
+
+    if (media instanceof Api.MessageMediaWebPage) {
+        if (media.webpage instanceof Api.WebPage) {
+            media = media.webpage.document || media.webpage.photo
+        }
+    }
+    if (media instanceof Api.MessageMediaPhoto || media instanceof Api.Photo) {
+        return client._downloadPhoto(media, args);
+    } else if (media instanceof Api.MessageMediaDocument || media instanceof Api.Document) {
+        return client._downloadDocument(media, args)
+    } else if (media instanceof Api.MessageMediaContact) {
+        return client._downloadContact(media, args)
+    } else if (media instanceof Api.WebDocument || media instanceof Api.WebDocumentNoProxy) {
+        return client._downloadWebDocument(media, args)
+    } else {
+        return Buffer.alloc(0);
+    }
+}
+
+export async function _downloadDocument(client: TelegramClient, doc: Api.MessageMediaDocument | Api.Document, args: DownloadMediaInterface): Promise<Buffer> {
+    if (doc instanceof Api.MessageMediaDocument) {
+        if (!doc.document) {
+            return Buffer.alloc(0);
+        }
+
+        doc = doc.document
+
+    }
+    if (!(doc instanceof Api.Document)) {
+        return Buffer.alloc(0);
+    }
+
+    let size = undefined;
+    if (args.sizeType) {
+        size = doc.thumbs ? pickFileSize(doc.thumbs, args.sizeType) : undefined;
+        if (!size && doc.mimeType.startsWith('video/')) {
+            return Buffer.alloc(0);
+        }
+
+        if (size && (size instanceof Api.PhotoCachedSize || size instanceof Api.PhotoStrippedSize)) {
+            return client._downloadCachedPhotoSize(size)
+        }
+    }
+    return client.downloadFile(
+        new Api.InputDocumentFileLocation({
+            id: doc.id,
+            accessHash: doc.accessHash,
+            fileReference: doc.fileReference,
+            thumbSize: size ? size.type : '',
+        }),
+        {
+            fileSize: (size && !(size instanceof Api.PhotoSizeEmpty)) ? size.size : doc.size,
+            progressCallback: args.progressCallback,
+            start: args.start,
+            end: args.end,
+            dcId: doc.dcId,
+            workers: args.workers,
+        },
+    )
+
+}
+
+export async function _downloadContact(client: TelegramClient, media: Api.MessageMediaContact, args: DownloadMediaInterface): Promise<Buffer> {
+    throw new Error('not implemented')
+}
+
+export async function _downloadWebDocument(client: TelegramClient, media: Api.WebDocument | Api.WebDocumentNoProxy, args: DownloadMediaInterface): Promise<Buffer> {
+    throw new Error('not implemented')
+}
+
+function pickFileSize(sizes: Api.TypePhotoSize[], sizeType: string) {
+    if (!sizeType || !sizes || !sizes.length) {
+        return undefined;
+    }
+    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 && !(size instanceof Api.PhotoSizeProgressive || size instanceof Api.PhotoPathSize)) {
+            return size
+        }
+    }
+    return undefined;
+
+}
+
+export function _downloadCachedPhotoSize(client: TelegramClient, size: Api.PhotoCachedSize | Api.PhotoStrippedSize) {
+    // No need to download anything, simply write the bytes
+    let data;
+    if (size instanceof Api.PhotoStrippedSize) {
+        data = strippedPhotoToJpg(size.bytes)
+    } else {
+        data = size.bytes
+    }
+    return data
+}
+
+export async function _downloadPhoto(client: TelegramClient, photo: Api.MessageMediaPhoto | Api.Photo, args: DownloadMediaInterface): Promise<Buffer> {
+    if (photo instanceof Api.MessageMediaPhoto) {
+        if (photo.photo instanceof Api.PhotoEmpty || !photo.photo) {
+            return Buffer.alloc(0);
+        }
+        photo = photo.photo
+
+    }
+    if (!(photo instanceof Api.Photo)) {
+        return Buffer.alloc(0);
+    }
+    const size = pickFileSize(photo.sizes, args.sizeType || sizeTypes[0]);
+    if (!size || (size instanceof Api.PhotoSizeEmpty)) {
+        return Buffer.alloc(0);
+    }
+
+    if (size instanceof Api.PhotoCachedSize || size instanceof Api.PhotoStrippedSize) {
+        return client._downloadCachedPhotoSize(size)
+    }
+    return client.downloadFile(
+        new Api.InputPhotoFileLocation({
+            id: photo.id,
+            accessHash: photo.accessHash,
+            fileReference: photo.fileReference,
+            thumbSize: size.type,
+        }),
+        {
+            dcId: photo.dcId,
+            fileSize: size.size,
+            progressCallback: args.progressCallback,
+        },
+    )
+
+}

+ 17 - 12
gramjs/client/messages.ts

@@ -2,7 +2,7 @@ import {Api} from "../tl";
 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 {_EntityType, _entityType, TotalList} from "../Helpers";
 import {getMessageId, getPeerId, isArrayLike} from "../Utils";
 import type {TelegramClient} from "../";
 import {utils} from "../";
@@ -11,6 +11,7 @@ const _MAX_CHUNK_SIZE = 100;
 
 
 interface MessageIterParams {
+    entity: EntityLike;
     offsetId: number;
     minId: number;
     maxId: number;
@@ -25,7 +26,7 @@ interface MessageIterParams {
 export class _MessagesIter extends RequestIter {
     private entity?: Api.TypeInputPeer;
 
-    async _init(entity: EntityLike, {offsetId, minId, maxId, fromUser, offsetDate, addOffset, filter, search, replyTo}: MessageIterParams) {
+    async _init({entity, offsetId, minId, maxId, fromUser, offsetDate, addOffset, filter, search, replyTo}: MessageIterParams) {
         if (entity) {
             this.entity = await this.client.getInputEntity(entity);
         } else {
@@ -225,8 +226,13 @@ export class _MessagesIter extends RequestIter {
     }
 }
 
+interface IDsIterInterface {
+    entity: EntityLike,
+    ids: MessageLike[]
+}
+
 export class _IDsIter extends RequestIter {
-    async _init(entity: EntityLike, ids: MessageLike[]) {
+    async _init({entity, ids}: IDsIterInterface) {
         this.total = ids.length;
         this._ids = this.reverse ? ids.reverse() : ids;
         this._offset = 0;
@@ -327,16 +333,15 @@ export function iterMessages(client: TelegramClient, entity: EntityLike, {limit,
         if (typeof ids == 'number') {
             ids = [ids]
         }
-        // @ts-ignore
-        return new _IDsIter(this, ids.length, {
+        return new _IDsIter(client, ids.length, {
             reverse: reverse,
             waitTime: waitTime
         }, {
-            entity: entity
+            entity: entity,
+            ids: ids
         });
     }
-    // @ts-ignore
-    return new _MessagesIter(client, limit, {
+    return new _MessagesIter(client, limit || 1, {
         waitTime: waitTime,
         reverse: reverse
     }, {
@@ -353,7 +358,7 @@ export function iterMessages(client: TelegramClient, entity: EntityLike, {limit,
     })
 }
 
-export async function getMessages(client: TelegramClient, entity: EntityLike, params: IterMessagesParams) {
+export async function getMessages(client: TelegramClient, entity: EntityLike, params: IterMessagesParams):Promise<TotalList<Api.Message>> {
     if (Object.keys(params).length == 1 && params.limit === undefined) {
         if (params.minId === undefined && params.maxId === undefined) {
             params.limit = undefined;
@@ -366,12 +371,12 @@ export async function getMessages(client: TelegramClient, entity: EntityLike, pa
     const ids = params.ids;
     if (ids && !isArrayLike(ids)) {
         for await (const message of it) {
-            return message;
+            return [message];
         }
-        return;
+        return [];
 
     }
-    return await it.collect();
+    return await it.collect() as TotalList<Api.Message>;
 }
 
 // region Message

+ 0 - 2
gramjs/client/updates.ts

@@ -20,7 +20,6 @@ export function addEventHandler(client: TelegramClient, callback: CallableFuncti
         const raw = require("../events/Raw");
         event = new raw({}) as Raw;
     }
-    console.log("event tpo add is", event);
     client._eventBuilders.push([event, callback])
 }
 
@@ -81,7 +80,6 @@ export async function _dispatchUpdate(client: TelegramClient, args: { update: Up
         if (!builder.resolved){
             await builder.resolve(client);
         }
-        console.log("builder is", builder);
         let event = args.update;
         if (event) {
             if (!client._selfInputPeer) {

+ 98 - 11
gramjs/client/uploads.ts

@@ -1,10 +1,11 @@
-import { Api } from '../tl';
+import {Api} from '../tl';
 
 import type {TelegramClient} from './TelegramClient';
-// @ts-ignore
-import { generateRandomBytes, readBigIntFromBuffer, sleep } from '../Helpers';
-// @ts-ignore
-import { getAppropriatedPartSize } from '../Utils';
+import {generateRandomBytes, readBigIntFromBuffer, sleep} from '../Helpers';
+import {getAppropriatedPartSize, getAttributes} from '../Utils';
+import type {EntityLike, FileLike, MessageIDLike} from "../define";
+import path from "path";
+import fs from "fs";
 
 interface OnProgress {
     // Float between 0 and 1.
@@ -14,11 +15,23 @@ interface OnProgress {
 }
 
 export interface UploadFileParams {
-    file: File;
+    file: File | CustomFile;
     workers: number;
     onProgress?: OnProgress;
 }
 
+export class CustomFile {
+    name: string;
+    size: number;
+    path: string;
+
+    constructor(name: string, size: number, path: string) {
+        this.name = name;
+        this.size = size;
+        this.path = path;
+    }
+}
+
 const KB_TO_BYTES = 1024;
 const LARGE_FILE_THRESHOLD = 10 * 1024 * 1024;
 const UPLOAD_TIMEOUT = 15 * 1000;
@@ -27,10 +40,10 @@ export async function uploadFile(
     client: TelegramClient,
     fileParams: UploadFileParams,
 ): Promise<Api.InputFile | Api.InputFileBig> {
-    const { file, onProgress } = fileParams;
-    let { workers } = fileParams;
+    const {file, onProgress} = fileParams;
+    let {workers} = fileParams;
 
-    const { name, size } = file;
+    const {name, size} = file;
     const fileId = readBigIntFromBuffer(generateRandomBytes(8), true, true);
     const isLarge = size > LARGE_FILE_THRESHOLD;
 
@@ -120,6 +133,80 @@ export async function uploadFile(
         });
 }
 
-function fileToBuffer(file: File) {
-    return new Response(file).arrayBuffer();
+export interface SendFileInterface {
+    file: string | CustomFile | File,
+    caption?: string,
+    forceDocument?: boolean,
+    fileSize?: number,
+    progressCallback?: OnProgress,
+    replyTo?: MessageIDLike,
+    attributes?: Api.TypeDocumentAttribute[],
+    thumb?: FileLike,
+    voiceNote?: boolean,
+    videoNote?: boolean,
+    supportStreaming?: boolean,
+}
+
+export async function sendFile(client: TelegramClient, entity: EntityLike, {file, caption, forceDocument, fileSize, progressCallback, replyTo, attributes, thumb, voiceNote, videoNote, supportStreaming}: SendFileInterface) {
+    if (!file) {
+        throw new Error("You need to specify a file");
+    }
+    if (!caption) {
+        caption = ""
+    }
+    if (typeof file == "string") {
+        file = new CustomFile(path.basename(file), fs.statSync(file).size, file);
+    }
+    const media = await client.uploadFile({
+        file: file,
+        workers: 1,
+        onProgress: progressCallback,
+    });
+    if (!attributes) {
+        attributes = [];
+    }
+    let mimeType = "application/octet-stream";
+    if (file instanceof CustomFile) {
+        const result = (getAttributes(file, {
+            attributes: attributes,
+            forceDocument: forceDocument,
+            voiceNote: voiceNote,
+            videoNote: videoNote,
+            supportsStreaming: supportStreaming,
+            thumb: thumb
+        }));
+        mimeType = result.mimeType;
+        attributes.push(...result.attrs);
+    }
+    let toSend;
+    if (mimeType.startsWith("photo/")) {
+        toSend = new Api.InputMediaUploadedPhoto({
+            file: media,
+        })
+    } else {
+        toSend = new Api.InputMediaUploadedDocument({
+            file: media,
+            mimeType: mimeType,
+            attributes: attributes,
+            forceFile: forceDocument,
+        })
+    }
+    const result = await client.invoke(new Api.messages.SendMedia({
+        peer: entity,
+        media: toSend,
+        replyToMsgId: replyTo,
+        message: caption,
+    }));
+    // TODO get result
+    return result;
+}
+
+function fileToBuffer(file: File | CustomFile) {
+    if (typeof File !== 'undefined' && file instanceof File) {
+        return new Response(file).arrayBuffer();
+    } else if (file instanceof CustomFile) {
+        return fs.readFileSync(file.path);
+    } else {
+        throw new Error("Could not create buffer from file " + file);
+    }
 }

+ 0 - 3
gramjs/client/users.ts

@@ -125,9 +125,6 @@ export async function getEntity(client: TelegramClient, entity: any): Promise<En
     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);
 
     if (users.length) {
         users = await client.invoke(new Api.users.GetUsers({

+ 5 - 1
gramjs/define.d.ts

@@ -1,5 +1,6 @@
 import type {Button} from "./tl/custom/button";
 import {Api} from "./tl";
+import type {CustomFile} from "./client/uploads";
 
 type ValueOf<T> = T[keyof T];
 type Phone = string;
@@ -25,7 +26,10 @@ type FileLike =
     Buffer |
     Api.TypeMessageMedia |
     Api.TypeInputFile |
-    Api.TypeInputFileLocation
+    Api.TypeInputFileLocation |
+    File |
+    CustomFile
+
 type ProgressCallback = (total: number, downloaded: number) => void;
 type ButtonLike = Api.TypeKeyboardButton | Button;
 

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

@@ -177,7 +177,7 @@ class PacketCodec {
         // Override
     }
 
-    async readPacket(reader: BinaryReader) :Promise<Buffer>{
+    async readPacket(reader: PromisedNetSockets | PromisedWebSockets) :Promise<Buffer>{
         // override
         throw new Error('Not Implemented')
     }

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

@@ -1,6 +1,6 @@
 import {readBufferFromBigInt} from '../../Helpers';
 import {Connection, PacketCodec} from './Connection';
-import type {BinaryReader} from "../../extensions";
+import type {PromisedNetSockets, PromisedWebSockets} from "../../extensions";
 
 import bigInt from "big-integer";
 
@@ -29,7 +29,7 @@ export class AbridgedPacketCodec extends PacketCodec {
         return Buffer.concat([temp, data])
     }
 
-    async readPacket(reader: BinaryReader): Promise<Buffer> {
+    async readPacket(reader: PromisedNetSockets | PromisedWebSockets): Promise<Buffer> {
         const readData = await reader.read(1);
         let length = readData[0];
         if (length >= 127) {

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

@@ -1,7 +1,7 @@
 import {Connection, PacketCodec} from './Connection';
 import {crc32} from '../../Helpers';
 import {InvalidChecksumError} from '../../errors';
-import type {BinaryReader} from "../../extensions";
+import type {PromisedNetSockets, PromisedWebSockets} from "../../extensions";
 
 class FullPacketCodec extends PacketCodec {
     private _sendCounter: number;
@@ -30,15 +30,15 @@ class FullPacketCodec extends PacketCodec {
      * @param reader {PromisedWebSockets}
      * @returns {Promise<*>}
      */
-    async readPacket(reader: BinaryReader): Promise<Buffer> {
-        const packetLenSeq = await reader.read(8); // 4 and 4
+    async readPacket(reader: PromisedNetSockets | PromisedWebSockets): Promise<Buffer> {
+        const packetLenSeq = await reader.readExactly(8); // 4 and 4
         // process.exit(0);
         if (packetLenSeq === undefined) {
             // Return empty buffer in case of issue
             return Buffer.alloc(0);
         }
         const packetLen = packetLenSeq.readInt32LE(0);
-        let body = await reader.read(packetLen - 8);
+        let body = await reader.readExactly(packetLen - 8);
         const checksum = body.slice(-4).readUInt32LE(0);
         body = body.slice(0, -4);
 

+ 6 - 6
gramjs/requestIter.ts

@@ -6,6 +6,7 @@ interface BaseRequestIterInterface {
     reverse?: boolean,
     waitTime?: number,
 }
+
 export class RequestIter implements AsyncIterable<any> {
 
     public client: TelegramClient;
@@ -17,6 +18,8 @@ export class RequestIter implements AsyncIterable<any> {
     private index: number;
     protected total: number | undefined;
     private lastLoad: number;
+    kwargs: {};
+
     [key: string]: any;
 
     constructor(client: TelegramClient, limit: number, params: BaseRequestIterInterface = {}, args = {}) {
@@ -26,16 +29,14 @@ export class RequestIter implements AsyncIterable<any> {
         this.limit = Math.max(!limit ? Number.MAX_SAFE_INTEGER : limit, 0);
         this.left = this.limit;
         this.buffer = undefined;
-        for (const name in args) {
-            this[name as any] = (args as any)[name];
-        }
+        this.kwargs = args;
         this.index = 0;
         this.total = undefined;
         this.lastLoad = 0
     }
 
 
-    async _init(...args: any): Promise<boolean | void> {
+    async _init(kwargs: any): Promise<boolean | void> {
         // for overload
     }
 
@@ -49,8 +50,7 @@ export class RequestIter implements AsyncIterable<any> {
 
                 if (this.buffer == undefined) {
                     this.buffer = [];
-                    // @ts-ignore
-                    if (await this._init(this.args)) {
+                    if (await this._init(this.kwargs)) {
                         this.left = this.buffer.length;
                     }
                 }

+ 1 - 1
package.json

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