feat: updated to layer 166

This commit is contained in:
alina 🌸 2023-10-29 08:14:00 +03:00
parent 49d9d5999d
commit fc42dcb973
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
32 changed files with 842 additions and 179 deletions

View file

@ -66,6 +66,7 @@ import { markChatUnread } from './methods/chats/mark-chat-unread.js'
import { reorderUsernames } from './methods/chats/reorder-usernames.js' import { reorderUsernames } from './methods/chats/reorder-usernames.js'
import { restrictChatMember } from './methods/chats/restrict-chat-member.js' import { restrictChatMember } from './methods/chats/restrict-chat-member.js'
import { saveDraft } from './methods/chats/save-draft.js' import { saveDraft } from './methods/chats/save-draft.js'
import { setChatColor } from './methods/chats/set-chat-color.js'
import { setChatDefaultPermissions } from './methods/chats/set-chat-default-permissions.js' import { setChatDefaultPermissions } from './methods/chats/set-chat-default-permissions.js'
import { setChatDescription } from './methods/chats/set-chat-description.js' import { setChatDescription } from './methods/chats/set-chat-description.js'
import { setChatPhoto } from './methods/chats/set-chat-photo.js' import { setChatPhoto } from './methods/chats/set-chat-photo.js'
@ -153,6 +154,7 @@ import { sendCopy, SendCopyParams } from './methods/messages/send-copy.js'
import { sendCopyGroup, SendCopyGroupParams } from './methods/messages/send-copy-group.js' import { sendCopyGroup, SendCopyGroupParams } from './methods/messages/send-copy-group.js'
import { sendMedia } from './methods/messages/send-media.js' import { sendMedia } from './methods/messages/send-media.js'
import { sendMediaGroup } from './methods/messages/send-media-group.js' import { sendMediaGroup } from './methods/messages/send-media-group.js'
import { QuoteParamsFrom, quoteWithMedia, quoteWithMediaGroup, quoteWithText } from './methods/messages/send-quote.js'
import { sendReaction } from './methods/messages/send-reaction.js' import { sendReaction } from './methods/messages/send-reaction.js'
import { replyMedia, replyMediaGroup, replyText } from './methods/messages/send-reply.js' import { replyMedia, replyMediaGroup, replyText } from './methods/messages/send-reply.js'
import { sendScheduled } from './methods/messages/send-scheduled.js' import { sendScheduled } from './methods/messages/send-scheduled.js'
@ -1687,6 +1689,39 @@ export interface TelegramClient extends BaseTelegramClient {
* @param draft Draft message, or `null` to delete. * @param draft Draft message, or `null` to delete.
*/ */
saveDraft(chatId: InputPeerLike, draft: null | Omit<tl.RawDraftMessage, '_' | 'date'>): Promise<void> saveDraft(chatId: InputPeerLike, draft: null | Omit<tl.RawDraftMessage, '_' | 'date'>): Promise<void>
/**
* Set chat name/replies color and optionally background pattern
* **Available**: 👤 users only
*
*/
setChatColor(params: {
/**
* Peer where to update the color.
*
* By default will change the color for the current user
*/
peer?: InputPeerLike
/**
* Color identificator
*
* Note that this value is **not** an RGB color representation. Instead, it is
* a number which should be used to pick a color from a predefined
* list of colors:
* - `0-6` are the default colors used by Telegram clients:
* `red, orange, purple, green, sea, blue, pink`
* - `>= 7` are returned by `help.getAppConfig`.
*/
color: number
/**
* Background pattern emoji ID.
*
* Must be an adaptive emoji, otherwise the request will fail.
*/
backgroundEmojiId?: tl.Long
}): Promise<void>
/** /**
* Change default chat permissions for all members. * Change default chat permissions for all members.
* *
@ -2916,6 +2951,14 @@ export interface TelegramClient extends BaseTelegramClient {
*/ */
disableWebPreview?: boolean disableWebPreview?: boolean
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
/** /**
* For bots: new reply markup. * For bots: new reply markup.
* If omitted, existing markup will be removed. * If omitted, existing markup will be removed.
@ -3001,6 +3044,14 @@ export interface TelegramClient extends BaseTelegramClient {
* to the client's update handler. * to the client's update handler.
*/ */
shouldDispatch?: true shouldDispatch?: true
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}, },
): Promise<Message> ): Promise<Message>
/** /**
@ -3224,7 +3275,7 @@ export interface TelegramClient extends BaseTelegramClient {
/** /**
* For messages containing a reply, fetch the message that is being replied. * For messages containing a reply, fetch the message that is being replied.
* *
* Note that even if a message has {@link replyToMessageId}, * Note that even if a message has {@link replyToMessage},
* the message itself may have been deleted, in which case * the message itself may have been deleted, in which case
* this method will also return `null`. * this method will also return `null`.
* **Available**: both users and bots * **Available**: both users and bots
@ -3668,6 +3719,14 @@ export interface TelegramClient extends BaseTelegramClient {
chatId: InputPeerLike, chatId: InputPeerLike,
medias: (InputMediaLike | string)[], medias: (InputMediaLike | string)[],
params?: CommonSendParams & { params?: CommonSendParams & {
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
/** /**
* Function that will be called after some part has been uploaded. * Function that will be called after some part has been uploaded.
* Only used when a file that requires uploading is passed, * Only used when a file that requires uploading is passed,
@ -3703,6 +3762,14 @@ export interface TelegramClient extends BaseTelegramClient {
*/ */
replyMarkup?: ReplyMarkup replyMarkup?: ReplyMarkup
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invert?: boolean
/** /**
* Override caption for `media`. * Override caption for `media`.
* *
@ -3730,6 +3797,30 @@ export interface TelegramClient extends BaseTelegramClient {
progressCallback?: (uploaded: number, total: number) => void progressCallback?: (uploaded: number, total: number) => void
}, },
): Promise<Message> ): Promise<Message>
/** Send a text in reply to a given quote */
quoteWithText(
message: Message,
params: QuoteParamsFrom<Parameters<typeof sendText>[3]> & {
/** Text to send */
text: Parameters<typeof sendText>[2]
},
): ReturnType<typeof sendText>
/** Send a media in reply to a given quote */
quoteWithMedia(
message: Message,
params: QuoteParamsFrom<Parameters<typeof sendMedia>[3]> & {
/** Media to send */
media: Parameters<typeof sendMedia>[2]
},
): ReturnType<typeof sendMedia>
/** Send a media group in reply to a given quote */
quoteWithMediaGroup(
message: Message,
params: QuoteParamsFrom<Parameters<typeof sendMediaGroup>[3]> & {
/** Media group to send */
medias: Parameters<typeof sendMediaGroup>[2]
},
): ReturnType<typeof sendMediaGroup>
/** /**
* Send or remove a reaction. * Send or remove a reaction.
* *
@ -3804,6 +3895,14 @@ export interface TelegramClient extends BaseTelegramClient {
* Whether to disable links preview in this message * Whether to disable links preview in this message
*/ */
disableWebPreview?: boolean disableWebPreview?: boolean
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}, },
): Promise<Message> ): Promise<Message>
/** /**
@ -5194,6 +5293,7 @@ export class TelegramClient extends BaseTelegramClient {
reorderUsernames = reorderUsernames.bind(null, this) reorderUsernames = reorderUsernames.bind(null, this)
restrictChatMember = restrictChatMember.bind(null, this) restrictChatMember = restrictChatMember.bind(null, this)
saveDraft = saveDraft.bind(null, this) saveDraft = saveDraft.bind(null, this)
setChatColor = setChatColor.bind(null, this)
setChatDefaultPermissions = setChatDefaultPermissions.bind(null, this) setChatDefaultPermissions = setChatDefaultPermissions.bind(null, this)
setChatDescription = setChatDescription.bind(null, this) setChatDescription = setChatDescription.bind(null, this)
setChatPhoto = setChatPhoto.bind(null, this) setChatPhoto = setChatPhoto.bind(null, this)
@ -5288,6 +5388,9 @@ export class TelegramClient extends BaseTelegramClient {
sendCopy = sendCopy.bind(null, this) sendCopy = sendCopy.bind(null, this)
sendMediaGroup = sendMediaGroup.bind(null, this) sendMediaGroup = sendMediaGroup.bind(null, this)
sendMedia = sendMedia.bind(null, this) sendMedia = sendMedia.bind(null, this)
quoteWithText = quoteWithText.bind(null, this)
quoteWithMedia = quoteWithMedia.bind(null, this)
quoteWithMediaGroup = quoteWithMediaGroup.bind(null, this)
sendReaction = sendReaction.bind(null, this) sendReaction = sendReaction.bind(null, this)
replyText = replyText.bind(null, this) replyText = replyText.bind(null, this)
replyMedia = replyMedia.bind(null, this) replyMedia = replyMedia.bind(null, this)

View file

@ -0,0 +1,71 @@
import { BaseTelegramClient, MtTypeAssertionError, tl } from '@mtcute/core'
import { InputPeerLike, MtInvalidPeerTypeError } from '../../types/index.js'
import { isInputPeerChannel, isInputPeerUser, normalizeToInputChannel } from '../../utils/index.js'
import { getAuthState } from '../auth/_state.js'
import { resolvePeer } from '../users/resolve-peer.js'
// @available=user
/**
* Set chat name/replies color and optionally background pattern
*/
export async function setChatColor(
client: BaseTelegramClient,
params: {
/**
* Peer where to update the color.
*
* By default will change the color for the current user
*/
peer?: InputPeerLike
/**
* Color identificator
*
* Note that this value is **not** an RGB color representation. Instead, it is
* a number which should be used to pick a color from a predefined
* list of colors:
* - `0-6` are the default colors used by Telegram clients:
* `red, orange, purple, green, sea, blue, pink`
* - `>= 7` are returned by `help.getAppConfig`.
*/
color: number
/**
* Background pattern emoji ID.
*
* Must be an adaptive emoji, otherwise the request will fail.
*/
backgroundEmojiId?: tl.Long
},
): Promise<void> {
const { color, backgroundEmojiId } = params
const peer = await resolvePeer(client, params.peer ?? 'me')
if (isInputPeerChannel(peer)) {
const res = await client.call({
_: 'channels.updateColor',
channel: normalizeToInputChannel(peer),
color,
backgroundEmojiId,
})
client.network.handleUpdate(res)
return
}
if (isInputPeerUser(peer)) {
if (peer._ !== 'inputPeerSelf' && peer.userId !== getAuthState(client).userId) {
throw new MtTypeAssertionError('setChatColor', 'inputPeerSelf | inputPeerUser', peer._)
}
await client.call({
_: 'account.updateColor',
color,
backgroundEmojiId,
})
}
throw new MtInvalidPeerTypeError(peer, 'channel | user')
}

