Bläddra i källkod

add editMessage,pinMessage,unpinMessage methods of client (#231)

* refactor: add return type of getInputMedia

* refactor: improve editMessage

- Now support media
- Add handler for when message property of EditMessageParams is of type Api.Message

* fix: EditMessageParams docs

* chore: update docs of editMessage method

* refactor: make text prop of EditMessageParams optional

- Add validation for if message prop is typeof number but both text and file props are undefined then throw an Error

* feat: pinMessage and unpinMessage convenience methods

- Add client.pinMessage and client.unpinMessage methods
- Add Message.pin and Message.unpin
- Add docs for all pin and unpin methods
Man Nguyen 3 år sedan
förälder
incheckning
328f30b515
4 ändrade filer med 247 tillägg och 19 borttagningar
  1. 1 1
      gramjs/Utils.ts
  2. 69 2
      gramjs/client/TelegramClient.ts
  3. 148 15
      gramjs/client/messages.ts
  4. 29 1
      gramjs/tl/custom/message.ts

+ 1 - 1
gramjs/Utils.ts

@@ -821,7 +821,7 @@ export function getInputMedia(
         videoNote = false,
         supportsStreaming = false,
     }: GetInputMediaInterface = {}
-): any {
+): Api.TypeInputMedia {
     if (media.SUBCLASS_OF_ID === undefined) {
         _raiseCastFail(media, "InputMedia");
     }

+ 69 - 2
gramjs/client/TelegramClient.ts

@@ -716,10 +716,11 @@ export class TelegramClient extends TelegramBaseClient {
      *  Used to edit a message by changing it's text or media
      *  message refers to the message to be edited not what to edit
      *  text refers to the new text
-     *  See also Message.edit()
+     *  See also Message.edit()<br/>
+     *  Notes: It is not possible to edit the media of a message that doesn't contain media.
      *  @param entity - From which chat to edit the message.<br/>
      *  This can also be the message to be edited, and the entity will be inferred from it, so the next parameter will be assumed to be the message text.<br/>
-     *  You may also pass a InputBotInlineMessageID, which is the only way to edit messages that were sent after the user selects an inline query result.
+     *  You may also pass a InputBotInlineMessageID, which is the only way to edit messages that were sent after the user selects an inline query result. Not supported yet!
      *  @param editMessageParams - see {@link EditMessageParams}.
      *  @return The edited Message.
      *  @throws
@@ -775,6 +776,72 @@ export class TelegramClient extends TelegramBaseClient {
         });
     }
 
+    /**
+     * Pins a message in a chat.
+     *
+     * See also {@link Message.pin}`.
+     *
+     * @remarks The default behavior is to **not** notify members, unlike the official applications.
+     * @param entity - The chat where the message should be pinned.
+     * @param message - The message or the message ID to pin. If it's `undefined`, all messages will be unpinned instead.
+     * @param pinMessageParams - see {@link UpdatePinMessageParams}.
+     * @return
+     * The pinned message. if message is undefined the return will be {@link AffectedHistory}
+     * @example
+     *  ```ts
+     *  const message = await client.sendMessage(chat, 'GramJS is awesome!');
+     *
+     *  await client.pinMessage(chat, message);
+     *  ```
+     */
+    pinMessage(
+        entity: EntityLike,
+        message?: MessageIDLike,
+        pinMessageParams?: messageMethods.UpdatePinMessageParams
+    ) {
+        return messageMethods.pinMessage(
+            this,
+            entity,
+            message,
+            pinMessageParams
+        );
+    }
+
+    /**
+     * Unpins a message in a chat.
+     *
+     * See also {@link Message.unpin}`.
+     *
+     * @remarks The default behavior is to **not** notify members, unlike the official applications.
+     * @param entity - The chat where the message should be pinned.
+     * @param message - The message or the message ID to pin. If it's `undefined`, all messages will be unpinned instead.
+     * @param pinMessageParams - see {@link UpdatePinMessageParams}.
+     * @return
+     * The pinned message. if message is undefined the return will be {@link AffectedHistory}
+     * @example
+     *  ```ts
+     *  const message = await client.sendMessage(chat, 'GramJS is awesome!');
+     *
+     *  // unpin one message
+     *  await client.unpinMessage(chat, message);
+     *
+     *  // unpin all messages
+     *  await client.unpinMessage(chat)
+     *  ```
+     */
+    unpinMessage(
+        entity: EntityLike,
+        message?: MessageIDLike,
+        unpinMessageParams?: messageMethods.UpdatePinMessageParams
+    ) {
+        return messageMethods.unpinMessage(
+            this,
+            entity,
+            message,
+            unpinMessageParams
+        );
+    }
+
     //endregion
     //region dialogs
 

