diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 189b0179..88ffb0fd 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -17,12 +17,16 @@ import { startTest } from './methods/auth/start-test' import { start } from './methods/auth/start' import { answerCallbackQuery } from './methods/bots/answer-callback-query' import { answerInlineQuery } from './methods/bots/answer-inline-query' +import { deleteMyCommands } from './methods/bots/delete-my-commands' import { getCallbackAnswer } from './methods/bots/get-callback-answer' import { getGameHighScores, getInlineGameHighScores, } from './methods/bots/get-game-high-scores' +import { getMyCommands } from './methods/bots/get-my-commands' +import { _normalizeCommandScope } from './methods/bots/normalize-command-scope' import { setGameScore, setInlineGameScore } from './methods/bots/set-game-score' +import { setMyCommands } from './methods/bots/set-my-commands' import { addChatMembers } from './methods/chats/add-chat-members' import { archiveChats } from './methods/chats/archive-chats' import { banChatMember } from './methods/chats/ban-chat-member' @@ -156,6 +160,7 @@ import { IMessageEntityParser } from './parser' import { Readable } from 'stream' import { ArrayWithTotal, + BotCommands, Chat, ChatEvent, ChatInviteLink, @@ -605,6 +610,27 @@ export interface TelegramClient extends BaseTelegramClient { parseMode?: string | null } ): Promise + /** + * Delete commands for the current bot and the given scope. + * + * Does the same as passing `null` to {@link setMyCommands} + * + * Learn more about scopes in the [Bot API docs](https://core.telegram.org/bots/api#botcommandscope) + * + */ + deleteMyCommands(params?: { + /** + * Scope of the commands. + * + * Defaults to `BotScope.default_` (i.e. `botCommandScopeDefault`) + */ + scope?: tl.TypeBotCommandScope | BotCommands.IntermediateScope + + /** + * User language applied to the scope. + */ + langCode?: string + }): Promise /** * Request a callback answer from a bot, * i.e. click an inline button that contains data. @@ -663,6 +689,26 @@ export interface TelegramClient extends BaseTelegramClient { messageId: string | tl.TypeInputBotInlineMessageID, userId?: InputPeerLike ): Promise + /** + * Get a list of current bot's commands for the given command scope + * and user language. If they are not set, empty set is returned. + * + * Learn more about scopes in the [Bot API docs](https://core.telegram.org/bots/api#botcommandscope) + * + */ + getMyCommands(params?: { + /** + * Scope of the commands. + * + * Defaults to `BotScope.default_` (i.e. `botCommandScopeDefault`) + */ + scope?: tl.TypeBotCommandScope | BotCommands.IntermediateScope + + /** + * User language applied to the scope. + */ + langCode?: string + }): Promise /** * Set a score of a user in a game * @@ -719,6 +765,32 @@ export interface TelegramClient extends BaseTelegramClient { force?: boolean } ): Promise + /** + * Set or delete commands for the current bot and the given scope + * + * Learn more about scopes in the [Bot API docs](https://core.telegram.org/bots/api#botcommandscope) + * + */ + setMyCommands(params: { + /** + * New list of bot commands for the given scope. + * + * Pass empty array or `null` to delete them. + */ + commands: tl.RawBotCommand[] | null + + /** + * Scope of the commands. + * + * Defaults to `BotScope.default_` (i.e. `botCommandScopeDefault`) + */ + scope?: tl.TypeBotCommandScope | BotCommands.IntermediateScope + + /** + * User language applied to the scope. + */ + langCode?: string + }): Promise /** * Add new members to a group, supergroup or channel. * @@ -3133,11 +3205,15 @@ export class TelegramClient extends BaseTelegramClient { start = start answerCallbackQuery = answerCallbackQuery answerInlineQuery = answerInlineQuery + deleteMyCommands = deleteMyCommands getCallbackAnswer = getCallbackAnswer getGameHighScores = getGameHighScores getInlineGameHighScores = getInlineGameHighScores + getMyCommands = getMyCommands + protected _normalizeCommandScope = _normalizeCommandScope setGameScore = setGameScore setInlineGameScore = setInlineGameScore + setMyCommands = setMyCommands addChatMembers = addChatMembers archiveChats = archiveChats banChatMember = banChatMember diff --git a/packages/client/src/methods/_imports.ts b/packages/client/src/methods/_imports.ts index 38276a75..413bd20a 100644 --- a/packages/client/src/methods/_imports.ts +++ b/packages/client/src/methods/_imports.ts @@ -39,6 +39,7 @@ import { ChatsIndex, GameHighScore, ArrayWithTotal, + BotCommands } from '../types' // @copy diff --git a/packages/client/src/methods/bots/delete-my-commands.ts b/packages/client/src/methods/bots/delete-my-commands.ts new file mode 100644 index 00000000..aaa653a1 --- /dev/null +++ b/packages/client/src/methods/bots/delete-my-commands.ts @@ -0,0 +1,41 @@ +import { TelegramClient } from '../../client' +import { tl } from '@mtcute/tl' +import { BotCommands } from '../../types' + +/** + * Delete commands for the current bot and the given scope. + * + * Does the same as passing `null` to {@link setMyCommands} + * + * Learn more about scopes in the [Bot API docs](https://core.telegram.org/bots/api#botcommandscope) + * + * @internal + */ +export async function deleteMyCommands( + this: TelegramClient, + params?: { + /** + * Scope of the commands. + * + * Defaults to `BotScope.default_` (i.e. `botCommandScopeDefault`) + */ + scope?: tl.TypeBotCommandScope | BotCommands.IntermediateScope + + /** + * User language applied to the scope. + */ + langCode?: string + } +): Promise { + const scope: tl.TypeBotCommandScope = params?.scope + ? await this._normalizeCommandScope(params.scope) + : { + _: 'botCommandScopeDefault', + } + + await this.call({ + _: 'bots.resetBotCommands', + scope, + langCode: params?.langCode ?? '', + }) +} diff --git a/packages/client/src/methods/bots/get-my-commands.ts b/packages/client/src/methods/bots/get-my-commands.ts new file mode 100644 index 00000000..f4cf5c4b --- /dev/null +++ b/packages/client/src/methods/bots/get-my-commands.ts @@ -0,0 +1,38 @@ +import { TelegramClient } from '../../client' +import { tl } from '@mtcute/tl' +import { BotCommands } from '../../types' + +/** + * Get a list of current bot's commands for the given command scope + * and user language. If they are not set, empty set is returned. + * + * Learn more about scopes in the [Bot API docs](https://core.telegram.org/bots/api#botcommandscope) + * + * @internal + */ +export async function getMyCommands( + this: TelegramClient, + params?: { + /** + * Scope of the commands. + * + * Defaults to `BotScope.default_` (i.e. `botCommandScopeDefault`) + */ + scope?: tl.TypeBotCommandScope | BotCommands.IntermediateScope + + /** + * User language applied to the scope. + */ + langCode?: string + } +): Promise { + return this.call({ + _: 'bots.getBotCommands', + scope: params?.scope + ? await this._normalizeCommandScope(params.scope) + : { + _: 'botCommandScopeDefault', + }, + langCode: params?.langCode ?? '', + }) +} diff --git a/packages/client/src/methods/bots/normalize-command-scope.ts b/packages/client/src/methods/bots/normalize-command-scope.ts new file mode 100644 index 00000000..a63fd19b --- /dev/null +++ b/packages/client/src/methods/bots/normalize-command-scope.ts @@ -0,0 +1,37 @@ +import { tl } from '@mtcute/tl' +import { BotCommands, MtCuteInvalidPeerTypeError } from '../../types' +import { TelegramClient } from '../../client' +import { normalizeToInputUser } from '../../utils/peer-utils' + +/** @internal */ +export async function _normalizeCommandScope( + this: TelegramClient, + scope: tl.TypeBotCommandScope | BotCommands.IntermediateScope +): Promise { + if (tl.isAnyBotCommandScope(scope)) return scope + + switch (scope.type) { + case 'peer': + case 'peer_admins': { + const peer = await this.resolvePeer(scope.peer) + + return { + _: scope.type === 'peer' ? 'botCommandScopePeer' : 'botCommandScopePeerAdmins', + peer + } + } + case 'member': { + const chat = await this.resolvePeer(scope.chat) + const user = normalizeToInputUser(await this.resolvePeer(scope.user)) + + if (!user) + throw new MtCuteInvalidPeerTypeError(scope.user, 'user') + + return { + _: 'botCommandScopePeerUser', + peer: chat, + userId: user + } + } + } +} diff --git a/packages/client/src/methods/bots/set-my-commands.ts b/packages/client/src/methods/bots/set-my-commands.ts new file mode 100644 index 00000000..a13298e2 --- /dev/null +++ b/packages/client/src/methods/bots/set-my-commands.ts @@ -0,0 +1,55 @@ +import { TelegramClient } from '../../client' +import { tl } from '@mtcute/tl' +import { BotCommands } from '../../types' + +/** + * Set or delete commands for the current bot and the given scope + * + * Learn more about scopes in the [Bot API docs](https://core.telegram.org/bots/api#botcommandscope) + * + * @internal + */ +export async function setMyCommands( + this: TelegramClient, + params: { + /** + * New list of bot commands for the given scope. + * + * Pass empty array or `null` to delete them. + */ + commands: tl.RawBotCommand[] | null + + /** + * Scope of the commands. + * + * Defaults to `BotScope.default_` (i.e. `botCommandScopeDefault`) + */ + scope?: tl.TypeBotCommandScope | BotCommands.IntermediateScope + + /** + * User language applied to the scope. + */ + langCode?: string + } +): Promise { + const scope: tl.TypeBotCommandScope = params.scope + ? await this._normalizeCommandScope(params.scope) + : { + _: 'botCommandScopeDefault', + } + + if (params.commands?.length) { + await this.call({ + _: 'bots.setBotCommands', + commands: params.commands, + scope, + langCode: params.langCode ?? '', + }) + } else { + await this.call({ + _: 'bots.resetBotCommands', + scope, + langCode: params.langCode ?? '', + }) + } +} diff --git a/packages/client/src/types/bots/command-scope.ts b/packages/client/src/types/bots/command-scope.ts new file mode 100644 index 00000000..61f5a945 --- /dev/null +++ b/packages/client/src/types/bots/command-scope.ts @@ -0,0 +1,102 @@ +import { tl } from '@mtcute/tl' +import { InputPeerLike } from '../peers' + +/** + * Helper constants and builder functions for methods + * related to bot commands. + * + * You can learn more about bot command scopes in + * [Bot API docs](https://core.telegram.org/bots/api#botcommandscope) + */ +export namespace BotCommands { + /** + * Intermediate bot scope, that is converted to + * TL type `BotCommandScope` by the respective functions. + * + * Used to avoid manually resolving peers. + */ + export type IntermediateScope = { + type: 'peer' | 'peer_admins' + peer: InputPeerLike + } | { + type: 'member' + chat: InputPeerLike + user: InputPeerLike + } + + /** + * Default commands scope. + * + * Used if no commands with a narrower scope are available. + */ + export const default_: tl.RawBotCommandScopeDefault = { + _: 'botCommandScopeDefault' + } as const + + /** + * Scope that covers all private chats + */ + export const allPrivate: tl.RawBotCommandScopeUsers = { + _: 'botCommandScopeUsers' + } as const + + /** + * Scope that covers all group chats (both legacy and supergroups) + */ + export const allGroups: tl.RawBotCommandScopeChats = { + _: 'botCommandScopeChats' + } as const + + /** + * Scope that covers all group chat administrators (both legacy and supergroups) + */ + export const allGroupAdmins: tl.RawBotCommandScopeChatAdmins = { + _: 'botCommandScopeChatAdmins' + } as const + + /** + * Scope that covers a specific peer (a single user in PMs, + * or all users of a legacy group or a supergroup) + */ + export function peer(peer: InputPeerLike): IntermediateScope { + return { + type: 'peer', + peer + } + } + + /** + * Scope that covers admins in a specific group + */ + export function groupAdmins(peer: InputPeerLike): IntermediateScope { + return { + type: 'peer_admins', + peer + } + } + + /** + * Scope that covers a specific user in a specific group + */ + export function groupMember(chat: InputPeerLike, user: InputPeerLike): IntermediateScope { + return { + type: 'member', + chat, + user + } + } + + /** + * Helper function to create a bot command object + * + * @param command Bot command (without slash) + * @param description Command description + */ + export function cmd(command: string, description: string): tl.RawBotCommand { + return { + _: 'botCommand', + command, + description + } + } +} diff --git a/packages/client/src/types/bots/index.ts b/packages/client/src/types/bots/index.ts index 3b44ff18..df7516c8 100644 --- a/packages/client/src/types/bots/index.ts +++ b/packages/client/src/types/bots/index.ts @@ -4,3 +4,4 @@ export * from './keyboards' export * from './inline-query' export * from './callback-query' export * from './game-high-score' +export * from './command-scope'