From e01c876690a9c68d3539647aae7f182ec3684587 Mon Sep 17 00:00:00 2001 From: Alina Sireneva Date: Mon, 9 Oct 2023 21:44:38 +0300 Subject: [PATCH] feat: accept high-level objects as inputs to methods --- packages/client/src/client.ts | 722 ++++++------------ packages/client/src/methods/_imports.ts | 1 + .../src/methods/bots/answer-callback-query.ts | 8 +- .../src/methods/bots/answer-inline-query.ts | 8 +- .../methods/bots/answer-pre-checkout-query.ts | 8 +- .../src/methods/bots/get-game-high-scores.ts | 13 +- .../client/src/methods/bots/set-game-score.ts | 13 +- packages/client/src/methods/chats/get-chat.ts | 1 + .../client/src/methods/chats/get-full-chat.ts | 1 + .../forums/delete-forum-topic-history.ts | 6 +- .../src/methods/forums/edit-forum-topic.ts | 9 +- .../forums/reorder-pinned-forum-topics.ts | 6 +- .../forums/toggle-forum-topic-closed.ts | 6 +- .../forums/toggle-forum-topic-pinned.ts | 6 +- .../methods/invite-links/edit-invite-link.ts | 4 +- .../invite-links/get-invite-link-members.ts | 6 +- .../invite-links/hide-all-join-requests.ts | 6 +- .../invite-links/revoke-invite-link.ts | 4 +- .../client/src/methods/messages/close-poll.ts | 14 +- .../src/methods/messages/delete-messages.ts | 69 +- .../messages/delete-scheduled-messages.ts | 8 +- .../src/methods/messages/edit-message.ts | 21 +- .../src/methods/messages/forward-messages.ts | 301 +++----- .../messages/get-discussion-message.ts | 9 +- .../src/methods/messages/get-message-group.ts | 10 +- .../methods/messages/get-message-reactions.ts | 70 +- .../methods/messages/get-messages-unsafe.ts | 35 +- .../src/methods/messages/get-messages.ts | 38 +- .../methods/messages/get-reaction-users.ts | 14 +- .../messages/get-scheduled-messages.ts | 28 +- .../methods/messages/iter-reaction-users.ts | 13 +- .../src/methods/messages/pin-message.ts | 12 +- .../client/src/methods/messages/send-copy.ts | 178 +++-- .../src/methods/messages/send-reaction.ts | 11 +- .../src/methods/messages/send-scheduled.ts | 24 +- .../client/src/methods/messages/send-vote.ts | 12 +- .../src/methods/messages/translate-message.ts | 13 +- .../src/methods/messages/unpin-message.ts | 12 +- .../src/methods/stories/get-stories-by-id.ts | 28 +- .../stories/get-stories-interactions.ts | 27 +- .../client/src/methods/users/resolve-peer.ts | 149 ++-- packages/client/src/types/messages/index.ts | 1 + .../src/types/messages/input-message-id.ts | 16 + packages/client/src/types/misc/sticker-set.ts | 3 + packages/client/src/types/peers/index.ts | 23 +- 45 files changed, 734 insertions(+), 1233 deletions(-) create mode 100644 packages/client/src/types/messages/input-message-id.ts diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 975a9007..d7ae0c36 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -128,16 +128,16 @@ import { iterInviteLinkMembers } from './methods/invite-links/iter-invite-link-m import { iterInviteLinks } from './methods/invite-links/iter-invite-links' import { revokeInviteLink } from './methods/invite-links/revoke-invite-link' import { closePoll } from './methods/messages/close-poll' -import { deleteMessages } from './methods/messages/delete-messages' +import { deleteMessages, deleteMessagesById, DeleteMessagesParams } from './methods/messages/delete-messages' import { deleteScheduledMessages } from './methods/messages/delete-scheduled-messages' import { editInlineMessage } from './methods/messages/edit-inline-message' import { editMessage } from './methods/messages/edit-message' import { _findMessageInUpdate } from './methods/messages/find-in-update' -import { forwardMessages } from './methods/messages/forward-messages' +import { ForwardMessageOptions, forwardMessages, forwardMessagesById } from './methods/messages/forward-messages' import { _getDiscussionMessage, getDiscussionMessage } from './methods/messages/get-discussion-message' import { getHistory, GetHistoryOffset } from './methods/messages/get-history' import { getMessageGroup } from './methods/messages/get-message-group' -import { getMessageReactions } from './methods/messages/get-message-reactions' +import { getMessageReactions, getMessageReactionsById } from './methods/messages/get-message-reactions' import { getMessages } from './methods/messages/get-messages' import { getMessagesUnsafe } from './methods/messages/get-messages-unsafe' import { getReactionUsers, GetReactionUsersOffset } from './methods/messages/get-reaction-users' @@ -152,7 +152,7 @@ import { readHistory } from './methods/messages/read-history' import { readReactions } from './methods/messages/read-reactions' import { searchGlobal, SearchGlobalOffset } from './methods/messages/search-global' import { searchMessages, SearchMessagesOffset } from './methods/messages/search-messages' -import { sendCopy } from './methods/messages/send-copy' +import { sendCopy, SendCopyParams } from './methods/messages/send-copy' import { sendMedia } from './methods/messages/send-media' import { sendMediaGroup } from './methods/messages/send-media-group' import { sendReaction } from './methods/messages/send-reaction' @@ -276,6 +276,7 @@ import { InputFileLike, InputInlineResult, InputMediaLike, + InputMessageId, InputPeerLike, InputPrivacyRule, InputReaction, @@ -710,11 +711,11 @@ export interface TelegramClient extends BaseTelegramClient { * * **Available**: 🤖 bots only * - * @param queryId ID of the callback query + * @param queryId ID of the callback query, or the query itself * @param params Parameters of the answer */ answerCallbackQuery( - queryId: Long, + queryId: Long | CallbackQuery, params?: { /** * Maximum amount of time in seconds for which @@ -761,7 +762,7 @@ export interface TelegramClient extends BaseTelegramClient { * @param params Additional parameters */ answerInlineQuery( - queryId: tl.Long, + queryId: tl.Long | InlineQuery, results: InputInlineResult[], params?: { /** @@ -851,7 +852,7 @@ export interface TelegramClient extends BaseTelegramClient { * @param queryId Pre-checkout query ID */ answerPreCheckoutQuery( - queryId: tl.Long, + queryId: tl.Long | PreCheckoutQuery, params?: { /** If pre-checkout is rejected, error message to show to the user */ error?: string @@ -946,16 +947,12 @@ export interface TelegramClient extends BaseTelegramClient { * **Available**: 🤖 bots only * */ - getGameHighScores(params: { - /** ID of the chat where the game was found */ - chatId: InputPeerLike - - /** ID of the message containing the game */ - message: number - - /** ID of the user to find high scores for */ - userId?: InputPeerLike - }): Promise + getGameHighScores( + params: InputMessageId & { + /** ID of the user to find high scores for */ + userId?: InputPeerLike + }, + ): Promise /** * Get high scores of a game from an inline message * @@ -1034,31 +1031,27 @@ export interface TelegramClient extends BaseTelegramClient { * @param params * @returns The modified message */ - setGameScore(params: { - /** Chat where the game was found */ - chatId: InputPeerLike + setGameScore( + params: InputMessageId & { + /** ID of the user who has scored */ + userId: InputPeerLike - /** ID of the message where the game was found */ - message: number + /** The new score (must be >0) */ + score: number - /** ID of the user who has scored */ - userId: InputPeerLike + /** + * When `true`, the game message will not be modified + * to include the new score + */ + noEdit?: boolean - /** The new score (must be >0) */ - score: number - - /** - * When `true`, the game message will not be modified - * to include the new score - */ - noEdit?: boolean - - /** - * Whether to allow user's score to decrease. - * This can be useful when fixing mistakes or banning cheaters - */ - force?: boolean - }): Promise + /** + * Whether to allow user's score to decrease. + * This can be useful when fixing mistakes or banning cheaters + */ + force?: boolean + }, + ): Promise /** * Set a score of a user in a game contained in * an inline message @@ -1484,10 +1477,11 @@ export interface TelegramClient extends BaseTelegramClient { * Use {@link getChat} or {@link getFullChat} instead. */ getChatPreview(inviteLink: string): Promise + /** * Get basic information about a chat. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId ID of the chat, its username or invite link * @throws MtArgumentError @@ -1495,10 +1489,11 @@ export interface TelegramClient extends BaseTelegramClient { * Use {@link getChatPreview} instead. */ getChat(chatId: InputPeerLike): Promise + /** * Get full information about a chat. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId ID of the chat, its username or invite link * @throws MtArgumentError @@ -2328,7 +2323,7 @@ export interface TelegramClient extends BaseTelegramClient { * @param chat Chat or user ID, username, phone number, `"me"` or `"self"` * @param topicId ID of the topic (i.e. its top message ID) */ - deleteForumTopicHistory(chat: InputPeerLike, topicId: number): Promise + deleteForumTopicHistory(chat: InputPeerLike, topicId: number | ForumTopic): Promise /** * Modify a topic in a forum * @@ -2343,9 +2338,10 @@ export interface TelegramClient extends BaseTelegramClient { editForumTopic(params: { /** Chat ID or username */ chatId: InputPeerLike - /** ID of the topic (i.e. its top message ID) */ - topicId: number + /** ID of the topic (i.e. its top message ID) */ + topicId: number | ForumTopic + /** * New topic title */ @@ -2436,7 +2432,7 @@ export interface TelegramClient extends BaseTelegramClient { /** * Order of the pinned topics */ - order: number[] + order: (number | ForumTopic)[] /** * Whether to un-pin topics not present in the order @@ -2457,7 +2453,7 @@ export interface TelegramClient extends BaseTelegramClient { chatId: InputPeerLike /** ID of the topic (i.e. its top message ID) */ - topicId: number + topicId: number | ForumTopic /** Whether the topic should be closed */ closed: boolean @@ -2473,7 +2469,7 @@ export interface TelegramClient extends BaseTelegramClient { /** Chat ID or username */ chatId: InputPeerLike /** ID of the topic (i.e. its top message ID) */ - topicId: number + topicId: number | ForumTopic /** Whether the topic should be pinned */ pinned: boolean }): Promise @@ -2551,7 +2547,7 @@ export interface TelegramClient extends BaseTelegramClient { /** Chat ID */ chatId: InputPeerLike /** Invite link to edit */ - link: string + link: string | ChatInviteLink /** * Date when this link will expire. * If `number` is passed, UNIX time in ms is expected. @@ -2599,7 +2595,7 @@ export interface TelegramClient extends BaseTelegramClient { /** * Invite link for which to get members */ - link?: string + link?: string | ChatInviteLink /** * Maximum number of users to return @@ -2704,7 +2700,7 @@ export interface TelegramClient extends BaseTelegramClient { action: 'approve' | 'deny' /** Invite link to target */ - link?: string + link?: string | ChatInviteLink }): Promise /** * Approve or deny join request to a chat. @@ -2789,7 +2785,7 @@ export interface TelegramClient extends BaseTelegramClient { * @param link Invite link to revoke * @returns If `link` is a primary invite, newly generated invite link, otherwise the revoked link */ - revokeInviteLink(chatId: InputPeerLike, link: string): Promise + revokeInviteLink(chatId: InputPeerLike, link: string | ChatInviteLink): Promise /** * Close a poll sent by you. * @@ -2798,42 +2794,33 @@ export interface TelegramClient extends BaseTelegramClient { * **Available**: ✅ both users and bots * */ - closePoll(params: { - /** Chat ID where this poll was found */ - chatId: InputPeerLike - /** Message ID where this poll was found */ - message: number - }): Promise + closePoll(params: InputMessageId): Promise /** - * Delete messages, including service messages. + * Delete messages by their IDs * * **Available**: ✅ both users and bots * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param ids Message(s) ID(s) to delete. */ - deleteMessages( - chatId: InputPeerLike, - ids: MaybeArray, - params?: { - /** - * Whether to "revoke" (i.e. delete for both sides). - * Only used for chats and private chats. - * - * @default true - */ - revoke?: boolean - }, - ): Promise + deleteMessagesById(chatId: InputPeerLike, ids: number[], params?: DeleteMessagesParams): Promise /** - * Delete scheduled messages. + * Delete one or more {@link Message} + * + * **Available**: ✅ both users and bots + * + * @param messages Message(s) to delete + */ + deleteMessages(messages: Message[], params?: DeleteMessagesParams): Promise + /** + * Delete scheduled messages by their IDs. * * **Available**: 👤 users only * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param ids Message(s) ID(s) to delete. */ - deleteScheduledMessages(chatId: InputPeerLike, ids: MaybeArray): Promise + deleteScheduledMessages(chatId: InputPeerLike, ids: number[]): Promise /** * Edit sent inline message text, media and reply markup. * @@ -2909,256 +2896,100 @@ export interface TelegramClient extends BaseTelegramClient { * @param message Message or its ID * @param params */ - editMessage(params: { - /** Chat ID */ - chatId: InputPeerLike - /** Message to edit */ - message: number | Message + editMessage( + params: InputMessageId & { + /** + * New message text + * + * When `media` is passed, `media.caption` is used instead + */ + text?: string | FormattedString - /** - * New message text - * - * When `media` is passed, `media.caption` is used instead - */ - text?: string | FormattedString + /** + * Parse mode to use to parse entities before sending + * the message. Defaults to current default parse mode (if any). + * + * Passing `null` will explicitly disable formatting. + */ + parseMode?: string | null - /** - * Parse mode to use to parse entities before sending - * the message. Defaults to current default parse mode (if any). - * - * Passing `null` will explicitly disable formatting. - */ - parseMode?: string | null + /** + * List of formatting entities to use instead of parsing via a + * parse mode. + * + * **Note:** Passing this makes the method ignore {@link parseMode} + * + * When `media` is passed, `media.entities` is used instead + */ + entities?: tl.TypeMessageEntity[] - /** - * List of formatting entities to use instead of parsing via a - * parse mode. - * - * **Note:** Passing this makes the method ignore {@link parseMode} - * - * When `media` is passed, `media.entities` is used instead - */ - entities?: tl.TypeMessageEntity[] + /** + * New message media + */ + media?: InputMediaLike - /** - * New message media - */ - media?: InputMediaLike + /** + * Whether to disable links preview in this message + */ + disableWebPreview?: boolean - /** - * Whether to disable links preview in this message - */ - disableWebPreview?: boolean + /** + * For bots: new reply markup. + * If omitted, existing markup will be removed. + */ + replyMarkup?: ReplyMarkup - /** - * For bots: new reply markup. - * If omitted, existing markup will be removed. - */ - replyMarkup?: ReplyMarkup + /** + * To re-schedule a message: new schedule date. + * When passing a number, a UNIX time in ms is expected. + */ + scheduleDate?: Date | number - /** - * To re-schedule a message: new schedule date. - * When passing a number, a UNIX time in ms is expected. - */ - scheduleDate?: Date | number - - /** - * For media, upload progress callback. - * - * @param uploaded Number of bytes uploaded - * @param total Total file size in bytes - */ - progressCallback?: (uploaded: number, total: number) => void - }): Promise + /** + * For media, upload progress callback. + * + * @param uploaded Number of bytes uploaded + * @param total Total file size in bytes + */ + progressCallback?: (uploaded: number, total: number) => void + }, + ): Promise _findMessageInUpdate(res: tl.TypeUpdates, isEdit?: boolean): Message /** - * Forward a single message. - * - * To forward with a caption, use another overload that takes an array of IDs. - * - * @param message Message ID - * @param params Additional sending parameters - * @returns Newly sent, forwarded messages in the destination chat - */ - forwardMessages(params: { - /** Source chat ID, username, phone, `"me"` or `"self"` */ - fromChatId: InputPeerLike - /** Destination chat ID, username, phone, `"me"` or `"self"` */ - toChatId: InputPeerLike - /** Message ID */ - messages: number - - /** - * Optionally, a caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - caption?: string | FormattedString - - /** - * Optionally, a media caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - captionMedia?: InputMediaLike - - /** - * Parse mode to use to parse entities in caption. - * Defaults to current default parse mode (if any). - * - * Passing `null` will explicitly disable formatting. - */ - parseMode?: string | null - - /** - * List of formatting entities in caption to use instead - * of parsing via a parse mode. - * - * **Note:** Passing this makes the method ignore {@link parseMode} - */ - entities?: tl.TypeMessageEntity[] - - /** - * Whether to forward silently (also applies to caption message). - */ - silent?: boolean - - /** - * If set, the forwarding will be scheduled to this date - * (also applies to caption message). - * When passing a number, a UNIX time in ms is expected. - * - * You can also pass `0x7FFFFFFE`, this will send the message - * once the peer is online - */ - schedule?: Date | number - - /** - * Whether to clear draft after sending this message (only used for caption) - * - * Defaults to `false` - */ - clearDraft?: boolean - - /** - * Whether to forward without author - */ - noAuthor?: boolean - - /** - * Whether to forward without caption (implies {@link noAuthor}) - */ - noCaption?: boolean - - /** - * Whether to disallow further forwards of this message. - * - * Only for bots, works even if the target chat does not - * have content protection. - */ - forbidForwards?: boolean - }): Promise - /** - * Forward one or more messages, optionally including a caption message. + * Forward one or more messages by their IDs. * You can forward no more than 100 messages at once. * * If a caption message was sent, it will be the first message in the resulting array. * + * **Available**: ✅ both users and bots + * * @param toChatId Destination chat ID, username, phone, `"me"` or `"self"` * @param fromChatId Source chat ID, username, phone, `"me"` or `"self"` * @param messages Message IDs * @param params Additional sending parameters - * @returns - * Newly sent, forwarded messages in the destination chat. - * If a caption message was provided, it will be the first message in the array. + * @returns Newly sent, forwarded messages in the destination chat. */ - forwardMessages(params: { - /** Source chat ID, username, phone, `"me"` or `"self"` */ - fromChatId: InputPeerLike - /** Destination chat ID, username, phone, `"me"` or `"self"` */ - toChatId: InputPeerLike - /** Message IDs */ - messages: number[] - - /** - * Optionally, a caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - caption?: string | FormattedString - - /** - * Optionally, a media caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - captionMedia?: InputMediaLike - - /** - * Parse mode to use to parse entities in caption. - * Defaults to current default parse mode (if any). - * - * Passing `null` will explicitly disable formatting. - */ - parseMode?: string | null - - /** - * List of formatting entities in caption to use instead - * of parsing via a parse mode. - * - * **Note:** Passing this makes the method ignore {@link parseMode} - */ - entities?: tl.TypeMessageEntity[] - - /** - * Whether to forward silently (also applies to caption message). - */ - silent?: boolean - - /** - * If set, the forwarding will be scheduled to this date - * (also applies to caption message). - * When passing a number, a UNIX time in ms is expected. - * - * You can also pass `0x7FFFFFFE`, this will send the message - * once the peer is online - */ - schedule?: Date | number - - /** - * Whether to clear draft after sending this message (only used for caption) - * - * Defaults to `false` - */ - clearDraft?: boolean - - /** - * Whether to forward without author - */ - noAuthor?: boolean - - /** - * Whether to forward without caption (implies {@link noAuthor}) - */ - noCaption?: boolean - - /** - * Whether to disallow further forwards of this message. - * - * Only for bots, works even if the target chat does not - * have content protection. - */ - forbidForwards?: boolean - }): Promise> + forwardMessagesById( + params: ForwardMessageOptions & { + /** Source chat ID, username, phone, `"me"` or `"self"` */ + fromChatId: InputPeerLike + /** Message IDs to forward */ + messages: number[] + }, + ): Promise + /** + * Forward one or more {@link Message}s to another chat. + * + * > **Note**: all messages must be from the same chat. + * **Available**: ✅ both users and bots + * + */ + forwardMessages( + params: ForwardMessageOptions & { + messages: Message[] + }, + ): Promise _getDiscussionMessage(peer: InputPeerLike, message: number): Promise<[tl.TypeInputPeer, number]> // public version of the same method because why not @@ -3178,7 +3009,7 @@ export interface TelegramClient extends BaseTelegramClient { * @param peer Channel where the post was found * @param message ID of the channel post */ - getDiscussionMessage(peer: InputPeerLike, message: number): Promise + getDiscussionMessage(params: InputMessageId): Promise /** * Get chat history. * @@ -3253,44 +3084,37 @@ export interface TelegramClient extends BaseTelegramClient { * @param chatId Chat ID * @param message ID of one of the messages in the group */ - getMessageGroup(chatId: InputPeerLike, message: number): Promise + getMessageGroup(params: InputMessageId): Promise /** - * Get reactions to a message. + * Get reactions to messages by their IDs. * * > Apps should short-poll reactions for visible messages * > (that weren't sent by the user) once every 15-30 seconds, * > but only if `message.reactions` is set * - * @param chatId ID of the chat with the message - * @param messages Message ID - * @returns Reactions to the corresponding message, or `null` if there are none - */ - getMessageReactions(chatId: InputPeerLike, messages: number): Promise - /** - * Get reactions to messages. - * - * > Apps should short-poll reactions for visible messages - * > (that weren't sent by the user) once every 15-30 seconds, - * > but only if `message.reactions` is set + * **Available**: 👤 users only * * @param chatId ID of the chat with messages * @param messages Message IDs * @returns Reactions to corresponding messages, or `null` if there are none */ - getMessageReactions(chatId: InputPeerLike, messages: number[]): Promise<(MessageReactions | null)[]> + getMessageReactionsById(chatId: InputPeerLike, messages: number[]): Promise<(MessageReactions | null)[]> /** - * Get a single message from PM or legacy group by its ID. - * For channels, use {@link getMessages}. + * Get reactions to {@link Message}s. * - * Unlike {@link getMessages}, this method does not - * check if the message belongs to some chat. + * > **Note**: messages must all be from the same chat. * - * @param messageId Messages ID - * @param [fromReply=false] - * Whether the reply to a given message should be fetched - * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) + * > Apps should short-poll reactions for visible messages + * > (that weren't sent by the user) once every 15-30 seconds, + * > but only if `message.reactions` is set + * + * **Available**: ✅ both users and bots + * + * @param chatId ID of the chat with messages + * @param messages Message IDs + * @returns Reactions to corresponding messages, or `null` if there are none */ - getMessagesUnsafe(messageId: number, fromReply?: boolean): Promise + getMessageReactions(messages: Message[]): Promise<(MessageReactions | null)[]> /** * Get messages from PM or legacy group by their IDs. * For channels, use {@link getMessages}. @@ -3301,48 +3125,39 @@ export interface TelegramClient extends BaseTelegramClient { * Fot messages that were not found, `null` will be * returned at that position. * + * **Available**: ✅ both users and bots + * * @param messageIds Messages IDs * @param [fromReply=false] * Whether the reply to a given message should be fetched * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) */ - getMessagesUnsafe(messageIds: number[], fromReply?: boolean): Promise<(Message | null)[]> - /** - * Get a single message in chat by its ID - * - * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` - * @param messageId Messages ID - * @param [fromReply=false] - * Whether the reply to a given message should be fetched - * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) - */ - getMessages(chatId: InputPeerLike, messageId: number, fromReply?: boolean): Promise + getMessagesUnsafe(messageIds: MaybeArray, fromReply?: boolean): Promise<(Message | null)[]> + /** * Get messages in chat by their IDs * * Fot messages that were not found, `null` will be * returned at that position. * + * **Available**: ✅ both users and bots + * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` * @param messageIds Messages IDs * @param [fromReply=false] * Whether the reply to a given message should be fetched * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) */ - getMessages(chatId: InputPeerLike, messageIds: number[], fromReply?: boolean): Promise<(Message | null)[]> + getMessages(chatId: InputPeerLike, messageIds: MaybeArray, fromReply?: boolean): Promise<(Message | null)[]> /** * Get users who have reacted to the message. * * **Available**: 👤 users only * - * @param chatId Chat ID - * @param messageId Message ID * @param params */ getReactionUsers( - chatId: InputPeerLike, - messageId: number, - params?: { + params: InputMessageId & { /** * Get only reactions with the specified emoji */ @@ -3361,23 +3176,18 @@ export interface TelegramClient extends BaseTelegramClient { offset?: GetReactionUsersOffset }, ): Promise> - /** - * Get a single scheduled message in chat by its ID - * - * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` - * @param messageId Scheduled message ID - */ - getScheduledMessages(chatId: InputPeerLike, messageId: number): Promise /** * Get scheduled messages in chat by their IDs * * Fot messages that were not found, `null` will be * returned at that position. * + * **Available**: 👤 users only + * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` * @param messageIds Scheduled messages IDs */ - getScheduledMessages(chatId: InputPeerLike, messageIds: number[]): Promise<(Message | null)[]> + getScheduledMessages(chatId: InputPeerLike, messageIds: MaybeArray): Promise<(Message | null)[]> /** * Iterate over chat history. Wrapper over {@link getHistory} * @@ -3416,9 +3226,7 @@ export interface TelegramClient extends BaseTelegramClient { * @param params */ iterReactionUsers( - chatId: InputPeerLike, - messageId: number, - params?: Parameters[3] & { + params: Parameters[1] & { /** * Limit the number of events returned. * @@ -3502,16 +3310,11 @@ export interface TelegramClient extends BaseTelegramClient { * * For supergroups/channels, you must have appropriate permissions, * either as an admin, or as default permissions - * * **Available**: ✅ both users and bots * - * @param chatId Chat ID, username, phone number, `"self"` or `"me"` - * @param messageId Message ID */ pinMessage( - chatId: InputPeerLike, - messageId: number, - params?: { + params: InputMessageId & { /** Whether to send a notification (only for legacy groups and supergroups) */ notify?: boolean /** Whether to pin for both sides (only for private chats) */ @@ -3700,8 +3503,7 @@ export interface TelegramClient extends BaseTelegramClient { fromUser?: InputPeerLike }): Promise> /** - * Copy a message (i.e. send the same message, - * but do not forward it). + * Copy a message (i.e. send the same message, but do not forward it). * * Note that if the message contains a webpage, * it will be copied simply as a text message, @@ -3712,81 +3514,18 @@ export interface TelegramClient extends BaseTelegramClient { * * @param params */ - sendCopy(params: { - /** Source chat ID */ - fromChatId: InputPeerLike - /** Target chat ID */ - toChatId: InputPeerLike - /** Message ID to forward */ - message: number - /** - * Whether to send this message silently. - */ - silent?: boolean - - /** - * If set, the message will be scheduled to this date. - * When passing a number, a UNIX time in ms is expected. - * - * You can also pass `0x7FFFFFFE`, this will send the message - * once the peer is online - */ - schedule?: Date | number - - /** - * New message caption (only used for media) - */ - caption?: string | FormattedString - - /** - * Parse mode to use to parse `text` entities before sending - * the message. Defaults to current default parse mode (if any). - * - * Passing `null` will explicitly disable formatting. - */ - parseMode?: string | null - - /** - * Message to reply to. Either a message object or message ID. - * - * For forums - can also be an ID of the topic (i.e. its top message ID) - */ - replyTo?: number | Message - - /** - * Whether to throw an error if {@link replyTo} - * message does not exist. - * - * If that message was not found, `NotFoundError` is thrown, - * with `text` set to `MESSAGE_NOT_FOUND`. - * - * Incurs an additional request, so only use when really needed. - * - * Defaults to `false` - */ - mustReply?: boolean - - /** - * List of formatting entities to use instead of parsing via a - * parse mode. - * - * **Note:** Passing this makes the method ignore {@link parseMode} - */ - entities?: tl.TypeMessageEntity[] - - /** - * For bots: inline or reply markup or an instruction - * to hide a reply keyboard or to force a reply. - */ - replyMarkup?: ReplyMarkup - - /** - * Whether to clear draft after sending this message. - * - * Defaults to `false` - */ - clearDraft?: boolean - }): Promise + sendCopy( + params: SendCopyParams & + ( + | { + /** Source chat ID */ + fromChatId: InputPeerLike + /** Message ID to forward */ + message: number + } + | { message: Message } + ), + ): Promise /** * Send a group of media. * @@ -4011,27 +3750,14 @@ export interface TelegramClient extends BaseTelegramClient { * * @returns Message to which the reaction was sent */ - sendReaction(params: { - /** Chat ID with the message to react to */ - chatId: InputPeerLike - /** Message ID to react to */ - message: number - /** Reaction emoji (or `null` to remove reaction) */ - emoji?: InputReaction | null - /** Whether to use a big reaction */ - big?: boolean - }): Promise - /** - * Send s previously scheduled message. - * - * Note that if the message belongs to a media group, - * the entire group will be sent, but only - * the first message will be returned (in this overload). - * - * @param peer Chat where the messages were scheduled - * @param id ID of the message - */ - sendScheduled(peer: InputPeerLike, id: number): Promise + sendReaction( + params: InputMessageId & { + /** Reaction emoji (or `null` to remove reaction) */ + emoji?: InputReaction | null + /** Whether to use a big reaction */ + big?: boolean + }, + ): Promise /** * Send previously scheduled message(s) * @@ -4039,10 +3765,12 @@ export interface TelegramClient extends BaseTelegramClient { * the entire group will be sent, and all the messages * will be returned. * + * **Available**: 👤 users only + * * @param peer Chat where the messages were scheduled * @param ids ID(s) of the messages */ - sendScheduled(peer: InputPeerLike, ids: number[]): Promise + sendScheduled(peer: InputPeerLike, ids: MaybeArray): Promise /** * Send a text message * @@ -4179,19 +3907,17 @@ export interface TelegramClient extends BaseTelegramClient { * **Available**: 👤 users only * */ - sendVote(params: { - /** Chat ID where this poll was found */ - chatId: InputPeerLike - /** Message ID where this poll was found */ - message: number - /** - * Selected options, or `null` to retract. - * You can pass indexes of the answers or the `Buffer`s - * representing them. In case of indexes, the poll will first - * be requested from the server. - */ - options: null | MaybeArray - }): Promise + sendVote( + params: InputMessageId & { + /** + * Selected options, or `null` to retract. + * You can pass indexes of the answers or the `Buffer`s + * representing them. In case of indexes, the poll will first + * be requested from the server. + */ + options: null | MaybeArray + }, + ): Promise /** * Translate message text to a given language. * @@ -4199,14 +3925,12 @@ export interface TelegramClient extends BaseTelegramClient { * **Available**: 👤 users only * */ - translateMessage(params: { - /** Chat or user ID */ - chatId: InputPeerLike - /** Identifier of the message to translate */ - messageId: number - /** Target language (two-letter ISO 639-1 language code) */ - toLanguage: string - }): Promise<[string, MessageEntity[]] | null> + translateMessage( + params: InputMessageId & { + /** Target language (two-letter ISO 639-1 language code) */ + toLanguage: string + }, + ): Promise<[string, MessageEntity[]] | null> /** * Translate text to a given language. * @@ -4247,7 +3971,7 @@ export interface TelegramClient extends BaseTelegramClient { * @param chatId Chat ID, username, phone number, `"self"` or `"me"` * @param messageId Message ID */ - unpinMessage(chatId: InputPeerLike, messageId: number): Promise + unpinMessage(params: InputMessageId): Promise /** * Create a new takeout session * @@ -4761,29 +4485,22 @@ export interface TelegramClient extends BaseTelegramClient { }, ): Promise> /** - * Get a single story by its ID + * Get one or more stories by their IDs * - * @param peerId Peer ID whose stories to fetch - * @param storyId Story ID - */ - getStoriesById(peerId: InputPeerLike, storyId: number): Promise - /** - * Get multiple stories by their IDs + * **Available**: 👤 users only * * @param peerId Peer ID whose stories to fetch * @param storyIds Story IDs */ - getStoriesById(peerId: InputPeerLike, storyIds: number[]): Promise - /** - * Get brief information about story interactions. - */ - getStoriesInteractions(peerId: InputPeerLike, storyId: number): Promise + getStoriesById(peerId: InputPeerLike, storyIds: MaybeArray): Promise /** * Get brief information about stories interactions. * * The result will be in the same order as the input IDs + * **Available**: ✅ both users and bots + * */ - getStoriesInteractions(peerId: InputPeerLike, storyIds: number[]): Promise + getStoriesInteractions(peerId: InputPeerLike, storyIds: MaybeArray): Promise /** * Generate a link to a story. * @@ -5604,25 +5321,23 @@ export class TelegramClient extends BaseTelegramClient { iterInviteLinks = iterInviteLinks.bind(null, this) revokeInviteLink = revokeInviteLink.bind(null, this) closePoll = closePoll.bind(null, this) + deleteMessagesById = deleteMessagesById.bind(null, this) deleteMessages = deleteMessages.bind(null, this) deleteScheduledMessages = deleteScheduledMessages.bind(null, this) editInlineMessage = editInlineMessage.bind(null, this) editMessage = editMessage.bind(null, this) _findMessageInUpdate = _findMessageInUpdate.bind(null, this) - // @ts-expect-error .bind() kinda breaks typings for overloads + forwardMessagesById = forwardMessagesById.bind(null, this) forwardMessages = forwardMessages.bind(null, this) _getDiscussionMessage = _getDiscussionMessage.bind(null, this) getDiscussionMessage = getDiscussionMessage.bind(null, this) getHistory = getHistory.bind(null, this) getMessageGroup = getMessageGroup.bind(null, this) - // @ts-expect-error .bind() kinda breaks typings for overloads + getMessageReactionsById = getMessageReactionsById.bind(null, this) getMessageReactions = getMessageReactions.bind(null, this) - // @ts-expect-error .bind() kinda breaks typings for overloads getMessagesUnsafe = getMessagesUnsafe.bind(null, this) - // @ts-expect-error .bind() kinda breaks typings for overloads getMessages = getMessages.bind(null, this) getReactionUsers = getReactionUsers.bind(null, this) - // @ts-expect-error .bind() kinda breaks typings for overloads getScheduledMessages = getScheduledMessages.bind(null, this) iterHistory = iterHistory.bind(null, this) iterReactionUsers = iterReactionUsers.bind(null, this) @@ -5638,7 +5353,6 @@ export class TelegramClient extends BaseTelegramClient { sendMediaGroup = sendMediaGroup.bind(null, this) sendMedia = sendMedia.bind(null, this) sendReaction = sendReaction.bind(null, this) - // @ts-expect-error .bind() kinda breaks typings for overloads sendScheduled = sendScheduled.bind(null, this) sendText = sendText.bind(null, this) sendTyping = sendTyping.bind(null, this) @@ -5680,9 +5394,7 @@ export class TelegramClient extends BaseTelegramClient { getBoosters = getBoosters.bind(null, this) getPeerStories = getPeerStories.bind(null, this) getProfileStories = getProfileStories.bind(null, this) - // @ts-expect-error .bind() kinda breaks typings for overloads getStoriesById = getStoriesById.bind(null, this) - // @ts-expect-error .bind() kinda breaks typings for overloads getStoriesInteractions = getStoriesInteractions.bind(null, this) getStoryLink = getStoryLink.bind(null, this) getStoryViewers = getStoryViewers.bind(null, this) diff --git a/packages/client/src/methods/_imports.ts b/packages/client/src/methods/_imports.ts index 1cfc5921..5efdcdde 100644 --- a/packages/client/src/methods/_imports.ts +++ b/packages/client/src/methods/_imports.ts @@ -42,6 +42,7 @@ import { InputFileLike, InputInlineResult, InputMediaLike, + InputMessageId, InputPeerLike, InputPrivacyRule, InputReaction, diff --git a/packages/client/src/methods/bots/answer-callback-query.ts b/packages/client/src/methods/bots/answer-callback-query.ts index 3ccde707..20bb7d86 100644 --- a/packages/client/src/methods/bots/answer-callback-query.ts +++ b/packages/client/src/methods/bots/answer-callback-query.ts @@ -1,14 +1,16 @@ import { BaseTelegramClient, Long } from '@mtcute/core' +import { CallbackQuery } from '../../types/bots/callback-query' + /** * Send an answer to a callback query. * - * @param queryId ID of the callback query + * @param queryId ID of the callback query, or the query itself * @param params Parameters of the answer */ export async function answerCallbackQuery( client: BaseTelegramClient, - queryId: Long, + queryId: Long | CallbackQuery, params?: { /** * Maximum amount of time in seconds for which @@ -49,7 +51,7 @@ export async function answerCallbackQuery( await client.call({ _: 'messages.setBotCallbackAnswer', - queryId, + queryId: Long.isLong(queryId) ? queryId : queryId.id, cacheTime, alert, message: text, diff --git a/packages/client/src/methods/bots/answer-inline-query.ts b/packages/client/src/methods/bots/answer-inline-query.ts index 7c018b32..3370cd7e 100644 --- a/packages/client/src/methods/bots/answer-inline-query.ts +++ b/packages/client/src/methods/bots/answer-inline-query.ts @@ -1,6 +1,6 @@ -import { BaseTelegramClient, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' -import { BotInline, InputInlineResult } from '../../types' +import { BotInline, InlineQuery, InputInlineResult } from '../../types/bots' /** * Answer an inline query. @@ -11,7 +11,7 @@ import { BotInline, InputInlineResult } from '../../types' */ export async function answerInlineQuery( client: BaseTelegramClient, - queryId: tl.Long, + queryId: tl.Long | InlineQuery, results: InputInlineResult[], params?: { /** @@ -99,7 +99,7 @@ export async function answerInlineQuery( await client.call({ _: 'messages.setInlineBotResults', - queryId, + queryId: Long.isLong(queryId) ? queryId : queryId.id, results: tlResults, cacheTime, gallery: gallery ?? defaultGallery, diff --git a/packages/client/src/methods/bots/answer-pre-checkout-query.ts b/packages/client/src/methods/bots/answer-pre-checkout-query.ts index 51ba014f..ffa004a6 100644 --- a/packages/client/src/methods/bots/answer-pre-checkout-query.ts +++ b/packages/client/src/methods/bots/answer-pre-checkout-query.ts @@ -1,4 +1,6 @@ -import { BaseTelegramClient, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' + +import { PreCheckoutQuery } from '../../types/updates' /** * Answer a pre-checkout query. @@ -7,7 +9,7 @@ import { BaseTelegramClient, tl } from '@mtcute/core' */ export async function answerPreCheckoutQuery( client: BaseTelegramClient, - queryId: tl.Long, + queryId: tl.Long | PreCheckoutQuery, params?: { /** If pre-checkout is rejected, error message to show to the user */ error?: string @@ -17,7 +19,7 @@ export async function answerPreCheckoutQuery( await client.call({ _: 'messages.setBotPrecheckoutResults', - queryId, + queryId: Long.isLong(queryId) ? queryId : queryId.queryId, success: !error, error, }) diff --git a/packages/client/src/methods/bots/get-game-high-scores.ts b/packages/client/src/methods/bots/get-game-high-scores.ts index 8b611835..86abc2da 100644 --- a/packages/client/src/methods/bots/get-game-high-scores.ts +++ b/packages/client/src/methods/bots/get-game-high-scores.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient, tl } from '@mtcute/core' -import { GameHighScore, InputPeerLike, PeersIndex } from '../../types' +import { GameHighScore, InputMessageId, InputPeerLike, normalizeInputMessageId, PeersIndex } from '../../types' import { normalizeInlineId } from '../../utils/inline-utils' import { normalizeToInputUser } from '../../utils/peer-utils' import { resolvePeer } from '../users/resolve-peer' @@ -10,18 +10,13 @@ import { resolvePeer } from '../users/resolve-peer' */ export async function getGameHighScores( client: BaseTelegramClient, - params: { - /** ID of the chat where the game was found */ - chatId: InputPeerLike - - /** ID of the message containing the game */ - message: number - + params: InputMessageId & { /** ID of the user to find high scores for */ userId?: InputPeerLike }, ): Promise { - const { chatId, message, userId } = params + const { userId } = params + const { chatId, message } = normalizeInputMessageId(params) const chat = await resolvePeer(client, chatId) diff --git a/packages/client/src/methods/bots/set-game-score.ts b/packages/client/src/methods/bots/set-game-score.ts index 11a19196..bccc0d0c 100644 --- a/packages/client/src/methods/bots/set-game-score.ts +++ b/packages/client/src/methods/bots/set-game-score.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient, tl } from '@mtcute/core' -import { InputPeerLike, Message } from '../../types' +import { InputMessageId, InputPeerLike, Message, normalizeInputMessageId } from '../../types' import { normalizeInlineId } from '../../utils/inline-utils' import { normalizeToInputUser } from '../../utils/peer-utils' import { _findMessageInUpdate } from '../messages/find-in-update' @@ -14,13 +14,7 @@ import { resolvePeer } from '../users/resolve-peer' */ export async function setGameScore( client: BaseTelegramClient, - params: { - /** Chat where the game was found */ - chatId: InputPeerLike - - /** ID of the message where the game was found */ - message: number - + params: InputMessageId & { /** ID of the user who has scored */ userId: InputPeerLike @@ -40,7 +34,8 @@ export async function setGameScore( force?: boolean }, ): Promise { - const { chatId, message, userId, score, noEdit, force } = params + const { userId, score, noEdit, force } = params + const { chatId, message } = normalizeInputMessageId(params) const user = normalizeToInputUser(await resolvePeer(client, userId), userId) const chat = await resolvePeer(client, chatId) diff --git a/packages/client/src/methods/chats/get-chat.ts b/packages/client/src/methods/chats/get-chat.ts index 4b36f284..9f295a2d 100644 --- a/packages/client/src/methods/chats/get-chat.ts +++ b/packages/client/src/methods/chats/get-chat.ts @@ -11,6 +11,7 @@ import { } from '../../utils/peer-utils' import { resolvePeer } from '../users/resolve-peer' +// @available=both /** * Get basic information about a chat. * diff --git a/packages/client/src/methods/chats/get-full-chat.ts b/packages/client/src/methods/chats/get-full-chat.ts index 84a2b791..08900cc9 100644 --- a/packages/client/src/methods/chats/get-full-chat.ts +++ b/packages/client/src/methods/chats/get-full-chat.ts @@ -11,6 +11,7 @@ import { } from '../../utils/peer-utils' import { resolvePeer } from '../users/resolve-peer' +// @available=both /** * Get full information about a chat. * diff --git a/packages/client/src/methods/forums/delete-forum-topic-history.ts b/packages/client/src/methods/forums/delete-forum-topic-history.ts index de6f07a4..6f13f71e 100644 --- a/packages/client/src/methods/forums/delete-forum-topic-history.ts +++ b/packages/client/src/methods/forums/delete-forum-topic-history.ts @@ -1,7 +1,7 @@ import { BaseTelegramClient } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { InputPeerLike } from '../../types' +import type { ForumTopic, InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' import { resolvePeer } from '../users/resolve-peer' @@ -15,7 +15,7 @@ import { resolvePeer } from '../users/resolve-peer' export async function deleteForumTopicHistory( client: BaseTelegramClient, chat: InputPeerLike, - topicId: number, + topicId: number | ForumTopic, ): Promise { const channel = normalizeToInputChannel(await resolvePeer(client, chat), chat) assertTypeIsNot('deleteForumTopicHistory', channel, 'inputChannelEmpty') @@ -23,7 +23,7 @@ export async function deleteForumTopicHistory( const res = await client.call({ _: 'channels.deleteTopicHistory', channel, - topMsgId: topicId, + topMsgId: typeof topicId === 'number' ? topicId : topicId.id, }) client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount, channel.channelId)) diff --git a/packages/client/src/methods/forums/edit-forum-topic.ts b/packages/client/src/methods/forums/edit-forum-topic.ts index 6fa284d1..e44fb9f6 100644 --- a/packages/client/src/methods/forums/edit-forum-topic.ts +++ b/packages/client/src/methods/forums/edit-forum-topic.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient, Long, tl } from '@mtcute/core' -import { InputPeerLike, Message } from '../../types' +import type { ForumTopic, InputPeerLike, Message } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' import { _findMessageInUpdate } from '../messages/find-in-update' import { resolvePeer } from '../users/resolve-peer' @@ -19,9 +19,10 @@ export async function editForumTopic( params: { /** Chat ID or username */ chatId: InputPeerLike - /** ID of the topic (i.e. its top message ID) */ - topicId: number + /** ID of the topic (i.e. its top message ID) */ + topicId: number | ForumTopic + /** * New topic title */ @@ -41,7 +42,7 @@ export async function editForumTopic( const res = await client.call({ _: 'channels.editForumTopic', channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), - topicId, + topicId: typeof topicId === 'number' ? topicId : topicId.id, title, iconEmojiId: icon ? icon ?? Long.ZERO : undefined, }) diff --git a/packages/client/src/methods/forums/reorder-pinned-forum-topics.ts b/packages/client/src/methods/forums/reorder-pinned-forum-topics.ts index b5f11e4e..f66a66d2 100644 --- a/packages/client/src/methods/forums/reorder-pinned-forum-topics.ts +++ b/packages/client/src/methods/forums/reorder-pinned-forum-topics.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike } from '../../types' +import type { ForumTopic, InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' import { resolvePeer } from '../users/resolve-peer' @@ -18,7 +18,7 @@ export async function reorderPinnedForumTopics( /** * Order of the pinned topics */ - order: number[] + order: (number | ForumTopic)[] /** * Whether to un-pin topics not present in the order @@ -30,7 +30,7 @@ export async function reorderPinnedForumTopics( await client.call({ _: 'channels.reorderPinnedForumTopics', channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), - order, + order: order.map((it) => (typeof it === 'number' ? it : it.id)), force, }) } diff --git a/packages/client/src/methods/forums/toggle-forum-topic-closed.ts b/packages/client/src/methods/forums/toggle-forum-topic-closed.ts index 8ea64771..b97760d6 100644 --- a/packages/client/src/methods/forums/toggle-forum-topic-closed.ts +++ b/packages/client/src/methods/forums/toggle-forum-topic-closed.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike, Message } from '../../types' +import type { ForumTopic, InputPeerLike, Message } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' import { _findMessageInUpdate } from '../messages/find-in-update' import { resolvePeer } from '../users/resolve-peer' @@ -19,7 +19,7 @@ export async function toggleForumTopicClosed( chatId: InputPeerLike /** ID of the topic (i.e. its top message ID) */ - topicId: number + topicId: number | ForumTopic /** Whether the topic should be closed */ closed: boolean @@ -30,7 +30,7 @@ export async function toggleForumTopicClosed( const res = await client.call({ _: 'channels.editForumTopic', channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), - topicId, + topicId: typeof topicId === 'number' ? topicId : topicId.id, closed, }) diff --git a/packages/client/src/methods/forums/toggle-forum-topic-pinned.ts b/packages/client/src/methods/forums/toggle-forum-topic-pinned.ts index a9571169..800f007d 100644 --- a/packages/client/src/methods/forums/toggle-forum-topic-pinned.ts +++ b/packages/client/src/methods/forums/toggle-forum-topic-pinned.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike } from '../../types' +import { ForumTopic, InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' import { resolvePeer } from '../users/resolve-peer' @@ -15,7 +15,7 @@ export async function toggleForumTopicPinned( /** Chat ID or username */ chatId: InputPeerLike /** ID of the topic (i.e. its top message ID) */ - topicId: number + topicId: number | ForumTopic /** Whether the topic should be pinned */ pinned: boolean }, @@ -25,7 +25,7 @@ export async function toggleForumTopicPinned( await client.call({ _: 'channels.updatePinnedForumTopic', channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), - topicId, + topicId: typeof topicId === 'number' ? topicId : topicId.id, pinned, }) } diff --git a/packages/client/src/methods/invite-links/edit-invite-link.ts b/packages/client/src/methods/invite-links/edit-invite-link.ts index ea974154..8bb51db7 100644 --- a/packages/client/src/methods/invite-links/edit-invite-link.ts +++ b/packages/client/src/methods/invite-links/edit-invite-link.ts @@ -21,7 +21,7 @@ export async function editInviteLink( /** Chat ID */ chatId: InputPeerLike /** Invite link to edit */ - link: string + link: string | ChatInviteLink /** * Date when this link will expire. * If `number` is passed, UNIX time in ms is expected. @@ -48,7 +48,7 @@ export async function editInviteLink( const res = await client.call({ _: 'messages.editExportedChatInvite', peer: await resolvePeer(client, chatId), - link, + link: typeof link === 'string' ? link : link.link, expireDate: normalizeDate(expires), usageLimit, requestNeeded: withApproval, diff --git a/packages/client/src/methods/invite-links/get-invite-link-members.ts b/packages/client/src/methods/invite-links/get-invite-link-members.ts index 3bf991a1..a91b0660 100644 --- a/packages/client/src/methods/invite-links/get-invite-link-members.ts +++ b/packages/client/src/methods/invite-links/get-invite-link-members.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient, tl } from '@mtcute/core' -import { ArrayPaginated, ChatInviteLinkMember, InputPeerLike, PeersIndex } from '../../types' +import { ArrayPaginated, ChatInviteLink, ChatInviteLinkMember, InputPeerLike, PeersIndex } from '../../types' import { makeArrayPaginated, normalizeDate, normalizeToInputUser } from '../../utils' import { resolvePeer } from '../users/resolve-peer' @@ -18,7 +18,7 @@ export async function getInviteLinkMembers( /** * Invite link for which to get members */ - link?: string + link?: string | ChatInviteLink /** * Maximum number of users to return @@ -65,7 +65,7 @@ export async function getInviteLinkMembers( _: 'messages.getChatInviteImporters', limit, peer, - link, + link: typeof link === 'string' ? link : link?.link, requested, q: requestedSearch, offsetDate, diff --git a/packages/client/src/methods/invite-links/hide-all-join-requests.ts b/packages/client/src/methods/invite-links/hide-all-join-requests.ts index 84ef5214..ccc43386 100644 --- a/packages/client/src/methods/invite-links/hide-all-join-requests.ts +++ b/packages/client/src/methods/invite-links/hide-all-join-requests.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike } from '../../types' +import type { ChatInviteLink, InputPeerLike } from '../../types' import { resolvePeer } from '../users/resolve-peer' /** @@ -16,7 +16,7 @@ export async function hideAllJoinRequests( action: 'approve' | 'deny' /** Invite link to target */ - link?: string + link?: string | ChatInviteLink }, ): Promise { const { chatId, action, link } = params @@ -25,6 +25,6 @@ export async function hideAllJoinRequests( _: 'messages.hideAllChatJoinRequests', approved: action === 'approve', peer: await resolvePeer(client, chatId), - link, + link: typeof link === 'string' ? link : link?.link, }) } diff --git a/packages/client/src/methods/invite-links/revoke-invite-link.ts b/packages/client/src/methods/invite-links/revoke-invite-link.ts index ac922e0e..aa702cf7 100644 --- a/packages/client/src/methods/invite-links/revoke-invite-link.ts +++ b/packages/client/src/methods/invite-links/revoke-invite-link.ts @@ -16,12 +16,12 @@ import { resolvePeer } from '../users/resolve-peer' export async function revokeInviteLink( client: BaseTelegramClient, chatId: InputPeerLike, - link: string, + link: string | ChatInviteLink, ): Promise { const res = await client.call({ _: 'messages.editExportedChatInvite', peer: await resolvePeer(client, chatId), - link, + link: typeof link === 'string' ? link : link.link, revoked: true, }) diff --git a/packages/client/src/methods/messages/close-poll.ts b/packages/client/src/methods/messages/close-poll.ts index 9012084e..508aa86e 100644 --- a/packages/client/src/methods/messages/close-poll.ts +++ b/packages/client/src/methods/messages/close-poll.ts @@ -1,7 +1,7 @@ import { BaseTelegramClient, Long, MtTypeAssertionError } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { InputPeerLike, PeersIndex, Poll } from '../../types' +import { InputMessageId, normalizeInputMessageId, PeersIndex, Poll } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { resolvePeer } from '../users/resolve-peer' @@ -11,16 +11,8 @@ import { resolvePeer } from '../users/resolve-peer' * Once closed, poll can't be re-opened, and nobody * will be able to vote in it */ -export async function closePoll( - client: BaseTelegramClient, - params: { - /** Chat ID where this poll was found */ - chatId: InputPeerLike - /** Message ID where this poll was found */ - message: number - }, -): Promise { - const { chatId, message } = params +export async function closePoll(client: BaseTelegramClient, params: InputMessageId): Promise { + const { chatId, message } = normalizeInputMessageId(params) const res = await client.call({ _: 'messages.editMessage', diff --git a/packages/client/src/methods/messages/delete-messages.ts b/packages/client/src/methods/messages/delete-messages.ts index e079f600..4101d1f8 100644 --- a/packages/client/src/methods/messages/delete-messages.ts +++ b/packages/client/src/methods/messages/delete-messages.ts @@ -1,34 +1,36 @@ -import { BaseTelegramClient, MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { InputPeerLike } from '../../types' +import { InputPeerLike, Message } from '../../types' import { isInputPeerChannel, normalizeToInputChannel } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' import { resolvePeer } from '../users/resolve-peer' +import { deleteScheduledMessages } from './delete-scheduled-messages' + +// @exported +export interface DeleteMessagesParams { + /** + * Whether to "revoke" (i.e. delete for both sides). + * Only used for chats and private chats. + * + * @default true + */ + revoke?: boolean +} /** - * Delete messages, including service messages. + * Delete messages by their IDs * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param ids Message(s) ID(s) to delete. */ -export async function deleteMessages( +export async function deleteMessagesById( client: BaseTelegramClient, chatId: InputPeerLike, - ids: MaybeArray, - params?: { - /** - * Whether to "revoke" (i.e. delete for both sides). - * Only used for chats and private chats. - * - * @default true - */ - revoke?: boolean - }, + ids: number[], + params?: DeleteMessagesParams, ): Promise { const { revoke = true } = params ?? {} - if (!Array.isArray(ids)) ids = [ids] - const peer = await resolvePeer(client, chatId) let upd @@ -52,3 +54,38 @@ export async function deleteMessages( client.network.handleUpdate(upd) } + +/** + * Delete one or more {@link Message} + * + * @param messages Message(s) to delete + */ +export async function deleteMessages( + client: BaseTelegramClient, + messages: Message[], + params?: DeleteMessagesParams, +): Promise { + if (messages.length === 1) { + return deleteMessagesById(client, messages[0].chat.inputPeer, [messages[0].id], params) + } + + const byChat = new Map() + const byChatScheduled = new Map() + + for (const msg of messages) { + const map = msg.isScheduled ? byChatScheduled : byChat + + if (!map.has(msg.chat.id)) { + map.set(msg.chat.id, [msg.chat.inputPeer, []]) + } + map.get(msg.chat.id)![1].push(msg.id) + } + + for (const [peer, ids] of byChat.values()) { + await deleteMessagesById(client, peer, ids, params) + } + + for (const [peer, ids] of byChatScheduled.values()) { + await deleteScheduledMessages(client, peer, ids) + } +} diff --git a/packages/client/src/methods/messages/delete-scheduled-messages.ts b/packages/client/src/methods/messages/delete-scheduled-messages.ts index 71f57574..a5c8a8ba 100644 --- a/packages/client/src/methods/messages/delete-scheduled-messages.ts +++ b/packages/client/src/methods/messages/delete-scheduled-messages.ts @@ -1,10 +1,10 @@ -import { BaseTelegramClient, MaybeArray } from '@mtcute/core' +import { BaseTelegramClient } from '@mtcute/core' import { InputPeerLike } from '../../types' import { resolvePeer } from '../users/resolve-peer' /** - * Delete scheduled messages. + * Delete scheduled messages by their IDs. * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param ids Message(s) ID(s) to delete. @@ -12,10 +12,8 @@ import { resolvePeer } from '../users/resolve-peer' export async function deleteScheduledMessages( client: BaseTelegramClient, chatId: InputPeerLike, - ids: MaybeArray, + ids: number[], ): Promise { - if (!Array.isArray(ids)) ids = [ids] - const peer = await resolvePeer(client, chatId) const res = await client.call({ diff --git a/packages/client/src/methods/messages/edit-message.ts b/packages/client/src/methods/messages/edit-message.ts index 7f786dc0..a1c2dc08 100644 --- a/packages/client/src/methods/messages/edit-message.ts +++ b/packages/client/src/methods/messages/edit-message.ts @@ -1,6 +1,14 @@ import { BaseTelegramClient, tl } from '@mtcute/core' -import { BotKeyboard, FormattedString, InputMediaLike, InputPeerLike, Message, ReplyMarkup } from '../../types' +import { + BotKeyboard, + FormattedString, + InputMediaLike, + InputMessageId, + Message, + normalizeInputMessageId, + ReplyMarkup, +} from '../../types' import { _normalizeInputMedia } from '../files/normalize-input-media' import { resolvePeer } from '../users/resolve-peer' import { _findMessageInUpdate } from './find-in-update' @@ -15,12 +23,7 @@ import { _parseEntities } from './parse-entities' */ export async function editMessage( client: BaseTelegramClient, - params: { - /** Chat ID */ - chatId: InputPeerLike - /** Message to edit */ - message: number | Message - + params: InputMessageId & { /** * New message text * @@ -77,7 +80,7 @@ export async function editMessage( progressCallback?: (uploaded: number, total: number) => void }, ): Promise { - const { chatId, message } = params + const { chatId, message } = normalizeInputMessageId(params) let content: string | undefined = undefined let entities: tl.TypeMessageEntity[] | undefined let media: tl.TypeInputMedia | undefined = undefined @@ -103,7 +106,7 @@ export async function editMessage( const res = await client.call({ _: 'messages.editMessage', - id: typeof message === 'number' ? message : message.id, + id: message, peer: await resolvePeer(client, chatId), noWebpage: params.disableWebPreview, replyMarkup: BotKeyboard._convertToTl(params.replyMarkup), diff --git a/packages/client/src/methods/messages/forward-messages.ts b/packages/client/src/methods/messages/forward-messages.ts index a3b7c79e..c179c947 100644 --- a/packages/client/src/methods/messages/forward-messages.ts +++ b/packages/client/src/methods/messages/forward-messages.ts @@ -1,4 +1,4 @@ -import { BaseTelegramClient, MaybeArray, MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' import { randomLong } from '@mtcute/core/utils' import { FormattedString, InputMediaLike, InputPeerLike, Message, PeersIndex } from '../../types' @@ -6,103 +6,93 @@ import { normalizeDate } from '../../utils/misc-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { resolvePeer } from '../users/resolve-peer' -/** - * Forward a single message. - * - * To forward with a caption, use another overload that takes an array of IDs. - * - * @param message Message ID - * @param params Additional sending parameters - * @returns Newly sent, forwarded messages in the destination chat - */ -export async function forwardMessages( - client: BaseTelegramClient, - params: { - /** Source chat ID, username, phone, `"me"` or `"self"` */ - fromChatId: InputPeerLike - /** Destination chat ID, username, phone, `"me"` or `"self"` */ - toChatId: InputPeerLike - /** Message ID */ - messages: number +// @exported +export interface ForwardMessageOptions { + /** Destination chat ID, username, phone, `"me"` or `"self"` */ + toChatId: InputPeerLike - /** - * Optionally, a caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - caption?: string | FormattedString + /** + * Optionally, a caption for your forwarded message(s). + * It will be sent as a separate message before the forwarded messages. + * + * You can either pass `caption` or `captionMedia`, passing both will + * result in an error + */ + caption?: string | FormattedString - /** - * Optionally, a media caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - captionMedia?: InputMediaLike + /** + * Optionally, a media caption for your forwarded message(s). + * It will be sent as a separate message before the forwarded messages. + * + * You can either pass `caption` or `captionMedia`, passing both will + * result in an error + */ + captionMedia?: InputMediaLike - /** - * Parse mode to use to parse entities in caption. - * Defaults to current default parse mode (if any). - * - * Passing `null` will explicitly disable formatting. - */ - parseMode?: string | null + /** + * Parse mode to use to parse entities in caption. + * Defaults to current default parse mode (if any). + * + * Passing `null` will explicitly disable formatting. + */ + parseMode?: string | null - /** - * List of formatting entities in caption to use instead - * of parsing via a parse mode. - * - * **Note:** Passing this makes the method ignore {@link parseMode} - */ - entities?: tl.TypeMessageEntity[] + /** + * List of formatting entities in caption to use instead + * of parsing via a parse mode. + * + * **Note:** Passing this makes the method ignore {@link parseMode} + */ + entities?: tl.TypeMessageEntity[] - /** - * Whether to forward silently (also applies to caption message). - */ - silent?: boolean + /** + * Whether to forward silently (also applies to caption message). + */ + silent?: boolean - /** - * If set, the forwarding will be scheduled to this date - * (also applies to caption message). - * When passing a number, a UNIX time in ms is expected. - * - * You can also pass `0x7FFFFFFE`, this will send the message - * once the peer is online - */ - schedule?: Date | number + /** + * If set, the forwarding will be scheduled to this date + * (also applies to caption message). + * When passing a number, a UNIX time in ms is expected. + * + * You can also pass `0x7FFFFFFE`, this will send the message + * once the peer is online + */ + schedule?: Date | number - /** - * Whether to clear draft after sending this message (only used for caption) - * - * Defaults to `false` - */ - clearDraft?: boolean + /** + * Whether to clear draft after sending this message (only used for caption) + * + * Defaults to `false` + */ + clearDraft?: boolean - /** - * Whether to forward without author - */ - noAuthor?: boolean + /** + * Whether to forward without author + */ + noAuthor?: boolean - /** - * Whether to forward without caption (implies {@link noAuthor}) - */ - noCaption?: boolean + /** + * Whether to forward without caption (implies {@link noAuthor}) + */ + noCaption?: boolean - /** - * Whether to disallow further forwards of this message. - * - * Only for bots, works even if the target chat does not - * have content protection. - */ - forbidForwards?: boolean - }, -): Promise + /** + * Whether to disallow further forwards of this message. + * + * Only for bots, works even if the target chat does not + * have content protection. + */ + forbidForwards?: boolean + + /** + * Peer to use when sending the message. + */ + sendAs?: InputPeerLike +} /** - * Forward one or more messages, optionally including a caption message. + * Forward one or more messages by their IDs. * You can forward no more than 100 messages at once. * * If a caption message was sent, it will be the first message in the resulting array. @@ -111,123 +101,18 @@ export async function forwardMessages( * @param fromChatId Source chat ID, username, phone, `"me"` or `"self"` * @param messages Message IDs * @param params Additional sending parameters - * @returns - * Newly sent, forwarded messages in the destination chat. - * If a caption message was provided, it will be the first message in the array. + * @returns Newly sent, forwarded messages in the destination chat. */ -export async function forwardMessages( +export async function forwardMessagesById( client: BaseTelegramClient, - params: { + params: ForwardMessageOptions & { /** Source chat ID, username, phone, `"me"` or `"self"` */ fromChatId: InputPeerLike - /** Destination chat ID, username, phone, `"me"` or `"self"` */ - toChatId: InputPeerLike - /** Message IDs */ + /** Message IDs to forward */ messages: number[] - - /** - * Optionally, a caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - caption?: string | FormattedString - - /** - * Optionally, a media caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - captionMedia?: InputMediaLike - - /** - * Parse mode to use to parse entities in caption. - * Defaults to current default parse mode (if any). - * - * Passing `null` will explicitly disable formatting. - */ - parseMode?: string | null - - /** - * List of formatting entities in caption to use instead - * of parsing via a parse mode. - * - * **Note:** Passing this makes the method ignore {@link parseMode} - */ - entities?: tl.TypeMessageEntity[] - - /** - * Whether to forward silently (also applies to caption message). - */ - silent?: boolean - - /** - * If set, the forwarding will be scheduled to this date - * (also applies to caption message). - * When passing a number, a UNIX time in ms is expected. - * - * You can also pass `0x7FFFFFFE`, this will send the message - * once the peer is online - */ - schedule?: Date | number - - /** - * Whether to clear draft after sending this message (only used for caption) - * - * Defaults to `false` - */ - clearDraft?: boolean - - /** - * Whether to forward without author - */ - noAuthor?: boolean - - /** - * Whether to forward without caption (implies {@link noAuthor}) - */ - noCaption?: boolean - - /** - * Whether to disallow further forwards of this message. - * - * Only for bots, works even if the target chat does not - * have content protection. - */ - forbidForwards?: boolean }, -): Promise> - -/** @internal */ -export async function forwardMessages( - client: BaseTelegramClient, - params: { - toChatId: InputPeerLike - fromChatId: InputPeerLike - messages: MaybeArray - parseMode?: string | null - entities?: tl.TypeMessageEntity[] - silent?: boolean - schedule?: Date | number - clearDraft?: boolean - noAuthor?: boolean - noCaption?: boolean - forbidForwards?: boolean - sendAs?: InputPeerLike - }, -): Promise> { - const { toChatId, fromChatId, silent, schedule, forbidForwards, sendAs, noAuthor, noCaption } = params - let { messages } = params - - let isSingle = false - - if (!Array.isArray(messages)) { - isSingle = true - messages = [messages] - } +): Promise { + const { messages, toChatId, fromChatId, silent, schedule, forbidForwards, sendAs, noAuthor, noCaption } = params // sending more than 100 will not result in a server-sent // error, instead only first 100 IDs will be forwarded, @@ -269,7 +154,25 @@ export async function forwardMessages( } }) - if (isSingle) return forwarded[0] - return forwarded } + +/** + * Forward one or more {@link Message}s to another chat. + * + * > **Note**: all messages must be from the same chat. + */ +export async function forwardMessages( + client: BaseTelegramClient, + params: ForwardMessageOptions & { + messages: Message[] + }, +): Promise { + const { messages, ...rest } = params + + return forwardMessagesById(client, { + ...rest, + fromChatId: messages[0].chat.inputPeer, + messages: messages.map((it) => it.id), + }) +} diff --git a/packages/client/src/methods/messages/get-discussion-message.ts b/packages/client/src/methods/messages/get-discussion-message.ts index 6094a0bd..392f65ba 100644 --- a/packages/client/src/methods/messages/get-discussion-message.ts +++ b/packages/client/src/methods/messages/get-discussion-message.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient, tl } from '@mtcute/core' -import { Message } from '../../types/messages' +import { InputMessageId, Message, normalizeInputMessageId } from '../../types/messages' import { InputPeerLike, PeersIndex } from '../../types/peers' import { resolvePeer } from '../users/resolve-peer' @@ -54,10 +54,11 @@ export async function _getDiscussionMessage( */ export async function getDiscussionMessage( client: BaseTelegramClient, - peer: InputPeerLike, - message: number, + params: InputMessageId, ): Promise { - const inputPeer = await resolvePeer(client, peer) + const { chatId, message } = normalizeInputMessageId(params) + + const inputPeer = await resolvePeer(client, chatId) const res = await client.call({ _: 'messages.getDiscussionMessage', diff --git a/packages/client/src/methods/messages/get-message-group.ts b/packages/client/src/methods/messages/get-message-group.ts index 282745c8..19d4bdfd 100644 --- a/packages/client/src/methods/messages/get-message-group.ts +++ b/packages/client/src/methods/messages/get-message-group.ts @@ -1,7 +1,7 @@ import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' import { isPresent } from '@mtcute/core/utils' -import { InputPeerLike, Message } from '../../types' +import { InputMessageId, Message, normalizeInputMessageId } from '../../types' import { isInputPeerChannel } from '../../utils/peer-utils' import { resolvePeer } from '../users/resolve-peer' import { getMessages } from './get-messages' @@ -12,11 +12,9 @@ import { getMessages } from './get-messages' * @param chatId Chat ID * @param message ID of one of the messages in the group */ -export async function getMessageGroup( - client: BaseTelegramClient, - chatId: InputPeerLike, - message: number, -): Promise { +export async function getMessageGroup(client: BaseTelegramClient, params: InputMessageId): Promise { + const { chatId, message } = normalizeInputMessageId(params) + // awesome hack stolen from pyrogram // groups have no more than 10 items // however, since for non-channels message ids are shared, diff --git a/packages/client/src/methods/messages/get-message-reactions.ts b/packages/client/src/methods/messages/get-message-reactions.ts index 5a327a8f..6213089e 100644 --- a/packages/client/src/methods/messages/get-message-reactions.ts +++ b/packages/client/src/methods/messages/get-message-reactions.ts @@ -1,29 +1,12 @@ -import { BaseTelegramClient, getMarkedPeerId, MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { InputPeerLike, MessageReactions, PeersIndex } from '../../types' +import { InputPeerLike, Message, MessageReactions, PeersIndex } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { resolvePeer } from '../users/resolve-peer' /** - * Get reactions to a message. - * - * > Apps should short-poll reactions for visible messages - * > (that weren't sent by the user) once every 15-30 seconds, - * > but only if `message.reactions` is set - * - * @param chatId ID of the chat with the message - * @param messages Message ID - * @returns Reactions to the corresponding message, or `null` if there are none - */ -export async function getMessageReactions( - client: BaseTelegramClient, - chatId: InputPeerLike, - messages: number, -): Promise - -/** - * Get reactions to messages. + * Get reactions to messages by their IDs. * * > Apps should short-poll reactions for visible messages * > (that weren't sent by the user) once every 15-30 seconds, @@ -33,26 +16,11 @@ export async function getMessageReactions( * @param messages Message IDs * @returns Reactions to corresponding messages, or `null` if there are none */ -export async function getMessageReactions( +export async function getMessageReactionsById( client: BaseTelegramClient, chatId: InputPeerLike, messages: number[], -): Promise<(MessageReactions | null)[]> - -/** - * @internal - */ -export async function getMessageReactions( - client: BaseTelegramClient, - chatId: InputPeerLike, - messages: MaybeArray, -): Promise> { - const single = !Array.isArray(messages) - - if (!Array.isArray(messages)) { - messages = [messages] - } - +): Promise<(MessageReactions | null)[]> { const res = await client.call({ _: 'messages.getMessagesReactions', peer: await resolvePeer(client, chatId), @@ -77,9 +45,29 @@ export async function getMessageReactions( index[update.msgId] = new MessageReactions(update.msgId, getMarkedPeerId(update.peer), update.reactions, peers) } - if (single) { - return index[messages[0]] ?? null - } - return messages.map((messageId) => index[messageId] ?? null) } + +/** + * Get reactions to {@link Message}s. + * + * > **Note**: messages must all be from the same chat. + * + * > Apps should short-poll reactions for visible messages + * > (that weren't sent by the user) once every 15-30 seconds, + * > but only if `message.reactions` is set + * + * @param chatId ID of the chat with messages + * @param messages Message IDs + * @returns Reactions to corresponding messages, or `null` if there are none + */ +export async function getMessageReactions( + client: BaseTelegramClient, + messages: Message[], +): Promise<(MessageReactions | null)[]> { + return getMessageReactionsById( + client, + messages[0].chat.inputPeer, + messages.map((it) => it.id), + ) +} diff --git a/packages/client/src/methods/messages/get-messages-unsafe.ts b/packages/client/src/methods/messages/get-messages-unsafe.ts index 017b67ad..e83aeac0 100644 --- a/packages/client/src/methods/messages/get-messages-unsafe.ts +++ b/packages/client/src/methods/messages/get-messages-unsafe.ts @@ -3,23 +3,6 @@ import { assertTypeIsNot } from '@mtcute/core/utils' import { Message, PeersIndex } from '../../types' -/** - * Get a single message from PM or legacy group by its ID. - * For channels, use {@link getMessages}. - * - * Unlike {@link getMessages}, this method does not - * check if the message belongs to some chat. - * - * @param messageId Messages ID - * @param [fromReply=false] - * Whether the reply to a given message should be fetched - * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) - */ -export async function getMessagesUnsafe( - client: BaseTelegramClient, - messageId: number, - fromReply?: boolean, -): Promise /** * Get messages from PM or legacy group by their IDs. * For channels, use {@link getMessages}. @@ -35,23 +18,15 @@ export async function getMessagesUnsafe( * Whether the reply to a given message should be fetched * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) */ -export async function getMessagesUnsafe( - client: BaseTelegramClient, - messageIds: number[], - fromReply?: boolean, -): Promise<(Message | null)[]> - -/** @internal */ export async function getMessagesUnsafe( client: BaseTelegramClient, messageIds: MaybeArray, fromReply = false, -): Promise> { - const isSingle = !Array.isArray(messageIds) - if (isSingle) messageIds = [messageIds as number] +): Promise<(Message | null)[]> { + if (!Array.isArray(messageIds)) messageIds = [messageIds] const type = fromReply ? 'inputMessageReplyTo' : 'inputMessageID' - const ids: tl.TypeInputMessage[] = (messageIds as number[]).map((it) => ({ + const ids: tl.TypeInputMessage[] = messageIds.map((it) => ({ _: type, id: it, })) @@ -65,11 +40,9 @@ export async function getMessagesUnsafe( const peers = PeersIndex.from(res) - const ret = res.messages.map((msg) => { + return res.messages.map((msg) => { if (msg._ === 'messageEmpty') return null return new Message(msg, peers) }) - - return isSingle ? ret[0] : ret } diff --git a/packages/client/src/methods/messages/get-messages.ts b/packages/client/src/methods/messages/get-messages.ts index 6c5126c7..441f9a81 100644 --- a/packages/client/src/methods/messages/get-messages.ts +++ b/packages/client/src/methods/messages/get-messages.ts @@ -7,21 +7,7 @@ import { isInputPeerChannel, normalizeToInputChannel } from '../../utils/peer-ut import { getAuthState } from '../auth/_state' import { resolvePeer } from '../users/resolve-peer' -/** - * Get a single message in chat by its ID - * - * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` - * @param messageId Messages ID - * @param [fromReply=false] - * Whether the reply to a given message should be fetched - * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) - */ -export async function getMessages( - client: BaseTelegramClient, - chatId: InputPeerLike, - messageId: number, - fromReply?: boolean, -): Promise +// @available=both /** * Get messages in chat by their IDs * @@ -34,28 +20,17 @@ export async function getMessages( * Whether the reply to a given message should be fetched * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) */ -export async function getMessages( - client: BaseTelegramClient, - chatId: InputPeerLike, - messageIds: number[], - fromReply?: boolean, -): Promise<(Message | null)[]> - -// @available=both -/** @internal */ export async function getMessages( client: BaseTelegramClient, chatId: InputPeerLike, messageIds: MaybeArray, fromReply = false, -): Promise> { +): Promise<(Message | null)[]> { const peer = await resolvePeer(client, chatId) - - const isSingle = !Array.isArray(messageIds) - if (isSingle) messageIds = [messageIds as number] + if (!Array.isArray(messageIds)) messageIds = [messageIds] const type = fromReply ? 'inputMessageReplyTo' : 'inputMessageID' - const ids: tl.TypeInputMessage[] = (messageIds as number[]).map((it) => ({ + const ids: tl.TypeInputMessage[] = messageIds.map((it) => ({ _: type, id: it, })) @@ -80,7 +55,8 @@ export async function getMessages( const peers = PeersIndex.from(res) let selfId: number | null | undefined = undefined - const ret = res.messages.map((msg) => { + + return res.messages.map((msg) => { if (msg._ === 'messageEmpty') return null if (!isChannel) { @@ -110,6 +86,4 @@ export async function getMessages( return new Message(msg, peers) }) - - return isSingle ? ret[0] : ret } diff --git a/packages/client/src/methods/messages/get-reaction-users.ts b/packages/client/src/methods/messages/get-reaction-users.ts index ff60e730..b9f780de 100644 --- a/packages/client/src/methods/messages/get-reaction-users.ts +++ b/packages/client/src/methods/messages/get-reaction-users.ts @@ -2,8 +2,9 @@ import { BaseTelegramClient } from '@mtcute/core' import { ArrayPaginated, - InputPeerLike, + InputMessageId, InputReaction, + normalizeInputMessageId, normalizeInputReaction, PeerReaction, PeersIndex, @@ -17,15 +18,11 @@ export type GetReactionUsersOffset = string /** * Get users who have reacted to the message. * - * @param chatId Chat ID - * @param messageId Message ID * @param params */ export async function getReactionUsers( client: BaseTelegramClient, - chatId: InputPeerLike, - messageId: number, - params?: { + params: InputMessageId & { /** * Get only reactions with the specified emoji */ @@ -44,9 +41,8 @@ export async function getReactionUsers( offset?: GetReactionUsersOffset }, ): Promise> { - if (!params) params = {} - const { limit = 100, offset, emoji } = params + const { chatId, message } = normalizeInputMessageId(params) const peer = await resolvePeer(client, chatId) @@ -55,7 +51,7 @@ export async function getReactionUsers( const res = await client.call({ _: 'messages.getMessageReactionsList', peer, - id: messageId, + id: message, reaction, limit, offset, diff --git a/packages/client/src/methods/messages/get-scheduled-messages.ts b/packages/client/src/methods/messages/get-scheduled-messages.ts index d84369bf..4bbe1577 100644 --- a/packages/client/src/methods/messages/get-scheduled-messages.ts +++ b/packages/client/src/methods/messages/get-scheduled-messages.ts @@ -4,17 +4,6 @@ import { assertTypeIsNot } from '@mtcute/core/utils' import { InputPeerLike, Message, PeersIndex } from '../../types' import { resolvePeer } from '../users/resolve-peer' -/** - * Get a single scheduled message in chat by its ID - * - * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` - * @param messageId Scheduled message ID - */ -export async function getScheduledMessages( - client: BaseTelegramClient, - chatId: InputPeerLike, - messageId: number, -): Promise /** * Get scheduled messages in chat by their IDs * @@ -24,27 +13,18 @@ export async function getScheduledMessages( * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` * @param messageIds Scheduled messages IDs */ -export async function getScheduledMessages( - client: BaseTelegramClient, - chatId: InputPeerLike, - messageIds: number[], -): Promise<(Message | null)[]> - -/** @internal */ export async function getScheduledMessages( client: BaseTelegramClient, chatId: InputPeerLike, messageIds: MaybeArray, -): Promise> { +): Promise<(Message | null)[]> { const peer = await resolvePeer(client, chatId) - - const isSingle = !Array.isArray(messageIds) - if (isSingle) messageIds = [messageIds as number] + if (!Array.isArray(messageIds)) messageIds = [messageIds] const res = await client.call({ _: 'messages.getScheduledMessages', peer, - id: messageIds as number[], + id: messageIds, }) assertTypeIsNot('getScheduledMessages', res, 'messages.messagesNotModified') @@ -57,5 +37,5 @@ export async function getScheduledMessages( return new Message(msg, peers, true) }) - return isSingle ? ret[0] : ret + return ret } diff --git a/packages/client/src/methods/messages/iter-reaction-users.ts b/packages/client/src/methods/messages/iter-reaction-users.ts index f670633b..5f2972a5 100644 --- a/packages/client/src/methods/messages/iter-reaction-users.ts +++ b/packages/client/src/methods/messages/iter-reaction-users.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike, normalizeInputReaction, PeerReaction } from '../../types' +import { normalizeInputMessageId, normalizeInputReaction, PeerReaction } from '../../types' import { resolvePeer } from '../users/resolve-peer' import { getReactionUsers } from './get-reaction-users' @@ -15,9 +15,7 @@ import { getReactionUsers } from './get-reaction-users' */ export async function* iterReactionUsers( client: BaseTelegramClient, - chatId: InputPeerLike, - messageId: number, - params?: Parameters[3] & { + params: Parameters[1] & { /** * Limit the number of events returned. * @@ -33,8 +31,7 @@ export async function* iterReactionUsers( chunkSize?: number }, ): AsyncIterableIterator { - if (!params) params = {} - + const { chatId, message } = normalizeInputMessageId(params) const peer = await resolvePeer(client, chatId) const { limit = Infinity, chunkSize = 100 } = params @@ -45,7 +42,9 @@ export async function* iterReactionUsers( const reaction = normalizeInputReaction(params.emoji) for (;;) { - const res = await getReactionUsers(client, peer, messageId, { + const res = await getReactionUsers(client, { + chatId: peer, + message, emoji: reaction, limit: Math.min(chunkSize, limit - current), offset, diff --git a/packages/client/src/methods/messages/pin-message.ts b/packages/client/src/methods/messages/pin-message.ts index 3f66bdbd..b2d78e64 100644 --- a/packages/client/src/methods/messages/pin-message.ts +++ b/packages/client/src/methods/messages/pin-message.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike } from '../../types' +import { InputMessageId, normalizeInputMessageId } from '../../types' import { resolvePeer } from '../users/resolve-peer' /** @@ -8,15 +8,10 @@ import { resolvePeer } from '../users/resolve-peer' * * For supergroups/channels, you must have appropriate permissions, * either as an admin, or as default permissions - * - * @param chatId Chat ID, username, phone number, `"self"` or `"me"` - * @param messageId Message ID */ export async function pinMessage( client: BaseTelegramClient, - chatId: InputPeerLike, - messageId: number, - params?: { + params: InputMessageId & { /** Whether to send a notification (only for legacy groups and supergroups) */ notify?: boolean /** Whether to pin for both sides (only for private chats) */ @@ -24,11 +19,12 @@ export async function pinMessage( }, ): Promise { const { notify, bothSides } = params ?? {} + const { chatId, message } = normalizeInputMessageId(params) const res = await client.call({ _: 'messages.updatePinnedMessage', peer: await resolvePeer(client, chatId), - id: messageId, + id: message, silent: !notify, pmOneside: !bothSides, }) diff --git a/packages/client/src/methods/messages/send-copy.ts b/packages/client/src/methods/messages/send-copy.ts index c8a7b705..d9cd8d75 100644 --- a/packages/client/src/methods/messages/send-copy.ts +++ b/packages/client/src/methods/messages/send-copy.ts @@ -6,9 +6,82 @@ import { getMessages } from './get-messages' import { sendMedia } from './send-media' import { sendText } from './send-text' +// @exported +export interface SendCopyParams { + /** Target chat ID */ + toChatId: InputPeerLike + + /** + * Whether to send this message silently. + */ + silent?: boolean + + /** + * If set, the message will be scheduled to this date. + * When passing a number, a UNIX time in ms is expected. + * + * You can also pass `0x7FFFFFFE`, this will send the message + * once the peer is online + */ + schedule?: Date | number + + /** + * New message caption (only used for media) + */ + caption?: string | FormattedString + + /** + * Parse mode to use to parse `text` entities before sending + * the message. Defaults to current default parse mode (if any). + * + * Passing `null` will explicitly disable formatting. + */ + parseMode?: string | null + + /** + * Message to reply to. Either a message object or message ID. + * + * For forums - can also be an ID of the topic (i.e. its top message ID) + */ + replyTo?: number | Message + + /** + * Whether to throw an error if {@link replyTo} + * message does not exist. + * + * If that message was not found, `NotFoundError` is thrown, + * with `text` set to `MESSAGE_NOT_FOUND`. + * + * Incurs an additional request, so only use when really needed. + * + * Defaults to `false` + */ + mustReply?: boolean + + /** + * List of formatting entities to use instead of parsing via a + * parse mode. + * + * **Note:** Passing this makes the method ignore {@link parseMode} + */ + entities?: tl.TypeMessageEntity[] + + /** + * For bots: inline or reply markup or an instruction + * to hide a reply keyboard or to force a reply. + */ + replyMarkup?: ReplyMarkup + + /** + * Whether to clear draft after sending this message. + * + * Defaults to `false` + */ + clearDraft?: boolean +} + /** - * Copy a message (i.e. send the same message, - * but do not forward it). + * Copy a message (i.e. send the same message, but do not forward it). * * Note that if the message contains a webpage, * it will be copied simply as a text message, @@ -19,90 +92,31 @@ import { sendText } from './send-text' */ export async function sendCopy( client: BaseTelegramClient, - params: { - /** Source chat ID */ - fromChatId: InputPeerLike - /** Target chat ID */ - toChatId: InputPeerLike - /** Message ID to forward */ - message: number - /** - * Whether to send this message silently. - */ - silent?: boolean - - /** - * If set, the message will be scheduled to this date. - * When passing a number, a UNIX time in ms is expected. - * - * You can also pass `0x7FFFFFFE`, this will send the message - * once the peer is online - */ - schedule?: Date | number - - /** - * New message caption (only used for media) - */ - caption?: string | FormattedString - - /** - * Parse mode to use to parse `text` entities before sending - * the message. Defaults to current default parse mode (if any). - * - * Passing `null` will explicitly disable formatting. - */ - parseMode?: string | null - - /** - * Message to reply to. Either a message object or message ID. - * - * For forums - can also be an ID of the topic (i.e. its top message ID) - */ - replyTo?: number | Message - - /** - * Whether to throw an error if {@link replyTo} - * message does not exist. - * - * If that message was not found, `NotFoundError` is thrown, - * with `text` set to `MESSAGE_NOT_FOUND`. - * - * Incurs an additional request, so only use when really needed. - * - * Defaults to `false` - */ - mustReply?: boolean - - /** - * List of formatting entities to use instead of parsing via a - * parse mode. - * - * **Note:** Passing this makes the method ignore {@link parseMode} - */ - entities?: tl.TypeMessageEntity[] - - /** - * For bots: inline or reply markup or an instruction - * to hide a reply keyboard or to force a reply. - */ - replyMarkup?: ReplyMarkup - - /** - * Whether to clear draft after sending this message. - * - * Defaults to `false` - */ - clearDraft?: boolean - }, + params: SendCopyParams & + ( + | { + /** Source chat ID */ + fromChatId: InputPeerLike + /** Message ID to forward */ + message: number + } + | { message: Message } + ), ): Promise { - const { fromChatId, toChatId, message, ...rest } = params + const { toChatId, ...rest } = params - const fromPeer = await resolvePeer(client, fromChatId) + let msg - const msg = await getMessages(client, fromPeer, message) + if ('fromChatId' in params) { + const fromPeer = await resolvePeer(client, params.fromChatId) - if (!msg) { - throw new MtMessageNotFoundError(getMarkedPeerId(fromPeer), message, 'to copy') + ;[msg] = await getMessages(client, fromPeer, params.message) + + if (!msg) { + throw new MtMessageNotFoundError(getMarkedPeerId(fromPeer), params.message, 'to copy') + } + } else { + msg = params.message } if (msg.raw._ === 'messageService') { diff --git a/packages/client/src/methods/messages/send-reaction.ts b/packages/client/src/methods/messages/send-reaction.ts index 9ebc2181..51ede864 100644 --- a/packages/client/src/methods/messages/send-reaction.ts +++ b/packages/client/src/methods/messages/send-reaction.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike, InputReaction, Message, normalizeInputReaction } from '../../types' +import { InputMessageId, InputReaction, Message, normalizeInputMessageId, normalizeInputReaction } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { resolvePeer } from '../users/resolve-peer' import { _findMessageInUpdate } from './find-in-update' @@ -12,18 +12,15 @@ import { _findMessageInUpdate } from './find-in-update' */ export async function sendReaction( client: BaseTelegramClient, - params: { - /** Chat ID with the message to react to */ - chatId: InputPeerLike - /** Message ID to react to */ - message: number + params: InputMessageId & { /** Reaction emoji (or `null` to remove reaction) */ emoji?: InputReaction | null /** Whether to use a big reaction */ big?: boolean }, ): Promise { - const { chatId, message, emoji, big } = params + const { emoji, big } = params + const { chatId, message } = normalizeInputMessageId(params) const reaction = normalizeInputReaction(emoji) diff --git a/packages/client/src/methods/messages/send-scheduled.ts b/packages/client/src/methods/messages/send-scheduled.ts index 1e924dd8..aed5fa64 100644 --- a/packages/client/src/methods/messages/send-scheduled.ts +++ b/packages/client/src/methods/messages/send-scheduled.ts @@ -4,18 +4,6 @@ import { InputPeerLike, Message, PeersIndex } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { resolvePeer } from '../users/resolve-peer' -/** - * Send s previously scheduled message. - * - * Note that if the message belongs to a media group, - * the entire group will be sent, but only - * the first message will be returned (in this overload). - * - * @param peer Chat where the messages were scheduled - * @param id ID of the message - */ -export async function sendScheduled(client: BaseTelegramClient, peer: InputPeerLike, id: number): Promise - /** * Send previously scheduled message(s) * @@ -26,21 +14,17 @@ export async function sendScheduled(client: BaseTelegramClient, peer: InputPeerL * @param peer Chat where the messages were scheduled * @param ids ID(s) of the messages */ -export async function sendScheduled(client: BaseTelegramClient, peer: InputPeerLike, ids: number[]): Promise - -/** @internal */ export async function sendScheduled( client: BaseTelegramClient, peer: InputPeerLike, ids: MaybeArray, -): Promise> { - const isSingle = !Array.isArray(ids) - if (isSingle) ids = [ids as number] +): Promise { + if (!Array.isArray(ids)) ids = [ids] const res = await client.call({ _: 'messages.sendScheduledMessages', peer: await resolvePeer(client, peer), - id: ids as number[], + id: ids, }) assertIsUpdatesGroup('sendScheduled', res) @@ -55,5 +39,5 @@ export async function sendScheduled( ) .map((u) => new Message(u.message, peers)) - return isSingle ? msgs[0] : msgs + return msgs } diff --git a/packages/client/src/methods/messages/send-vote.ts b/packages/client/src/methods/messages/send-vote.ts index d3f74492..7a5b7eec 100644 --- a/packages/client/src/methods/messages/send-vote.ts +++ b/packages/client/src/methods/messages/send-vote.ts @@ -1,7 +1,7 @@ import { BaseTelegramClient, getMarkedPeerId, MaybeArray, MtArgumentError, MtTypeAssertionError } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { InputPeerLike, MtMessageNotFoundError, PeersIndex, Poll } from '../../types' +import { InputMessageId, MtMessageNotFoundError, normalizeInputMessageId, PeersIndex, Poll } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { resolvePeer } from '../users/resolve-peer' import { getMessages } from './get-messages' @@ -11,11 +11,7 @@ import { getMessages } from './get-messages' */ export async function sendVote( client: BaseTelegramClient, - params: { - /** Chat ID where this poll was found */ - chatId: InputPeerLike - /** Message ID where this poll was found */ - message: number + params: InputMessageId & { /** * Selected options, or `null` to retract. * You can pass indexes of the answers or the `Buffer`s @@ -25,7 +21,7 @@ export async function sendVote( options: null | MaybeArray }, ): Promise { - const { chatId, message } = params + const { chatId, message } = normalizeInputMessageId(params) let { options } = params if (options === null) options = [] @@ -36,7 +32,7 @@ export async function sendVote( let poll: Poll | undefined = undefined if (options.some((it) => typeof it === 'number')) { - const msg = await getMessages(client, peer, message) + const [msg] = await getMessages(client, peer, message) if (!msg) { throw new MtMessageNotFoundError(getMarkedPeerId(peer), message, 'to vote in') diff --git a/packages/client/src/methods/messages/translate-message.ts b/packages/client/src/methods/messages/translate-message.ts index ca0c6fda..5bb76794 100644 --- a/packages/client/src/methods/messages/translate-message.ts +++ b/packages/client/src/methods/messages/translate-message.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike, MessageEntity } from '../../types' +import { InputMessageId, MessageEntity, normalizeInputMessageId } from '../../types' import { resolvePeer } from '../users/resolve-peer' /** @@ -10,21 +10,18 @@ import { resolvePeer } from '../users/resolve-peer' */ export async function translateMessage( client: BaseTelegramClient, - params: { - /** Chat or user ID */ - chatId: InputPeerLike - /** Identifier of the message to translate */ - messageId: number + params: InputMessageId & { /** Target language (two-letter ISO 639-1 language code) */ toLanguage: string }, ): Promise<[string, MessageEntity[]] | null> { - const { chatId, messageId, toLanguage } = params + const { toLanguage } = params + const { chatId, message } = normalizeInputMessageId(params) const res = await client.call({ _: 'messages.translateText', peer: await resolvePeer(client, chatId), - id: [messageId], + id: [message], toLang: toLanguage, }) diff --git a/packages/client/src/methods/messages/unpin-message.ts b/packages/client/src/methods/messages/unpin-message.ts index d323e3ba..3b392ce5 100644 --- a/packages/client/src/methods/messages/unpin-message.ts +++ b/packages/client/src/methods/messages/unpin-message.ts @@ -1,6 +1,6 @@ import { BaseTelegramClient } from '@mtcute/core' -import { InputPeerLike } from '../../types' +import { InputMessageId, normalizeInputMessageId } from '../../types' import { resolvePeer } from '../users/resolve-peer' /** @@ -12,15 +12,13 @@ import { resolvePeer } from '../users/resolve-peer' * @param chatId Chat ID, username, phone number, `"self"` or `"me"` * @param messageId Message ID */ -export async function unpinMessage( - client: BaseTelegramClient, - chatId: InputPeerLike, - messageId: number, -): Promise { +export async function unpinMessage(client: BaseTelegramClient, params: InputMessageId): Promise { + const { chatId, message } = normalizeInputMessageId(params) + const res = await client.call({ _: 'messages.updatePinnedMessage', peer: await resolvePeer(client, chatId), - id: messageId, + id: message, unpin: true, }) diff --git a/packages/client/src/methods/stories/get-stories-by-id.ts b/packages/client/src/methods/stories/get-stories-by-id.ts index 6d864293..beb01724 100644 --- a/packages/client/src/methods/stories/get-stories-by-id.ts +++ b/packages/client/src/methods/stories/get-stories-by-id.ts @@ -5,40 +5,22 @@ import { InputPeerLike, PeersIndex, Story } from '../../types' import { resolvePeer } from '../users/resolve-peer' /** - * Get a single story by its ID - * - * @param peerId Peer ID whose stories to fetch - * @param storyId Story ID - */ -export async function getStoriesById(client: BaseTelegramClient, peerId: InputPeerLike, storyId: number): Promise - -/** - * Get multiple stories by their IDs + * Get one or more stories by their IDs * * @param peerId Peer ID whose stories to fetch * @param storyIds Story IDs */ -export async function getStoriesById( - client: BaseTelegramClient, - peerId: InputPeerLike, - storyIds: number[], -): Promise - -/** - * @internal - */ export async function getStoriesById( client: BaseTelegramClient, peerId: InputPeerLike, storyIds: MaybeArray, -): Promise> { - const single = !Array.isArray(storyIds) - if (single) storyIds = [storyIds as number] +): Promise { + if (!Array.isArray(storyIds)) storyIds = [storyIds] const res = await client.call({ _: 'stories.getStoriesByID', peer: await resolvePeer(client, peerId), - id: storyIds as number[], + id: storyIds, }) const peers = PeersIndex.from(res) @@ -49,5 +31,5 @@ export async function getStoriesById( return new Story(it, peers) }) - return single ? stories[0] : stories + return stories } diff --git a/packages/client/src/methods/stories/get-stories-interactions.ts b/packages/client/src/methods/stories/get-stories-interactions.ts index f377b83f..38ea708d 100644 --- a/packages/client/src/methods/stories/get-stories-interactions.ts +++ b/packages/client/src/methods/stories/get-stories-interactions.ts @@ -3,46 +3,27 @@ import { BaseTelegramClient, MaybeArray } from '@mtcute/core' import { InputPeerLike, PeersIndex, StoryInteractions } from '../../types' import { resolvePeer } from '../users/resolve-peer' -/** - * Get brief information about story interactions. - */ -export async function getStoriesInteractions( - client: BaseTelegramClient, - peerId: InputPeerLike, - storyId: number, -): Promise - /** * Get brief information about stories interactions. * * The result will be in the same order as the input IDs */ -export async function getStoriesInteractions( - client: BaseTelegramClient, - peerId: InputPeerLike, - storyIds: number[], -): Promise - -/** - * @internal - */ export async function getStoriesInteractions( client: BaseTelegramClient, peerId: InputPeerLike, storyIds: MaybeArray, -): Promise> { - const isSingle = !Array.isArray(storyIds) - if (isSingle) storyIds = [storyIds as number] +): Promise { + if (!Array.isArray(storyIds)) storyIds = [storyIds] const res = await client.call({ _: 'stories.getStoriesViews', peer: await resolvePeer(client, peerId), - id: storyIds as number[], + id: storyIds, }) const peers = PeersIndex.from(res) const infos = res.views.map((it) => new StoryInteractions(it, peers)) - return isSingle ? infos[0] : infos + return infos } diff --git a/packages/client/src/methods/users/resolve-peer.ts b/packages/client/src/methods/users/resolve-peer.ts index 7342c882..929da3da 100644 --- a/packages/client/src/methods/users/resolve-peer.ts +++ b/packages/client/src/methods/users/resolve-peer.ts @@ -7,7 +7,6 @@ import { tl, toggleChannelIdMark, } from '@mtcute/core' -import { assertTypeIs } from '@mtcute/core/utils' import { MtPeerNotFoundError } from '../../types/errors' import { InputPeerLike } from '../../types/peers' @@ -26,10 +25,13 @@ export async function resolvePeer( peerId: InputPeerLike, force = false, ): Promise { - // for convenience we also accept tl objects directly + // for convenience we also accept tl and User/Chat objects directly if (typeof peerId === 'object') { if (tl.isAnyPeer(peerId)) { peerId = getMarkedPeerId(peerId) + } else if ('type' in peerId) { + // User | Chat + return peerId.inputPeer } else { return normalizeToInputPeer(peerId) } @@ -45,29 +47,17 @@ export async function resolvePeer( peerId = peerId.replace(/[@+\s()]/g, '') + let res + if (peerId.match(/^\d+$/)) { // phone number const fromStorage = await client.storage.getPeerByPhone(peerId) if (fromStorage) return fromStorage - const res = await client.call({ - _: 'contacts.getContacts', - hash: Long.ZERO, + res = await client.call({ + _: 'contacts.resolvePhone', + phone: peerId, }) - - assertTypeIs('contacts.getContacts', res, 'contacts.contacts') - - const found = res.users.find((it) => (it as tl.RawUser).phone === peerId) - - if (found && found._ === 'user') { - return { - _: 'inputPeerUser', - userId: found.id, - accessHash: found.accessHash!, - } - } - - throw new MtPeerNotFoundError(`Could not find a peer by phone ${peerId}`) } else { // username if (!force) { @@ -75,64 +65,62 @@ export async function resolvePeer( if (fromStorage) return fromStorage } - const res = await client.call({ + res = await client.call({ _: 'contacts.resolveUsername', username: peerId, }) - - if (res.peer._ === 'peerUser') { - const id = res.peer.userId - - const found = res.users.find((it) => it.id === id) - - if (found && found._ === 'user') { - if (!found.accessHash) { - // no access hash, we can't use it - // this may happen when bot resolves a username - // of a user who hasn't started a conversation with it - throw new MtPeerNotFoundError( - `Peer (user) with username ${peerId} was found, but it has no access hash`, - ) - } - - return { - _: 'inputPeerUser', - userId: found.id, - accessHash: found.accessHash, - } - } - } else if (res.peer._ === 'peerChannel') { - const id = res.peer.channelId - const found = res.chats.find((it) => it.id === id) - - if (found) { - if (!(found._ === 'channel' || found._ === 'channelForbidden')) { - // chats can't have usernames - // furthermore, our id is a channel id, so it must be a channel - // this should never happen, unless Telegram goes crazy - throw new MtTypeAssertionError('contacts.resolveUsername#chats', 'channel', found._) - } - - if (!found.accessHash) { - // shouldn't happen? but just in case - throw new MtPeerNotFoundError( - `Peer (channel) with username ${peerId} was found, but it has no access hash`, - ) - } - - return { - _: 'inputPeerChannel', - channelId: found.id, - accessHash: found.accessHash, - } - } - } else { - // chats can't have usernames - throw new MtTypeAssertionError('contacts.resolveUsername', 'user or channel', res.peer._) - } - - throw new MtPeerNotFoundError(`Could not find a peer by username ${peerId}`) } + + if (res.peer._ === 'peerUser') { + const id = res.peer.userId + + const found = res.users.find((it) => it.id === id) + + if (found && found._ === 'user') { + if (!found.accessHash) { + // no access hash, we can't use it + // this may happen when bot resolves a username + // of a user who hasn't started a conversation with it + throw new MtPeerNotFoundError( + `Peer (user) with username ${peerId} was found, but it has no access hash`, + ) + } + + return { + _: 'inputPeerUser', + userId: found.id, + accessHash: found.accessHash, + } + } + } else if (res.peer._ === 'peerChannel') { + const id = res.peer.channelId + const found = res.chats.find((it) => it.id === id) + + if (found) { + if (!(found._ === 'channel' || found._ === 'channelForbidden')) { + // chats can't have usernames + // furthermore, our id is a channel id, so it must be a channel + // this should never happen, unless Telegram goes crazy + throw new MtTypeAssertionError('contacts.resolveUsername#chats', 'channel', found._) + } + + if (!found.accessHash) { + // shouldn't happen? but just in case + throw new MtPeerNotFoundError(`Peer (channel) with ${peerId} was found, but it has no access hash`) + } + + return { + _: 'inputPeerChannel', + channelId: found.id, + accessHash: found.accessHash, + } + } + } else { + // chats can't have usernames + throw new MtTypeAssertionError('contacts.resolveUsername', 'user or channel', res.peer._) + } + + throw new MtPeerNotFoundError(`Could not find a peer by ${peerId}`) } const peerType = getBasicPeerType(peerId) @@ -171,25 +159,10 @@ export async function resolvePeer( break } case 'chat': { - // do we really need to make a call? - // const id = -peerId - // const res = await client.call({ - // _: 'messages.getChats', - // id: [id], - // }) - // - // const found = res.chats.find((it) => it.id === id) - // if (found && (found._ === 'chat' || found._ === 'chatForbidden')) - // return { - // _: 'inputPeerChat', - // chatId: found.id - // } - return { _: 'inputPeerChat', chatId: -peerId, } - // break } case 'channel': { const id = toggleChannelIdMark(peerId) diff --git a/packages/client/src/types/messages/index.ts b/packages/client/src/types/messages/index.ts index 987edf14..4e010729 100644 --- a/packages/client/src/types/messages/index.ts +++ b/packages/client/src/types/messages/index.ts @@ -1,5 +1,6 @@ export * from './dialog' export * from './draft-message' +export * from './input-message-id' export * from './message' export * from './message-action' export * from './message-entity' diff --git a/packages/client/src/types/messages/input-message-id.ts b/packages/client/src/types/messages/input-message-id.ts new file mode 100644 index 00000000..1843bbca --- /dev/null +++ b/packages/client/src/types/messages/input-message-id.ts @@ -0,0 +1,16 @@ +import type { InputPeerLike } from '../peers' +import type { Message } from './message' + +/** + * Parameters for methods that accept a message + * + * Either a message object (in `message` field), or a chat ID and a message ID + */ +export type InputMessageId = { chatId: InputPeerLike; message: number } | { message: Message } + +/** @internal */ +export function normalizeInputMessageId(id: InputMessageId) { + if ('chatId' in id) return id + + return { chatId: id.message.chat.inputPeer, message: id.message.id } +} diff --git a/packages/client/src/types/misc/sticker-set.ts b/packages/client/src/types/misc/sticker-set.ts index 75be987e..1ed338c6 100644 --- a/packages/client/src/types/misc/sticker-set.ts +++ b/packages/client/src/types/misc/sticker-set.ts @@ -12,6 +12,7 @@ import { parseDocument } from '../media/document-utils' * Can be one of: * - Raw TL object * - Sticker set short name + * - {@link StickerSet} object * - `{ dice: "" }` (e.g. `{ dice: "🎲" }`) - Used for fetching animated dice stickers * - `{ system: string }` - for system stickersets: * - `"animated"` - Animated emojis stickerset @@ -35,6 +36,7 @@ export type InputStickerSet = | 'default_statuses' | 'default_topic_icons' } + | StickerSet | string export function normalizeInputStickerSet(input: InputStickerSet): tl.TypeInputStickerSet { @@ -45,6 +47,7 @@ export function normalizeInputStickerSet(input: InputStickerSet): tl.TypeInputSt } } if ('_' in input) return input + if (input instanceof StickerSet) return input.inputStickerSet if ('dice' in input) { return { diff --git a/packages/client/src/types/peers/index.ts b/packages/client/src/types/peers/index.ts index 1bc49584..20afb4e1 100644 --- a/packages/client/src/types/peers/index.ts +++ b/packages/client/src/types/peers/index.ts @@ -1,5 +1,8 @@ import { tl } from '@mtcute/core' +import { Chat } from './chat' +import { User } from './user' + export * from './chat' export * from './chat-event' export * from './chat-invite-link' @@ -21,15 +24,23 @@ export * from './user' export type PeerType = 'user' | 'bot' | 'group' | 'channel' | 'supergroup' /** - * Type that can be used as an input peer - * to most of the high-level methods. Can be: + * Type that can be used as an input peer to most of the high-level methods. Can be: * - `number`, representing peer's marked ID* - * - `string`, representing peer's username (w/out preceding `@`) - * - `string`, representing user's phone number (only for contacts) + * - `string`, representing peer's username (without preceding `@`) + * - `string`, representing user's phone number * - `"me"` and `"self"` which will be replaced with the current user/bot + * - `Chat` or `User` object * - Raw TL object * - * > Telegram has moved to int64 IDs. Though, Levin [has confirmed](https://t.me/tdlibchat/25075) + * > * Telegram has moved to int64 IDs. Though, Levin [has confirmed](https://t.me/tdlibchat/25071) * > that new IDs *will* still fit into int53, meaning JS integers are fine. */ -export type InputPeerLike = string | number | tl.TypePeer | tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel +export type InputPeerLike = + | string + | number + | tl.TypePeer + | tl.TypeInputPeer + | tl.TypeInputUser + | tl.TypeInputChannel + | Chat + | User