View file

@ -202,6 +202,16 @@ export async function _normalizeInputMedia(
} }
} }
if (media.type === 'webpage') {
return {
_: 'inputMediaWebPage',
forceLargeMedia: media.size === 'large',
forceSmallMedia: media.size === 'small',
optional: !media.required,
url: media.url,
}
}
let inputFile: tl.TypeInputFile | undefined = undefined let inputFile: tl.TypeInputFile | undefined = undefined
let thumb: tl.TypeInputFile | undefined = undefined let thumb: tl.TypeInputFile | undefined = undefined
let mime = 'application/octet-stream' let mime = 'application/octet-stream'

View file

@ -80,7 +80,8 @@ export async function uploadMedia(
// eslint-disable-next-line // eslint-disable-next-line
return parseDocument(res.document, res) as any return parseDocument(res.document, res) as any
case 'inputMediaStory': case 'inputMediaStory':
throw new MtArgumentError("This media (story) can't be uploaded") case 'inputMediaWebPage':
throw new MtArgumentError(`This media (${normMedia._}) can't be uploaded`)
default: default:
assertNever(normMedia) assertNever(normMedia)
} }

View file

@ -57,6 +57,14 @@ export async function editInlineMessage(
*/ */
disableWebPreview?: boolean disableWebPreview?: boolean
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
/** /**
* For bots: new reply markup. * For bots: new reply markup.
* If omitted, existing markup will be removed. * If omitted, existing markup will be removed.
@ -108,6 +116,7 @@ export async function editInlineMessage(
message: content, message: content,
entities, entities,
media, media,
invertMedia: params.invertMedia,
}, },
{ dcId: id.dcId }, { dcId: id.dcId },
) )

View file

@ -84,6 +84,14 @@ export async function editMessage(
* to the client's update handler. * to the client's update handler.
*/ */
shouldDispatch?: true shouldDispatch?: true
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}, },
): Promise<Message> { ): Promise<Message> {
const { chatId, message } = normalizeInputMessageId(params) const { chatId, message } = normalizeInputMessageId(params)
@ -119,6 +127,7 @@ export async function editMessage(
message: content, message: content,
entities, entities,
media, media,
invertMedia: params.invertMedia,
}) })
return _findMessageInUpdate(client, res, true, !params.shouldDispatch) return _findMessageInUpdate(client, res, true, !params.shouldDispatch)

