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, InputFileLike,
InputInlineResult, InputInlineResult,
InputMediaLike, InputMediaLike,
InputMessageId,
InputPeerLike, InputPeerLike,
InputPrivacyRule, InputPrivacyRule,
InputReaction, InputReaction,

View file

@ -1,14 +1,16 @@
import { BaseTelegramClient, Long } from '@mtcute/core' import { BaseTelegramClient, Long } from '@mtcute/core'
import { CallbackQuery } from '../../types/bots/callback-query'
/** /**
* Send an answer to a 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 * @param params Parameters of the answer
*/ */
export async function answerCallbackQuery( export async function answerCallbackQuery(
client: BaseTelegramClient, client: BaseTelegramClient,
queryId: Long, queryId: Long | CallbackQuery,
params?: { params?: {
/** /**
* Maximum amount of time in seconds for which * Maximum amount of time in seconds for which
@ -49,7 +51,7 @@ export async function answerCallbackQuery(
await client.call({ await client.call({
_: 'messages.setBotCallbackAnswer', _: 'messages.setBotCallbackAnswer',
queryId, queryId: Long.isLong(queryId) ? queryId : queryId.id,
cacheTime, cacheTime,
alert, alert,
message: text, 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. * Answer an inline query.
@ -11,7 +11,7 @@ import { BotInline, InputInlineResult } from '../../types'
*/ */
export async function answerInlineQuery( export async function answerInlineQuery(
client: BaseTelegramClient, client: BaseTelegramClient,
queryId: tl.Long, queryId: tl.Long | InlineQuery,
results: InputInlineResult[], results: InputInlineResult[],
params?: { params?: {
/** /**
@ -99,7 +99,7 @@ export async function answerInlineQuery(
await client.call({ await client.call({
_: 'messages.setInlineBotResults', _: 'messages.setInlineBotResults',
queryId, queryId: Long.isLong(queryId) ? queryId : queryId.id,
results: tlResults, results: tlResults,
cacheTime, cacheTime,
gallery: gallery ?? defaultGallery, 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. * Answer a pre-checkout query.
@ -7,7 +9,7 @@ import { BaseTelegramClient, tl } from '@mtcute/core'
*/ */
export async function answerPreCheckoutQuery( export async function answerPreCheckoutQuery(
client: BaseTelegramClient, client: BaseTelegramClient,
queryId: tl.Long, queryId: tl.Long | PreCheckoutQuery,
params?: { params?: {
/** If pre-checkout is rejected, error message to show to the user */ /** If pre-checkout is rejected, error message to show to the user */
error?: string error?: string
@ -17,7 +19,7 @@ export async function answerPreCheckoutQuery(
await client.call({ await client.call({
_: 'messages.setBotPrecheckoutResults', _: 'messages.setBotPrecheckoutResults',
queryId, queryId: Long.isLong(queryId) ? queryId : queryId.queryId,
success: !error, success: !error,
error, error,
}) })

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient, tl } from '@mtcute/core' 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 { normalizeInlineId } from '../../utils/inline-utils'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
@ -10,18 +10,13 @@ import { resolvePeer } from '../users/resolve-peer'
*/ */
export async function getGameHighScores( export async function getGameHighScores(
client: BaseTelegramClient, client: BaseTelegramClient,
params: { params: InputMessageId & {
/** 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 */ /** ID of the user to find high scores for */
userId?: InputPeerLike userId?: InputPeerLike
}, },
): Promise<GameHighScore[]> { ): Promise<GameHighScore[]> {
const { chatId, message, userId } = params const { userId } = params
const { chatId, message } = normalizeInputMessageId(params)
const chat = await resolvePeer(client, chatId) const chat = await resolvePeer(client, chatId)

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient, tl } from '@mtcute/core' 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 { normalizeInlineId } from '../../utils/inline-utils'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
import { _findMessageInUpdate } from '../messages/find-in-update' import { _findMessageInUpdate } from '../messages/find-in-update'
@ -14,13 +14,7 @@ import { resolvePeer } from '../users/resolve-peer'
*/ */
export async function setGameScore( export async function setGameScore(
client: BaseTelegramClient, client: BaseTelegramClient,
params: { params: InputMessageId & {
/** Chat where the game was found */
chatId: InputPeerLike
/** ID of the message where the game was found */
message: number
/** ID of the user who has scored */ /** ID of the user who has scored */
userId: InputPeerLike userId: InputPeerLike
@ -40,7 +34,8 @@ export async function setGameScore(
force?: boolean force?: boolean
}, },
): Promise<Message> { ): 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 user = normalizeToInputUser(await resolvePeer(client, userId), userId)
const chat = await resolvePeer(client, chatId) const chat = await resolvePeer(client, chatId)

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import { BaseTelegramClient } from '@mtcute/core' import { BaseTelegramClient } from '@mtcute/core'
import { assertTypeIsNot } from '@mtcute/core/utils' import { assertTypeIsNot } from '@mtcute/core/utils'
import { InputPeerLike } from '../../types' import type { ForumTopic, InputPeerLike } from '../../types'
import { normalizeToInputChannel } from '../../utils/peer-utils' import { normalizeToInputChannel } from '../../utils/peer-utils'
import { createDummyUpdate } from '../../utils/updates-utils' import { createDummyUpdate } from '../../utils/updates-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
@ -15,7 +15,7 @@ import { resolvePeer } from '../users/resolve-peer'
export async function deleteForumTopicHistory( export async function deleteForumTopicHistory(
client: BaseTelegramClient, client: BaseTelegramClient,
chat: InputPeerLike, chat: InputPeerLike,
topicId: number, topicId: number | ForumTopic,
): Promise<void> { ): Promise<void> {
const channel = normalizeToInputChannel(await resolvePeer(client, chat), chat) const channel = normalizeToInputChannel(await resolvePeer(client, chat), chat)
assertTypeIsNot('deleteForumTopicHistory', channel, 'inputChannelEmpty') assertTypeIsNot('deleteForumTopicHistory', channel, 'inputChannelEmpty')
@ -23,7 +23,7 @@ export async function deleteForumTopicHistory(
const res = await client.call({ const res = await client.call({
_: 'channels.deleteTopicHistory', _: 'channels.deleteTopicHistory',
channel, channel,
topMsgId: topicId, topMsgId: typeof topicId === 'number' ? topicId : topicId.id,
}) })
client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount, channel.channelId)) client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount, channel.channelId))

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient, Long, tl } from '@mtcute/core' 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 { normalizeToInputChannel } from '../../utils/peer-utils'
import { _findMessageInUpdate } from '../messages/find-in-update' import { _findMessageInUpdate } from '../messages/find-in-update'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
@ -19,9 +19,10 @@ export async function editForumTopic(
params: { params: {
/** Chat ID or username */ /** Chat ID or username */
chatId: InputPeerLike 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 * New topic title
*/ */
@ -41,7 +42,7 @@ export async function editForumTopic(
const res = await client.call({ const res = await client.call({
_: 'channels.editForumTopic', _: 'channels.editForumTopic',
channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId),
topicId, topicId: typeof topicId === 'number' ? topicId : topicId.id,
title, title,
iconEmojiId: icon ? icon ?? Long.ZERO : undefined, iconEmojiId: icon ? icon ?? Long.ZERO : undefined,
}) })

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient } from '@mtcute/core' import { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike } from '../../types' import type { ForumTopic, InputPeerLike } from '../../types'
import { normalizeToInputChannel } from '../../utils/peer-utils' import { normalizeToInputChannel } from '../../utils/peer-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
@ -18,7 +18,7 @@ export async function reorderPinnedForumTopics(
/** /**
* Order of the pinned topics * Order of the pinned topics
*/ */
order: number[] order: (number | ForumTopic)[]
/** /**
* Whether to un-pin topics not present in the order * Whether to un-pin topics not present in the order
@ -30,7 +30,7 @@ export async function reorderPinnedForumTopics(
await client.call({ await client.call({
_: 'channels.reorderPinnedForumTopics', _: 'channels.reorderPinnedForumTopics',
channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId),
order, order: order.map((it) => (typeof it === 'number' ? it : it.id)),
force, force,
}) })
} }

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient } from '@mtcute/core' import { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike, Message } from '../../types' import type { ForumTopic, InputPeerLike, Message } from '../../types'
import { normalizeToInputChannel } from '../../utils/peer-utils' import { normalizeToInputChannel } from '../../utils/peer-utils'
import { _findMessageInUpdate } from '../messages/find-in-update' import { _findMessageInUpdate } from '../messages/find-in-update'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
@ -19,7 +19,7 @@ export async function toggleForumTopicClosed(
chatId: InputPeerLike chatId: InputPeerLike
/** ID of the topic (i.e. its top message ID) */ /** ID of the topic (i.e. its top message ID) */
topicId: number topicId: number | ForumTopic
/** Whether the topic should be closed */ /** Whether the topic should be closed */
closed: boolean closed: boolean
@ -30,7 +30,7 @@ export async function toggleForumTopicClosed(
const res = await client.call({ const res = await client.call({
_: 'channels.editForumTopic', _: 'channels.editForumTopic',
channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId),
topicId, topicId: typeof topicId === 'number' ? topicId : topicId.id,
closed, closed,
}) })

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient } from '@mtcute/core' import { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike } from '../../types' import { ForumTopic, InputPeerLike } from '../../types'
import { normalizeToInputChannel } from '../../utils/peer-utils' import { normalizeToInputChannel } from '../../utils/peer-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
@ -15,7 +15,7 @@ export async function toggleForumTopicPinned(
/** Chat ID or username */ /** Chat ID or username */
chatId: InputPeerLike chatId: InputPeerLike
/** ID of the topic (i.e. its top message ID) */ /** ID of the topic (i.e. its top message ID) */
topicId: number topicId: number | ForumTopic
/** Whether the topic should be pinned */ /** Whether the topic should be pinned */
pinned: boolean pinned: boolean
}, },
@ -25,7 +25,7 @@ export async function toggleForumTopicPinned(
await client.call({ await client.call({
_: 'channels.updatePinnedForumTopic', _: 'channels.updatePinnedForumTopic',
channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId),
topicId, topicId: typeof topicId === 'number' ? topicId : topicId.id,
pinned, pinned,
}) })
} }

View file

@ -21,7 +21,7 @@ export async function editInviteLink(
/** Chat ID */ /** Chat ID */
chatId: InputPeerLike chatId: InputPeerLike
/** Invite link to edit */ /** Invite link to edit */
link: string link: string | ChatInviteLink
/** /**
* Date when this link will expire. * Date when this link will expire.
* If `number` is passed, UNIX time in ms is expected. * If `number` is passed, UNIX time in ms is expected.
@ -48,7 +48,7 @@ export async function editInviteLink(
const res = await client.call({ const res = await client.call({
_: 'messages.editExportedChatInvite', _: 'messages.editExportedChatInvite',
peer: await resolvePeer(client, chatId), peer: await resolvePeer(client, chatId),
link, link: typeof link === 'string' ? link : link.link,
expireDate: normalizeDate(expires), expireDate: normalizeDate(expires),
usageLimit, usageLimit,
requestNeeded: withApproval, requestNeeded: withApproval,

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient, tl } from '@mtcute/core' 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 { makeArrayPaginated, normalizeDate, normalizeToInputUser } from '../../utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
@ -18,7 +18,7 @@ export async function getInviteLinkMembers(
/** /**
* Invite link for which to get members * Invite link for which to get members
*/ */
link?: string link?: string | ChatInviteLink
/** /**
* Maximum number of users to return * Maximum number of users to return
@ -65,7 +65,7 @@ export async function getInviteLinkMembers(
_: 'messages.getChatInviteImporters', _: 'messages.getChatInviteImporters',
limit, limit,
peer, peer,
link, link: typeof link === 'string' ? link : link?.link,
requested, requested,
q: requestedSearch, q: requestedSearch,
offsetDate, offsetDate,

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient } from '@mtcute/core' import { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike } from '../../types' import type { ChatInviteLink, InputPeerLike } from '../../types'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
/** /**
@ -16,7 +16,7 @@ export async function hideAllJoinRequests(
action: 'approve' | 'deny' action: 'approve' | 'deny'
/** Invite link to target */ /** Invite link to target */
link?: string link?: string | ChatInviteLink
}, },
): Promise<void> { ): Promise<void> {
const { chatId, action, link } = params const { chatId, action, link } = params
@ -25,6 +25,6 @@ export async function hideAllJoinRequests(
_: 'messages.hideAllChatJoinRequests', _: 'messages.hideAllChatJoinRequests',
approved: action === 'approve', approved: action === 'approve',
peer: await resolvePeer(client, chatId), 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( export async function revokeInviteLink(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, chatId: InputPeerLike,
link: string, link: string | ChatInviteLink,
): Promise<ChatInviteLink> { ): Promise<ChatInviteLink> {
const res = await client.call({ const res = await client.call({
_: 'messages.editExportedChatInvite', _: 'messages.editExportedChatInvite',
peer: await resolvePeer(client, chatId), peer: await resolvePeer(client, chatId),
link, link: typeof link === 'string' ? link : link.link,
revoked: true, revoked: true,
}) })

View file

@ -1,7 +1,7 @@
import { BaseTelegramClient, Long, MtTypeAssertionError } from '@mtcute/core' import { BaseTelegramClient, Long, MtTypeAssertionError } from '@mtcute/core'
import { assertTypeIs } from '@mtcute/core/utils' 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 { assertIsUpdatesGroup } from '../../utils/updates-utils'
import { resolvePeer } from '../users/resolve-peer' 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 * Once closed, poll can't be re-opened, and nobody
* will be able to vote in it * will be able to vote in it
*/ */
export async function closePoll( export async function closePoll(client: BaseTelegramClient, params: InputMessageId): Promise<Poll> {
client: BaseTelegramClient, const { chatId, message } = normalizeInputMessageId(params)
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
const res = await client.call({ const res = await client.call({
_: 'messages.editMessage', _: '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 { isInputPeerChannel, normalizeToInputChannel } from '../../utils/peer-utils'
import { createDummyUpdate } from '../../utils/updates-utils' import { createDummyUpdate } from '../../utils/updates-utils'
import { resolvePeer } from '../users/resolve-peer' 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 chatId Chat's marked ID, its username, phone or `"me"` or `"self"`.
* @param ids Message(s) ID(s) to delete. * @param ids Message(s) ID(s) to delete.
*/ */
export async function deleteMessages( export async function deleteMessagesById(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, chatId: InputPeerLike,
ids: MaybeArray<number>, ids: number[],
params?: { params?: DeleteMessagesParams,
/**
* Whether to "revoke" (i.e. delete for both sides).
* Only used for chats and private chats.
*
* @default true
*/
revoke?: boolean
},
): Promise<void> { ): Promise<void> {
const { revoke = true } = params ?? {} const { revoke = true } = params ?? {}
if (!Array.isArray(ids)) ids = [ids]
const peer = await resolvePeer(client, chatId) const peer = await resolvePeer(client, chatId)
let upd let upd
@ -52,3 +54,38 @@ export async function deleteMessages(
client.network.handleUpdate(upd) 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 { InputPeerLike } from '../../types'
import { resolvePeer } from '../users/resolve-peer' 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 chatId Chat's marked ID, its username, phone or `"me"` or `"self"`.
* @param ids Message(s) ID(s) to delete. * @param ids Message(s) ID(s) to delete.
@ -12,10 +12,8 @@ import { resolvePeer } from '../users/resolve-peer'
export async function deleteScheduledMessages( export async function deleteScheduledMessages(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, chatId: InputPeerLike,
ids: MaybeArray<number>, ids: number[],
): Promise<void> { ): Promise<void> {
if (!Array.isArray(ids)) ids = [ids]
const peer = await resolvePeer(client, chatId) const peer = await resolvePeer(client, chatId)
const res = await client.call({ const res = await client.call({

View file

@ -1,6 +1,14 @@
import { BaseTelegramClient, tl } from '@mtcute/core' 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 { _normalizeInputMedia } from '../files/normalize-input-media'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
import { _findMessageInUpdate } from './find-in-update' import { _findMessageInUpdate } from './find-in-update'
@ -15,12 +23,7 @@ import { _parseEntities } from './parse-entities'
*/ */
export async function editMessage( export async function editMessage(
client: BaseTelegramClient, client: BaseTelegramClient,
params: { params: InputMessageId & {
/** Chat ID */
chatId: InputPeerLike
/** Message to edit */
message: number | Message
/** /**
* New message text * New message text
* *
@ -77,7 +80,7 @@ export async function editMessage(
progressCallback?: (uploaded: number, total: number) => void progressCallback?: (uploaded: number, total: number) => void
}, },
): Promise<Message> { ): Promise<Message> {
const { chatId, message } = params const { chatId, message } = normalizeInputMessageId(params)
let content: string | undefined = undefined let content: string | undefined = undefined
let entities: tl.TypeMessageEntity[] | undefined let entities: tl.TypeMessageEntity[] | undefined
let media: tl.TypeInputMedia | undefined = undefined let media: tl.TypeInputMedia | undefined = undefined
@ -103,7 +106,7 @@ export async function editMessage(
const res = await client.call({ const res = await client.call({
_: 'messages.editMessage', _: 'messages.editMessage',
id: typeof message === 'number' ? message : message.id, id: message,
peer: await resolvePeer(client, chatId), peer: await resolvePeer(client, chatId),
noWebpage: params.disableWebPreview, noWebpage: params.disableWebPreview,
replyMarkup: BotKeyboard._convertToTl(params.replyMarkup), 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 { randomLong } from '@mtcute/core/utils'
import { FormattedString, InputMediaLike, InputPeerLike, Message, PeersIndex } from '../../types' 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 { assertIsUpdatesGroup } from '../../utils/updates-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
/** // @exported
* Forward a single message. export interface ForwardMessageOptions {
* /** Destination chat ID, username, phone, `"me"` or `"self"` */
* To forward with a caption, use another overload that takes an array of IDs. toChatId: InputPeerLike
*
* @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
/** /**
* Optionally, a caption for your forwarded message(s). * Optionally, a caption for your forwarded message(s).
* It will be sent as a separate message before the forwarded messages. * It will be sent as a separate message before the forwarded messages.
* *
* You can either pass `caption` or `captionMedia`, passing both will * You can either pass `caption` or `captionMedia`, passing both will
* result in an error * result in an error
*/ */
caption?: string | FormattedString<string> caption?: string | FormattedString<string>
/** /**
* Optionally, a media caption for your forwarded message(s). * Optionally, a media caption for your forwarded message(s).
* It will be sent as a separate message before the forwarded messages. * It will be sent as a separate message before the forwarded messages.
* *
* You can either pass `caption` or `captionMedia`, passing both will * You can either pass `caption` or `captionMedia`, passing both will
* result in an error * result in an error
*/ */
captionMedia?: InputMediaLike captionMedia?: InputMediaLike
/** /**
* Parse mode to use to parse entities in caption. * Parse mode to use to parse entities in caption.
* Defaults to current default parse mode (if any). * Defaults to current default parse mode (if any).
* *
* Passing `null` will explicitly disable formatting. * Passing `null` will explicitly disable formatting.
*/ */
parseMode?: string | null parseMode?: string | null
/** /**
* List of formatting entities in caption to use instead * List of formatting entities in caption to use instead
* of parsing via a parse mode. * of parsing via a parse mode.
* *
* **Note:** Passing this makes the method ignore {@link parseMode} * **Note:** Passing this makes the method ignore {@link parseMode}
*/ */
entities?: tl.TypeMessageEntity[] entities?: tl.TypeMessageEntity[]
/** /**
* Whether to forward silently (also applies to caption message). * Whether to forward silently (also applies to caption message).
*/ */
silent?: boolean silent?: boolean
/** /**
* If set, the forwarding will be scheduled to this date * If set, the forwarding will be scheduled to this date
* (also applies to caption message). * (also applies to caption message).
* When passing a number, a UNIX time in ms is expected. * When passing a number, a UNIX time in ms is expected.
* *
* You can also pass `0x7FFFFFFE`, this will send the message * You can also pass `0x7FFFFFFE`, this will send the message
* once the peer is online * once the peer is online
*/ */
schedule?: Date | number schedule?: Date | number
/** /**
* Whether to clear draft after sending this message (only used for caption) * Whether to clear draft after sending this message (only used for caption)
* *
* Defaults to `false` * Defaults to `false`
*/ */
clearDraft?: boolean clearDraft?: boolean
/** /**
* Whether to forward without author * Whether to forward without author
*/ */
noAuthor?: boolean noAuthor?: boolean
/** /**
* Whether to forward without caption (implies {@link noAuthor}) * Whether to forward without caption (implies {@link noAuthor})
*/ */
noCaption?: boolean noCaption?: boolean
/** /**
* Whether to disallow further forwards of this message. * Whether to disallow further forwards of this message.
* *
* Only for bots, works even if the target chat does not * Only for bots, works even if the target chat does not
* have content protection. * have content protection.
*/ */
forbidForwards?: boolean forbidForwards?: boolean
},
): Promise<Message> /**
* 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. * 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. * 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 fromChatId Source chat ID, username, phone, `"me"` or `"self"`
* @param messages Message IDs * @param messages Message IDs
* @param params Additional sending parameters * @param params Additional sending parameters
* @returns * @returns Newly sent, forwarded messages in the destination chat.
* Newly sent, forwarded messages in the destination chat.
* If a caption message was provided, it will be the first message in the array.
*/ */
export async function forwardMessages( export async function forwardMessagesById(
client: BaseTelegramClient, client: BaseTelegramClient,
params: { params: ForwardMessageOptions & {
/** Source chat ID, username, phone, `"me"` or `"self"` */ /** Source chat ID, username, phone, `"me"` or `"self"` */
fromChatId: InputPeerLike fromChatId: InputPeerLike
/** Destination chat ID, username, phone, `"me"` or `"self"` */ /** Message IDs to forward */
toChatId: InputPeerLike
/** Message IDs */
messages: number[] 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>> ): Promise<Message[]> {
const { messages, toChatId, fromChatId, silent, schedule, forbidForwards, sendAs, noAuthor, noCaption } = params
/** @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]
}
// sending more than 100 will not result in a server-sent // sending more than 100 will not result in a server-sent
// error, instead only first 100 IDs will be forwarded, // error, instead only first 100 IDs will be forwarded,
@ -269,7 +154,25 @@ export async function forwardMessages(
} }
}) })
if (isSingle) return forwarded[0]
return forwarded 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 { BaseTelegramClient, tl } from '@mtcute/core'
import { Message } from '../../types/messages' import { InputMessageId, Message, normalizeInputMessageId } from '../../types/messages'
import { InputPeerLike, PeersIndex } from '../../types/peers' import { InputPeerLike, PeersIndex } from '../../types/peers'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
@ -54,10 +54,11 @@ export async function _getDiscussionMessage(
*/ */
export async function getDiscussionMessage( export async function getDiscussionMessage(
client: BaseTelegramClient, client: BaseTelegramClient,
peer: InputPeerLike, params: InputMessageId,
message: number,
): Promise<Message | null> { ): 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({ const res = await client.call({
_: 'messages.getDiscussionMessage', _: 'messages.getDiscussionMessage',

View file

@ -1,7 +1,7 @@
import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' import { BaseTelegramClient, MtArgumentError } from '@mtcute/core'
import { isPresent } from '@mtcute/core/utils' import { isPresent } from '@mtcute/core/utils'
import { InputPeerLike, Message } from '../../types' import { InputMessageId, Message, normalizeInputMessageId } from '../../types'
import { isInputPeerChannel } from '../../utils/peer-utils' import { isInputPeerChannel } from '../../utils/peer-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
import { getMessages } from './get-messages' import { getMessages } from './get-messages'
@ -12,11 +12,9 @@ import { getMessages } from './get-messages'
* @param chatId Chat ID * @param chatId Chat ID
* @param message ID of one of the messages in the group * @param message ID of one of the messages in the group
*/ */
export async function getMessageGroup( export async function getMessageGroup(client: BaseTelegramClient, params: InputMessageId): Promise<Message[]> {
client: BaseTelegramClient, const { chatId, message } = normalizeInputMessageId(params)
chatId: InputPeerLike,
message: number,
): Promise<Message[]> {
// awesome hack stolen from pyrogram // awesome hack stolen from pyrogram
// groups have no more than 10 items // groups have no more than 10 items
// however, since for non-channels message ids are shared, // 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 { 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 { assertIsUpdatesGroup } from '../../utils/updates-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
/** /**
* 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
*/
export async function getMessageReactions(
client: BaseTelegramClient,
chatId: InputPeerLike,
messages: number,
): Promise<MessageReactions | null>
/**
* Get reactions to messages.
* *
* > Apps should short-poll reactions for visible messages * > Apps should short-poll reactions for visible messages
* > (that weren't sent by the user) once every 15-30 seconds, * > (that weren't sent by the user) once every 15-30 seconds,
@ -33,26 +16,11 @@ export async function getMessageReactions(
* @param messages Message IDs * @param messages Message IDs
* @returns Reactions to corresponding messages, or `null` if there are none * @returns Reactions to corresponding messages, or `null` if there are none
*/ */
export async function getMessageReactions( export async function getMessageReactionsById(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, chatId: InputPeerLike,
messages: number[], messages: number[],
): Promise<(MessageReactions | null)[]> ): 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]
}
const res = await client.call({ const res = await client.call({
_: 'messages.getMessagesReactions', _: 'messages.getMessagesReactions',
peer: await resolvePeer(client, chatId), 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) 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) 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' 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. * Get messages from PM or legacy group by their IDs.
* For channels, use {@link getMessages}. * For channels, use {@link getMessages}.
@ -35,23 +18,15 @@ export async function getMessagesUnsafe(
* Whether the reply to a given message should be fetched * Whether the reply to a given message should be fetched
* (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) * (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( export async function getMessagesUnsafe(
client: BaseTelegramClient, client: BaseTelegramClient,
messageIds: MaybeArray<number>, messageIds: MaybeArray<number>,
fromReply = false, fromReply = false,
): Promise<MaybeArray<Message | null>> { ): Promise<(Message | null)[]> {
const isSingle = !Array.isArray(messageIds) if (!Array.isArray(messageIds)) messageIds = [messageIds]
if (isSingle) messageIds = [messageIds as number]
const type = fromReply ? 'inputMessageReplyTo' : 'inputMessageID' const type = fromReply ? 'inputMessageReplyTo' : 'inputMessageID'
const ids: tl.TypeInputMessage[] = (messageIds as number[]).map((it) => ({ const ids: tl.TypeInputMessage[] = messageIds.map((it) => ({
_: type, _: type,
id: it, id: it,
})) }))
@ -65,11 +40,9 @@ export async function getMessagesUnsafe(
const peers = PeersIndex.from(res) const peers = PeersIndex.from(res)
const ret = res.messages.map((msg) => { return res.messages.map((msg) => {
if (msg._ === 'messageEmpty') return null if (msg._ === 'messageEmpty') return null
return new Message(msg, peers) 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 { getAuthState } from '../auth/_state'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
/** // @available=both
* 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>
/** /**
* Get messages in chat by their IDs * 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 * Whether the reply to a given message should be fetched
* (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) * (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( export async function getMessages(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, chatId: InputPeerLike,
messageIds: MaybeArray<number>, messageIds: MaybeArray<number>,
fromReply = false, fromReply = false,
): Promise<MaybeArray<Message | null>> { ): Promise<(Message | null)[]> {
const peer = await resolvePeer(client, chatId) const peer = await resolvePeer(client, chatId)
if (!Array.isArray(messageIds)) messageIds = [messageIds]
const isSingle = !Array.isArray(messageIds)
if (isSingle) messageIds = [messageIds as number]
const type = fromReply ? 'inputMessageReplyTo' : 'inputMessageID' const type = fromReply ? 'inputMessageReplyTo' : 'inputMessageID'
const ids: tl.TypeInputMessage[] = (messageIds as number[]).map((it) => ({ const ids: tl.TypeInputMessage[] = messageIds.map((it) => ({
_: type, _: type,
id: it, id: it,
})) }))
@ -80,7 +55,8 @@ export async function getMessages(
const peers = PeersIndex.from(res) const peers = PeersIndex.from(res)
let selfId: number | null | undefined = undefined let selfId: number | null | undefined = undefined
const ret = res.messages.map((msg) => {
return res.messages.map((msg) => {
if (msg._ === 'messageEmpty') return null if (msg._ === 'messageEmpty') return null
if (!isChannel) { if (!isChannel) {
@ -110,6 +86,4 @@ export async function getMessages(
return new Message(msg, peers) return new Message(msg, peers)
}) })
return isSingle ? ret[0] : ret
} }

View file

@ -2,8 +2,9 @@ import { BaseTelegramClient } from '@mtcute/core'
import { import {
ArrayPaginated, ArrayPaginated,
InputPeerLike, InputMessageId,
InputReaction, InputReaction,
normalizeInputMessageId,
normalizeInputReaction, normalizeInputReaction,
PeerReaction, PeerReaction,
PeersIndex, PeersIndex,
@ -17,15 +18,11 @@ export type GetReactionUsersOffset = string
/** /**
* Get users who have reacted to the message. * Get users who have reacted to the message.
* *
* @param chatId Chat ID
* @param messageId Message ID
* @param params * @param params
*/ */
export async function getReactionUsers( export async function getReactionUsers(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, params: InputMessageId & {
messageId: number,
params?: {
/** /**
* Get only reactions with the specified emoji * Get only reactions with the specified emoji
*/ */
@ -44,9 +41,8 @@ export async function getReactionUsers(
offset?: GetReactionUsersOffset offset?: GetReactionUsersOffset
}, },
): Promise<ArrayPaginated<PeerReaction, GetReactionUsersOffset>> { ): Promise<ArrayPaginated<PeerReaction, GetReactionUsersOffset>> {
if (!params) params = {}
const { limit = 100, offset, emoji } = params const { limit = 100, offset, emoji } = params
const { chatId, message } = normalizeInputMessageId(params)
const peer = await resolvePeer(client, chatId) const peer = await resolvePeer(client, chatId)
@ -55,7 +51,7 @@ export async function getReactionUsers(
const res = await client.call({ const res = await client.call({
_: 'messages.getMessageReactionsList', _: 'messages.getMessageReactionsList',
peer, peer,
id: messageId, id: message,
reaction, reaction,
limit, limit,
offset, offset,

View file

@ -4,17 +4,6 @@ import { assertTypeIsNot } from '@mtcute/core/utils'
import { InputPeerLike, Message, PeersIndex } from '../../types' import { InputPeerLike, Message, PeersIndex } from '../../types'
import { resolvePeer } from '../users/resolve-peer' 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 * 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 chatId Chat's marked ID, its username, phone or `"me"` or `"self"`
* @param messageIds Scheduled messages IDs * @param messageIds Scheduled messages IDs
*/ */
export async function getScheduledMessages(
client: BaseTelegramClient,
chatId: InputPeerLike,
messageIds: number[],
): Promise<(Message | null)[]>
/** @internal */
export async function getScheduledMessages( export async function getScheduledMessages(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, chatId: InputPeerLike,
messageIds: MaybeArray<number>, messageIds: MaybeArray<number>,
): Promise<MaybeArray<Message | null>> { ): Promise<(Message | null)[]> {
const peer = await resolvePeer(client, chatId) const peer = await resolvePeer(client, chatId)
if (!Array.isArray(messageIds)) messageIds = [messageIds]
const isSingle = !Array.isArray(messageIds)
if (isSingle) messageIds = [messageIds as number]
const res = await client.call({ const res = await client.call({
_: 'messages.getScheduledMessages', _: 'messages.getScheduledMessages',
peer, peer,
id: messageIds as number[], id: messageIds,
}) })
assertTypeIsNot('getScheduledMessages', res, 'messages.messagesNotModified') assertTypeIsNot('getScheduledMessages', res, 'messages.messagesNotModified')
@ -57,5 +37,5 @@ export async function getScheduledMessages(
return new Message(msg, peers, true) 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 { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike, normalizeInputReaction, PeerReaction } from '../../types' import { normalizeInputMessageId, normalizeInputReaction, PeerReaction } from '../../types'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
import { getReactionUsers } from './get-reaction-users' import { getReactionUsers } from './get-reaction-users'
@ -15,9 +15,7 @@ import { getReactionUsers } from './get-reaction-users'
*/ */
export async function* iterReactionUsers( export async function* iterReactionUsers(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, params: Parameters<typeof getReactionUsers>[1] & {
messageId: number,
params?: Parameters<typeof getReactionUsers>[3] & {
/** /**
* Limit the number of events returned. * Limit the number of events returned.
* *
@ -33,8 +31,7 @@ export async function* iterReactionUsers(
chunkSize?: number chunkSize?: number
}, },
): AsyncIterableIterator<PeerReaction> { ): AsyncIterableIterator<PeerReaction> {
if (!params) params = {} const { chatId, message } = normalizeInputMessageId(params)
const peer = await resolvePeer(client, chatId) const peer = await resolvePeer(client, chatId)
const { limit = Infinity, chunkSize = 100 } = params const { limit = Infinity, chunkSize = 100 } = params
@ -45,7 +42,9 @@ export async function* iterReactionUsers(
const reaction = normalizeInputReaction(params.emoji) const reaction = normalizeInputReaction(params.emoji)
for (;;) { for (;;) {
const res = await getReactionUsers(client, peer, messageId, { const res = await getReactionUsers(client, {
chatId: peer,
message,
emoji: reaction, emoji: reaction,
limit: Math.min(chunkSize, limit - current), limit: Math.min(chunkSize, limit - current),
offset, offset,

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient } from '@mtcute/core' import { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike } from '../../types' import { InputMessageId, normalizeInputMessageId } from '../../types'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
/** /**
@ -8,15 +8,10 @@ import { resolvePeer } from '../users/resolve-peer'
* *
* For supergroups/channels, you must have appropriate permissions, * For supergroups/channels, you must have appropriate permissions,
* either as an admin, or as default 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( export async function pinMessage(
client: BaseTelegramClient, client: BaseTelegramClient,
chatId: InputPeerLike, params: InputMessageId & {
messageId: number,
params?: {
/** Whether to send a notification (only for legacy groups and supergroups) */ /** Whether to send a notification (only for legacy groups and supergroups) */
notify?: boolean notify?: boolean
/** Whether to pin for both sides (only for private chats) */ /** Whether to pin for both sides (only for private chats) */
@ -24,11 +19,12 @@ export async function pinMessage(
}, },
): Promise<void> { ): Promise<void> {
const { notify, bothSides } = params ?? {} const { notify, bothSides } = params ?? {}
const { chatId, message } = normalizeInputMessageId(params)
const res = await client.call({ const res = await client.call({
_: 'messages.updatePinnedMessage', _: 'messages.updatePinnedMessage',
peer: await resolvePeer(client, chatId), peer: await resolvePeer(client, chatId),
id: messageId, id: message,
silent: !notify, silent: !notify,
pmOneside: !bothSides, pmOneside: !bothSides,
}) })

View file

@ -6,9 +6,82 @@ import { getMessages } from './get-messages'
import { sendMedia } from './send-media' import { sendMedia } from './send-media'
import { sendText } from './send-text' 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, * Copy a message (i.e. send the same message, but do not forward it).
* but do not forward it).
* *
* Note that if the message contains a webpage, * Note that if the message contains a webpage,
* it will be copied simply as a text message, * it will be copied simply as a text message,
@ -19,90 +92,31 @@ import { sendText } from './send-text'
*/ */
export async function sendCopy( export async function sendCopy(
client: BaseTelegramClient, client: BaseTelegramClient,
params: { params: SendCopyParams &
/** Source chat ID */ (
fromChatId: InputPeerLike | {
/** Target chat ID */ /** Source chat ID */
toChatId: InputPeerLike fromChatId: InputPeerLike
/** Message ID to forward */ /** Message ID to forward */
message: number message: number
/** }
* Whether to send this message silently. | { message: Message }
*/ ),
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
},
): Promise<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) { ;[msg] = await getMessages(client, fromPeer, params.message)
throw new MtMessageNotFoundError(getMarkedPeerId(fromPeer), message, 'to copy')
if (!msg) {
throw new MtMessageNotFoundError(getMarkedPeerId(fromPeer), params.message, 'to copy')
}
} else {
msg = params.message
} }
if (msg.raw._ === 'messageService') { if (msg.raw._ === 'messageService') {

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient } from '@mtcute/core' 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 { assertIsUpdatesGroup } from '../../utils/updates-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
import { _findMessageInUpdate } from './find-in-update' import { _findMessageInUpdate } from './find-in-update'
@ -12,18 +12,15 @@ import { _findMessageInUpdate } from './find-in-update'
*/ */
export async function sendReaction( export async function sendReaction(
client: BaseTelegramClient, client: BaseTelegramClient,
params: { params: InputMessageId & {
/** Chat ID with the message to react to */
chatId: InputPeerLike
/** Message ID to react to */
message: number
/** Reaction emoji (or `null` to remove reaction) */ /** Reaction emoji (or `null` to remove reaction) */
emoji?: InputReaction | null emoji?: InputReaction | null
/** Whether to use a big reaction */ /** Whether to use a big reaction */
big?: boolean big?: boolean
}, },
): Promise<Message> { ): Promise<Message> {
const { chatId, message, emoji, big } = params const { emoji, big } = params
const { chatId, message } = normalizeInputMessageId(params)
const reaction = normalizeInputReaction(emoji) const reaction = normalizeInputReaction(emoji)

View file

@ -4,18 +4,6 @@ import { InputPeerLike, Message, PeersIndex } from '../../types'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
import { resolvePeer } from '../users/resolve-peer' 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) * 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 peer Chat where the messages were scheduled
* @param ids ID(s) of the messages * @param ids ID(s) of the messages
*/ */
export async function sendScheduled(client: BaseTelegramClient, peer: InputPeerLike, ids: number[]): Promise<Message[]>
/** @internal */
export async function sendScheduled( export async function sendScheduled(
client: BaseTelegramClient, client: BaseTelegramClient,
peer: InputPeerLike, peer: InputPeerLike,
ids: MaybeArray<number>, ids: MaybeArray<number>,
): Promise<MaybeArray<Message>> { ): Promise<Message[]> {
const isSingle = !Array.isArray(ids) if (!Array.isArray(ids)) ids = [ids]
if (isSingle) ids = [ids as number]
const res = await client.call({ const res = await client.call({
_: 'messages.sendScheduledMessages', _: 'messages.sendScheduledMessages',
peer: await resolvePeer(client, peer), peer: await resolvePeer(client, peer),
id: ids as number[], id: ids,
}) })
assertIsUpdatesGroup('sendScheduled', res) assertIsUpdatesGroup('sendScheduled', res)
@ -55,5 +39,5 @@ export async function sendScheduled(
) )
.map((u) => new Message(u.message, peers)) .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 { BaseTelegramClient, getMarkedPeerId, MaybeArray, MtArgumentError, MtTypeAssertionError } from '@mtcute/core'
import { assertTypeIs } from '@mtcute/core/utils' 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 { assertIsUpdatesGroup } from '../../utils/updates-utils'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
import { getMessages } from './get-messages' import { getMessages } from './get-messages'
@ -11,11 +11,7 @@ import { getMessages } from './get-messages'
*/ */
export async function sendVote( export async function sendVote(
client: BaseTelegramClient, client: BaseTelegramClient,
params: { params: InputMessageId & {
/** Chat ID where this poll was found */
chatId: InputPeerLike
/** Message ID where this poll was found */
message: number
/** /**
* Selected options, or `null` to retract. * Selected options, or `null` to retract.
* You can pass indexes of the answers or the `Buffer`s * You can pass indexes of the answers or the `Buffer`s
@ -25,7 +21,7 @@ export async function sendVote(
options: null | MaybeArray<number | Buffer> options: null | MaybeArray<number | Buffer>
}, },
): Promise<Poll> { ): Promise<Poll> {
const { chatId, message } = params const { chatId, message } = normalizeInputMessageId(params)
let { options } = params let { options } = params
if (options === null) options = [] if (options === null) options = []
@ -36,7 +32,7 @@ export async function sendVote(
let poll: Poll | undefined = undefined let poll: Poll | undefined = undefined
if (options.some((it) => typeof it === 'number')) { if (options.some((it) => typeof it === 'number')) {
const msg = await getMessages(client, peer, message) const [msg] = await getMessages(client, peer, message)
if (!msg) { if (!msg) {
throw new MtMessageNotFoundError(getMarkedPeerId(peer), message, 'to vote in') throw new MtMessageNotFoundError(getMarkedPeerId(peer), message, 'to vote in')

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient } from '@mtcute/core' import { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike, MessageEntity } from '../../types' import { InputMessageId, MessageEntity, normalizeInputMessageId } from '../../types'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
/** /**
@ -10,21 +10,18 @@ import { resolvePeer } from '../users/resolve-peer'
*/ */
export async function translateMessage( export async function translateMessage(
client: BaseTelegramClient, client: BaseTelegramClient,
params: { params: InputMessageId & {
/** Chat or user ID */
chatId: InputPeerLike
/** Identifier of the message to translate */
messageId: number
/** Target language (two-letter ISO 639-1 language code) */ /** Target language (two-letter ISO 639-1 language code) */
toLanguage: string toLanguage: string
}, },
): Promise<[string, MessageEntity[]] | null> { ): Promise<[string, MessageEntity[]] | null> {
const { chatId, messageId, toLanguage } = params const { toLanguage } = params
const { chatId, message } = normalizeInputMessageId(params)
const res = await client.call({ const res = await client.call({
_: 'messages.translateText', _: 'messages.translateText',
peer: await resolvePeer(client, chatId), peer: await resolvePeer(client, chatId),
id: [messageId], id: [message],
toLang: toLanguage, toLang: toLanguage,
}) })

View file

@ -1,6 +1,6 @@
import { BaseTelegramClient } from '@mtcute/core' import { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike } from '../../types' import { InputMessageId, normalizeInputMessageId } from '../../types'
import { resolvePeer } from '../users/resolve-peer' 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 chatId Chat ID, username, phone number, `"self"` or `"me"`
* @param messageId Message ID * @param messageId Message ID
*/ */
export async function unpinMessage( export async function unpinMessage(client: BaseTelegramClient, params: InputMessageId): Promise<void> {
client: BaseTelegramClient, const { chatId, message } = normalizeInputMessageId(params)
chatId: InputPeerLike,
messageId: number,
): Promise<void> {
const res = await client.call({ const res = await client.call({
_: 'messages.updatePinnedMessage', _: 'messages.updatePinnedMessage',
peer: await resolvePeer(client, chatId), peer: await resolvePeer(client, chatId),
id: messageId, id: message,
unpin: true, unpin: true,
}) })

View file

@ -5,40 +5,22 @@ import { InputPeerLike, PeersIndex, Story } from '../../types'
import { resolvePeer } from '../users/resolve-peer' import { resolvePeer } from '../users/resolve-peer'
/** /**
* 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
*/
export async function getStoriesById(client: BaseTelegramClient, peerId: InputPeerLike, storyId: number): Promise<Story>
/**
* Get multiple stories by their IDs
* *
* @param peerId Peer ID whose stories to fetch * @param peerId Peer ID whose stories to fetch
* @param storyIds Story IDs * @param storyIds Story IDs
*/ */
export async function getStoriesById(
client: BaseTelegramClient,
peerId: InputPeerLike,
storyIds: number[],
): Promise<Story[]>
/**
* @internal
*/
export async function getStoriesById( export async function getStoriesById(
client: BaseTelegramClient, client: BaseTelegramClient,
peerId: InputPeerLike, peerId: InputPeerLike,
storyIds: MaybeArray<number>, storyIds: MaybeArray<number>,
): Promise<MaybeArray<Story>> { ): Promise<Story[]> {
const single = !Array.isArray(storyIds) if (!Array.isArray(storyIds)) storyIds = [storyIds]
if (single) storyIds = [storyIds as number]
const res = await client.call({ const res = await client.call({
_: 'stories.getStoriesByID', _: 'stories.getStoriesByID',
peer: await resolvePeer(client, peerId), peer: await resolvePeer(client, peerId),
id: storyIds as number[], id: storyIds,
}) })
const peers = PeersIndex.from(res) const peers = PeersIndex.from(res)
@ -49,5 +31,5 @@ export async function getStoriesById(
return new Story(it, peers) 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 { InputPeerLike, PeersIndex, StoryInteractions } from '../../types'
import { resolvePeer } from '../users/resolve-peer' 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. * Get brief information about stories interactions.
* *
* The result will be in the same order as the input IDs * 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( export async function getStoriesInteractions(
client: BaseTelegramClient, client: BaseTelegramClient,
peerId: InputPeerLike, peerId: InputPeerLike,
storyIds: MaybeArray<number>, storyIds: MaybeArray<number>,
): Promise<MaybeArray<StoryInteractions>> { ): Promise<StoryInteractions[]> {
const isSingle = !Array.isArray(storyIds) if (!Array.isArray(storyIds)) storyIds = [storyIds]
if (isSingle) storyIds = [storyIds as number]
const res = await client.call({ const res = await client.call({
_: 'stories.getStoriesViews', _: 'stories.getStoriesViews',
peer: await resolvePeer(client, peerId), peer: await resolvePeer(client, peerId),
id: storyIds as number[], id: storyIds,
}) })
const peers = PeersIndex.from(res) const peers = PeersIndex.from(res)
const infos = res.views.map((it) => new StoryInteractions(it, peers)) 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, tl,
toggleChannelIdMark, toggleChannelIdMark,
} from '@mtcute/core' } from '@mtcute/core'
import { assertTypeIs } from '@mtcute/core/utils'
import { MtPeerNotFoundError } from '../../types/errors' import { MtPeerNotFoundError } from '../../types/errors'
import { InputPeerLike } from '../../types/peers' import { InputPeerLike } from '../../types/peers'
@ -26,10 +25,13 @@ export async function resolvePeer(
peerId: InputPeerLike, peerId: InputPeerLike,
force = false, force = false,
): Promise<tl.TypeInputPeer> { ): 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 (typeof peerId === 'object') {
if (tl.isAnyPeer(peerId)) { if (tl.isAnyPeer(peerId)) {
peerId = getMarkedPeerId(peerId) peerId = getMarkedPeerId(peerId)
} else if ('type' in peerId) {
// User | Chat
return peerId.inputPeer
} else { } else {
return normalizeToInputPeer(peerId) return normalizeToInputPeer(peerId)
} }
@ -45,29 +47,17 @@ export async function resolvePeer(
peerId = peerId.replace(/[@+\s()]/g, '') peerId = peerId.replace(/[@+\s()]/g, '')
let res
if (peerId.match(/^\d+$/)) { if (peerId.match(/^\d+$/)) {
// phone number // phone number
const fromStorage = await client.storage.getPeerByPhone(peerId) const fromStorage = await client.storage.getPeerByPhone(peerId)
if (fromStorage) return fromStorage if (fromStorage) return fromStorage
const res = await client.call({ res = await client.call({
_: 'contacts.getContacts', _: 'contacts.resolvePhone',
hash: Long.ZERO, 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 { } else {
// username // username
if (!force) { if (!force) {
@ -75,64 +65,62 @@ export async function resolvePeer(
if (fromStorage) return fromStorage if (fromStorage) return fromStorage
} }
const res = await client.call({ res = await client.call({
_: 'contacts.resolveUsername', _: 'contacts.resolveUsername',
username: peerId, 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) const peerType = getBasicPeerType(peerId)
@ -171,25 +159,10 @@ export async function resolvePeer(
break break
} }
case 'chat': { 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 { return {
_: 'inputPeerChat', _: 'inputPeerChat',
chatId: -peerId, chatId: -peerId,
} }
// break
} }
case 'channel': { case 'channel': {
const id = toggleChannelIdMark(peerId) const id = toggleChannelIdMark(peerId)

View file

@ -1,5 +1,6 @@
export * from './dialog' export * from './dialog'
export * from './draft-message' export * from './draft-message'
export * from './input-message-id'
export * from './message' export * from './message'
export * from './message-action' export * from './message-action'
export * from './message-entity' 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: * Can be one of:
* - Raw TL object * - Raw TL object
* - Sticker set short name * - Sticker set short name
* - {@link StickerSet} object
* - `{ dice: "<emoji>" }` (e.g. `{ dice: "🎲" }`) - Used for fetching animated dice stickers * - `{ dice: "<emoji>" }` (e.g. `{ dice: "🎲" }`) - Used for fetching animated dice stickers
* - `{ system: string }` - for system stickersets: * - `{ system: string }` - for system stickersets:
* - `"animated"` - Animated emojis stickerset * - `"animated"` - Animated emojis stickerset
@ -35,6 +36,7 @@ export type InputStickerSet =
| 'default_statuses' | 'default_statuses'
| 'default_topic_icons' | 'default_topic_icons'
} }
| StickerSet
| string | string
export function normalizeInputStickerSet(input: InputStickerSet): tl.TypeInputStickerSet { export function normalizeInputStickerSet(input: InputStickerSet): tl.TypeInputStickerSet {
@ -45,6 +47,7 @@ export function normalizeInputStickerSet(input: InputStickerSet): tl.TypeInputSt
} }
} }
if ('_' in input) return input if ('_' in input) return input
if (input instanceof StickerSet) return input.inputStickerSet
if ('dice' in input) { if ('dice' in input) {
return { return {

View file

@ -1,5 +1,8 @@
import { tl } from '@mtcute/core' import { tl } from '@mtcute/core'
import { Chat } from './chat'
import { User } from './user'
export * from './chat' export * from './chat'
export * from './chat-event' export * from './chat-event'
export * from './chat-invite-link' export * from './chat-invite-link'
@ -21,15 +24,23 @@ export * from './user'
export type PeerType = 'user' | 'bot' | 'group' | 'channel' | 'supergroup' export type PeerType = 'user' | 'bot' | 'group' | 'channel' | 'supergroup'
/** /**
* Type that can be used as an input peer * Type that can be used as an input peer to most of the high-level methods. Can be:
* to most of the high-level methods. Can be:
* - `number`, representing peer's marked ID* * - `number`, representing peer's marked ID*
* - `string`, representing peer's username (w/out preceding `@`) * - `string`, representing peer's username (without preceding `@`)
* - `string`, representing user's phone number (only for contacts) * - `string`, representing user's phone number
* - `"me"` and `"self"` which will be replaced with the current user/bot * - `"me"` and `"self"` which will be replaced with the current user/bot
* - `Chat` or `User` object
* - Raw TL 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. * > 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