From c92292249b05e5e08863805a314b26f46eff6aa4 Mon Sep 17 00:00:00 2001 From: alina sireneva Date: Sat, 28 Sep 2024 17:26:26 +0300 Subject: [PATCH] feat(core): `isPeerAvailable` method --- packages/core/src/highlevel/client.ts | 25 +++++++ packages/core/src/highlevel/methods.ts | 1 + .../methods/users/is-peer-available.ts | 71 +++++++++++++++++++ .../highlevel/methods/users/resolve-peer.ts | 54 ++++++++------ 4 files changed, 128 insertions(+), 23 deletions(-) create mode 100644 packages/core/src/highlevel/methods/users/is-peer-available.ts diff --git a/packages/core/src/highlevel/client.ts b/packages/core/src/highlevel/client.ts index 022fd910..c61cc2b5 100644 --- a/packages/core/src/highlevel/client.ts +++ b/packages/core/src/highlevel/client.ts @@ -264,6 +264,7 @@ import { getMyUsername } from './methods/users/get-my-username.js' import { getProfilePhoto } from './methods/users/get-profile-photo.js' import { getProfilePhotos } from './methods/users/get-profile-photos.js' import { getUsers } from './methods/users/get-users.js' +import { isPeerAvailable } from './methods/users/is-peer-available.js' import { iterProfilePhotos } from './methods/users/iter-profile-photos.js' import { resolvePeerMany } from './methods/users/resolve-peer-many.js' import { resolveChannel, resolvePeer, resolveUser } from './methods/users/resolve-peer.js' @@ -5536,6 +5537,27 @@ export interface TelegramClient extends ITelegramClient { * @param ids Users' identifiers. Can be ID, username, phone number, `"me"`, `"self"` or TL object */ getUsers(ids: MaybeArray): Promise<(User | null)[]> + /** + * Check whether a given peer ID can be used to actually + * interact with the Telegram API. + * This method checks the internal peers cache for the given + * input peer, and returns `true` if it is available there. + * + * You can think of this method as a stripped down version of + * {@link resolvePeer}, which only returns `true` or `false`. + * + * > **Note:** This method works offline and never sends any requests. + * > This means that when passing a username or phone number, it will + * > only return `true` if the user with that username/phone number + * > is cached in the storage, and will not try to resolve the peer by calling the API, + * > which *may* lead to false negatives. + * + * **Available**: ✅ both users and bots + * + * @returns + */ + isPeerAvailable( + peerId: InputPeerLike): Promise /** * Iterate over profile photos * @@ -6529,6 +6551,9 @@ TelegramClient.prototype.getProfilePhotos = function (...args) { TelegramClient.prototype.getUsers = function (...args) { return getUsers(this._client, ...args) } +TelegramClient.prototype.isPeerAvailable = function (...args) { + return isPeerAvailable(this._client, ...args) +} TelegramClient.prototype.iterProfilePhotos = function (...args) { return iterProfilePhotos(this._client, ...args) } diff --git a/packages/core/src/highlevel/methods.ts b/packages/core/src/highlevel/methods.ts index 22f3f6b2..8a26b885 100644 --- a/packages/core/src/highlevel/methods.ts +++ b/packages/core/src/highlevel/methods.ts @@ -263,6 +263,7 @@ export { getMyUsername } from './methods/users/get-my-username.js' export { getProfilePhoto } from './methods/users/get-profile-photo.js' export { getProfilePhotos } from './methods/users/get-profile-photos.js' export { getUsers } from './methods/users/get-users.js' +export { isPeerAvailable } from './methods/users/is-peer-available.js' export { iterProfilePhotos } from './methods/users/iter-profile-photos.js' export { resolvePeerMany } from './methods/users/resolve-peer-many.js' export { resolvePeer } from './methods/users/resolve-peer.js' diff --git a/packages/core/src/highlevel/methods/users/is-peer-available.ts b/packages/core/src/highlevel/methods/users/is-peer-available.ts new file mode 100644 index 00000000..0c41f0e5 --- /dev/null +++ b/packages/core/src/highlevel/methods/users/is-peer-available.ts @@ -0,0 +1,71 @@ +import { parseMarkedPeerId } from '../../../utils/peer-utils.js' +import type { ITelegramClient } from '../../client.types' +import type { InputPeerLike } from '../../types' + +import { _normalizePeerId } from './resolve-peer.js' + +/** + * Check whether a given peer ID can be used to actually + * interact with the Telegram API. + * This method checks the internal peers cache for the given + * input peer, and returns `true` if it is available there. + * + * You can think of this method as a stripped down version of + * {@link resolvePeer}, which only returns `true` or `false`. + * + * > **Note:** This method works offline and never sends any requests. + * > This means that when passing a username or phone number, it will + * > only return `true` if the user with that username/phone number + * > is cached in the storage, and will not try to resolve the peer by calling the API, + * > which *may* lead to false negatives. + * + * @returns + */ +export async function isPeerAvailable( + client: ITelegramClient, + peerId: InputPeerLike, +): Promise { + peerId = _normalizePeerId(peerId) + + if (typeof peerId === 'object') { + // InputPeer (actual one, not mtcute.*) + return true + } + + if (typeof peerId === 'number') { + const fromStorage = await client.storage.peers.getById(peerId) + if (fromStorage) return true + + // in some cases, the server allows bots to use access_hash=0. + const [peerType] = parseMarkedPeerId(peerId) + + if (peerType === 'chat' || client.storage.self.getCached(true)?.isBot) { + return true + } + + return false + } + + if (typeof peerId === 'string') { + if (peerId === 'self' || peerId === 'me') { + // inputPeerSelf is always available + return true + } + + peerId = peerId.replace(/[@+\s()]/g, '') + + if (peerId.match(/^\d+$/)) { + // phone number + const fromStorage = await client.storage.peers.getByPhone(peerId) + if (fromStorage) return true + } else { + // username + const fromStorage = await client.storage.peers.getByUsername(peerId) + if (fromStorage) return true + } + + return false + } + + return false +} diff --git a/packages/core/src/highlevel/methods/users/resolve-peer.ts b/packages/core/src/highlevel/methods/users/resolve-peer.ts index 77a2442d..33a3bbf5 100644 --- a/packages/core/src/highlevel/methods/users/resolve-peer.ts +++ b/packages/core/src/highlevel/methods/users/resolve-peer.ts @@ -8,6 +8,33 @@ import { MtPeerNotFoundError } from '../../types/errors.js' import type { InputPeerLike } from '../../types/peers/index.js' import { toInputChannel, toInputPeer, toInputUser } from '../../utils/peer-utils.js' +export function _normalizePeerId(peerId: InputPeerLike): number | string | tl.TypeInputPeer { +// for convenience we also accept tl and User/Chat objects directly + if (typeof peerId === 'object') { + if (tl.isAnyPeer(peerId)) { + peerId = getMarkedPeerId(peerId) + } else if ('inputPeer' in peerId) { + // User | Chat + peerId = peerId.inputPeer + } else { + peerId = toInputPeer(peerId) + } + } + + if (typeof peerId === 'object') { + switch (peerId._) { + case 'mtcute.dummyInputPeerMinUser': + return peerId.userId + case 'mtcute.dummyInputPeerMinChannel': + return toggleChannelIdMark(peerId.channelId) + default: + return peerId + } + } + + return peerId +} + // @available=both /** * Get the `InputPeer` of a known peer id. @@ -21,29 +48,10 @@ export async function resolvePeer( peerId: InputPeerLike, force = false, ): Promise { - // for convenience we also accept tl and User/Chat objects directly + peerId = _normalizePeerId(peerId) if (typeof peerId === 'object') { - if (tl.isAnyPeer(peerId)) { - peerId = getMarkedPeerId(peerId) - } else if ('inputPeer' in peerId) { - // User | Chat - peerId = peerId.inputPeer - } else { - peerId = toInputPeer(peerId) - } - } - - if (typeof peerId === 'object') { - switch (peerId._) { - case 'mtcute.dummyInputPeerMinUser': - peerId = peerId.userId - break - case 'mtcute.dummyInputPeerMinChannel': - peerId = toggleChannelIdMark(peerId.channelId) - break - default: - return peerId - } + // InputPeer (actual one, not mtcute.*) + return peerId } if (typeof peerId === 'number' && !force) { @@ -152,7 +160,7 @@ export async function resolvePeer( // if it's not the case, we'll get an `PEER_ID_INVALID` error anyways const [peerType, bareId] = parseMarkedPeerId(peerId) - if (peerType !== 'chat' && !client.storage.self.getCached(true)?.isBot) { + if (!(peerType === 'chat' || client.storage.self.getCached(true)?.isBot)) { throw new MtPeerNotFoundError(`Peer ${peerId} is not found in local cache`) }