From 451fdd0cfc507b80a122d7ea76a2a87f06a34484 Mon Sep 17 00:00:00 2001 From: teidesu Date: Thu, 8 Apr 2021 21:40:04 +0300 Subject: [PATCH] feat(client): iterHistory method also fixed a few issues with getHistory --- packages/client/src/client.ts | 60 +++++++++++- .../src/methods/messages/get-history.ts | 13 ++- .../src/methods/messages/iter-history.ts | 91 +++++++++++++++++++ 3 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 packages/client/src/methods/messages/iter-history.ts diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 57aeb965..d9e5d643 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -22,6 +22,7 @@ import { deleteMessages } from './methods/messages/delete-messages' import { _findMessageInUpdate } from './methods/messages/find-in-update' import { getHistory } from './methods/messages/get-history' import { getMessages } from './methods/messages/get-messages' +import { iterHistory } from './methods/messages/iter-history' import { _parseEntities } from './methods/messages/parse-entities' import { sendPhoto } from './methods/messages/send-photo' import { sendText } from './methods/messages/send-text' @@ -440,7 +441,7 @@ export class TelegramClient extends BaseTelegramClient { * Retrieve a chunk of the chat history. * * You can get up to 100 messages with one call. - * For larger chunks, use {@link TelegramClient.iterHistory}. + * For larger chunks, use {@link iterHistory}. * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param params Additional fetch parameters @@ -524,6 +525,63 @@ export class TelegramClient extends BaseTelegramClient { ): Promise> { return getMessages.apply(this, arguments) } + /** + * Iterate through a chat history sequentially. + * + * This method wraps {@link getHistory} to allow processing large + * groups of messages or entire chats. + * + * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. + * @param params Additional fetch parameters + */ + iterHistory( + chatId: InputPeerLike, + params?: { + /** + * Limits the number of messages to be retrieved. + * + * By default, no limit is applied and all messages + * are returned. + */ + limit?: number + + /** + * Sequential number of the first message to be returned. + * Defaults to 0 (most recent message). + * + * Negative values are also accepted and are useful + * in case you set `offsetId` or `offsetDate`. + */ + offset?: number + + /** + * Pass a message identifier as an offset to retrieve + * only older messages starting from that message + */ + offsetId?: number + + /** + * Pass a date (`Date` or Unix time in ms) as an offset to retrieve + * only older messages starting from that date. + */ + offsetDate?: number | Date + + /** + * Pass `true` to retrieve messages in reversed order (from older to recent) + */ + reverse?: boolean + + /** + * Chunk size, which will be passed as `limit` parameter + * to {@link getHistory}. Usually you shouldn't care about this. + * + * Defaults to `100` + */ + chunkSize?: number + } + ): AsyncIterableIterator { + return iterHistory.apply(this, arguments) + } protected _parseEntities( text?: string, diff --git a/packages/client/src/methods/messages/get-history.ts b/packages/client/src/methods/messages/get-history.ts index 3cce7b7b..ae8d84db 100644 --- a/packages/client/src/methods/messages/get-history.ts +++ b/packages/client/src/methods/messages/get-history.ts @@ -1,5 +1,5 @@ import { TelegramClient } from '../../client' -import { InputPeerLike, Message, MtCuteTypeAssertionError } from '../../types' +import { InputPeerLike, Message, MtCuteArgumentError, MtCuteTypeAssertionError } from '../../types' import { createUsersChatsIndex, normalizeToInputPeer } from '../../utils/peer-utils' import { normalizeDate } from '../../utils/misc-utils' @@ -7,7 +7,7 @@ import { normalizeDate } from '../../utils/misc-utils' * Retrieve a chunk of the chat history. * * You can get up to 100 messages with one call. - * For larger chunks, use {@link TelegramClient.iterHistory}. + * For larger chunks, use {@link iterHistory}. * * @param chatId Chat's marked ID, its username, phone or `"me"` or `"self"`. * @param params Additional fetch parameters @@ -53,7 +53,8 @@ export async function getHistory( ): Promise { if (!params) params = {} - const offsetId = params.offsetId || (params.reverse ? 1 : 0) + const offsetId = + params.offsetId ?? (params.reverse && !params.offsetDate ? 1 : 0) const limit = params.limit || 100 const peer = normalizeToInputPeer(await this.resolvePeer(chatId)) @@ -81,5 +82,9 @@ export async function getHistory( const { users, chats } = createUsersChatsIndex(res) - return res.messages.map((msg) => new Message(this, msg, users, chats)) + const msgs = res.messages.map((msg) => new Message(this, msg, users, chats)) + + if (params.reverse) msgs.reverse() + + return msgs } diff --git a/packages/client/src/methods/messages/iter-history.ts b/packages/client/src/methods/messages/iter-history.ts new file mode 100644 index 00000000..94f18982 --- /dev/null +++ b/packages/client/src/methods/messages/iter-history.ts @@ -0,0 +1,91 @@ +import { TelegramClient } from '../../client' +import { InputPeerLike, Message } from '../../types' +import { normalizeToInputPeer } from '../../utils/peer-utils' + +/** + * Iterate through a chat history sequentially. + * + * This method wraps {@link getHistory} to allow processing large + * groups of messages or entire chats. + * + * @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, + chatId: InputPeerLike, + params?: { + /** + * Limits the number of messages to be retrieved. + * + * By default, no limit is applied and all messages + * are returned. + */ + limit?: number + + /** + * Sequential number of the first message to be returned. + * Defaults to 0 (most recent message). + * + * Negative values are also accepted and are useful + * in case you set `offsetId` or `offsetDate`. + */ + offset?: number + + /** + * Pass a message identifier as an offset to retrieve + * only older messages starting from that message + */ + offsetId?: number + + /** + * Pass a date (`Date` or Unix time in ms) as an offset to retrieve + * only older messages starting from that date. + */ + offsetDate?: number | Date + + /** + * Pass `true` to retrieve messages in reversed order (from older to recent) + */ + reverse?: boolean + + /** + * Chunk size, which will be passed as `limit` parameter + * to {@link getHistory}. Usually you shouldn't care about this. + * + * Defaults to `100` + */ + chunkSize?: number + } +): AsyncIterableIterator { + if (!params) params = {} + + let offsetId = + params.offsetId ?? (params.reverse && !params.offsetDate ? 1 : 0) + let current = 0 + const total = params.limit || Infinity + const limit = Math.min(params.chunkSize || 100, total) + + // resolve peer once and pass an InputPeer afterwards + const peer = normalizeToInputPeer(await this.resolvePeer(chatId)) + + for (;;) { + const messages = await this.getHistory(peer, { + limit: Math.min(limit, total - current), + offset: params.offset, + offsetId, + offsetDate: params.offsetDate, + reverse: params.reverse, + }) + + if (!messages.length) break + + offsetId = messages[messages.length - 1].id + (params.reverse ? 1 : 0) + + yield* messages + current += messages.length + + if (current >= total) break + } +}