+ 148 - 15
gramjs/client/messages.ts

@@ -15,12 +15,13 @@ import {
     isArrayLike,
     groupBy,
 } from "../Helpers";
-import { getMessageId, getPeerId, parseID } from "../Utils";
+import { getInputMedia, getMessageId, getPeerId, parseID } from "../Utils";
 import type { TelegramClient } from "../";
 import { utils } from "../";
 import { _parseMessageText } from "./messageParse";
 import { _getPeer } from "./users";
 import bigInt from "big-integer";
+import { _fileToMedia } from "./uploads";
 
 const _MAX_CHUNK_SIZE = 100;
 
@@ -545,16 +546,16 @@ export interface EditMessageParams {
     /** The ID of the message (or Message itself) to be edited. If the entity was a Message, then this message will be treated as the new text. */
     message: Api.Message | number;
     /** The new text of the message. Does nothing if the entity was a Message. */
-    text: string;
+    text?: string;
     /** See the {@link TelegramClient.parseMode} property for allowed values. Markdown parsing will be used by default. */
     parseMode?: any;
     /** A list of message formatting entities. When provided, the parseMode is ignored. */
     formattingEntities?: Api.TypeMessageEntity[];
     /** Should the link preview be shown? */
     linkPreview?: boolean;
-    /** The file object that should replace the existing media in the message. // not supported yet. */
-    file?: FileLike | FileLike[];
-    /** thumbnail to be edited. // not supported yet */
+    /** The file object that should replace the existing media in the message. Does nothing if entity was a Message */
+    file?: FileLike;
+    /** Whether to send the given file as a document or not. */
     forceDocument?: false;
     /** The matrix (list of lists), row list or button to be shown after sending the message.<br/>
      *  This parameter will only work if you have signed in as a bot. You can also pass your own ReplyMarkup here.<br/>
@@ -570,6 +571,18 @@ export interface EditMessageParams {
     schedule?: DateLike;
 }
 
+/** Interface for editing messages */
+export interface UpdatePinMessageParams {
+    /** Whether the pin should notify people or not. <br />
+     *  By default it has the opposite behavior of official clients, it will not notify members.
+     */
+    notify?: boolean;
+    /** Whether the message should be pinned for everyone or not. <br />
+     *  By default it has the opposite behavior of official clients, and it will pin the message for both sides, in private chats.
+     */
+    pmOneSide?: boolean;
+}
+
 /** @hidden */
 export function iterMessages(
     client: TelegramClient,
@@ -870,22 +883,59 @@ export async function editMessage(
         schedule,
     }: EditMessageParams
 ) {
+    if (typeof message === "number" && typeof text === "undefined" && !file) {
+        throw Error("You have to provide either file and text property.");
+    }
     entity = await client.getInputEntity(entity);
-    if (formattingEntities == undefined) {
-        [text, formattingEntities] = await _parseMessageText(
-            client,
-            text,
-            parseMode
-        );
+    let id: number | undefined;
+    let markup: Api.TypeReplyMarkup | undefined;
+    let entities: Api.TypeMessageEntity[] | undefined;
+    let inputMedia: Api.TypeInputMedia | undefined;
+    if (file) {
+        const { fileHandle, media, image } = await _fileToMedia(client, {
+            file,
+            forceDocument,
+        });
+        inputMedia = media;
+    }
+    if (message instanceof Api.Message) {
+        id = getMessageId(message);
+        text = message.message;
+        entities = message.entities;
+        if (buttons == undefined) {
+            markup = message.replyMarkup;
+        } else {
+            markup = client.buildReplyMarkup(buttons);
+        }
+        if (message.media) {
+            inputMedia = getInputMedia(message.media, { forceDocument });
+        }
+    } else {
+        if (typeof message !== "number") {
+            throw Error(
+                "editMessageParams.message must be either a number or a Api.Message type"
+            );
+        }
+        id = message;
+        if (formattingEntities == undefined) {
+            [text, entities] = await _parseMessageText(
+                client,
+                text || "",
+                parseMode
+            );
+        } else {
+            entities = formattingEntities;
+        }
+        markup = client.buildReplyMarkup(buttons);
     }
     const request = new Api.messages.EditMessage({
         peer: entity,
-        id: utils.getMessageId(message),
+        id,
         message: text,
         noWebpage: !linkPreview,
-        entities: formattingEntities,
-        //media: no media for now,
-        replyMarkup: client.buildReplyMarkup(buttons),
+        entities,
+        media: inputMedia,
+        replyMarkup: markup,
         scheduleDate: schedule,
     });
     const result = await client.invoke(request);
@@ -946,4 +996,87 @@ export async function deleteMessages(
     return Promise.all(results);
 }
 
+/** @hidden */
+export async function pinMessage(
+    client: TelegramClient,
+    entity: EntityLike,
+    message?: MessageIDLike,
+    pinMessageParams?: UpdatePinMessageParams
+) {
+    return await _pin(
+        client,
+        entity,
+        message,
+        false,
+        pinMessageParams?.notify,
+        pinMessageParams?.pmOneSide
+    );
+}
+
+/** @hidden */
+export async function unpinMessage(
+    client: TelegramClient,
+    entity: EntityLike,
+    message?: MessageIDLike,
+    unpinMessageParams?: UpdatePinMessageParams
+) {
+    return await _pin(
+        client,
+        entity,
+        message,
+        true,
+        unpinMessageParams?.notify,
+        unpinMessageParams?.pmOneSide
+    );
+}
+
+/** @hidden */
+export async function _pin(
+    client: TelegramClient,
+    entity: EntityLike,
+    message: MessageIDLike | undefined,
+    unpin: boolean,
+    notify: boolean = false,
+    pmOneSide: boolean = false
+) {
+    message = utils.getMessageId(message) || 0;
+    entity = await client.getInputEntity(entity);
+    let request:
+        | Api.messages.UnpinAllMessages
+        | Api.messages.UpdatePinnedMessage;
+
+    if (message === 0) {
+        request = new Api.messages.UnpinAllMessages({
+            peer: entity,
+        });
+        return await client.invoke(request);
+    }
+
+    request = new Api.messages.UpdatePinnedMessage({
+        silent: !notify,
+        unpin,
+        pmOneside: pmOneSide,
+        peer: entity,
+        id: message,
+    });
+    const result = await client.invoke(request);
+
+    /**
+     * Unpinning does not produce a service message.
+     * Pinning a message that was already pinned also produces no service message.
+     * Pinning a message in your own chat does not produce a service message,
+     * but pinning on a private conversation with someone else does.
+     */
+    if (
+        unpin ||
+        !("updates" in result) ||
+        ("updates" in result && !result.updates)
+    ) {
+        return;
+    }
+
+    // Pinning a message that doesn't exist would RPC-error earlier
+    return client._getResponseMessage(request, result, entity) as Api.Message;
+}
+
 // TODO do the rest

+ 29 - 1
gramjs/tl/custom/message.ts

@@ -6,7 +6,11 @@ import { ChatGetter } from "./chatGetter";
 import * as utils from "../../Utils";
 import { Forward } from "./forward";
 import type { File } from "./file";
-import { EditMessageParams, SendMessageParams } from "../../client/messages";
+import {
+    EditMessageParams,
+    SendMessageParams,
+    UpdatePinMessageParams,
+} from "../../client/messages";
 import { DownloadMediaInterface } from "../../client/downloads";
 import { inspect } from "util";
 import { betterConsoleLog, returnBigInt } from "../../Helpers";
@@ -816,6 +820,30 @@ export class CustomMessage extends SenderGetter {
         }
     }
 
+    async pin(params?: UpdatePinMessageParams) {
+        if (this._client) {
+            const entity = await this.getInputChat();
+            if (entity === undefined) {
+                throw Error(
+                    "Failed to pin message due to cannot get input chat."
+                );
+            }
+            return this._client.pinMessage(entity, this.id, params);
+        }
+    }
+
+    async unpin(params?: UpdatePinMessageParams) {
+        if (this._client) {
+            const entity = await this.getInputChat();
+            if (entity === undefined) {
+                throw Error(
+                    "Failed to unpin message due to cannot get input chat."
+                );
+            }
+            return this._client.unpinMessage(entity, this.id, params);
+        }
+    }
+
     async downloadMedia(params: DownloadMediaInterface) {
         // small hack for patched method
         if (this._client)