DRDL

Discord Recent Deletions Logger - view recent edits and deletions in console.

  1. // ==UserScript==
  2. // @name DRDL
  3. // @description Discord Recent Deletions Logger - view recent edits and deletions in console.
  4. // @author 0vC4
  5. // @version 1.0
  6. // @namespace https://greasyfork.org/users/670183-exnonull
  7. // @match *://discordapp.com/*
  8. // @match *://discord.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=discord.com
  10. // @run-at document-start
  11. // @grant none
  12. // @license MIT
  13. // @require https://greasyfork.org/scripts/438620-workertimer/code/WorkerTimer.js
  14. // @require https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.10/pako.js
  15. // ==/UserScript==
  16.  
  17.  
  18.  
  19.  
  20.  
  21. const log = console.log;
  22. const logError = console.error;
  23. (() => {
  24. window.console.log = ()=>0;
  25. window.console.info = ()=>0;
  26. window.console.warn = ()=>0;
  27. window.console.warning = ()=>0;
  28. window.setTimeout = window.WorkerTimer.setTimeout;
  29. window.setInterval = window.WorkerTimer.setInterval;
  30. window.clearTimeout = window.WorkerTimer.clearTimeout;
  31. window.clearInterval = window.WorkerTimer.clearInterval;
  32. })();
  33.  
  34.  
  35.  
  36.  
  37.  
  38. (() => {
  39. class Client {
  40. static ws = null;
  41.  
  42. static guilds = [];
  43. static channels = [];
  44. static users = [];
  45. static undefinedChannel = {
  46. path: 'undefined',
  47. name: 'undefined',
  48. guild: null,
  49. parent: null,
  50. messages: [],
  51. addMessage(msg) {
  52. const minutes = 60;
  53. Client.undefinedChannel.messages = Client.undefinedChannel.messages.filter((m, i) => {
  54. if ((+new Date() - +new Date(m.timestamp))/1e3 < 5*minutes) return true;
  55. return i < 200;
  56. });
  57. Client.undefinedChannel.messages.push(msg);
  58. }
  59. };
  60.  
  61. static guild(id) {
  62. return Client.guilds.find(g=>g.id == id);
  63. }
  64. static channel(id) {
  65. return Client.channels.find(c=>c.id == id) ?? Client.undefinedChannel;
  66. }
  67. static user(id) {
  68. return Client.users.find(u=>u.id == id);
  69. }
  70.  
  71. static newChannel(channel) {
  72. if (!channel) return;
  73. if (channel.recipient_ids?.length == 1)
  74. channel.name ??= '@' + Client.user(u => u.id == channel.recipient_ids[0])?.username;
  75.  
  76. Object.defineProperty(channel, 'guild', {
  77. get(){
  78. return Client.guild(channel.guild_id);
  79. },
  80. });
  81. Object.defineProperty(channel, 'parent', {
  82. get(){
  83. return Client.channel(channel.parent_id);
  84. },
  85. });
  86. Object.defineProperty(channel, 'path', {
  87. get(){
  88. const ch = Client.channel(channel.parent_id);
  89. let name = this.name;
  90.  
  91. if (ch?.parent_id) return ch.path + '/' + name;
  92. return channel.guild?.properties?.name + '/' + name;
  93. },
  94. });
  95.  
  96. channel.messages = [];
  97. channel.addMessage = (msg) => {
  98. const minutes = 60;
  99. channel.messages = channel.messages.filter((m, i) => {
  100. if ((+new Date() - +new Date(m.timestamp))/1e3 < 5*minutes) return true;
  101. return i < 200;
  102. });
  103. channel.messages.push(msg);
  104. }
  105. Client.channels.push(channel);
  106. }
  107.  
  108. static newMessage(message) {
  109. if (!message) return {};
  110.  
  111. message.history = [];
  112. message.getHistory = () => [message, ...message.history];
  113. message.getLastEdit = () => [(message.history[message.history.length-2] ?? message), message.history[message.history.length-1] ?? message];
  114. message.updated = false;
  115. message.deleted = false;
  116. if (message.edited_timestamp) {
  117. message.updated = true;
  118. }
  119.  
  120. message.update = function(msg) {
  121. if (!msg.edited_timestamp) return;
  122. message.history.push(msg);
  123. message.updated = true;
  124. Client.updates.push(message);
  125.  
  126. log('<EDIT>');
  127. log(
  128. message.channel.path,
  129. '@'+message.author.username+':',
  130. message.getLastEdit()[0].content ?? '<???>', ' |-> ', msg.content,
  131. );
  132. if (message.embeds.map(a=>a.url).join(' ') != '') log('embeds:', message.embeds.map(a=>a.url).join(' '));
  133. if (message.attachments.map(a=>a.url).join(' ') != '') log('attachments:', message.attachments.map(a=>a.url).join(' '));
  134. };
  135.  
  136. message.delete = function(msg) {
  137. message.history.push(msg);
  138. message.deleted = true;
  139. Client.deletions.push(message);
  140.  
  141. log('<DELETE>');
  142. log(
  143. message.channel.path,
  144. '@'+message.author.username+':',
  145. message.getLastEdit()[0].content,
  146. );
  147. if (message.embeds.map(a=>a.url).join(' ') != '') log('embeds:', message.embeds.map(a=>a.url).join(' '));
  148. if (message.attachments.map(a=>a.url).join(' ') != '') log('attachments:', message.attachments.map(a=>a.url).join(' '));
  149. if (message.updated) log(message.getHistory().map(m => !m ? '' : m.content).filter(a=>!!a).join(' |-> '));
  150. };
  151.  
  152. Object.defineProperty(message, 'channel', {
  153. get(){
  154. return Client.channel(message.channel_id);
  155. },
  156. });
  157.  
  158. return message;
  159. }
  160.  
  161. static message(id) {
  162. for (let c of Client.channels)
  163. for (let m of c.messages)
  164. if (m.id == id)
  165. return m;
  166. return null;
  167. }
  168.  
  169. static forEachMessage(cb) {
  170. Client.channels.forEach(c => c.messages.forEach(m => cb(m)));
  171. }
  172.  
  173. static updates = [];
  174. static deletions = [];
  175. static actions = [];
  176.  
  177. static onMessage(msg) {
  178. Client.actions.push(msg);
  179. let data = msg.d;
  180.  
  181. if (msg.t == 'READY') {
  182. Client.guilds = msg.d.guilds;
  183. Client.users = msg.d.users;
  184. [
  185. ...msg.d.private_channels,
  186. ...msg.d.guilds.map(g => [
  187. ...g.channels.map(c => {
  188. c.guild_id ??= g.id;
  189. return c;
  190. }), ...g.threads.map(t => {
  191. t.guild_id ??= g.id;
  192. return t;
  193. })
  194. ]).flat(),
  195. ].forEach(Client.newChannel);
  196. }
  197.  
  198. if (msg.t == 'MESSAGE_CREATE') {
  199. if (!Client.message(data.id)) Client.channel(data.channel_id).addMessage(Client.newMessage(data));
  200. }
  201.  
  202. if (msg.t == 'MESSAGE_UPDATE') {
  203. const msg = Client.newMessage(data);
  204. if (!Client.message(msg.id)) Client.channel(data.channel_id).addMessage(msg);
  205. else Client.message(msg.id)?.update(msg);
  206. }
  207.  
  208. if (msg.t == 'MESSAGE_DELETE') {
  209. Client.message(data.id)?.delete(data);
  210. }
  211. }
  212. };
  213. window.client = Client;
  214.  
  215.  
  216.  
  217.  
  218.  
  219. // [init decoder]
  220. // took from discord source code, compress=zlib-stream (pako.js 1.0.10)
  221. let decoder;
  222. function initDecoder() {
  223. if (decoder) log('reinit decoder');
  224. decoder = new window.pako.Inflate({chunkSize:65536, to: "string"});
  225. decoder.onEnd = decodeOutput;
  226. }
  227. function decodeOutput(status) {
  228. let msg;
  229. if (status !== window.pako.Z_OK)
  230. throw Error("zlib error, ".concat(status, ", ").concat(decoder.strm.msg));
  231. let {chunks: a} = decoder
  232. , s = a.length;
  233. if (true) // wants string
  234. msg = s > 1 ? a.join("") : a[0];
  235. else if (s > 1) {
  236. let e = 0;
  237. for (let t = 0; t < s; t++)
  238. e += a[t].length;
  239. let n = new Uint8Array(e)
  240. , i = 0;
  241. for (let e = 0; e < s; e++) {
  242. let t = a[e];
  243. n.set(t, i);
  244. i += t.length;
  245. }
  246. msg = n
  247. } else
  248. msg = a[0];
  249. a.length = 0;
  250. try {
  251. Client.onMessage(JSON.parse(msg));
  252. } catch (e) {
  253. logError(e);
  254. }
  255. }
  256. function decodeInput(event) {
  257. if (event instanceof ArrayBuffer) {
  258. let buffer = new DataView(event);
  259. let fin = buffer.byteLength >= 4 && 65535 === buffer.getUint32(buffer.byteLength - 4, !1);
  260. decoder.push(event, !!fin && window.pako.Z_SYNC_FLUSH)
  261. } else
  262. throw Error("Expected array buffer, but got " + typeof event)
  263. }
  264.  
  265.  
  266.  
  267.  
  268.  
  269. // [hook actions]
  270. // cancel fast connecct
  271. Object.defineProperty(window, '_ws', {set(){}});
  272.  
  273. // hook socket actions
  274. const hook = (obj, key, fn) => {
  275. const scheme = Object.getOwnPropertyDescriptor(obj, key);
  276. Object.defineProperty(obj, key, {
  277. set(value) {
  278. fn.call(this, value, () => Object.defineProperty(this, 'onmessage', {
  279. ...scheme
  280. }));
  281. }
  282. });
  283. };
  284.  
  285. hook(window.WebSocket.prototype, 'onmessage', function(callback, restore) {
  286. restore();
  287. this.onmessage = function(event) {
  288. if (!this.url.match(/^wss\:\/\/[a-zA-Z0-9-]+\.discord\.gg/)) return callback.call(this, event);
  289.  
  290. if (!Client.ws || Client.ws.readyState == WebSocket.CLOSED) {
  291. Client.ws = this;
  292. initDecoder();
  293. }
  294. decodeInput(event.data);
  295.  
  296. callback.call(this, event);
  297. };
  298. });
  299.  
  300. // hook http message history
  301. (() => {
  302. const proto = window.XMLHttpRequest.prototype;
  303.  
  304. if (!proto._open) proto._open = proto.open;
  305. proto.open = function () {
  306. const [method, url] = arguments;
  307. Object.assign(this, {method, url});
  308. return this._open.apply(this, arguments);
  309. };
  310.  
  311. if (!proto._send) proto._send = proto.send;
  312. proto.send = function (body) {
  313. if (this.url.match(/discord\.com\/api\/v9\/channels\/[0-9]+\/messages\?/)) {
  314. this._lnd = this.onload;
  315. this.onload = function (e) {
  316. try {
  317. const list = JSON.parse(this.response);
  318. list.map(Client.newMessage).forEach(m => Client.channel(m.channel_id).addMessage(m));
  319. } catch (e) {logError(e);}
  320. this._lnd?.call(this, e);
  321. };
  322. }
  323. return this._send.apply(this, arguments)
  324. };
  325. })();
  326. })();
  327.  
  328.  
  329.  
  330.  
  331.  
  332. // 0vC4#7152