123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- import { Api } from "../tl";
- import { RequestIter } from "../requestIter";
- import { TelegramClient, utils } from "../index";
- import { Dialog } from "../tl/custom/dialog";
- import { DateLike, EntityLike } from "../define";
- import { TotalList } from "../Helpers";
- import bigInt from "big-integer";
- const _MAX_CHUNK_SIZE = 100;
- /**
- Get the key to get messages from a dialog.
- We cannot just use the message ID because channels share message IDs,
- and the peer ID is required to distinguish between them. But it is not
- necessary in small group chats and private chats.
- * @param {Api.TypePeer} [peer] the dialog peer
- * @param {number} [messageId] the message id
- * @return {[number,number]} the channel id and message id
- */
- function _dialogMessageKey(peer: Api.TypePeer, messageId: number): string {
- // can't use arrays as keys for map :( need to convert to string.
- return (
- "" +
- [
- peer instanceof Api.PeerChannel ? peer.channelId : undefined,
- messageId,
- ]
- );
- }
- export interface DialogsIterInterface {
- offsetDate: number;
- offsetId: number;
- offsetPeer: Api.TypePeer;
- ignorePinned: boolean;
- ignoreMigrated: boolean;
- folder: number;
- }
- export class _DialogsIter extends RequestIter {
- private request?: Api.messages.GetDialogs;
- private seen?: Set<any>;
- private offsetDate?: number;
- private ignoreMigrated?: boolean;
- async _init({
- offsetDate,
- offsetId,
- offsetPeer,
- ignorePinned,
- ignoreMigrated,
- folder,
- }: DialogsIterInterface) {
- this.request = new Api.messages.GetDialogs({
- offsetDate,
- offsetId,
- offsetPeer,
- limit: 1,
- hash: bigInt.zero,
- excludePinned: ignorePinned,
- folderId: folder,
- });
- if (this.limit <= 0) {
- // Special case, get a single dialog and determine count
- const dialogs = await this.client.invoke(this.request);
- if ("count" in dialogs) {
- this.total = dialogs.count;
- } else {
- this.total = dialogs.dialogs.length;
- }
- return true;
- }
- this.seen = new Set();
- this.offsetDate = offsetDate;
- this.ignoreMigrated = ignoreMigrated;
- }
- async _loadNextChunk(): Promise<boolean | undefined> {
- if (!this.request || !this.seen || !this.buffer) {
- return;
- }
- this.request.limit = Math.min(this.left, _MAX_CHUNK_SIZE);
- const r = await this.client.invoke(this.request);
- if (r instanceof Api.messages.DialogsNotModified) {
- return;
- }
- if ("count" in r) {
- this.total = r.count;
- } else {
- this.total = r.dialogs.length;
- }
- const entities = new Map<string, Api.TypeUser | Api.TypeChat>();
- const messages = new Map<string, Api.Message>();
- for (const entity of [...r.users, ...r.chats]) {
- if (
- entity instanceof Api.UserEmpty ||
- entity instanceof Api.ChatEmpty
- ) {
- continue;
- }
- entities.set(utils.getPeerId(entity), entity);
- }
- for (const m of r.messages) {
- let message = m as unknown as Api.Message;
- try {
- // todo make sure this never fails
- message._finishInit(this.client, entities, undefined);
- } catch (e) {
- this.client._log.error(
- "Got error while trying to finish init message with id " +
- m.id
- );
- if (this.client._log.canSend("error")) {
- console.error(e);
- }
- }
- messages.set(
- _dialogMessageKey(message.peerId!, message.id),
- message
- );
- }
- for (const d of r.dialogs) {
- if (d instanceof Api.DialogFolder) {
- continue;
- }
- const message = messages.get(
- _dialogMessageKey(d.peer, d.topMessage)
- );
- if (this.offsetDate != undefined) {
- const date = message?.date!;
- if (date == undefined || date > this.offsetDate) {
- continue;
- }
- }
- const peerId = utils.getPeerId(d.peer);
- if (!this.seen.has(peerId)) {
- this.seen.add(peerId);
- if (!entities.has(peerId)) {
- /*
- > In which case can a UserEmpty appear in the list of banned members?
- > In a very rare cases. This is possible but isn't an expected behavior.
- Real world example: https://t.me/TelethonChat/271471
- */
- continue;
- }
- const cd = new Dialog(this.client, d, entities, message);
- if (
- !this.ignoreMigrated ||
- (cd.entity != undefined && "migratedTo" in cd.entity)
- ) {
- this.buffer.push(cd);
- }
- }
- }
- if (
- r.dialogs.length < this.request.limit ||
- !(r instanceof Api.messages.DialogsSlice)
- ) {
- return true;
- }
- let lastMessage;
- for (let dialog of r.dialogs.reverse()) {
- lastMessage = messages.get(
- _dialogMessageKey(dialog.peer, dialog.topMessage)
- );
- if (lastMessage) {
- break;
- }
- }
- this.request.excludePinned = true;
- this.request.offsetId = lastMessage ? lastMessage.id : 0;
- this.request.offsetDate = lastMessage ? lastMessage.date! : 0;
- this.request.offsetPeer =
- this.buffer[this.buffer.length - 1].inputEntity;
- }
- }
- /** interface for iterating and getting dialogs. */
- export interface IterDialogsParams {
- /** How many dialogs to be retrieved as maximum. Can be set to undefined to retrieve all dialogs.<br/>
- * Note that this may take whole minutes if you have hundreds of dialogs, as Telegram will tell the library to slow down through a FloodWaitError.*/
- limit?: number;
- /** The offset date of last message of dialog to be used. */
- offsetDate?: DateLike;
- /** The message ID to be used as offset. */
- offsetId?: number;
- /** offset Peer to be used (defaults to Empty = no offset) */
- offsetPeer?: EntityLike;
- /** Whether pinned dialogs should be ignored or not. When set to true, these won't be yielded at all. */
- ignorePinned?: boolean;
- /** Whether Chat that have migratedTo a Supergroup should be included or not.<br/>
- * By default all the chats in your dialogs are returned, but setting this to True will ignore (i.e. skip) them in the same way official applications do.*/
- ignoreMigrated?: boolean;
- /** The folder from which the dialogs should be retrieved.<br/>
- * If left unspecified, all dialogs (including those from folders) will be returned.<br/>
- * If set to 0, all dialogs that don't belong to any folder will be returned.<br/>
- * If set to a folder number like 1, only those from said folder will be returned.<br/>
- * By default Telegram assigns the folder ID 1 to archived chats, so you should use that if you need to fetch the archived dialogs.<br/> */
- folder?: number;
- /** Alias for folder. If unspecified, all will be returned, false implies `folder:0` and True implies `folder:1`.*/
- archived?: boolean;
- }
- /** @hidden */
- export function iterDialogs(
- client: TelegramClient,
- {
- limit = undefined,
- offsetDate = undefined,
- offsetId = 0,
- offsetPeer = new Api.InputPeerEmpty(),
- ignorePinned = false,
- ignoreMigrated = false,
- folder = undefined,
- archived = undefined,
- }: IterDialogsParams
- ): _DialogsIter {
- if (archived != undefined) {
- folder = archived ? 1 : 0;
- }
- return new _DialogsIter(
- client,
- limit,
- {},
- {
- offsetDate,
- offsetId,
- offsetPeer,
- ignorePinned,
- ignoreMigrated,
- folder,
- }
- );
- }
- /** @hidden */
- export async function getDialogs(
- client: TelegramClient,
- params: IterDialogsParams
- ): Promise<TotalList<Dialog>> {
- return (await client.iterDialogs(params).collect()) as TotalList<Dialog>;
- }
|