ソースを参照

Add friendly method for forwarding messages

painor 4 年 前
コミット
e2fafcdaa3
7 ファイル変更260 行追加179 行削除
  1. 14 0
      gramjs/Helpers.ts
  2. 1 1
      gramjs/Version.ts
  3. 4 0
      gramjs/client/TelegramClient.ts
  4. 96 32
      gramjs/client/messages.ts
  5. 0 10
      gramjs/index.d.ts
  6. 3 2
      gramjs/index.ts
  7. 142 134
      gramjs/tl/api.js

+ 14 - 0
gramjs/Helpers.ts

@@ -2,6 +2,7 @@ import { isNode } from "browser-or-node";
 import bigInt from "big-integer";
 import type { EntityLike } from "./define";
 import type { Api } from "./tl";
+import exp from "constants";
 
 export const IS_NODE = isNode;
 const crypto = require(isNode ? "crypto" : "./crypto/crypto");
@@ -38,6 +39,19 @@ export function generateRandomBigInt() {
 export function escapeRegex(string: string) {
     return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
 }
+export function groupBy(list: any[], keyGetter: Function) {
+    const map = new Map();
+    list.forEach((item) => {
+        const key = keyGetter(item);
+        const collection = map.get(key);
+        if (!collection) {
+            map.set(key, [item]);
+        } else {
+            collection.push(item);
+        }
+    });
+    return map;
+}
 
 /**
  * Helper to find if a given object is an array (or similar)

+ 1 - 1
gramjs/Version.ts

@@ -1 +1 @@
-export const version = "1.6.10";
+export const version = "1.7.2";

+ 4 - 0
gramjs/client/TelegramClient.ts

@@ -230,6 +230,10 @@ export class TelegramClient extends TelegramBaseClient {
         return messageMethods.sendMessage(this, entity, params);
     }
 
+    forwardMessages(entity: EntityLike, params: messageMethods.ForwardMessagesParams) {
+        return messageMethods.forwardMessages(this, entity, params);
+    }
+
     editMessage(entity: EntityLike, params: messageMethods.EditMessageParams) {
         return messageMethods.editMessage(this, entity, params);
     }

+ 96 - 32
gramjs/client/messages.ts

@@ -6,13 +6,15 @@ import type {
     FileLike,
     MarkupLike,
     MessageIDLike,
-    MessageLike,
+    MessageLike
 } from "../define";
 import { RequestIter } from "../requestIter";
-import { _EntityType, _entityType, TotalList, isArrayLike } from "../Helpers";
+import { _EntityType, _entityType, TotalList, isArrayLike, groupBy } from "../Helpers";
 import { getMessageId, getPeerId } from "../Utils";
 import type { TelegramClient } from "../";
 import { utils } from "../";
+import { cli } from "webpack";
+
 
 const _MAX_CHUNK_SIZE = 100;
 
@@ -43,17 +45,17 @@ export class _MessagesIter extends RequestIter {
     lastId?: number;
 
     async _init({
-        entity,
-        offsetId,
-        minId,
-        maxId,
-        fromUser,
-        offsetDate,
-        addOffset,
-        filter,
-        search,
-        replyTo,
-    }: MessageIterParams) {
+                    entity,
+                    offsetId,
+                    minId,
+                    maxId,
+                    fromUser,
+                    offsetDate,
+                    addOffset,
+                    filter,
+                    search,
+                    replyTo
+                }: MessageIterParams) {
         if (entity) {
             this.entity = await this.client.getInputEntity(entity);
         } else {
@@ -110,7 +112,7 @@ export class _MessagesIter extends RequestIter {
                 offsetRate: undefined,
                 offsetPeer: new Api.InputPeerEmpty(),
                 offsetId: offsetId,
-                limit: 1,
+                limit: 1
             });
         } else if (replyTo !== undefined) {
             this.request = new Api.messages.GetReplies({
@@ -122,7 +124,7 @@ export class _MessagesIter extends RequestIter {
                 limit: 0,
                 maxId: 0,
                 minId: 0,
-                hash: 0,
+                hash: 0
             });
         } else if (
             search !== undefined ||
@@ -147,7 +149,7 @@ export class _MessagesIter extends RequestIter {
                 maxId: 0,
                 minId: 0,
                 hash: 0,
-                fromId: fromUser,
+                fromId: fromUser
             });
             if (
                 filter instanceof Api.InputMessagesFilterEmpty &&
@@ -157,7 +159,7 @@ export class _MessagesIter extends RequestIter {
             ) {
                 for await (const m of this.client.iterMessages(this.entity, {
                     limit: 1,
-                    offsetDate: offsetDate,
+                    offsetDate: offsetDate
                 })) {
                     this.request.offsetId = m.id + 1;
                 }
@@ -171,7 +173,7 @@ export class _MessagesIter extends RequestIter {
                 minId: 0,
                 maxId: 0,
                 addOffset: addOffset,
-                hash: 0,
+                hash: 0
             });
         }
         if (this.limit <= 0) {
@@ -241,7 +243,8 @@ export class _MessagesIter extends RequestIter {
             try {
                 // if this fails it shouldn't be a big problem
                 message._finishInit(this.client, entities, this.entity);
-            } catch (e) {}
+            } catch (e) {
+            }
             message._entities = entities;
             this.buffer?.push(message);
         }
@@ -311,6 +314,7 @@ export class _IDsIter extends RequestIter {
     _offset?: number;
     _ty: number | undefined;
     private _entity: Api.TypeInputPeer | undefined;
+
     async _init({ entity, ids }: IDsIterInterface) {
         this.total = ids.length;
         this._ids = this.reverse ? ids.reverse() : ids;
@@ -346,13 +350,13 @@ export class _IDsIter extends RequestIter {
                 r = await this.client.invoke(
                     new Api.channels.GetMessages({
                         channel: this._entity,
-                        id: ids,
+                        id: ids
                     })
                 );
             } catch (e) {
                 if (e.message == "MESSAGE_IDS_EMPTY") {
                     r = new Api.messages.MessagesNotModified({
-                        count: ids.length,
+                        count: ids.length
                     });
                 } else {
                     throw e;
@@ -361,7 +365,7 @@ export class _IDsIter extends RequestIter {
         } else {
             r = await this.client.invoke(
                 new Api.messages.GetMessages({
-                    id: ids,
+                    id: ids
                 })
             );
             if (this._entity) {
@@ -430,6 +434,13 @@ export interface SendMessageParams {
     schedule?: DateLike;
 }
 
+export interface ForwardMessagesParams {
+    messages: MessageIDLike[];
+    fromPeer: EntityLike;
+    silent?: boolean;
+    schedule?: DateLike;
+}
+
 export interface EditMessageParams {
     message: Api.Message | number;
     text: string;
@@ -460,7 +471,7 @@ export function iterMessages(
         waitTime,
         ids,
         reverse = false,
-        replyTo,
+        replyTo
     }: IterMessagesParams
 ) {
     if (ids) {
@@ -472,11 +483,11 @@ export function iterMessages(
             ids.length,
             {
                 reverse: reverse,
-                waitTime: waitTime,
+                waitTime: waitTime
             },
             {
                 entity: entity,
-                ids: ids,
+                ids: ids
             }
         );
     }
@@ -485,7 +496,7 @@ export function iterMessages(
         limit || 1,
         {
             waitTime: waitTime,
-            reverse: reverse,
+            reverse: reverse
         },
         {
             entity: entity,
@@ -497,7 +508,7 @@ export function iterMessages(
             addOffset: addOffset,
             filter: filter,
             search: search,
-            replyTo: replyTo,
+            replyTo: replyTo
         }
     );
 }
@@ -542,7 +553,7 @@ export async function sendMessage(
         clearDraft,
         buttons,
         silent,
-        schedule,
+        schedule
     }: SendMessageParams
 ) {
     if (file) {
@@ -586,7 +597,7 @@ export async function sendMessage(
             entities: message.entities,
             clearDraft: clearDraft,
             noWebpage: !(message.media instanceof Api.MessageMediaWebPage),
-            scheduleDate: schedule,
+            scheduleDate: schedule
         });
         message = message.message;
     } else {
@@ -610,7 +621,7 @@ export async function sendMessage(
             clearDraft: clearDraft,
             silent: silent,
             replyMarkup: client.buildReplyMarkup(buttons),
-            scheduleDate: schedule,
+            scheduleDate: schedule
         });
     }
     const result = await client.invoke(request);
@@ -618,6 +629,59 @@ export async function sendMessage(
     //return client._getResponseMessage(request, result, entity);
 }
 
+export async function forwardMessages(client: TelegramClient,
+                                      entity: EntityLike,
+                                      {
+                                          messages,
+                                          fromPeer,
+                                          silent,
+                                          schedule
+                                      }: ForwardMessagesParams) {
+    entity = await client.getInputEntity(entity);
+    let fromPeerId: number | undefined;
+    if (fromPeer) {
+        fromPeer = await client.getInputEntity(fromPeer);
+        fromPeerId = await client.getPeerId(fromPeer);
+    }
+    const getKey = (m: number | Message) => {
+        if (typeof m == "number") {
+            if (fromPeerId !== undefined) {
+                return fromPeerId;
+            }
+            throw new Error("fromPeer must be given if integer IDs are used");
+        } else if (m instanceof Api.Message) {
+            return m.chatId;
+        } else {
+            throw new Error(`Cannot forward ${m}`);
+        }
+    };
+    const sent = [];
+    for (let [chatId, chunk] of groupBy(messages, getKey) as Map<number, Message[] | number[]>) {
+        let chat;
+        let numbers: number[] = [];
+        if (typeof chunk[0] == "number") {
+            chat = fromPeer;
+            numbers = chunk as number[];
+        } else {
+            chat = await chunk[0].getInputChat();
+            numbers = (chunk as Message[]).map((m: Message) => m.id);
+        }
+        chunk.push();
+        const request = new Api.messages.ForwardMessages({
+            fromPeer: chat,
+            id: numbers,
+            toPeer: entity,
+            silent: silent,
+            scheduleDate: schedule
+        });
+        const result = await client.invoke(request);
+        //sent.push(client._getResponseMessage(req, result, entity))
+        sent.push(result);
+    }
+    return sent;
+}
+
+
 /**
  * Used to edit a message by changing it's text or media
  * message refers to the message to be edited not what to edit
@@ -635,7 +699,7 @@ export async function editMessage(
         file,
         forceDocument,
         buttons,
-        schedule,
+        schedule
     }: EditMessageParams
 ) {
     entity = await client.getInputEntity(entity);
@@ -654,7 +718,7 @@ export async function editMessage(
             entities: formattingEntities,
             //media: no media for now,
             replyMarkup: client.buildReplyMarkup(buttons),
-            scheduleDate: schedule,
+            scheduleDate: schedule
         })
     );
     return msg;

+ 0 - 10
gramjs/index.d.ts

@@ -1,10 +0,0 @@
-export * as Api from "./tl/api";
-export { default as TelegramClient } from "./client/TelegramClient";
-export { default as connection } from "./network";
-export { default as tl } from "./tl";
-export { default as version } from "./Version";
-export { default as events } from "./events";
-export { default as utils } from "./Utils";
-export { default as errors } from "./errors";
-export { default as sessions } from "./sessions";
-export { default as helpers } from "./Helpers";

+ 3 - 2
gramjs/index.ts

@@ -2,11 +2,12 @@ export { Api } from "./tl";
 export { TelegramClient } from "./client/TelegramClient";
 export { Connection } from "./network";
 export { version } from "./Version";
-//export {events} from './events';
 import * as utils from "./Utils";
+import * as events from './events';
 import * as errors from "./errors";
 import * as sessions from "./sessions";
 import * as extensions from "./extensions";
 import * as helpers from "./Helpers";
+import * as tl from "./tl";
 
-export { utils, errors, sessions, extensions, helpers };
+export { utils, errors, sessions, extensions, helpers, events, tl };

+ 142 - 134
gramjs/tl/api.js

@@ -3,7 +3,7 @@ const bigInt = require("big-integer");
 const {
     generateRandomBytes,
     readBigIntFromBuffer,
-    isArrayLike,
+    isArrayLike
 } = require("../Helpers");
 
 function generateRandomBigInt() {
@@ -13,7 +13,7 @@ function generateRandomBigInt() {
 const {
     parseTl,
     serializeBytes,
-    serializeDate,
+    serializeDate
 } = require("./generationHelpers");
 const { IS_NODE, toSignedLittleBuffer } = require("../Helpers");
 let tlContent, schemeContent;
@@ -38,7 +38,7 @@ const AUTO_CASTS = new Set([
     "InputPhoto",
     "InputMessage",
     "InputDocument",
-    "InputChatPhoto",
+    "InputChatPhoto"
 ]);
 
 class CastError extends Error {
@@ -114,63 +114,63 @@ function extractParams(fileContent) {
 
 function argToBytes(x, type) {
     switch (type) {
-        case "int":
-            const i = Buffer.alloc(4);
-            i.writeInt32LE(x, 0);
-            return i;
-        case "long":
-            return toSignedLittleBuffer(x, 8);
-        case "int128":
-            return toSignedLittleBuffer(x, 16);
-        case "int256":
-            return toSignedLittleBuffer(x, 32);
-        case "double":
-            const d = Buffer.alloc(8);
-            d.writeDoubleLE(x, 0);
-            return d;
-        case "string":
-            return serializeBytes(x);
-        case "Bool":
-            return x
-                ? Buffer.from("b5757299", "hex")
-                : Buffer.from("379779bc", "hex");
-        case "true":
-            return Buffer.alloc(0);
-        case "bytes":
-            return serializeBytes(x);
-        case "date":
-            return serializeDate(x);
-        default:
-            return x.getBytes();
+    case "int":
+        const i = Buffer.alloc(4);
+        i.writeInt32LE(x, 0);
+        return i;
+    case "long":
+        return toSignedLittleBuffer(x, 8);
+    case "int128":
+        return toSignedLittleBuffer(x, 16);
+    case "int256":
+        return toSignedLittleBuffer(x, 32);
+    case "double":
+        const d = Buffer.alloc(8);
+        d.writeDoubleLE(x, 0);
+        return d;
+    case "string":
+        return serializeBytes(x);
+    case "Bool":
+        return x
+            ? Buffer.from("b5757299", "hex")
+            : Buffer.from("379779bc", "hex");
+    case "true":
+        return Buffer.alloc(0);
+    case "bytes":
+        return serializeBytes(x);
+    case "date":
+        return serializeDate(x);
+    default:
+        return x.getBytes();
     }
 }
 
 async function getInputFromResolve(utils, client, peer, peerType) {
     switch (peerType) {
-        case "InputPeer":
-            return utils.getInputPeer(await client.getInputEntity(peer));
-        case "InputChannel":
-            return utils.getInputChannel(await client.getInputEntity(peer));
-        case "InputUser":
-            return utils.getInputUser(await client.getInputEntity(peer));
-        case "InputDialogPeer":
-            return await client._getInputDialog(peer);
-        case "InputNotifyPeer":
-            return await client._getInputNotify(peer);
-        case "InputMedia":
-            return utils.getInputMedia(peer);
-        case "InputPhoto":
-            return utils.getInputPhoto(peer);
-        case "InputMessage":
-            return utils.getInputMessage(peer);
-        case "InputDocument":
-            return utils.getInputDocument(peer);
-        case "InputChatPhoto":
-            return utils.getInputChatPhoto(peer);
-        case "chatId,int":
-            return await client.getPeerId(peer, false);
-        default:
-            throw new Error("unsupported peer type : " + peerType);
+    case "InputPeer":
+        return utils.getInputPeer(await client.getInputEntity(peer));
+    case "InputChannel":
+        return utils.getInputChannel(await client.getInputEntity(peer));
+    case "InputUser":
+        return utils.getInputUser(await client.getInputEntity(peer));
+    case "InputDialogPeer":
+        return await client._getInputDialog(peer);
+    case "InputNotifyPeer":
+        return await client._getInputNotify(peer);
+    case "InputMedia":
+        return utils.getInputMedia(peer);
+    case "InputPhoto":
+        return utils.getInputPhoto(peer);
+    case "InputMessage":
+        return utils.getInputMessage(peer);
+    case "InputDocument":
+        return utils.getInputDocument(peer);
+    case "InputChatPhoto":
+        return utils.getInputChatPhoto(peer);
+    case "chatId,int":
+        return await client.getPeerId(peer, false);
+    default:
+        throw new Error("unsupported peer type : " + peerType);
     }
 }
 
@@ -191,32 +191,32 @@ function getArgFromReader(reader, arg) {
         return reader.readInt();
     } else {
         switch (arg.type) {
-            case "int":
-                return reader.readInt();
-            case "long":
-                return reader.readLong();
-            case "int128":
-                return reader.readLargeInt(128);
-            case "int256":
-                return reader.readLargeInt(256);
-            case "double":
-                return reader.readDouble();
-            case "string":
-                return reader.tgReadString();
-            case "Bool":
-                return reader.tgReadBool();
-            case "true":
-                return true;
-            case "bytes":
-                return reader.tgReadBytes();
-            case "date":
-                return reader.tgReadDate();
-            default:
-                if (!arg.skipConstructorId) {
-                    return reader.tgReadObject();
-                } else {
-                    return api.constructors[arg.type].fromReader(reader);
-                }
+        case "int":
+            return reader.readInt();
+        case "long":
+            return reader.readLong();
+        case "int128":
+            return reader.readLargeInt(128);
+        case "int256":
+            return reader.readLargeInt(256);
+        case "double":
+            return reader.readDouble();
+        case "string":
+            return reader.tgReadString();
+        case "Bool":
+            return reader.tgReadBool();
+        case "true":
+            return true;
+        case "bytes":
+            return reader.tgReadBytes();
+        case "date":
+            return reader.tgReadDate();
+        default:
+            if (!arg.skipConstructorId) {
+                return reader.tgReadObject();
+            } else {
+                return api.constructors[arg.type].fromReader(reader);
+            }
         }
     }
 }
@@ -224,31 +224,31 @@ function getArgFromReader(reader, arg) {
 function compareType(value, type) {
     let correct = true;
     switch (type) {
-        case "number":
-            correct = typeof value === "number" || value===undefined;
-            break;
-        case "string":
-        case "boolean":
-            correct = typeof value === type;
-            break;
-        case "bigInt":
-            correct = bigInt.isInstance(value) ||  value===undefined;
-            break;
-        case "true":
-            // true value is always correct
-            break;
-        case "buffer":
-            correct = Buffer.isBuffer(value);
-            break;
-        case "date":
-            correct =
-                (value &&
-                    Object.prototype.toString.call(value) === "[object Date]" &&
-                    !isNaN(value)) ||
-                typeof value === "number";
-            break;
-        default:
-            console.error(new Error("Unknown type." + type));
+    case "number":
+        correct = typeof value === "number" || value === undefined;
+        break;
+    case "string":
+    case "boolean":
+        correct = typeof value === type;
+        break;
+    case "bigInt":
+        correct = bigInt.isInstance(value) || value === undefined;
+        break;
+    case "true":
+        // true value is always correct
+        break;
+    case "buffer":
+        correct = Buffer.isBuffer(value);
+        break;
+    case "date":
+        correct =
+            (value &&
+                Object.prototype.toString.call(value) === "[object Date]" &&
+                !isNaN(value)) ||
+            typeof value === "number";
+        break;
+    default:
+        console.error(new Error("Unknown type." + type));
     }
     return correct;
 }
@@ -263,7 +263,7 @@ function createClasses(classesType, params) {
             argsConfig,
             namespace,
             isFunction,
-            result,
+            result
         } = classParams;
         const fullName = [namespace, name].join(".").replace(/^\./, "");
 
@@ -282,7 +282,15 @@ function createClasses(classesType, params) {
                 args = args || {};
                 for (const argName in argsConfig) {
                     if (argName === "randomId" && !args[argName]) {
-                        this[argName] = generateRandomBigInt();
+                        if (argsConfig[argName].isVector) {
+                            const rands = [];
+                            for (let i = 0; i < args["id"].length; i++) {
+                                rands.push(generateRandomBigInt());
+                            }
+                            this[argName] = rands;
+                        } else {
+                            this[argName] = generateRandomBigInt();
+                        }
                     } else {
                         this[argName] = args[argName];
                     }
@@ -351,32 +359,32 @@ function createClasses(classesType, params) {
                     }
                 } else {
                     switch (object["type"]) {
-                        case "int":
-                            expected = "number";
-                            break;
-                        case "long":
-                        case "int128":
-                        case "int256":
-                        case "double":
-                            expected = "bigInt";
-                            break;
-                        case "string":
-                            expected = "string";
-                            break;
-                        case "Bool":
-                            expected = "boolean";
-                            break;
-                        case "true":
-                            expected = "true";
-                            break;
-                        case "bytes":
-                            expected = "buffer";
-                            break;
-                        case "date":
-                            expected = "date";
-                            break;
-                        default:
-                            expected = "object";
+                    case "int":
+                        expected = "number";
+                        break;
+                    case "long":
+                    case "int128":
+                    case "int256":
+                    case "double":
+                        expected = "bigInt";
+                        break;
+                    case "string":
+                        expected = "string";
+                        break;
+                    case "Bool":
+                        expected = "boolean";
+                        break;
+                    case "true":
+                        expected = "true";
+                        break;
+                    case "bytes":
+                        expected = "buffer";
+                        break;
+                    case "date":
+                        expected = "date";
+                        break;
+                    default:
+                        expected = "object";
                     }
                     if (expected === "object") {
                         // will be validated in get byte();