feat: accept high-level objects as inputs to methods

This commit is contained in:
alina 🌸 2023-10-09 21:44:38 +03:00 committed by Alina Tumanova
parent a03d73503a
commit e01c876690
45 changed files with 734 additions and 1233 deletions

File diff suppressed because it is too large Load diff

View file

@ -42,6 +42,7 @@ import {
InputFileLike,
InputInlineResult,
InputMediaLike,
InputMessageId,
InputPeerLike,
InputPrivacyRule,
InputReaction,

View file

@ -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,

View file

@ -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,

View file

@ -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,
})

View file

@ -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<GameHighScore[]> {
const { chatId, message, userId } = params
const { userId } = params
const { chatId, message } = normalizeInputMessageId(params)
const chat = await resolvePeer(client, chatId)

View file

@ -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<Message> {
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)

View file

@ -11,6 +11,7 @@ import {
} from '../../utils/peer-utils'
import { resolvePeer } from '../users/resolve-peer'
// @available=both
/**
* Get basic information about a chat.
*

View file

@ -11,6 +11,7 @@ import {
} from '../../utils/peer-utils'
import { resolvePeer } from '../users/resolve-peer'
// @available=both
/**
* Get full information about a chat.
*

View file

@ -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<void> {
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))

View file

@ -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,
})

View file

@ -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,
})
}

View file

@ -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,
})

View file

@ -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,
})
}

View file

@ -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,

View file

@ -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,

View file

@ -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<void> {
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,
})
}

View file

@ -16,12 +16,12 @@ import { resolvePeer } from '../users/resolve-peer'
export async function revokeInviteLink(
client: BaseTelegramClient,
chatId: InputPeerLike,
link: string,
link: string | ChatInviteLink,
): Promise<ChatInviteLink> {
const res = await client.call({
_: 'messages.editExportedChatInvite',
peer: await resolvePeer(client, chatId),
link,
link: typeof link === 'string' ? link : link.link,
revoked: true,
})

View file

@ -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<Poll> {
const { chatId, message } = params
export async function closePoll(client: BaseTelegramClient, params: InputMessageId): Promise<Poll> {
const { chatId, message } = normalizeInputMessageId(params)
const res = await client.call({
_: 'messages.editMessage',

View file

@ -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<number>,
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<void> {
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<void> {
if (messages.length === 1) {
return deleteMessagesById(client, messages[0].chat.inputPeer, [messages[0].id], params)
}
const byChat = new Map<number, [tl.TypeInputPeer, number[]]>()
const byChatScheduled = new Map<number, [tl.TypeInputPeer, number[]]>()
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)
}
}

View file

@ -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<number>,
ids: number[],
): Promise<void> {
if (!Array.isArray(ids)) ids = [ids]
const peer = await resolvePeer(client, chatId)
const res = await client.call({

View file

@ -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<Message> {
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),

View file

@ -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<string>
/**
* 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<string>
/**
* 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<Message>
/**
* 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<string>
/**
* 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<MaybeArray<Message>>
/** @internal */
export async function forwardMessages(
client: BaseTelegramClient,
params: {
toChatId: InputPeerLike
fromChatId: InputPeerLike
messages: MaybeArray<number>
parseMode?: string | null
entities?: tl.TypeMessageEntity[]
silent?: boolean
schedule?: Date | number
clearDraft?: boolean
noAuthor?: boolean
noCaption?: boolean
forbidForwards?: boolean
sendAs?: InputPeerLike
},
): Promise<MaybeArray<Message>> {
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<Message[]> {
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<Message[]> {
const { messages, ...rest } = params
return forwardMessagesById(client, {
...rest,
fromChatId: messages[0].chat.inputPeer,
messages: messages.map((it) => it.id),
})
}

View file

@ -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<Message | null> {
const inputPeer = await resolvePeer(client, peer)
const { chatId, message } = normalizeInputMessageId(params)
const inputPeer = await resolvePeer(client, chatId)
const res = await client.call({
_: 'messages.getDiscussionMessage',

View file

@ -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<Message[]> {
export async function getMessageGroup(client: BaseTelegramClient, params: InputMessageId): Promise<Message[]> {
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,

View file

@ -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<MessageReactions | null>
/**
* 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<number>,
): Promise<MaybeArray<MessageReactions | null>> {
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),
)
}

View file

@ -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<Message | null>
/**
* 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<number>,
fromReply = false,
): Promise<MaybeArray<Message | null>> {
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
}

View file

@ -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<Message | null>
// @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<number>,
fromReply = false,
): Promise<MaybeArray<Message | null>> {
): 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
}

View file

@ -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<ArrayPaginated<PeerReaction, GetReactionUsersOffset>> {
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,

View file

@ -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<Message | null>
/**
* 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<number>,
): Promise<MaybeArray<Message | null>> {
): 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
}

View file

@ -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<typeof getReactionUsers>[3] & {
params: Parameters<typeof getReactionUsers>[1] & {
/**
* Limit the number of events returned.
*
@ -33,8 +31,7 @@ export async function* iterReactionUsers(
chunkSize?: number
},
): AsyncIterableIterator<PeerReaction> {
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,

View file

@ -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<void> {
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,
})

View file

@ -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<string>
/**
* 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<string>
/**
* 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<Message> {
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') {

View file

@ -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<Message> {
const { chatId, message, emoji, big } = params
const { emoji, big } = params
const { chatId, message } = normalizeInputMessageId(params)
const reaction = normalizeInputReaction(emoji)

View file

@ -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<Message>
/**
* 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<Message[]>
/** @internal */
export async function sendScheduled(
client: BaseTelegramClient,
peer: InputPeerLike,
ids: MaybeArray<number>,
): Promise<MaybeArray<Message>> {
const isSingle = !Array.isArray(ids)
if (isSingle) ids = [ids as number]
): Promise<Message[]> {
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
}

View file

@ -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<number | Buffer>
},
): Promise<Poll> {
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')

View file

@ -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,
})

View file

@ -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<void> {
export async function unpinMessage(client: BaseTelegramClient, params: InputMessageId): Promise<void> {
const { chatId, message } = normalizeInputMessageId(params)
const res = await client.call({
_: 'messages.updatePinnedMessage',
peer: await resolvePeer(client, chatId),
id: messageId,
id: message,
unpin: true,
})

View file

@ -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<Story>
/**
* 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<Story[]>
/**
* @internal
*/
export async function getStoriesById(
client: BaseTelegramClient,
peerId: InputPeerLike,
storyIds: MaybeArray<number>,
): Promise<MaybeArray<Story>> {
const single = !Array.isArray(storyIds)
if (single) storyIds = [storyIds as number]
): Promise<Story[]> {
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
}

View file

@ -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<StoryInteractions>
/**
* 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<StoryInteractions[]>
/**
* @internal
*/
export async function getStoriesInteractions(
client: BaseTelegramClient,
peerId: InputPeerLike,
storyIds: MaybeArray<number>,
): Promise<MaybeArray<StoryInteractions>> {
const isSingle = !Array.isArray(storyIds)
if (isSingle) storyIds = [storyIds as number]
): Promise<StoryInteractions[]> {
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
}

View file

@ -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<tl.TypeInputPeer> {
// 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)

View file

@ -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'

View file

@ -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 }
}

View file

@ -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: "<emoji>" }` (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 {

View file

@ -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