View file

@ -7,12 +7,12 @@ import { getMessagesUnsafe } from './get-messages-unsafe.js'
/** /**
* For messages containing a reply, fetch the message that is being replied. * For messages containing a reply, fetch the message that is being replied.
* *
* Note that even if a message has {@link replyToMessageId}, * Note that even if a message has {@link replyToMessage},
* the message itself may have been deleted, in which case * the message itself may have been deleted, in which case
* this method will also return `null`. * this method will also return `null`.
*/ */
export async function getReplyTo(client: BaseTelegramClient, message: Message): Promise<Message | null> { export async function getReplyTo(client: BaseTelegramClient, message: Message): Promise<Message | null> {
if (!message.replyToMessageId) { if (!message.replyToMessage?.id) {
return null return null
} }

View file

@ -12,12 +12,12 @@ export function answerText(
message: Message, message: Message,
...params: ParametersSkip2<typeof sendText> ...params: ParametersSkip2<typeof sendText>
): ReturnType<typeof sendText> { ): ReturnType<typeof sendText> {
if (!message.isTopicMessage || !message.replyToThreadId) { if (!message.isTopicMessage || !message.replyToMessage?.threadId) {
return sendText(client, message.chat.inputPeer, ...params) return sendText(client, message.chat.inputPeer, ...params)
} }
const [text, params_ = {}] = params const [text, params_ = {}] = params
params_.replyTo = message.replyToThreadId params_.replyTo = message.replyToMessage.threadId
return sendText(client, message.chat.inputPeer, text, params_) return sendText(client, message.chat.inputPeer, text, params_)
} }
@ -28,12 +28,12 @@ export function answerMedia(
message: Message, message: Message,
...params: ParametersSkip2<typeof sendMedia> ...params: ParametersSkip2<typeof sendMedia>
): ReturnType<typeof sendMedia> { ): ReturnType<typeof sendMedia> {
if (!message.isTopicMessage || !message.replyToThreadId) { if (!message.isTopicMessage || !message.replyToMessage?.threadId) {
return sendMedia(client, message.chat.inputPeer, ...params) return sendMedia(client, message.chat.inputPeer, ...params)
} }
const [media, params_ = {}] = params const [media, params_ = {}] = params
params_.replyTo = message.replyToThreadId params_.replyTo = message.replyToMessage.threadId
return sendMedia(client, message.chat.inputPeer, media, params_) return sendMedia(client, message.chat.inputPeer, media, params_)
} }
@ -44,12 +44,12 @@ export function answerMediaGroup(
message: Message, message: Message,
...params: ParametersSkip2<typeof sendMediaGroup> ...params: ParametersSkip2<typeof sendMediaGroup>
): ReturnType<typeof sendMediaGroup> { ): ReturnType<typeof sendMediaGroup> {
if (!message.isTopicMessage || !message.replyToThreadId) { if (!message.isTopicMessage || !message.replyToMessage?.threadId) {
return sendMediaGroup(client, message.chat.inputPeer, ...params) return sendMediaGroup(client, message.chat.inputPeer, ...params)
} }
const [media, params_ = {}] = params const [media, params_ = {}] = params
params_.replyTo = message.replyToThreadId params_.replyTo = message.replyToMessage.threadId
return sendMediaGroup(client, message.chat.inputPeer, media, params_) return sendMediaGroup(client, message.chat.inputPeer, media, params_)
} }

View file

