fix: added MessageNotFoundError, improved getMessages, fixed methods that use it

This commit is contained in:
teidesu 2021-07-06 02:24:58 +03:00
parent 36ba4c3b87
commit d5e595d7cf
13 changed files with 257 additions and 34 deletions

View file

@ -97,6 +97,7 @@ import { forwardMessages } from './methods/messages/forward-messages'
import { _getDiscussionMessage } from './methods/messages/get-discussion-message' import { _getDiscussionMessage } from './methods/messages/get-discussion-message'
import { getHistory } from './methods/messages/get-history' import { getHistory } from './methods/messages/get-history'
import { getMessageGroup } from './methods/messages/get-message-group' import { getMessageGroup } from './methods/messages/get-message-group'
import { getMessagesUnsafe } from './methods/messages/get-messages-unsafe'
import { getMessages } from './methods/messages/get-messages' import { getMessages } from './methods/messages/get-messages'
import { iterHistory } from './methods/messages/iter-history' import { iterHistory } from './methods/messages/iter-history'
import { _normalizeInline } from './methods/messages/normalize-inline' import { _normalizeInline } from './methods/messages/normalize-inline'
@ -2101,9 +2102,42 @@ export interface TelegramClient extends BaseTelegramClient {
*/ */
getMessageGroup(chatId: InputPeerLike, message: number): Promise<Message[]> getMessageGroup(chatId: InputPeerLike, message: number): Promise<Message[]>
/** /**
* Get a single message in chat by its ID * Get a single message from PM or legacy group by its ID.
* For channels, use {@link getMessages}.
* *
* **Note**: this method might return empty message * 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`)
*/
getMessagesUnsafe(
messageId: number,
fromReply?: boolean
): Promise<Message | null>
/**
* Get messages from PM or legacy group by their IDs.
* For channels, use {@link getMessages}.
*
* Unlike {@link getMessages}, this method does not
* check if the message belongs to some chat.
*
* Fot messages that were not found, `null` will be
* returned at that position.
*
* @param messageIds Messages IDs
* @param [fromReply=false]
* Whether the reply to a given message should be fetched
* (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`)
*/
getMessagesUnsafe(
messageIds: number[],
fromReply?: boolean
): Promise<(Message | null)[]>
/**
* Get a single message in chat by its ID
* *
* @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`
* @param messageId Messages ID * @param messageId Messages ID
@ -2115,11 +2149,12 @@ export interface TelegramClient extends BaseTelegramClient {
chatId: InputPeerLike, chatId: InputPeerLike,
messageId: number, messageId: number,
fromReply?: boolean fromReply?: boolean
): Promise<Message> ): Promise<Message | null>
/** /**
* Get messages in chat by their IDs * Get messages in chat by their IDs
* *
* **Note**: this method might return empty messages * Fot messages that were not found, `null` will be
* returned at that position.
* *
* @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 Messages IDs * @param messageIds Messages IDs
@ -2131,7 +2166,7 @@ export interface TelegramClient extends BaseTelegramClient {
chatId: InputPeerLike, chatId: InputPeerLike,
messageIds: number[], messageIds: number[],
fromReply?: boolean fromReply?: boolean
): Promise<Message[]> ): Promise<(Message | null)[]>
/** /**
* Iterate through a chat history sequentially. * Iterate through a chat history sequentially.
* *
@ -2569,6 +2604,19 @@ export interface TelegramClient extends BaseTelegramClient {
*/ */
replyTo?: number | Message 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
/** /**
* Message to comment to. Either a message object or message ID. * Message to comment to. Either a message object or message ID.
* *
@ -3321,6 +3369,7 @@ export class TelegramClient extends BaseTelegramClient {
protected _getDiscussionMessage = _getDiscussionMessage protected _getDiscussionMessage = _getDiscussionMessage
getHistory = getHistory getHistory = getHistory
getMessageGroup = getMessageGroup getMessageGroup = getMessageGroup
getMessagesUnsafe = getMessagesUnsafe
getMessages = getMessages getMessages = getMessages
iterHistory = iterHistory iterHistory = iterHistory
protected _normalizeInline = _normalizeInline protected _normalizeInline = _normalizeInline

View file

@ -1,5 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, MtCuteArgumentError, Message } from '../../types' import { InputPeerLike, MtCuteArgumentError, Message } from '../../types'
import { isInputPeerChannel } from '../../utils/peer-utils'
/** /**
* Get all messages inside of a message group * Get all messages inside of a message group
@ -15,16 +16,25 @@ export async function getMessageGroup(
): Promise<Message[]> { ): 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,
// we use larger number.
// still, this might not be enough :shrug:
const peer = await this.resolvePeer(chatId)
const delta = isInputPeerChannel(peer) ? 9 : 19
const ids: number[] = [] const ids: number[] = []
for (let i = Math.max(message - 9, 0); i <= message + 9; i++) { for (let i = Math.max(message - delta, 0); i <= message + delta; i++) {
ids.push(i) ids.push(i)
} }
const messages = await this.getMessages(chatId, ids) const messages = await this.getMessages(chatId, ids)
const groupedId = messages.find((it) => it.id === message)!.groupedId const groupedId = messages.find((it) => it?.id === message)!.groupedId
if (!groupedId) throw new MtCuteArgumentError('This message is not grouped') if (!groupedId) throw new MtCuteArgumentError('This message is not grouped')
return messages.filter((it) => it.groupedId?.eq(groupedId)) return messages.filter(
(it) => it && it.groupedId?.eq(groupedId)
) as Message[]
} }

View file

@ -0,0 +1,86 @@
import { TelegramClient } from '../../client'
import { MaybeArray } from '@mtcute/core'
import {
createUsersChatsIndex,
} from '../../utils/peer-utils'
import { tl } from '@mtcute/tl'
import { Message, MtCuteTypeAssertionError } 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`)
* @internal
*/
export async function getMessagesUnsafe(
this: TelegramClient,
messageId: number,
fromReply?: boolean
): Promise<Message | null>
/**
* Get messages from PM or legacy group by their IDs.
* For channels, use {@link getMessages}.
*
* Unlike {@link getMessages}, this method does not
* check if the message belongs to some chat.
*
* Fot messages that were not found, `null` will be
* returned at that position.
*
* @param messageIds Messages IDs
* @param [fromReply=false]
* Whether the reply to a given message should be fetched
* (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`)
* @internal
*/
export async function getMessagesUnsafe(
this: TelegramClient,
messageIds: number[],
fromReply?: boolean
): Promise<(Message | null)[]>
/** @internal */
export async function getMessagesUnsafe(
this: TelegramClient,
messageIds: MaybeArray<number>,
fromReply = false
): Promise<MaybeArray<Message | null>> {
const isSingle = !Array.isArray(messageIds)
if (isSingle) messageIds = [messageIds as number]
const type = fromReply ? 'inputMessageReplyTo' : 'inputMessageID'
const ids: tl.TypeInputMessage[] = (messageIds as number[]).map((it) => ({
_: type,
id: it,
}))
const res = await this.call({
_: 'messages.getMessages',
id: ids,
})
if (res._ === 'messages.messagesNotModified')
throw new MtCuteTypeAssertionError(
'getMessages',
'!messages.messagesNotModified',
res._
)
const { users, chats } = createUsersChatsIndex(res)
const ret = res.messages
.map((msg) => {
if (msg._ === 'messageEmpty') return null
return new Message(this, msg, users, chats)
})
return isSingle ? ret[0] : ret
}

View file

@ -11,8 +11,6 @@ import { Message, InputPeerLike, MtCuteTypeAssertionError } from '../../types'
/** /**
* Get a single message in chat by its ID * Get a single message in chat by its ID
* *
* **Note**: this method might return empty message
*
* @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 messageId Messages ID * @param messageId Messages ID
* @param [fromReply=false] * @param [fromReply=false]
@ -25,11 +23,12 @@ export async function getMessages(
chatId: InputPeerLike, chatId: InputPeerLike,
messageId: number, messageId: number,
fromReply?: boolean fromReply?: boolean
): Promise<Message> ): Promise<Message | null>
/** /**
* Get messages in chat by their IDs * Get messages in chat by their IDs
* *
* **Note**: this method might return empty messages * Fot messages that were not found, `null` will be
* returned at that position.
* *
* @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 Messages IDs * @param messageIds Messages IDs
@ -43,7 +42,7 @@ export async function getMessages(
chatId: InputPeerLike, chatId: InputPeerLike,
messageIds: number[], messageIds: number[],
fromReply?: boolean fromReply?: boolean
): Promise<Message[]> ): Promise<(Message | null)[]>
/** @internal */ /** @internal */
export async function getMessages( export async function getMessages(
@ -51,7 +50,7 @@ export async function getMessages(
chatId: InputPeerLike, chatId: InputPeerLike,
messageIds: MaybeArray<number>, messageIds: MaybeArray<number>,
fromReply = false fromReply = false
): Promise<MaybeArray<Message>> { ): Promise<MaybeArray<Message | null>> {
const peer = await this.resolvePeer(chatId) const peer = await this.resolvePeer(chatId)
const isSingle = !Array.isArray(messageIds) const isSingle = !Array.isArray(messageIds)
@ -63,12 +62,14 @@ export async function getMessages(
id: it, id: it,
})) }))
const isChannel = isInputPeerChannel(peer)
const res = await this.call( const res = await this.call(
isInputPeerChannel(peer) isChannel
? { ? {
_: 'channels.getMessages', _: 'channels.getMessages',
id: ids, id: ids,
channel: normalizeToInputChannel(peer), channel: normalizeToInputChannel(peer)!,
} }
: { : {
_: 'messages.getMessages', _: 'messages.getMessages',
@ -85,9 +86,31 @@ export async function getMessages(
const { users, chats } = createUsersChatsIndex(res) const { users, chats } = createUsersChatsIndex(res)
const ret = res.messages const ret = res.messages.map((msg) => {
.filter((msg) => msg._ !== 'messageEmpty') if (msg._ === 'messageEmpty') return null
.map((msg) => new Message(this, msg, users, chats))
if (!isChannel) {
// make sure that the messages belong to the given chat
// (channels have their own message numbering)
switch (peer._) {
case 'inputPeerSelf':
if (!(msg.peerId._ === 'peerUser' && msg.peerId.userId === this._userId))
return null
break;
case 'inputPeerUser':
case 'inputPeerUserFromMessage':
if (!(msg.peerId._ === 'peerUser' && msg.peerId.userId === peer.userId))
return null
break;
case 'inputPeerChat':
if (!(msg.peerId._ === 'peerChat' && msg.peerId.chatId === peer.chatId))
return null
break;
}
}
return new Message(this, msg, users, chats)
})
return isSingle ? ret[0] : ret return isSingle ? ret[0] : ret
} }

View file

@ -1,6 +1,7 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, Message, ReplyMarkup } from '../../types' import { InputPeerLike, Message, ReplyMarkup } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { MessageNotFoundError } from '@mtcute/tl/errors'
/** /**
* Copy a message (i.e. send the same message, * Copy a message (i.e. send the same message,
@ -84,5 +85,8 @@ export async function sendCopy(
const fromPeer = await this.resolvePeer(fromChatId) const fromPeer = await this.resolvePeer(fromChatId)
const msg = await this.getMessages(fromPeer, message) const msg = await this.getMessages(fromPeer, message)
if (!msg) throw new MessageNotFoundError()
return msg.sendCopy(toChatId, params) return msg.sendCopy(toChatId, params)
} }

View file

@ -1,6 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { inputPeerToPeer, normalizeToInputUser } from '../../utils/peer-utils' import { inputPeerToPeer } from '../../utils/peer-utils'
import { import {
normalizeDate, normalizeDate,
normalizeMessageId, normalizeMessageId,
@ -14,8 +14,9 @@ import {
UsersIndex, UsersIndex,
MtCuteTypeAssertionError, MtCuteTypeAssertionError,
ChatsIndex, ChatsIndex,
MtCuteArgumentError,
} from '../../types' } from '../../types'
import { getMarkedPeerId } from '@mtcute/core' import { getMarkedPeerId, MessageNotFoundError } from '@mtcute/core'
import { createDummyUpdate } from '../../utils/updates-utils' import { createDummyUpdate } from '../../utils/updates-utils'
/** /**
@ -36,6 +37,19 @@ export async function sendText(
*/ */
replyTo?: number | Message 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
/** /**
* Message to comment to. Either a message object or message ID. * Message to comment to. Either a message object or message ID.
* *
@ -111,6 +125,18 @@ export async function sendText(
) )
} }
if (params.mustReply) {
if (!replyTo)
throw new MtCuteArgumentError(
'mustReply used, but replyTo was not passed'
)
const msg = await this.getMessages(peer, replyTo)
if (!msg)
throw new MessageNotFoundError()
}
const res = await this.call({ const res = await this.call({
_: 'messages.sendMessage', _: 'messages.sendMessage',
peer, peer,
@ -124,6 +150,9 @@ export async function sendText(
entities, entities,
clearDraft: params.clearDraft, clearDraft: params.clearDraft,
}) })
// } catch (e) {
//
// }
if (res._ === 'updateShortSentMessage') { if (res._ === 'updateShortSentMessage') {
const msg: tl.RawMessage = { const msg: tl.RawMessage = {
@ -157,7 +186,7 @@ export async function sendText(
// we need to do it manually // we need to do it manually
cached = await this.call({ cached = await this.call({
_: 'messages.getChats', _: 'messages.getChats',
id: [peer.chatId] id: [peer.chatId],
}).then((res) => res.chats[0]) }).then((res) => res.chats[0])
break break
default: default:

View file

@ -5,7 +5,7 @@ import {
MtCuteTypeAssertionError, MtCuteTypeAssertionError,
Poll, Poll,
} from '../../types' } from '../../types'
import { MaybeArray } from '@mtcute/core' import { MaybeArray, MessageNotFoundError } from '@mtcute/core'
import { createUsersChatsIndex } from '../../utils/peer-utils' import { createUsersChatsIndex } from '../../utils/peer-utils'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
@ -36,6 +36,9 @@ 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 this.getMessages(peer, message) const msg = await this.getMessages(peer, message)
if (!msg) throw new MessageNotFoundError()
if (!(msg.media instanceof Poll)) if (!(msg.media instanceof Poll))
throw new MtCuteArgumentError( throw new MtCuteArgumentError(
'This message does not contain a poll' 'This message does not contain a poll'

View file

@ -6,6 +6,7 @@ import { MtCuteArgumentError } from '../errors'
import { BasicPeerType, getBasicPeerType, getMarkedPeerId } from '@mtcute/core' import { BasicPeerType, getBasicPeerType, getMarkedPeerId } from '@mtcute/core'
import { encodeInlineMessageId } from '../../utils/inline-utils' import { encodeInlineMessageId } from '../../utils/inline-utils'
import { User, UsersIndex } from '../peers' import { User, UsersIndex } from '../peers'
import { MessageNotFoundError } from '@mtcute/core'
/** /**
* An incoming callback query, originated from a callback button * An incoming callback query, originated from a callback button
@ -179,6 +180,9 @@ export class CallbackQuery {
/** /**
* Message that contained the callback button that was clicked. * Message that contained the callback button that was clicked.
* *
* Note that the message may have been deleted, in which case
* `MessageNotFoundError` is thrown.
*
* Can only be used if `isInline = false` * Can only be used if `isInline = false`
*/ */
async getMessage(): Promise<Message> { async getMessage(): Promise<Message> {
@ -187,10 +191,13 @@ export class CallbackQuery {
'Cannot get a message for inline callback' 'Cannot get a message for inline callback'
) )
return this.client.getMessages( const msg = await this.client.getMessages(
getMarkedPeerId(this.raw.peer), getMarkedPeerId(this.raw.peer),
this.raw.msgId this.raw.msgId
) )
if (!msg) throw new MessageNotFoundError()
return msg
} }
/** /**

View file

@ -69,7 +69,8 @@ export class MtCuteInvalidPeerTypeError extends MtCuteError {
} }
/** /**
* Trying to access to some property on an "empty" object. * Trying to access to some property on an object that does not
* contain that information.
*/ */
export class MtCuteEmptyError extends MtCuteError { export class MtCuteEmptyError extends MtCuteError {
constructor() { constructor() {

View file

@ -4,8 +4,7 @@ import { Chat, ChatsIndex, UsersIndex } from '../peers'
import { Message } from './message' import { Message } from './message'
import { DraftMessage } from './draft-message' import { DraftMessage } from './draft-message'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
import { getMarkedPeerId } from '@mtcute/core' import { getMarkedPeerId, MessageNotFoundError } from '@mtcute/core'
import { MtCuteEmptyError } from '../errors'
/** /**
* A dialog. * A dialog.
@ -191,6 +190,8 @@ export class Dialog {
private _lastMessage?: Message private _lastMessage?: Message
/** /**
* The latest message sent in this chat * The latest message sent in this chat
*
* Throws `MessageNotFoundError` if it was not found
*/ */
get lastMessage(): Message { get lastMessage(): Message {
if (!this._lastMessage) { if (!this._lastMessage) {
@ -203,7 +204,7 @@ export class Dialog {
this._chats this._chats
) )
} else { } else {
throw new MtCuteEmptyError() throw new MessageNotFoundError()
} }
} }

View file

@ -4,7 +4,6 @@ import { BotKeyboard, ReplyMarkup } from '../bots'
import { getMarkedPeerId, MAX_CHANNEL_ID } from '@mtcute/core' import { getMarkedPeerId, MAX_CHANNEL_ID } from '@mtcute/core'
import { import {
MtCuteArgumentError, MtCuteArgumentError,
MtCuteEmptyError,
MtCuteTypeAssertionError, MtCuteTypeAssertionError,
} from '../errors' } from '../errors'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
@ -534,13 +533,18 @@ export class Message {
/** /**
* For replies, fetch the message that is being replied. * For replies, fetch the message that is being replied.
* *
* @throws MtCuteArgumentError In case the message is not a reply * Note that even if a message has {@link replyToMessageId},
* the message itself may have been deleted, in which case
* this method will also return `null`.
*/ */
getReplyTo(): Promise<Message> { getReplyTo(): Promise<Message | null> {
if (!this.replyToMessageId) if (!this.replyToMessageId)
throw new MtCuteArgumentError('This message is not a reply!') return Promise.resolve(null)
if (this.raw.peerId._ === 'peerChannel')
return this.client.getMessages(this.chat.inputPeer, this.id, true) return this.client.getMessages(this.chat.inputPeer, this.id, true)
return this.client.getMessagesUnsafe(this.id, true)
} }
/** /**

File diff suppressed because one or more lines are too long

View file

@ -84,6 +84,12 @@ const customErrors = [
name: 'RPC_TIMEOUT', name: 'RPC_TIMEOUT',
codes: '408', codes: '408',
description: 'Timeout of {ms} ms exceeded', description: 'Timeout of {ms} ms exceeded',
},
{
virtual: true,
name: 'MESSAGE_NOT_FOUND',
codes: '404',
description: 'Message was not found'
} }
] ]