messages.ts 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  1. import { Api } from "../tl";
  2. import type {
  3. DateLike,
  4. EntityLike,
  5. FileLike,
  6. MarkupLike,
  7. MessageIDLike,
  8. MessageLike,
  9. } from "../define";
  10. import { RequestIter } from "../requestIter";
  11. import {
  12. _EntityType,
  13. _entityType,
  14. TotalList,
  15. isArrayLike,
  16. groupBy,
  17. } from "../Helpers";
  18. import { getInputMedia, getMessageId, getPeerId, parseID } from "../Utils";
  19. import type { TelegramClient } from "../";
  20. import { utils } from "../";
  21. import { _parseMessageText } from "./messageParse";
  22. import { _getPeer } from "./users";
  23. import bigInt from "big-integer";
  24. import { _fileToMedia } from "./uploads";
  25. const _MAX_CHUNK_SIZE = 100;
  26. interface MessageIterParams {
  27. entity: EntityLike;
  28. offsetId: number;
  29. minId: number;
  30. maxId: number;
  31. fromUser?: EntityLike;
  32. offsetDate: DateLike;
  33. addOffset: number;
  34. filter: any;
  35. search: string;
  36. replyTo: MessageIDLike;
  37. }
  38. export class _MessagesIter extends RequestIter {
  39. entity?: Api.TypeInputPeer;
  40. request?:
  41. | Api.messages.SearchGlobal
  42. | Api.messages.GetReplies
  43. | Api.messages.GetHistory
  44. | Api.messages.Search;
  45. fromId?: string | bigInt.BigInteger;
  46. addOffset?: number;
  47. maxId?: number;
  48. minId?: number;
  49. lastId?: number;
  50. async _init({
  51. entity,
  52. offsetId,
  53. minId,
  54. maxId,
  55. fromUser,
  56. offsetDate,
  57. addOffset,
  58. filter,
  59. search,
  60. replyTo,
  61. }: MessageIterParams) {
  62. if (entity) {
  63. this.entity = await this.client.getInputEntity(entity);
  64. } else {
  65. this.entity = undefined;
  66. if (this.reverse) {
  67. throw new Error("Cannot reverse global search");
  68. }
  69. }
  70. if (this.reverse) {
  71. offsetId = Math.max(offsetId, minId);
  72. if (offsetId && maxId) {
  73. if (maxId - offsetId <= 1) {
  74. return false;
  75. }
  76. }
  77. if (!maxId) {
  78. maxId = Number.MAX_SAFE_INTEGER;
  79. }
  80. } else {
  81. offsetId = Math.max(offsetId, maxId);
  82. if (offsetId && minId) {
  83. if (offsetId - minId <= 1) {
  84. return false;
  85. }
  86. }
  87. }
  88. if (this.reverse) {
  89. if (offsetId) {
  90. offsetId += 1;
  91. } else if (!offsetDate) {
  92. offsetId = 1;
  93. }
  94. }
  95. if (fromUser) {
  96. fromUser = await this.client.getInputEntity(fromUser);
  97. this.fromId = await this.client.getPeerId(fromUser);
  98. } else {
  99. this.fromId = undefined;
  100. }
  101. if (!this.entity && fromUser) {
  102. this.entity = new Api.InputPeerEmpty();
  103. }
  104. if (!filter) {
  105. filter = new Api.InputMessagesFilterEmpty();
  106. }
  107. if (!this.entity) {
  108. this.request = new Api.messages.SearchGlobal({
  109. q: search || "",
  110. filter: filter,
  111. minDate: undefined,
  112. // TODO fix this smh
  113. maxDate: offsetDate,
  114. offsetRate: undefined,
  115. offsetPeer: new Api.InputPeerEmpty(),
  116. offsetId: offsetId,
  117. limit: 1,
  118. });
  119. } else if (replyTo !== undefined) {
  120. this.request = new Api.messages.GetReplies({
  121. peer: this.entity,
  122. msgId: replyTo,
  123. offsetId: offsetId,
  124. offsetDate: offsetDate,
  125. addOffset: addOffset,
  126. limit: 0,
  127. maxId: 0,
  128. minId: 0,
  129. hash: bigInt.zero,
  130. });
  131. } else if (
  132. search !== undefined ||
  133. filter !== undefined ||
  134. fromUser !== undefined
  135. ) {
  136. const ty = _entityType(this.entity);
  137. if (ty == _EntityType.USER) {
  138. fromUser = undefined;
  139. } else {
  140. this.fromId = undefined;
  141. }
  142. this.request = new Api.messages.Search({
  143. peer: this.entity,
  144. q: search || "",
  145. filter: typeof filter === "function" ? new filter() : filter,
  146. minDate: undefined,
  147. maxDate: offsetDate,
  148. offsetId: offsetId,
  149. addOffset: addOffset,
  150. limit: 0,
  151. maxId: 0,
  152. minId: 0,
  153. hash: bigInt.zero,
  154. fromId: fromUser,
  155. });
  156. if (
  157. filter instanceof Api.InputMessagesFilterEmpty &&
  158. offsetDate &&
  159. !search &&
  160. !offsetId
  161. ) {
  162. for await (const m of this.client.iterMessages(this.entity, {
  163. limit: 1,
  164. offsetDate: offsetDate,
  165. })) {
  166. this.request.offsetId = m.id + 1;
  167. }
  168. }
  169. } else {
  170. this.request = new Api.messages.GetHistory({
  171. peer: this.entity,
  172. limit: 1,
  173. offsetDate: offsetDate,
  174. offsetId: offsetId,
  175. minId: 0,
  176. maxId: 0,
  177. addOffset: addOffset,
  178. hash: bigInt.zero,
  179. });
  180. }
  181. if (this.limit <= 0) {
  182. const result = await this.client.invoke(this.request);
  183. if (result instanceof Api.messages.MessagesNotModified) {
  184. this.total = result.count;
  185. } else {
  186. if ("count" in result) {
  187. this.total = result.count;
  188. } else {
  189. this.total = result.messages.length;
  190. }
  191. }
  192. return false;
  193. }
  194. if (!this.waitTime) {
  195. this.waitTime = this.limit > 3000 ? 1 : 0;
  196. }
  197. if (
  198. this.reverse &&
  199. !(this.request instanceof Api.messages.SearchGlobal)
  200. ) {
  201. this.request.addOffset -= _MAX_CHUNK_SIZE;
  202. }
  203. this.addOffset = addOffset;
  204. this.maxId = maxId;
  205. this.minId = minId;
  206. this.lastId = this.reverse ? 0 : Number.MAX_SAFE_INTEGER;
  207. }
  208. async _loadNextChunk() {
  209. if (!this.request) {
  210. throw new Error("Request not set yet");
  211. }
  212. this.request.limit = Math.min(this.left, _MAX_CHUNK_SIZE);
  213. if (this.reverse && this.request.limit != _MAX_CHUNK_SIZE) {
  214. if (!(this.request instanceof Api.messages.SearchGlobal)) {
  215. this.request.addOffset = this.addOffset! - this.request.limit;
  216. }
  217. }
  218. const r = await this.client.invoke(this.request);
  219. if (r instanceof Api.messages.MessagesNotModified) {
  220. return true;
  221. }
  222. if ("count" in r) {
  223. this.total = r.count;
  224. } else {
  225. this.total = r.messages.length;
  226. }
  227. const entities = new Map();
  228. for (const x of [...r.users, ...r.chats]) {
  229. entities.set(getPeerId(x), x);
  230. }
  231. const messages: Api.Message[] = this.reverse
  232. ? (r.messages.reverse() as unknown as Api.Message[])
  233. : (r.messages as unknown as Api.Message[]);
  234. for (const message of messages) {
  235. if (this.fromId && message.senderId?.notEquals(this.fromId)) {
  236. continue;
  237. }
  238. if (!this._messageInRange(message)) {
  239. return true;
  240. }
  241. this.lastId = message.id;
  242. try {
  243. // if this fails it shouldn't be a big problem
  244. message._finishInit(this.client, entities, this.entity);
  245. } catch (e) {}
  246. message._entities = entities;
  247. this.buffer?.push(message);
  248. }
  249. if (r.messages.length < this.request.limit) {
  250. return true;
  251. }
  252. if (this.buffer) {
  253. this._updateOffset(this.buffer[this.buffer.length - 1], r);
  254. } else {
  255. return true;
  256. }
  257. }
  258. _messageInRange(message: Api.Message) {
  259. if (this.entity) {
  260. if (this.reverse) {
  261. if (message.id <= this.lastId! || message.id >= this.maxId!) {
  262. return false;
  263. }
  264. } else {
  265. if (message.id >= this.lastId! || message.id <= this.minId!) {
  266. return false;
  267. }
  268. }
  269. }
  270. return true;
  271. }
  272. [Symbol.asyncIterator](): AsyncIterator<Api.Message, any, undefined> {
  273. return super[Symbol.asyncIterator]();
  274. }
  275. _updateOffset(lastMessage: Api.Message, response: any) {
  276. if (!this.request) {
  277. throw new Error("Request not set yet");
  278. }
  279. this.request.offsetId = Number(lastMessage.id);
  280. if (this.reverse) {
  281. this.request.offsetId += 1;
  282. }
  283. if (this.request instanceof Api.messages.Search) {
  284. this.request.maxDate = -1;
  285. } else {
  286. if (!(this.request instanceof Api.messages.SearchGlobal)) {
  287. this.request.offsetDate = lastMessage.date!;
  288. }
  289. }
  290. if (this.request instanceof Api.messages.SearchGlobal) {
  291. if (lastMessage.inputChat) {
  292. this.request.offsetPeer = lastMessage.inputChat;
  293. } else {
  294. this.request.offsetPeer = new Api.InputPeerEmpty();
  295. }
  296. this.request.offsetRate = response.nextRate;
  297. }
  298. }
  299. }
  300. interface IDsIterInterface {
  301. entity: EntityLike;
  302. ids: Api.TypeInputMessage[];
  303. }
  304. export class _IDsIter extends RequestIter {
  305. _ids?: Api.TypeInputMessage[];
  306. _offset?: number;
  307. _ty: number | undefined;
  308. private _entity: Api.TypeInputPeer | undefined;
  309. async _init({ entity, ids }: IDsIterInterface) {
  310. this.total = ids.length;
  311. this._ids = this.reverse ? ids.reverse() : ids;
  312. this._offset = 0;
  313. this._entity = entity
  314. ? await this.client.getInputEntity(entity)
  315. : undefined;
  316. this._ty = this._entity ? _entityType(this._entity) : undefined;
  317. if (!this.waitTime) {
  318. this.waitTime = this.limit > 300 ? 10 : 0;
  319. }
  320. }
  321. [Symbol.asyncIterator](): AsyncIterator<Api.Message, any, undefined> {
  322. return super[Symbol.asyncIterator]();
  323. }
  324. async _loadNextChunk() {
  325. const ids = this._ids!.slice(
  326. this._offset,
  327. this._offset! + _MAX_CHUNK_SIZE
  328. );
  329. if (!ids.length) {
  330. return false;
  331. }
  332. this._offset! += _MAX_CHUNK_SIZE;
  333. let fromId;
  334. let r;
  335. if (this._ty == _EntityType.CHANNEL) {
  336. try {
  337. r = await this.client.invoke(
  338. new Api.channels.GetMessages({
  339. channel: this._entity,
  340. id: ids,
  341. })
  342. );
  343. } catch (e: any) {
  344. if (e.errorMessage == "MESSAGE_IDS_EMPTY") {
  345. r = new Api.messages.MessagesNotModified({
  346. count: ids.length,
  347. });
  348. } else {
  349. throw e;
  350. }
  351. }
  352. } else {
  353. r = await this.client.invoke(
  354. new Api.messages.GetMessages({
  355. id: ids,
  356. })
  357. );
  358. if (this._entity) {
  359. fromId = await _getPeer(this.client, this._entity);
  360. }
  361. }
  362. if (r instanceof Api.messages.MessagesNotModified) {
  363. this.buffer?.push(...Array(ids.length));
  364. return;
  365. }
  366. const entities = new Map();
  367. for (const entity of [...r.users, ...r.chats]) {
  368. entities.set(utils.getPeerId(entity), entity);
  369. }
  370. let message: Api.TypeMessage;
  371. for (message of r.messages) {
  372. if (
  373. message instanceof Api.MessageEmpty ||
  374. (fromId &&
  375. utils.getPeerId(message.peerId) != utils.getPeerId(fromId))
  376. ) {
  377. this.buffer?.push(undefined);
  378. } else {
  379. const temp: Api.Message = message as unknown as Api.Message;
  380. temp._finishInit(this.client, entities, this._entity);
  381. temp._entities = entities;
  382. this.buffer?.push(temp);
  383. }
  384. }
  385. }
  386. }
  387. /**
  388. * Interface for iterating over messages. used in both {@link iterMessages} and {@link getMessages}.
  389. */
  390. export interface IterMessagesParams {
  391. /** Number of messages to be retrieved.<br/>
  392. * Due to limitations with the API retrieving more than 3000 messages will take longer than half a minute. (might even take longer)<br/>
  393. * if undefined is passed instead of a number the library will try to retrieve all the messages.*/
  394. limit?: number;
  395. /** Offset date (messages previous to this date will be retrieved). Exclusive. */
  396. offsetDate?: DateLike;
  397. /** Offset message ID (only messages previous to the given ID will be retrieved). Exclusive. */
  398. offsetId: number;
  399. /** All the messages with a higher (newer) ID or equal to this will be excluded. */
  400. maxId: number;
  401. /** All the messages with a lower (older) ID or equal to this will be excluded. */
  402. minId: number;
  403. /** Additional message offset (all of the specified offsets + this offset = older messages). */
  404. addOffset: number;
  405. /** The string to be used as a search query. */
  406. search?: string;
  407. /** The filter to use when returning messages.<br/>
  408. * For instance, InputMessagesFilterPhotos would yield only messages containing photos.
  409. */
  410. filter?: Api.TypeMessagesFilter | Api.TypeMessagesFilter[];
  411. /** Only messages from this user will be returned. */
  412. fromUser?: EntityLike;
  413. /** Wait time (in seconds) between different GetHistory requests.<br/>
  414. * Use this parameter to avoid hitting the FloodWaitError as needed.<br/>
  415. * If left to undefined, it will default to 1 second only if the number of messages is higher than 3000.
  416. * If the ids parameter is used, this time will default to 10 seconds only if the amount of IDs is higher than 300.
  417. */
  418. waitTime?: number;
  419. /** A single integer ID (or several IDs) for the message that should be returned.<br/>
  420. * This parameter takes precedence over the rest (which will be ignored if this is set).<br/>
  421. * This can for instance be used to get the message with ID 123 from a channel.<br/>
  422. * **Note** that if the message doesn"t exist, undefined will appear in its place.
  423. */
  424. ids?: number | number[] | Api.TypeInputMessage | Api.TypeInputMessage[];
  425. /** If set to `true`, the messages will be returned in reverse order (from oldest to newest, instead of the default newest to oldest).<br/>
  426. * This also means that the meaning of offsetId and offsetDate parameters is reversed, although they will still be exclusive.<br/>
  427. * `minId` becomes equivalent to `offsetId` instead of being `maxId` as well since messages are returned in ascending order.<br/>
  428. * You cannot use this if both entity and ids are undefined.
  429. */
  430. reverse?: boolean;
  431. /** If set to a message ID, the messages that reply to this ID will be returned.<br/>
  432. * This feature is also known as comments in posts of broadcast channels, or viewing threads in groups.<br/>
  433. * This feature can only be used in broadcast channels and their linked supergroups. Using it in a chat or private conversation will result in PEER_ID_INVALID error.<br/>
  434. * When using this parameter, the filter and search parameters have no effect, since Telegram's API doesn't support searching messages in replies.
  435. */
  436. replyTo?: number;
  437. /** If set to `true`, messages which are scheduled will be returned.
  438. * All other parameters will be ignored for this, except `entity`.
  439. */
  440. scheduled: boolean;
  441. }
  442. const IterMessagesDefaults: IterMessagesParams = {
  443. limit: undefined,
  444. offsetDate: undefined,
  445. offsetId: 0,
  446. maxId: 0,
  447. minId: 0,
  448. addOffset: 0,
  449. search: undefined,
  450. filter: undefined,
  451. fromUser: undefined,
  452. waitTime: undefined,
  453. ids: undefined,
  454. reverse: false,
  455. replyTo: undefined,
  456. scheduled: false,
  457. };
  458. /**
  459. * Interface for sending a message. only message is required
  460. */
  461. export interface SendMessageParams {
  462. /** The message to be sent, or another message object to resend as a copy.<br/>
  463. * The maximum length for a message is 35,000 bytes or 4,096 characters.<br/>
  464. * Longer messages will not be sliced automatically, and you should slice them manually if the text to send is longer than said length. */
  465. message?: MessageLike;
  466. /** Whether to reply to a message or not. If an integer is provided, it should be the ID of the message that it should reply to. */
  467. replyTo?: number | Api.Message;
  468. /** Optional attributes that override the inferred ones, like DocumentAttributeFilename and so on. */
  469. attributes?: Api.TypeDocumentAttribute[];
  470. /** See the {@link parseMode} property for allowed values. Markdown parsing will be used by default. */
  471. parseMode?: any;
  472. /** A list of message formatting entities. When provided, the parseMode is ignored. */
  473. formattingEntities?: Api.TypeMessageEntity[];
  474. /** Should the link preview be shown? */
  475. linkPreview?: boolean;
  476. /** Sends a message with a file attached (e.g. a photo, video, audio or document). The message may be empty. */
  477. file?: FileLike | FileLike[];
  478. /** Optional JPEG thumbnail (for documents). Telegram will ignore this parameter unless you pass a .jpg file!<br/>
  479. * The file must also be small in dimensions and in disk size. Successful thumbnails were files below 20kB and 320x320px.<br/>
  480. * Width/height and dimensions/size ratios may be important.
  481. * For Telegram to accept a thumbnail, you must provide the dimensions of the underlying media through `attributes:` with DocumentAttributesVideo.
  482. */
  483. thumb?: FileLike;
  484. /** Whether to send the given file as a document or not. */
  485. forceDocument?: false;
  486. /** Whether the existing draft should be cleared or not. */
  487. clearDraft?: false;
  488. /** The matrix (list of lists), row list or button to be shown after sending the message.<br/>
  489. * This parameter will only work if you have signed in as a bot. You can also pass your own ReplyMarkup here.<br/>
  490. * <br/>
  491. * All the following limits apply together:
  492. * - There can be 100 buttons at most (any more are ignored).
  493. * - There can be 8 buttons per row at most (more are ignored).
  494. * - The maximum callback data per button is 64 bytes.
  495. * - The maximum data that can be embedded in total is just over 4KB, shared between inline callback data and text.
  496. */
  497. buttons?: MarkupLike;
  498. /** Whether the message should notify people in a broadcast channel or not. Defaults to false, which means it will notify them. Set it to True to alter this behaviour. */
  499. silent?: boolean;
  500. /** Whether the sent video supports streaming or not.<br/>
  501. * Note that Telegram only recognizes as streamable some formats like MP4, and others like AVI or MKV will not work.<br/>
  502. * You should convert these to MP4 before sending if you want them to be streamable. Unsupported formats will result in VideoContentTypeError. */
  503. supportStreaming?: boolean;
  504. /** If set, the message won't send immediately, and instead it will be scheduled to be automatically sent at a later time. */
  505. schedule?: DateLike;
  506. noforwards?: boolean;
  507. }
  508. /** interface used for forwarding messages */
  509. export interface ForwardMessagesParams {
  510. /** The message(s) to forward, or their integer IDs. */
  511. messages: MessageIDLike | MessageIDLike[];
  512. /** If the given messages are integer IDs and not instances of the Message class, this must be specified in order for the forward to work.<br/> */
  513. fromPeer: EntityLike;
  514. /** Whether the message should notify people with sound or not.<br/>
  515. * Defaults to false (send with a notification sound unless the person has the chat muted). Set it to true to alter this behaviour. */
  516. silent?: boolean;
  517. /** If set, the message(s) won't forward immediately, and instead they will be scheduled to be automatically sent at a later time. */
  518. schedule?: DateLike;
  519. noforwards?: boolean;
  520. }
  521. /** Interface for editing messages */
  522. export interface EditMessageParams {
  523. /** The ID of the message (or Message itself) to be edited. If the entity was a Message, then this message will be treated as the new text. */
  524. message: Api.Message | number;
  525. /** The new text of the message. Does nothing if the entity was a Message. */
  526. text?: string;
  527. /** See the {@link TelegramClient.parseMode} property for allowed values. Markdown parsing will be used by default. */
  528. parseMode?: any;
  529. /** A list of message formatting entities. When provided, the parseMode is ignored. */
  530. formattingEntities?: Api.TypeMessageEntity[];
  531. /** Should the link preview be shown? */
  532. linkPreview?: boolean;
  533. /** The file object that should replace the existing media in the message. Does nothing if entity was a Message */
  534. file?: FileLike;
  535. /** Whether to send the given file as a document or not. */
  536. forceDocument?: false;
  537. /** The matrix (list of lists), row list or button to be shown after sending the message.<br/>
  538. * This parameter will only work if you have signed in as a bot. You can also pass your own ReplyMarkup here.<br/>
  539. * <br/>
  540. * All the following limits apply together:
  541. * - There can be 100 buttons at most (any more are ignored).
  542. * - There can be 8 buttons per row at most (more are ignored).
  543. * - The maximum callback data per button is 64 bytes.
  544. * - The maximum data that can be embedded in total is just over 4KB, shared between inline callback data and text.
  545. */
  546. buttons?: MarkupLike;
  547. /** If set, the message won't be edited immediately, and instead it will be scheduled to be automatically edited at a later time. */
  548. schedule?: DateLike;
  549. }
  550. /** Interface for editing messages */
  551. export interface UpdatePinMessageParams {
  552. /** Whether the pin should notify people or not. <br />
  553. * By default it has the opposite behavior of official clients, it will not notify members.
  554. */
  555. notify?: boolean;
  556. /** Whether the message should be pinned for everyone or not. <br />
  557. * By default it has the opposite behavior of official clients, and it will pin the message for both sides, in private chats.
  558. */
  559. pmOneSide?: boolean;
  560. }
  561. /** Interface for mark message as read */
  562. export interface MarkAsReadParams {
  563. /**
  564. * Until which message should the read acknowledge be sent for. <br />
  565. * This has priority over the `message` parameter.
  566. */
  567. maxId?: number;
  568. /**
  569. * Whether the mention badge should be cleared (so that there are no more mentions) or not for the given entity. <br />
  570. * If no message is provided, this will be the only action taken.
  571. */
  572. clearMentions?: boolean;
  573. }
  574. /** @hidden */
  575. export function iterMessages(
  576. client: TelegramClient,
  577. entity: EntityLike | undefined,
  578. options: Partial<IterMessagesParams>
  579. ) {
  580. const {
  581. limit,
  582. offsetDate,
  583. offsetId,
  584. maxId,
  585. minId,
  586. addOffset,
  587. search,
  588. filter,
  589. fromUser,
  590. waitTime,
  591. ids,
  592. reverse,
  593. replyTo,
  594. } = { ...IterMessagesDefaults, ...options };
  595. if (ids) {
  596. let idsArray;
  597. if (!isArrayLike(ids)) {
  598. idsArray = [ids];
  599. } else {
  600. idsArray = ids;
  601. }
  602. return new _IDsIter(
  603. client,
  604. idsArray.length,
  605. {
  606. reverse: reverse,
  607. waitTime: waitTime,
  608. },
  609. {
  610. entity: entity,
  611. ids: idsArray,
  612. }
  613. );
  614. }
  615. return new _MessagesIter(
  616. client,
  617. limit,
  618. {
  619. waitTime: waitTime,
  620. reverse: reverse,
  621. },
  622. {
  623. entity: entity,
  624. offsetId: offsetId,
  625. minId: minId,
  626. maxId: maxId,
  627. fromUser: fromUser,
  628. offsetDate: offsetDate,
  629. addOffset: addOffset,
  630. filter: filter,
  631. search: search,
  632. replyTo: replyTo,
  633. }
  634. );
  635. }
  636. /** @hidden */
  637. export async function getMessages(
  638. client: TelegramClient,
  639. entity: EntityLike | undefined,
  640. params: Partial<IterMessagesParams>
  641. ): Promise<TotalList<Api.Message>> {
  642. if (Object.keys(params).length == 1 && params.limit === undefined) {
  643. if (params.minId === undefined && params.maxId === undefined) {
  644. params.limit = undefined;
  645. } else {
  646. params.limit = 1;
  647. }
  648. }
  649. const it = client.iterMessages(entity, params);
  650. const ids = params.ids;
  651. if (ids && !isArrayLike(ids)) {
  652. for await (const message of it) {
  653. return [message];
  654. }
  655. return [];
  656. }
  657. return (await it.collect()) as TotalList<Api.Message>;
  658. }
  659. // region Message
  660. /** @hidden */
  661. export async function sendMessage(
  662. client: TelegramClient,
  663. /** To who will it be sent. */
  664. entity: EntityLike,
  665. /** The message to be sent, or another message object to resend as a copy.<br/>
  666. * The maximum length for a message is 35,000 bytes or 4,096 characters.<br/>
  667. * Longer messages will not be sliced automatically, and you should slice them manually if the text to send is longer than said length. */
  668. {
  669. message,
  670. replyTo,
  671. attributes,
  672. parseMode,
  673. formattingEntities,
  674. linkPreview = true,
  675. file,
  676. thumb,
  677. forceDocument,
  678. clearDraft,
  679. buttons,
  680. silent,
  681. supportStreaming,
  682. schedule,
  683. noforwards,
  684. }: SendMessageParams = {}
  685. ) {
  686. if (file) {
  687. return client.sendFile(entity, {
  688. file: file,
  689. caption: message
  690. ? typeof message == "string"
  691. ? message
  692. : message.message
  693. : "",
  694. forceDocument: forceDocument,
  695. clearDraft: clearDraft,
  696. replyTo: replyTo,
  697. attributes: attributes,
  698. thumb: thumb,
  699. supportsStreaming: supportStreaming,
  700. parseMode: parseMode,
  701. formattingEntities: formattingEntities,
  702. silent: silent,
  703. scheduleDate: schedule,
  704. buttons: buttons,
  705. noforwards: noforwards,
  706. });
  707. }
  708. entity = await client.getInputEntity(entity);
  709. let markup, request;
  710. if (message && message instanceof Api.Message) {
  711. if (buttons == undefined) {
  712. markup = message.replyMarkup;
  713. } else {
  714. markup = client.buildReplyMarkup(buttons);
  715. }
  716. if (silent == undefined) {
  717. silent = message.silent;
  718. }
  719. if (
  720. message.media &&
  721. !(message.media instanceof Api.MessageMediaWebPage)
  722. ) {
  723. throw new Error("Not Supported Yet");
  724. /*
  725. return this.sendFile(entity, message.media, {
  726. caption: message.message,
  727. silent: silent,
  728. replyTo: replyTo,
  729. buttons: markup,
  730. formattingEntities: message.entities,
  731. schedule: schedule
  732. })
  733. */
  734. }
  735. request = new Api.messages.SendMessage({
  736. peer: entity,
  737. message: message.message || "",
  738. silent: silent,
  739. replyToMsgId: getMessageId(replyTo),
  740. replyMarkup: markup,
  741. entities: message.entities,
  742. clearDraft: clearDraft,
  743. noWebpage: !(message.media instanceof Api.MessageMediaWebPage),
  744. scheduleDate: schedule,
  745. noforwards: noforwards,
  746. });
  747. message = message.message;
  748. } else {
  749. if (formattingEntities == undefined) {
  750. [message, formattingEntities] = await _parseMessageText(
  751. client,
  752. message || "",
  753. parseMode
  754. );
  755. }
  756. if (!message) {
  757. throw new Error(
  758. "The message cannot be empty unless a file is provided"
  759. );
  760. }
  761. request = new Api.messages.SendMessage({
  762. peer: entity,
  763. message: message.toString(),
  764. entities: formattingEntities,
  765. noWebpage: !linkPreview,
  766. replyToMsgId: getMessageId(replyTo),
  767. clearDraft: clearDraft,
  768. silent: silent,
  769. replyMarkup: client.buildReplyMarkup(buttons),
  770. scheduleDate: schedule,
  771. noforwards: noforwards,
  772. });
  773. }
  774. const result = await client.invoke(request);
  775. if (result instanceof Api.UpdateShortSentMessage) {
  776. const msg = new Api.Message({
  777. id: result.id,
  778. peerId: await _getPeer(client, entity),
  779. message: message,
  780. date: result.date,
  781. out: result.out,
  782. media: result.media,
  783. entities: result.entities,
  784. replyMarkup: request.replyMarkup,
  785. ttlPeriod: result.ttlPeriod,
  786. });
  787. msg._finishInit(client, new Map(), entity);
  788. return msg;
  789. }
  790. return client._getResponseMessage(request, result, entity) as Api.Message;
  791. }
  792. /** @hidden */
  793. export async function forwardMessages(
  794. client: TelegramClient,
  795. entity: EntityLike,
  796. { messages, fromPeer, silent, schedule, noforwards }: ForwardMessagesParams
  797. ) {
  798. if (!isArrayLike(messages)) {
  799. messages = [messages];
  800. }
  801. entity = await client.getInputEntity(entity);
  802. let fromPeerId: string | undefined;
  803. if (fromPeer) {
  804. fromPeer = await client.getInputEntity(fromPeer);
  805. fromPeerId = await client.getPeerId(fromPeer);
  806. }
  807. const getKey = (m: string | Api.Message) => {
  808. if (m instanceof Api.Message) {
  809. return m.chatId;
  810. }
  811. let msgId = parseID(m);
  812. if (msgId) {
  813. if (fromPeerId !== undefined) {
  814. return fromPeerId;
  815. }
  816. throw new Error("fromPeer must be given if integer IDs are used");
  817. } else {
  818. throw new Error(`Cannot forward ${m}`);
  819. }
  820. };
  821. const sent: Api.Message[] = [];
  822. for (let [chatId, chunk] of groupBy(messages, getKey) as Map<
  823. number,
  824. Api.Message[] | number[]
  825. >) {
  826. let chat;
  827. let numbers: number[] = [];
  828. if (typeof chunk[0] == "number") {
  829. chat = fromPeer;
  830. numbers = chunk as number[];
  831. } else {
  832. chat = await chunk[0].getInputChat();
  833. numbers = (chunk as Api.Message[]).map((m: Api.Message) => m.id);
  834. }
  835. chunk.push();
  836. const request = new Api.messages.ForwardMessages({
  837. fromPeer: chat,
  838. id: numbers,
  839. toPeer: entity,
  840. silent: silent,
  841. scheduleDate: schedule,
  842. noforwards: noforwards,
  843. });
  844. const result = await client.invoke(request);
  845. sent.push(
  846. client._getResponseMessage(request, result, entity) as Api.Message
  847. );
  848. }
  849. return sent;
  850. }
  851. /** @hidden */
  852. export async function editMessage(
  853. client: TelegramClient,
  854. entity: EntityLike,
  855. {
  856. message,
  857. text,
  858. parseMode,
  859. formattingEntities,
  860. linkPreview = true,
  861. file,
  862. forceDocument,
  863. buttons,
  864. schedule,
  865. }: EditMessageParams
  866. ) {
  867. if (typeof message === "number" && typeof text === "undefined" && !file) {
  868. throw Error("You have to provide either file and text property.");
  869. }
  870. entity = await client.getInputEntity(entity);
  871. let id: number | undefined;
  872. let markup: Api.TypeReplyMarkup | undefined;
  873. let entities: Api.TypeMessageEntity[] | undefined;
  874. let inputMedia: Api.TypeInputMedia | undefined;
  875. if (file) {
  876. const { fileHandle, media, image } = await _fileToMedia(client, {
  877. file,
  878. forceDocument,
  879. });
  880. inputMedia = media;
  881. }
  882. if (message instanceof Api.Message) {
  883. id = getMessageId(message);
  884. text = message.message;
  885. entities = message.entities;
  886. if (buttons == undefined) {
  887. markup = message.replyMarkup;
  888. } else {
  889. markup = client.buildReplyMarkup(buttons);
  890. }
  891. if (message.media) {
  892. inputMedia = getInputMedia(message.media, { forceDocument });
  893. }
  894. } else {
  895. if (typeof message !== "number") {
  896. throw Error(
  897. "editMessageParams.message must be either a number or a Api.Message type"
  898. );
  899. }
  900. id = message;
  901. if (formattingEntities == undefined) {
  902. [text, entities] = await _parseMessageText(
  903. client,
  904. text || "",
  905. parseMode
  906. );
  907. } else {
  908. entities = formattingEntities;
  909. }
  910. markup = client.buildReplyMarkup(buttons);
  911. }
  912. const request = new Api.messages.EditMessage({
  913. peer: entity,
  914. id,
  915. message: text,
  916. noWebpage: !linkPreview,
  917. entities,
  918. media: inputMedia,
  919. replyMarkup: markup,
  920. scheduleDate: schedule,
  921. });
  922. const result = await client.invoke(request);
  923. return client._getResponseMessage(request, result, entity) as Api.Message;
  924. }
  925. /** @hidden */
  926. export async function deleteMessages(
  927. client: TelegramClient,
  928. entity: EntityLike | undefined,
  929. messageIds: MessageIDLike[],
  930. { revoke = false }
  931. ) {
  932. let ty = _EntityType.USER;
  933. if (entity) {
  934. entity = await client.getInputEntity(entity);
  935. ty = _entityType(entity);
  936. }
  937. const ids: number[] = [];
  938. for (const messageId of messageIds) {
  939. if (
  940. messageId instanceof Api.Message ||
  941. messageId instanceof Api.MessageService ||
  942. messageId instanceof Api.MessageEmpty
  943. ) {
  944. ids.push(messageId.id);
  945. } else if (typeof messageId === "number") {
  946. ids.push(messageId);
  947. } else {
  948. throw new Error(`Cannot convert ${messageId} to an integer`);
  949. }
  950. }
  951. const results = [];
  952. if (ty == _EntityType.CHANNEL) {
  953. for (const chunk of utils.chunks(ids)) {
  954. results.push(
  955. client.invoke(
  956. new Api.channels.DeleteMessages({
  957. channel: entity,
  958. id: chunk,
  959. })
  960. )
  961. );
  962. }
  963. } else {
  964. for (const chunk of utils.chunks(ids)) {
  965. results.push(
  966. client.invoke(
  967. new Api.messages.DeleteMessages({
  968. id: chunk,
  969. revoke: revoke,
  970. })
  971. )
  972. );
  973. }
  974. }
  975. return Promise.all(results);
  976. }
  977. /** @hidden */
  978. export async function pinMessage(
  979. client: TelegramClient,
  980. entity: EntityLike,
  981. message?: MessageIDLike,
  982. pinMessageParams?: UpdatePinMessageParams
  983. ) {
  984. return await _pin(
  985. client,
  986. entity,
  987. message,
  988. false,
  989. pinMessageParams?.notify,
  990. pinMessageParams?.pmOneSide
  991. );
  992. }
  993. /** @hidden */
  994. export async function unpinMessage(
  995. client: TelegramClient,
  996. entity: EntityLike,
  997. message?: MessageIDLike,
  998. unpinMessageParams?: UpdatePinMessageParams
  999. ) {
  1000. return await _pin(
  1001. client,
  1002. entity,
  1003. message,
  1004. true,
  1005. unpinMessageParams?.notify,
  1006. unpinMessageParams?.pmOneSide
  1007. );
  1008. }
  1009. /** @hidden */
  1010. export async function _pin(
  1011. client: TelegramClient,
  1012. entity: EntityLike,
  1013. message: MessageIDLike | undefined,
  1014. unpin: boolean,
  1015. notify: boolean = false,
  1016. pmOneSide: boolean = false
  1017. ) {
  1018. message = utils.getMessageId(message) || 0;
  1019. if (message === 0) {
  1020. return await client.invoke(
  1021. new Api.messages.UnpinAllMessages({
  1022. peer: entity,
  1023. })
  1024. );
  1025. }
  1026. entity = await client.getInputEntity(entity);
  1027. const request = new Api.messages.UpdatePinnedMessage({
  1028. silent: !notify,
  1029. unpin,
  1030. pmOneside: pmOneSide,
  1031. peer: entity,
  1032. id: message,
  1033. });
  1034. const result = await client.invoke(request);
  1035. /**
  1036. * Unpinning does not produce a service message.
  1037. * Pinning a message that was already pinned also produces no service message.
  1038. * Pinning a message in your own chat does not produce a service message,
  1039. * but pinning on a private conversation with someone else does.
  1040. */
  1041. if (
  1042. unpin ||
  1043. !("updates" in result) ||
  1044. ("updates" in result && !result.updates)
  1045. ) {
  1046. return;
  1047. }
  1048. // Pinning a message that doesn't exist would RPC-error earlier
  1049. return client._getResponseMessage(request, result, entity) as Api.Message;
  1050. }
  1051. /** @hidden */
  1052. export async function markAsRead(
  1053. client: TelegramClient,
  1054. entity: EntityLike,
  1055. message?: MessageIDLike | MessageIDLike[],
  1056. markAsReadParams?: MarkAsReadParams
  1057. ): Promise<boolean> {
  1058. let maxId: number = markAsReadParams?.maxId || 0;
  1059. const maxIdIsUndefined = markAsReadParams?.maxId === undefined;
  1060. if (maxIdIsUndefined) {
  1061. if (message) {
  1062. if (Array.isArray(message)) {
  1063. maxId = Math.max(
  1064. ...message.map((v) => utils.getMessageId(v) as number)
  1065. );
  1066. } else {
  1067. maxId = utils.getMessageId(message) as number;
  1068. }
  1069. }
  1070. }
  1071. entity = await client.getInputEntity(entity);
  1072. if (markAsReadParams && !markAsReadParams.clearMentions) {
  1073. await client.invoke(new Api.messages.ReadMentions({ peer: entity }));
  1074. if (maxIdIsUndefined && message === undefined) {
  1075. return true;
  1076. }
  1077. }
  1078. if (_entityType(entity) === _EntityType.CHANNEL) {
  1079. return await client.invoke(
  1080. new Api.channels.ReadHistory({ channel: entity, maxId })
  1081. );
  1082. } else {
  1083. await client.invoke(
  1084. new Api.messages.ReadHistory({ peer: entity, maxId })
  1085. );
  1086. return true;
  1087. }
  1088. }
  1089. // TODO do the rest