@ -1,9 +1,9 @@
import { BaseTelegramClient, getMarkedPeerId, MtArgumentError } from '@mtcute/core' import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core'
import { MtMessageNotFoundError } from '../../types/errors.js' import { MtMessageNotFoundError } from '../../types/errors.js'
import { Message } from '../../types/messages/message.js' import { Message } from '../../types/messages/message.js'
import { InputPeerLike } from '../../types/peers/index.js' import { InputPeerLike } from '../../types/peers/index.js'
import { normalizeMessageId } from '../../utils/index.js' import { normalizeMessageId, normalizeToInputUser } from '../../utils/index.js'
import { resolvePeer } from '../users/resolve-peer.js' import { resolvePeer } from '../users/resolve-peer.js'
import { _getDiscussionMessage } from './get-discussion-message.js' import { _getDiscussionMessage } from './get-discussion-message.js'
import { getMessages } from './get-messages.js' import { getMessages } from './get-messages.js'
@ -14,6 +14,8 @@ export interface CommonSendParams {
* Message to reply to. Either a message object or message ID. * 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) * For forums - can also be an ID of the topic (i.e. its top message ID)
*
* Can also be a message from another chat, in which case a quote will be sent.
*/ */
replyTo?: number | Message replyTo?: number | Message
@ -37,6 +39,28 @@ export interface CommonSendParams {
*/ */
commentTo?: number | Message commentTo?: number | Message
/**
* Story to reply to.
*
* Must be the story sent by the peer you are sending the message to.
*
* Can't be used together with {@link replyTo} or {@link commentTo}.
*/
replyToStory?: number
/**
* Quoted text. Must be exactly contained in the message
* being quoted to be accepted by the server
*/
quoteText?: string
/**
* Entities contained in the quoted text.
* Must be exactly contained in the message
* being quoted to be accepted by the server
*/
quoteEntities?: tl.TypeMessageEntity[]
/** /**
* Parse mode to use to parse entities before sending * Parse mode to use to parse entities before sending
* the message. Defaults to current default parse mode (if any). * the message. Defaults to current default parse mode (if any).
@ -98,6 +122,7 @@ export async function _processCommonSendParameters(
let peer = await resolvePeer(client, chatId) let peer = await resolvePeer(client, chatId)
let replyTo = normalizeMessageId(params.replyTo) let replyTo = normalizeMessageId(params.replyTo)
const replyToPeer = typeof params.replyTo === 'number' ? peer : params.replyTo?.chat.inputPeer
if (params.commentTo) { if (params.commentTo) {
[peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!) [peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!)
@ -115,5 +140,30 @@ export async function _processCommonSendParameters(
} }
} }
return { peer, replyTo } if (params.replyToStory && replyTo) {
throw new MtArgumentError('replyTo/commentTo and replyToStory cannot be used together')
}
let tlReplyTo: tl.TypeInputReplyTo | undefined = undefined
if (replyTo) {
tlReplyTo = {
_: 'inputReplyToMessage',
replyToMsgId: replyTo,
replyToPeerId: replyToPeer,
quoteText: params.quoteText,
quoteEntities: params.quoteEntities,
}
} else if (params.replyToStory) {
tlReplyTo = {
_: 'inputReplyToStory',
storyId: params.replyToStory,
userId: normalizeToInputUser(peer),
}
}
return {
peer,
replyTo: tlReplyTo,
}
} }

View file

@ -28,6 +28,14 @@ export async function sendMediaGroup(
chatId: InputPeerLike, chatId: InputPeerLike,
medias: (InputMediaLike | string)[], medias: (InputMediaLike | string)[],
params?: CommonSendParams & { params?: CommonSendParams & {
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
/** /**
* Function that will be called after some part has been uploaded. * Function that will be called after some part has been uploaded.
* Only used when a file that requires uploading is passed, * Only used when a file that requires uploading is passed,
@ -93,16 +101,12 @@ export async function sendMediaGroup(
peer, peer,
multiMedia, multiMedia,
silent: params.silent, silent: params.silent,
replyTo: replyTo ? replyTo,
{
_: 'inputReplyToMessage',
replyToMsgId: replyTo,
} :
undefined,
scheduleDate: normalizeDate(params.schedule), scheduleDate: normalizeDate(params.schedule),
clearDraft: params.clearDraft, clearDraft: params.clearDraft,
noforwards: params.forbidForwards, noforwards: params.forbidForwards,
sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined,
invertMedia: params.invertMedia,
}) })
assertIsUpdatesGroup('sendMediaGroup', res) assertIsUpdatesGroup('sendMediaGroup', res)

View file

@ -36,6 +36,14 @@ export async function sendMedia(
*/ */
replyMarkup?: ReplyMarkup replyMarkup?: ReplyMarkup
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invert?: boolean
/** /**
* Override caption for `media`. * Override caption for `media`.
* *
@ -92,12 +100,7 @@ export async function sendMedia(
peer, peer,
media: inputMedia, media: inputMedia,
silent: params.silent, silent: params.silent,
replyTo: replyTo ? replyTo,
{
_: 'inputReplyToMessage',
replyToMsgId: replyTo,
} :
undefined,
randomId: randomLong(), randomId: randomLong(),
scheduleDate: normalizeDate(params.schedule), scheduleDate: normalizeDate(params.schedule),
replyMarkup, replyMarkup,
@ -106,6 +109,7 @@ export async function sendMedia(
clearDraft: params.clearDraft, clearDraft: params.clearDraft,
noforwards: params.forbidForwards, noforwards: params.forbidForwards,
sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined,
invertMedia: params.invert,
}) })
const msg = _findMessageInUpdate(client, res, false, !params.shouldDispatch) const msg = _findMessageInUpdate(client, res, false, !params.shouldDispatch)

View file

@ -0,0 +1,106 @@
import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core'
import { InputPeerLike } from '../../index.js'
import { Message } from '../../types/messages/message.js'
import { sendMedia } from './send-media.js'
import { sendMediaGroup } from './send-media-group.js'
import { sendText } from './send-text.js'
// @exported
export type QuoteParamsFrom<T> = Omit<NonNullable<T>, 'quoteText' | 'quoteEntities'> & {
/**
* Destination chat ID, username, phone, `"me"` or `"self"`
*
* @default `message.chat`
*/
toChatId?: InputPeerLike
/** Index of the first character to quote (inclusive) */
start: number
/** Index of the last character to quote (exclusive) */
end: number
}
function extractQuote(message: Message, from: number, to: number): [string, tl.TypeMessageEntity[] | undefined] {
const { raw } = message
if (raw._ === 'messageService') throw new MtArgumentError('Cannot quote service message')
if (!raw.message) throw new MtArgumentError('Cannot quote empty message')
const text = raw.message
if (from < 0) from = 0
if (to > text.length) to = text.length
if (from >= to) throw new MtArgumentError('Invalid quote range')
if (!raw.entities) return [text.slice(from, to), undefined]
const entities: tl.TypeMessageEntity[] = []
for (const ent of raw.entities) {
const start = ent.offset
const end = ent.offset + ent.length
if (start >= to || end <= from) continue
const newStart = Math.max(start, from) - from
const newEnd = Math.min(end, to) - from
const newEnt = { ...ent, offset: newStart, length: newEnd - newStart }
entities.push(newEnt)
}
return [text.slice(from, to), entities]
}
/** Send a text in reply to a given quote */
export function quoteWithText(
client: BaseTelegramClient,
message: Message,
params: QuoteParamsFrom<Parameters<typeof sendText>[3]> & {
/** Text to send */
text: Parameters<typeof sendText>[2]
},
): ReturnType<typeof sendText> {
const { toChatId = message.chat, start, end, text, ...params__ } = params
const params_ = params__ as NonNullable<Parameters<typeof sendText>[3]>
params_.replyTo = message
;[params_.quoteText, params_.quoteEntities] = extractQuote(message, params.start, params.end)
return sendText(client, toChatId, text, params_)
}
/** Send a media in reply to a given quote */
export function quoteWithMedia(
client: BaseTelegramClient,
message: Message,
params: QuoteParamsFrom<Parameters<typeof sendMedia>[3]> & {
/** Media to send */
media: Parameters<typeof sendMedia>[2]
},
): ReturnType<typeof sendMedia> {
const { toChatId = message.chat, start, end, media, ...params__ } = params
const params_ = params__ as NonNullable<Parameters<typeof sendMedia>[3]>
params_.replyTo = message
;[params_.quoteText, params_.quoteEntities] = extractQuote(message, params.start, params.end)
return sendMedia(client, toChatId, media, params_)
}
/** Send a media group in reply to a given quote */
export function quoteWithMediaGroup(
client: BaseTelegramClient,
message: Message,
params: QuoteParamsFrom<Parameters<typeof sendMediaGroup>[3]> & {
/** Media group to send */
medias: Parameters<typeof sendMediaGroup>[2]
},
): ReturnType<typeof sendMediaGroup> {
const { toChatId, start, end, medias, ...params__ } = params
const params_ = params__ as NonNullable<Parameters<typeof sendMediaGroup>[3]>
params_.replyTo = message
;[params_.quoteText, params_.quoteEntities] = extractQuote(message, params.start, params.end)
return sendMediaGroup(client, message.chat.inputPeer, medias, params_)
}

View file

@ -45,6 +45,14 @@ export async function sendText(
* Whether to disable links preview in this message * Whether to disable links preview in this message
*/ */
disableWebPreview?: boolean disableWebPreview?: boolean
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}, },
): Promise<Message> { ): Promise<Message> {
if (!params) params = {} if (!params) params = {}
@ -59,12 +67,7 @@ export async function sendText(
peer, peer,
noWebpage: params.disableWebPreview, noWebpage: params.disableWebPreview,
silent: params.silent, silent: params.silent,
replyTo: replyTo ? replyTo,
{
_: 'inputReplyToMessage',
replyToMsgId: replyTo,
} :
undefined,
randomId: randomLong(), randomId: randomLong(),
scheduleDate: normalizeDate(params.schedule), scheduleDate: normalizeDate(params.schedule),
replyMarkup, replyMarkup,
@ -73,6 +76,7 @@ export async function sendText(
clearDraft: params.clearDraft, clearDraft: params.clearDraft,
noforwards: params.forbidForwards, noforwards: params.forbidForwards,
sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined,
invertMedia: params.invertMedia,
}) })
if (res._ === 'updateShortSentMessage') { if (res._ === 'updateShortSentMessage') {

View file

@ -1,7 +1,13 @@
import { assertNever, BaseTelegramClient, tl } from '@mtcute/core' import { assertNever, BaseTelegramClient, tl } from '@mtcute/core'
import { _parseEntities } from '../../../methods/messages/parse-entities.js' import { _parseEntities } from '../../../methods/messages/parse-entities.js'
import { InputMediaContact, InputMediaGeo, InputMediaGeoLive, InputMediaVenue } from '../../media/index.js' import {
InputMediaContact,
InputMediaGeo,
InputMediaGeoLive,
InputMediaVenue,
InputMediaWebpage,
} from '../../media/index.js'
import { FormattedString } from '../../parser.js' import { FormattedString } from '../../parser.js'
import { BotKeyboard, ReplyMarkup } from '../keyboards.js' import { BotKeyboard, ReplyMarkup } from '../keyboards.js'
@ -31,6 +37,14 @@ export interface InputInlineMessageText {
* Whether to disable links preview in this message * Whether to disable links preview in this message
*/ */
disableWebPreview?: boolean disableWebPreview?: boolean
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
} }
/** /**
@ -55,6 +69,14 @@ export interface InputInlineMessageMedia {
* Message reply markup * Message reply markup
*/ */
replyMarkup?: ReplyMarkup replyMarkup?: ReplyMarkup
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
} }
/** /**
@ -109,6 +131,32 @@ export interface InputInlineMessageContact extends InputMediaContact {
replyMarkup?: ReplyMarkup replyMarkup?: ReplyMarkup
} }
export interface InputInlineMessageWebpage extends InputMediaWebpage {
/**
* Text of the message
*/
text: string | FormattedString<string>
/**
* Text markup entities.
* If passed, parse mode is ignored
*/
entities?: tl.TypeMessageEntity[]
/**
* Message reply markup
*/
replyMarkup?: ReplyMarkup
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}
export type InputInlineMessage = export type InputInlineMessage =
| InputInlineMessageText | InputInlineMessageText
| InputInlineMessageMedia | InputInlineMessageMedia
@ -117,6 +165,7 @@ export type InputInlineMessage =
| InputInlineMessageVenue | InputInlineMessageVenue
| InputInlineMessageGame | InputInlineMessageGame
| InputInlineMessageContact | InputInlineMessageContact
| InputInlineMessageWebpage
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
export namespace BotInlineMessage { export namespace BotInlineMessage {
@ -203,6 +252,16 @@ export namespace BotInlineMessage {
return ret return ret
} }
/**
* Create an inline message containing a webpage
*/
export function webpage(params: Omit<InputInlineMessageWebpage, 'type'>): InputInlineMessageWebpage {
const ret = params as tl.Mutable<InputInlineMessageWebpage>
ret.type = 'webpage'
return ret
}
/** @internal */ /** @internal */
export async function _convertToTl( export async function _convertToTl(
client: BaseTelegramClient, client: BaseTelegramClient,
@ -218,6 +277,7 @@ export namespace BotInlineMessage {
message, message,
entities, entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup), replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
} }
} }
case 'media': { case 'media': {
@ -228,6 +288,7 @@ export namespace BotInlineMessage {
message, message,
entities, entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup), replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
} }
} }
case 'geo': case 'geo':
@ -274,6 +335,21 @@ export namespace BotInlineMessage {
vcard: obj.vcard ?? '', vcard: obj.vcard ?? '',
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup), replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
} }
case 'webpage': {
const [message, entities] = await _parseEntities(client, obj.text, parseMode, obj.entities)
return {
_: 'inputBotInlineMessageMediaWebPage',
message,
entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
forceLargeMedia: obj.size === 'large',
forceSmallMedia: obj.size === 'small',
optional: !obj.required,
url: obj.url,
}
}
default: default:
assertNever(obj) assertNever(obj)
} }

View file

@ -424,8 +424,8 @@ export class Conversation {
} }
const pred = filter ? const pred = filter ?
(msg: Message) => (msg.replyToMessageId === msgId ? filter(msg) : false) : (msg: Message) => (msg.replyToMessage?.id === msgId ? filter(msg) : false) :
(msg: Message) => msg.replyToMessageId === msgId (msg: Message) => msg.replyToMessage?.id === msgId
return this.waitForNewMessage(pred, params?.timeout) return this.waitForNewMessage(pred, params?.timeout)
} }

View file

