Advertisement
tcyknhrabirwjyljhp

Untitled

Feb 12th, 2023
103
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 50.15 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Undiscord
  3. // @description Delete all messages in a Discord channel or DM (Bulk deletion)
  4. // @version 5.0.3
  5. // @author victornpb
  6. // @homepageURL https://github.com/victornpb/undiscord
  7. // @supportURL https://github.com/victornpb/undiscord/issues
  8. // @match https://*.discord.com/app
  9. // @match https://*.discord.com/channels/*
  10. // @match https://*.discord.com/login
  11. // @license MIT
  12. // @namespace https://github.com/victornpb/deleteDiscordMessages
  13. // @downloadURL https://raw.githubusercontent.com/victornpb/deleteDiscordMessages/master/deleteDiscordMessages.user.js
  14. // @contributionURL https://www.buymeacoffee.com/vitim
  15. // @grant none
  16. // ==/UserScript==
  17. (function () {
  18. 'use strict';
  19.  
  20. var version = "5.0.3";
  21.  
  22. var discordStyles = (`
  23. /* undiscord window */
  24. #undiscord.browser {
  25. box-shadow: var(--elevation-stroke), var(--elevation-high);
  26. overflow: hidden;
  27. }
  28. #undiscord.container,
  29. #undiscord .container {
  30. background-color: var(--background-secondary);
  31. border-radius: 8px;
  32. box-sizing: border-box;
  33. cursor: default;
  34. flex-direction: column;
  35. }
  36. #undiscord .header {
  37. background-color: var(--background-tertiary);
  38. height: 48px;
  39. align-items: center;
  40. min-height: 48px;
  41. padding: 0 16px;
  42. display: flex;
  43. color: var(--header-secondary);
  44. }
  45. #undiscord .header .icon {
  46. color: var(--interactive-normal);
  47. margin-right: 8px;
  48. flex-shrink: 0;
  49. width: 24;
  50. height: 24;
  51. }
  52. #undiscord .header .icon:hover {
  53. color: var(--interactive-hover);
  54. }
  55. #undiscord .header h3 {
  56. font-size: 16px;
  57. line-height: 20px;
  58. font-weight: 500;
  59. font-family: var(--font-display);
  60. color: var(--header-primary);
  61. flex-shrink: 0;
  62. margin-right: 16px;
  63. }
  64. #undiscord .header .spacer {
  65. flex-grow: 1;
  66. }
  67. #undiscord .header .vert-divider {
  68. width: 1px;
  69. height: 24px;
  70. background-color: var(--background-modifier-accent);
  71. margin-right: 16px;
  72. flex-shrink: 0;
  73. }
  74. #undiscord legend,
  75. #undiscord label {
  76. display: block;
  77. width: 100%;
  78. color: var(--header-secondary);
  79. font-size: 12px;
  80. line-height: 16px;
  81. font-weight: 500;
  82. text-transform: uppercase;
  83. cursor: default;
  84. font-family: var(--font-display);
  85. margin-bottom: 8px;
  86. }
  87. #undiscord .multiInput {
  88. display: flex;
  89. align-items: center;
  90. font-size: 16px;
  91. box-sizing: border-box;
  92. width: 100%;
  93. border-radius: 3px;
  94. color: var(--text-normal);
  95. background-color: var(--input-background);
  96. border: none;
  97. transition: border-color 0.2s ease-in-out 0s;
  98. }
  99. #undiscord .multiInput :first-child {
  100. flex-grow: 1;
  101. }
  102. #undiscord .multiInput button:last-child {
  103. margin-right: 4px;
  104. }
  105. #undiscord .input {
  106. font-size: 16px;
  107. box-sizing: border-box;
  108. width: 100%;
  109. border-radius: 3px;
  110. color: var(--text-normal);
  111. background-color: var(--input-background);
  112. border: none;
  113. transition: border-color 0.2s ease-in-out 0s;
  114. padding: 10px;
  115. height: 40px;
  116. }
  117. #undiscord fieldset {
  118. margin-top: 16px;
  119. }
  120. #undiscord .input-wrapper {
  121. display: flex;
  122. align-items: center;
  123. font-size: 16px;
  124. box-sizing: border-box;
  125. width: 100%;
  126. border-radius: 3px;
  127. color: var(--text-normal);
  128. background-color: var(--input-background);
  129. border: none;
  130. transition: border-color 0.2s ease-in-out 0s;
  131. }
  132. #undiscord input[type="text"],
  133. #undiscord input[type="search"],
  134. #undiscord input[type="password"],
  135. #undiscord input[type="datetime-local"],
  136. #undiscord input[type="number"] {
  137. font-size: 16px;
  138. box-sizing: border-box;
  139. width: 100%;
  140. border-radius: 3px;
  141. color: var(--text-normal);
  142. background-color: var(--input-background);
  143. border: none;
  144. transition: border-color 0.2s ease-in-out 0s;
  145. padding: 10px;
  146. height: 40px;
  147. }
  148. #undiscord .divider,
  149. #undiscord hr {
  150. border: none;
  151. margin-bottom: 24px;
  152. padding-bottom: 4px;
  153. border-bottom: 1px solid var(--background-modifier-accent);
  154. }
  155. #undiscord .sectionDescription {
  156. margin-bottom: 16px;
  157. color: var(--header-secondary);
  158. font-size: 14px;
  159. line-height: 20px;
  160. font-weight: 400;
  161. }
  162. #undiscord a {
  163. color: var(--text-link);
  164. text-decoration: none;
  165. }
  166. #undiscord .btn,
  167. #undiscord button {
  168. position: relative;
  169. display: flex;
  170. -webkit-box-pack: center;
  171. justify-content: center;
  172. -webkit-box-align: center;
  173. align-items: center;
  174. box-sizing: border-box;
  175. background: none;
  176. border: none;
  177. border-radius: 3px;
  178. font-size: 14px;
  179. font-weight: 500;
  180. line-height: 16px;
  181. padding: 2px 16px;
  182. user-select: none;
  183. /* sizeSmall */
  184. width: 60px;
  185. height: 32px;
  186. min-width: 60px;
  187. min-height: 32px;
  188. /* lookFilled colorPrimary */
  189. color: rgb(255, 255, 255);
  190. background-color: var(--button-secondary-background);
  191. }
  192. #undiscord .sizeMedium {
  193. width: 96px;
  194. height: 38px;
  195. min-width: 96px;
  196. min-height: 38px;
  197. }
  198. /* lookFilled colorPrimary */
  199. #undiscord .accent {
  200. background-color: var(--brand-experiment);
  201. }
  202. #undiscord .danger {
  203. background-color: var(--button-danger-background);
  204. }
  205. #undiscord .positive {
  206. background-color: var(--button-positive-background);
  207. }
  208. #undiscord .info {
  209. font-size: 12px;
  210. line-height: 16px;
  211. padding: 8px 10px;
  212. color: var(--text-muted);
  213. }
  214. /* Scrollbar */
  215. #undiscord .scroll::-webkit-scrollbar {
  216. width: 8px;
  217. height: 8px;
  218. }
  219. #undiscord .scroll::-webkit-scrollbar-corner {
  220. background-color: transparent;
  221. }
  222. #undiscord .scroll::-webkit-scrollbar-thumb {
  223. background-clip: padding-box;
  224. border: 2px solid transparent;
  225. border-radius: 4px;
  226. background-color: var(--scrollbar-thin-thumb);
  227. min-height: 40px;
  228. }
  229. #undiscord .scroll::-webkit-scrollbar-track {
  230. border-color: var(--scrollbar-thin-track);
  231. background-color: var(--scrollbar-thin-track);
  232. border: 2px solid var(--scrollbar-thin-track);
  233. }
  234. /* fade scrollbar */
  235. #undiscord .scroll::-webkit-scrollbar-thumb,
  236. #undiscord .scroll::-webkit-scrollbar-track {
  237. visibility: hidden;
  238. }
  239. #undiscord .scroll:hover::-webkit-scrollbar-thumb,
  240. #undiscord .scroll:hover::-webkit-scrollbar-track {
  241. visibility: visible;
  242. }
  243. `);
  244.  
  245. var undiscordStyles = (`
  246. /**** Undiscord Button ****/
  247. #undicord-btn {
  248. position: relative;
  249. width: auto;
  250. height: 24px;
  251. margin: 0 8px;
  252. cursor: pointer;
  253. color: var(--interactive-normal);
  254. flex: 0 0 auto;
  255. }
  256. #undicord-btn progress {
  257. position: absolute;
  258. top: 7px;
  259. left: 5px;
  260. width: 14px;
  261. height: 14px;
  262. }
  263. /**** Undiscord Interface ****/
  264. #undiscord {
  265. position: fixed;
  266. z-index: 99;
  267. top: 44px;
  268. right: 10px;
  269. display: flex;
  270. flex-direction: column;
  271. width:800px;
  272. height: 80vh;
  273. min-width: 610px;
  274. max-width: 100vw;
  275. min-height: 448px;
  276. max-height: 100vh;
  277. color: var(--text-normal);
  278. border-radius: 4px;
  279. background-color: var(--background-secondary);
  280. box-shadow: var(--elevation-stroke), var(--elevation-high);
  281. will-change: top, left, width, height;
  282. }
  283. #undiscord .header .icon {
  284. cursor: pointer;
  285. }
  286. #undiscord .window-body {
  287. height: calc(100% - 48px);
  288. }
  289. #undiscord .sidebar {
  290. overflow: hidden scroll;
  291. overflow-y: auto;
  292. width: 270px;
  293. min-width: 250px;
  294. height: 100%;
  295. max-height: 100%;
  296. padding: 8px;
  297. background: var(--background-secondary);
  298. }
  299. #undiscord .main {
  300. display: flex;
  301. max-width: calc(100% - 250px);
  302. background-color: var(--background-primary);
  303. flex-grow: 1;
  304. }
  305. #undiscord #logArea {
  306. font-family: Consolas, Liberation Mono, Menlo, Courier, monospace;
  307. font-size: .75rem;
  308. overflow: auto;
  309. padding: 10px;
  310. user-select: text;
  311. flex-grow: 1;
  312. flex-grow: 1;
  313. }
  314. #undiscord .tbar {
  315. padding: 8px;
  316. background-color: var(--background-secondary-alt);
  317. }
  318. #undiscord .tbar button {
  319. margin-right: 4px;
  320. margin-bottom: 4px;
  321. }
  322. #undiscord .footer {
  323. cursor: se-resize;
  324. }
  325. /**** Elements ****/
  326. #undiscord summary {
  327. font-size: 16px;
  328. font-weight: 500;
  329. line-height: 20px;
  330. position: relative;
  331. overflow: hidden;
  332. margin-bottom: 2px;
  333. padding: 6px 10px;
  334. cursor: pointer;
  335. white-space: nowrap;
  336. text-overflow: ellipsis;
  337. color: var(--interactive-normal);
  338. border-radius: 4px;
  339. flex-shrink: 0;
  340. }
  341. #undiscord fieldset {
  342. padding-left: 8px;
  343. }
  344. /* help link */
  345. #undiscord legend a {
  346. float: right;
  347. text-transform: initial;
  348. }
  349. #undiscord progress {
  350. height: 8px;
  351. margin-top: 4px;
  352. flex-grow: 1;
  353. /* background-color: var(--background-primary);
  354. border-radius: 3px; */
  355. }
  356. /* #undiscord progress::-webkit-progress-value{
  357. background-color: var(--brand-experiment);
  358. } */
  359. /**** functional classes ****/
  360. #undiscord.redact .priv {
  361. display: none !important;
  362. }
  363. #undiscord:not(.redact) .mask {
  364. display: none !important;
  365. }
  366. #undiscord.redact [priv] {
  367. -webkit-text-security: disc !important;
  368. }
  369. #undiscord :disabled {
  370. display: none;
  371. }
  372. /**** layout misc ****/
  373. #undiscord,
  374. #undiscord * {
  375. box-sizing: border-box;
  376. }
  377. #undiscord .col {
  378. display: flex;
  379. flex-direction: column;
  380. }
  381. #undiscord .row {
  382. display: flex;
  383. flex-direction: row;
  384. align-items: center;
  385. }
  386. #undiscord .mb1 {
  387. margin-bottom: 8px;
  388. }
  389. `);
  390.  
  391. var buttonHtml = (`
  392. <div id="undicord-btn" tabindex="0" role="button" aria-label="Delete Messages" title="Delete Messages with Undiscord">
  393. <svg aria-hidden="false" width="24" height="24" viewBox="0 0 24 24">
  394. <path fill="currentColor" d="M15 3.999V2H9V3.999H3V5.999H21V3.999H15Z"></path>
  395. <path fill="currentColor" d="M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z"></path>
  396. </svg>
  397. <progress style="display:none;"></progress>
  398. </div>
  399. `);
  400.  
  401. var undiscordTemplate = (`
  402. <div id="undiscord" class="browser container redact" style="display:none;">
  403. <div class="header">
  404. <svg class="icon" aria-hidden="false" width="24" height="24" viewBox="0 0 24 24">
  405. <path fill="currentColor" d="M15 3.999V2H9V3.999H3V5.999H21V3.999H15Z"></path>
  406. <path fill="currentColor"
  407. d="M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z">
  408. </path>
  409. </svg>
  410. <h3>Undiscord</h3>
  411. <div class="vert-divider"></div>
  412. <span> Bulk delete messages</span>
  413. <div class="spacer"></div>
  414. <div id="hide" class="icon" aria-label="Close" role="button" tabindex="0">
  415. <svg aria-hidden="false" width="24" height="24" viewBox="0 0 24 24">
  416. <path fill="currentColor"
  417. d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z">
  418. </path>
  419. </svg>
  420. </div>
  421. </div>
  422. <div class="window-body" style="display: flex; flex-direction: row;">
  423. <div class="sidebar scroll">
  424. <details open>
  425. <summary>General</summary>
  426. <fieldset>
  427. <legend>
  428. Author ID
  429. <a href="{{WIKI}}/authorId" title="Help" target="_blank">help</a>
  430. </legend>
  431. <div class="multiInput">
  432. <div class="input-wrapper">
  433. <input class="input" id="authorId" type="text" priv>
  434. </div>
  435. <button id="getAuthor">me</button>
  436. </div>
  437. </fieldset>
  438. <hr>
  439. <fieldset>
  440. <legend>
  441. Server ID
  442. <a href="{{WIKI}}/guildId" title="Help" target="_blank">help</a>
  443. </legend>
  444. <div class="multiInput">
  445. <div class="input-wrapper">
  446. <input class="input" id="guildId" type="text" priv>
  447. </div>
  448. <button id="getGuild">current</button>
  449. </div>
  450. </fieldset>
  451. <fieldset>
  452. <legend>
  453. Channel ID
  454. <a href="{{WIKI}}/channelId" title="Help" target="_blank">help</a>
  455. </legend>
  456. <div class="multiInput mb1">
  457. <div class="input-wrapper">
  458. <input class="input" id="channelId" type="text" priv>
  459. </div>
  460. <button id="getChannel">current</button>
  461. </div>
  462. <div class="sectionDescription">
  463. <label class="row"><input id="includeNsfw" type="checkbox">This is a NSFW channel</label>
  464. </div>
  465. </fieldset>
  466. </details>
  467. <details>
  468. <summary>Import</summary>
  469. <fieldset>
  470. <legend>
  471. Import JSON
  472. <a href="{{WIKI}}/importJson" title="Help" target="_blank">help</a>
  473. </legend>
  474. <div class="sectionDescription">
  475. The import feature will be added back in the future.
  476. </div>
  477. <div class="">
  478. <button id="importJson" disabled>Import</button>
  479. </div>
  480. </fieldset>
  481. </details>
  482. <hr>
  483. <details>
  484. <summary>Filter</summary>
  485. <fieldset>
  486. <legend>
  487. Search
  488. <a href="{{WIKI}}/filters" title="Help" target="_blank">help</a>
  489. </legend>
  490. <div class="input-wrapper">
  491. <input id="search" type="text" placeholder="Containing text" priv>
  492. </div>
  493. <div class="sectionDescription">
  494. Only delete messages that contain the text
  495. </div>
  496. <div class="sectionDescription">
  497. <label><input id="hasLink" type="checkbox">has: link</label>
  498. </div>
  499. <div class="sectionDescription">
  500. <label><input id="hasFile" type="checkbox">has: file</label>
  501. </div>
  502. <div class="sectionDescription">
  503. <label><input id="includePinned" type="checkbox">Include pinned</label>
  504. </div>
  505. </fieldset>
  506. <hr>
  507. <fieldset>
  508. <legend>
  509. Pattern
  510. <a href="{{WIKI}}/pattern" title="Help" target="_blank">help</a>
  511. </legend>
  512. <div class="sectionDescription">
  513. Delete messages that match the regular expression
  514. </div>
  515. <div class="input-wrapper">
  516. <span class="info">/</span>
  517. <input id="pattern" type="text" placeholder="regular expression" priv>
  518. <span class="info">/</span>
  519. </div>
  520. </fieldset>
  521. </details>
  522. <details>
  523. <summary>Messages interval</summary>
  524. <fieldset>
  525. <legend>
  526. Interval of messages
  527. <a href="{{WIKI}}/messageId" title="Help" target="_blank">help</a>
  528. </legend>
  529. <div class="multiInput mb1">
  530. <div class="input-wrapper">
  531. <input id="minId" type="text" placeholder="After a message" priv>
  532. </div>
  533. <button id="pickMessageAfter">select</button>
  534. </div>
  535. <div class="multiInput">
  536. <div class="input-wrapper">
  537. <input id="maxId" type="text" placeholder="Before a message" priv>
  538. </div>
  539. <button id="pickMessageBefore">select</button>
  540. </div>
  541. <div class="sectionDescription">
  542. Specify an interval to delete messages.
  543. </div>
  544. </fieldset>
  545. </details>
  546. <details>
  547. <summary>Date</summary>
  548. <fieldset>
  549. <legend>
  550. After date
  551. <a href="{{WIKI}}/dateRange" title="Help" target="_blank">help</a>
  552. </legend>
  553. <div class="input-wrapper mb1">
  554. <input id="minDate" type="datetime-local" title="Messages posted AFTER this date">
  555. </div>
  556. <legend>
  557. Before date
  558. <a href="{{WIKI}}/dateRange" title="Help" target="_blank">help</a>
  559. </legend>
  560. <div class="input-wrapper">
  561. <input id="maxDate" type="datetime-local" title="Messages posted BEFORE this date">
  562. </div>
  563. <div class="sectionDescription">
  564. Delete messages that were posted between the two dates.
  565. </div>
  566. <div class="sectionDescription">
  567. * Filtering by date doesn't work if you use the "Messages interval".
  568. </div>
  569. </fieldset>
  570. </details>
  571. <hr>
  572. <details>
  573. <summary>Advanced settings</summary>
  574. <fieldset>
  575. <legend>
  576. Search delay
  577. <a href="{{WIKI}}/delay" title="Help" target="_blank">help</a>
  578. </legend>
  579. <div class="input-wrapper">
  580. <input id="searchDelay" type="number" value="100" step="100">
  581. </div>
  582. </fieldset>
  583. <fieldset>
  584. <legend>
  585. Delete delay
  586. <a href="{{WIKI}}/delay" title="Help" target="_blank">help</a>
  587. </legend>
  588. <div class="input-wrapper">
  589. <input id="deleteDelay" type="number" value="1000" step="100">
  590. </div>
  591. <br>
  592. <div class="sectionDescription">
  593. This will affect the speed in which the messages are deleted.
  594. Use the help link for more information.
  595. </div>
  596. </fieldset>
  597. </details>
  598. <hr>
  599. <div></div>
  600. <div class="info">
  601. Undiscord {{VERSION}}
  602. <br> victornpb
  603. </div>
  604. </div>
  605. <div class="main col">
  606. <div class="tbar col">
  607. <div class="row">
  608. <button id="start" class="sizeMedium accent">Start</button>
  609. <button id="stop" class="sizeMedium danger" disabled>Stop</button>
  610. <button id="clear" class="sizeMedium">Clear log</button>
  611. <label class="row" title="Hide sensitive information on your screen for taking screenshots">
  612. <input id="redact" type="checkbox" checked> Streamer mode
  613. </label>
  614. </div>
  615. <div class="row">
  616. <progress id="progressBar" value="-1"></progress>
  617. </div>
  618. </div>
  619. <pre id="logArea" class="logarea scroll">
  620. <center>
  621. <div>Star <a href="{{HOME}}" target="_blank">this project</a> on GitHub!</div>
  622. <div><a href="{{HOME}}/discussions" target="_blank">Issues or help</a></div>
  623. </center>
  624. </pre>
  625. <div class="tbar footer row">
  626. <label>
  627. <input id="autoScroll" type="checkbox" checked> Auto scroll
  628. </label>
  629. <span id="progressPercent"></span>
  630. </div>
  631. </div>
  632. </div>
  633. </div>
  634. `);
  635.  
  636. /**
  637. * Delete all messages in a Discord channel or DM
  638. * @param {string} authToken Your authorization token
  639. * @param {string} authorId Author of the messages you want to delete
  640. * @param {string} guildId Server were the messages are located
  641. * @param {string} channelId Channel were the messages are located
  642. * @param {string} minId Only delete messages after this, leave blank do delete all
  643. * @param {string} maxId Only delete messages before this, leave blank do delete all
  644. * @param {string} content Filter messages that contains this text content
  645. * @param {boolean} hasLink Filter messages that contains link
  646. * @param {boolean} hasFile Filter messages that contains file
  647. * @param {boolean} includeNsfw Search in NSFW channels
  648. * @param {function(string, Array)} extLogger Function for logging
  649. * @param {function} stopHndl stopHndl used for stopping
  650. * @author Victornpb <https://www.github.com/victornpb>
  651. * @see https://github.com/victornpb/undiscord
  652. */
  653. async function deleteMessages(authToken, authorId, guildId, channelId, minId, maxId, content, hasLink, hasFile, includeNsfw, includePinned, pattern, searchDelay, deleteDelay, extLogger, stopHndl, onProgress) {
  654. const start = new Date();
  655. let delCount = 0;
  656. let failCount = 0;
  657. let avgPing;
  658. let lastPing;
  659. let grandTotal;
  660. let throttledCount = 0;
  661. let throttledTotalTime = 0;
  662. let offset = 0;
  663. let iterations = -1;
  664.  
  665. const wait = async ms => new Promise(done => setTimeout(done, ms));
  666. const msToHMS = s => `${s / 3.6e6 | 0}h ${(s % 3.6e6) / 6e4 | 0}m ${(s % 6e4) / 1000 | 0}s`;
  667. const escapeHTML = html => html.replace(/[&<"']/g, m => ({ '&': '&amp;', '<': '&lt;', '"': '&quot;', '\'': '&#039;' })[m]);
  668. const redact = str => `<span class="priv">${escapeHTML(str)}</span><span class="mask">REDACTED</span>`;
  669. const queryString = params => params.filter(p => p[1] !== undefined).map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&');
  670. const ask = async msg => new Promise(resolve => setTimeout(() => resolve(window.confirm(msg)), 10));
  671. const printDelayStats = () => log.verb(`Delete delay: ${deleteDelay}ms, Search delay: ${searchDelay}ms`, `Last Ping: ${lastPing}ms, Average Ping: ${avgPing | 0}ms`);
  672. const toSnowflake = (date) => /:/.test(date) ? ((new Date(date).getTime() - 1420070400000) * Math.pow(2, 22)) : date;
  673.  
  674. const log = {
  675. debug() { return extLogger ? extLogger('debug', arguments) : console.debug.apply(console, arguments); },
  676. info() { return extLogger ? extLogger('info', arguments) : console.info.apply(console, arguments); },
  677. verb() { return extLogger ? extLogger('verb', arguments) : console.log.apply(console, arguments); },
  678. warn() { return extLogger ? extLogger('warn', arguments) : console.warn.apply(console, arguments); },
  679. error() { return extLogger ? extLogger('error', arguments) : console.error.apply(console, arguments); },
  680. success() { return extLogger ? extLogger('success', arguments) : console.info.apply(console, arguments); },
  681. };
  682.  
  683. async function recurse() {
  684. let API_SEARCH_URL;
  685. if (guildId === '@me') {
  686. API_SEARCH_URL = `https://discord.com/api/v9/channels/${channelId}/messages/`; // DMs
  687. }
  688. else {
  689. API_SEARCH_URL = `https://discord.com/api/v9/guilds/${guildId}/messages/`; // Server
  690. }
  691.  
  692. const headers = {
  693. 'Authorization': authToken
  694. };
  695.  
  696. if (onProgress) onProgress(-1, 1);
  697.  
  698. let resp;
  699. try {
  700. const s = Date.now();
  701. resp = await fetch(API_SEARCH_URL + 'search?' + queryString([
  702. ['author_id', authorId || undefined],
  703. ['channel_id', (guildId !== '@me' ? channelId : undefined) || undefined],
  704. ['min_id', minId ? toSnowflake(minId) : undefined],
  705. ['max_id', maxId ? toSnowflake(maxId) : undefined],
  706. ['sort_by', 'timestamp'],
  707. ['sort_order', 'desc'],
  708. ['offset', offset],
  709. ['has', hasLink ? 'link' : undefined],
  710. ['has', hasFile ? 'file' : undefined],
  711. ['content', content || undefined],
  712. ['include_nsfw', includeNsfw ? true : undefined],
  713. ]), { headers });
  714. lastPing = (Date.now() - s);
  715. avgPing = avgPing > 0 ? (avgPing * 0.9) + (lastPing * 0.1) : lastPing;
  716. } catch (err) {
  717. return log.error('Search request threw an error:', err);
  718. }
  719.  
  720. // not indexed yet
  721. if (resp.status === 202) {
  722. const w = (await resp.json()).retry_after * 1000;
  723. throttledCount++;
  724. throttledTotalTime += w;
  725. log.warn(`This channel wasn't indexed, waiting ${w}ms for discord to index it...`);
  726. await wait(w);
  727. return await recurse();
  728. }
  729.  
  730. if (!resp.ok) {
  731. // searching messages too fast
  732. if (resp.status === 429) {
  733. const w = (await resp.json()).retry_after * 1000;
  734. throttledCount++;
  735. throttledTotalTime += w;
  736. searchDelay += w; // increase delay
  737. log.warn(`Being rate limited by the API for ${w}ms! Increasing search delay...`);
  738. printDelayStats();
  739. log.verb(`Cooling down for ${w * 2}ms before retrying...`);
  740.  
  741. await wait(w * 2);
  742. return await recurse();
  743. } else {
  744. return log.error(`Error searching messages, API responded with status ${resp.status}!\n`, await resp.json());
  745. }
  746. }
  747.  
  748. let regex;
  749.  
  750. try {
  751. regex = new RegExp(pattern);
  752. } catch (e) {
  753. log.warn('Ignoring RegExp because pattern is malformed');
  754. }
  755.  
  756. const data = await resp.json();
  757. const total = data.total_results;
  758. if (!grandTotal) grandTotal = total;
  759. const discoveredMessages = data.messages.map(convo => convo.find(message => message.hit === true));
  760. const messagesToDelete = discoveredMessages.filter(msg => {
  761. return (msg.type === 0 || (msg.type >= 6 && msg.type <= 21) || (msg.pinned && includePinned)) && (!regex || msg.content.match(regex));
  762. });
  763. const skippedMessages = discoveredMessages.filter(msg => !messagesToDelete.find(m => m.id === msg.id));
  764.  
  765. const end = () => {
  766. log.success(`Ended at ${new Date().toLocaleString()}! Total time: ${msToHMS(Date.now() - start.getTime())}`);
  767. printDelayStats();
  768. log.verb(`Rate Limited: ${throttledCount} times. Total time throttled: ${msToHMS(throttledTotalTime)}.`);
  769. log.debug(`Deleted ${delCount} messages, ${failCount} failed.\n`);
  770. };
  771.  
  772. const etr = msToHMS((searchDelay * Math.round(total / 25)) + ((deleteDelay + avgPing) * total));
  773. log.info(`Total messages found: ${data.total_results}`, `(Messages in current page: ${data.messages.length}, To be deleted: ${messagesToDelete.length}, System: ${skippedMessages.length})`, `offset: ${offset}`);
  774. printDelayStats();
  775. log.verb(`Estimated time remaining: ${etr}`);
  776.  
  777.  
  778. if (messagesToDelete.length > 0 || skippedMessages.length > 0) {
  779.  
  780. if (++iterations < 1) {
  781. log.verb('Waiting for your confirmation...');
  782. if (!await ask(`Do you want to delete ~${total} messages?\nEstimated time: ${etr}\n\n---- Preview ----\n` +
  783. messagesToDelete.map(m => `${m.author.username}#${m.author.discriminator}: ${m.attachments.length ? '[ATTACHMENTS]' : m.content}`).join('\n')))
  784. return end(log.error('Aborted by you!'));
  785. log.verb('OK');
  786. }
  787.  
  788. for (let i = 0; i < messagesToDelete.length; i++) {
  789. const message = messagesToDelete[i];
  790. if (stopHndl && stopHndl()) return end(log.error('Stopped by you!'));
  791.  
  792. log.debug(`${((delCount + 1) / grandTotal * 100).toFixed(2)}% (${delCount + 1}/${grandTotal})`,
  793. `Deleting ID:${redact(message.id)} <b>${redact(message.author.username + '#' + message.author.discriminator)} <small>(${redact(new Date(message.timestamp).toLocaleString())})</small>:</b> <i>${redact(message.content).replace(/\n/g, '↵')}</i>`,
  794. message.attachments.length ? redact(JSON.stringify(message.attachments)) : '');
  795. if (onProgress) onProgress(delCount + 1, grandTotal);
  796.  
  797. let resp;
  798. try {
  799. const s = Date.now();
  800. const API_DELETE_URL = `https://discord.com/api/v9/channels/${message.channel_id}/messages/${message.id}`;
  801. resp = await fetch(API_DELETE_URL, {
  802. headers,
  803. method: 'DELETE'
  804. });
  805. lastPing = (Date.now() - s);
  806. avgPing = (avgPing * 0.9) + (lastPing * 0.1);
  807. delCount++;
  808. } catch (err) {
  809. log.error('Delete request throwed an error:', err);
  810. log.verb('Related object:', redact(JSON.stringify(message)));
  811. failCount++;
  812. }
  813.  
  814. if (!resp.ok) {
  815. // deleting messages too fast
  816. if (resp.status === 429) {
  817. const w = (await resp.json()).retry_after * 1000;
  818. throttledCount++;
  819. throttledTotalTime += w;
  820. deleteDelay = w; // increase delay
  821. log.warn(`Being rate limited by the API for ${w}ms! Adjusted delete delay to ${deleteDelay}ms.`);
  822. printDelayStats();
  823. log.verb(`Cooling down for ${w * 2}ms before retrying...`);
  824. await wait(w * 2);
  825. i--; // retry
  826. } else {
  827. log.error(`Error deleting message, API responded with status ${resp.status}!`, await resp.json());
  828. log.verb('Related object:', redact(JSON.stringify(message)));
  829. failCount++;
  830. }
  831. }
  832.  
  833. await wait(deleteDelay);
  834. }
  835.  
  836. if (skippedMessages.length > 0) {
  837. grandTotal -= skippedMessages.length;
  838. offset += skippedMessages.length;
  839. log.verb(`Found ${skippedMessages.length} system messages! Decreasing grandTotal to ${grandTotal} and increasing offset to ${offset}.`);
  840. }
  841.  
  842. log.verb(`Searching next messages in ${searchDelay}ms...`, (offset ? `(offset: ${offset})` : ''));
  843. await wait(searchDelay);
  844.  
  845. if (stopHndl && stopHndl()) return end(log.error('Stopped by you!'));
  846.  
  847. return await recurse();
  848. } else {
  849. if (total - offset > 0) log.warn('Ended because API returned an empty page.');
  850. return end();
  851. }
  852. }
  853.  
  854. log.success(`\nStarted at ${start.toLocaleString()}`);
  855. log.debug(`authorId="${redact(authorId)}" guildId="${redact(guildId)}" channelId="${redact(channelId)}" minId="${redact(minId)}" maxId="${redact(maxId)}" hasLink=${!!hasLink} hasFile=${!!hasFile}`);
  856. if (onProgress) onProgress(null, 1);
  857. return await recurse();
  858. }
  859.  
  860. class Drag {
  861. /**
  862. * Make an element draggable/resizable
  863. * @param {Element} targetElm The element that will be dragged/resized
  864. * @param {Element} handleElm The element that will listen to events (handdle/grabber)
  865. * @param {object} [options] Options
  866. * @param {string} [options.mode="move"] Define the type of operation (move/resize)
  867. * @param {number} [options.minWidth=200] Minimum width allowed to resize
  868. * @param {number} [options.maxWidth=Infinity] Maximum width allowed to resize
  869. * @param {number} [options.minHeight=100] Maximum height allowed to resize
  870. * @param {number} [options.maxHeight=Infinity] Maximum height allowed to resize
  871. * @param {string} [options.draggingClass="drag"] Class added to targetElm while being dragged
  872. * @param {boolean} [options.useMouseEvents=true] Use mouse events
  873. * @param {boolean} [options.useTouchEvents=true] Use touch events
  874. *
  875. * @author Victor N. wwww.vitim.us
  876. */
  877. constructor(targetElm, handleElm, options) {
  878. this.options = Object.assign({
  879. mode: 'move',
  880.  
  881. minWidth: 200,
  882. maxWidth: Infinity,
  883. minHeight: 100,
  884. maxHeight: Infinity,
  885. xAxis: true,
  886. yAxis: true,
  887.  
  888. draggingClass: 'drag',
  889.  
  890. useMouseEvents: true,
  891. useTouchEvents: true,
  892. }, options);
  893.  
  894. // Public properties
  895. this.minWidth = this.options.minWidth;
  896. this.maxWidth = this.options.maxWidth;
  897. this.minHeight = this.options.minHeight;
  898. this.maxHeight = this.options.maxHeight;
  899. this.xAxis = this.options.xAxis;
  900. this.yAxis = this.options.yAxis;
  901. this.draggingClass = this.options.draggingClass;
  902.  
  903. /** @private */
  904. this._targetElm = targetElm;
  905. /** @private */
  906. this._handleElm = handleElm;
  907.  
  908. const moveOp = (x, y) => {
  909. let l = x - offLeft;
  910. if (x - offLeft < 0) l = 0; //offscreen <-
  911. else if (x - offRight > vw) l = vw - this._targetElm.clientWidth; //offscreen ->
  912. let t = y - offTop;
  913. if (y - offTop < 0) t = 0; //offscreen /\
  914. else if (y - offBottom > vh) t = vh - this._targetElm.clientHeight; //offscreen \/
  915.  
  916. if(this.xAxis) this._targetElm.style.left = `${l}px`;
  917. if(this.yAxis) this._targetElm.style.top = `${t}px`;
  918. // NOTE: profilling on chrome translate wasn't faster than top/left as expected. And it also permanently creates a new layer, increasing vram usage.
  919. // this._targetElm.style.transform = `translate(${l}px, ${t}px)`;
  920. };
  921.  
  922. const resizeOp = (x, y) => {
  923. let w = x - this._targetElm.offsetLeft - offRight;
  924. if (x - offRight > vw) w = Math.min(vw - this._targetElm.offsetLeft, this.maxWidth); //offscreen ->
  925. else if (x - offRight - this._targetElm.offsetLeft > this.maxWidth) w = this.maxWidth; //max width
  926. else if (x - offRight - this._targetElm.offsetLeft < this.minWidth) w = this.minWidth; //min width
  927. let h = y - this._targetElm.offsetTop - offBottom;
  928. if (y - offBottom > vh) h = Math.min(vh - this._targetElm.offsetTop, this.maxHeight); //offscreen \/
  929. else if (y - offBottom - this._targetElm.offsetTop > this.maxHeight) h = this.maxHeight; //max height
  930. else if (y - offBottom - this._targetElm.offsetTop < this.minHeight) h = this.minHeight; //min height
  931.  
  932. if(this.xAxis) this._targetElm.style.width = `${w}px`;
  933. if(this.yAxis) this._targetElm.style.height = `${h}px`;
  934. };
  935.  
  936. // define which operation is performed on drag
  937. const operation = this.options.mode === 'move' ? moveOp : resizeOp;
  938.  
  939. // offset from the initial click to the target boundaries
  940. let offTop, offLeft, offBottom, offRight;
  941.  
  942. let vw = window.innerWidth;
  943. let vh = window.innerHeight;
  944.  
  945.  
  946. function dragStartHandler(e) {
  947. const touch = e.type === 'touchstart';
  948.  
  949. if ((e.buttons === 1 || e.which === 1) || touch) {
  950. e.preventDefault();
  951.  
  952. const x = touch ? e.touches[0].clientX : e.clientX;
  953. const y = touch ? e.touches[0].clientY : e.clientY;
  954.  
  955. const targetOffset = this._targetElm.getBoundingClientRect();
  956.  
  957. //offset from the click to the top-left corner of the target (drag)
  958. offTop = y - targetOffset.y;
  959. offLeft = x - targetOffset.x;
  960. //offset from the click to the bottom-right corner of the target (resize)
  961. offBottom = y - (targetOffset.y + targetOffset.height);
  962. offRight = x - (targetOffset.x + targetOffset.width);
  963.  
  964. vw = window.innerWidth;
  965. vh = window.innerHeight;
  966.  
  967. if (this.options.useMouseEvents) {
  968. document.addEventListener('mousemove', this._dragMoveHandler);
  969. document.addEventListener('mouseup', this._dragEndHandler);
  970. }
  971. if (this.options.useTouchEvents) {
  972. document.addEventListener('touchmove', this._dragMoveHandler, {
  973. passive: false,
  974. });
  975. document.addEventListener('touchend', this._dragEndHandler);
  976. }
  977.  
  978. this._targetElm.classList.add(this.draggingClass);
  979. }
  980. }
  981.  
  982. function dragMoveHandler(e) {
  983. e.preventDefault();
  984. let x, y;
  985.  
  986. const touch = e.type === 'touchmove';
  987. if (touch) {
  988. const t = e.touches[0];
  989. x = t.clientX;
  990. y = t.clientY;
  991. } else { //mouse
  992.  
  993. // If the button is not down, dispatch a "fake" mouse up event, to stop listening to mousemove
  994. // This happens when the mouseup is not captured (outside the browser)
  995. if ((e.buttons || e.which) !== 1) {
  996. this._dragEndHandler();
  997. return;
  998. }
  999.  
  1000. x = e.clientX;
  1001. y = e.clientY;
  1002. }
  1003.  
  1004. operation(x, y);
  1005. }
  1006.  
  1007. function dragEndHandler(e) {
  1008. if (this.options.useMouseEvents) {
  1009. document.removeEventListener('mousemove', this._dragMoveHandler);
  1010. document.removeEventListener('mouseup', this._dragEndHandler);
  1011. }
  1012. if (this.options.useTouchEvents) {
  1013. document.removeEventListener('touchmove', this._dragMoveHandler);
  1014. document.removeEventListener('touchend', this._dragEndHandler);
  1015. }
  1016. this._targetElm.classList.remove(this.draggingClass);
  1017. }
  1018.  
  1019. // We need to bind the handlers to this instance and expose them to methods enable and destroy
  1020. /** @private */
  1021. this._dragStartHandler = dragStartHandler.bind(this);
  1022. /** @private */
  1023. this._dragMoveHandler = dragMoveHandler.bind(this);
  1024. /** @private */
  1025. this._dragEndHandler = dragEndHandler.bind(this);
  1026.  
  1027. this.enable();
  1028. }
  1029.  
  1030. /**
  1031. * Turn on the drag and drop of the instancea
  1032. * @memberOf Drag
  1033. */
  1034. enable() {
  1035. // this.destroy(); // prevent events from getting binded twice
  1036. if (this.options.useMouseEvents) this._handleElm.addEventListener('mousedown', this._dragStartHandler);
  1037. if (this.options.useTouchEvents) this._handleElm.addEventListener('touchstart', this._dragStartHandler, { passive: false });
  1038. }
  1039. /**
  1040. * Teardown all events bound to the document and elements
  1041. * You can resurrect this instance by calling enable()
  1042. * @memberOf Drag
  1043. */
  1044. destroy() {
  1045. this._targetElm.classList.remove(this.draggingClass);
  1046.  
  1047. if (this.options.useMouseEvents) {
  1048. this._handleElm.removeEventListener('mousedown', this._dragStartHandler);
  1049. document.removeEventListener('mousemove', this._dragMoveHandler);
  1050. document.removeEventListener('mouseup', this._dragEndHandler);
  1051. }
  1052. if (this.options.useTouchEvents) {
  1053. this._handleElm.removeEventListener('touchstart', this._dragStartHandler);
  1054. document.removeEventListener('touchmove', this._dragMoveHandler);
  1055. document.removeEventListener('touchend', this._dragEndHandler);
  1056. }
  1057. }
  1058. }
  1059.  
  1060. function createElm(html) {
  1061. const temp = document.createElement('div');
  1062. temp.innerHTML = html;
  1063. return temp.removeChild(temp.firstElementChild);
  1064. }
  1065.  
  1066. function insertCss(css) {
  1067. const style = document.createElement('style');
  1068. style.appendChild(document.createTextNode(css));
  1069. document.head.appendChild(style);
  1070. return style;
  1071. }
  1072.  
  1073. const messagePickerCss = `
  1074. body.undiscord-pick-message [data-list-id="chat-messages"] {
  1075. background-color: var(--background-secondary-alt);
  1076. box-shadow: inset 0 0 0px 2px var(--button-outline-brand-border);
  1077. }
  1078. body.undiscord-pick-message [id^="message-content-"]:hover {
  1079. cursor: pointer;
  1080. cursor: cell;
  1081. background: var(--background-message-automod-hover);
  1082. }
  1083. body.undiscord-pick-message [id^="message-content-"]:hover::after {
  1084. position: absolute;
  1085. top: calc(50% - 11px);
  1086. left: 4px;
  1087. z-index: 1;
  1088. width: 65px;
  1089. height: 22px;
  1090. line-height: 22px;
  1091. font-family: var(--font-display);
  1092. background-color: var(--button-secondary-background);
  1093. color: var(--header-secondary);
  1094. font-size: 12px;
  1095. font-weight: 500;
  1096. text-transform: uppercase;
  1097. text-align: center;
  1098. border-radius: 3px;
  1099. content: 'This 👉';
  1100. }
  1101. body.undiscord-pick-message.before [id^="message-content-"]:hover::after {
  1102. content: 'Before 👆';
  1103. }
  1104. body.undiscord-pick-message.after [id^="message-content-"]:hover::after {
  1105. content: 'After 👇';
  1106. }
  1107. `;
  1108.  
  1109. const messagePicker = {
  1110. init() {
  1111. insertCss(messagePickerCss);
  1112. },
  1113. grab(auxiliary) {
  1114. return new Promise((resolve, reject) => {
  1115. document.body.classList.add('undiscord-pick-message');
  1116. if (auxiliary) document.body.classList.add(auxiliary);
  1117. function clickHandler(e) {
  1118. const message = e.target.closest('[id^="message-content-"]');
  1119. if (message) {
  1120. e.preventDefault();
  1121. e.stopPropagation();
  1122. e.stopImmediatePropagation();
  1123. if (auxiliary) document.body.classList.remove(auxiliary);
  1124. document.body.classList.remove('undiscord-pick-message');
  1125. document.removeEventListener('click', clickHandler);
  1126. try {
  1127. resolve(message.id.match(/message-content-(\d+)/)[1]);
  1128. } catch (e) {
  1129. resolve(null);
  1130. }
  1131. }
  1132. }
  1133. document.addEventListener('click', clickHandler);
  1134. });
  1135. }
  1136. };
  1137.  
  1138. var messagePicker$1 = messagePicker;
  1139. window.messagePicker = messagePicker;
  1140.  
  1141. function getToken() {
  1142. window.dispatchEvent(new Event('beforeunload'));
  1143. const LS = document.body.appendChild(document.createElement('iframe')).contentWindow.localStorage;
  1144. return JSON.parse(LS.token);
  1145. }
  1146.  
  1147. function getAuthorId() {
  1148. const LS = document.body.appendChild(document.createElement('iframe')).contentWindow.localStorage;
  1149. return JSON.parse(LS.user_id_cache);
  1150. }
  1151.  
  1152. function getGuildId() {
  1153. const m = location.href.match(/channels\/([\w@]+)\/(\d+)/);
  1154. if (m) return m[1];
  1155. else alert('Could not the Guild ID!\nPlease make sure you are on a Server or DM.');
  1156. }
  1157.  
  1158. function getChannelId() {
  1159. const m = location.href.match(/channels\/([\w@]+)\/(\d+)/);
  1160. if (m) return m[2];
  1161. else alert('Could not the Channel ID!\nPlease make sure you are on a Channel or DM.');
  1162. }
  1163.  
  1164. // ------------------------- User interface ------------------------------ //
  1165.  
  1166. const HOME = 'https://github.com/victornpb/undiscord';
  1167. const WIKI = 'https://github.com/victornpb/undiscord/wiki';
  1168.  
  1169. const $ = s => undiscordWindow.querySelector(s);
  1170.  
  1171. let undiscordWindow;
  1172. let undiscordBtn;
  1173.  
  1174. function initUI() {
  1175.  
  1176. insertCss(discordStyles);
  1177. insertCss(undiscordStyles);
  1178.  
  1179. function replaceInterpolations(str, obj, removeMissing = false) {
  1180. return str.replace(/\{\{([\w_]+)\}\}/g, (m, key) => obj[key] || (removeMissing ? '' : m));
  1181. }
  1182.  
  1183. const templateVariables = {
  1184. VERSION: version,
  1185. HOME,
  1186. WIKI,
  1187. };
  1188.  
  1189. // create undiscord window
  1190. const undiscordUI = replaceInterpolations(undiscordTemplate, templateVariables);
  1191. undiscordWindow = createElm(undiscordUI);
  1192. document.body.appendChild(undiscordWindow);
  1193.  
  1194. new Drag(undiscordWindow, $('.header'), { mode: 'move' });
  1195. new Drag(undiscordWindow, $('.footer'), { mode: 'resize' });
  1196.  
  1197. // create undiscord button
  1198. undiscordBtn = createElm(buttonHtml);
  1199. undiscordBtn.onclick = toggleWindow;
  1200. function mountBtn() {
  1201. const toolbar = document.querySelector('#app-mount [class^=toolbar]');
  1202. if (toolbar) toolbar.appendChild(undiscordBtn);
  1203. }
  1204. mountBtn();
  1205.  
  1206. // watch for changes and re-mount button if necessary
  1207. const discordElm = document.querySelector('#app-mount');
  1208. let observerThrottle = null;
  1209. const observer = new MutationObserver((_mutationsList, _observer) => {
  1210. if (observerThrottle) return;
  1211. observerThrottle = setTimeout(() => {
  1212. observerThrottle = null;
  1213. if (!discordElm.contains(undiscordBtn)) mountBtn(); // re-mount the button to the toolbar
  1214. }, 3000);
  1215. });
  1216. observer.observe(discordElm, { attributes: false, childList: true, subtree: true });
  1217.  
  1218. function toggleWindow() {
  1219. if (undiscordWindow.style.display !== 'none') {
  1220. undiscordWindow.style.display = 'none';
  1221. undiscordBtn.style.color = 'var(--interactive-normal)';
  1222. }
  1223. else {
  1224. undiscordWindow.style.display = '';
  1225. undiscordBtn.style.color = 'var(--interactive-active)';
  1226. }
  1227. }
  1228.  
  1229. messagePicker$1.init();
  1230.  
  1231. // register event listeners
  1232. $('#hide').onclick = toggleWindow;
  1233. $('button#start').onclick = start;
  1234. $('button#stop').onclick = stop;
  1235. $('button#clear').onclick = () => $('#logArea').innerHTML = '';
  1236. $('button#getAuthor').onclick = () => $('input#authorId').value = getAuthorId();
  1237. $('button#getGuild').onclick = () => {
  1238. const guildId = $('input#guildId').value = getGuildId();
  1239. if (guildId === '@me') $('input#channelId').value = getChannelId();
  1240. };
  1241. $('button#getChannel').onclick = () => {
  1242. $('input#channelId').value = getChannelId();
  1243. $('input#guildId').value = getGuildId();
  1244. };
  1245. $('#redact').onchange = () => {
  1246. const b = undiscordWindow.classList.toggle('redact');
  1247. if (b) alert('This mode will attempt to hide personal information, so you can screen share / take screenshots.\nAlways double check you are not sharing sensitive information!');
  1248. };
  1249.  
  1250. $('#pickMessageAfter').onclick = async () => {
  1251. // alert('Select a message on the chat.\nThe message below it will be deleted.');
  1252. const id = await messagePicker$1.grab('after');
  1253. if (id) $('input#minId').value = id;
  1254. };
  1255. $('#pickMessageBefore').onclick = async () => {
  1256. // alert('Select a message on the chat.\nThe message above it will be deleted.');
  1257. const id = await messagePicker$1.grab('before');
  1258. if (id) $('input#maxId').value = id;
  1259. };
  1260.  
  1261. // const fileSelection = $('input#importJson');
  1262. // fileSelection.onchange = () => {
  1263. // const files = fileSelection.files;
  1264. // const channelIdField = $('input#channelId');
  1265. // if (files.length > 0) {
  1266. // const file = files[0];
  1267. // file.text().then(text => {
  1268. // let json = JSON.parse(text);
  1269. // let channels = Object.keys(json);
  1270. // channelIdField.value = channels.join(',');
  1271. // });
  1272. // }
  1273. // };
  1274.  
  1275. }
  1276.  
  1277. let _stopFlag = false;
  1278. const stopHndl = () => _stopFlag;
  1279.  
  1280. async function start() {
  1281. console.log('start');
  1282. _stopFlag = false;
  1283.  
  1284. // general
  1285. const authToken = getToken();
  1286. const authorId = $('input#authorId').value.trim();
  1287. const guildId = $('input#guildId').value.trim();
  1288. const channelIds = $('input#channelId').value.trim().split(/\s*,\s*/);
  1289. const includeNsfw = $('input#includeNsfw').checked;
  1290. // filter
  1291. const content = $('input#search').value.trim();
  1292. const hasLink = $('input#hasLink').checked;
  1293. const hasFile = $('input#hasFile').checked;
  1294. const includePinned = $('input#includePinned').checked;
  1295. const pattern = $('input#pattern').value;
  1296. // message interval
  1297. const minId = $('input#minId').value.trim();
  1298. const maxId = $('input#maxId').value.trim();
  1299. // date range
  1300. const minDate = $('input#minDate').value.trim();
  1301. const maxDate = $('input#maxDate').value.trim();
  1302. //advanced
  1303. const searchDelay = parseInt($('input#searchDelay').value.trim());
  1304. const deleteDelay = parseInt($('input#deleteDelay').value.trim());
  1305.  
  1306. // progress handler
  1307. const progress = $('#progressBar');
  1308. const progress2 = undiscordBtn.querySelector('progress');
  1309. const percent = $('#progressPercent');
  1310. const onProg = (value, max) => {
  1311. if (value && max && value > max) max = value;
  1312. progress.setAttribute('max', max);
  1313. progress2.setAttribute('max', max);
  1314. progress.value = value;
  1315. progress2.value = value;
  1316. progress.style.display = max ? '' : 'none';
  1317. progress2.style.display = max ? '' : 'none';
  1318. percent.style.display = value && max ? '' : 'none';
  1319. percent.innerHTML = value >= 0 && max ? Math.round(value / max * 100) + '%' : '';
  1320. // indeterminate progress bar
  1321. if (value === -1) {
  1322. progress.removeAttribute('value');
  1323. progress2.removeAttribute('value');
  1324. percent.innerHTML = '...';
  1325. }
  1326. };
  1327.  
  1328. let logArea = $('#logArea');
  1329. let autoScroll = $('#autoScroll');
  1330. const logger = (type = '', args) => {
  1331. const style = { '': '', info: 'color:#00b0f4;', verb: 'color:#72767d;', warn: 'color:#faa61a;', error: 'color:#f04747;', success: 'color:#43b581;' }[type];
  1332. logArea.insertAdjacentHTML('beforeend', `<div style="${style}">${Array.from(args).map(o => typeof o === 'object' ? JSON.stringify(o, o instanceof Error && Object.getOwnPropertyNames(o)) : o).join('\t')}</div>`);
  1333. if (autoScroll.checked) logArea.querySelector('div:last-child').scrollIntoView(false);
  1334. };
  1335.  
  1336. logArea.innerHTML = '';
  1337.  
  1338. // validate input
  1339. if (!authToken) return logger('error', ['Could not detect the authorization token!']) || logger('info', ['Please make sure Undiscord is up to date']);
  1340. else if (!guildId) return logger('error', ['You must provide a Server ID!']);
  1341.  
  1342. for (let i = 0; i < channelIds.length; i++) {
  1343. $('#start').disabled = true;
  1344. $('#stop').disabled = false;
  1345. await deleteMessages(authToken, authorId, guildId, channelIds[i], minId || minDate, maxId || maxDate, content, hasLink, hasFile, includeNsfw, includePinned, pattern, searchDelay, deleteDelay, logger, stopHndl, onProg);
  1346. stop(); // clear the running state
  1347. }
  1348.  
  1349. }
  1350.  
  1351. function stop() {
  1352. _stopFlag = true;
  1353. $('#start').disabled = false;
  1354. $('#stop').disabled = true;
  1355.  
  1356. $('#progressBar').style.display = 'none';
  1357. $('#progressPercent').style.display = 'none';
  1358. undiscordBtn.querySelector('progress').style.display = 'none';
  1359. }
  1360.  
  1361. initUI();
  1362.  
  1363.  
  1364. // ---- END Undiscord ----
  1365.  
  1366. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement