diff --git a/packages/core/scripts/update-types.txt b/packages/core/scripts/update-types.txt index 4e25ca85..9e6b0123 100644 --- a/packages/core/scripts/update-types.txt +++ b/packages/core/scripts/update-types.txt @@ -20,4 +20,9 @@ pre_checkout_query = PreCheckoutQuery in PreCheckoutQueryContext story: StoryUpdate = StoryUpdate delete_story = DeleteStoryUpdate bot_reaction: BotReactionUpdate = BotReactionUpdate -bot_reaction_count: BotReactionCountUpdate = BotReactionCountUpdate \ No newline at end of file +bot_reaction_count: BotReactionCountUpdate = BotReactionCountUpdate +business_connection: BusinessConnectionUpdate = BusinessConnection +new_business_message = BusinessMessage + State in BusinessMessageContext +edit_business_message = BusinessMessage + State in BusinessMessageContext +business_message_group = BusinessMessage[] + State in BusinessMessageContext +delete_business_message = DeleteBusinessMessageUpdate \ No newline at end of file diff --git a/packages/core/src/highlevel/client.ts b/packages/core/src/highlevel/client.ts index f5d70440..48451023 100644 --- a/packages/core/src/highlevel/client.ts +++ b/packages/core/src/highlevel/client.ts @@ -183,6 +183,7 @@ import { deleteBusinessChatLink, editBusinessChatLink } from './methods/premium/ import { getBoostStats } from './methods/premium/get-boost-stats.js' import { getBoosts } from './methods/premium/get-boosts.js' import { getBusinessChatLinks } from './methods/premium/get-business-chat-links.js' +import { getBusinessConnection } from './methods/premium/get-business-connection.js' import { getMyBoostSlots } from './methods/premium/get-my-boost-slots.js' import { iterBoosters } from './methods/premium/iter-boosters.js' import { setBusinessIntro } from './methods/premium/set-business-intro.js' @@ -255,6 +256,8 @@ import { BotReactionUpdate, BotStoppedUpdate, BusinessChatLink, + BusinessConnection, + BusinessMessage, BusinessWorkHoursDay, CallbackQuery, Chat, @@ -267,6 +270,7 @@ import { ChatPreview, ChosenInlineResult, CollectibleInfo, + DeleteBusinessMessageUpdate, DeleteMessageUpdate, DeleteStoryUpdate, Dialog, @@ -524,6 +528,41 @@ export interface TelegramClient extends ITelegramClient { * @param handler Bot reaction count update handler */ on(name: 'bot_reaction_count', handler: (upd: BotReactionCountUpdate) => void): this + /** + * Register a business connection update handler + * + * @param name Event name + * @param handler Business connection update handler + */ + on(name: 'business_connection', handler: (upd: BusinessConnection) => void): this + /** + * Register a new business message handler + * + * @param name Event name + * @param handler New business message handler + */ + on(name: 'new_business_message', handler: (upd: BusinessMessage) => void): this + /** + * Register an edit business message handler + * + * @param name Event name + * @param handler Edit business message handler + */ + on(name: 'edit_business_message', handler: (upd: BusinessMessage) => void): this + /** + * Register a business message group handler + * + * @param name Event name + * @param handler Business message group handler + */ + on(name: 'business_message_group', handler: (upd: BusinessMessage[]) => void): this + /** + * Register a delete business message handler + * + * @param name Event name + * @param handler Delete business message handler + */ + on(name: 'delete_business_message', handler: (upd: DeleteBusinessMessageUpdate) => void): this // eslint-disable-next-line @typescript-eslint/no-explicit-any on(name: string, handler: (...args: any[]) => void): this @@ -2338,6 +2377,7 @@ export interface TelegramClient extends ITelegramClient { params?: { progressCallback?: (uploaded: number, total: number) => void uploadPeer?: tl.TypeInputPeer + businessConnectionId?: string }, uploadMedia?: boolean, ): Promise @@ -3123,6 +3163,8 @@ export interface TelegramClient extends ITelegramClient { * client render the preview above the caption and not below. */ invertMedia?: boolean + + businessConnectionId?: string }, ): Promise /** @@ -4009,6 +4051,11 @@ export interface TelegramClient extends ITelegramClient { */ progress?: number + /** + * Unique identifier of the business connection on behalf of which the action will be sent + */ + businessConnectionId?: string + /** * For comment threads, ID of the thread (i.e. top message) */ @@ -4263,6 +4310,15 @@ export interface TelegramClient extends ITelegramClient { * */ getBusinessChatLinks(): Promise + + /** + * Get information about the connection of the bot with a business account + * + * **Available**: 🤖 bots only + * + * @param connectionId ID of the business connection + */ + getBusinessConnection(connectionId: string): Promise /** * Get boost slots information of the current user. * @@ -5935,6 +5991,9 @@ TelegramClient.prototype.getBoosts = function (...args) { TelegramClient.prototype.getBusinessChatLinks = function (...args) { return getBusinessChatLinks(this._client, ...args) } +TelegramClient.prototype.getBusinessConnection = function (...args) { + return getBusinessConnection(this._client, ...args) +} TelegramClient.prototype.getMyBoostSlots = function (...args) { return getMyBoostSlots(this._client, ...args) } diff --git a/packages/core/src/highlevel/methods.ts b/packages/core/src/highlevel/methods.ts index 70368a04..9b77586c 100644 --- a/packages/core/src/highlevel/methods.ts +++ b/packages/core/src/highlevel/methods.ts @@ -199,6 +199,7 @@ export { deleteBusinessChatLink } from './methods/premium/edit-business-chat-lin export { getBoostStats } from './methods/premium/get-boost-stats.js' export { getBoosts } from './methods/premium/get-boosts.js' export { getBusinessChatLinks } from './methods/premium/get-business-chat-links.js' +export { getBusinessConnection } from './methods/premium/get-business-connection.js' export { getMyBoostSlots } from './methods/premium/get-my-boost-slots.js' export { iterBoosters } from './methods/premium/iter-boosters.js' export { setBusinessIntro } from './methods/premium/set-business-intro.js' diff --git a/packages/core/src/highlevel/methods/_imports.ts b/packages/core/src/highlevel/methods/_imports.ts index 428784c3..9540f2b0 100644 --- a/packages/core/src/highlevel/methods/_imports.ts +++ b/packages/core/src/highlevel/methods/_imports.ts @@ -27,6 +27,8 @@ import { BotReactionUpdate, BotStoppedUpdate, BusinessChatLink, + BusinessConnection, + BusinessMessage, BusinessWorkHoursDay, CallbackQuery, Chat, @@ -39,6 +41,7 @@ import { ChatPreview, ChosenInlineResult, CollectibleInfo, + DeleteBusinessMessageUpdate, DeleteMessageUpdate, DeleteStoryUpdate, Dialog, diff --git a/packages/core/src/highlevel/methods/files/normalize-input-media.ts b/packages/core/src/highlevel/methods/files/normalize-input-media.ts index 8d3372ca..14ae3b3f 100644 --- a/packages/core/src/highlevel/methods/files/normalize-input-media.ts +++ b/packages/core/src/highlevel/methods/files/normalize-input-media.ts @@ -28,6 +28,7 @@ export async function _normalizeInputMedia( params: { progressCallback?: (uploaded: number, total: number) => void uploadPeer?: tl.TypeInputPeer + businessConnectionId?: string } = {}, uploadMedia = false, ): Promise { @@ -254,6 +255,7 @@ export async function _normalizeInputMedia( _: 'messages.uploadMedia', peer: uploadPeer, media: inputMedia, + businessConnectionId: params.businessConnectionId, }) if (photo) { diff --git a/packages/core/src/highlevel/methods/messages/_business-connection.ts b/packages/core/src/highlevel/methods/messages/_business-connection.ts new file mode 100644 index 00000000..a992ee9c --- /dev/null +++ b/packages/core/src/highlevel/methods/messages/_business-connection.ts @@ -0,0 +1,59 @@ +import { tl } from '@mtcute/tl' + +import { RpcCallOptions } from '../../../network/network-manager.js' +import { LruMap } from '../../../utils/lru-map.js' +import { ITelegramClient } from '../../client.types.js' +import { getBusinessConnection } from '../premium/get-business-connection.js' + +// temporary solution +// todo – rework once we have either a more generic caching solution +const DC_MAP_SYMBOL = Symbol('dcMap') + +const getDcMap = (client: ITelegramClient): LruMap => { + const client_ = client as typeof client & { [DC_MAP_SYMBOL]?: LruMap } + + if (!client_[DC_MAP_SYMBOL]) { + client_[DC_MAP_SYMBOL] = new LruMap(50) + } + + return client_[DC_MAP_SYMBOL] +} + +// @available=both +/** + * @internal + */ +export async function _maybeInvokeWithBusinessConnection( + client: ITelegramClient, + businessConnectionId: string | undefined, + request: T, + params?: RpcCallOptions, +): Promise { + if (!businessConnectionId) { + return client.call(request, params) + } + + const dcMap = getDcMap(client) + + if (!dcMap.has(businessConnectionId)) { + const res = await getBusinessConnection(client, businessConnectionId) + + dcMap.set(businessConnectionId, res.dcId) + } + + const dcId = dcMap.get(businessConnectionId)! + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return client.call( + { + _: 'invokeWithBusinessConnection', + connectionId: businessConnectionId, + query: request, + }, + { + ...params, + localMigrate: true, // just in case + dcId, + }, + ) +} diff --git a/packages/core/src/highlevel/methods/messages/edit-message.ts b/packages/core/src/highlevel/methods/messages/edit-message.ts index 57208451..d84a51fa 100644 --- a/packages/core/src/highlevel/methods/messages/edit-message.ts +++ b/packages/core/src/highlevel/methods/messages/edit-message.ts @@ -13,6 +13,7 @@ import { import { _normalizeInputMedia } from '../files/normalize-input-media.js' import { _normalizeInputText } from '../misc/normalize-text.js' import { resolvePeer } from '../users/resolve-peer.js' +import { _maybeInvokeWithBusinessConnection } from './_business-connection.js' import { _findMessageInUpdate } from './find-in-update.js' /** @@ -75,6 +76,8 @@ export async function editMessage( * client render the preview above the caption and not below. */ invertMedia?: boolean + + businessConnectionId?: string }, ): Promise { const { chatId, message } = normalizeInputMessageId(params) @@ -96,7 +99,7 @@ export async function editMessage( [content, entities] = await _normalizeInputText(client, params.text) } - const res = await client.call({ + const res = await _maybeInvokeWithBusinessConnection(client, params.businessConnectionId, { _: 'messages.editMessage', id: message, peer: await resolvePeer(client, chatId), diff --git a/packages/core/src/highlevel/methods/messages/find-in-update.ts b/packages/core/src/highlevel/methods/messages/find-in-update.ts index b93a1ed5..7d3d414b 100644 --- a/packages/core/src/highlevel/methods/messages/find-in-update.ts +++ b/packages/core/src/highlevel/methods/messages/find-in-update.ts @@ -57,14 +57,23 @@ export function _findMessageInUpdate( } if (isEdit) { - if (!(u._ === 'updateEditMessage' || u._ === 'updateEditChannelMessage')) continue + if ( + !( + u._ === 'updateEditMessage' || + u._ === 'updateEditChannelMessage' || + u._ === 'updateBotEditBusinessMessage' + ) + ) { + continue + } } else { if ( !( u._ === 'updateNewMessage' || u._ === 'updateNewChannelMessage' || u._ === 'updateNewScheduledMessage' || - u._ === 'updateQuickReplyMessage' + u._ === 'updateQuickReplyMessage' || + u._ === 'updateBotNewBusinessMessage' ) ) { continue diff --git a/packages/core/src/highlevel/methods/messages/send-common.ts b/packages/core/src/highlevel/methods/messages/send-common.ts index 40a64c9d..924decbd 100644 --- a/packages/core/src/highlevel/methods/messages/send-common.ts +++ b/packages/core/src/highlevel/methods/messages/send-common.ts @@ -111,6 +111,12 @@ export interface CommonSendParams { * to the client's update handler. */ shouldDispatch?: true + + /** + * Unique identifier of the business connection on behalf of which + * the message will be sent + */ + businessConnectionId?: string } /** diff --git a/packages/core/src/highlevel/methods/messages/send-media-group.ts b/packages/core/src/highlevel/methods/messages/send-media-group.ts index caad0bb8..ec2a2de9 100644 --- a/packages/core/src/highlevel/methods/messages/send-media-group.ts +++ b/packages/core/src/highlevel/methods/messages/send-media-group.ts @@ -9,6 +9,7 @@ import { assertIsUpdatesGroup } from '../../updates/utils.js' import { _normalizeInputMedia } from '../files/normalize-input-media.js' import { _normalizeInputText } from '../misc/normalize-text.js' import { resolvePeer } from '../users/resolve-peer.js' +import { _maybeInvokeWithBusinessConnection } from './_business-connection.js' import { _getDiscussionMessage } from './get-discussion-message.js' import { _processCommonSendParameters, CommonSendParams } from './send-common.js' @@ -77,6 +78,7 @@ export async function sendMediaGroup( // but otherwise Telegram throws MEDIA_INVALID // fuck my life uploadPeer: peer, + businessConnectionId: params.businessConnectionId, }, true, ) @@ -97,7 +99,9 @@ export async function sendMediaGroup( }) } - const res = await client.call( + const res = await _maybeInvokeWithBusinessConnection( + client, + params.businessConnectionId, { _: 'messages.sendMultiMedia', peer, diff --git a/packages/core/src/highlevel/methods/messages/send-media.ts b/packages/core/src/highlevel/methods/messages/send-media.ts index 04960af0..254ef73e 100644 --- a/packages/core/src/highlevel/methods/messages/send-media.ts +++ b/packages/core/src/highlevel/methods/messages/send-media.ts @@ -8,6 +8,7 @@ import { InputPeerLike } from '../../types/peers/index.js' import { _normalizeInputMedia } from '../files/normalize-input-media.js' import { _normalizeInputText } from '../misc/normalize-text.js' import { resolvePeer } from '../users/resolve-peer.js' +import { _maybeInvokeWithBusinessConnection } from './_business-connection.js' import { _findMessageInUpdate } from './find-in-update.js' import { _getDiscussionMessage } from './get-discussion-message.js' import { _processCommonSendParameters, CommonSendParams } from './send-common.js' @@ -87,7 +88,9 @@ export async function sendMedia( ) const randomId = randomLong() - const res = await client.call( + const res = await _maybeInvokeWithBusinessConnection( + client, + params.businessConnectionId, { _: 'messages.sendMedia', peer, diff --git a/packages/core/src/highlevel/methods/messages/send-text.ts b/packages/core/src/highlevel/methods/messages/send-text.ts index 8ba178f3..d027b734 100644 --- a/packages/core/src/highlevel/methods/messages/send-text.ts +++ b/packages/core/src/highlevel/methods/messages/send-text.ts @@ -13,6 +13,7 @@ import { inputPeerToPeer } from '../../utils/peer-utils.js' import { _getRawPeerBatched } from '../chats/batched-queries.js' import { _normalizeInputText } from '../misc/normalize-text.js' import { resolvePeer } from '../users/resolve-peer.js' +import { _maybeInvokeWithBusinessConnection } from './_business-connection.js' import { _findMessageInUpdate } from './find-in-update.js' import { _getDiscussionMessage } from './get-discussion-message.js' import { _processCommonSendParameters, CommonSendParams } from './send-common.js' @@ -61,7 +62,9 @@ export async function sendText( ) const randomId = randomLong() - const res = await client.call( + const res = await _maybeInvokeWithBusinessConnection( + client, + params.businessConnectionId, { _: 'messages.sendMessage', peer, diff --git a/packages/core/src/highlevel/methods/messages/send-typing.ts b/packages/core/src/highlevel/methods/messages/send-typing.ts index 5855f6d1..f4dcfbf2 100644 --- a/packages/core/src/highlevel/methods/messages/send-typing.ts +++ b/packages/core/src/highlevel/methods/messages/send-typing.ts @@ -5,6 +5,7 @@ import { assertTrue } from '../../../utils/type-assertions.js' import { ITelegramClient } from '../../client.types.js' import { InputPeerLike, TypingStatus } from '../../types/index.js' import { resolvePeer } from '../users/resolve-peer.js' +import { _maybeInvokeWithBusinessConnection } from './_business-connection.js' /** * Sends a current user/bot typing event @@ -28,6 +29,11 @@ export async function sendTyping( */ progress?: number + /** + * Unique identifier of the business connection on behalf of which the action will be sent + */ + businessConnectionId?: string + /** * For comment threads, ID of the thread (i.e. top message) */ @@ -91,7 +97,7 @@ export async function sendTyping( } } - const r = await client.call({ + const r = await _maybeInvokeWithBusinessConnection(client, params?.businessConnectionId, { _: 'messages.setTyping', peer: await resolvePeer(client, chatId), action: status, diff --git a/packages/core/src/highlevel/methods/premium/get-business-connection.ts b/packages/core/src/highlevel/methods/premium/get-business-connection.ts new file mode 100644 index 00000000..23f187bc --- /dev/null +++ b/packages/core/src/highlevel/methods/premium/get-business-connection.ts @@ -0,0 +1,28 @@ +import { assertTypeIs } from '../../../utils/type-assertions.js' +import { ITelegramClient } from '../../client.types.js' +import { PeersIndex } from '../../types/peers/peers-index.js' +import { BusinessConnection } from '../../types/premium/business-connection.js' +import { assertIsUpdatesGroup } from '../../updates/utils.js' + +// @available=bot +/** + * Get information about the connection of the bot with a business account + * + * @param connectionId ID of the business connection + */ +export async function getBusinessConnection( + client: ITelegramClient, + connectionId: string, +): Promise { + const res = await client.call({ + _: 'account.getBotBusinessConnection', + connectionId, + }) + + assertIsUpdatesGroup('account.getBotBusinessConnection', res) + assertTypeIs('account.getBotBusinessConnection', res.updates[0], 'updateBotBusinessConnect') + + const peers = PeersIndex.from(res) + + return new BusinessConnection(res.updates[0].connection, peers) +} diff --git a/packages/core/src/highlevel/types/messages/dialog.ts b/packages/core/src/highlevel/types/messages/dialog.ts index 6b873dde..32575125 100644 --- a/packages/core/src/highlevel/types/messages/dialog.ts +++ b/packages/core/src/highlevel/types/messages/dialog.ts @@ -194,21 +194,7 @@ export class Dialog { * Chat that this dialog represents */ get chat(): Chat { - const peer = this.raw.peer - - let chat - - switch (peer._) { - case 'peerChannel': - case 'peerChat': - chat = this._peers.chat(peer._ === 'peerChannel' ? peer.channelId : peer.chatId) - break - default: - chat = this._peers.user(peer.userId) - break - } - - return new Chat(chat) + return Chat._parseFromPeer(this.raw.peer, this._peers) } /** diff --git a/packages/core/src/highlevel/types/messages/message.ts b/packages/core/src/highlevel/types/messages/message.ts index 18b29157..bd5c53ab 100644 --- a/packages/core/src/highlevel/types/messages/message.ts +++ b/packages/core/src/highlevel/types/messages/message.ts @@ -253,6 +253,20 @@ export class Message { return new User(this._peers.user(this.raw.viaBotId)) } + /** + * If this message was sent by a business bot on behalf of {@link sender}, + * information about the business bot. + * + * **Note**: only available to the business account and the bot itself. + */ + get viaBusinessBot(): User | null { + if (this.raw._ === 'messageService' || !this.raw.viaBusinessBotId) { + return null + } + + return new User(this._peers.user(this.raw.viaBusinessBotId)) + } + /** * Message text or media caption. * diff --git a/packages/core/src/highlevel/types/premium/business-connection.ts b/packages/core/src/highlevel/types/premium/business-connection.ts new file mode 100644 index 00000000..354e1c75 --- /dev/null +++ b/packages/core/src/highlevel/types/premium/business-connection.ts @@ -0,0 +1,49 @@ +import { tl } from '@mtcute/tl' + +import { makeInspectable } from '../../utils/inspectable.js' +import { memoizeGetters } from '../../utils/memoize.js' +import { PeersIndex } from '../peers/peers-index.js' +import { User } from '../peers/user.js' + +/** + * Describes the connection of the bot with a business account. + */ +export class BusinessConnection { + constructor( + readonly raw: tl.TypeBotBusinessConnection, + readonly _peers: PeersIndex, + ) {} + + /** Whether the connection was removed by the user */ + get isRemoved(): boolean { + return this.raw.disabled! + } + + /** ID of the connection */ + get id(): string { + return this.raw.connectionId + } + + /** Datacenter ID of the connected user */ + get dcId(): number { + return this.raw.dcId + } + + /** Date when the connection was created */ + get date(): Date { + return new Date(this.raw.date * 1000) + } + + /** Whether the bot can reply on behalf of the user */ + get canReply(): boolean { + return this.raw.canReply! + } + + /** Business account user that created the business connection */ + get user(): User { + return new User(this._peers.user(this.raw.userId)) + } +} + +makeInspectable(BusinessConnection) +memoizeGetters(BusinessConnection, ['user']) diff --git a/packages/core/src/highlevel/types/premium/index.ts b/packages/core/src/highlevel/types/premium/index.ts index 7dead005..2e41e8c8 100644 --- a/packages/core/src/highlevel/types/premium/index.ts +++ b/packages/core/src/highlevel/types/premium/index.ts @@ -3,5 +3,6 @@ export * from './boost-slot.js' export * from './boost-stats.js' export * from './business-account.js' export * from './business-chat-link.js' +export * from './business-connection.js' export * from './business-intro.js' export * from './business-work-hours.js' diff --git a/packages/core/src/highlevel/types/updates/business-message.ts b/packages/core/src/highlevel/types/updates/business-message.ts new file mode 100644 index 00000000..142aa892 --- /dev/null +++ b/packages/core/src/highlevel/types/updates/business-message.ts @@ -0,0 +1,32 @@ +import { tl } from '@mtcute/tl' + +import { Message } from '../messages/message.js' +import { PeersIndex } from '../peers/peers-index.js' + +/** + * Update about a new or edited business message. + */ +export class BusinessMessage extends Message { + constructor( + readonly update: tl.RawUpdateBotNewBusinessMessage | tl.RawUpdateBotEditBusinessMessage, + readonly _peers: PeersIndex, + ) { + super(update.message, _peers) + } + + /** + * Unique identifier of the business connection from which the message was received. + */ + get connectionId(): string { + return this.update.connectionId + } + + get groupedIdUnique(): string { + return `${super.groupedIdUnique}|${this.update.connectionId}` + } + + /** The replied message (if any) */ + get replyTo(): Message | null { + return this.update.replyToMessage ? new Message(this.update.replyToMessage, this._peers) : null + } +} diff --git a/packages/core/src/highlevel/types/updates/delete-business-message-update.ts b/packages/core/src/highlevel/types/updates/delete-business-message-update.ts new file mode 100644 index 00000000..c867e31a --- /dev/null +++ b/packages/core/src/highlevel/types/updates/delete-business-message-update.ts @@ -0,0 +1,38 @@ +import { tl } from '@mtcute/tl' + +import { makeInspectable } from '../../utils/index.js' +import { memoizeGetters } from '../../utils/memoize.js' +import { Chat } from '../peers/chat.js' +import { PeersIndex } from '../peers/peers-index.js' + +/** + * One or more messages were deleted from a connected business account + */ +export class DeleteBusinessMessageUpdate { + constructor( + readonly raw: tl.RawUpdateBotDeleteBusinessMessage, + readonly _peers: PeersIndex, + ) {} + + /** Unique identifier of the business connection */ + get connectionId(): string { + return this.raw.connectionId + } + + /** + * IDs of the messages which were deleted + */ + get messageIds(): number[] { + return this.raw.messages + } + + /** + * Chat where the messages were deleted + */ + get chat(): Chat { + return Chat._parseFromPeer(this.raw.peer, this._peers) + } +} + +makeInspectable(DeleteBusinessMessageUpdate) +memoizeGetters(DeleteBusinessMessageUpdate, ['chat']) diff --git a/packages/core/src/highlevel/types/updates/index.ts b/packages/core/src/highlevel/types/updates/index.ts index 8af3c1e6..ad772c0d 100644 --- a/packages/core/src/highlevel/types/updates/index.ts +++ b/packages/core/src/highlevel/types/updates/index.ts @@ -1,4 +1,4 @@ -import type { Message } from '../../types/index.js' +import type { BusinessConnection, Message } from '../../types/index.js' import { BotChatJoinRequestUpdate } from './bot-chat-join-request.js' import { BotStoppedUpdate } from './bot-stopped.js' import { CallbackQuery, InlineCallbackQuery } from './callback-query.js' @@ -7,7 +7,9 @@ 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 { BusinessMessage } from './business-message.js' import { ChosenInlineResult } from './chosen-inline-result.js' +import { DeleteBusinessMessageUpdate } from './delete-business-message-update.js' import { DeleteMessageUpdate } from './delete-message-update.js' import { DeleteStoryUpdate } from './delete-story-update.js' import { HistoryReadUpdate } from './history-read-update.js' @@ -23,10 +25,12 @@ export { BotReactionCountUpdate, BotReactionUpdate, BotStoppedUpdate, + BusinessMessage, CallbackQuery, ChatJoinRequestUpdate, ChatMemberUpdate, ChosenInlineResult, + DeleteBusinessMessageUpdate, DeleteMessageUpdate, DeleteStoryUpdate, HistoryReadUpdate, @@ -64,5 +68,10 @@ export type ParsedUpdate = | { name: 'delete_story'; data: DeleteStoryUpdate } | { name: 'bot_reaction'; data: BotReactionUpdate } | { name: 'bot_reaction_count'; data: BotReactionCountUpdate } + | { name: 'business_connection'; data: BusinessConnection } + | { name: 'new_business_message'; data: BusinessMessage } + | { name: 'edit_business_message'; data: BusinessMessage } + | { name: 'business_message_group'; data: BusinessMessage[] } + | { name: 'delete_business_message'; data: DeleteBusinessMessageUpdate } // end-codegen diff --git a/packages/core/src/highlevel/types/updates/parse-update.ts b/packages/core/src/highlevel/types/updates/parse-update.ts index ff01145d..c0a662f3 100644 --- a/packages/core/src/highlevel/types/updates/parse-update.ts +++ b/packages/core/src/highlevel/types/updates/parse-update.ts @@ -6,10 +6,13 @@ import { BotReactionCountUpdate, BotReactionUpdate, BotStoppedUpdate, + BusinessConnection, + BusinessMessage, CallbackQuery, ChatJoinRequestUpdate, ChatMemberUpdate, ChosenInlineResult, + DeleteBusinessMessageUpdate, DeleteMessageUpdate, DeleteStoryUpdate, HistoryReadUpdate, @@ -94,6 +97,14 @@ export function _parseUpdate(update: tl.TypeUpdate, peers: PeersIndex): ParsedUp return { name: 'bot_reaction', data: new BotReactionUpdate(update, peers) } case 'updateBotMessageReactions': return { name: 'bot_reaction_count', data: new BotReactionCountUpdate(update, peers) } + case 'updateBotBusinessConnect': + return { name: 'business_connection', data: new BusinessConnection(update.connection, peers) } + case 'updateBotNewBusinessMessage': + return { name: 'new_business_message', data: new BusinessMessage(update, peers) } + case 'updateBotEditBusinessMessage': + return { name: 'edit_business_message', data: new BusinessMessage(update, peers) } + case 'updateBotDeleteBusinessMessage': + return { name: 'delete_business_message', data: new DeleteBusinessMessageUpdate(update, peers) } default: return null } diff --git a/packages/core/src/highlevel/updates/manager.ts b/packages/core/src/highlevel/updates/manager.ts index 226be0d7..6be62b08 100644 --- a/packages/core/src/highlevel/updates/manager.ts +++ b/packages/core/src/highlevel/updates/manager.ts @@ -523,7 +523,8 @@ export class UpdatesManager { switch (upd._) { case 'updateNewMessage': - case 'updateNewChannelMessage': { + case 'updateNewChannelMessage': + case 'updateBotNewBusinessMessage': { const channelId = upd.message.peerId?._ === 'peerChannel' ? upd.message.peerId.channelId : 0 const set = noDispatchMsg.get(channelId) @@ -1260,7 +1261,11 @@ export class UpdatesManager { if (this.noDispatchEnabled) { const channelId = pending.channelId ?? 0 const msgId = - upd._ === 'updateNewMessage' || upd._ === 'updateNewChannelMessage' ? upd.message.id : undefined + upd._ === 'updateNewMessage' || + upd._ === 'updateNewChannelMessage' || + upd._ === 'updateBotNewBusinessMessage' ? + upd.message.id : + undefined // we first need to remove it from each index, and then check if it was there const foundByMsgId = msgId && this.noDispatchMsg.get(channelId)?.delete(msgId) diff --git a/packages/core/src/highlevel/updates/parsed.ts b/packages/core/src/highlevel/updates/parsed.ts index df97d7b2..76bcbaa3 100644 --- a/packages/core/src/highlevel/updates/parsed.ts +++ b/packages/core/src/highlevel/updates/parsed.ts @@ -1,5 +1,5 @@ import { Message } from '../types/messages/index.js' -import { ParsedUpdate } from '../types/updates/index.js' +import { BusinessMessage, ParsedUpdate } from '../types/updates/index.js' import { _parseUpdate } from '../types/updates/parse-update.js' import { RawUpdateHandler } from './types.js' @@ -54,10 +54,11 @@ export function makeParsedUpdateHandler(params: ParsedUpdateHandlerParams): RawU onRawUpdate(update, peers) if (parsed) { - if (parsed.name === 'new_message') { + if (parsed.name === 'new_message' || parsed.name === 'new_business_message') { const group = parsed.data.groupedIdUnique if (group) { + const isBusiness = parsed.name === 'new_business_message' const pendingGroup = pending.get(group) if (pendingGroup) { @@ -66,7 +67,12 @@ export function makeParsedUpdateHandler(params: ParsedUpdateHandlerParams): RawU const messages = [parsed.data] const timeout = setTimeout(() => { pending.delete(group) - onUpdate({ name: 'message_group', data: messages }) + + if (isBusiness) { + onUpdate({ name: 'business_message_group', data: messages as BusinessMessage[] }) + } else { + onUpdate({ name: 'message_group', data: messages }) + } }, messageGroupingInterval) pending.set(group, [messages, timeout]) diff --git a/packages/core/src/network/network-manager.ts b/packages/core/src/network/network-manager.ts index df4e1d7d..86ad70c2 100644 --- a/packages/core/src/network/network-manager.ts +++ b/packages/core/src/network/network-manager.ts @@ -173,6 +173,15 @@ export interface RpcCallOptions { */ throw503?: boolean + /** + * Whether the `X_MIGRATE_%d` errors should be handled locally on request level + * instead of changing the default datacenter for the entire client. + * + * Useful for `invokeWithBusinessConnection`, as it returns a `USER_MIGRATE_%d` error + * that is in fact not related to the user, but to the specific request. + */ + localMigrate?: boolean + /** * Some requests should be processed consecutively, and not in parallel. * Using the same `chainId` for multiple requests will ensure that they are processed in the order @@ -807,10 +816,15 @@ export class NetworkManager { if (manager === this._primaryDc) { if (e.is('PHONE_MIGRATE_%d') || e.is('NETWORK_MIGRATE_%d') || e.is('USER_MIGRATE_%d')) { - this._log.info('Migrate error, new dc = %d', e.newDc) + if (params?.localMigrate) { + manager = await this._getOtherDc(e.newDc) + } else { + this._log.info('Migrate error, new dc = %d', e.newDc) + + await this.changePrimaryDc(e.newDc) + manager = this._primaryDc! + } - await this.changePrimaryDc(e.newDc) - manager = this._primaryDc! multi = manager[kind] continue diff --git a/packages/dispatcher/scripts/generate.cjs b/packages/dispatcher/scripts/generate.cjs index 59b6f58c..197b4ecb 100644 --- a/packages/dispatcher/scripts/generate.cjs +++ b/packages/dispatcher/scripts/generate.cjs @@ -1,4 +1,4 @@ -const { types, toSentence, replaceSections, formatFile } = require('../../mtcute/scripts/generate-updates.cjs') +const { types, toSentence, replaceSections, formatFile } = require('../../core/scripts/generate-updates.cjs') function generateHandler() { const lines = [] diff --git a/packages/dispatcher/src/context/business-message.ts b/packages/dispatcher/src/context/business-message.ts new file mode 100644 index 00000000..92ff060d --- /dev/null +++ b/packages/dispatcher/src/context/business-message.ts @@ -0,0 +1,187 @@ +import { BusinessMessage, OmitInputMessageId, ParametersSkip1 } from '@mtcute/core' +import { TelegramClient } from '@mtcute/core/client.js' +import { + DeleteMessagesParams, + ForwardMessageOptions, + SendCopyGroupParams, + SendCopyParams, +} from '@mtcute/core/methods.js' + +import { UpdateContext } from './base.js' + +/** + * Context of a business message related update. + * + * This is a subclass of {@link BusinessMessage}, 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 BusinessMessageContext#messages}. + */ +export class BusinessMessageContext extends BusinessMessage implements UpdateContext { + // this is primarily for proper types in filters, so don't bother much with actual value + readonly _name = 'new_business_message' + + /** + * List of messages in the message group. + * + * For other updates, this is a list with a single element (`this`). + */ + readonly messages: BusinessMessageContext[] + + /** Whether this update is about a message group */ + readonly isMessageGroup: boolean + + constructor( + readonly client: TelegramClient, + message: BusinessMessage | BusinessMessage[], + ) { + const msg = Array.isArray(message) ? message[message.length - 1] : message + super(msg.update, msg._peers) + + this.messages = Array.isArray(message) ? message.map((it) => new BusinessMessageContext(client, it)) : [this] + this.isMessageGroup = Array.isArray(message) + } + + /** 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) { + const [send, params_ = {}] = params + params_.businessConnectionId = this.update.connectionId + + return this.client.answerText(this, send, params_) + } + + /** Send a media to the same chat (and topic, if applicable) as a given message */ + answerMedia(...params: ParametersSkip1) { + const [send, params_ = {}] = params + params_.businessConnectionId = this.update.connectionId + + return this.client.answerMedia(this, send, params_) + } + + /** Send a media group to the same chat (and topic, if applicable) as a given message */ + answerMediaGroup(...params: ParametersSkip1) { + const [send, params_ = {}] = params + params_.businessConnectionId = this.update.connectionId + + return this.client.answerMediaGroup(this, send, params_) + } + + /** Send a text message in reply to this message */ + replyText(...params: ParametersSkip1) { + const [send, params_ = {}] = params + params_.businessConnectionId = this.update.connectionId + + return this.client.replyText(this, send, params_) + } + + /** Send a media in reply to this message */ + replyMedia(...params: ParametersSkip1) { + const [send, params_ = {}] = params + params_.businessConnectionId = this.update.connectionId + + return this.client.replyMedia(this, send, params_) + } + + /** Send a media group in reply to this message */ + replyMediaGroup(...params: ParametersSkip1) { + const [send, params_ = {}] = params + params_.businessConnectionId = this.update.connectionId + + return this.client.replyMediaGroup(this, send, params_) + } + + /** Send a text message in reply to this message */ + quoteWithText(params: Parameters[1]) { + params.businessConnectionId = this.update.connectionId + + return this.client.quoteWithText(this, params) + } + + /** Send a media in reply to this message */ + quoteWithMedia(params: Parameters[1]) { + params.businessConnectionId = this.update.connectionId + + return this.client.quoteWithMedia(this, params) + } + + /** Send a media group in reply to this message */ + quoteWithMediaGroup(params: Parameters[1]) { + params.businessConnectionId = this.update.connectionId + + return this.client.quoteWithMediaGroup(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[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[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[0]>) { + return this.client.sendReaction({ + chatId: this.chat.inputPeer, + message: this.id, + ...params, + }) + } +} diff --git a/packages/dispatcher/src/context/parse.ts b/packages/dispatcher/src/context/parse.ts index d382f64c..e3bf707e 100644 --- a/packages/dispatcher/src/context/parse.ts +++ b/packages/dispatcher/src/context/parse.ts @@ -2,6 +2,7 @@ import { ParsedUpdate } from '@mtcute/core' import { TelegramClient } from '@mtcute/core/client.js' import { UpdateContextDistributed } from './base.js' +import { BusinessMessageContext } from './business-message.js' import { CallbackQueryContext } from './callback-query.js' import { ChatJoinRequestUpdateContext } from './chat-join-request.js' import { ChosenInlineResultContext } from './chosen-inline-result.js' @@ -26,6 +27,10 @@ export function _parsedUpdateToContext(client: TelegramClient, update: ParsedUpd return new ChatJoinRequestUpdateContext(client, update.data) case 'pre_checkout_query': return new PreCheckoutQueryContext(client, update.data) + case 'new_business_message': + case 'edit_business_message': + case 'business_message_group': + return new BusinessMessageContext(client, update.data) } const _update = update.data as UpdateContextDistributed diff --git a/packages/dispatcher/src/dispatcher.ts b/packages/dispatcher/src/dispatcher.ts index b896b508..fffcbe9e 100644 --- a/packages/dispatcher/src/dispatcher.ts +++ b/packages/dispatcher/src/dispatcher.ts @@ -7,8 +7,10 @@ import { BotReactionCountUpdate, BotReactionUpdate, BotStoppedUpdate, + BusinessConnection, ChatJoinRequestUpdate, ChatMemberUpdate, + DeleteBusinessMessageUpdate, DeleteMessageUpdate, DeleteStoryUpdate, HistoryReadUpdate, @@ -26,6 +28,7 @@ import { import { TelegramClient } from '@mtcute/core/client.js' import { UpdateContext } from './context/base.js' +import { BusinessMessageContext } from './context/business-message.js' import { CallbackQueryContext, ChatJoinRequestUpdateContext, @@ -43,17 +46,22 @@ import { BotReactionCountUpdateHandler, BotReactionUpdateHandler, BotStoppedHandler, + BusinessConnectionUpdateHandler, + BusinessMessageGroupHandler, CallbackQueryHandler, ChatJoinRequestHandler, ChatMemberUpdateHandler, ChosenInlineResultHandler, + DeleteBusinessMessageHandler, DeleteMessageHandler, DeleteStoryHandler, + EditBusinessMessageHandler, EditMessageHandler, HistoryReadHandler, InlineCallbackQueryHandler, InlineQueryHandler, MessageGroupHandler, + NewBusinessMessageHandler, NewMessageHandler, PollUpdateHandler, PollVoteHandler, @@ -1014,9 +1022,9 @@ export class Dispatcher { if (typeof handler === 'number' || typeof handler === 'undefined') { this.addUpdateHandler( { - name, + name: name, callback: filter, - }, + } as UpdateHandler, handler, ) } else { @@ -1025,7 +1033,7 @@ export class Dispatcher { name, callback: handler, check: filter, - }, + } as UpdateHandler, group, ) } @@ -1743,5 +1751,212 @@ export class Dispatcher { this._addKnownHandler('bot_reaction_count', filter, handler, group) } + /** + * Register a business connection update handler without any filters + * + * @param handler Business connection update handler + * @param group Handler group index + */ + onBusinessConnectionUpdate(handler: BusinessConnectionUpdateHandler['callback'], group?: number): void + + /** + * Register a business connection update handler with a filter + * + * @param filter Update filter + * @param handler Business connection update handler + * @param group Handler group index + */ + onBusinessConnectionUpdate( + filter: UpdateFilter, Mod>, + handler: BusinessConnectionUpdateHandler, Mod>>['callback'], + group?: number, + ): void + + /** @internal */ + onBusinessConnectionUpdate(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('business_connection', filter, handler, group) + } + + /** + * Register a new business message handler without any filters + * + * @param handler New business message handler + * @param group Handler group index + */ + onNewBusinessMessage( + handler: NewBusinessMessageHandler< + BusinessMessageContext, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** + * Register a new business message handler with a filter + * + * @param filter Update filter + * @param handler New business message handler + * @param group Handler group index + */ + onNewBusinessMessage( + filter: UpdateFilter, + handler: NewBusinessMessageHandler< + filters.Modify, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** + * Register a new business message handler with a filter + * + * @param filter Update filter + * @param handler New business message handler + * @param group Handler group index + */ + onNewBusinessMessage( + filter: UpdateFilter, + handler: NewBusinessMessageHandler< + filters.Modify, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** @internal */ + onNewBusinessMessage(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('new_business_message', filter, handler, group) + } + + /** + * Register an edit business message handler without any filters + * + * @param handler Edit business message handler + * @param group Handler group index + */ + onEditBusinessMessage( + handler: EditBusinessMessageHandler< + BusinessMessageContext, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** + * Register an edit business message handler with a filter + * + * @param filter Update filter + * @param handler Edit business message handler + * @param group Handler group index + */ + onEditBusinessMessage( + filter: UpdateFilter, + handler: EditBusinessMessageHandler< + filters.Modify, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** + * Register an edit business message handler with a filter + * + * @param filter Update filter + * @param handler Edit business message handler + * @param group Handler group index + */ + onEditBusinessMessage( + filter: UpdateFilter, + handler: EditBusinessMessageHandler< + filters.Modify, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** @internal */ + onEditBusinessMessage(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('edit_business_message', filter, handler, group) + } + + /** + * Register a business message group handler without any filters + * + * @param handler Business message group handler + * @param group Handler group index + */ + onBusinessMessageGroup( + handler: BusinessMessageGroupHandler< + BusinessMessageContext, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** + * Register a business message group handler with a filter + * + * @param filter Update filter + * @param handler Business message group handler + * @param group Handler group index + */ + onBusinessMessageGroup( + filter: UpdateFilter, + handler: BusinessMessageGroupHandler< + filters.Modify, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** + * Register a business message group handler with a filter + * + * @param filter Update filter + * @param handler Business message group handler + * @param group Handler group index + */ + onBusinessMessageGroup( + filter: UpdateFilter, + handler: BusinessMessageGroupHandler< + filters.Modify, + State extends never ? never : UpdateState + >['callback'], + group?: number, + ): void + + /** @internal */ + onBusinessMessageGroup(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('business_message_group', filter, handler, group) + } + + /** + * Register a delete business message handler without any filters + * + * @param handler Delete business message handler + * @param group Handler group index + */ + onDeleteBusinessMessage(handler: DeleteBusinessMessageHandler['callback'], group?: number): void + + /** + * Register a delete business message handler with a filter + * + * @param filter Update filter + * @param handler Delete business message handler + * @param group Handler group index + */ + onDeleteBusinessMessage( + filter: UpdateFilter, Mod>, + handler: DeleteBusinessMessageHandler< + filters.Modify, Mod> + >['callback'], + group?: number, + ): void + + /** @internal */ + onDeleteBusinessMessage(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('delete_business_message', filter, handler, group) + } + // end-codegen } diff --git a/packages/dispatcher/src/filters/bots.ts b/packages/dispatcher/src/filters/bots.ts index bf6f3d9a..fd61ea50 100644 --- a/packages/dispatcher/src/filters/bots.ts +++ b/packages/dispatcher/src/filters/bots.ts @@ -1,5 +1,6 @@ import { MaybeArray, MaybePromise, Message } from '@mtcute/core' +import { BusinessMessageContext } from '../context/business-message.js' import { MessageContext } from '../context/message.js' import { chat } from './chat.js' import { and, or } from './logic.js' @@ -30,7 +31,7 @@ export const command = ( prefixes?: MaybeArray | null caseSensitive?: boolean } = {}, -): UpdateFilter => { +): UpdateFilter => { if (!Array.isArray(commands)) commands = [commands] if (!caseSensitive) { @@ -51,7 +52,7 @@ export const command = ( const _prefixes = prefixes - const check = (msg: MessageContext): MaybePromise => { + const check = (msg: MessageContext | BusinessMessageContext): MaybePromise => { if (msg.isMessageGroup) return check(msg.messages[0]) for (const pref of _prefixes) { @@ -107,8 +108,10 @@ export const start = and(chat('private'), command('start')) export const startGroup = and(or(chat('supergroup'), chat('group')), command('start')) const deeplinkBase = - (base: UpdateFilter) => - (params: MaybeArray): UpdateFilter => { + (base: UpdateFilter) => + ( + params: MaybeArray, + ): UpdateFilter => { if (!Array.isArray(params)) { return and(start, (_msg: Message) => { const msg = _msg as Message & { command: string[] } diff --git a/packages/dispatcher/src/filters/chat.ts b/packages/dispatcher/src/filters/chat.ts index d482c38f..f00789f3 100644 --- a/packages/dispatcher/src/filters/chat.ts +++ b/packages/dispatcher/src/filters/chat.ts @@ -3,6 +3,7 @@ import { Chat, ChatMemberUpdate, ChatType, + DeleteBusinessMessageUpdate, HistoryReadUpdate, MaybeArray, Message, @@ -58,6 +59,7 @@ export const chatId: { | HistoryReadUpdate | PollVoteUpdate | BotChatJoinRequestUpdate + | DeleteBusinessMessageUpdate >> } = (id) => { const indexId = new Set() diff --git a/packages/dispatcher/src/filters/group.ts b/packages/dispatcher/src/filters/group.ts index 06774ff4..a9ee5056 100644 --- a/packages/dispatcher/src/filters/group.ts +++ b/packages/dispatcher/src/filters/group.ts @@ -1,5 +1,6 @@ import { MaybePromise, Message } from '@mtcute/core' +import { BusinessMessageContext } from '../context/business-message.js' import { MessageContext } from '../context/message.js' import { Modify, UpdateFilter } from './types.js' @@ -15,9 +16,9 @@ import { Modify, UpdateFilter } from './types.js' export function every( filter: UpdateFilter, ): UpdateFilter< - MessageContext, + MessageContext | BusinessMessageContext, Mod & { - messages: Modify[] + messages: Modify[] }, State > { @@ -61,7 +62,7 @@ export function some( // eslint-disable-next-line filter: UpdateFilter, // eslint-disable-next-line -): UpdateFilter { +): UpdateFilter { return (ctx, state) => { let i = 0 const upds = ctx.messages diff --git a/packages/dispatcher/src/filters/message.ts b/packages/dispatcher/src/filters/message.ts index 14f700ec..57ea1be2 100644 --- a/packages/dispatcher/src/filters/message.ts +++ b/packages/dispatcher/src/filters/message.ts @@ -18,6 +18,7 @@ import { Video, } from '@mtcute/core' +import { BusinessMessageContext } from '../context/business-message.js' import { MessageContext } from '../index.js' import { Modify, UpdateFilter } from './types.js' @@ -237,14 +238,16 @@ export const sender = export const replyTo = ( filter?: UpdateFilter, - ): UpdateFilter Promise }, State> => + ): UpdateFilter Promise }, State> => async (msg, state) => { if (!msg.replyToMessage?.id) return false - const reply = await msg.getReplyTo() + const reply = msg._name === 'new_message' ? await msg.getReplyTo() : msg.replyTo if (!reply) return false - msg.getReplyTo = () => Promise.resolve(reply) + if (msg._name === 'new_message') { + msg.getReplyTo = () => Promise.resolve(reply) + } if (!filter) return true diff --git a/packages/dispatcher/src/filters/user.ts b/packages/dispatcher/src/filters/user.ts index 4030e63e..a0420f13 100644 --- a/packages/dispatcher/src/filters/user.ts +++ b/packages/dispatcher/src/filters/user.ts @@ -83,7 +83,9 @@ export const userId: { return (upd) => { switch (upd._name) { case 'new_message': - case 'edit_message': { + case 'edit_message': + case 'new_business_message': + case 'edit_business_message': { const sender = upd.sender return (matchSelf && sender.isSelf) || diff --git a/packages/dispatcher/src/handler.ts b/packages/dispatcher/src/handler.ts index eb4aaad8..01e2c707 100644 --- a/packages/dispatcher/src/handler.ts +++ b/packages/dispatcher/src/handler.ts @@ -2,8 +2,10 @@ import { BotReactionCountUpdate, BotReactionUpdate, BotStoppedUpdate, + BusinessConnection, ChatJoinRequestUpdate, ChatMemberUpdate, + DeleteBusinessMessageUpdate, DeleteMessageUpdate, DeleteStoryUpdate, HistoryReadUpdate, @@ -19,6 +21,7 @@ import { import { TelegramClient } from '@mtcute/core/client.js' import { UpdateContext } from './context/base.js' +import { BusinessMessageContext } from './context/business-message.js' import { CallbackQueryContext, ChatJoinRequestUpdateContext, @@ -89,6 +92,29 @@ export type BotReactionCountUpdateHandler +export type BusinessConnectionUpdateHandler> = ParsedUpdateHandler< + 'business_connection', + T +> +export type NewBusinessMessageHandler = ParsedUpdateHandler< + 'new_business_message', + T, + S +> +export type EditBusinessMessageHandler = ParsedUpdateHandler< + 'edit_business_message', + T, + S +> +export type BusinessMessageGroupHandler = ParsedUpdateHandler< + 'business_message_group', + T, + S +> +export type DeleteBusinessMessageHandler> = ParsedUpdateHandler< + 'delete_business_message', + T +> export type UpdateHandler = | RawUpdateHandler @@ -114,5 +140,10 @@ export type UpdateHandler = | DeleteStoryHandler | BotReactionUpdateHandler | BotReactionCountUpdateHandler + | BusinessConnectionUpdateHandler + | NewBusinessMessageHandler + | EditBusinessMessageHandler + | BusinessMessageGroupHandler + | DeleteBusinessMessageHandler // end-codegen diff --git a/packages/dispatcher/src/state/key.ts b/packages/dispatcher/src/state/key.ts index 713c51d9..4b63838a 100644 --- a/packages/dispatcher/src/state/key.ts +++ b/packages/dispatcher/src/state/key.ts @@ -1,5 +1,6 @@ import { assertNever, MaybePromise, Peer } from '@mtcute/core' +import { BusinessMessageContext } from '../context/business-message.js' import { CallbackQueryContext, MessageContext } from '../context/index.js' /** @@ -10,7 +11,9 @@ import { CallbackQueryContext, MessageContext } from '../context/index.js' * @param msg Message or callback from which to derive the key * @param scene Current scene UID, or `null` if none */ -export type StateKeyDelegate = (upd: MessageContext | CallbackQueryContext | Peer) => MaybePromise +export type StateKeyDelegate = ( + upd: MessageContext | BusinessMessageContext | CallbackQueryContext | Peer, +) => MaybePromise /** * Default state key delegate. @@ -29,7 +32,7 @@ export const defaultStateKeyDelegate: StateKeyDelegate = (upd): string | null => return String(upd.id) } - if (upd._name === 'new_message') { + if (upd._name === 'new_message' || upd._name === 'new_business_message') { switch (upd.chat.chatType) { case 'private': case 'bot':