@ -567,6 +567,28 @@ export interface InputMediaStory extends CaptionMixin {
id: number id: number
} }
/** A webpage to be sent */
export interface InputMediaWebpage extends CaptionMixin {
type: 'webpage'
/**
* Whether the link must be present in the message
* for the preview to appear
*/
required?: boolean
/**
* By default, size of the media in the preview
* is determined based on content type.
*
* You can override this behaviour by passing the wanted size here
*/
size?: 'large' | 'small'
/** Webpage URL */
url: string
}
/** /**
* Input media that can be sent somewhere. * Input media that can be sent somewhere.
* *
@ -594,6 +616,7 @@ export type InputMediaLike =
| InputMediaPoll | InputMediaPoll
| InputMediaQuiz | InputMediaQuiz
| InputMediaStory | InputMediaStory
| InputMediaWebpage
| tl.TypeInputMedia | tl.TypeInputMedia
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
@ -843,6 +866,20 @@ export namespace InputMedia {
return ret return ret
} }
/**
* Create a webpage to be sent
*
* @param url Webpage URL
* @param params Additional parameters
*/
export function webpage(url: string, params: OmitTypeAndFile<InputMediaWebpage, 'url'> = {}): InputMediaWebpage {
const ret = params as tl.Mutable<InputMediaWebpage>
ret.type = 'webpage'
ret.url = url
return ret
}
/** /**
* Create a document to be sent, which subtype * Create a document to be sent, which subtype
* is inferred automatically by file contents. * is inferred automatically by file contents.

View file

@ -18,10 +18,12 @@ export class DraftMessage {
} }
/** /**
* The message this message will reply to * Information about replies/quotes in this message
*/ */
get replyToMessageId(): number | null { get replyToMessage(): tl.RawInputReplyToMessage | null {
return this.raw.replyToMsgId ?? null if (this.raw.replyTo?._ !== 'inputReplyToMessage') return null
return this.raw.replyTo
} }
/** /**

View file

@ -4,6 +4,8 @@ export * from './input-message-id.js'
export * from './message.js' export * from './message.js'
export * from './message-action.js' export * from './message-action.js'
export * from './message-entity.js' export * from './message-entity.js'
export * from './message-forward.js'
export * from './message-media.js' export * from './message-media.js'
export * from './message-reactions.js' export * from './message-reactions.js'
export * from './message-replies.js'
export * from './search-filters.js' export * from './search-filters.js'

View file

@ -0,0 +1,149 @@
import { MtTypeAssertionError, tl } from '@mtcute/core'
import { makeInspectable } from '../../utils/inspectable.js'
import { memoizeGetters } from '../../utils/memoize.js'
import { Chat } from '../peers/chat.js'
import { PeersIndex } from '../peers/peers-index.js'
import { User } from '../peers/user.js'
import { MessageEntity } from './message-entity.js'
import { _messageMediaFromTl, MessageMedia } from './message-media.js'
/**
* Information about forwarded message origin
*/
export class MessageForwardInfo {
constructor(
readonly raw: tl.RawMessageFwdHeader,
readonly _peers: PeersIndex,
) {}
/**
* Date the original message was sent
*/
get date(): Date {
return new Date(this.raw.date * 1000)
}
/**
* Sender of the original message (either user or a channel)
* or their name (for users with private forwards)
*/
get sender(): User | Chat | string {
if (this.raw.fromName) {
return this.raw.fromName
}
if (this.raw.fromId) {
switch (this.raw.fromId._) {
case 'peerChannel':
return new Chat(this._peers.chat(this.raw.fromId.channelId))
case 'peerUser':
return new User(this._peers.user(this.raw.fromId.userId))
default:
throw new MtTypeAssertionError('raw.fwdFrom.fromId', 'peerUser | peerChannel', this.raw.fromId._)
}
}
throw new MtTypeAssertionError('MessageForwardInfo', 'to have fromId or fromName', 'neither')
}
/**
* For "saved" messages (i.e. messages forwarded to yourself,
* "Saved Messages"), the peer where the message was originally sent
*/
fromChat(): User | Chat | null {
if (!this.raw.savedFromPeer) return null
return Chat._parseFromPeer(this.raw.savedFromPeer, this._peers)
}
/**
* For messages forwarded from channels,
* identifier of the original message in the channel
*/
get fromMessageId(): number | null {
return this.raw.savedFromMsgId ?? null
}
/**
* For messages forwarded from channels,
* signature of the post author (if present)
*/
get signature(): string | null {
return this.raw.postAuthor ?? null
}
}
memoizeGetters(MessageForwardInfo, ['sender', 'fromChat'])
makeInspectable(MessageForwardInfo)
/**
* Information about a message that this message is a reply to
*/
export class MessageReplyInfo {
constructor(
readonly raw: tl.RawMessageReplyHeader,
readonly _peers: PeersIndex,
) {}
/** Whether this message is a reply to another scheduled message */
get isScheduled(): boolean {
return this.raw.replyToScheduled!
}
/** Whether this message was sent to a forum topic */
get isForumTopic(): boolean {
return this.raw.forumTopic!
}
/** Whether this message is quoting another message */
get isQuote(): boolean {
return this.raw.quote!
}
/**
* If replied-to message is available, its ID
*/
get id(): number | null {
return this.raw.replyToMsgId ?? null
}
/** ID of the replies thread where this message belongs to */
get threadId(): number | null {
return this.raw.replyToTopId ?? null
}
/**
* If replied-to message is available, chat where the message was sent.
*
* If `null`, the message was sent in the same chat.
*/
get chat(): Chat | null {
return this.raw.replyToPeerId ? Chat._parseFromPeer(this.raw.replyToPeerId, this._peers) : null
}
/** If this message is a quote, text of the quote */
get quoteText(): string {
return this.raw.quoteText ?? ''
}
/** Message entities contained in the quote */
get quoteEntities(): MessageEntity[] {
return this.raw.quoteEntities?.map((e) => new MessageEntity(e)) ?? []
}
/**
* Media contained in the replied-to message
*
* Only available in case the replied-to message is in a different chat
* (i.e. {@link chat} is not `null`)
*/
get media(): MessageMedia {
if (!this.raw.replyMedia) return null
return _messageMediaFromTl(this._peers, this.raw.replyMedia)
}
}
memoizeGetters(MessageReplyInfo, ['chat', 'quoteEntities', 'media'])
makeInspectable(MessageReplyInfo)

View file

@ -0,0 +1,79 @@
import { getMarkedPeerId, tl } from '@mtcute/core'
import { makeInspectable } from '../../utils/inspectable.js'
import { memoizeGetters } from '../../utils/memoize.js'
import { Chat } from '../peers/chat.js'
import { PeersIndex } from '../peers/peers-index.js'
import { User } from '../peers/user.js'
/**
* Information about replies to a message
*/
export class MessageRepliesInfo {
constructor(
readonly raw: tl.RawMessageReplies,
readonly _peers: PeersIndex,
) {}
/**
* Whether this message is a channel post that has a comments thread
* in the linked discussion group
*/
get hasComments(): boolean {
return this.raw.comments!
}
/**
* Total number of replies
*/
get count(): number {
return this.raw.replies
}
/**
* Whether this reply thread has unread messages
*/
get hasUnread(): boolean {
return this.raw.readMaxId !== undefined && this.raw.readMaxId !== this.raw.maxId
}
/**
* ID of the last message in the thread (if any)
*/
get lastMessageId(): number | null {
return this.raw.maxId ?? null
}
/**
* ID of the last read message in the thread (if any)
*/
get lastReadMessageId(): number | null {
return this.raw.readMaxId ?? null
}
/**
* ID of the discussion group for the post
*/
get discussion(): number {
return getMarkedPeerId(this.raw.channelId!, 'channel')
}
/**
* Last few commenters to the post (usually 3)
*/
get repliers(): (User | Chat)[] {
return this.raw.recentRepliers!.map((it) => {
switch (it._) {
case 'peerUser':
return new User(this._peers.user(it.userId))
case 'peerChannel':
return new Chat(this._peers.chat(it.channelId))
default:
throw new Error('Unexpected peer type: ' + it._)
}
})
}
}
memoizeGetters(MessageRepliesInfo, ['discussion', 'repliers'])
makeInspectable(MessageRepliesInfo)

