dialogs.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import { Api } from "../tl";
  2. import { RequestIter } from "../requestIter";
  3. import { TelegramClient, utils } from "../index";
  4. import { Dialog } from "../tl/custom/dialog";
  5. import { DateLike, EntityLike } from "../define";
  6. import { TotalList } from "../Helpers";
  7. import bigInt from "big-integer";
  8. const _MAX_CHUNK_SIZE = 100;
  9. /**
  10. Get the key to get messages from a dialog.
  11. We cannot just use the message ID because channels share message IDs,
  12. and the peer ID is required to distinguish between them. But it is not
  13. necessary in small group chats and private chats.
  14. * @param {Api.TypePeer} [peer] the dialog peer
  15. * @param {number} [messageId] the message id
  16. * @return {[number,number]} the channel id and message id
  17. */
  18. function _dialogMessageKey(peer: Api.TypePeer, messageId: number): string {
  19. // can't use arrays as keys for map :( need to convert to string.
  20. return (
  21. "" +
  22. [
  23. peer instanceof Api.PeerChannel ? peer.channelId : undefined,
  24. messageId,
  25. ]
  26. );
  27. }
  28. export interface DialogsIterInterface {
  29. offsetDate: number;
  30. offsetId: number;
  31. offsetPeer: Api.TypePeer;
  32. ignorePinned: boolean;
  33. ignoreMigrated: boolean;
  34. folder: number;
  35. }
  36. export class _DialogsIter extends RequestIter {
  37. private request?: Api.messages.GetDialogs;
  38. private seen?: Set<any>;
  39. private offsetDate?: number;
  40. private ignoreMigrated?: boolean;
  41. async _init({
  42. offsetDate,
  43. offsetId,
  44. offsetPeer,
  45. ignorePinned,
  46. ignoreMigrated,
  47. folder,
  48. }: DialogsIterInterface) {
  49. this.request = new Api.messages.GetDialogs({
  50. offsetDate,
  51. offsetId,
  52. offsetPeer,
  53. limit: 1,
  54. hash: bigInt.zero,
  55. excludePinned: ignorePinned,
  56. folderId: folder,
  57. });
  58. if (this.limit <= 0) {
  59. // Special case, get a single dialog and determine count
  60. const dialogs = await this.client.invoke(this.request);
  61. if ("count" in dialogs) {
  62. this.total = dialogs.count;
  63. } else {
  64. this.total = dialogs.dialogs.length;
  65. }
  66. return true;
  67. }
  68. this.seen = new Set();
  69. this.offsetDate = offsetDate;
  70. this.ignoreMigrated = ignoreMigrated;
  71. }
  72. async _loadNextChunk(): Promise<boolean | undefined> {
  73. if (!this.request || !this.seen || !this.buffer) {
  74. return;
  75. }
  76. this.request.limit = Math.min(this.left, _MAX_CHUNK_SIZE);
  77. const r = await this.client.invoke(this.request);
  78. if (r instanceof Api.messages.DialogsNotModified) {
  79. return;
  80. }
  81. if ("count" in r) {
  82. this.total = r.count;
  83. } else {
  84. this.total = r.dialogs.length;
  85. }
  86. const entities = new Map<string, Api.TypeUser | Api.TypeChat>();
  87. const messages = new Map<string, Api.Message>();
  88. for (const entity of [...r.users, ...r.chats]) {
  89. if (
  90. entity instanceof Api.UserEmpty ||
  91. entity instanceof Api.ChatEmpty
  92. ) {
  93. continue;
  94. }
  95. entities.set(utils.getPeerId(entity), entity);
  96. }
  97. for (const m of r.messages) {
  98. let message = m as unknown as Api.Message;
  99. try {
  100. // todo make sure this never fails
  101. message._finishInit(this.client, entities, undefined);
  102. } catch (e) {
  103. this.client._log.error(
  104. "Got error while trying to finish init message with id " +
  105. m.id
  106. );
  107. if (this.client._log.canSend("error")) {
  108. console.error(e);
  109. }
  110. }
  111. messages.set(
  112. _dialogMessageKey(message.peerId!, message.id),
  113. message
  114. );
  115. }
  116. for (const d of r.dialogs) {
  117. if (d instanceof Api.DialogFolder) {
  118. continue;
  119. }
  120. const message = messages.get(
  121. _dialogMessageKey(d.peer, d.topMessage)
  122. );
  123. if (this.offsetDate != undefined) {
  124. const date = message?.date!;
  125. if (date == undefined || date > this.offsetDate) {
  126. continue;
  127. }
  128. }
  129. const peerId = utils.getPeerId(d.peer);
  130. if (!this.seen.has(peerId)) {
  131. this.seen.add(peerId);
  132. if (!entities.has(peerId)) {
  133. /*
  134. > In which case can a UserEmpty appear in the list of banned members?
  135. > In a very rare cases. This is possible but isn't an expected behavior.
  136. Real world example: https://t.me/TelethonChat/271471
  137. */
  138. continue;
  139. }
  140. const cd = new Dialog(this.client, d, entities, message);
  141. if (
  142. !this.ignoreMigrated ||
  143. (cd.entity != undefined && "migratedTo" in cd.entity)
  144. ) {
  145. this.buffer.push(cd);
  146. }
  147. }
  148. }
  149. if (
  150. r.dialogs.length < this.request.limit ||
  151. !(r instanceof Api.messages.DialogsSlice)
  152. ) {
  153. return true;
  154. }
  155. let lastMessage;
  156. for (let dialog of r.dialogs.reverse()) {
  157. lastMessage = messages.get(
  158. _dialogMessageKey(dialog.peer, dialog.topMessage)
  159. );
  160. if (lastMessage) {
  161. break;
  162. }
  163. }
  164. this.request.excludePinned = true;
  165. this.request.offsetId = lastMessage ? lastMessage.id : 0;
  166. this.request.offsetDate = lastMessage ? lastMessage.date! : 0;
  167. this.request.offsetPeer =
  168. this.buffer[this.buffer.length - 1].inputEntity;
  169. }
  170. }
  171. /** interface for iterating and getting dialogs. */
  172. export interface IterDialogsParams {
  173. /** How many dialogs to be retrieved as maximum. Can be set to undefined to retrieve all dialogs.<br/>
  174. * 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.*/
  175. limit?: number;
  176. /** The offset date of last message of dialog to be used. */
  177. offsetDate?: DateLike;
  178. /** The message ID to be used as offset. */
  179. offsetId?: number;
  180. /** offset Peer to be used (defaults to Empty = no offset) */
  181. offsetPeer?: EntityLike;
  182. /** Whether pinned dialogs should be ignored or not. When set to true, these won't be yielded at all. */
  183. ignorePinned?: boolean;
  184. /** Whether Chat that have migratedTo a Supergroup should be included or not.<br/>
  185. * 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.*/
  186. ignoreMigrated?: boolean;
  187. /** The folder from which the dialogs should be retrieved.<br/>
  188. * If left unspecified, all dialogs (including those from folders) will be returned.<br/>
  189. * If set to 0, all dialogs that don't belong to any folder will be returned.<br/>
  190. * If set to a folder number like 1, only those from said folder will be returned.<br/>
  191. * 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/> */
  192. folder?: number;
  193. /** Alias for folder. If unspecified, all will be returned, false implies `folder:0` and True implies `folder:1`.*/
  194. archived?: boolean;
  195. }
  196. /** @hidden */
  197. export function iterDialogs(
  198. client: TelegramClient,
  199. {
  200. limit = undefined,
  201. offsetDate = undefined,
  202. offsetId = 0,
  203. offsetPeer = new Api.InputPeerEmpty(),
  204. ignorePinned = false,
  205. ignoreMigrated = false,
  206. folder = undefined,
  207. archived = undefined,
  208. }: IterDialogsParams
  209. ): _DialogsIter {
  210. if (archived != undefined) {
  211. folder = archived ? 1 : 0;
  212. }
  213. return new _DialogsIter(
  214. client,
  215. limit,
  216. {},
  217. {
  218. offsetDate,
  219. offsetId,
  220. offsetPeer,
  221. ignorePinned,
  222. ignoreMigrated,
  223. folder,
  224. }
  225. );
  226. }
  227. /** @hidden */
  228. export async function getDialogs(
  229. client: TelegramClient,
  230. params: IterDialogsParams
  231. ): Promise<TotalList<Dialog>> {
  232. return (await client.iterDialogs(params).collect()) as TotalList<Dialog>;
  233. }