feat(client): reactions for bots

This commit is contained in:
alina 🌸 2023-12-29 15:04:18 +03:00
parent 9459748d0d
commit 0474ab918a
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
12 changed files with 273 additions and 36 deletions

View file

@ -18,4 +18,6 @@ bot_chat_join_request = BotChatJoinRequestUpdate in ChatJoinRequestUpdateContext
chat_join_request = ChatJoinRequestUpdate
pre_checkout_query = PreCheckoutQuery in PreCheckoutQueryContext
story: StoryUpdate = StoryUpdate
delete_story = DeleteStoryUpdate
delete_story = DeleteStoryUpdate
bot_reaction: BotReactionUpdate = BotReactionUpdate
bot_reaction_count: BotReactionCountUpdate = BotReactionCountUpdate

View file

@ -260,6 +260,8 @@ import {
BoostStats,
BotChatJoinRequestUpdate,
BotCommands,
BotReactionCountUpdate,
BotReactionUpdate,
BotStoppedUpdate,
CallbackQuery,
Chat,
@ -532,6 +534,20 @@ export interface TelegramClient extends BaseTelegramClient {
* @param handler Delete story handler
*/
on(name: 'delete_story', handler: (upd: DeleteStoryUpdate) => void): this
/**
* Register a bot reaction update handler
*
* @param name Event name
* @param handler Bot reaction update handler
*/
on(name: 'bot_reaction', handler: (upd: BotReactionUpdate) => void): this
/**
* Register a bot reaction count update handler
*
* @param name Event name
* @param handler Bot reaction count update handler
*/
on(name: 'bot_reaction_count', handler: (upd: BotReactionCountUpdate) => void): this
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(name: string, handler: (...args: any[]) => void): this
@ -3907,12 +3923,14 @@ export interface TelegramClient extends BaseTelegramClient {
*
* **Available**: 👤 users only
*
* @returns Message to which the reaction was sent
* @returns
* Message to which the reaction was sent, if available.
* The message is normally available for users, but may not be available for bots in PMs.
*/
sendReaction(
params: InputMessageId & {
/** Reaction emoji (or `null` to remove reaction) */
emoji?: InputReaction | null
emoji?: MaybeArray<InputReaction> | null
/** Whether to use a big reaction */
big?: boolean
@ -3922,7 +3940,7 @@ export interface TelegramClient extends BaseTelegramClient {
*/
shouldDispatch?: true
},
): Promise<Message>
): Promise<Message | null>
/** 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 */

View file

@ -24,6 +24,8 @@ import {
BoostStats,
BotChatJoinRequestUpdate,
BotCommands,
BotReactionCountUpdate,
BotReactionUpdate,
BotStoppedUpdate,
CallbackQuery,
Chat,

View file

@ -1,4 +1,4 @@
import { BaseTelegramClient, MtTypeAssertionError } from '@mtcute/core'
import { BaseTelegramClient } from '@mtcute/core'
import { InputPeerLike, Message, MtInvalidPeerTypeError } from '../../types/index.js'
import { isInputPeerChannel, isInputPeerChat, toInputChannel, toInputUser } from '../../utils/peer-utils.js'
@ -56,14 +56,5 @@ export async function banChatMember(
})
} else throw new MtInvalidPeerTypeError(chatId, 'chat or channel')
try {
return _findMessageInUpdate(client, res, false, !shouldDispatch)
} catch (e) {
if (e instanceof MtTypeAssertionError && e.context === '_findInUpdate (@ .updates[*])') {
// no service message
return null
}
throw e
}
return _findMessageInUpdate(client, res, false, !shouldDispatch, true)
}

View file

@ -1,16 +1,44 @@
/* eslint-disable max-params */
import { BaseTelegramClient, MtTypeAssertionError, tl } from '@mtcute/core'
import { Message } from '../../types/messages/index.js'
import { PeersIndex } from '../../types/peers/index.js'
import { assertIsUpdatesGroup } from '../../utils/updates-utils.js'
/** @internal */
/**
* @internal
* @noemit
*/
export function _findMessageInUpdate(
client: BaseTelegramClient,
res: tl.TypeUpdates,
isEdit?: boolean,
noDispatch?: boolean,
allowNull?: false,
): Message
/**
* @internal
* @noemit
*/
export function _findMessageInUpdate(
client: BaseTelegramClient,
res: tl.TypeUpdates,
isEdit?: boolean,
noDispatch?: boolean,
allowNull?: true,
): Message | null
/**
* @internal
* @noemit
*/
export function _findMessageInUpdate(
client: BaseTelegramClient,
res: tl.TypeUpdates,
isEdit = false,
noDispatch = true,
): Message {
allowNull = false,
): Message | null {
assertIsUpdatesGroup('_findMessageInUpdate', res)
client.network.handleUpdate(res, noDispatch)
@ -29,6 +57,8 @@ export function _findMessageInUpdate(
}
}
if (allowNull) return null
throw new MtTypeAssertionError(
'_findInUpdate (@ .updates[*])',
'updateNewMessage | updateNewChannelMessage | updateNewScheduledMessage',

View file

@ -1,4 +1,4 @@
import { BaseTelegramClient, MtTypeAssertionError } from '@mtcute/core'
import { BaseTelegramClient } from '@mtcute/core'
import { InputMessageId, Message, normalizeInputMessageId } from '../../types/index.js'
import { resolvePeer } from '../users/resolve-peer.js'
@ -38,14 +38,5 @@ export async function pinMessage(
pmOneside: !bothSides,
})
try {
return _findMessageInUpdate(client, res, false, !shouldDispatch)
} catch (e) {
if (e instanceof MtTypeAssertionError && e.context === '_findInUpdate (@ .updates[*])') {
// no service message
return null
}
throw e
}
return _findMessageInUpdate(client, res, false, !shouldDispatch, true)
}

