diff --git a/packages/client/package.json b/packages/client/package.json index 14607205..cb161975 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -14,6 +14,7 @@ "dependencies": { "@mtcute/tl": "^0.0.0", "@mtcute/core": "^0.0.0", - "@mtcute/file-id": "^0.0.0" + "@mtcute/file-id": "^0.0.0", + "eager-async-pool": "^1.0.0" } } diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 27b74dbc..1df1cc1e 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -129,6 +129,7 @@ import { getMe } from './methods/users/get-me' import { getProfilePhotos } from './methods/users/get-profile-photos' import { getUsers } from './methods/users/get-users' import { iterProfilePhotos } from './methods/users/iter-profile-photos' +import { resolvePeerMany } from './methods/users/resolve-peer-many' import { resolvePeer } from './methods/users/resolve-peer' import { setOffline } from './methods/users/set-offline' import { setProfilePhoto } from './methods/users/set-profile-photo' @@ -2646,6 +2647,34 @@ export interface TelegramClient extends BaseTelegramClient { maxId?: tl.Long } ): AsyncIterableIterator + /** + * Get multiple `InputPeer`s at once, + * while also normalizing and removing + * peers that can't be normalized to that type. + * Uses `async-eager-pool` internally, with a + * limit of 10. + * + * @param peerIds Peer Ids + * @param normalizer Normalization function + */ + resolvePeerMany< + T extends tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel + >( + peerIds: InputPeerLike[], + normalizer: ( + obj: tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel + ) => T | null + ): Promise + /** + * Get multiple `InputPeer`s at once. + * Uses `async-eager-pool` internally, with a + * limit of 10. + * + * @param peerIds Peer Ids + */ + resolvePeerMany( + peerIds: InputPeerLike[] + ): Promise<(tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel)[]> /** * Get the `InputPeer` of a known peer id. * Useful when an `InputPeer` is needed. @@ -2867,6 +2896,7 @@ export class TelegramClient extends BaseTelegramClient { getProfilePhotos = getProfilePhotos getUsers = getUsers iterProfilePhotos = iterProfilePhotos + resolvePeerMany = resolvePeerMany resolvePeer = resolvePeer setOffline = setOffline setProfilePhoto = setProfilePhoto diff --git a/packages/client/src/methods/chats/add-chat-members.ts b/packages/client/src/methods/chats/add-chat-members.ts index 3c64d5c2..1c35ba07 100644 --- a/packages/client/src/methods/chats/add-chat-members.ts +++ b/packages/client/src/methods/chats/add-chat-members.ts @@ -46,11 +46,10 @@ export async function addChatMembers( const updates = await this.call({ _: 'channels.inviteToChannel', channel: normalizeToInputChannel(chat)!, - users: await Promise.all( - (users as InputPeerLike[]).map((u) => - this.resolvePeer(u).then(normalizeToInputUser) - ) - ).then((res) => res.filter(Boolean)) as tl.TypeInputUser[], + users: await this.resolvePeerMany( + users as InputPeerLike[], + normalizeToInputUser + ), fwdLimit: forwardCount, }) this._handleUpdate(updates) diff --git a/packages/client/src/methods/chats/create-group.ts b/packages/client/src/methods/chats/create-group.ts index 91ed3547..2bafc532 100644 --- a/packages/client/src/methods/chats/create-group.ts +++ b/packages/client/src/methods/chats/create-group.ts @@ -23,10 +23,10 @@ export async function createGroup( ): Promise { if (!Array.isArray(users)) users = [users] - const peers = (await Promise.all( - (users as InputPeerLike[]) - .map(u => this.resolvePeer(u).then(normalizeToInputUser)) - )).filter(Boolean) as tl.TypeInputUser[] + const peers = await this.resolvePeerMany( + users as InputPeerLike[], + normalizeToInputUser + ) const res = await this.call({ _: 'messages.createChat', 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 c4bd8463..1fdedc5c 100644 --- a/packages/client/src/methods/chats/get-chat-event-log.ts +++ b/packages/client/src/methods/chats/get-chat-event-log.ts @@ -97,11 +97,10 @@ export async function* getChatEventLog( const chunkSize = Math.min(params.chunkSize ?? 100, total) const admins: tl.TypeInputUser[] | undefined = params.users - ? ((await Promise.all( - params.users - .map((u) => this.resolvePeer(u).then(normalizeToInputUser)) - .filter(Boolean) - )) as tl.TypeInputUser[]) + ? await this.resolvePeerMany( + params.users, + normalizeToInputUser + ) : undefined let serverFilter: diff --git a/packages/client/src/methods/contacts/delete-contacts.ts b/packages/client/src/methods/contacts/delete-contacts.ts index 61fe94e1..44e69914 100644 --- a/packages/client/src/methods/contacts/delete-contacts.ts +++ b/packages/client/src/methods/contacts/delete-contacts.ts @@ -40,13 +40,10 @@ export async function deleteContacts( const single = !Array.isArray(userIds) if (single) userIds = [userIds as InputPeerLike] - const inputPeers = (( - await Promise.all( - (userIds as InputPeerLike[]).map((it) => - this.resolvePeer(it).then(normalizeToInputUser) - ) - ) - ).filter(Boolean) as unknown) as tl.TypeInputUser[] + const inputPeers = await this.resolvePeerMany( + userIds as InputPeerLike[], + normalizeToInputUser + ) if (single && !inputPeers.length) throw new MtCuteInvalidPeerTypeError( diff --git a/packages/client/src/methods/users/get-users.ts b/packages/client/src/methods/users/get-users.ts index 7007a521..e0b007a1 100644 --- a/packages/client/src/methods/users/get-users.ts +++ b/packages/client/src/methods/users/get-users.ts @@ -17,7 +17,9 @@ export async function getUsers( /** * Get information about multiple users. - * You can retrieve up to 200 users at once + * You can retrieve up to 200 users at once. + * + * Note that order is not guaranteed. * * @param ids Users' identifiers. Can be ID, username, phone number, `"me"`, `"self"` or TL object * @internal @@ -35,13 +37,10 @@ export async function getUsers( const isArray = Array.isArray(ids) if (!isArray) ids = [ids as InputPeerLike] - const inputPeers = (( - await Promise.all( - (ids as InputPeerLike[]).map((it) => - this.resolvePeer(it).then(normalizeToInputUser) - ) - ) - ).filter(Boolean) as unknown) as tl.TypeInputUser[] + const inputPeers = await this.resolvePeerMany( + ids as InputPeerLike[], + normalizeToInputUser + ) let res = await this.call({ _: 'users.getUsers', diff --git a/packages/client/src/methods/users/resolve-peer-many.ts b/packages/client/src/methods/users/resolve-peer-many.ts new file mode 100644 index 00000000..0036e8be --- /dev/null +++ b/packages/client/src/methods/users/resolve-peer-many.ts @@ -0,0 +1,92 @@ +import { tl } from '@mtcute/tl' +import { TelegramClient } from '../../client' +import { InputPeerLike } from '../../types' +import { asyncPool } from 'eager-async-pool' + +/** + * Get multiple `InputPeer`s at once, + * while also normalizing and removing + * peers that can't be normalized to that type. + * Uses `async-eager-pool` internally, with a + * limit of 10. + * + * @param peerIds Peer Ids + * @param normalizer Normalization function + * @internal + */ +export async function resolvePeerMany< + T extends tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel +>( + this: TelegramClient, + peerIds: InputPeerLike[], + normalizer: ( + obj: tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel + ) => T | null +): Promise + +/** + * Get multiple `InputPeer`s at once. + * Uses `async-eager-pool` internally, with a + * limit of 10. + * + * @param peerIds Peer Ids + * @internal + */ +export async function resolvePeerMany( + this: TelegramClient, + peerIds: InputPeerLike[] +): Promise<(tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel)[]> + +/** + * @internal + */ +export async function resolvePeerMany( + this: TelegramClient, + peerIds: InputPeerLike[], + normalizer?: ( + obj: tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel + ) => tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel | null +): Promise<(tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel)[]> { + const ret: ( + | tl.TypeInputPeer + | tl.TypeInputUser + | tl.TypeInputChannel + )[] = [] + + if (peerIds.length < 10) { + // no point in using async pool for <10 peers + const res = await Promise.all(peerIds.map((it) => this.resolvePeer(it))) + + if (!normalizer) return res + + for (const value of res) { + const norm = normalizer(value) + if (norm) { + ret.push(norm) + } + } + } else { + for await (const { error, value } of asyncPool( + (it) => this.resolvePeer(it), + peerIds, + { + limit: 10, + } + )) { + if (error) { + throw error + } + if (!value) continue + + if (!normalizer) { + ret.push(value) + } else { + const norm = normalizer(value) + if (norm) { + ret.push(norm) + } + } + } + } + return ret +}