diff --git a/CHANGELOG.md b/CHANGELOG.md index 66808f8..f33499b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +# **4.14.0 - (2026-06-06)** + +## **Updates** + +- Support api `9.4`, `9.5`, `9.6` ([00268d1](https://github.com/telegramsjs/Telegramsjs/commit/00268d1a895644a8be654e86127837b6de6d0e36), [cf48906](https://github.com/telegramsjs/Telegramsjs/commit/cf4890611978f04aa28f0f18a30d78cfb910bace), [0f710dd](https://github.com/telegramsjs/Telegramsjs/commit/0f710dd7262dafb3ccbb25ebd2c2e5e0ec4f84bb)) +- **User:** removed the check due to the introduction of (Bot-to-Bot) ([371f2ee](https://github.com/telegramsjs/Telegramsjs/commit/371f2ee0ea5fbabae9de89cb57ddf67085802cfc)) + +## **Bug Fixes** + +- **LanguageCode:** `user.language` is not compatible with ISO 639-1 ([c19f0ee](https://github.com/telegramsjs/Telegramsjs/commit/c19f0eeec737cc7a1d156177bd28efad395901c5)) +- **ManagerCached:** `fullInfo` flag will be fetched instead of being taken from the cache of the regular `User` object ([aa97e17](https://github.com/telegramsjs/Telegramsjs/commit/aa97e171e52dc2c084f19b38c70bd802bf032334)) +- **Ready:** ready event no longer returns nullable `ClientUser` ([9a63d3e](https://github.com/telegramsjs/Telegramsjs/commit/9a63d3e1694e0b8d7e4062790380a8bff4b30273)) + +## **Features** + +- **PermissionManager**: deduplicate code with base `PermissionManager` class ([e838533](https://github.com/telegramsjs/Telegramsjs/commit/e838533bf983b587b5eb2ca51deee8c37a148aea), [01f459b](https://github.com/telegramsjs/Telegramsjs/commit/01f459b0533add328eae8a5ea7e9e1b543e310de)) +- **ClientUser:** replace plain fields with a `ClientCapabilities` class ([0f710dd](https://github.com/telegramsjs/Telegramsjs/commit/0f710dd7262dafb3ccbb25ebd2c2e5e0ec4f84bb)) +- **KeyboardBuilder:** `equals` deep property comparison ([07de1b9](https://github.com/telegramsjs/Telegramsjs/commit/07de1b9079c877c6c862020b9b4c5323a8b21d33)) + +## **Typings** + +- **Remove private members from typings** ([c7e6ef7](https://github.com/telegramsjs/Telegramsjs/commit/c7e6ef742613f206b7ad364e8473ad9d3f49981b)) +- **Collector:** normalize `lastCollectedAt` to `Date | null` ([f6c50ef](https://github.com/telegramsjs/Telegramsjs/commit/f6c50ef078431bda17cade31095c83fefdf408aa)) +- **KeyboardButtonRequestUsers:** correct fields `user_is_premium` ([5c98de3](https://github.com/telegramsjs/Telegramsjs/commit/5c98de38373afebf7e299e724d4e7ff42270c86a)) + +--- + # **4.13.1 - (2026-01-14)** ## **Bug Fixes** @@ -27,11 +54,11 @@ ## **Bug Fixes** -- **MediaData:** parsing inside array fields cover and thumbnail ([3a3bb6a](https://github.com/telegramsjs/Telegramsjs/commit/3a3bb6ab470191f5afbde0c0d3212de896fec3c2)) +- **MediaData:** parsing inside array fields `cover` and `thumbnail` ([3a3bb6a](https://github.com/telegramsjs/Telegramsjs/commit/3a3bb6ab470191f5afbde0c0d3212de896fec3c2)) ## **Refactor** -- **Array:** use Collection instead of Array ([8c36298](https://github.com/telegramsjs/Telegramsjs/commit/8c36298080442b93cd89941a0e591f2d377f601f)) +- **Array:** use `Collection` instead of Array ([8c36298](https://github.com/telegramsjs/Telegramsjs/commit/8c36298080442b93cd89941a0e591f2d377f601f)) - **MessageEntities:** add `languageCode` union type ([457959c](https://github.com/telegramsjs/Telegramsjs/commit/457959c6ce531ca4318fb4cf0213897e8bcf2e61)) - **Symbol.iterator:** refactored to a unified style ([846b90d](https://github.com/telegramsjs/Telegramsjs/commit/846b90df8de01bbc2ad4b1ac2c4bc3b6f8d1f459)) @@ -53,14 +80,14 @@ ## **Features** -- **TelegramClient:** resolve login timing issue with async ready handling ([39726ee](https://github.com/telegramsjs/Telegramsjs/commit/39726ee189b1575e0200763c0245179dd3ec5d62)) +- **TelegramClient:** resolve `login` timing issue with async ready handling ([39726ee](https://github.com/telegramsjs/Telegramsjs/commit/39726ee189b1575e0200763c0245179dd3ec5d62)) - **BaseManager:** allow fetch via resolved chat object ([fe95849](https://github.com/telegramsjs/Telegramsjs/commit/fe95849120b89c39d37050c1db4e747552a5bbb4)) - **BaseManager:** fetches multiple users or chats at once ([19180de](https://github.com/telegramsjs/Telegramsjs/commit/19180de54a9564417f9be5448b819bdcd69f5ce9)) ## **Typings** - **ChatManager:** correct name parameters ([a874d79](https://github.com/telegramsjs/Telegramsjs/commit/a874d7942194c44229856a8fc4111245f0113c93)) -- **BaseManager:** add UserResolvable and ChatResolvable typings ([49a57ad](https://github.com/telegramsjs/Telegramsjs/commit/49a57ad6cdb550616ef60de39e49b03b5008c69b)) +- **BaseManager:** add `UserResolvable` and `ChatResolvable` typings ([49a57ad](https://github.com/telegramsjs/Telegramsjs/commit/49a57ad6cdb550616ef60de39e49b03b5008c69b)) --- @@ -97,7 +124,6 @@ - **WorkerClient:** type in the name `worket` ([5ff4122](https://github.com/telegramsjs/Telegramsjs/commit/5ff4122d6c98ed5b5e3d2385916276aa79f3f7b1)) - **WebhookClient:** async `close` now waits for server shutdown ([e2605d8](https://github.com/telegramsjs/Telegramsjs/commit/e2605d8717a3d039b818d308d42a2f642dbaf581)) - ## **Typings** - Fix build `#private` fields ([c1dcc8d](https://github.com/telegramsjs/Telegramsjs/commit/c1dcc8d92fd3269196a3ec516d3e868f9e10b0bb)) @@ -132,7 +158,7 @@ ## **Bug Fixes** -- **tsconfig.test.json:** esModuleInterop enable ([0cb33c6](https://github.com/telegramsjs/Telegramsjs/commit/0cb33c65467b96c5a851e80435c68c309c6e1ff6)) +- **tsconfig.test.json:** `esModuleInterop` enable ([0cb33c6](https://github.com/telegramsjs/Telegramsjs/commit/0cb33c65467b96c5a851e80435c68c309c6e1ff6)) ## **Updates** @@ -176,7 +202,7 @@ ## **Updates** -- Support for API 8.1 ([8b2bb08](https://github.com/telegramsjs/Telegramsjs/commit/8b2bb080deebaf2da11cbdb88e7031c2c254d4eb)). +- **Support for API 8.1** ([8b2bb08](https://github.com/telegramsjs/Telegramsjs/commit/8b2bb080deebaf2da11cbdb88e7031c2c254d4eb)). - `removeComments` parameter (to reduce package size) ([430f34d](https://github.com/telegramsjs/Telegramsjs/commit/430f34d226144258a72b4ce495a56222d189b602)). --- @@ -186,7 +212,7 @@ ## **Bug Fixes** - **InputFile:** fixed field names and argument validation ([f05bf32](https://github.com/telegramsjs/Telegramsjs/commit/f05bf32f1093c9c5fa18e169a21adea279918abb)). -- **WebhookClient:** resolved duplicate triggering of the "ready" event ([a820d97](https://github.com/telegramsjs/Telegramsjs/commit/a820d975a5cadf7a74c3df77677b1d372b382f8f)). +- **WebhookClient:** resolved duplicate triggering of the `ready` event ([a820d97](https://github.com/telegramsjs/Telegramsjs/commit/a820d975a5cadf7a74c3df77677b1d372b382f8f)). - **getStarTransactions:** typings returned value `StarTransactions` ([165eb5c](https://github.com/telegramsjs/Telegramsjs/commit/165eb5cc975c9f7202cfeb24ee9315bc24d69ae2)). - **InlineKeyboardBuilder:** at least one optional field is required ([fc38ccb](https://github.com/telegramsjs/Telegramsjs/commit/fc38ccbcd34362f24cf66046b9c728f2e01ea97b)). - **Version:** missing type annotations ([d48f78f](https://github.com/telegramsjs/Telegramsjs/commit/d48f78fd4a80cb056c0bad1ee0014447b37eae1c)). @@ -210,15 +236,15 @@ - **Array:** converted some fields to `Collections` for improved data handling of large arrays ([9fc6037](https://github.com/telegramsjs/Telegramsjs/commit/9fc6037dad2233978ba71ab132a0a6800a6b9c43)). - **Collection:** introduced `ReadonlyCollection` to prevent mutation of essential data ([73a76f4](https://github.com/telegramsjs/Telegramsjs/commit/73a76f41271deae303e5cd53bc2bbb1105950bd2)). - **TelegramClient:** `user` field now supports `null` values ([cb49cf3](https://github.com/telegramsjs/Telegramsjs/commit/cb49cf37f9b1919dba66d3562baf95acd1b9b4d4)). -- **disconnect:** now triggers even when using the error event ([86186ce](https://github.com/telegramsjs/Telegramsjs/commit/86186ce42288f7c317b186a483f7118518159692)). +- **disconnect:** now triggers even when using the `error` event ([86186ce](https://github.com/telegramsjs/Telegramsjs/commit/86186ce42288f7c317b186a483f7118518159692)). - **errorHandler:** update error handling logic ([f47e03b](https://github.com/telegramsjs/Telegramsjs/commit/f47e03bb2f12d78c8bc0a0119aca4fb893fac5ea)). - **Fetch data**: data retrieval logic ([3b978ee](https://github.com/telegramsjs/Telegramsjs/commit/3b978eed6a528d17743f584ef7688f30ad36cd1d)). -- **Prefix names:** all methods for struct classes prefixed with 'get' have been changed to 'fetch' ([5619dd1](https://github.com/telegramsjs/Telegramsjs/commit/5619dd1b87370ec6a21803e28b0ed2e1abe6ab8f)). +- **Prefix names:** all methods for struct classes prefixed with `get` have been changed to `fetch` ([5619dd1](https://github.com/telegramsjs/Telegramsjs/commit/5619dd1b87370ec6a21803e28b0ed2e1abe6ab8f)). ## **Updates** -- Support for API 8.0 ([cec95aa](https://github.com/telegramsjs/Telegramsjs/commit/cec95aad043575dbebb653a2262558e0f8aa5283)). -- **InputFile:** fix handling for download parameters to use class instances ([f32fb75](https://github.com/telegramsjs/Telegramsjs/commit/f32fb755fd564f3b224a190d8a9602a1384bd2d7)). +- **Support for API 8.0** ([cec95aa](https://github.com/telegramsjs/Telegramsjs/commit/cec95aad043575dbebb653a2262558e0f8aa5283)). +- **InputFile:** fix handling for **download** parameters to use class instances ([f32fb75](https://github.com/telegramsjs/Telegramsjs/commit/f32fb755fd564f3b224a190d8a9602a1384bd2d7)). - **InputFile:** add fallback to fetch if `filePath` is missing ([1ac66f2](https://github.com/telegramsjs/Telegramsjs/commit/1ac66f23bf65d47d8941d5d8d8d525f79f7355ab)). - Bump version of `@telegram.ts/types` to `1.18.2` ([04d68de](https://github.com/telegramsjs/Telegramsjs/commit/04d68de607fcf3a5544bc3f705b9d83df039df18)). @@ -252,7 +278,7 @@ ## **Refactor** -- Support api 7.11 ([7169314](https://github.com/telegramsjs/Telegramsjs/commit/716931437f15b5a630f6c8c162d4375e3b58bf8d)) +- **Support api 7.11** ([7169314](https://github.com/telegramsjs/Telegramsjs/commit/716931437f15b5a630f6c8c162d4375e3b58bf8d)) - Convert individual parameters to object structure ([a772144](https://github.com/telegramsjs/Telegramsjs/commit/a7721447d7450bb20eba9266944731b62f35d26b)) - `Symbol.iterator` to some classes ([e7f0193](https://github.com/telegramsjs/Telegramsjs/commit/e7f01933f06897d7f98ff4f51e98424dfdf406d5)) - Add `languageCode` type ([9d8c19f](https://github.com/telegramsjs/Telegramsjs/commit/9d8c19f7351a2b0ac00d94c99565365cff9a23f7)) diff --git a/README.md b/README.md index d83a2d7..a71f314 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