View file

@ -1,6 +1,12 @@
import { BaseTelegramClient } from '@mtcute/core'
import { BaseTelegramClient, MaybeArray } from '@mtcute/core'
import { InputMessageId, InputReaction, Message, normalizeInputMessageId, normalizeInputReaction } from '../../types/index.js'
import {
InputMessageId,
InputReaction,
Message,
normalizeInputMessageId,
normalizeInputReaction,
} from '../../types/index.js'
import { assertIsUpdatesGroup } from '../../utils/updates-utils.js'
import { resolvePeer } from '../users/resolve-peer.js'
import { _findMessageInUpdate } from './find-in-update.js'
@ -8,13 +14,15 @@ import { _findMessageInUpdate } from './find-in-update.js'
/**
* Send or remove a reaction.
*
* @returns Message to which the reaction was sent
* @returns
* Message to which the reaction was sent, if available.
* The message is normally available for users, but may not be available for bots in PMs.
*/
export async function sendReaction(
client: BaseTelegramClient,
params: InputMessageId & {
/** Reaction emoji (or `null` to remove reaction) */
emoji?: InputReaction | null
emoji?: MaybeArray<InputReaction> | null
/** Whether to use a big reaction */
big?: boolean
@ -24,17 +32,18 @@ export async function sendReaction(
*/
shouldDispatch?: true
},
): Promise<Message> {
): Promise<Message | null> {
const { emoji, big } = params
const { chatId, message } = normalizeInputMessageId(params)
const reaction = normalizeInputReaction(emoji)
const emojis = Array.isArray(emoji) ? emoji : [emoji]
const reactions = emojis.map(normalizeInputReaction)
const res = await client.call({
_: 'messages.sendReaction',
peer: await resolvePeer(client, chatId),
msgId: message,
reaction: [reaction],
reaction: reactions,
big,
})
@ -45,6 +54,9 @@ export async function sendReaction(
// updateMessageReactions
// idk why, they contain literally the same data
// so we can just return the message from the first one
//
// for whatever reason, sendReaction for bots returns empty updates
// group in pms, so we should handle that too
return _findMessageInUpdate(client, res, true, !params.shouldDispatch)
return _findMessageInUpdate(client, res, true, !params.shouldDispatch, true)
}

View file

@ -0,0 +1,115 @@
import { tl } from '@mtcute/core'
import { makeInspectable } from '../../utils/inspectable.js'
import { memoizeGetters } from '../../utils/memoize.js'
import { Chat } from '../peers/chat.js'
import { parsePeer, Peer } from '../peers/peer.js'
import { PeersIndex } from '../peers/peers-index.js'
import { ReactionCount } from '../reactions/reaction-count.js'
import { InputReaction, toReactionEmoji } from '../reactions/types.js'
/**
* A reaction to a message was changed by a user.
*
* These updates are only received for bots - for PMs and in chats
* where the bot is an administrator.
*
* Reactions sent by other bots are not received.
*/
export class BotReactionUpdate {
constructor(
readonly raw: tl.RawUpdateBotMessageReaction,
readonly _peers: PeersIndex,
) {}
/**
* Chat where the reaction has been changed
*/
get chat(): Chat {
return Chat._parseFromPeer(this.raw.peer, this._peers)
}
/**
* ID of the message where the reaction has been changed
*/
get messageId(): number {
return this.raw.msgId
}
/**
* Date when the reaction has been changed
*/
get date(): Date {
return new Date(this.raw.date * 1000)
}
/**
* ID of the user who has set/removed the reaction
*/
get actor(): Peer {
return parsePeer(this.raw.actor, this._peers)
}
/**
* List of reactions before the change
*/
get before(): InputReaction[] {
return this.raw.oldReactions.map((it) => toReactionEmoji(it))
}
/**
* List of reactions after the change
*/
get after(): InputReaction[] {
return this.raw.newReactions.map((it) => toReactionEmoji(it))
}
}
memoizeGetters(BotReactionUpdate, ['chat', 'actor', 'before', 'after'])
makeInspectable(BotReactionUpdate)
/**
* The count of reactions to a message has been updated.
*
* These updates are only received for bots in chats where
* the bot is an administrator. Unlike {@link BotReactionUpdate},
* this update is used for chats where the list of users who
* reacted to a message is not visible (e.g. channels).
*/
export class BotReactionCountUpdate {
constructor(
readonly raw: tl.RawUpdateBotMessageReactions,
readonly _peers: PeersIndex,
) {}
/**
* Chat where the reaction has been changed
*/
get chat(): Chat {
return Chat._parseFromPeer(this.raw.peer, this._peers)
}
/**
* ID of the message where the reaction has been changed
*/
get messageId(): number {
return this.raw.msgId
}
/**
* Date when the reaction has been changed
*/
get date(): Date {
return new Date(this.raw.date * 1000)
}
/**
* The new list of reactions to the message
*/
get reactions(): ReactionCount[] {
return this.raw.reactions.map((it) => new ReactionCount(it))
}
}
memoizeGetters(BotReactionCountUpdate, ['chat'])
makeInspectable(BotReactionCountUpdate)

View file

@ -6,6 +6,7 @@ import { ChatJoinRequestUpdate } from './chat-join-request.js'
import { ChatMemberUpdate } from './chat-member-update.js'
import { InlineQuery } from './inline-query.js'
export type { ChatMemberUpdateType } from './chat-member-update.js'
import { BotReactionCountUpdate, BotReactionUpdate } from './bot-reaction.js'
import { ChosenInlineResult } from './chosen-inline-result.js'
import { DeleteMessageUpdate } from './delete-message-update.js'
import { DeleteStoryUpdate } from './delete-story-update.js'
@ -19,6 +20,8 @@ import { UserTypingUpdate } from './user-typing-update.js'
export {
BotChatJoinRequestUpdate,
BotReactionCountUpdate,
BotReactionUpdate,
BotStoppedUpdate,
CallbackQuery,
ChatJoinRequestUpdate,
@ -59,5 +62,7 @@ export type ParsedUpdate =
| { name: 'pre_checkout_query'; data: PreCheckoutQuery }
| { name: 'story'; data: StoryUpdate }
| { name: 'delete_story'; data: DeleteStoryUpdate }
| { name: 'bot_reaction'; data: BotReactionUpdate }
| { name: 'bot_reaction_count'; data: BotReactionCountUpdate }
// end-codegen

View file

@ -3,6 +3,8 @@ import { tl } from '@mtcute/core'
import {
BotChatJoinRequestUpdate,
BotReactionCountUpdate,
BotReactionUpdate,
BotStoppedUpdate,
CallbackQuery,
ChatJoinRequestUpdate,
@ -88,6 +90,10 @@ export function _parseUpdate(update: tl.TypeUpdate, peers: PeersIndex): ParsedUp
data: new StoryUpdate(update, peers),
}
}
case 'updateBotMessageReaction':
return { name: 'bot_reaction', data: new BotReactionUpdate(update, peers) }
case 'updateBotMessageReactions':
return { name: 'bot_reaction_count', data: new BotReactionCountUpdate(update, peers) }
default:
return null
}

View file

@ -4,6 +4,8 @@
// ^^ will be looked into in MTQ-29
import {
BotReactionCountUpdate,
BotReactionUpdate,
BotStoppedUpdate,
ChatJoinRequestUpdate,
ChatMemberUpdate,
@ -38,6 +40,8 @@ import { filters, UpdateFilter } from './filters/index.js'
// begin-codegen-imports
import {
BotChatJoinRequestHandler,
BotReactionCountUpdateHandler,
BotReactionUpdateHandler,
BotStoppedHandler,
CallbackQueryHandler,
ChatJoinRequestHandler,
@ -1698,5 +1702,57 @@ export class Dispatcher<State extends object = never> {
this._addKnownHandler('delete_story', filter, handler, group)
}
/**
* Register a bot reaction update handler without any filters
*
* @param handler Bot reaction update handler
* @param group Handler group index
*/
onBotReactionUpdate(handler: BotReactionUpdateHandler['callback'], group?: number): void
/**
* Register a bot reaction update handler with a filter
*
* @param filter Update filter
* @param handler Bot reaction update handler
* @param group Handler group index
*/
onBotReactionUpdate<Mod>(
filter: UpdateFilter<UpdateContext<BotReactionUpdate>, Mod>,
handler: BotReactionUpdateHandler<filters.Modify<UpdateContext<BotReactionUpdate>, Mod>>['callback'],
group?: number,
): void
/** @internal */
onBotReactionUpdate(filter: any, handler?: any, group?: number): void {
this._addKnownHandler('bot_reaction', filter, handler, group)
}
/**
* Register a bot reaction count update handler without any filters
*
* @param handler Bot reaction count update handler
* @param group Handler group index
*/
onBotReactionCountUpdate(handler: BotReactionCountUpdateHandler['callback'], group?: number): void
/**
* Register a bot reaction count update handler with a filter
*
* @param filter Update filter
* @param handler Bot reaction count update handler
* @param group Handler group index
*/
onBotReactionCountUpdate<Mod>(
filter: UpdateFilter<UpdateContext<BotReactionCountUpdate>, Mod>,
handler: BotReactionCountUpdateHandler<filters.Modify<UpdateContext<BotReactionCountUpdate>, Mod>>['callback'],
group?: number,
): void
/** @internal */
onBotReactionCountUpdate(filter: any, handler?: any, group?: number): void {
this._addKnownHandler('bot_reaction_count', filter, handler, group)
}
// end-codegen
}

View file

@ -1,4 +1,6 @@
import {
BotReactionCountUpdate,
BotReactionUpdate,
BotStoppedUpdate,
ChatJoinRequestUpdate,
ChatMemberUpdate,
@ -82,6 +84,11 @@ export type ChatJoinRequestHandler<T = UpdateContext<ChatJoinRequestUpdate>> = P
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 BotReactionUpdateHandler<T = UpdateContext<BotReactionUpdate>> = ParsedUpdateHandler<'bot_reaction', T>
export type BotReactionCountUpdateHandler<T = UpdateContext<BotReactionCountUpdate>> = ParsedUpdateHandler<
'bot_reaction_count',
T
>
export type UpdateHandler =
| RawUpdateHandler
@ -105,5 +112,7 @@ export type UpdateHandler =
| PreCheckoutQueryHandler
| StoryUpdateHandler
| DeleteStoryHandler
| BotReactionUpdateHandler
| BotReactionCountUpdateHandler
// end-codegen