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 { getHistory } from './methods/messages/get-history'
import { getMessageGroup } from './methods/messages/get-message-group'
import { getMessagesUnsafe } from './methods/messages/get-messages-unsafe'
import { getMessages } from './methods/messages/get-messages'
import { iterHistory } from './methods/messages/iter-history'
import { _normalizeInline } from './methods/messages/normalize-inline'
@ -2101,9 +2102,42 @@ export interface TelegramClient extends BaseTelegramClient {
*/
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 messageId Messages ID
@ -2115,11 +2149,12 @@ export interface TelegramClient extends BaseTelegramClient {
chatId: InputPeerLike,
messageId: number,
fromReply?: boolean
): Promise<Message>
): Promise<Message | null>
/**
* 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 messageIds Messages IDs
@ -2131,7 +2166,7 @@ export interface TelegramClient extends BaseTelegramClient {
chatId: InputPeerLike,
messageIds: number[],
fromReply?: boolean
): Promise<Message[]>
): Promise<(Message | null)[]>
/**
* Iterate through a chat history sequentially.
*
@ -2569,6 +2604,19 @@ export interface TelegramClient extends BaseTelegramClient {
*/
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.
*
@ -3321,6 +3369,7 @@ export class TelegramClient extends BaseTelegramClient {
protected _getDiscussionMessage = _getDiscussionMessage
getHistory = getHistory
getMessageGroup = getMessageGroup
getMessagesUnsafe = getMessagesUnsafe
getMessages = getMessages
iterHistory = iterHistory
protected _normalizeInline = _normalizeInline

View file

@ -1,5 +1,6 @@
import { TelegramClient } from '../../client'
import { InputPeerLike, MtCuteArgumentError, Message } from '../../types'
import { isInputPeerChannel } from '../../utils/peer-utils'
/**
* Get all messages inside of a message group
@ -15,16 +16,25 @@ export async function getMessageGroup(
): Promise<Message[]> {
// awesome hack stolen from pyrogram
// 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[] = []
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)
}
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')
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
*
* **Note**: this method might return empty message
*
* @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`
* @param messageId Messages ID
* @param [fromReply=false]
@ -25,11 +23,12 @@ export async function getMessages(
chatId: InputPeerLike,
messageId: number,
fromReply?: boolean
): Promise<Message>
): Promise<Message | null>
/**
* 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 messageIds Messages IDs
@ -43,7 +42,7 @@ export async function getMessages(
chatId: InputPeerLike,
messageIds: number[],
fromReply?: boolean
): Promise<Message[]>
): Promise<(Message | null)[]>
/** @internal */
export async function getMessages(
@ -51,7 +50,7 @@ export async function getMessages(
chatId: InputPeerLike,
messageIds: MaybeArray<number>,
fromReply = false
): Promise<MaybeArray<Message>> {
): Promise<MaybeArray<Message | null>> {
const peer = await this.resolvePeer(chatId)
const isSingle = !Array.isArray(messageIds)
@ -63,12 +62,14 @@ export async function getMessages(
id: it,
}))
const isChannel = isInputPeerChannel(peer)
const res = await this.call(
isInputPeerChannel(peer)
isChannel
? {
_: 'channels.getMessages',
id: ids,
channel: normalizeToInputChannel(peer),
channel: normalizeToInputChannel(peer)!,
}
: {
_: 'messages.getMessages',
@ -85,9 +86,31 @@ export async function getMessages(
const { users, chats } = createUsersChatsIndex(res)
const ret = res.messages
.filter((msg) => msg._ !== 'messageEmpty')
.map((msg) => new Message(this, msg, users, chats))
const ret = res.messages.map((msg) => {
if (msg._ === 'messageEmpty') return null
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
}

View file

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

View file

@ -1,6 +1,6 @@
import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl'
import { inputPeerToPeer, normalizeToInputUser } from '../../utils/peer-utils'
import { inputPeerToPeer } from '../../utils/peer-utils'
import {
normalizeDate,
normalizeMessageId,
@ -14,8 +14,9 @@ import {
UsersIndex,
MtCuteTypeAssertionError,
ChatsIndex,
MtCuteArgumentError,
} from '../../types'
import { getMarkedPeerId } from '@mtcute/core'
import { getMarkedPeerId, MessageNotFoundError } from '@mtcute/core'
import { createDummyUpdate } from '../../utils/updates-utils'
/**
@ -36,6 +37,19 @@ export async function sendText(
*/
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.
*
@ -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({
_: 'messages.sendMessage',
peer,
@ -124,6 +150,9 @@ export async function sendText(
entities,
clearDraft: params.clearDraft,
})
// } catch (e) {
//
// }
if (res._ === 'updateShortSentMessage') {
const msg: tl.RawMessage = {
@ -157,7 +186,7 @@ export async function sendText(
// we need to do it manually
cached = await this.call({
_: 'messages.getChats',
id: [peer.chatId]
id: [peer.chatId],
}).then((res) => res.chats[0])
break
default:

View file

@ -5,7 +5,7 @@ import {
MtCuteTypeAssertionError,
Poll,
} from '../../types'
import { MaybeArray } from '@mtcute/core'
import { MaybeArray, MessageNotFoundError } from '@mtcute/core'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { assertTypeIs } from '../../utils/type-assertion'
import { assertIsUpdatesGroup } from '../../utils/updates-utils'
@ -36,6 +36,9 @@ export async function sendVote(
let poll: Poll | undefined = undefined
if (options.some((it) => typeof it === 'number')) {
const msg = await this.getMessages(peer, message)
if (!msg) throw new MessageNotFoundError()
if (!(msg.media instanceof Poll))
throw new MtCuteArgumentError(
'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 { encodeInlineMessageId } from '../../utils/inline-utils'
import { User, UsersIndex } from '../peers'
import { MessageNotFoundError } from '@mtcute/core'
/**
* 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.
*
* Note that the message may have been deleted, in which case
* `MessageNotFoundError` is thrown.
*
* Can only be used if `isInline = false`
*/
async getMessage(): Promise<Message> {
@ -187,10 +191,13 @@ export class CallbackQuery {
'Cannot get a message for inline callback'
)
return this.client.getMessages(
const msg = await this.client.getMessages(
getMarkedPeerId(this.raw.peer),
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 {
constructor() {

View file

@ -4,8 +4,7 @@ import { Chat, ChatsIndex, UsersIndex } from '../peers'
import { Message } from './message'
import { DraftMessage } from './draft-message'
import { makeInspectable } from '../utils'
import { getMarkedPeerId } from '@mtcute/core'
import { MtCuteEmptyError } from '../errors'
import { getMarkedPeerId, MessageNotFoundError } from '@mtcute/core'
/**
* A dialog.
@ -191,6 +190,8 @@ export class Dialog {
private _lastMessage?: Message
/**
* The latest message sent in this chat
*
* Throws `MessageNotFoundError` if it was not found
*/
get lastMessage(): Message {
if (!this._lastMessage) {
@ -203,7 +204,7 @@ export class Dialog {
this._chats
)
} 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 {
MtCuteArgumentError,
MtCuteEmptyError,
MtCuteTypeAssertionError,
} from '../errors'
import { TelegramClient } from '../../client'
@ -534,13 +533,18 @@ export class Message {
/**
* 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)
throw new MtCuteArgumentError('This message is not a reply!')
return Promise.resolve(null)
return this.client.getMessages(this.chat.inputPeer, this.id, true)
if (this.raw.peerId._ === 'peerChannel')
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',
codes: '408',
description: 'Timeout of {ms} ms exceeded',
},
{
virtual: true,
name: 'MESSAGE_NOT_FOUND',
codes: '404',
description: 'Message was not found'
}
]