Telegramsjs



-[![Bot API](https://img.shields.io/badge/Bot%20API-v.9.3-00aced.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) +[![Bot API](https://img.shields.io/badge/Bot%20API-v.9.6-00aced.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) [![NPM Version](https://img.shields.io/npm/v/telegramsjs.svg?maxAge=3600)](https://www.npmjs.com/package/telegramsjs) [![NPM Downloads](https://img.shields.io/npm/dt/telegramsjs.svg?maxAge=3600)](https://www.npmjs.com/package/telegramsjs) @@ -50,9 +50,17 @@ Afterwards we can create a quite simple example bot: ```js // ECMAscript/TypeScript -import { TelegramClient } from "telegramsjs"; +import { + TelegramClient, + InlineKeyboardBuilder, + MarkupStyles, +} from "telegramsjs"; // CommonJS -const { TelegramClient } = require("telegramsjs"); +const { + TelegramClient, + InlineKeyboardBuilder, + MarkupStyles, +} = require("telegramsjs"); const client = new TelegramClient("TELEGRAM_BOT_TOKEN"); @@ -64,15 +72,55 @@ client.on("ready", async ({ user }) => { }, ]); + if (user.capabilities.allow("joinGroups")) { + console.log("The bot has permission to be added to other groups. 🔓"); + } + console.log(`Bot @${user.username} is the ready status!`); + }); -client.on("message", async (message) => { - if (message.content === "/start" && message.author) { - await message.reply( - `Hello ${message.author.username ? `@${message.author.username}` : message.author.firstName}!`, - ); - return; +client.on("message", async (msg) => { + if (!msg.content) return; + + if (msg.content === "/menu" || msg.content === "/start") { + const menu = new InlineKeyboardBuilder() + .text("Pay", "menu_pay", { + style: MarkupStyles.Green, + icon: "5463122435425448565", + }) + .text("Rules", "menu_rules", { + style: MarkupStyles.Danger, + icon: "5465154440287757794", + }) + .row() + .text("Home", "menu_home", { + style: MarkupStyles.Primary, + icon: "5253997076169115797", + }); + + await msg.chat.send("Panel for payment", { + replyMarkup: menu, + }); + } + + if (msg.content.startsWith("/tag")) { + if (!msg.chat.isGroup()) { + return msg.reply(`Changing your nickname is only available in groups.`); + } + + const [, newTag] = msg.content.split(" "); + + try { + await msg.member.setTag(newTag); + return msg.chat.send( + `${msg.author.firstName} Your custom tag has been changed to: ${newTag}`, + ); + } catch (err) { + condole.log(err); + + return msg.chat.send("Error when changing custom tag."); + } } }); diff --git a/package.json b/package.json index 659e48b..0cf313d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "telegramsjs", "description": "A powerful library for interacting with the Telegram Bot API", - "version": "4.13.1", + "version": "4.14.0", "main": "./dist/src/index.js", "types": "./typings/index.d.ts", "scripts": { @@ -53,7 +53,7 @@ }, "homepage": "https://docs-telegramsjs.vercel.app/", "devDependencies": { - "@telegram.ts/types": "^1.25.1", + "@telegram.ts/types": "^1.28.1", "@types/node": "^18.19.130", "@types/node-fetch": "^2.6.11", "@types/safe-compare": "^1.1.2", diff --git a/src/client/BaseClient.ts b/src/client/BaseClient.ts index 17748c2..238d515 100644 --- a/src/client/BaseClient.ts +++ b/src/client/BaseClient.ts @@ -4,7 +4,7 @@ import { Collection } from "@telegram.ts/collection"; import { UserManager } from "../managers/UserManager"; import { ChatManager } from "../managers/ChatManager"; import type { LanguageCode } from "./interfaces/Language"; -import type { MediaDataParam } from "./interfaces/Methods"; +import type { MediaDataParam, InputProfilePhoto } from "./interfaces/Methods"; import type { ClientOptions, TelegramClient } from "./TelegramClient"; import { Message, @@ -14,6 +14,7 @@ import { WebhookInfo, UserChatBoosts, UserProfilePhotos, + UserProfileAudios, BusinessConnection, ChatAdministratorRights, InputFile, @@ -65,7 +66,9 @@ function toSnakeCase>( interface EventHandlers { ready: ( - telegram: import("./TelegramClient").TelegramClient, + telegram: import("./TelegramClient").TelegramClient & { + user: typeof ClientUser; + }, ) => PossiblyAsync; disconnect: ( telegram: import("./TelegramClient").TelegramClient, @@ -153,6 +156,9 @@ interface EventHandlers { purchasedPaidMedia: ( paidMedia: import("../structures/PaidMediaPurchased").PaidMediaPurchased, ) => PossiblyAsync; + managedBotUpdated: ( + managedBotUpdated: import("../structures/ManagedBotUpdated").ManagedBotUpdated, + ) => PossiblyAsync; } type EventHandlerParameters = @@ -173,7 +179,8 @@ type EventHandlerParameters = | import("../structures/ChatJoinRequest").ChatJoinRequest | import("../structures/ChatBoostUpdated").ChatBoostUpdated | import("../structures/ChatBoostRemoved").ChatBoostRemoved - | import("../structures/PaidMediaPurchased").PaidMediaPurchased; + | import("../structures/PaidMediaPurchased").PaidMediaPurchased + | import("../structures/ManagedBotUpdated").ManagedBotUpdated; class BaseClient extends EventEmitter { public readonly rest: Rest; @@ -673,6 +680,17 @@ class BaseClient extends EventEmitter { .then((res) => new UserProfilePhotos(this, res)); } + /** Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object. */ + async getUserProfileAudios( + params: MethodParameters["getUserProfileAudios"], + ): Promise { + return this.rest + .request< + MethodsApiReturnType["getUserProfileAudios"] + >("getUserProfileAudios", toSnakeCase(params)) + .then((res) => new UserProfileAudios(this, res)); + } + /** Changes the emoji status for a given user that previously allowed the bot to manage their emoji status via the Mini App method requestEmojiStatusAccess. Returns True on success. */ async setUserEmojiStatus( params: MethodParameters["setUserEmojiStatus"], @@ -755,6 +773,16 @@ class BaseClient extends EventEmitter { >("setChatAdministratorCustomTitle", toSnakeCase(params)); } + /** Use this method to set a custom tag for a member of a supergroup. Returns True on success. */ + async setChatMemberTag( + params: MethodParameters["setChatMemberTag"], + ): Promise { + return this.rest.request( + "setChatMemberTag", + toSnakeCase(params), + ); + } + /** Use this method to ban a channel chat in a supergroup or a channel. Until the chat is unbanned, the owner of the banned chat won't be able to send messages on behalf of any of their channels. The bot must be an administrator in the supergroup or channel for this to work and must have the appropriate administrator rights. Returns True on success. */ async banChatSenderChat( chatId: number | string, @@ -861,6 +889,26 @@ class BaseClient extends EventEmitter { .then((res) => new ChatInviteLink(this, res)); } + /** Use this method to get the token of a managed bot. Returns the token as String on success. */ + async getManagedBotToken( + userId: string | number, + ): Promise { + return this.rest.request( + "getManagedBotToken", + { user_id: userId }, + ); + } + + /** Use this method to revoke the current token of a managed bot and generate a new one. Returns the new token as String on success. */ + async replaceManagedBotToken( + userId: string | number, + ): Promise { + return this.rest.request( + "replaceManagedBotToken", + { user_id: userId }, + ); + } + /** Use this method to approve a chat join get. The bot must be an administrator in the chat for this to work and must have the can_invite_users administrator right. Returns True on success. */ async approveChatJoinRequest( userId: number | string, @@ -1111,7 +1159,7 @@ class BaseClient extends EventEmitter { ); } - /** Use this method to get custom emoji stickers, which can be used as a forum topic icon by any user. Requires no parameters. Returns an Array of Sticker objects. */ + /** Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. Returns information about the created topic as a ForumTopic object. */ async getForumTopicIconStickers(): Promise< MethodsLibReturnType["getForumTopicIconStickers"] > { @@ -1380,6 +1428,25 @@ class BaseClient extends EventEmitter { .then((res) => res.short_description); } + /** Changes the profile photo of the bot. Returns True on success. */ + async setMyProfilePhoto( + photo: InputProfilePhoto, + ): Promise { + return this.rest.request( + "setMyProfilePhoto", + { photo }, + ); + } + + /** Removes the profile photo of the bot. Requires no parameters. Returns True on success. */ + async removeMyProfilePhoto(): Promise< + MethodsApiReturnType["removeMyProfilePhoto"] + > { + return this.rest.request( + "removeMyProfilePhoto", + ); + } + /** Use this method to change the bot's menu button in a private chat, or the default menu button. Returns True on success. */ async setChatMenuButton( chatId?: string | number, @@ -1978,6 +2045,17 @@ class BaseClient extends EventEmitter { .then((res) => new PreparedInlineMessage(res)); } + /** Stores a keyboard button that can be used by a user within a Mini App. Returns a PreparedKeyboardButton object. */ + async savePreparedKeyboardButton( + params: MethodParameters["savePreparedKeyboardButton"], + ): Promise { + return this.rest + .request< + MethodsApiReturnType["savePreparedKeyboardButton"] + >("savePreparedKeyboardButton", toSnakeCase(params)) + .then((res) => res.id); + } + /** Use this method to send invoices. On success, the sent Message is returned. */ async sendInvoice( params: MethodParameters["sendInvoice"], diff --git a/src/client/WorkerClient.ts b/src/client/WorkerClient.ts index 8d573f9..0ecda04 100644 --- a/src/client/WorkerClient.ts +++ b/src/client/WorkerClient.ts @@ -17,8 +17,29 @@ import { CallbackQuery } from "../structures/CallbackQuery"; import { BusinessConnection } from "../structures/business/BusinessConnection"; import { BusinessMessagesDeleted } from "../structures/business/BusinessMessagesDeleted"; import { PaidMediaPurchased } from "../structures/PaidMediaPurchased"; +import { ManagedBotUpdated } from "../structures/ManagedBotUpdated"; import type { TelegramClient } from "./TelegramClient"; +type UpdateResult = + | Message + | BusinessConnection + | BusinessMessagesDeleted + | MessageReactionUpdated + | MessageReactionCountUpdated + | InlineQuery + | ChosenInlineResult + | CallbackQuery + | ShippingQuery + | PreCheckoutQuery + | Poll + | PollAnswer + | ChatMemberUpdated + | ChatJoinRequest + | ChatBoostUpdated + | ChatBoostRemoved + | PaidMediaPurchased + | ManagedBotUpdated; + /** * Handles incoming updates from the Telegram API and routes them to the appropriate event handlers. */ @@ -33,27 +54,7 @@ class WorkerClient { * Processes an incoming update and emits the corresponding event. * @param data - The update data received from Telegram. */ - processUpdate( - data: Update, - ): - | Message - | BusinessConnection - | BusinessMessagesDeleted - | MessageReactionUpdated - | MessageReactionCountUpdated - | InlineQuery - | ChosenInlineResult - | CallbackQuery - | ShippingQuery - | PreCheckoutQuery - | Poll - | PollAnswer - | ChatMemberUpdated - | ChatJoinRequest - | ChatBoostUpdated - | ChatBoostRemoved - | PaidMediaPurchased - | undefined { + processUpdate(data: Update): UpdateResult | void { this.client.emit( Events.RawUpdate, Object.assign({}, data, { client: this.client }), @@ -71,72 +72,93 @@ class WorkerClient { return this.onChatMemberRemove(data.message!); } return this.onMessage( - data.message || data.channel_post || data.business_message, + (data.message || data.channel_post || data.business_message)!, ); } - if ("business_connection" in data) { + + if ("business_connection" in data && data.business_connection) { return this.onBusinessConnection(data.business_connection); } + if ( "edited_message" in data || "edited_channel_post" in data || "edited_business_message" in data ) { - return this.onMessageEdit( + const edited = data.edited_message || - data.edited_channel_post || - data.edited_business_message, - ); + data.edited_channel_post || + data.edited_business_message; + if (edited) return this.onMessageEdit(edited); } - if ("deleted_business_messages" in data) { + + if ("deleted_business_messages" in data && data.deleted_business_messages) { return this.onDeletedBusinessMessages(data.deleted_business_messages); } - if ("message_reaction" in data) { + + if ("message_reaction" in data && data.message_reaction) { return this.onMessageReaction(data.message_reaction); } - if ("message_reaction_count" in data) { + + if ("message_reaction_count" in data && data.message_reaction_count) { return this.onMessageReactionCount(data.message_reaction_count); } - if ("inline_query" in data) { + + if ("inline_query" in data && data.inline_query) { return this.onInlineQuery(data.inline_query); } - if ("chosen_inline_result" in data) { + + if ("chosen_inline_result" in data && data.chosen_inline_result) { return this.onChosenInlineResult(data.chosen_inline_result); } - if ("callback_query" in data) { + + if ("callback_query" in data && data.callback_query) { return this.onCallbackQuery(data.callback_query); } - if ("shipping_query" in data) { + + if ("shipping_query" in data && data.shipping_query) { return this.onShippingQuery(data.shipping_query); } - if ("pre_checkout_query" in data) { + + if ("pre_checkout_query" in data && data.pre_checkout_query) { return this.onPreCheckoutQuery(data.pre_checkout_query); } - if ("poll" in data) { + + if ("poll" in data && data.poll) { return this.onPoll(data.poll); } - if ("poll_answer" in data) { + + if ("poll_answer" in data && data.poll_answer) { return this.onPollAnswer(data.poll_answer); } - if ("my_chat_member" in data) { + + if ("my_chat_member" in data && data.my_chat_member) { return this.onMyChatMember(data.my_chat_member); } - if ("chat_member" in data) { + + if ("chat_member" in data && data.chat_member) { return this.onChatMember(data.chat_member); } - if ("chat_join_request" in data) { + + if ("chat_join_request" in data && data.chat_join_request) { return this.onChatJoinRequest(data.chat_join_request); } - if ("chat_boost" in data) { + + if ("chat_boost" in data && data.chat_boost) { return this.onChatBoost(data.chat_boost); } - if ("removed_chat_boost" in data) { + + if ("removed_chat_boost" in data && data.removed_chat_boost) { return this.onRemovedChatBoost(data.removed_chat_boost); } - if ("purchased_paid_media" in data) { + + if ("purchased_paid_media" in data && data.purchased_paid_media) { return this.onPurchasedPaidMedia(data.purchased_paid_media); } - return; + + if ("managed_bot" in data && data.managed_bot) { + return this.onManagedUpdatedBot(data.managed_bot); + } } /** @@ -144,16 +166,12 @@ class WorkerClient { * @param data - The message data. */ onMessage( - data: - | Update["message"] - | Update["channel_post"] - | Update["business_message"], - ): Message | undefined { - if (!data) return; - + data: NonNullable< + Update["message"] | Update["channel_post"] | Update["business_message"] + >, + ): Message { const message = new Message(this.client, data); this.client.emit(Events.Message, message); - return message; } @@ -162,13 +180,10 @@ class WorkerClient { * @param data - The business connection data. */ onBusinessConnection( - data: Update["business_connection"], - ): BusinessConnection | undefined { - if (!data) return; - + data: NonNullable, + ): BusinessConnection { const business = new BusinessConnection(this.client, data); this.client.emit(Events.BusinessConnection, business); - return business; } @@ -177,17 +192,15 @@ class WorkerClient { * @param data - The edited message data. */ onMessageEdit( - data: + data: NonNullable< | Update["edited_message"] | Update["edited_channel_post"] - | Update["edited_business_message"], - ): Message | undefined { - if (!data) return; - - const newMessage = new Message(this.client, data); - this.client.emit(Events.EditedMessage, newMessage); - - return newMessage; + | Update["edited_business_message"] + >, + ): Message { + const message = new Message(this.client, data); + this.client.emit(Events.EditedMessage, message); + return message; } /** @@ -195,13 +208,10 @@ class WorkerClient { * @param data - The deleted business messages data. */ onDeletedBusinessMessages( - data: Update["deleted_business_messages"], - ): BusinessMessagesDeleted | undefined { - if (!data) return; - + data: NonNullable, + ): BusinessMessagesDeleted { const businessMessage = new BusinessMessagesDeleted(this.client, data); this.client.emit(Events.DeletedBusinessMessages, businessMessage); - return businessMessage; } @@ -210,13 +220,10 @@ class WorkerClient { * @param data - The message reaction data. */ onMessageReaction( - data: Update["message_reaction"], - ): MessageReactionUpdated | undefined { - if (!data) return; - + data: NonNullable, + ): MessageReactionUpdated { const messageReaction = new MessageReactionUpdated(this.client, data); this.client.emit(Events.MessageReaction, messageReaction); - return messageReaction; } @@ -225,13 +232,10 @@ class WorkerClient { * @param data - The message reaction count data. */ onMessageReactionCount( - data: Update["message_reaction_count"], - ): MessageReactionCountUpdated | undefined { - if (!data) return; - + data: NonNullable, + ): MessageReactionCountUpdated { const messageReaction = new MessageReactionCountUpdated(this.client, data); this.client.emit(Events.MessageReactionCount, messageReaction); - return messageReaction; } @@ -239,12 +243,9 @@ class WorkerClient { * Handles incoming inline queries. * @param data - The inline query data. */ - onInlineQuery(data: Update["inline_query"]): InlineQuery | undefined { - if (!data) return; - + onInlineQuery(data: NonNullable): InlineQuery { const inline = new InlineQuery(this.client, data); this.client.emit(Events.InlineQuery, inline); - return inline; } @@ -253,13 +254,10 @@ class WorkerClient { * @param data - The chosen inline result data. */ onChosenInlineResult( - data: Update["chosen_inline_result"], - ): ChosenInlineResult | undefined { - if (!data) return; - + data: NonNullable, + ): ChosenInlineResult { const chosenInline = new ChosenInlineResult(this.client, data); this.client.emit(Events.ChosenInlineResult, chosenInline); - return chosenInline; } @@ -267,12 +265,9 @@ class WorkerClient { * Handles incoming callback queries. * @param data - The callback query data. */ - onCallbackQuery(data: Update["callback_query"]): CallbackQuery | undefined { - if (!data) return; - + onCallbackQuery(data: NonNullable): CallbackQuery { const callback = new CallbackQuery(this.client, data); this.client.emit(Events.CallbackQuery, callback); - return callback; } @@ -280,12 +275,9 @@ class WorkerClient { * Handles incoming shipping queries. * @param data - The shipping query data. */ - onShippingQuery(data: Update["shipping_query"]): ShippingQuery | undefined { - if (!data) return; - + onShippingQuery(data: NonNullable): ShippingQuery { const shipping = new ShippingQuery(this.client, data); this.client.emit(Events.ShippingQuery, shipping); - return shipping; } @@ -294,13 +286,10 @@ class WorkerClient { * @param data - The pre-checkout query data. */ onPreCheckoutQuery( - data: Update["pre_checkout_query"], - ): PreCheckoutQuery | undefined { - if (!data) return; - + data: NonNullable, + ): PreCheckoutQuery { const preCheckout = new PreCheckoutQuery(this.client, data); this.client.emit(Events.PreCheckoutQuery, preCheckout); - return preCheckout; } @@ -308,12 +297,9 @@ class WorkerClient { * Handles new polls. * @param data - The poll data. */ - onPoll(data: Update["poll"]): Poll | undefined { - if (!data) return; - + onPoll(data: NonNullable): Poll { const poll = new Poll(this.client, data); this.client.emit(Events.Poll, poll); - return poll; } @@ -321,12 +307,9 @@ class WorkerClient { * Handles new poll answers. * @param data - The poll answer data. */ - onPollAnswer(data: Update["poll_answer"]): PollAnswer | undefined { - if (!data) return; - + onPollAnswer(data: NonNullable): PollAnswer { const poll = new PollAnswer(this.client, data); this.client.emit(Events.PollAnswer, poll); - return poll; } @@ -335,13 +318,10 @@ class WorkerClient { * @param data - The chat member update data. */ onMyChatMember( - data: Update["my_chat_member"], - ): ChatMemberUpdated | undefined { - if (!data) return; - + data: NonNullable, + ): ChatMemberUpdated { const myChat = new ChatMemberUpdated(this.client, data); this.client.emit(Events.MyChatMember, myChat); - return myChat; } @@ -349,12 +329,9 @@ class WorkerClient { * Handles updates to chat members. * @param data - The chat member update data. */ - onChatMember(data: Update["chat_member"]): ChatMemberUpdated | undefined { - if (!data) return; - + onChatMember(data: NonNullable): ChatMemberUpdated { const chatMember = new ChatMemberUpdated(this.client, data); this.client.emit(Events.ChatMember, chatMember); - return chatMember; } @@ -362,43 +339,39 @@ class WorkerClient { * Handles new chat members being added. * @param data - The message data containing new chat members. */ - onChatMemberAdd(data: Update["message"]): Message | undefined { - if (!data) return; - + onChatMemberAdd(data: NonNullable): Message { const message = new Message(this.client, data); if ( - this.client.user !== null && + this.client.user && message.newChatMembers?.some(({ id }) => id === this.client.user!.id) ) { this.client.emit(Events.ChatCreate, message); - return message; } else { this.client.emit(Events.ChatMemberAdd, message); - return message; } + + return message; } /** * Handles chat members being removed. * @param data - The message data containing removed chat members. */ - onChatMemberRemove(data: Update["message"]): Message | undefined { - if (!data) return; - + onChatMemberRemove(data: NonNullable): Message { const message = new Message(this.client, data); if ( - message.leftChatMember !== undefined && - this.client.user !== null && + message.leftChatMember && + this.client.user && message.leftChatMember.id === this.client.user.id ) { this.client.emit(Events.ChatDelete, message); - return message; } else { this.client.emit(Events.ChatMemberRemove, message); - return message; } + + return message; } /** @@ -406,13 +379,10 @@ class WorkerClient { * @param data - The chat join request data. */ onChatJoinRequest( - data: Update["chat_join_request"], - ): ChatJoinRequest | undefined { - if (!data) return; - + data: NonNullable, + ): ChatJoinRequest { const chatJoin = new ChatJoinRequest(this.client, data); this.client.emit(Events.ChatJoinRequest, chatJoin); - return chatJoin; } @@ -420,12 +390,9 @@ class WorkerClient { * Handles updates to chat boosts. * @param data - The chat boost update data. */ - onChatBoost(data: Update["chat_boost"]): ChatBoostUpdated | undefined { - if (!data) return; - + onChatBoost(data: NonNullable): ChatBoostUpdated { const chatBoost = new ChatBoostUpdated(this.client, data); this.client.emit(Events.ChatBoost, chatBoost); - return chatBoost; } @@ -434,30 +401,36 @@ class WorkerClient { * @param data - The removed chat boost data. */ onRemovedChatBoost( - data: Update["removed_chat_boost"], - ): ChatBoostRemoved | undefined { - if (!data) return; - + data: NonNullable, + ): ChatBoostRemoved { const chatBoost = new ChatBoostRemoved(this.client, data); this.client.emit(Events.RemovedChatBoost, chatBoost); - return chatBoost; } /** * Handles purchased paid media. - * @param data - The purchased paid media. + * @param data - The purchased paid media data. */ onPurchasedPaidMedia( - data: Update["purchased_paid_media"], - ): PaidMediaPurchased | undefined { - if (!data) return; - + data: NonNullable, + ): PaidMediaPurchased { const paidMedia = new PaidMediaPurchased(this.client, data); this.client.emit(Events.PurchasedPaidMedia, paidMedia); - return paidMedia; } + + /** + * Handles managed bot events. + * @param data - The managed bot data. + */ + onManagedUpdatedBot( + data: NonNullable, + ): ManagedBotUpdated { + const managedBot = new ManagedBotUpdated(this.client, data); + this.client.emit(Events.ManagedBotUpdated, managedBot); + return managedBot; + } } export { WorkerClient }; diff --git a/src/client/interfaces/Checklist.ts b/src/client/interfaces/Checklist.ts index cbc638b..8e92f92 100644 --- a/src/client/interfaces/Checklist.ts +++ b/src/client/interfaces/Checklist.ts @@ -9,7 +9,7 @@ export interface InputChecklistTask { text: string; /** Mode for parsing entities in the text. See formatting options for more details. */ parse_mode?: ParseMode; - /** List of special entities that appear in the text, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, and custom_emoji entities are allowed. */ + /** List of special entities that appear in the text, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities are allowed. */ text_entities?: MessageEntity[]; } @@ -19,7 +19,7 @@ export interface InputChecklist { title: string; /** Mode for parsing entities in the title. See formatting options for more details. */ parse_mode?: ParseMode; - /** List of special entities that appear in the title, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, and custom_emoji entities are allowed. */ + /** List of special entities that appear in the text, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities are allowed. */ title_entities?: MessageEntity[]; /** List of 1-30 tasks in the checklist */ tasks: InputChecklistTask[]; diff --git a/src/client/interfaces/Markup.ts b/src/client/interfaces/Markup.ts index 55c1cf5..35c9059 100644 --- a/src/client/interfaces/Markup.ts +++ b/src/client/interfaces/Markup.ts @@ -10,6 +10,10 @@ export declare namespace InlineKeyboardButton { interface AbstractInlineKeyboardButton { /** Label text on the button */ text: string; + /** Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription. */ + icon_custom_emoji_id?: string; + /** Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used. */ + style?: "danger" | "success" | "primary"; } export interface UrlButton extends AbstractInlineKeyboardButton { /** HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id= can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings. */ @@ -151,8 +155,12 @@ export interface ReplyKeyboardMarkup { export declare namespace KeyboardButton { export interface CommonButton { - /** Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed */ + /** Text of the button. If none of the fields other than text, icon_custom_emoji_id, and style are used, it will be sent as a message when the button is pressed */ text: string; + /** Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription. */ + icon_custom_emoji_id?: string; + /** Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used. */ + style?: "danger" | "success" | "primary"; } export interface RequestUsersButton extends CommonButton { /** If specified, pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a “users_shared” service message. Available in private chats only. */ @@ -174,13 +182,17 @@ export declare namespace KeyboardButton { /** If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only. */ request_poll: KeyboardButtonPollType; } + export interface RequestManagedBotButton extends CommonButton { + /** If specified, pressing the button will ask the user to create and share a bot that will be managed by the current bot. Available for bots that enabled management of other bots in the @BotFather Mini App. Available in private chats only. */ + request_managed_bot: KeyboardButtonRequestManagedBot; + } export interface WebAppButton extends CommonButton { /** If specified, the described Web App will be launched when the button is pressed. The Web App will be able to send a “web_app_data” service message. Available in private chats only. */ web_app: WebAppInfo; } } -/** This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, String can be used instead of this object to specify the button text. */ +/** This object represents one button of the reply keyboard. At most one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button. For simple text buttons, String can be used instead of this object to specify the button text. */ export type KeyboardButton = | KeyboardButton.CommonButton | KeyboardButton.RequestUsersButton @@ -188,6 +200,7 @@ export type KeyboardButton = | KeyboardButton.RequestContactButton | KeyboardButton.RequestLocationButton | KeyboardButton.RequestPollButton + | KeyboardButton.RequestManagedBotButton | KeyboardButton.WebAppButton | string; @@ -197,6 +210,16 @@ export interface KeyboardButtonPollType { type?: "quiz" | "regular"; } +/** This object defines the parameters for the creation of a managed bot. Information about the created bot will be shared with the bot using the update managed_bot and a Message with the field managed_bot_created. */ +export interface KeyboardButtonRequestManagedBot { + /** Signed 32-bit identifier of the request. Must be unique within the message */ + request_id: number; + /** Suggested name for the bot */ + suggested_name?: string; + /** Suggested username for the bot */ + suggested_username?: string; +} + /** Upon receiving a message with this object, Telegram clients will remove the current custom keyboard and display the default letter-keyboard. By default, custom keyboards are displayed until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are hidden immediately after the user presses a button (see ReplyKeyboardMarkup). Not supported in channels and for messages sent on behalf of a Telegram Business account. */ export interface ReplyKeyboardRemove { /** Requests clients to remove the custom keyboard (user will not be able to summon this keyboard; if you want to hide the keyboard from sight but keep it accessible, use one_time_keyboard in ReplyKeyboardMarkup) */ @@ -214,7 +237,7 @@ export interface KeyboardButtonRequestUsers { /** Pass True to request bots, pass False to request regular users. If not specified, no additional restrictions are applied. */ user_is_bot?: boolean; /** Pass True to request premium users, pass False to request non-premium users. If not specified, no additional restrictions are applied. */ - user_is_remium?: boolean; + user_is_premium?: boolean; /** The maximum number of users to be selected; 1-10. Defaults to 1. */ max_quantity?: boolean; /** Pass True to request the users' first and last names */ diff --git a/src/client/interfaces/Message.ts b/src/client/interfaces/Message.ts index e3ee25c..bbce0ee 100644 --- a/src/client/interfaces/Message.ts +++ b/src/client/interfaces/Message.ts @@ -13,7 +13,7 @@ export interface User { /** User's or bot's username */ username?: string; /** IETF language tag of the user's language */ - language_code?: LanguageCode; + language_code?: string; /** True, if this user is a Telegram Premium user */ is_premium?: true; /** True, if this user added the bot to the attachment menu */ @@ -36,7 +36,7 @@ export interface LinkPreviewOptions { export declare namespace MessageEntity { interface AbstractMessageEntity { - /** Type of the entity. Currently, can be “mention” (@username), “hashtag” (#hashtag), “cashtag” ($USD), “bot_command” (/start@jobs_bot), “url” (https://telegram.org), “email” (do-not-reply@telegram.org), “phone_number” (+1-212-555-0123), “bold” (bold text), “italic” (italic text), “underline” (underlined text), “strikethrough” (strikethrough text), “spoiler” (spoiler message), “blockquote” (block quotation), “expandable_blockquote” (collapsed-by-default block quotation), “code” (monowidth string), “pre” (monowidth block), “text_link” (for clickable text URLs), “text_mention” (for users without usernames), “custom_emoji” (for inline custom emoji stickers) */ + /** Type of the entity. Currently, can be “mention” (@username), “hashtag” (#hashtag), “cashtag” ($USD), “bot_command” (/start@jobs_bot), “url” (https://telegram.org), “email” (do-not-reply@telegram.org), “phone_number” (+1-212-555-0123), “bold” (bold text), “italic” (italic text), “underline” (underlined text), “strikethrough” (strikethrough text), “spoiler” (spoiler message), “blockquote” (block quotation), “expandable_blockquote” (collapsed-by-default block quotation), “code” (monowidth string), “pre” (monowidth block), “text_link” (for clickable text URLs), “text_mention” (for users without usernames), “custom_emoji” (for inline custom emoji stickers), “date_time” (for a formatted date and time) */ type: string; /** Offset in UTF-16 code units to the start of the entity */ offset: number; @@ -59,7 +59,8 @@ export declare namespace MessageEntity { | "spoiler" | "blockquote" | "expandable_blockquote" - | "code"; + | "code" + | "date_time"; } export interface PreMessageEntity extends AbstractMessageEntity { type: "pre"; @@ -81,6 +82,13 @@ export declare namespace MessageEntity { /** For “custom_emoji” only, unique identifier of the custom emoji. Use getCustomEmojiStickers to get full information about the sticker */ custom_emoji_id: string; } + export interface DateTimeMessageEntity extends AbstractMessageEntity { + type: "date_time"; + /** For “date_time” only, the Unix time associated with the entity */ + unix_time: number; + /** For “date_time” only, the string that defines the formatting of the date and time. See date-time entity formatting for more details. */ + date_time_format: "r" | `${"w" | ""}${"d" | "D" | ""}${"t" | "T" | ""}`; + } } /** This object represents one special entity in a text message. For example, hashtags, usernames, URLs, etc. */ @@ -89,7 +97,8 @@ export type MessageEntity = | MessageEntity.CustomEmojiMessageEntity | MessageEntity.PreMessageEntity | MessageEntity.TextLinkMessageEntity - | MessageEntity.TextMentionMessageEntity; + | MessageEntity.TextMentionMessageEntity + | MessageEntity.DateTimeMessageEntity; /** This object describes the position on faces where a mask should be placed by default. */ export interface MaskPosition { @@ -121,9 +130,11 @@ export interface ReplyParameters { chat_id?: number | string; /** Identifier of the specific checklist task to be replied to */ checklist_task_id?: number; + /** Persistent identifier of the specific poll option to be replied to */ + poll_option_id?: string; /** Pass True if the message should be sent even if the specified message to be replied to is not found; can be used only for replies in the same chat and forum topic. Always True for messages sent on behalf of a business account. */ allow_sending_without_reply?: boolean; - /** Quoted part of the message to be replied to; 0-1024 characters after entities parsing. The quote must be an exact substring of the message to be replied to, including bold, italic, underline, strikethrough, spoiler, and custom_emoji entities. The message will fail to send if the quote isn't found in the original message. */ + /** Quoted part of the message to be replied to; 0-1024 characters after entities parsing. The quote must be an exact substring of the message to be replied to, including bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities. The message will fail to send if the quote isn't found in the original message. */ quote?: string; /** Mode for parsing entities in the quote. See formatting options for more details. */ quote_parse_mode?: string; diff --git a/src/client/interfaces/Methods.ts b/src/client/interfaces/Methods.ts index e304e4e..c18302e 100644 --- a/src/client/interfaces/Methods.ts +++ b/src/client/interfaces/Methods.ts @@ -29,6 +29,7 @@ import type { ForceReply, ReplyKeyboardMarkup, ReplyKeyboardRemove, + KeyboardButton, } from "./Markup"; import type { StoryArea } from "./Story"; import type { ParseMode, Update } from "@telegram.ts/types"; @@ -952,19 +953,33 @@ export type ApiMethods = { isAnonymous?: boolean; /** Poll type, “quiz” or “regular”, defaults to “regular” */ type?: "quiz" | "regular"; - /** True, if the poll allows multiple answers, ignored for polls in quiz mode, defaults to False */ + /** Pass True, if the poll allows multiple answers, defaults to False */ allowsMultipleAnswers?: boolean; - /** 0-based identifier of the correct answer option, required for polls in quiz mode */ - correctOptionId?: number; + /** Pass True, if the poll allows to change chosen answer options, defaults to False for quizzes and to True for regular polls */ + allowsRevoting?: boolean; + /** Pass True, if the poll options must be shown in random order */ + shuffleOptions?: boolean; + /** Pass True, if answer options can be added to the poll after creation; not supported for anonymous polls and quizzes */ + allowAddingOptions?: boolean; + /** Pass True, if poll results must be shown only after the poll closes */ + hideResultsUntilCloses?: boolean; + /** A list of monotonically increasing 0-based identifiers of the correct answer options, required for polls in quiz mode */ + correctOptionIds?: number[]; /** Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line feeds after entities parsing */ explanation?: string; /** Mode for parsing entities in the explanation. See formatting options for more details. */ explanationParseMode?: ParseMode; /** A list of special entities that appear in the poll explanation. It can be specified instead of explanationParseMode */ explanationEntities?: MessageEntity[]; - /** Amount of time in seconds the poll will be active after creation, 5-600. Can't be used together with closeDate. */ + /** Description of the poll to be sent, 0-1024 characters after entities parsing */ + description?: string; + /** Mode for parsing entities in the poll description. See formatting options for more details. */ + descriptionParseMode?: ParseMode; + /** A list of special entities that appear in the poll description, which can be specified instead of description_parse_mode */ + descriptionEntities?: MessageEntity[]; + /** Amount of time in seconds the poll will be active after creation, 5-2628000. Can't be used together with close_date. */ openPeriod?: number; - /** Point in time (Unix timestamp) when the poll will be automatically closed. Must be at least 5 and no more than 600 seconds in the future. Can't be used together with openPeriod. */ + /** Point in time (Unix timestamp) when the poll will be automatically closed. Must be at least 5 and no more than 2628000 seconds in the future. Can't be used together with open_period. */ closeDate?: number; /** Pass True if the poll needs to be immediately closed. This can be useful for poll preview. */ isClosed?: boolean; @@ -1119,6 +1134,16 @@ export type ApiMethods = { limit?: number; }): import("../../structures/misc/UserProfilePhotos").UserProfilePhotos; + /** Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object. */ + getUserProfileAudios(args: { + /** Unique identifier of the target user */ + userId: string | number; + /** Sequential number of the first audio to be returned. By default, all audios are returned. */ + offset?: number; + /** Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100. */ + limit?: number; + }): import("../../structures/misc/UserProfileAudios").UserProfileAudios; + /** Changes the emoji status for a given user that previously allowed the bot to manage their emoji status via the Mini App method requestEmojiStatusAccess. Returns True on success. */ setUserEmojiStatus(args: { /** Unique identifier of the target user */ @@ -1195,6 +1220,16 @@ export type ApiMethods = { customTitle: string; }): true; + /** Use this method to set a custom tag for a member of a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate administrator rights. Returns True on success. */ + setChatMemberTag(args: { + /** Unique identifier for the target supergroup (in the format @supergroupusername) */ + chatId: number | string; + /** Unique identifier of the target user */ + userId: string | number; + /** New tag for the member; 0-32 characters */ + tag?: string; + }): true; + /** Use this method to ban a channel chat in a supergroup or a channel. Until the chat is unbanned, the owner of the banned chat won't be able to send messages on behalf of any of their channels. The bot must be an administrator in the supergroup or channel for this to work and must have the appropriate administrator rights. Returns True on success. */ banChatSenderChat(args: { /** Unique identifier for the target chat or username of the target channel (in the format @channelusername) */ @@ -1289,6 +1324,18 @@ export type ApiMethods = { inviteLink: string; }): import("../../structures/chat/ChatInviteLink").ChatInviteLink; + /** Use this method to get the token of a managed bot. Returns the token as String on success. */ + getManagedBotToken(args: { + /** User identifier of the managed bot whose token will be returned */ + userId: number | string; + }): string; + + /** Use this method to revoke the current token of a managed bot and generate a new one. Returns the new token as String on success. */ + replaceManagedBotToken(args: { + /** User identifier of the managed bot whose token will be replaced */ + userId: number | string; + }): string; + /** Use this method to approve a chat join request. The bot must be an administrator in the chat for this to work and must have the can_invite_users administrator right. Returns True on success. */ approveChatJoinRequest(args: { /** Unique identifier for the target chat or username of the target channel (in the format @channelusername) */ @@ -1433,7 +1480,7 @@ export type ApiMethods = { /** Use this method to get custom emoji stickers, which can be used as a forum topic icon by any user. Requires no parameters. Returns an Array of Sticker objects. */ getForumTopicIconStickers(): import("../../structures/media/Sticker").Sticker[]; - /** Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator rights. Returns information about the created topic as a ForumTopic object. */ + /** Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. Returns information about the created topic as a ForumTopic object. */ createForumTopic(args: { /** Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) */ chatId: number | string; @@ -1675,6 +1722,15 @@ export type ApiMethods = { languageCode?: LanguageCode; }): string; + /** Changes the profile photo of the bot. Returns True on success. */ + setMyProfilePhoto(args: { + /** The new profile photo to set */ + photo: InputProfilePhoto; + }): true; + + /** Removes the profile photo of the bot. Requires no parameters. Returns True on success. */ + removeMyProfilePhoto(): true; + /** Use this method to change the bot's menu button in a private chat, or the default menu button. Returns True on success. */ setChatMenuButton(args: { /** Unique identifier for the target private chat. If not specified, default bot's menu button will be changed */ @@ -2225,9 +2281,9 @@ export type ApiMethods = { payForUpgrade?: boolean; /** Text that will be shown along with the gift; 0-255 characters */ text?: string; - /** Mode for parsing entities in the text. See formatting options for more details. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, and “custom_emoji” are ignored. */ + /** Mode for parsing entities in the text. See formatting options for more details. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, “custom_emoji”, and “date_time” are ignored. */ textParseMode?: ParseMode; - /** A list of special entities that appear in the gift text. It can be specified instead of text_parse_mode. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, and “custom_emoji” are ignored. */ + /** A list of special entities that appear in the gift text. It can be specified instead of text_parse_mode. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, “custom_emoji”, and “date_time” are ignored. */ textEntities?: MessageEntity[]; }): true; @@ -2241,9 +2297,9 @@ export type ApiMethods = { starCount: 1000 | 1500 | 2500; /** Text that will be shown along with the service message about the subscription; 0-128 characters */ text?: string; - /** Mode for parsing entities in the text. See formatting options for more details. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, and “custom_emoji” are ignored. */ + /** Mode for parsing entities in the text. See formatting options for more details. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, “custom_emoji”, and “date_time” are ignored. */ textParseMode?: ParseMode; - /** A list of special entities that appear in the gift text. It can be specified instead of text_parse_mode. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, and “custom_emoji” are ignored. */ + /** A list of special entities that appear in the gift text. It can be specified instead of text_parse_mode. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, “custom_emoji”, and “date_time” are ignored. */ textEntities?: MessageEntity[]; }): true; @@ -2298,6 +2354,17 @@ export type ApiMethods = { allowChannelChats?: boolean; }): import("../../structures/misc/PreparedInlineMessage").PreparedInlineMessage; + /** Stores a keyboard button that can be used by a user within a Mini App. Returns a PreparedKeyboardButton object. */ + savePreparedKeyboardButton(args: { + /** Unique identifier of the target user that can use the button */ + userId: number | string; + /** An object describing the button to be saved. The button must be of the type request_users, request_chat, or request_managed_bot */ + button: + | KeyboardButton.RequestUsersButton + | KeyboardButton.RequestChatButton + | KeyboardButton.RequestManagedBotButton; + }): string; + /** Use this method to send invoices. On success, the sent Message is returned. */ sendInvoice(args: { /** Unique identifier for the target chat or username of the target channel (in the format @channelusername) */ diff --git a/src/index.ts b/src/index.ts index d1aaef8..1050621 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,12 +13,15 @@ export * from "./util/inline/InlineQueryResultCachedBuilder"; export * from "./util/inline/InputMessageContentBuilder"; export * from "./util/markup/InlineKeyboardBuilder"; export * from "./util/markup/KeyboardBuilder"; +export { type MarkupOptions } from "./util/markup/utils"; export * from "./util/permission/ChatPermissions"; export * from "./util/permission/UserPermissions"; +export * from "./util/permission/ClientCapabilities"; export * from "./util/permission/BusinessPermissions"; export * from "./util/Constants"; export { flatten } from "./util/Utils"; export * from "./util/permission/ApiPermissions"; +export * from "./util/permission/PermissionManager"; export * from "./managers/BaseManager"; export * from "./managers/ChatManager"; export * from "./managers/UserManager"; diff --git a/src/managers/ChatManager.ts b/src/managers/ChatManager.ts index a9474aa..05c14ff 100644 --- a/src/managers/ChatManager.ts +++ b/src/managers/ChatManager.ts @@ -90,7 +90,7 @@ class ChatManager extends BaseManager { ): Promise { const id = this.resolveId(this.resolve(chat)); - if (!force) { + if (!force && !fullInfo) { const existing = this.cache.get(String(id)); if (existing) return existing; } diff --git a/src/managers/UserManager.ts b/src/managers/UserManager.ts index 5c961f0..372d5dc 100644 --- a/src/managers/UserManager.ts +++ b/src/managers/UserManager.ts @@ -105,7 +105,7 @@ class UserManager extends BaseManager { ): Promise { const id = this.resolveId(this.resolve(user)); - if (!force) { + if (!force && !fullInfo) { const existing = this.cache.get(String(id)); if (existing) return existing; } diff --git a/src/structures/ManagedBotUpdated.js b/src/structures/ManagedBotUpdated.js new file mode 100644 index 0000000..3363cdb --- /dev/null +++ b/src/structures/ManagedBotUpdated.js @@ -0,0 +1,46 @@ +// @ts-check +const { Base } = require("./Base"); + +/** + * @typedef {import("../types").MethodParameters} MethodParameters + */ + +class ManagedBotUpdated extends Base { + /** + * @param {import("../client/TelegramClient").TelegramClient | import("../client/BaseClient").BaseClient} client - The client that instantiated this + * @param {import("@telegram.ts/types").ManagedBotUpdated} data - Data about the creation, token update, or owner update of a bot that is managed by the current bot. + */ + constructor(client, data) { + super(client); + + /** + * User that created the bot + * @type {import("./misc/User").User} + */ + this.author = this.client.users._add(data.user); + + /** + * Information about the bot. + * @type {import("./misc/User").User} + */ + this.bot = this.client.users._add(data.bot); + } + + /** + * Use this method to get the token of a managed bot. + * @returns {Promise} the token as String on success. + */ + fetchBotToken() { + return this.client.getManagedBotToken(this.author.id); + } + + /** + * Use this method to revoke the current token of a managed bot and generate a new one. + * @returns {Promise} the new token as String on success. + */ + replaceBotToken() { + return this.client.replaceManagedBotToken(this.author.id); + } +} + +module.exports = { ManagedBotUpdated }; diff --git a/src/structures/PollAnswer.js b/src/structures/PollAnswer.js index cc6249f..69fee19 100644 --- a/src/structures/PollAnswer.js +++ b/src/structures/PollAnswer.js @@ -28,8 +28,8 @@ class PollAnswer extends Base { this.user = this.client.users._add(data.user); } - /** 0-based identifiers of chosen answer options. May be empty if the vote was retracted */ - this.ids = data.option_ids; + /** Persistent identifiers of the chosen answer options. May be empty if the vote was retracted. */ + this.persistentIds = data.option_persistent_ids; } } diff --git a/src/structures/business/BusinessConnection.js b/src/structures/business/BusinessConnection.js index 24ff773..fce2e56 100644 --- a/src/structures/business/BusinessConnection.js +++ b/src/structures/business/BusinessConnection.js @@ -235,6 +235,18 @@ class BusinessConnection extends Base { }); } + /** + * Stores a keyboard button that can be used by a user within a Mini App. + * @param {MethodParameters["savePreparedKeyboardButton"]["button"]} button - An object describing the button to be saved. The button must be of the type request_users, request_chat, or request_managed_bot. + * @returns {Promise} - Returns a unique identifier of the keyboard button. + */ + saveKeyboardButton(button) { + return this.client.savePreparedKeyboardButton({ + userId: this.userChatId, + button, + }); + } + /** * Allows the bot to cancel or re-enable extension of a subscription paid in Telegram Stars. * @param {string} telegramPaymentChargeId - Telegram payment identifier for the subscription. diff --git a/src/structures/chat/Chat.js b/src/structures/chat/Chat.js index 9779915..71b613a 100644 --- a/src/structures/chat/Chat.js +++ b/src/structures/chat/Chat.js @@ -495,7 +495,7 @@ class Chat extends Base { } /** - * Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator rights. + * Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. * @param {string} name - Topic name, 1-128 characters * @param {Omit} [options={}] - out parameters * @returns {Promise} - Returns information about the created topic as a ForumTopic object. diff --git a/src/structures/chat/ChatAdministratorRights.js b/src/structures/chat/ChatAdministratorRights.js index 558d370..ef18e72 100644 --- a/src/structures/chat/ChatAdministratorRights.js +++ b/src/structures/chat/ChatAdministratorRights.js @@ -47,6 +47,10 @@ class ChatAdministratorRights { permissions.manageTopics = data.can_manage_topics; } + if ("can_manage_tags" in data) { + permissions.manageTags = data.can_manage_tags; + } + /** Represents the rights of an administrator in a chat */ this.permissions = new ChatPermissions(permissions); } diff --git a/src/structures/chat/ChatFullInfo.js b/src/structures/chat/ChatFullInfo.js index a3213a4..d4bd017 100644 --- a/src/structures/chat/ChatFullInfo.js +++ b/src/structures/chat/ChatFullInfo.js @@ -3,6 +3,7 @@ const { Chat } = require("./Chat"); const { Photo } = require("../media/Photo"); const { Location } = require("../misc/Location"); const { Sticker } = require("../media/Sticker"); +const { Audio } = require("../media/Audio"); const { ReactionType } = require("../misc/ReactionType"); const { UserRating } = require("../misc/UserRating"); const { Message } = require("../message/Message"); @@ -101,6 +102,11 @@ class ChatFullInfo extends Chat { }; } + if ("first_profile_audio" in data && data.first_profile_audio) { + /** For private chats, the first audio added to the profile of the user */ + this.firstProfileAudio = new Audio(this.client, data.first_profile_audio); + } + if ("active_usernames" in data) { /** * The active usernames of the chat. diff --git a/src/structures/chat/ChatMember.js b/src/structures/chat/ChatMember.js index 72eba9f..e826031 100644 --- a/src/structures/chat/ChatMember.js +++ b/src/structures/chat/ChatMember.js @@ -87,6 +87,10 @@ class ChatMember extends Base { permissions.manageTopics = data.can_manage_topics; } + if ("can_manage_tags" in data) { + permissions.manageTags = data.can_manage_tags; + } + if ("can_send_messages" in data) { permissions.sendMessages = data.can_send_messages; } @@ -127,6 +131,10 @@ class ChatMember extends Base { permissions.addWebPagePreviews = data.can_add_web_page_previews; } + if ("can_edit_tag" in data) { + permissions.editTag = data.can_edit_tag; + } + /** Represents the rights of an administrator in a chat */ this.permissions = new UserPermissions( this.status === "creator" ? UserPermissions.Flags : permissions, @@ -162,6 +170,14 @@ class ChatMember extends Base { this.nickName = data.custom_title; } + if ("tag" in data) { + /** + * Custom tag for this member + * @type {string | undefined} + */ + this.tag = data.tag; + } + if ("is_member" in data) { /** * True, if the user is a member of the chat at the moment of the request @@ -335,6 +351,23 @@ class ChatMember extends Base { }); } + /** + * Use this method to set a custom tag for a member of a supergroup. + * @param {string} [tag] - New tag for the member; 0-32 characters + * @returns {Promise} - Returns True on success. + */ + setTag(tag) { + if (!this.id) { + throw new TelegramError(ErrorCodes.UserIdNotAvailable); + } + + return this.client.setChatMemberTag({ + chatId: this.chatId, + userId: this.id, + ...(tag && { tag }), + }); + } + /** * Checks if this member is equal to another member. * @param {ChatMember} other - The other object to compare with. @@ -353,6 +386,7 @@ class ChatMember extends Base { this.user.equals(other.user) && this.anonymous === other.anonymous && this.nickName === other.nickName && + this.tag === other.tag && this.isMember === other.isMember && this.untilUnixTime === other.untilUnixTime ); diff --git a/src/structures/gift/UniqueGift.js b/src/structures/gift/UniqueGift.js index a4deb6b..60ce095 100644 --- a/src/structures/gift/UniqueGift.js +++ b/src/structures/gift/UniqueGift.js @@ -47,7 +47,7 @@ class UniqueGift extends Base { name: data.model.name, /** The sticker that represents the unique gift */ sticker: new Sticker(client, data.model.sticker), - /** The number of unique gifts that receive this model for every 1000 gifts upgraded */ + /** The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts */ rarityPerMille: data.model.rarity_per_mille, }; @@ -57,7 +57,7 @@ class UniqueGift extends Base { name: data.symbol.name, /** The sticker that represents the unique gift */ sticker: new Sticker(client, data.symbol.sticker), - /** The number of unique gifts that receive this model for every 1000 gifts upgraded */ + /** The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts */ rarityPerMille: data.symbol.rarity_per_mille, }; @@ -103,6 +103,11 @@ class UniqueGift extends Base { }, }; } + + if ("is_burned" in data) { + /** True, if the gift was used to craft another gift and isn't available anymore */ + this.isBurned = data.is_burned; + } } /** @@ -123,7 +128,8 @@ class UniqueGift extends Base { isDeepStrictEqual(this.model, other.model) && isDeepStrictEqual(this.symbol, other.symbol) && isDeepStrictEqual(this.backdrop, other.backdrop) && - isDeepStrictEqual(this.colors, other.colors) + isDeepStrictEqual(this.colors, other.colors) && + this.isBurned === other.isBurned ); } } diff --git a/src/structures/index.js b/src/structures/index.js index d6b4349..a487a70 100644 --- a/src/structures/index.js +++ b/src/structures/index.js @@ -17,6 +17,8 @@ module.exports.MessageReactionUpdated = require("./MessageReactionUpdated").MessageReactionUpdated; module.exports.PaidMediaPurchased = require("./PaidMediaPurchased").PaidMediaPurchased; +module.exports.ManagedBotUpdated = + require("./ManagedBotUpdated").ManagedBotUpdated; module.exports.PollAnswer = require("./PollAnswer").PollAnswer; module.exports.PreCheckoutQuery = require("./PreCheckoutQuery").PreCheckoutQuery; @@ -111,6 +113,7 @@ module.exports.Sticker = require("./media/Sticker").Sticker; module.exports.StickerSet = require("./media/StickerSet").StickerSet; module.exports.Video = require("./media/Video").Video; module.exports.VideoNote = require("./media/VideoNote").VideoNote; +module.exports.VideoQuality = require("./media/VideoQuality").VideoQuality; module.exports.Voice = require("./media/Voice").Voice; module.exports.Message = require("./message/Message").Message; module.exports.MessageEntities = @@ -133,6 +136,8 @@ module.exports.SharedUser = require("./misc/SharedUser").SharedUser; module.exports.StarAmount = require("./misc/StarAmount").StarAmount; module.exports.TextQuote = require("./misc/TextQuote").TextQuote; module.exports.User = require("./misc/User").User; +module.exports.UserProfileAudios = + require("./misc/UserProfileAudios").UserProfileAudios; module.exports.UserProfilePhotos = require("./misc/UserProfilePhotos").UserProfilePhotos; module.exports.UsersShared = require("./misc/UsersShared").UsersShared; diff --git a/src/structures/media/Poll.js b/src/structures/media/Poll.js index 16c7b0d..291b80e 100644 --- a/src/structures/media/Poll.js +++ b/src/structures/media/Poll.js @@ -54,9 +54,13 @@ class Poll extends Base { if ("options" in data) { /** * @typedef {Object} PollOptions + * @property {string} persistentId - Unique identifier of the option, persistent on option addition and deletion. * @property {string} text - Option text, 1-100 characters * @property {MessageEntities} entities - Special entities that appear in the option text. Currently, only custom emoji entities are allowed in poll option texts - * @property {number} voterCount - Number of users that voted for this option + * @property {number} voterCount - Number of users who voted for this option; may be 0 if unknown. + * @property {import("../misc/User").User|undefined} addedUser - User who added the option; omitted if the option wasn't added by a user after poll creation. + * @property {import("../chat/Chat").Chat|undefined} addedChat - Chat that added the option; omitted if the option wasn't added by a chat after poll creation. + * @property {number|undefined} additionUnixTime - Point in time (Unix timestamp) when the option was added; omitted if the option existed in the original poll. */ /** @type {PollOptions[]} */ @@ -67,6 +71,7 @@ class Poll extends Base { /** @type {PollOptions} */ const result = {}; + result.persistentId = opts.persistent_id; result.text = opts.text; if ("text_entities" in opts) { result.entities = new MessageEntities( @@ -76,6 +81,19 @@ class Poll extends Base { ); } result.voterCount = opts.voter_count; + + if ("added_by_user" in opts) { + result.addedUser = this.client.users._add(opts.added_by_user); + } + + if ("added_by_chat" in opts) { + result.addedChat = this.client.chats._add(opts.added_by_chat); + } + + if ("addition_date" in opts) { + result.additionUnixTime = opts.addition_date; + } + options.push(result); } } @@ -87,14 +105,6 @@ class Poll extends Base { this.options = options; } - if ("correct_option_id" in data) { - /** - * 0-based identifier of the correct answer option. Available only for polls in the quiz mode, which are closed, or was sent (not forwarded) by the bot or to the private chat with the bot - * @type {number | undefined} - */ - this.correctId = data.correct_option_id; - } - if ("explanation" in data) { /** * Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-200 characters diff --git a/src/structures/media/Video.js b/src/structures/media/Video.js index 267ce7a..9789470 100644 --- a/src/structures/media/Video.js +++ b/src/structures/media/Video.js @@ -1,5 +1,7 @@ // @ts-check const { Photo } = require("./Photo"); +const { Collection } = require("@telegram.ts/collection"); +const { VideoQuality } = require("./VideoQuality"); const { InputFile } = require("../misc/InputFile"); class Video extends InputFile { @@ -43,6 +45,16 @@ class Video extends InputFile { /** MIME type of the file as defined by sender */ this.mimeType = data.mime_type; } + + if ("qualities" in data) { + /** List of available qualities of the video. */ + this.qualities = new Collection( + data.qualities.map((quality) => [ + quality.file_id, + new VideoQuality(client, quality), + ]), + ); + } } /** diff --git a/src/structures/media/VideoQuality.js b/src/structures/media/VideoQuality.js new file mode 100644 index 0000000..746a943 --- /dev/null +++ b/src/structures/media/VideoQuality.js @@ -0,0 +1,23 @@ +// @ts-check +const { InputFile } = require("../misc/InputFile"); + +class VideoQuality extends InputFile { + /** + * @param {import("../../client/TelegramClient").TelegramClient | import("../../client/BaseClient").BaseClient} client - The client that instantiated this + * @param {import("@telegram.ts/types").VideoQuality} data - Data about the represents video file of a specific quality + */ + constructor(client, data) { + super(client, data); + + /** Photo width */ + this.width = data.width; + + /** Photo height */ + this.height = data.height; + + /** Codec that was used to encode the video, for example, “h264”, “h265”, or “av01” */ + this.codec = data.codec; + } +} + +module.exports = { VideoQuality }; diff --git a/src/structures/message/Message.js b/src/structures/message/Message.js index cf760bd..c13c58a 100644 --- a/src/structures/message/Message.js +++ b/src/structures/message/Message.js @@ -161,6 +161,7 @@ class Message extends Base { this.member = new ChatMember(this.client, this.chat.id, { user: data.from, status: "member", + ...(data.sender_tag && { tag: data.sender_tag }), }); } } @@ -221,6 +222,14 @@ class Message extends Base { this.senderBusinessBot = this.client.users._add(data.sender_business_bot); } + if ("sender_tag" in data) { + /** + * For messages in channels and replies to channel messages in supergroups, the tag of the message sender + * @type {string | undefined} + */ + this.senderTag = data.sender_tag; + } + if ("forward_origin" in data) { /** * Information about the original message for forwarded messages @@ -253,6 +262,14 @@ class Message extends Base { this.checklistTaskId = data.reply_to_checklist_task_id; } + if ("reply_to_poll_option_id" in data) { + /** + * Persistent identifier of the specific poll option that is being replied to + * @type {string | undefined} + */ + this.pollOptionId = data.reply_to_poll_option_id; + } + if ("is_paid_post" in data) { /** * True, if the message is a paid post. Note that such posts must not be deleted for 24 hours to receive the payment and can't be edited. @@ -445,6 +462,80 @@ class Message extends Base { this.channelChatCreated = data.channel_chat_created; } + if ("managed_bot_created" in data) { + /** + * User created a bot that will be managed by the current bot. + * @type {import("../misc/User").User|undefined} + */ + this.managedBotCreated = this.client.users._add( + data.managed_bot_created.bot, + ); + } + + if ("poll_option_added" in data) { + /** + * @typedef {Object} PollOptionAdded + * @property {Message} [message] - Message containing the poll to which the option was added. Note that the Message object in this field will not contain the reply_to_message field even if it itself is a reply. + * @property {string} persistentId - Unique identifier of the added option. + * @property {string} text - Optional text + * @property {MessageEntities} [entities] - Special entities that appear in the option_text. + */ + + /** + * Answer option was added to a poll + * @type {PollOptionAdded|undefined} + */ + this.pollOptionAdded = { + ...(data.poll_option_added.poll_message && { + message: new Message( + this.client, + data.poll_option_added.poll_message, + ), + }), + persistentId: data.poll_option_added.option_persistent_id, + text: data.poll_option_added.option_text, + ...(data.poll_option_added.option_text_entities && { + entities: new MessageEntities( + this.client, + data.poll_option_added.option_text, + data.poll_option_added.option_text_entities, + ), + }), + }; + } + + if ("poll_option_deleted" in data) { + /** + * @typedef {Object} PollOptionDeleted + * @property {Message} [message] - Message containing the poll from which the option was deleted. Note that the Message object in this field will not contain the reply_to_message field even if it itself is a reply. + * @property {string} persistentId - Unique identifier of the deleted option. + * @property {string} text - Option text + * @property {MessageEntities} [entities] - Special entities that appear in the option_text. + */ + + /** + * Answer option was deleted from a poll + * @type {PollOptionDeleted|undefined} + */ + this.pollOptionDeleted = { + ...(data.poll_option_deleted.poll_message && { + message: new Message( + this.client, + data.poll_option_deleted.poll_message, + ), + }), + persistentId: data.poll_option_deleted.option_persistent_id, + text: data.poll_option_deleted.option_text, + ...(data.poll_option_deleted.option_text_entities && { + entities: new MessageEntities( + this.client, + data.poll_option_deleted.option_text, + data.poll_option_deleted.option_text_entities, + ), + }), + }; + } + if ("message_auto_delete_timer_changed" in data) { /** * @typedef {Object} MessageAutoDeleteTimerChanged @@ -923,6 +1014,22 @@ class Message extends Base { ); } + if ("chat_owner_left" in data && data.chat_owner_left?.new_owner) { + /** + * Service message: chat owner has left + */ + this.ownerLeft = this.client.users._add(data.chat_owner_left.new_owner); + } + + if ("chat_owner_changed" in data) { + /** + * Service message: chat owner has changed + */ + this.ownerChanged = this.client.users._add( + data.chat_owner_changed.new_owner, + ); + } + if ("suggested_post_refunded" in data) { /** * Service message: payment for a suggested post was refunded diff --git a/src/structures/message/MessageEntities.js b/src/structures/message/MessageEntities.js index 0d8cf00..f440a8e 100644 --- a/src/structures/message/MessageEntities.js +++ b/src/structures/message/MessageEntities.js @@ -186,10 +186,21 @@ class MessageEntities extends Base { ); } + /** + * Retrieves all date_time entities from the message. + * @returns {import("@telegram.ts/collection").ReadonlyCollection} A collection of date_time entities. + */ + get dateTime() { + // @ts-ignore + return this.searchEntity("date_time").filter( + (entity) => "unixTime" in entity && "timeFormat" in entity, + ); + } + /** * Searches for a specific type of entity in the message. - * @param {"mention" | "hashtag" | "cashtag" | "bot_command" | "url" | "email" | "phone_number" | "bold" | "italic" | "underline" | "strikethrough" | "spoiler" | "blockquote" | "code" | "pre" | "text_link" | "text_mention" | "custom_emoji"} searchType - The type of entity to search for. - * @returns {import("@telegram.ts/collection").ReadonlyCollection} A collection of found entities. + * @param {"mention" | "hashtag" | "cashtag" | "bot_command" | "url" | "email" | "phone_number" | "bold" | "italic" | "underline" | "strikethrough" | "spoiler" | "blockquote" | "code" | "pre" | "text_link" | "text_mention" | "custom_emoji" | "date_time"} searchType - The type of entity to search for. + * @returns {import("@telegram.ts/collection").ReadonlyCollection} A collection of found entities. */ searchEntity(searchType) { const results = new Collection(); @@ -207,6 +218,12 @@ class MessageEntities extends Base { ...("custom_emoji_id" in entity && { customEmojiId: entity.custom_emoji_id, }), + ...("unix_time" in entity && { + unixTime: entity.unix_time, + }), + ...("date_time_format" in entity && { + timeFormat: entity.date_time_format, + }), search: this.searchText.substring(offset, offset + length), }); } @@ -218,7 +235,7 @@ class MessageEntities extends Base { /** * Enables iteration over the message entities. * @returns {Generator<(SearchResult & ({ type: "mention" | "hashtag" | "cashtag" | "botCommand" | "url" | "email" | - "phoneNumber" | "bold" | "italic" | "underline" | "strikethrough" | "spoiler" | "blockquote" | "code" | { type: "pre", language?: import("../../client/interfaces/Language").LanguageCode } | { type: "text_link", url: string } | { type: "text_mention", user: User } | { type: "customEmoji", customEmojiId: string }}))>} An iterator over the message entities. + "phoneNumber" | "bold" | "italic" | "underline" | "strikethrough" | "spoiler" | "blockquote" | "code" | "dateTime" | { type: "pre", language?: import("../../client/interfaces/Language").LanguageCode } | { type: "text_link", url: string } | { type: "text_mention", user: User } | { type: "customEmoji", customEmojiId: string } | { type: "dateTime"; unixTime: number; timeFormat: "r" | `${"w" | ""}${"d" | "D" | ""}${"t" | "T" | ""}` } }))>} An iterator over the message entities. */ *[Symbol.iterator]() { /** @type {(keyof MessageEntities)[]} */ @@ -241,6 +258,7 @@ class MessageEntities extends Base { "textLink", "textMention", "customEmoji", + "dateTime", ]; const allEntities = new Collection(); diff --git a/src/structures/message/MessageOrigin.js b/src/structures/message/MessageOrigin.js index bbed51f..0feee54 100644 --- a/src/structures/message/MessageOrigin.js +++ b/src/structures/message/MessageOrigin.js @@ -310,7 +310,7 @@ class MessageOrigin extends Base { /** * Use this method to edit a checklist on behalf of a connected business account. * @param {string} businessConnectionId - Unique identifier of the business connection on behalf of which the message will be sent. - * @param {import("@telegram.ts/types").InputChecklist} checklist - An object for the new checklist. + * @param {import("../../client/interfaces/Checklist").InputChecklist} checklist - An object for the new checklist. * @param {Omit} [options] - out parameters. * @returns {Promise} - On success, the edited Message is returned. */ diff --git a/src/structures/misc/ClientUser.js b/src/structures/misc/ClientUser.js index 43f630b..129cca5 100644 --- a/src/structures/misc/ClientUser.js +++ b/src/structures/misc/ClientUser.js @@ -1,5 +1,8 @@ // @ts-check const { User } = require("./User"); +const { + ClientCapabilities, +} = require("../../util/permission/ClientCapabilities"); /** * @typedef {import("../../client/interfaces/Language").LanguageCode} LanguageCode @@ -19,23 +22,17 @@ class ClientUser extends User { /** The bot's or user's username */ this.username = data.username; - /** Indicates if the bot can be invited to groups */ - this.canJoinGroups = data.can_join_groups; - - /** Indicates if privacy mode is disabled for the bot */ - this.canReadAllMessages = data.can_read_all_group_messages; - - /** Indicates if the bot supports inline queries */ - this.inlineQueries = data.supports_inline_queries; - - /** Indicates if the bot can be connected to a Telegram Business account */ - this.connectBusiness = data.can_connect_to_business; - - /** Indicates if the bot has a main Web App */ - this.mainWebApp = data.has_main_web_app; - - /** True, if the bot has forum topic mode enabled in private chats. Returned only in getMe. */ - this.topicsEnabled = data.has_topics_enabled; + /** Represents a set of bot capabilities and provides methods to manage them. */ + this.capabilities = new ClientCapabilities({ + joinGroups: data.can_join_groups, + readAllMessages: data.can_read_all_group_messages, + inlineQueries: data.supports_inline_queries, + connectBusiness: data.can_connect_to_business, + mainWebApp: data.has_main_web_app, + topicsEnabled: data.has_topics_enabled, + userTopicCreation: data.allows_users_to_create_topics, + manageBots: data.can_manage_bots, + }); this._patch(data); } @@ -181,6 +178,23 @@ class ClientUser extends User { return this.client.getMyShortDescription(language); } + /** + * Changes the profile photo of the bot. + * @param {import("../../client/interfaces/Methods").InputProfilePhoto} photo - The new profile photo to set. + * @returns {Promise} - Returns True on success. + **/ + setProfilePhoto(photo) { + return this.client.setMyProfilePhoto(photo); + } + + /** + * Removes the profile photo of the bot. + * @returns {Promise} - Returns True on success. + **/ + removeProfilePhoto() { + return this.client.removeMyProfilePhoto(); + } + /** * Use this method to change the bot's menu button in a private chat, or the default menu button. * @param {number} [chatId] - Unique identifier for the target private chat. If not specified, default bot's menu button will be changed @@ -238,13 +252,7 @@ class ClientUser extends User { if (!super.equals(other)) return false; - return ( - this.canJoinGroups === other.canJoinGroups && - this.canReadAllMessages === other.canReadAllMessages && - this.inlineQueries === other.inlineQueries && - this.connectBusiness === other.connectBusiness && - this.mainWebApp === other.mainWebApp - ); + return this.capabilities.equals(other.capabilities); } } diff --git a/src/structures/misc/SharedUser.js b/src/structures/misc/SharedUser.js index bdc6573..f75e74e 100644 --- a/src/structures/misc/SharedUser.js +++ b/src/structures/misc/SharedUser.js @@ -134,6 +134,18 @@ class SharedUser extends Base { }); } + /** + * Stores a keyboard button that can be used by a user within a Mini App. + * @param {MethodParameters["savePreparedKeyboardButton"]["button"]} button - An object describing the button to be saved. The button must be of the type request_users, request_chat, or request_managed_bot. + * @returns {Promise} - Returns a unique identifier of the keyboard button. + */ + saveKeyboardButton(button) { + return this.client.savePreparedKeyboardButton({ + userId: this.userId, + button, + }); + } + /** * Allows the bot to cancel or re-enable extension of a subscription paid in Telegram Stars. * @param {string} telegramPaymentChargeId - Telegram payment identifier for the subscription. diff --git a/src/structures/misc/TextQuote.js b/src/structures/misc/TextQuote.js index dff4eab..11588ad 100644 --- a/src/structures/misc/TextQuote.js +++ b/src/structures/misc/TextQuote.js @@ -11,7 +11,7 @@ class TextQuote { this.text = data.text; if ("entities" in data) { - /** Special entities that appear in the quote. Currently, only bold, italic, underline, strikethrough, spoiler, and custom_emoji entities are kept in quotes. */ + /** Special entities that appear in the quote. Currently, only bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities are kept in quotes. */ this.entities = new MessageEntities(client, data.text, data.entities); } diff --git a/src/structures/misc/User.js b/src/structures/misc/User.js index 3afa992..4a1776f 100644 --- a/src/structures/misc/User.js +++ b/src/structures/misc/User.js @@ -21,9 +21,7 @@ class User extends Base { this.id = String(data.id); /** True, if this user is a bot */ - this.isBot = Boolean( - data.id == ("user" in client ? client.user?.id : 0) ? true : data.is_bot, - ); + this.isBot = data.is_bot; this._patch(data); } @@ -55,7 +53,7 @@ class User extends Base { if ("language_code" in data) { /** * IETF language tag of the user's language - * @type {LanguageCode | undefined} + * @type {string | undefined} */ this.language = data.language_code; } @@ -148,6 +146,18 @@ class User extends Base { }); } + /** + * Stores a keyboard button that can be used by a user within a Mini App. + * @param {MethodParameters["savePreparedKeyboardButton"]["button"]} button - An object describing the button to be saved. The button must be of the type request_users, request_chat, or request_managed_bot. + * @returns {Promise} - Returns a unique identifier of the keyboard button. + */ + saveKeyboardButton(button) { + return this.client.savePreparedKeyboardButton({ + userId: this.id, + button, + }); + } + /** * Allows the bot to cancel or re-enable extension of a subscription paid in Telegram Stars. * @param {string} telegramPaymentChargeId - Telegram payment identifier for the subscription. diff --git a/src/structures/misc/UserProfileAudios.js b/src/structures/misc/UserProfileAudios.js new file mode 100644 index 0000000..6b0d113 --- /dev/null +++ b/src/structures/misc/UserProfileAudios.js @@ -0,0 +1,32 @@ +// @ts-check +const { Base } = require("../Base"); +const { Audio } = require("../media/Audio"); +const { Collection } = require("@telegram.ts/collection"); + +class UserProfileAudios extends Base { + /** + * @param {import("../../client/TelegramClient").TelegramClient | import("../../client/BaseClient").BaseClient} client - The client that instantiated this + * @param {import("@telegram.ts/types").UserProfileAudios} data - Data about the audios displayed on a user's profile + */ + constructor(client, data) { + super(client); + + /** Total number of profile audios for the target user */ + this.count = data.total_count; + + /** Requested profile audios */ + this.audios = new Collection( + data.audios.map((audio) => [audio.file_id, new Audio(client, audio)]), + ); + } + + /** + * Makes the class iterable, returning each `Audio` object. + * @returns {IterableIterator