feat: contexts
This commit is contained in:
parent
6629d91274
commit
337418a34c
51 changed files with 1550 additions and 1078 deletions
|
@ -343,7 +343,7 @@ async function addSingleMethod(state, fileName) {
|
|||
state.imports[module] = new Set()
|
||||
}
|
||||
|
||||
state.imports[module].add(name)
|
||||
if (!isManual) state.imports[module].add(name)
|
||||
}
|
||||
}
|
||||
} else if (stmt.kind === ts.SyntaxKind.InterfaceDeclaration) {
|
||||
|
|
|
@ -26,7 +26,7 @@ function parseUpdateTypes() {
|
|||
const ret = []
|
||||
|
||||
for (const line of lines) {
|
||||
const m = line.match(/^([a-z_]+)(?:: ([a-zA-Z]+))? = ([a-zA-Z]+(?:\[\])?)( \+ State)?$/)
|
||||
const m = line.match(/^([a-z_]+)(?:: ([a-zA-Z]+))? = ([a-zA-Z]+(?:\[\])?)( \+ State)?(?: in ([a-zA-Z]+))?$/)
|
||||
if (!m) throw new Error(`invalid syntax: ${line}`)
|
||||
ret.push({
|
||||
typeName: m[1],
|
||||
|
@ -34,6 +34,7 @@ function parseUpdateTypes() {
|
|||
updateType: m[3],
|
||||
funcName: m[2] ? m[2][0].toLowerCase() + m[2].substr(1) : snakeToCamel(m[1]),
|
||||
state: Boolean(m[4]),
|
||||
context: m[5] ?? `UpdateContext<${m[3]}>`,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
# format: type_name[: handler_type_name] = update_type[ + State]
|
||||
new_message = Message + State
|
||||
edit_message = Message + State
|
||||
message_group = Message[] + State
|
||||
# format: type_name[: handler_type_name] = update_type[ + State][in ContextClassName]
|
||||
new_message = Message + State in MessageContext
|
||||
edit_message = Message + State in MessageContext
|
||||
message_group = Message[] + State in MessageContext
|
||||
delete_message = DeleteMessageUpdate
|
||||
chat_member: ChatMemberUpdate = ChatMemberUpdate
|
||||
inline_query = InlineQuery
|
||||
chosen_inline_result = ChosenInlineResult
|
||||
callback_query = CallbackQuery + State
|
||||
inline_query = InlineQuery in InlineQueryContext
|
||||
chosen_inline_result = ChosenInlineResult in ChosenInlineResultContext
|
||||
callback_query = CallbackQuery + State in CallbackQueryContext
|
||||
poll: PollUpdate = PollUpdate
|
||||
poll_vote = PollVoteUpdate
|
||||
user_status: UserStatusUpdate = UserStatusUpdate
|
||||
user_typing = UserTypingUpdate
|
||||
history_read = HistoryReadUpdate
|
||||
bot_stopped = BotStoppedUpdate
|
||||
bot_chat_join_request = BotChatJoinRequestUpdate
|
||||
bot_chat_join_request = BotChatJoinRequestUpdate in ChatJoinRequestUpdateContext
|
||||
chat_join_request = ChatJoinRequestUpdate
|
||||
pre_checkout_query = PreCheckoutQuery
|
||||
pre_checkout_query = PreCheckoutQuery in PreCheckoutQueryContext
|
||||
story: StoryUpdate = StoryUpdate
|
||||
delete_story = DeleteStoryUpdate
|
|
@ -20,7 +20,7 @@ import { getPasswordHint } from './methods/auth/get-password-hint'
|
|||
import { logOut } from './methods/auth/log-out'
|
||||
import { recoverPassword } from './methods/auth/recover-password'
|
||||
import { resendCode } from './methods/auth/resend-code'
|
||||
import { run } from './methods/auth/run'
|
||||
import {} from './methods/auth/run'
|
||||
import { sendCode } from './methods/auth/send-code'
|
||||
import { sendRecoveryCode } from './methods/auth/send-recovery-code'
|
||||
import { signIn } from './methods/auth/sign-in'
|
||||
|
@ -141,6 +141,7 @@ import { getMessageReactions, getMessageReactionsById } from './methods/messages
|
|||
import { getMessages } from './methods/messages/get-messages'
|
||||
import { getMessagesUnsafe } from './methods/messages/get-messages-unsafe'
|
||||
import { getReactionUsers, GetReactionUsersOffset } from './methods/messages/get-reaction-users'
|
||||
import { getReplyTo } from './methods/messages/get-reply-to'
|
||||
import { getScheduledMessages } from './methods/messages/get-scheduled-messages'
|
||||
import { iterHistory } from './methods/messages/iter-history'
|
||||
import { iterReactionUsers } from './methods/messages/iter-reaction-users'
|
||||
|
@ -152,10 +153,15 @@ import { readHistory } from './methods/messages/read-history'
|
|||
import { readReactions } from './methods/messages/read-reactions'
|
||||
import { searchGlobal, SearchGlobalOffset } from './methods/messages/search-global'
|
||||
import { searchMessages, SearchMessagesOffset } from './methods/messages/search-messages'
|
||||
import { answerMedia, answerMediaGroup, answerText } from './methods/messages/send-answer'
|
||||
import { commentMedia, commentMediaGroup, commentText } from './methods/messages/send-comment'
|
||||
import { CommonSendParams } from './methods/messages/send-common'
|
||||
import { sendCopy, SendCopyParams } from './methods/messages/send-copy'
|
||||
import { sendCopyGroup, SendCopyGroupParams } from './methods/messages/send-copy-group'
|
||||
import { sendMedia } from './methods/messages/send-media'
|
||||
import { sendMediaGroup } from './methods/messages/send-media-group'
|
||||
import { sendReaction } from './methods/messages/send-reaction'
|
||||
import { replyMedia, replyMediaGroup, replyText } from './methods/messages/send-reply'
|
||||
import { sendScheduled } from './methods/messages/send-scheduled'
|
||||
import { sendText } from './methods/messages/send-text'
|
||||
import { sendTyping } from './methods/messages/send-typing'
|
||||
|
@ -180,7 +186,7 @@ import { removeCloudPassword } from './methods/password/remove-cloud-password'
|
|||
import { addStickerToSet } from './methods/stickers/add-sticker-to-set'
|
||||
import { createStickerSet } from './methods/stickers/create-sticker-set'
|
||||
import { deleteStickerFromSet } from './methods/stickers/delete-sticker-from-set'
|
||||
import { getCustomEmojis } from './methods/stickers/get-custom-emojis'
|
||||
import { getCustomEmojis, getCustomEmojisFromMessages } from './methods/stickers/get-custom-emojis'
|
||||
import { getInstalledStickers } from './methods/stickers/get-installed-stickers'
|
||||
import { getStickerSet } from './methods/stickers/get-sticker-set'
|
||||
import { moveStickerInSet } from './methods/stickers/move-sticker-in-set'
|
||||
|
@ -287,6 +293,7 @@ import {
|
|||
MessageEntity,
|
||||
MessageMedia,
|
||||
MessageReactions,
|
||||
ParametersSkip2,
|
||||
ParsedUpdate,
|
||||
PeerReaction,
|
||||
PeersIndex,
|
||||
|
@ -547,6 +554,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*
|
||||
* @param params Parameters to be passed to {@link start}
|
||||
* @param then Function to be called after {@link start} returns
|
||||
* @manual
|
||||
*/
|
||||
run(params: Parameters<typeof start>[1], then?: (user: User) => void | Promise<void>): void
|
||||
/**
|
||||
|
@ -2713,7 +2721,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*/
|
||||
getPrimaryInviteLink(chatId: InputPeerLike): Promise<ChatInviteLink>
|
||||
/**
|
||||
* Approve or deny multiple join requests to a chat.
|
||||
* Approve or decline multiple join requests to a chat.
|
||||
* **Available**: 👤 users only
|
||||
*
|
||||
*/
|
||||
|
@ -2721,14 +2729,14 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
/** Chat/channel ID */
|
||||
chatId: InputPeerLike
|
||||
|
||||
/** Whether to approve or deny the join requests */
|
||||
action: 'approve' | 'deny'
|
||||
/** Whether to approve or decline the join requests */
|
||||
action: 'approve' | 'decline'
|
||||
|
||||
/** Invite link to target */
|
||||
link?: string | ChatInviteLink
|
||||
}): Promise<void>
|
||||
/**
|
||||
* Approve or deny join request to a chat.
|
||||
* Approve or decline join request to a chat.
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
*/
|
||||
|
@ -2737,8 +2745,8 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
chatId: InputPeerLike
|
||||
/** User ID */
|
||||
user: InputPeerLike
|
||||
/** Whether to approve or deny the join request */
|
||||
action: 'approve' | 'deny'
|
||||
/** Whether to approve or decline the join request */
|
||||
action: 'approve' | 'decline'
|
||||
}): Promise<void>
|
||||
/**
|
||||
* Iterate over users who have joined
|
||||
|
@ -3215,6 +3223,16 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
offset?: GetReactionUsersOffset
|
||||
},
|
||||
): Promise<ArrayPaginated<PeerReaction, GetReactionUsersOffset>>
|
||||
/**
|
||||
* For messages containing a reply, fetch the message that is being replied.
|
||||
*
|
||||
* 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`.
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
*/
|
||||
getReplyTo(message: Message): Promise<Message | null>
|
||||
/**
|
||||
* Get scheduled messages in chat by their IDs
|
||||
*
|
||||
|
@ -3549,6 +3567,76 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*/
|
||||
fromUser?: InputPeerLike
|
||||
}): Promise<ArrayPaginated<Message, SearchMessagesOffset>>
|
||||
/** Send a text to the same chat (and topic, if applicable) as a given message */
|
||||
answerText(message: Message, ...params: ParametersSkip2<typeof sendText>): ReturnType<typeof sendText>
|
||||
/** Send a media to the same chat (and topic, if applicable) as a given message */
|
||||
answerMedia(message: Message, ...params: ParametersSkip2<typeof sendMedia>): ReturnType<typeof sendMedia>
|
||||
/** Send a media group to the same chat (and topic, if applicable) as a given message */
|
||||
answerMediaGroup(
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMediaGroup>
|
||||
): ReturnType<typeof sendMediaGroup>
|
||||
/**
|
||||
* Send a text comment to a given message.
|
||||
*
|
||||
* If this is a normal message (not a channel post),
|
||||
* a simple reply will be sent.
|
||||
*
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
* @throws MtArgumentError
|
||||
* If this is a channel post which does not have comments section.
|
||||
* To check if a post has comments, use {@link Message#replies}.hasComments
|
||||
*/
|
||||
commentText(message: Message, ...params: ParametersSkip2<typeof sendText>): ReturnType<typeof sendText>
|
||||
/**
|
||||
* Send a text comment to a given message.
|
||||
*
|
||||
* If this is a normal message (not a channel post),
|
||||
* a simple reply will be sent.
|
||||
*
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
* @throws MtArgumentError
|
||||
* If this is a channel post which does not have comments section.
|
||||
* To check if a post has comments, use {@link Message#replies}.hasComments
|
||||
*/
|
||||
commentMedia(message: Message, ...params: ParametersSkip2<typeof sendMedia>): ReturnType<typeof sendMedia>
|
||||
/**
|
||||
* Send a text comment to a given message.
|
||||
*
|
||||
* If this is a normal message (not a channel post),
|
||||
* a simple reply will be sent.
|
||||
*
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
* @throws MtArgumentError
|
||||
* If this is a channel post which does not have comments section.
|
||||
* To check if a post has comments, use {@link Message#replies}.hasComments
|
||||
*/
|
||||
commentMediaGroup(
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMediaGroup>
|
||||
): ReturnType<typeof sendMediaGroup>
|
||||
/**
|
||||
* Copy a message group (i.e. send the same message group, but do not forward it).
|
||||
*
|
||||
* Note that all the provided messages must be in the same message group
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
*/
|
||||
sendCopyGroup(
|
||||
params: SendCopyGroupParams &
|
||||
(
|
||||
| {
|
||||
/** Source chat ID */
|
||||
fromChatId: InputPeerLike
|
||||
/** Message IDs to forward */
|
||||
messages: number[]
|
||||
}
|
||||
| { messages: Message[] }
|
||||
),
|
||||
): Promise<Message[]>
|
||||
/**
|
||||
* Copy a message (i.e. send the same message, but do not forward it).
|
||||
*
|
||||
|
@ -3556,10 +3644,8 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* it will be copied simply as a text message,
|
||||
* and if the message contains an invoice,
|
||||
* it can't be copied.
|
||||
*
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
sendCopy(
|
||||
params: SendCopyParams &
|
||||
|
@ -3589,56 +3675,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
sendMediaGroup(
|
||||
chatId: InputPeerLike,
|
||||
medias: (InputMediaLike | string)[],
|
||||
params?: {
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Message to comment to. Either a message object or message ID.
|
||||
*
|
||||
* This overwrites `replyTo` if it was passed
|
||||
*/
|
||||
commentTo?: number | Message
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
params?: CommonSendParams & {
|
||||
/**
|
||||
* Function that will be called after some part has been uploaded.
|
||||
* Only used when a file that requires uploading is passed,
|
||||
|
@ -3649,26 +3686,6 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* @param total Total file size
|
||||
*/
|
||||
progressCallback?: (index: number, uploaded: number, total: number) => void
|
||||
|
||||
/**
|
||||
* Whether to clear draft after sending this message.
|
||||
*
|
||||
* Defaults to `false`
|
||||
*/
|
||||
clearDraft?: 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
|
||||
|
||||
/**
|
||||
* Peer to use when sending the message.
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
},
|
||||
): Promise<Message[]>
|
||||
/**
|
||||
|
@ -3687,7 +3704,13 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
sendMedia(
|
||||
chatId: InputPeerLike,
|
||||
media: InputMediaLike | string,
|
||||
params?: {
|
||||
params?: CommonSendParams & {
|
||||
/**
|
||||
* For bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
replyMarkup?: ReplyMarkup
|
||||
|
||||
/**
|
||||
* Override caption for `media`.
|
||||
*
|
||||
|
@ -3704,61 +3727,6 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*/
|
||||
entities?: tl.TypeMessageEntity[]
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Message to comment to. Either a message object or message ID.
|
||||
*
|
||||
* This overwrites `replyTo` if it was passed
|
||||
*/
|
||||
commentTo?: number | Message
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* For bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
replyMarkup?: ReplyMarkup
|
||||
|
||||
/**
|
||||
* Function that will be called after some part has been uploaded.
|
||||
* Only used when a file that requires uploading is passed,
|
||||
|
@ -3768,32 +3736,6 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* @param total Total file size
|
||||
*/
|
||||
progressCallback?: (uploaded: number, total: number) => void
|
||||
|
||||
/**
|
||||
* Whether to clear draft after sending this message.
|
||||
*
|
||||
* Defaults to `false`
|
||||
*/
|
||||
clearDraft?: 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
|
||||
|
||||
/**
|
||||
* Peer to use when sending the message.
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Whether to dispatch the returned message
|
||||
* to the client's update handler.
|
||||
*/
|
||||
shouldDispatch?: true
|
||||
},
|
||||
): Promise<Message>
|
||||
/**
|
||||
|
@ -3817,6 +3759,15 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
shouldDispatch?: true
|
||||
},
|
||||
): Promise<Message>
|
||||
/** Send a text in reply to a given message */
|
||||
replyText(message: Message, ...params: ParametersSkip2<typeof sendText>): ReturnType<typeof sendText>
|
||||
/** Send a media in reply to a given message */
|
||||
replyMedia(message: Message, ...params: ParametersSkip2<typeof sendMedia>): ReturnType<typeof sendMedia>
|
||||
/** Send a media group in reply to a given message */
|
||||
replyMediaGroup(
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMediaGroup>
|
||||
): ReturnType<typeof sendMediaGroup>
|
||||
/**
|
||||
* Send previously scheduled message(s)
|
||||
*
|
||||
|
@ -3842,41 +3793,12 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
sendText(
|
||||
chatId: InputPeerLike,
|
||||
text: string | FormattedString<string>,
|
||||
params?: {
|
||||
params?: CommonSendParams & {
|
||||
/**
|
||||
* 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 bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* This overwrites `replyTo` if it was passed
|
||||
*/
|
||||
commentTo?: number | Message
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
replyMarkup?: ReplyMarkup
|
||||
|
||||
/**
|
||||
* List of formatting entities to use instead of parsing via a
|
||||
|
@ -3890,52 +3812,6 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* Whether to disable links preview in this message
|
||||
*/
|
||||
disableWebPreview?: boolean
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Whether to disallow further forwards of this message.
|
||||
*
|
||||
* Only for bots, works even if the target chat does not
|
||||
* have content protection.
|
||||
*/
|
||||
forbidForwards?: boolean
|
||||
|
||||
/**
|
||||
* Peer to use when sending the message.
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Whether to dispatch the returned message
|
||||
* to the client's update handler.
|
||||
*/
|
||||
shouldDispatch?: true
|
||||
},
|
||||
): Promise<Message>
|
||||
/**
|
||||
|
@ -4279,6 +4155,12 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* @param ids IDs of the stickers (as defined in {@link MessageEntity.emojiId})
|
||||
*/
|
||||
getCustomEmojis(ids: tl.Long[]): Promise<Sticker[]>
|
||||
/**
|
||||
* Given one or more messages, extract all unique custom emojis from it and fetch them
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
*/
|
||||
getCustomEmojisFromMessages(messages: MaybeArray<Message>): Promise<Sticker[]>
|
||||
/**
|
||||
* Get a list of all installed sticker packs
|
||||
*
|
||||
|
@ -5259,6 +5141,11 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
} else {
|
||||
this.start = start.bind(null, this)
|
||||
}
|
||||
this.run = (params, then) => {
|
||||
this.start(params)
|
||||
.then(then)
|
||||
.catch((err) => this._emitError(err))
|
||||
}
|
||||
}
|
||||
getAuthState = getAuthState.bind(null, this)
|
||||
_onAuthorization = _onAuthorization.bind(null, this)
|
||||
|
@ -5267,7 +5154,6 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
logOut = logOut.bind(null, this)
|
||||
recoverPassword = recoverPassword.bind(null, this)
|
||||
resendCode = resendCode.bind(null, this)
|
||||
run = run.bind(null, this)
|
||||
sendCode = sendCode.bind(null, this)
|
||||
sendRecoveryCode = sendRecoveryCode.bind(null, this)
|
||||
signInBot = signInBot.bind(null, this)
|
||||
|
@ -5396,6 +5282,7 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
getMessagesUnsafe = getMessagesUnsafe.bind(null, this)
|
||||
getMessages = getMessages.bind(null, this)
|
||||
getReactionUsers = getReactionUsers.bind(null, this)
|
||||
getReplyTo = getReplyTo.bind(null, this)
|
||||
getScheduledMessages = getScheduledMessages.bind(null, this)
|
||||
iterHistory = iterHistory.bind(null, this)
|
||||
iterReactionUsers = iterReactionUsers.bind(null, this)
|
||||
|
@ -5407,10 +5294,20 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
readReactions = readReactions.bind(null, this)
|
||||
searchGlobal = searchGlobal.bind(null, this)
|
||||
searchMessages = searchMessages.bind(null, this)
|
||||
answerText = answerText.bind(null, this)
|
||||
answerMedia = answerMedia.bind(null, this)
|
||||
answerMediaGroup = answerMediaGroup.bind(null, this)
|
||||
commentText = commentText.bind(null, this)
|
||||
commentMedia = commentMedia.bind(null, this)
|
||||
commentMediaGroup = commentMediaGroup.bind(null, this)
|
||||
sendCopyGroup = sendCopyGroup.bind(null, this)
|
||||
sendCopy = sendCopy.bind(null, this)
|
||||
sendMediaGroup = sendMediaGroup.bind(null, this)
|
||||
sendMedia = sendMedia.bind(null, this)
|
||||
sendReaction = sendReaction.bind(null, this)
|
||||
replyText = replyText.bind(null, this)
|
||||
replyMedia = replyMedia.bind(null, this)
|
||||
replyMediaGroup = replyMediaGroup.bind(null, this)
|
||||
sendScheduled = sendScheduled.bind(null, this)
|
||||
sendText = sendText.bind(null, this)
|
||||
sendTyping = sendTyping.bind(null, this)
|
||||
|
@ -5436,6 +5333,7 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
createStickerSet = createStickerSet.bind(null, this)
|
||||
deleteStickerFromSet = deleteStickerFromSet.bind(null, this)
|
||||
getCustomEmojis = getCustomEmojis.bind(null, this)
|
||||
getCustomEmojisFromMessages = getCustomEmojisFromMessages.bind(null, this)
|
||||
getInstalledStickers = getInstalledStickers.bind(null, this)
|
||||
getStickerSet = getStickerSet.bind(null, this)
|
||||
moveStickerInSet = moveStickerInSet.bind(null, this)
|
||||
|
|
|
@ -53,6 +53,7 @@ import {
|
|||
MessageEntity,
|
||||
MessageMedia,
|
||||
MessageReactions,
|
||||
ParametersSkip2,
|
||||
ParsedUpdate,
|
||||
PeerReaction,
|
||||
PeersIndex,
|
||||
|
|
|
@ -45,4 +45,9 @@ function _initializeClient(this: TelegramClient, opts: TelegramClientOptions) {
|
|||
} else {
|
||||
this.start = start.bind(null, this)
|
||||
}
|
||||
this.run = (params, then) => {
|
||||
this.start(params)
|
||||
.then(then)
|
||||
.catch((err) => this._emitError(err))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { start } from './start'
|
|||
*
|
||||
* @param params Parameters to be passed to {@link start}
|
||||
* @param then Function to be called after {@link start} returns
|
||||
* @manual
|
||||
*/
|
||||
export function run(
|
||||
client: BaseTelegramClient,
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { ChatInviteLink, InputPeerLike } from '../../types'
|
|||
import { resolvePeer } from '../users/resolve-peer'
|
||||
|
||||
/**
|
||||
* Approve or deny multiple join requests to a chat.
|
||||
* Approve or decline multiple join requests to a chat.
|
||||
*/
|
||||
export async function hideAllJoinRequests(
|
||||
client: BaseTelegramClient,
|
||||
|
@ -12,8 +12,8 @@ export async function hideAllJoinRequests(
|
|||
/** Chat/channel ID */
|
||||
chatId: InputPeerLike
|
||||
|
||||
/** Whether to approve or deny the join requests */
|
||||
action: 'approve' | 'deny'
|
||||
/** Whether to approve or decline the join requests */
|
||||
action: 'approve' | 'decline'
|
||||
|
||||
/** Invite link to target */
|
||||
link?: string | ChatInviteLink
|
||||
|
|
|
@ -5,7 +5,7 @@ import { normalizeToInputUser } from '../../utils/peer-utils'
|
|||
import { resolvePeer } from '../users/resolve-peer'
|
||||
|
||||
/**
|
||||
* Approve or deny join request to a chat.
|
||||
* Approve or decline join request to a chat.
|
||||
*/
|
||||
export async function hideJoinRequest(
|
||||
client: BaseTelegramClient,
|
||||
|
@ -14,8 +14,8 @@ export async function hideJoinRequest(
|
|||
chatId: InputPeerLike
|
||||
/** User ID */
|
||||
user: InputPeerLike
|
||||
/** Whether to approve or deny the join request */
|
||||
action: 'approve' | 'deny'
|
||||
/** Whether to approve or decline the join request */
|
||||
action: 'approve' | 'decline'
|
||||
},
|
||||
): Promise<void> {
|
||||
const { chatId, user, action } = params
|
||||
|
|
28
packages/client/src/methods/messages/get-reply-to.ts
Normal file
28
packages/client/src/methods/messages/get-reply-to.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { BaseTelegramClient } from '@mtcute/core'
|
||||
|
||||
import { Message } from '../../types/messages'
|
||||
import { getMessages } from './get-messages'
|
||||
import { getMessagesUnsafe } from './get-messages-unsafe'
|
||||
|
||||
/**
|
||||
* For messages containing a reply, fetch the message that is being replied.
|
||||
*
|
||||
* 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`.
|
||||
*/
|
||||
export async function getReplyTo(client: BaseTelegramClient, message: Message): Promise<Message | null> {
|
||||
if (!message.replyToMessageId) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (message.raw.peerId._ === 'peerChannel') {
|
||||
const [msg] = await getMessages(client, message.chat.inputPeer, message.id, true)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
const [msg] = await getMessagesUnsafe(client, message.id, true)
|
||||
|
||||
return msg
|
||||
}
|
55
packages/client/src/methods/messages/send-answer.ts
Normal file
55
packages/client/src/methods/messages/send-answer.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { BaseTelegramClient } from '@mtcute/core'
|
||||
|
||||
import { Message } from '../../types/messages/message'
|
||||
import { ParametersSkip2 } from '../../types/utils'
|
||||
import { sendMedia } from './send-media'
|
||||
import { sendMediaGroup } from './send-media-group'
|
||||
import { sendText } from './send-text'
|
||||
|
||||
/** Send a text to the same chat (and topic, if applicable) as a given message */
|
||||
export function answerText(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendText>
|
||||
): ReturnType<typeof sendText> {
|
||||
if (!message.isTopicMessage || !message.replyToThreadId) {
|
||||
return sendText(client, message.chat.inputPeer, ...params)
|
||||
}
|
||||
|
||||
const [text, params_ = {}] = params
|
||||
params_.replyTo = message.replyToThreadId
|
||||
|
||||
return sendText(client, message.chat.inputPeer, text, params_)
|
||||
}
|
||||
|
||||
/** Send a media to the same chat (and topic, if applicable) as a given message */
|
||||
export function answerMedia(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMedia>
|
||||
): ReturnType<typeof sendMedia> {
|
||||
if (!message.isTopicMessage || !message.replyToThreadId) {
|
||||
return sendMedia(client, message.chat.inputPeer, ...params)
|
||||
}
|
||||
|
||||
const [media, params_ = {}] = params
|
||||
params_.replyTo = message.replyToThreadId
|
||||
|
||||
return sendMedia(client, message.chat.inputPeer, media, params_)
|
||||
}
|
||||
|
||||
/** Send a media group to the same chat (and topic, if applicable) as a given message */
|
||||
export function answerMediaGroup(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMediaGroup>
|
||||
): ReturnType<typeof sendMediaGroup> {
|
||||
if (!message.isTopicMessage || !message.replyToThreadId) {
|
||||
return sendMediaGroup(client, message.chat.inputPeer, ...params)
|
||||
}
|
||||
|
||||
const [media, params_ = {}] = params
|
||||
params_.replyTo = message.replyToThreadId
|
||||
|
||||
return sendMediaGroup(client, message.chat.inputPeer, media, params_)
|
||||
}
|
95
packages/client/src/methods/messages/send-comment.ts
Normal file
95
packages/client/src/methods/messages/send-comment.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
import { BaseTelegramClient, MtArgumentError } from '@mtcute/core'
|
||||
|
||||
import { Message } from '../../types/messages/message'
|
||||
import { ParametersSkip2 } from '../../types/utils'
|
||||
import { sendMedia } from './send-media'
|
||||
import { sendMediaGroup } from './send-media-group'
|
||||
import { replyMedia, replyMediaGroup, replyText } from './send-reply'
|
||||
import { sendText } from './send-text'
|
||||
|
||||
/**
|
||||
* Send a text comment to a given message.
|
||||
*
|
||||
* If this is a normal message (not a channel post),
|
||||
* a simple reply will be sent.
|
||||
*
|
||||
* @throws MtArgumentError
|
||||
* If this is a channel post which does not have comments section.
|
||||
* To check if a post has comments, use {@link Message#replies}.hasComments
|
||||
*/
|
||||
export function commentText(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendText>
|
||||
): ReturnType<typeof sendText> {
|
||||
if (message.chat.chatType !== 'channel') {
|
||||
return replyText(client, message, ...params)
|
||||
}
|
||||
|
||||
if (!message.replies || !message.replies.hasComments) {
|
||||
throw new MtArgumentError('This message does not have comments section')
|
||||
}
|
||||
|
||||
const [text, params_ = {}] = params
|
||||
params_.commentTo = message.id
|
||||
|
||||
return sendText(client, message.chat.inputPeer, text, params_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a text comment to a given message.
|
||||
*
|
||||
* If this is a normal message (not a channel post),
|
||||
* a simple reply will be sent.
|
||||
*
|
||||
* @throws MtArgumentError
|
||||
* If this is a channel post which does not have comments section.
|
||||
* To check if a post has comments, use {@link Message#replies}.hasComments
|
||||
*/
|
||||
export function commentMedia(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMedia>
|
||||
): ReturnType<typeof sendMedia> {
|
||||
if (message.chat.chatType !== 'channel') {
|
||||
return replyMedia(client, message, ...params)
|
||||
}
|
||||
|
||||
if (!message.replies || !message.replies.hasComments) {
|
||||
throw new MtArgumentError('This message does not have comments section')
|
||||
}
|
||||
|
||||
const [media, params_ = {}] = params
|
||||
params_.commentTo = message.id
|
||||
|
||||
return sendMedia(client, message.chat.inputPeer, media, params_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a text comment to a given message.
|
||||
*
|
||||
* If this is a normal message (not a channel post),
|
||||
* a simple reply will be sent.
|
||||
*
|
||||
* @throws MtArgumentError
|
||||
* If this is a channel post which does not have comments section.
|
||||
* To check if a post has comments, use {@link Message#replies}.hasComments
|
||||
*/
|
||||
export function commentMediaGroup(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMediaGroup>
|
||||
): ReturnType<typeof sendMediaGroup> {
|
||||
if (message.chat.chatType !== 'channel') {
|
||||
return replyMediaGroup(client, message, ...params)
|
||||
}
|
||||
|
||||
if (!message.replies || !message.replies.hasComments) {
|
||||
throw new MtArgumentError('This message does not have comments section')
|
||||
}
|
||||
|
||||
const [media, params_ = {}] = params
|
||||
params_.commentTo = message.id
|
||||
|
||||
return sendMediaGroup(client, message.chat.inputPeer, media, params_)
|
||||
}
|
119
packages/client/src/methods/messages/send-common.ts
Normal file
119
packages/client/src/methods/messages/send-common.ts
Normal file
|
@ -0,0 +1,119 @@
|
|||
import { BaseTelegramClient, getMarkedPeerId, MtArgumentError } from '@mtcute/core'
|
||||
|
||||
import { MtMessageNotFoundError } from '../../types/errors'
|
||||
import { Message } from '../../types/messages/message'
|
||||
import { InputPeerLike } from '../../types/peers'
|
||||
import { normalizeMessageId } from '../../utils'
|
||||
import { resolvePeer } from '../users/resolve-peer'
|
||||
import { _getDiscussionMessage } from './get-discussion-message'
|
||||
import { getMessages } from './get-messages'
|
||||
|
||||
// @exported
|
||||
export interface CommonSendParams {
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Message to comment to. Either a message object or message ID.
|
||||
*
|
||||
* This overwrites `replyTo` if it was passed
|
||||
*/
|
||||
commentTo?: number | Message
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Whether to clear draft after sending this message.
|
||||
*
|
||||
* Defaults to `false`
|
||||
*/
|
||||
clearDraft?: 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
|
||||
|
||||
/**
|
||||
* Peer to use when sending the message.
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Whether to dispatch the returned message
|
||||
* to the client's update handler.
|
||||
*/
|
||||
shouldDispatch?: true
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @noemit
|
||||
*/
|
||||
export async function _processCommonSendParameters(
|
||||
client: BaseTelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
params: CommonSendParams,
|
||||
) {
|
||||
let peer = await resolvePeer(client, chatId)
|
||||
|
||||
let replyTo = normalizeMessageId(params.replyTo)
|
||||
|
||||
if (params.commentTo) {
|
||||
[peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!)
|
||||
}
|
||||
|
||||
if (params.mustReply) {
|
||||
if (!replyTo) {
|
||||
throw new MtArgumentError('mustReply used, but replyTo was not passed')
|
||||
}
|
||||
|
||||
const msg = await getMessages(client, peer, replyTo)
|
||||
|
||||
if (!msg) {
|
||||
throw new MtMessageNotFoundError(getMarkedPeerId(peer), replyTo, 'to reply to')
|
||||
}
|
||||
}
|
||||
|
||||
return { peer, replyTo }
|
||||
}
|
70
packages/client/src/methods/messages/send-copy-group.ts
Normal file
70
packages/client/src/methods/messages/send-copy-group.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core'
|
||||
import { isPresent } from '@mtcute/core/utils'
|
||||
|
||||
import { Message } from '../../types/messages/message'
|
||||
import { InputPeerLike } from '../../types/peers'
|
||||
import { resolvePeer } from '../users/resolve-peer'
|
||||
import { getMessages } from './get-messages'
|
||||
import { CommonSendParams } from './send-common'
|
||||
import { sendMediaGroup } from './send-media-group'
|
||||
|
||||
// @exported
|
||||
export interface SendCopyGroupParams extends CommonSendParams {
|
||||
/** Destination chat ID */
|
||||
toChatId: InputPeerLike
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a message group (i.e. send the same message group, but do not forward it).
|
||||
*
|
||||
* Note that all the provided messages must be in the same message group
|
||||
*/
|
||||
export async function sendCopyGroup(
|
||||
client: BaseTelegramClient,
|
||||
params: SendCopyGroupParams &
|
||||
(
|
||||
| {
|
||||
/** Source chat ID */
|
||||
fromChatId: InputPeerLike
|
||||
/** Message IDs to forward */
|
||||
messages: number[]
|
||||
}
|
||||
| { messages: Message[] }
|
||||
),
|
||||
): Promise<Message[]> {
|
||||
const { toChatId, ...rest } = params
|
||||
|
||||
let msgs
|
||||
|
||||
if ('fromChatId' in params) {
|
||||
const fromPeer = await resolvePeer(client, params.fromChatId)
|
||||
|
||||
msgs = await getMessages(client, fromPeer, params.messages).then((r) => r.filter(isPresent))
|
||||
} else {
|
||||
msgs = params.messages
|
||||
}
|
||||
|
||||
const messageGroupId = msgs[0].groupedId!
|
||||
|
||||
for (let i = 1; i < msgs.length; i++) {
|
||||
if (!msgs[i].groupedId?.eq(messageGroupId) || !msgs[i].media) {
|
||||
throw new MtArgumentError('All messages must be in the same message group')
|
||||
}
|
||||
}
|
||||
|
||||
return sendMediaGroup(
|
||||
client,
|
||||
toChatId,
|
||||
msgs.map((msg) => {
|
||||
const raw = msg.raw as tl.RawMessage
|
||||
|
||||
return {
|
||||
type: 'auto',
|
||||
file: msg.media!.inputMedia,
|
||||
caption: raw.message,
|
||||
entities: raw.entities,
|
||||
}
|
||||
}),
|
||||
rest,
|
||||
)
|
||||
}
|
|
@ -3,28 +3,15 @@ import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, tl } from '@mtcut
|
|||
import { FormattedString, InputPeerLike, Message, MtMessageNotFoundError, ReplyMarkup } from '../../types'
|
||||
import { resolvePeer } from '../users/resolve-peer'
|
||||
import { getMessages } from './get-messages'
|
||||
import { CommonSendParams } from './send-common'
|
||||
import { sendMedia } from './send-media'
|
||||
import { sendText } from './send-text'
|
||||
|
||||
// @exported
|
||||
export interface SendCopyParams {
|
||||
export interface SendCopyParams extends CommonSendParams {
|
||||
/** 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)
|
||||
*/
|
||||
|
@ -38,26 +25,6 @@ export interface SendCopyParams {
|
|||
*/
|
||||
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.
|
||||
|
@ -71,13 +38,6 @@ export interface SendCopyParams {
|
|||
* 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
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,8 +47,6 @@ export interface SendCopyParams {
|
|||
* it will be copied simply as a text message,
|
||||
* and if the message contains an invoice,
|
||||
* it can't be copied.
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
export async function sendCopy(
|
||||
client: BaseTelegramClient,
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core'
|
||||
import { BaseTelegramClient, tl } from '@mtcute/core'
|
||||
import { randomLong } from '@mtcute/core/utils'
|
||||
|
||||
import { MtMessageNotFoundError } from '../../types/errors'
|
||||
import { InputMediaLike } from '../../types/media/input-media'
|
||||
import { Message } from '../../types/messages/message'
|
||||
import { InputPeerLike, PeersIndex } from '../../types/peers'
|
||||
import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils'
|
||||
import { normalizeDate } from '../../utils/misc-utils'
|
||||
import { assertIsUpdatesGroup } from '../../utils/updates-utils'
|
||||
import { _normalizeInputMedia } from '../files/normalize-input-media'
|
||||
import { resolvePeer } from '../users/resolve-peer'
|
||||
import { _getDiscussionMessage } from './get-discussion-message'
|
||||
import { getMessages } from './get-messages'
|
||||
import { _parseEntities } from './parse-entities'
|
||||
import { _processCommonSendParameters, CommonSendParams } from './send-common'
|
||||
|
||||
/**
|
||||
* Send a group of media.
|
||||
|
@ -28,56 +27,7 @@ export async function sendMediaGroup(
|
|||
client: BaseTelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
medias: (InputMediaLike | string)[],
|
||||
params?: {
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Message to comment to. Either a message object or message ID.
|
||||
*
|
||||
* This overwrites `replyTo` if it was passed
|
||||
*/
|
||||
commentTo?: number | Message
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
params?: CommonSendParams & {
|
||||
/**
|
||||
* Function that will be called after some part has been uploaded.
|
||||
* Only used when a file that requires uploading is passed,
|
||||
|
@ -88,49 +38,11 @@ export async function sendMediaGroup(
|
|||
* @param total Total file size
|
||||
*/
|
||||
progressCallback?: (index: number, uploaded: number, total: number) => void
|
||||
|
||||
/**
|
||||
* Whether to clear draft after sending this message.
|
||||
*
|
||||
* Defaults to `false`
|
||||
*/
|
||||
clearDraft?: 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
|
||||
|
||||
/**
|
||||
* Peer to use when sending the message.
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
},
|
||||
): Promise<Message[]> {
|
||||
if (!params) params = {}
|
||||
|
||||
let peer = await resolvePeer(client, chatId)
|
||||
|
||||
let replyTo = normalizeMessageId(params.replyTo)
|
||||
|
||||
if (params.commentTo) {
|
||||
[peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!)
|
||||
}
|
||||
|
||||
if (params.mustReply) {
|
||||
if (!replyTo) {
|
||||
throw new MtArgumentError('mustReply used, but replyTo was not passed')
|
||||
}
|
||||
|
||||
const msg = await getMessages(client, peer, replyTo)
|
||||
|
||||
if (!msg) {
|
||||
throw new MtMessageNotFoundError(getMarkedPeerId(peer), replyTo, 'to reply to')
|
||||
}
|
||||
}
|
||||
const { peer, replyTo } = await _processCommonSendParameters(client, chatId, params)
|
||||
|
||||
const multiMedia: tl.RawInputSingleMedia[] = []
|
||||
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core'
|
||||
import { BaseTelegramClient, tl } from '@mtcute/core'
|
||||
import { randomLong } from '@mtcute/core/utils'
|
||||
|
||||
import { BotKeyboard, ReplyMarkup } from '../../types/bots/keyboards'
|
||||
import { MtMessageNotFoundError } from '../../types/errors'
|
||||
import { InputMediaLike } from '../../types/media/input-media'
|
||||
import { Message } from '../../types/messages/message'
|
||||
import { FormattedString } from '../../types/parser'
|
||||
import { InputPeerLike } from '../../types/peers'
|
||||
import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils'
|
||||
import { normalizeDate } from '../../utils/misc-utils'
|
||||
import { _normalizeInputMedia } from '../files/normalize-input-media'
|
||||
import { resolvePeer } from '../users/resolve-peer'
|
||||
import { _findMessageInUpdate } from './find-in-update'
|
||||
import { _getDiscussionMessage } from './get-discussion-message'
|
||||
import { getMessages } from './get-messages'
|
||||
import { _parseEntities } from './parse-entities'
|
||||
import { _processCommonSendParameters, CommonSendParams } from './send-common'
|
||||
|
||||
/**
|
||||
* Send a single media (a photo or a document-based media)
|
||||
|
@ -30,7 +29,13 @@ export async function sendMedia(
|
|||
client: BaseTelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
media: InputMediaLike | string,
|
||||
params?: {
|
||||
params?: CommonSendParams & {
|
||||
/**
|
||||
* For bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
replyMarkup?: ReplyMarkup
|
||||
|
||||
/**
|
||||
* Override caption for `media`.
|
||||
*
|
||||
|
@ -47,61 +52,6 @@ export async function sendMedia(
|
|||
*/
|
||||
entities?: tl.TypeMessageEntity[]
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Message to comment to. Either a message object or message ID.
|
||||
*
|
||||
* This overwrites `replyTo` if it was passed
|
||||
*/
|
||||
commentTo?: number | Message
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* For bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
replyMarkup?: ReplyMarkup
|
||||
|
||||
/**
|
||||
* Function that will be called after some part has been uploaded.
|
||||
* Only used when a file that requires uploading is passed,
|
||||
|
@ -111,32 +61,6 @@ export async function sendMedia(
|
|||
* @param total Total file size
|
||||
*/
|
||||
progressCallback?: (uploaded: number, total: number) => void
|
||||
|
||||
/**
|
||||
* Whether to clear draft after sending this message.
|
||||
*
|
||||
* Defaults to `false`
|
||||
*/
|
||||
clearDraft?: 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
|
||||
|
||||
/**
|
||||
* Peer to use when sending the message.
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Whether to dispatch the returned message
|
||||
* to the client's update handler.
|
||||
*/
|
||||
shouldDispatch?: true
|
||||
},
|
||||
): Promise<Message> {
|
||||
if (!params) params = {}
|
||||
|
@ -160,26 +84,8 @@ export async function sendMedia(
|
|||
params.entities || (media as Extract<typeof media, { entities?: unknown }>).entities,
|
||||
)
|
||||
|
||||
let peer = await resolvePeer(client, chatId)
|
||||
const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup)
|
||||
|
||||
let replyTo = normalizeMessageId(params.replyTo)
|
||||
|
||||
if (params.commentTo) {
|
||||
[peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!)
|
||||
}
|
||||
|
||||
if (params.mustReply) {
|
||||
if (!replyTo) {
|
||||
throw new MtArgumentError('mustReply used, but replyTo was not passed')
|
||||
}
|
||||
|
||||
const msg = await getMessages(client, peer, replyTo)
|
||||
|
||||
if (!msg) {
|
||||
throw new MtMessageNotFoundError(getMarkedPeerId(peer), replyTo, 'to reply to')
|
||||
}
|
||||
}
|
||||
const { peer, replyTo } = await _processCommonSendParameters(client, chatId, params)
|
||||
|
||||
const res = await client.call({
|
||||
_: 'messages.sendMedia',
|
||||
|
|
43
packages/client/src/methods/messages/send-reply.ts
Normal file
43
packages/client/src/methods/messages/send-reply.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { BaseTelegramClient } from '@mtcute/core'
|
||||
|
||||
import { Message } from '../../types/messages/message'
|
||||
import { ParametersSkip2 } from '../../types/utils'
|
||||
import { sendMedia } from './send-media'
|
||||
import { sendMediaGroup } from './send-media-group'
|
||||
import { sendText } from './send-text'
|
||||
|
||||
/** Send a text in reply to a given message */
|
||||
export function replyText(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendText>
|
||||
): ReturnType<typeof sendText> {
|
||||
const [text, params_ = {}] = params
|
||||
params_.replyTo = message.id
|
||||
|
||||
return sendText(client, message.chat.inputPeer, text, params_)
|
||||
}
|
||||
|
||||
/** Send a media in reply to a given message */
|
||||
export function replyMedia(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMedia>
|
||||
): ReturnType<typeof sendMedia> {
|
||||
const [media, params_ = {}] = params
|
||||
params_.replyTo = message.id
|
||||
|
||||
return sendMedia(client, message.chat.inputPeer, media, params_)
|
||||
}
|
||||
|
||||
/** Send a media group in reply to a given message */
|
||||
export function replyMediaGroup(
|
||||
client: BaseTelegramClient,
|
||||
message: Message,
|
||||
...params: ParametersSkip2<typeof sendMediaGroup>
|
||||
): ReturnType<typeof sendMediaGroup> {
|
||||
const [media, params_ = {}] = params
|
||||
params_.replyTo = message.id
|
||||
|
||||
return sendMediaGroup(client, message.chat.inputPeer, media, params_)
|
||||
}
|
|
@ -1,20 +1,19 @@
|
|||
import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, MtTypeAssertionError, tl } from '@mtcute/core'
|
||||
import { BaseTelegramClient, getMarkedPeerId, MtTypeAssertionError, tl } from '@mtcute/core'
|
||||
import { randomLong } from '@mtcute/core/utils'
|
||||
|
||||
import { BotKeyboard, ReplyMarkup } from '../../types/bots/keyboards'
|
||||
import { MtMessageNotFoundError } from '../../types/errors'
|
||||
import { Message } from '../../types/messages/message'
|
||||
import { FormattedString } from '../../types/parser'
|
||||
import { InputPeerLike, PeersIndex } from '../../types/peers'
|
||||
import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils'
|
||||
import { normalizeDate } from '../../utils/misc-utils'
|
||||
import { inputPeerToPeer } from '../../utils/peer-utils'
|
||||
import { createDummyUpdate } from '../../utils/updates-utils'
|
||||
import { getAuthState } from '../auth/_state'
|
||||
import { resolvePeer } from '../users/resolve-peer'
|
||||
import { _findMessageInUpdate } from './find-in-update'
|
||||
import { _getDiscussionMessage } from './get-discussion-message'
|
||||
import { getMessages } from './get-messages'
|
||||
import { _parseEntities } from './parse-entities'
|
||||
import { _processCommonSendParameters, CommonSendParams } from './send-common'
|
||||
|
||||
/**
|
||||
* Send a text message
|
||||
|
@ -27,41 +26,12 @@ export async function sendText(
|
|||
client: BaseTelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
text: string | FormattedString<string>,
|
||||
params?: {
|
||||
params?: CommonSendParams & {
|
||||
/**
|
||||
* 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 bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* This overwrites `replyTo` if it was passed
|
||||
*/
|
||||
commentTo?: number | Message
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
replyMarkup?: ReplyMarkup
|
||||
|
||||
/**
|
||||
* List of formatting entities to use instead of parsing via a
|
||||
|
@ -75,78 +45,14 @@ export async function sendText(
|
|||
* Whether to disable links preview in this message
|
||||
*/
|
||||
disableWebPreview?: boolean
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Whether to disallow further forwards of this message.
|
||||
*
|
||||
* Only for bots, works even if the target chat does not
|
||||
* have content protection.
|
||||
*/
|
||||
forbidForwards?: boolean
|
||||
|
||||
/**
|
||||
* Peer to use when sending the message.
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Whether to dispatch the returned message
|
||||
* to the client's update handler.
|
||||
*/
|
||||
shouldDispatch?: true
|
||||
},
|
||||
): Promise<Message> {
|
||||
if (!params) params = {}
|
||||
|
||||
const [message, entities] = await _parseEntities(client, text, params.parseMode, params.entities)
|
||||
|
||||
let peer = await resolvePeer(client, chatId)
|
||||
const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup)
|
||||
|
||||
let replyTo = normalizeMessageId(params.replyTo)
|
||||
|
||||
if (params.commentTo) {
|
||||
[peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!)
|
||||
}
|
||||
|
||||
if (params.mustReply) {
|
||||
if (!replyTo) {
|
||||
throw new MtArgumentError('mustReply used, but replyTo was not passed')
|
||||
}
|
||||
|
||||
const msg = await getMessages(client, peer, replyTo)
|
||||
|
||||
if (!msg) {
|
||||
throw new MtMessageNotFoundError(getMarkedPeerId(peer), replyTo, 'to reply to')
|
||||
}
|
||||
}
|
||||
const { peer, replyTo } = await _processCommonSendParameters(client, chatId, params)
|
||||
|
||||
const res = await client.call({
|
||||
_: 'messages.sendMessage',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { BaseTelegramClient, MtTypeAssertionError, tl } from '@mtcute/core'
|
||||
import { assertTypeIs } from '@mtcute/core/utils'
|
||||
import { BaseTelegramClient, MaybeArray, MtTypeAssertionError, tl } from '@mtcute/core'
|
||||
import { assertTypeIs, LongSet } from '@mtcute/core/utils'
|
||||
|
||||
import { Sticker } from '../../types'
|
||||
import { Message, Sticker } from '../../types'
|
||||
import { parseDocument } from '../../types/media/document-utils'
|
||||
|
||||
/**
|
||||
|
@ -27,3 +27,30 @@ export async function getCustomEmojis(client: BaseTelegramClient, ids: tl.Long[]
|
|||
return doc
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Given one or more messages, extract all unique custom emojis from it and fetch them
|
||||
*/
|
||||
export async function getCustomEmojisFromMessages(
|
||||
client: BaseTelegramClient,
|
||||
messages: MaybeArray<Message>,
|
||||
): Promise<Sticker[]> {
|
||||
const set = new LongSet()
|
||||
|
||||
if (!Array.isArray(messages)) messages = [messages]
|
||||
|
||||
for (const { raw } of messages) {
|
||||
if (raw._ === 'messageService' || !raw.entities) continue
|
||||
|
||||
for (const entity of raw.entities) {
|
||||
if (entity._ === 'messageEntityCustomEmoji') {
|
||||
set.add(entity.documentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const arr = set.toArray()
|
||||
if (!arr.length) return []
|
||||
|
||||
return getCustomEmojis(client, arr)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import type { Message } from './message'
|
|||
*/
|
||||
export type InputMessageId = { chatId: InputPeerLike; message: number } | { message: Message }
|
||||
|
||||
/** Remove {@link InputMessageId} type from the given type */
|
||||
export type OmitInputMessageId<T> = Omit<T, 'chatId' | 'message'>
|
||||
|
||||
/** @internal */
|
||||
export function normalizeInputMessageId(id: InputMessageId) {
|
||||
if ('chatId' in id) return id
|
||||
|
|
|
@ -47,9 +47,10 @@ export interface MessageForwardInfo {
|
|||
/** Information about replies to a message */
|
||||
export interface MessageRepliesInfo {
|
||||
/**
|
||||
* Whether this is a comments thread under a channel post
|
||||
* Whether this message is a channel post that has a comments thread
|
||||
* in the linked discussion group
|
||||
*/
|
||||
isComments: false
|
||||
hasComments: false
|
||||
|
||||
/**
|
||||
* Total number of replies
|
||||
|
@ -75,7 +76,8 @@ export interface MessageRepliesInfo {
|
|||
/** Information about comments to a channel post */
|
||||
export interface MessageCommentsInfo extends Omit<MessageRepliesInfo, 'isComments'> {
|
||||
/**
|
||||
* Whether this is a comments thread under a channel post
|
||||
* Whether this message is a channel post that has a comments thread
|
||||
* in the linked discussion group
|
||||
*/
|
||||
isComments: true
|
||||
|
||||
|
@ -303,7 +305,7 @@ export class Message {
|
|||
if (!this._replies) {
|
||||
const r = this.raw.replies
|
||||
const obj: MessageRepliesInfo = {
|
||||
isComments: r.comments as false,
|
||||
hasComments: r.comments as false,
|
||||
count: r.replies,
|
||||
hasUnread: r.readMaxId !== undefined && r.readMaxId !== r.maxId,
|
||||
lastMessageId: r.maxId,
|
||||
|
|
|
@ -68,13 +68,6 @@ export class BotChatJoinRequestUpdate {
|
|||
get invite(): ChatInviteLink {
|
||||
return (this._invite ??= new ChatInviteLink(this.raw.invite))
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve or deny the request.
|
||||
*/
|
||||
// hide(action: Parameters<TelegramClient['hideJoinRequest']>[1]['action']): Promise<void> {
|
||||
// return this.client.hideJoinRequest(this.chat.inputPeer, { action, user: this.user.inputPeer })
|
||||
// }
|
||||
}
|
||||
|
||||
makeInspectable(BotChatJoinRequestUpdate)
|
||||
|
|
|
@ -47,23 +47,6 @@ export class ChatJoinRequestUpdate {
|
|||
get totalPending(): number {
|
||||
return this.raw.requestsPending
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve or deny the last requested user
|
||||
*/
|
||||
// hideLast(action: Parameters<TelegramClient['hideJoinRequest']>[1]['action']): Promise<void> {
|
||||
// return this.client.hideJoinRequest(this.chatId, { user: this.raw.recentRequesters[0], action })
|
||||
// }
|
||||
|
||||
/**
|
||||
* Approve or deny all recent requests
|
||||
* (the ones available in {@link recentRequesters})
|
||||
*/
|
||||
// async hideAllRecent(action: Parameters<TelegramClient['hideJoinRequest']>[1]['action']): Promise<void> {
|
||||
// for (const id of this.raw.recentRequesters) {
|
||||
// await this.client.hideJoinRequest(this.chatId, { user: id, action })
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
makeInspectable(ChatJoinRequestUpdate)
|
||||
|
|
|
@ -76,14 +76,6 @@ export class ChosenInlineResult {
|
|||
|
||||
return encodeInlineMessageId(this.raw.msgId)
|
||||
}
|
||||
|
||||
// async editMessage(params: Parameters<TelegramClient['editInlineMessage']>[1]): Promise<void> {
|
||||
// if (!this.raw.msgId) {
|
||||
// throw new MtArgumentError('No message ID, make sure you have included reply markup!')
|
||||
// }
|
||||
|
||||
// return this.client.editInlineMessage(this.raw.msgId, params)
|
||||
// }
|
||||
}
|
||||
|
||||
makeInspectable(ChosenInlineResult)
|
||||
|
|
|
@ -24,12 +24,18 @@ export class PollUpdate {
|
|||
return this.raw.pollId
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a shortened version of update, not containing the poll itself.
|
||||
*/
|
||||
get isShort(): boolean {
|
||||
return this.raw.poll === undefined
|
||||
}
|
||||
|
||||
private _poll?: Poll
|
||||
/**
|
||||
* The poll.
|
||||
*
|
||||
* Note that sometimes the update does not have the poll
|
||||
* (Telegram limitation), and mtcute creates a stub poll
|
||||
* When {@link isShort} is set, mtcute creates a stub poll
|
||||
* with empty question, answers and flags
|
||||
* (like `quiz`, `public`, etc.)
|
||||
*
|
||||
|
@ -39,8 +45,7 @@ export class PollUpdate {
|
|||
*
|
||||
* Bot API and TDLib do basically the same internally,
|
||||
* and thus are able to always provide them,
|
||||
* but mtcute tries to keep it simple in terms of local
|
||||
* storage and only stores the necessary information.
|
||||
* but mtcute currently does not have a way to do that.
|
||||
*/
|
||||
get poll(): Poll {
|
||||
if (!this._poll) {
|
||||
|
|
|
@ -159,4 +159,14 @@ export class LongSet {
|
|||
clear() {
|
||||
this._set.clear()
|
||||
}
|
||||
|
||||
toArray() {
|
||||
const arr: Long[] = []
|
||||
|
||||
for (const v of this._set) {
|
||||
arr.push(longFromFastString(v))
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,19 +8,21 @@ function generateHandler() {
|
|||
|
||||
types.forEach((type) => {
|
||||
lines.push(
|
||||
`export type ${type.handlerTypeName}Handler<T = ${type.updateType}` +
|
||||
`export type ${type.handlerTypeName}Handler<T = ${type.context}` +
|
||||
`${type.state ? ', S = never' : ''}> = ParsedUpdateHandler<` +
|
||||
`'${type.typeName}', T${type.state ? ', S' : ''}>`,
|
||||
)
|
||||
names.push(`${type.handlerTypeName}Handler`)
|
||||
})
|
||||
|
||||
replaceSections('handler.ts', {
|
||||
replaceSections(
|
||||
'handler.ts',
|
||||
{
|
||||
codegen:
|
||||
lines.join('\n') +
|
||||
'\n\nexport type UpdateHandler = \n' +
|
||||
names.map((i) => ` | ${i}\n`).join(''),
|
||||
}, __dirname)
|
||||
lines.join('\n') + '\n\nexport type UpdateHandler = \n' + names.map((i) => ` | ${i}\n`).join(''),
|
||||
},
|
||||
__dirname,
|
||||
)
|
||||
}
|
||||
|
||||
function generateDispatcher() {
|
||||
|
@ -37,9 +39,13 @@ function generateDispatcher() {
|
|||
* @param handler ${toSentence(type, 'full')}
|
||||
* @param group Handler group index
|
||||
*/
|
||||
on${type.handlerTypeName}(handler: ${type.handlerTypeName}Handler${type.state ? `<${type.updateType}, State extends never ? never : UpdateState<State, SceneName>>` : ''}['callback'], group?: number): void
|
||||
on${type.handlerTypeName}(handler: ${type.handlerTypeName}Handler${
|
||||
type.state ? `<${type.context}, State extends never ? never : UpdateState<State, SceneName>>` : ''
|
||||
}['callback'], group?: number): void
|
||||
|
||||
${type.state ? `
|
||||
${
|
||||
type.state ?
|
||||
`
|
||||
/**
|
||||
* Register ${toSentence(type)} with a filter
|
||||
*
|
||||
|
@ -48,11 +54,15 @@ ${type.state ? `
|
|||
* @param group Handler group index
|
||||
*/
|
||||
on${type.handlerTypeName}<Mod>(
|
||||
filter: UpdateFilter<${type.updateType}, Mod, State>,
|
||||
handler: ${type.handlerTypeName}Handler<filters.Modify<${type.updateType}, Mod>, State extends never ? never : UpdateState<State, SceneName>>['callback'],
|
||||
filter: UpdateFilter<${type.context}, Mod, State>,
|
||||
handler: ${type.handlerTypeName}Handler<filters.Modify<${
|
||||
type.context
|
||||
}, Mod>, State extends never ? never : UpdateState<State, SceneName>>['callback'],
|
||||
group?: number
|
||||
): void
|
||||
` : ''}
|
||||
` :
|
||||
''
|
||||
}
|
||||
|
||||
/**
|
||||
* Register ${toSentence(type)} with a filter
|
||||
|
@ -62,8 +72,10 @@ ${type.state ? `
|
|||
* @param group Handler group index
|
||||
*/
|
||||
on${type.handlerTypeName}<Mod>(
|
||||
filter: UpdateFilter<${type.updateType}, Mod>,
|
||||
handler: ${type.handlerTypeName}Handler<filters.Modify<${type.updateType}, Mod>${type.state ? ', State extends never ? never : UpdateState<State, SceneName>' : ''}>['callback'],
|
||||
filter: UpdateFilter<${type.context}, Mod>,
|
||||
handler: ${type.handlerTypeName}Handler<filters.Modify<${type.context}, Mod>${
|
||||
type.state ? ', State extends never ? never : UpdateState<State, SceneName>' : ''
|
||||
}>['callback'],
|
||||
group?: number
|
||||
): void
|
||||
|
||||
|
@ -74,13 +86,20 @@ ${type.state ? `
|
|||
`)
|
||||
})
|
||||
|
||||
replaceSections('dispatcher.ts', {
|
||||
replaceSections(
|
||||
'dispatcher.ts',
|
||||
{
|
||||
codegen: lines.join('\n'),
|
||||
'codegen-imports':
|
||||
'import {\n' +
|
||||
imports.sort().map((i) => ` ${i},\n`).join('') +
|
||||
imports
|
||||
.sort()
|
||||
.map((i) => ` ${i},\n`)
|
||||
.join('') +
|
||||
"} from './handler'",
|
||||
}, __dirname)
|
||||
},
|
||||
__dirname,
|
||||
)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
|
8
packages/dispatcher/src/context/base.ts
Normal file
8
packages/dispatcher/src/context/base.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { ParsedUpdate, TelegramClient } from '@mtcute/client'
|
||||
|
||||
export type UpdateContext<T> = T & {
|
||||
client: TelegramClient
|
||||
_name: Extract<ParsedUpdate, { data: T }>['name']
|
||||
}
|
||||
|
||||
export type UpdateContextDistributed<T> = T extends never ? never : UpdateContext<T>
|
64
packages/dispatcher/src/context/callback-query.ts
Normal file
64
packages/dispatcher/src/context/callback-query.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { CallbackQuery, getMarkedPeerId, MtArgumentError, MtMessageNotFoundError, TelegramClient } from '@mtcute/client'
|
||||
|
||||
import { UpdateContext } from './base'
|
||||
|
||||
/**
|
||||
* Context of a callback query update.
|
||||
*
|
||||
* This is a subclass of {@link CallbackQuery}, so all its fields are also available.
|
||||
*/
|
||||
export class CallbackQueryContext extends CallbackQuery implements UpdateContext<CallbackQuery> {
|
||||
readonly _name = 'callback_query'
|
||||
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
query: CallbackQuery,
|
||||
) {
|
||||
super(query.raw, query._peers)
|
||||
}
|
||||
|
||||
/** Answer to this callback query */
|
||||
answer(params: Parameters<TelegramClient['answerCallbackQuery']>[1]) {
|
||||
return this.client.answerCallbackQuery(this.id, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* * 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() {
|
||||
if (this.raw._ !== 'updateBotCallbackQuery') {
|
||||
throw new MtArgumentError('Cannot get message for inline callback query')
|
||||
}
|
||||
|
||||
const [msg] = await this.client.getMessages(this.raw.peer, this.raw.msgId)
|
||||
|
||||
if (!msg) {
|
||||
throw new MtMessageNotFoundError(getMarkedPeerId(this.raw.peer), this.raw.msgId, 'Message not found')
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the message that contained the callback button that was clicked.
|
||||
*/
|
||||
async editMessage(params: Omit<Parameters<TelegramClient['editInlineMessage']>[0], 'messageId'>) {
|
||||
if (this.raw._ === 'updateInlineBotCallbackQuery') {
|
||||
return this.client.editInlineMessage({
|
||||
messageId: this.raw.msgId,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
return this.client.editMessage({
|
||||
chatId: this.raw.peer,
|
||||
message: this.raw.msgId,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
}
|
39
packages/dispatcher/src/context/chat-join-request.ts
Normal file
39
packages/dispatcher/src/context/chat-join-request.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { BotChatJoinRequestUpdate, TelegramClient } from '@mtcute/client'
|
||||
|
||||
import { UpdateContext } from './base'
|
||||
|
||||
/**
|
||||
* Context of a chat join request update (for bots).
|
||||
*
|
||||
* This is a subclass of {@link BotChatJoinRequestUpdate}, so all its fields are also available.
|
||||
*/
|
||||
export class ChatJoinRequestUpdateContext
|
||||
extends BotChatJoinRequestUpdate
|
||||
implements UpdateContext<BotChatJoinRequestUpdate> {
|
||||
readonly _name = 'bot_chat_join_request'
|
||||
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
update: BotChatJoinRequestUpdate,
|
||||
) {
|
||||
super(update.raw, update._peers)
|
||||
}
|
||||
|
||||
/** Approve the request */
|
||||
approve(): Promise<void> {
|
||||
return this.client.hideJoinRequest({
|
||||
action: 'approve',
|
||||
user: this.user.inputPeer,
|
||||
chatId: this.chat.inputPeer,
|
||||
})
|
||||
}
|
||||
|
||||
/** Decline the request */
|
||||
decline(): Promise<void> {
|
||||
return this.client.hideJoinRequest({
|
||||
action: 'decline',
|
||||
user: this.user.inputPeer,
|
||||
chatId: this.chat.inputPeer,
|
||||
})
|
||||
}
|
||||
}
|
38
packages/dispatcher/src/context/chosen-inline-result.ts
Normal file
38
packages/dispatcher/src/context/chosen-inline-result.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { ChosenInlineResult, MtArgumentError, TelegramClient } from '@mtcute/client'
|
||||
|
||||
import { UpdateContext } from './base'
|
||||
|
||||
/**
|
||||
* Context of a chosen inline result update.
|
||||
*
|
||||
* This is a subclass of {@link ChosenInlineResult}, so all its fields are also available.
|
||||
*
|
||||
* > **Note**: To receive these updates, you must enable
|
||||
* > Inline feedback in [@BotFather](//t.me/botfather)
|
||||
*/
|
||||
export class ChosenInlineResultContext extends ChosenInlineResult implements UpdateContext<ChosenInlineResult> {
|
||||
readonly _name = 'chosen_inline_result'
|
||||
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
result: ChosenInlineResult,
|
||||
) {
|
||||
super(result.raw, result._peers)
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the message that was sent when this inline result that was chosen.
|
||||
*
|
||||
* > **Note**: This method can only be used if the message contained a reply markup
|
||||
*/
|
||||
async editMessage(params: Parameters<TelegramClient['editInlineMessage']>[0]): Promise<void> {
|
||||
if (!this.raw.msgId) {
|
||||
throw new MtArgumentError('No message ID, make sure you have included reply markup!')
|
||||
}
|
||||
|
||||
return this.client.editInlineMessage({
|
||||
...params,
|
||||
messageId: this.raw.msgId,
|
||||
})
|
||||
}
|
||||
}
|
7
packages/dispatcher/src/context/index.ts
Normal file
7
packages/dispatcher/src/context/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export * from './base'
|
||||
export * from './callback-query'
|
||||
export * from './chat-join-request'
|
||||
export * from './chosen-inline-result'
|
||||
export * from './inline-query'
|
||||
export * from './message'
|
||||
export * from './pre-checkout-query'
|
24
packages/dispatcher/src/context/inline-query.ts
Normal file
24
packages/dispatcher/src/context/inline-query.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { InlineQuery, ParametersSkip1, TelegramClient } from '@mtcute/client'
|
||||
|
||||
import { UpdateContext } from './base'
|
||||
|
||||
/**
|
||||
* Context of an inline query update.
|
||||
*
|
||||
* This is a subclass of {@link InlineQuery}, so all its fields are also available.
|
||||
*/
|
||||
export class InlineQueryContext extends InlineQuery implements UpdateContext<InlineQuery> {
|
||||
readonly _name = 'inline_query'
|
||||
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
query: InlineQuery,
|
||||
) {
|
||||
super(query.raw, query._peers)
|
||||
}
|
||||
|
||||
/** Answer to this inline query */
|
||||
answer(...params: ParametersSkip1<TelegramClient['answerInlineQuery']>) {
|
||||
return this.client.answerInlineQuery(this.id, ...params)
|
||||
}
|
||||
}
|
170
packages/dispatcher/src/context/message.ts
Normal file
170
packages/dispatcher/src/context/message.ts
Normal file
|
@ -0,0 +1,170 @@
|
|||
import { Message, OmitInputMessageId, ParametersSkip1, TelegramClient } from '@mtcute/client'
|
||||
import { DeleteMessagesParams } from '@mtcute/client/src/methods/messages/delete-messages'
|
||||
import { ForwardMessageOptions } from '@mtcute/client/src/methods/messages/forward-messages'
|
||||
import { SendCopyParams } from '@mtcute/client/src/methods/messages/send-copy'
|
||||
import { SendCopyGroupParams } from '@mtcute/client/src/methods/messages/send-copy-group'
|
||||
|
||||
import { UpdateContext } from './base'
|
||||
|
||||
/**
|
||||
* Context of a message-related update.
|
||||
*
|
||||
* This is a subclass of {@link Message}, so all fields
|
||||
* of the message are available.
|
||||
*
|
||||
* For message groups, own fields are related to the last message
|
||||
* in the group. To access all messages, use {@link MessageContext#messages}.
|
||||
*/
|
||||
export class MessageContext extends Message implements UpdateContext<Message> {
|
||||
// this is primarily for proper types in filters, so don't bother much with actual value
|
||||
readonly _name = 'new_message'
|
||||
|
||||
/**
|
||||
* List of messages in the message group.
|
||||
*
|
||||
* For other updates, this is a list with a single element (`this`).
|
||||
*/
|
||||
readonly messages: MessageContext[]
|
||||
|
||||
/** Whether this update is about a message group */
|
||||
readonly isMessageGroup: boolean
|
||||
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
message: Message | Message[],
|
||||
) {
|
||||
const msg = Array.isArray(message) ? message[message.length - 1] : message
|
||||
super(msg.raw, msg._peers, msg.isScheduled)
|
||||
|
||||
this.messages = Array.isArray(message) ? message.map((it) => new MessageContext(client, it)) : [this]
|
||||
this.isMessageGroup = Array.isArray(message)
|
||||
}
|
||||
|
||||
/** Get a message that this message is a reply to */
|
||||
getReplyTo() {
|
||||
return this.client.getReplyTo(this)
|
||||
}
|
||||
|
||||
/** If this is a channel post, get its automatic forward in the discussion group */
|
||||
getDiscussionMessage() {
|
||||
return this.client.getDiscussionMessage({ chatId: this.chat.inputPeer, message: this.id })
|
||||
}
|
||||
|
||||
/** Get all custom emojis contained in this message (message group), if any */
|
||||
getCustomEmojis() {
|
||||
return this.client.getCustomEmojisFromMessages(this.messages)
|
||||
}
|
||||
|
||||
/** Send a text message to the same chat (and topic, if applicable) as a given message */
|
||||
answerText(...params: ParametersSkip1<TelegramClient['answerText']>) {
|
||||
return this.client.answerText(this, ...params)
|
||||
}
|
||||
|
||||
/** Send a media to the same chat (and topic, if applicable) as a given message */
|
||||
answerMedia(...params: ParametersSkip1<TelegramClient['answerMedia']>) {
|
||||
return this.client.answerMedia(this, ...params)
|
||||
}
|
||||
|
||||
/** Send a media group to the same chat (and topic, if applicable) as a given message */
|
||||
answerMediaGroup(...params: ParametersSkip1<TelegramClient['answerMediaGroup']>) {
|
||||
return this.client.answerMediaGroup(this, ...params)
|
||||
}
|
||||
|
||||
/** Send a text message in reply to this message */
|
||||
replyText(...params: ParametersSkip1<TelegramClient['replyText']>) {
|
||||
return this.client.replyText(this, ...params)
|
||||
}
|
||||
|
||||
/** Send a media in reply to this message */
|
||||
replyMedia(...params: ParametersSkip1<TelegramClient['replyMedia']>) {
|
||||
return this.client.replyMedia(this, ...params)
|
||||
}
|
||||
|
||||
/** Send a media group in reply to this message */
|
||||
replyMediaGroup(...params: ParametersSkip1<TelegramClient['replyMediaGroup']>) {
|
||||
return this.client.replyMediaGroup(this, ...params)
|
||||
}
|
||||
|
||||
/** Send a text as a comment to this message */
|
||||
commentText(...params: ParametersSkip1<TelegramClient['commentText']>) {
|
||||
return this.client.commentText(this, ...params)
|
||||
}
|
||||
|
||||
/** Send a media as a comment to this message */
|
||||
commentMedia(...params: ParametersSkip1<TelegramClient['commentMedia']>) {
|
||||
return this.client.commentMedia(this, ...params)
|
||||
}
|
||||
|
||||
/** Send a media group as a comment to this message */
|
||||
commentMediaGroup(...params: ParametersSkip1<TelegramClient['commentMediaGroup']>) {
|
||||
return this.client.commentMediaGroup(this, ...params)
|
||||
}
|
||||
|
||||
/** Delete this message (message group) */
|
||||
delete(params?: DeleteMessagesParams) {
|
||||
return this.client.deleteMessagesById(
|
||||
this.chat.inputPeer,
|
||||
this.messages.map((it) => it.id),
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
/** Pin this message */
|
||||
pin(params?: OmitInputMessageId<Parameters<TelegramClient['pinMessage']>[0]>) {
|
||||
return this.client.pinMessage({
|
||||
chatId: this.chat.inputPeer,
|
||||
message: this.id,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/** Unpin this message */
|
||||
unpin() {
|
||||
return this.client.unpinMessage({
|
||||
chatId: this.chat.inputPeer,
|
||||
message: this.id,
|
||||
})
|
||||
}
|
||||
|
||||
/** Edit this message */
|
||||
edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>) {
|
||||
return this.client.editMessage({
|
||||
chatId: this.chat.inputPeer,
|
||||
message: this.id,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/** Forward this message (message group) */
|
||||
forwardTo(params: ForwardMessageOptions) {
|
||||
return this.client.forwardMessagesById({
|
||||
fromChatId: this.chat.inputPeer,
|
||||
messages: this.messages.map((it) => it.id),
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/** Send a copy of this message (message group) */
|
||||
copy(params: SendCopyParams & SendCopyGroupParams) {
|
||||
if (this.isMessageGroup) {
|
||||
return this.client.sendCopyGroup({
|
||||
messages: this.messages,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
return this.client.sendCopy({
|
||||
message: this,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/** React to this message */
|
||||
react(params: OmitInputMessageId<Parameters<TelegramClient['sendReaction']>[0]>) {
|
||||
return this.client.sendReaction({
|
||||
chatId: this.chat.inputPeer,
|
||||
message: this.id,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
}
|
37
packages/dispatcher/src/context/parse.ts
Normal file
37
packages/dispatcher/src/context/parse.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { ParsedUpdate, TelegramClient } from '@mtcute/client'
|
||||
|
||||
import { UpdateContext } from './base'
|
||||
import { CallbackQueryContext } from './callback-query'
|
||||
import { ChatJoinRequestUpdateContext } from './chat-join-request'
|
||||
import { ChosenInlineResultContext } from './chosen-inline-result'
|
||||
import { InlineQueryContext } from './inline-query'
|
||||
import { MessageContext } from './message'
|
||||
import { PreCheckoutQueryContext } from './pre-checkout-query'
|
||||
|
||||
/** @internal */
|
||||
export function _parsedUpdateToContext(client: TelegramClient, update: ParsedUpdate) {
|
||||
switch (update.name) {
|
||||
case 'new_message':
|
||||
case 'edit_message':
|
||||
case 'message_group':
|
||||
return new MessageContext(client, update.data)
|
||||
case 'inline_query':
|
||||
return new InlineQueryContext(client, update.data)
|
||||
case 'chosen_inline_result':
|
||||
return new ChosenInlineResultContext(client, update.data)
|
||||
case 'callback_query':
|
||||
return new CallbackQueryContext(client, update.data)
|
||||
case 'bot_chat_join_request':
|
||||
return new ChatJoinRequestUpdateContext(client, update.data)
|
||||
case 'pre_checkout_query':
|
||||
return new PreCheckoutQueryContext(client, update.data)
|
||||
}
|
||||
|
||||
const _update = update.data as UpdateContext<typeof update.data>
|
||||
_update.client = client
|
||||
_update._name = update.name
|
||||
|
||||
return _update
|
||||
}
|
||||
|
||||
export type UpdateContextType = ReturnType<typeof _parsedUpdateToContext>
|
29
packages/dispatcher/src/context/pre-checkout-query.ts
Normal file
29
packages/dispatcher/src/context/pre-checkout-query.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { PreCheckoutQuery, TelegramClient } from '@mtcute/client'
|
||||
|
||||
import { UpdateContext } from './base'
|
||||
|
||||
/**
|
||||
* Context of a pre-checkout query update
|
||||
*
|
||||
* This is a subclass of {@link PreCheckoutQuery}, so all its fields are also available.
|
||||
*/
|
||||
export class PreCheckoutQueryContext extends PreCheckoutQuery implements UpdateContext<PreCheckoutQuery> {
|
||||
readonly _name = 'pre_checkout_query'
|
||||
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
query: PreCheckoutQuery,
|
||||
) {
|
||||
super(query.raw, query._peers)
|
||||
}
|
||||
|
||||
/** Approve the query */
|
||||
approve(): Promise<void> {
|
||||
return this.client.answerPreCheckoutQuery(this.raw.queryId)
|
||||
}
|
||||
|
||||
/** Decline the query, optionally with an error */
|
||||
decline(error = ''): Promise<void> {
|
||||
return this.client.answerPreCheckoutQuery(this.raw.queryId, { error })
|
||||
}
|
||||
}
|
|
@ -1,32 +1,38 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable @typescript-eslint/unified-signatures,@typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,max-depth,dot-notation */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types */
|
||||
// ^^ will be looked into in MTQ-29
|
||||
|
||||
import {
|
||||
BotChatJoinRequestUpdate,
|
||||
BotStoppedUpdate,
|
||||
CallbackQuery,
|
||||
ChatJoinRequestUpdate,
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
DeleteMessageUpdate,
|
||||
DeleteStoryUpdate,
|
||||
HistoryReadUpdate,
|
||||
InlineQuery,
|
||||
MaybeAsync,
|
||||
Message,
|
||||
ParsedUpdate,
|
||||
PeersIndex,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
PreCheckoutQuery,
|
||||
StoryUpdate,
|
||||
DeleteStoryUpdate,
|
||||
TelegramClient,
|
||||
tl,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
tl,
|
||||
} from '@mtcute/client'
|
||||
import { MtArgumentError } from '@mtcute/core'
|
||||
|
||||
import {
|
||||
CallbackQueryContext,
|
||||
ChatJoinRequestUpdateContext,
|
||||
ChosenInlineResultContext,
|
||||
InlineQueryContext,
|
||||
MessageContext,
|
||||
PreCheckoutQueryContext,
|
||||
} from './context'
|
||||
import { UpdateContext } from './context/base'
|
||||
import { _parsedUpdateToContext, UpdateContextType } from './context/parse'
|
||||
import { filters, UpdateFilter } from './filters'
|
||||
// begin-codegen-imports
|
||||
import {
|
||||
|
@ -55,7 +61,6 @@ import {
|
|||
// end-codegen-imports
|
||||
import { PropagationAction } from './propagation'
|
||||
import { defaultStateKeyDelegate, IStateStorage, StateKeyDelegate, UpdateState } from './state'
|
||||
import { MtArgumentError } from '@mtcute/core'
|
||||
|
||||
/**
|
||||
* Updates dispatcher
|
||||
|
@ -193,7 +198,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
|
||||
// order does not matter in the dispatcher,
|
||||
// so we can handle each update in its own task
|
||||
this.dispatchRawUpdateNow(update, peers).catch((err) => this._client!['_emitError'](err))
|
||||
this.dispatchRawUpdateNow(update, peers).catch((err) => this._client!._emitError(err))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -263,7 +268,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
|
||||
// order does not matter in the dispatcher,
|
||||
// so we can handle each update in its own task
|
||||
this.dispatchUpdateNow(update).catch((err) => this._client!['_emitError'](err))
|
||||
this.dispatchUpdateNow(update).catch((err) => this._client!._emitError(err))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -287,6 +292,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
parsedState?: UpdateState<State, SceneName> | null,
|
||||
parsedScene?: string | null,
|
||||
forceScene?: true,
|
||||
parsedContext?: UpdateContextType,
|
||||
): Promise<boolean> {
|
||||
if (!this._client) return false
|
||||
|
||||
|
@ -301,9 +307,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
) {
|
||||
// no need to fetch scene if there are no registered scenes
|
||||
|
||||
const key = await this._stateKeyDelegate!(
|
||||
update.name === 'message_group' ? update.data[0] : update.data,
|
||||
)
|
||||
if (!parsedContext) parsedContext = _parsedUpdateToContext(this._client, update)
|
||||
const key = await this._stateKeyDelegate!(parsedContext as any)
|
||||
|
||||
if (key) {
|
||||
parsedScene = await this._storage.getCurrentScene(key)
|
||||
|
@ -334,16 +339,20 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
if (parsedState === undefined) {
|
||||
if (
|
||||
this._storage &&
|
||||
(update.name === 'new_message' || update.name === 'edit_message' || update.name === 'callback_query')
|
||||
(update.name === 'new_message' ||
|
||||
update.name === 'edit_message' ||
|
||||
update.name === 'callback_query' ||
|
||||
update.name === 'message_group')
|
||||
) {
|
||||
const key = await this._stateKeyDelegate!(update.data)
|
||||
if (!parsedContext) parsedContext = _parsedUpdateToContext(this._client, update)
|
||||
const key = await this._stateKeyDelegate!(parsedContext as any)
|
||||
|
||||
if (key) {
|
||||
let customKey
|
||||
|
||||
if (
|
||||
!this._customStateKeyDelegate ||
|
||||
(customKey = await this._customStateKeyDelegate(update.data))
|
||||
(customKey = await this._customStateKeyDelegate(parsedContext as any))
|
||||
) {
|
||||
parsedState = new UpdateState(
|
||||
this._storage,
|
||||
|
@ -386,8 +395,9 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
for (const h of handlers) {
|
||||
let result: void | PropagationAction
|
||||
|
||||
if (!h.check || (await h.check(update.data as any, parsedState as never))) {
|
||||
result = await h.callback(update.data as any, parsedState as never)
|
||||
if (!parsedContext) parsedContext = _parsedUpdateToContext(this._client, update)
|
||||
if (!h.check || (await h.check(parsedContext as any, parsedState as never))) {
|
||||
result = await h.callback(parsedContext as any, parsedState as never)
|
||||
handled = true
|
||||
} else continue
|
||||
|
||||
|
@ -436,7 +446,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
}
|
||||
}
|
||||
|
||||
this._postUpdateHandler?.(handled, update, parsedState as any)
|
||||
await this._postUpdateHandler?.(handled, update, parsedState as any)
|
||||
|
||||
return handled
|
||||
}
|
||||
|
@ -601,9 +611,9 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
if (child._client) {
|
||||
throw new MtArgumentError(
|
||||
'Provided dispatcher is ' +
|
||||
(child._parent
|
||||
? 'already a child. Use parent.removeChild() before calling addChild()'
|
||||
: 'already bound to a client. Use unbind() before calling addChild()'),
|
||||
(child._parent ?
|
||||
'already a child. Use parent.removeChild() before calling addChild()' :
|
||||
'already bound to a client. Use unbind() before calling addChild()'),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -953,21 +963,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onNewMessage(
|
||||
handler: NewMessageHandler<Message, State extends never ? never : UpdateState<State, SceneName>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
/**
|
||||
* Register a new message handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler New message handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onNewMessage<Mod>(
|
||||
filter: UpdateFilter<Message, Mod, State>,
|
||||
handler: NewMessageHandler<
|
||||
filters.Modify<Message, Mod>,
|
||||
MessageContext,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -981,9 +978,25 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onNewMessage<Mod>(
|
||||
filter: UpdateFilter<Message, Mod>,
|
||||
filter: UpdateFilter<MessageContext, Mod, State>,
|
||||
handler: NewMessageHandler<
|
||||
filters.Modify<Message, Mod>,
|
||||
filters.Modify<MessageContext, Mod>,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
/**
|
||||
* Register a new message handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler New message handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onNewMessage<Mod>(
|
||||
filter: UpdateFilter<MessageContext, Mod>,
|
||||
handler: NewMessageHandler<
|
||||
filters.Modify<MessageContext, Mod>,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1001,21 +1014,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onEditMessage(
|
||||
handler: EditMessageHandler<Message, State extends never ? never : UpdateState<State, SceneName>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
/**
|
||||
* Register an edit message handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Edit message handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onEditMessage<Mod>(
|
||||
filter: UpdateFilter<Message, Mod, State>,
|
||||
handler: EditMessageHandler<
|
||||
filters.Modify<Message, Mod>,
|
||||
MessageContext,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1029,9 +1029,25 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onEditMessage<Mod>(
|
||||
filter: UpdateFilter<Message, Mod>,
|
||||
filter: UpdateFilter<MessageContext, Mod, State>,
|
||||
handler: EditMessageHandler<
|
||||
filters.Modify<Message, Mod>,
|
||||
filters.Modify<MessageContext, Mod>,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
/**
|
||||
* Register an edit message handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Edit message handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onEditMessage<Mod>(
|
||||
filter: UpdateFilter<MessageContext, Mod>,
|
||||
handler: EditMessageHandler<
|
||||
filters.Modify<MessageContext, Mod>,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1050,7 +1066,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
*/
|
||||
onMessageGroup(
|
||||
handler: MessageGroupHandler<
|
||||
Message[],
|
||||
MessageContext,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1064,9 +1080,9 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onMessageGroup<Mod>(
|
||||
filter: UpdateFilter<Message[], Mod, State>,
|
||||
filter: UpdateFilter<MessageContext, Mod, State>,
|
||||
handler: MessageGroupHandler<
|
||||
filters.Modify<Message[], Mod>,
|
||||
filters.Modify<MessageContext, Mod>,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1080,9 +1096,9 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onMessageGroup<Mod>(
|
||||
filter: UpdateFilter<Message[], Mod>,
|
||||
filter: UpdateFilter<MessageContext, Mod>,
|
||||
handler: MessageGroupHandler<
|
||||
filters.Modify<Message[], Mod>,
|
||||
filters.Modify<MessageContext, Mod>,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1109,8 +1125,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onDeleteMessage<Mod>(
|
||||
filter: UpdateFilter<DeleteMessageUpdate, Mod>,
|
||||
handler: DeleteMessageHandler<filters.Modify<DeleteMessageUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<DeleteMessageUpdate>, Mod>,
|
||||
handler: DeleteMessageHandler<filters.Modify<UpdateContext<DeleteMessageUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1135,8 +1151,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onChatMemberUpdate<Mod>(
|
||||
filter: UpdateFilter<ChatMemberUpdate, Mod>,
|
||||
handler: ChatMemberUpdateHandler<filters.Modify<ChatMemberUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<ChatMemberUpdate>, Mod>,
|
||||
handler: ChatMemberUpdateHandler<filters.Modify<UpdateContext<ChatMemberUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1161,8 +1177,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onInlineQuery<Mod>(
|
||||
filter: UpdateFilter<InlineQuery, Mod>,
|
||||
handler: InlineQueryHandler<filters.Modify<InlineQuery, Mod>>['callback'],
|
||||
filter: UpdateFilter<InlineQueryContext, Mod>,
|
||||
handler: InlineQueryHandler<filters.Modify<InlineQueryContext, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1187,8 +1203,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onChosenInlineResult<Mod>(
|
||||
filter: UpdateFilter<ChosenInlineResult, Mod>,
|
||||
handler: ChosenInlineResultHandler<filters.Modify<ChosenInlineResult, Mod>>['callback'],
|
||||
filter: UpdateFilter<ChosenInlineResultContext, Mod>,
|
||||
handler: ChosenInlineResultHandler<filters.Modify<ChosenInlineResultContext, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1205,7 +1221,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
*/
|
||||
onCallbackQuery(
|
||||
handler: CallbackQueryHandler<
|
||||
CallbackQuery,
|
||||
CallbackQueryContext,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1219,9 +1235,9 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onCallbackQuery<Mod>(
|
||||
filter: UpdateFilter<CallbackQuery, Mod, State>,
|
||||
filter: UpdateFilter<CallbackQueryContext, Mod, State>,
|
||||
handler: CallbackQueryHandler<
|
||||
filters.Modify<CallbackQuery, Mod>,
|
||||
filters.Modify<CallbackQueryContext, Mod>,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1235,9 +1251,9 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onCallbackQuery<Mod>(
|
||||
filter: UpdateFilter<CallbackQuery, Mod>,
|
||||
filter: UpdateFilter<CallbackQueryContext, Mod>,
|
||||
handler: CallbackQueryHandler<
|
||||
filters.Modify<CallbackQuery, Mod>,
|
||||
filters.Modify<CallbackQueryContext, Mod>,
|
||||
State extends never ? never : UpdateState<State, SceneName>
|
||||
>['callback'],
|
||||
group?: number,
|
||||
|
@ -1264,8 +1280,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onPollUpdate<Mod>(
|
||||
filter: UpdateFilter<PollUpdate, Mod>,
|
||||
handler: PollUpdateHandler<filters.Modify<PollUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<PollUpdate>, Mod>,
|
||||
handler: PollUpdateHandler<filters.Modify<UpdateContext<PollUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1290,8 +1306,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onPollVote<Mod>(
|
||||
filter: UpdateFilter<PollVoteUpdate, Mod>,
|
||||
handler: PollVoteHandler<filters.Modify<PollVoteUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<PollVoteUpdate>, Mod>,
|
||||
handler: PollVoteHandler<filters.Modify<UpdateContext<PollVoteUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1316,8 +1332,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onUserStatusUpdate<Mod>(
|
||||
filter: UpdateFilter<UserStatusUpdate, Mod>,
|
||||
handler: UserStatusUpdateHandler<filters.Modify<UserStatusUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<UserStatusUpdate>, Mod>,
|
||||
handler: UserStatusUpdateHandler<filters.Modify<UpdateContext<UserStatusUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1342,8 +1358,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onUserTyping<Mod>(
|
||||
filter: UpdateFilter<UserTypingUpdate, Mod>,
|
||||
handler: UserTypingHandler<filters.Modify<UserTypingUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<UserTypingUpdate>, Mod>,
|
||||
handler: UserTypingHandler<filters.Modify<UpdateContext<UserTypingUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1368,8 +1384,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onHistoryRead<Mod>(
|
||||
filter: UpdateFilter<HistoryReadUpdate, Mod>,
|
||||
handler: HistoryReadHandler<filters.Modify<HistoryReadUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<HistoryReadUpdate>, Mod>,
|
||||
handler: HistoryReadHandler<filters.Modify<UpdateContext<HistoryReadUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1394,8 +1410,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onBotStopped<Mod>(
|
||||
filter: UpdateFilter<BotStoppedUpdate, Mod>,
|
||||
handler: BotStoppedHandler<filters.Modify<BotStoppedUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<BotStoppedUpdate>, Mod>,
|
||||
handler: BotStoppedHandler<filters.Modify<UpdateContext<BotStoppedUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1420,8 +1436,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onBotChatJoinRequest<Mod>(
|
||||
filter: UpdateFilter<BotChatJoinRequestUpdate, Mod>,
|
||||
handler: BotChatJoinRequestHandler<filters.Modify<BotChatJoinRequestUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<ChatJoinRequestUpdateContext, Mod>,
|
||||
handler: BotChatJoinRequestHandler<filters.Modify<ChatJoinRequestUpdateContext, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1446,8 +1462,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onChatJoinRequest<Mod>(
|
||||
filter: UpdateFilter<ChatJoinRequestUpdate, Mod>,
|
||||
handler: ChatJoinRequestHandler<filters.Modify<ChatJoinRequestUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<ChatJoinRequestUpdate>, Mod>,
|
||||
handler: ChatJoinRequestHandler<filters.Modify<UpdateContext<ChatJoinRequestUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1472,8 +1488,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onPreCheckoutQuery<Mod>(
|
||||
filter: UpdateFilter<PreCheckoutQuery, Mod>,
|
||||
handler: PreCheckoutQueryHandler<filters.Modify<PreCheckoutQuery, Mod>>['callback'],
|
||||
filter: UpdateFilter<PreCheckoutQueryContext, Mod>,
|
||||
handler: PreCheckoutQueryHandler<filters.Modify<PreCheckoutQueryContext, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1498,8 +1514,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onStoryUpdate<Mod>(
|
||||
filter: UpdateFilter<StoryUpdate, Mod>,
|
||||
handler: StoryUpdateHandler<filters.Modify<StoryUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<StoryUpdate>, Mod>,
|
||||
handler: StoryUpdateHandler<filters.Modify<UpdateContext<StoryUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
@ -1524,8 +1540,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
* @param group Handler group index
|
||||
*/
|
||||
onDeleteStory<Mod>(
|
||||
filter: UpdateFilter<DeleteStoryUpdate, Mod>,
|
||||
handler: DeleteStoryHandler<filters.Modify<DeleteStoryUpdate, Mod>>['callback'],
|
||||
filter: UpdateFilter<UpdateContext<DeleteStoryUpdate>, Mod>,
|
||||
handler: DeleteStoryHandler<filters.Modify<UpdateContext<DeleteStoryUpdate>, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Message } from '@mtcute/client'
|
||||
import { MaybeArray, MaybeAsync } from '@mtcute/core'
|
||||
|
||||
import { MessageContext } from '../context'
|
||||
import { chat } from './chat'
|
||||
import { and } from './logic'
|
||||
import { UpdateFilter } from './types'
|
||||
|
@ -25,7 +26,7 @@ export const command = (
|
|||
commands: MaybeArray<string | RegExp>,
|
||||
prefixes: MaybeArray<string> | null = '/',
|
||||
caseSensitive = false,
|
||||
): UpdateFilter<Message, { command: string[] }> => {
|
||||
): UpdateFilter<MessageContext, { command: string[] }> => {
|
||||
if (!Array.isArray(commands)) commands = [commands]
|
||||
|
||||
commands = commands.map((i) => (typeof i === 'string' ? i.toLowerCase() : i))
|
||||
|
@ -44,7 +45,9 @@ export const command = (
|
|||
|
||||
const _prefixes = prefixes
|
||||
|
||||
const check = (msg: Message): MaybeAsync<boolean> => {
|
||||
const check = (msg: MessageContext): MaybeAsync<boolean> => {
|
||||
if (msg.isMessageGroup) return check(msg.messages[0])
|
||||
|
||||
for (const pref of _prefixes) {
|
||||
if (!msg.text.startsWith(pref)) continue
|
||||
|
||||
|
@ -54,17 +57,15 @@ export const command = (
|
|||
const m = withoutPrefix.match(regex)
|
||||
if (!m) continue
|
||||
|
||||
// const lastGroup = m[m.length - 1]
|
||||
const lastGroup = m[m.length - 1]
|
||||
|
||||
// eslint-disable-next-line dot-notation
|
||||
// todo
|
||||
// if (lastGroup && msg.client['_isBot']) {
|
||||
// // check bot username
|
||||
// // eslint-disable-next-line dot-notation
|
||||
// if (lastGroup !== msg.client['_selfUsername']) {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
if (lastGroup) {
|
||||
const state = msg.client.getAuthState()
|
||||
|
||||
if (state.isBot && lastGroup !== state.selfUsername) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const match = m.slice(1, -1)
|
||||
|
||||
|
@ -74,7 +75,7 @@ export const command = (
|
|||
|
||||
return ''
|
||||
})
|
||||
;(msg as Message & { command: string[] }).command = match
|
||||
;(msg as MessageContext & { command: string[] }).command = match
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -98,7 +99,7 @@ export const start = and(chat('private'), command('start'))
|
|||
* If the parameter is a regex, groups are added to `msg.command`,
|
||||
* meaning that the first group is available in `msg.command[2]`.
|
||||
*/
|
||||
export const deeplink = (params: MaybeArray<string | RegExp>): UpdateFilter<Message, { command: string[] }> => {
|
||||
export const deeplink = (params: MaybeArray<string | RegExp>): UpdateFilter<MessageContext, { command: string[] }> => {
|
||||
if (!Array.isArray(params)) {
|
||||
return and(start, (_msg: Message) => {
|
||||
const msg = _msg as Message & { command: string[] }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export * from './bots'
|
||||
export * from './chat'
|
||||
export * from './group'
|
||||
export * from './logic'
|
||||
export * from './message'
|
||||
export * from './state'
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
import { Chat, ChatType, Message, PollVoteUpdate, User } from '@mtcute/client'
|
||||
import {
|
||||
BotChatJoinRequestUpdate,
|
||||
Chat,
|
||||
ChatMemberUpdate,
|
||||
ChatType,
|
||||
HistoryReadUpdate,
|
||||
Message,
|
||||
PollVoteUpdate,
|
||||
User,
|
||||
UserTypingUpdate,
|
||||
} from '@mtcute/client'
|
||||
import { MaybeArray } from '@mtcute/core'
|
||||
|
||||
import { UpdateContextDistributed } from '../context'
|
||||
import { Modify, UpdateFilter } from './types'
|
||||
|
||||
/**
|
||||
|
@ -19,59 +30,68 @@ export const chat =
|
|||
(msg) =>
|
||||
msg.chat.chatType === type
|
||||
|
||||
// prettier-ignore
|
||||
/**
|
||||
* Filter updates by chat ID(s) or username(s)
|
||||
* Filter updates by marked chat ID(s) or username(s)
|
||||
*
|
||||
* Note that only some updates support filtering by username.
|
||||
*
|
||||
* For messages, this filter checks for chat where the message
|
||||
* was sent to, NOT the chat sender.
|
||||
*/
|
||||
export const chatId = (id: MaybeArray<number | string>): UpdateFilter<Message | PollVoteUpdate> => {
|
||||
if (Array.isArray(id)) {
|
||||
const index: Record<number | string, true> = {}
|
||||
export const chatId: {
|
||||
(id: MaybeArray<number>): UpdateFilter<UpdateContextDistributed<
|
||||
| Message
|
||||
| ChatMemberUpdate
|
||||
| PollVoteUpdate
|
||||
| BotChatJoinRequestUpdate
|
||||
>>
|
||||
(id: MaybeArray<number | string>): UpdateFilter<UpdateContextDistributed<
|
||||
| Message
|
||||
| ChatMemberUpdate
|
||||
| UserTypingUpdate
|
||||
| HistoryReadUpdate
|
||||
| PollVoteUpdate
|
||||
| BotChatJoinRequestUpdate
|
||||
>>
|
||||
} = (id) => {
|
||||
const indexId = new Set<number>()
|
||||
const indexUsername = new Set<string>()
|
||||
let matchSelf = false
|
||||
|
||||
if (!Array.isArray(id)) id = [id]
|
||||
id.forEach((id) => {
|
||||
if (id === 'me' || id === 'self') {
|
||||
matchSelf = true
|
||||
} else if (typeof id === 'number') {
|
||||
indexId.add(id)
|
||||
} else {
|
||||
index[id] = true
|
||||
indexUsername.add(id)
|
||||
}
|
||||
})
|
||||
|
||||
return (upd) => {
|
||||
if (upd.constructor === PollVoteUpdate) {
|
||||
switch (upd._name) {
|
||||
case 'poll_vote': {
|
||||
const peer = upd.peer
|
||||
|
||||
return peer.type === 'chat' && peer.id in index
|
||||
return peer.type === 'chat' && (
|
||||
indexId.has(peer.id) ||
|
||||
Boolean(peer.usernames?.some((u) => indexUsername.has(u.username)))
|
||||
)
|
||||
}
|
||||
case 'history_read':
|
||||
case 'user_typing': {
|
||||
const id = upd.chatId
|
||||
|
||||
const chat = (upd as Exclude<typeof upd, PollVoteUpdate>).chat
|
||||
|
||||
return (matchSelf && chat.isSelf) || chat.id in index || chat.username! in index
|
||||
return (matchSelf && id === upd.client.getAuthState().userId) || indexId.has(id)
|
||||
}
|
||||
}
|
||||
|
||||
if (id === 'me' || id === 'self') {
|
||||
return (upd) => {
|
||||
if (upd.constructor === PollVoteUpdate) {
|
||||
return upd.peer.type === 'chat' && upd.peer.isSelf
|
||||
}
|
||||
const chat = upd.chat
|
||||
|
||||
return (upd as Exclude<typeof upd, PollVoteUpdate>).chat.isSelf
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof id === 'string') {
|
||||
return (upd) => {
|
||||
if (upd.constructor === PollVoteUpdate) {
|
||||
return upd.peer.type === 'chat' && upd.peer.username === id
|
||||
}
|
||||
|
||||
return (upd as Exclude<typeof upd, PollVoteUpdate>).chat.username === id
|
||||
}
|
||||
}
|
||||
|
||||
return (upd) => {
|
||||
if (upd.constructor === PollVoteUpdate) {
|
||||
return upd.peer.type === 'chat' && upd.peer.id === id
|
||||
}
|
||||
|
||||
return (upd as Exclude<typeof upd, PollVoteUpdate>).chat.id === id
|
||||
return (matchSelf && chat.isSelf) ||
|
||||
indexId.has(chat.id) ||
|
||||
Boolean(chat.usernames?.some((u) => indexUsername.has(u.username)))
|
||||
}
|
||||
}
|
||||
|
|
88
packages/dispatcher/src/filters/group.ts
Normal file
88
packages/dispatcher/src/filters/group.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { Message } from '@mtcute/client'
|
||||
import { MaybeAsync } from '@mtcute/core'
|
||||
|
||||
import { MessageContext } from '../context'
|
||||
import { Modify, UpdateFilter } from './types'
|
||||
|
||||
/**
|
||||
* For message groups, apply a filter to every message in the group.
|
||||
* Filter will match if **all** messages match.
|
||||
*
|
||||
* > **Note**: This also applies type modification to every message in the group
|
||||
*
|
||||
* @param filter
|
||||
* @returns
|
||||
*/
|
||||
export function every<Mod, State>(
|
||||
filter: UpdateFilter<Message, Mod, State>,
|
||||
): UpdateFilter<
|
||||
MessageContext,
|
||||
Mod & {
|
||||
messages: Modify<MessageContext, Mod>[]
|
||||
},
|
||||
State
|
||||
> {
|
||||
return (ctx, state) => {
|
||||
let i = 0
|
||||
const upds = ctx.messages
|
||||
const max = upds.length
|
||||
|
||||
const next = (): MaybeAsync<boolean> => {
|
||||
if (i === max) return true
|
||||
|
||||
const res = filter(upds[i++], state)
|
||||
|
||||
if (typeof res === 'boolean') {
|
||||
if (!res) return false
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
return res.then((r: boolean) => {
|
||||
if (!r) return false
|
||||
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For message groups, apply a filter to every message in the group.
|
||||
* Filter will match if **any** message matches.
|
||||
*
|
||||
* > **Note**: This *does not* apply type modification to any message
|
||||
*
|
||||
* @param filter
|
||||
* @returns
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
export function some<State>(filter: UpdateFilter<Message, any, State>): UpdateFilter<MessageContext, {}, State> {
|
||||
return (ctx, state) => {
|
||||
let i = 0
|
||||
const upds = ctx.messages
|
||||
const max = upds.length
|
||||
|
||||
const next = (): MaybeAsync<boolean> => {
|
||||
if (i === max) return false
|
||||
|
||||
const res = filter(upds[i++], state)
|
||||
|
||||
if (typeof res === 'boolean') {
|
||||
if (res) return true
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
return res.then((r: boolean) => {
|
||||
if (r) return true
|
||||
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
|
@ -256,8 +256,6 @@ export function or<
|
|||
* @param fns Filters to combine
|
||||
*/
|
||||
export function or(...fns: UpdateFilter<any, any, any>[]): UpdateFilter<any, any, any> {
|
||||
if (fns.length === 2) return or(fns[0], fns[1])
|
||||
|
||||
return (upd, state) => {
|
||||
let i = 0
|
||||
const max = fns.length
|
||||
|
@ -283,79 +281,3 @@ export function or(...fns: UpdateFilter<any, any, any>[]): UpdateFilter<any, any
|
|||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For updates that contain an array of updates (e.g. `message_group`),
|
||||
* apply a filter to every element of the array.
|
||||
*
|
||||
* Filter will match if **all** elements match.
|
||||
*
|
||||
* > **Note**: This also applies type modification to every element of the array.
|
||||
*
|
||||
* @param filter
|
||||
* @returns
|
||||
*/
|
||||
export function every<Base, Mod, State>(filter: UpdateFilter<Base, Mod, State>): UpdateFilter<Base[], Mod, State> {
|
||||
return (upds, state) => {
|
||||
let i = 0
|
||||
const max = upds.length
|
||||
|
||||
const next = (): MaybeAsync<boolean> => {
|
||||
if (i === max) return true
|
||||
|
||||
const res = filter(upds[i++], state)
|
||||
|
||||
if (typeof res === 'boolean') {
|
||||
if (!res) return false
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
return res.then((r: boolean) => {
|
||||
if (!r) return false
|
||||
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For updates that contain an array of updates (e.g. `message_group`),
|
||||
* apply a filter to every element of the array.
|
||||
*
|
||||
* Filter will match if **all** elements match.
|
||||
*
|
||||
* > **Note**: This *does not* apply type modification to any element of the array
|
||||
*
|
||||
* @param filter
|
||||
* @returns
|
||||
*/
|
||||
export function some<Base, Mod, State>(filter: UpdateFilter<Base, Mod, State>): UpdateFilter<Base[], Mod, State> {
|
||||
return (upds, state) => {
|
||||
let i = 0
|
||||
const max = upds.length
|
||||
|
||||
const next = (): MaybeAsync<boolean> => {
|
||||
if (i === max) return false
|
||||
|
||||
const res = filter(upds[i++], state)
|
||||
|
||||
if (typeof res === 'boolean') {
|
||||
if (res) return true
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
return res.then((r: boolean) => {
|
||||
if (r) return true
|
||||
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,3 +201,10 @@ export const action = <T extends Exclude<MessageAction, null>['type']>(
|
|||
|
||||
return (msg) => msg.action?.type === type
|
||||
}
|
||||
|
||||
export const sender =
|
||||
<T extends Message['sender']['type']>(
|
||||
type: T,
|
||||
): UpdateFilter<Message, { sender: Extract<Message['sender'], { type: T }> }> =>
|
||||
(msg) =>
|
||||
msg.sender.type === type
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CallbackQuery, Message } from '@mtcute/client'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { MaybeAsync } from '@mtcute/core'
|
||||
|
||||
import { UpdateFilter } from './types'
|
||||
|
@ -6,7 +6,7 @@ import { UpdateFilter } from './types'
|
|||
/**
|
||||
* Create a filter for the cases when the state is empty
|
||||
*/
|
||||
export const stateEmpty: UpdateFilter<Message> = async (upd, state) => {
|
||||
export const stateEmpty: UpdateFilter<any> = async (upd, state) => {
|
||||
if (!state) return false
|
||||
|
||||
return !(await state.get())
|
||||
|
@ -23,7 +23,7 @@ export const stateEmpty: UpdateFilter<Message> = async (upd, state) => {
|
|||
export const state = <T>(
|
||||
predicate: (state: T) => MaybeAsync<boolean>,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
): UpdateFilter<Message | Message[] | CallbackQuery, {}, T> => {
|
||||
): UpdateFilter<any, {}, T> => {
|
||||
return async (upd, state) => {
|
||||
if (!state) return false
|
||||
const data = await state.get()
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
// ^^ will be looked into in MTQ-29
|
||||
|
||||
import { CallbackQuery, ChosenInlineResult, InlineQuery, Message } from '@mtcute/client'
|
||||
|
||||
import { UpdateContextDistributed } from '../context'
|
||||
import { UpdateFilter } from './types'
|
||||
|
||||
function extractText(obj: Message | InlineQuery | ChosenInlineResult | CallbackQuery): string | null {
|
||||
if (obj.constructor === Message) {
|
||||
type UpdatesWithText = UpdateContextDistributed<Message | InlineQuery | ChosenInlineResult | CallbackQuery>
|
||||
|
||||
function extractText(obj: UpdatesWithText): string | null {
|
||||
switch (obj._name) {
|
||||
case 'new_message':
|
||||
return obj.text
|
||||
} else if (obj.constructor === InlineQuery) {
|
||||
case 'inline_query':
|
||||
return obj.query
|
||||
} else if (obj.constructor === ChosenInlineResult) {
|
||||
case 'chosen_inline_result':
|
||||
return obj.id
|
||||
} else if (obj.constructor === CallbackQuery) {
|
||||
case 'callback_query':
|
||||
if (obj.raw.data) return obj.dataStr
|
||||
}
|
||||
|
||||
|
@ -31,9 +33,7 @@ function extractText(obj: Message | InlineQuery | ChosenInlineResult | CallbackQ
|
|||
* @param regex Regex to be matched
|
||||
*/
|
||||
export const regex =
|
||||
(
|
||||
regex: RegExp,
|
||||
): UpdateFilter<Message | InlineQuery | ChosenInlineResult | CallbackQuery, { match: RegExpMatchArray }> =>
|
||||
(regex: RegExp): UpdateFilter<UpdatesWithText, { match: RegExpMatchArray }> =>
|
||||
(obj) => {
|
||||
const txt = extractText(obj)
|
||||
if (!txt) return false
|
||||
|
@ -59,10 +59,7 @@ export const regex =
|
|||
* @param str String to be matched
|
||||
* @param ignoreCase Whether string case should be ignored
|
||||
*/
|
||||
export const equals = (
|
||||
str: string,
|
||||
ignoreCase = false,
|
||||
): UpdateFilter<Message | InlineQuery | ChosenInlineResult | CallbackQuery> => {
|
||||
export const equals = (str: string, ignoreCase = false): UpdateFilter<UpdatesWithText> => {
|
||||
if (ignoreCase) {
|
||||
str = str.toLowerCase()
|
||||
|
||||
|
@ -82,10 +79,7 @@ export const equals = (
|
|||
* @param str Substring to be matched
|
||||
* @param ignoreCase Whether string case should be ignored
|
||||
*/
|
||||
export const contains = (
|
||||
str: string,
|
||||
ignoreCase = false,
|
||||
): UpdateFilter<Message | InlineQuery | ChosenInlineResult | CallbackQuery> => {
|
||||
export const contains = (str: string, ignoreCase = false): UpdateFilter<UpdatesWithText> => {
|
||||
if (ignoreCase) {
|
||||
str = str.toLowerCase()
|
||||
|
||||
|
@ -113,10 +107,7 @@ export const contains = (
|
|||
* @param str Substring to be matched
|
||||
* @param ignoreCase Whether string case should be ignored
|
||||
*/
|
||||
export const startsWith = (
|
||||
str: string,
|
||||
ignoreCase = false,
|
||||
): UpdateFilter<Message | InlineQuery | ChosenInlineResult | CallbackQuery> => {
|
||||
export const startsWith = (str: string, ignoreCase = false): UpdateFilter<UpdatesWithText> => {
|
||||
if (ignoreCase) {
|
||||
str = str.toLowerCase()
|
||||
|
||||
|
@ -144,10 +135,7 @@ export const startsWith = (
|
|||
* @param str Substring to be matched
|
||||
* @param ignoreCase Whether string case should be ignored
|
||||
*/
|
||||
export const endsWith = (
|
||||
str: string,
|
||||
ignoreCase = false,
|
||||
): UpdateFilter<Message | InlineQuery | ChosenInlineResult | CallbackQuery> => {
|
||||
export const endsWith = (str: string, ignoreCase = false): UpdateFilter<UpdatesWithText> => {
|
||||
if (ignoreCase) {
|
||||
str = str.toLowerCase()
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// ^^ will be looked into in MTQ-29
|
||||
|
||||
|
@ -73,13 +74,13 @@ import { UpdateState } from '../state'
|
|||
* > like `and`, `or`, etc. Those are meant to be inferred by the compiler!
|
||||
*/
|
||||
// we need the second parameter because it carries meta information
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/ban-types
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export type UpdateFilter<Base, Mod = {}, State = never> = (
|
||||
update: Base,
|
||||
state?: UpdateState<State>,
|
||||
) => MaybeAsync<boolean>
|
||||
|
||||
export type Modify<Base, Mod> = Base extends (infer T)[] ? Modify<T, Mod>[] : Omit<Base, keyof Mod> & Mod
|
||||
export type Modify<Base, Mod> = Omit<Base, keyof Mod> & Mod
|
||||
export type Invert<Base, Mod> = {
|
||||
[P in keyof Mod & keyof Base]: Exclude<Base[P], Mod[P]>
|
||||
}
|
||||
|
|
|
@ -3,15 +3,19 @@ import {
|
|||
CallbackQuery,
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
DeleteStoryUpdate,
|
||||
HistoryReadUpdate,
|
||||
InlineQuery,
|
||||
Message,
|
||||
PollVoteUpdate,
|
||||
StoryUpdate,
|
||||
User,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
} from '@mtcute/client'
|
||||
import { MaybeArray } from '@mtcute/core'
|
||||
|
||||
import { UpdateContextDistributed } from '../context'
|
||||
import { UpdateFilter } from './types'
|
||||
|
||||
/**
|
||||
|
@ -25,130 +29,95 @@ export const me: UpdateFilter<Message, { sender: User }> = (msg) =>
|
|||
*/
|
||||
export const bot: UpdateFilter<Message, { sender: User }> = (msg) => msg.sender.constructor === User && msg.sender.isBot
|
||||
|
||||
// prettier-ignore
|
||||
/**
|
||||
* Filter updates by user ID(s) or username(s)
|
||||
*
|
||||
* Usernames are not supported for UserStatusUpdate
|
||||
* and UserTypingUpdate.
|
||||
*
|
||||
*
|
||||
* For chat member updates, uses `user.id`
|
||||
* Note that only some updates support filtering by username.
|
||||
*/
|
||||
export const userId = (
|
||||
id: MaybeArray<number | string>,
|
||||
): UpdateFilter<
|
||||
export const userId: {
|
||||
(id: MaybeArray<number>): UpdateFilter<UpdateContextDistributed<
|
||||
| Message
|
||||
| UserStatusUpdate
|
||||
| UserTypingUpdate
|
||||
| StoryUpdate
|
||||
| DeleteStoryUpdate
|
||||
| InlineQuery
|
||||
| ChatMemberUpdate
|
||||
| ChosenInlineResult
|
||||
| CallbackQuery
|
||||
| PollVoteUpdate
|
||||
| BotChatJoinRequestUpdate
|
||||
> => {
|
||||
if (Array.isArray(id)) {
|
||||
const index: Record<number | string, true> = {}
|
||||
>>
|
||||
(id: MaybeArray<number | string>): UpdateFilter<UpdateContextDistributed<
|
||||
| Message
|
||||
| UserStatusUpdate
|
||||
| UserTypingUpdate
|
||||
| StoryUpdate
|
||||
| HistoryReadUpdate
|
||||
| DeleteStoryUpdate
|
||||
| InlineQuery
|
||||
| ChatMemberUpdate
|
||||
| ChosenInlineResult
|
||||
| CallbackQuery
|
||||
| PollVoteUpdate
|
||||
| BotChatJoinRequestUpdate
|
||||
>>
|
||||
} = (id) => {
|
||||
const indexId = new Set<number>()
|
||||
const indexUsername = new Set<string>()
|
||||
let matchSelf = false
|
||||
|
||||
if (!Array.isArray(id)) id = [id]
|
||||
id.forEach((id) => {
|
||||
if (id === 'me' || id === 'self') {
|
||||
matchSelf = true
|
||||
} else if (typeof id === 'string') {
|
||||
indexUsername.add(id)
|
||||
} else {
|
||||
index[id] = true
|
||||
indexId.add(id)
|
||||
}
|
||||
})
|
||||
|
||||
return (upd) => {
|
||||
const ctor = upd.constructor
|
||||
switch (upd._name) {
|
||||
case 'new_message':
|
||||
case 'edit_message': {
|
||||
const sender = upd.sender
|
||||
|
||||
if (ctor === Message) {
|
||||
const sender = (upd as Message).sender
|
||||
return (matchSelf && sender.isSelf) ||
|
||||
indexId.has(sender.id) ||
|
||||
indexUsername.has(sender.username!)
|
||||
}
|
||||
case 'user_status':
|
||||
case 'user_typing': {
|
||||
const id = upd.userId
|
||||
|
||||
return (matchSelf && sender.isSelf) || sender.id in index || sender.username! in index
|
||||
} else if (ctor === UserStatusUpdate || ctor === UserTypingUpdate) {
|
||||
// const id = (upd as UserStatusUpdate | UserTypingUpdate).userId
|
||||
|
||||
return false
|
||||
// todo
|
||||
// eslint-disable-next-line dot-notation
|
||||
// (matchSelf && id === upd.client['_userId']) || id in index
|
||||
} else if (ctor === PollVoteUpdate) {
|
||||
const peer = (upd as PollVoteUpdate).peer
|
||||
return (matchSelf && id === upd.client.getAuthState().userId) ||
|
||||
indexId.has(id)
|
||||
}
|
||||
case 'poll_vote':
|
||||
case 'story':
|
||||
case 'delete_story': {
|
||||
const peer = upd.peer
|
||||
if (peer.type !== 'user') return false
|
||||
|
||||
return (matchSelf && peer.isSelf) || peer.id in index || peer.username! in index
|
||||
return (matchSelf && peer.isSelf) ||
|
||||
indexId.has(peer.id) ||
|
||||
Boolean(peer.usernames?.some((u) => indexUsername.has(u.username)))
|
||||
}
|
||||
case 'history_read': {
|
||||
const id = upd.chatId
|
||||
|
||||
const user = (upd as Exclude<typeof upd, Message | UserStatusUpdate | UserTypingUpdate | PollVoteUpdate>)
|
||||
.user
|
||||
|
||||
return (matchSelf && user.isSelf) || user.id in index || user.username! in index
|
||||
return (matchSelf && id === upd.client.getAuthState().userId) ||
|
||||
indexId.has(id)
|
||||
}
|
||||
}
|
||||
|
||||
if (id === 'me' || id === 'self') {
|
||||
return (upd) => {
|
||||
const ctor = upd.constructor
|
||||
|
||||
if (ctor === Message) {
|
||||
return (upd as Message).sender.isSelf
|
||||
} else if (ctor === UserStatusUpdate || ctor === UserTypingUpdate) {
|
||||
return false
|
||||
// todo
|
||||
// (upd as UserStatusUpdate | UserTypingUpdate).userId ===
|
||||
// eslint-disable-next-line dot-notation
|
||||
// upd.client['_userId']
|
||||
} else if (ctor === PollVoteUpdate) {
|
||||
const peer = (upd as PollVoteUpdate).peer
|
||||
if (peer.type !== 'user') return false
|
||||
|
||||
return peer.isSelf
|
||||
}
|
||||
|
||||
return (upd as Exclude<typeof upd, Message | UserStatusUpdate | UserTypingUpdate | PollVoteUpdate>).user
|
||||
.isSelf
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof id === 'string') {
|
||||
return (upd) => {
|
||||
const ctor = upd.constructor
|
||||
|
||||
if (ctor === Message) {
|
||||
return (upd as Message).sender.username === id
|
||||
} else if (ctor === UserStatusUpdate || ctor === UserTypingUpdate) {
|
||||
// username is not available
|
||||
return false
|
||||
} else if (ctor === PollVoteUpdate) {
|
||||
const peer = (upd as PollVoteUpdate).peer
|
||||
if (peer.type !== 'user') return false
|
||||
|
||||
return peer.username === id
|
||||
}
|
||||
const user = upd.user
|
||||
|
||||
return (
|
||||
(upd as Exclude<typeof upd, Message | UserStatusUpdate | UserTypingUpdate | PollVoteUpdate>).user
|
||||
.username === id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (upd) => {
|
||||
const ctor = upd.constructor
|
||||
|
||||
if (ctor === Message) {
|
||||
return (upd as Message).sender.id === id
|
||||
} else if (ctor === UserStatusUpdate || ctor === UserTypingUpdate) {
|
||||
return (upd as UserStatusUpdate | UserTypingUpdate).userId === id
|
||||
} else if (ctor === PollVoteUpdate) {
|
||||
const peer = (upd as PollVoteUpdate).peer
|
||||
if (peer.type !== 'user') return false
|
||||
|
||||
return peer.id === id
|
||||
}
|
||||
|
||||
return (
|
||||
(upd as Exclude<typeof upd, Message | UserStatusUpdate | UserTypingUpdate | PollVoteUpdate>).user.id === id
|
||||
(matchSelf && user.isSelf) ||
|
||||
indexId.has(user.id) ||
|
||||
Boolean(user.usernames?.some((u) => indexUsername.has(u.username)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
import {
|
||||
BotChatJoinRequestUpdate,
|
||||
BotStoppedUpdate,
|
||||
CallbackQuery,
|
||||
ChatJoinRequestUpdate,
|
||||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
DeleteMessageUpdate,
|
||||
DeleteStoryUpdate,
|
||||
HistoryReadUpdate,
|
||||
InlineQuery,
|
||||
MaybeAsync,
|
||||
Message,
|
||||
PeersIndex,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
PreCheckoutQuery,
|
||||
StoryUpdate,
|
||||
TelegramClient,
|
||||
tl,
|
||||
|
@ -22,6 +16,15 @@ import {
|
|||
UserTypingUpdate,
|
||||
} from '@mtcute/client'
|
||||
|
||||
import {
|
||||
CallbackQueryContext,
|
||||
ChatJoinRequestUpdateContext,
|
||||
ChosenInlineResultContext,
|
||||
InlineQueryContext,
|
||||
MessageContext,
|
||||
PreCheckoutQueryContext,
|
||||
} from './context'
|
||||
import { UpdateContext } from './context/base'
|
||||
import { PropagationAction } from './propagation'
|
||||
|
||||
export interface BaseUpdateHandler<Name, Handler, Checker> {
|
||||
|
@ -48,25 +51,31 @@ export type RawUpdateHandler = BaseUpdateHandler<
|
|||
>
|
||||
|
||||
// begin-codegen
|
||||
export type NewMessageHandler<T = Message, S = never> = ParsedUpdateHandler<'new_message', T, S>
|
||||
export type EditMessageHandler<T = Message, S = never> = ParsedUpdateHandler<'edit_message', T, S>
|
||||
export type MessageGroupHandler<T = Message[], S = never> = ParsedUpdateHandler<'message_group', T, S>
|
||||
export type DeleteMessageHandler<T = DeleteMessageUpdate> = ParsedUpdateHandler<'delete_message', T>
|
||||
export type ChatMemberUpdateHandler<T = ChatMemberUpdate> = ParsedUpdateHandler<'chat_member', T>
|
||||
export type InlineQueryHandler<T = InlineQuery> = ParsedUpdateHandler<'inline_query', T>
|
||||
export type ChosenInlineResultHandler<T = ChosenInlineResult> = ParsedUpdateHandler<'chosen_inline_result', T>
|
||||
export type CallbackQueryHandler<T = CallbackQuery, S = never> = ParsedUpdateHandler<'callback_query', T, S>
|
||||
export type PollUpdateHandler<T = PollUpdate> = ParsedUpdateHandler<'poll', T>
|
||||
export type PollVoteHandler<T = PollVoteUpdate> = ParsedUpdateHandler<'poll_vote', T>
|
||||
export type UserStatusUpdateHandler<T = UserStatusUpdate> = ParsedUpdateHandler<'user_status', T>
|
||||
export type UserTypingHandler<T = UserTypingUpdate> = ParsedUpdateHandler<'user_typing', T>
|
||||
export type HistoryReadHandler<T = HistoryReadUpdate> = ParsedUpdateHandler<'history_read', T>
|
||||
export type BotStoppedHandler<T = BotStoppedUpdate> = ParsedUpdateHandler<'bot_stopped', T>
|
||||
export type BotChatJoinRequestHandler<T = BotChatJoinRequestUpdate> = ParsedUpdateHandler<'bot_chat_join_request', T>
|
||||
export type ChatJoinRequestHandler<T = ChatJoinRequestUpdate> = ParsedUpdateHandler<'chat_join_request', T>
|
||||
export type PreCheckoutQueryHandler<T = PreCheckoutQuery> = ParsedUpdateHandler<'pre_checkout_query', T>
|
||||
export type StoryUpdateHandler<T = StoryUpdate> = ParsedUpdateHandler<'story', T>
|
||||
export type DeleteStoryHandler<T = DeleteStoryUpdate> = ParsedUpdateHandler<'delete_story', T>
|
||||
export type NewMessageHandler<T = MessageContext, S = never> = ParsedUpdateHandler<'new_message', T, S>
|
||||
export type EditMessageHandler<T = MessageContext, S = never> = ParsedUpdateHandler<'edit_message', T, S>
|
||||
export type MessageGroupHandler<T = MessageContext, S = never> = ParsedUpdateHandler<'message_group', T, S>
|
||||
export type DeleteMessageHandler<T = UpdateContext<DeleteMessageUpdate>> = ParsedUpdateHandler<'delete_message', T>
|
||||
export type ChatMemberUpdateHandler<T = UpdateContext<ChatMemberUpdate>> = ParsedUpdateHandler<'chat_member', T>
|
||||
export type InlineQueryHandler<T = InlineQueryContext> = ParsedUpdateHandler<'inline_query', T>
|
||||
export type ChosenInlineResultHandler<T = ChosenInlineResultContext> = ParsedUpdateHandler<'chosen_inline_result', T>
|
||||
export type CallbackQueryHandler<T = CallbackQueryContext, S = never> = ParsedUpdateHandler<'callback_query', T, S>
|
||||
export type PollUpdateHandler<T = UpdateContext<PollUpdate>> = ParsedUpdateHandler<'poll', T>
|
||||
export type PollVoteHandler<T = UpdateContext<PollVoteUpdate>> = ParsedUpdateHandler<'poll_vote', T>
|
||||
export type UserStatusUpdateHandler<T = UpdateContext<UserStatusUpdate>> = ParsedUpdateHandler<'user_status', T>
|
||||
export type UserTypingHandler<T = UpdateContext<UserTypingUpdate>> = ParsedUpdateHandler<'user_typing', T>
|
||||
export type HistoryReadHandler<T = UpdateContext<HistoryReadUpdate>> = ParsedUpdateHandler<'history_read', T>
|
||||
export type BotStoppedHandler<T = UpdateContext<BotStoppedUpdate>> = ParsedUpdateHandler<'bot_stopped', T>
|
||||
export type BotChatJoinRequestHandler<T = ChatJoinRequestUpdateContext> = ParsedUpdateHandler<
|
||||
'bot_chat_join_request',
|
||||
T
|
||||
>
|
||||
export type ChatJoinRequestHandler<T = UpdateContext<ChatJoinRequestUpdate>> = ParsedUpdateHandler<
|
||||
'chat_join_request',
|
||||
T
|
||||
>
|
||||
export type PreCheckoutQueryHandler<T = PreCheckoutQueryContext> = ParsedUpdateHandler<'pre_checkout_query', T>
|
||||
export type StoryUpdateHandler<T = UpdateContext<StoryUpdate>> = ParsedUpdateHandler<'story', T>
|
||||
export type DeleteStoryHandler<T = UpdateContext<DeleteStoryUpdate>> = ParsedUpdateHandler<'delete_story', T>
|
||||
|
||||
export type UpdateHandler =
|
||||
| RawUpdateHandler
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export * from './callback-data-builder'
|
||||
export * from './context'
|
||||
export * from './dispatcher'
|
||||
export * from './filters'
|
||||
export * from './handler'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { MaybeAsync, Message } from '@mtcute/client'
|
||||
import { MaybeAsync } from '@mtcute/client'
|
||||
|
||||
import { MessageContext } from './context'
|
||||
import { Dispatcher } from './dispatcher'
|
||||
import { filters } from './filters'
|
||||
import { UpdateState } from './state'
|
||||
|
@ -54,13 +55,13 @@ export class WizardScene<State, SceneName extends string = string> extends Dispa
|
|||
* Add a step to the wizard
|
||||
*/
|
||||
addStep(
|
||||
handler: (msg: Message, state: UpdateState<State, SceneName>) => MaybeAsync<WizardSceneAction | number>,
|
||||
handler: (msg: MessageContext, state: UpdateState<State, SceneName>) => MaybeAsync<WizardSceneAction | number>,
|
||||
): void {
|
||||
const step = this._steps++
|
||||
|
||||
const filter = filters.state<WizardInternalState>((it) => it.$step === step)
|
||||
|
||||
this.onNewMessage(step === 0 ? filters.or(filters.stateEmpty, filter) : filter, async (msg: Message, state) => {
|
||||
this.onNewMessage(step === 0 ? filters.or(filters.stateEmpty, filter) : filter, async (msg, state) => {
|
||||
const result = await handler(msg, state)
|
||||
|
||||
if (typeof result === 'number') {
|
||||
|
|
Loading…
Reference in a new issue