View file

@ -16,82 +16,10 @@ import { PeersIndex } from '../peers/peers-index.js'
import { User } from '../peers/user.js' import { User } from '../peers/user.js'
import { _messageActionFromTl, MessageAction } from './message-action.js' import { _messageActionFromTl, MessageAction } from './message-action.js'
import { MessageEntity } from './message-entity.js' import { MessageEntity } from './message-entity.js'
import { MessageForwardInfo, MessageReplyInfo } from './message-forward.js'
import { _messageMediaFromTl, MessageMedia } from './message-media.js' import { _messageMediaFromTl, MessageMedia } from './message-media.js'
import { MessageReactions } from './message-reactions.js' import { MessageReactions } from './message-reactions.js'
import { MessageRepliesInfo } from './message-replies.js'
/** Information about a forward */
export interface MessageForwardInfo {
/**
* Date the original message was sent
*/
date: Date
/**
* Sender of the original message (either user or a channel)
* or their name (for users with private forwards)
*/
sender: User | Chat | string
/**
* For messages forwarded from channels,
* identifier of the original message in the channel
*/
fromMessageId?: number
/**
* For messages forwarded from channels,
* signature of the post author (if present)
*/
signature?: string
}
/** Information about replies to a message */
export interface MessageRepliesInfo {
/**
* Whether this message is a channel post that has a comments thread
* in the linked discussion group
*/
hasComments: false
/**
* Total number of replies
*/
count: number
/**
* Whether this reply thread has unread messages
*/
hasUnread: boolean
/**
* ID of the last message in the thread (if any)
*/
lastMessageId?: number
/**
* ID of the last read message in the thread (if any)
*/
lastReadMessageId?: number
}
/** Information about comments to a channel post */
export interface MessageCommentsInfo extends Omit<MessageRepliesInfo, 'isComments'> {
/**
* Whether this message is a channel post that has a comments thread
* in the linked discussion group
*/
isComments: true
/**
* ID of the discussion group for the post
*/
discussion: number
/**
* IDs of the last few commenters to the post
*/
repliers: number[]
}
/** /**
* A Telegram message. * A Telegram message.
@ -226,33 +154,8 @@ export class Message {
if (this.raw._ !== 'message' || !this.raw.fwdFrom) { if (this.raw._ !== 'message' || !this.raw.fwdFrom) {
return null return null
} }
const fwd = this.raw.fwdFrom
let sender: User | Chat | string return new MessageForwardInfo(this.raw.fwdFrom, this._peers)
if (fwd.fromName) {
sender = fwd.fromName
} else if (fwd.fromId) {
switch (fwd.fromId._) {
case 'peerChannel':
sender = new Chat(this._peers.chat(fwd.fromId.channelId))
break
case 'peerUser':
sender = new User(this._peers.user(fwd.fromId.userId))
break
default:
throw new MtTypeAssertionError('raw.fwdFrom.fromId', 'peerUser | peerChannel', fwd.fromId._)
}
} else {
return null
}
return {
date: new Date(fwd.date * 1000),
sender,
fromMessageId: fwd.savedFromMsgId,
signature: fwd.postAuthor,
}
} }
/** /**
@ -275,51 +178,29 @@ export class Message {
/** /**
* Information about comments (for channels) or replies (for groups) * Information about comments (for channels) or replies (for groups)
*/ */
get replies(): MessageRepliesInfo | MessageCommentsInfo | null { get replies(): MessageRepliesInfo | null {
if (this.raw._ !== 'message' || !this.raw.replies) return null if (this.raw._ !== 'message' || !this.raw.replies) return null
const r = this.raw.replies return new MessageRepliesInfo(this.raw.replies, this._peers)
const obj: MessageRepliesInfo = {
hasComments: r.comments as false,
count: r.replies,
hasUnread: r.readMaxId !== undefined && r.readMaxId !== r.maxId,
lastMessageId: r.maxId,
lastReadMessageId: r.readMaxId,
}
if (r.comments) {
const o = obj as unknown as MessageCommentsInfo
o.discussion = getMarkedPeerId(r.channelId!, 'channel')
o.repliers = r.recentRepliers?.map((it) => getMarkedPeerId(it)) ?? []
}
return obj
} }
/** /**
* For replies, the ID of the message that current message * For replies, information about the that is being replied to.
* replies to. *
* Mutually exclusive with {@link replyToStory}
*/ */
get replyToMessageId(): number | null { get replyToMessage(): MessageReplyInfo | null {
if (this.raw.replyTo?._ !== 'messageReplyHeader') return null if (this.raw.replyTo?._ !== 'messageReplyHeader') return null
return this.raw.replyTo.replyToMsgId ?? null return new MessageReplyInfo(this.raw.replyTo, this._peers)
}
/**
* For replies, ID of the thread/topic
* (i.e. ID of the top message in the thread/topic)
*/
get replyToThreadId(): number | null {
if (this.raw.replyTo?._ !== 'messageReplyHeader') return null
return this.raw.replyTo.replyToTopId ?? null
} }
/** /**
* For replies, information about the story that is being replied to * For replies, information about the story that is being replied to
*
* Mutually exclusive with {@link replyToMessage}
*/ */
get replyToStoryId(): tl.RawMessageReplyStoryHeader | null { get replyToStory(): tl.RawMessageReplyStoryHeader | null {
if (this.raw.replyTo?._ !== 'messageReplyStoryHeader') return null if (this.raw.replyTo?._ !== 'messageReplyStoryHeader') return null
return this.raw.replyTo return this.raw.replyTo

View file

@ -464,6 +464,33 @@ export class Chat {
return 0 return 0
} }
/**
* Name color of this chat, which should also be used when
* rendering replies to their messages and web previews sent by them.
*
* Note that this value is **not** an RGB color representation. Instead, it is
* a number which should be used to pick a color from a predefined
* list of colors:
* - `0-6` are the default colors used by Telegram clients:
* `red, orange, purple, green, sea, blue, pink`
* - `>= 7` are returned by `help.getAppConfig`.
*/
get color(): number {
if (this.peer._ !== 'channel' && this.peer._ !== 'user') return this.peer.id % 7
return this.peer.color ?? this.peer.id % 7
}
/**
* ID of the emoji that should be used as a background pattern
* when rendering replies to this user's messages.
*/
get replyBackgroundEmojiId(): tl.Long | null {
if (this.peer._ !== 'channel' && this.peer._ !== 'user') return null
return this.peer.backgroundEmojiId ?? null
}
/** /**
* Get a {@link User} from this chat. * Get a {@link User} from this chat.
* *

View file

@ -320,6 +320,29 @@ export class User {
return this.raw.storiesMaxId ?? 0 return this.raw.storiesMaxId ?? 0
} }
/**
* Name color of this user, which should also be used when
* rendering replies to their messages and web previews sent by them.
*
* Note that this value is **not** an RGB color representation. Instead, it is
* a number which should be used to pick a color from a predefined
* list of colors:
* - `0-6` are the default colors used by Telegram clients:
* `red, orange, purple, green, sea, blue, pink`
* - `>= 7` are returned by `help.getAppConfig`.
*/
get color(): number {
return this.raw.color ?? this.raw.id % 7
}
/**
* ID of the emoji that should be used as a background pattern
* when rendering replies to this user's messages.
*/
get replyBackgroundEmojiId(): tl.Long | null {
return this.raw.backgroundEmojiId ?? null
}
/** /**
* Create a mention for the user. * Create a mention for the user.
* *

View file

@ -6,7 +6,7 @@ import { makeInspectable } from '../../utils/index.js'
* Information about boosts in a channel * Information about boosts in a channel
*/ */
export class BoostStats { export class BoostStats {
constructor(readonly raw: tl.stories.RawBoostsStatus) {} constructor(readonly raw: tl.premium.RawBoostsStatus) {}
/** Whether this channel is being boosted by the current user */ /** Whether this channel is being boosted by the current user */
get isBoosting(): boolean { get isBoosting(): boolean {

View file

@ -44,7 +44,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@mtcute/tl": "workspace:^165.0.0", "@mtcute/tl": "workspace:^166.0.0",
"@mtcute/tl-runtime": "workspace:^1.0.0", "@mtcute/tl-runtime": "workspace:^1.0.0",
"@types/events": "3.0.0", "@types/events": "3.0.0",
"@types/node": "18.16.0", "@types/node": "18.16.0",
@ -58,4 +58,4 @@
"node-forge": "1.3.1", "node-forge": "1.3.1",
"ws": "8.13.0" "ws": "8.13.0"
} }
} }

View file

@ -85,6 +85,21 @@ export class MessageContext extends Message implements UpdateContext<Message> {
return this.client.replyMediaGroup(this, ...params) return this.client.replyMediaGroup(this, ...params)
} }
/** Send a text message in reply to this message */
quoteWithText(params: Parameters<TelegramClient['quoteWithText']>[1]) {
return this.client.quoteWithText(this, params)
}
/** Send a media in reply to this message */
quoteWithMedia(params: Parameters<TelegramClient['quoteWithMedia']>[1]) {
return this.client.quoteWithMedia(this, params)
}
/** Send a media group in reply to this message */
quoteWithMediaGroup(params: Parameters<TelegramClient['quoteWithMediaGroup']>[1]) {
return this.client.quoteWithMediaGroup(this, params)
}
/** Send a text as a comment to this message */ /** Send a text as a comment to this message */
commentText(...params: ParametersSkip1<TelegramClient['commentText']>) { commentText(...params: ParametersSkip1<TelegramClient['commentText']>) {
return this.client.commentText(this, ...params) return this.client.commentText(this, ...params)

View file

@ -6,6 +6,7 @@ import {
Message, Message,
MessageAction, MessageAction,
MessageMediaType, MessageMediaType,
MessageReplyInfo,
RawDocument, RawDocument,
RawLocation, RawLocation,
Sticker, Sticker,
@ -35,7 +36,7 @@ export const outgoing: UpdateFilter<Message, { isOutgoing: true }> = (msg) => ms
/** /**
* Filter messages that are replies to some other message * Filter messages that are replies to some other message
*/ */
export const reply: UpdateFilter<Message, { replyToMessageId: number }> = (msg) => msg.replyToMessageId !== null export const reply: UpdateFilter<Message, { replyToMessage: MessageReplyInfo }> = (msg) => msg.replyToMessage !== null
/** /**
* Filter messages containing some media * Filter messages containing some media
@ -220,7 +221,7 @@ export const replyTo =
filter?: UpdateFilter<Message, Mod, State>, filter?: UpdateFilter<Message, Mod, State>,
): UpdateFilter<MessageContext, { getReplyTo: () => Promise<Message & Mod> }, State> => ): UpdateFilter<MessageContext, { getReplyTo: () => Promise<Message & Mod> }, State> =>
async (msg, state) => { async (msg, state) => {
if (!msg.replyToMessageId) return false if (!msg.replyToMessage?.id) return false
const reply = await msg.getReplyTo() const reply = await msg.getReplyTo()
if (!reply) return false if (!reply) return false

View file

@ -22,7 +22,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@mtcute/tl": "workspace:^165.0.0", "@mtcute/tl": "workspace:^166.0.0",
"htmlparser2": "^6.0.1", "htmlparser2": "^6.0.1",
"long": "5.2.3" "long": "5.2.3"
}, },

View file

@ -22,7 +22,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@mtcute/tl": "workspace:^165.0.0", "@mtcute/tl": "workspace:^166.0.0",
"long": "5.2.3" "long": "5.2.3"
}, },
"devDependencies": { "devDependencies": {

View file

@ -2,7 +2,7 @@
> TL schema and related utils used for mtcute. > TL schema and related utils used for mtcute.
Generated from TL layer **165** (last updated on 04.10.2023). Generated from TL layer **166** (last updated on 29.10.2023).
## About ## About

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
{ {
"name": "@mtcute/tl", "name": "@mtcute/tl",
"version": "165.0.0", "version": "166.0.0",
"description": "TL schema used for mtcute", "description": "TL schema used for mtcute",
"main": "index.js", "main": "index.js",
"author": "Alina Sireneva <alina@tei.su>", "author": "Alina Sireneva <alina@tei.su>",