diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 24718488..d96f814b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -33,4 +33,6 @@ jobs: env: NODE_OPTIONS: "--max_old_space_size=4096" run: pnpm run lint:ci + - name: 'Circular dependencies' + run: pnpm run lint:dpdm - run: pnpm run test:all \ No newline at end of file diff --git a/.lintstagedrc.cjs b/.lintstagedrc.cjs index 51521972..0bd80c35 100644 --- a/.lintstagedrc.cjs +++ b/.lintstagedrc.cjs @@ -15,6 +15,7 @@ module.exports = { return [ `prettier --write ${filenames.join(' ')}`, `eslint -c ${eslintCiConfig} --fix ${filenames.join(' ')}`, + 'pnpm run lint:dpdm', ...[...modifiedPackages].map((pkg) => `pnpm -C packages/${pkg} run --if-present build --noEmit`) ] } diff --git a/package.json b/package.json index d6680ff9..1aeec324 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "eslint .", "lint:ci": "eslint --config .eslintrc.ci.js .", "lint:tsc": "pnpm run -r build --noEmit", + "lint:dpdm": "dpdm -T --no-warning --no-tree --exit-code circular:1 packages/*", "lint:fix": "eslint --fix .", "format": "prettier --write \"packages/**/*.ts\"", "publish-all": "node scripts/publish.js all", @@ -32,6 +33,7 @@ "@typescript-eslint/parser": "6.4.0", "chai": "4.3.7", "dotenv-flow": "3.2.0", + "dpdm": "^3.14.0", "eslint": "8.47.0", "eslint-config-prettier": "8.8.0", "eslint-import-resolver-typescript": "3.6.0", diff --git a/packages/client/package.json b/packages/client/package.json index 7d61a080..357fe69c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -6,10 +6,11 @@ "author": "Alina Sireneva ", "license": "LGPL-3.0", "main": "src/index.ts", + "module": "_esm/index.js", "scripts": { "test": "mocha -r ts-node/register \"tests/**/*.spec.ts\"", "docs": "typedoc", - "build": "tsc", + "build": "tsc | tsc -p tsconfig.cjs.json", "gen-client": "node ./scripts/generate-client.js", "gen-updates": "node ./scripts/generate-updates.js" }, diff --git a/packages/client/scripts/generate-client.js b/packages/client/scripts/generate-client.js index d0a60bd0..dac19e91 100644 --- a/packages/client/scripts/generate-client.js +++ b/packages/client/scripts/generate-client.js @@ -64,7 +64,11 @@ function visitRecursively(ast, check, callback) { } function findRawApiUsages(ast, fileName) { - // find `this.call({ _: '...', ...}) + // find `cilent.call({ _: '...', ...}) + + if (ast.kind !== ts.SyntaxKind.FunctionDeclaration) return [] + const firstParamName = ast.parameters[0]?.name?.escapedText + if (!firstParamName) return [] const usages = [] @@ -75,7 +79,11 @@ function findRawApiUsages(ast, fileName) { if (call.expression.kind !== ts.SyntaxKind.PropertyAccessExpression) return const prop = call.expression - if (prop.name.escapedText === 'call' && prop.expression.kind === ts.SyntaxKind.ThisKeyword) { + if ( + prop.name.escapedText === 'call' && + prop.expression.kind === ts.SyntaxKind.Identifier && + prop.expression.escapedText === firstParamName + ) { usages.push(call) } }, @@ -248,12 +256,6 @@ async function addSingleMethod(state, fileName) { ) } - const isPrivate = - name[0] === '_' && - name !== '_handleUpdate' && - name !== '_normalizeInputFile' && - name !== '_normalizeInputMedia' - const isExported = (stmt.modifiers || []).find((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) const isInitialize = checkForFlag(stmt, '@initialize') const aliases = (function () { @@ -281,18 +283,6 @@ async function addSingleMethod(state, fileName) { const rawApiMethods = available === null && findRawApiUsages(stmt, fileName) const dependencies = findDependencies(stmt).filter((it) => it !== name) - if (!isExported && !isPrivate) { - throwError(stmt, fileName, 'Public methods MUST be exported.') - } - - if (isExported && !checkForFlag(stmt, '@internal')) { - throwError( - isExported, - fileName, - 'Exported methods must be marked as @internal so TS compiler strips them away.', - ) - } - if (isInitialize && isExported) { throwError(isExported, fileName, 'Initialization methods must not be exported') } @@ -312,16 +302,8 @@ async function addSingleMethod(state, fileName) { const firstArg = stmt.parameters[0] - if ( - isExported && - (!firstArg || - (firstArg.type.getText() !== 'TelegramClient' && firstArg.type.getText() !== 'BaseTelegramClient')) - ) { - throwError( - firstArg || stmt.name, - fileName, - 'Exported methods must have `BaseTelegramClient` or `TelegramClient` as their first parameter', - ) + if (isExported && (!firstArg || firstArg.type.getText() !== 'BaseTelegramClient')) { + continue } // overloads @@ -334,25 +316,35 @@ async function addSingleMethod(state, fileName) { } if (isExported) { - state.methods.list.push({ - from: relPath, - name, - isPrivate, - func: stmt, - comment: getLeadingComments(stmt), - aliases, - available, - rawApiMethods, - dependencies, - overload: isOverload, - hasOverloads: hasOverloads[name] && !isOverload, - }) + const isPrivate = checkForFlag(stmt, '@internal') + const isManual = checkForFlag(stmt, '@manual') + const isNoemit = checkForFlag(stmt, '@noemit') + const shouldEmit = !isNoemit && !(isPrivate && !isOverload && !hasOverloads) - if (!(module in state.imports)) { - state.imports[module] = new Set() + if (shouldEmit) { + state.methods.list.push({ + from: relPath, + name, + isPrivate, + isManual, + isNoemit, + shouldEmit, + func: stmt, + comment: getLeadingComments(stmt), + aliases, + available, + rawApiMethods, + dependencies, + overload: isOverload, + hasOverloads: hasOverloads[name] && !isOverload, + }) + + if (!(module in state.imports)) { + state.imports[module] = new Set() + } + + state.imports[module].add(name) } - - state.imports[module].add(name) } } else if (stmt.kind === ts.SyntaxKind.InterfaceDeclaration) { if (isCopy) { @@ -484,6 +476,7 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this ({ name: origName, // isPrivate, + isManual, func, comment, aliases, @@ -531,7 +524,9 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this const generics = func.typeParameters ? `<${func.typeParameters.map((it) => it.getFullText()).join(', ')}>` : '' - const rawParams = (func.parameters || []).filter((it) => !it.type || it.type.getText() !== 'TelegramClient') + const rawParams = (func.parameters || []).filter( + (it) => !it.type || it.type.getText() !== 'BaseTelegramClient', + ) const parameters = rawParams .map((it) => { if (it.initializer) { @@ -590,7 +585,7 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this // remove @internal mark and set default values for parameters comment = comment - .replace(/^\s*\/\/+\s*@(alias|available).*$/m, '') + .replace(/^\s*\/\/+\s*@(alias|available|manual).*$/gm, '') .replace(/(\n^|\/\*)\s*\*\s*@internal.*/m, '') .replace(/((?:\n^|\/\*)\s*\*\s*@param )([^\s]+?)($|\s+)/gm, (_, pref, arg, post) => { const param = rawParams.find((it) => it.name.escapedText === arg) @@ -599,7 +594,6 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this return `${pref}[${arg}=${param._savedDefault.trim()}]${post}` }) - // insert "some text" at the end of comment before jsdoc .replace(/(?<=\/\*.*)(?=\n\s*\*\s*(?:@[a-z]+|\/))/s, () => { switch (available) { case 'user': @@ -623,8 +617,11 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this output.write(`${name}${generics}(${parameters})${returnType}\n`) } - if (!overload) { - classContents.push(`${name} = ${origName}`) + if (!overload && !isManual) { + if (hasOverloads) { + classContents.push('// @ts-expect-error .bind() kinda breaks typings for overloads') + } + classContents.push(`${name} = ${origName}.bind(null, this)`) } } }, diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 651f5434..975a9007 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -12,10 +12,9 @@ import { PartialOnly, tl, } from '@mtcute/core' -import { AsyncLock, ConditionVariable, Deque, Logger, SortedLinkedList } from '@mtcute/core/utils' import { tdFileId } from '@mtcute/file-id' -import { _onAuthorization } from './methods/auth/_initialize' +import { _onAuthorization, AuthState, getAuthState } from './methods/auth/_state' import { checkPassword } from './methods/auth/check-password' import { getPasswordHint } from './methods/auth/get-password-hint' import { logOut } from './methods/auth/log-out' @@ -88,7 +87,6 @@ import { addContact } from './methods/contacts/add-contact' import { deleteContacts } from './methods/contacts/delete-contacts' import { getContacts } from './methods/contacts/get-contacts' import { importContacts } from './methods/contacts/import-contacts' -import { _pushConversationMessage } from './methods/dialogs/_init-conversation' import { createFolder } from './methods/dialogs/create-folder' import { deleteFolder } from './methods/dialogs/delete-folder' import { editFolder } from './methods/dialogs/edit-folder' @@ -168,16 +166,17 @@ import { unpinAllMessages } from './methods/messages/unpin-all-messages' import { unpinMessage } from './methods/messages/unpin-message' import { initTakeoutSession } from './methods/misc/init-takeout-session' import { _normalizePrivacyRules } from './methods/misc/normalize-privacy-rules' +import { getParseModesState, ParseModesState } from './methods/parse-modes/_state' import { getParseMode, registerParseMode, setDefaultParseMode, unregisterParseMode, } from './methods/parse-modes/parse-modes' -import { changeCloudPassword } from './methods/pasword/change-cloud-password' -import { enableCloudPassword } from './methods/pasword/enable-cloud-password' -import { cancelPasswordEmail, resendPasswordEmail, verifyPasswordEmail } from './methods/pasword/password-email' -import { removeCloudPassword } from './methods/pasword/remove-cloud-password' +import { changeCloudPassword } from './methods/password/change-cloud-password' +import { enableCloudPassword } from './methods/password/enable-cloud-password' +import { cancelPasswordEmail, resendPasswordEmail, verifyPasswordEmail } from './methods/password/password-email' +import { removeCloudPassword } from './methods/password/remove-cloud-password' import { addStickerToSet } from './methods/stickers/add-sticker-to-set' import { createStickerSet } from './methods/stickers/create-sticker-set' import { deleteStickerFromSet } from './methods/stickers/delete-sticker-from-set' @@ -214,22 +213,15 @@ import { sendStory } from './methods/stories/send-story' import { sendStoryReaction } from './methods/stories/send-story-reaction' import { togglePeerStoriesArchived } from './methods/stories/toggle-peer-stories-archived' import { toggleStoriesPinned } from './methods/stories/toggle-stories-pinned' +import { enableUpdatesProcessing, makeParsedUpdateHandler, ParsedUpdateHandlerParams } from './methods/updates' import { - _dispatchUpdate, - _fetchUpdatesState, - _handleUpdate, - _keepAliveAction, - _loadStorage, - _onStop, - _saveStorage, - _updatesLoop, catchUp, enableRps, getCurrentRpsIncoming, getCurrentRpsProcessing, startUpdatesLoop, stopUpdatesLoop, -} from './methods/updates' +} from './methods/updates/manager' import { blockUser } from './methods/users/block-user' import { deleteProfilePhotos } from './methods/users/delete-profile-photos' import { editCloseFriends, editCloseFriendsRaw } from './methods/users/edit-close-friends' @@ -269,7 +261,6 @@ import { ChatMemberUpdate, ChatPreview, ChosenInlineResult, - Conversation, DeleteMessageUpdate, DeleteStoryUpdate, Dialog, @@ -325,84 +316,14 @@ import { UserStatusUpdate, UserTypingUpdate, } from './types' -import { RpsMeter } from './utils/rps-meter' +import { Conversation } from './types/conversation' -// from methods/_options.ts +// from methods/_init.ts interface TelegramClientOptions extends BaseTelegramClientOptions { /** - * **ADVANCED** - * - * Whether to disable no-dispatch mechanism. - * - * No-dispatch is a mechanism that allows you to call methods - * that return updates and correctly handle them, without - * actually dispatching them to the event handlers. - * - * In other words, the following code will work differently: - * ```ts - * dp.onNewMessage(console.log) - * console.log(await tg.sendText('me', 'hello')) - * ``` - * - if `disableNoDispatch` is `true`, the sent message will be - * dispatched to the event handler, thus it will be printed twice - * - if `disableNoDispatch` is `false`, the sent message will not be - * dispatched to the event handler, thus it will onlt be printed once - * - * Disabling it also may improve performance, but it's not guaranteed. - * - * @default false + * Parameters for updates manager. */ - disableNoDispatch?: boolean - - /** - * Limit of {@link resolvePeerMany} internal async pool. - * - * Higher value means more parallel requests, but also - * higher risk of getting flood-wait errors. - * Most resolves will however likely be a DB cache hit. - * - * Only change this if you know what you're doing. - * - * @default 8 - */ - resolvePeerManyPoolLimit?: number - - /** - * When non-zero, allows the library to automatically handle Telegram - * media groups (e.g. albums) in {@link MessageGroup} updates - * in a given time interval (in ms). - * - * **Note**: this does not catch messages that happen to be consecutive, - * only messages belonging to the same "media group". - * - * This will cause up to `messageGroupingInterval` delay - * in handling media group messages. - * - * This option only applies to `new_message` updates, - * and the updates being grouped **will not** be dispatched on their own. - * - * Recommended value is 250 ms. - * - * @default 0 (disabled) - */ - messageGroupingInterval?: number -} -// from methods/updates.ts -interface PendingUpdateContainer { - upd: tl.TypeUpdates - seqStart: number - seqEnd: number -} -// from methods/updates.ts -interface PendingUpdate { - update: tl.TypeUpdate - channelId?: number - pts?: number - ptsBefore?: number - qts?: number - qtsBefore?: number - timeout?: number - peers?: PeersIndex + updates?: Omit } export interface TelegramClient extends BaseTelegramClient { @@ -554,7 +475,9 @@ export interface TelegramClient extends BaseTelegramClient { */ on(name: 'delete_story', handler: (upd: DeleteStoryUpdate) => void): this - _onAuthorization(auth: tl.auth.TypeAuthorization, bot?: boolean): Promise + getAuthState(): AuthState + + _onAuthorization(auth: tl.auth.TypeAuthorization, bot?: boolean): User /** * Check your Two-Step verification password and log in * @@ -601,7 +524,6 @@ export interface TelegramClient extends BaseTelegramClient { * * The type of the code to be re-sent is specified in the `nextType` attribute of * {@link SentCode} object returned by {@link sendCode} - * * **Available**: 👤 users only * */ @@ -622,10 +544,10 @@ export interface TelegramClient extends BaseTelegramClient { * * **Available**: ✅ both users and bots * - * @param params Parameters to be passed to {@link TelegramClient.start} - * @param then Function to be called after {@link TelegramClient.start} returns + * @param params Parameters to be passed to {@link start} + * @param then Function to be called after {@link start} returns */ - run(params: Parameters[0], then?: (user: User) => void | Promise): void + run(params: Parameters[1], then?: (user: User) => void | Promise): void /** * Send the confirmation code to the given phone number * @@ -717,7 +639,6 @@ export interface TelegramClient extends BaseTelegramClient { * This method is intended for simple and fast use in automated * scripts and bots. If you are developing a custom client, * you'll probably need to use other auth methods. - * * **Available**: ✅ both users and bots * */ @@ -778,28 +699,11 @@ export interface TelegramClient extends BaseTelegramClient { * to show a GUI alert of some kind. * Defaults to `console.log`. * - * This method is called *before* {@link TelegramClient.start.params.code}. + * This method is called *before* {@link start.params.code}. * * @param code */ codeSentCallback?: (code: SentCode) => MaybeAsync - - /** - * Whether to "catch up" (load missed updates). - * Only applicable if the saved session already - * contained authorization and updates state. - * - * Note: you should register your handlers - * before calling `start()`, otherwise they will - * not be called. - * - * Note: In case the storage was not properly - * closed the last time, "catching up" might - * result in duplicate updates. - * - * Defaults to `false`. - */ - catchUp?: boolean }): Promise /** * Send an answer to a callback query. @@ -959,7 +863,6 @@ export interface TelegramClient extends BaseTelegramClient { * 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) - * * **Available**: ✅ both users and bots * */ @@ -978,7 +881,6 @@ export interface TelegramClient extends BaseTelegramClient { }): Promise /** * Gets information about a bot the current uzer owns (or the current bot) - * * **Available**: ✅ both users and bots * */ @@ -997,7 +899,6 @@ export interface TelegramClient extends BaseTelegramClient { }): Promise /** * Fetches the menu button set for the given user. - * * **Available**: 🤖 bots only * */ @@ -1042,7 +943,6 @@ export interface TelegramClient extends BaseTelegramClient { }): Promise /** * Get high scores of a game - * * **Available**: 🤖 bots only * */ @@ -1073,7 +973,6 @@ export interface TelegramClient extends BaseTelegramClient { * 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) - * * **Available**: ✅ both users and bots * */ @@ -1096,7 +995,6 @@ export interface TelegramClient extends BaseTelegramClient { ): Promise /** * Sets information about a bot the current uzer owns (or the current bot) - * * **Available**: ✅ both users and bots * */ @@ -1124,7 +1022,6 @@ export interface TelegramClient extends BaseTelegramClient { }): Promise /** * Sets a menu button for the given user. - * * **Available**: ✅ both users and bots * */ @@ -1193,7 +1090,6 @@ export interface TelegramClient extends BaseTelegramClient { * 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) - * * **Available**: 🤖 bots only * */ @@ -1219,7 +1115,6 @@ export interface TelegramClient extends BaseTelegramClient { }): Promise /** * Sets the default chat permissions for the bot in the supergroup or channel. - * * **Available**: 🤖 bots only * */ @@ -1299,7 +1194,6 @@ export interface TelegramClient extends BaseTelegramClient { * * If you want to create a supergroup, use {@link createSupergroup} * instead. - * * **Available**: 👤 users only * */ @@ -1389,9 +1283,7 @@ export interface TelegramClient extends BaseTelegramClient { */ deleteGroup(chatId: InputPeerLike): Promise /** - * Delete communication history (for private chats - * and legacy groups) - * + * Delete communication history (for private chats and legacy groups) * **Available**: 👤 users only * */ @@ -1420,7 +1312,6 @@ export interface TelegramClient extends BaseTelegramClient { ): Promise /** * Delete all messages of a user (or channel) in a supergroup - * * **Available**: 👤 users only * */ @@ -1432,7 +1323,6 @@ export interface TelegramClient extends BaseTelegramClient { }): Promise /** * Edit supergroup/channel admin rights of a user. - * * **Available**: ✅ both users and bots * */ @@ -1630,14 +1520,14 @@ export interface TelegramClient extends BaseTelegramClient { * * Small wrapper over {@link getChatEventLog} * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId Chat ID * @param params */ iterChatEventLog( chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Total number of events to return. * @@ -1668,10 +1558,10 @@ export interface TelegramClient extends BaseTelegramClient { */ iterChatMembers( chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Chunk size, which will be passed as `limit` parameter - * to {@link TelegramClient.getChatMembers}. Usually you shouldn't care about this. + * to {@link getChatMembers}. Usually you shouldn't care about this. * * Defaults to `200` */ @@ -1696,7 +1586,6 @@ export interface TelegramClient extends BaseTelegramClient { * Kick a user from a chat. * * This effectively bans a user and immediately unbans them. - * * **Available**: ✅ both users and bots * */ @@ -1709,7 +1598,7 @@ export interface TelegramClient extends BaseTelegramClient { /** * Leave a group chat, supergroup or channel * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId Chat ID or username */ @@ -1740,7 +1629,6 @@ export interface TelegramClient extends BaseTelegramClient { reorderUsernames(peerId: InputPeerLike, order: string[]): Promise /** * Restrict a user in a supergroup. - * * **Available**: ✅ both users and bots * */ @@ -1811,7 +1699,6 @@ export interface TelegramClient extends BaseTelegramClient { * Set a new chat photo or video. * * You must be an administrator and have the appropriate permissions. - * * **Available**: ✅ both users and bots * */ @@ -1888,7 +1775,6 @@ export interface TelegramClient extends BaseTelegramClient { * * > **Note**: non-collectible usernames must still be changed * > using {@link setUsername}/{@link setChatUsername} - * * **Available**: 👤 users only * */ @@ -1946,7 +1832,6 @@ export interface TelegramClient extends BaseTelegramClient { * just allows the user to join the chat again, if they want. * * This method acts as a no-op in case a legacy group is passed. - * * **Available**: ✅ both users and bots * */ @@ -1965,7 +1850,6 @@ export interface TelegramClient extends BaseTelegramClient { * just allows the user to join the chat again, if they want. * * This method acts as a no-op in case a legacy group is passed. - * * **Available**: ✅ both users and bots * */ @@ -1978,7 +1862,6 @@ export interface TelegramClient extends BaseTelegramClient { }): Promise /** * Add an existing Telegram user as a contact - * * **Available**: 👤 users only * */ @@ -2041,8 +1924,6 @@ export interface TelegramClient extends BaseTelegramClient { importContacts( contacts: PartialOnly, 'clientId'>[], ): Promise - - _pushConversationMessage(msg: Message, incoming?: boolean): void /** * Create a folder from given parameters * @@ -2089,7 +1970,7 @@ export interface TelegramClient extends BaseTelegramClient { * > accurate since you can set the same title and/or emoji * > to multiple folders. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param params Search parameters. At least one must be set. */ @@ -2245,7 +2126,7 @@ export interface TelegramClient extends BaseTelegramClient { * > **Note**: This method _will_ download the entire file * > into memory at once. This might cause an issue, so use wisely! * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param params File download parameters */ @@ -2254,7 +2135,7 @@ export interface TelegramClient extends BaseTelegramClient { * Download a remote file to a local file (only for NodeJS). * Promise will resolve once the download is complete. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param filename Local file name to which the remote file will be downloaded * @param params File download parameters @@ -2274,7 +2155,7 @@ export interface TelegramClient extends BaseTelegramClient { * Download a file and return it as a Node readable stream, * streaming file contents. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param params File download parameters */ @@ -2292,7 +2173,6 @@ export interface TelegramClient extends BaseTelegramClient { /** * Normalize a {@link InputFileLike} to `InputFile`, * uploading it if needed. - * * **Available**: ✅ both users and bots * */ @@ -2308,7 +2188,6 @@ export interface TelegramClient extends BaseTelegramClient { /** * Normalize an {@link InputMediaLike} to `InputMedia`, * uploading the file if needed. - * * **Available**: ✅ both users and bots * */ @@ -2523,13 +2402,13 @@ export interface TelegramClient extends BaseTelegramClient { /** * Iterate over forum topics. Wrapper over {@link getForumTopics}. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId Chat ID or username */ iterForumTopics( chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Maximum number of topics to return. * @@ -2547,7 +2426,6 @@ export interface TelegramClient extends BaseTelegramClient { * Reorder pinned forum topics * * Only admins with `manageTopics` permission can do this. - * * **Available**: ✅ both users and bots * */ @@ -2588,7 +2466,6 @@ export interface TelegramClient extends BaseTelegramClient { * Toggle whether a topic in a forum is pinned * * Only admins with `manageTopics` permission can do this. - * * **Available**: ✅ both users and bots * */ @@ -2816,7 +2693,6 @@ export interface TelegramClient extends BaseTelegramClient { getPrimaryInviteLink(chatId: InputPeerLike): Promise /** * Approve or deny multiple join requests to a chat. - * * **Available**: 👤 users only * */ @@ -2832,7 +2708,6 @@ export interface TelegramClient extends BaseTelegramClient { }): Promise /** * Approve or deny join request to a chat. - * * **Available**: ✅ both users and bots * */ @@ -2848,14 +2723,14 @@ export interface TelegramClient extends BaseTelegramClient { * Iterate over users who have joined * the chat with the given invite link. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId Chat ID * @param params Additional params */ iterInviteLinkMembers( chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Maximum number of users to return * @@ -2879,7 +2754,7 @@ export interface TelegramClient extends BaseTelegramClient { * (i.e. `adminId = "self"`), as a creator you can get * any other admin's links. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId Chat ID * @param adminId Admin who created the links @@ -2887,7 +2762,7 @@ export interface TelegramClient extends BaseTelegramClient { */ iterInviteLinks( chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Limit the number of invite links to be fetched. * By default, all links are fetched. @@ -2920,7 +2795,6 @@ export interface TelegramClient extends BaseTelegramClient { * * Once closed, poll can't be re-opened, and nobody * will be able to vote in it - * * **Available**: ✅ both users and bots * */ @@ -3507,14 +3381,14 @@ export interface TelegramClient extends BaseTelegramClient { /** * Iterate over chat history. Wrapper over {@link getHistory} * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param params Additional fetch parameters */ iterHistory( chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Limits the number of messages to be retrieved. * @@ -3535,7 +3409,7 @@ export interface TelegramClient extends BaseTelegramClient { * * Wrapper over {@link getReactionUsers}. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId Chat ID * @param messageId Message ID @@ -3544,7 +3418,7 @@ export interface TelegramClient extends BaseTelegramClient { iterReactionUsers( chatId: InputPeerLike, messageId: number, - params?: Parameters[2] & { + params?: Parameters[3] & { /** * Limit the number of events returned. * @@ -3567,12 +3441,12 @@ export interface TelegramClient extends BaseTelegramClient { * * **Note**: Due to Telegram limitations, you can only get up to ~10000 messages * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param params Search parameters */ iterSearchGlobal( - params?: Parameters[0] & { + params?: Parameters[1] & { /** * Limits the number of messages to be retrieved. * @@ -3594,13 +3468,13 @@ export interface TelegramClient extends BaseTelegramClient { * * Iterable version of {@link searchMessages} * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param params Additional search parameters */ iterSearchMessages( - params?: Parameters[0] & { + params?: Parameters[1] & { /** * Limits the number of messages to be retrieved. * @@ -3834,10 +3708,6 @@ export interface TelegramClient extends BaseTelegramClient { * and if the message contains an invoice, * it can't be copied. * - * > **Note**: if you already have {@link Message} object, - * > use {@link Message.sendCopy} instead, since that is - * > much more efficient, and that is what this method wraps. - * * **Available**: ✅ both users and bots * * @param params @@ -3923,7 +3793,7 @@ export interface TelegramClient extends BaseTelegramClient { * To add a caption to the group, add caption to the first * media in the group and don't add caption for any other. * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` * @param medias Medias contained in the message. @@ -4018,7 +3888,7 @@ export interface TelegramClient extends BaseTelegramClient { /** * Send a single media (a photo or a document-based media) * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` * @param media @@ -4176,7 +4046,7 @@ export interface TelegramClient extends BaseTelegramClient { /** * Send a text message * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` * @param text Text of the message @@ -4306,7 +4176,6 @@ export interface TelegramClient extends BaseTelegramClient { ): Promise /** * Send or retract a vote in a poll. - * * **Available**: 👤 users only * */ @@ -4327,7 +4196,6 @@ export interface TelegramClient extends BaseTelegramClient { * Translate message text to a given language. * * Returns `null` if it could not translate the message. - * * **Available**: 👤 users only * */ @@ -4391,11 +4259,12 @@ export interface TelegramClient extends BaseTelegramClient { /** * Normalize {@link InputPrivacyRule}[] to `tl.TypeInputPrivacyRule`, * resolving the peers if needed. - * * **Available**: ✅ both users and bots * */ _normalizePrivacyRules(rules: InputPrivacyRule[]): Promise + + getParseModesState(): ParseModesState /** * Register a given {@link IMessageEntityParser} as a parse mode * for messages. When this method is first called, given parse @@ -4439,7 +4308,6 @@ export interface TelegramClient extends BaseTelegramClient { setDefaultParseMode(name: string): void /** * Change your 2FA password - * * **Available**: 👤 users only * */ @@ -4458,7 +4326,6 @@ export interface TelegramClient extends BaseTelegramClient { * thrown, and you should use {@link verifyPasswordEmail}, * {@link resendPasswordEmail} or {@link cancelPasswordEmail}, * and the call this method again - * * **Available**: 👤 users only * */ @@ -4480,14 +4347,12 @@ export interface TelegramClient extends BaseTelegramClient { verifyPasswordEmail(code: string): Promise /** * Resend the code to verify an email to use as 2FA recovery method. - * * **Available**: 👤 users only * */ resendPasswordEmail(): Promise /** * Cancel the code that was sent to verify an email to use as 2FA recovery method - * * **Available**: 👤 users only * */ @@ -4632,7 +4497,6 @@ export interface TelegramClient extends BaseTelegramClient { * > the packs, that does not include the stickers themselves. * > Use {@link StickerSet.getFull} or {@link getStickerSet} * > to get a stickerset that will include the stickers - * * **Available**: 👤 users only * */ @@ -4813,7 +4677,6 @@ export interface TelegramClient extends BaseTelegramClient { _findStoryInUpdate(res: tl.TypeUpdates): Story /** * Get all stories (e.g. to load the top bar) - * * **Available**: ✅ both users and bots * */ @@ -4869,7 +4732,6 @@ export interface TelegramClient extends BaseTelegramClient { getPeerStories(peerId: InputPeerLike): Promise /** * Get profile stories - * * **Available**: ✅ both users and bots * */ @@ -4914,14 +4776,12 @@ export interface TelegramClient extends BaseTelegramClient { getStoriesById(peerId: InputPeerLike, storyIds: number[]): Promise /** * Get brief information about story interactions. - * */ getStoriesInteractions(peerId: InputPeerLike, storyId: number): Promise /** * Get brief information about stories interactions. * * The result will be in the same order as the input IDs - * */ getStoriesInteractions(peerId: InputPeerLike, storyIds: number[]): Promise /** @@ -4931,14 +4791,12 @@ export interface TelegramClient extends BaseTelegramClient { * and if the user doesn't have a username, `USER_PUBLIC_MISSING` is thrown. * * I have no idea why is this an RPC call, but whatever - * * **Available**: ✅ both users and bots * */ getStoryLink(peerId: InputPeerLike, storyId: number): Promise /** * Get viewers list of a story - * * **Available**: ✅ both users and bots * */ @@ -4982,7 +4840,6 @@ export interface TelegramClient extends BaseTelegramClient { * Hide own stories views (activate so called "stealth mode") * * Currently has a cooldown of 1 hour, and throws FLOOD_WAIT error if it is on cooldown. - * * **Available**: ✅ both users and bots * */ @@ -5017,40 +4874,31 @@ export interface TelegramClient extends BaseTelegramClient { * Iterate over all stories (e.g. to load the top bar) * * Wrapper over {@link getAllStories} - * * **Available**: ✅ both users and bots * */ - iterAllStories(params?: { - /** - * Offset from which to start fetching stories - */ - offset?: string - - /** - * Maximum number of stories to fetch - * - * @default Infinity - */ - limit?: number - - /** - * Whether to fetch stories from "archived" (or "hidden") peers - */ - archived?: boolean - }): AsyncIterableIterator + iterAllStories( + params?: Parameters[1] & { + /** + * Maximum number of stories to fetch + * + * @default Infinity + */ + limit?: number + }, + ): AsyncIterableIterator /** * Iterate over boosters of a channel. * * Wrapper over {@link getBoosters} * - * **Available**: 👤 users only + * **Available**: ✅ both users and bots * * @returns IDs of stories that were removed */ iterBoosters( peerId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Total number of boosters to fetch * @@ -5069,13 +4917,12 @@ export interface TelegramClient extends BaseTelegramClient { ): AsyncIterableIterator /** * Iterate over profile stories. Wrapper over {@link getProfileStories} - * * **Available**: ✅ both users and bots * */ iterProfileStories( peerId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Total number of stories to fetch * @@ -5095,14 +4942,13 @@ export interface TelegramClient extends BaseTelegramClient { /** * Iterate over viewers list of a story. * Wrapper over {@link getStoryViewers} - * * **Available**: ✅ both users and bots * */ iterStoryViewers( peerId: InputPeerLike, storyId: number, - params?: Parameters[2] & { + params?: Parameters[3] & { /** * Total number of viewers to fetch * @@ -5132,7 +4978,6 @@ export interface TelegramClient extends BaseTelegramClient { readStories(peerId: InputPeerLike, maxId: number): Promise /** * Report a story (or multiple stories) to the moderation team - * * **Available**: ✅ both users and bots * */ @@ -5155,21 +5000,18 @@ export interface TelegramClient extends BaseTelegramClient { ): Promise /** * Send (or remove) a reaction to a story - * * **Available**: ✅ both users and bots * */ - sendStoryReaction( - peerId: InputPeerLike, - storyId: number, - reaction: InputReaction, - params?: { - /** - * Whether to add this reaction to recently used - */ - addToRecent?: boolean - }, - ): Promise + sendStoryReaction(params: { + peerId: InputPeerLike + storyId: number + reaction: InputReaction + /** + * Whether to add this reaction to recently used + */ + addToRecent?: boolean + }): Promise /** * Send a story * @@ -5244,7 +5086,6 @@ export interface TelegramClient extends BaseTelegramClient { * Toggle whether peer's stories are archived (hidden) or not. * * This **does not** archive the chat with that peer, only stories. - * * **Available**: ✅ both users and bots * */ @@ -5274,6 +5115,7 @@ export interface TelegramClient extends BaseTelegramClient { */ peer?: InputPeerLike }): Promise + // code in this file is very bad, thanks to Telegram's awesome updates mechanism /** * Enable RPS meter. * Only available in NodeJS v10.7.0 and newer @@ -5294,7 +5136,6 @@ export interface TelegramClient extends BaseTelegramClient { * they should be around the same, except * rare situations when processing rps * may peak. - * * **Available**: ✅ both users and bots * */ @@ -5307,33 +5148,24 @@ export interface TelegramClient extends BaseTelegramClient { * they should be around the same, except * rare situations when processing rps * may peak. - * * **Available**: ✅ both users and bots * */ getCurrentRpsProcessing(): number /** - * Fetch updates state from the server. - * Meant to be used right after authorization, - * but before force-saving the session. + * Start updates loop. + * + * You must first call {@link enableUpdatesProcessing} to use this method. + * + * It is recommended to use this method in callback to {@link start}, + * or otherwise make sure the user is logged in. + * + * > **Note**: If you are using {@link UpdatesManagerParams.catchUp} option, + * > catching up will be done in background, you can't await it. * **Available**: ✅ both users and bots * */ - _fetchUpdatesState(): Promise - /** - * **Available**: ✅ both users and bots - * - */ - _loadStorage(): Promise - /** - * **ADVANCED** - * - * Manually start updates loop. - * Usually done automatically inside {@link start} - * **Available**: ✅ both users and bots - * - */ - startUpdatesLoop(): void + startUpdatesLoop(): Promise /** * **ADVANCED** * @@ -5343,36 +5175,16 @@ export interface TelegramClient extends BaseTelegramClient { * */ stopUpdatesLoop(): void - - _onStop(): void - /** - * **Available**: ✅ both users and bots - * - */ - _saveStorage(afterImport?: boolean): Promise - /** - * **Available**: ✅ both users and bots - * - */ - _dispatchUpdate(update: tl.TypeUpdate, peers: PeersIndex): void - /** - * **Available**: ✅ both users and bots - * - */ - _handleUpdate(update: tl.TypeUpdates, noDispatch?: boolean): void /** * Catch up with the server by loading missed updates. * + * > **Note**: In case the storage was not properly + * > closed the last time, "catching up" might + * > result in duplicate updates. * **Available**: ✅ both users and bots * */ catchUp(): void - // todo: updateChannelTooLong with catchUpChannels disabled should not trigger getDifference (?) - // todo: when min peer or similar use pts_before as base pts for channels - - _updatesLoop(): Promise - - _keepAliveAction(): void /** * Block a user * @@ -5416,14 +5228,12 @@ export interface TelegramClient extends BaseTelegramClient { getCommonChats(userId: InputPeerLike): Promise /** * Gets the current default value of the Time-To-Live setting, applied to all new chats. - * * **Available**: ✅ both users and bots * */ getGlobalTtl(): Promise /** * Get currently authorized user's full information - * * **Available**: ✅ both users and bots * */ @@ -5433,7 +5243,6 @@ export interface TelegramClient extends BaseTelegramClient { * * This method uses locally available information and * does not call any API methods. - * * **Available**: ✅ both users and bots * */ @@ -5499,7 +5308,7 @@ export interface TelegramClient extends BaseTelegramClient { */ iterProfilePhotos( userId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Maximum number of items to fetch * @@ -5515,14 +5324,12 @@ export interface TelegramClient extends BaseTelegramClient { chunkSize?: number }, ): AsyncIterableIterator - /* eslint-enable @typescript-eslint/no-unused-vars */ /** * Get multiple `InputPeer`s at once, * while also normalizing and removing * peers that can't be normalized to that type. * - * Uses async pool internally, with a - * configurable concurrent limit (see {@link TelegramClientOptions#resolvePeerManyPoolLimit}). + * Uses async pool internally, with a concurrent limit of 8 * * @param peerIds Peer Ids * @param normalizer Normalization function @@ -5534,9 +5341,7 @@ export interface TelegramClient extends BaseTelegramClient { /** * Get multiple `InputPeer`s at once. * - * Uses async pool internally, with a - * configurable concurrent limit (see {@link TelegramClientOptions#resolvePeerManyPoolLimit}). - * + * Uses async pool internally, with a concurrent limit of 8 * * @param peerIds Peer Ids */ @@ -5589,7 +5394,6 @@ export interface TelegramClient extends BaseTelegramClient { * Set a new profile photo or video. * * You can also pass a file ID or an InputPhoto to re-use existing photo. - * * **Available**: ✅ both users and bots * */ @@ -5650,326 +5454,277 @@ export interface TelegramClient extends BaseTelegramClient { export { TelegramClientOptions } export class TelegramClient extends BaseTelegramClient { - protected _userId: number | null - protected _isBot: boolean - protected _selfUsername: string | null - protected _pendingConversations: Map - protected _hasConversations: boolean - protected _parseModes: Map - protected _defaultParseMode: string | null - protected _updatesLoopActive: boolean - protected _updatesLoopCv: ConditionVariable - protected _pendingUpdateContainers: SortedLinkedList - protected _pendingPtsUpdates: SortedLinkedList - protected _pendingPtsUpdatesPostponed: SortedLinkedList - protected _pendingQtsUpdates: SortedLinkedList - protected _pendingQtsUpdatesPostponed: SortedLinkedList - protected _pendingUnorderedUpdates: Deque - protected _noDispatchEnabled: boolean - protected _noDispatchMsg: Map> - protected _noDispatchPts: Map> - protected _noDispatchQts: Set - protected _updLock: AsyncLock - protected _rpsIncoming?: RpsMeter - protected _rpsProcessing?: RpsMeter - protected _messageGroupingInterval: number - protected _messageGroupingPending: Map - protected _pts?: number - protected _qts?: number - protected _date?: number - protected _seq?: number - protected _oldPts?: number - protected _oldQts?: number - protected _oldDate?: number - protected _oldSeq?: number - protected _selfChanged: boolean - protected _catchUpChannels?: boolean - protected _cpts: Map - protected _cptsMod: Map - protected _updsLog: Logger - protected _resolvePeerManyPoolLimit: number constructor(opts: TelegramClientOptions) { super(opts) - this._userId = null - this._isBot = false - this._selfUsername = null - this.log.prefix = '[USER N/A] ' - this._pendingConversations = new Map() - this._hasConversations = false - this._parseModes = new Map() - this._defaultParseMode = null - this._updatesLoopActive = false - this._updatesLoopCv = new ConditionVariable() - this._pendingUpdateContainers = new SortedLinkedList((a, b) => a.seqStart - b.seqStart) - this._pendingPtsUpdates = new SortedLinkedList((a, b) => a.ptsBefore! - b.ptsBefore!) - this._pendingPtsUpdatesPostponed = new SortedLinkedList((a, b) => a.ptsBefore! - b.ptsBefore!) - this._pendingQtsUpdates = new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!) - this._pendingQtsUpdatesPostponed = new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!) - this._pendingUnorderedUpdates = new Deque() + if (!opts.disableUpdates) { + enableUpdatesProcessing(this, { + onUpdate: makeParsedUpdateHandler({ + ...opts.updates, + onUpdate: (update) => { + Conversation.handleUpdate(this, update) + this.emit('update', update) + this.emit(update.name, update.data) + }, + onRawUpdate: (update, peers) => { + this.emit('raw_update', update, peers) + }, + }), + }) - this._noDispatchEnabled = !opts.disableNoDispatch - this._noDispatchMsg = new Map() - this._noDispatchPts = new Map() - this._noDispatchQts = new Set() + this.start = async (params) => { + const user = await start(this, params) + await this.startUpdatesLoop() - this._messageGroupingInterval = opts.messageGroupingInterval ?? 0 - this._messageGroupingPending = new Map() - - this._updLock = new AsyncLock() - // we dont need to initialize state fields since - // they are always loaded either from the server, or from storage. - - // channel PTS are not loaded immediately, and instead are cached here - // after the first time they were retrieved from the storage. - this._cpts = new Map() - // modified channel pts, to avoid unnecessary - // DB calls for not modified cpts - this._cptsMod = new Map() - - this._selfChanged = false - - this._updsLog = this.log.create('updates') - this._resolvePeerManyPoolLimit = opts.resolvePeerManyPoolLimit ?? 8 + return user + } + } else { + this.start = start.bind(null, this) + } } - _onAuthorization = _onAuthorization - checkPassword = checkPassword - getPasswordHint = getPasswordHint - logOut = logOut - recoverPassword = recoverPassword - resendCode = resendCode - run = run - sendCode = sendCode - sendRecoveryCode = sendRecoveryCode - signInBot = signInBot - signIn = signIn - startTest = startTest - start = start - answerCallbackQuery = answerCallbackQuery - answerInlineQuery = answerInlineQuery - answerPreCheckoutQuery = answerPreCheckoutQuery - deleteMyCommands = deleteMyCommands - getBotInfo = getBotInfo - getBotMenuButton = getBotMenuButton - getCallbackAnswer = getCallbackAnswer - getGameHighScores = getGameHighScores - getInlineGameHighScores = getInlineGameHighScores - getMyCommands = getMyCommands - _normalizeCommandScope = _normalizeCommandScope - setBotInfo = setBotInfo - setBotMenuButton = setBotMenuButton - setGameScore = setGameScore - setInlineGameScore = setInlineGameScore - setMyCommands = setMyCommands - setMyDefaultRights = setMyDefaultRights - addChatMembers = addChatMembers - archiveChats = archiveChats - banChatMember = banChatMember - createChannel = createChannel - createGroup = createGroup - createSupergroup = createSupergroup - deleteChannel = deleteChannel - deleteSupergroup = deleteChannel - deleteChatPhoto = deleteChatPhoto - deleteGroup = deleteGroup - deleteHistory = deleteHistory - deleteUserHistory = deleteUserHistory - editAdminRights = editAdminRights - getChatEventLog = getChatEventLog - getChatMember = getChatMember - getChatMembers = getChatMembers - getChatPreview = getChatPreview - getChat = getChat - getFullChat = getFullChat - getNearbyChats = getNearbyChats - iterChatEventLog = iterChatEventLog - iterChatMembers = iterChatMembers - joinChat = joinChat - kickChatMember = kickChatMember - leaveChat = leaveChat - markChatUnread = markChatUnread - reorderUsernames = reorderUsernames - restrictChatMember = restrictChatMember - saveDraft = saveDraft - setChatDefaultPermissions = setChatDefaultPermissions - setChatDescription = setChatDescription - setChatPhoto = setChatPhoto - setChatTitle = setChatTitle - setChatTtl = setChatTtl - setChatUsername = setChatUsername - setSlowMode = setSlowMode - toggleContentProtection = toggleContentProtection - toggleFragmentUsername = toggleFragmentUsername - toggleJoinRequests = toggleJoinRequests - toggleJoinToSend = toggleJoinToSend - unarchiveChats = unarchiveChats - unbanChatMember = unbanChatMember - unrestrictChatMember = unbanChatMember - addContact = addContact - deleteContacts = deleteContacts - getContacts = getContacts - importContacts = importContacts - _pushConversationMessage = _pushConversationMessage - createFolder = createFolder - deleteFolder = deleteFolder - editFolder = editFolder - findFolder = findFolder - getFolders = getFolders - _normalizeInputFolder = _normalizeInputFolder - getPeerDialogs = getPeerDialogs - iterDialogs = iterDialogs - setFoldersOrder = setFoldersOrder - downloadAsBuffer = downloadAsBuffer - downloadToFile = downloadToFile - downloadAsIterable = downloadAsIterable - downloadAsStream = downloadAsStream - _normalizeFileToDocument = _normalizeFileToDocument - _normalizeInputFile = _normalizeInputFile - _normalizeInputMedia = _normalizeInputMedia - uploadFile = uploadFile - uploadMedia = uploadMedia - createForumTopic = createForumTopic - deleteForumTopicHistory = deleteForumTopicHistory - editForumTopic = editForumTopic - getForumTopicsById = getForumTopicsById - getForumTopics = getForumTopics - iterForumTopics = iterForumTopics - reorderPinnedForumTopics = reorderPinnedForumTopics - toggleForumTopicClosed = toggleForumTopicClosed - toggleForumTopicPinned = toggleForumTopicPinned - toggleForum = toggleForum - toggleGeneralTopicHidden = toggleGeneralTopicHidden - createInviteLink = createInviteLink - editInviteLink = editInviteLink - exportInviteLink = exportInviteLink - getInviteLinkMembers = getInviteLinkMembers - getInviteLink = getInviteLink - getInviteLinks = getInviteLinks - getPrimaryInviteLink = getPrimaryInviteLink - hideAllJoinRequests = hideAllJoinRequests - hideJoinRequest = hideJoinRequest - iterInviteLinkMembers = iterInviteLinkMembers - iterInviteLinks = iterInviteLinks - revokeInviteLink = revokeInviteLink - closePoll = closePoll - deleteMessages = deleteMessages - deleteScheduledMessages = deleteScheduledMessages - editInlineMessage = editInlineMessage - editMessage = editMessage - _findMessageInUpdate = _findMessageInUpdate - forwardMessages = forwardMessages - _getDiscussionMessage = _getDiscussionMessage - getDiscussionMessage = getDiscussionMessage - getHistory = getHistory - getMessageGroup = getMessageGroup - getMessageReactions = getMessageReactions - getMessagesUnsafe = getMessagesUnsafe - getMessages = getMessages - getReactionUsers = getReactionUsers - getScheduledMessages = getScheduledMessages - iterHistory = iterHistory - iterReactionUsers = iterReactionUsers - iterSearchGlobal = iterSearchGlobal - iterSearchMessages = iterSearchMessages - _parseEntities = _parseEntities - pinMessage = pinMessage - readHistory = readHistory - readReactions = readReactions - searchGlobal = searchGlobal - searchMessages = searchMessages - sendCopy = sendCopy - sendMediaGroup = sendMediaGroup - sendMedia = sendMedia - sendReaction = sendReaction - sendScheduled = sendScheduled - sendText = sendText - sendTyping = sendTyping - sendVote = sendVote - translateMessage = translateMessage - translateText = translateText - unpinAllMessages = unpinAllMessages - unpinMessage = unpinMessage - initTakeoutSession = initTakeoutSession - _normalizePrivacyRules = _normalizePrivacyRules - registerParseMode = registerParseMode - unregisterParseMode = unregisterParseMode - getParseMode = getParseMode - setDefaultParseMode = setDefaultParseMode - changeCloudPassword = changeCloudPassword - enableCloudPassword = enableCloudPassword - verifyPasswordEmail = verifyPasswordEmail - resendPasswordEmail = resendPasswordEmail - cancelPasswordEmail = cancelPasswordEmail - removeCloudPassword = removeCloudPassword - addStickerToSet = addStickerToSet - createStickerSet = createStickerSet - deleteStickerFromSet = deleteStickerFromSet - getCustomEmojis = getCustomEmojis - getInstalledStickers = getInstalledStickers - getStickerSet = getStickerSet - moveStickerInSet = moveStickerInSet - setChatStickerSet = setChatStickerSet - setStickerSetThumb = setStickerSetThumb - applyBoost = applyBoost - canApplyBoost = canApplyBoost - canSendStory = canSendStory - deleteStories = deleteStories - editStory = editStory - _findStoryInUpdate = _findStoryInUpdate - getAllStories = getAllStories - getBoostStats = getBoostStats - getBoosters = getBoosters - getPeerStories = getPeerStories - getProfileStories = getProfileStories - getStoriesById = getStoriesById - getStoriesInteractions = getStoriesInteractions - getStoryLink = getStoryLink - getStoryViewers = getStoryViewers - hideMyStoriesViews = hideMyStoriesViews - incrementStoriesViews = incrementStoriesViews - iterAllStories = iterAllStories - iterBoosters = iterBoosters - iterProfileStories = iterProfileStories - iterStoryViewers = iterStoryViewers - readStories = readStories - reportStory = reportStory - sendStoryReaction = sendStoryReaction - sendStory = sendStory - togglePeerStoriesArchived = togglePeerStoriesArchived - toggleStoriesPinned = toggleStoriesPinned - enableRps = enableRps - getCurrentRpsIncoming = getCurrentRpsIncoming - getCurrentRpsProcessing = getCurrentRpsProcessing - _fetchUpdatesState = _fetchUpdatesState - _loadStorage = _loadStorage - startUpdatesLoop = startUpdatesLoop - stopUpdatesLoop = stopUpdatesLoop - _onStop = _onStop - _saveStorage = _saveStorage - _dispatchUpdate = _dispatchUpdate - _handleUpdate = _handleUpdate - catchUp = catchUp - _updatesLoop = _updatesLoop - _keepAliveAction = _keepAliveAction - blockUser = blockUser - deleteProfilePhotos = deleteProfilePhotos - editCloseFriendsRaw = editCloseFriendsRaw - editCloseFriends = editCloseFriends - getCommonChats = getCommonChats - getGlobalTtl = getGlobalTtl - getMe = getMe - getMyUsername = getMyUsername - getProfilePhoto = getProfilePhoto - getProfilePhotos = getProfilePhotos - getUsers = getUsers - iterProfilePhotos = iterProfilePhotos - resolvePeerMany = resolvePeerMany - resolvePeer = resolvePeer - setEmojiStatus = setEmojiStatus - setGlobalTtl = setGlobalTtl - setOffline = setOffline - setProfilePhoto = setProfilePhoto - setUsername = setUsername - unblockUser = unblockUser - updateProfile = updateProfile + getAuthState = getAuthState.bind(null, this) + _onAuthorization = _onAuthorization.bind(null, this) + checkPassword = checkPassword.bind(null, this) + getPasswordHint = getPasswordHint.bind(null, this) + logOut = logOut.bind(null, this) + recoverPassword = recoverPassword.bind(null, this) + resendCode = resendCode.bind(null, this) + run = run.bind(null, this) + sendCode = sendCode.bind(null, this) + sendRecoveryCode = sendRecoveryCode.bind(null, this) + signInBot = signInBot.bind(null, this) + signIn = signIn.bind(null, this) + startTest = startTest.bind(null, this) + answerCallbackQuery = answerCallbackQuery.bind(null, this) + answerInlineQuery = answerInlineQuery.bind(null, this) + answerPreCheckoutQuery = answerPreCheckoutQuery.bind(null, this) + deleteMyCommands = deleteMyCommands.bind(null, this) + getBotInfo = getBotInfo.bind(null, this) + getBotMenuButton = getBotMenuButton.bind(null, this) + getCallbackAnswer = getCallbackAnswer.bind(null, this) + getGameHighScores = getGameHighScores.bind(null, this) + getInlineGameHighScores = getInlineGameHighScores.bind(null, this) + getMyCommands = getMyCommands.bind(null, this) + _normalizeCommandScope = _normalizeCommandScope.bind(null, this) + setBotInfo = setBotInfo.bind(null, this) + setBotMenuButton = setBotMenuButton.bind(null, this) + setGameScore = setGameScore.bind(null, this) + setInlineGameScore = setInlineGameScore.bind(null, this) + setMyCommands = setMyCommands.bind(null, this) + setMyDefaultRights = setMyDefaultRights.bind(null, this) + addChatMembers = addChatMembers.bind(null, this) + archiveChats = archiveChats.bind(null, this) + banChatMember = banChatMember.bind(null, this) + createChannel = createChannel.bind(null, this) + createGroup = createGroup.bind(null, this) + createSupergroup = createSupergroup.bind(null, this) + deleteChannel = deleteChannel.bind(null, this) + deleteSupergroup = deleteChannel.bind(null, this) + deleteChatPhoto = deleteChatPhoto.bind(null, this) + deleteGroup = deleteGroup.bind(null, this) + deleteHistory = deleteHistory.bind(null, this) + deleteUserHistory = deleteUserHistory.bind(null, this) + editAdminRights = editAdminRights.bind(null, this) + getChatEventLog = getChatEventLog.bind(null, this) + getChatMember = getChatMember.bind(null, this) + getChatMembers = getChatMembers.bind(null, this) + getChatPreview = getChatPreview.bind(null, this) + getChat = getChat.bind(null, this) + getFullChat = getFullChat.bind(null, this) + getNearbyChats = getNearbyChats.bind(null, this) + iterChatEventLog = iterChatEventLog.bind(null, this) + iterChatMembers = iterChatMembers.bind(null, this) + joinChat = joinChat.bind(null, this) + kickChatMember = kickChatMember.bind(null, this) + leaveChat = leaveChat.bind(null, this) + markChatUnread = markChatUnread.bind(null, this) + reorderUsernames = reorderUsernames.bind(null, this) + restrictChatMember = restrictChatMember.bind(null, this) + saveDraft = saveDraft.bind(null, this) + setChatDefaultPermissions = setChatDefaultPermissions.bind(null, this) + setChatDescription = setChatDescription.bind(null, this) + setChatPhoto = setChatPhoto.bind(null, this) + setChatTitle = setChatTitle.bind(null, this) + setChatTtl = setChatTtl.bind(null, this) + setChatUsername = setChatUsername.bind(null, this) + setSlowMode = setSlowMode.bind(null, this) + toggleContentProtection = toggleContentProtection.bind(null, this) + toggleFragmentUsername = toggleFragmentUsername.bind(null, this) + toggleJoinRequests = toggleJoinRequests.bind(null, this) + toggleJoinToSend = toggleJoinToSend.bind(null, this) + unarchiveChats = unarchiveChats.bind(null, this) + unbanChatMember = unbanChatMember.bind(null, this) + unrestrictChatMember = unbanChatMember.bind(null, this) + addContact = addContact.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + deleteContacts = deleteContacts.bind(null, this) + getContacts = getContacts.bind(null, this) + importContacts = importContacts.bind(null, this) + createFolder = createFolder.bind(null, this) + deleteFolder = deleteFolder.bind(null, this) + editFolder = editFolder.bind(null, this) + findFolder = findFolder.bind(null, this) + getFolders = getFolders.bind(null, this) + _normalizeInputFolder = _normalizeInputFolder.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getPeerDialogs = getPeerDialogs.bind(null, this) + iterDialogs = iterDialogs.bind(null, this) + setFoldersOrder = setFoldersOrder.bind(null, this) + downloadAsBuffer = downloadAsBuffer.bind(null, this) + downloadToFile = downloadToFile.bind(null, this) + downloadAsIterable = downloadAsIterable.bind(null, this) + downloadAsStream = downloadAsStream.bind(null, this) + _normalizeFileToDocument = _normalizeFileToDocument.bind(null, this) + _normalizeInputFile = _normalizeInputFile.bind(null, this) + _normalizeInputMedia = _normalizeInputMedia.bind(null, this) + uploadFile = uploadFile.bind(null, this) + uploadMedia = uploadMedia.bind(null, this) + createForumTopic = createForumTopic.bind(null, this) + deleteForumTopicHistory = deleteForumTopicHistory.bind(null, this) + editForumTopic = editForumTopic.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getForumTopicsById = getForumTopicsById.bind(null, this) + getForumTopics = getForumTopics.bind(null, this) + iterForumTopics = iterForumTopics.bind(null, this) + reorderPinnedForumTopics = reorderPinnedForumTopics.bind(null, this) + toggleForumTopicClosed = toggleForumTopicClosed.bind(null, this) + toggleForumTopicPinned = toggleForumTopicPinned.bind(null, this) + toggleForum = toggleForum.bind(null, this) + toggleGeneralTopicHidden = toggleGeneralTopicHidden.bind(null, this) + createInviteLink = createInviteLink.bind(null, this) + editInviteLink = editInviteLink.bind(null, this) + exportInviteLink = exportInviteLink.bind(null, this) + getInviteLinkMembers = getInviteLinkMembers.bind(null, this) + getInviteLink = getInviteLink.bind(null, this) + getInviteLinks = getInviteLinks.bind(null, this) + getPrimaryInviteLink = getPrimaryInviteLink.bind(null, this) + hideAllJoinRequests = hideAllJoinRequests.bind(null, this) + hideJoinRequest = hideJoinRequest.bind(null, this) + iterInviteLinkMembers = iterInviteLinkMembers.bind(null, this) + iterInviteLinks = iterInviteLinks.bind(null, this) + revokeInviteLink = revokeInviteLink.bind(null, this) + closePoll = closePoll.bind(null, this) + deleteMessages = deleteMessages.bind(null, this) + deleteScheduledMessages = deleteScheduledMessages.bind(null, this) + editInlineMessage = editInlineMessage.bind(null, this) + editMessage = editMessage.bind(null, this) + _findMessageInUpdate = _findMessageInUpdate.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + forwardMessages = forwardMessages.bind(null, this) + _getDiscussionMessage = _getDiscussionMessage.bind(null, this) + getDiscussionMessage = getDiscussionMessage.bind(null, this) + getHistory = getHistory.bind(null, this) + getMessageGroup = getMessageGroup.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getMessageReactions = getMessageReactions.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getMessagesUnsafe = getMessagesUnsafe.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getMessages = getMessages.bind(null, this) + getReactionUsers = getReactionUsers.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getScheduledMessages = getScheduledMessages.bind(null, this) + iterHistory = iterHistory.bind(null, this) + iterReactionUsers = iterReactionUsers.bind(null, this) + iterSearchGlobal = iterSearchGlobal.bind(null, this) + iterSearchMessages = iterSearchMessages.bind(null, this) + _parseEntities = _parseEntities.bind(null, this) + pinMessage = pinMessage.bind(null, this) + readHistory = readHistory.bind(null, this) + readReactions = readReactions.bind(null, this) + searchGlobal = searchGlobal.bind(null, this) + searchMessages = searchMessages.bind(null, this) + sendCopy = sendCopy.bind(null, this) + sendMediaGroup = sendMediaGroup.bind(null, this) + sendMedia = sendMedia.bind(null, this) + sendReaction = sendReaction.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + sendScheduled = sendScheduled.bind(null, this) + sendText = sendText.bind(null, this) + sendTyping = sendTyping.bind(null, this) + sendVote = sendVote.bind(null, this) + translateMessage = translateMessage.bind(null, this) + translateText = translateText.bind(null, this) + unpinAllMessages = unpinAllMessages.bind(null, this) + unpinMessage = unpinMessage.bind(null, this) + initTakeoutSession = initTakeoutSession.bind(null, this) + _normalizePrivacyRules = _normalizePrivacyRules.bind(null, this) + getParseModesState = getParseModesState.bind(null, this) + registerParseMode = registerParseMode.bind(null, this) + unregisterParseMode = unregisterParseMode.bind(null, this) + getParseMode = getParseMode.bind(null, this) + setDefaultParseMode = setDefaultParseMode.bind(null, this) + changeCloudPassword = changeCloudPassword.bind(null, this) + enableCloudPassword = enableCloudPassword.bind(null, this) + verifyPasswordEmail = verifyPasswordEmail.bind(null, this) + resendPasswordEmail = resendPasswordEmail.bind(null, this) + cancelPasswordEmail = cancelPasswordEmail.bind(null, this) + removeCloudPassword = removeCloudPassword.bind(null, this) + addStickerToSet = addStickerToSet.bind(null, this) + createStickerSet = createStickerSet.bind(null, this) + deleteStickerFromSet = deleteStickerFromSet.bind(null, this) + getCustomEmojis = getCustomEmojis.bind(null, this) + getInstalledStickers = getInstalledStickers.bind(null, this) + getStickerSet = getStickerSet.bind(null, this) + moveStickerInSet = moveStickerInSet.bind(null, this) + setChatStickerSet = setChatStickerSet.bind(null, this) + setStickerSetThumb = setStickerSetThumb.bind(null, this) + applyBoost = applyBoost.bind(null, this) + canApplyBoost = canApplyBoost.bind(null, this) + canSendStory = canSendStory.bind(null, this) + deleteStories = deleteStories.bind(null, this) + editStory = editStory.bind(null, this) + _findStoryInUpdate = _findStoryInUpdate.bind(null, this) + getAllStories = getAllStories.bind(null, this) + getBoostStats = getBoostStats.bind(null, this) + getBoosters = getBoosters.bind(null, this) + getPeerStories = getPeerStories.bind(null, this) + getProfileStories = getProfileStories.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getStoriesById = getStoriesById.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getStoriesInteractions = getStoriesInteractions.bind(null, this) + getStoryLink = getStoryLink.bind(null, this) + getStoryViewers = getStoryViewers.bind(null, this) + hideMyStoriesViews = hideMyStoriesViews.bind(null, this) + incrementStoriesViews = incrementStoriesViews.bind(null, this) + iterAllStories = iterAllStories.bind(null, this) + iterBoosters = iterBoosters.bind(null, this) + iterProfileStories = iterProfileStories.bind(null, this) + iterStoryViewers = iterStoryViewers.bind(null, this) + readStories = readStories.bind(null, this) + reportStory = reportStory.bind(null, this) + sendStoryReaction = sendStoryReaction.bind(null, this) + sendStory = sendStory.bind(null, this) + togglePeerStoriesArchived = togglePeerStoriesArchived.bind(null, this) + toggleStoriesPinned = toggleStoriesPinned.bind(null, this) + enableRps = enableRps.bind(null, this) + getCurrentRpsIncoming = getCurrentRpsIncoming.bind(null, this) + getCurrentRpsProcessing = getCurrentRpsProcessing.bind(null, this) + startUpdatesLoop = startUpdatesLoop.bind(null, this) + stopUpdatesLoop = stopUpdatesLoop.bind(null, this) + catchUp = catchUp.bind(null, this) + blockUser = blockUser.bind(null, this) + deleteProfilePhotos = deleteProfilePhotos.bind(null, this) + editCloseFriendsRaw = editCloseFriendsRaw.bind(null, this) + editCloseFriends = editCloseFriends.bind(null, this) + getCommonChats = getCommonChats.bind(null, this) + getGlobalTtl = getGlobalTtl.bind(null, this) + getMe = getMe.bind(null, this) + getMyUsername = getMyUsername.bind(null, this) + getProfilePhoto = getProfilePhoto.bind(null, this) + getProfilePhotos = getProfilePhotos.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + getUsers = getUsers.bind(null, this) + iterProfilePhotos = iterProfilePhotos.bind(null, this) + // @ts-expect-error .bind() kinda breaks typings for overloads + resolvePeerMany = resolvePeerMany.bind(null, this) + resolvePeer = resolvePeer.bind(null, this) + setEmojiStatus = setEmojiStatus.bind(null, this) + setGlobalTtl = setGlobalTtl.bind(null, this) + setOffline = setOffline.bind(null, this) + setProfilePhoto = setProfilePhoto.bind(null, this) + setUsername = setUsername.bind(null, this) + unblockUser = unblockUser.bind(null, this) + updateProfile = updateProfile.bind(null, this) } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index f6c76784..ce84f215 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,4 +1,3 @@ -/** @hidden */ export * from './client' export * from './types' export * from './utils/peer-utils' diff --git a/packages/client/src/methods/README.md b/packages/client/src/methods/README.md index 6e1a01a2..20f79a15 100644 --- a/packages/client/src/methods/README.md +++ b/packages/client/src/methods/README.md @@ -57,7 +57,7 @@ Example: ```typescript // @initialize -function _initializeAwesomeExtension(this: TelegramClient) { +function _initializeAwesomeExtension(client: BaseTelegramClient) { this._field1 = 42 this._field2 = 'uwu' } @@ -74,7 +74,7 @@ Example: // @exported export type FooOrBar = Foo | Bar -export function getFooOrBar(this: TelegramClient): FooOrBar { +export function getFooOrBar(client: BaseTelegramClient): FooOrBar { return new Foo() } ``` diff --git a/packages/client/src/methods/_imports.ts b/packages/client/src/methods/_imports.ts index 0c398b83..1cfc5921 100644 --- a/packages/client/src/methods/_imports.ts +++ b/packages/client/src/methods/_imports.ts @@ -5,8 +5,6 @@ import { Readable } from 'stream' // @copy import { MaybeArray, MaybeAsync, PartialExcept, PartialOnly } from '@mtcute/core' // @copy -import { AsyncLock, ConditionVariable, Deque, Logger, SortedLinkedList } from '@mtcute/core/utils' -// @copy import { tdFileId } from '@mtcute/file-id' // @copy @@ -29,7 +27,6 @@ import { ChatMemberUpdate, ChatPreview, ChosenInlineResult, - Conversation, DeleteMessageUpdate, DeleteStoryUpdate, Dialog, diff --git a/packages/client/src/methods/_init.ts b/packages/client/src/methods/_init.ts new file mode 100644 index 00000000..5e58bc60 --- /dev/null +++ b/packages/client/src/methods/_init.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import { BaseTelegramClientOptions } from '@mtcute/core' + +import { TelegramClient } from '../client' +// @copy +import { Conversation } from '../types/conversation' +// @copy +import { start } from './auth/start' +// @copy +import { enableUpdatesProcessing, makeParsedUpdateHandler, ParsedUpdateHandlerParams } from './updates' + +// @copy +interface TelegramClientOptions extends BaseTelegramClientOptions { + /** + * Parameters for updates manager. + */ + updates?: Omit +} + +// @initialize +/** @internal */ +function _initializeClient(this: TelegramClient, opts: TelegramClientOptions) { + if (!opts.disableUpdates) { + enableUpdatesProcessing(this, { + onUpdate: makeParsedUpdateHandler({ + ...opts.updates, + onUpdate: (update) => { + Conversation.handleUpdate(this, update) + this.emit('update', update) + this.emit(update.name, update.data) + }, + onRawUpdate: (update, peers) => { + this.emit('raw_update', update, peers) + }, + }), + }) + + this.start = async (params) => { + const user = await start(this, params) + await this.startUpdatesLoop() + + return user + } + } else { + this.start = start.bind(null, this) + } +} diff --git a/packages/client/src/methods/_options.ts b/packages/client/src/methods/_options.ts deleted file mode 100644 index d7f0da4d..00000000 --- a/packages/client/src/methods/_options.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - -import { BaseTelegramClientOptions } from '@mtcute/core' - -// @copy -interface TelegramClientOptions extends BaseTelegramClientOptions { - /** - * **ADVANCED** - * - * Whether to disable no-dispatch mechanism. - * - * No-dispatch is a mechanism that allows you to call methods - * that return updates and correctly handle them, without - * actually dispatching them to the event handlers. - * - * In other words, the following code will work differently: - * ```ts - * dp.onNewMessage(console.log) - * console.log(await tg.sendText('me', 'hello')) - * ``` - * - if `disableNoDispatch` is `true`, the sent message will be - * dispatched to the event handler, thus it will be printed twice - * - if `disableNoDispatch` is `false`, the sent message will not be - * dispatched to the event handler, thus it will onlt be printed once - * - * Disabling it also may improve performance, but it's not guaranteed. - * - * @default false - */ - disableNoDispatch?: boolean - - /** - * Limit of {@link resolvePeerMany} internal async pool. - * - * Higher value means more parallel requests, but also - * higher risk of getting flood-wait errors. - * Most resolves will however likely be a DB cache hit. - * - * Only change this if you know what you're doing. - * - * @default 8 - */ - resolvePeerManyPoolLimit?: number - - /** - * When non-zero, allows the library to automatically handle Telegram - * media groups (e.g. albums) in {@link MessageGroup} updates - * in a given time interval (in ms). - * - * **Note**: this does not catch messages that happen to be consecutive, - * only messages belonging to the same "media group". - * - * This will cause up to `messageGroupingInterval` delay - * in handling media group messages. - * - * This option only applies to `new_message` updates, - * and the updates being grouped **will not** be dispatched on their own. - * - * Recommended value is 250 ms. - * - * @default 0 (disabled) - */ - messageGroupingInterval?: number -} diff --git a/packages/client/src/methods/auth/_initialize.ts b/packages/client/src/methods/auth/_initialize.ts deleted file mode 100644 index 9f2e0a83..00000000 --- a/packages/client/src/methods/auth/_initialize.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { MtUnsupportedError, tl } from '@mtcute/core' -import { assertTypeIs } from '@mtcute/core/utils' - -import { TelegramClient } from '../../client' -import { User } from '../../types' - -// @extension -interface AuthState { - // local copy of "self" in storage, - // so we can use it w/out relying on storage. - // they are both loaded and saved to storage along with the updates - // (see methods/updates) - _userId: number | null - _isBot: boolean - - _selfUsername: string | null -} - -// @initialize -function _initializeAuthState(this: TelegramClient) { - this._userId = null - this._isBot = false - this._selfUsername = null - this.log.prefix = '[USER N/A] ' -} - -/** @internal */ -export async function _onAuthorization( - this: TelegramClient, - auth: tl.auth.TypeAuthorization, - bot = false, -): Promise { - if (auth._ === 'auth.authorizationSignUpRequired') { - throw new MtUnsupportedError( - 'Signup is no longer supported by Telegram for non-official clients. Please use your mobile device to sign up.', - ) - } - - assertTypeIs('_onAuthorization (@ auth.authorization -> user)', auth.user, 'user') - - this._userId = auth.user.id - this.log.prefix = `[USER ${this._userId}] ` - this._isBot = bot - this._selfUsername = auth.user.username! - this._selfChanged = true - - await this.network.notifyLoggedIn(auth) - - await this._fetchUpdatesState() - await this._saveStorage() - - // telegram ignores invokeWithoutUpdates for auth methods - if (this.network.params.disableUpdates) this.network.resetSessions() - else this.startUpdatesLoop() - - return new User(this, auth.user) -} diff --git a/packages/client/src/methods/auth/_state.ts b/packages/client/src/methods/auth/_state.ts new file mode 100644 index 00000000..a699a279 --- /dev/null +++ b/packages/client/src/methods/auth/_state.ts @@ -0,0 +1,98 @@ +/* eslint-disable no-inner-declarations */ +import { BaseTelegramClient, MtUnsupportedError, tl } from '@mtcute/core' +import { assertTypeIs } from '@mtcute/core/utils' + +import { User } from '../../types/peers/user' + +const STATE_SYMBOL = Symbol('authState') +/** + * @internal + * @exported + */ +export interface AuthState { + // local copy of "self" in storage, + // so we can use it w/out relying on storage. + // they are both loaded and saved to storage along with the updates + // (see methods/updates) + userId: number | null + isBot: boolean + selfUsername: string | null + selfChanged?: boolean +} + +/** @internal */ +export function getAuthState(client: BaseTelegramClient): AuthState { + // eslint-disable-next-line + let state: AuthState = (client as any)[STATE_SYMBOL] + + if (!state) { + // init + // eslint-disable-next-line @typescript-eslint/no-explicit-any + state = (client as any)[STATE_SYMBOL] = { + userId: null, + isBot: false, + selfUsername: null, + } + + client.log.prefix = '[USER N/A] ' + + function onBeforeConnect() { + Promise.resolve(client.storage.getSelf()) + .then((self) => { + if (!self) return + + state.userId = self.userId + state.isBot = self.isBot + client.log.prefix = `[USER ${self.userId}] ` + }) + .catch((err) => client._emitError(err)) + } + + async function onBeforeStorageSave() { + if (state.selfChanged) { + await client.storage.setSelf( + state.userId ? + { + userId: state.userId, + isBot: state.isBot, + } : + null, + ) + state.selfChanged = false + } + } + + client.on('before_connect', onBeforeConnect) + client.beforeStorageSave(onBeforeStorageSave) + client.on('before_stop', () => { + client.off('before_connect', onBeforeConnect) + client.offBeforeStorageSave(onBeforeStorageSave) + }) + } + + return state +} + +/** @internal */ +export function _onAuthorization(client: BaseTelegramClient, auth: tl.auth.TypeAuthorization, bot = false): User { + if (auth._ === 'auth.authorizationSignUpRequired') { + throw new MtUnsupportedError( + 'Signup is no longer supported by Telegram for non-official clients. Please use your mobile device to sign up.', + ) + } + + assertTypeIs('_onAuthorization (@ auth.authorization -> user)', auth.user, 'user') + + const state = getAuthState(client) + state.userId = auth.user.id + state.isBot = bot + state.selfUsername = auth.user.username ?? null + state.selfChanged = true + + client.notifyLoggedIn(auth) + + // telegram ignores invokeWithoutUpdates for auth methods + if (client.network.params.disableUpdates) client.network.resetSessions() + + return new User(auth.user) +} diff --git a/packages/client/src/methods/auth/check-password.ts b/packages/client/src/methods/auth/check-password.ts index 4cbd1bf9..c2625511 100644 --- a/packages/client/src/methods/auth/check-password.ts +++ b/packages/client/src/methods/auth/check-password.ts @@ -1,7 +1,8 @@ +import { BaseTelegramClient } from '@mtcute/core' import { computeSrpParams } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { User } from '../../types' +import { _onAuthorization } from './_state' /** * Check your Two-Step verification password and log in @@ -9,19 +10,18 @@ import { User } from '../../types' * @param password Your Two-Step verification password * @returns The authorized user * @throws BadRequestError In case the password is invalid - * @internal */ -export async function checkPassword(this: TelegramClient, password: string): Promise { - const res = await this.call({ +export async function checkPassword(client: BaseTelegramClient, password: string): Promise { + const res = await client.call({ _: 'auth.checkPassword', password: await computeSrpParams( - this._crypto, - await this.call({ + client.crypto, + await client.call({ _: 'account.getPassword', }), password, ), }) - return this._onAuthorization(res) + return _onAuthorization(client, res) } diff --git a/packages/client/src/methods/auth/get-password-hint.ts b/packages/client/src/methods/auth/get-password-hint.ts index 460517b5..9bc05030 100644 --- a/packages/client/src/methods/auth/get-password-hint.ts +++ b/packages/client/src/methods/auth/get-password-hint.ts @@ -1,13 +1,14 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' /** * Get your Two-Step Verification password hint. * * @returns The password hint as a string, if any - * @internal */ -export function getPasswordHint(this: TelegramClient): Promise { - return this.call({ - _: 'account.getPassword', - }).then((res) => res.hint ?? null) +export function getPasswordHint(client: BaseTelegramClient): Promise { + return client + .call({ + _: 'account.getPassword', + }) + .then((res) => res.hint ?? null) } diff --git a/packages/client/src/methods/auth/log-out.ts b/packages/client/src/methods/auth/log-out.ts index 2d9ac71c..07070270 100644 --- a/packages/client/src/methods/auth/log-out.ts +++ b/packages/client/src/methods/auth/log-out.ts @@ -1,4 +1,6 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + +import { getAuthState } from './_state' /** * Log out from Telegram account and optionally reset the session storage. @@ -7,20 +9,20 @@ import { TelegramClient } from '../../client' * the same {@link TelegramClient} instance. * * @returns On success, `true` is returned - * @internal */ -export async function logOut(this: TelegramClient): Promise { - await this.call({ _: 'auth.logOut' }) +export async function logOut(client: BaseTelegramClient): Promise { + await client.call({ _: 'auth.logOut' }) - this._userId = null - this._isBot = false - // some implicit magic in favor of performance - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment - this._pts = this._seq = this._date = undefined as any - this._selfUsername = null - this._selfChanged = true - this.storage.reset() - await this._saveStorage() + const authState = getAuthState(client) + authState.userId = null + authState.isBot = false + authState.selfUsername = null + authState.selfChanged = true + + client.emit('logged_out') + + client.storage.reset() + await client.saveStorage() return true } diff --git a/packages/client/src/methods/auth/recover-password.ts b/packages/client/src/methods/auth/recover-password.ts index 67966207..1a8b22b5 100644 --- a/packages/client/src/methods/auth/recover-password.ts +++ b/packages/client/src/methods/auth/recover-password.ts @@ -1,15 +1,16 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { User } from '../../types' +import { _onAuthorization } from './_state' /** * Recover your password with a recovery code and log in. * * @returns The authorized user * @throws BadRequestError In case the code is invalid - * @internal */ export async function recoverPassword( - this: TelegramClient, + client: BaseTelegramClient, params: { /** The recovery code sent via email */ recoveryCode: string @@ -17,10 +18,10 @@ export async function recoverPassword( ): Promise { const { recoveryCode } = params - const res = await this.call({ + const res = await client.call({ _: 'auth.recoverPassword', code: recoveryCode, }) - return this._onAuthorization(res) + return _onAuthorization(client, res) } diff --git a/packages/client/src/methods/auth/resend-code.ts b/packages/client/src/methods/auth/resend-code.ts index 6329a5b0..853ff9af 100644 --- a/packages/client/src/methods/auth/resend-code.ts +++ b/packages/client/src/methods/auth/resend-code.ts @@ -1,6 +1,6 @@ +import { BaseTelegramClient } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { SentCode } from '../../types' import { normalizePhoneNumber } from '../../utils/misc-utils' @@ -9,11 +9,9 @@ import { normalizePhoneNumber } from '../../utils/misc-utils' * * The type of the code to be re-sent is specified in the `nextType` attribute of * {@link SentCode} object returned by {@link sendCode} - * - * @internal */ export async function resendCode( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Phone number in international format */ phone: string @@ -24,7 +22,7 @@ export async function resendCode( ): Promise { const { phone, phoneCodeHash } = params - const res = await this.call({ + const res = await client.call({ _: 'auth.resendCode', phoneNumber: normalizePhoneNumber(phone), phoneCodeHash, diff --git a/packages/client/src/methods/auth/run.ts b/packages/client/src/methods/auth/run.ts index 9949d0df..3d78cc8f 100644 --- a/packages/client/src/methods/auth/run.ts +++ b/packages/client/src/methods/auth/run.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { User } from '../../types' +import { start } from './start' /** * Simple wrapper that calls {@link start} and then @@ -9,16 +11,15 @@ import { User } from '../../types' * Errors that were encountered while calling {@link start} * and `then` will be emitted as usual, and can be caught with {@link onError} * - * @param params Parameters to be passed to {@link TelegramClient.start} - * @param then Function to be called after {@link TelegramClient.start} returns - * @internal + * @param params Parameters to be passed to {@link start} + * @param then Function to be called after {@link start} returns */ export function run( - this: TelegramClient, - params: Parameters[0], + client: BaseTelegramClient, + params: Parameters[1], then?: (user: User) => void | Promise, ): void { - this.start(params) + start(client, params) .then(then) - .catch((err) => this._emitError(err)) + .catch((err) => client._emitError(err)) } diff --git a/packages/client/src/methods/auth/send-code.ts b/packages/client/src/methods/auth/send-code.ts index 9f69ed4a..b36ed406 100644 --- a/packages/client/src/methods/auth/send-code.ts +++ b/packages/client/src/methods/auth/send-code.ts @@ -1,6 +1,6 @@ +import { BaseTelegramClient } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { SentCode } from '../../types' import { normalizePhoneNumber } from '../../utils/misc-utils' @@ -8,10 +8,9 @@ import { normalizePhoneNumber } from '../../utils/misc-utils' * Send the confirmation code to the given phone number * * @returns An object containing information about the sent confirmation code - * @internal */ export async function sendCode( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Phone number in international format */ phone: string @@ -19,11 +18,12 @@ export async function sendCode( ): Promise { const phone = normalizePhoneNumber(params.phone) - const res = await this.call({ + const res = await client.call({ _: 'auth.sendCode', phoneNumber: phone, - apiId: this.network._initConnectionParams.apiId, - apiHash: this._apiHash, + apiId: client.network._initConnectionParams.apiId, + // eslint-disable-next-line dot-notation + apiHash: client['_apiHash'], settings: { _: 'codeSettings' }, }) diff --git a/packages/client/src/methods/auth/send-recovery-code.ts b/packages/client/src/methods/auth/send-recovery-code.ts index f624c335..d6a4e6ce 100644 --- a/packages/client/src/methods/auth/send-recovery-code.ts +++ b/packages/client/src/methods/auth/send-recovery-code.ts @@ -1,13 +1,14 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' /** * Send a code to email needed to recover your password * * @returns String containing email pattern to which the recovery code was sent - * @internal */ -export function sendRecoveryCode(this: TelegramClient): Promise { - return this.call({ - _: 'auth.requestPasswordRecovery', - }).then((res) => res.emailPattern) +export function sendRecoveryCode(client: BaseTelegramClient): Promise { + return client + .call({ + _: 'auth.requestPasswordRecovery', + }) + .then((res) => res.emailPattern) } diff --git a/packages/client/src/methods/auth/sign-in-bot.ts b/packages/client/src/methods/auth/sign-in-bot.ts index faa0b9a7..a2ae64fe 100644 --- a/packages/client/src/methods/auth/sign-in-bot.ts +++ b/packages/client/src/methods/auth/sign-in-bot.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { User } from '../../types' +import { _onAuthorization } from './_state' /** * Authorize a bot using its token issued by [@BotFather](//t.me/BotFather) @@ -7,16 +9,16 @@ import { User } from '../../types' * @param token Bot token issued by BotFather * @returns Bot's {@link User} object * @throws BadRequestError In case the bot token is invalid - * @internal */ -export async function signInBot(this: TelegramClient, token: string): Promise { - const res = await this.call({ +export async function signInBot(client: BaseTelegramClient, token: string): Promise { + const res = await client.call({ _: 'auth.importBotAuthorization', flags: 0, - apiId: this.network._initConnectionParams.apiId, - apiHash: this._apiHash, + apiId: client.network._initConnectionParams.apiId, + // eslint-disable-next-line dot-notation + apiHash: client['_apiHash'], botAuthToken: token, }) - return this._onAuthorization(res, true) + return _onAuthorization(client, res, true) } diff --git a/packages/client/src/methods/auth/sign-in.ts b/packages/client/src/methods/auth/sign-in.ts index 9369c74e..3aae1a26 100644 --- a/packages/client/src/methods/auth/sign-in.ts +++ b/packages/client/src/methods/auth/sign-in.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { User } from '../../types' import { normalizePhoneNumber } from '../../utils/misc-utils' +import { _onAuthorization } from './_state' /** * Authorize a user in Telegram with a valid confirmation code. @@ -8,10 +10,9 @@ import { normalizePhoneNumber } from '../../utils/misc-utils' * @returns If the code was valid and authorization succeeded, the {@link User} is returned. * @throws BadRequestError In case the arguments are invalid * @throws SessionPasswordNeededError In case a password is needed to sign in - * @internal */ export async function signIn( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Phone number in international format */ phone: string @@ -23,12 +24,12 @@ export async function signIn( ): Promise { const { phone, phoneCodeHash, phoneCode } = params - const res = await this.call({ + const res = await client.call({ _: 'auth.signIn', phoneNumber: normalizePhoneNumber(phone), phoneCodeHash, phoneCode, }) - return this._onAuthorization(res) + return _onAuthorization(client, res) } diff --git a/packages/client/src/methods/auth/start-test.ts b/packages/client/src/methods/auth/start-test.ts index 4f3af8f3..aa63a256 100644 --- a/packages/client/src/methods/auth/start-test.ts +++ b/packages/client/src/methods/auth/start-test.ts @@ -1,7 +1,8 @@ -import { MtArgumentError } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' -import { TelegramClient } from '../../client' import { User } from '../../types' +import { logOut } from './log-out' +import { start } from './start' /** * Utility function to quickly authorize on test DC @@ -12,10 +13,9 @@ import { User } from '../../types' * > are using a test DC in `primaryDc` parameter. * * @param params Additional parameters - * @internal */ export async function startTest( - this: TelegramClient, + client: BaseTelegramClient, params?: { /** * Whether to log out if current session is logged in. @@ -41,13 +41,15 @@ export async function startTest( if (params.logout) { try { - await this.logOut() + await logOut(client) } catch (e) {} } - const availableDcs = await this.call({ - _: 'help.getConfig', - }).then((res) => res.dcOptions) + const availableDcs = await client + .call({ + _: 'help.getConfig', + }) + .then((res) => res.dcOptions) let phone = params.phone @@ -61,7 +63,7 @@ export async function startTest( throw new MtArgumentError(`${phone} has invalid DC ID (${id})`) } } else { - let dcId = this._defaultDcs.main.id + let dcId = client.network.getPrimaryDcId() if (params.dcId) { if (!availableDcs.find((dc) => dc.id === params!.dcId)) { @@ -78,7 +80,7 @@ export async function startTest( let code = '' - return this.start({ + return start(client, { phone, code: () => code, codeSentCallback: (sent) => { diff --git a/packages/client/src/methods/auth/start.ts b/packages/client/src/methods/auth/start.ts index 14eb32f4..89debe31 100644 --- a/packages/client/src/methods/auth/start.ts +++ b/packages/client/src/methods/auth/start.ts @@ -1,10 +1,16 @@ /* eslint-disable no-console */ -import { MaybeAsync, MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MaybeAsync, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { MaybeDynamic, SentCode, User } from '../../types' import { normalizePhoneNumber, resolveMaybeDynamic } from '../../utils/misc-utils' +import { getMe } from '../users/get-me' +import { checkPassword } from './check-password' +import { resendCode } from './resend-code' +import { sendCode } from './send-code' +import { signIn } from './sign-in' +import { signInBot } from './sign-in-bot' +// @manual // @available=both /** * Start the client in an interactive and declarative manner, @@ -18,11 +24,9 @@ import { normalizePhoneNumber, resolveMaybeDynamic } from '../../utils/misc-util * This method is intended for simple and fast use in automated * scripts and bots. If you are developing a custom client, * you'll probably need to use other auth methods. - * - * @internal */ export async function start( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * String session exported using {@link TelegramClient.exportSession}. @@ -80,58 +84,25 @@ export async function start( * to show a GUI alert of some kind. * Defaults to `console.log`. * - * This method is called *before* {@link TelegramClient.start.params.code}. + * This method is called *before* {@link start.params.code}. * * @param code */ codeSentCallback?: (code: SentCode) => MaybeAsync - - /** - * Whether to "catch up" (load missed updates). - * Only applicable if the saved session already - * contained authorization and updates state. - * - * Note: you should register your handlers - * before calling `start()`, otherwise they will - * not be called. - * - * Note: In case the storage was not properly - * closed the last time, "catching up" might - * result in duplicate updates. - * - * Defaults to `false`. - */ - catchUp?: boolean }, ): Promise { if (params.session) { - this.importSession(params.session, params.sessionForce) + client.importSession(params.session, params.sessionForce) } try { - const me = await this.getMe() + const me = await getMe(client) // user is already authorized - this.log.prefix = `[USER ${me.id}] ` - this.log.info('Logged in as %s (ID: %s, username: %s, bot: %s)', me.displayName, me.id, me.username, me.isBot) + client.log.info('Logged in as %s (ID: %s, username: %s, bot: %s)', me.displayName, me.id, me.username, me.isBot) - this.network.setIsPremium(me.isPremium) - - if (!this.network.params.disableUpdates) { - this._catchUpChannels = Boolean(params.catchUp) - - if (!params.catchUp) { - // otherwise we will catch up as soon as we receive a new update - await this._fetchUpdatesState() - } - - this.startUpdatesLoop() - - if (params.catchUp) { - this.catchUp() - } - } + client.network.setIsPremium(me.isPremium) return me } catch (e) { @@ -157,13 +128,13 @@ export async function start( throw new MtArgumentError('Either bot token or phone number must be provided') } - return await this.signInBot(botToken) + return await signInBot(client, botToken) } - let sentCode = await this.sendCode({ phone }) + let sentCode = await sendCode(client, { phone }) if (params.forceSms && sentCode.type === 'app') { - sentCode = await this.resendCode({ phone, phoneCodeHash: sentCode.phoneCodeHash }) + sentCode = await resendCode(client, { phone, phoneCodeHash: sentCode.phoneCodeHash }) } if (params.codeSentCallback) { @@ -179,7 +150,7 @@ export async function start( if (!code) throw new tl.RpcError(400, 'PHONE_CODE_EMPTY') try { - return await this.signIn({ phone, phoneCodeHash: sentCode.phoneCodeHash, phoneCode: code }) + return await signIn(client, { phone, phoneCodeHash: sentCode.phoneCodeHash, phoneCode: code }) } catch (e) { if (!tl.RpcError.is(e)) throw e @@ -219,7 +190,7 @@ export async function start( const password = await resolveMaybeDynamic(params.password) try { - return await this.checkPassword(password) + return await checkPassword(client, password) } catch (e) { if (typeof params.password !== 'function') { throw new MtArgumentError('Provided password was invalid') diff --git a/packages/client/src/methods/bots/answer-callback-query.ts b/packages/client/src/methods/bots/answer-callback-query.ts index f21d0757..3ccde707 100644 --- a/packages/client/src/methods/bots/answer-callback-query.ts +++ b/packages/client/src/methods/bots/answer-callback-query.ts @@ -1,16 +1,13 @@ -import { Long } from '@mtcute/core' - -import { TelegramClient } from '../../client' +import { BaseTelegramClient, Long } from '@mtcute/core' /** * Send an answer to a callback query. * * @param queryId ID of the callback query * @param params Parameters of the answer - * @internal */ export async function answerCallbackQuery( - this: TelegramClient, + client: BaseTelegramClient, queryId: Long, params?: { /** @@ -50,7 +47,7 @@ export async function answerCallbackQuery( ): Promise { const { cacheTime = 0, text, alert, url } = params ?? {} - await this.call({ + await client.call({ _: 'messages.setBotCallbackAnswer', queryId, cacheTime, diff --git a/packages/client/src/methods/bots/answer-inline-query.ts b/packages/client/src/methods/bots/answer-inline-query.ts index cf974449..7c018b32 100644 --- a/packages/client/src/methods/bots/answer-inline-query.ts +++ b/packages/client/src/methods/bots/answer-inline-query.ts @@ -1,6 +1,5 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { BotInline, InputInlineResult } from '../../types' /** @@ -9,10 +8,9 @@ import { BotInline, InputInlineResult } from '../../types' * @param queryId Inline query ID * @param results Results of the query * @param params Additional parameters - * @internal */ export async function answerInlineQuery( - this: TelegramClient, + client: BaseTelegramClient, queryId: tl.Long, results: InputInlineResult[], params?: { @@ -97,9 +95,9 @@ export async function answerInlineQuery( ): Promise { const { cacheTime = 300, gallery, private: priv, nextOffset, switchPm, parseMode } = params ?? {} - const [defaultGallery, tlResults] = await BotInline._convertToTl(this, results, parseMode) + const [defaultGallery, tlResults] = await BotInline._convertToTl(client, results, parseMode) - await this.call({ + await client.call({ _: 'messages.setInlineBotResults', queryId, results: tlResults, diff --git a/packages/client/src/methods/bots/answer-pre-checkout-query.ts b/packages/client/src/methods/bots/answer-pre-checkout-query.ts index 9e78d9ee..51ba014f 100644 --- a/packages/client/src/methods/bots/answer-pre-checkout-query.ts +++ b/packages/client/src/methods/bots/answer-pre-checkout-query.ts @@ -1,15 +1,12 @@ -import { tl } from '@mtcute/core' - -import { TelegramClient } from '../../client' +import { BaseTelegramClient, tl } from '@mtcute/core' /** * Answer a pre-checkout query. * * @param queryId Pre-checkout query ID - * @internal */ export async function answerPreCheckoutQuery( - this: TelegramClient, + client: BaseTelegramClient, queryId: tl.Long, params?: { /** If pre-checkout is rejected, error message to show to the user */ @@ -18,7 +15,7 @@ export async function answerPreCheckoutQuery( ): Promise { const { error } = params ?? {} - await this.call({ + await client.call({ _: 'messages.setBotPrecheckoutResults', queryId, success: !error, diff --git a/packages/client/src/methods/bots/delete-my-commands.ts b/packages/client/src/methods/bots/delete-my-commands.ts index 2e9650d9..da2216f2 100644 --- a/packages/client/src/methods/bots/delete-my-commands.ts +++ b/packages/client/src/methods/bots/delete-my-commands.ts @@ -1,7 +1,7 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { BotCommands } from '../../types' +import { _normalizeCommandScope } from './normalize-command-scope' /** * Delete commands for the current bot and the given scope. @@ -9,11 +9,9 @@ import { BotCommands } from '../../types' * 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, + client: BaseTelegramClient, params?: { /** * Scope of the commands. @@ -29,12 +27,12 @@ export async function deleteMyCommands( }, ): Promise { const scope: tl.TypeBotCommandScope = params?.scope ? - await this._normalizeCommandScope(params.scope) : + await _normalizeCommandScope(client, params.scope) : { _: 'botCommandScopeDefault', } - await this.call({ + await client.call({ _: 'bots.resetBotCommands', scope, langCode: params?.langCode ?? '', diff --git a/packages/client/src/methods/bots/get-bot-info.ts b/packages/client/src/methods/bots/get-bot-info.ts index 78861640..4f7a29cb 100644 --- a/packages/client/src/methods/bots/get-bot-info.ts +++ b/packages/client/src/methods/bots/get-bot-info.ts @@ -1,16 +1,14 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Gets information about a bot the current uzer owns (or the current bot) - * - * @internal */ export async function getBotInfo( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * When called by a user, a bot the user owns must be specified. @@ -27,9 +25,9 @@ export async function getBotInfo( ): Promise { const { bot, langCode = '' } = params - return this.call({ + return client.call({ _: 'bots.getBotInfo', - bot: bot ? normalizeToInputUser(await this.resolvePeer(bot), bot) : undefined, + bot: bot ? normalizeToInputUser(await resolvePeer(client, bot), bot) : undefined, langCode: langCode, }) } diff --git a/packages/client/src/methods/bots/get-bot-menu-button.ts b/packages/client/src/methods/bots/get-bot-menu-button.ts index 171b2f35..e3c23fa0 100644 --- a/packages/client/src/methods/bots/get-bot-menu-button.ts +++ b/packages/client/src/methods/bots/get-bot-menu-button.ts @@ -1,17 +1,15 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Fetches the menu button set for the given user. - * - * @internal */ -export async function getBotMenuButton(this: TelegramClient, user: InputPeerLike): Promise { - return await this.call({ +export async function getBotMenuButton(client: BaseTelegramClient, user: InputPeerLike): Promise { + return await client.call({ _: 'bots.getBotMenuButton', - userId: normalizeToInputUser(await this.resolvePeer(user), user), + userId: normalizeToInputUser(await resolvePeer(client, user), user), }) } diff --git a/packages/client/src/methods/bots/get-callback-answer.ts b/packages/client/src/methods/bots/get-callback-answer.ts index 49a37ec2..2693e7e3 100644 --- a/packages/client/src/methods/bots/get-callback-answer.ts +++ b/packages/client/src/methods/bots/get-callback-answer.ts @@ -1,18 +1,17 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { computeSrpParams } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Request a callback answer from a bot, * i.e. click an inline button that contains data. * * @param params - * @internal */ export async function getCallbackAnswer( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID where the message was found */ chatId: InputPeerLike @@ -49,14 +48,14 @@ export async function getCallbackAnswer( let password: tl.TypeInputCheckPasswordSRP | undefined = undefined if (params?.password) { - const pwd = await this.call({ _: 'account.getPassword' }) - password = await computeSrpParams(this._crypto, pwd, params.password) + const pwd = await client.call({ _: 'account.getPassword' }) + password = await computeSrpParams(client.crypto, pwd, params.password) } - return await this.call( + return await client.call( { _: 'messages.getBotCallbackAnswer', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), msgId: message, data: typeof data === 'string' ? Buffer.from(data) : data, password, diff --git a/packages/client/src/methods/bots/get-game-high-scores.ts b/packages/client/src/methods/bots/get-game-high-scores.ts index 0afafb7f..8b611835 100644 --- a/packages/client/src/methods/bots/get-game-high-scores.ts +++ b/packages/client/src/methods/bots/get-game-high-scores.ts @@ -1,17 +1,15 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { GameHighScore, InputPeerLike, PeersIndex } from '../../types' import { normalizeInlineId } from '../../utils/inline-utils' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get high scores of a game - * - * @internal */ export async function getGameHighScores( - this: TelegramClient, + client: BaseTelegramClient, params: { /** ID of the chat where the game was found */ chatId: InputPeerLike @@ -25,17 +23,17 @@ export async function getGameHighScores( ): Promise { const { chatId, message, userId } = params - const chat = await this.resolvePeer(chatId) + const chat = await resolvePeer(client, chatId) let user: tl.TypeInputUser if (userId) { - user = normalizeToInputUser(await this.resolvePeer(userId), userId) + user = normalizeToInputUser(await resolvePeer(client, userId), userId) } else { user = { _: 'inputUserEmpty' } } - const res = await this.call({ + const res = await client.call({ _: 'messages.getGameHighScores', peer: chat, id: message, @@ -44,7 +42,7 @@ export async function getGameHighScores( const peers = PeersIndex.from(res) - return res.scores.map((score) => new GameHighScore(this, score, peers)) + return res.scores.map((score) => new GameHighScore(score, peers)) } /** @@ -52,10 +50,9 @@ export async function getGameHighScores( * * @param messageId ID of the inline message containing the game * @param userId ID of the user to find high scores for - * @internal */ export async function getInlineGameHighScores( - this: TelegramClient, + client: BaseTelegramClient, messageId: string | tl.TypeInputBotInlineMessageID, userId?: InputPeerLike, ): Promise { @@ -64,12 +61,12 @@ export async function getInlineGameHighScores( let user: tl.TypeInputUser if (userId) { - user = normalizeToInputUser(await this.resolvePeer(userId), userId) + user = normalizeToInputUser(await resolvePeer(client, userId), userId) } else { user = { _: 'inputUserEmpty' } } - const res = await this.call( + const res = await client.call( { _: 'messages.getInlineGameHighScores', id, @@ -80,5 +77,5 @@ export async function getInlineGameHighScores( const peers = PeersIndex.from(res) - return res.scores.map((score) => new GameHighScore(this, score, peers)) + return res.scores.map((score) => new GameHighScore(score, peers)) } diff --git a/packages/client/src/methods/bots/get-my-commands.ts b/packages/client/src/methods/bots/get-my-commands.ts index f02bb37a..97375cb5 100644 --- a/packages/client/src/methods/bots/get-my-commands.ts +++ b/packages/client/src/methods/bots/get-my-commands.ts @@ -1,18 +1,16 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { BotCommands } from '../../types' +import { _normalizeCommandScope } from './normalize-command-scope' /** * 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, + client: BaseTelegramClient, params?: { /** * Scope of the commands. @@ -27,10 +25,10 @@ export async function getMyCommands( langCode?: string }, ): Promise { - return this.call({ + return client.call({ _: 'bots.getBotCommands', scope: params?.scope ? - await this._normalizeCommandScope(params.scope) : + await _normalizeCommandScope(client, params.scope) : { _: 'botCommandScopeDefault', }, diff --git a/packages/client/src/methods/bots/normalize-command-scope.ts b/packages/client/src/methods/bots/normalize-command-scope.ts index 2fa8c8e3..6b6597a9 100644 --- a/packages/client/src/methods/bots/normalize-command-scope.ts +++ b/packages/client/src/methods/bots/normalize-command-scope.ts @@ -1,12 +1,12 @@ -import { assertNever, tl } from '@mtcute/core' +import { assertNever, BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { BotCommands } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** @internal */ export async function _normalizeCommandScope( - this: TelegramClient, + client: BaseTelegramClient, scope: tl.TypeBotCommandScope | BotCommands.IntermediateScope, ): Promise { if (tl.isAnyBotCommandScope(scope)) return scope @@ -14,7 +14,7 @@ export async function _normalizeCommandScope( switch (scope.type) { case 'peer': case 'peer_admins': { - const peer = await this.resolvePeer(scope.peer) + const peer = await resolvePeer(client, scope.peer) return { _: scope.type === 'peer' ? 'botCommandScopePeer' : 'botCommandScopePeerAdmins', @@ -22,8 +22,8 @@ export async function _normalizeCommandScope( } } case 'member': { - const user = normalizeToInputUser(await this.resolvePeer(scope.user), scope.user) - const chat = await this.resolvePeer(scope.chat) + const user = normalizeToInputUser(await resolvePeer(client, scope.user), scope.user) + const chat = await resolvePeer(client, scope.chat) return { _: 'botCommandScopePeerUser', diff --git a/packages/client/src/methods/bots/set-bot-info.ts b/packages/client/src/methods/bots/set-bot-info.ts index e31fc587..dab0835a 100644 --- a/packages/client/src/methods/bots/set-bot-info.ts +++ b/packages/client/src/methods/bots/set-bot-info.ts @@ -1,14 +1,14 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Sets information about a bot the current uzer owns (or the current bot) - * - * @internal */ export async function setBotInfo( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * When called by a user, a bot the user owns must be specified. @@ -34,9 +34,9 @@ export async function setBotInfo( ): Promise { const { bot, langCode = '', name, bio, description } = params - await this.call({ + await client.call({ _: 'bots.setBotInfo', - bot: bot ? normalizeToInputUser(await this.resolvePeer(bot), bot) : undefined, + bot: bot ? normalizeToInputUser(await resolvePeer(client, bot), bot) : undefined, langCode: langCode, name, about: bio, diff --git a/packages/client/src/methods/bots/set-bot-menu-button.ts b/packages/client/src/methods/bots/set-bot-menu-button.ts index dcb16c0c..2a54aba9 100644 --- a/packages/client/src/methods/bots/set-bot-menu-button.ts +++ b/packages/client/src/methods/bots/set-bot-menu-button.ts @@ -1,22 +1,20 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Sets a menu button for the given user. - * - * @internal */ export async function setBotMenuButton( - this: TelegramClient, + client: BaseTelegramClient, user: InputPeerLike, button: tl.TypeBotMenuButton, ): Promise { - await this.call({ + await client.call({ _: 'bots.setBotMenuButton', - userId: normalizeToInputUser(await this.resolvePeer(user), user), + userId: normalizeToInputUser(await resolvePeer(client, user), user), button, }) } diff --git a/packages/client/src/methods/bots/set-game-score.ts b/packages/client/src/methods/bots/set-game-score.ts index 6bbbbef9..11a19196 100644 --- a/packages/client/src/methods/bots/set-game-score.ts +++ b/packages/client/src/methods/bots/set-game-score.ts @@ -1,19 +1,19 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, Message } from '../../types' import { normalizeInlineId } from '../../utils/inline-utils' import { normalizeToInputUser } from '../../utils/peer-utils' +import { _findMessageInUpdate } from '../messages/find-in-update' +import { resolvePeer } from '../users/resolve-peer' /** * Set a score of a user in a game * * @param params * @returns The modified message - * @internal */ export async function setGameScore( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat where the game was found */ chatId: InputPeerLike @@ -42,10 +42,10 @@ export async function setGameScore( ): Promise { const { chatId, message, userId, score, noEdit, force } = params - const user = normalizeToInputUser(await this.resolvePeer(userId), userId) - const chat = await this.resolvePeer(chatId) + const user = normalizeToInputUser(await resolvePeer(client, userId), userId) + const chat = await resolvePeer(client, chatId) - const res = await this.call({ + const res = await client.call({ _: 'messages.setGameScore', peer: chat, id: message, @@ -55,7 +55,7 @@ export async function setGameScore( force, }) - return this._findMessageInUpdate(res, true) + return _findMessageInUpdate(client, res, true) } /** @@ -63,10 +63,9 @@ export async function setGameScore( * an inline message * * @param params - * @internal */ export async function setInlineGameScore( - this: TelegramClient, + client: BaseTelegramClient, params: { /** ID of the inline message */ messageId: string | tl.TypeInputBotInlineMessageID @@ -89,11 +88,11 @@ export async function setInlineGameScore( ): Promise { const { messageId, userId, score, noEdit, force } = params - const user = normalizeToInputUser(await this.resolvePeer(userId), userId) + const user = normalizeToInputUser(await resolvePeer(client, userId), userId) const id = normalizeInlineId(messageId) - await this.call( + await client.call( { _: 'messages.setInlineGameScore', id, diff --git a/packages/client/src/methods/bots/set-my-commands.ts b/packages/client/src/methods/bots/set-my-commands.ts index f32e63d2..2e9f9eff 100644 --- a/packages/client/src/methods/bots/set-my-commands.ts +++ b/packages/client/src/methods/bots/set-my-commands.ts @@ -1,17 +1,15 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { BotCommands } from '../../types' +import { _normalizeCommandScope } from './normalize-command-scope' /** * 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, + client: BaseTelegramClient, params: { /** * New list of bot commands for the given scope. @@ -34,20 +32,20 @@ export async function setMyCommands( }, ): Promise { const scope: tl.TypeBotCommandScope = params.scope ? - await this._normalizeCommandScope(params.scope) : + await _normalizeCommandScope(client, params.scope) : { _: 'botCommandScopeDefault', } if (params.commands?.length) { - await this.call({ + await client.call({ _: 'bots.setBotCommands', commands: params.commands, scope, langCode: params.langCode ?? '', }) } else { - await this.call({ + await client.call({ _: 'bots.resetBotCommands', scope, langCode: params.langCode ?? '', diff --git a/packages/client/src/methods/bots/set-my-default-rights.ts b/packages/client/src/methods/bots/set-my-default-rights.ts index 0b96533f..a06d56ee 100644 --- a/packages/client/src/methods/bots/set-my-default-rights.ts +++ b/packages/client/src/methods/bots/set-my-default-rights.ts @@ -1,14 +1,10 @@ -import { tl } from '@mtcute/core' - -import { TelegramClient } from '../../client' +import { BaseTelegramClient, tl } from '@mtcute/core' /** * Sets the default chat permissions for the bot in the supergroup or channel. - * - * @internal */ export async function setMyDefaultRights( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Whether to target groups or channels. */ target: 'channel' | 'group' @@ -18,7 +14,7 @@ export async function setMyDefaultRights( ): Promise { const { target, rights } = params - await this.call({ + await client.call({ _: target === 'group' ? 'bots.setBotGroupDefaultAdminRights' : 'bots.setBotBroadcastDefaultAdminRights', adminRights: { _: 'chatAdminRights', diff --git a/packages/client/src/methods/chats/add-chat-members.ts b/packages/client/src/methods/chats/add-chat-members.ts index 1e97dd6b..5243a8fd 100644 --- a/packages/client/src/methods/chats/add-chat-members.ts +++ b/packages/client/src/methods/chats/add-chat-members.ts @@ -1,6 +1,5 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, MtInvalidPeerTypeError } from '../../types' import { isInputPeerChannel, @@ -8,16 +7,17 @@ import { normalizeToInputChannel, normalizeToInputUser, } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' +import { resolvePeerMany } from '../users/resolve-peer-many' /** * Add one or more new members to a group, supergroup or channel. * * @param chatId ID of the chat or its username * @param users ID(s) of the user(s) to add - * @internal */ export async function addChatMembers( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, users: MaybeArray, params: { @@ -32,28 +32,29 @@ export async function addChatMembers( ): Promise { const { forwardCount = 100 } = params - const chat = await this.resolvePeer(chatId) + const chat = await resolvePeer(client, chatId) if (!Array.isArray(users)) users = [users] if (isInputPeerChat(chat)) { for (const user of users) { - const p = normalizeToInputUser(await this.resolvePeer(user)) + const p = normalizeToInputUser(await resolvePeer(client, user)) - const updates = await this.call({ + const updates = await client.call({ _: 'messages.addChatUser', chatId: chat.chatId, userId: p, fwdLimit: forwardCount, }) - this._handleUpdate(updates) + client.network.handleUpdate(updates) } } else if (isInputPeerChannel(chat)) { - const updates = await this.call({ + const updates = await client.call({ _: 'channels.inviteToChannel', channel: normalizeToInputChannel(chat), - users: await this.resolvePeerMany(users, normalizeToInputUser), + users: await resolvePeerMany(client, users, normalizeToInputUser), }) - this._handleUpdate(updates) + + client.network.handleUpdate(updates) } else throw new MtInvalidPeerTypeError(chatId, 'chat or channel') } diff --git a/packages/client/src/methods/chats/archive-chats.ts b/packages/client/src/methods/chats/archive-chats.ts index 0ad4ea63..47d159e6 100644 --- a/packages/client/src/methods/chats/archive-chats.ts +++ b/packages/client/src/methods/chats/archive-chats.ts @@ -1,20 +1,19 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeerMany } from '../users/resolve-peer-many' /** * Archive one or more chats * * @param chats Chat ID(s), username(s), phone number(s), `"me"` or `"self"` - * @internal */ -export async function archiveChats(this: TelegramClient, chats: MaybeArray): Promise { +export async function archiveChats(client: BaseTelegramClient, chats: MaybeArray): Promise { if (!Array.isArray(chats)) chats = [chats] - const resolvedPeers = await this.resolvePeerMany(chats) + const resolvedPeers = await resolvePeerMany(client, chats) - const updates = await this.call({ + const updates = await client.call({ _: 'folders.editPeerFolders', folderPeers: resolvedPeers.map((peer) => ({ _: 'inputFolderPeer', @@ -22,5 +21,5 @@ export async function archiveChats(this: TelegramClient, chats: MaybeArray { - const chat = await this.resolvePeer(params.chatId) - const peer = await this.resolvePeer(params.participantId) + const chat = await resolvePeer(client, params.chatId) + const peer = await resolvePeer(client, params.participantId) let res if (isInputPeerChannel(chat)) { - res = await this.call({ + res = await client.call({ _: 'channels.editBanned', channel: normalizeToInputChannel(chat), participant: peer, @@ -46,7 +46,7 @@ export async function banChatMember( }, }) } else if (isInputPeerChat(chat)) { - res = await this.call({ + res = await client.call({ _: 'messages.deleteChatUser', chatId: chat.chatId, userId: normalizeToInputUser(peer), @@ -54,7 +54,7 @@ export async function banChatMember( } else throw new MtInvalidPeerTypeError(params.chatId, 'chat or channel') try { - return this._findMessageInUpdate(res) + return _findMessageInUpdate(client, res) } catch (e) { if (e instanceof MtTypeAssertionError && e.context === '_findInUpdate (@ .updates[*])') { // no service message diff --git a/packages/client/src/methods/chats/create-channel.ts b/packages/client/src/methods/chats/create-channel.ts index 03994ee3..b9388506 100644 --- a/packages/client/src/methods/chats/create-channel.ts +++ b/packages/client/src/methods/chats/create-channel.ts @@ -1,4 +1,5 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { Chat } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' @@ -6,10 +7,9 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * Create a new broadcast channel * * @returns Newly created channel - * @internal */ export async function createChannel( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Channel title @@ -24,7 +24,7 @@ export async function createChannel( ): Promise { const { title, description = '' } = params - const res = await this.call({ + const res = await client.call({ _: 'channels.createChannel', title, about: description, @@ -33,7 +33,7 @@ export async function createChannel( assertIsUpdatesGroup('channels.createChannel', res) - this._handleUpdate(res) + client.network.handleUpdate(res) - return new Chat(this, res.chats[0]) + return new Chat(res.chats[0]) } diff --git a/packages/client/src/methods/chats/create-group.ts b/packages/client/src/methods/chats/create-group.ts index 63bcd9c0..ae8d7d5d 100644 --- a/packages/client/src/methods/chats/create-group.ts +++ b/packages/client/src/methods/chats/create-group.ts @@ -1,20 +1,18 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { Chat, InputPeerLike } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeerMany } from '../users/resolve-peer-many' /** * Create a legacy group chat * * If you want to create a supergroup, use {@link createSupergroup} * instead. - * - * @internal */ export async function createGroup( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Group title @@ -40,9 +38,9 @@ export async function createGroup( if (!Array.isArray(users)) users = [users] - const peers = await this.resolvePeerMany(users, normalizeToInputUser) + const peers = await resolvePeerMany(client, users, normalizeToInputUser) - const res = await this.call({ + const res = await client.call({ _: 'messages.createChat', title, users: peers, @@ -50,7 +48,7 @@ export async function createGroup( assertIsUpdatesGroup('messages.createChat', res) - this._handleUpdate(res) + client.network.handleUpdate(res) - return new Chat(this, res.chats[0]) + return new Chat(res.chats[0]) } diff --git a/packages/client/src/methods/chats/create-supergroup.ts b/packages/client/src/methods/chats/create-supergroup.ts index 3274845e..843ae040 100644 --- a/packages/client/src/methods/chats/create-supergroup.ts +++ b/packages/client/src/methods/chats/create-supergroup.ts @@ -1,4 +1,5 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { Chat } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' @@ -6,10 +7,9 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * Create a new supergroup * * @returns Newly created supergroup - * @internal */ export async function createSupergroup( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Supergroup title @@ -36,7 +36,7 @@ export async function createSupergroup( ): Promise { const { title, description = '', forum, ttlPeriod = 0 } = params - const res = await this.call({ + const res = await client.call({ _: 'channels.createChannel', title, about: description, @@ -47,7 +47,7 @@ export async function createSupergroup( assertIsUpdatesGroup('channels.createChannel', res) - this._handleUpdate(res) + client.network.handleUpdate(res) - return new Chat(this, res.chats[0]) + return new Chat(res.chats[0]) } diff --git a/packages/client/src/methods/chats/delete-channel.ts b/packages/client/src/methods/chats/delete-channel.ts index d7f18ec1..5223386f 100644 --- a/packages/client/src/methods/chats/delete-channel.ts +++ b/packages/client/src/methods/chats/delete-channel.ts @@ -1,18 +1,19 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' // @alias=deleteSupergroup /** * Delete a channel or a supergroup * * @param chatId Chat ID or username - * @internal */ -export async function deleteChannel(this: TelegramClient, chatId: InputPeerLike): Promise { - const res = await this.call({ +export async function deleteChannel(client: BaseTelegramClient, chatId: InputPeerLike): Promise { + const res = await client.call({ _: 'channels.deleteChannel', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/delete-chat-photo.ts b/packages/client/src/methods/chats/delete-chat-photo.ts index dc2ad751..37dc277a 100644 --- a/packages/client/src/methods/chats/delete-chat-photo.ts +++ b/packages/client/src/methods/chats/delete-chat-photo.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, MtInvalidPeerTypeError } from '../../types' import { isInputPeerChannel, isInputPeerChat, normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Delete a chat photo @@ -8,25 +10,24 @@ import { isInputPeerChannel, isInputPeerChat, normalizeToInputChannel } from '.. * You must be an administrator and have the appropriate permissions. * * @param chatId Chat ID or username - * @internal */ -export async function deleteChatPhoto(this: TelegramClient, chatId: InputPeerLike): Promise { - const chat = await this.resolvePeer(chatId) +export async function deleteChatPhoto(client: BaseTelegramClient, chatId: InputPeerLike): Promise { + const chat = await resolvePeer(client, chatId) let res if (isInputPeerChat(chat)) { - res = await this.call({ + res = await client.call({ _: 'messages.editChatPhoto', chatId: chat.chatId, photo: { _: 'inputChatPhotoEmpty' }, }) } else if (isInputPeerChannel(chat)) { - res = await this.call({ + res = await client.call({ _: 'channels.editPhoto', channel: normalizeToInputChannel(chat), photo: { _: 'inputChatPhotoEmpty' }, }) } else throw new MtInvalidPeerTypeError(chatId, 'chat or channel') - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/delete-group.ts b/packages/client/src/methods/chats/delete-group.ts index 249ab2a2..3c3a3547 100644 --- a/packages/client/src/methods/chats/delete-group.ts +++ b/packages/client/src/methods/chats/delete-group.ts @@ -1,26 +1,27 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, MtInvalidPeerTypeError } from '../../types' import { isInputPeerChat } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Delete a legacy group chat for all members * * @param chatId Chat ID - * @internal */ -export async function deleteGroup(this: TelegramClient, chatId: InputPeerLike): Promise { - const chat = await this.resolvePeer(chatId) +export async function deleteGroup(client: BaseTelegramClient, chatId: InputPeerLike): Promise { + const chat = await resolvePeer(client, chatId) if (!isInputPeerChat(chat)) throw new MtInvalidPeerTypeError(chatId, 'chat') - const res = await this.call({ + const res = await client.call({ _: 'messages.deleteChatUser', revokeHistory: true, chatId: chat.chatId, userId: { _: 'inputUserSelf' }, }) - this._handleUpdate(res) + client.network.handleUpdate(res) - await this.call({ + await client.call({ _: 'messages.deleteChat', chatId: chat.chatId, }) diff --git a/packages/client/src/methods/chats/delete-history.ts b/packages/client/src/methods/chats/delete-history.ts index 15e0ec4c..042500c9 100644 --- a/packages/client/src/methods/chats/delete-history.ts +++ b/packages/client/src/methods/chats/delete-history.ts @@ -1,16 +1,15 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { isInputPeerChannel } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** - * Delete communication history (for private chats - * and legacy groups) - * - * @internal + * Delete communication history (for private chats and legacy groups) */ export async function deleteHistory( - this: TelegramClient, + client: BaseTelegramClient, chat: InputPeerLike, params?: { /** @@ -35,9 +34,9 @@ export async function deleteHistory( ): Promise { const { mode = 'delete', maxId = 0 } = params ?? {} - const peer = await this.resolvePeer(chat) + const peer = await resolvePeer(client, chat) - const res = await this.call({ + const res = await client.call({ _: 'messages.deleteHistory', justClear: mode === 'clear', revoke: mode === 'revoke', @@ -46,8 +45,8 @@ export async function deleteHistory( }) if (isInputPeerChannel(peer)) { - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount, peer.channelId)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount, peer.channelId)) } else { - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) } } diff --git a/packages/client/src/methods/chats/delete-user-history.ts b/packages/client/src/methods/chats/delete-user-history.ts index c7cb9ad9..660f63ad 100644 --- a/packages/client/src/methods/chats/delete-user-history.ts +++ b/packages/client/src/methods/chats/delete-user-history.ts @@ -1,17 +1,15 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Delete all messages of a user (or channel) in a supergroup - * - * @internal */ export async function deleteUserHistory( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID */ chatId: InputPeerLike @@ -21,15 +19,15 @@ export async function deleteUserHistory( ): Promise { const { chatId, participantId } = params - const channel = normalizeToInputChannel(await this.resolvePeer(chatId), chatId) + const channel = normalizeToInputChannel(await resolvePeer(client, chatId), chatId) - const peer = await this.resolvePeer(participantId) + const peer = await resolvePeer(client, participantId) - const res = await this.call({ + const res = await client.call({ _: 'channels.deleteParticipantHistory', channel, participant: peer, }) - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount, (channel as tl.RawInputChannel).channelId)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount, (channel as tl.RawInputChannel).channelId)) } diff --git a/packages/client/src/methods/chats/edit-admin-rights.ts b/packages/client/src/methods/chats/edit-admin-rights.ts index 626cd897..2187ffb4 100644 --- a/packages/client/src/methods/chats/edit-admin-rights.ts +++ b/packages/client/src/methods/chats/edit-admin-rights.ts @@ -1,16 +1,14 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' import { normalizeToInputChannel, normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Edit supergroup/channel admin rights of a user. - * - * @internal */ export async function editAdminRights( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID */ chatId: InputPeerLike @@ -24,10 +22,10 @@ export async function editAdminRights( ): Promise { const { chatId, userId, rights, rank = '' } = params - const chat = normalizeToInputChannel(await this.resolvePeer(chatId), chatId) - const user = normalizeToInputUser(await this.resolvePeer(userId), userId) + const chat = normalizeToInputChannel(await resolvePeer(client, chatId), chatId) + const user = normalizeToInputUser(await resolvePeer(client, userId), userId) - const res = await this.call({ + const res = await client.call({ _: 'channels.editAdmin', channel: chat, userId: user, @@ -38,5 +36,5 @@ export async function editAdminRights( rank, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/get-chat-event-log.ts b/packages/client/src/methods/chats/get-chat-event-log.ts index 4c714430..d5f32392 100644 --- a/packages/client/src/methods/chats/get-chat-event-log.ts +++ b/packages/client/src/methods/chats/get-chat-event-log.ts @@ -1,9 +1,10 @@ -import { Long, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { ChatEvent, InputPeerLike, PeersIndex } from '../../types' import { InputChatEventFilters, normalizeChatEventFilters } from '../../types/peers/chat-event/filters' import { normalizeToInputChannel, normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' +import { resolvePeerMany } from '../users/resolve-peer-many' /** * Get chat event log ("Recent actions" in official clients). @@ -17,10 +18,9 @@ import { normalizeToInputChannel, normalizeToInputUser } from '../../utils/peer- * events have bigger event ID) * * @param params - * @internal */ export async function getChatEventLog( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -74,15 +74,15 @@ export async function getChatEventLog( ): Promise { const { maxId = Long.ZERO, minId = Long.ZERO, query = '', limit = 100, users, filters } = params ?? {} - const channel = normalizeToInputChannel(await this.resolvePeer(chatId), chatId) + const channel = normalizeToInputChannel(await resolvePeer(client, chatId), chatId) const admins: tl.TypeInputUser[] | undefined = users ? - await this.resolvePeerMany(users, normalizeToInputUser) : + await resolvePeerMany(client, users, normalizeToInputUser) : undefined const { serverFilter, localFilter } = normalizeChatEventFilters(filters) - const res = await this.call({ + const res = await client.call({ _: 'channels.getAdminLog', channel, q: query, @@ -100,7 +100,7 @@ export async function getChatEventLog( const results: ChatEvent[] = [] for (const evt of res.events) { - const parsed = new ChatEvent(this, evt, peers) + const parsed = new ChatEvent(evt, peers) if (localFilter && (!parsed.action || !localFilter[parsed.action.type])) { continue diff --git a/packages/client/src/methods/chats/get-chat-member.ts b/packages/client/src/methods/chats/get-chat-member.ts index 84066b03..3a37aa0b 100644 --- a/packages/client/src/methods/chats/get-chat-member.ts +++ b/packages/client/src/methods/chats/get-chat-member.ts @@ -1,9 +1,9 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { ChatMember, InputPeerLike, MtInvalidPeerTypeError, PeersIndex } from '../../types' import { isInputPeerChannel, isInputPeerChat, isInputPeerUser, normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get information about a single chat member @@ -11,10 +11,9 @@ import { isInputPeerChannel, isInputPeerChat, isInputPeerUser, normalizeToInputC * @param chatId Chat ID or username * @param userId User ID, username, phone number, `"me"` or `"self"` * @throws UserNotParticipantError In case given user is not a participant of a given chat - * @internal */ export async function getChatMember( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID or username */ chatId: InputPeerLike @@ -24,15 +23,15 @@ export async function getChatMember( ): Promise { const { chatId, userId } = params - const user = await this.resolvePeer(userId) - const chat = await this.resolvePeer(chatId) + const user = await resolvePeer(client, userId) + const chat = await resolvePeer(client, chatId) if (isInputPeerChat(chat)) { if (!isInputPeerUser(user)) { throw new MtInvalidPeerTypeError(userId, 'user') } - const res = await this.call({ + const res = await client.call({ _: 'messages.getFullChat', chatId: chat.chatId, }) @@ -49,13 +48,13 @@ export async function getChatMember( (user._ === 'inputPeerSelf' && (peers.user(m.userId) as tl.RawUser).self) || (user._ === 'inputPeerUser' && m.userId === user.userId) ) { - return new ChatMember(this, m, peers) + return new ChatMember(m, peers) } } throw new tl.RpcError(404, 'USER_NOT_PARTICIPANT') } else if (isInputPeerChannel(chat)) { - const res = await this.call({ + const res = await client.call({ _: 'channels.getParticipant', channel: normalizeToInputChannel(chat), participant: user, @@ -63,6 +62,6 @@ export async function getChatMember( const peers = PeersIndex.from(res) - return new ChatMember(this, res.participant, peers) + return new ChatMember(res.participant, peers) } else throw new MtInvalidPeerTypeError(chatId, 'chat or channel') } diff --git a/packages/client/src/methods/chats/get-chat-members.ts b/packages/client/src/methods/chats/get-chat-members.ts index 1fcce9a5..24a56ece 100644 --- a/packages/client/src/methods/chats/get-chat-members.ts +++ b/packages/client/src/methods/chats/get-chat-members.ts @@ -1,10 +1,10 @@ -import { assertNever, Long, tl } from '@mtcute/core' +import { assertNever, BaseTelegramClient, Long, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { ArrayWithTotal, ChatMember, InputPeerLike, MtInvalidPeerTypeError, PeersIndex } from '../../types' import { makeArrayWithTotal } from '../../utils' import { isInputPeerChannel, isInputPeerChat, normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get a chunk of members of some chat. @@ -13,10 +13,9 @@ import { isInputPeerChannel, isInputPeerChat, normalizeToInputChannel } from '.. * * @param chatId Chat ID or username * @param params Additional parameters - * @internal */ export async function getChatMembers( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -62,10 +61,10 @@ export async function getChatMembers( ): Promise> { const { query = '', offset = 0, limit = 200, type = 'recent' } = params ?? {} - const chat = await this.resolvePeer(chatId) + const chat = await resolvePeer(client, chatId) if (isInputPeerChat(chat)) { - const res = await this.call({ + const res = await client.call({ _: 'messages.getFullChat', chatId: chat.chatId, }) @@ -80,7 +79,7 @@ export async function getChatMembers( const peers = PeersIndex.from(res) - const ret = members.map((m) => new ChatMember(this, m, peers)) + const ret = members.map((m) => new ChatMember(m, peers)) return makeArrayWithTotal(ret, ret.length) } @@ -119,7 +118,7 @@ export async function getChatMembers( assertNever(type) } - const res = await this.call({ + const res = await client.call({ _: 'channels.getParticipants', channel: normalizeToInputChannel(chat), filter, @@ -132,7 +131,7 @@ export async function getChatMembers( const peers = PeersIndex.from(res) - const ret = res.participants.map((i) => new ChatMember(this, i, peers)) as ArrayWithTotal + const ret = res.participants.map((i) => new ChatMember(i, peers)) return makeArrayWithTotal(ret, res.count) } diff --git a/packages/client/src/methods/chats/get-chat-preview.ts b/packages/client/src/methods/chats/get-chat-preview.ts index 2f6fde40..d07143c5 100644 --- a/packages/client/src/methods/chats/get-chat-preview.ts +++ b/packages/client/src/methods/chats/get-chat-preview.ts @@ -1,6 +1,5 @@ -import { MtArgumentError } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' -import { TelegramClient } from '../../client' import { ChatPreview, MtPeerNotFoundError } from '../../types' import { INVITE_LINK_REGEX } from '../../utils/peer-utils' @@ -12,13 +11,12 @@ import { INVITE_LINK_REGEX } from '../../utils/peer-utils' * @throws MtPeerNotFoundError * In case you are trying to get info about private chat that you have already joined. * Use {@link getChat} or {@link getFullChat} instead. - * @internal */ -export async function getChatPreview(this: TelegramClient, inviteLink: string): Promise { +export async function getChatPreview(client: BaseTelegramClient, inviteLink: string): Promise { const m = inviteLink.match(INVITE_LINK_REGEX) if (!m) throw new MtArgumentError('Invalid invite link') - const res = await this.call({ + const res = await client.call({ _: 'messages.checkChatInvite', hash: m[1], }) @@ -27,5 +25,5 @@ export async function getChatPreview(this: TelegramClient, inviteLink: string): throw new MtPeerNotFoundError('You have already joined this chat!') } - return new ChatPreview(this, res, inviteLink) + return new ChatPreview(res, inviteLink) } diff --git a/packages/client/src/methods/chats/get-chat.ts b/packages/client/src/methods/chats/get-chat.ts index 376f1c54..4b36f284 100644 --- a/packages/client/src/methods/chats/get-chat.ts +++ b/packages/client/src/methods/chats/get-chat.ts @@ -1,6 +1,5 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { Chat, InputPeerLike } from '../../types' import { INVITE_LINK_REGEX, @@ -10,6 +9,7 @@ import { normalizeToInputChannel, normalizeToInputUser, } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get basic information about a chat. @@ -18,14 +18,13 @@ import { * @throws MtArgumentError * In case you are trying to get info about private chat that you haven't joined. * Use {@link getChatPreview} instead. - * @internal */ -export async function getChat(this: TelegramClient, chatId: InputPeerLike): Promise { +export async function getChat(client: BaseTelegramClient, chatId: InputPeerLike): Promise { if (typeof chatId === 'string') { const m = chatId.match(INVITE_LINK_REGEX) if (m) { - const res = await this.call({ + const res = await client.call({ _: 'messages.checkChatInvite', hash: m[1], }) @@ -34,32 +33,32 @@ export async function getChat(this: TelegramClient, chatId: InputPeerLike): Prom throw new MtArgumentError(`You haven't joined ${JSON.stringify(res.title)}`) } - return new Chat(this, res.chat) + return new Chat(res.chat) } } - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) let res: tl.TypeChat | tl.TypeUser if (isInputPeerChannel(peer)) { - const r = await this.call({ + const r = await client.call({ _: 'channels.getChannels', id: [normalizeToInputChannel(peer)], }) res = r.chats[0] } else if (isInputPeerUser(peer)) { - const r = await this.call({ + const r = await client.call({ _: 'users.getUsers', id: [normalizeToInputUser(peer)], }) res = r[0] } else if (isInputPeerChat(peer)) { - const r = await this.call({ + const r = await client.call({ _: 'messages.getChats', id: [peer.chatId], }) res = r.chats[0] } else throw new Error('should not happen') - return new Chat(this, res) + return new Chat(res) } diff --git a/packages/client/src/methods/chats/get-full-chat.ts b/packages/client/src/methods/chats/get-full-chat.ts index f20b254e..84a2b791 100644 --- a/packages/client/src/methods/chats/get-full-chat.ts +++ b/packages/client/src/methods/chats/get-full-chat.ts @@ -1,6 +1,5 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { Chat, InputPeerLike } from '../../types' import { INVITE_LINK_REGEX, @@ -10,6 +9,7 @@ import { normalizeToInputChannel, normalizeToInputUser, } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get full information about a chat. @@ -18,14 +18,13 @@ import { * @throws MtArgumentError * In case you are trying to get info about private chat that you haven't joined. * Use {@link getChatPreview} instead. - * @internal */ -export async function getFullChat(this: TelegramClient, chatId: InputPeerLike): Promise { +export async function getFullChat(client: BaseTelegramClient, chatId: InputPeerLike): Promise { if (typeof chatId === 'string') { const m = chatId.match(INVITE_LINK_REGEX) if (m) { - const res = await this.call({ + const res = await client.call({ _: 'messages.checkChatInvite', hash: m[1], }) @@ -39,25 +38,25 @@ export async function getFullChat(this: TelegramClient, chatId: InputPeerLike): } } - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) let res: tl.messages.TypeChatFull | tl.users.TypeUserFull if (isInputPeerChannel(peer)) { - res = await this.call({ + res = await client.call({ _: 'channels.getFullChannel', channel: normalizeToInputChannel(peer), }) } else if (isInputPeerUser(peer)) { - res = await this.call({ + res = await client.call({ _: 'users.getFullUser', id: normalizeToInputUser(peer)!, }) } else if (isInputPeerChat(peer)) { - res = await this.call({ + res = await client.call({ _: 'messages.getFullChat', chatId: peer.chatId, }) } else throw new Error('should not happen') - return Chat._parseFull(this, res) + return Chat._parseFull(res) } diff --git a/packages/client/src/methods/chats/get-nearby-chats.ts b/packages/client/src/methods/chats/get-nearby-chats.ts index 646dd0d0..3122044b 100644 --- a/packages/client/src/methods/chats/get-nearby-chats.ts +++ b/packages/client/src/methods/chats/get-nearby-chats.ts @@ -1,7 +1,6 @@ -import { getMarkedPeerId, tl } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { Chat } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' @@ -10,10 +9,9 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * * @param latitude Latitude of the location * @param longitude Longitude of the location - * @internal */ -export async function getNearbyChats(this: TelegramClient, latitude: number, longitude: number): Promise { - const res = await this.call({ +export async function getNearbyChats(client: BaseTelegramClient, latitude: number, longitude: number): Promise { + const res = await client.call({ _: 'contacts.getLocated', geoPoint: { _: 'inputGeoPoint', @@ -23,13 +21,13 @@ export async function getNearbyChats(this: TelegramClient, latitude: number, lon }) assertIsUpdatesGroup('contacts.getLocated', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) if (!res.updates.length) return [] assertTypeIs('contacts.getLocated (@ .updates[0])', res.updates[0], 'updatePeerLocated') - const chats = res.chats.map((it) => new Chat(this, it)) + const chats = res.chats.map((it) => new Chat(it)) const index: Record = {} chats.forEach((c) => (index[c.id] = c)) diff --git a/packages/client/src/methods/chats/iter-chat-event-log.ts b/packages/client/src/methods/chats/iter-chat-event-log.ts index f754da0a..ac2ae9eb 100644 --- a/packages/client/src/methods/chats/iter-chat-event-log.ts +++ b/packages/client/src/methods/chats/iter-chat-event-log.ts @@ -1,9 +1,11 @@ -import { Long, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { ChatEvent, InputPeerLike } from '../../types' import { normalizeChatEventFilters } from '../../types/peers/chat-event/filters' import { normalizeToInputChannel, normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' +import { resolvePeerMany } from '../users/resolve-peer-many' +import { getChatEventLog } from './get-chat-event-log' /** * Iterate over chat event log. @@ -12,12 +14,11 @@ import { normalizeToInputChannel, normalizeToInputUser } from '../../utils/peer- * * @param chatId Chat ID * @param params - * @internal */ export async function* iterChatEventLog( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Total number of events to return. * @@ -36,12 +37,12 @@ export async function* iterChatEventLog( ): AsyncIterableIterator { if (!params) params = {} - const channel = normalizeToInputChannel(await this.resolvePeer(chatId), chatId) + const channel = normalizeToInputChannel(await resolvePeer(client, chatId), chatId) const { minId = Long.ZERO, query = '', limit = Infinity, chunkSize = 100, users, filters } = params const admins: tl.TypeInputUser[] | undefined = users ? - await this.resolvePeerMany(users, normalizeToInputUser) : + await resolvePeerMany(client, users, normalizeToInputUser) : undefined const { serverFilter, localFilter } = normalizeChatEventFilters(filters) @@ -50,7 +51,7 @@ export async function* iterChatEventLog( let maxId = params.maxId ?? Long.ZERO for (;;) { - const chunk = await this.getChatEventLog(channel, { + const chunk = await getChatEventLog(client, channel, { minId, maxId, query, diff --git a/packages/client/src/methods/chats/iter-chat-members.ts b/packages/client/src/methods/chats/iter-chat-members.ts index 8338f887..c3cd16c4 100644 --- a/packages/client/src/methods/chats/iter-chat-members.ts +++ b/packages/client/src/methods/chats/iter-chat-members.ts @@ -1,6 +1,9 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ChatMember, InputPeerLike } from '../../types' import { isInputPeerChat } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' +import { getChatMembers } from './get-chat-members' /** * Iterate through chat members @@ -11,15 +14,14 @@ import { isInputPeerChat } from '../../utils/peer-utils' * * @param chatId Chat ID or username * @param params Additional parameters - * @internal */ export async function* iterChatMembers( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Chunk size, which will be passed as `limit` parameter - * to {@link TelegramClient.getChatMembers}. Usually you shouldn't care about this. + * to {@link getChatMembers}. Usually you shouldn't care about this. * * Defaults to `200` */ @@ -34,10 +36,10 @@ export async function* iterChatMembers( let offset = params.offset ?? 0 const yielded = new Set() - const chat = await this.resolvePeer(chatId) + const chat = await resolvePeer(client, chatId) for (;;) { - const members = await this.getChatMembers(chat, { + const members = await getChatMembers(client, chat, { offset, limit, query: params.query, diff --git a/packages/client/src/methods/chats/join-chat.ts b/packages/client/src/methods/chats/join-chat.ts index dc71c89d..fe93678d 100644 --- a/packages/client/src/methods/chats/join-chat.ts +++ b/packages/client/src/methods/chats/join-chat.ts @@ -1,7 +1,9 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { Chat, InputPeerLike } from '../../types' import { INVITE_LINK_REGEX, normalizeToInputChannel } from '../../utils/peer-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Join a channel or supergroup @@ -13,33 +15,32 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * @param chatId * Chat identifier. Either an invite link (`t.me/joinchat/*`), a username (`@username`) * or ID of the linked supergroup or channel. - * @internal */ -export async function joinChat(this: TelegramClient, chatId: InputPeerLike): Promise { +export async function joinChat(client: BaseTelegramClient, chatId: InputPeerLike): Promise { if (typeof chatId === 'string') { const m = chatId.match(INVITE_LINK_REGEX) if (m) { - const res = await this.call({ + const res = await client.call({ _: 'messages.importChatInvite', hash: m[1], }) assertIsUpdatesGroup('messages.importChatInvite', res) - this._handleUpdate(res) + client.network.handleUpdate(res) - return new Chat(this, res.chats[0]) + return new Chat(res.chats[0]) } } - const res = await this.call({ + const res = await client.call({ _: 'channels.joinChannel', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), }) assertIsUpdatesGroup('channels.joinChannel', res) - this._handleUpdate(res) + client.network.handleUpdate(res) - return new Chat(this, res.chats[0]) + return new Chat(res.chats[0]) } diff --git a/packages/client/src/methods/chats/kick-chat-member.ts b/packages/client/src/methods/chats/kick-chat-member.ts index 7cdabc15..ba98369a 100644 --- a/packages/client/src/methods/chats/kick-chat-member.ts +++ b/packages/client/src/methods/chats/kick-chat-member.ts @@ -1,18 +1,19 @@ +import { BaseTelegramClient } from '@mtcute/core' import { sleep } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' import { isInputPeerChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' +import { banChatMember } from './ban-chat-member' +import { unbanChatMember } from './unban-chat-member' /** * Kick a user from a chat. * * This effectively bans a user and immediately unbans them. - * - * @internal */ export async function kickChatMember( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID */ chatId: InputPeerLike @@ -22,15 +23,15 @@ export async function kickChatMember( ): Promise { const { chatId, userId } = params - const chat = await this.resolvePeer(chatId) - const user = await this.resolvePeer(userId) + const chat = await resolvePeer(client, chatId) + const user = await resolvePeer(client, userId) - await this.banChatMember({ chatId: chat, participantId: user }) + await banChatMember(client, { chatId: chat, participantId: user }) // not needed in case this is a legacy group if (isInputPeerChannel(chat)) { // i fucking love telegram serverside race conditions await sleep(1000) - await this.unbanChatMember({ chatId: chat, participantId: user }) + await unbanChatMember(client, { chatId: chat, participantId: user }) } } diff --git a/packages/client/src/methods/chats/leave-chat.ts b/packages/client/src/methods/chats/leave-chat.ts index be417a26..ffd9c641 100644 --- a/packages/client/src/methods/chats/leave-chat.ts +++ b/packages/client/src/methods/chats/leave-chat.ts @@ -1,15 +1,17 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, MtInvalidPeerTypeError } from '../../types' import { isInputPeerChannel, isInputPeerChat, normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' +import { deleteHistory } from './delete-history' /** * Leave a group chat, supergroup or channel * * @param chatId Chat ID or username - * @internal */ export async function leaveChat( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -18,24 +20,24 @@ export async function leaveChat( clear?: boolean }, ): Promise { - const chat = await this.resolvePeer(chatId) + const chat = await resolvePeer(client, chatId) if (isInputPeerChannel(chat)) { - const res = await this.call({ + const res = await client.call({ _: 'channels.leaveChannel', channel: normalizeToInputChannel(chat), }) - this._handleUpdate(res) + client.network.handleUpdate(res) } else if (isInputPeerChat(chat)) { - const res = await this.call({ + const res = await client.call({ _: 'messages.deleteChatUser', chatId: chat.chatId, userId: { _: 'inputUserSelf' }, }) - this._handleUpdate(res) + client.network.handleUpdate(res) if (params?.clear) { - await this.deleteHistory(chat) + await deleteHistory(client, chat) } } else throw new MtInvalidPeerTypeError(chatId, 'chat or channel') } diff --git a/packages/client/src/methods/chats/mark-chat-unread.ts b/packages/client/src/methods/chats/mark-chat-unread.ts index ed7f971e..232d024d 100644 --- a/packages/client/src/methods/chats/mark-chat-unread.ts +++ b/packages/client/src/methods/chats/mark-chat-unread.ts @@ -1,18 +1,19 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Mark a chat as unread * * @param chatId Chat ID - * @internal */ -export async function markChatUnread(this: TelegramClient, chatId: InputPeerLike): Promise { - await this.call({ +export async function markChatUnread(client: BaseTelegramClient, chatId: InputPeerLike): Promise { + await client.call({ _: 'messages.markDialogUnread', peer: { _: 'inputDialogPeer', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), }, unread: true, }) diff --git a/packages/client/src/methods/chats/reorder-usernames.ts b/packages/client/src/methods/chats/reorder-usernames.ts index 5531b07a..47c29cf7 100644 --- a/packages/client/src/methods/chats/reorder-usernames.ts +++ b/packages/client/src/methods/chats/reorder-usernames.ts @@ -1,22 +1,28 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { isInputPeerChannel, isInputPeerUser, normalizeToInputChannel, normalizeToInputUser } from '../../utils' +import { getAuthState } from '../auth/_state' +import { resolvePeer } from '../users/resolve-peer' /** * Reorder usernames * * @param peerId Bot, channel or "me"/"self" - * @internal */ -export async function reorderUsernames(this: TelegramClient, peerId: InputPeerLike, order: string[]): Promise { - const peer = await this.resolvePeer(peerId) +export async function reorderUsernames( + client: BaseTelegramClient, + peerId: InputPeerLike, + order: string[], +): Promise { + const peer = await resolvePeer(client, peerId) if (isInputPeerUser(peer)) { // either a bot or self - if (peer._ === 'inputPeerSelf' || peer.userId === this._userId) { + if (peer._ === 'inputPeerSelf' || peer.userId === getAuthState(client).userId) { // self - await this.call({ + await client.call({ _: 'account.reorderUsernames', order, }) @@ -25,13 +31,13 @@ export async function reorderUsernames(this: TelegramClient, peerId: InputPeerLi } // bot - await this.call({ + await client.call({ _: 'bots.reorderUsernames', bot: normalizeToInputUser(peer, peerId), order, }) } else if (isInputPeerChannel(peer)) { - await this.call({ + await client.call({ _: 'channels.reorderUsernames', channel: normalizeToInputChannel(peer, peerId), order, diff --git a/packages/client/src/methods/chats/restrict-chat-member.ts b/packages/client/src/methods/chats/restrict-chat-member.ts index e80d428d..f821a7d7 100644 --- a/packages/client/src/methods/chats/restrict-chat-member.ts +++ b/packages/client/src/methods/chats/restrict-chat-member.ts @@ -1,17 +1,15 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, MtInvalidPeerTypeError } from '../../types' import { normalizeDate } from '../../utils/misc-utils' import { isInputPeerChannel, normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Restrict a user in a supergroup. - * - * @internal */ export async function restrictChatMember( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID */ chatId: InputPeerLike @@ -40,15 +38,15 @@ export async function restrictChatMember( ): Promise { const { chatId, userId, restrictions, until = 0 } = params - const chat = await this.resolvePeer(chatId) + const chat = await resolvePeer(client, chatId) if (!isInputPeerChannel(chat)) { throw new MtInvalidPeerTypeError(chatId, 'channel') } - const user = await this.resolvePeer(userId) + const user = await resolvePeer(client, userId) - const res = await this.call({ + const res = await client.call({ _: 'channels.editBanned', channel: normalizeToInputChannel(chat), participant: user, @@ -58,5 +56,5 @@ export async function restrictChatMember( ...restrictions, }, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/save-draft.ts b/packages/client/src/methods/chats/save-draft.ts index 5b54cfd3..434d0e59 100644 --- a/packages/client/src/methods/chats/save-draft.ts +++ b/packages/client/src/methods/chats/save-draft.ts @@ -1,30 +1,29 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Save or delete a draft message associated with some chat * * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` * @param draft Draft message, or `null` to delete. - * @internal */ export async function saveDraft( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, draft: null | Omit, ): Promise { - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) if (draft) { - await this.call({ + await client.call({ _: 'messages.saveDraft', peer, ...draft, }) } else { - await this.call({ + await client.call({ _: 'messages.saveDraft', peer, message: '', diff --git a/packages/client/src/methods/chats/set-chat-default-permissions.ts b/packages/client/src/methods/chats/set-chat-default-permissions.ts index aa6e65e7..8d98001a 100644 --- a/packages/client/src/methods/chats/set-chat-default-permissions.ts +++ b/packages/client/src/methods/chats/set-chat-default-permissions.ts @@ -1,8 +1,8 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { Chat, InputPeerLike } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Change default chat permissions for all members. @@ -15,16 +15,15 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * the restrictions, and not the permissions, i.e. * passing `sendMessages=true` will disallow the users to send messages, * and passing `{}` (empty object) will lift any restrictions - * @internal */ export async function setChatDefaultPermissions( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, restrictions: Omit, ): Promise { - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) - const res = await this.call({ + const res = await client.call({ _: 'messages.editChatDefaultBannedRights', peer, bannedRights: { @@ -36,7 +35,7 @@ export async function setChatDefaultPermissions( assertIsUpdatesGroup('messages.editChatDefaultBannedRights', res) - this._handleUpdate(res) + client.network.handleUpdate(res) - return new Chat(this, res.chats[0]) + return new Chat(res.chats[0]) } diff --git a/packages/client/src/methods/chats/set-chat-description.ts b/packages/client/src/methods/chats/set-chat-description.ts index b4e4c5d4..65c378d4 100644 --- a/packages/client/src/methods/chats/set-chat-description.ts +++ b/packages/client/src/methods/chats/set-chat-description.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Change chat description @@ -8,16 +10,15 @@ import { InputPeerLike } from '../../types' * * @param chatId Chat ID or username * @param description New chat description, 0-255 characters - * @internal */ export async function setChatDescription( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, description: string, ): Promise { - const chat = await this.resolvePeer(chatId) + const chat = await resolvePeer(client, chatId) - await this.call({ + await client.call({ _: 'messages.editChatAbout', peer: chat, about: description, diff --git a/packages/client/src/methods/chats/set-chat-photo.ts b/packages/client/src/methods/chats/set-chat-photo.ts index a90c2a1e..3f3b9a3b 100644 --- a/packages/client/src/methods/chats/set-chat-photo.ts +++ b/packages/client/src/methods/chats/set-chat-photo.ts @@ -1,19 +1,18 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' import { fileIdToInputPhoto, tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { InputFileLike, InputPeerLike, isUploadedFile, MtInvalidPeerTypeError } from '../../types' import { isInputPeerChannel, isInputPeerChat, normalizeToInputChannel } from '../../utils/peer-utils' +import { uploadFile } from '../files/upload-file' +import { resolvePeer } from '../users/resolve-peer' /** * Set a new chat photo or video. * * You must be an administrator and have the appropriate permissions. - * - * @internal */ export async function setChatPhoto( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID or username */ chatId: InputPeerLike @@ -33,7 +32,7 @@ export async function setChatPhoto( ): Promise { const { chatId, type, media, previewSec } = params - const chat = await this.resolvePeer(chatId) + const chat = await resolvePeer(client, chatId) if (!(isInputPeerChannel(chat) || isInputPeerChat(chat))) { throw new MtInvalidPeerTypeError(chatId, 'chat or channel') @@ -48,7 +47,7 @@ export async function setChatPhoto( throw new MtArgumentError("Chat photo can't be external") } if (typeof media === 'string' && media.match(/^file:/)) { - const uploaded = await this.uploadFile({ + const uploaded = await uploadFile(client, { file: media.substring(5), }) inputFile = uploaded.inputFile @@ -71,7 +70,7 @@ export async function setChatPhoto( } else if (typeof media === 'object' && tl.isAnyInputFile(media)) { inputFile = media } else { - const uploaded = await this.uploadFile({ + const uploaded = await uploadFile(client, { file: media, }) inputFile = uploaded.inputFile @@ -88,17 +87,17 @@ export async function setChatPhoto( let res if (isInputPeerChat(chat)) { - res = await this.call({ + res = await client.call({ _: 'messages.editChatPhoto', chatId: chat.chatId, photo, }) } else { - res = await this.call({ + res = await client.call({ _: 'channels.editPhoto', channel: normalizeToInputChannel(chat), photo, }) } - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/set-chat-title.ts b/packages/client/src/methods/chats/set-chat-title.ts index 08c52b98..d5eb7981 100644 --- a/packages/client/src/methods/chats/set-chat-title.ts +++ b/packages/client/src/methods/chats/set-chat-title.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, MtInvalidPeerTypeError } from '../../types' import { isInputPeerChannel, isInputPeerChat, normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Change chat title @@ -9,25 +11,24 @@ import { isInputPeerChannel, isInputPeerChat, normalizeToInputChannel } from '.. * * @param chatId Chat ID or username * @param title New chat title, 1-255 characters - * @internal */ -export async function setChatTitle(this: TelegramClient, chatId: InputPeerLike, title: string): Promise { - const chat = await this.resolvePeer(chatId) +export async function setChatTitle(client: BaseTelegramClient, chatId: InputPeerLike, title: string): Promise { + const chat = await resolvePeer(client, chatId) let res if (isInputPeerChat(chat)) { - res = await this.call({ + res = await client.call({ _: 'messages.editChatTitle', chatId: chat.chatId, title, }) } else if (isInputPeerChannel(chat)) { - res = await this.call({ + res = await client.call({ _: 'channels.editTitle', channel: normalizeToInputChannel(chat), title, }) } else throw new MtInvalidPeerTypeError(chatId, 'chat or channel') - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/set-chat-ttl.ts b/packages/client/src/methods/chats/set-chat-ttl.ts index 9cdc536e..ba29f6f8 100644 --- a/packages/client/src/methods/chats/set-chat-ttl.ts +++ b/packages/client/src/methods/chats/set-chat-ttl.ts @@ -1,17 +1,18 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Set maximum Time-To-Live of all newly sent messages in the specified chat * * @param chatId Chat ID * @param period New TTL period, in seconds (or 0 to disable) - * @internal */ -export async function setChatTtl(this: TelegramClient, chatId: InputPeerLike, period: number): Promise { - await this.call({ +export async function setChatTtl(client: BaseTelegramClient, chatId: InputPeerLike, period: number): Promise { + await client.call({ _: 'messages.setHistoryTTL', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), period, }) } diff --git a/packages/client/src/methods/chats/set-chat-username.ts b/packages/client/src/methods/chats/set-chat-username.ts index d7c82439..4ea2ec9c 100644 --- a/packages/client/src/methods/chats/set-chat-username.ts +++ b/packages/client/src/methods/chats/set-chat-username.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Change supergroup/channel username @@ -9,16 +11,15 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * * @param chatId Chat ID or current username * @param username New username, or `null` to remove - * @internal */ export async function setChatUsername( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, username: string | null, ): Promise { - await this.call({ + await client.call({ _: 'channels.updateUsername', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), username: username || '', }) } diff --git a/packages/client/src/methods/chats/set-slow-mode.ts b/packages/client/src/methods/chats/set-slow-mode.ts index ff91a685..7ca3fefb 100644 --- a/packages/client/src/methods/chats/set-slow-mode.ts +++ b/packages/client/src/methods/chats/set-slow-mode.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Set supergroup's slow mode interval. @@ -10,13 +12,12 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * Slow mode interval in seconds. * Users will be able to send a message only once per this interval. * Valid values are: `0 (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h)` - * @internal */ -export async function setSlowMode(this: TelegramClient, chatId: InputPeerLike, seconds = 0): Promise { - const res = await this.call({ +export async function setSlowMode(client: BaseTelegramClient, chatId: InputPeerLike, seconds = 0): Promise { + const res = await client.call({ _: 'channels.toggleSlowMode', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), seconds, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/toggle-content-protection.ts b/packages/client/src/methods/chats/toggle-content-protection.ts index 82570801..eddfdc15 100644 --- a/packages/client/src/methods/chats/toggle-content-protection.ts +++ b/packages/client/src/methods/chats/toggle-content-protection.ts @@ -1,22 +1,23 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Set whether a chat has content protection (i.e. forwarding messages is disabled) * * @param chatId Chat ID or username * @param enabled Whether content protection should be enabled - * @internal */ export async function toggleContentProtection( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, enabled = false, ): Promise { - const res = await this.call({ + const res = await client.call({ _: 'messages.toggleNoForwards', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), enabled, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/toggle-fragment-username.ts b/packages/client/src/methods/chats/toggle-fragment-username.ts index 533652da..913eb3ec 100644 --- a/packages/client/src/methods/chats/toggle-fragment-username.ts +++ b/packages/client/src/methods/chats/toggle-fragment-username.ts @@ -1,17 +1,18 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { isInputPeerChannel, isInputPeerUser, normalizeToInputChannel, normalizeToInputUser } from '../../utils' +import { getAuthState } from '../auth/_state' +import { resolvePeer } from '../users/resolve-peer' /** * Toggle a collectible (Fragment) username * * > **Note**: non-collectible usernames must still be changed * > using {@link setUsername}/{@link setChatUsername} - * - * @internal */ export async function toggleFragmentUsername( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Peer ID whose username to toggle */ peerId: InputPeerLike @@ -29,14 +30,14 @@ export async function toggleFragmentUsername( ): Promise { const { peerId, username, active } = params - const peer = await this.resolvePeer(peerId) + const peer = await resolvePeer(client, peerId) if (isInputPeerUser(peer)) { // either a bot or self - if (peer._ === 'inputPeerSelf' || peer.userId === this._userId) { + if (peer._ === 'inputPeerSelf' || peer.userId === getAuthState(client).userId) { // self - await this.call({ + await client.call({ _: 'account.toggleUsername', username, active, @@ -46,14 +47,14 @@ export async function toggleFragmentUsername( } // bot - await this.call({ + await client.call({ _: 'bots.toggleUsername', bot: normalizeToInputUser(peer, peerId), username, active, }) } else if (isInputPeerChannel(peer)) { - await this.call({ + await client.call({ _: 'channels.toggleUsername', channel: normalizeToInputChannel(peer, peerId), username, diff --git a/packages/client/src/methods/chats/toggle-join-requests.ts b/packages/client/src/methods/chats/toggle-join-requests.ts index bf8bfdd9..da9cbb57 100644 --- a/packages/client/src/methods/chats/toggle-join-requests.ts +++ b/packages/client/src/methods/chats/toggle-join-requests.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Set whether a channel/supergroup has join requests enabled. @@ -10,13 +12,16 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * * @param chatId Chat ID or username * @param enabled Whether join requests should be enabled - * @internal */ -export async function toggleJoinRequests(this: TelegramClient, chatId: InputPeerLike, enabled = false): Promise { - const res = await this.call({ +export async function toggleJoinRequests( + client: BaseTelegramClient, + chatId: InputPeerLike, + enabled = false, +): Promise { + const res = await client.call({ _: 'channels.toggleJoinRequest', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), enabled, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/toggle-join-to-send.ts b/packages/client/src/methods/chats/toggle-join-to-send.ts index f5f32dd4..fd36864e 100644 --- a/packages/client/src/methods/chats/toggle-join-to-send.ts +++ b/packages/client/src/methods/chats/toggle-join-to-send.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Set whether a channel/supergroup has join-to-send setting enabled. @@ -10,13 +12,16 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * * @param chatId Chat ID or username * @param enabled Whether join-to-send setting should be enabled - * @internal */ -export async function toggleJoinToSend(this: TelegramClient, chatId: InputPeerLike, enabled = false): Promise { - const res = await this.call({ +export async function toggleJoinToSend( + client: BaseTelegramClient, + chatId: InputPeerLike, + enabled = false, +): Promise { + const res = await client.call({ _: 'channels.toggleJoinToSend', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), enabled, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/chats/unarchive-chats.ts b/packages/client/src/methods/chats/unarchive-chats.ts index 149f3dd8..554cefbe 100644 --- a/packages/client/src/methods/chats/unarchive-chats.ts +++ b/packages/client/src/methods/chats/unarchive-chats.ts @@ -1,15 +1,14 @@ -import { MaybeArray, tl } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Unarchive one or more chats * * @param chats Chat ID(s), username(s), phone number(s), `"me"` or `"self"` - * @internal */ -export async function unarchiveChats(this: TelegramClient, chats: MaybeArray): Promise { +export async function unarchiveChats(client: BaseTelegramClient, chats: MaybeArray): Promise { if (!Array.isArray(chats)) chats = [chats] const folderPeers: tl.TypeInputFolderPeer[] = [] @@ -17,14 +16,14 @@ export async function unarchiveChats(this: TelegramClient, chats: MaybeArray { const { chatId, participantId } = params - const chat = await this.resolvePeer(chatId) - const peer = await this.resolvePeer(participantId) + const chat = await resolvePeer(client, chatId) + const peer = await resolvePeer(client, participantId) if (isInputPeerChannel(chat)) { - const res = await this.call({ + const res = await client.call({ _: 'channels.editBanned', channel: normalizeToInputChannel(chat), participant: peer, @@ -38,7 +38,7 @@ export async function unbanChatMember( }, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } else if (isInputPeerChat(chat)) { // no-op // } else throw new MtInvalidPeerTypeError(chatId, 'chat or channel') diff --git a/packages/client/src/methods/contacts/add-contact.ts b/packages/client/src/methods/contacts/add-contact.ts index 9f9a2c07..9e4c0c82 100644 --- a/packages/client/src/methods/contacts/add-contact.ts +++ b/packages/client/src/methods/contacts/add-contact.ts @@ -1,15 +1,15 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, User } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Add an existing Telegram user as a contact - * - * @internal */ export async function addContact( - this: TelegramClient, + client: BaseTelegramClient, params: { /** User ID, username or phone number */ userId: InputPeerLike @@ -37,9 +37,9 @@ export async function addContact( }, ): Promise { const { userId, firstName, lastName = '', phone = '', sharePhone = false } = params - const peer = normalizeToInputUser(await this.resolvePeer(userId), userId) + const peer = normalizeToInputUser(await resolvePeer(client, userId), userId) - const res = await this.call({ + const res = await client.call({ _: 'contacts.addContact', id: peer, firstName, @@ -50,7 +50,7 @@ export async function addContact( assertIsUpdatesGroup('contacts.addContact', res) - this._handleUpdate(res) + client.network.handleUpdate(res) - return new User(this, res.users[0]) + return new User(res.users[0]) } diff --git a/packages/client/src/methods/contacts/delete-contacts.ts b/packages/client/src/methods/contacts/delete-contacts.ts index f1332879..838d5508 100644 --- a/packages/client/src/methods/contacts/delete-contacts.ts +++ b/packages/client/src/methods/contacts/delete-contacts.ts @@ -1,9 +1,9 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, MtInvalidPeerTypeError, User } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeerMany } from '../users/resolve-peer-many' /** * Delete a single contact from your Telegram contacts list @@ -12,9 +12,8 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * that user was not in your contacts list * * @param userId User ID, username or phone number - * @internal */ -export async function deleteContacts(this: TelegramClient, userId: InputPeerLike): Promise +export async function deleteContacts(client: BaseTelegramClient, userId: InputPeerLike): Promise /** * Delete one or more contacts from your Telegram contacts list @@ -23,25 +22,24 @@ export async function deleteContacts(this: TelegramClient, userId: InputPeerLike * profiles of users that were not in your contacts list * * @param userIds User IDs, usernames or phone numbers - * @internal */ -export async function deleteContacts(this: TelegramClient, userIds: InputPeerLike[]): Promise +export async function deleteContacts(client: BaseTelegramClient, userIds: InputPeerLike[]): Promise /** @internal */ export async function deleteContacts( - this: TelegramClient, + client: BaseTelegramClient, userIds: MaybeArray, ): Promise | null> { const single = !Array.isArray(userIds) if (single) userIds = [userIds as InputPeerLike] - const inputPeers = await this.resolvePeerMany(userIds as InputPeerLike[], normalizeToInputUser) + const inputPeers = await resolvePeerMany(client, userIds as InputPeerLike[], normalizeToInputUser) if (single && !inputPeers.length) { throw new MtInvalidPeerTypeError((userIds as InputPeerLike[])[0], 'user') } - const res = await this.call({ + const res = await client.call({ _: 'contacts.deleteContacts', id: inputPeers, }) @@ -50,9 +48,9 @@ export async function deleteContacts( if (single && !res.updates.length) return null - this._handleUpdate(res) + client.network.handleUpdate(res) - const users = res.users.map((user) => new User(this, user)) + const users = res.users.map((user) => new User(user)) return single ? users[0] : users } diff --git a/packages/client/src/methods/contacts/get-contacts.ts b/packages/client/src/methods/contacts/get-contacts.ts index b9cb864c..91635195 100644 --- a/packages/client/src/methods/contacts/get-contacts.ts +++ b/packages/client/src/methods/contacts/get-contacts.ts @@ -1,19 +1,17 @@ -import { Long } from '@mtcute/core' +import { BaseTelegramClient, Long } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { User } from '../../types' /** * Get list of contacts from your Telegram contacts list. - * @internal */ -export async function getContacts(this: TelegramClient): Promise { - const res = await this.call({ +export async function getContacts(client: BaseTelegramClient): Promise { + const res = await client.call({ _: 'contacts.getContacts', hash: Long.ZERO, }) assertTypeIs('getContacts', res, 'contacts.contacts') - return res.users.map((user) => new User(this, user)) + return res.users.map((user) => new User(user)) } diff --git a/packages/client/src/methods/contacts/import-contacts.ts b/packages/client/src/methods/contacts/import-contacts.ts index 4de01dee..f79c40cb 100644 --- a/packages/client/src/methods/contacts/import-contacts.ts +++ b/packages/client/src/methods/contacts/import-contacts.ts @@ -1,15 +1,12 @@ -import { Long, PartialOnly, tl } from '@mtcute/core' - -import { TelegramClient } from '../../client' +import { BaseTelegramClient, Long, PartialOnly, tl } from '@mtcute/core' /** * Import contacts to your Telegram contacts list. * * @param contacts List of contacts - * @internal */ export async function importContacts( - this: TelegramClient, + client: BaseTelegramClient, contacts: PartialOnly, 'clientId'>[], ): Promise { let seq = Long.ZERO @@ -20,7 +17,7 @@ export async function importContacts( ...input, })) - return await this.call({ + return await client.call({ _: 'contacts.importContacts', contacts: contactsNorm, }) diff --git a/packages/client/src/methods/dialogs/_init-conversation.ts b/packages/client/src/methods/dialogs/_init-conversation.ts deleted file mode 100644 index 8a62d274..00000000 --- a/packages/client/src/methods/dialogs/_init-conversation.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars, dot-notation */ -import { getMarkedPeerId } from '@mtcute/core' - -import { TelegramClient } from '../../client' -import { Conversation, Message } from '../../types' - -// @extension -interface ConversationsState { - _pendingConversations: Map - _hasConversations: boolean -} - -// @initialize -function _initializeConversation(this: TelegramClient) { - this._pendingConversations = new Map() - this._hasConversations = false -} - -/** @internal */ -export function _pushConversationMessage(this: TelegramClient, msg: Message, incoming = false): void { - // shortcut - if (!this._hasConversations) return - - const chatId = getMarkedPeerId(msg.raw.peerId) - const msgId = msg.raw.id - - this._pendingConversations.get(chatId)?.forEach((conv) => { - conv['_lastMessage'] = msgId - if (incoming) conv['_lastReceivedMessage'] = msgId - }) -} diff --git a/packages/client/src/methods/dialogs/create-folder.ts b/packages/client/src/methods/dialogs/create-folder.ts index e0ada444..b1372636 100644 --- a/packages/client/src/methods/dialogs/create-folder.ts +++ b/packages/client/src/methods/dialogs/create-folder.ts @@ -1,6 +1,6 @@ -import { PartialExcept, tl } from '@mtcute/core' +import { BaseTelegramClient, PartialExcept, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' +import { getFolders } from './get-folders' /** * Create a folder from given parameters @@ -10,16 +10,15 @@ import { TelegramClient } from '../../client' * * @param folder Parameters for the folder * @returns Newly created folder - * @internal */ export async function createFolder( - this: TelegramClient, + client: BaseTelegramClient, folder: PartialExcept, ): Promise { let id = folder.id if (!id) { - const old = await this.getFolders() + const old = await getFolders(client) // determine next id by finding max id // thanks durov for awesome api @@ -39,7 +38,7 @@ export async function createFolder( id, } - await this.call({ + await client.call({ _: 'messages.updateDialogFilter', id, filter, diff --git a/packages/client/src/methods/dialogs/delete-folder.ts b/packages/client/src/methods/dialogs/delete-folder.ts index 626f9a39..fa90a258 100644 --- a/packages/client/src/methods/dialogs/delete-folder.ts +++ b/packages/client/src/methods/dialogs/delete-folder.ts @@ -1,15 +1,12 @@ -import { tl } from '@mtcute/core' - -import { TelegramClient } from '../../client' +import { BaseTelegramClient, tl } from '@mtcute/core' /** * Delete a folder by its ID * * @param id Folder ID or folder itself - * @internal */ -export async function deleteFolder(this: TelegramClient, id: number | tl.RawDialogFilter): Promise { - await this.call({ +export async function deleteFolder(client: BaseTelegramClient, id: number | tl.RawDialogFilter): Promise { + await client.call({ _: 'messages.updateDialogFilter', id: typeof id === 'number' ? id : id.id, }) diff --git a/packages/client/src/methods/dialogs/edit-folder.ts b/packages/client/src/methods/dialogs/edit-folder.ts index 66f0de16..fc04c922 100644 --- a/packages/client/src/methods/dialogs/edit-folder.ts +++ b/packages/client/src/methods/dialogs/edit-folder.ts @@ -1,15 +1,14 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' +import { getFolders } from './get-folders' /** * Edit a folder with given modification * * @returns Modified folder - * @internal */ export async function editFolder( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Folder, folder ID or name. @@ -30,7 +29,7 @@ export async function editFolder( throw new MtArgumentError('Cannot modify default folder') } if (typeof folder === 'number' || typeof folder === 'string') { - const old = await this.getFolders() + const old = await getFolders(client) const found = old.find((it) => it._ === 'dialogFilter' && (it.id === folder || it.title === folder)) if (!found) { @@ -45,7 +44,7 @@ export async function editFolder( ...modification, } - await this.call({ + await client.call({ _: 'messages.updateDialogFilter', id: folder.id, filter, diff --git a/packages/client/src/methods/dialogs/find-folder.ts b/packages/client/src/methods/dialogs/find-folder.ts index 484fee25..39121f22 100644 --- a/packages/client/src/methods/dialogs/find-folder.ts +++ b/packages/client/src/methods/dialogs/find-folder.ts @@ -1,6 +1,6 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' +import { getFolders } from './get-folders' /** * Find a folder by its parameter. @@ -10,10 +10,9 @@ import { TelegramClient } from '../../client' * > to multiple folders. * * @param params Search parameters. At least one must be set. - * @internal */ export async function findFolder( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Folder title */ title?: string @@ -27,7 +26,7 @@ export async function findFolder( throw new MtArgumentError('One of search parameters must be passed') } - const folders = await this.getFolders() + const folders = await getFolders(client) return ( (folders.find((it) => { diff --git a/packages/client/src/methods/dialogs/get-folders.ts b/packages/client/src/methods/dialogs/get-folders.ts index 9907dc38..b4a8e716 100644 --- a/packages/client/src/methods/dialogs/get-folders.ts +++ b/packages/client/src/methods/dialogs/get-folders.ts @@ -1,25 +1,23 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputDialogFolder } from '../../types' /** * Get list of folders. - * @internal */ -export async function getFolders(this: TelegramClient): Promise { - return this.call({ +export async function getFolders(client: BaseTelegramClient): Promise { + return client.call({ _: 'messages.getDialogFilters', }) } /** @internal */ export async function _normalizeInputFolder( - this: TelegramClient, + client: BaseTelegramClient, folder: InputDialogFolder, ): Promise { if (typeof folder === 'string' || typeof folder === 'number') { - const folders = await this.getFolders() + const folders = await getFolders(client) const found = folders.find((it) => { if (it._ === 'dialogFilterDefault') { return folder === 0 diff --git a/packages/client/src/methods/dialogs/get-peer-dialogs.ts b/packages/client/src/methods/dialogs/get-peer-dialogs.ts index 048331f2..97eecd56 100644 --- a/packages/client/src/methods/dialogs/get-peer-dialogs.ts +++ b/packages/client/src/methods/dialogs/get-peer-dialogs.ts @@ -1,36 +1,33 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' -import { Dialog, InputPeerLike } from '../../types' +import { Dialog } from '../../types/messages/dialog' +import { InputPeerLike } from '../../types/peers' +import { resolvePeerMany } from '../users/resolve-peer-many' /** * Get dialogs with certain peers. * * @param peers Peers for which to fetch dialogs. - * @internal */ -export async function getPeerDialogs(this: TelegramClient, peers: InputPeerLike): Promise +export async function getPeerDialogs(client: BaseTelegramClient, peers: InputPeerLike): Promise /** * Get dialogs with certain peers. * * @param peers Peers for which to fetch dialogs. - * @internal */ -export async function getPeerDialogs(this: TelegramClient, peers: InputPeerLike[]): Promise +export async function getPeerDialogs(client: BaseTelegramClient, peers: InputPeerLike[]): Promise -/** - * @internal - */ +/** @internal */ export async function getPeerDialogs( - this: TelegramClient, + client: BaseTelegramClient, peers: MaybeArray, ): Promise> { const isSingle = !Array.isArray(peers) if (isSingle) peers = [peers as InputPeerLike] - const res = await this.call({ + const res = await client.call({ _: 'messages.getPeerDialogs', - peers: await this.resolvePeerMany(peers as InputPeerLike[]).then((peers) => + peers: await resolvePeerMany(client, peers as InputPeerLike[]).then((peers) => peers.map((it) => ({ _: 'inputDialogPeer', peer: it, @@ -38,7 +35,7 @@ export async function getPeerDialogs( ), }) - const dialogs = Dialog.parseTlDialogs(this, res) + const dialogs = Dialog.parseTlDialogs(res) return isSingle ? dialogs[0] : dialogs } diff --git a/packages/client/src/methods/dialogs/iter-dialogs.ts b/packages/client/src/methods/dialogs/iter-dialogs.ts index 0885ab15..44b3137a 100644 --- a/packages/client/src/methods/dialogs/iter-dialogs.ts +++ b/packages/client/src/methods/dialogs/iter-dialogs.ts @@ -1,8 +1,8 @@ -import { Long, MtUnsupportedError, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, MtUnsupportedError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { Dialog, InputDialogFolder } from '../../types' import { normalizeDate } from '../../utils/misc-utils' +import { _normalizeInputFolder } from './get-folders' /** * Iterate over dialogs. @@ -13,10 +13,9 @@ import { normalizeDate } from '../../utils/misc-utils' * is not considered when sorting. * * @param params Fetch parameters - * @internal */ export async function* iterDialogs( - this: TelegramClient, + client: BaseTelegramClient, params?: { /** * Offset message date used as an anchor for pagination. @@ -127,7 +126,7 @@ export async function* iterDialogs( let localFilters_: tl.TypeDialogFilter | undefined if (folder) { - localFilters_ = await this._normalizeInputFolder(folder) + localFilters_ = await _normalizeInputFolder(client, folder) } if (filter) { @@ -167,7 +166,7 @@ export async function* iterDialogs( if (!localFilters || !localFilters.pinnedPeers.length) { return null } - const res = await this.call({ + const res = await client.call({ _: 'messages.getPeerDialogs', peers: localFilters.pinnedPeers.map((peer) => ({ _: 'inputDialogPeer' as const, @@ -186,12 +185,12 @@ export async function* iterDialogs( if (localFilters) { res = await fetchPinnedDialogsFromFolder() } else { - res = await this.call({ + res = await client.call({ _: 'messages.getPinnedDialogs', folderId: archived === 'exclude' ? 0 : 1, }) } - if (res) yield* Dialog.parseTlDialogs(this, res, limit) + if (res) yield* Dialog.parseTlDialogs(res, limit) return } @@ -208,7 +207,7 @@ export async function* iterDialogs( const res = await fetchPinnedDialogsFromFolder() if (res) { - const dialogs = Dialog.parseTlDialogs(this, res, limit) + const dialogs = Dialog.parseTlDialogs(res, limit) for (const d of dialogs) { yield d @@ -234,8 +233,7 @@ export async function* iterDialogs( for (;;) { const dialogs = Dialog.parseTlDialogs( - this, - await this.call({ + await client.call({ _: 'messages.getDialogs', excludePinned: params.pinned === 'exclude', folderId, diff --git a/packages/client/src/methods/dialogs/set-folders-order.ts b/packages/client/src/methods/dialogs/set-folders-order.ts index 8452639a..c8b0cfca 100644 --- a/packages/client/src/methods/dialogs/set-folders-order.ts +++ b/packages/client/src/methods/dialogs/set-folders-order.ts @@ -1,13 +1,12 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' /** * Reorder folders * * @param order New order of folders (folder IDs, where default = 0) - * @internal */ -export async function setFoldersOrder(this: TelegramClient, order: number[]): Promise { - await this.call({ +export async function setFoldersOrder(client: BaseTelegramClient, order: number[]): Promise { + await client.call({ _: 'messages.updateDialogFiltersOrder', order, }) diff --git a/packages/client/src/methods/files/download-buffer.ts b/packages/client/src/methods/files/download-buffer.ts index d31036ba..7130122f 100644 --- a/packages/client/src/methods/files/download-buffer.ts +++ b/packages/client/src/methods/files/download-buffer.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { FileDownloadParameters, FileLocation } from '../../types' +import { downloadAsIterable } from './download-iterable' /** * Download a file and return its contents as a Buffer. @@ -8,16 +10,15 @@ import { FileDownloadParameters, FileLocation } from '../../types' * > into memory at once. This might cause an issue, so use wisely! * * @param params File download parameters - * @internal */ -export async function downloadAsBuffer(this: TelegramClient, params: FileDownloadParameters): Promise { +export async function downloadAsBuffer(client: BaseTelegramClient, params: FileDownloadParameters): Promise { if (params.location instanceof FileLocation && Buffer.isBuffer(params.location.location)) { return params.location.location } const chunks = [] - for await (const chunk of this.downloadAsIterable(params)) { + for await (const chunk of downloadAsIterable(client, params)) { chunks.push(chunk) } diff --git a/packages/client/src/methods/files/download-file.ts b/packages/client/src/methods/files/download-file.ts index 2ae72b46..3f22267c 100644 --- a/packages/client/src/methods/files/download-file.ts +++ b/packages/client/src/methods/files/download-file.ts @@ -1,7 +1,7 @@ -import { MtUnsupportedError } from '@mtcute/core' +import { BaseTelegramClient, MtUnsupportedError } from '@mtcute/core' -import { TelegramClient } from '../../client' import { FileDownloadParameters, FileLocation } from '../../types' +import { downloadAsStream } from './download-stream' let fs: typeof import('fs') | null = null try { @@ -14,9 +14,12 @@ try { * * @param filename Local file name to which the remote file will be downloaded * @param params File download parameters - * @internal */ -export function downloadToFile(this: TelegramClient, filename: string, params: FileDownloadParameters): Promise { +export function downloadToFile( + client: BaseTelegramClient, + filename: string, + params: FileDownloadParameters, +): Promise { if (!fs) { throw new MtUnsupportedError('Downloading to file is only supported in NodeJS') } @@ -34,11 +37,11 @@ export function downloadToFile(this: TelegramClient, filename: string, params: F } const output = fs.createWriteStream(filename) - const stream = this.downloadAsStream(params) + const stream = downloadAsStream(client, params) if (params.abortSignal) { params.abortSignal.addEventListener('abort', () => { - this.log.debug('aborting file download %s - cleaning up', filename) + client.log.debug('aborting file download %s - cleaning up', filename) output.destroy() stream.destroy() fs!.rmSync(filename) diff --git a/packages/client/src/methods/files/download-iterable.ts b/packages/client/src/methods/files/download-iterable.ts index 94a55bbb..2501828a 100644 --- a/packages/client/src/methods/files/download-iterable.ts +++ b/packages/client/src/methods/files/download-iterable.ts @@ -1,8 +1,7 @@ -import { ConnectionKind, MtArgumentError, MtUnsupportedError, tl } from '@mtcute/core' +import { BaseTelegramClient, ConnectionKind, MtArgumentError, MtUnsupportedError, tl } from '@mtcute/core' import { ConditionVariable } from '@mtcute/core/utils' import { fileIdToInputFileLocation, fileIdToInputWebFileLocation, parseFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { FileDownloadParameters, FileLocation } from '../../types' import { determinePartSize } from '../../utils/file-utils' @@ -18,10 +17,9 @@ const REQUESTS_PER_CONNECTION = 3 // some arbitrary magic value that seems to wo * consecutive. * * @param params Download parameters - * @internal */ export async function* downloadAsIterable( - this: TelegramClient, + client: BaseTelegramClient, params: FileDownloadParameters, ): AsyncIterableIterator { const offset = params.offset ?? 0 @@ -63,7 +61,7 @@ export async function* downloadAsIterable( const isWeb = tl.isAnyInputWebFileLocation(location) // we will receive a FileMigrateError in case this is invalid - if (!dcId) dcId = this._defaultDcs.main.id + if (!dcId) dcId = client.network.getPrimaryDcId() const partSizeKb = params.partSize ?? (fileSize ? determinePartSize(fileSize) : 64) @@ -87,13 +85,13 @@ export async function* downloadAsIterable( let connectionKind: ConnectionKind if (isSmall) { - connectionKind = dcId === this.network.getPrimaryDcId() ? 'main' : 'downloadSmall' + connectionKind = dcId === client.network.getPrimaryDcId() ? 'main' : 'downloadSmall' } else { connectionKind = 'download' } - const poolSize = this.network.getPoolSize(connectionKind, dcId) + const poolSize = client.network.getPoolSize(connectionKind, dcId) - this.log.debug( + client.log.debug( 'Downloading file of size %d from dc %d using %s connection pool (pool size: %d)', limitBytes, dcId, @@ -105,7 +103,7 @@ export async function* downloadAsIterable( let result: tl.RpcCallReturn['upload.getFile'] | tl.RpcCallReturn['upload.getWebFile'] try { - result = await this.call( + result = await client.call( { _: isWeb ? 'upload.getWebFile' : 'upload.getFile', // eslint-disable-next-line @@ -155,12 +153,12 @@ export async function* downloadAsIterable( let error: unknown = undefined void Promise.all(Array.from({ length: Math.min(poolSize * REQUESTS_PER_CONNECTION, numChunks) }, downloadChunk)) .catch((e) => { - this.log.debug('download workers errored: %s', e.message) + client.log.debug('download workers errored: %s', e.message) error = e nextChunkCv.notify() }) .then(() => { - this.log.debug('download workers finished') + client.log.debug('download workers finished') }) let position = offset diff --git a/packages/client/src/methods/files/download-stream.ts b/packages/client/src/methods/files/download-stream.ts index 8b86cbfe..f29b79c2 100644 --- a/packages/client/src/methods/files/download-stream.ts +++ b/packages/client/src/methods/files/download-stream.ts @@ -1,17 +1,18 @@ import { Readable } from 'stream' -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { FileDownloadParameters, FileLocation } from '../../types' import { bufferToStream } from '../../utils/stream-utils' +import { downloadAsIterable } from './download-iterable' /** * Download a file and return it as a Node readable stream, * streaming file contents. * * @param params File download parameters - * @internal */ -export function downloadAsStream(this: TelegramClient, params: FileDownloadParameters): Readable { +export function downloadAsStream(client: BaseTelegramClient, params: FileDownloadParameters): Readable { if (params.location instanceof FileLocation && Buffer.isBuffer(params.location.location)) { return bufferToStream(params.location.location) } @@ -23,7 +24,7 @@ export function downloadAsStream(this: TelegramClient, params: FileDownloadParam // eslint-disable-next-line @typescript-eslint/no-misused-promises setTimeout(async () => { try { - for await (const chunk of this.downloadAsIterable(params)) { + for await (const chunk of downloadAsIterable(client, params)) { ret.push(chunk) } diff --git a/packages/client/src/methods/files/normalize-file-to-document.ts b/packages/client/src/methods/files/normalize-file-to-document.ts index 347b7b3d..b5bf9ebe 100644 --- a/packages/client/src/methods/files/normalize-file-to-document.ts +++ b/packages/client/src/methods/files/normalize-file-to-document.ts @@ -1,14 +1,14 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputFileLike } from '../../types' +import { _normalizeInputMedia } from './normalize-input-media' /** * @internal */ export async function _normalizeFileToDocument( - this: TelegramClient, + client: BaseTelegramClient, file: InputFileLike | tl.TypeInputDocument, params: { progressCallback?: (uploaded: number, total: number) => void @@ -18,7 +18,8 @@ export async function _normalizeFileToDocument( return file } - const media = await this._normalizeInputMedia( + const media = await _normalizeInputMedia( + client, { type: 'document', file, @@ -27,8 +28,8 @@ export async function _normalizeFileToDocument( true, ) - assertTypeIs('createStickerSet', media, 'inputMediaDocument') - assertTypeIs('createStickerSet', media.id, 'inputDocument') + assertTypeIs('_normalizeFileToDocument', media, 'inputMediaDocument') + assertTypeIs('_normalizeFileToDocument', media.id, 'inputDocument') return media.id } diff --git a/packages/client/src/methods/files/normalize-input-file.ts b/packages/client/src/methods/files/normalize-input-file.ts index 009bdaed..e38bc1b5 100644 --- a/packages/client/src/methods/files/normalize-input-file.ts +++ b/packages/client/src/methods/files/normalize-input-file.ts @@ -1,17 +1,15 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' import { tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' -import { InputFileLike, isUploadedFile } from '../../types' +import { InputFileLike, isUploadedFile } from '../../types/files' +import { uploadFile } from './upload-file' /** * Normalize a {@link InputFileLike} to `InputFile`, * uploading it if needed. - * - * @internal */ export async function _normalizeInputFile( - this: TelegramClient, + client: BaseTelegramClient, input: InputFileLike, params: { progressCallback?: (uploaded: number, total: number) => void @@ -24,7 +22,7 @@ export async function _normalizeInputFile( throw new MtArgumentError("InputFile can't be created from an InputMedia") } else if (tdFileId.isFileIdLike(input)) { if (typeof input === 'string' && input.match(/^file:/)) { - const uploaded = await this.uploadFile({ + const uploaded = await uploadFile(client, { file: input.substring(5), ...params, }) @@ -37,7 +35,7 @@ export async function _normalizeInputFile( } else if (typeof input === 'object' && tl.isAnyInputFile(input)) { return input } else { - const uploaded = await this.uploadFile({ + const uploaded = await uploadFile(client, { file: input, ...params, }) diff --git a/packages/client/src/methods/files/normalize-input-media.ts b/packages/client/src/methods/files/normalize-input-media.ts index ef5baaca..60f9711e 100644 --- a/packages/client/src/methods/files/normalize-input-media.ts +++ b/packages/client/src/methods/files/normalize-input-media.ts @@ -1,21 +1,24 @@ -import { Long, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' import { fileIdToInputDocument, fileIdToInputPhoto, parseFileId, tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' -import { InputMediaLike, isUploadedFile, UploadFileLike } from '../../types' +import { isUploadedFile } from '../../types/files/uploaded-file' +import { UploadFileLike } from '../../types/files/utils' +import { InputMediaLike } from '../../types/media/input-media' import { extractFileName } from '../../utils/file-utils' import { normalizeDate } from '../../utils/misc-utils' import { encodeWaveform } from '../../utils/voice-utils' +import { _parseEntities } from '../messages/parse-entities' +import { resolvePeer } from '../users/resolve-peer' +import { _normalizeInputFile } from './normalize-input-file' +import { uploadFile } from './upload-file' /** * Normalize an {@link InputMediaLike} to `InputMedia`, * uploading the file if needed. - * - * @internal */ export async function _normalizeInputMedia( - this: TelegramClient, + client: BaseTelegramClient, media: InputMediaLike, params: { parseMode?: string | null @@ -127,7 +130,7 @@ export async function _normalizeInputMedia( }, startParam: media.startParam, extendedMedia: media.extendedMedia ? - await this._normalizeInputMedia(media.extendedMedia, params) : + await _normalizeInputMedia(client, media.extendedMedia, params) : undefined, } } @@ -162,7 +165,8 @@ export async function _normalizeInputMedia( }) if (media.solution) { - [solution, solutionEntities] = await this._parseEntities( + [solution, solutionEntities] = await _parseEntities( + client, media.solution, params.parseMode, media.solutionEntities, @@ -193,7 +197,7 @@ export async function _normalizeInputMedia( if (media.type === 'story') { return { _: 'inputMediaStory', - peer: await this.resolvePeer(media.peer), + peer: await resolvePeer(client, media.peer), id: media.id, } } @@ -211,7 +215,7 @@ export async function _normalizeInputMedia( sendMime = media.fileMime } - const uploaded = await this.uploadFile({ + const uploaded = await uploadFile(client, { file, progressCallback: params.progressCallback, fileName: media.fileName, @@ -227,7 +231,7 @@ export async function _normalizeInputMedia( const uploadMediaIfNeeded = async (inputMedia: tl.TypeInputMedia, photo: boolean): Promise => { if (!uploadMedia) return inputMedia - const res = await this.call({ + const res = await client.call({ _: 'messages.uploadMedia', peer: uploadPeer, media: inputMedia, @@ -330,7 +334,7 @@ export async function _normalizeInputMedia( } if ('thumb' in media && media.thumb) { - thumb = await this._normalizeInputFile(media.thumb, {}) + thumb = await _normalizeInputFile(client, media.thumb, {}) } const attributes: tl.TypeDocumentAttribute[] = [] diff --git a/packages/client/src/methods/files/upload-file.ts b/packages/client/src/methods/files/upload-file.ts index c8978970..470eaa22 100644 --- a/packages/client/src/methods/files/upload-file.ts +++ b/packages/client/src/methods/files/upload-file.ts @@ -2,10 +2,9 @@ import { fromBuffer as fileTypeFromBuffer } from 'file-type' import type { ReadStream } from 'fs' import { Readable } from 'stream' -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' import { AsyncLock, randomLong } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { UploadedFile, UploadFileLike } from '../../types' import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils' import { bufferToStream, convertWebStreamToNodeReadable, readBytesFromStream } from '../../utils/stream-utils' @@ -40,10 +39,9 @@ const MAX_PART_COUNT_PREMIUM = 8000 // 512 kb * 8000 = 4000 MiB * methods like {@link sendMedia} that handle this under the hood. * * @param params Upload parameters - * @internal */ export async function uploadFile( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Upload file source. @@ -205,7 +203,7 @@ export async function uploadFile( const partSize = partSizeKb * 1024 let partCount = fileSize === -1 ? -1 : ~~((fileSize + partSize - 1) / partSize) - const maxPartCount = this.network.params.isPremium ? MAX_PART_COUNT_PREMIUM : MAX_PART_COUNT + const maxPartCount = client.network.params.isPremium ? MAX_PART_COUNT_PREMIUM : MAX_PART_COUNT if (partCount > maxPartCount) { throw new MtArgumentError(`File is too large (max ${maxPartCount} parts, got ${partCount})`) @@ -214,10 +212,10 @@ export async function uploadFile( const isBig = fileSize === -1 || fileSize > BIG_FILE_MIN_SIZE const isSmall = fileSize !== -1 && fileSize < SMALL_FILE_MAX_SIZE const connectionKind = isSmall ? 'main' : 'upload' - const connectionPoolSize = Math.min(this.network.getPoolSize(connectionKind), partCount) + const connectionPoolSize = Math.min(client.network.getPoolSize(connectionKind), partCount) const requestsPerConnection = params.requestsPerConnection ?? REQUESTS_PER_CONNECTION - this.log.debug( + client.log.debug( 'uploading %d bytes file in %d chunks, each %d bytes in %s connection pool of size %d', fileSize, partCount, @@ -255,7 +253,7 @@ export async function uploadFile( fileSize = pos + (part?.length ?? 0) partCount = ~~((fileSize + partSize - 1) / partSize) if (!part) part = Buffer.alloc(0) - this.log.debug('readable ended, file size = %d, part count = %d', fileSize, partCount) + client.log.debug('readable ended, file size = %d, part count = %d', fileSize, partCount) } if (!Buffer.isBuffer(part)) { @@ -295,7 +293,7 @@ export async function uploadFile( bytes: part, } satisfies tl.upload.RawSaveFilePartRequest) - const result = await this.call(request, { kind: connectionKind }) + const result = await client.call(request, { kind: connectionKind }) if (!result) throw new Error(`Failed to upload part ${idx}`) pos += part.length diff --git a/packages/client/src/methods/files/upload-media.ts b/packages/client/src/methods/files/upload-media.ts index c4224819..71b15275 100644 --- a/packages/client/src/methods/files/upload-media.ts +++ b/packages/client/src/methods/files/upload-media.ts @@ -1,9 +1,10 @@ -import { assertNever, MtArgumentError } from '@mtcute/core' +import { assertNever, BaseTelegramClient, MtArgumentError } from '@mtcute/core' import { assertTypeIs, assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputMediaLike, InputPeerLike, MessageMedia, Photo, RawDocument } from '../../types' import { parseDocument } from '../../types/media/document-utils' +import { resolvePeer } from '../users/resolve-peer' +import { _normalizeInputMedia } from './normalize-input-media' /** * Upload a media to Telegram servers, without actually @@ -15,10 +16,9 @@ import { parseDocument } from '../../types/media/document-utils' * * @param media Media to upload * @param params Upload parameters - * @internal */ export async function uploadMedia( - this: TelegramClient, + client: BaseTelegramClient, media: InputMediaLike, params: { /** @@ -36,7 +36,7 @@ export async function uploadMedia( progressCallback?: (uploaded: number, total: number) => void } = {}, ): Promise> { - const normMedia = await this._normalizeInputMedia(media, params, false) + const normMedia = await _normalizeInputMedia(client, media, params, false) switch (normMedia._) { case 'inputMediaEmpty': @@ -51,10 +51,10 @@ export async function uploadMedia( throw new MtArgumentError("This media can't be uploaded") } - const res = await this.call({ + const res = await client.call({ _: 'messages.uploadMedia', peer: params.peer ? - await this.resolvePeer(params.peer) : + await resolvePeer(client, params.peer) : { _: 'inputPeerSelf', }, @@ -70,7 +70,7 @@ export async function uploadMedia( assertTypeIs('uploadMedia', res, 'messageMediaPhoto') assertTypeIs('uploadMedia', res.photo!, 'photo') - return new Photo(this, res.photo, res) + return new Photo(res.photo) case 'inputMediaUploadedDocument': case 'inputMediaDocument': case 'inputMediaDocumentExternal': @@ -78,11 +78,10 @@ export async function uploadMedia( assertTypeIs('uploadMedia', res.document!, 'document') // eslint-disable-next-line - return parseDocument(this, res.document, res) as any + return parseDocument(res.document, res) as any case 'inputMediaStory': throw new MtArgumentError("This media (story) can't be uploaded") default: assertNever(normMedia) - // ^? } } diff --git a/packages/client/src/methods/forums/create-forum-topic.ts b/packages/client/src/methods/forums/create-forum-topic.ts index 9facc38a..ae3e3093 100644 --- a/packages/client/src/methods/forums/create-forum-topic.ts +++ b/packages/client/src/methods/forums/create-forum-topic.ts @@ -1,9 +1,10 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { randomLong } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike, Message } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { _findMessageInUpdate } from '../messages/find-in-update' +import { resolvePeer } from '../users/resolve-peer' /** * Create a topic in a forum @@ -11,10 +12,9 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * Only admins with `manageTopics` permission can do this. * * @returns Service message for the created topic - * @internal */ export async function createForumTopic( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID or username */ chatId: InputPeerLike @@ -42,15 +42,15 @@ export async function createForumTopic( ): Promise { const { chatId, title, icon, sendAs } = params - const res = await this.call({ + const res = await client.call({ _: 'channels.createForumTopic', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), title, iconColor: typeof icon === 'number' ? icon : undefined, iconEmojiId: typeof icon !== 'number' ? icon : undefined, - sendAs: sendAs ? await this.resolvePeer(sendAs) : undefined, + sendAs: sendAs ? await resolvePeer(client, sendAs) : undefined, randomId: randomLong(), }) - return this._findMessageInUpdate(res) + return _findMessageInUpdate(client, res) } diff --git a/packages/client/src/methods/forums/delete-forum-topic-history.ts b/packages/client/src/methods/forums/delete-forum-topic-history.ts index 50c0bd3d..de6f07a4 100644 --- a/packages/client/src/methods/forums/delete-forum-topic-history.ts +++ b/packages/client/src/methods/forums/delete-forum-topic-history.ts @@ -1,30 +1,30 @@ +import { BaseTelegramClient } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Delete a forum topic and all its history * * @param chat Chat or user ID, username, phone number, `"me"` or `"self"` * @param topicId ID of the topic (i.e. its top message ID) - * @internal */ export async function deleteForumTopicHistory( - this: TelegramClient, + client: BaseTelegramClient, chat: InputPeerLike, topicId: number, ): Promise { - const channel = normalizeToInputChannel(await this.resolvePeer(chat), chat) + const channel = normalizeToInputChannel(await resolvePeer(client, chat), chat) assertTypeIsNot('deleteForumTopicHistory', channel, 'inputChannelEmpty') - const res = await this.call({ + const res = await client.call({ _: 'channels.deleteTopicHistory', channel, topMsgId: topicId, }) - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount, channel.channelId)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount, channel.channelId)) } diff --git a/packages/client/src/methods/forums/edit-forum-topic.ts b/packages/client/src/methods/forums/edit-forum-topic.ts index 75d21075..6fa284d1 100644 --- a/packages/client/src/methods/forums/edit-forum-topic.ts +++ b/packages/client/src/methods/forums/edit-forum-topic.ts @@ -1,8 +1,9 @@ -import { Long, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, Message } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { _findMessageInUpdate } from '../messages/find-in-update' +import { resolvePeer } from '../users/resolve-peer' /** * Modify a topic in a forum @@ -12,10 +13,9 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * @param chatId Chat ID or username * @param topicId ID of the topic (i.e. its top message ID) * @returns Service message about the modification - * @internal */ export async function editForumTopic( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID or username */ chatId: InputPeerLike @@ -38,13 +38,13 @@ export async function editForumTopic( ): Promise { const { chatId, topicId, title, icon } = params - const res = await this.call({ + const res = await client.call({ _: 'channels.editForumTopic', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), topicId, title, iconEmojiId: icon ? icon ?? Long.ZERO : undefined, }) - return this._findMessageInUpdate(res) + return _findMessageInUpdate(client, res) } diff --git a/packages/client/src/methods/forums/get-forum-topics-by-id.ts b/packages/client/src/methods/forums/get-forum-topics-by-id.ts index 880c7caa..e0528288 100644 --- a/packages/client/src/methods/forums/get-forum-topics-by-id.ts +++ b/packages/client/src/methods/forums/get-forum-topics-by-id.ts @@ -1,25 +1,27 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { ForumTopic, InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get a single forum topic by its ID * * @param chatId Chat ID or username - * @internal */ -export async function getForumTopicsById(this: TelegramClient, chatId: InputPeerLike, ids: number): Promise +export async function getForumTopicsById( + client: BaseTelegramClient, + chatId: InputPeerLike, + ids: number, +): Promise /** * Get forum topics by their IDs * * @param chatId Chat ID or username - * @internal */ export async function getForumTopicsById( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, ids: number[], ): Promise @@ -28,23 +30,22 @@ export async function getForumTopicsById( * Get forum topics by their IDs * * @param chatId Chat ID or username - * @internal */ export async function getForumTopicsById( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, ids: MaybeArray, ): Promise> { const single = !Array.isArray(ids) if (single) ids = [ids as number] - const res = await this.call({ + const res = await client.call({ _: 'channels.getForumTopicsByID', - channel: normalizeToInputChannel(await this.resolvePeer(chatId)), + channel: normalizeToInputChannel(await resolvePeer(client, chatId)), topics: ids as number[], }) - const topics = ForumTopic.parseTlForumTopics(this, res) + const topics = ForumTopic.parseTlForumTopics(res) return single ? topics[0] : topics } diff --git a/packages/client/src/methods/forums/get-forum-topics.ts b/packages/client/src/methods/forums/get-forum-topics.ts index 36e0dbd0..daf18d24 100644 --- a/packages/client/src/methods/forums/get-forum-topics.ts +++ b/packages/client/src/methods/forums/get-forum-topics.ts @@ -1,7 +1,9 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ArrayPaginated, ForumTopic, InputPeerLike } from '../../types' import { makeArrayPaginated } from '../../utils' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' // @exported export interface GetForumTopicsOffset { @@ -20,10 +22,9 @@ const defaultOffset: GetForumTopicsOffset = { * Get forum topics * * @param chatId Chat ID or username - * @internal */ export async function getForumTopics( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -52,9 +53,9 @@ export async function getForumTopics( limit = 100, } = params - const res = await this.call({ + const res = await client.call({ _: 'channels.getForumTopics', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), q: query, offsetDate, offsetId, @@ -62,7 +63,7 @@ export async function getForumTopics( limit, }) - const topics = ForumTopic.parseTlForumTopics(this, res) + const topics = ForumTopic.parseTlForumTopics(res) const last = topics[topics.length - 1] const next = last ? diff --git a/packages/client/src/methods/forums/iter-forum-topics.ts b/packages/client/src/methods/forums/iter-forum-topics.ts index 5273310d..624db236 100644 --- a/packages/client/src/methods/forums/iter-forum-topics.ts +++ b/packages/client/src/methods/forums/iter-forum-topics.ts @@ -1,17 +1,19 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ForumTopic, InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' +import { getForumTopics } from './get-forum-topics' /** * Iterate over forum topics. Wrapper over {@link getForumTopics}. * * @param chatId Chat ID or username - * @internal */ export async function* iterForumTopics( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Maximum number of topics to return. * @@ -29,13 +31,13 @@ export async function* iterForumTopics( const { query, limit = Infinity, chunkSize = 100 } = params - const peer = normalizeToInputChannel(await this.resolvePeer(chatId)) + const peer = normalizeToInputChannel(await resolvePeer(client, chatId)) let { offset } = params let current = 0 for (;;) { - const res = await this.getForumTopics(peer, { + const res = await getForumTopics(client, peer, { query, offset, limit: Math.min(chunkSize, limit - current), diff --git a/packages/client/src/methods/forums/reorder-pinned-forum-topics.ts b/packages/client/src/methods/forums/reorder-pinned-forum-topics.ts index 023169a9..b5f11e4e 100644 --- a/packages/client/src/methods/forums/reorder-pinned-forum-topics.ts +++ b/packages/client/src/methods/forums/reorder-pinned-forum-topics.ts @@ -1,16 +1,16 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Reorder pinned forum topics * * Only admins with `manageTopics` permission can do this. - * - * @internal */ export async function reorderPinnedForumTopics( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID or username */ chatId: InputPeerLike @@ -27,9 +27,9 @@ export async function reorderPinnedForumTopics( }, ): Promise { const { chatId, order, force } = params - await this.call({ + await client.call({ _: 'channels.reorderPinnedForumTopics', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), order, force, }) diff --git a/packages/client/src/methods/forums/toggle-forum-topic-closed.ts b/packages/client/src/methods/forums/toggle-forum-topic-closed.ts index 25afdc35..8ea64771 100644 --- a/packages/client/src/methods/forums/toggle-forum-topic-closed.ts +++ b/packages/client/src/methods/forums/toggle-forum-topic-closed.ts @@ -1,6 +1,9 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, Message } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { _findMessageInUpdate } from '../messages/find-in-update' +import { resolvePeer } from '../users/resolve-peer' /** * Toggle open/close status of a topic in a forum @@ -8,10 +11,9 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * Only admins with `manageTopics` permission can do this. * * @returns Service message about the modification - * @internal */ export async function toggleForumTopicClosed( - this: TelegramClient, + client: BaseTelegramClient, parmas: { /** Chat ID or username */ chatId: InputPeerLike @@ -25,12 +27,12 @@ export async function toggleForumTopicClosed( ): Promise { const { chatId, topicId, closed } = parmas - const res = await this.call({ + const res = await client.call({ _: 'channels.editForumTopic', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), topicId, closed, }) - return this._findMessageInUpdate(res) + return _findMessageInUpdate(client, res) } diff --git a/packages/client/src/methods/forums/toggle-forum-topic-pinned.ts b/packages/client/src/methods/forums/toggle-forum-topic-pinned.ts index 87a05b79..a9571169 100644 --- a/packages/client/src/methods/forums/toggle-forum-topic-pinned.ts +++ b/packages/client/src/methods/forums/toggle-forum-topic-pinned.ts @@ -1,16 +1,16 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Toggle whether a topic in a forum is pinned * * Only admins with `manageTopics` permission can do this. - * - * @internal */ export async function toggleForumTopicPinned( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID or username */ chatId: InputPeerLike @@ -22,9 +22,9 @@ export async function toggleForumTopicPinned( ): Promise { const { chatId, topicId, pinned } = params - await this.call({ + await client.call({ _: 'channels.updatePinnedForumTopic', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), topicId, pinned, }) diff --git a/packages/client/src/methods/forums/toggle-forum.ts b/packages/client/src/methods/forums/toggle-forum.ts index 323a05a4..d9538ce1 100644 --- a/packages/client/src/methods/forums/toggle-forum.ts +++ b/packages/client/src/methods/forums/toggle-forum.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Set whether a supergroup is a forum. @@ -9,13 +11,12 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * * @param chatId Chat ID or username * @param enabled Whether the supergroup should be a forum - * @internal */ -export async function toggleForum(this: TelegramClient, chatId: InputPeerLike, enabled = false): Promise { - const res = await this.call({ +export async function toggleForum(client: BaseTelegramClient, chatId: InputPeerLike, enabled = false): Promise { + const res = await client.call({ _: 'channels.toggleForum', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), enabled, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/forums/toggle-general-topic-hidden.ts b/packages/client/src/methods/forums/toggle-general-topic-hidden.ts index 2f1ff952..237b643b 100644 --- a/packages/client/src/methods/forums/toggle-general-topic-hidden.ts +++ b/packages/client/src/methods/forums/toggle-general-topic-hidden.ts @@ -1,6 +1,9 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, Message } from '../../types' import { normalizeToInputChannel } from '../../utils/peer-utils' +import { _findMessageInUpdate } from '../messages/find-in-update' +import { resolvePeer } from '../users/resolve-peer' /** * Toggle whether "General" topic in a forum is hidden or not @@ -10,19 +13,18 @@ import { normalizeToInputChannel } from '../../utils/peer-utils' * @param chatId Chat ID or username * @param hidden Whether the topic should be hidden * @returns Service message about the modification - * @internal */ export async function toggleGeneralTopicHidden( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, hidden: boolean, ): Promise { - const res = await this.call({ + const res = await client.call({ _: 'channels.editForumTopic', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), topicId: 1, hidden, }) - return this._findMessageInUpdate(res) + return _findMessageInUpdate(client, res) } diff --git a/packages/client/src/methods/invite-links/create-invite-link.ts b/packages/client/src/methods/invite-links/create-invite-link.ts index dc8f62f6..f4a98789 100644 --- a/packages/client/src/methods/invite-links/create-invite-link.ts +++ b/packages/client/src/methods/invite-links/create-invite-link.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ChatInviteLink, InputPeerLike } from '../../types' import { normalizeDate } from '../../utils/misc-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Create an additional invite link for the chat. @@ -9,10 +11,9 @@ import { normalizeDate } from '../../utils/misc-utils' * * @param chatId Chat ID * @param params - * @internal */ export async function createInviteLink( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -38,13 +39,13 @@ export async function createInviteLink( ): Promise { if (!params) params = {} - const res = await this.call({ + const res = await client.call({ _: 'messages.exportChatInvite', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), expireDate: normalizeDate(params.expires), usageLimit: params.usageLimit, requestNeeded: params.withApproval, }) - return new ChatInviteLink(this, res) + return new ChatInviteLink(res) } diff --git a/packages/client/src/methods/invite-links/edit-invite-link.ts b/packages/client/src/methods/invite-links/edit-invite-link.ts index 0c93e122..ea974154 100644 --- a/packages/client/src/methods/invite-links/edit-invite-link.ts +++ b/packages/client/src/methods/invite-links/edit-invite-link.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types' import { normalizeDate } from '../../utils/misc-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Edit an invite link. You can only edit non-primary @@ -12,10 +14,9 @@ import { normalizeDate } from '../../utils/misc-utils' * @param link Invite link to edit * @param params * @returns Modified invite link - * @internal */ export async function editInviteLink( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID */ chatId: InputPeerLike @@ -44,9 +45,9 @@ export async function editInviteLink( ): Promise { const { chatId, link, expires, usageLimit, withApproval } = params - const res = await this.call({ + const res = await client.call({ _: 'messages.editExportedChatInvite', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), link, expireDate: normalizeDate(expires), usageLimit, @@ -55,5 +56,5 @@ export async function editInviteLink( const peers = PeersIndex.from(res) - return new ChatInviteLink(this, res.invite, peers) + return new ChatInviteLink(res.invite, peers) } diff --git a/packages/client/src/methods/invite-links/export-invite-link.ts b/packages/client/src/methods/invite-links/export-invite-link.ts index 680bee95..4f6a9695 100644 --- a/packages/client/src/methods/invite-links/export-invite-link.ts +++ b/packages/client/src/methods/invite-links/export-invite-link.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ChatInviteLink, InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Generate a new primary invite link for a chat, @@ -9,14 +11,13 @@ import { ChatInviteLink, InputPeerLike } from '../../types' * > and bots by default don't have one. * * @param chatId Chat IDs - * @internal */ -export async function exportInviteLink(this: TelegramClient, chatId: InputPeerLike): Promise { - const res = await this.call({ +export async function exportInviteLink(client: BaseTelegramClient, chatId: InputPeerLike): Promise { + const res = await client.call({ _: 'messages.exportChatInvite', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), legacyRevokePermanent: true, }) - return new ChatInviteLink(this, res) + return new ChatInviteLink(res) } diff --git a/packages/client/src/methods/invite-links/get-invite-link-members.ts b/packages/client/src/methods/invite-links/get-invite-link-members.ts index ba965d6c..3bf991a1 100644 --- a/packages/client/src/methods/invite-links/get-invite-link-members.ts +++ b/packages/client/src/methods/invite-links/get-invite-link-members.ts @@ -1,8 +1,8 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { ArrayPaginated, ChatInviteLinkMember, InputPeerLike, PeersIndex } from '../../types' import { makeArrayPaginated, normalizeDate, normalizeToInputUser } from '../../utils' +import { resolvePeer } from '../users/resolve-peer' /** * Iterate over users who have joined @@ -10,10 +10,9 @@ import { makeArrayPaginated, normalizeDate, normalizeToInputUser } from '../../u * * @param chatId Chat ID * @param params Additional params - * @internal */ export async function getInviteLinkMembers( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -53,7 +52,7 @@ export async function getInviteLinkMembers( requestedSearch?: string }, ): Promise> { - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) if (!params) params = {} const { limit = 100, link, requestedSearch, requested = Boolean(requestedSearch) } = params @@ -62,7 +61,7 @@ export async function getInviteLinkMembers( const offsetDate = normalizeDate(params.offsetDate) ?? 0 - const res = await this.call({ + const res = await client.call({ _: 'messages.getChatInviteImporters', limit, peer, @@ -75,7 +74,7 @@ export async function getInviteLinkMembers( const peers = PeersIndex.from(res) - const members = res.importers.map((it) => new ChatInviteLinkMember(this, it, peers)) + const members = res.importers.map((it) => new ChatInviteLinkMember(it, peers)) const last = members[members.length - 1] const nextOffset = last ? diff --git a/packages/client/src/methods/invite-links/get-invite-link.ts b/packages/client/src/methods/invite-links/get-invite-link.ts index 1eb45094..272fb4ce 100644 --- a/packages/client/src/methods/invite-links/get-invite-link.ts +++ b/packages/client/src/methods/invite-links/get-invite-link.ts @@ -1,25 +1,26 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Get detailed information about an invite link * * @param chatId Chat ID * @param link The invite link - * @internal */ export async function getInviteLink( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, link: string, ): Promise { - const res = await this.call({ + const res = await client.call({ _: 'messages.getExportedChatInvite', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), link, }) const peers = PeersIndex.from(res) - return new ChatInviteLink(this, res.invite, peers) + return new ChatInviteLink(res.invite, peers) } diff --git a/packages/client/src/methods/invite-links/get-invite-links.ts b/packages/client/src/methods/invite-links/get-invite-links.ts index 1b163007..08f101d0 100644 --- a/packages/client/src/methods/invite-links/get-invite-links.ts +++ b/packages/client/src/methods/invite-links/get-invite-links.ts @@ -1,7 +1,9 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ArrayPaginated, ChatInviteLink, InputPeerLike, PeersIndex } from '../../types' import { makeArrayPaginated } from '../../utils' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' // @exported export interface GetInviteLinksOffset { @@ -19,10 +21,9 @@ export interface GetInviteLinksOffset { * @param chatId Chat ID * @param adminId Admin who created the links * @param params - * @internal */ export async function getInviteLinks( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -54,11 +55,11 @@ export async function getInviteLinks( const { revoked = false, limit = Infinity, admin, offset } = params - const res = await this.call({ + const res = await client.call({ _: 'messages.getExportedChatInvites', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), revoked, - adminId: admin ? normalizeToInputUser(await this.resolvePeer(admin), admin) : { _: 'inputUserSelf' }, + adminId: admin ? normalizeToInputUser(await resolvePeer(client, admin), admin) : { _: 'inputUserSelf' }, limit, offsetDate: offset?.date, offsetLink: offset?.link, @@ -66,7 +67,7 @@ export async function getInviteLinks( const peers = PeersIndex.from(res) - const links = res.invites.map((it) => new ChatInviteLink(this, it, peers)) + const links = res.invites.map((it) => new ChatInviteLink(it, peers)) const last = links[links.length - 1] const nextOffset = last ? diff --git a/packages/client/src/methods/invite-links/get-primary-invite-link.ts b/packages/client/src/methods/invite-links/get-primary-invite-link.ts index 4967ccaa..27a110ae 100644 --- a/packages/client/src/methods/invite-links/get-primary-invite-link.ts +++ b/packages/client/src/methods/invite-links/get-primary-invite-link.ts @@ -1,18 +1,17 @@ -import { MtTypeAssertionError } from '@mtcute/core' +import { BaseTelegramClient, MtTypeAssertionError } from '@mtcute/core' -import { TelegramClient } from '../../client' import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Get primary invite link of a chat * * @param chatId Chat ID - * @internal */ -export async function getPrimaryInviteLink(this: TelegramClient, chatId: InputPeerLike): Promise { - const res = await this.call({ +export async function getPrimaryInviteLink(client: BaseTelegramClient, chatId: InputPeerLike): Promise { + const res = await client.call({ _: 'messages.getExportedChatInvites', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), adminId: { _: 'inputUserSelf' }, limit: 1, revoked: false, @@ -32,5 +31,5 @@ export async function getPrimaryInviteLink(this: TelegramClient, chatId: InputPe const peers = PeersIndex.from(res) - return new ChatInviteLink(this, res.invites[0], peers) + return new ChatInviteLink(res.invites[0], peers) } diff --git a/packages/client/src/methods/invite-links/hide-all-join-requests.ts b/packages/client/src/methods/invite-links/hide-all-join-requests.ts index fb2fe17a..84ef5214 100644 --- a/packages/client/src/methods/invite-links/hide-all-join-requests.ts +++ b/packages/client/src/methods/invite-links/hide-all-join-requests.ts @@ -1,13 +1,13 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Approve or deny multiple join requests to a chat. - * - * @internal */ export async function hideAllJoinRequests( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat/channel ID */ chatId: InputPeerLike @@ -21,10 +21,10 @@ export async function hideAllJoinRequests( ): Promise { const { chatId, action, link } = params - await this.call({ + await client.call({ _: 'messages.hideAllChatJoinRequests', approved: action === 'approve', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), link, }) } diff --git a/packages/client/src/methods/invite-links/hide-join-request.ts b/packages/client/src/methods/invite-links/hide-join-request.ts index c94c12b8..b063d7e8 100644 --- a/packages/client/src/methods/invite-links/hide-join-request.ts +++ b/packages/client/src/methods/invite-links/hide-join-request.ts @@ -1,14 +1,14 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Approve or deny join request to a chat. - * - * @internal */ export async function hideJoinRequest( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat/channel ID */ chatId: InputPeerLike @@ -20,12 +20,12 @@ export async function hideJoinRequest( ): Promise { const { chatId, user, action } = params - const userId = normalizeToInputUser(await this.resolvePeer(user), user) + const userId = normalizeToInputUser(await resolvePeer(client, user), user) - await this.call({ + await client.call({ _: 'messages.hideChatJoinRequest', approved: action === 'approve', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), userId, }) } diff --git a/packages/client/src/methods/invite-links/iter-invite-link-members.ts b/packages/client/src/methods/invite-links/iter-invite-link-members.ts index fda9de7b..4ffe3a47 100644 --- a/packages/client/src/methods/invite-links/iter-invite-link-members.ts +++ b/packages/client/src/methods/invite-links/iter-invite-link-members.ts @@ -1,5 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ChatInviteLinkMember, InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' +import { getInviteLinkMembers } from './get-invite-link-members' /** * Iterate over users who have joined @@ -7,12 +10,11 @@ import { ChatInviteLinkMember, InputPeerLike } from '../../types' * * @param chatId Chat ID * @param params Additional params - * @internal */ export async function* iterInviteLinkMembers( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Maximum number of users to return * @@ -29,7 +31,7 @@ export async function* iterInviteLinkMembers( chunkSize?: number }, ): AsyncIterableIterator { - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) if (!params) params = {} const { limit = Infinity, chunkSize = 100, link, requestedSearch, requested = Boolean(requestedSearch) } = params @@ -39,7 +41,7 @@ export async function* iterInviteLinkMembers( let current = 0 for (;;) { - const items = await this.getInviteLinkMembers(peer, { + const items = await getInviteLinkMembers(client, peer, { limit: Math.min(chunkSize, limit - current), link, requested, diff --git a/packages/client/src/methods/invite-links/iter-invite-links.ts b/packages/client/src/methods/invite-links/iter-invite-links.ts index 122bc8de..0050d94f 100644 --- a/packages/client/src/methods/invite-links/iter-invite-links.ts +++ b/packages/client/src/methods/invite-links/iter-invite-links.ts @@ -1,5 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ChatInviteLink, InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' +import { getInviteLinks } from './get-invite-links' /** * Iterate over invite links created by some administrator in the chat. @@ -11,12 +14,11 @@ import { ChatInviteLink, InputPeerLike } from '../../types' * @param chatId Chat ID * @param adminId Admin who created the links * @param params - * @internal */ export async function* iterInviteLinks( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Limit the number of invite links to be fetched. * By default, all links are fetched. @@ -39,11 +41,11 @@ export async function* iterInviteLinks( let current = 0 - const peer = await this.resolvePeer(chatId) - const adminResolved = admin ? await this.resolvePeer(admin) : ({ _: 'inputUserSelf' } as const) + const peer = await resolvePeer(client, chatId) + const adminResolved = admin ? await resolvePeer(client, admin) : ({ _: 'inputUserSelf' } as const) for (;;) { - const links = await this.getInviteLinks(peer, { + const links = await getInviteLinks(client, peer, { admin: adminResolved, revoked, limit: Math.min(chunkSize, limit - current), diff --git a/packages/client/src/methods/invite-links/revoke-invite-link.ts b/packages/client/src/methods/invite-links/revoke-invite-link.ts index da76ff4c..ac922e0e 100644 --- a/packages/client/src/methods/invite-links/revoke-invite-link.ts +++ b/packages/client/src/methods/invite-links/revoke-invite-link.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Revoke an invite link. @@ -10,16 +12,15 @@ import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types' * @param chatId Chat ID * @param link Invite link to revoke * @returns If `link` is a primary invite, newly generated invite link, otherwise the revoked link - * @internal */ export async function revokeInviteLink( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, link: string, ): Promise { - const res = await this.call({ + const res = await client.call({ _: 'messages.editExportedChatInvite', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), link, revoked: true, }) @@ -28,5 +29,5 @@ export async function revokeInviteLink( const invite = res._ === 'messages.exportedChatInviteReplaced' ? res.newInvite : res.invite - return new ChatInviteLink(this, invite, peers) + return new ChatInviteLink(invite, peers) } diff --git a/packages/client/src/methods/messages/close-poll.ts b/packages/client/src/methods/messages/close-poll.ts index f7d71bff..9012084e 100644 --- a/packages/client/src/methods/messages/close-poll.ts +++ b/packages/client/src/methods/messages/close-poll.ts @@ -1,20 +1,18 @@ -import { Long, MtTypeAssertionError } from '@mtcute/core' +import { BaseTelegramClient, Long, MtTypeAssertionError } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike, PeersIndex, Poll } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Close a poll sent by you. * * Once closed, poll can't be re-opened, and nobody * will be able to vote in it - * - * @internal */ export async function closePoll( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID where this poll was found */ chatId: InputPeerLike @@ -24,9 +22,9 @@ export async function closePoll( ): Promise { const { chatId, message } = params - const res = await this.call({ + const res = await client.call({ _: 'messages.editMessage', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), id: message, media: { _: 'inputMediaPoll', @@ -42,7 +40,7 @@ export async function closePoll( assertIsUpdatesGroup('messages.editMessage', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) const upd = res.updates[0] assertTypeIs('messages.editMessage (@ .updates[0])', upd, 'updateMessagePoll') @@ -53,5 +51,5 @@ export async function closePoll( const peers = PeersIndex.from(res) - return new Poll(this, upd.poll, peers, upd.results) + return new Poll(upd.poll, peers, upd.results) } diff --git a/packages/client/src/methods/messages/delete-messages.ts b/packages/client/src/methods/messages/delete-messages.ts index 0517d8c4..e079f600 100644 --- a/packages/client/src/methods/messages/delete-messages.ts +++ b/packages/client/src/methods/messages/delete-messages.ts @@ -1,19 +1,18 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' import { isInputPeerChannel, normalizeToInputChannel } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Delete messages, including service messages. * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param ids Message(s) ID(s) to delete. - * @internal */ export async function deleteMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, ids: MaybeArray, params?: { @@ -30,20 +29,20 @@ export async function deleteMessages( if (!Array.isArray(ids)) ids = [ids] - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) let upd if (isInputPeerChannel(peer)) { const channel = normalizeToInputChannel(peer) - const res = await this.call({ + const res = await client.call({ _: 'channels.deleteMessages', channel, id: ids, }) upd = createDummyUpdate(res.pts, res.ptsCount, peer.channelId) } else { - const res = await this.call({ + const res = await client.call({ _: 'messages.deleteMessages', id: ids, revoke, @@ -51,5 +50,5 @@ export async function deleteMessages( upd = createDummyUpdate(res.pts, res.ptsCount) } - this._handleUpdate(upd) + client.network.handleUpdate(upd) } diff --git a/packages/client/src/methods/messages/delete-scheduled-messages.ts b/packages/client/src/methods/messages/delete-scheduled-messages.ts index 671d3b37..71f57574 100644 --- a/packages/client/src/methods/messages/delete-scheduled-messages.ts +++ b/packages/client/src/methods/messages/delete-scheduled-messages.ts @@ -1,29 +1,28 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Delete scheduled messages. * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param ids Message(s) ID(s) to delete. - * @internal */ export async function deleteScheduledMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, ids: MaybeArray, ): Promise { if (!Array.isArray(ids)) ids = [ids] - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) - const res = await this.call({ + const res = await client.call({ _: 'messages.deleteScheduledMessages', peer, id: ids, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/messages/edit-inline-message.ts b/packages/client/src/methods/messages/edit-inline-message.ts index 34c22240..238d9f27 100644 --- a/packages/client/src/methods/messages/edit-inline-message.ts +++ b/packages/client/src/methods/messages/edit-inline-message.ts @@ -1,8 +1,9 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { BotKeyboard, FormattedString, InputMediaLike, ReplyMarkup } from '../../types' import { normalizeInlineId } from '../../utils/inline-utils' +import { _normalizeInputMedia } from '../files/normalize-input-media' +import { _parseEntities } from './parse-entities' /** * Edit sent inline message text, media and reply markup. @@ -11,10 +12,9 @@ import { normalizeInlineId } from '../../utils/inline-utils' * Inline message ID, either as a TL object, or as a * TDLib and Bot API compatible string * @param params - * @internal */ export async function editInlineMessage( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Inline message ID, either as a TL object, or as a @@ -79,26 +79,27 @@ export async function editInlineMessage( const id = normalizeInlineId(params.messageId) if (params.media) { - media = await this._normalizeInputMedia(params.media, params, true) + media = await _normalizeInputMedia(client, params.media, params, true) // if there's no caption in input media (i.e. not present or undefined), // user wants to keep current caption, thus `content` needs to stay `undefined` if ('caption' in params.media && params.media.caption !== undefined) { - [content, entities] = await this._parseEntities( + [content, entities] = await _parseEntities( + client, params.media.caption, params.parseMode, params.media.entities, ) } } else if (params.text) { - [content, entities] = await this._parseEntities(params.text, params.parseMode, params.entities) + [content, entities] = await _parseEntities(client, params.text, params.parseMode, params.entities) } let retries = 3 while (retries--) { try { - await this.call( + await client.call( { _: 'messages.editInlineBotMessage', id, diff --git a/packages/client/src/methods/messages/edit-message.ts b/packages/client/src/methods/messages/edit-message.ts index 84810279..7f786dc0 100644 --- a/packages/client/src/methods/messages/edit-message.ts +++ b/packages/client/src/methods/messages/edit-message.ts @@ -1,7 +1,10 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { BotKeyboard, FormattedString, InputMediaLike, InputPeerLike, Message, ReplyMarkup } from '../../types' +import { _normalizeInputMedia } from '../files/normalize-input-media' +import { resolvePeer } from '../users/resolve-peer' +import { _findMessageInUpdate } from './find-in-update' +import { _parseEntities } from './parse-entities' /** * Edit message text, media, reply markup and schedule date. @@ -9,10 +12,9 @@ import { BotKeyboard, FormattedString, InputMediaLike, InputPeerLike, Message, R * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` * @param message Message or its ID * @param params - * @internal */ export async function editMessage( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID */ chatId: InputPeerLike @@ -81,12 +83,13 @@ export async function editMessage( let media: tl.TypeInputMedia | undefined = undefined if (params.media) { - media = await this._normalizeInputMedia(params.media, params) + media = await _normalizeInputMedia(client, params.media, params) // if there's no caption in input media (i.e. not present or undefined), // user wants to keep current caption, thus `content` needs to stay `undefined` if ('caption' in params.media && params.media.caption !== undefined) { - [content, entities] = await this._parseEntities( + [content, entities] = await _parseEntities( + client, params.media.caption, params.parseMode, params.media.entities, @@ -95,13 +98,13 @@ export async function editMessage( } if (params.text) { - [content, entities] = await this._parseEntities(params.text, params.parseMode, params.entities) + [content, entities] = await _parseEntities(client, params.text, params.parseMode, params.entities) } - const res = await this.call({ + const res = await client.call({ _: 'messages.editMessage', id: typeof message === 'number' ? message : message.id, - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), noWebpage: params.disableWebPreview, replyMarkup: BotKeyboard._convertToTl(params.replyMarkup), message: content, @@ -109,6 +112,5 @@ export async function editMessage( media, }) - // eslint-disable-next-line - return this._findMessageInUpdate(res, true) as any + return _findMessageInUpdate(client, res, true) } diff --git a/packages/client/src/methods/messages/find-in-update.ts b/packages/client/src/methods/messages/find-in-update.ts index f379425d..026b6675 100644 --- a/packages/client/src/methods/messages/find-in-update.ts +++ b/packages/client/src/methods/messages/find-in-update.ts @@ -1,14 +1,14 @@ -import { MtTypeAssertionError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtTypeAssertionError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' -import { Message, PeersIndex } from '../../types' +import { Message } from '../../types/messages' +import { PeersIndex } from '../../types/peers' import { assertIsUpdatesGroup } from '../../utils/updates-utils' /** @internal */ -export function _findMessageInUpdate(this: TelegramClient, res: tl.TypeUpdates, isEdit = false): Message { +export function _findMessageInUpdate(client: BaseTelegramClient, res: tl.TypeUpdates, isEdit = false): Message { assertIsUpdatesGroup('_findMessageInUpdate', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) for (const u of res.updates) { if ( @@ -20,7 +20,7 @@ export function _findMessageInUpdate(this: TelegramClient, res: tl.TypeUpdates, ) { const peers = PeersIndex.from(res) - return new Message(this, u.message, peers, u._ === 'updateNewScheduledMessage') + return new Message(u.message, peers, u._ === 'updateNewScheduledMessage') } } diff --git a/packages/client/src/methods/messages/forward-messages.ts b/packages/client/src/methods/messages/forward-messages.ts index d3598f73..a3b7c79e 100644 --- a/packages/client/src/methods/messages/forward-messages.ts +++ b/packages/client/src/methods/messages/forward-messages.ts @@ -1,10 +1,10 @@ -import { MaybeArray, MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray, MtArgumentError, tl } from '@mtcute/core' import { randomLong } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { FormattedString, InputMediaLike, InputPeerLike, Message, PeersIndex } from '../../types' import { normalizeDate } from '../../utils/misc-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Forward a single message. @@ -14,10 +14,9 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * @param message Message ID * @param params Additional sending parameters * @returns Newly sent, forwarded messages in the destination chat - * @internal */ export async function forwardMessages( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Source chat ID, username, phone, `"me"` or `"self"` */ fromChatId: InputPeerLike @@ -115,10 +114,9 @@ export async function forwardMessages( * @returns * Newly sent, forwarded messages in the destination chat. * If a caption message was provided, it will be the first message in the array. - * @internal */ export async function forwardMessages( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Source chat ID, username, phone, `"me"` or `"self"` */ fromChatId: InputPeerLike @@ -205,104 +203,23 @@ export async function forwardMessages( /** @internal */ export async function forwardMessages( - this: TelegramClient, + client: BaseTelegramClient, params: { toChatId: InputPeerLike fromChatId: InputPeerLike messages: MaybeArray - - /** - * Optionally, a caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - caption?: string | FormattedString - - /** - * Optionally, a media caption for your forwarded message(s). - * It will be sent as a separate message before the forwarded messages. - * - * You can either pass `caption` or `captionMedia`, passing both will - * result in an error - */ - captionMedia?: InputMediaLike - - /** - * Parse mode to use to parse entities in caption. - * Defaults to current default parse mode (if any). - * - * Passing `null` will explicitly disable formatting. - */ parseMode?: string | null - - /** - * List of formatting entities in caption to use instead - * of parsing via a parse mode. - * - * **Note:** Passing this makes the method ignore {@link parseMode} - */ entities?: tl.TypeMessageEntity[] - - /** - * Whether to send this message silently. - */ silent?: boolean - - /** - * If set, the message will be scheduled to this date. - * When passing a number, a UNIX time in ms is expected. - * - * You can also pass `0x7FFFFFFE`, this will send the message - * once the peer is online - */ schedule?: Date | number - - /** - * Whether to clear draft after sending this message (only used for caption) - * - * Defaults to `false` - */ clearDraft?: boolean - - /** - * Whether to forward without author - */ noAuthor?: boolean - - /** - * Whether to forward without caption (implies {@link noAuthor}) - */ noCaption?: boolean - - /** - * Whether to disallow further forwards of this message. - * - * Only for bots, works even if the target chat does not - * have content protection. - */ forbidForwards?: boolean - - /** - * Peer to use when sending the message. - */ sendAs?: InputPeerLike }, ): Promise> { - const { - toChatId, - fromChatId, - parseMode, - entities, - silent, - schedule, - clearDraft, - forbidForwards, - sendAs, - noAuthor, - noCaption, - } = params + const { toChatId, fromChatId, silent, schedule, forbidForwards, sendAs, noAuthor, noCaption } = params let { messages } = params let isSingle = false @@ -319,39 +236,12 @@ export async function forwardMessages( throw new MtArgumentError('You can forward no more than 100 messages at once') } - const toPeer = await this.resolvePeer(toChatId) + const toPeer = await resolvePeer(client, toChatId) - let captionMessage: Message | null = null - - if (params.caption) { - if (params.captionMedia) { - throw new MtArgumentError('You can either pass `caption` or `captionMedia`') - } - - captionMessage = await this.sendText(toPeer, params.caption, { - parseMode, - entities, - silent, - schedule, - clearDraft, - forbidForwards, - sendAs, - }) - } else if (params.captionMedia) { - captionMessage = await this.sendMedia(toPeer, params.captionMedia, { - parseMode, - silent, - schedule, - clearDraft, - forbidForwards, - sendAs, - }) - } - - const res = await this.call({ + const res = await client.call({ _: 'messages.forwardMessages', toPeer, - fromPeer: await this.resolvePeer(fromChatId), + fromPeer: await resolvePeer(client, fromChatId), id: messages, silent, scheduleDate: normalizeDate(schedule), @@ -359,12 +249,12 @@ export async function forwardMessages( dropAuthor: noAuthor, dropMediaCaptions: noCaption, noforwards: forbidForwards, - sendAs: sendAs ? await this.resolvePeer(sendAs) : undefined, + sendAs: sendAs ? await resolvePeer(client, sendAs) : undefined, }) assertIsUpdatesGroup('messages.forwardMessages', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) const peers = PeersIndex.from(res) @@ -374,15 +264,12 @@ export async function forwardMessages( case 'updateNewMessage': case 'updateNewChannelMessage': case 'updateNewScheduledMessage': - forwarded.push(new Message(this, upd.message, peers, upd._ === 'updateNewScheduledMessage')) + forwarded.push(new Message(upd.message, peers, upd._ === 'updateNewScheduledMessage')) break } }) - this._pushConversationMessage(forwarded[forwarded.length - 1]) - if (isSingle) return forwarded[0] - if (captionMessage) forwarded.unshift(captionMessage) return forwarded } diff --git a/packages/client/src/methods/messages/get-discussion-message.ts b/packages/client/src/methods/messages/get-discussion-message.ts index 1da503b8..6094a0bd 100644 --- a/packages/client/src/methods/messages/get-discussion-message.ts +++ b/packages/client/src/methods/messages/get-discussion-message.ts @@ -1,17 +1,18 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' -import { InputPeerLike, Message, PeersIndex } from '../../types' +import { Message } from '../../types/messages' +import { InputPeerLike, PeersIndex } from '../../types/peers' +import { resolvePeer } from '../users/resolve-peer' /** @internal */ export async function _getDiscussionMessage( - this: TelegramClient, + client: BaseTelegramClient, peer: InputPeerLike, message: number, ): Promise<[tl.TypeInputPeer, number]> { - const inputPeer = await this.resolvePeer(peer) + const inputPeer = await resolvePeer(client, peer) - const res = await this.call({ + const res = await client.call({ _: 'messages.getDiscussionMessage', peer: inputPeer, msgId: message, @@ -50,16 +51,15 @@ export async function _getDiscussionMessage( * * @param peer Channel where the post was found * @param message ID of the channel post - * @internal */ export async function getDiscussionMessage( - this: TelegramClient, + client: BaseTelegramClient, peer: InputPeerLike, message: number, ): Promise { - const inputPeer = await this.resolvePeer(peer) + const inputPeer = await resolvePeer(client, peer) - const res = await this.call({ + const res = await client.call({ _: 'messages.getDiscussionMessage', peer: inputPeer, msgId: message, @@ -73,5 +73,5 @@ export async function getDiscussionMessage( const msg = res.messages[0] const peers = PeersIndex.from(res) - return new Message(this, msg, peers) + return new Message(msg, peers) } diff --git a/packages/client/src/methods/messages/get-history.ts b/packages/client/src/methods/messages/get-history.ts index 3444bbb7..2e6a6aef 100644 --- a/packages/client/src/methods/messages/get-history.ts +++ b/packages/client/src/methods/messages/get-history.ts @@ -1,9 +1,9 @@ -import { Long, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { ArrayPaginated, InputPeerLike, Message, PeersIndex } from '../../types' import { makeArrayPaginated } from '../../utils' +import { resolvePeer } from '../users/resolve-peer' // @exported export interface GetHistoryOffset { @@ -21,10 +21,9 @@ const defaultOffset: GetHistoryOffset = { * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param params Additional fetch parameters - * @internal */ export async function getHistory( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -95,9 +94,9 @@ export async function getHistory( const addOffsetAdjusted = addOffset + (reverse ? -limit : 0) - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) - const res = await this.call({ + const res = await client.call({ _: 'messages.getHistory', peer, offsetId, @@ -112,7 +111,7 @@ export async function getHistory( assertTypeIsNot('getHistory', res, 'messages.messagesNotModified') const peers = PeersIndex.from(res) - const msgs = res.messages.filter((msg) => msg._ !== 'messageEmpty').map((msg) => new Message(this, msg, peers)) + const msgs = res.messages.filter((msg) => msg._ !== 'messageEmpty').map((msg) => new Message(msg, peers)) if (reverse) msgs.reverse() diff --git a/packages/client/src/methods/messages/get-message-group.ts b/packages/client/src/methods/messages/get-message-group.ts index 2b066150..282745c8 100644 --- a/packages/client/src/methods/messages/get-message-group.ts +++ b/packages/client/src/methods/messages/get-message-group.ts @@ -1,19 +1,19 @@ -import { MtArgumentError } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' import { isPresent } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike, Message } from '../../types' import { isInputPeerChannel } from '../../utils/peer-utils' +import { resolvePeer } from '../users/resolve-peer' +import { getMessages } from './get-messages' /** * Get all messages inside of a message group * * @param chatId Chat ID * @param message ID of one of the messages in the group - * @internal */ export async function getMessageGroup( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, message: number, ): Promise { @@ -23,7 +23,7 @@ export async function getMessageGroup( // we use larger number. // still, this might not be enough :shrug: - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) const delta = isInputPeerChannel(peer) ? 9 : 19 @@ -33,7 +33,7 @@ export async function getMessageGroup( ids.push(i) } - const messages = await this.getMessages(chatId, ids) + const messages = await getMessages(client, chatId, ids) const groupedId = messages.find((it) => it?.id === message)!.groupedId if (!groupedId) throw new MtArgumentError('This message is not grouped') diff --git a/packages/client/src/methods/messages/get-message-reactions.ts b/packages/client/src/methods/messages/get-message-reactions.ts index 23171113..5a327a8f 100644 --- a/packages/client/src/methods/messages/get-message-reactions.ts +++ b/packages/client/src/methods/messages/get-message-reactions.ts @@ -1,9 +1,9 @@ -import { getMarkedPeerId, MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId, MaybeArray } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike, MessageReactions, PeersIndex } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get reactions to a message. @@ -15,10 +15,9 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * @param chatId ID of the chat with the message * @param messages Message ID * @returns Reactions to the corresponding message, or `null` if there are none - * @internal */ export async function getMessageReactions( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messages: number, ): Promise @@ -33,10 +32,9 @@ export async function getMessageReactions( * @param chatId ID of the chat with messages * @param messages Message IDs * @returns Reactions to corresponding messages, or `null` if there are none - * @internal */ export async function getMessageReactions( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messages: number[], ): Promise<(MessageReactions | null)[]> @@ -45,7 +43,7 @@ export async function getMessageReactions( * @internal */ export async function getMessageReactions( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messages: MaybeArray, ): Promise> { @@ -55,9 +53,9 @@ export async function getMessageReactions( messages = [messages] } - const res = await this.call({ + const res = await client.call({ _: 'messages.getMessagesReactions', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), id: messages, }) @@ -76,13 +74,7 @@ export async function getMessageReactions( for (const update of res.updates) { assertTypeIs('messages.getMessagesReactions', update, 'updateMessageReactions') - index[update.msgId] = new MessageReactions( - this, - update.msgId, - getMarkedPeerId(update.peer), - update.reactions, - peers, - ) + index[update.msgId] = new MessageReactions(update.msgId, getMarkedPeerId(update.peer), update.reactions, peers) } if (single) { diff --git a/packages/client/src/methods/messages/get-messages-unsafe.ts b/packages/client/src/methods/messages/get-messages-unsafe.ts index 45cab3db..017b67ad 100644 --- a/packages/client/src/methods/messages/get-messages-unsafe.ts +++ b/packages/client/src/methods/messages/get-messages-unsafe.ts @@ -1,7 +1,6 @@ -import { MaybeArray, tl } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray, tl } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { Message, PeersIndex } from '../../types' /** @@ -15,10 +14,9 @@ import { Message, PeersIndex } from '../../types' * @param [fromReply=false] * Whether the reply to a given message should be fetched * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) - * @internal */ export async function getMessagesUnsafe( - this: TelegramClient, + client: BaseTelegramClient, messageId: number, fromReply?: boolean, ): Promise @@ -36,17 +34,16 @@ export async function getMessagesUnsafe( * @param [fromReply=false] * Whether the reply to a given message should be fetched * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) - * @internal */ export async function getMessagesUnsafe( - this: TelegramClient, + client: BaseTelegramClient, messageIds: number[], fromReply?: boolean, ): Promise<(Message | null)[]> /** @internal */ export async function getMessagesUnsafe( - this: TelegramClient, + client: BaseTelegramClient, messageIds: MaybeArray, fromReply = false, ): Promise> { @@ -59,7 +56,7 @@ export async function getMessagesUnsafe( id: it, })) - const res = await this.call({ + const res = await client.call({ _: 'messages.getMessages', id: ids, }) @@ -71,7 +68,7 @@ export async function getMessagesUnsafe( const ret = res.messages.map((msg) => { if (msg._ === 'messageEmpty') return null - return new Message(this, msg, peers) + return new Message(msg, peers) }) return isSingle ? ret[0] : ret diff --git a/packages/client/src/methods/messages/get-messages.ts b/packages/client/src/methods/messages/get-messages.ts index debb4cf6..6c5126c7 100644 --- a/packages/client/src/methods/messages/get-messages.ts +++ b/packages/client/src/methods/messages/get-messages.ts @@ -1,9 +1,11 @@ -import { MaybeArray, tl } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray, tl } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' -import { InputPeerLike, Message, PeersIndex } from '../../types' +import { Message } from '../../types/messages' +import { InputPeerLike, PeersIndex } from '../../types/peers' import { isInputPeerChannel, normalizeToInputChannel } from '../../utils/peer-utils' +import { getAuthState } from '../auth/_state' +import { resolvePeer } from '../users/resolve-peer' /** * Get a single message in chat by its ID @@ -13,10 +15,9 @@ import { isInputPeerChannel, normalizeToInputChannel } from '../../utils/peer-ut * @param [fromReply=false] * Whether the reply to a given message should be fetched * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) - * @internal */ export async function getMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageId: number, fromReply?: boolean, @@ -32,10 +33,9 @@ export async function getMessages( * @param [fromReply=false] * Whether the reply to a given message should be fetched * (i.e. `getMessages(msg.chat.id, msg.id, true).id === msg.replyToMessageId`) - * @internal */ export async function getMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageIds: number[], fromReply?: boolean, @@ -44,12 +44,12 @@ export async function getMessages( // @available=both /** @internal */ export async function getMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageIds: MaybeArray, fromReply = false, ): Promise> { - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) const isSingle = !Array.isArray(messageIds) if (isSingle) messageIds = [messageIds as number] @@ -62,7 +62,7 @@ export async function getMessages( const isChannel = isInputPeerChannel(peer) - const res = await this.call( + const res = await client.call( isChannel ? { _: 'channels.getMessages', @@ -79,6 +79,7 @@ export async function getMessages( const peers = PeersIndex.from(res) + let selfId: number | null | undefined = undefined const ret = res.messages.map((msg) => { if (msg._ === 'messageEmpty') return null @@ -87,7 +88,9 @@ export async function getMessages( // (channels have their own message numbering) switch (peer._) { case 'inputPeerSelf': - if (!(msg.peerId._ === 'peerUser' && msg.peerId.userId === this._userId)) { + if (selfId === undefined) selfId = getAuthState(client).userId + + if (!(msg.peerId._ === 'peerUser' && msg.peerId.userId === selfId)) { return null } break @@ -105,7 +108,7 @@ export async function getMessages( } } - return new Message(this, msg, peers) + return new Message(msg, peers) }) return isSingle ? ret[0] : ret diff --git a/packages/client/src/methods/messages/get-reaction-users.ts b/packages/client/src/methods/messages/get-reaction-users.ts index d59d3d07..ff60e730 100644 --- a/packages/client/src/methods/messages/get-reaction-users.ts +++ b/packages/client/src/methods/messages/get-reaction-users.ts @@ -1,4 +1,5 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ArrayPaginated, InputPeerLike, @@ -8,6 +9,7 @@ import { PeersIndex, } from '../../types' import { makeArrayPaginated } from '../../utils' +import { resolvePeer } from '../users/resolve-peer' // @exported export type GetReactionUsersOffset = string @@ -18,10 +20,9 @@ export type GetReactionUsersOffset = string * @param chatId Chat ID * @param messageId Message ID * @param params - * @internal */ export async function getReactionUsers( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageId: number, params?: { @@ -47,11 +48,11 @@ export async function getReactionUsers( const { limit = 100, offset, emoji } = params - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) const reaction = normalizeInputReaction(emoji) - const res = await this.call({ + const res = await client.call({ _: 'messages.getMessageReactionsList', peer, id: messageId, @@ -63,7 +64,7 @@ export async function getReactionUsers( const peers = PeersIndex.from(res) return makeArrayPaginated( - res.reactions.map((it) => new PeerReaction(this, it, peers)), + res.reactions.map((it) => new PeerReaction(it, peers)), res.count, res.nextOffset, ) diff --git a/packages/client/src/methods/messages/get-scheduled-messages.ts b/packages/client/src/methods/messages/get-scheduled-messages.ts index 43dd296d..d84369bf 100644 --- a/packages/client/src/methods/messages/get-scheduled-messages.ts +++ b/packages/client/src/methods/messages/get-scheduled-messages.ts @@ -1,18 +1,17 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike, Message, PeersIndex } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Get a single scheduled message in chat by its ID * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` * @param messageId Scheduled message ID - * @internal */ export async function getScheduledMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageId: number, ): Promise @@ -24,26 +23,25 @@ export async function getScheduledMessages( * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"` * @param messageIds Scheduled messages IDs - * @internal */ export async function getScheduledMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageIds: number[], ): Promise<(Message | null)[]> /** @internal */ export async function getScheduledMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageIds: MaybeArray, ): Promise> { - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) const isSingle = !Array.isArray(messageIds) if (isSingle) messageIds = [messageIds as number] - const res = await this.call({ + const res = await client.call({ _: 'messages.getScheduledMessages', peer, id: messageIds as number[], @@ -56,7 +54,7 @@ export async function getScheduledMessages( const ret = res.messages.map((msg) => { if (msg._ === 'messageEmpty') return null - return new Message(this, msg, peers, true) + return new Message(msg, peers, true) }) return isSingle ? ret[0] : ret diff --git a/packages/client/src/methods/messages/iter-history.ts b/packages/client/src/methods/messages/iter-history.ts index b5b62f14..77e1c09e 100644 --- a/packages/client/src/methods/messages/iter-history.ts +++ b/packages/client/src/methods/messages/iter-history.ts @@ -1,17 +1,19 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, Message } from '../../types' +import { resolvePeer } from '../users/resolve-peer' +import { getHistory } from './get-history' /** * Iterate over chat history. Wrapper over {@link getHistory} * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param params Additional fetch parameters - * @internal */ export async function* iterHistory( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Limits the number of messages to be retrieved. * @@ -35,10 +37,10 @@ export async function* iterHistory( let current = 0 // resolve peer once and pass an InputPeer afterwards - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) for (;;) { - const res = await this.getHistory(peer, { + const res = await getHistory(client, peer, { offset, addOffset, limit: Math.min(chunkSize, limit - current), diff --git a/packages/client/src/methods/messages/iter-reaction-users.ts b/packages/client/src/methods/messages/iter-reaction-users.ts index 9fba21e0..f670633b 100644 --- a/packages/client/src/methods/messages/iter-reaction-users.ts +++ b/packages/client/src/methods/messages/iter-reaction-users.ts @@ -1,5 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, normalizeInputReaction, PeerReaction } from '../../types' +import { resolvePeer } from '../users/resolve-peer' +import { getReactionUsers } from './get-reaction-users' /** * Iterate over users who have reacted to the message. @@ -9,13 +12,12 @@ import { InputPeerLike, normalizeInputReaction, PeerReaction } from '../../types * @param chatId Chat ID * @param messageId Message ID * @param params - * @internal */ export async function* iterReactionUsers( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageId: number, - params?: Parameters[2] & { + params?: Parameters[3] & { /** * Limit the number of events returned. * @@ -33,7 +35,7 @@ export async function* iterReactionUsers( ): AsyncIterableIterator { if (!params) params = {} - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) const { limit = Infinity, chunkSize = 100 } = params @@ -43,7 +45,7 @@ export async function* iterReactionUsers( const reaction = normalizeInputReaction(params.emoji) for (;;) { - const res = await this.getReactionUsers(peer, messageId, { + const res = await getReactionUsers(client, peer, messageId, { emoji: reaction, limit: Math.min(chunkSize, limit - current), offset, diff --git a/packages/client/src/methods/messages/iter-search-global.ts b/packages/client/src/methods/messages/iter-search-global.ts index a2ed8681..58ea2794 100644 --- a/packages/client/src/methods/messages/iter-search-global.ts +++ b/packages/client/src/methods/messages/iter-search-global.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { Message, SearchFilters } from '../../types' import { normalizeDate } from '../../utils' +import { searchGlobal } from './search-global' /** * Search for messages globally from all of your chats. @@ -10,11 +12,10 @@ import { normalizeDate } from '../../utils' * **Note**: Due to Telegram limitations, you can only get up to ~10000 messages * * @param params Search parameters - * @internal */ export async function* iterSearchGlobal( - this: TelegramClient, - params?: Parameters[0] & { + client: BaseTelegramClient, + params?: Parameters[1] & { /** * Limits the number of messages to be retrieved. * @@ -42,7 +43,7 @@ export async function* iterSearchGlobal( let current = 0 for (;;) { - const res = await this.searchGlobal({ + const res = await searchGlobal(client, { query, filter, limit: Math.min(chunkSize, limit - current), diff --git a/packages/client/src/methods/messages/iter-search-messages.ts b/packages/client/src/methods/messages/iter-search-messages.ts index 9460330f..9fc17346 100644 --- a/packages/client/src/methods/messages/iter-search-messages.ts +++ b/packages/client/src/methods/messages/iter-search-messages.ts @@ -1,6 +1,9 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { Message, SearchFilters } from '../../types' import { normalizeDate } from '../../utils/misc-utils' +import { resolvePeer } from '../users/resolve-peer' +import { searchMessages } from './search-messages' /** * Search for messages inside a specific chat @@ -9,11 +12,10 @@ import { normalizeDate } from '../../utils/misc-utils' * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param params Additional search parameters - * @internal */ export async function* iterSearchMessages( - this: TelegramClient, - params?: Parameters[0] & { + client: BaseTelegramClient, + params?: Parameters[1] & { /** * Limits the number of messages to be retrieved. * @@ -45,14 +47,14 @@ export async function* iterSearchMessages( const minDate = normalizeDate(params.minDate) ?? 0 const maxDate = normalizeDate(params.maxDate) ?? 0 - const peer = await this.resolvePeer(chatId) - const fromUser = params.fromUser ? await this.resolvePeer(params.fromUser) : undefined + const peer = await resolvePeer(client, chatId) + const fromUser = params.fromUser ? await resolvePeer(client, params.fromUser) : undefined let { offset, addOffset } = params let current = 0 for (;;) { - const res = await this.searchMessages({ + const res = await searchMessages(client, { query, chatId: peer, offset, diff --git a/packages/client/src/methods/messages/parse-entities.ts b/packages/client/src/methods/messages/parse-entities.ts index b58c267d..f1f45371 100644 --- a/packages/client/src/methods/messages/parse-entities.ts +++ b/packages/client/src/methods/messages/parse-entities.ts @@ -1,15 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { FormattedString } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { getParseModesState } from '../parse-modes/_state' +import { resolvePeer } from '../users/resolve-peer' const empty: [string, undefined] = ['', undefined] /** @internal */ export async function _parseEntities( - this: TelegramClient, + client: BaseTelegramClient, text?: string | FormattedString, mode?: string | null, entities?: tl.TypeMessageEntity[], @@ -24,13 +25,15 @@ export async function _parseEntities( } if (!entities) { + const parseModesState = getParseModesState(client) + if (mode === undefined) { - mode = this._defaultParseMode + mode = parseModesState.defaultParseMode } // either explicitly disabled or no available parser if (!mode) return [text, []] - const modeImpl = this._parseModes.get(mode) + const modeImpl = parseModesState.parseModes.get(mode) if (!modeImpl) { throw new MtArgumentError(`Parse mode ${mode} is not registered.`) @@ -43,7 +46,7 @@ export async function _parseEntities( for (const ent of entities) { if (ent._ === 'messageEntityMentionName') { try { - const inputPeer = normalizeToInputUser(await this.resolvePeer(ent.userId), ent.userId) + const inputPeer = normalizeToInputUser(await resolvePeer(client, ent.userId), ent.userId) // not a user if (!inputPeer) continue diff --git a/packages/client/src/methods/messages/pin-message.ts b/packages/client/src/methods/messages/pin-message.ts index 3a5f1c95..3f66bdbd 100644 --- a/packages/client/src/methods/messages/pin-message.ts +++ b/packages/client/src/methods/messages/pin-message.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Pin a message in a group, supergroup, channel or PM. @@ -9,10 +11,9 @@ import { InputPeerLike } from '../../types' * * @param chatId Chat ID, username, phone number, `"self"` or `"me"` * @param messageId Message ID - * @internal */ export async function pinMessage( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, messageId: number, params?: { @@ -24,13 +25,13 @@ export async function pinMessage( ): Promise { const { notify, bothSides } = params ?? {} - const res = await this.call({ + const res = await client.call({ _: 'messages.updatePinnedMessage', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), id: messageId, silent: !notify, pmOneside: !bothSides, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/messages/read-history.ts b/packages/client/src/methods/messages/read-history.ts index 358eb6c8..6141735e 100644 --- a/packages/client/src/methods/messages/read-history.ts +++ b/packages/client/src/methods/messages/read-history.ts @@ -1,16 +1,17 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { isInputPeerChannel, normalizeToInputChannel } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Mark chat history as read. * * @param chatId Chat ID - * @internal */ export async function readHistory( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -28,33 +29,33 @@ export async function readHistory( ): Promise { const { maxId = 0, clearMentions } = params ?? {} - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) if (clearMentions) { - const res = await this.call({ + const res = await client.call({ _: 'messages.readMentions', peer, }) if (isInputPeerChannel(peer)) { - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount, peer.channelId)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount, peer.channelId)) } else { - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) } } if (isInputPeerChannel(peer)) { - await this.call({ + await client.call({ _: 'channels.readHistory', channel: normalizeToInputChannel(peer), maxId, }) } else { - const res = await this.call({ + const res = await client.call({ _: 'messages.readHistory', peer, maxId, }) - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) } } diff --git a/packages/client/src/methods/messages/read-reactions.ts b/packages/client/src/methods/messages/read-reactions.ts index dd86347c..6d5d81a6 100644 --- a/packages/client/src/methods/messages/read-reactions.ts +++ b/packages/client/src/methods/messages/read-reactions.ts @@ -1,17 +1,18 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { createDummyUpdate } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Mark all reactions in chat as read. * * @param chatId Chat ID - * @internal */ -export async function readReactions(this: TelegramClient, chatId: InputPeerLike): Promise { - const res = await this.call({ +export async function readReactions(client: BaseTelegramClient, chatId: InputPeerLike): Promise { + const res = await client.call({ _: 'messages.readReactions', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), }) - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) } diff --git a/packages/client/src/methods/messages/search-global.ts b/packages/client/src/methods/messages/search-global.ts index 84a6e760..8dc2cf77 100644 --- a/packages/client/src/methods/messages/search-global.ts +++ b/packages/client/src/methods/messages/search-global.ts @@ -1,7 +1,6 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { ArrayPaginated, Message, PeersIndex, SearchFilters } from '../../types' import { makeArrayPaginated, normalizeDate } from '../../utils' @@ -24,10 +23,9 @@ const defaultOffset: SearchGlobalOffset = { * **Note**: Due to Telegram limitations, you can only get up to ~10000 messages * * @param params Search parameters - * @internal */ export async function searchGlobal( - this: TelegramClient, + client: BaseTelegramClient, params?: { /** * Text query string. Use `"@"` to search for mentions. @@ -79,7 +77,7 @@ export async function searchGlobal( const minDate = normalizeDate(params.minDate) ?? 0 const maxDate = normalizeDate(params.maxDate) ?? 0 - const res = await this.call({ + const res = await client.call({ _: 'messages.searchGlobal', q: query, filter, @@ -94,7 +92,7 @@ export async function searchGlobal( assertTypeIsNot('searchGlobal', res, 'messages.messagesNotModified') const peers = PeersIndex.from(res) - const msgs = res.messages.filter((msg) => msg._ !== 'messageEmpty').map((msg) => new Message(this, msg, peers)) + const msgs = res.messages.filter((msg) => msg._ !== 'messageEmpty').map((msg) => new Message(msg, peers)) const last = msgs[msgs.length - 1] diff --git a/packages/client/src/methods/messages/search-messages.ts b/packages/client/src/methods/messages/search-messages.ts index 789d5547..ebdbefdc 100644 --- a/packages/client/src/methods/messages/search-messages.ts +++ b/packages/client/src/methods/messages/search-messages.ts @@ -1,9 +1,9 @@ -import { Long, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { ArrayPaginated, InputPeerLike, Message, PeersIndex, SearchFilters } from '../../types' import { makeArrayPaginated, normalizeDate } from '../../utils/misc-utils' +import { resolvePeer } from '../users/resolve-peer' // @exported export type SearchMessagesOffset = number @@ -13,10 +13,9 @@ export type SearchMessagesOffset = number * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param params Additional search parameters - * @internal */ export async function searchMessages( - this: TelegramClient, + client: BaseTelegramClient, params?: { /** * Text query string. Required for text-only messages, @@ -129,10 +128,10 @@ export async function searchMessages( const minDate = normalizeDate(params.minDate) ?? 0 const maxDate = normalizeDate(params.maxDate) ?? 0 - const peer = await this.resolvePeer(chatId) - const fromUser = params.fromUser ? await this.resolvePeer(params.fromUser) : undefined + const peer = await resolvePeer(client, chatId) + const fromUser = params.fromUser ? await resolvePeer(client, params.fromUser) : undefined - const res = await this.call({ + const res = await client.call({ _: 'messages.search', peer, q: query, @@ -153,7 +152,7 @@ export async function searchMessages( const peers = PeersIndex.from(res) - const msgs = res.messages.filter((msg) => msg._ !== 'messageEmpty').map((msg) => new Message(this, msg, peers)) + const msgs = res.messages.filter((msg) => msg._ !== 'messageEmpty').map((msg) => new Message(msg, peers)) const last = msgs[msgs.length - 1] const next = last ? last.id : undefined diff --git a/packages/client/src/methods/messages/send-copy.ts b/packages/client/src/methods/messages/send-copy.ts index 3241bbc1..c8a7b705 100644 --- a/packages/client/src/methods/messages/send-copy.ts +++ b/packages/client/src/methods/messages/send-copy.ts @@ -1,7 +1,10 @@ -import { getMarkedPeerId, tl } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { FormattedString, InputPeerLike, Message, MtMessageNotFoundError, ReplyMarkup } from '../../types' +import { resolvePeer } from '../users/resolve-peer' +import { getMessages } from './get-messages' +import { sendMedia } from './send-media' +import { sendText } from './send-text' /** * Copy a message (i.e. send the same message, @@ -12,15 +15,10 @@ import { FormattedString, InputPeerLike, Message, MtMessageNotFoundError, ReplyM * and if the message contains an invoice, * it can't be copied. * - * > **Note**: if you already have {@link Message} object, - * > use {@link Message.sendCopy} instead, since that is - * > much more efficient, and that is what this method wraps. - * * @param params - * @internal */ export async function sendCopy( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Source chat ID */ fromChatId: InputPeerLike @@ -99,13 +97,32 @@ export async function sendCopy( ): Promise { const { fromChatId, toChatId, message, ...rest } = params - const fromPeer = await this.resolvePeer(fromChatId) + const fromPeer = await resolvePeer(client, fromChatId) - const msg = await this.getMessages(fromPeer, message) + const msg = await getMessages(client, fromPeer, message) if (!msg) { throw new MtMessageNotFoundError(getMarkedPeerId(fromPeer), message, 'to copy') } - return msg.sendCopy(toChatId, rest) + if (msg.raw._ === 'messageService') { + throw new MtArgumentError("Service messages can't be copied") + } + + if (msg.media && msg.media.type !== 'web_page' && msg.media.type !== 'invoice') { + return sendMedia( + client, + toChatId, + { + type: 'auto', + file: msg.media.inputMedia, + caption: params.caption ?? msg.raw.message, + // we shouldn't use original entities if the user wants custom text + entities: params.entities ?? params.caption ? undefined : msg.raw.entities, + }, + rest, + ) + } + + return sendText(client, toChatId, msg.raw.message, rest) } diff --git a/packages/client/src/methods/messages/send-media-group.ts b/packages/client/src/methods/messages/send-media-group.ts index 1cdddf6d..27060085 100644 --- a/packages/client/src/methods/messages/send-media-group.ts +++ b/packages/client/src/methods/messages/send-media-group.ts @@ -1,10 +1,17 @@ -import { getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core' import { randomLong } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' -import { InputMediaLike, InputPeerLike, Message, MtMessageNotFoundError, PeersIndex } from '../../types' +import { MtMessageNotFoundError } from '../../types/errors' +import { InputMediaLike } from '../../types/media/input-media' +import { Message } from '../../types/messages/message' +import { InputPeerLike, PeersIndex } from '../../types/peers' import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { _normalizeInputMedia } from '../files/normalize-input-media' +import { resolvePeer } from '../users/resolve-peer' +import { _getDiscussionMessage } from './get-discussion-message' +import { getMessages } from './get-messages' +import { _parseEntities } from './parse-entities' /** * Send a group of media. @@ -16,10 +23,9 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * @param medias Medias contained in the message. * @param params Additional sending parameters * @link InputMedia - * @internal */ export async function sendMediaGroup( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, medias: (InputMediaLike | string)[], params?: { @@ -106,12 +112,12 @@ export async function sendMediaGroup( ): Promise { if (!params) params = {} - let peer = await this.resolvePeer(chatId) + let peer = await resolvePeer(client, chatId) let replyTo = normalizeMessageId(params.replyTo) if (params.commentTo) { - [peer, replyTo] = await this._getDiscussionMessage(peer, normalizeMessageId(params.commentTo)!) + [peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!) } if (params.mustReply) { @@ -119,7 +125,7 @@ export async function sendMediaGroup( throw new MtArgumentError('mustReply used, but replyTo was not passed') } - const msg = await this.getMessages(peer, replyTo) + const msg = await getMessages(client, peer, replyTo) if (!msg) { throw new MtMessageNotFoundError(getMarkedPeerId(peer), replyTo, 'to reply to') @@ -138,7 +144,8 @@ export async function sendMediaGroup( } } - const inputMedia = await this._normalizeInputMedia( + const inputMedia = await _normalizeInputMedia( + client, media, { progressCallback: params.progressCallback?.bind(null, i), @@ -150,7 +157,8 @@ export async function sendMediaGroup( true, ) - const [message, entities] = await this._parseEntities( + const [message, entities] = await _parseEntities( + client, // some types dont have `caption` field, and ts warns us, // but since it's JS, they'll just be `undefined` and properly // handled by _parseEntities method @@ -168,7 +176,7 @@ export async function sendMediaGroup( }) } - const res = await this.call({ + const res = await client.call({ _: 'messages.sendMultiMedia', peer, multiMedia, @@ -182,11 +190,11 @@ export async function sendMediaGroup( scheduleDate: normalizeDate(params.schedule), clearDraft: params.clearDraft, noforwards: params.forbidForwards, - sendAs: params.sendAs ? await this.resolvePeer(params.sendAs) : undefined, + sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, }) assertIsUpdatesGroup('_findMessageInUpdate', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) const peers = PeersIndex.from(res) @@ -195,9 +203,7 @@ export async function sendMediaGroup( (u): u is tl.RawUpdateNewMessage | tl.RawUpdateNewChannelMessage | tl.RawUpdateNewScheduledMessage => u._ === 'updateNewMessage' || u._ === 'updateNewChannelMessage' || u._ === 'updateNewScheduledMessage', ) - .map((u) => new Message(this, u.message, peers, u._ === 'updateNewScheduledMessage')) - - this._pushConversationMessage(msgs[msgs.length - 1]) + .map((u) => new Message(u.message, peers, u._ === 'updateNewScheduledMessage')) return msgs } diff --git a/packages/client/src/methods/messages/send-media.ts b/packages/client/src/methods/messages/send-media.ts index 28a13d1a..a092da62 100644 --- a/packages/client/src/methods/messages/send-media.ts +++ b/packages/client/src/methods/messages/send-media.ts @@ -1,17 +1,19 @@ -import { getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core' import { randomLong } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' -import { - BotKeyboard, - FormattedString, - InputMediaLike, - InputPeerLike, - Message, - MtMessageNotFoundError, - ReplyMarkup, -} from '../../types' +import { BotKeyboard, ReplyMarkup } from '../../types/bots/keyboards' +import { MtMessageNotFoundError } from '../../types/errors' +import { InputMediaLike } from '../../types/media/input-media' +import { Message } from '../../types/messages/message' +import { FormattedString } from '../../types/parser' +import { InputPeerLike } from '../../types/peers' import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils' +import { _normalizeInputMedia } from '../files/normalize-input-media' +import { resolvePeer } from '../users/resolve-peer' +import { _findMessageInUpdate } from './find-in-update' +import { _getDiscussionMessage } from './get-discussion-message' +import { getMessages } from './get-messages' +import { _parseEntities } from './parse-entities' /** * Send a single media (a photo or a document-based media) @@ -23,10 +25,9 @@ import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils' * in {@link InputMedia.auto} * @param params Additional sending parameters * @link InputMedia - * @internal */ export async function sendMedia( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, media: InputMediaLike | string, params?: { @@ -141,9 +142,10 @@ export async function sendMedia( } } - const inputMedia = await this._normalizeInputMedia(media, params) + const inputMedia = await _normalizeInputMedia(client, media, params) - const [message, entities] = await this._parseEntities( + const [message, entities] = await _parseEntities( + client, // some types dont have `caption` field, and ts warns us, // but since it's JS, they'll just be `undefined` and properly // handled by _parseEntities method @@ -152,13 +154,13 @@ export async function sendMedia( params.entities || (media as Extract).entities, ) - let peer = await this.resolvePeer(chatId) + let peer = await resolvePeer(client, chatId) const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup) let replyTo = normalizeMessageId(params.replyTo) if (params.commentTo) { - [peer, replyTo] = await this._getDiscussionMessage(peer, normalizeMessageId(params.commentTo)!) + [peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!) } if (params.mustReply) { @@ -166,14 +168,14 @@ export async function sendMedia( throw new MtArgumentError('mustReply used, but replyTo was not passed') } - const msg = await this.getMessages(peer, replyTo) + const msg = await getMessages(client, peer, replyTo) if (!msg) { throw new MtMessageNotFoundError(getMarkedPeerId(peer), replyTo, 'to reply to') } } - const res = await this.call({ + const res = await client.call({ _: 'messages.sendMedia', peer, media: inputMedia, @@ -191,12 +193,10 @@ export async function sendMedia( entities, clearDraft: params.clearDraft, noforwards: params.forbidForwards, - sendAs: params.sendAs ? await this.resolvePeer(params.sendAs) : undefined, + sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, }) - const msg = this._findMessageInUpdate(res) - - this._pushConversationMessage(msg) + const msg = _findMessageInUpdate(client, res) return msg } diff --git a/packages/client/src/methods/messages/send-reaction.ts b/packages/client/src/methods/messages/send-reaction.ts index 5f484b05..9ebc2181 100644 --- a/packages/client/src/methods/messages/send-reaction.ts +++ b/packages/client/src/methods/messages/send-reaction.ts @@ -1,15 +1,17 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, InputReaction, Message, normalizeInputReaction } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' +import { _findMessageInUpdate } from './find-in-update' /** * Send or remove a reaction. * * @returns Message to which the reaction was sent - * @internal */ export async function sendReaction( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID with the message to react to */ chatId: InputPeerLike @@ -25,9 +27,9 @@ export async function sendReaction( const reaction = normalizeInputReaction(emoji) - const res = await this.call({ + const res = await client.call({ _: 'messages.sendReaction', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), msgId: message, reaction: [reaction], big, @@ -36,10 +38,10 @@ export async function sendReaction( assertIsUpdatesGroup('messages.sendReaction', res) // normally the group contains 2 updates: - // updateEditChannelMessage + // updateEdit(Channel)Message // updateMessageReactions // idk why, they contain literally the same data // so we can just return the message from the first one - return this._findMessageInUpdate(res, true) + return _findMessageInUpdate(client, res, true) } diff --git a/packages/client/src/methods/messages/send-scheduled.ts b/packages/client/src/methods/messages/send-scheduled.ts index fbc0c0d9..1e924dd8 100644 --- a/packages/client/src/methods/messages/send-scheduled.ts +++ b/packages/client/src/methods/messages/send-scheduled.ts @@ -1,8 +1,8 @@ -import { MaybeArray, tl } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, Message, PeersIndex } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Send s previously scheduled message. @@ -13,9 +13,8 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils' * * @param peer Chat where the messages were scheduled * @param id ID of the message - * @internal */ -export async function sendScheduled(this: TelegramClient, peer: InputPeerLike, id: number): Promise +export async function sendScheduled(client: BaseTelegramClient, peer: InputPeerLike, id: number): Promise /** * Send previously scheduled message(s) @@ -26,27 +25,26 @@ export async function sendScheduled(this: TelegramClient, peer: InputPeerLike, i * * @param peer Chat where the messages were scheduled * @param ids ID(s) of the messages - * @internal */ -export async function sendScheduled(this: TelegramClient, peer: InputPeerLike, ids: number[]): Promise +export async function sendScheduled(client: BaseTelegramClient, peer: InputPeerLike, ids: number[]): Promise /** @internal */ export async function sendScheduled( - this: TelegramClient, + client: BaseTelegramClient, peer: InputPeerLike, ids: MaybeArray, ): Promise> { const isSingle = !Array.isArray(ids) if (isSingle) ids = [ids as number] - const res = await this.call({ + const res = await client.call({ _: 'messages.sendScheduledMessages', - peer: await this.resolvePeer(peer), + peer: await resolvePeer(client, peer), id: ids as number[], }) assertIsUpdatesGroup('sendScheduled', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) const peers = PeersIndex.from(res) @@ -55,9 +53,7 @@ export async function sendScheduled( (u): u is Extract => u._ === 'updateNewMessage' || u._ === 'updateNewChannelMessage', ) - .map((u) => new Message(this, u.message, peers)) - - this._pushConversationMessage(msgs[msgs.length - 1]) + .map((u) => new Message(u.message, peers)) return isSingle ? msgs[0] : msgs } diff --git a/packages/client/src/methods/messages/send-text.ts b/packages/client/src/methods/messages/send-text.ts index 895b166f..9985e987 100644 --- a/packages/client/src/methods/messages/send-text.ts +++ b/packages/client/src/methods/messages/send-text.ts @@ -1,19 +1,20 @@ -import { getMarkedPeerId, MtArgumentError, MtTypeAssertionError, tl } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId, MtArgumentError, MtTypeAssertionError, tl } from '@mtcute/core' import { randomLong } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' -import { - BotKeyboard, - FormattedString, - InputPeerLike, - Message, - MtMessageNotFoundError, - PeersIndex, - ReplyMarkup, -} from '../../types' +import { BotKeyboard, ReplyMarkup } from '../../types/bots/keyboards' +import { MtMessageNotFoundError } from '../../types/errors' +import { Message } from '../../types/messages/message' +import { FormattedString } from '../../types/parser' +import { InputPeerLike, PeersIndex } from '../../types/peers' import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils' import { inputPeerToPeer } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' +import { getAuthState } from '../auth/_state' +import { resolvePeer } from '../users/resolve-peer' +import { _findMessageInUpdate } from './find-in-update' +import { _getDiscussionMessage } from './get-discussion-message' +import { getMessages } from './get-messages' +import { _parseEntities } from './parse-entities' /** * Send a text message @@ -21,10 +22,9 @@ import { createDummyUpdate } from '../../utils/updates-utils' * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` * @param text Text of the message * @param params Additional sending parameters - * @internal */ export async function sendText( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, text: string | FormattedString, params?: { @@ -119,15 +119,15 @@ export async function sendText( ): Promise { if (!params) params = {} - const [message, entities] = await this._parseEntities(text, params.parseMode, params.entities) + const [message, entities] = await _parseEntities(client, text, params.parseMode, params.entities) - let peer = await this.resolvePeer(chatId) + let peer = await resolvePeer(client, chatId) const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup) let replyTo = normalizeMessageId(params.replyTo) if (params.commentTo) { - [peer, replyTo] = await this._getDiscussionMessage(peer, normalizeMessageId(params.commentTo)!) + [peer, replyTo] = await _getDiscussionMessage(client, peer, normalizeMessageId(params.commentTo)!) } if (params.mustReply) { @@ -135,14 +135,14 @@ export async function sendText( throw new MtArgumentError('mustReply used, but replyTo was not passed') } - const msg = await this.getMessages(peer, replyTo) + const msg = await getMessages(client, peer, replyTo) if (!msg) { throw new MtMessageNotFoundError(getMarkedPeerId(peer), replyTo, 'to reply to') } } - const res = await this.call({ + const res = await client.call({ _: 'messages.sendMessage', peer, noWebpage: params.disableWebPreview, @@ -160,15 +160,16 @@ export async function sendText( entities, clearDraft: params.clearDraft, noforwards: params.forbidForwards, - sendAs: params.sendAs ? await this.resolvePeer(params.sendAs) : undefined, + sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, }) if (res._ === 'updateShortSentMessage') { + // todo extract this to updates manager? const msg: tl.RawMessage = { _: 'message', id: res.id, peerId: inputPeerToPeer(peer), - fromId: { _: 'peerUser', userId: this._userId! }, + fromId: { _: 'peerUser', userId: getAuthState(client).userId! }, message, date: res.date, out: res.out, @@ -176,15 +177,16 @@ export async function sendText( entities: res.entities, } - this._date = res.date - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) + // is this needed? + // this._date = res.date + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) const peers = new PeersIndex() const fetchPeer = async (peer: tl.TypePeer | tl.TypeInputPeer): Promise => { const id = getMarkedPeerId(peer) - let cached = await this.storage.getFullPeerById(id) + let cached = await client.storage.getFullPeerById(id) if (!cached) { switch (peer._) { @@ -192,14 +194,16 @@ export async function sendText( case 'peerChat': // resolvePeer does not fetch the chat. // we need to do it manually - cached = await this.call({ - _: 'messages.getChats', - id: [peer.chatId], - }).then((res) => res.chats[0]) + cached = await client + .call({ + _: 'messages.getChats', + id: [peer.chatId], + }) + .then((res) => res.chats[0]) break default: - await this.resolvePeer(peer) - cached = await this.storage.getFullPeerById(id) + await resolvePeer(client, peer) + cached = await client.storage.getFullPeerById(id) } } @@ -229,14 +233,12 @@ export async function sendText( await fetchPeer(peer) await fetchPeer(msg.fromId!) - const ret = new Message(this, msg, peers) - this._pushConversationMessage(ret) + const ret = new Message(msg, peers) return ret } - const msg = this._findMessageInUpdate(res) - this._pushConversationMessage(msg) + const msg = _findMessageInUpdate(client, res) return msg } diff --git a/packages/client/src/methods/messages/send-typing.ts b/packages/client/src/methods/messages/send-typing.ts index 8abc2a59..87c617c1 100644 --- a/packages/client/src/methods/messages/send-typing.ts +++ b/packages/client/src/methods/messages/send-typing.ts @@ -1,7 +1,7 @@ -import { assertNever, tl } from '@mtcute/core' +import { assertNever, BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, TypingStatus } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Sends a current user/bot typing event @@ -14,10 +14,9 @@ import { InputPeerLike, TypingStatus } from '../../types' * @param chatId Chat ID * @param status Typing status * @param params - * @internal */ export async function sendTyping( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, status: TypingStatus | tl.TypeSendMessageAction = 'typing', params?: { @@ -89,9 +88,9 @@ export async function sendTyping( } } - await this.call({ + await client.call({ _: 'messages.setTyping', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), action: status, topMsgId: params?.threadId, }) diff --git a/packages/client/src/methods/messages/send-vote.ts b/packages/client/src/methods/messages/send-vote.ts index 98d8d132..d3f74492 100644 --- a/packages/client/src/methods/messages/send-vote.ts +++ b/packages/client/src/methods/messages/send-vote.ts @@ -1,17 +1,16 @@ -import { getMarkedPeerId, MaybeArray, MtArgumentError, MtTypeAssertionError } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId, MaybeArray, MtArgumentError, MtTypeAssertionError } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike, MtMessageNotFoundError, PeersIndex, Poll } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' +import { getMessages } from './get-messages' /** * Send or retract a vote in a poll. - * - * @internal */ export async function sendVote( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat ID where this poll was found */ chatId: InputPeerLike @@ -32,12 +31,12 @@ export async function sendVote( if (options === null) options = [] if (!Array.isArray(options)) options = [options] - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) let poll: Poll | undefined = undefined if (options.some((it) => typeof it === 'number')) { - const msg = await this.getMessages(peer, message) + const msg = await getMessages(client, peer, message) if (!msg) { throw new MtMessageNotFoundError(getMarkedPeerId(peer), message, 'to vote in') @@ -57,7 +56,7 @@ export async function sendVote( }) } - const res = await this.call({ + const res = await client.call({ _: 'messages.sendVote', peer, msgId: message, @@ -66,7 +65,7 @@ export async function sendVote( assertIsUpdatesGroup('messages.sendVote', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) const upd = res.updates[0] assertTypeIs('messages.sendVote (@ .updates[0])', upd, 'updateMessagePoll') @@ -77,5 +76,5 @@ export async function sendVote( const peers = PeersIndex.from(res) - return new Poll(this, upd.poll, peers, upd.results) + return new Poll(upd.poll, peers, upd.results) } diff --git a/packages/client/src/methods/messages/translate-message.ts b/packages/client/src/methods/messages/translate-message.ts index 6cc18bbe..ca0c6fda 100644 --- a/packages/client/src/methods/messages/translate-message.ts +++ b/packages/client/src/methods/messages/translate-message.ts @@ -1,15 +1,15 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, MessageEntity } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Translate message text to a given language. * * Returns `null` if it could not translate the message. - * - * @internal */ export async function translateMessage( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Chat or user ID */ chatId: InputPeerLike @@ -21,9 +21,9 @@ export async function translateMessage( ): Promise<[string, MessageEntity[]] | null> { const { chatId, messageId, toLanguage } = params - const res = await this.call({ + const res = await client.call({ _: 'messages.translateText', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), id: [messageId], toLang: toLanguage, }) diff --git a/packages/client/src/methods/messages/translate-text.ts b/packages/client/src/methods/messages/translate-text.ts index 7cce463d..b74d6d5a 100644 --- a/packages/client/src/methods/messages/translate-text.ts +++ b/packages/client/src/methods/messages/translate-text.ts @@ -1,4 +1,4 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' /** * Translate text to a given language. @@ -9,10 +9,13 @@ import { TelegramClient } from '../../client' * * @param text Text to translate * @param toLanguage Target language (two-letter ISO 639-1 language code) - * @internal */ -export async function translateText(this: TelegramClient, text: string, toLanguage: string): Promise { - const res = await this.call({ +export async function translateText( + client: BaseTelegramClient, + text: string, + toLanguage: string, +): Promise { + const res = await client.call({ _: 'messages.translateText', text: [ { diff --git a/packages/client/src/methods/messages/unpin-all-messages.ts b/packages/client/src/methods/messages/unpin-all-messages.ts index 3221473c..fce30ea7 100644 --- a/packages/client/src/methods/messages/unpin-all-messages.ts +++ b/packages/client/src/methods/messages/unpin-all-messages.ts @@ -1,16 +1,17 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { isInputPeerChannel } from '../../utils/peer-utils' import { createDummyUpdate } from '../../utils/updates-utils' +import { resolvePeer } from '../users/resolve-peer' /** * Unpin all pinned messages in a chat. * * @param chatId Chat or user ID - * @internal */ export async function unpinAllMessages( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, params?: { /** @@ -21,17 +22,17 @@ export async function unpinAllMessages( ): Promise { const { topicId } = params ?? {} - const peer = await this.resolvePeer(chatId) + const peer = await resolvePeer(client, chatId) - const res = await this.call({ + const res = await client.call({ _: 'messages.unpinAllMessages', peer, topMsgId: topicId, }) if (isInputPeerChannel(peer)) { - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount, peer.channelId)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount, peer.channelId)) } else { - this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) + client.network.handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) } } diff --git a/packages/client/src/methods/messages/unpin-message.ts b/packages/client/src/methods/messages/unpin-message.ts index 8a722b93..d323e3ba 100644 --- a/packages/client/src/methods/messages/unpin-message.ts +++ b/packages/client/src/methods/messages/unpin-message.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Unpin a message in a group, supergroup, channel or PM. @@ -9,15 +11,18 @@ import { InputPeerLike } from '../../types' * * @param chatId Chat ID, username, phone number, `"self"` or `"me"` * @param messageId Message ID - * @internal */ -export async function unpinMessage(this: TelegramClient, chatId: InputPeerLike, messageId: number): Promise { - const res = await this.call({ +export async function unpinMessage( + client: BaseTelegramClient, + chatId: InputPeerLike, + messageId: number, +): Promise { + const res = await client.call({ _: 'messages.updatePinnedMessage', - peer: await this.resolvePeer(chatId), + peer: await resolvePeer(client, chatId), id: messageId, unpin: true, }) - this._handleUpdate(res) + client.network.handleUpdate(res) } diff --git a/packages/client/src/methods/misc/init-takeout-session.ts b/packages/client/src/methods/misc/init-takeout-session.ts index 7ac340e2..affeca2d 100644 --- a/packages/client/src/methods/misc/init-takeout-session.ts +++ b/packages/client/src/methods/misc/init-takeout-session.ts @@ -1,21 +1,19 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { TakeoutSession } from '../../types' /** * Create a new takeout session * * @param params Takeout session parameters - * @internal */ export async function initTakeoutSession( - this: TelegramClient, + client: BaseTelegramClient, params: Omit, ): Promise { return new TakeoutSession( - this, - await this.call({ + client, + await client.call({ _: 'account.initTakeoutSession', ...params, }), diff --git a/packages/client/src/methods/misc/normalize-privacy-rules.ts b/packages/client/src/methods/misc/normalize-privacy-rules.ts index 5fcbada3..e1825a1f 100644 --- a/packages/client/src/methods/misc/normalize-privacy-rules.ts +++ b/packages/client/src/methods/misc/normalize-privacy-rules.ts @@ -1,17 +1,15 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPrivacyRule } from '../../types' import { normalizeToInputUser } from '../../utils' +import { resolvePeerMany } from '../users/resolve-peer-many' /** * Normalize {@link InputPrivacyRule}[] to `tl.TypeInputPrivacyRule`, * resolving the peers if needed. - * - * @internal */ export async function _normalizePrivacyRules( - this: TelegramClient, + client: BaseTelegramClient, rules: InputPrivacyRule[], ): Promise { const res: tl.TypeInputPrivacyRule[] = [] @@ -23,7 +21,7 @@ export async function _normalizePrivacyRules( } if ('users' in rule) { - const users = await this.resolvePeerMany(rule.users, normalizeToInputUser) + const users = await resolvePeerMany(client, rule.users, normalizeToInputUser) res.push({ _: rule.allow ? 'inputPrivacyValueAllowUsers' : 'inputPrivacyValueDisallowUsers', @@ -33,7 +31,7 @@ export async function _normalizePrivacyRules( } if ('chats' in rule) { - const chats = await this.resolvePeerMany(rule.chats) + const chats = await resolvePeerMany(client, rule.chats) res.push({ _: rule.allow ? 'inputPrivacyValueAllowChatParticipants' : 'inputPrivacyValueDisallowChatParticipants', diff --git a/packages/client/src/methods/parse-modes/_initialize.ts b/packages/client/src/methods/parse-modes/_initialize.ts deleted file mode 100644 index bbc4ae1d..00000000 --- a/packages/client/src/methods/parse-modes/_initialize.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { TelegramClient } from '../../client' -import { IMessageEntityParser } from '../../types' - -// @extension -interface ParseModesExtension { - _parseModes: Map - _defaultParseMode: string | null -} - -// @initialize -function _initializeParseModes(this: TelegramClient) { - this._parseModes = new Map() - this._defaultParseMode = null -} diff --git a/packages/client/src/methods/parse-modes/_state.ts b/packages/client/src/methods/parse-modes/_state.ts new file mode 100644 index 00000000..dc392eeb --- /dev/null +++ b/packages/client/src/methods/parse-modes/_state.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { BaseTelegramClient } from '@mtcute/core' + +import { IMessageEntityParser } from '../../types' + +const STATE_SYMBOL = Symbol('parseModesState') + +/** + * @internal + * @exported + */ +export interface ParseModesState { + parseModes: Map + defaultParseMode: string | null +} + +/** @internal */ +export function getParseModesState(client: BaseTelegramClient): ParseModesState { + // eslint-disable-next-line + return ((client as any)[STATE_SYMBOL] ??= { + parseModes: new Map(), + defaultParseMode: null, + } satisfies ParseModesState) +} diff --git a/packages/client/src/methods/parse-modes/parse-modes.ts b/packages/client/src/methods/parse-modes/parse-modes.ts index 1e72de90..b1bc469e 100644 --- a/packages/client/src/methods/parse-modes/parse-modes.ts +++ b/packages/client/src/methods/parse-modes/parse-modes.ts @@ -1,7 +1,7 @@ -import { MtArgumentError } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' -import { TelegramClient } from '../../client' import { IMessageEntityParser } from '../../types' +import { getParseModesState } from './_state' /** * Register a given {@link IMessageEntityParser} as a parse mode @@ -10,18 +10,19 @@ import { IMessageEntityParser } from '../../types' * * @param parseMode Parse mode to register * @throws MtClientError When the parse mode with a given name is already registered. - * @internal */ -export function registerParseMode(this: TelegramClient, parseMode: IMessageEntityParser): void { +export function registerParseMode(client: BaseTelegramClient, parseMode: IMessageEntityParser): void { const name = parseMode.name - if (this._parseModes.has(name)) { + const state = getParseModesState(client) + + if (state.parseModes.has(name)) { throw new MtArgumentError(`Parse mode ${name} is already registered. Unregister it first!`) } - this._parseModes.set(name, parseMode) + state.parseModes.set(name, parseMode) - if (!this._defaultParseMode) { - this._defaultParseMode = name + if (!state.defaultParseMode) { + state.defaultParseMode = name } } @@ -32,14 +33,15 @@ export function registerParseMode(this: TelegramClient, parseMode: IMessageEntit * Also updates the default parse mode to the next one available, if any * * @param name Name of the parse mode to unregister - * @internal */ -export function unregisterParseMode(this: TelegramClient, name: string): void { - this._parseModes.delete(name) +export function unregisterParseMode(client: BaseTelegramClient, name: string): void { + const state = getParseModesState(client) - if (this._defaultParseMode === name) { - const [first] = this._parseModes.keys() - this._defaultParseMode = first ?? null + state.parseModes.delete(name) + + if (state.defaultParseMode === name) { + const [first] = state.parseModes.keys() + state.defaultParseMode = first ?? null } } @@ -49,18 +51,19 @@ export function unregisterParseMode(this: TelegramClient, name: string): void { * @param name Name of the parse mode which parser to get. * @throws MtClientError When the provided parse mode is not registered * @throws MtClientError When `name` is omitted and there is no default parse mode - * @internal */ -export function getParseMode(this: TelegramClient, name?: string | null): IMessageEntityParser { +export function getParseMode(client: BaseTelegramClient, name?: string | null): IMessageEntityParser { + const state = getParseModesState(client) + if (!name) { - if (!this._defaultParseMode) { + if (!state.defaultParseMode) { throw new MtArgumentError('There is no default parse mode') } - name = this._defaultParseMode + name = state.defaultParseMode } - const mode = this._parseModes.get(name) + const mode = state.parseModes.get(name) if (!mode) { throw new MtArgumentError(`Parse mode ${name} is not registered.`) @@ -74,12 +77,13 @@ export function getParseMode(this: TelegramClient, name?: string | null): IMessa * * @param name Name of the parse mode * @throws MtClientError When given parse mode is not registered. - * @internal */ -export function setDefaultParseMode(this: TelegramClient, name: string): void { - if (!this._parseModes.has(name)) { +export function setDefaultParseMode(client: BaseTelegramClient, name: string): void { + const state = getParseModesState(client) + + if (!state.parseModes.has(name)) { throw new MtArgumentError(`Parse mode ${name} is not registered.`) } - this._defaultParseMode = name + state.defaultParseMode = name } diff --git a/packages/client/src/methods/pasword/change-cloud-password.ts b/packages/client/src/methods/password/change-cloud-password.ts similarity index 71% rename from packages/client/src/methods/pasword/change-cloud-password.ts rename to packages/client/src/methods/password/change-cloud-password.ts index 96f1ee81..347f7542 100644 --- a/packages/client/src/methods/pasword/change-cloud-password.ts +++ b/packages/client/src/methods/password/change-cloud-password.ts @@ -1,15 +1,11 @@ -import { MtArgumentError } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' import { assertTypeIs, computeNewPasswordHash, computeSrpParams } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' - /** * Change your 2FA password - * - * @internal */ export async function changeCloudPassword( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Current password as plaintext */ currentPassword: string @@ -21,7 +17,7 @@ export async function changeCloudPassword( ): Promise { const { currentPassword, newPassword, hint } = params - const pwd = await this.call({ _: 'account.getPassword' }) + const pwd = await client.call({ _: 'account.getPassword' }) if (!pwd.hasPassword) { throw new MtArgumentError('Cloud password is not enabled') @@ -30,10 +26,10 @@ export async function changeCloudPassword( const algo = pwd.newAlgo assertTypeIs('account.getPassword', algo, 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow') - const oldSrp = await computeSrpParams(this._crypto, pwd, currentPassword) - const newHash = await computeNewPasswordHash(this._crypto, algo, newPassword) + const oldSrp = await computeSrpParams(client.crypto, pwd, currentPassword) + const newHash = await computeNewPasswordHash(client.crypto, algo, newPassword) - await this.call({ + await client.call({ _: 'account.updatePasswordSettings', password: oldSrp, newSettings: { diff --git a/packages/client/src/methods/pasword/enable-cloud-password.ts b/packages/client/src/methods/password/enable-cloud-password.ts similarity index 80% rename from packages/client/src/methods/pasword/enable-cloud-password.ts rename to packages/client/src/methods/password/enable-cloud-password.ts index 413f3545..e114278e 100644 --- a/packages/client/src/methods/pasword/enable-cloud-password.ts +++ b/packages/client/src/methods/password/enable-cloud-password.ts @@ -1,8 +1,6 @@ -import { MtArgumentError } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' import { assertTypeIs, computeNewPasswordHash } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' - /** * Enable 2FA password on your account * @@ -10,11 +8,9 @@ import { TelegramClient } from '../../client' * thrown, and you should use {@link verifyPasswordEmail}, * {@link resendPasswordEmail} or {@link cancelPasswordEmail}, * and the call this method again - * - * @internal */ export async function enableCloudPassword( - this: TelegramClient, + client: BaseTelegramClient, params: { /** 2FA password as plaintext */ password: string @@ -26,7 +22,7 @@ export async function enableCloudPassword( ): Promise { const { password, hint, email } = params - const pwd = await this.call({ _: 'account.getPassword' }) + const pwd = await client.call({ _: 'account.getPassword' }) if (pwd.hasPassword) { throw new MtArgumentError('Cloud password is already enabled') @@ -35,9 +31,9 @@ export async function enableCloudPassword( const algo = pwd.newAlgo assertTypeIs('account.getPassword', algo, 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow') - const newHash = await computeNewPasswordHash(this._crypto, algo, password) + const newHash = await computeNewPasswordHash(client.crypto, algo, password) - await this.call({ + await client.call({ _: 'account.updatePasswordSettings', password: { _: 'inputCheckPasswordEmpty' }, newSettings: { diff --git a/packages/client/src/methods/pasword/password-email.ts b/packages/client/src/methods/password/password-email.ts similarity index 51% rename from packages/client/src/methods/pasword/password-email.ts rename to packages/client/src/methods/password/password-email.ts index 5b128de2..27dfa4c2 100644 --- a/packages/client/src/methods/pasword/password-email.ts +++ b/packages/client/src/methods/password/password-email.ts @@ -1,13 +1,12 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' /** * Verify an email to use as 2FA recovery method * * @param code Code which was sent via email - * @internal */ -export async function verifyPasswordEmail(this: TelegramClient, code: string): Promise { - await this.call({ +export async function verifyPasswordEmail(client: BaseTelegramClient, code: string): Promise { + await client.call({ _: 'account.confirmPasswordEmail', code, }) @@ -15,22 +14,18 @@ export async function verifyPasswordEmail(this: TelegramClient, code: string): P /** * Resend the code to verify an email to use as 2FA recovery method. - * - * @internal */ -export async function resendPasswordEmail(this: TelegramClient): Promise { - await this.call({ +export async function resendPasswordEmail(client: BaseTelegramClient): Promise { + await client.call({ _: 'account.resendPasswordEmail', }) } /** * Cancel the code that was sent to verify an email to use as 2FA recovery method - * - * @internal */ -export async function cancelPasswordEmail(this: TelegramClient): Promise { - await this.call({ +export async function cancelPasswordEmail(client: BaseTelegramClient): Promise { + await client.call({ _: 'account.cancelPasswordEmail', }) } diff --git a/packages/client/src/methods/pasword/remove-cloud-password.ts b/packages/client/src/methods/password/remove-cloud-password.ts similarity index 59% rename from packages/client/src/methods/pasword/remove-cloud-password.ts rename to packages/client/src/methods/password/remove-cloud-password.ts index aa3b5464..a65f498d 100644 --- a/packages/client/src/methods/pasword/remove-cloud-password.ts +++ b/packages/client/src/methods/password/remove-cloud-password.ts @@ -1,24 +1,21 @@ -import { MtArgumentError } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError } from '@mtcute/core' import { computeSrpParams } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' - /** * Remove 2FA password from your account * * @param password 2FA password as plaintext - * @internal */ -export async function removeCloudPassword(this: TelegramClient, password: string): Promise { - const pwd = await this.call({ _: 'account.getPassword' }) +export async function removeCloudPassword(client: BaseTelegramClient, password: string): Promise { + const pwd = await client.call({ _: 'account.getPassword' }) if (!pwd.hasPassword) { throw new MtArgumentError('Cloud password is not enabled') } - const oldSrp = await computeSrpParams(this._crypto, pwd, password) + const oldSrp = await computeSrpParams(client.crypto, pwd, password) - await this.call({ + await client.call({ _: 'account.updatePasswordSettings', password: oldSrp, newSettings: { diff --git a/packages/client/src/methods/stickers/add-sticker-to-set.ts b/packages/client/src/methods/stickers/add-sticker-to-set.ts index 00d4d30a..7355b16d 100644 --- a/packages/client/src/methods/stickers/add-sticker-to-set.ts +++ b/packages/client/src/methods/stickers/add-sticker-to-set.ts @@ -1,4 +1,5 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputStickerSet, InputStickerSetItem, @@ -6,6 +7,7 @@ import { normalizeInputStickerSet, StickerSet, } from '../../types' +import { _normalizeFileToDocument } from '../files/normalize-file-to-document' /** * Add a sticker to a sticker set. @@ -17,10 +19,9 @@ import { * @param sticker Sticker to be added * @param params * @returns Modfiied sticker set - * @internal */ export async function addStickerToSet( - this: TelegramClient, + client: BaseTelegramClient, setId: InputStickerSet, sticker: InputStickerSetItem, params?: { @@ -33,12 +34,12 @@ export async function addStickerToSet( progressCallback?: (uploaded: number, total: number) => void }, ): Promise { - const res = await this.call({ + const res = await client.call({ _: 'stickers.addStickerToSet', stickerset: normalizeInputStickerSet(setId), sticker: { _: 'inputStickerSetItem', - document: await this._normalizeFileToDocument(sticker.file, params ?? {}), + document: await _normalizeFileToDocument(client, sticker.file, params ?? {}), emoji: sticker.emojis, maskCoords: sticker.maskPosition ? { @@ -52,5 +53,5 @@ export async function addStickerToSet( }, }) - return new StickerSet(this, res) + return new StickerSet(res) } diff --git a/packages/client/src/methods/stickers/create-sticker-set.ts b/packages/client/src/methods/stickers/create-sticker-set.ts index 290fd4f7..dd4fd973 100644 --- a/packages/client/src/methods/stickers/create-sticker-set.ts +++ b/packages/client/src/methods/stickers/create-sticker-set.ts @@ -1,6 +1,5 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputFileLike, InputPeerLike, @@ -11,6 +10,8 @@ import { StickerType, } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { _normalizeFileToDocument } from '../files/normalize-file-to-document' +import { resolvePeer } from '../users/resolve-peer' /** * Create a new sticker set. @@ -20,10 +21,9 @@ import { normalizeToInputUser } from '../../utils/peer-utils' * * @param params * @returns Newly created sticker set - * @internal */ export async function createStickerSet( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Owner of the sticker set (must be user). @@ -93,7 +93,7 @@ export async function createStickerSet( throw new MtArgumentError('Creating emoji stickers is not supported yet by the API') } - const owner = normalizeToInputUser(await this.resolvePeer(params.owner), params.owner) + const owner = normalizeToInputUser(await resolvePeer(client, params.owner), params.owner) const inputStickers: tl.TypeInputStickerSetItem[] = [] @@ -104,7 +104,7 @@ export async function createStickerSet( inputStickers.push({ _: 'inputStickerSetItem', - document: await this._normalizeFileToDocument(sticker.file, { + document: await _normalizeFileToDocument(client, sticker.file, { progressCallback, }), emoji: sticker.emojis, @@ -122,7 +122,7 @@ export async function createStickerSet( i += 1 } - const res = await this.call({ + const res = await client.call({ _: 'stickers.createStickerSet', animated: params.sourceType === 'animated', videos: params.sourceType === 'video', @@ -133,8 +133,8 @@ export async function createStickerSet( title: params.title, shortName: params.shortName, stickers: inputStickers, - thumb: params.thumb ? await this._normalizeFileToDocument(params.thumb, {}) : undefined, + thumb: params.thumb ? await _normalizeFileToDocument(client, params.thumb, {}) : undefined, }) - return new StickerSet(this, res) + return new StickerSet(res) } diff --git a/packages/client/src/methods/stickers/delete-sticker-from-set.ts b/packages/client/src/methods/stickers/delete-sticker-from-set.ts index 3a6c2e49..f07f9c8b 100644 --- a/packages/client/src/methods/stickers/delete-sticker-from-set.ts +++ b/packages/client/src/methods/stickers/delete-sticker-from-set.ts @@ -1,7 +1,6 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { fileIdToInputDocument, tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { StickerSet } from '../../types' /** @@ -14,20 +13,19 @@ import { StickerSet } from '../../types' * TDLib and Bot API compatible File ID, or a * TL object representing a sticker to be removed * @returns Modfiied sticker set - * @internal */ export async function deleteStickerFromSet( - this: TelegramClient, + client: BaseTelegramClient, sticker: string | tdFileId.RawFullRemoteFileLocation | tl.TypeInputDocument, ): Promise { if (tdFileId.isFileIdLike(sticker)) { sticker = fileIdToInputDocument(sticker) } - const res = await this.call({ + const res = await client.call({ _: 'stickers.removeStickerFromSet', sticker, }) - return new StickerSet(this, res) + return new StickerSet(res) } diff --git a/packages/client/src/methods/stickers/get-custom-emojis.ts b/packages/client/src/methods/stickers/get-custom-emojis.ts index a87354d8..8d7fa3e9 100644 --- a/packages/client/src/methods/stickers/get-custom-emojis.ts +++ b/packages/client/src/methods/stickers/get-custom-emojis.ts @@ -1,7 +1,6 @@ -import { MtTypeAssertionError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtTypeAssertionError, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { Sticker } from '../../types' import { parseDocument } from '../../types/media/document-utils' @@ -9,10 +8,9 @@ import { parseDocument } from '../../types/media/document-utils' * Get custom emoji stickers by their IDs * * @param ids IDs of the stickers (as defined in {@link MessageEntity.emojiId}) - * @internal */ -export async function getCustomEmojis(this: TelegramClient, ids: tl.Long[]): Promise { - const res = await this.call({ +export async function getCustomEmojis(client: BaseTelegramClient, ids: tl.Long[]): Promise { + const res = await client.call({ _: 'messages.getCustomEmojiDocuments', documentId: ids, }) @@ -20,7 +18,7 @@ export async function getCustomEmojis(this: TelegramClient, ids: tl.Long[]): Pro return res.map((it) => { assertTypeIs('getCustomEmojis', it, 'document') - const doc = parseDocument(this, it) + const doc = parseDocument(it) if (doc.type !== 'sticker') { throw new MtTypeAssertionError('getCustomEmojis', 'sticker', doc.type) diff --git a/packages/client/src/methods/stickers/get-installed-stickers.ts b/packages/client/src/methods/stickers/get-installed-stickers.ts index 82d19b9d..1e08e524 100644 --- a/packages/client/src/methods/stickers/get-installed-stickers.ts +++ b/packages/client/src/methods/stickers/get-installed-stickers.ts @@ -1,7 +1,6 @@ -import { Long } from '@mtcute/core' +import { BaseTelegramClient, Long } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { StickerSet } from '../../types' /** @@ -11,16 +10,14 @@ import { StickerSet } from '../../types' * > the packs, that does not include the stickers themselves. * > Use {@link StickerSet.getFull} or {@link getStickerSet} * > to get a stickerset that will include the stickers - * - * @internal */ -export async function getInstalledStickers(this: TelegramClient): Promise { - const res = await this.call({ +export async function getInstalledStickers(client: BaseTelegramClient): Promise { + const res = await client.call({ _: 'messages.getAllStickers', hash: Long.ZERO, }) assertTypeIs('getInstalledStickers', res, 'messages.allStickers') - return res.sets.map((set) => new StickerSet(this, set)) + return res.sets.map((set) => new StickerSet(set)) } diff --git a/packages/client/src/methods/stickers/get-sticker-set.ts b/packages/client/src/methods/stickers/get-sticker-set.ts index 771e82e9..368aedfd 100644 --- a/packages/client/src/methods/stickers/get-sticker-set.ts +++ b/packages/client/src/methods/stickers/get-sticker-set.ts @@ -1,18 +1,18 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputStickerSet, normalizeInputStickerSet, StickerSet } from '../../types' /** * Get a sticker pack and stickers inside of it. * * @param setId Sticker pack short name, dice emoji, `"emoji"` for animated emojis or input ID - * @internal */ -export async function getStickerSet(this: TelegramClient, setId: InputStickerSet): Promise { - const res = await this.call({ +export async function getStickerSet(client: BaseTelegramClient, setId: InputStickerSet): Promise { + const res = await client.call({ _: 'messages.getStickerSet', stickerset: normalizeInputStickerSet(setId), hash: 0, }) - return new StickerSet(this, res) + return new StickerSet(res) } diff --git a/packages/client/src/methods/stickers/move-sticker-in-set.ts b/packages/client/src/methods/stickers/move-sticker-in-set.ts index 97e47e14..7c6d136a 100644 --- a/packages/client/src/methods/stickers/move-sticker-in-set.ts +++ b/packages/client/src/methods/stickers/move-sticker-in-set.ts @@ -1,7 +1,6 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { fileIdToInputDocument, tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { StickerSet } from '../../types' /** @@ -16,11 +15,10 @@ import { StickerSet } from '../../types' * TL object representing a sticker to be removed * @param position New sticker position (starting from 0) * @returns Modfiied sticker set - * @internal */ export async function moveStickerInSet( - this: TelegramClient, + client: BaseTelegramClient, sticker: string | tdFileId.RawFullRemoteFileLocation | tl.TypeInputDocument, position: number, ): Promise { @@ -28,11 +26,11 @@ export async function moveStickerInSet( sticker = fileIdToInputDocument(sticker) } - const res = await this.call({ + const res = await client.call({ _: 'stickers.changeStickerPosition', sticker, position, }) - return new StickerSet(this, res) + return new StickerSet(res) } diff --git a/packages/client/src/methods/stickers/set-chat-sticker-set.ts b/packages/client/src/methods/stickers/set-chat-sticker-set.ts index 2e351f45..932491d9 100644 --- a/packages/client/src/methods/stickers/set-chat-sticker-set.ts +++ b/packages/client/src/methods/stickers/set-chat-sticker-set.ts @@ -1,6 +1,8 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, InputStickerSet, normalizeInputStickerSet } from '../../types' import { normalizeToInputChannel } from '../../utils' +import { resolvePeer } from '../users/resolve-peer' /** * Set group sticker set for a supergroup @@ -9,16 +11,15 @@ import { normalizeToInputChannel } from '../../utils' * @param thumb Sticker set thumbnail * @param params * @returns Modified sticker set - * @internal */ export async function setChatStickerSet( - this: TelegramClient, + client: BaseTelegramClient, chatId: InputPeerLike, setId: InputStickerSet, ): Promise { - await this.call({ + await client.call({ _: 'channels.setStickers', - channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId), + channel: normalizeToInputChannel(await resolvePeer(client, chatId), chatId), stickerset: normalizeInputStickerSet(setId), }) } diff --git a/packages/client/src/methods/stickers/set-sticker-set-thumb.ts b/packages/client/src/methods/stickers/set-sticker-set-thumb.ts index ae250207..eebde11e 100644 --- a/packages/client/src/methods/stickers/set-sticker-set-thumb.ts +++ b/packages/client/src/methods/stickers/set-sticker-set-thumb.ts @@ -1,7 +1,7 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputFileLike, InputStickerSet, normalizeInputStickerSet, StickerSet } from '../../types' +import { _normalizeFileToDocument } from '../files/normalize-file-to-document' /** * Set sticker set thumbnail @@ -10,10 +10,9 @@ import { InputFileLike, InputStickerSet, normalizeInputStickerSet, StickerSet } * @param thumb Sticker set thumbnail * @param params * @returns Modified sticker set - * @internal */ export async function setStickerSetThumb( - this: TelegramClient, + client: BaseTelegramClient, id: InputStickerSet, thumb: InputFileLike | tl.TypeInputDocument, params?: { @@ -26,11 +25,11 @@ export async function setStickerSetThumb( progressCallback?: (uploaded: number, total: number) => void }, ): Promise { - const res = await this.call({ + const res = await client.call({ _: 'stickers.setStickerSetThumb', stickerset: normalizeInputStickerSet(id), - thumb: await this._normalizeFileToDocument(thumb, params ?? {}), + thumb: await _normalizeFileToDocument(client, thumb, params ?? {}), }) - return new StickerSet(this, res) + return new StickerSet(res) } diff --git a/packages/client/src/methods/stories/apply-boost.ts b/packages/client/src/methods/stories/apply-boost.ts index ba02f449..b714b232 100644 --- a/packages/client/src/methods/stories/apply-boost.ts +++ b/packages/client/src/methods/stories/apply-boost.ts @@ -1,15 +1,16 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Boost a given channel * * @param peerId Peer ID to boost - * @internal */ -export async function applyBoost(this: TelegramClient, peerId: InputPeerLike): Promise { - await this.call({ +export async function applyBoost(client: BaseTelegramClient, peerId: InputPeerLike): Promise { + await client.call({ _: 'stories.applyBoost', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), }) } diff --git a/packages/client/src/methods/stories/can-apply-boost.ts b/packages/client/src/methods/stories/can-apply-boost.ts index 4f876871..5763c68c 100644 --- a/packages/client/src/methods/stories/can-apply-boost.ts +++ b/packages/client/src/methods/stories/can-apply-boost.ts @@ -1,7 +1,7 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { Chat, InputPeerLike, PeersIndex } from '../../types' +import { resolvePeer } from '../users/resolve-peer' // @exported export type CanApplyBoostResult = @@ -21,14 +21,13 @@ export type CanApplyBoostResult = * - `.reason == "need_premium"` if the user needs Premium to boost this channel * - `.reason == "timeout"` if the user has recently boosted a channel and needs to wait * (`.until` contains the date until which the user needs to wait) - * @internal */ -export async function canApplyBoost(this: TelegramClient, peerId: InputPeerLike): Promise { +export async function canApplyBoost(client: BaseTelegramClient, peerId: InputPeerLike): Promise { try { - const res = await this.call( + const res = await client.call( { _: 'stories.canApplyBoost', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), }, { floodSleepThreshold: 0 }, ) @@ -36,7 +35,7 @@ export async function canApplyBoost(this: TelegramClient, peerId: InputPeerLike) if (res._ === 'stories.canApplyBoostOk') return { can: true } const peers = PeersIndex.from(res) - const chat = new Chat(this, peers.get(res.currentBoost)) + const chat = new Chat(peers.get(res.currentBoost)) return { can: true, current: chat } } catch (e) { diff --git a/packages/client/src/methods/stories/can-send-story.ts b/packages/client/src/methods/stories/can-send-story.ts index 93935762..39278ec3 100644 --- a/packages/client/src/methods/stories/can-send-story.ts +++ b/packages/client/src/methods/stories/can-send-story.ts @@ -1,7 +1,7 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' // @exported export type CanSendStoryResult = true | 'need_admin' | 'need_boosts' @@ -14,13 +14,12 @@ export type CanSendStoryResult = true | 'need_admin' | 'need_boosts' * - `true` if the user can post stories * - `"need_admin"` if the user is not an admin in the chat * - `"need_boosts"` if the channel doesn't have enough boosts - * @internal */ -export async function canSendStory(this: TelegramClient, peerId: InputPeerLike): Promise { +export async function canSendStory(client: BaseTelegramClient, peerId: InputPeerLike): Promise { try { - const res = await this.call({ + const res = await client.call({ _: 'stories.canSendStory', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), }) if (!res) return 'need_admin' diff --git a/packages/client/src/methods/stories/delete-stories.ts b/packages/client/src/methods/stories/delete-stories.ts index d52b9fa7..13041f24 100644 --- a/packages/client/src/methods/stories/delete-stories.ts +++ b/packages/client/src/methods/stories/delete-stories.ts @@ -1,16 +1,15 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Delete a story * * @returns IDs of stories that were removed - * @internal */ export async function deleteStories( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Story IDs to delete @@ -27,9 +26,9 @@ export async function deleteStories( ): Promise { const { ids, peer = 'me' } = params - return this.call({ + return client.call({ _: 'stories.deleteStories', - peer: await this.resolvePeer(peer), + peer: await resolvePeer(client, peer), id: Array.isArray(ids) ? ids : [ids], }) } diff --git a/packages/client/src/methods/stories/edit-story.ts b/packages/client/src/methods/stories/edit-story.ts index f45fcc9b..1fce7409 100644 --- a/packages/client/src/methods/stories/edit-story.ts +++ b/packages/client/src/methods/stories/edit-story.ts @@ -1,16 +1,19 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { FormattedString, InputMediaLike, InputPeerLike, InputPrivacyRule, Story } from '../../types' +import { _normalizeInputMedia } from '../files/normalize-input-media' +import { _parseEntities } from '../messages/parse-entities' +import { _normalizePrivacyRules } from '../misc/normalize-privacy-rules' +import { resolvePeer } from '../users/resolve-peer' +import { _findStoryInUpdate } from './find-in-update' /** * Edit a sent story * * @returns Edited story - * @internal */ export async function editStory( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Story ID to edit @@ -67,12 +70,13 @@ export async function editStory( let media: tl.TypeInputMedia | undefined = undefined if (params.media) { - media = await this._normalizeInputMedia(params.media, params) + media = await _normalizeInputMedia(client, params.media, params) // if there's no caption in input media (i.e. not present or undefined), // user wants to keep current caption, thus `content` needs to stay `undefined` if ('caption' in params.media && params.media.caption !== undefined) { - [caption, entities] = await this._parseEntities( + [caption, entities] = await _parseEntities( + client, params.media.caption, params.parseMode, params.media.entities, @@ -81,14 +85,14 @@ export async function editStory( } if (params.caption) { - [caption, entities] = await this._parseEntities(params.caption, params.parseMode, params.entities) + [caption, entities] = await _parseEntities(client, params.caption, params.parseMode, params.entities) } - const privacyRules = params.privacyRules ? await this._normalizePrivacyRules(params.privacyRules) : undefined + const privacyRules = params.privacyRules ? await _normalizePrivacyRules(client, params.privacyRules) : undefined - const res = await this.call({ + const res = await client.call({ _: 'stories.editStory', - peer: await this.resolvePeer(peer), + peer: await resolvePeer(client, peer), id, media, mediaAreas: interactiveElements, @@ -97,5 +101,5 @@ export async function editStory( privacyRules, }) - return this._findStoryInUpdate(res) + return _findStoryInUpdate(client, res) } diff --git a/packages/client/src/methods/stories/find-in-update.ts b/packages/client/src/methods/stories/find-in-update.ts index 6cc182d2..b8e5be53 100644 --- a/packages/client/src/methods/stories/find-in-update.ts +++ b/packages/client/src/methods/stories/find-in-update.ts @@ -1,15 +1,14 @@ -import { MtTypeAssertionError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtTypeAssertionError, tl } from '@mtcute/core' import { assertTypeIs, hasValueAtKey } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { PeersIndex, Story } from '../../types' import { assertIsUpdatesGroup } from '../../utils/updates-utils' /** @internal */ -export function _findStoryInUpdate(this: TelegramClient, res: tl.TypeUpdates): Story { +export function _findStoryInUpdate(client: BaseTelegramClient, res: tl.TypeUpdates): Story { assertIsUpdatesGroup('_findStoryInUpdate', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) const peers = PeersIndex.from(res) const updateStory = res.updates.find(hasValueAtKey('_', 'updateStory')) @@ -20,5 +19,5 @@ export function _findStoryInUpdate(this: TelegramClient, res: tl.TypeUpdates): S assertTypeIs('updateStory.story', updateStory.story, 'storyItem') - return new Story(this, updateStory.story, peers) + return new Story(updateStory.story, peers) } diff --git a/packages/client/src/methods/stories/get-all-stories.ts b/packages/client/src/methods/stories/get-all-stories.ts index 667a1f7c..2e40ae2b 100644 --- a/packages/client/src/methods/stories/get-all-stories.ts +++ b/packages/client/src/methods/stories/get-all-stories.ts @@ -1,15 +1,13 @@ +import { BaseTelegramClient } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { AllStories } from '../../types' /** * Get all stories (e.g. to load the top bar) - * - * @internal */ export async function getAllStories( - this: TelegramClient, + client: BaseTelegramClient, params?: { /** * Offset from which to fetch stories @@ -26,7 +24,7 @@ export async function getAllStories( const { offset, archived } = params - const res = await this.call({ + const res = await client.call({ _: 'stories.getAllStories', state: offset, next: Boolean(offset), @@ -35,5 +33,5 @@ export async function getAllStories( assertTypeIsNot('getAllStories', res, 'stories.allStoriesNotModified') - return new AllStories(this, res) + return new AllStories(res) } diff --git a/packages/client/src/methods/stories/get-boost-stats.ts b/packages/client/src/methods/stories/get-boost-stats.ts index 3639c2a3..6ca3cddc 100644 --- a/packages/client/src/methods/stories/get-boost-stats.ts +++ b/packages/client/src/methods/stories/get-boost-stats.ts @@ -1,17 +1,18 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { BoostStats } from '../../types/stories/boost-stats' +import { resolvePeer } from '../users/resolve-peer' /** * Get information about boosts in a channel * * @returns IDs of stories that were removed - * @internal */ -export async function getBoostStats(this: TelegramClient, peerId: InputPeerLike): Promise { - const res = await this.call({ +export async function getBoostStats(client: BaseTelegramClient, peerId: InputPeerLike): Promise { + const res = await client.call({ _: 'stories.getBoostsStatus', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), }) return new BoostStats(res) diff --git a/packages/client/src/methods/stories/get-boosters.ts b/packages/client/src/methods/stories/get-boosters.ts index 21fcbc16..52bae994 100644 --- a/packages/client/src/methods/stories/get-boosters.ts +++ b/packages/client/src/methods/stories/get-boosters.ts @@ -1,16 +1,17 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { ArrayPaginated, InputPeerLike, PeersIndex } from '../../types' import { Booster } from '../../types/stories/booster' import { makeArrayPaginated } from '../../utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get boosters of a channel * * @returns IDs of stories that were removed - * @internal */ export async function getBoosters( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, params?: { /** @@ -28,9 +29,9 @@ export async function getBoosters( ): Promise> { const { offset = '', limit = 100 } = params ?? {} - const res = await this.call({ + const res = await client.call({ _: 'stories.getBoostersList', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), offset, limit, }) @@ -38,7 +39,7 @@ export async function getBoosters( const peers = PeersIndex.from(res) return makeArrayPaginated( - res.boosters.map((it) => new Booster(this, it, peers)), + res.boosters.map((it) => new Booster(it, peers)), res.count, res.nextOffset, ) diff --git a/packages/client/src/methods/stories/get-peer-stories.ts b/packages/client/src/methods/stories/get-peer-stories.ts index 7cbf351a..a4435a0e 100644 --- a/packages/client/src/methods/stories/get-peer-stories.ts +++ b/packages/client/src/methods/stories/get-peer-stories.ts @@ -1,19 +1,20 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, PeersIndex, PeerStories } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Get stories of a given peer * * @param peerId Peer ID whose stories to fetch - * @internal */ -export async function getPeerStories(this: TelegramClient, peerId: InputPeerLike): Promise { - const res = await this.call({ +export async function getPeerStories(client: BaseTelegramClient, peerId: InputPeerLike): Promise { + const res = await client.call({ _: 'stories.getPeerStories', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), }) const peers = PeersIndex.from(res) - return new PeerStories(this, res.stories, peers) + return new PeerStories(res.stories, peers) } diff --git a/packages/client/src/methods/stories/get-profile-stories.ts b/packages/client/src/methods/stories/get-profile-stories.ts index ef59612a..deea120d 100644 --- a/packages/client/src/methods/stories/get-profile-stories.ts +++ b/packages/client/src/methods/stories/get-profile-stories.ts @@ -1,16 +1,15 @@ +import { BaseTelegramClient } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { ArrayPaginated, InputPeerLike, PeersIndex, Story } from '../../types' import { makeArrayPaginated } from '../../utils' +import { resolvePeer } from '../users/resolve-peer' /** * Get profile stories - * - * @internal */ export async function getProfileStories( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, params?: { /** @@ -39,9 +38,9 @@ export async function getProfileStories( const { kind = 'pinned', offsetId = 0, limit = 100 } = params - const res = await this.call({ + const res = await client.call({ _: kind === 'pinned' ? 'stories.getPinnedStories' : 'stories.getStoriesArchive', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), offsetId, limit, }) @@ -51,7 +50,7 @@ export async function getProfileStories( const stories = res.stories.map((it) => { assertTypeIs('getProfileStories', it, 'storyItem') - return new Story(this, it, peers) + return new Story(it, peers) }) const last = stories[stories.length - 1] const next = last?.id diff --git a/packages/client/src/methods/stories/get-stories-by-id.ts b/packages/client/src/methods/stories/get-stories-by-id.ts index d27927af..6d864293 100644 --- a/packages/client/src/methods/stories/get-stories-by-id.ts +++ b/packages/client/src/methods/stories/get-stories-by-id.ts @@ -1,41 +1,43 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike, PeersIndex, Story } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Get a single story by its ID * * @param peerId Peer ID whose stories to fetch * @param storyId Story ID - * @internal */ -export async function getStoriesById(this: TelegramClient, peerId: InputPeerLike, storyId: number): Promise +export async function getStoriesById(client: BaseTelegramClient, peerId: InputPeerLike, storyId: number): Promise /** * Get multiple stories by their IDs * * @param peerId Peer ID whose stories to fetch * @param storyIds Story IDs - * @internal */ -export async function getStoriesById(this: TelegramClient, peerId: InputPeerLike, storyIds: number[]): Promise +export async function getStoriesById( + client: BaseTelegramClient, + peerId: InputPeerLike, + storyIds: number[], +): Promise /** * @internal */ export async function getStoriesById( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, storyIds: MaybeArray, ): Promise> { const single = !Array.isArray(storyIds) if (single) storyIds = [storyIds as number] - const res = await this.call({ + const res = await client.call({ _: 'stories.getStoriesByID', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), id: storyIds as number[], }) @@ -44,7 +46,7 @@ export async function getStoriesById( const stories = res.stories.map((it) => { assertTypeIs('getProfileStories', it, 'storyItem') - return new Story(this, it, peers) + return new Story(it, peers) }) return single ? stories[0] : stories diff --git a/packages/client/src/methods/stories/get-stories-interactions.ts b/packages/client/src/methods/stories/get-stories-interactions.ts index 7ef350a7..f377b83f 100644 --- a/packages/client/src/methods/stories/get-stories-interactions.ts +++ b/packages/client/src/methods/stories/get-stories-interactions.ts @@ -1,15 +1,13 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, PeersIndex, StoryInteractions } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Get brief information about story interactions. - * - * @internal */ export async function getStoriesInteractions( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, storyId: number, ): Promise @@ -18,11 +16,9 @@ export async function getStoriesInteractions( * Get brief information about stories interactions. * * The result will be in the same order as the input IDs - * - * @internal */ export async function getStoriesInteractions( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, storyIds: number[], ): Promise @@ -31,22 +27,22 @@ export async function getStoriesInteractions( * @internal */ export async function getStoriesInteractions( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, storyIds: MaybeArray, ): Promise> { const isSingle = !Array.isArray(storyIds) if (isSingle) storyIds = [storyIds as number] - const res = await this.call({ + const res = await client.call({ _: 'stories.getStoriesViews', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), id: storyIds as number[], }) const peers = PeersIndex.from(res) - const infos = res.views.map((it) => new StoryInteractions(this, it, peers)) + const infos = res.views.map((it) => new StoryInteractions(it, peers)) return isSingle ? infos[0] : infos } diff --git a/packages/client/src/methods/stories/get-story-link.ts b/packages/client/src/methods/stories/get-story-link.ts index 47b528c8..f6074ff7 100644 --- a/packages/client/src/methods/stories/get-story-link.ts +++ b/packages/client/src/methods/stories/get-story-link.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Generate a link to a story. @@ -8,13 +10,17 @@ import { InputPeerLike } from '../../types' * and if the user doesn't have a username, `USER_PUBLIC_MISSING` is thrown. * * I have no idea why is this an RPC call, but whatever - * - * @internal */ -export async function getStoryLink(this: TelegramClient, peerId: InputPeerLike, storyId: number): Promise { - return this.call({ - _: 'stories.exportStoryLink', - peer: await this.resolvePeer(peerId), - id: storyId, - }).then((r) => r.link) +export async function getStoryLink( + client: BaseTelegramClient, + peerId: InputPeerLike, + storyId: number, +): Promise { + return client + .call({ + _: 'stories.exportStoryLink', + peer: await resolvePeer(client, peerId), + id: storyId, + }) + .then((r) => r.link) } diff --git a/packages/client/src/methods/stories/get-story-viewers.ts b/packages/client/src/methods/stories/get-story-viewers.ts index fe19343b..83a322d5 100644 --- a/packages/client/src/methods/stories/get-story-viewers.ts +++ b/packages/client/src/methods/stories/get-story-viewers.ts @@ -1,13 +1,13 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, StoryViewersList } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Get viewers list of a story - * - * @internal */ export async function getStoryViewers( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, storyId: number, params?: { @@ -47,9 +47,9 @@ export async function getStoryViewers( const { onlyContacts, sortBy = 'reaction', query, offset = '', limit = 100 } = params - const res = await this.call({ + const res = await client.call({ _: 'stories.getStoryViewsList', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), justContacts: onlyContacts, reactionsFirst: sortBy === 'reaction', q: query, @@ -58,5 +58,5 @@ export async function getStoryViewers( limit, }) - return new StoryViewersList(this, res) + return new StoryViewersList(res) } diff --git a/packages/client/src/methods/stories/hide-my-stories-views.ts b/packages/client/src/methods/stories/hide-my-stories-views.ts index 323276c3..adcc7aa5 100644 --- a/packages/client/src/methods/stories/hide-my-stories-views.ts +++ b/packages/client/src/methods/stories/hide-my-stories-views.ts @@ -1,6 +1,5 @@ -import { MtTypeAssertionError } from '@mtcute/core' +import { BaseTelegramClient, MtTypeAssertionError } from '@mtcute/core' -import { TelegramClient } from '../../client' import { StoriesStealthMode } from '../../types/stories/stealth-mode' import { assertIsUpdatesGroup, hasValueAtKey } from '../../utils' @@ -8,11 +7,9 @@ import { assertIsUpdatesGroup, hasValueAtKey } from '../../utils' * Hide own stories views (activate so called "stealth mode") * * Currently has a cooldown of 1 hour, and throws FLOOD_WAIT error if it is on cooldown. - * - * @internal */ export async function hideMyStoriesViews( - this: TelegramClient, + client: BaseTelegramClient, params?: { /** * Whether to hide views from the last 5 minutes @@ -31,14 +28,14 @@ export async function hideMyStoriesViews( ): Promise { const { past = true, future = true } = params ?? {} - const res = await this.call({ + const res = await client.call({ _: 'stories.activateStealthMode', past, future, }) assertIsUpdatesGroup('hideMyStoriesViews', res) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) const upd = res.updates.find(hasValueAtKey('_', 'updateStoriesStealthMode')) diff --git a/packages/client/src/methods/stories/increment-stories-views.ts b/packages/client/src/methods/stories/increment-stories-views.ts index 448b6cfc..9ccdd30d 100644 --- a/packages/client/src/methods/stories/increment-stories-views.ts +++ b/packages/client/src/methods/stories/increment-stories-views.ts @@ -1,7 +1,7 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Increment views of one or more stories. @@ -11,16 +11,15 @@ import { InputPeerLike } from '../../types' * * @param peerId Peer ID whose stories to mark as read * @param ids ID(s) of the stories to increment views of (max 200) - * @internal */ export async function incrementStoriesViews( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, ids: MaybeArray, ): Promise { - return this.call({ + return client.call({ _: 'stories.incrementStoryViews', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), id: Array.isArray(ids) ? ids : [ids], }) } diff --git a/packages/client/src/methods/stories/iter-all-stories.ts b/packages/client/src/methods/stories/iter-all-stories.ts index 288f9adf..dff4db19 100644 --- a/packages/client/src/methods/stories/iter-all-stories.ts +++ b/packages/client/src/methods/stories/iter-all-stories.ts @@ -1,32 +1,22 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { PeerStories } from '../../types' +import { getAllStories } from './get-all-stories' /** * Iterate over all stories (e.g. to load the top bar) * * Wrapper over {@link getAllStories} - * - * @internal */ export async function* iterAllStories( - this: TelegramClient, - params?: { - /** - * Offset from which to start fetching stories - */ - offset?: string - + client: BaseTelegramClient, + params?: Parameters[1] & { /** * Maximum number of stories to fetch * * @default Infinity */ limit?: number - - /** - * Whether to fetch stories from "archived" (or "hidden") peers - */ - archived?: boolean }, ): AsyncIterableIterator { if (!params) params = {} @@ -36,7 +26,7 @@ export async function* iterAllStories( let current = 0 for (;;) { - const res = await this.getAllStories({ + const res = await getAllStories(client, { offset, archived, }) diff --git a/packages/client/src/methods/stories/iter-boosters.ts b/packages/client/src/methods/stories/iter-boosters.ts index 08d02b3e..7ce3a7f2 100644 --- a/packages/client/src/methods/stories/iter-boosters.ts +++ b/packages/client/src/methods/stories/iter-boosters.ts @@ -1,6 +1,9 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { Booster } from '../../types/stories/booster' +import { resolvePeer } from '../users/resolve-peer' +import { getBoosters } from './get-boosters' /** * Iterate over boosters of a channel. @@ -8,12 +11,11 @@ import { Booster } from '../../types/stories/booster' * Wrapper over {@link getBoosters} * * @returns IDs of stories that were removed - * @internal */ export async function* iterBoosters( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Total number of boosters to fetch * @@ -36,10 +38,10 @@ export async function* iterBoosters( let { offset } = params let current = 0 - const peer = await this.resolvePeer(peerId) + const peer = await resolvePeer(client, peerId) for (;;) { - const res = await this.getBoosters(peer, { + const res = await getBoosters(client, peer, { offset, limit: Math.min(limit - current, chunkSize), }) diff --git a/packages/client/src/methods/stories/iter-profile-stories.ts b/packages/client/src/methods/stories/iter-profile-stories.ts index 6b9471bc..089d027b 100644 --- a/packages/client/src/methods/stories/iter-profile-stories.ts +++ b/packages/client/src/methods/stories/iter-profile-stories.ts @@ -1,15 +1,16 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, Story } from '../../types' +import { resolvePeer } from '../users/resolve-peer' +import { getProfileStories } from './get-profile-stories' /** * Iterate over profile stories. Wrapper over {@link getProfileStories} - * - * @internal */ export async function* iterProfileStories( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Total number of stories to fetch * @@ -33,10 +34,10 @@ export async function* iterProfileStories( let { offsetId } = params let current = 0 - const peer = await this.resolvePeer(peerId) + const peer = await resolvePeer(client, peerId) for (;;) { - const res = await this.getProfileStories(peer, { + const res = await getProfileStories(client, peer, { kind, offsetId, limit: Math.min(limit - current, chunkSize), diff --git a/packages/client/src/methods/stories/iter-story-viewers.ts b/packages/client/src/methods/stories/iter-story-viewers.ts index c9406423..5bb8c5be 100644 --- a/packages/client/src/methods/stories/iter-story-viewers.ts +++ b/packages/client/src/methods/stories/iter-story-viewers.ts @@ -1,17 +1,18 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, StoryViewer } from '../../types' +import { resolvePeer } from '../users/resolve-peer' +import { getStoryViewers } from './get-story-viewers' /** * Iterate over viewers list of a story. * Wrapper over {@link getStoryViewers} - * - * @internal */ export async function* iterStoryViewers( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, storyId: number, - params?: Parameters[2] & { + params?: Parameters[3] & { /** * Total number of viewers to fetch * @@ -35,10 +36,10 @@ export async function* iterStoryViewers( let { offset = '' } = params let current = 0 - const peer = await this.resolvePeer(peerId) + const peer = await resolvePeer(client, peerId) for (;;) { - const res = await this.getStoryViewers(peer, storyId, { + const res = await getStoryViewers(client, peer, storyId, { onlyContacts, sortBy, query, diff --git a/packages/client/src/methods/stories/read-stories.ts b/packages/client/src/methods/stories/read-stories.ts index 504d6038..210bb543 100644 --- a/packages/client/src/methods/stories/read-stories.ts +++ b/packages/client/src/methods/stories/read-stories.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Mark all stories up to a given ID as read @@ -8,12 +10,11 @@ import { InputPeerLike } from '../../types' * * @param peerId Peer ID whose stories to mark as read * @returns IDs of the stores that were marked as read - * @internal */ -export async function readStories(this: TelegramClient, peerId: InputPeerLike, maxId: number): Promise { - return this.call({ +export async function readStories(client: BaseTelegramClient, peerId: InputPeerLike, maxId: number): Promise { + return client.call({ _: 'stories.readStories', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), maxId, }) } diff --git a/packages/client/src/methods/stories/report-story.ts b/packages/client/src/methods/stories/report-story.ts index f8729cee..27a08558 100644 --- a/packages/client/src/methods/stories/report-story.ts +++ b/packages/client/src/methods/stories/report-story.ts @@ -1,15 +1,13 @@ -import { MaybeArray, tl } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Report a story (or multiple stories) to the moderation team - * - * @internal */ export async function reportStory( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, storyIds: MaybeArray, params?: { @@ -28,9 +26,9 @@ export async function reportStory( ): Promise { const { reason = { _: 'inputReportReasonSpam' }, message = '' } = params ?? {} - await this.call({ + await client.call({ _: 'stories.report', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), id: Array.isArray(storyIds) ? storyIds : [storyIds], message, reason, diff --git a/packages/client/src/methods/stories/send-story-reaction.ts b/packages/client/src/methods/stories/send-story-reaction.ts index 01673d4c..9c34d7db 100644 --- a/packages/client/src/methods/stories/send-story-reaction.ts +++ b/packages/client/src/methods/stories/send-story-reaction.ts @@ -1,32 +1,32 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, InputReaction, normalizeInputReaction } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Send (or remove) a reaction to a story - * - * @internal */ export async function sendStoryReaction( - this: TelegramClient, - peerId: InputPeerLike, - storyId: number, - reaction: InputReaction, - params?: { + client: BaseTelegramClient, + params: { + peerId: InputPeerLike + storyId: number + reaction: InputReaction /** * Whether to add this reaction to recently used */ addToRecent?: boolean }, ): Promise { - const { addToRecent } = params ?? {} + const { peerId, storyId, reaction, addToRecent } = params - const res = await this.call({ + const res = await client.call({ _: 'stories.sendReaction', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), storyId, reaction: normalizeInputReaction(reaction), addToRecent, }) - this._handleUpdate(res, true) + client.network.handleUpdate(res, true) } diff --git a/packages/client/src/methods/stories/send-story.ts b/packages/client/src/methods/stories/send-story.ts index f0056d2e..b7b4bb6e 100644 --- a/packages/client/src/methods/stories/send-story.ts +++ b/packages/client/src/methods/stories/send-story.ts @@ -1,17 +1,20 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { randomLong } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { FormattedString, InputMediaLike, InputPeerLike, InputPrivacyRule, Story } from '../../types' +import { _normalizeInputMedia } from '../files/normalize-input-media' +import { _parseEntities } from '../messages/parse-entities' +import { _normalizePrivacyRules } from '../misc/normalize-privacy-rules' +import { resolvePeer } from '../users/resolve-peer' +import { _findStoryInUpdate } from './find-in-update' /** * Send a story * * @returns Created story - * @internal */ export async function sendStory( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Peer ID to send story as @@ -86,12 +89,13 @@ export async function sendStory( } } - const inputMedia = await this._normalizeInputMedia(media, params) + const inputMedia = await _normalizeInputMedia(client, media, params) const privacyRules = params.privacyRules ? - await this._normalizePrivacyRules(params.privacyRules) : + await _normalizePrivacyRules(client, params.privacyRules) : [{ _: 'inputPrivacyValueAllowAll' } as const] - const [caption, entities] = await this._parseEntities( + const [caption, entities] = await _parseEntities( + client, // some types dont have `caption` field, and ts warns us, // but since it's JS, they'll just be `undefined` and properly // handled by _parseEntities method @@ -100,11 +104,11 @@ export async function sendStory( params.entities || (media as Extract).entities, ) - const res = await this.call({ + const res = await client.call({ _: 'stories.sendStory', pinned, noforwards: forbidForwards, - peer: await this.resolvePeer(peer), + peer: await resolvePeer(client, peer), media: inputMedia, mediaAreas: interactiveElements, caption, @@ -114,5 +118,5 @@ export async function sendStory( period, }) - return this._findStoryInUpdate(res) + return _findStoryInUpdate(client, res) } diff --git a/packages/client/src/methods/stories/toggle-peer-stories-archived.ts b/packages/client/src/methods/stories/toggle-peer-stories-archived.ts index 204af95b..83d6865c 100644 --- a/packages/client/src/methods/stories/toggle-peer-stories-archived.ts +++ b/packages/client/src/methods/stories/toggle-peer-stories-archived.ts @@ -1,21 +1,21 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Toggle whether peer's stories are archived (hidden) or not. * * This **does not** archive the chat with that peer, only stories. - * - * @internal */ export async function togglePeerStoriesArchived( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, archived: boolean, ): Promise { - await this.call({ + await client.call({ _: 'stories.togglePeerStoriesHidden', - peer: await this.resolvePeer(peerId), + peer: await resolvePeer(client, peerId), hidden: archived, }) } diff --git a/packages/client/src/methods/stories/toggle-stories-pinned.ts b/packages/client/src/methods/stories/toggle-stories-pinned.ts index 9529c5a6..22342e50 100644 --- a/packages/client/src/methods/stories/toggle-stories-pinned.ts +++ b/packages/client/src/methods/stories/toggle-stories-pinned.ts @@ -1,16 +1,15 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike } from '../../types' +import { resolvePeer } from '../users/resolve-peer' /** * Toggle one or more stories pinned status * * @returns IDs of stories that were toggled - * @internal */ export async function toggleStoriesPinned( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * Story ID(s) to toggle @@ -32,9 +31,9 @@ export async function toggleStoriesPinned( ): Promise { const { ids, pinned, peer = 'me' } = params - return await this.call({ + return await client.call({ _: 'stories.togglePinned', - peer: await this.resolvePeer(peer), + peer: await resolvePeer(client, peer), id: Array.isArray(ids) ? ids : [ids], pinned, }) diff --git a/packages/client/src/methods/updates/index.ts b/packages/client/src/methods/updates/index.ts new file mode 100644 index 00000000..6cb2eb61 --- /dev/null +++ b/packages/client/src/methods/updates/index.ts @@ -0,0 +1,3 @@ +export * from './manager' +export * from './parsed' +export * from './types' diff --git a/packages/client/src/methods/updates.ts b/packages/client/src/methods/updates/manager.ts similarity index 56% rename from packages/client/src/methods/updates.ts rename to packages/client/src/methods/updates/manager.ts index 6e1122b3..53e8dc7a 100644 --- a/packages/client/src/methods/updates.ts +++ b/packages/client/src/methods/updates/manager.ts @@ -1,136 +1,17 @@ /* eslint-disable max-depth,max-params */ -import { assertNever, MtArgumentError, tl } from '@mtcute/core' -import { - AsyncLock, - ConditionVariable, - Deque, - getBarePeerId, - getMarkedPeerId, - Logger, - markedPeerIdToBare, - SortedLinkedList, - toggleChannelIdMark, -} from '@mtcute/core/utils' +import { assertNever, BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' +import { getBarePeerId, getMarkedPeerId, markedPeerIdToBare, toggleChannelIdMark } from '@mtcute/core/utils' -import { TelegramClient, TelegramClientOptions } from '../client' -import { Message, PeersIndex } from '../types' -import { _parseUpdate } from '../types/updates/parse-update' -import { extractChannelIdFromUpdate } from '../utils/misc-utils' -import { normalizeToInputChannel } from '../utils/peer-utils' -// @copy -import { RpsMeter } from '../utils/rps-meter' +import { PeersIndex } from '../../types' +import { normalizeToInputChannel } from '../../utils/peer-utils' +import { RpsMeter } from '../../utils/rps-meter' +import { getAuthState } from '../auth/_state' +import { resolvePeer } from '../users/resolve-peer' +import { createUpdatesState, PendingUpdate, toPendingUpdate, UpdatesManagerParams, UpdatesState } from './types' +import { extractChannelIdFromUpdate, messageToUpdate } from './utils' // code in this file is very bad, thanks to Telegram's awesome updates mechanism -// @copy -interface PendingUpdateContainer { - upd: tl.TypeUpdates - seqStart: number - seqEnd: number -} - -// @copy -interface PendingUpdate { - update: tl.TypeUpdate - channelId?: number - pts?: number - ptsBefore?: number - qts?: number - qtsBefore?: number - timeout?: number - peers?: PeersIndex -} - -/* eslint-disable @typescript-eslint/no-unused-vars */ -// @extension -interface UpdatesState { - _updatesLoopActive: boolean - _updatesLoopCv: ConditionVariable - - _pendingUpdateContainers: SortedLinkedList - _pendingPtsUpdates: SortedLinkedList - _pendingPtsUpdatesPostponed: SortedLinkedList - _pendingQtsUpdates: SortedLinkedList - _pendingQtsUpdatesPostponed: SortedLinkedList - _pendingUnorderedUpdates: Deque - - _noDispatchEnabled: boolean - // channel id or 0 => msg id - _noDispatchMsg: Map> - // channel id or 0 => pts - _noDispatchPts: Map> - _noDispatchQts: Set - - _updLock: AsyncLock - _rpsIncoming?: RpsMeter - _rpsProcessing?: RpsMeter - - _messageGroupingInterval: number - _messageGroupingPending: Map - - // accessing storage every time might be expensive, - // so store everything here, and load & save - // every time session is loaded & saved. - _pts?: number - _qts?: number - _date?: number - _seq?: number - - // old values of the updates state (i.e. as in DB) - // used to avoid redundant storage calls - _oldPts?: number - _oldQts?: number - _oldDate?: number - _oldSeq?: number - _selfChanged: boolean - - // whether to catch up channels from the locally stored pts - // usually set in start() method based on `catchUp` param - _catchUpChannels?: boolean - - _cpts: Map - _cptsMod: Map - - _updsLog: Logger -} -/* eslint-enable @typescript-eslint/no-unused-vars */ - -// @initialize -function _initializeUpdates(this: TelegramClient, opts: TelegramClientOptions) { - this._updatesLoopActive = false - this._updatesLoopCv = new ConditionVariable() - - this._pendingUpdateContainers = new SortedLinkedList((a, b) => a.seqStart - b.seqStart) - this._pendingPtsUpdates = new SortedLinkedList((a, b) => a.ptsBefore! - b.ptsBefore!) - this._pendingPtsUpdatesPostponed = new SortedLinkedList((a, b) => a.ptsBefore! - b.ptsBefore!) - this._pendingQtsUpdates = new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!) - this._pendingQtsUpdatesPostponed = new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!) - this._pendingUnorderedUpdates = new Deque() - - this._noDispatchEnabled = !opts.disableNoDispatch - this._noDispatchMsg = new Map() - this._noDispatchPts = new Map() - this._noDispatchQts = new Set() - - this._messageGroupingInterval = opts.messageGroupingInterval ?? 0 - this._messageGroupingPending = new Map() - - this._updLock = new AsyncLock() - // we dont need to initialize state fields since - // they are always loaded either from the server, or from storage. - - // channel PTS are not loaded immediately, and instead are cached here - // after the first time they were retrieved from the storage. - this._cpts = new Map() - // modified channel pts, to avoid unnecessary - // DB calls for not modified cpts - this._cptsMod = new Map() - - this._selfChanged = false - - this._updsLog = this.log.create('updates') -} - /** * Enable RPS meter. * Only available in NodeJS v10.7.0 and newer @@ -139,11 +20,11 @@ function _initializeUpdates(this: TelegramClient, opts: TelegramClientOptions) { * * @param size Sampling size * @param time Window time - * @internal */ -export function enableRps(this: TelegramClient, size?: number, time?: number): void { - this._rpsIncoming = new RpsMeter(size, time) - this._rpsProcessing = new RpsMeter(size, time) +export function enableRps(client: BaseTelegramClient, size?: number, time?: number): void { + const state = getState(client) + state.rpsIncoming = new RpsMeter(size, time) + state.rpsProcessing = new RpsMeter(size, time) } /** @@ -154,15 +35,15 @@ export function enableRps(this: TelegramClient, size?: number, time?: number): v * they should be around the same, except * rare situations when processing rps * may peak. - * - * @internal */ -export function getCurrentRpsIncoming(this: TelegramClient): number { - if (!this._rpsIncoming) { +export function getCurrentRpsIncoming(client: BaseTelegramClient): number { + const state = getState(client) + + if (!state.rpsIncoming) { throw new MtArgumentError('RPS meter is not enabled, use .enableRps() first') } - return this._rpsIncoming.getRps() + return state.rpsIncoming.getRps() } /** @@ -173,128 +54,139 @@ export function getCurrentRpsIncoming(this: TelegramClient): number { * they should be around the same, except * rare situations when processing rps * may peak. - * - * @internal */ -export function getCurrentRpsProcessing(this: TelegramClient): number { - if (!this._rpsProcessing) { +export function getCurrentRpsProcessing(client: BaseTelegramClient): number { + const state = getState(client) + + if (!state.rpsProcessing) { throw new MtArgumentError('RPS meter is not enabled, use .enableRps() first') } - return this._rpsProcessing.getRps() + return state.rpsProcessing.getRps() } /** - * Fetch updates state from the server. - * Meant to be used right after authorization, - * but before force-saving the session. - * @internal - */ -export async function _fetchUpdatesState(this: TelegramClient): Promise { - await this._updLock.acquire() - - this._updsLog.debug('fetching initial state') - - try { - let state = await this.call({ _: 'updates.getState' }) - - this._updsLog.debug( - 'updates.getState returned state: pts=%d, qts=%d, date=%d, seq=%d', - state.pts, - state.qts, - state.date, - state.seq, - ) - - // for some unknown fucking reason getState may return old qts - // call getDifference to get actual values :shrug: - const diff = await this.call({ - _: 'updates.getDifference', - pts: state.pts, - qts: state.qts, - date: state.date, - }) - - switch (diff._) { - case 'updates.differenceEmpty': - break - case 'updates.differenceTooLong': // shouldn't happen, but who knows? - (state as tl.Mutable).pts = diff.pts - break - case 'updates.differenceSlice': - state = diff.intermediateState - break - case 'updates.difference': - state = diff.state - break - default: - assertNever(diff) - } - - this._qts = state.qts - this._pts = state.pts - this._date = state.date - this._seq = state.seq - - this._updsLog.debug( - 'loaded initial state: pts=%d, qts=%d, date=%d, seq=%d', - state.pts, - state.qts, - state.date, - state.seq, - ) - } catch (e) { - this._updsLog.error('failed to fetch updates state: %s', e) - } - - this._updLock.release() -} - -/** - * @internal - */ -export async function _loadStorage(this: TelegramClient): Promise { - // load updates state from the session - await this.storage.load?.() - const state = await this.storage.getUpdatesState() - - if (state) { - this._pts = this._oldPts = state[0] - this._qts = this._oldQts = state[1] - this._date = this._oldDate = state[2] - this._seq = this._oldSeq = state[3] - this._updsLog.debug( - 'loaded stored state: pts=%d, qts=%d, date=%d, seq=%d', - state[0], - state[1], - state[2], - state[3], - ) - } - // if no state, don't bother initializing properties - // since that means that there is no authorization, - // and thus _fetchUpdatesState will be called - - const self = await this.storage.getSelf() - - if (self) { - this._userId = self.userId - this._isBot = self.isBot - } -} - -/** - * **ADVANCED** + * Add updates handling capabilities to {@link BaseTelegramClient} * - * Manually start updates loop. - * Usually done automatically inside {@link start} - * @internal + * {@link BaseTelegramClient} doesn't do any updates processing on its own, and instead + * dispatches raw TL updates to user of the class. + * + * This method enables updates processing according to Telegram's updates mechanism. + * + * > **Note**: you don't need to use this if you are using {@link TelegramClient} + * + * @param client Client instance + * @param params Updates manager parameters + * @noemit */ -export function startUpdatesLoop(this: TelegramClient): void { - if (this._updatesLoopActive) return +export function enableUpdatesProcessing(client: BaseTelegramClient, params: UpdatesManagerParams): void { + if (getState(client)) return - this._updatesLoopActive = true - this._updatesLoop().catch((err) => this._emitError(err)) + if (client.network.params.disableUpdates) { + throw new MtArgumentError('Updates must be enabled to use updates manager') + } + + const authState = getAuthState(client) + + const state = createUpdatesState(client, authState, params) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(client as any)[STATE_SYMBOL] = state + + function onLoggedIn(): void { + fetchUpdatesState(client, state).catch((err) => client._emitError(err)) + } + + function onLoggedOut(): void { + stopUpdatesLoop(client) + state.cpts.clear() + state.cptsMod.clear() + state.pts = state.qts = state.date = state.seq = undefined + } + + function onBeforeConnect(): void { + loadUpdatesStorage(client, state).catch((err) => client._emitError(err)) + } + + function onBeforeStorageSave(): Promise { + return saveUpdatesStorage(client, state).catch((err) => client._emitError(err)) + } + + function onKeepAlive() { + state.log.debug('no updates for >15 minutes, catching up') + handleUpdate(state, { _: 'updatesTooLong' }) + } + + state.postponedTimer.onTimeout(() => { + state.hasTimedoutPostponed = true + state.updatesLoopCv.notify() + }) + + client.on('logged_in', onLoggedIn) + client.on('logged_out', onLoggedOut) + client.on('before_connect', onBeforeConnect) + client.beforeStorageSave(onBeforeStorageSave) + client.on('keep_alive', onKeepAlive) + client.network.setUpdateHandler((upd, fromClient) => handleUpdate(state, upd, fromClient)) + + function cleanup() { + client.off('logged_in', onLoggedIn) + client.off('logged_out', onLoggedOut) + client.off('before_connect', onBeforeConnect) + client.offBeforeStorageSave(onBeforeStorageSave) + client.off('keep_alive', onKeepAlive) + client.off('before_stop', cleanup) + client.network.setUpdateHandler(() => {}) + stopUpdatesLoop(client) + } + + state.stop = cleanup + client.on('before_stop', cleanup) +} + +/** + * Disable updates processing. + * + * Basically reverts {@link enableUpdatesProcessing} + * + * @param client Client instance + * @noemit + */ +export function disableUpdatesProcessing(client: BaseTelegramClient): void { + const state = getState(client) + if (!state) return + + state.stop() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (client as any)[STATE_SYMBOL] +} + +/** + * Start updates loop. + * + * You must first call {@link enableUpdatesProcessing} to use this method. + * + * It is recommended to use this method in callback to {@link start}, + * or otherwise make sure the user is logged in. + * + * > **Note**: If you are using {@link UpdatesManagerParams.catchUp} option, + * > catching up will be done in background, you can't await it. + */ +export async function startUpdatesLoop(client: BaseTelegramClient): Promise { + const state = getState(client) + if (state.updatesLoopActive) return + + // otherwise we will catch up on the first update + if (!state.catchUpOnStart) { + await fetchUpdatesState(client, state) + } + + // start updates loop in background + state.updatesLoopActive = true + updatesLoop(client, state).catch((err) => client._emitError(err)) + + if (state.catchUpOnStart) { + catchUp(client) + } } /** @@ -302,114 +194,162 @@ export function startUpdatesLoop(this: TelegramClient): void { * * Manually stop updates loop. * Usually done automatically when stopping the client with {@link close} - * @internal */ -export function stopUpdatesLoop(this: TelegramClient): void { - if (!this._updatesLoopActive) return +export function stopUpdatesLoop(client: BaseTelegramClient): void { + const state = getState(client) + if (!state.updatesLoopActive) return - this._updatesLoopActive = false - this._updatesLoopCv.notify() -} - -/** @internal */ -export function _onStop(this: TelegramClient): void { - this.stopUpdatesLoop() + state.updatesLoopActive = false + state.pendingUpdateContainers.clear() + state.pendingUnorderedUpdates.clear() + state.pendingPtsUpdates.clear() + state.pendingQtsUpdates.clear() + state.pendingPtsUpdatesPostponed.clear() + state.pendingQtsUpdatesPostponed.clear() + state.postponedTimer.reset() + state.updatesLoopCv.notify() } /** - * @internal + * Catch up with the server by loading missed updates. + * + * > **Note**: In case the storage was not properly + * > closed the last time, "catching up" might + * > result in duplicate updates. */ -export async function _saveStorage(this: TelegramClient, afterImport = false): Promise { - // save updates state to the session +export function catchUp(client: BaseTelegramClient): void { + const state = getState(client) - if (afterImport) { - // we need to get `self` from db and store it - const self = await this.storage.getSelf() + state.log.debug('catch up requested') - if (self) { - this._userId = self.userId - this._isBot = self.isBot - } - } + state.catchUpChannels = true + handleUpdate(state, { _: 'updatesTooLong' }) +} + +////////////////////////////////////////////// IMPLEMENTATION ////////////////////////////////////////////// + +const STATE_SYMBOL = Symbol('updatesState') + +function getState(client: BaseTelegramClient): UpdatesState { + // eslint-disable-next-line + return (client as any)[STATE_SYMBOL] +} + +async function fetchUpdatesState(client: BaseTelegramClient, state: UpdatesState): Promise { + await state.lock.acquire() + + state.log.debug('fetching initial state') try { - // before any authorization pts will be undefined - if (this._pts !== undefined) { - // if old* value is not available, assume it has changed. - if (this._oldPts === undefined || this._oldPts !== this._pts) { - await this.storage.setUpdatesPts(this._pts) - } - if (this._oldQts === undefined || this._oldQts !== this._qts) { - await this.storage.setUpdatesQts(this._qts!) - } - if (this._oldDate === undefined || this._oldDate !== this._date) { - await this.storage.setUpdatesDate(this._date!) - } - if (this._oldSeq === undefined || this._oldSeq !== this._seq) { - await this.storage.setUpdatesSeq(this._seq!) - } + let fetchedState = await client.call({ _: 'updates.getState' }) - // update old* values - this._oldPts = this._pts - this._oldQts = this._qts - this._oldDate = this._date - this._oldSeq = this._seq + state.log.debug( + 'updates.getState returned state: pts=%d, qts=%d, date=%d, seq=%d', + fetchedState.pts, + fetchedState.qts, + fetchedState.date, + fetchedState.seq, + ) - await this.storage.setManyChannelPts(this._cptsMod) - this._cptsMod.clear() - } - if (this._userId !== null && this._selfChanged) { - await this.storage.setSelf({ - userId: this._userId, - isBot: this._isBot, - }) - this._selfChanged = false + // for some unknown fucking reason getState may return old qts + // call getDifference to get actual values :shrug: + const diff = await client.call({ + _: 'updates.getDifference', + pts: fetchedState.pts, + qts: fetchedState.qts, + date: fetchedState.date, + }) + + switch (diff._) { + case 'updates.differenceEmpty': + break + case 'updates.differenceTooLong': // shouldn't happen, but who knows? + (fetchedState as tl.Mutable).pts = diff.pts + break + case 'updates.differenceSlice': + fetchedState = diff.intermediateState + break + case 'updates.difference': + fetchedState = diff.state + break + default: + assertNever(diff) } - await this.storage.save?.() - } catch (err: unknown) { - this._emitError(err) + state.qts = fetchedState.qts + state.pts = fetchedState.pts + state.date = fetchedState.date + state.seq = fetchedState.seq + + state.log.debug( + 'loaded initial state: pts=%d, qts=%d, date=%d, seq=%d', + state.pts, + state.qts, + state.date, + state.seq, + ) + } catch (e) { + state.log.error('failed to fetch updates state: %s', e) + } + + state.lock.release() +} + +async function loadUpdatesStorage(client: BaseTelegramClient, state: UpdatesState): Promise { + const storedState = await client.storage.getUpdatesState() + + if (storedState) { + state.pts = state.oldPts = storedState[0] + state.qts = state.oldQts = storedState[1] + state.date = state.oldDate = storedState[2] + state.seq = state.oldSeq = storedState[3] + + state.log.debug( + 'loaded stored state: pts=%d, qts=%d, date=%d, seq=%d', + storedState[0], + storedState[1], + storedState[2], + storedState[3], + ) + } + // if no state, don't bother initializing properties + // since that means that there is no authorization, + // and thus fetchUpdatesState will be called +} + +async function saveUpdatesStorage(client: BaseTelegramClient, state: UpdatesState, save = false): Promise { + // before any authorization pts will be undefined + if (state.pts !== undefined) { + // if old* value is not available, assume it has changed. + if (state.oldPts === undefined || state.oldPts !== state.pts) { + await client.storage.setUpdatesPts(state.pts) + } + if (state.oldQts === undefined || state.oldQts !== state.qts) { + await client.storage.setUpdatesQts(state.qts!) + } + if (state.oldDate === undefined || state.oldDate !== state.date) { + await client.storage.setUpdatesDate(state.date!) + } + if (state.oldSeq === undefined || state.oldSeq !== state.seq) { + await client.storage.setUpdatesSeq(state.seq!) + } + + // update old* values + state.oldPts = state.pts + state.oldQts = state.qts + state.oldDate = state.date + state.oldSeq = state.seq + + await client.storage.setManyChannelPts(state.cptsMod) + state.cptsMod.clear() + + if (save) { + await client.storage.save?.() + } } } -/** - * @internal - */ -export function _dispatchUpdate(this: TelegramClient, update: tl.TypeUpdate, peers: PeersIndex): void { - this.emit('raw_update', update, peers) - - const parsed = _parseUpdate(this, update, peers) - - if (parsed) { - if (this._messageGroupingInterval && parsed.name === 'new_message') { - const group = parsed.data.groupedIdUnique - - if (group) { - const pendingGroup = this._messageGroupingPending.get(group) - - if (pendingGroup) { - pendingGroup[0].push(parsed.data) - } else { - const messages = [parsed.data] - const timeout = setTimeout(() => { - this._messageGroupingPending.delete(group) - this.emit('update', { name: 'message_group', data: messages }) - this.emit('message_group', messages) - }, this._messageGroupingInterval) - - this._messageGroupingPending.set(group, [messages, timeout]) - } - - return - } - } - - this.emit('update', parsed) - this.emit(parsed.name, parsed.data) - } -} - -function _addToNoDispatchIndex(this: TelegramClient, updates?: tl.TypeUpdates): void { +function addToNoDispatchIndex(state: UpdatesState, updates?: tl.TypeUpdates): void { if (!updates) return const addUpdate = (upd: tl.TypeUpdate) => { @@ -417,15 +357,15 @@ function _addToNoDispatchIndex(this: TelegramClient, updates?: tl.TypeUpdates): const pts = 'pts' in upd ? upd.pts : undefined if (pts) { - const set = this._noDispatchPts.get(channelId) - if (!set) this._noDispatchPts.set(channelId, new Set([pts])) + const set = state.noDispatchPts.get(channelId) + if (!set) state.noDispatchPts.set(channelId, new Set([pts])) else set.add(pts) } const qts = 'qts' in upd ? upd.qts : undefined if (qts) { - this._noDispatchQts.add(qts) + state.noDispatchQts.add(qts) } switch (upd._) { @@ -433,8 +373,8 @@ function _addToNoDispatchIndex(this: TelegramClient, updates?: tl.TypeUpdates): case 'updateNewChannelMessage': { const channelId = upd.message.peerId?._ === 'peerChannel' ? upd.message.peerId.channelId : 0 - const set = this._noDispatchMsg.get(channelId) - if (!set) this._noDispatchMsg.set(channelId, new Set([upd.message.id])) + const set = state.noDispatchMsg.get(channelId) + if (!set) state.noDispatchMsg.set(channelId, new Set([upd.message.id])) else set.add(upd.message.id) break @@ -451,12 +391,12 @@ function _addToNoDispatchIndex(this: TelegramClient, updates?: tl.TypeUpdates): case 'updateShortChatMessage': case 'updateShortSentMessage': { // these updates are only used for non-channel messages, so we use 0 - let set = this._noDispatchMsg.get(0) - if (!set) this._noDispatchMsg.set(0, new Set([updates.id])) + let set = state.noDispatchMsg.get(0) + if (!set) state.noDispatchMsg.set(0, new Set([updates.id])) else set.add(updates.id) - set = this._noDispatchPts.get(0) - if (!set) this._noDispatchPts.set(0, new Set([updates.pts])) + set = state.noDispatchPts.get(0) + if (!set) state.noDispatchPts.set(0, new Set([updates.pts])) else set.add(updates.pts) break } @@ -470,12 +410,12 @@ function _addToNoDispatchIndex(this: TelegramClient, updates?: tl.TypeUpdates): } } -async function _replaceMinPeers(this: TelegramClient, peers: PeersIndex): Promise { +async function replaceMinPeers(client: BaseTelegramClient, peers: PeersIndex): Promise { for (const [key, user_] of peers.users) { const user = user_ as Exclude if (user.min) { - const cached = await this.storage.getFullPeerById(user.id) + const cached = await client.storage.getFullPeerById(user.id) if (!cached) return false peers.users.set(key, cached as tl.TypeUser) } @@ -495,7 +435,7 @@ async function _replaceMinPeers(this: TelegramClient, peers: PeersIndex): Promis id = -chat.id } - const cached = await this.storage.getFullPeerById(id) + const cached = await client.storage.getFullPeerById(id) if (!cached) return false peers.chats.set(key, cached as tl.TypeChat) } @@ -506,8 +446,8 @@ async function _replaceMinPeers(this: TelegramClient, peers: PeersIndex): Promis return true } -async function _fetchPeersForShort( - this: TelegramClient, +async function fetchPeersForShort( + client: BaseTelegramClient, upd: tl.TypeUpdate | tl.RawMessage | tl.RawMessageService, ): Promise { const peers = new PeersIndex() @@ -519,7 +459,7 @@ async function _fetchPeersForShort( const marked = typeof peer === 'number' ? peer : getMarkedPeerId(peer) - const cached = await this.storage.getFullPeerById(marked) + const cached = await client.storage.getFullPeerById(marked) if (!cached) return false if (marked > 0) { @@ -627,29 +567,21 @@ async function _fetchPeersForShort( return peers } -function _isMessageEmpty(upd: tl.TypeUpdate): boolean { +function isMessageEmpty(upd: tl.TypeUpdate): boolean { return (upd as Extract).message?._ === 'messageEmpty' } -/** - * @internal - */ -export function _handleUpdate( - this: TelegramClient, - update: tl.TypeUpdates, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - noDispatch = false, // fixme -): void { - if (noDispatch && this._noDispatchEnabled) { - _addToNoDispatchIndex.call(this, update) +function handleUpdate(state: UpdatesState, update: tl.TypeUpdates, noDispatch = false): void { + if (noDispatch && state.noDispatchEnabled) { + addToNoDispatchIndex(state, update) } - this._updsLog.debug( + state.log.debug( 'received %s, queueing for processing. containers queue size: %d', update._, - this._pendingUpdateContainers.length, + state.pendingUpdateContainers.length, ) - this._rpsIncoming?.hit() + state.rpsIncoming?.hit() switch (update._) { case 'updatesTooLong': @@ -657,7 +589,7 @@ export function _handleUpdate( case 'updateShortChatMessage': case 'updateShort': case 'updateShortSentMessage': - this._pendingUpdateContainers.add({ + state.pendingUpdateContainers.add({ upd: update, seqStart: 0, seqEnd: 0, @@ -665,7 +597,7 @@ export function _handleUpdate( break case 'updates': case 'updatesCombined': - this._pendingUpdateContainers.add({ + state.pendingUpdateContainers.add({ upd: update, seqStart: update._ === 'updatesCombined' ? update.seqStart : update.seq, seqEnd: update.seq, @@ -675,103 +607,25 @@ export function _handleUpdate( assertNever(update) } - this._updatesLoopCv.notify() + state.updatesLoopCv.notify() } -/** - * Catch up with the server by loading missed updates. - * - * @internal - */ -export function catchUp(this: TelegramClient): void { - // we also use a lock here so new updates are not processed - // while we are catching up with older ones - - this._updsLog.debug('catch up requested') - - this._catchUpChannels = true - this._handleUpdate({ _: 'updatesTooLong' }) - - // return this._updLock - // .acquire() - // .then(() => _loadDifference.call(this)) - // .catch((err) => this._emitError(err)) - // .then(() => this._updLock.release()) - // .then(() => this._saveStorage()) -} - -function _toPendingUpdate(upd: tl.TypeUpdate, peers?: PeersIndex): PendingUpdate { - const channelId = extractChannelIdFromUpdate(upd) || 0 - const pts = 'pts' in upd ? upd.pts : undefined - // eslint-disable-next-line no-nested-ternary - const ptsCount = 'ptsCount' in upd ? upd.ptsCount : pts ? 0 : undefined - const qts = 'qts' in upd ? upd.qts : undefined - - return { - update: upd, - channelId, - pts, - ptsBefore: pts ? pts - ptsCount! : undefined, - qts, - qtsBefore: qts ? qts - 1 : undefined, - peers, - } -} - -function _messageToUpdate(message: tl.TypeMessage): tl.TypeUpdate { - switch (message.peerId!._) { - case 'peerUser': - case 'peerChat': - return { - _: 'updateNewMessage', - message, - pts: 0, - ptsCount: 0, - } - case 'peerChannel': - return { - _: 'updateNewChannelMessage', - message, - pts: 0, - ptsCount: 0, - } - } -} - -// function _checkPts(local: number, remote: number): number { -// if (remote === 0x7ffffffff /* INT32_MAX */) { -// return 0 -// } -// -// const diff = remote - local -// // diff > 0 - there is a gap -// // diff < 0 - already applied -// // diff = 0 - ok -// -// if (diff < -399999) { -// // pts can only go up or drop cardinally -// return 0 -// } -// -// return diff -// } -// todo: pts/qts drop - -async function _fetchChannelDifference( - this: TelegramClient, +async function fetchChannelDifference( + client: BaseTelegramClient, + state: UpdatesState, channelId: number, fallbackPts?: number, force = false, ): Promise { - let _pts: number | null | undefined = this._cpts.get(channelId) + let _pts: number | null | undefined = state.cpts.get(channelId) - if (!_pts && this._catchUpChannels) { - _pts = await this.storage.getChannelPts(channelId) + if (!_pts && state.catchUpChannels) { + _pts = await client.storage.getChannelPts(channelId) } if (!_pts) _pts = fallbackPts if (!_pts) { - this._updsLog.debug('fetchChannelDifference failed for channel %d: base pts not available', channelId) + state.log.debug('fetchChannelDifference failed for channel %d: base pts not available', channelId) return } @@ -779,16 +633,16 @@ async function _fetchChannelDifference( let channel try { - channel = normalizeToInputChannel(await this.resolvePeer(toggleChannelIdMark(channelId))) + channel = normalizeToInputChannel(await resolvePeer(client, toggleChannelIdMark(channelId))) } catch (e) { - this._updsLog.warn('fetchChannelDifference failed for channel %d: input peer not found', channelId) + state.log.warn('fetchChannelDifference failed for channel %d: input peer not found', channelId) return } // to make TS happy let pts = _pts - let limit = this._isBot ? 100000 : 100 + let limit = state.auth.isBot ? 100000 : 100 if (pts <= 0) { pts = 1 @@ -796,25 +650,20 @@ async function _fetchChannelDifference( } for (;;) { - const diff = await this.call( - { - _: 'updates.getChannelDifference', - force, - channel, - pts, - limit, - filter: { _: 'channelMessagesFilterEmpty' }, - }, - // { flush: !isFirst } - ) + const diff = await client.call({ + _: 'updates.getChannelDifference', + force, + channel, + pts, + limit, + filter: { _: 'channelMessagesFilterEmpty' }, + }) if (diff._ === 'updates.channelDifferenceEmpty') { - this._updsLog.debug('getChannelDifference (cid = %d) returned channelDifferenceEmpty', channelId) + state.log.debug('getChannelDifference (cid = %d) returned channelDifferenceEmpty', channelId) break } - await this._cachePeersFrom(diff) - const peers = PeersIndex.from(diff) if (diff._ === 'updates.channelDifferenceTooLong') { @@ -822,7 +671,7 @@ async function _fetchChannelDifference( pts = diff.dialog.pts! } - this._updsLog.warn( + state.log.warn( 'getChannelDifference (cid = %d) returned channelDifferenceTooLong. new pts: %d, recent msgs: %d', channelId, pts, @@ -830,7 +679,7 @@ async function _fetchChannelDifference( ) diff.messages.forEach((message) => { - this._updsLog.debug( + state.log.debug( 'processing message %d (%s) from TooLong diff for channel %d', message.id, message._, @@ -839,12 +688,12 @@ async function _fetchChannelDifference( if (message._ === 'messageEmpty') return - this._pendingUnorderedUpdates.pushBack(_toPendingUpdate(_messageToUpdate(message), peers)) + state.pendingUnorderedUpdates.pushBack(toPendingUpdate(messageToUpdate(message), peers)) }) break } - this._updsLog.debug( + state.log.debug( 'getChannelDifference (cid = %d) returned %d messages, %d updates. new pts: %d, final: %b', channelId, diff.newMessages.length, @@ -854,17 +703,17 @@ async function _fetchChannelDifference( ) diff.newMessages.forEach((message) => { - this._updsLog.debug('processing message %d (%s) from diff for channel %d', message.id, message._, channelId) + state.log.debug('processing message %d (%s) from diff for channel %d', message.id, message._, channelId) if (message._ === 'messageEmpty') return - this._pendingUnorderedUpdates.pushBack(_toPendingUpdate(_messageToUpdate(message), peers)) + state.pendingUnorderedUpdates.pushBack(toPendingUpdate(messageToUpdate(message), peers)) }) diff.otherUpdates.forEach((upd) => { - const parsed = _toPendingUpdate(upd, peers) + const parsed = toPendingUpdate(upd, peers) - this._updsLog.debug( + state.log.debug( 'processing %s from diff for channel %d, pts_before: %d, pts: %d', upd._, channelId, @@ -872,9 +721,9 @@ async function _fetchChannelDifference( parsed.pts, ) - if (_isMessageEmpty(upd)) return + if (isMessageEmpty(upd)) return - this._pendingUnorderedUpdates.pushBack(parsed) + state.pendingUnorderedUpdates.pushBack(parsed) }) pts = diff.pts @@ -882,12 +731,13 @@ async function _fetchChannelDifference( if (diff.final) break } - this._cpts.set(channelId, pts) - this._cptsMod.set(channelId, pts) + state.cpts.set(channelId, pts) + state.cptsMod.set(channelId, pts) } -function _fetchChannelDifferenceLater( - this: TelegramClient, +function fetchChannelDifferenceLater( + client: BaseTelegramClient, + state: UpdatesState, requestedDiff: Map>, channelId: number, fallbackPts?: number, @@ -896,10 +746,9 @@ function _fetchChannelDifferenceLater( if (!requestedDiff.has(channelId)) { requestedDiff.set( channelId, - _fetchChannelDifference - .call(this, channelId, fallbackPts, force) + fetchChannelDifference(client, state, channelId, fallbackPts, force) .catch((err) => { - this._updsLog.warn('error fetching difference for %d: %s', channelId, err) + state.log.warn('error fetching difference for %d: %s', channelId, err) }) .then(() => { requestedDiff.delete(channelId) @@ -908,83 +757,80 @@ function _fetchChannelDifferenceLater( } } -async function _fetchDifference(this: TelegramClient, requestedDiff: Map>): Promise { +async function fetchDifference( + client: BaseTelegramClient, + state: UpdatesState, + requestedDiff: Map>, +): Promise { for (;;) { - const diff = await this.call({ + const diff = await client.call({ _: 'updates.getDifference', - pts: this._pts!, - date: this._date!, - qts: this._qts!, + pts: state.pts!, + date: state.date!, + qts: state.qts!, }) switch (diff._) { case 'updates.differenceEmpty': - this._updsLog.debug('updates.getDifference returned updates.differenceEmpty') + state.log.debug('updates.getDifference returned updates.differenceEmpty') return case 'updates.differenceTooLong': - this._pts = diff.pts - this._updsLog.debug('updates.getDifference returned updates.differenceTooLong') + state.pts = diff.pts + state.log.debug('updates.getDifference returned updates.differenceTooLong') return } - const state = diff._ === 'updates.difference' ? diff.state : diff.intermediateState + const fetchedState = diff._ === 'updates.difference' ? diff.state : diff.intermediateState - this._updsLog.debug( + state.log.debug( 'updates.getDifference returned %d messages, %d updates. new pts: %d, qts: %d, seq: %d, final: %b', diff.newMessages.length, diff.otherUpdates.length, - state.pts, - state.qts, - state.seq, + fetchedState.pts, + fetchedState.qts, + fetchedState.seq, diff._ === 'updates.difference', ) - await this._cachePeersFrom(diff) - const peers = PeersIndex.from(diff) diff.newMessages.forEach((message) => { - this._updsLog.debug( - 'processing message %d in %j (%s) from common diff', - message.id, - message.peerId, - message._, - ) + state.log.debug('processing message %d in %j (%s) from common diff', message.id, message.peerId, message._) if (message._ === 'messageEmpty') return // pts does not need to be checked for them - this._pendingUnorderedUpdates.pushBack(_toPendingUpdate(_messageToUpdate(message), peers)) + state.pendingUnorderedUpdates.pushBack(toPendingUpdate(messageToUpdate(message), peers)) }) diff.otherUpdates.forEach((upd) => { if (upd._ === 'updateChannelTooLong') { - this._updsLog.debug( + state.log.debug( 'received updateChannelTooLong for channel %d in common diff (pts = %d), fetching diff', upd.channelId, upd.pts, ) - _fetchChannelDifferenceLater.call(this, requestedDiff, upd.channelId, upd.pts) + fetchChannelDifferenceLater(client, state, requestedDiff, upd.channelId, upd.pts) return } - if (_isMessageEmpty(upd)) return + if (isMessageEmpty(upd)) return - const parsed = _toPendingUpdate(upd, peers) + const parsed = toPendingUpdate(upd, peers) if (parsed.channelId && parsed.ptsBefore) { // we need to check pts for these updates, put into pts queue - this._pendingPtsUpdates.add(parsed) + state.pendingPtsUpdates.add(parsed) } else { // the updates are in order already, we can treat them as unordered - this._pendingUnorderedUpdates.pushBack(parsed) + state.pendingUnorderedUpdates.pushBack(parsed) } - this._updsLog.debug( + state.log.debug( 'received %s from common diff, cid: %d, pts_before: %d, pts: %d, qts_before: %d', upd._, parsed.channelId, @@ -994,10 +840,10 @@ async function _fetchDifference(this: TelegramClient, requestedDiff: Map>): void { +function fetchDifferenceLater( + client: BaseTelegramClient, + state: UpdatesState, + requestedDiff: Map>, +): void { if (!requestedDiff.has(0)) { requestedDiff.set( 0, - _fetchDifference - .call(this, requestedDiff) + fetchDifference(client, state, requestedDiff) .catch((err) => { - this._updsLog.warn('error fetching common difference: %s', err) + state.log.warn('error fetching common difference: %s', err) }) .then(() => { requestedDiff.delete(0) @@ -1021,8 +870,9 @@ function _fetchDifferenceLater(this: TelegramClient, requestedDiff: Map>, postponed = false, @@ -1033,8 +883,8 @@ async function _onUpdate( // check for min peers, try to replace them // it is important to do this before updating pts if (pending.peers && pending.peers.hasMin) { - if (!(await _replaceMinPeers.call(this, pending.peers))) { - this._updsLog.debug( + if (!(await replaceMinPeers(client, pending.peers))) { + state.log.debug( 'fetching difference because some peers were min and not cached for %s (pts = %d, cid = %d)', upd._, pending.pts, @@ -1042,9 +892,9 @@ async function _onUpdate( ) if (pending.channelId) { - _fetchChannelDifferenceLater.call(this, requestedDiff, pending.channelId) + fetchChannelDifferenceLater(client, state, requestedDiff, pending.channelId) } else { - _fetchDifferenceLater.call(this, requestedDiff) + fetchDifferenceLater(client, state, requestedDiff) } return @@ -1053,10 +903,10 @@ async function _onUpdate( if (!pending.peers) { // this is a short update, we need to fetch the peers - const peers = await _fetchPeersForShort.call(this, upd) + const peers = await fetchPeersForShort(client, upd) if (!peers) { - this._updsLog.debug( + state.log.debug( 'fetching difference because some peers were not available for short %s (pts = %d, cid = %d)', upd._, pending.pts, @@ -1064,9 +914,9 @@ async function _onUpdate( ) if (pending.channelId) { - _fetchChannelDifferenceLater.call(this, requestedDiff, pending.channelId) + fetchChannelDifferenceLater(client, state, requestedDiff, pending.channelId) } else { - _fetchDifferenceLater.call(this, requestedDiff) + fetchDifferenceLater(client, state, requestedDiff) } return @@ -1079,10 +929,10 @@ async function _onUpdate( // because unordered may contain pts/qts values when received from diff if (pending.pts) { - const localPts = pending.channelId ? this._cpts.get(pending.channelId) : this._pts + const localPts = pending.channelId ? state.cpts.get(pending.channelId) : state.pts if (localPts && pending.ptsBefore !== localPts) { - this._updsLog.warn( + state.log.warn( 'pts_before does not match local_pts for %s (cid = %d, pts_before = %d, pts = %d, local_pts = %d)', upd._, pending.channelId, @@ -1092,7 +942,7 @@ async function _onUpdate( ) } - this._updsLog.debug( + state.log.debug( 'applying new pts (cid = %d) because received %s: %d -> %d (before: %d, count: %d) (postponed = %s)', pending.channelId, upd._, @@ -1104,29 +954,29 @@ async function _onUpdate( ) if (pending.channelId) { - this._cpts.set(pending.channelId, pending.pts) - this._cptsMod.set(pending.channelId, pending.pts) + state.cpts.set(pending.channelId, pending.pts) + state.cptsMod.set(pending.channelId, pending.pts) } else { - this._pts = pending.pts + state.pts = pending.pts } } if (pending.qtsBefore) { - this._updsLog.debug( + state.log.debug( 'applying new qts because received %s: %d -> %d (postponed = %s)', upd._, - this._qts, + state.qts, pending.qtsBefore + 1, postponed, ) - this._qts = pending.qtsBefore + 1 + state.qts = pending.qts } } - if (_isMessageEmpty(upd)) return + if (isMessageEmpty(upd)) return - this._rpsProcessing?.hit() + state.rpsProcessing?.hit() // updates that are also used internally switch (upd._) { @@ -1134,102 +984,103 @@ async function _onUpdate( // we just needed to apply new pts values return case 'updateDcOptions': { - const config = this.network.config.getNow() + const config = client.network.config.getNow() if (config) { - this.network.config.setConfig({ + client.network.config.setConfig({ ...config, dcOptions: upd.dcOptions, }) } else { - await this.network.config.update(true) + await client.network.config.update(true) } break } case 'updateConfig': - await this.network.config.update(true) + await client.network.config.update(true) break case 'updateUserName': - if (upd.userId === this._userId) { - this._selfUsername = upd.usernames.find((it) => it.active)?.username ?? null + if (upd.userId === state.auth.userId) { + state.auth.selfUsername = upd.usernames.find((it) => it.active)?.username ?? null } break } // dispatch the update - if (this._noDispatchEnabled) { + if (state.noDispatchEnabled) { const channelId = pending.channelId ?? 0 const msgId = upd._ === 'updateNewMessage' || upd._ === 'updateNewChannelMessage' ? upd.message.id : undefined // we first need to remove it from each index, and then check if it was there - const foundByMsgId = msgId && this._noDispatchMsg.get(channelId)?.delete(msgId) - const foundByPts = this._noDispatchPts.get(channelId)?.delete(pending.pts!) - const foundByQts = this._noDispatchQts.delete(pending.qts!) + const foundByMsgId = msgId && state.noDispatchMsg.get(channelId)?.delete(msgId) + const foundByPts = state.noDispatchPts.get(channelId)?.delete(pending.pts!) + const foundByQts = state.noDispatchQts.delete(pending.qts!) if (foundByMsgId || foundByPts || foundByQts) { - this._updsLog.debug('not dispatching %s because it is in no_dispatch index', upd._) + state.log.debug('not dispatching %s because it is in no_dispatch index', upd._) return } } - this._updsLog.debug('dispatching %s (postponed = %s)', upd._, postponed) - this._dispatchUpdate(upd, pending.peers) + state.log.debug('dispatching %s (postponed = %s)', upd._, postponed) + state.handler(upd, pending.peers) } // todo: updateChannelTooLong with catchUpChannels disabled should not trigger getDifference (?) // todo: when min peer or similar use pts_before as base pts for channels +// todo: fetchDiff when Session loss on the server: the client receives a new session created notification -/** @internal */ -export async function _updatesLoop(this: TelegramClient): Promise { - const log = this._updsLog +async function updatesLoop(client: BaseTelegramClient, state: UpdatesState): Promise { + const { log } = state - log.debug('updates loop started, state available? %b', this._pts) + log.debug('updates loop started, state available? %b', state.pts) try { - if (!this._pts) { - await this._fetchUpdatesState() + if (!state.pts) { + await fetchUpdatesState(client, state) } - while (this._updatesLoopActive) { + while (state.updatesLoopActive) { if ( !( - this._pendingUpdateContainers.length || - this._pendingPtsUpdates.length || - this._pendingQtsUpdates.length || - this._pendingUnorderedUpdates.length + state.pendingUpdateContainers.length || + state.pendingPtsUpdates.length || + state.pendingQtsUpdates.length || + state.pendingUnorderedUpdates.length || + state.hasTimedoutPostponed ) ) { - await this._updatesLoopCv.wait() + await state.updatesLoopCv.wait() } - if (!this._updatesLoopActive) break + if (!state.updatesLoopActive) break log.debug( 'updates loop tick. pending containers: %d, pts: %d, pts_postponed: %d, qts: %d, qts_postponed: %d, unordered: %d', - this._pendingUpdateContainers.length, - this._pendingPtsUpdates.length, - this._pendingPtsUpdatesPostponed.length, - this._pendingQtsUpdates.length, - this._pendingQtsUpdatesPostponed.length, - this._pendingUnorderedUpdates.length, + state.pendingUpdateContainers.length, + state.pendingPtsUpdates.length, + state.pendingPtsUpdatesPostponed.length, + state.pendingQtsUpdates.length, + state.pendingQtsUpdatesPostponed.length, + state.pendingUnorderedUpdates.length, ) const requestedDiff = new Map>() // first process pending containers - while (this._pendingUpdateContainers.length) { - const { upd, seqStart, seqEnd } = this._pendingUpdateContainers.popFront()! + while (state.pendingUpdateContainers.length) { + const { upd, seqStart, seqEnd } = state.pendingUpdateContainers.popFront()! switch (upd._) { case 'updatesTooLong': log.debug('received updatesTooLong, fetching difference') - _fetchDifferenceLater.call(this, requestedDiff) + fetchDifferenceLater(client, state, requestedDiff) break case 'updatesCombined': case 'updates': { if (seqStart !== 0) { // https://t.me/tdlibchat/5843 - const nextLocalSeq = this._seq! + 1 + const nextLocalSeq = state.seq! + 1 log.debug( 'received seq-ordered %s (seq_start = %d, seq_end = %d, size = %d)', upd._, @@ -1255,25 +1106,13 @@ export async function _updatesLoop(this: TelegramClient): Promise { seqStart, ) // "there's an updates gap that must be filled" - _fetchDifferenceLater.call(this, requestedDiff) + fetchDifferenceLater(client, state, requestedDiff) } } else { log.debug('received %s (size = %d)', upd._, upd.updates.length) } - await this._cachePeersFrom(upd) - // if (hasMin) { - // if (!( - // await _replaceMinPeers.call(this, upd) - // )) { - // log.debug( - // 'fetching difference because some peers were min and not cached' - // ) - // // some min peer is not cached. - // // need to re-fetch the thing, and cache them on the way - // await _fetchDifference.call(this) - // } - // } + await client._cachePeersFrom(upd) const peers = PeersIndex.from(upd) @@ -1284,24 +1123,24 @@ export async function _updatesLoop(this: TelegramClient): Promise { update.channelId, update.pts, ) - _fetchChannelDifferenceLater.call(this, requestedDiff, update.channelId, update.pts) + fetchChannelDifferenceLater(client, state, requestedDiff, update.channelId, update.pts) continue } - const parsed = _toPendingUpdate(update, peers) + const parsed = toPendingUpdate(update, peers) if (parsed.ptsBefore !== undefined) { - this._pendingPtsUpdates.add(parsed) + state.pendingPtsUpdates.add(parsed) } else if (parsed.qtsBefore !== undefined) { - this._pendingQtsUpdates.add(parsed) + state.pendingQtsUpdates.add(parsed) } else { - this._pendingUnorderedUpdates.pushBack(parsed) + state.pendingUnorderedUpdates.pushBack(parsed) } } - if (seqEnd !== 0 && seqEnd > this._seq!) { - this._seq = seqEnd - this._date = upd.date + if (seqEnd !== 0 && seqEnd > state.seq!) { + state.seq = seqEnd + state.date = upd.date } break @@ -1309,14 +1148,14 @@ export async function _updatesLoop(this: TelegramClient): Promise { case 'updateShort': { log.debug('received short %s', upd._) - const parsed = _toPendingUpdate(upd.update) + const parsed = toPendingUpdate(upd.update) if (parsed.ptsBefore !== undefined) { - this._pendingPtsUpdates.add(parsed) + state.pendingPtsUpdates.add(parsed) } else if (parsed.qtsBefore !== undefined) { - this._pendingQtsUpdates.add(parsed) + state.pendingQtsUpdates.add(parsed) } else { - this._pendingUnorderedUpdates.pushBack(parsed) + state.pendingUnorderedUpdates.pushBack(parsed) } break @@ -1333,7 +1172,7 @@ export async function _updatesLoop(this: TelegramClient): Promise { id: upd.id, fromId: { _: 'peerUser', - userId: upd.out ? this._userId! : upd.userId, + userId: upd.out ? state.auth.userId! : upd.userId, }, peerId: { _: 'peerUser', @@ -1355,7 +1194,7 @@ export async function _updatesLoop(this: TelegramClient): Promise { ptsCount: upd.ptsCount, } - this._pendingPtsUpdates.add({ + state.pendingPtsUpdates.add({ update, ptsBefore: upd.pts - upd.ptsCount, pts: upd.pts, @@ -1397,7 +1236,7 @@ export async function _updatesLoop(this: TelegramClient): Promise { ptsCount: upd.ptsCount, } - this._pendingPtsUpdates.add({ + state.pendingPtsUpdates.add({ update, ptsBefore: upd.pts - upd.ptsCount, pts: upd.pts, @@ -1416,18 +1255,18 @@ export async function _updatesLoop(this: TelegramClient): Promise { } // process pts-ordered updates - while (this._pendingPtsUpdates.length) { - const pending = this._pendingPtsUpdates.popFront()! + while (state.pendingPtsUpdates.length) { + const pending = state.pendingPtsUpdates.popFront()! const upd = pending.update // check pts let localPts: number | null = null - if (!pending.channelId) localPts = this._pts! - else if (this._cpts.has(pending.channelId)) { - localPts = this._cpts.get(pending.channelId)! - } else if (this._catchUpChannels) { + if (!pending.channelId) localPts = state.pts! + else if (state.cpts.has(pending.channelId)) { + localPts = state.cpts.get(pending.channelId)! + } else if (state.catchUpChannels) { // only load stored channel pts in case // the user has enabled catching up. // not loading stored pts effectively disables @@ -1435,16 +1274,20 @@ export async function _updatesLoop(this: TelegramClient): Promise { // update gaps (i.e. first update received is considered // to be the base state) - const saved = await this.storage.getChannelPts(pending.channelId) + const saved = await client.storage.getChannelPts(pending.channelId) if (saved) { - this._cpts.set(pending.channelId, saved) + state.cpts.set(pending.channelId, saved) localPts = saved } } if (localPts) { - if (localPts > pending.ptsBefore!) { + const diff = localPts - pending.ptsBefore! + // PTS can only go up or drop cardinally + const isPtsDrop = diff > 1000009 + + if (diff > 0 && !isPtsDrop) { // "the update was already applied, and must be ignored" log.debug( 'ignoring %s (cid = %d) because already applied (by pts: exp %d, got %d)', @@ -1455,53 +1298,75 @@ export async function _updatesLoop(this: TelegramClient): Promise { ) continue } - if (localPts < pending.ptsBefore!) { + if (diff < 0) { // "there's an update gap that must be filled" // if the gap is less than 3, put the update into postponed queue // otherwise, call getDifference - if (pending.ptsBefore! - localPts < 3) { + if (diff > -3) { log.debug( - 'postponing %s for 0.5s (cid = %d) because small gap detected (by pts: exp %d, got %d)', + 'postponing %s for 0.5s (cid = %d) because small gap detected (by pts: exp %d, got %d, diff=%d)', upd._, pending.channelId, localPts, pending.ptsBefore, + diff, ) - pending.timeout = Date.now() + 700 - this._pendingPtsUpdatesPostponed.add(pending) - } else { + pending.timeout = Date.now() + 500 + state.pendingPtsUpdatesPostponed.add(pending) + state.postponedTimer.emitBefore(pending.timeout) + } else if (diff > -1000000) { log.debug( - 'fetching difference after %s (cid = %d) because pts gap detected (by pts: exp %d, got %d)', + 'fetching difference after %s (cid = %d) because pts gap detected (by pts: exp %d, got %d, diff=%d)', upd._, pending.channelId, localPts, pending.ptsBefore, + diff, ) if (pending.channelId) { - _fetchChannelDifferenceLater.call(this, requestedDiff, pending.channelId) + fetchChannelDifferenceLater(client, state, requestedDiff, pending.channelId) } else { - _fetchDifferenceLater.call(this, requestedDiff) + fetchDifferenceLater(client, state, requestedDiff) + } + } else { + log.debug( + 'skipping all updates because pts gap is too big (by pts: exp %d, got %d, diff=%d)', + localPts, + pending.ptsBefore, + diff, + ) + + if (pending.channelId) { + state.cpts.set(pending.channelId, 0) + state.cptsMod.set(pending.channelId, 0) + } else { + await fetchUpdatesState(client, state) } } continue } + + if (isPtsDrop) { + log.debug('pts drop detected (%d -> %d)', localPts, pending.ptsBefore) + } } - await _onUpdate.call(this, pending, requestedDiff) + await onUpdate(client, state, pending, requestedDiff) } // process postponed pts-ordered updates - for (let item = this._pendingPtsUpdatesPostponed._first; item; item = item.n) { + for (let item = state.pendingPtsUpdatesPostponed._first; item; item = item.n) { // awesome fucking iteration because i'm so fucking tired and wanna kms const pending = item.v + const upd = pending.update let localPts - if (!pending.channelId) localPts = this._pts! - else if (this._cpts.has(pending.channelId)) { - localPts = this._cpts.get(pending.channelId) + if (!pending.channelId) localPts = state.pts! + else if (state.cpts.has(pending.channelId)) { + localPts = state.cpts.get(pending.channelId) } // channel pts from storage will be available because we loaded it earlier @@ -1520,7 +1385,7 @@ export async function _updatesLoop(this: TelegramClient): Promise { localPts, pending.ptsBefore, ) - this._pendingPtsUpdatesPostponed._remove(item) + state.pendingPtsUpdatesPostponed._remove(item) continue } if (localPts < pending.ptsBefore!) { @@ -1546,85 +1411,93 @@ export async function _updatesLoop(this: TelegramClient): Promise { localPts, pending.ptsBefore, ) - this._pendingPtsUpdatesPostponed._remove(item) + state.pendingPtsUpdatesPostponed._remove(item) if (pending.channelId) { - _fetchChannelDifferenceLater.call(this, requestedDiff, pending.channelId) + fetchChannelDifferenceLater(client, state, requestedDiff, pending.channelId) } else { - _fetchDifferenceLater.call(this, requestedDiff) + fetchDifferenceLater(client, state, requestedDiff) } } continue } - await _onUpdate.call(this, pending, requestedDiff, true) - this._pendingPtsUpdatesPostponed._remove(item) + await onUpdate(client, state, pending, requestedDiff, true) + state.pendingPtsUpdatesPostponed._remove(item) } // process qts-ordered updates - while (this._pendingQtsUpdates.length) { - const pending = this._pendingQtsUpdates.popFront()! + while (state.pendingQtsUpdates.length) { + const pending = state.pendingQtsUpdates.popFront()! const upd = pending.update // check qts + const diff = state.qts! - pending.qtsBefore! + const isQtsDrop = diff > 1000009 - if (this._qts! > pending.qtsBefore!) { + if (diff > 0 && !isQtsDrop) { // "the update was already applied, and must be ignored" log.debug( 'ignoring %s because already applied (by qts: exp %d, got %d)', upd._, - this._qts!, + state.qts!, pending.qtsBefore, ) continue } - if (this._qts! < pending.qtsBefore!) { + if (state.qts! < pending.qtsBefore!) { // "there's an update gap that must be filled" // if the gap is less than 3, put the update into postponed queue // otherwise, call getDifference - // - if (pending.qtsBefore! - this._qts! < 3) { + if (diff > -3) { log.debug( - 'postponing %s for 0.5s because small gap detected (by qts: exp %d, got %d)', + 'postponing %s for 0.5s because small gap detected (by qts: exp %d, got %d, diff=%d)', upd._, - this._qts!, + state.qts!, pending.qtsBefore, + diff, ) - pending.timeout = Date.now() + 700 - this._pendingQtsUpdatesPostponed.add(pending) + pending.timeout = Date.now() + 500 + state.pendingQtsUpdatesPostponed.add(pending) + state.postponedTimer.emitBefore(pending.timeout) } else { log.debug( - 'fetching difference after %s because qts gap detected (by qts: exp %d, got %d)', + 'fetching difference after %s because qts gap detected (by qts: exp %d, got %d, diff=%d)', upd._, - this._qts!, + state.qts!, pending.qtsBefore, + diff, ) - _fetchDifferenceLater.call(this, requestedDiff) + fetchDifferenceLater(client, state, requestedDiff) } continue } - await _onUpdate.call(this, pending, requestedDiff) + if (isQtsDrop) { + log.debug('qts drop detected (%d -> %d)', state.qts, pending.qtsBefore) + } + + await onUpdate(client, state, pending, requestedDiff) } // process postponed qts-ordered updates - for (let item = this._pendingQtsUpdatesPostponed._first; item; item = item.n) { + for (let item = state.pendingQtsUpdatesPostponed._first; item; item = item.n) { // awesome fucking iteration because i'm so fucking tired and wanna kms const pending = item.v const upd = pending.update // check the pts to see if the gap was filled - if (this._qts! > pending.qtsBefore!) { + if (state.qts! > pending.qtsBefore!) { // "the update was already applied, and must be ignored" log.debug( 'ignoring postponed %s because already applied (by qts: exp %d, got %d)', upd._, - this._qts!, + state.qts!, pending.qtsBefore, ) continue } - if (this._qts! < pending.qtsBefore!) { + if (state.qts! < pending.qtsBefore!) { // "there's an update gap that must be filled" // if the timeout has not expired yet, keep the update in the queue // otherwise, fetch diff @@ -1635,27 +1508,29 @@ export async function _updatesLoop(this: TelegramClient): Promise { 'postponed %s is still waiting (%dms left) (current qts %d, need %d)', upd._, pending.timeout! - now, - this._qts!, + state.qts!, pending.qtsBefore, ) } else { log.debug( "gap for postponed %s wasn't filled, fetching diff (current qts %d, need %d)", upd._, - this._qts!, + state.qts!, pending.qtsBefore, ) - this._pendingQtsUpdatesPostponed._remove(item) - _fetchDifferenceLater.call(this, requestedDiff) + state.pendingQtsUpdatesPostponed._remove(item) + fetchDifferenceLater(client, state, requestedDiff) } continue } // gap was filled, and the update can be applied - await _onUpdate.call(this, pending, requestedDiff, true) - this._pendingQtsUpdatesPostponed._remove(item) + await onUpdate(client, state, pending, requestedDiff, true) + state.pendingQtsUpdatesPostponed._remove(item) } + state.hasTimedoutPostponed = false + // wait for all pending diffs to load while (requestedDiff.size) { log.debug( @@ -1664,9 +1539,6 @@ export async function _updatesLoop(this: TelegramClient): Promise { requestedDiff.keys(), ) - // is this necessary? - // this.primaryConnection._flushSendQueue() - await Promise.all([...requestedDiff.values()]) // diff results may as well contain new diffs to be requested @@ -1678,10 +1550,10 @@ export async function _updatesLoop(this: TelegramClient): Promise { } // process unordered updates (or updates received from diff) - while (this._pendingUnorderedUpdates.length) { - const pending = this._pendingUnorderedUpdates.popFront()! + while (state.pendingUnorderedUpdates.length) { + const pending = state.pendingUnorderedUpdates.popFront()! - await _onUpdate.call(this, pending, requestedDiff, false, true) + await onUpdate(client, state, pending, requestedDiff, false, true) } // onUpdate may also call getDiff in some cases, so we also need to check @@ -1694,9 +1566,6 @@ export async function _updatesLoop(this: TelegramClient): Promise { requestedDiff.keys(), ) - // is this necessary? - // this.primaryConnection._flushSendQueue() - await Promise.all([...requestedDiff.values()]) // diff results may as well contain new diffs to be requested @@ -1708,18 +1577,12 @@ export async function _updatesLoop(this: TelegramClient): Promise { } // save new update state - await this._saveStorage() + await saveUpdatesStorage(client, state, true) } log.debug('updates loop stopped') } catch (e) { log.error('updates loop encountered error, restarting: %s', e) - this._updatesLoop().catch((err) => this._emitError(err)) + updatesLoop(client, state).catch((err) => client._emitError(err)) } } - -/** @internal */ -export function _keepAliveAction(this: TelegramClient): void { - this._updsLog.debug('no updates for >15 minutes, catching up') - this._handleUpdate({ _: 'updatesTooLong' }) -} diff --git a/packages/client/src/methods/updates/parsed.ts b/packages/client/src/methods/updates/parsed.ts new file mode 100644 index 00000000..74e7bfed --- /dev/null +++ b/packages/client/src/methods/updates/parsed.ts @@ -0,0 +1,82 @@ +import { Message } from '../../types/messages' +import { ParsedUpdate } from '../../types/updates/index' +import { _parseUpdate } from '../../types/updates/parse-update' +import { RawUpdateHandler } from './types' + +export interface ParsedUpdateHandlerParams { + /** + * When non-zero, allows the library to automatically handle Telegram + * media groups (e.g. albums) in {@link MessageGroup} updates + * in a given time interval (in ms). + * + * > **Note**: this does not catch messages that happen to be consecutive, + * > only messages belonging to the same "media group". + * + * This will cause up to `messageGroupingInterval` delay + * in handling media group messages. + * + * This option only applies to `new_message` updates, + * and the updates being grouped **will not** be dispatched on their own. + * + * Recommended value is 250 ms. + * + * @default 0 (disabled) + */ + messageGroupingInterval?: number + + /** Handler for parsed updates */ + onUpdate: (update: ParsedUpdate) => void + /** + * Handler for raw updates. + * + * Note that this handler will be called **before** the parsed update handler. + */ + onRawUpdate?: RawUpdateHandler +} + +export function makeParsedUpdateHandler(params: ParsedUpdateHandlerParams): RawUpdateHandler { + const { messageGroupingInterval, onUpdate, onRawUpdate = () => {} } = params + + if (!messageGroupingInterval) { + return (update, peers) => { + const parsed = _parseUpdate(update, peers) + + onRawUpdate(update, peers) + if (parsed) onUpdate(parsed) + } + } + + const pending = new Map() + + return (update, peers) => { + const parsed = _parseUpdate(update, peers) + + onRawUpdate(update, peers) + + if (parsed) { + if (parsed.name === 'new_message') { + const group = parsed.data.groupedIdUnique + + if (group) { + const pendingGroup = pending.get(group) + + if (pendingGroup) { + pendingGroup[0].push(parsed.data) + } else { + const messages = [parsed.data] + const timeout = setTimeout(() => { + pending.delete(group) + onUpdate({ name: 'message_group', data: messages }) + }, messageGroupingInterval) + + pending.set(group, [messages, timeout]) + } + + return + } + } + + onUpdate(parsed) + } + } +} diff --git a/packages/client/src/methods/updates/types.ts b/packages/client/src/methods/updates/types.ts new file mode 100644 index 00000000..a09d8341 --- /dev/null +++ b/packages/client/src/methods/updates/types.ts @@ -0,0 +1,197 @@ +import { BaseTelegramClient, tl } from '@mtcute/core' +import { AsyncLock, ConditionVariable, Deque, EarlyTimer, Logger, SortedLinkedList } from '@mtcute/core/utils' + +import { PeersIndex } from '../../types' +import { RpsMeter } from '../../utils' +import { AuthState } from '../auth/_state' +import { extractChannelIdFromUpdate } from './utils' + +/** + * Function to be called for each update. + * + * @param upd The update + * @param peers Peers that are present in the update + */ +export type RawUpdateHandler = (upd: tl.TypeUpdate, peers: PeersIndex) => void + +/** + * Parameters for {@link enableUpdatesProcessing} + */ +export interface UpdatesManagerParams { + /** + * **ADVANCED** + * + * Whether to disable no-dispatch mechanism. + * + * No-dispatch is a mechanism that allows you to call methods + * that return updates and correctly handle them, without + * actually dispatching them to the event handlers. + * + * In other words, the following code will work differently: + * ```ts + * dp.onNewMessage(console.log) + * console.log(await tg.sendText('me', 'hello')) + * ``` + * - if `disableNoDispatch` is `true`, the sent message will be + * dispatched to the event handler, thus it will be printed twice + * - if `disableNoDispatch` is `false`, the sent message will not be + * dispatched to the event handler, thus it will onlt be printed once + * + * Disabling it may also improve performance, but it's not guaranteed. + * + * @default false + */ + disableNoDispatch?: boolean + + /** + * Whether to catch up with missed updates when starting updates loop. + * + * > **Note**: In case the storage was not properly closed the last time, + * > "catching up" might result in duplicate updates. + */ + catchUp?: boolean + + /** + * Handler for raw updates. + */ + onUpdate: RawUpdateHandler +} + +/** @internal */ +export interface PendingUpdateContainer { + upd: tl.TypeUpdates + seqStart: number + seqEnd: number +} + +/** @internal */ +export interface PendingUpdate { + update: tl.TypeUpdate + channelId?: number + pts?: number + ptsBefore?: number + qts?: number + qtsBefore?: number + timeout?: number + peers?: PeersIndex +} + +/** @internal */ +export interface UpdatesState { + updatesLoopActive: boolean + updatesLoopCv: ConditionVariable + + postponedTimer: EarlyTimer + hasTimedoutPostponed: boolean + + pendingUpdateContainers: SortedLinkedList + pendingPtsUpdates: SortedLinkedList + pendingPtsUpdatesPostponed: SortedLinkedList + pendingQtsUpdates: SortedLinkedList + pendingQtsUpdatesPostponed: SortedLinkedList + pendingUnorderedUpdates: Deque + + noDispatchEnabled: boolean + // channel id or 0 => msg id + noDispatchMsg: Map> + // channel id or 0 => pts + noDispatchPts: Map> + noDispatchQts: Set + + lock: AsyncLock + rpsIncoming?: RpsMeter + rpsProcessing?: RpsMeter + + // accessing storage every time might be expensive, + // so store everything here, and load & save + // every time session is loaded & saved. + pts?: number + qts?: number + date?: number + seq?: number + + // old values of the updates state (i.e. as in DB) + // used to avoid redundant storage calls + oldPts?: number + oldQts?: number + oldDate?: number + oldSeq?: number + selfChanged: boolean + + // whether to catch up channels from the locally stored pts + catchUpChannels: boolean + catchUpOnStart: boolean + + cpts: Map + cptsMod: Map + + log: Logger + stop: () => void + handler: RawUpdateHandler + auth: AuthState +} + +/** + * @internal + * @noemit + */ +export function createUpdatesState( + client: BaseTelegramClient, + authState: AuthState, + opts: UpdatesManagerParams, +): UpdatesState { + return { + updatesLoopActive: false, + updatesLoopCv: new ConditionVariable(), + postponedTimer: new EarlyTimer(), + hasTimedoutPostponed: false, + pendingUpdateContainers: new SortedLinkedList((a, b) => a.seqStart - b.seqStart), + pendingPtsUpdates: new SortedLinkedList((a, b) => a.ptsBefore! - b.ptsBefore!), + pendingPtsUpdatesPostponed: new SortedLinkedList((a, b) => a.ptsBefore! - b.ptsBefore!), + pendingQtsUpdates: new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!), + pendingQtsUpdatesPostponed: new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!), + pendingUnorderedUpdates: new Deque(), + noDispatchEnabled: !opts.disableNoDispatch, + noDispatchMsg: new Map(), + noDispatchPts: new Map(), + noDispatchQts: new Set(), + lock: new AsyncLock(), + pts: undefined, + qts: undefined, + date: undefined, + seq: undefined, + oldPts: undefined, + oldQts: undefined, + oldDate: undefined, + oldSeq: undefined, + rpsIncoming: undefined, + rpsProcessing: undefined, + selfChanged: false, + catchUpChannels: false, + catchUpOnStart: opts.catchUp ?? false, + cpts: new Map(), + cptsMod: new Map(), + log: client.log.create('updates'), + stop: () => {}, // will be set later + handler: opts.onUpdate, + auth: authState, + } +} + +export function toPendingUpdate(upd: tl.TypeUpdate, peers?: PeersIndex): PendingUpdate { + const channelId = extractChannelIdFromUpdate(upd) || 0 + const pts = 'pts' in upd ? upd.pts : undefined + // eslint-disable-next-line no-nested-ternary + const ptsCount = 'ptsCount' in upd ? upd.ptsCount : pts ? 0 : undefined + const qts = 'qts' in upd ? upd.qts : undefined + + return { + update: upd, + channelId, + pts, + ptsBefore: pts ? pts - ptsCount! : undefined, + qts, + qtsBefore: qts ? qts - 1 : undefined, + peers, + } +} diff --git a/packages/client/src/methods/updates/utils.ts b/packages/client/src/methods/updates/utils.ts new file mode 100644 index 00000000..6897d0f8 --- /dev/null +++ b/packages/client/src/methods/updates/utils.ts @@ -0,0 +1,42 @@ +import { tl } from '@mtcute/core' + +export function messageToUpdate(message: tl.TypeMessage): tl.TypeUpdate { + switch (message.peerId!._) { + case 'peerUser': + case 'peerChat': + return { + _: 'updateNewMessage', + message, + pts: 0, + ptsCount: 0, + } + case 'peerChannel': + return { + _: 'updateNewChannelMessage', + message, + pts: 0, + ptsCount: 0, + } + } +} + +export function extractChannelIdFromUpdate(upd: tl.TypeUpdate): number | undefined { + // holy shit + let res = 0 + + if ('channelId' in upd) { + res = upd.channelId + } else if ( + 'message' in upd && + typeof upd.message !== 'string' && + 'peerId' in upd.message && + upd.message.peerId && + 'channelId' in upd.message.peerId + ) { + res = upd.message.peerId.channelId + } + + if (res === 0) return undefined + + return res +} diff --git a/packages/client/src/methods/users/block-user.ts b/packages/client/src/methods/users/block-user.ts index e4a3c84b..fc8dd872 100644 --- a/packages/client/src/methods/users/block-user.ts +++ b/packages/client/src/methods/users/block-user.ts @@ -1,15 +1,16 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from './resolve-peer' /** * Block a user * * @param id User ID, username or phone number - * @internal */ -export async function blockUser(this: TelegramClient, id: InputPeerLike): Promise { - await this.call({ +export async function blockUser(client: BaseTelegramClient, id: InputPeerLike): Promise { + await client.call({ _: 'contacts.block', - id: await this.resolvePeer(id), + id: await resolvePeer(client, id), }) } diff --git a/packages/client/src/methods/users/delete-profile-photos.ts b/packages/client/src/methods/users/delete-profile-photos.ts index 0f942172..d1bb3861 100644 --- a/packages/client/src/methods/users/delete-profile-photos.ts +++ b/packages/client/src/methods/users/delete-profile-photos.ts @@ -1,16 +1,13 @@ -import { MaybeArray, tl } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray, tl } from '@mtcute/core' import { fileIdToInputPhoto } from '@mtcute/file-id' -import { TelegramClient } from '../../client' - /** * Delete your own profile photos * * @param ids ID(s) of the photos. Can be file IDs or raw TL objects - * @internal */ export async function deleteProfilePhotos( - this: TelegramClient, + client: BaseTelegramClient, ids: MaybeArray, ): Promise { if (!Array.isArray(ids)) ids = [ids] @@ -23,7 +20,7 @@ export async function deleteProfilePhotos( return id }) - await this.call({ + await client.call({ _: 'photos.deletePhotos', id: photos, }) diff --git a/packages/client/src/methods/users/edit-close-friends.ts b/packages/client/src/methods/users/edit-close-friends.ts index af6a4624..86baff6c 100644 --- a/packages/client/src/methods/users/edit-close-friends.ts +++ b/packages/client/src/methods/users/edit-close-friends.ts @@ -1,15 +1,16 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' import { normalizeToInputUser } from '../../utils' +import { resolvePeerMany } from './resolve-peer-many' /** * Edit "close friends" list directly using user IDs * * @param ids User IDs - * @internal */ -export async function editCloseFriendsRaw(this: TelegramClient, ids: number[]): Promise { - await this.call({ +export async function editCloseFriendsRaw(client: BaseTelegramClient, ids: number[]): Promise { + await client.call({ _: 'contacts.editCloseFriends', id: ids, }) @@ -19,12 +20,11 @@ export async function editCloseFriendsRaw(this: TelegramClient, ids: number[]): * Edit "close friends" list using `InputPeerLike`s * * @param ids User IDs - * @internal */ -export async function editCloseFriends(this: TelegramClient, ids: InputPeerLike[]): Promise { - await this.call({ +export async function editCloseFriends(client: BaseTelegramClient, ids: InputPeerLike[]): Promise { + await client.call({ _: 'contacts.editCloseFriends', - id: await this.resolvePeerMany(ids, normalizeToInputUser).then((r) => + id: await resolvePeerMany(client, ids, normalizeToInputUser).then((r) => r.map((u) => { if ('userId' in u) return u.userId diff --git a/packages/client/src/methods/users/get-common-chats.ts b/packages/client/src/methods/users/get-common-chats.ts index 3da48881..b97eaae6 100644 --- a/packages/client/src/methods/users/get-common-chats.ts +++ b/packages/client/src/methods/users/get-common-chats.ts @@ -1,19 +1,22 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { Chat, InputPeerLike } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from './resolve-peer' /** * Get a list of common chats you have with a given user * * @param userId User's ID, username or phone number * @throws MtInvalidPeerTypeError - * @internal */ -export async function getCommonChats(this: TelegramClient, userId: InputPeerLike): Promise { - return this.call({ - _: 'messages.getCommonChats', - userId: normalizeToInputUser(await this.resolvePeer(userId), userId), - maxId: 0, - limit: 100, - }).then((res) => res.chats.map((it) => new Chat(this, it))) +export async function getCommonChats(client: BaseTelegramClient, userId: InputPeerLike): Promise { + return client + .call({ + _: 'messages.getCommonChats', + userId: normalizeToInputUser(await resolvePeer(client, userId), userId), + maxId: 0, + limit: 100, + }) + .then((res) => res.chats.map((it) => new Chat(it))) } diff --git a/packages/client/src/methods/users/get-global-ttl.ts b/packages/client/src/methods/users/get-global-ttl.ts index b64ce05f..76f54ed8 100644 --- a/packages/client/src/methods/users/get-global-ttl.ts +++ b/packages/client/src/methods/users/get-global-ttl.ts @@ -1,12 +1,12 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' /** * Gets the current default value of the Time-To-Live setting, applied to all new chats. - * - * @internal */ -export async function getGlobalTtl(this: TelegramClient): Promise { - return this.call({ - _: 'messages.getDefaultHistoryTTL', - }).then((r) => r.period) +export async function getGlobalTtl(client: BaseTelegramClient): Promise { + return client + .call({ + _: 'messages.getDefaultHistoryTTL', + }) + .then((r) => r.period) } diff --git a/packages/client/src/methods/users/get-me.ts b/packages/client/src/methods/users/get-me.ts index 258d8e6a..f25da618 100644 --- a/packages/client/src/methods/users/get-me.ts +++ b/packages/client/src/methods/users/get-me.ts @@ -1,37 +1,41 @@ +import { BaseTelegramClient } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { User } from '../../types' +import { getAuthState } from '../auth/_state' /** * Get currently authorized user's full information - * - * @internal */ -export function getMe(this: TelegramClient): Promise { - return this.call({ - _: 'users.getUsers', - id: [ - { - _: 'inputUserSelf', - }, - ], - }).then(async ([user]) => { - assertTypeIs('getMe (@ users.getUsers)', user, 'user') +export function getMe(client: BaseTelegramClient): Promise { + return client + .call({ + _: 'users.getUsers', + id: [ + { + _: 'inputUserSelf', + }, + ], + }) + .then(async ([user]) => { + assertTypeIs('getMe (@ users.getUsers)', user, 'user') - if (this._userId !== user.id) { - // there is such possibility, e.g. when - // using a string session without `self`, - // or logging out and re-logging in - // we need to update the fields accordingly, - // and force-save the session - this._userId = user.id - this._isBot = Boolean(user.bot) - await this._saveStorage() - } + const authState = getAuthState(client) - this._selfUsername = user.username ?? null + if (authState.userId !== user.id) { + // there is such possibility, e.g. when + // using a string session without `self`, + // or logging out and re-logging in + // we need to update the fields accordingly, + // and force-save the session + authState.userId = user.id + authState.isBot = Boolean(user.bot) + authState.selfChanged = true + await client.saveStorage() + } - return new User(this, user) - }) + authState.selfUsername = user.username ?? null + + return new User(user) + }) } diff --git a/packages/client/src/methods/users/get-my-username.ts b/packages/client/src/methods/users/get-my-username.ts index 430f5a72..6b10e7ea 100644 --- a/packages/client/src/methods/users/get-my-username.ts +++ b/packages/client/src/methods/users/get-my-username.ts @@ -1,13 +1,13 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + +import { getAuthState } from '../auth/_state' /** * Get currently authorized user's username. * * This method uses locally available information and * does not call any API methods. - * - * @internal */ -export function getMyUsername(this: TelegramClient): string | null { - return this._selfUsername +export function getMyUsername(client: BaseTelegramClient): string | null { + return getAuthState(client).selfUsername } diff --git a/packages/client/src/methods/users/get-profile-photo.ts b/packages/client/src/methods/users/get-profile-photo.ts index f0073e6e..e957fbc3 100644 --- a/packages/client/src/methods/users/get-profile-photo.ts +++ b/packages/client/src/methods/users/get-profile-photo.ts @@ -1,9 +1,9 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { InputPeerLike, Photo } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from './resolve-peer' /** * Get a single profile picture of a user by its ID @@ -11,12 +11,15 @@ import { normalizeToInputUser } from '../../utils/peer-utils' * @param userId User ID, username, phone number, `"me"` or `"self"` * @param photoId ID of the photo to fetch * @param params - * @internal */ -export async function getProfilePhoto(this: TelegramClient, userId: InputPeerLike, photoId: tl.Long): Promise { - const res = await this.call({ +export async function getProfilePhoto( + client: BaseTelegramClient, + userId: InputPeerLike, + photoId: tl.Long, +): Promise { + const res = await client.call({ _: 'photos.getUserPhotos', - userId: normalizeToInputUser(await this.resolvePeer(userId), userId), + userId: normalizeToInputUser(await resolvePeer(client, userId), userId), offset: -1, limit: 1, maxId: photoId, @@ -25,5 +28,5 @@ export async function getProfilePhoto(this: TelegramClient, userId: InputPeerLik const photo = res.photos[0] assertTypeIs('getProfilePhotos', photo, 'photo') - return new Photo(this, photo) + return new Photo(photo) } diff --git a/packages/client/src/methods/users/get-profile-photos.ts b/packages/client/src/methods/users/get-profile-photos.ts index 65b7ed0f..c5f4609e 100644 --- a/packages/client/src/methods/users/get-profile-photos.ts +++ b/packages/client/src/methods/users/get-profile-photos.ts @@ -1,20 +1,19 @@ -import { Long, tl } from '@mtcute/core' +import { BaseTelegramClient, Long, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { ArrayPaginated, InputPeerLike, Photo } from '../../types' import { makeArrayPaginated } from '../../utils' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeer } from './resolve-peer' /** * Get a list of profile pictures of a user * * @param userId User ID, username, phone number, `"me"` or `"self"` * @param params - * @internal */ export async function getProfilePhotos( - this: TelegramClient, + client: BaseTelegramClient, userId: InputPeerLike, params?: { /** @@ -36,9 +35,9 @@ export async function getProfilePhotos( const { offset = 0, limit = 100 } = params - const res = await this.call({ + const res = await client.call({ _: 'photos.getUserPhotos', - userId: normalizeToInputUser(await this.resolvePeer(userId), userId), + userId: normalizeToInputUser(await resolvePeer(client, userId), userId), offset, limit, maxId: Long.ZERO, @@ -48,7 +47,7 @@ export async function getProfilePhotos( res.photos.map((it) => { assertTypeIs('getProfilePhotos', it, 'photo') - return new Photo(this, it) + return new Photo(it) }), (res as tl.photos.RawPhotosSlice).count ?? res.photos.length, offset + res.photos.length, diff --git a/packages/client/src/methods/users/get-users.ts b/packages/client/src/methods/users/get-users.ts index 32c20a33..faaeb63d 100644 --- a/packages/client/src/methods/users/get-users.ts +++ b/packages/client/src/methods/users/get-users.ts @@ -1,16 +1,15 @@ -import { MaybeArray } from '@mtcute/core' +import { BaseTelegramClient, MaybeArray } from '@mtcute/core' -import { TelegramClient } from '../../client' import { InputPeerLike, User } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { resolvePeerMany } from './resolve-peer-many' /** * Get information about a single user. * * @param id User's identifier. Can be ID, username, phone number, `"me"` or `"self"` or TL object - * @internal */ -export async function getUsers(this: TelegramClient, id: InputPeerLike): Promise +export async function getUsers(client: BaseTelegramClient, id: InputPeerLike): Promise /** * Get information about multiple users. @@ -19,23 +18,22 @@ export async function getUsers(this: TelegramClient, id: InputPeerLike): Promise * Note that order is not guaranteed. * * @param ids Users' identifiers. Can be ID, username, phone number, `"me"`, `"self"` or TL object - * @internal */ -export async function getUsers(this: TelegramClient, ids: InputPeerLike[]): Promise +export async function getUsers(client: BaseTelegramClient, ids: InputPeerLike[]): Promise /** @internal */ -export async function getUsers(this: TelegramClient, ids: MaybeArray): Promise> { +export async function getUsers(client: BaseTelegramClient, ids: MaybeArray): Promise> { const isArray = Array.isArray(ids) if (!isArray) ids = [ids as InputPeerLike] - const inputPeers = await this.resolvePeerMany(ids as InputPeerLike[], normalizeToInputUser) + const inputPeers = await resolvePeerMany(client, ids as InputPeerLike[], normalizeToInputUser) - let res = await this.call({ + let res = await client.call({ _: 'users.getUsers', id: inputPeers, }) res = res.filter((it) => it._ !== 'userEmpty') - return isArray ? res.map((it) => new User(this, it)) : new User(this, res[0]) + return isArray ? res.map((it) => new User(it)) : new User(res[0]) } diff --git a/packages/client/src/methods/users/iter-profile-photos.ts b/packages/client/src/methods/users/iter-profile-photos.ts index 0885c6f7..ebb435ec 100644 --- a/packages/client/src/methods/users/iter-profile-photos.ts +++ b/packages/client/src/methods/users/iter-profile-photos.ts @@ -1,18 +1,20 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike, Photo } from '../../types' import { normalizeToInputUser } from '../../utils/peer-utils' +import { getProfilePhotos } from './get-profile-photos' +import { resolvePeer } from './resolve-peer' /** * Iterate over profile photos * * @param userId User ID, username, phone number, `"me"` or `"self"` * @param params - * @internal */ export async function* iterProfilePhotos( - this: TelegramClient, + client: BaseTelegramClient, userId: InputPeerLike, - params?: Parameters[1] & { + params?: Parameters[2] & { /** * Maximum number of items to fetch * @@ -30,7 +32,7 @@ export async function* iterProfilePhotos( ): AsyncIterableIterator { if (!params) params = {} - const peer = normalizeToInputUser(await this.resolvePeer(userId), userId) + const peer = normalizeToInputUser(await resolvePeer(client, userId), userId) const { limit = Infinity, chunkSize = 100 } = params @@ -38,7 +40,7 @@ export async function* iterProfilePhotos( let current = 0 for (;;) { - const res = await this.getProfilePhotos(peer, { + const res = await getProfilePhotos(client, peer, { offset, limit: Math.min(chunkSize, limit - current), }) diff --git a/packages/client/src/methods/users/resolve-peer-many.ts b/packages/client/src/methods/users/resolve-peer-many.ts index eee97033..d3cecc13 100644 --- a/packages/client/src/methods/users/resolve-peer-many.ts +++ b/packages/client/src/methods/users/resolve-peer-many.ts @@ -1,35 +1,21 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' import { ConditionVariable } from '@mtcute/core/utils' -import { TelegramClient, TelegramClientOptions } from '../../client' import { InputPeerLike } from '../../types' - -/* eslint-disable @typescript-eslint/no-unused-vars */ -// @extension -interface ResolvePeerManyExtension { - _resolvePeerManyPoolLimit: number -} - -// @initialize -function _resolvePeerManyInitializer(this: TelegramClient, opts: TelegramClientOptions) { - this._resolvePeerManyPoolLimit = opts.resolvePeerManyPoolLimit ?? 8 -} -/* eslint-enable @typescript-eslint/no-unused-vars */ +import { resolvePeer } from './resolve-peer' /** * Get multiple `InputPeer`s at once, * while also normalizing and removing * peers that can't be normalized to that type. * - * Uses async pool internally, with a - * configurable concurrent limit (see {@link TelegramClientOptions#resolvePeerManyPoolLimit}). + * Uses async pool internally, with a concurrent limit of 8 * * @param peerIds Peer Ids * @param normalizer Normalization function - * @internal */ export async function resolvePeerMany( - this: TelegramClient, + client: BaseTelegramClient, peerIds: InputPeerLike[], normalizer: (obj: tl.TypeInputPeer) => T | null, ): Promise @@ -37,30 +23,27 @@ export async function resolvePeerMany +export async function resolvePeerMany(client: BaseTelegramClient, peerIds: InputPeerLike[]): Promise /** * @internal */ export async function resolvePeerMany( - this: TelegramClient, + client: BaseTelegramClient, peerIds: InputPeerLike[], normalizer?: (obj: tl.TypeInputPeer) => tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel | null, ): Promise<(tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel)[]> { const ret: (tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel)[] = [] - const limit = this._resolvePeerManyPoolLimit + const limit = 8 if (peerIds.length < limit) { // no point in using async pool for this.resolvePeer(it))) + const res = await Promise.all(peerIds.map((it) => resolvePeer(client, it))) if (!normalizer) return res @@ -82,7 +65,7 @@ export async function resolvePeerMany( let nextWorkerIdx = 0 const fetchNext = async (idx = nextWorkerIdx++): Promise => { - const result = await this.resolvePeer(peerIds[idx]) + const result = await resolvePeer(client, peerIds[idx]) buffer[idx] = result @@ -96,15 +79,11 @@ export async function resolvePeerMany( } let error: unknown = undefined - void Promise.all(Array.from({ length: limit }, (_, i) => fetchNext(i))) - .catch((e) => { - this.log.debug('resolvePeerMany errored: %s', e.message) - error = e - cv.notify() - }) - .then(() => { - this.log.debug('resolvePeerMany finished') - }) + void Promise.all(Array.from({ length: limit }, (_, i) => fetchNext(i))).catch((e) => { + client.log.debug('resolvePeerMany errored: %s', e.message) + error = e + cv.notify() + }) while (nextIdx < peerIds.length) { await cv.wait() diff --git a/packages/client/src/methods/users/resolve-peer.ts b/packages/client/src/methods/users/resolve-peer.ts index 90ff15a5..7342c882 100644 --- a/packages/client/src/methods/users/resolve-peer.ts +++ b/packages/client/src/methods/users/resolve-peer.ts @@ -1,8 +1,16 @@ -import { getBasicPeerType, getMarkedPeerId, Long, MtTypeAssertionError, tl, toggleChannelIdMark } from '@mtcute/core' +import { + BaseTelegramClient, + getBasicPeerType, + getMarkedPeerId, + Long, + MtTypeAssertionError, + tl, + toggleChannelIdMark, +} from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' -import { InputPeerLike, MtPeerNotFoundError } from '../../types' +import { MtPeerNotFoundError } from '../../types/errors' +import { InputPeerLike } from '../../types/peers' import { normalizeToInputPeer } from '../../utils/peer-utils' // @available=both @@ -12,10 +20,9 @@ import { normalizeToInputPeer } from '../../utils/peer-utils' * * @param peerId The peer identifier that you want to extract the `InputPeer` from. * @param force Whether to force re-fetch the peer from the server - * @internal */ export async function resolvePeer( - this: TelegramClient, + client: BaseTelegramClient, peerId: InputPeerLike, force = false, ): Promise { @@ -29,7 +36,7 @@ export async function resolvePeer( } if (typeof peerId === 'number' && !force) { - const fromStorage = await this.storage.getPeerById(peerId) + const fromStorage = await client.storage.getPeerById(peerId) if (fromStorage) return fromStorage } @@ -40,10 +47,10 @@ export async function resolvePeer( if (peerId.match(/^\d+$/)) { // phone number - const fromStorage = await this.storage.getPeerByPhone(peerId) + const fromStorage = await client.storage.getPeerByPhone(peerId) if (fromStorage) return fromStorage - const res = await this.call({ + const res = await client.call({ _: 'contacts.getContacts', hash: Long.ZERO, }) @@ -64,11 +71,11 @@ export async function resolvePeer( } else { // username if (!force) { - const fromStorage = await this.storage.getPeerByUsername(peerId.toLowerCase()) + const fromStorage = await client.storage.getPeerByUsername(peerId.toLowerCase()) if (fromStorage) return fromStorage } - const res = await this.call({ + const res = await client.call({ _: 'contacts.resolveUsername', username: peerId, }) @@ -133,7 +140,7 @@ export async function resolvePeer( // try fetching by id, with access_hash set to 0 switch (peerType) { case 'user': { - const res = await this.call({ + const res = await client.call({ _: 'users.getUsers', id: [ { @@ -166,7 +173,7 @@ export async function resolvePeer( case 'chat': { // do we really need to make a call? // const id = -peerId - // const res = await this.call({ + // const res = await client.call({ // _: 'messages.getChats', // id: [id], // }) @@ -187,7 +194,7 @@ export async function resolvePeer( case 'channel': { const id = toggleChannelIdMark(peerId) - const res = await this.call({ + const res = await client.call({ _: 'channels.getChannels', id: [ { diff --git a/packages/client/src/methods/users/set-emoji-status.ts b/packages/client/src/methods/users/set-emoji-status.ts index edbdeb6b..58f2d078 100644 --- a/packages/client/src/methods/users/set-emoji-status.ts +++ b/packages/client/src/methods/users/set-emoji-status.ts @@ -1,16 +1,14 @@ -import { tl } from '@mtcute/core' +import { BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { normalizeDate } from '../../utils' /** * Set an emoji status for the current user * * @param emoji Custom emoji ID or `null` to remove the emoji - * @internal */ export async function setEmojiStatus( - this: TelegramClient, + client: BaseTelegramClient, emoji: tl.Long | null, params?: { /** @@ -38,7 +36,7 @@ export async function setEmojiStatus( } } - await this.call({ + await client.call({ _: 'account.updateEmojiStatus', emojiStatus, }) diff --git a/packages/client/src/methods/users/set-global-ttl.ts b/packages/client/src/methods/users/set-global-ttl.ts index 2e51fc3c..82438c50 100644 --- a/packages/client/src/methods/users/set-global-ttl.ts +++ b/packages/client/src/methods/users/set-global-ttl.ts @@ -1,14 +1,13 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' /** * Changes the current default value of the Time-To-Live setting, * applied to all new chats. * * @param period New TTL period, in seconds (or 0 to disable) - * @internal */ -export async function setGlobalTtl(this: TelegramClient, period: number): Promise { - await this.call({ +export async function setGlobalTtl(client: BaseTelegramClient, period: number): Promise { + await client.call({ _: 'messages.setDefaultHistoryTTL', period, }) diff --git a/packages/client/src/methods/users/set-offline.ts b/packages/client/src/methods/users/set-offline.ts index c0836e76..18e71ed6 100644 --- a/packages/client/src/methods/users/set-offline.ts +++ b/packages/client/src/methods/users/set-offline.ts @@ -1,13 +1,12 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' /** * Change user status to offline or online * * @param offline Whether the user is currently offline - * @internal */ -export async function setOffline(this: TelegramClient, offline = true): Promise { - await this.call({ +export async function setOffline(client: BaseTelegramClient, offline = true): Promise { + await client.call({ _: 'account.updateStatus', offline, }) diff --git a/packages/client/src/methods/users/set-profile-photo.ts b/packages/client/src/methods/users/set-profile-photo.ts index 2023869e..f8ab09ce 100644 --- a/packages/client/src/methods/users/set-profile-photo.ts +++ b/packages/client/src/methods/users/set-profile-photo.ts @@ -1,18 +1,16 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' import { fileIdToInputPhoto, tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { InputFileLike, Photo } from '../../types' +import { _normalizeInputFile } from '../files/normalize-input-file' /** * Set a new profile photo or video. * * You can also pass a file ID or an InputPhoto to re-use existing photo. - * - * @internal */ export async function setProfilePhoto( - this: TelegramClient, + client: BaseTelegramClient, params: { /** Media type (photo or video) */ type: 'photo' | 'video' @@ -36,20 +34,20 @@ export async function setProfilePhoto( media = fileIdToInputPhoto(media) } - const res = await this.call({ + const res = await client.call({ _: 'photos.updateProfilePhoto', id: media, }) - return new Photo(this, res.photo as tl.RawPhoto) + return new Photo(res.photo as tl.RawPhoto) } } - const res = await this.call({ + const res = await client.call({ _: 'photos.uploadProfilePhoto', - [type === 'photo' ? 'file' : 'video']: await this._normalizeInputFile(media, {}), + [type === 'photo' ? 'file' : 'video']: await _normalizeInputFile(client, media, {}), videoStartTs: previewSec, }) - return new Photo(this, res.photo as tl.RawPhoto) + return new Photo(res.photo as tl.RawPhoto) } diff --git a/packages/client/src/methods/users/set-username.ts b/packages/client/src/methods/users/set-username.ts index dbb8f559..619b53ce 100644 --- a/packages/client/src/methods/users/set-username.ts +++ b/packages/client/src/methods/users/set-username.ts @@ -1,5 +1,7 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { User } from '../../types' +import { getAuthState } from '../auth/_state' /** * Change username of the current user. @@ -8,17 +10,16 @@ import { User } from '../../types' * bot support or re-created from scratch. * * @param username New username (5-32 chars, allowed chars: `a-zA-Z0-9_`), or `null` to remove - * @internal */ -export async function setUsername(this: TelegramClient, username: string | null): Promise { +export async function setUsername(client: BaseTelegramClient, username: string | null): Promise { if (username === null) username = '' - const res = await this.call({ + const res = await client.call({ _: 'account.updateUsername', username, }) - this._selfUsername = username || null + getAuthState(client).selfUsername = username || null - return new User(this, res) + return new User(res) } diff --git a/packages/client/src/methods/users/unblock-user.ts b/packages/client/src/methods/users/unblock-user.ts index a08d1f86..b8ddf7f9 100644 --- a/packages/client/src/methods/users/unblock-user.ts +++ b/packages/client/src/methods/users/unblock-user.ts @@ -1,15 +1,16 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { InputPeerLike } from '../../types' +import { resolvePeer } from './resolve-peer' /** * Unblock a user * * @param id User ID, username or phone number - * @internal */ -export async function unblockUser(this: TelegramClient, id: InputPeerLike): Promise { - await this.call({ +export async function unblockUser(client: BaseTelegramClient, id: InputPeerLike): Promise { + await client.call({ _: 'contacts.unblock', - id: await this.resolvePeer(id), + id: await resolvePeer(client, id), }) } diff --git a/packages/client/src/methods/users/update-profile.ts b/packages/client/src/methods/users/update-profile.ts index fc59e706..8a00f7ca 100644 --- a/packages/client/src/methods/users/update-profile.ts +++ b/packages/client/src/methods/users/update-profile.ts @@ -1,4 +1,5 @@ -import { TelegramClient } from '../../client' +import { BaseTelegramClient } from '@mtcute/core' + import { User } from '../../types' /** @@ -7,10 +8,9 @@ import { User } from '../../types' * Only pass fields that you want to change. * * @param params - * @internal */ export async function updateProfile( - this: TelegramClient, + client: BaseTelegramClient, params: { /** * New first name @@ -28,12 +28,12 @@ export async function updateProfile( bio?: string }, ): Promise { - const res = await this.call({ + const res = await client.call({ _: 'account.updateProfile', firstName: params.firstName, lastName: params.lastName, about: params.bio, }) - return new User(this, res) + return new User(res) } diff --git a/packages/client/src/types/bots/callback-query.ts b/packages/client/src/types/bots/callback-query.ts index b246370f..a366f920 100644 --- a/packages/client/src/types/bots/callback-query.ts +++ b/packages/client/src/types/bots/callback-query.ts @@ -1,11 +1,9 @@ import { BasicPeerType, getBasicPeerType, getMarkedPeerId, MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { encodeInlineMessageId } from '../../utils/inline-utils' -import { MtMessageNotFoundError } from '../errors' -import { Message } from '../messages' -import { PeersIndex, User } from '../peers' +import { PeersIndex } from '../peers/peers-index' +import { User } from '../peers/user' /** * An incoming callback query, originated from a callback button @@ -13,7 +11,6 @@ import { PeersIndex, User } from '../peers' */ export class CallbackQuery { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateBotCallbackQuery | tl.RawUpdateInlineBotCallbackQuery, readonly _peers: PeersIndex, ) {} @@ -30,7 +27,7 @@ export class CallbackQuery { * User who has pressed the button */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } /** @@ -152,47 +149,6 @@ export class CallbackQuery { get game(): string | null { return this.raw.gameShortName ?? null } - - /** - * Message that contained the callback button that was clicked. - * - * Note that the message may have been deleted, in which case - * `MessageNotFoundError` is thrown. - * - * Can only be used if `isInline = false` - */ - async getMessage(): Promise { - if (this.raw._ !== 'updateBotCallbackQuery') { - throw new MtArgumentError('Cannot get a message for inline callback') - } - - const msg = await this.client.getMessages(this.user.inputPeer, this.raw.msgId) - - if (!msg) { - throw new MtMessageNotFoundError(getMarkedPeerId(this.raw.peer), this.raw.msgId, 'with button') - } - - return msg - } - - /** - * Answer this query - */ - async answer(params?: Parameters[1]): Promise { - return this.client.answerCallbackQuery(this.raw.queryId, params) - } - - // /** - // * Edit the message that originated this callback query - // */ - // async editMessage(params: Parameters[1]): Promise { - // // we can use editInlineMessage as a parameter since they share most of the parameters, - // // except the ones that won't apply to already sent message anyways. - // if (this.raw._ === 'updateInlineBotCallbackQuery') { - // return this.client.editInlineMessage(this.raw.msgId, params) - // } - // await this.client.editMessage(getMarkedPeerId(this.raw.peer), this.raw.msgId, params) - // } } makeInspectable(CallbackQuery) diff --git a/packages/client/src/types/bots/game-high-score.ts b/packages/client/src/types/bots/game-high-score.ts index 207f1b6c..595730b1 100644 --- a/packages/client/src/types/bots/game-high-score.ts +++ b/packages/client/src/types/bots/game-high-score.ts @@ -1,15 +1,14 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { PeersIndex, User } from '../peers' +import { PeersIndex } from '../peers/peers-index' +import { User } from '../peers/user' /** * Game high score */ export class GameHighScore { constructor( - readonly client: TelegramClient, readonly raw: tl.RawHighScore, readonly _peers: PeersIndex, ) {} @@ -19,7 +18,7 @@ export class GameHighScore { * User who has scored this score */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } /** diff --git a/packages/client/src/types/bots/inline-query.ts b/packages/client/src/types/bots/inline-query.ts index a34f52a5..0e5e3d56 100644 --- a/packages/client/src/types/bots/inline-query.ts +++ b/packages/client/src/types/bots/inline-query.ts @@ -1,10 +1,8 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { Location } from '../media' import { PeersIndex, PeerType, User } from '../peers' -import { InputInlineResult } from './input' const PEER_TYPE_MAP: Record = { inlineQueryPeerTypeBroadcast: 'channel', @@ -17,7 +15,6 @@ const PEER_TYPE_MAP: Record = { export class InlineQuery { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateBotInlineQuery, readonly _peers: PeersIndex, ) {} @@ -34,7 +31,7 @@ export class InlineQuery { * User who sent this query */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } /** @@ -53,7 +50,7 @@ export class InlineQuery { get location(): Location | null { if (this.raw.geo?._ !== 'geoPoint') return null - return (this._location ??= new Location(this.client, this.raw.geo)) + return (this._location ??= new Location(this.raw.geo)) } /** @@ -77,19 +74,6 @@ export class InlineQuery { get peerType(): PeerType | null { return this.raw.peerType ? PEER_TYPE_MAP[this.raw.peerType._] : null } - - /** - * Answer to this inline query - * - * @param results Inline results - * @param params Additional parameters - */ - async answer( - results: InputInlineResult[], - params?: Parameters[2], - ): Promise { - return this.client.answerInlineQuery(this.raw.queryId, results, params) - } } makeInspectable(InlineQuery) diff --git a/packages/client/src/types/bots/input/input-inline-message.ts b/packages/client/src/types/bots/input/input-inline-message.ts index 55e96ab9..55af3833 100644 --- a/packages/client/src/types/bots/input/input-inline-message.ts +++ b/packages/client/src/types/bots/input/input-inline-message.ts @@ -1,6 +1,6 @@ -import { assertNever, tl } from '@mtcute/core' +import { assertNever, BaseTelegramClient, tl } from '@mtcute/core' -import { TelegramClient } from '../../../client' +import { _parseEntities } from '../../../methods/messages/parse-entities' import { InputMediaContact, InputMediaGeo, InputMediaGeoLive, InputMediaVenue } from '../../media' import { FormattedString } from '../../parser' import { BotKeyboard, ReplyMarkup } from '../keyboards' @@ -205,13 +205,13 @@ export namespace BotInlineMessage { /** @internal */ export async function _convertToTl( - client: TelegramClient, + client: BaseTelegramClient, obj: InputInlineMessage, parseMode?: string | null, ): Promise { switch (obj.type) { case 'text': { - const [message, entities] = await client._parseEntities(obj.text, parseMode, obj.entities) + const [message, entities] = await _parseEntities(client, obj.text, parseMode, obj.entities) return { _: 'inputBotInlineMessageText', @@ -221,7 +221,7 @@ export namespace BotInlineMessage { } } case 'media': { - const [message, entities] = await client._parseEntities(obj.text, parseMode, obj.entities) + const [message, entities] = await _parseEntities(client, obj.text, parseMode, obj.entities) return { _: 'inputBotInlineMessageMediaAuto', diff --git a/packages/client/src/types/bots/input/input-inline-result.ts b/packages/client/src/types/bots/input/input-inline-result.ts index 63c5ec04..19f7fe64 100644 --- a/packages/client/src/types/bots/input/input-inline-result.ts +++ b/packages/client/src/types/bots/input/input-inline-result.ts @@ -1,7 +1,6 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' import { fileIdToInputDocument, fileIdToInputPhoto } from '@mtcute/file-id' -import { TelegramClient } from '../../../client' import { extractFileName } from '../../../utils/file-utils' import { BotInlineMessage, InputInlineMessage } from './input-inline-message' @@ -715,7 +714,7 @@ export namespace BotInline { /** @internal */ export async function _convertToTl( - client: TelegramClient, + client: BaseTelegramClient, results: InputInlineResult[], parseMode?: string | null, ): Promise<[boolean, tl.TypeInputBotInlineResult[]]> { diff --git a/packages/client/src/types/conversation.ts b/packages/client/src/types/conversation.ts index 14ac9996..6aeb6210 100644 --- a/packages/client/src/types/conversation.ts +++ b/packages/client/src/types/conversation.ts @@ -1,13 +1,16 @@ -/* eslint-disable dot-notation */ -import { getMarkedPeerId, MaybeAsync, MtArgumentError, MtTimeoutError, tl } from '@mtcute/core' +import { BaseTelegramClient, getMarkedPeerId, MaybeAsync, MtArgumentError, MtTimeoutError, tl } from '@mtcute/core' import { AsyncLock, ControllablePromise, createControllablePromise, Deque } from '@mtcute/core/utils' -import { TelegramClient } from '../client' -import { InputMediaLike } from './media' -import { Message } from './messages' -import { FormattedString } from './parser' -import { InputPeerLike } from './peers' -import { HistoryReadUpdate } from './updates' +import { getPeerDialogs } from '../methods/dialogs/get-peer-dialogs' +import { readHistory } from '../methods/messages/read-history' +import { sendMedia } from '../methods/messages/send-media' +import { sendMediaGroup } from '../methods/messages/send-media-group' +import { sendText } from '../methods/messages/send-text' +import { resolvePeer } from '../methods/users/resolve-peer' +import type { Message } from './messages' +import type { InputPeerLike } from './peers' +import type { HistoryReadUpdate, ParsedUpdate } from './updates' +import { ParametersSkip2 } from './utils' interface QueuedHandler { promise: ControllablePromise @@ -15,6 +18,13 @@ interface QueuedHandler { timeout?: NodeJS.Timeout } +const CONVERSATION_SYMBOL = Symbol('conversation') + +interface ConversationsState { + pendingConversations: Map + hasConversations: boolean +} + /** * Represents a conversation inside some chat. * @@ -44,12 +54,57 @@ export class Conversation { private _pendingRead: Map> = new Map() constructor( - readonly client: TelegramClient, + readonly client: BaseTelegramClient, readonly chat: InputPeerLike, ) { - this._onNewMessage = this._onNewMessage.bind(this) - this._onEditMessage = this._onEditMessage.bind(this) - this._onHistoryRead = this._onHistoryRead.bind(this) + if (!(CONVERSATION_SYMBOL in client)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (client as any)[CONVERSATION_SYMBOL] = { + pendingConversations: new Map(), + hasConversations: false, + } satisfies ConversationsState + } + } + + private static _getState(client: BaseTelegramClient): ConversationsState { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (client as any)[CONVERSATION_SYMBOL] as ConversationsState + } + + static handleUpdate(client: BaseTelegramClient, update: ParsedUpdate): void { + const state = Conversation._getState(client) + if (!state?.hasConversations) return + + let chatId + + switch (update.name) { + case 'new_message': + case 'edit_message': + chatId = getMarkedPeerId(update.data.raw.peerId) + break + case 'history_read': + chatId = update.data.chatId + break + default: + return + } + + const conv = state.pendingConversations.get(chatId) + if (!conv) return + + for (const c of conv) { + switch (update.name) { + case 'new_message': + c._onNewMessage(update.data) + break + case 'edit_message': + c._onEditMessage(update.data) + break + case 'history_read': + c._onHistoryRead(update.data) + break + } + } } /** @@ -95,10 +150,10 @@ export class Conversation { if (this._started) return this._started = true - this._inputPeer = await this.client.resolvePeer(this.chat) + this._inputPeer = await resolvePeer(this.client, this.chat) this._chatId = getMarkedPeerId(this._inputPeer) - const dialog = await this.client.getPeerDialogs(this._inputPeer) + const dialog = await getPeerDialogs(this.client, this._inputPeer) const lastMessage = dialog.lastMessage if (lastMessage) { @@ -110,11 +165,13 @@ export class Conversation { this.client.on('edit_message', this._onEditMessage) this.client.on('history_read', this._onHistoryRead) - if (this.client['_pendingConversations'].has(this._chatId)) { - this.client['_pendingConversations'].set(this._chatId, []) + const state = Conversation._getState(this.client) + + if (!state.pendingConversations.has(this._chatId)) { + state.pendingConversations.set(this._chatId, []) } - this.client['_pendingConversations'].get(this._chatId)!.push(this) - this.client['_hasConversations'] = true + state.pendingConversations.get(this._chatId)!.push(this) + state.hasConversations = true } /** @@ -127,7 +184,9 @@ export class Conversation { this.client.off('edit_message', this._onEditMessage) this.client.off('history_read', this._onHistoryRead) - const pending = this.client['_pendingConversations'].get(this._chatId) + const state = Conversation._getState(this.client) + + const pending = state.pendingConversations.get(this._chatId) const pendingIdx = pending?.indexOf(this) ?? -1 if (pendingIdx > -1) { @@ -135,9 +194,9 @@ export class Conversation { pending!.splice(pendingIdx, 1) } if (pending && !pending.length) { - this.client['_pendingConversations'].delete(this._chatId) + state.pendingConversations.delete(this._chatId) } - this.client['_hasConversations'] = Boolean(this.client['_pendingConversations'].size) + state.hasConversations = Boolean(state.pendingConversations.size) // reset pending status this._queuedNewMessage.clear() @@ -149,65 +208,67 @@ export class Conversation { this._started = false } + private _recordMessage(msg: Message, incoming = false): Message { + this._lastMessage = msg.id + if (incoming) this._lastReceivedMessage = msg.id + + return msg + } + /** * Send a text message to this conversation. * - * @param text Text of the message - * @param params + * Wrapper over {@link sendText} */ - async sendText( - text: string | FormattedString, - params?: Parameters[2], - ): ReturnType { + async sendText(...params: ParametersSkip2): ReturnType { if (!this._started) { throw new MtArgumentError("Conversation hasn't started yet") } - return this.client.sendText(this._inputPeer, text, params) + return this._recordMessage(await sendText(this.client, this._inputPeer, ...params)) } /** * Send a media to this conversation. * - * @param media Media to send - * @param params + * Wrapper over {@link sendMedia} */ - async sendMedia( - media: InputMediaLike | string, - params?: Parameters[2], - ): ReturnType { + async sendMedia(...params: ParametersSkip2): ReturnType { if (!this._started) { throw new MtArgumentError("Conversation hasn't started yet") } - return this.client.sendMedia(this._inputPeer, media, params) + return this._recordMessage(await sendMedia(this.client, this._inputPeer, ...params)) } /** * Send a media group to this conversation. * - * @param medias Medias to send - * @param params + * Wrapper over {@link sendMediaGroup} */ - async sendMediaGroup( - medias: (InputMediaLike | string)[], - params?: Parameters[2], - ): ReturnType { + async sendMediaGroup(...params: ParametersSkip2): ReturnType { if (!this._started) { throw new MtArgumentError("Conversation hasn't started yet") } - return this.client.sendMediaGroup(this._inputPeer, medias, params) + const msgs = await sendMediaGroup(this.client, this._inputPeer, ...params) + + this._recordMessage(msgs[msgs.length - 1]) + + return msgs } /** * Mark the conversation as read up to a certain point. * * By default, reads until the last message. - * You can pass `null` to read the entire conversation, + * You can pass `message=null` to read the entire conversation, * or pass message ID to read up until that ID. */ - markRead(message?: number | null, clearMentions = true): Promise { + markRead({ + message, + clearMentions = true, + }: { message?: number | null; clearMentions?: boolean } = {}): Promise { if (!this._started) { throw new MtArgumentError("Conversation hasn't started yet") } @@ -218,17 +279,18 @@ export class Conversation { message = this._lastMessage ?? 0 } - return this.client.readHistory(this._inputPeer, { maxId: message, clearMentions }) + return readHistory(this.client, this._inputPeer, { maxId: message, clearMentions }) } /** * Helper method that calls {@link start}, - * the provided function and the {@link stop}. + * the provided function and then {@link stop}. * * It is preferred that you use this function rather than * manually starting and stopping the conversation. * - * If you don't stop the conversation, this *will* lead to memory leaks. + * If you don't stop the conversation when you're done, + * it *will* lead to memory leaks. * * @param handler */ @@ -256,8 +318,8 @@ export class Conversation { * Wait for a new message in the conversation * * @param filter Filter for the handler. You can use any filter you can use for dispatcher - * @param timeout Timeout for the handler in ms, def. 15 sec. Pass `null` to disable. - * When the timeout is reached, `TimeoutError` is thrown. + * @param [timeout=15000] Timeout for the handler in ms. Pass `null` to disable. + * When the timeout is reached, `MtTimeoutError` is thrown. */ waitForNewMessage( filter?: (msg: Message) => MaybeAsync, @@ -453,7 +515,7 @@ export class Conversation { } // check if the message is already read - const dialog = await this.client.getPeerDialogs(this._inputPeer) + const dialog = await getPeerDialogs(this.client, this._inputPeer) if (dialog.lastRead >= msgId) return const promise = createControllablePromise() @@ -495,7 +557,7 @@ export class Conversation { this._queuedNewMessage.popFront() } } catch (e: unknown) { - this.client['_emitError'](e) + this.client._emitError(e) } this._lastMessage = this._lastReceivedMessage = msg.id @@ -525,7 +587,7 @@ export class Conversation { this._pendingEditMessage.delete(msg.id) } })().catch((e) => { - this.client['_emitError'](e) + this.client._emitError(e) }) } diff --git a/packages/client/src/types/files/file-location.ts b/packages/client/src/types/files/file-location.ts index 44a3501e..8798f164 100644 --- a/packages/client/src/types/files/file-location.ts +++ b/packages/client/src/types/files/file-location.ts @@ -1,10 +1,6 @@ -import { Readable } from 'stream' - import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { FileDownloadParameters } from './utils' /** * Information about file location. @@ -14,10 +10,6 @@ import { FileDownloadParameters } from './utils' */ export class FileLocation { constructor( - /** - * Client that was used to create this object - */ - readonly client: TelegramClient, /** * Location of the file. * @@ -43,62 +35,62 @@ export class FileLocation { readonly dcId?: number, ) {} - /** - * Download a file and return it as an iterable, which yields file contents - * in chunks of a given size. Order of the chunks is guaranteed to be - * consecutive. - * - * @param params Download parameters - * @link TelegramClient.downloadAsIterable - */ - downloadIterable(params?: Partial): AsyncIterableIterator { - return this.client.downloadAsIterable({ - ...params, - location: this, - }) - } + // /** + // * Download a file and return it as an iterable, which yields file contents + // * in chunks of a given size. Order of the chunks is guaranteed to be + // * consecutive. + // * + // * @param params Download parameters + // * @link TelegramClient.downloadAsIterable + // */ + // downloadIterable(params?: Partial): AsyncIterableIterator { + // return this.client.downloadAsIterable({ + // ...params, + // location: this, + // }) + // } - /** - * Download a file and return it as a Node readable stream, - * streaming file contents. - * - * @link TelegramClient.downloadAsStream - */ - downloadStream(params?: Partial): Readable { - return this.client.downloadAsStream({ - ...params, - location: this, - }) - } + // /** + // * Download a file and return it as a Node readable stream, + // * streaming file contents. + // * + // * @link TelegramClient.downloadAsStream + // */ + // downloadStream(params?: Partial): Readable { + // return this.client.downloadAsStream({ + // ...params, + // location: this, + // }) + // } - /** - * Download a file and return its contents as a Buffer. - * - * @param params File download parameters - * @link TelegramClient.downloadAsBuffer - */ - downloadBuffer(params?: Partial): Promise { - return this.client.downloadAsBuffer({ - ...params, - location: this, - }) - } + // /** + // * Download a file and return its contents as a Buffer. + // * + // * @param params File download parameters + // * @link TelegramClient.downloadAsBuffer + // */ + // downloadBuffer(params?: Partial): Promise { + // return this.client.downloadAsBuffer({ + // ...params, + // location: this, + // }) + // } - /** - * Download a remote file to a local file (only for NodeJS). - * Promise will resolve once the download is complete. - * - * @param filename Local file name - * @param params File download parameters - * @link TelegramClient.downloadToFile - */ - downloadToFile(filename: string, params?: Partial): Promise { - return this.client.downloadToFile(filename, { - ...params, - location: this, - fileSize: this.fileSize, - }) - } + // /** + // * Download a remote file to a local file (only for NodeJS). + // * Promise will resolve once the download is complete. + // * + // * @param filename Local file name + // * @param params File download parameters + // * @link TelegramClient.downloadToFile + // */ + // downloadToFile(filename: string, params?: Partial): Promise { + // return this.client.downloadToFile(filename, { + // ...params, + // location: this, + // fileSize: this.fileSize, + // }) + // } } makeInspectable(FileLocation, ['fileSize', 'dcId']) diff --git a/packages/client/src/types/files/web-document.ts b/packages/client/src/types/files/web-document.ts index ab57071c..2d63c97b 100644 --- a/packages/client/src/types/files/web-document.ts +++ b/packages/client/src/types/files/web-document.ts @@ -1,6 +1,5 @@ import { MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { FileLocation } from './file-location' @@ -19,12 +18,8 @@ const STUB_LOCATION = () => { * > To be sure, check `isDownloadable` property. */ export class WebDocument extends FileLocation { - constructor( - client: TelegramClient, - readonly raw: tl.TypeWebDocument, - ) { + constructor(readonly raw: tl.TypeWebDocument) { super( - client, raw._ === 'webDocument' ? { _: 'inputWebFileLocation', diff --git a/packages/client/src/types/media/audio.ts b/packages/client/src/types/media/audio.ts index 15b2200e..c65cd9fb 100644 --- a/packages/client/src/types/media/audio.ts +++ b/packages/client/src/types/media/audio.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { RawDocument } from './document' @@ -16,11 +15,10 @@ export class Audio extends RawDocument { } constructor( - client: TelegramClient, doc: tl.RawDocument, readonly attr: tl.RawDocumentAttributeAudio, ) { - super(client, doc) + super(doc) } /** diff --git a/packages/client/src/types/media/document-utils.ts b/packages/client/src/types/media/document-utils.ts index 8773d8ab..7adef26a 100644 --- a/packages/client/src/types/media/document-utils.ts +++ b/packages/client/src/types/media/document-utils.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { Audio } from './audio' import { Document } from './document' import { Sticker } from './sticker' @@ -10,11 +9,7 @@ import { Voice } from './voice' export type ParsedDocument = Sticker | Voice | Audio | Video | Document /** @internal */ -export function parseDocument( - client: TelegramClient, - doc: tl.RawDocument, - media?: tl.RawMessageMediaDocument, -): ParsedDocument { +export function parseDocument(doc: tl.RawDocument, media?: tl.RawMessageMediaDocument): ParsedDocument { const stickerAttr = doc.attributes.find( (a) => a._ === 'documentAttributeSticker' || a._ === 'documentAttributeCustomEmoji', ) @@ -24,33 +19,28 @@ export function parseDocument( (it) => it._ === 'documentAttributeImageSize' || it._ === 'documentAttributeVideo', )! as tl.RawDocumentAttributeImageSize | tl.RawDocumentAttributeVideo - return new Sticker( - client, - doc, - stickerAttr as tl.RawDocumentAttributeSticker | tl.RawDocumentAttributeCustomEmoji, - sz, - ) + return new Sticker(doc, stickerAttr as tl.RawDocumentAttributeSticker | tl.RawDocumentAttributeCustomEmoji, sz) } for (const attr of doc.attributes) { switch (attr._) { case 'documentAttributeAudio': if (attr.voice) { - return new Voice(client, doc, attr) + return new Voice(doc, attr) } - return new Audio(client, doc, attr) + return new Audio(doc, attr) case 'documentAttributeVideo': - return new Video(client, doc, attr, media) + return new Video(doc, attr, media) case 'documentAttributeImageSize': // legacy gif if (doc.mimeType === 'image/gif') { - return new Video(client, doc, attr, media) + return new Video(doc, attr, media) } } } - return new Document(client, doc) + return new Document(doc) } diff --git a/packages/client/src/types/media/document.ts b/packages/client/src/types/media/document.ts index fbabc938..3bb81ed6 100644 --- a/packages/client/src/types/media/document.ts +++ b/packages/client/src/types/media/document.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { FileLocation } from '../files' import { Thumbnail } from './thumbnail' @@ -14,12 +13,8 @@ import { Thumbnail } from './thumbnail' export abstract class RawDocument extends FileLocation { abstract type: string - constructor( - client: TelegramClient, - readonly raw: tl.RawDocument, - ) { + constructor(readonly raw: tl.RawDocument) { super( - client, { _: 'inputDocumentFileLocation', id: raw.id, @@ -71,8 +66,8 @@ export abstract class RawDocument extends FileLocation { if (!this._thumbnails) { const arr: Thumbnail[] = [] - this.raw.thumbs?.forEach((sz) => arr.push(new Thumbnail(this.client, this.raw, sz))) - this.raw.videoThumbs?.forEach((sz) => arr.push(new Thumbnail(this.client, this.raw, sz))) + this.raw.thumbs?.forEach((sz) => arr.push(new Thumbnail(this.raw, sz))) + this.raw.videoThumbs?.forEach((sz) => arr.push(new Thumbnail(this.raw, sz))) this._thumbnails = arr } diff --git a/packages/client/src/types/media/game.ts b/packages/client/src/types/media/game.ts index ad6041d9..b644d7f5 100644 --- a/packages/client/src/types/media/game.ts +++ b/packages/client/src/types/media/game.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { Photo } from './photo' import { Video } from './video' @@ -8,10 +7,7 @@ import { Video } from './video' export class Game { readonly type = 'game' as const - constructor( - readonly client: TelegramClient, - readonly game: tl.RawGame, - ) {} + constructor(readonly game: tl.RawGame) {} /** * Unique identifier of the game. @@ -48,7 +44,7 @@ export class Game { get photo(): Photo | null { if (this.game.photo._ === 'photoEmpty') return null - return (this._photo ??= new Photo(this.client, this.game.photo)) + return (this._photo ??= new Photo(this.game.photo)) } private _animation?: Video | null @@ -66,7 +62,7 @@ export class Game { if (!attr) { this._animation = null } else { - this._animation = new Video(this.client, this.game.document, attr) + this._animation = new Video(this.game.document, attr) } } diff --git a/packages/client/src/types/media/invoice.ts b/packages/client/src/types/media/invoice.ts index 341f9608..5ef25b3c 100644 --- a/packages/client/src/types/media/invoice.ts +++ b/packages/client/src/types/media/invoice.ts @@ -1,9 +1,8 @@ import { MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { WebDocument } from '../files/web-document' -import { _messageMediaFromTl, MessageMedia } from '../messages' +import type { MessageMedia } from '../messages/message-media' import { Thumbnail } from './thumbnail' /** @@ -15,10 +14,7 @@ import { Thumbnail } from './thumbnail' export type InvoiceExtendedMediaState = 'none' | 'preview' | 'full' export class InvoiceExtendedMediaPreview { - constructor( - public readonly client: TelegramClient, - public readonly raw: tl.RawMessageExtendedMediaPreview, - ) {} + constructor(public readonly raw: tl.RawMessageExtendedMediaPreview) {} /** * Width of the preview, in pixels (if available, else 0) @@ -40,7 +36,7 @@ export class InvoiceExtendedMediaPreview { return null } - return (this._thumbnail ??= new Thumbnail(this.client, this.raw, this.raw.thumb)) + return (this._thumbnail ??= new Thumbnail(this.raw, this.raw.thumb)) } /** @@ -59,8 +55,8 @@ export class Invoice { readonly type = 'invoice' as const constructor( - readonly client: TelegramClient, readonly raw: tl.RawMessageMediaInvoice, + private readonly _extendedMedia?: MessageMedia, ) {} /** @@ -98,7 +94,7 @@ export class Invoice { get photo(): WebDocument | null { if (!this.raw.photo) return null - return (this._photo ??= new WebDocument(this.client, this.raw.photo)) + return (this._photo ??= new WebDocument(this.raw.photo)) } /** @@ -159,21 +155,20 @@ export class Invoice { throw new MtArgumentError('No extended media preview available') } - return (this._extendedMediaPreview ??= new InvoiceExtendedMediaPreview(this.client, this.raw.extendedMedia)) + return (this._extendedMediaPreview ??= new InvoiceExtendedMediaPreview(this.raw.extendedMedia)) } - private _extendedMedia?: MessageMedia /** * Get the invoice's extended media. * Only available if {@link extendedMediaState} is `full`. * Otherwise, throws an error. */ get extendedMedia(): MessageMedia { - if (this.raw.extendedMedia?._ !== 'messageExtendedMedia') { + if (!this._extendedMedia) { throw new MtArgumentError('No extended media available') } - return (this._extendedMedia ??= _messageMediaFromTl(this.client, null, this.raw.extendedMedia.media)) + return this._extendedMedia } /** diff --git a/packages/client/src/types/media/location.ts b/packages/client/src/types/media/location.ts index af9d7106..5450bf8a 100644 --- a/packages/client/src/types/media/location.ts +++ b/packages/client/src/types/media/location.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { FileLocation } from '../files' @@ -8,10 +7,7 @@ import { FileLocation } from '../files' * A point on the map */ export class RawLocation { - constructor( - readonly client: TelegramClient, - readonly geo: tl.RawGeoPoint, - ) {} + constructor(readonly geo: tl.RawGeoPoint) {} /** * Geo point latitude @@ -69,7 +65,7 @@ export class RawLocation { scale?: number } = {}, ): FileLocation { - return new FileLocation(this.client, { + return new FileLocation({ _: 'inputWebFileGeoPointLocation', geoPoint: { _: 'inputGeoPoint', @@ -110,11 +106,8 @@ export class Location extends RawLocation { export class LiveLocation extends RawLocation { readonly type = 'live_location' as const - constructor( - client: TelegramClient, - readonly live: tl.RawMessageMediaGeoLive, - ) { - super(client, live.geo as tl.RawGeoPoint) + constructor(readonly live: tl.RawMessageMediaGeoLive) { + super(live.geo as tl.RawGeoPoint) } /** diff --git a/packages/client/src/types/media/photo.ts b/packages/client/src/types/media/photo.ts index 516b5624..caa2b77d 100644 --- a/packages/client/src/types/media/photo.ts +++ b/packages/client/src/types/media/photo.ts @@ -1,6 +1,5 @@ import { MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { FileLocation } from '../files' import { Thumbnail } from './thumbnail' @@ -20,7 +19,6 @@ export class Photo extends FileLocation { private _bestSize?: tl.RawPhotoSize | tl.RawPhotoSizeProgressive constructor( - client: TelegramClient, readonly raw: tl.RawPhoto, readonly media?: tl.RawMessageMediaPhoto, ) { @@ -70,7 +68,7 @@ export class Photo extends FileLocation { } } - super(client, location, size, raw.dcId) + super(location, size, raw.dcId) this._bestSize = bestSize this.width = width this.height = height @@ -124,8 +122,8 @@ export class Photo extends FileLocation { */ get thumbnails(): ReadonlyArray { if (!this._thumbnails) { - this._thumbnails = this.raw.sizes.map((sz) => new Thumbnail(this.client, this.raw, sz)) - this.raw.videoSizes?.forEach((sz) => this._thumbnails!.push(new Thumbnail(this.client, this.raw, sz))) + this._thumbnails = this.raw.sizes.map((sz) => new Thumbnail(this.raw, sz)) + this.raw.videoSizes?.forEach((sz) => this._thumbnails!.push(new Thumbnail(this.raw, sz))) } return this._thumbnails diff --git a/packages/client/src/types/media/poll.ts b/packages/client/src/types/media/poll.ts index 6ecdf86a..99bb480d 100644 --- a/packages/client/src/types/media/poll.ts +++ b/packages/client/src/types/media/poll.ts @@ -1,9 +1,8 @@ import { Long, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { MessageEntity } from '../messages' -import { PeersIndex } from '../peers' +import { MessageEntity } from '../messages/message-entity' +import { PeersIndex } from '../peers/peers-index' export interface PollAnswer { /** @@ -39,7 +38,6 @@ export class Poll { readonly type = 'poll' as const constructor( - readonly client: TelegramClient, readonly raw: tl.TypePoll, readonly _peers: PeersIndex, readonly results?: tl.TypePollResults, @@ -160,18 +158,6 @@ export class Poll { return this._entities } - /** - * Get the solution text formatted with a given parse mode. - * Returns `null` if solution is not available - * - * @param parseMode Parse mode to use (`null` for default) - */ - unparseSolution(parseMode?: string | null): string | null { - if (!this.results?.solutionEntities) return null - - return this.client.getParseMode(parseMode).unparse(this.results.solution!, this.results.solutionEntities) - } - /** * Input media TL object generated from this object, * to be used inside {@link InputMediaLike} and diff --git a/packages/client/src/types/media/sticker.ts b/packages/client/src/types/media/sticker.ts index 601824ac..3b3e8372 100644 --- a/packages/client/src/types/media/sticker.ts +++ b/packages/client/src/types/media/sticker.ts @@ -1,9 +1,7 @@ import { MtArgumentError, tl } from '@mtcute/core' import { tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { StickerSet } from '../misc' import { RawDocument } from './document' export const MASK_POSITION_POINT_TO_TL = { @@ -69,12 +67,11 @@ export class Sticker extends RawDocument { } constructor( - client: TelegramClient, doc: tl.RawDocument, readonly attr: tl.RawDocumentAttributeSticker | tl.RawDocumentAttributeCustomEmoji, readonly attr2?: tl.RawDocumentAttributeImageSize | tl.RawDocumentAttributeVideo, ) { - super(client, doc) + super(doc) } /** @@ -208,30 +205,6 @@ export class Sticker extends RawDocument { return this._maskPosition } - - /** - * Get the sticker set that this sticker belongs to. - * - * Returns `null` if there's no sticker set. - */ - async getStickerSet(): Promise { - if (!this.hasStickerSet) return null - - return this.client.getStickerSet(this.attr.stickerset) - } - - /** - * Fetch all the emojis that are associated with the current sticker - * - * Returns empty string if the sticker is not associated - * with a sticker pack. - */ - async getAllEmojis(): Promise { - const set = await this.getStickerSet() - if (!set) return '' - - return set.stickers.find((it) => it.sticker.raw.id.eq(this.raw.id))!.emoji - } } makeInspectable(Sticker, ['fileSize', 'dcId'], ['inputMedia', 'inputDocument']) diff --git a/packages/client/src/types/media/thumbnail.ts b/packages/client/src/types/media/thumbnail.ts index 32ac0d0f..397670cc 100644 --- a/packages/client/src/types/media/thumbnail.ts +++ b/packages/client/src/types/media/thumbnail.ts @@ -2,7 +2,6 @@ import { Long, MtArgumentError, MtTypeAssertionError, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { inflateSvgPath, strippedPhotoToJpg, svgPathToFile } from '../../utils/file-utils' import { FileLocation } from '../files' @@ -56,7 +55,6 @@ export class Thumbnail extends FileLocation { private _media: tl.RawPhoto | tl.RawDocument | tl.RawStickerSet | tl.RawMessageExtendedMediaPreview constructor( - client: TelegramClient, media: tl.RawPhoto | tl.RawDocument | tl.RawStickerSet | tl.RawMessageExtendedMediaPreview, sz: tl.TypePhotoSize | tl.TypeVideoSize, ) { @@ -129,7 +127,7 @@ export class Thumbnail extends FileLocation { dcId = media.dcId } - super(client, location, size, dcId) + super(location, size, dcId) this.raw = sz this.width = width this.height = height diff --git a/packages/client/src/types/media/venue.ts b/packages/client/src/types/media/venue.ts index 32024b11..17fbcdfc 100644 --- a/packages/client/src/types/media/venue.ts +++ b/packages/client/src/types/media/venue.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { Location } from './location' @@ -30,10 +29,7 @@ export interface VenueSource { export class Venue { readonly type = 'venue' as const - constructor( - readonly client: TelegramClient, - readonly raw: tl.RawMessageMediaVenue, - ) {} + constructor(readonly raw: tl.RawMessageMediaVenue) {} private _location?: Location /** @@ -42,7 +38,7 @@ export class Venue { get location(): Location { if (!this._location) { assertTypeIs('Venue#location', this.raw.geo, 'geoPoint') - this._location = new Location(this.client, this.raw.geo) + this._location = new Location(this.raw.geo) } return this._location diff --git a/packages/client/src/types/media/video.ts b/packages/client/src/types/media/video.ts index aaa5cb6f..45ae75ab 100644 --- a/packages/client/src/types/media/video.ts +++ b/packages/client/src/types/media/video.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { RawDocument } from './document' @@ -21,12 +20,11 @@ export class Video extends RawDocument { } constructor( - client: TelegramClient, doc: tl.RawDocument, readonly attr: tl.RawDocumentAttributeVideo | tl.RawDocumentAttributeImageSize, readonly media?: tl.RawMessageMediaDocument, ) { - super(client, doc) + super(doc) } /** diff --git a/packages/client/src/types/media/voice.ts b/packages/client/src/types/media/voice.ts index 6a2cfd6c..a6cf19b6 100644 --- a/packages/client/src/types/media/voice.ts +++ b/packages/client/src/types/media/voice.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { tdFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { decodeWaveform } from '../../utils/voice-utils' import { RawDocument } from './document' @@ -17,11 +16,10 @@ export class Voice extends RawDocument { } constructor( - client: TelegramClient, doc: tl.RawDocument, readonly attr: tl.RawDocumentAttributeAudio, ) { - super(client, doc) + super(doc) } /** diff --git a/packages/client/src/types/media/web-page.ts b/packages/client/src/types/media/web-page.ts index e9ffe4bd..082ac68f 100644 --- a/packages/client/src/types/media/web-page.ts +++ b/packages/client/src/types/media/web-page.ts @@ -1,6 +1,5 @@ import { MtArgumentError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { RawDocument } from './document' import { parseDocument } from './document-utils' @@ -19,10 +18,7 @@ import { Photo } from './photo' export class WebPage { readonly type = 'web_page' as const - constructor( - readonly client: TelegramClient, - readonly raw: tl.RawWebPage, - ) {} + constructor(readonly raw: tl.RawWebPage) {} /** * Unique ID of the preview @@ -179,7 +175,7 @@ export class WebPage { if (this.raw.photo?._ !== 'photo') { this._photo = null } else { - this._photo = new Photo(this.client, this.raw.photo) + this._photo = new Photo(this.raw.photo) } } @@ -200,7 +196,7 @@ export class WebPage { if (this.raw.document?._ !== 'document') { this._document = null } else { - this._document = parseDocument(this.client, this.raw.document) + this._document = parseDocument(this.raw.document) } } diff --git a/packages/client/src/types/messages/dialog.ts b/packages/client/src/types/messages/dialog.ts index 9d2201ce..86cc1149 100644 --- a/packages/client/src/types/messages/dialog.ts +++ b/packages/client/src/types/messages/dialog.ts @@ -1,9 +1,9 @@ import { getMarkedPeerId, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { assertTypeIsNot, hasValueAtKey, makeInspectable } from '../../utils' import { MtMessageNotFoundError } from '../errors' -import { Chat, PeersIndex } from '../peers' +import { Chat } from '../peers/chat' +import { PeersIndex } from '../peers/peers-index' import { DraftMessage } from './draft-message' import { Message } from './message' @@ -22,7 +22,6 @@ export type InputDialogFolder = string | number | tl.RawDialogFilter */ export class Dialog { constructor( - readonly client: TelegramClient, readonly raw: tl.RawDialog, readonly _peers: PeersIndex, readonly _messages: Map, @@ -35,11 +34,7 @@ export class Dialog { * @param dialogs TL object * @param limit Maximum number of dialogs to parse */ - static parseTlDialogs( - client: TelegramClient, - dialogs: tl.messages.TypeDialogs | tl.messages.TypePeerDialogs, - limit?: number, - ): Dialog[] { + static parseTlDialogs(dialogs: tl.messages.TypeDialogs | tl.messages.TypePeerDialogs, limit?: number): Dialog[] { assertTypeIsNot('parseDialogs', dialogs, 'messages.dialogsNotModified') const peers = PeersIndex.from(dialogs) @@ -51,9 +46,7 @@ export class Dialog { messages.set(getMarkedPeerId(msg.peerId), msg) }) - const arr = dialogs.dialogs - .filter(hasValueAtKey('_', 'dialog')) - .map((it) => new Dialog(client, it, peers, messages)) + const arr = dialogs.dialogs.filter(hasValueAtKey('_', 'dialog')).map((it) => new Dialog(it, peers, messages)) if (limit) { return arr.slice(0, limit) @@ -214,7 +207,7 @@ export class Dialog { break } - this._chat = new Chat(this.client, chat) + this._chat = new Chat(chat) } return this._chat @@ -229,7 +222,7 @@ export class Dialog { const cid = this.chat.id if (this._messages.has(cid)) { - this._lastMessage = new Message(this.client, this._messages.get(cid)!, this._peers) + this._lastMessage = new Message(this._messages.get(cid)!, this._peers) } else { throw new MtMessageNotFoundError(cid, 0) } @@ -287,7 +280,7 @@ export class Dialog { get draftMessage(): DraftMessage | null { if (this._draftMessage === undefined) { if (this.raw.draft?._ === 'draftMessage') { - this._draftMessage = new DraftMessage(this.client, this.raw.draft) + this._draftMessage = new DraftMessage(this.raw.draft) } else { this._draftMessage = null } diff --git a/packages/client/src/types/messages/draft-message.ts b/packages/client/src/types/messages/draft-message.ts index 9db5f796..bf4649f1 100644 --- a/packages/client/src/types/messages/draft-message.ts +++ b/packages/client/src/types/messages/draft-message.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { MessageEntity } from './message-entity' @@ -8,10 +7,7 @@ import { MessageEntity } from './message-entity' * A draft message */ export class DraftMessage { - constructor( - readonly client: TelegramClient, - readonly raw: tl.RawDraftMessage, - ) {} + constructor(readonly raw: tl.RawDraftMessage) {} /** * Text of the draft message diff --git a/packages/client/src/types/messages/message-action.ts b/packages/client/src/types/messages/message-action.ts index 198875ed..0583cb42 100644 --- a/packages/client/src/types/messages/message-action.ts +++ b/packages/client/src/types/messages/message-action.ts @@ -2,7 +2,7 @@ import { getMarkedPeerId, tl } from '@mtcute/core' import { _callDiscardReasonFromTl, CallDiscardReason } from '../calls' import { Photo } from '../media' -import { Message } from './message' +import type { Message } from './message' /** Group was created */ export interface ActionChatCreated { @@ -494,7 +494,7 @@ export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction): case 'messageActionChatEditPhoto': return { type: 'photo_changed', - photo: new Photo(this.client, act.photo as tl.RawPhoto), + photo: new Photo(act.photo as tl.RawPhoto), } case 'messageActionChatDeletePhoto': return { @@ -653,7 +653,7 @@ export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction): case 'messageActionSuggestProfilePhoto': return { type: 'photo_suggested', - photo: new Photo(this.client, act.photo as tl.RawPhoto), + photo: new Photo(act.photo as tl.RawPhoto), } case 'messageActionRequestedPeer': return { diff --git a/packages/client/src/types/messages/message-media.ts b/packages/client/src/types/messages/message-media.ts index fc97fcf2..ce9c2113 100644 --- a/packages/client/src/types/messages/message-media.ts +++ b/packages/client/src/types/messages/message-media.ts @@ -1,25 +1,21 @@ import { MtTypeAssertionError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' -import { - Audio, - Contact, - Dice, - Document, - Game, - Invoice, - LiveLocation, - Location, - Photo, - Poll, - Sticker, - Venue, - Video, - Voice, - WebPage, -} from '../media' +import { Audio } from '../media/audio' +import { Contact } from '../media/contact' +import { Dice } from '../media/dice' +import { Document } from '../media/document' import { parseDocument } from '../media/document-utils' -import { PeersIndex } from '../peers' +import { Game } from '../media/game' +import { Invoice } from '../media/invoice' +import { LiveLocation, Location } from '../media/location' +import { Photo } from '../media/photo' +import { Poll } from '../media/poll' +import { Sticker } from '../media/sticker' +import { Venue } from '../media/venue' +import { Video } from '../media/video' +import { Voice } from '../media/voice' +import { WebPage } from '../media/web-page' +import { PeersIndex } from '../peers/peers-index' /** A media inside of a {@link Message} */ export type MessageMedia = @@ -44,16 +40,12 @@ export type MessageMediaType = Exclude['type'] // todo: successful_payment, connected_website /** @internal */ -export function _messageMediaFromTl( - client: TelegramClient, - peers: PeersIndex | null, - m: tl.TypeMessageMedia, -): MessageMedia { +export function _messageMediaFromTl(peers: PeersIndex | null, m: tl.TypeMessageMedia): MessageMedia { switch (m._) { case 'messageMediaPhoto': if (!(m.photo?._ === 'photo')) return null - return new Photo(client, m.photo, m) + return new Photo(m.photo, m) case 'messageMediaDice': return new Dice(m) case 'messageMediaContact': @@ -61,23 +53,23 @@ export function _messageMediaFromTl( case 'messageMediaDocument': if (!(m.document?._ === 'document')) return null - return parseDocument(client, m.document, m) as MessageMedia + return parseDocument(m.document, m) case 'messageMediaGeo': if (!(m.geo._ === 'geoPoint')) return null - return new Location(client, m.geo) + return new Location(m.geo) case 'messageMediaGeoLive': if (!(m.geo._ === 'geoPoint')) return null - return new LiveLocation(client, m) + return new LiveLocation(m) case 'messageMediaGame': - return new Game(client, m.game) + return new Game(m.game) case 'messageMediaWebPage': if (!(m.webpage._ === 'webPage')) return null - return new WebPage(client, m.webpage) + return new WebPage(m.webpage) case 'messageMediaVenue': - return new Venue(client, m) + return new Venue(m) case 'messageMediaPoll': if (!peers) { // should only be possible in extended media @@ -85,9 +77,13 @@ export function _messageMediaFromTl( throw new MtTypeAssertionError("can't create poll without peers index", 'PeersIndex', 'null') } - return new Poll(client, m.poll, peers, m.results) - case 'messageMediaInvoice': - return new Invoice(client, m) + return new Poll(m.poll, peers, m.results) + case 'messageMediaInvoice': { + const extended = + m.extendedMedia?._ === 'messageExtendedMedia' ? _messageMediaFromTl(peers, m.extendedMedia.media) : null + + return new Invoice(m, extended) + } default: return null } diff --git a/packages/client/src/types/messages/message-reactions.ts b/packages/client/src/types/messages/message-reactions.ts index e5d8853f..cc135a9d 100644 --- a/packages/client/src/types/messages/message-reactions.ts +++ b/packages/client/src/types/messages/message-reactions.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../..' import { makeInspectable } from '../../utils' import { PeersIndex } from '../peers' import { PeerReaction } from '../reactions/peer-reaction' @@ -11,7 +10,6 @@ import { ReactionCount } from '../reactions/reaction-count' */ export class MessageReactions { constructor( - readonly client: TelegramClient, readonly messageId: number, readonly chatId: number, readonly raw: tl.RawMessageReactions, @@ -47,7 +45,7 @@ export class MessageReactions { } return (this._recentReactions ??= this.raw.recentReactions.map( - (reaction) => new PeerReaction(this.client, reaction, this._peers), + (reaction) => new PeerReaction(reaction, this._peers), )) } } diff --git a/packages/client/src/types/messages/message.ts b/packages/client/src/types/messages/message.ts index 5ed31659..57de3388 100644 --- a/packages/client/src/types/messages/message.ts +++ b/packages/client/src/types/messages/message.ts @@ -8,12 +8,11 @@ import { } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { BotKeyboard, ReplyMarkup } from '../bots' -import { InputMediaLike, Sticker, WebPage } from '../media' -import { FormattedString } from '../parser' -import { Chat, InputPeerLike, PeersIndex, User } from '../peers' +import { BotKeyboard, ReplyMarkup } from '../bots/keyboards' +import { Chat } from '../peers/chat' +import { PeersIndex } from '../peers/peers-index' +import { User } from '../peers/user' import { _messageActionFromTl, MessageAction } from './message-action' import { MessageEntity } from './message-entity' import { _messageMediaFromTl, MessageMedia } from './message-media' @@ -101,12 +100,11 @@ export class Message { readonly raw: tl.RawMessage | tl.RawMessageService constructor( - readonly client: TelegramClient, raw: tl.TypeMessage, readonly _peers: PeersIndex, /** * Whether the message is scheduled. - * If it is, then its {@link Message.date} is set to future. + * If it is, then its {@link date} is set to future. */ readonly isScheduled = false, ) { @@ -191,7 +189,7 @@ export class Message { if (!from) { if (this.raw.peerId._ === 'peerUser') { - this._sender = new User(this.client, this._peers.user(this.raw.peerId.userId)) + this._sender = new User(this._peers.user(this.raw.peerId.userId)) } else { // anon admin, return the chat this._sender = this.chat @@ -199,10 +197,10 @@ export class Message { } else { switch (from._) { case 'peerChannel': // forwarded channel post - this._sender = new Chat(this.client, this._peers.chat(from.channelId)) + this._sender = new Chat(this._peers.chat(from.channelId)) break case 'peerUser': - this._sender = new User(this.client, this._peers.user(from.userId)) + this._sender = new User(this._peers.user(from.userId)) break default: throw new MtTypeAssertionError('raw.fromId', 'peerUser | peerChannel', from._) @@ -220,7 +218,7 @@ export class Message { */ get chat(): Chat { if (this._chat === undefined) { - this._chat = Chat._parseFromMessage(this.client, this.raw, this._peers) + this._chat = Chat._parseFromMessage(this.raw, this._peers) } return this._chat @@ -252,10 +250,10 @@ export class Message { } else if (fwd.fromId) { switch (fwd.fromId._) { case 'peerChannel': - sender = new Chat(this.client, this._peers.chat(fwd.fromId.channelId)) + sender = new Chat(this._peers.chat(fwd.fromId.channelId)) break case 'peerUser': - sender = new User(this.client, this._peers.user(fwd.fromId.userId)) + sender = new User(this._peers.user(fwd.fromId.userId)) break default: throw new MtTypeAssertionError('raw.fwdFrom.fromId', 'peerUser | peerChannel', fwd.fromId._) @@ -377,7 +375,7 @@ export class Message { if (this.raw._ === 'messageService' || !this.raw.viaBotId) { this._viaBot = null } else { - this._viaBot = new User(this.client, this._peers.user(this.raw.viaBotId)) + this._viaBot = new User(this._peers.user(this.raw.viaBotId)) } } @@ -443,7 +441,7 @@ export class Message { if (this.raw._ === 'messageService' || !this.raw.media || this.raw.media._ === 'messageMediaEmpty') { this._media = null } else { - this._media = _messageMediaFromTl(this.client, this._peers, this.raw.media) + this._media = _messageMediaFromTl(this._peers, this.raw.media) } } @@ -534,7 +532,6 @@ export class Message { this._reactions = null } else { this._reactions = new MessageReactions( - this.client, this.raw.id, getMarkedPeerId(this.raw.peerId), this.raw.reactions, @@ -562,390 +559,6 @@ export class Message { throw new MtArgumentError(`Cannot generate message link for ${this.chat.chatType}`) } - - /** - * Get the message text formatted with a given parse mode - * - * Shorthand for `client.getParseMode(...).unparse(msg.text, msg.entities)` - * - * @param parseMode Parse mode to use (`null` for default) - */ - unparse(parseMode?: string | null): string { - if (this.raw._ === 'messageService') return '' - - return this.client.getParseMode(parseMode).unparse(this.text, this.raw.entities ?? []) - } - - /** - * For replies, fetch the message that is being replied. - * - * Note that even if a message has {@link replyToMessageId}, - * the message itself may have been deleted, in which case - * this method will also return `null`. - */ - getReplyTo(): Promise { - if (!this.replyToMessageId) return Promise.resolve(null) - - if (this.raw.peerId._ === 'peerChannel') { - return this.client.getMessages(this.chat.inputPeer, this.id, true) - } - - return this.client.getMessagesUnsafe(this.id, true) - } - - /** - * Send a message as an answer to this message. - * - * This just sends a message to the same chat - * as this message - * - * @param text Text of the message - * @param params - */ - answerText( - text: string | FormattedString, - params?: Parameters[2], - ): ReturnType { - return this.client.sendText(this.chat.inputPeer, text, params) - } - - /** - * Send a media as an answer to this message. - * - * This just sends a message to the same chat - * as this message - * - * @param media Media to send - * @param params - */ - answerMedia( - media: InputMediaLike | string, - params?: Parameters[2], - ): ReturnType { - return this.client.sendMedia(this.chat.inputPeer, media, params) - } - - /** - * Send a media group as an answer to this message. - * - * This just sends a message to the same chat - * as this message - * - * @param medias Medias to send - * @param params - */ - answerMediaGroup( - medias: (InputMediaLike | string)[], - params?: Parameters[2], - ): ReturnType { - return this.client.sendMediaGroup(this.chat.inputPeer, medias, params) - } - - /** - * Send a message in reply to this message. - * - * @param text Text of the message - * @param params - */ - replyText( - text: string | FormattedString, - params?: Parameters[2], - ): ReturnType { - if (!params) params = {} - params.replyTo = this.id - - return this.client.sendText(this.chat.inputPeer, text, params) - } - - /** - * Send a media in reply to this message. - * - * @param media Media to send - * @param params - */ - replyMedia( - media: InputMediaLike | string, - params?: Parameters[2], - ): ReturnType { - if (!params) params = {} - params.replyTo = this.id - - return this.client.sendMedia(this.chat.inputPeer, media, params) - } - - /** - * Send a media group in reply to this message. - * - * @param medias Medias to send - * @param params - */ - replyMediaGroup( - medias: (InputMediaLike | string)[], - params?: Parameters[2], - ): ReturnType { - if (!params) params = {} - params.replyTo = this.id - - return this.client.sendMediaGroup(this.chat.inputPeer, medias, params) - } - - /** - * Send a text-only comment to this message. - * - * If this is a normal message (not a channel post), - * a simple reply will be sent. - * - * If this post does not have comments section, - * {@link MtArgumentError} is thrown. To check - * if a message has comments, use {@link replies} - * - * @param text Text of the message - * @param params - */ - commentText( - text: string | FormattedString, - params?: Parameters[2], - ): ReturnType { - if (this.chat.chatType !== 'channel') { - return this.replyText(text, params) - } - - if (!this.replies || !this.replies.isComments) { - throw new MtArgumentError('This message does not have comments section') - } - - if (!params) params = {} - params.commentTo = this.id - - return this.client.sendText(this.chat.inputPeer, text, params) - } - - /** - * Send a media comment to this message - * . - * If this is a normal message (not a channel post), - * a simple reply will be sent. - * - * If this post does not have comments section, - * {@link MtArgumentError} is thrown. To check - * if a message has comments, use {@link replies} - * - * @param media Media to send - * @param params - */ - commentMedia( - media: InputMediaLike | string, - params?: Parameters[2], - ): ReturnType { - if (this.chat.chatType !== 'channel') { - return this.replyMedia(media, params) - } - - if (!this.replies || !this.replies.isComments) { - throw new MtArgumentError('This message does not have comments section') - } - if (!params) params = {} - params.commentTo = this.id - - return this.client.sendMedia(this.chat.inputPeer, media, params) - } - - /** - * Send a media group comment to this message - * . - * If this is a normal message (not a channel post), - * a simple reply will be sent. - * - * If this post does not have comments section, - * {@link MtArgumentError} is thrown. To check - * if a message has comments, use {@link replies} - * - * @param medias Medias to send - * @param params - */ - commentMediaGroup( - medias: (InputMediaLike | string)[], - params?: Parameters[2], - ): ReturnType { - if (this.chat.chatType !== 'channel') { - return this.replyMediaGroup(medias, params) - } - - if (!this.replies || !this.replies.isComments) { - throw new MtArgumentError('This message does not have comments section') - } - if (!params) params = {} - params.commentTo = this.id - - return this.client.sendMediaGroup(this.chat.inputPeer, medias, params) - } - - /** - * Delete this message. - * - * @param revoke Whether to "revoke" (i.e. delete for both sides). Only used for chats and private chats. - */ - delete(revoke = false): Promise { - return this.client.deleteMessages(this.chat.inputPeer, this.id, { revoke }) - } - - /** - * Pin this message. - */ - pin(params?: Parameters[2]): Promise { - return this.client.pinMessage(this.chat.inputPeer, this.id, params) - } - - /** - * Unpin this message. - */ - unpin(): Promise { - return this.client.pinMessage(this.chat.inputPeer, this.id) - } - - // /** - // * Edit this message's text and/or reply markup - // * - // * @link TelegramClient.editMessage - // */ - // edit(params: Parameters[2]): Promise { - // return this.client.editMessage(this.chat.inputPeer, this.id, params) - // } - - // /** - // * Edit message text and optionally reply markup. - // * - // * Convenience method that just wraps {@link edit}, - // * passing positional `text` as object field. - // * - // * @param text New message text - // * @param params? Additional parameters - // * @link TelegramClient.editMessage - // */ - // editText( - // text: string | FormattedString, - // params?: Omit[2], 'text'>, - // ): Promise { - // return this.edit({ - // text, - // ...(params || {}), - // }) - // } - - /** - * Forward this message to some chat - * - * @param peer Chat where to forward this message - * @param params - * @returns Forwarded message - */ - forwardTo( - peer: InputPeerLike, - params?: Omit[0], 'messages' | 'toChatId' | 'fromChatId'>, - ): Promise { - return this.client.forwardMessages({ - toChatId: peer, - fromChatId: this.chat.inputPeer, - messages: this.id, - ...params, - }) - } - - /** - * Send this message as a copy (i.e. send the same message, but do not forward it). - * - * Note that if the message contains a webpage, - * it will be copied simply as a text message, - * and if the message contains an invoice, - * it can't be copied. - * - * @param toChatId Target chat ID - * @param params Copy parameters - */ - sendCopy( - toChatId: InputPeerLike, - params?: Omit[0], 'fromChatId' | 'message' | 'toChatId'>, - ): Promise { - if (!params) params = {} - - if (this.raw._ === 'messageService') { - throw new MtArgumentError("Service messages can't be copied") - } - - if (this.media && !(this.media instanceof WebPage)) { - return this.client.sendMedia( - toChatId, - { - type: 'auto', - file: this.media.inputMedia, - caption: params.caption ?? this.raw.message, - // we shouldn't use original entities if the user wants custom text - entities: params.entities ?? params.caption ? undefined : this.raw.entities, - }, - params, - ) - } - - return this.client.sendText(toChatId, this.raw.message, params) - } - - /** - * Get all messages inside a group that this - * message belongs to (see {@link groupedId}), - * including this message. - * - * In case this message is not inside of a group, - * will just return itself. - */ - async getGroup(): Promise { - if (!this.groupedId) return [this] - - return this.client.getMessageGroup(this.chat.inputPeer, this.raw.id) - } - - /** - * Get discussion message for some channel post. - * - * Returns `null` if the post does not have a discussion - * message. - */ - async getDiscussionMessage(): Promise { - return this.client.getDiscussionMessage(this.chat.inputPeer, this.raw.id) - } - - /** - * Read history in the chat up until this message - * - * @param clearMentions Whether to also clear mentions - */ - async read(clearMentions = false): Promise { - return this.client.readHistory(this.chat.inputPeer, { maxId: this.raw.id, clearMentions }) - } - - /** - * React to this message - * - * @param emoji Reaction emoji - * @param big Whether to use a big reaction - */ - async react(emoji: string | null, big?: boolean): Promise { - return this.client.sendReaction({ - chatId: this.chat.inputPeer, - message: this.raw.id, - emoji, - big, - }) - } - - async getCustomEmojis(): Promise { - if (this.raw._ === 'messageService' || !this.raw.entities) return [] - - return this.client.getCustomEmojis( - this.raw.entities - .filter((it) => it._ === 'messageEntityCustomEmoji') - .map((it) => (it as tl.RawMessageEntityCustomEmoji).documentId), - ) - } } makeInspectable(Message, ['isScheduled'], ['link']) diff --git a/packages/client/src/types/misc/sticker-set.ts b/packages/client/src/types/misc/sticker-set.ts index 5c8e7962..75be987e 100644 --- a/packages/client/src/types/misc/sticker-set.ts +++ b/packages/client/src/types/misc/sticker-set.ts @@ -1,7 +1,6 @@ import { MtTypeAssertionError, tl } from '@mtcute/core' import { LongMap } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { MtEmptyError } from '../errors' import { InputFileLike } from '../files' @@ -102,10 +101,7 @@ export class StickerSet { */ readonly isFull: boolean - constructor( - readonly client: TelegramClient, - raw: tl.TypeStickerSet | tl.messages.TypeStickerSet, - ) { + constructor(raw: tl.TypeStickerSet | tl.messages.TypeStickerSet) { if (raw._ === 'messages.stickerSet') { this.full = raw this.brief = raw.set @@ -218,7 +214,7 @@ export class StickerSet { const index = new LongMap>() this.full!.documents.forEach((doc) => { - const sticker = parseDocument(this.client, doc as tl.RawDocument) + const sticker = parseDocument(doc as tl.RawDocument) if (!(sticker instanceof Sticker)) { throw new MtTypeAssertionError('full.documents', 'Sticker', sticker.mimeType) @@ -255,7 +251,7 @@ export class StickerSet { * (i.e. first sticker should be used as thumbnail) */ get thumbnails(): ReadonlyArray { - return (this._thumbnails ??= this.brief.thumbs?.map((sz) => new Thumbnail(this.client, this.brief, sz)) ?? []) + return (this._thumbnails ??= this.brief.thumbs?.map((sz) => new Thumbnail(this.brief, sz)) ?? []) } /** @@ -282,31 +278,6 @@ export class StickerSet { return this.stickers.filter((it) => it.alt === emoji || it.emoji.includes(emoji)) } - /** - * Get full sticker set object. - * - * If this object is already full, this method will just - * return `this` - */ - async getFull(): Promise { - if (this.isFull) return this - - return this.client.getStickerSet(this.inputStickerSet) - } - - /** - * Add a new sticker to this sticker set. - * - * Only for bots, and the sticker set must - * have been created by this bot. - * - * Note that this method returns a new - * {@link StickerSet} object instead of modifying current. - */ - async addSticker(sticker: InputStickerSetItem): Promise { - return this.client.addStickerToSet(this.inputStickerSet, sticker) - } - private _getInputDocument(idx: number): tl.TypeInputDocument { if (!this.full) throw new MtEmptyError() @@ -324,74 +295,6 @@ export class StickerSet { fileReference: doc.fileReference, } } - - /** - * Delete a sticker from this set. - * - * Only for bots, and the sticker set must - * have been created by this bot. - * - * Note that this method returns a new - * {@link StickerSet} object instead of modifying current. - * - * @param sticker - * Sticker File ID. In case this is a full sticker set object, - * you can also pass index (even negative), and that sticker will be removed - */ - async deleteSticker(sticker: number | Parameters[0]): Promise { - if (typeof sticker === 'number') { - sticker = this._getInputDocument(sticker) - } - - return this.client.deleteStickerFromSet(sticker) - } - - /** - * Move a sticker in this set. - * - * Only for bots, and the sticker set must - * have been created by this bot. - * - * Note that this method returns a new - * {@link StickerSet} object instead of modifying current. - * - * @param sticker - * Sticker File ID. In case this is a full sticker set object, - * you can also pass index (even negative), and that sticker will be removed - * @param position New sticker position - */ - async moveSticker( - sticker: number | Parameters[0], - position: number, - ): Promise { - if (typeof sticker === 'number') { - sticker = this._getInputDocument(sticker) - } - - return this.client.moveStickerInSet(sticker, position) - } - - /** - * Set sticker set thumbnail. - * - * Only for bots, and the sticker set must - * have been created by this bot. - * - * Note that this method returns a new - * {@link StickerSet} object instead of modifying current. - * - * @param thumb - * Thumbnail file. In case this is a full sticker set object, - * you can also pass index (even negative), and that sticker - * will be used as a thumb - */ - async setThumb(thumb: number | Parameters[1]): Promise { - if (typeof thumb === 'number') { - thumb = this._getInputDocument(thumb) - } - - return this.client.setStickerSetThumb(this.inputStickerSet, thumb) - } } makeInspectable(StickerSet, ['isFull']) diff --git a/packages/client/src/types/misc/takeout-session.ts b/packages/client/src/types/misc/takeout-session.ts index 2d89b7b4..38578a25 100644 --- a/packages/client/src/types/misc/takeout-session.ts +++ b/packages/client/src/types/misc/takeout-session.ts @@ -1,6 +1,5 @@ -import { MustEqual, RpcCallOptions, tl } from '@mtcute/core' +import { BaseTelegramClient, MustEqual, RpcCallOptions, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' /** @@ -13,7 +12,7 @@ export class TakeoutSession { readonly id: tl.Long constructor( - readonly client: TelegramClient, + readonly client: BaseTelegramClient, session: tl.account.RawTakeout, ) { this.id = session.id @@ -61,7 +60,7 @@ export class TakeoutSession { * that call should be called via takeout session or not. * Returning `true` will use takeout session, `false` will not. */ - createProxy(predicate?: (obj: tl.TlObject) => boolean): TelegramClient { + createProxy(predicate?: (obj: tl.TlObject) => boolean): BaseTelegramClient { const boundCall: TakeoutSession['call'] = predicate ? (obj, params) => { if (predicate(obj)) { diff --git a/packages/client/src/types/peers/chat-event/actions.ts b/packages/client/src/types/peers/chat-event/actions.ts index b668e5da..1f6cbf83 100644 --- a/packages/client/src/types/peers/chat-event/actions.ts +++ b/packages/client/src/types/peers/chat-event/actions.ts @@ -1,13 +1,14 @@ -import { tl } from '@mtcute/core' +import { tl, toggleChannelIdMark } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { ForumTopic, PeersIndex, TelegramClient, toggleChannelIdMark } from '../../..' -import { Photo } from '../../media' -import { Message } from '../../messages' +import { Photo } from '../../media/photo' +import { Message } from '../../messages/message' import { ChatInviteLink } from '../chat-invite-link' import { ChatLocation } from '../chat-location' import { ChatMember } from '../chat-member' import { ChatPermissions } from '../chat-permissions' +import { ForumTopic } from '../forum-topic' +import { PeersIndex } from '../peers-index' import { User } from '../user' /** A user has joined the channel (in the case of big groups, info of the user that has joined isn't shown) */ @@ -393,11 +394,7 @@ export type ChatAction = | null /** @internal */ -export function _actionFromTl( - e: tl.TypeChannelAdminLogEventAction, - client: TelegramClient, - peers: PeersIndex, -): ChatAction { +export function _actionFromTl(e: tl.TypeChannelAdminLogEventAction, peers: PeersIndex): ChatAction { // todo - MTQ-72 // channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction; // channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions @@ -433,8 +430,8 @@ export function _actionFromTl( case 'channelAdminLogEventActionChangePhoto': return { type: 'photo_changed', - old: new Photo(client, e.prevPhoto as tl.RawPhoto), - new: new Photo(client, e.newPhoto as tl.RawPhoto), + old: new Photo(e.prevPhoto as tl.RawPhoto), + new: new Photo(e.newPhoto as tl.RawPhoto), } case 'channelAdminLogEventActionToggleInvites': return { @@ -451,37 +448,37 @@ export function _actionFromTl( case 'channelAdminLogEventActionUpdatePinned': return { type: 'msg_pinned', - message: new Message(client, e.message, peers), + message: new Message(e.message, peers), } case 'channelAdminLogEventActionEditMessage': return { type: 'msg_edited', - old: new Message(client, e.prevMessage, peers), - new: new Message(client, e.newMessage, peers), + old: new Message(e.prevMessage, peers), + new: new Message(e.newMessage, peers), } case 'channelAdminLogEventActionDeleteMessage': return { type: 'msg_deleted', - message: new Message(client, e.message, peers), + message: new Message(e.message, peers), } case 'channelAdminLogEventActionParticipantLeave': return { type: 'user_left' } case 'channelAdminLogEventActionParticipantInvite': return { type: 'user_invited', - member: new ChatMember(client, e.participant, peers), + member: new ChatMember(e.participant, peers), } case 'channelAdminLogEventActionParticipantToggleBan': return { type: 'user_perms_changed', - old: new ChatMember(client, e.prevParticipant, peers), - new: new ChatMember(client, e.newParticipant, peers), + old: new ChatMember(e.prevParticipant, peers), + new: new ChatMember(e.newParticipant, peers), } case 'channelAdminLogEventActionParticipantToggleAdmin': return { type: 'user_admin_perms_changed', - old: new ChatMember(client, e.prevParticipant, peers), - new: new ChatMember(client, e.newParticipant, peers), + old: new ChatMember(e.prevParticipant, peers), + new: new ChatMember(e.newParticipant, peers), } case 'channelAdminLogEventActionChangeStickerSet': return { @@ -504,7 +501,7 @@ export function _actionFromTl( case 'channelAdminLogEventActionStopPoll': return { type: 'poll_stopped', - message: new Message(client, e.message, peers), + message: new Message(e.message, peers), } case 'channelAdminLogEventActionChangeLinkedChat': return { @@ -515,8 +512,8 @@ export function _actionFromTl( case 'channelAdminLogEventActionChangeLocation': return { type: 'location_changed', - old: e.prevValue._ === 'channelLocationEmpty' ? null : new ChatLocation(client, e.prevValue), - new: e.newValue._ === 'channelLocationEmpty' ? null : new ChatLocation(client, e.newValue), + old: e.prevValue._ === 'channelLocationEmpty' ? null : new ChatLocation(e.prevValue), + new: e.newValue._ === 'channelLocationEmpty' ? null : new ChatLocation(e.newValue), } case 'channelAdminLogEventActionToggleSlowMode': return { @@ -547,23 +544,23 @@ export function _actionFromTl( case 'channelAdminLogEventActionParticipantJoinByInvite': return { type: 'user_joined_invite', - link: new ChatInviteLink(client, e.invite, peers), + link: new ChatInviteLink(e.invite, peers), } case 'channelAdminLogEventActionExportedInviteDelete': return { type: 'invite_deleted', - link: new ChatInviteLink(client, e.invite, peers), + link: new ChatInviteLink(e.invite, peers), } case 'channelAdminLogEventActionExportedInviteRevoke': return { type: 'invite_revoked', - link: new ChatInviteLink(client, e.invite, peers), + link: new ChatInviteLink(e.invite, peers), } case 'channelAdminLogEventActionExportedInviteEdit': return { type: 'invite_edited', - old: new ChatInviteLink(client, e.prevInvite, peers), - new: new ChatInviteLink(client, e.newInvite, peers), + old: new ChatInviteLink(e.prevInvite, peers), + new: new ChatInviteLink(e.newInvite, peers), } case 'channelAdminLogEventActionChangeHistoryTTL': return { @@ -574,8 +571,8 @@ export function _actionFromTl( case 'channelAdminLogEventActionParticipantJoinByRequest': return { type: 'user_joined_approved', - link: new ChatInviteLink(client, e.invite, peers), - approvedBy: new User(client, peers.user(e.approvedBy)), + link: new ChatInviteLink(e.invite, peers), + approvedBy: new User(peers.user(e.approvedBy)), } // channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLogEventAction; // channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic @@ -590,7 +587,7 @@ export function _actionFromTl( return { type: 'topic_created', - topic: new ForumTopic(client, e.topic, peers), + topic: new ForumTopic(e.topic, peers), } case 'channelAdminLogEventActionEditTopic': assertTypeIs('ChannelAdminLogEventActionCreateTopic#topic', e.prevTopic, 'forumTopic') @@ -598,15 +595,15 @@ export function _actionFromTl( return { type: 'topic_edited', - old: new ForumTopic(client, e.prevTopic, peers), - new: new ForumTopic(client, e.newTopic, peers), + old: new ForumTopic(e.prevTopic, peers), + new: new ForumTopic(e.newTopic, peers), } case 'channelAdminLogEventActionDeleteTopic': assertTypeIs('ChannelAdminLogEventActionCreateTopic#topic', e.topic, 'forumTopic') return { type: 'topic_deleted', - topic: new ForumTopic(client, e.topic, peers), + topic: new ForumTopic(e.topic, peers), } case 'channelAdminLogEventActionToggleNoForwards': return { diff --git a/packages/client/src/types/peers/chat-event/index.ts b/packages/client/src/types/peers/chat-event/index.ts index 5150ea6f..bbbbbec0 100644 --- a/packages/client/src/types/peers/chat-event/index.ts +++ b/packages/client/src/types/peers/chat-event/index.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../../client' import { makeInspectable } from '../../../utils' import { PeersIndex } from '../peers-index' import { User } from '../user' @@ -11,7 +10,6 @@ export { InputChatEventFilters } from './filters' export class ChatEvent { constructor( - readonly client: TelegramClient, readonly raw: tl.TypeChannelAdminLogEvent, readonly _peers: PeersIndex, ) {} @@ -38,12 +36,12 @@ export class ChatEvent { * Actor of the event */ get actor(): User { - return (this._actor ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._actor ??= new User(this._peers.user(this.raw.userId))) } private _action?: ChatAction get action(): ChatAction { - return (this._action ??= _actionFromTl(this.raw.action, this.client, this._peers)) + return (this._action ??= _actionFromTl(this.raw.action, this._peers)) } } diff --git a/packages/client/src/types/peers/chat-invite-link-member.ts b/packages/client/src/types/peers/chat-invite-link-member.ts index 95d6c974..3168c0d3 100644 --- a/packages/client/src/types/peers/chat-invite-link-member.ts +++ b/packages/client/src/types/peers/chat-invite-link-member.ts @@ -1,13 +1,11 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { PeersIndex } from './peers-index' import { User } from './user' export class ChatInviteLinkMember { constructor( - readonly client: TelegramClient, readonly raw: tl.RawChatInviteImporter, readonly _peers: PeersIndex, ) {} @@ -17,7 +15,7 @@ export class ChatInviteLinkMember { * User who joined the chat */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } /** @@ -56,7 +54,7 @@ export class ChatInviteLinkMember { get approvedBy(): User | null { if (!this.raw.approvedBy) return null - return (this._approvedBy ??= new User(this.client, this._peers.user(this.raw.approvedBy))) + return (this._approvedBy ??= new User(this._peers.user(this.raw.approvedBy))) } } diff --git a/packages/client/src/types/peers/chat-invite-link.ts b/packages/client/src/types/peers/chat-invite-link.ts index bfb80ee5..f068c831 100644 --- a/packages/client/src/types/peers/chat-invite-link.ts +++ b/packages/client/src/types/peers/chat-invite-link.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { assertTypeIsNot } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { PeersIndex } from './index' import { User } from './user' @@ -10,10 +9,9 @@ import { User } from './user' * An invite link */ export class ChatInviteLink { - raw: tl.RawChatInviteExported + readonly raw: tl.RawChatInviteExported constructor( - readonly client: TelegramClient, raw: tl.TypeExportedChatInvite, readonly _peers?: PeersIndex, ) { @@ -49,7 +47,7 @@ export class ChatInviteLink { get creator(): User | null { if (!this._peers) return null - return (this._creator ??= new User(this.client, this._peers.user(this.raw.adminId))) + return (this._creator ??= new User(this._peers.user(this.raw.adminId))) } /** diff --git a/packages/client/src/types/peers/chat-location.ts b/packages/client/src/types/peers/chat-location.ts index a8edadd2..66e30a6e 100644 --- a/packages/client/src/types/peers/chat-location.ts +++ b/packages/client/src/types/peers/chat-location.ts @@ -1,24 +1,20 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { Location } from '../media' +import { Location } from '../media/location' /** * Geolocation of a supergroup */ export class ChatLocation { - constructor( - readonly client: TelegramClient, - readonly raw: tl.RawChannelLocation, - ) {} + constructor(readonly raw: tl.RawChannelLocation) {} private _location?: Location /** * Location of the chat */ get location(): Location { - return (this._location ??= new Location(this.client, this.raw.geoPoint as tl.RawGeoPoint)) + return (this._location ??= new Location(this.raw.geoPoint as tl.RawGeoPoint)) } /** diff --git a/packages/client/src/types/peers/chat-member.ts b/packages/client/src/types/peers/chat-member.ts index dc831206..67fe797a 100644 --- a/packages/client/src/types/peers/chat-member.ts +++ b/packages/client/src/types/peers/chat-member.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { ChatPermissions } from './chat-permissions' import { PeersIndex } from './index' @@ -23,7 +22,6 @@ export type ChatMemberStatus = 'creator' | 'admin' | 'member' | 'restricted' | ' */ export class ChatMember { constructor( - readonly client: TelegramClient, readonly raw: tl.TypeChatParticipant | tl.TypeChannelParticipant, readonly _peers: PeersIndex, ) {} @@ -39,10 +37,10 @@ export class ChatMember { case 'channelParticipantLeft': assertTypeIs('ChatMember#user (raw.peer)', this.raw.peer, 'peerUser') - this._user = new User(this.client, this._peers.user(this.raw.peer.userId)) + this._user = new User(this._peers.user(this.raw.peer.userId)) break default: - this._user = new User(this.client, this._peers.user(this.raw.userId)) + this._user = new User(this._peers.user(this.raw.userId)) break } } @@ -118,7 +116,7 @@ export class ChatMember { get invitedBy(): User | null { if (this._invitedBy === undefined) { if ('inviterId' in this.raw && this.raw.inviterId) { - this._invitedBy = new User(this.client, this._peers.user(this.raw.inviterId)) + this._invitedBy = new User(this._peers.user(this.raw.inviterId)) } else { this._invitedBy = null } @@ -136,7 +134,7 @@ export class ChatMember { get promotedBy(): User | null { if (this._promotedBy === undefined) { if (this.raw._ === 'channelParticipantAdmin') { - this._promotedBy = new User(this.client, this._peers.user(this.raw.promotedBy)) + this._promotedBy = new User(this._peers.user(this.raw.promotedBy)) } else { this._promotedBy = null } @@ -154,7 +152,7 @@ export class ChatMember { get restrictedBy(): User | null { if (this._restrictedBy === undefined) { if (this.raw._ === 'channelParticipantBanned') { - this._restrictedBy = new User(this.client, this._peers.user(this.raw.kickedBy)) + this._restrictedBy = new User(this._peers.user(this.raw.kickedBy)) } else { this._restrictedBy = null } diff --git a/packages/client/src/types/peers/chat-photo.ts b/packages/client/src/types/peers/chat-photo.ts index 5154a506..1ca035fc 100644 --- a/packages/client/src/types/peers/chat-photo.ts +++ b/packages/client/src/types/peers/chat-photo.ts @@ -1,7 +1,6 @@ import { Long, MtArgumentError, tl, toggleChannelIdMark } from '@mtcute/core' import { tdFileId, toFileId, toUniqueFileId } from '@mtcute/file-id' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { strippedPhotoToJpg } from '../../utils/file-utils' import { FileLocation } from '../files' @@ -11,13 +10,11 @@ import { FileLocation } from '../files' */ export class ChatPhotoSize extends FileLocation { constructor( - readonly client: TelegramClient, readonly peer: tl.TypeInputPeer, readonly obj: tl.RawUserProfilePhoto | tl.RawChatPhoto, readonly big: boolean, ) { super( - client, { _: 'inputPeerPhotoFileLocation', peer, @@ -110,7 +107,6 @@ makeInspectable(ChatPhotoSize, ['dcId', 'big']) */ export class ChatPhoto { constructor( - readonly client: TelegramClient, readonly peer: tl.TypeInputPeer, readonly raw: tl.RawUserProfilePhoto | tl.RawChatPhoto, ) {} @@ -126,14 +122,14 @@ export class ChatPhoto { /** Chat photo file location in small resolution (160x160) */ get small(): ChatPhotoSize { - return (this._smallFile ??= new ChatPhotoSize(this.client, this.peer, this.raw, false)) + return (this._smallFile ??= new ChatPhotoSize(this.peer, this.raw, false)) } private _bigFile?: ChatPhotoSize /** Chat photo file location in big resolution (640x640) */ get big(): ChatPhotoSize { - return (this._bigFile ??= new ChatPhotoSize(this.client, this.peer, this.raw, true)) + return (this._bigFile ??= new ChatPhotoSize(this.peer, this.raw, true)) } private _thumb?: Buffer diff --git a/packages/client/src/types/peers/chat-preview.ts b/packages/client/src/types/peers/chat-preview.ts index 0bcadbbf..be431c8f 100644 --- a/packages/client/src/types/peers/chat-preview.ts +++ b/packages/client/src/types/peers/chat-preview.ts @@ -1,9 +1,7 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { Photo } from '../media' -import { Chat } from './chat' import { User } from './user' /** @@ -16,7 +14,6 @@ export type ChatPreviewType = 'group' | 'supergroup' | 'channel' export class ChatPreview { constructor( - readonly client: TelegramClient, readonly invite: tl.RawChatInvite, /** * Original invite link used to fetch this preview @@ -62,7 +59,7 @@ export class ChatPreview { get photo(): Photo | null { if (this.invite.photo._ === 'photoEmpty') return null - return (this._photo ??= new Photo(this.client, this.invite.photo)) + return (this._photo ??= new Photo(this.invite.photo)) } private _someMembers?: User[] @@ -75,7 +72,7 @@ export class ChatPreview { */ get someMembers(): ReadonlyArray { return (this._someMembers ??= this.invite.participants ? - this.invite.participants.map((it) => new User(this.client, it)) : + this.invite.participants.map((it) => new User(it)) : []) } @@ -86,13 +83,6 @@ export class ChatPreview { get withApproval(): boolean { return this.invite.requestNeeded! } - - /** - * Join this chat - */ - async join(): Promise { - return this.client.joinChat(this.link) - } } makeInspectable(ChatPreview, ['link']) diff --git a/packages/client/src/types/peers/chat.ts b/packages/client/src/types/peers/chat.ts index a8c7132d..1d30dc14 100644 --- a/packages/client/src/types/peers/chat.ts +++ b/packages/client/src/types/peers/chat.ts @@ -1,13 +1,12 @@ -import { getMarkedPeerId, MaybeArray, MtArgumentError, MtTypeAssertionError, tl } from '@mtcute/core' +import { getMarkedPeerId, MtArgumentError, MtTypeAssertionError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { InputMediaLike } from '../media' -import { FormattedString } from '../parser' +import { MessageEntity } from '../messages/message-entity' import { ChatLocation } from './chat-location' import { ChatPermissions } from './chat-permissions' import { ChatPhoto } from './chat-photo' -import { InputPeerLike, PeersIndex, User } from './index' +import { PeersIndex } from './peers-index' +import { User } from './user' /** * Chat type. Can be: @@ -32,7 +31,6 @@ export class Chat { readonly peer: tl.RawUser | tl.RawChat | tl.RawChannel | tl.RawChatForbidden | tl.RawChannelForbidden constructor( - readonly client: TelegramClient, peer: tl.TypeUser | tl.TypeChat, readonly fullPeer?: tl.TypeUserFull | tl.TypeChatFull, ) { @@ -49,9 +47,7 @@ export class Chat { throw new MtTypeAssertionError('peer', 'user | chat | channel', peer._) } - this.client = client this.peer = peer - this.fullPeer = fullPeer } /** Marked ID of this chat */ @@ -315,7 +311,7 @@ export class Chat { return null } - return (this._photo ??= new ChatPhoto(this.client, this.inputPeer, this.peer.photo)) + return (this._photo ??= new ChatPhoto(this.inputPeer, this.peer.photo)) } /** @@ -451,7 +447,7 @@ export class Chat { return null } - return (this._location ??= new ChatLocation(this.client, this.fullPeer.location)) + return (this._location ??= new ChatLocation(this.fullPeer.location)) } private _linkedChat?: Chat @@ -496,32 +492,28 @@ export class Chat { get user(): User | null { if (this.peer._ !== 'user') return null - return (this._user ??= new User(this.client, this.peer)) + return (this._user ??= new User(this.peer)) } /** @internal */ - static _parseFromMessage( - client: TelegramClient, - message: tl.RawMessage | tl.RawMessageService, - peers: PeersIndex, - ): Chat { - return Chat._parseFromPeer(client, message.peerId, peers) + static _parseFromMessage(message: tl.RawMessage | tl.RawMessageService, peers: PeersIndex): Chat { + return Chat._parseFromPeer(message.peerId, peers) } /** @internal */ - static _parseFromPeer(client: TelegramClient, peer: tl.TypePeer, peers: PeersIndex): Chat { + static _parseFromPeer(peer: tl.TypePeer, peers: PeersIndex): Chat { switch (peer._) { case 'peerUser': - return new Chat(client, peers.user(peer.userId)) + return new Chat(peers.user(peer.userId)) case 'peerChat': - return new Chat(client, peers.chat(peer.chatId)) + return new Chat(peers.chat(peer.chatId)) } - return new Chat(client, peers.chat(peer.channelId)) + return new Chat(peers.chat(peer.channelId)) } /** @internal */ - static _parseFull(client: TelegramClient, full: tl.messages.RawChatFull | tl.users.TypeUserFull): Chat { + static _parseFull(full: tl.messages.RawChatFull | tl.users.TypeUserFull): Chat { if (full._ === 'users.userFull') { const user = full.users.find((it) => it.id === full.fullUser.id) @@ -529,7 +521,7 @@ export class Chat { throw new MtTypeAssertionError('Chat._parseFull', 'user', user?._ ?? 'undefined') } - return new Chat(client, user, full.fullUser) + return new Chat(user, full.fullUser) } const fullChat = full.fullChat @@ -545,8 +537,8 @@ export class Chat { } } - const ret = new Chat(client, chat!, fullChat) - ret._linkedChat = linked ? new Chat(client, linked) : undefined + const ret = new Chat(chat!, fullChat) + ret._linkedChat = linked ? new Chat(linked) : undefined return ret } @@ -555,7 +547,7 @@ export class Chat { * Create a mention for the chat. * * If this is a user, works just like {@link User.mention}. - * Otherwise, if the chat has a username, a @username is created + * Otherwise, if the chat has a username, a `@username` is created * (or text link, if `text` is passed). If it does not, chat title is * simply returned without additional formatting. * @@ -565,15 +557,18 @@ export class Chat { * Use `null` as `text` (first parameter) to force create a text * mention with display name, even if there is a username. * + * > **Note**: This method doesn't format anything on its own. + * > Instead, it returns a {@link MessageEntity} that can later + * > be used with `html` or `md` template tags, or `unparse` method directly. + * * @param text Text of the mention. - * @param parseMode Parse mode to use when creating mention. * @example * ```typescript - * msg.replyText(`Hello, ${msg.chat.mention()`) + * msg.replyText(html`Hello, ${msg.chat.mention()`) * ``` */ - mention(text?: string | null, parseMode?: T | null): string | FormattedString { - if (this.user) return this.user.mention(text, parseMode) + mention(text?: string | null): string | MessageEntity { + if (this.user) return this.user.mention(text) if (text === undefined && this.username) { return `@${this.username}` @@ -582,103 +577,16 @@ export class Chat { if (!text) text = this.displayName if (!this.username) return text - // eslint-disable-next-line dot-notation - if (!parseMode) parseMode = this.client['_defaultParseMode'] as T - - return new FormattedString( - this.client.getParseMode(parseMode).unparse(text, [ - { - _: 'messageEntityTextUrl', - offset: 0, - length: text.length, - url: `https://t.me/${this.username}`, - }, - ]), - parseMode, + return new MessageEntity( + { + _: 'messageEntityTextUrl', + offset: 0, + length: text.length, + url: `https://t.me/${this.username}`, + }, + text, ) } - - /** - * Join this chat. - */ - async join(): Promise { - await this.client.joinChat(this.inputPeer) - } - - /** - * Add user(s) to this chat - * - * @param users ID(s) of the users, their username(s) or phone(s). - * @param forwardCount - * Number of old messages to be forwarded (0-100). - * Only applicable to legacy groups, ignored for supergroups and channels - */ - async addMembers(users: MaybeArray, forwardCount?: number): Promise { - return this.client.addChatMembers(this.inputPeer, users, { forwardCount }) - } - - /** - * Archive this chat - */ - async archive(): Promise { - return this.client.archiveChats(this.inputPeer) - } - - /** - * Unarchive this chat - */ - async unarchive(): Promise { - return this.client.unarchiveChats(this.inputPeer) - } - - /** - * Read history in this chat - * - * @param message Message up until which to read history (by default everything is read) - * @param clearMentions Whether to also clear all mentions in the chat - */ - async readHistory(message = 0, clearMentions = false): Promise { - return this.client.readHistory(this.inputPeer, { maxId: message, clearMentions }) - } - - /** - * Send a text message in this chat. - * - * @param text Text of the message - * @param params - */ - sendText( - text: string | FormattedString, - params?: Parameters[2], - ): ReturnType { - return this.client.sendText(this.inputPeer, text, params) - } - - /** - * Send a media in this chat. - * - * @param media Media to send - * @param params - */ - sendMedia( - media: InputMediaLike | string, - params?: Parameters[2], - ): ReturnType { - return this.client.sendMedia(this.inputPeer, media, params) - } - - /** - * Send a media group in this chat. - * - * @param medias Medias to send - * @param params - */ - sendMediaGroup( - medias: (InputMediaLike | string)[], - params?: Parameters[2], - ): ReturnType { - return this.client.sendMediaGroup(this.inputPeer, medias, params) - } } makeInspectable(Chat, [], ['user']) diff --git a/packages/client/src/types/peers/forum-topic.ts b/packages/client/src/types/peers/forum-topic.ts index c32ec809..69a7f8df 100644 --- a/packages/client/src/types/peers/forum-topic.ts +++ b/packages/client/src/types/peers/forum-topic.ts @@ -1,6 +1,5 @@ import { MtTypeAssertionError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { hasValueAtKey, makeInspectable } from '../../utils' import { MtMessageNotFoundError } from '../errors' import { DraftMessage, Message } from '../messages' @@ -17,13 +16,12 @@ export class ForumTopic { static COLOR_RED = 0xfb6f5f constructor( - readonly client: TelegramClient, readonly raw: tl.RawForumTopic, readonly _peers: PeersIndex, readonly _messages?: Map, ) {} - static parseTlForumTopics(client: TelegramClient, topics: tl.messages.TypeForumTopics): ForumTopic[] { + static parseTlForumTopics(topics: tl.messages.TypeForumTopics): ForumTopic[] { const peers = PeersIndex.from(topics) const messages = new Map() @@ -33,9 +31,7 @@ export class ForumTopic { messages.set(msg.id, msg) }) - return topics.topics - .filter(hasValueAtKey('_', 'forumTopic')) - .map((it) => new ForumTopic(client, it, peers, messages)) + return topics.topics.filter(hasValueAtKey('_', 'forumTopic')).map((it) => new ForumTopic(it, peers, messages)) } /** @@ -117,9 +113,9 @@ export class ForumTopic { switch (this.raw.fromId._) { case 'peerUser': - return (this._creator = new User(this.client, this._peers.user(this.raw.fromId.userId))) + return (this._creator = new User(this._peers.user(this.raw.fromId.userId))) case 'peerChat': - return (this._creator = new Chat(this.client, this._peers.chat(this.raw.fromId.chatId))) + return (this._creator = new Chat(this._peers.chat(this.raw.fromId.chatId))) default: throw new MtTypeAssertionError('ForumTopic#creator', 'peerUser | peerChat', this.raw.fromId._) } @@ -134,7 +130,7 @@ export class ForumTopic { const id = this.raw.topMessage if (this._messages?.has(id)) { - this._lastMessage = new Message(this.client, this._messages.get(id)!, this._peers) + this._lastMessage = new Message(this._messages.get(id)!, this._peers) } else { throw new MtMessageNotFoundError(0, id) } @@ -195,7 +191,7 @@ export class ForumTopic { if (!this.raw.draft || this.raw.draft._ === 'draftMessageEmpty') return null - return (this._draftMessage = new DraftMessage(this.client, this.raw.draft)) + return (this._draftMessage = new DraftMessage(this.raw.draft)) } } diff --git a/packages/client/src/types/peers/user.ts b/packages/client/src/types/peers/user.ts index 0f1ddc0e..4e46c0a4 100644 --- a/packages/client/src/types/peers/user.ts +++ b/packages/client/src/types/peers/user.ts @@ -1,10 +1,8 @@ import { MtArgumentError, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { InputMediaLike } from '../media' -import { FormattedString } from '../parser' +import { MessageEntity } from '../messages/message-entity' import { EmojiStatus } from '../reactions/emoji-status' import { ChatPhoto } from './chat-photo' @@ -35,10 +33,7 @@ export class User { */ readonly raw: tl.RawUser - constructor( - readonly client: TelegramClient, - user: tl.TypeUser, - ) { + constructor(user: tl.TypeUser) { assertTypeIs('User#init', user, 'user') this.raw = user @@ -284,7 +279,7 @@ export class User { get photo(): ChatPhoto | null { if (this.raw.photo?._ !== 'userProfilePhoto') return null - return (this._photo ??= new ChatPhoto(this.client, this.inputPeer, this.raw.photo)) + return (this._photo ??= new ChatPhoto(this.inputPeer, this.raw.photo)) } /** @@ -338,37 +333,36 @@ export class User { * Create a mention for the user. * * When available and `text` is omitted, this method will return `@username`. - * Otherwise, text mention is created for the given (or default) parse mode. + * Otherwise, text mention is created. * * Use `null` as `text` (first parameter) to force create a text * mention with display name, even if there is a username. * + * > **Note**: This method doesn't format anything on its own. + * > Instead, it returns a {@link MessageEntity} that can later + * > be used with `html` or `md` template tags, or `unparse` method directly. + * * @param text Text of the mention. - * @param parseMode Parse mode to use when creating mention. * @example * ```typescript - * msg.replyText(`Hello, ${msg.sender.mention()`) + * msg.replyText(html`Hello, ${msg.sender.mention()`) * ``` */ - mention(text?: string | null, parseMode?: T | null): string | FormattedString { + mention(text?: string | null): string | MessageEntity { if (text === undefined && this.username) { return `@${this.username}` } if (!text) text = this.displayName - // eslint-disable-next-line dot-notation - if (!parseMode) parseMode = this.client['_defaultParseMode'] as T - return new FormattedString( - this.client.getParseMode(parseMode).unparse(text, [ - { - _: 'messageEntityMentionName', - offset: 0, - length: text.length, - userId: this.raw.id, - }, - ]), - parseMode, + return new MessageEntity( + { + _: 'messageEntityMentionName', + offset: 0, + length: text.length, + userId: this.raw.id, + }, + text, ) } @@ -386,84 +380,43 @@ export class User { * somewhere and load it from there if needed. * * This method is only needed when the result will be - * stored somewhere outside current mtcute instance, + * stored somewhere outside current mtcute instance (e.g. saved for later use), * otherwise {@link mention} will be enough. * + * > **Note**: This method doesn't format anything on its own. + * > Instead, it returns a {@link MessageEntity} that can later + * > be used with `html` or `md` template tags, or `unparse` method directly. + * * > **Note**: the resulting text can only be used by clients * > that support mtcute notation of permanent * > mention links (`tg://user?id=123&hash=abc`). * > - * > Both `@mtcute/html-parser` and `@mtcute/markdown-parser` support it. - * > * > Also note that these permanent mentions are only * > valid for current account, since peer access hashes are * > account-specific and can't be used on another account. + * > + * > Also note that for some users such mentions might not work at all + * > due to privacy settings. * * @param text Mention text - * @param parseMode Parse mode to use when creating mention */ - permanentMention(text?: string | null, parseMode?: T | null): FormattedString { + permanentMention(text?: string | null): MessageEntity { if (!this.raw.accessHash) { - throw new MtArgumentError("user's access hash is not available!") + throw new MtArgumentError("User's access hash is not available!") } if (!text) text = this.displayName - // eslint-disable-next-line dot-notation - if (!parseMode) parseMode = this.client['_defaultParseMode'] as T - // since we are just creating a link and not actual tg entity, - // we can use this hack to create a valid link through our parse mode - return new FormattedString( - this.client.getParseMode(parseMode).unparse(text, [ - { - _: 'messageEntityTextUrl', - offset: 0, - length: text.length, - url: `tg://user?id=${this.id}&hash=${this.raw.accessHash.toString(16)}`, - }, - ]), - parseMode, + return new MessageEntity( + { + _: 'messageEntityTextUrl', + offset: 0, + length: text.length, + url: `tg://user?id=${this.id}&hash=${this.raw.accessHash.toString(16)}`, + }, + text, ) } - - /** - * Send a text message to this user. - * - * @param text Text of the message - * @param params - */ - sendText( - text: string | FormattedString, - params?: Parameters[2], - ): ReturnType { - return this.client.sendText(this.inputPeer, text, params) - } - - /** - * Send a media to this user. - * - * @param media Media to send - * @param params - */ - sendMedia( - media: InputMediaLike | string, - params?: Parameters[2], - ): ReturnType { - return this.client.sendMedia(this.inputPeer, media, params) - } - - /** - * Send a media group to this user. - * - * @param medias Medias to send - * @param params - */ - sendMediaGroup( - medias: (InputMediaLike | string)[], - params?: Parameters[2], - ): ReturnType { - return this.client.sendMediaGroup(this.inputPeer, medias, params) - } } makeInspectable(User) diff --git a/packages/client/src/types/reactions/peer-reaction.ts b/packages/client/src/types/reactions/peer-reaction.ts index 09b286c5..63885bcb 100644 --- a/packages/client/src/types/reactions/peer-reaction.ts +++ b/packages/client/src/types/reactions/peer-reaction.ts @@ -1,9 +1,9 @@ import { getMarkedPeerId, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../..' import { makeInspectable } from '../../utils' -import { PeersIndex, User } from '../peers' +import { PeersIndex } from '../peers/peers-index' +import { User } from '../peers/user' import { ReactionEmoji, toReactionEmoji } from './types' /** @@ -11,7 +11,6 @@ import { ReactionEmoji, toReactionEmoji } from './types' */ export class PeerReaction { constructor( - readonly client: TelegramClient, readonly raw: tl.RawMessagePeerReaction, readonly _peers: PeersIndex, ) {} @@ -53,7 +52,7 @@ export class PeerReaction { if (!this._user) { assertTypeIs('PeerReaction#user', this.raw.peerId, 'peerUser') - this._user = new User(this.client, this._peers.user(this.raw.peerId.userId)) + this._user = new User(this._peers.user(this.raw.peerId.userId)) } return this._user diff --git a/packages/client/src/types/stories/all-stories.ts b/packages/client/src/types/stories/all-stories.ts index cd12608a..84eee9e0 100644 --- a/packages/client/src/types/stories/all-stories.ts +++ b/packages/client/src/types/stories/all-stories.ts @@ -1,5 +1,7 @@ -import { PeersIndex, TelegramClient, tl } from '../..' +import { tl } from '@mtcute/core' + import { makeInspectable } from '../../utils' +import { PeersIndex } from '../peers' import { PeerStories } from './peer-stories' import { StoriesStealthMode } from './stealth-mode' @@ -9,10 +11,7 @@ import { StoriesStealthMode } from './stealth-mode' * Returned by {@link TelegramClient.getAllStories} */ export class AllStories { - constructor( - readonly client: TelegramClient, - readonly raw: tl.stories.RawAllStories, - ) {} + constructor(readonly raw: tl.stories.RawAllStories) {} readonly _peers = PeersIndex.from(this.raw) @@ -35,7 +34,7 @@ export class AllStories { /** Peers with their stories */ get peerStories(): PeerStories[] { if (!this._peerStories) { - this._peerStories = this.raw.peerStories.map((it) => new PeerStories(this.client, it, this._peers)) + this._peerStories = this.raw.peerStories.map((it) => new PeerStories(it, this._peers)) } return this._peerStories diff --git a/packages/client/src/types/stories/booster.ts b/packages/client/src/types/stories/booster.ts index 33be3bc8..ad36d2f7 100644 --- a/packages/client/src/types/stories/booster.ts +++ b/packages/client/src/types/stories/booster.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../..' import { makeInspectable } from '../../utils' import { PeersIndex, User } from '../peers' @@ -9,7 +8,6 @@ import { PeersIndex, User } from '../peers' */ export class Booster { constructor( - readonly client: TelegramClient, readonly raw: tl.RawBooster, readonly _peers: PeersIndex, ) {} @@ -28,7 +26,7 @@ export class Booster { * User who is boosting the channel */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } } diff --git a/packages/client/src/types/stories/interactive/base.ts b/packages/client/src/types/stories/interactive/base.ts index e8054ea7..c56c5471 100644 --- a/packages/client/src/types/stories/interactive/base.ts +++ b/packages/client/src/types/stories/interactive/base.ts @@ -1,14 +1,9 @@ import { tl } from '@mtcute/core/src' -import { TelegramClient } from '../../../client' - export abstract class StoryInteractiveArea { abstract type: string - constructor( - readonly client: TelegramClient, - readonly raw: Exclude, - ) { + constructor(readonly raw: Exclude) { this.raw = raw } diff --git a/packages/client/src/types/stories/interactive/index.ts b/packages/client/src/types/stories/interactive/index.ts index 85df58c4..33510b7e 100644 --- a/packages/client/src/types/stories/interactive/index.ts +++ b/packages/client/src/types/stories/interactive/index.ts @@ -1,6 +1,5 @@ import { MtTypeAssertionError, tl } from '@mtcute/core' -import { TelegramClient } from '../../../client' import { StoryInteractiveLocation } from './location' import { StoryInteractiveReaction } from './reaction' import { StoryInteractiveVenue } from './venue' @@ -9,14 +8,14 @@ export * from './input' export type StoryInteractiveElement = StoryInteractiveReaction | StoryInteractiveLocation | StoryInteractiveVenue -export function _storyInteractiveElementFromTl(client: TelegramClient, raw: tl.TypeMediaArea): StoryInteractiveElement { +export function _storyInteractiveElementFromTl(raw: tl.TypeMediaArea): StoryInteractiveElement { switch (raw._) { case 'mediaAreaSuggestedReaction': - return new StoryInteractiveReaction(client, raw) + return new StoryInteractiveReaction(raw) case 'mediaAreaGeoPoint': - return new StoryInteractiveLocation(client, raw) + return new StoryInteractiveLocation(raw) case 'mediaAreaVenue': - return new StoryInteractiveVenue(client, raw) + return new StoryInteractiveVenue(raw) case 'inputMediaAreaVenue': throw new MtTypeAssertionError('StoryInteractiveElement', '!input*', raw._) } diff --git a/packages/client/src/types/stories/interactive/location.ts b/packages/client/src/types/stories/interactive/location.ts index fa3a3201..3b16cafb 100644 --- a/packages/client/src/types/stories/interactive/location.ts +++ b/packages/client/src/types/stories/interactive/location.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../../client' import { makeInspectable } from '../../../utils' import { Location } from '../../media' import { StoryInteractiveArea } from './base' @@ -12,11 +11,8 @@ import { StoryInteractiveArea } from './base' export class StoryInteractiveLocation extends StoryInteractiveArea { readonly type = 'location' as const - constructor( - client: TelegramClient, - readonly raw: tl.RawMediaAreaGeoPoint, - ) { - super(client, raw) + constructor(readonly raw: tl.RawMediaAreaGeoPoint) { + super(raw) } private _location?: Location @@ -26,7 +22,7 @@ export class StoryInteractiveLocation extends StoryInteractiveArea { get location(): Location { if (!this._location) { assertTypeIs('StoryInteractiveLocation#location', this.raw.geo, 'geoPoint') - this._location = new Location(this.client, this.raw.geo) + this._location = new Location(this.raw.geo) } return this._location diff --git a/packages/client/src/types/stories/interactive/reaction.ts b/packages/client/src/types/stories/interactive/reaction.ts index d33d5652..86865562 100644 --- a/packages/client/src/types/stories/interactive/reaction.ts +++ b/packages/client/src/types/stories/interactive/reaction.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../../client' import { makeInspectable } from '../../../utils' import { ReactionEmoji, toReactionEmoji } from '../../reactions' import { StoryInteractiveArea } from './base' @@ -15,11 +14,8 @@ import { StoryInteractiveArea } from './base' export class StoryInteractiveReaction extends StoryInteractiveArea { readonly type = 'reaction' as const - constructor( - client: TelegramClient, - readonly raw: tl.RawMediaAreaSuggestedReaction, - ) { - super(client, raw) + constructor(readonly raw: tl.RawMediaAreaSuggestedReaction) { + super(raw) } /** Whether this reaction is on a dark background */ diff --git a/packages/client/src/types/stories/interactive/venue.ts b/packages/client/src/types/stories/interactive/venue.ts index a83bc767..dde3bbfc 100644 --- a/packages/client/src/types/stories/interactive/venue.ts +++ b/packages/client/src/types/stories/interactive/venue.ts @@ -1,7 +1,6 @@ import { tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../../client' import { makeInspectable } from '../../../utils' import { Location, VenueSource } from '../../media' import { StoryInteractiveArea } from './base' @@ -12,11 +11,8 @@ import { StoryInteractiveArea } from './base' export class StoryInteractiveVenue extends StoryInteractiveArea { readonly type = 'venue' as const - constructor( - client: TelegramClient, - readonly raw: tl.RawMediaAreaVenue, - ) { - super(client, raw) + constructor(readonly raw: tl.RawMediaAreaVenue) { + super(raw) } private _location?: Location @@ -26,7 +22,7 @@ export class StoryInteractiveVenue extends StoryInteractiveArea { get location(): Location { if (!this._location) { assertTypeIs('StoryInteractiveVenue#location', this.raw.geo, 'geoPoint') - this._location = new Location(this.client, this.raw.geo) + this._location = new Location(this.raw.geo) } return this._location diff --git a/packages/client/src/types/stories/peer-stories.ts b/packages/client/src/types/stories/peer-stories.ts index b271bec2..cf8f227d 100644 --- a/packages/client/src/types/stories/peer-stories.ts +++ b/packages/client/src/types/stories/peer-stories.ts @@ -1,13 +1,11 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { assertTypeIs, makeInspectable } from '../../utils' import { Chat, PeersIndex, User } from '../peers' import { Story } from './story' export class PeerStories { constructor( - readonly client: TelegramClient, readonly raw: tl.RawPeerStories, readonly _peers: PeersIndex, ) {} @@ -21,11 +19,11 @@ export class PeerStories { switch (this.raw.peer._) { case 'peerUser': - return (this._peer = new User(this.client, this._peers.user(this.raw.peer.userId))) + return (this._peer = new User(this._peers.user(this.raw.peer.userId))) case 'peerChat': - return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.chatId))) + return (this._peer = new Chat(this._peers.chat(this.raw.peer.chatId))) case 'peerChannel': - return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.channelId))) + return (this._peer = new Chat(this._peers.chat(this.raw.peer.channelId))) } } @@ -44,7 +42,7 @@ export class PeerStories { return (this._stories ??= this.raw.stories.map((it) => { assertTypeIs('PeerStories#stories', it, 'storyItem') - return new Story(this.client, it, this._peers) + return new Story(it, this._peers) })) } } diff --git a/packages/client/src/types/stories/story-interactions.ts b/packages/client/src/types/stories/story-interactions.ts index 4ebdf8b4..f2a6db1d 100644 --- a/packages/client/src/types/stories/story-interactions.ts +++ b/packages/client/src/types/stories/story-interactions.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { PeersIndex, User } from '../peers' import { ReactionCount } from '../reactions/reaction-count' @@ -10,7 +9,6 @@ import { ReactionCount } from '../reactions/reaction-count' */ export class StoryInteractions { constructor( - readonly client: TelegramClient, readonly raw: tl.RawStoryViews, readonly _peers: PeersIndex, ) {} @@ -57,7 +55,7 @@ export class StoryInteractions { */ get recentViewers(): User[] { if (!this._recentViewers) { - this._recentViewers = this.raw.recentViewers?.map((it) => new User(this.client, this._peers.user(it))) ?? [] + this._recentViewers = this.raw.recentViewers?.map((it) => new User(this._peers.user(it))) ?? [] } return this._recentViewers diff --git a/packages/client/src/types/stories/story-viewer.ts b/packages/client/src/types/stories/story-viewer.ts index 12b14df5..1cdadef3 100644 --- a/packages/client/src/types/stories/story-viewer.ts +++ b/packages/client/src/types/stories/story-viewer.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { PeersIndex, User } from '../peers' import { ReactionEmoji, toReactionEmoji } from '../reactions' @@ -10,7 +9,6 @@ import { ReactionEmoji, toReactionEmoji } from '../reactions' */ export class StoryViewer { constructor( - readonly client: TelegramClient, readonly raw: tl.RawStoryView, readonly _peers: PeersIndex, ) {} @@ -40,7 +38,7 @@ export class StoryViewer { private _user?: User /** Information about the user */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } } @@ -50,10 +48,7 @@ makeInspectable(StoryViewer) * List of story viewers. */ export class StoryViewersList { - constructor( - readonly client: TelegramClient, - readonly raw: tl.stories.RawStoryViewsList, - ) {} + constructor(readonly raw: tl.stories.RawStoryViewsList) {} readonly _peers = PeersIndex.from(this.raw) @@ -76,7 +71,7 @@ export class StoryViewersList { /** List of viewers */ get viewers(): StoryViewer[] { if (!this._viewers) { - this._viewers = this.raw.views.map((it) => new StoryViewer(this.client, it, this._peers)) + this._viewers = this.raw.views.map((it) => new StoryViewer(it, this._peers)) } return this._viewers diff --git a/packages/client/src/types/stories/story.ts b/packages/client/src/types/stories/story.ts index 86ef033b..06ab752a 100644 --- a/packages/client/src/types/stories/story.ts +++ b/packages/client/src/types/stories/story.ts @@ -1,6 +1,5 @@ import { MtUnsupportedError, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { Photo, Video } from '../media' import { _messageMediaFromTl, MessageEntity } from '../messages' @@ -23,7 +22,6 @@ export type StoryMedia = Photo | Video export class Story { constructor( - readonly client: TelegramClient, readonly raw: tl.RawStoryItem, readonly _peers: PeersIndex, ) {} @@ -120,7 +118,7 @@ export class Story { */ get media(): StoryMedia { if (this._media === undefined) { - const media = _messageMediaFromTl(this.client, this._peers, this.raw.media) + const media = _messageMediaFromTl(this._peers, this.raw.media) switch (media?.type) { case 'photo': @@ -143,7 +141,7 @@ export class Story { if (!this.raw.mediaAreas) return [] if (this._interactiveElements === undefined) { - this._interactiveElements = this.raw.mediaAreas.map((it) => _storyInteractiveElementFromTl(this.client, it)) + this._interactiveElements = this.raw.mediaAreas.map((it) => _storyInteractiveElementFromTl(it)) } return this._interactiveElements @@ -167,7 +165,7 @@ export class Story { get interactions(): StoryInteractions | null { if (!this.raw.views) return null - return (this._interactions ??= new StoryInteractions(this.client, this.raw.views, this._peers)) + return (this._interactions ??= new StoryInteractions(this.raw.views, this._peers)) } /** diff --git a/packages/client/src/types/updates/bot-chat-join-request.ts b/packages/client/src/types/updates/bot-chat-join-request.ts index ff68538b..83c4acda 100644 --- a/packages/client/src/types/updates/bot-chat-join-request.ts +++ b/packages/client/src/types/updates/bot-chat-join-request.ts @@ -1,6 +1,5 @@ import { getBarePeerId, getMarkedPeerId, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { Chat, ChatInviteLink, PeersIndex, User } from '../peers' @@ -13,7 +12,6 @@ import { Chat, ChatInviteLink, PeersIndex, User } from '../peers' */ export class BotChatJoinRequestUpdate { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateBotChatInviteRequester, readonly _peers: PeersIndex, ) {} @@ -30,7 +28,7 @@ export class BotChatJoinRequestUpdate { * Object containing the chat information. */ get chat(): Chat { - return (this._chat ??= new Chat(this.client, this._peers.chat(getBarePeerId(this.raw.peer)))) + return (this._chat ??= new Chat(this._peers.chat(getBarePeerId(this.raw.peer)))) } /** @@ -45,7 +43,7 @@ export class BotChatJoinRequestUpdate { * Object containing the user information. */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } /** @@ -68,7 +66,7 @@ export class BotChatJoinRequestUpdate { * Invite link used to request joining. */ get invite(): ChatInviteLink { - return (this._invite ??= new ChatInviteLink(this.client, this.raw.invite)) + return (this._invite ??= new ChatInviteLink(this.raw.invite)) } /** diff --git a/packages/client/src/types/updates/bot-stopped.ts b/packages/client/src/types/updates/bot-stopped.ts index bfb57b72..96c10cce 100644 --- a/packages/client/src/types/updates/bot-stopped.ts +++ b/packages/client/src/types/updates/bot-stopped.ts @@ -1,6 +1,5 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { PeersIndex, User } from '../peers' @@ -12,7 +11,6 @@ import { PeersIndex, User } from '../peers' */ export class BotStoppedUpdate { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateBotStopped, readonly _peers: PeersIndex, ) {} @@ -30,7 +28,7 @@ export class BotStoppedUpdate { * User who stopped or restarted the bot */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } /** diff --git a/packages/client/src/types/updates/chat-join-request.ts b/packages/client/src/types/updates/chat-join-request.ts index 2817c1fe..90e90eba 100644 --- a/packages/client/src/types/updates/chat-join-request.ts +++ b/packages/client/src/types/updates/chat-join-request.ts @@ -1,6 +1,5 @@ import { getBarePeerId, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { PeersIndex, User } from '../peers' @@ -13,7 +12,6 @@ import { PeersIndex, User } from '../peers' */ export class ChatJoinRequestUpdate { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdatePendingJoinRequests, readonly _peers: PeersIndex, ) {} @@ -40,9 +38,7 @@ export class ChatJoinRequestUpdate { * Users who recently requested to join the chat */ get recentRequesters(): User[] { - return (this._recentRequesters ??= this.raw.recentRequesters.map( - (id) => new User(this.client, this._peers.user(id)), - )) + return (this._recentRequesters ??= this.raw.recentRequesters.map((id) => new User(this._peers.user(id)))) } /** diff --git a/packages/client/src/types/updates/chat-member-update.ts b/packages/client/src/types/updates/chat-member-update.ts index 671d8130..9202443b 100644 --- a/packages/client/src/types/updates/chat-member-update.ts +++ b/packages/client/src/types/updates/chat-member-update.ts @@ -1,8 +1,11 @@ import { getMarkedPeerId, tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { Chat, ChatInviteLink, ChatMember, PeersIndex, User } from '../' +import { Chat } from '../peers/chat' +import { ChatInviteLink } from '../peers/chat-invite-link' +import { ChatMember } from '../peers/chat-member' +import { PeersIndex } from '../peers/peers-index' +import { User } from '../peers/user' /** * Type of the event. Can be one of: @@ -63,7 +66,6 @@ function extractPeerId(raw?: tl.TypeChatParticipant | tl.TypeChannelParticipant) */ export class ChatMemberUpdate { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateChatParticipant | tl.RawUpdateChannelParticipant, readonly _peers: PeersIndex, ) {} @@ -186,7 +188,7 @@ export class ChatMemberUpdate { get chat(): Chat { if (!this._chat) { const id = this.raw._ === 'updateChannelParticipant' ? this.raw.channelId : this.raw.chatId - this._chat = new Chat(this.client, this._peers.chat(id)) + this._chat = new Chat(this._peers.chat(id)) } return this._chat @@ -199,7 +201,7 @@ export class ChatMemberUpdate { * Can be chat/channel administrator or the {@link user} themself. */ get actor(): User { - return (this._actor ??= new User(this.client, this._peers.user(this.raw.actorId))) + return (this._actor ??= new User(this._peers.user(this.raw.actorId))) } private _user?: User @@ -207,7 +209,7 @@ export class ChatMemberUpdate { * User representing the chat member whose status was changed. */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } /** Whether this is a self-made action (i.e. actor == user) */ @@ -222,7 +224,7 @@ export class ChatMemberUpdate { get oldMember(): ChatMember | null { if (!this.raw.prevParticipant) return null - return (this._oldMember ??= new ChatMember(this.client, this.raw.prevParticipant, this._peers)) + return (this._oldMember ??= new ChatMember(this.raw.prevParticipant, this._peers)) } private _newMember?: ChatMember @@ -232,7 +234,7 @@ export class ChatMemberUpdate { get newMember(): ChatMember | null { if (!this.raw.newParticipant) return null - return (this._newMember ??= new ChatMember(this.client, this.raw.newParticipant, this._peers)) + return (this._newMember ??= new ChatMember(this.raw.newParticipant, this._peers)) } private _inviteLink?: ChatInviteLink @@ -242,7 +244,7 @@ export class ChatMemberUpdate { get inviteLink(): ChatInviteLink | null { if (!this.raw.invite) return null - return (this._inviteLink ??= new ChatInviteLink(this.client, this.raw.invite)) + return (this._inviteLink ??= new ChatInviteLink(this.raw.invite)) } } diff --git a/packages/client/src/types/updates/chosen-inline-result.ts b/packages/client/src/types/updates/chosen-inline-result.ts index 8d9001af..5abd2236 100644 --- a/packages/client/src/types/updates/chosen-inline-result.ts +++ b/packages/client/src/types/updates/chosen-inline-result.ts @@ -1,9 +1,9 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { encodeInlineMessageId } from '../../utils/inline-utils' -import { Location, PeersIndex, User } from '../' +import { Location } from '../media/location' +import { PeersIndex, User } from '../peers' /** * An inline result was chosen by the user and sent to some chat @@ -13,7 +13,6 @@ import { Location, PeersIndex, User } from '../' */ export class ChosenInlineResult { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateBotInlineSend, readonly _peers: PeersIndex, ) {} @@ -31,7 +30,7 @@ export class ChosenInlineResult { * User who has chosen the query */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.raw.userId))) + return (this._user ??= new User(this._peers.user(this.raw.userId))) } /** @@ -49,7 +48,7 @@ export class ChosenInlineResult { get location(): Location | null { if (this.raw.geo?._ !== 'geoPoint') return null - return (this._location ??= new Location(this.client, this.raw.geo)) + return (this._location ??= new Location(this.raw.geo)) } /** diff --git a/packages/client/src/types/updates/delete-message-update.ts b/packages/client/src/types/updates/delete-message-update.ts index 59abcd2a..3f61e03f 100644 --- a/packages/client/src/types/updates/delete-message-update.ts +++ b/packages/client/src/types/updates/delete-message-update.ts @@ -1,16 +1,12 @@ import { tl, toggleChannelIdMark } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' /** * One or more messages were deleted */ export class DeleteMessageUpdate { - constructor( - readonly client: TelegramClient, - readonly raw: tl.RawUpdateDeleteMessages | tl.RawUpdateDeleteChannelMessages, - ) {} + constructor(readonly raw: tl.RawUpdateDeleteMessages | tl.RawUpdateDeleteChannelMessages) {} /** * IDs of the messages which were deleted diff --git a/packages/client/src/types/updates/delete-story-update.ts b/packages/client/src/types/updates/delete-story-update.ts index b1e87ca8..7d7abd5f 100644 --- a/packages/client/src/types/updates/delete-story-update.ts +++ b/packages/client/src/types/updates/delete-story-update.ts @@ -1,4 +1,6 @@ -import { Chat, PeersIndex, TelegramClient, tl, User } from '../..' +import { tl } from '@mtcute/core' + +import { Chat, PeersIndex, User } from '../../types/peers' import { makeInspectable } from '../../utils' /** @@ -6,7 +8,6 @@ import { makeInspectable } from '../../utils' */ export class DeleteStoryUpdate { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateStory, readonly _peers: PeersIndex, ) {} @@ -20,11 +21,11 @@ export class DeleteStoryUpdate { switch (this.raw.peer._) { case 'peerUser': - return (this._peer = new User(this.client, this._peers.user(this.raw.peer.userId))) + return (this._peer = new User(this._peers.user(this.raw.peer.userId))) case 'peerChat': - return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.chatId))) + return (this._peer = new Chat(this._peers.chat(this.raw.peer.chatId))) case 'peerChannel': - return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.channelId))) + return (this._peer = new Chat(this._peers.chat(this.raw.peer.channelId))) } } diff --git a/packages/client/src/types/updates/history-read-update.ts b/packages/client/src/types/updates/history-read-update.ts index 124eaa89..61523569 100644 --- a/packages/client/src/types/updates/history-read-update.ts +++ b/packages/client/src/types/updates/history-read-update.ts @@ -1,11 +1,9 @@ import { getMarkedPeerId, tl, toggleChannelIdMark } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' export class HistoryReadUpdate { constructor( - readonly client: TelegramClient, readonly raw: | tl.RawUpdateReadHistoryInbox | tl.RawUpdateReadHistoryOutbox diff --git a/packages/client/src/types/updates/index.ts b/packages/client/src/types/updates/index.ts index ad417aed..a03a6da7 100644 --- a/packages/client/src/types/updates/index.ts +++ b/packages/client/src/types/updates/index.ts @@ -1,4 +1,4 @@ -import { CallbackQuery, InlineQuery, Message } from '../..' +import type { CallbackQuery, InlineQuery, Message } from '../../types' import { BotChatJoinRequestUpdate } from './bot-chat-join-request' import { BotStoppedUpdate } from './bot-stopped' import { ChatJoinRequestUpdate } from './chat-join-request' diff --git a/packages/client/src/types/updates/parse-update.ts b/packages/client/src/types/updates/parse-update.ts index 38b540b5..9112326d 100644 --- a/packages/client/src/types/updates/parse-update.ts +++ b/packages/client/src/types/updates/parse-update.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-argument */ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { BotChatJoinRequestUpdate, BotStoppedUpdate, @@ -25,66 +24,66 @@ import { } from '../index' /** @internal */ -export function _parseUpdate(client: TelegramClient, update: tl.TypeUpdate, peers: PeersIndex): ParsedUpdate | null { +export function _parseUpdate(update: tl.TypeUpdate, peers: PeersIndex): ParsedUpdate | null { switch (update._) { case 'updateNewMessage': case 'updateNewChannelMessage': case 'updateNewScheduledMessage': return { name: 'new_message', - data: new Message(client, update.message, peers, update._ === 'updateNewScheduledMessage'), + data: new Message(update.message, peers, update._ === 'updateNewScheduledMessage'), } case 'updateEditMessage': case 'updateEditChannelMessage': - return { name: 'edit_message', data: new Message(client, update.message, peers) } + return { name: 'edit_message', data: new Message(update.message, peers) } case 'updateChatParticipant': case 'updateChannelParticipant': - return { name: 'chat_member', data: new ChatMemberUpdate(client, update, peers) } + return { name: 'chat_member', data: new ChatMemberUpdate(update, peers) } case 'updateBotInlineQuery': - return { name: 'inline_query', data: new InlineQuery(client, update, peers) } + return { name: 'inline_query', data: new InlineQuery(update, peers) } case 'updateBotInlineSend': - return { name: 'chosen_inline_result', data: new ChosenInlineResult(client, update, peers) } + return { name: 'chosen_inline_result', data: new ChosenInlineResult(update, peers) } case 'updateBotCallbackQuery': case 'updateInlineBotCallbackQuery': - return { name: 'callback_query', data: new CallbackQuery(client, update, peers) } + return { name: 'callback_query', data: new CallbackQuery(update, peers) } case 'updateMessagePoll': - return { name: 'poll', data: new PollUpdate(client, update, peers) } + return { name: 'poll', data: new PollUpdate(update, peers) } case 'updateMessagePollVote': - return { name: 'poll_vote', data: new PollVoteUpdate(client, update, peers) } + return { name: 'poll_vote', data: new PollVoteUpdate(update, peers) } case 'updateUserStatus': - return { name: 'user_status', data: new UserStatusUpdate(client, update) } + return { name: 'user_status', data: new UserStatusUpdate(update) } case 'updateChannelUserTyping': case 'updateChatUserTyping': case 'updateUserTyping': - return { name: 'user_typing', data: new UserTypingUpdate(client, update) } + return { name: 'user_typing', data: new UserTypingUpdate(update) } case 'updateDeleteChannelMessages': case 'updateDeleteMessages': - return { name: 'delete_message', data: new DeleteMessageUpdate(client, update) } + return { name: 'delete_message', data: new DeleteMessageUpdate(update) } case 'updateReadHistoryInbox': case 'updateReadHistoryOutbox': case 'updateReadChannelInbox': case 'updateReadChannelOutbox': case 'updateReadChannelDiscussionInbox': case 'updateReadChannelDiscussionOutbox': - return { name: 'history_read', data: new HistoryReadUpdate(client, update) } + return { name: 'history_read', data: new HistoryReadUpdate(update) } case 'updateBotStopped': - return { name: 'bot_stopped', data: new BotStoppedUpdate(client, update, peers) } + return { name: 'bot_stopped', data: new BotStoppedUpdate(update, peers) } case 'updateBotChatInviteRequester': - return { name: 'bot_chat_join_request', data: new BotChatJoinRequestUpdate(client, update, peers) } + return { name: 'bot_chat_join_request', data: new BotChatJoinRequestUpdate(update, peers) } case 'updatePendingJoinRequests': - return { name: 'chat_join_request', data: new ChatJoinRequestUpdate(client, update, peers) } + return { name: 'chat_join_request', data: new ChatJoinRequestUpdate(update, peers) } case 'updateBotPrecheckoutQuery': - return { name: 'pre_checkout_query', data: new PreCheckoutQuery(client, update, peers) } + return { name: 'pre_checkout_query', data: new PreCheckoutQuery(update, peers) } case 'updateStory': { const story = update.story if (story._ === 'storyItemDeleted') { - return { name: 'delete_story', data: new DeleteStoryUpdate(client, update, peers) } + return { name: 'delete_story', data: new DeleteStoryUpdate(update, peers) } } return { name: 'story', - data: new StoryUpdate(client, update, peers), + data: new StoryUpdate(update, peers), } } default: diff --git a/packages/client/src/types/updates/poll-update.ts b/packages/client/src/types/updates/poll-update.ts index a5ede27b..8586edea 100644 --- a/packages/client/src/types/updates/poll-update.ts +++ b/packages/client/src/types/updates/poll-update.ts @@ -1,8 +1,8 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { PeersIndex, Poll } from '../' +import { Poll } from '../media/poll' +import { PeersIndex } from '../peers/peers-index' /** * Poll state has changed (stopped, somebody @@ -13,7 +13,6 @@ import { PeersIndex, Poll } from '../' */ export class PollUpdate { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateMessagePoll, readonly _peers: PeersIndex, ) {} @@ -62,7 +61,7 @@ export class PollUpdate { } } - this._poll = new Poll(this.client, poll, this._peers, this.raw.results) + this._poll = new Poll(poll, this._peers, this.raw.results) } return this._poll diff --git a/packages/client/src/types/updates/poll-vote.ts b/packages/client/src/types/updates/poll-vote.ts index 97e9d84f..a2935e22 100644 --- a/packages/client/src/types/updates/poll-vote.ts +++ b/packages/client/src/types/updates/poll-vote.ts @@ -1,9 +1,8 @@ import { MtUnsupportedError, tl } from '@mtcute/core' import { assertTypeIs } from '@mtcute/core/utils' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { Chat, PeersIndex, User } from '../' +import { Chat, PeersIndex, User } from '../peers' /** * Some user has voted in a public poll. @@ -13,7 +12,6 @@ import { Chat, PeersIndex, User } from '../' */ export class PollVoteUpdate { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateMessagePollVote, readonly _peers: PeersIndex, ) {} @@ -33,12 +31,12 @@ export class PollVoteUpdate { if (this._peer) return this._peer if (this.raw.peer._ === 'peerUser') { - return (this._peer = new User(this.client, this._peers.user(this.raw.peer.userId))) + return (this._peer = new User(this._peers.user(this.raw.peer.userId))) } assertTypeIs('PollVoteUpdate.peer', this.raw.peer, 'peerChannel') - return (this._peer = new User(this.client, this._peers.user(this.raw.peer.channelId))) + return (this._peer = new User(this._peers.user(this.raw.peer.channelId))) } /** diff --git a/packages/client/src/types/updates/pre-checkout-query.ts b/packages/client/src/types/updates/pre-checkout-query.ts index acb586a5..bec6f6d1 100644 --- a/packages/client/src/types/updates/pre-checkout-query.ts +++ b/packages/client/src/types/updates/pre-checkout-query.ts @@ -1,12 +1,10 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' import { PeersIndex, User } from '../peers' export class PreCheckoutQuery { constructor( - public readonly client: TelegramClient, public readonly raw: tl.RawUpdateBotPrecheckoutQuery, public readonly _peers: PeersIndex, ) {} @@ -30,7 +28,7 @@ export class PreCheckoutQuery { * User who sent the query */ get user(): User { - return (this._user ??= new User(this.client, this._peers.user(this.userId))) + return (this._user ??= new User(this._peers.user(this.userId))) } /** @@ -63,20 +61,6 @@ export class PreCheckoutQuery { get totalAmount(): tl.Long { return this.raw.totalAmount } - - /** - * Approve the query - */ - approve(): Promise { - return this.client.answerPreCheckoutQuery(this.queryId) - } - - /** - * Reject the query - */ - reject(error = ''): Promise { - return this.client.answerPreCheckoutQuery(this.queryId, { error }) - } } makeInspectable(PreCheckoutQuery) diff --git a/packages/client/src/types/updates/story-update.ts b/packages/client/src/types/updates/story-update.ts index aa9f824c..6b995899 100644 --- a/packages/client/src/types/updates/story-update.ts +++ b/packages/client/src/types/updates/story-update.ts @@ -1,4 +1,7 @@ -import { Chat, PeersIndex, Story, TelegramClient, tl, User } from '../..' +import { tl } from '@mtcute/core' + +import { Chat, PeersIndex, User } from '../../types/peers' +import { Story } from '../../types/stories' import { assertTypeIs, makeInspectable } from '../../utils' /** @@ -9,7 +12,6 @@ import { assertTypeIs, makeInspectable } from '../../utils' */ export class StoryUpdate { constructor( - readonly client: TelegramClient, readonly raw: tl.RawUpdateStory, readonly _peers: PeersIndex, ) {} @@ -23,11 +25,11 @@ export class StoryUpdate { switch (this.raw.peer._) { case 'peerUser': - return (this._peer = new User(this.client, this._peers.user(this.raw.peer.userId))) + return (this._peer = new User(this._peers.user(this.raw.peer.userId))) case 'peerChat': - return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.chatId))) + return (this._peer = new Chat(this._peers.chat(this.raw.peer.chatId))) case 'peerChannel': - return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.channelId))) + return (this._peer = new Chat(this._peers.chat(this.raw.peer.channelId))) } } @@ -40,7 +42,7 @@ export class StoryUpdate { assertTypeIs('StoryUpdate.story', this.raw.story, 'storyItem') - return (this._story = new Story(this.client, this.raw.story, this._peers)) + return (this._story = new Story(this.raw.story, this._peers)) } } diff --git a/packages/client/src/types/updates/user-status-update.ts b/packages/client/src/types/updates/user-status-update.ts index 4ee6b24d..024cf733 100644 --- a/packages/client/src/types/updates/user-status-update.ts +++ b/packages/client/src/types/updates/user-status-update.ts @@ -1,17 +1,13 @@ import { tl } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { User, UserParsedStatus, UserStatus } from '../' +import { User, UserParsedStatus, UserStatus } from '../peers' /** * User status has changed */ export class UserStatusUpdate { - constructor( - readonly client: TelegramClient, - readonly raw: tl.RawUpdateUserStatus, - ) {} + constructor(readonly raw: tl.RawUpdateUserStatus) {} /** * ID of the user whose status has updated @@ -54,13 +50,6 @@ export class UserStatusUpdate { return this._parsedStatus!.nextOffline } - - /** - * Fetch information about the user - */ - getUser(): Promise { - return this.client.getUsers(this.raw.userId) - } } makeInspectable(UserStatusUpdate) diff --git a/packages/client/src/types/updates/user-typing-update.ts b/packages/client/src/types/updates/user-typing-update.ts index b411e514..1aa0a4ff 100644 --- a/packages/client/src/types/updates/user-typing-update.ts +++ b/packages/client/src/types/updates/user-typing-update.ts @@ -1,8 +1,7 @@ import { BasicPeerType, getBarePeerId, MtUnsupportedError, tl, toggleChannelIdMark } from '@mtcute/core' -import { TelegramClient } from '../../client' import { makeInspectable } from '../../utils' -import { Chat, TypingStatus, User } from '../' +import { TypingStatus } from '../peers' /** * User's typing status has changed. @@ -10,10 +9,7 @@ import { Chat, TypingStatus, User } from '../' * This update is valid for 6 seconds. */ export class UserTypingUpdate { - constructor( - readonly client: TelegramClient, - readonly raw: tl.RawUpdateUserTyping | tl.RawUpdateChatUserTyping | tl.RawUpdateChannelUserTyping, - ) {} + constructor(readonly raw: tl.RawUpdateUserTyping | tl.RawUpdateChatUserTyping | tl.RawUpdateChannelUserTyping) {} /** * ID of the user whose typing status changed @@ -92,20 +88,6 @@ export class UserTypingUpdate { throw new MtUnsupportedError() } - - /** - * Fetch the user whose typing status has changed - */ - getUser(): Promise { - return this.client.getUsers(this.userId) - } - - /** - * Fetch the chat where the update has happened - */ - getChat(): Promise { - return this.client.getChat(this.chatId) - } } makeInspectable(UserTypingUpdate) diff --git a/packages/client/src/types/utils.ts b/packages/client/src/types/utils.ts index 1e3c5aad..078fb9b4 100644 --- a/packages/client/src/types/utils.ts +++ b/packages/client/src/types/utils.ts @@ -1,6 +1,11 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { MaybeAsync } from '@mtcute/core' export type MaybeDynamic = MaybeAsync | (() => MaybeAsync) export type ArrayWithTotal = T[] & { total: number } export type ArrayPaginated = T[] & { total: number; next?: Offset } + +export type ParametersSkip1 = T extends (a: any, ...args: infer P) => any ? P : never +export type ParametersSkip2 = T extends (a: any, b: any, ...args: infer P) => any ? P : never +export type ParametersSkip3 = T extends (a: any, b: any, c: any, ...args: infer P) => any ? P : never diff --git a/packages/client/src/utils/misc-utils.ts b/packages/client/src/utils/misc-utils.ts index 8070dd68..52a01228 100644 --- a/packages/client/src/utils/misc-utils.ts +++ b/packages/client/src/utils/misc-utils.ts @@ -1,4 +1,4 @@ -import { MtArgumentError, tl } from '@mtcute/core' +import { MtArgumentError } from '@mtcute/core' import { ArrayPaginated, ArrayWithTotal, MaybeDynamic, Message } from '../types' @@ -32,27 +32,6 @@ export function makeArrayPaginated(arr: T[], total: number, next?: Of return a } -export function extractChannelIdFromUpdate(upd: tl.TypeUpdate): number | undefined { - // holy shit - let res = 0 - - if ('channelId' in upd) { - res = upd.channelId - } else if ( - 'message' in upd && - typeof upd.message !== 'string' && - 'peerId' in upd.message && - upd.message.peerId && - 'channelId' in upd.message.peerId - ) { - res = upd.message.peerId.channelId - } - - if (res === 0) return undefined - - return res -} - export function normalizeDate(date: Date | number): number export function normalizeDate(date: Date | number | undefined): number | undefined diff --git a/packages/client/src/utils/peer-utils.ts b/packages/client/src/utils/peer-utils.ts index b6f70398..b497a663 100644 --- a/packages/client/src/utils/peer-utils.ts +++ b/packages/client/src/utils/peer-utils.ts @@ -1,6 +1,7 @@ import { assertNever, Long, tl } from '@mtcute/core' -import { InputPeerLike, MtInvalidPeerTypeError } from '../types' +import { MtInvalidPeerTypeError } from '../types/errors' +import { InputPeerLike } from '../types/peers' export const INVITE_LINK_REGEX = /^(?:https?:\/\/)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)\/(?:joinchat\/|\+))([\w-]+)$/i diff --git a/packages/client/tsconfig.cjs.json b/packages/client/tsconfig.cjs.json new file mode 100644 index 00000000..45130b34 --- /dev/null +++ b/packages/client/tsconfig.cjs.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "./dist/_cjs", + }, +} diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 169a3bd8..062216e2 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "./dist", }, "include": [ diff --git a/packages/core/package.json b/packages/core/package.json index e5c4710d..977d144e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -15,6 +15,7 @@ "./utils/platform/crypto.js": "./utils/platform/crypto.web.js", "./utils/platform/transport.js": "./utils/platform/transport.web.js", "./utils/platform/logging.js": "./utils/platform/logging.web.js", + "./utils/platform/random.js": "./utils/platform/random.web.js", "./storage/json-file.js": false }, "dependencies": { @@ -29,7 +30,6 @@ "devDependencies": { "@mtcute/dispatcher": "workspace:^1.0.0", "@types/ws": "8.5.4", - "exit-hook": "^4.0.0", "ws": "8.13.0" } -} \ No newline at end of file +} diff --git a/packages/core/src/base-client.ts b/packages/core/src/base-client.ts index 85fdb5ab..df7c2fdd 100644 --- a/packages/core/src/base-client.ts +++ b/packages/core/src/base-client.ts @@ -190,7 +190,7 @@ export class BaseTelegramClient extends EventEmitter { /** * Crypto provider taken from {@link BaseTelegramClientOptions.crypto} */ - protected readonly _crypto: ICryptoProvider + readonly crypto: ICryptoProvider /** * Telegram storage taken from {@link BaseTelegramClientOptions.storage} @@ -235,16 +235,6 @@ export class BaseTelegramClient extends EventEmitter { private _importFrom?: string private _importForce?: boolean - /** - * Method which is called every time the client receives a new update. - * - * User of the class is expected to override it and handle the given update - * - * @param update Raw update object sent by Telegram - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected _handleUpdate(update: tl.TypeUpdates): void {} - readonly log = new LogManager('client') readonly network: NetworkManager @@ -257,7 +247,7 @@ export class BaseTelegramClient extends EventEmitter { throw new Error('apiId must be a number or a numeric string!') } - this._crypto = (opts.crypto ?? defaultCryptoProviderFactory)() + this.crypto = (opts.crypto ?? defaultCryptoProviderFactory)() this.storage = opts.storage ?? new MemoryStorage() this._apiHash = opts.apiHash this._useIpv6 = Boolean(opts.useIpv6) @@ -283,7 +273,7 @@ export class BaseTelegramClient extends EventEmitter { this.network = new NetworkManager( { apiId, - crypto: this._crypto, + crypto: this.crypto, disableUpdates: opts.disableUpdates ?? false, initConnectionOptions: opts.initConnectionOptions, layer: this._layer, @@ -310,19 +300,27 @@ export class BaseTelegramClient extends EventEmitter { } protected _keepAliveAction(): void { - // core does not have update handling, so we just use getState so the server knows - // we still do need updates - this.call({ _: 'updates.getState' }).catch((e) => { - this.log.error('failed to send keep-alive: %s', e) - }) + this.emit('keep_alive') } protected async _loadStorage(): Promise { await this.storage.load?.() } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected async _saveStorage(afterImport = false): Promise { + _beforeStorageSave: (() => Promise)[] = [] + + beforeStorageSave(cb: () => Promise): void { + this._beforeStorageSave.push(cb) + } + + offBeforeStorageSave(cb: () => Promise): void { + this._beforeStorageSave = this._beforeStorageSave.filter((x) => x !== cb) + } + + async saveStorage(): Promise { + for (const cb of this._beforeStorageSave) { + await cb() + } await this.storage.save?.() } @@ -340,9 +338,6 @@ export class BaseTelegramClient extends EventEmitter { return } - // we cant do this in constructor because we need to support subclassing - this.network.setUpdateHandler(this._handleUpdate.bind(this)) - const promise = (this._connected = createControllablePromise()) await this._loadStorage() @@ -373,9 +368,11 @@ export class BaseTelegramClient extends EventEmitter { // await this.primaryConnection.setupKeys(data.authKey) await this.storage.setAuthKeyFor(data.primaryDcs.main.id, data.authKey) - await this._saveStorage(true) + await this.saveStorage() } + this.emit('before_connect') + this.network .connect(this._defaultDcs) .then(() => { @@ -385,23 +382,19 @@ export class BaseTelegramClient extends EventEmitter { .catch((err: Error) => this._emitError(err)) } - /** - * Additional cleanup for subclasses. - * @protected - */ - protected _onClose(): void {} - /** * Close all connections and finalize the client. */ async close(): Promise { - this._onClose() + this.emit('before_close') this._config.destroy() this.network.destroy() - await this._saveStorage() + await this.saveStorage() await this.storage.destroy?.() + + this.emit('closed') } /** @@ -461,7 +454,12 @@ export class BaseTelegramClient extends EventEmitter { this._onError = handler } - protected _emitError(err: unknown, connection?: SessionConnection): void { + notifyLoggedIn(auth: tl.auth.RawAuthorization): void { + this.network.notifyLoggedIn(auth) + this.emit('logged_in', auth) + } + + _emitError(err: unknown, connection?: SessionConnection): void { if (this._onError) { this._onError(err, connection) } else { @@ -474,7 +472,7 @@ export class BaseTelegramClient extends EventEmitter { * * @returns `true` if there were any `min` peers */ - protected async _cachePeersFrom(obj: object): Promise { + async _cachePeersFrom(obj: object): Promise { const parsedPeers: ITelegramStorage.PeerInfo[] = [] let hadMin = false diff --git a/packages/core/src/network/authorization.ts b/packages/core/src/network/authorization.ts index d0af4fa5..dea2cd0e 100644 --- a/packages/core/src/network/authorization.ts +++ b/packages/core/src/network/authorization.ts @@ -7,10 +7,11 @@ import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/ import { MtArgumentError, MtSecurityError, MtTypeAssertionError } from '../types' import { bigIntToBuffer, bufferToBigInt, ICryptoProvider, Logger } from '../utils' -import { buffersEqual, randomBytes, xorBuffer, xorBufferInPlace } from '../utils/buffer-utils' +import { buffersEqual, randomBytes } from '../utils/buffer-utils' import { findKeyByFingerprints } from '../utils/crypto/keys' import { millerRabin } from '../utils/crypto/miller-rabin' import { generateKeyAndIvFromNonce } from '../utils/crypto/mtproto' +import { xorBuffer, xorBufferInPlace } from '../utils/crypto/utils' import { mtpAssertTypeIs } from '../utils/type-assertions' import { SessionConnection } from './session-connection' diff --git a/packages/core/src/network/network-manager.ts b/packages/core/src/network/network-manager.ts index bddcacc0..92d36842 100644 --- a/packages/core/src/network/network-manager.ts +++ b/packages/core/src/network/network-manager.ts @@ -363,7 +363,7 @@ export class NetworkManager { private _keepAliveInterval?: NodeJS.Timeout private _lastUpdateTime = 0 - private _updateHandler: (upd: tl.TypeUpdates) => void = () => {} + private _updateHandler: (upd: tl.TypeUpdates, fromClient: boolean) => void = () => {} constructor( readonly params: NetworkManagerParams & NetworkManagerExtraParams, @@ -470,7 +470,7 @@ export class NetworkManager { }) dc.main.on('update', (update: tl.TypeUpdates) => { this._lastUpdateTime = Date.now() - this._updateHandler(update) + this._updateHandler(update, false) }) return dc.loadKeys().then(() => dc.main.ensureConnected()) @@ -577,9 +577,7 @@ export class NetworkManager { } } - // future-proofing. should probably remove once the implementation is stable - // eslint-disable-next-line @typescript-eslint/require-await - async notifyLoggedIn(auth: tl.auth.TypeAuthorization): Promise { + notifyLoggedIn(auth: tl.auth.TypeAuthorization): void { if (auth._ === 'auth.authorizationSignUpRequired' || auth.user._ === 'userEmpty') { return } @@ -589,8 +587,6 @@ export class NetworkManager { } this.setIsPremium(auth.user.premium!) - - // await this.exportAuth() } resetSessions(): void { @@ -742,6 +738,10 @@ export class NetworkManager { this._updateHandler = handler } + handleUpdate(update: tl.TypeUpdates, fromClient = true): void { + this._updateHandler(update, fromClient) + } + changeTransport(factory: TransportFactory): void { for (const dc of this._dcConnections.values()) { dc.main.changeTransport(factory) diff --git a/packages/core/src/storage/json-file.ts b/packages/core/src/storage/json-file.ts index 4d9cb91f..9b09c4c9 100644 --- a/packages/core/src/storage/json-file.ts +++ b/packages/core/src/storage/json-file.ts @@ -1,4 +1,3 @@ -import type * as exitHookNs from 'exit-hook' import type * as fsNs from 'fs' import { MtUnsupportedError } from '../types' @@ -11,20 +10,13 @@ try { fs = require('fs') as fs } catch (e) {} -type exitHook = typeof exitHookNs -let exitHook: exitHook | null = null - -try { - exitHook = require('exit-hook') as exitHook -} catch (e) {} +const EVENTS = ['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'SIGTERM'] export class JsonFileStorage extends JsonMemoryStorage { private readonly _filename: string private readonly _safe: boolean private readonly _cleanup: boolean - private readonly _unsubscribe?: () => void - constructor( filename: string, params?: { @@ -43,9 +35,8 @@ export class JsonFileStorage extends JsonMemoryStorage { /** * Whether to save file on process exit. - * Uses [`exit-hook`](https://www.npmjs.com/package/exit-hook) * - * Defaults to `true` if `exit-hook` is installed, otherwise `false` + * Defaults to `true` */ cleanup?: boolean }, @@ -58,14 +49,11 @@ export class JsonFileStorage extends JsonMemoryStorage { this._filename = filename this._safe = params?.safe ?? true - this._cleanup = params?.cleanup ?? Boolean(exitHook) - - if (this._cleanup && !exitHook) { - throw new MtUnsupportedError('Cleanup on exit is supported through `exit-hook` library, install it first!') - } + this._cleanup = params?.cleanup ?? true if (this._cleanup) { - this._unsubscribe = exitHook!.default(this._onProcessExit.bind(this)) + this._onProcessExit = this._onProcessExit.bind(this) + EVENTS.forEach((event) => process.on(event, this._onProcessExit)) } } @@ -93,8 +81,11 @@ export class JsonFileStorage extends JsonMemoryStorage { }) } + private _processExitHandled = false private _onProcessExit(): void { // on exit handler must be synchronous, thus we use sync methods here + if (this._processExitHandled) return + this._processExitHandled = true try { fs!.writeFileSync(this._filename, this._saveJson()) @@ -109,7 +100,7 @@ export class JsonFileStorage extends JsonMemoryStorage { destroy(): void { if (this._cleanup) { - this._unsubscribe?.() + EVENTS.forEach((event) => process.off(event, this._onProcessExit)) } } } diff --git a/packages/core/src/utils/buffer-utils.ts b/packages/core/src/utils/buffer-utils.ts index 3ae52360..e581995e 100644 --- a/packages/core/src/utils/buffer-utils.ts +++ b/packages/core/src/utils/buffer-utils.ts @@ -1,21 +1,7 @@ -export { _randomBytes as randomBytes } from './platform/crypto' +export { _randomBytes as randomBytes } from './platform/random' const b64urlAvailable = Buffer.isEncoding('base64url') -// from https://github.com/feross/typedarray-to-buffer -// licensed under MIT -/** - * Convert a typed array to a Buffer. - * @param arr Typed array to convert - */ -export function typedArrayToBuffer(arr: NodeJS.TypedArray): Buffer { - // To avoid a copy, use the typed array's underlying ArrayBuffer to back - // new Buffer, respecting the "view", i.e. byteOffset and byteLength - return ArrayBuffer.isView(arr) ? - Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength) : // Pass through all other types to `Buffer.from` - Buffer.from(arr) -} - /** * Check if two buffers are equal * @@ -32,34 +18,6 @@ export function buffersEqual(a: Buffer, b: Buffer): boolean { return true } -/** - * Perform XOR operation on two buffers and return the new buffer - * - * @param data Buffer to XOR - * @param key Key to XOR with - */ -export function xorBuffer(data: Buffer, key: Buffer): Buffer { - const ret = Buffer.alloc(data.length) - - for (let i = 0; i < data.length; i++) { - ret[i] = data[i] ^ key[i] - } - - return ret -} - -/** - * Perform XOR operation on two buffers in-place - * - * @param data Buffer to XOR - * @param key Key to XOR with - */ -export function xorBufferInPlace(data: Buffer, key: Buffer): void { - for (let i = 0; i < data.length; i++) { - data[i] ^= key[i] - } -} - /** * Copy a buffer * diff --git a/packages/core/src/utils/crypto/common.ts b/packages/core/src/utils/crypto/common.ts index d379395a..ca2172e9 100644 --- a/packages/core/src/utils/crypto/common.ts +++ b/packages/core/src/utils/crypto/common.ts @@ -1,5 +1,5 @@ -import { xorBufferInPlace } from '../buffer-utils' -import { IEncryptionScheme } from './abstract' +import type { IEncryptionScheme } from './abstract' +import { xorBufferInPlace } from './utils' /** * AES mode of operation IGE implementation in JS diff --git a/packages/core/src/utils/crypto/password.ts b/packages/core/src/utils/crypto/password.ts index b1219295..4b088870 100644 --- a/packages/core/src/utils/crypto/password.ts +++ b/packages/core/src/utils/crypto/password.ts @@ -4,8 +4,9 @@ import { tl } from '@mtcute/tl' import { MtSecurityError, MtUnsupportedError } from '../../types' import { bigIntToBuffer, bufferToBigInt } from '../bigint-utils' -import { randomBytes, xorBuffer } from '../buffer-utils' +import { randomBytes } from '../buffer-utils' import { ICryptoProvider } from './abstract' +import { xorBuffer } from './utils' /** * Compute password hash as defined by MTProto. diff --git a/packages/core/src/utils/crypto/utils.ts b/packages/core/src/utils/crypto/utils.ts new file mode 100644 index 00000000..8ab366e2 --- /dev/null +++ b/packages/core/src/utils/crypto/utils.ts @@ -0,0 +1,27 @@ +/** + * Perform XOR operation on two buffers and return the new buffer + * + * @param data Buffer to XOR + * @param key Key to XOR with + */ +export function xorBuffer(data: Buffer, key: Buffer): Buffer { + const ret = Buffer.alloc(data.length) + + for (let i = 0; i < data.length; i++) { + ret[i] = data[i] ^ key[i] + } + + return ret +} + +/** + * Perform XOR operation on two buffers in-place + * + * @param data Buffer to XOR + * @param key Key to XOR with + */ +export function xorBufferInPlace(data: Buffer, key: Buffer): void { + for (let i = 0; i < data.length; i++) { + data[i] ^= key[i] + } +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 6a476813..5f8212cf 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -20,3 +20,4 @@ export * from './sorted-array' export * from './string-session' export * from './tl-json' export * from './type-assertions' +export * from './web-utils' diff --git a/packages/core/src/utils/linked-list.ts b/packages/core/src/utils/linked-list.ts index b11face9..0d506989 100644 --- a/packages/core/src/utils/linked-list.ts +++ b/packages/core/src/utils/linked-list.ts @@ -76,4 +76,9 @@ export class SortedLinkedList { this._size += 1 } + + clear(): void { + this._first = this._last = undefined + this._size = 0 + } } diff --git a/packages/core/src/utils/peer-utils.ts b/packages/core/src/utils/peer-utils.ts index fa433e15..eecc15e0 100644 --- a/packages/core/src/utils/peer-utils.ts +++ b/packages/core/src/utils/peer-utils.ts @@ -1,6 +1,7 @@ import { tl } from '@mtcute/tl' -import { BasicPeerType, MtArgumentError, MtUnsupportedError } from '../types' +import { MtArgumentError, MtUnsupportedError } from '../types/errors' +import { BasicPeerType } from '../types/peers' // src: https://github.com/tdlib/td/blob/master/td/telegram/DialogId.h const ZERO_CHANNEL_ID = -1000000000000 diff --git a/packages/core/src/utils/platform/crypto.ts b/packages/core/src/utils/platform/crypto.ts index d29183a4..4aafd292 100644 --- a/packages/core/src/utils/platform/crypto.ts +++ b/packages/core/src/utils/platform/crypto.ts @@ -1,9 +1,4 @@ -import { randomBytes } from 'crypto' - -// noinspection ES6PreferShortImport import { NodeCryptoProvider } from '../crypto/node-crypto' /** @internal */ export const _defaultCryptoProviderFactory = () => new NodeCryptoProvider() - -export const _randomBytes = randomBytes diff --git a/packages/core/src/utils/platform/crypto.web.ts b/packages/core/src/utils/platform/crypto.web.ts index 07f143d2..25cf4412 100644 --- a/packages/core/src/utils/platform/crypto.web.ts +++ b/packages/core/src/utils/platform/crypto.web.ts @@ -1,12 +1,4 @@ -import { typedArrayToBuffer } from '../buffer-utils' import { ForgeCryptoProvider } from '../crypto' /** @internal */ export const _defaultCryptoProviderFactory = () => new ForgeCryptoProvider() - -export function _randomBytes(size: number): Buffer { - const ret = new Uint8Array(size) - crypto.getRandomValues(ret) - - return typedArrayToBuffer(ret) -} diff --git a/packages/core/src/utils/platform/random.ts b/packages/core/src/utils/platform/random.ts new file mode 100644 index 00000000..674fbf46 --- /dev/null +++ b/packages/core/src/utils/platform/random.ts @@ -0,0 +1,3 @@ +import { randomBytes } from 'crypto' + +export const _randomBytes = randomBytes diff --git a/packages/core/src/utils/platform/random.web.ts b/packages/core/src/utils/platform/random.web.ts new file mode 100644 index 00000000..1c45db0a --- /dev/null +++ b/packages/core/src/utils/platform/random.web.ts @@ -0,0 +1,8 @@ +import { typedArrayToBuffer } from '../web-utils' + +export function _randomBytes(size: number): Buffer { + const ret = new Uint8Array(size) + crypto.getRandomValues(ret) + + return typedArrayToBuffer(ret) +} diff --git a/packages/core/src/utils/platform/transport.ts b/packages/core/src/utils/platform/transport.ts index 283a5f30..88f858ab 100644 --- a/packages/core/src/utils/platform/transport.ts +++ b/packages/core/src/utils/platform/transport.ts @@ -1,4 +1,4 @@ -import { TcpTransport } from '../../network' +import { TcpTransport } from '../../network/transports/tcp' /** @internal */ export const _defaultTransportFactory = () => new TcpTransport() diff --git a/packages/core/src/utils/platform/transport.web.ts b/packages/core/src/utils/platform/transport.web.ts index 8a0bc536..9c25daea 100644 --- a/packages/core/src/utils/platform/transport.web.ts +++ b/packages/core/src/utils/platform/transport.web.ts @@ -1,4 +1,4 @@ -import { WebSocketTransport } from '../../network' +import { WebSocketTransport } from '../../network/transports/websocket' import { MtUnsupportedError } from '../../types' /** @internal */ diff --git a/packages/core/src/utils/sorted-array.ts b/packages/core/src/utils/sorted-array.ts index 6fcb1bbb..568e8868 100644 --- a/packages/core/src/utils/sorted-array.ts +++ b/packages/core/src/utils/sorted-array.ts @@ -1,6 +1,3 @@ -// -// - /** * Array that adds elements in sorted order. * diff --git a/packages/core/src/utils/web-utils.ts b/packages/core/src/utils/web-utils.ts new file mode 100644 index 00000000..9930ad80 --- /dev/null +++ b/packages/core/src/utils/web-utils.ts @@ -0,0 +1,13 @@ +// from https://github.com/feross/typedarray-to-buffer +// licensed under MIT +/** + * Convert a typed array to a Buffer. + * @param arr Typed array to convert + */ +export function typedArrayToBuffer(arr: NodeJS.TypedArray): Buffer { + // To avoid a copy, use the typed array's underlying ArrayBuffer to back + // new Buffer, respecting the "view", i.e. byteOffset and byteLength + return ArrayBuffer.isView(arr) ? + Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength) : // Pass through all other types to `Buffer.from` + Buffer.from(arr) +} diff --git a/packages/core/tests/buffer-utils.spec.ts b/packages/core/tests/buffer-utils.spec.ts index 788b606d..3e86b25d 100644 --- a/packages/core/tests/buffer-utils.spec.ts +++ b/packages/core/tests/buffer-utils.spec.ts @@ -7,9 +7,8 @@ import { encodeUrlSafeBase64, parseUrlSafeBase64, randomBytes, - xorBuffer, - xorBufferInPlace, } from '../src/utils/buffer-utils' +import { xorBuffer, xorBufferInPlace } from '../src/utils/crypto/utils' describe('buffersEqual', () => { it('should return true for equal buffers', () => { diff --git a/packages/dispatcher/src/dispatcher.ts b/packages/dispatcher/src/dispatcher.ts index 62195527..eb605fc5 100644 --- a/packages/dispatcher/src/dispatcher.ts +++ b/packages/dispatcher/src/dispatcher.ts @@ -467,7 +467,6 @@ export class Dispatcher { * * @param handler Update handler to remove, its name or `'all'` to remove all * @param group Handler group index (-1 to affect all groups) - * @internal */ removeUpdateHandler(handler: UpdateHandler | UpdateHandler['name'] | 'all', group = 0): void { if (group !== -1 && !(group in this._groups)) { diff --git a/packages/dispatcher/src/filters/bots.ts b/packages/dispatcher/src/filters/bots.ts index d1003702..bb1d35cc 100644 --- a/packages/dispatcher/src/filters/bots.ts +++ b/packages/dispatcher/src/filters/bots.ts @@ -54,16 +54,17 @@ export const command = ( const m = withoutPrefix.match(regex) if (!m) continue - const lastGroup = m[m.length - 1] + // const lastGroup = m[m.length - 1] // eslint-disable-next-line dot-notation - if (lastGroup && msg.client['_isBot']) { - // check bot username - // eslint-disable-next-line dot-notation - if (lastGroup !== msg.client['_selfUsername']) { - return false - } - } + // todo + // if (lastGroup && msg.client['_isBot']) { + // // check bot username + // // eslint-disable-next-line dot-notation + // if (lastGroup !== msg.client['_selfUsername']) { + // return false + // } + // } const match = m.slice(1, -1) diff --git a/packages/dispatcher/src/filters/text.ts b/packages/dispatcher/src/filters/text.ts index 57fee477..b883dfcc 100644 --- a/packages/dispatcher/src/filters/text.ts +++ b/packages/dispatcher/src/filters/text.ts @@ -22,7 +22,7 @@ function extractText(obj: Message | InlineQuery | ChosenInlineResult | CallbackQ * Filter objects that match a given regular expression * - for `Message`, `Message.text` is used * - for `InlineQuery`, `InlineQuery.query` is used - * - for {@link ChosenInlineResult}, {@link ChosenInlineResult.id} is used + * - for {@link ChosenInlineResult}, {@link ChosenInlineResult#id} is used * - for `CallbackQuery`, `CallbackQuery.dataStr` is used * * When a regex matches, the match array is stored in a diff --git a/packages/dispatcher/src/filters/user.ts b/packages/dispatcher/src/filters/user.ts index c0ded1e2..4213fc21 100644 --- a/packages/dispatcher/src/filters/user.ts +++ b/packages/dispatcher/src/filters/user.ts @@ -18,7 +18,7 @@ import { UpdateFilter } from './types' * Filter messages generated by yourself (including Saved Messages) */ export const me: UpdateFilter = (msg) => - (msg.sender.constructor === User && msg.sender.isSelf) || msg.isOutgoing + (msg.sender.type === 'user' && msg.sender.isSelf) || msg.isOutgoing /** * Filter messages sent by bots @@ -66,12 +66,12 @@ export const userId = ( return (matchSelf && sender.isSelf) || sender.id in index || sender.username! in index } else if (ctor === UserStatusUpdate || ctor === UserTypingUpdate) { - const id = (upd as UserStatusUpdate | UserTypingUpdate).userId + // const id = (upd as UserStatusUpdate | UserTypingUpdate).userId - return ( - // eslint-disable-next-line dot-notation - (matchSelf && id === upd.client['_userId']) || id in index - ) + return false + // todo + // eslint-disable-next-line dot-notation + // (matchSelf && id === upd.client['_userId']) || id in index } else if (ctor === PollVoteUpdate) { const peer = (upd as PollVoteUpdate).peer if (peer.type !== 'user') return false @@ -93,11 +93,11 @@ export const userId = ( if (ctor === Message) { return (upd as Message).sender.isSelf } else if (ctor === UserStatusUpdate || ctor === UserTypingUpdate) { - return ( - (upd as UserStatusUpdate | UserTypingUpdate).userId === - // eslint-disable-next-line dot-notation - upd.client['_userId'] - ) + return false + // todo + // (upd as UserStatusUpdate | UserTypingUpdate).userId === + // eslint-disable-next-line dot-notation + // upd.client['_userId'] } else if (ctor === PollVoteUpdate) { const peer = (upd as PollVoteUpdate).peer if (peer.type !== 'user') return false diff --git a/packages/html-parser/src/index.ts b/packages/html-parser/src/index.ts index 24d8781e..d148d44e 100644 --- a/packages/html-parser/src/index.ts +++ b/packages/html-parser/src/index.ts @@ -1,7 +1,7 @@ import { Parser } from 'htmlparser2' import Long from 'long' -import type { FormattedString, IMessageEntityParser, tl } from '@mtcute/client' +import type { FormattedString, IMessageEntityParser, MessageEntity, tl } from '@mtcute/client' const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/ @@ -15,7 +15,7 @@ const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&| */ export function html( strings: TemplateStringsArray, - ...sub: (string | FormattedString<'html'> | boolean | undefined | null)[] + ...sub: (string | FormattedString<'html'> | MessageEntity | boolean | undefined | null)[] ): FormattedString<'html'> { let str = '' sub.forEach((it, idx) => { @@ -23,6 +23,8 @@ export function html( if (typeof it === 'string') { it = HtmlMessageEntityParser.escape(it, Boolean(str.match(/=['"]$/))) + } else if ('raw' in it) { + it = new HtmlMessageEntityParser().unparse(it.text, [it.raw]) } else { if (it.mode && it.mode !== 'html') { throw new Error(`Incompatible parse mode: ${it.mode}`) diff --git a/packages/i18n/tests/i18n.spec.ts b/packages/i18n/tests/i18n.spec.ts index 2bdeae55..f8b38541 100644 --- a/packages/i18n/tests/i18n.spec.ts +++ b/packages/i18n/tests/i18n.spec.ts @@ -84,7 +84,6 @@ describe('i18n', () => { it('should parse language from a message', () => { const message = new Message( - null as never, { _: 'message', peerId: { _: 'peerUser', userId: 1 } } as never, PeersIndex.from({ users: [{ _: 'user', id: 1, firstName: 'Пыня', langCode: 'ru' }], diff --git a/packages/markdown-parser/src/index.ts b/packages/markdown-parser/src/index.ts index 4edfb83d..ea7e4722 100644 --- a/packages/markdown-parser/src/index.ts +++ b/packages/markdown-parser/src/index.ts @@ -1,6 +1,6 @@ import Long from 'long' -import type { FormattedString, IMessageEntityParser, tl } from '@mtcute/client' +import type { FormattedString, IMessageEntityParser, MessageEntity, tl } from '@mtcute/client' const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/ const EMOJI_REGEX = /^tg:\/\/emoji\?id=(-?\d+)/ @@ -25,14 +25,16 @@ const TO_BE_ESCAPED = /[*_\-~`[\\\]|]/g */ export function md( strings: TemplateStringsArray, - ...sub: (string | FormattedString<'markdown'> | boolean | undefined | null)[] + ...sub: (string | FormattedString<'markdown'> | MessageEntity | boolean | undefined | null)[] ): FormattedString<'markdown'> { let str = '' sub.forEach((it, idx) => { if (typeof it === 'boolean' || !it) return if (typeof it === 'string') it = MarkdownMessageEntityParser.escape(it) - else { + else if ('raw' in it) { + it = new MarkdownMessageEntityParser().unparse(it.text, [it.raw]) + } else { if (it.mode && it.mode !== 'markdown') { throw new Error(`Incompatible parse mode: ${it.mode}`) } diff --git a/packages/tl-utils/src/ctor-id.ts b/packages/tl-utils/src/ctor-id.ts index e2dc6220..3f45ee77 100644 --- a/packages/tl-utils/src/ctor-id.ts +++ b/packages/tl-utils/src/ctor-id.ts @@ -1,18 +1,8 @@ import CRC32 from 'crc-32' -import { parseTlToEntries } from './parse' import { writeTlEntryToString } from './stringify' import { TlEntry } from './types' -/** - * Computes the constructor id for a given TL entry string. - * - * @param line Line containing TL entry definition - */ -export function computeConstructorIdFromString(line: string): number { - return computeConstructorIdFromEntry(parseTlToEntries(line, { forIdComputation: true })[0]) -} - /** * Computes the constructor id for a given TL entry. * diff --git a/packages/tl-utils/src/parse.ts b/packages/tl-utils/src/parse.ts index f2e2ace1..f0ddfb66 100644 --- a/packages/tl-utils/src/parse.ts +++ b/packages/tl-utils/src/parse.ts @@ -1,9 +1,13 @@ -import { computeConstructorIdFromString } from './ctor-id' +import { computeConstructorIdFromEntry } from './ctor-id' import { TL_PRIMITIVES, TlArgument, TlEntry } from './types' import { parseArgumentType, parseTdlibStyleComment } from './utils' const SINGLE_REGEX = /^(.+?)(?:#([0-9a-f]{1,8}))?(?: \?)?(?: {(.+?:.+?)})? ((?:.+? )*)= (.+);$/ +export function computeConstructorIdFromString(line: string): number { + return computeConstructorIdFromEntry(parseTlToEntries(line, { forIdComputation: true })[0]) +} + /** * Parse TL schema into a list of entries. * diff --git a/packages/tl-utils/tests/ctor-id.spec.ts b/packages/tl-utils/tests/ctor-id.spec.ts index 9075c2e9..4cfc14fc 100644 --- a/packages/tl-utils/tests/ctor-id.spec.ts +++ b/packages/tl-utils/tests/ctor-id.spec.ts @@ -1,52 +1,7 @@ import { expect } from 'chai' import { describe, it } from 'mocha' -import { computeConstructorIdFromEntry, computeConstructorIdFromString, TlArgument, TlEntry } from '../src' - -describe('computeConstructorIdFromString', () => { - const test = (tl: string, expected: number) => { - expect(computeConstructorIdFromString(tl)).eq(expected) - } - - it('computes for constructors without parameters', () => { - test('auth.logOut = Bool;', 0x5717da40) - test('auth.resetAuthorizations = Bool;', 0x9fab0d1a) - }) - - it('ignores existing constructor id', () => { - test('auth.logOut#aef001df = Bool;', 0x5717da40) - }) - - it('computes for constructors with simple parameters', () => { - test('auth.exportAuthorization dc_id:int = auth.ExportedAuthorization;', 0xe5bfffcd) - }) - - it('computes for constructors with vector parameters', () => { - test('account.deleteSecureValue types:Vector = Bool;', 0xb880bc4b) - }) - - it('computes for constructors with vector return type', () => { - test('account.getSecureValue types:Vector = Vector;', 0x73665bc2) - }) - - it('computes for constructors with optional parameters', () => { - test( - 'account.uploadTheme flags:# file:InputFile thumb:flags.0?InputFile file_name:string mime_type:string = Document;', - 0x1c3db333, - ) - }) - - it('computes for constructors with optional true parameters', () => { - test( - 'account.installTheme flags:# dark:flags.0?true format:flags.1?string theme:flags.1?InputTheme = Bool;', - 0x7ae43737, - ) - }) - - it('computes for constructors with generics', () => { - test('invokeAfterMsg {X:Type} msg_id:long query:!X = X;', 0xcb9f372d) - }) -}) +import { computeConstructorIdFromEntry, TlArgument, TlEntry } from '../src' describe('computeConstructorIdFromEntry', () => { const make = (name: string, type: string, ...args: string[]): TlEntry => ({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8268afda..66cd9295 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ importers: dotenv-flow: specifier: 3.2.0 version: 3.2.0 + dpdm: + specifier: ^3.14.0 + version: 3.14.0 eslint: specifier: 8.47.0 version: 8.47.0 @@ -148,9 +151,6 @@ importers: '@types/ws': specifier: 8.5.4 version: 8.5.4 - exit-hook: - specifier: ^4.0.0 - version: 4.0.0 ws: specifier: 8.13.0 version: 8.13.0 @@ -1364,7 +1364,6 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false /better-sqlite3@8.4.0: resolution: {integrity: sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==} @@ -1396,7 +1395,6 @@ packages: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.0 - dev: false /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1441,7 +1439,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: false /cacache@16.0.7: resolution: {integrity: sha512-a4zfQpp5vm4Ipdvbj+ZrPonikRhm6WBEd4zT1Yc1DXsmAxrPgDwWBLF/u/wTVXSFPIgOJ1U3ghSa2Xm4s3h28w==} @@ -1612,6 +1609,11 @@ packages: restore-cursor: 3.1.0 dev: true + /cli-spinners@2.9.1: + resolution: {integrity: sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==} + engines: {node: '>=6'} + dev: true + /cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} @@ -1653,6 +1655,11 @@ packages: wrap-ansi: 7.0.0 dev: true + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true + /code-point-at@1.1.0: resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} engines: {node: '>=0.10.0'} @@ -1893,6 +1900,12 @@ packages: strip-bom: 4.0.0 dev: true + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + dependencies: + clone: 1.0.4 + dev: true + /define-properties@1.2.0: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} @@ -2014,6 +2027,19 @@ packages: engines: {node: '>=10'} dev: true + /dpdm@3.14.0: + resolution: {integrity: sha512-YJzsFSyEtj88q5eTELg3UWU7TVZkG1dpbF4JDQ3t1b07xuzXmdoGeSz9TKOke1mUuOpWlk4q+pBh+aHzD6GBTg==} + hasBin: true + dependencies: + chalk: 4.1.2 + fs-extra: 11.1.1 + glob: 10.3.10 + ora: 5.4.1 + tslib: 2.6.2 + typescript: 5.2.2 + yargs: 17.7.2 + dev: true + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true @@ -2416,11 +2442,6 @@ packages: strip-final-newline: 3.0.0 dev: true - /exit-hook@4.0.0: - resolution: {integrity: sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==} - engines: {node: '>=18'} - dev: true - /exit-on-epipe@1.0.1: resolution: {integrity: sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==} engines: {node: '>=0.8'} @@ -2731,6 +2752,18 @@ packages: path-scurry: 1.9.2 dev: true + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.1 + minipass: 6.0.2 + path-scurry: 1.10.1 + dev: true + /glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} dependencies: @@ -2960,7 +2993,6 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false /ignore@5.2.0: resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} @@ -3107,6 +3139,11 @@ packages: is-extglob: 2.1.1 dev: true + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true + /is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} dev: false @@ -3305,6 +3342,15 @@ packages: '@pkgjs/parseargs': 0.11.0 dev: true + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -4053,6 +4099,21 @@ packages: type-check: 0.4.0 dev: true + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.1 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + dev: true + /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4166,6 +4227,14 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 9.1.2 + minipass: 6.0.2 + dev: true + /path-scurry@1.9.2: resolution: {integrity: sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==} engines: {node: '>=16 || 14 >=14.17'} @@ -5036,6 +5105,10 @@ packages: resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} dev: true + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: true + /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -5113,6 +5186,12 @@ packages: hasBin: true dev: true + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -5173,6 +5252,12 @@ packages: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} dev: true + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.4 + dev: true + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: