Greasy Fork is available in English.

DRDL

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

// ==UserScript==
// @name         DRDL
// @description  Discord Recent Deletions Logger - view recent edits and deletions in console.
// @author       0vC4
// @version      1.0
// @namespace    https://greasyfork.org/users/670183-exnonull
// @match        *://discordapp.com/*
// @match        *://discord.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=discord.com
// @run-at       document-start
// @grant        none
// @license      MIT
// @require      https://greasyfork.org/scripts/438620-workertimer/code/WorkerTimer.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.10/pako.js
// ==/UserScript==





const log = console.log;
const logError = console.error;
(() => {
    window.console.log = ()=>0;
    window.console.info = ()=>0;
    window.console.warn = ()=>0;
    window.console.warning = ()=>0;
    window.setTimeout = window.WorkerTimer.setTimeout;
    window.setInterval = window.WorkerTimer.setInterval;
    window.clearTimeout = window.WorkerTimer.clearTimeout;
    window.clearInterval = window.WorkerTimer.clearInterval;
})();





(() => {
    class Client {
        static ws = null;

        static guilds = [];
        static channels = [];
        static users = [];
        static undefinedChannel = {
            path: 'undefined',
            name: 'undefined',
            guild: null,
            parent: null,
            messages: [],
            addMessage(msg) {
                const minutes = 60;
                Client.undefinedChannel.messages = Client.undefinedChannel.messages.filter((m, i) => {
                    if ((+new Date() - +new Date(m.timestamp))/1e3 < 5*minutes) return true;
                    return i < 200;
                });
                Client.undefinedChannel.messages.push(msg);
            }
        };

        static guild(id) {
            return Client.guilds.find(g=>g.id == id);
        }
        static channel(id) {
            return Client.channels.find(c=>c.id == id) ?? Client.undefinedChannel;
        }
        static user(id) {
            return Client.users.find(u=>u.id == id);
        }

        static newChannel(channel) {
            if (!channel) return;
            if (channel.recipient_ids?.length == 1)
                channel.name ??= '@' + Client.user(u => u.id == channel.recipient_ids[0])?.username;

            Object.defineProperty(channel, 'guild', {
                get(){
                    return Client.guild(channel.guild_id);
                },
            });
            Object.defineProperty(channel, 'parent', {
                get(){
                    return Client.channel(channel.parent_id);
                },
            });
            Object.defineProperty(channel, 'path', {
                get(){
                    const ch = Client.channel(channel.parent_id);
                    let name = this.name;

                    if (ch?.parent_id) return ch.path + '/' + name;
                    return channel.guild?.properties?.name + '/' + name;
                },
            });

            channel.messages = [];
            channel.addMessage = (msg) => {
                const minutes = 60;
                channel.messages = channel.messages.filter((m, i) => {
                    if ((+new Date() - +new Date(m.timestamp))/1e3 < 5*minutes) return true;
                    return i < 200;
                });
                channel.messages.push(msg);
            }
            Client.channels.push(channel);
        }

        static newMessage(message) {
            if (!message) return {};

            message.history = [];
            message.getHistory = () => [message, ...message.history];
            message.getLastEdit = () => [(message.history[message.history.length-2] ?? message), message.history[message.history.length-1] ?? message];
            message.updated = false;
            message.deleted = false;
            if (message.edited_timestamp) {
                message.updated = true;
            }

            message.update = function(msg) {
                if (!msg.edited_timestamp) return;
                message.history.push(msg);
                message.updated = true;
                Client.updates.push(message);

                log('<EDIT>');
                log(
                    message.channel.path,
                    '@'+message.author.username+':',
                    message.getLastEdit()[0].content ?? '<???>', ' |-> ', msg.content,
                );
                if (message.embeds.map(a=>a.url).join(' ') != '') log('embeds:', message.embeds.map(a=>a.url).join(' '));
                if (message.attachments.map(a=>a.url).join(' ') != '') log('attachments:', message.attachments.map(a=>a.url).join(' '));
            };

            message.delete = function(msg) {
                message.history.push(msg);
                message.deleted = true;
                Client.deletions.push(message);

                log('<DELETE>');
                log(
                    message.channel.path,
                    '@'+message.author.username+':',
                    message.getLastEdit()[0].content,
                );
                if (message.embeds.map(a=>a.url).join(' ') != '') log('embeds:', message.embeds.map(a=>a.url).join(' '));
                if (message.attachments.map(a=>a.url).join(' ') != '') log('attachments:', message.attachments.map(a=>a.url).join(' '));
                if (message.updated) log(message.getHistory().map(m => !m ? '' : m.content).filter(a=>!!a).join(' |-> '));
            };

            Object.defineProperty(message, 'channel', {
                get(){
                    return Client.channel(message.channel_id);
                },
            });

            return message;
        }

        static message(id) {
            for (let c of Client.channels)
                for (let m of c.messages)
                    if (m.id == id)
                        return m;
            return null;
        }

        static forEachMessage(cb) {
            Client.channels.forEach(c => c.messages.forEach(m => cb(m)));
        }

        static updates = [];
        static deletions = [];
        static actions = [];

        static onMessage(msg) {
            Client.actions.push(msg);
            let data = msg.d;

            if (msg.t == 'READY') {
                Client.guilds = msg.d.guilds;
                Client.users = msg.d.users;
                [
                    ...msg.d.private_channels,
                    ...msg.d.guilds.map(g => [
                        ...g.channels.map(c => {
                            c.guild_id ??= g.id;
                            return c;
                        }), ...g.threads.map(t => {
                            t.guild_id ??= g.id;
                            return t;
                        })
                    ]).flat(),
                ].forEach(Client.newChannel);
            }

            if (msg.t == 'MESSAGE_CREATE') {
                if (!Client.message(data.id)) Client.channel(data.channel_id).addMessage(Client.newMessage(data));
            }

            if (msg.t == 'MESSAGE_UPDATE') {
                const msg = Client.newMessage(data);
                if (!Client.message(msg.id)) Client.channel(data.channel_id).addMessage(msg);
                else Client.message(msg.id)?.update(msg);
            }

            if (msg.t == 'MESSAGE_DELETE') {
                Client.message(data.id)?.delete(data);
            }
        }
    };
    window.client = Client;





    // [init decoder]
    // took from discord source code, compress=zlib-stream (pako.js 1.0.10)
    let decoder;
    function initDecoder() {
        if (decoder) log('reinit decoder');
        decoder = new window.pako.Inflate({chunkSize:65536, to: "string"});
        decoder.onEnd = decodeOutput;
    }
    function decodeOutput(status) {
        let msg;
        if (status !== window.pako.Z_OK)
            throw Error("zlib error, ".concat(status, ", ").concat(decoder.strm.msg));
        let {chunks: a} = decoder
        , s = a.length;
        if (true) // wants string
            msg = s > 1 ? a.join("") : a[0];
        else if (s > 1) {
            let e = 0;
            for (let t = 0; t < s; t++)
                e += a[t].length;
            let n = new Uint8Array(e)
            , i = 0;
            for (let e = 0; e < s; e++) {
                let t = a[e];
                n.set(t, i);
                i += t.length;
            }
            msg = n
        } else
            msg = a[0];
        a.length = 0;
        try {
            Client.onMessage(JSON.parse(msg));
        } catch (e) {
            logError(e);
        }
    }
    function decodeInput(event) {
        if (event instanceof ArrayBuffer) {
            let buffer = new DataView(event);
            let fin = buffer.byteLength >= 4 && 65535 === buffer.getUint32(buffer.byteLength - 4, !1);
            decoder.push(event, !!fin && window.pako.Z_SYNC_FLUSH)
        } else
            throw Error("Expected array buffer, but got " + typeof event)
    }





    // [hook actions]
    // cancel fast connecct
    Object.defineProperty(window, '_ws', {set(){}});

    // hook socket actions
    const hook = (obj, key, fn) => {
        const scheme = Object.getOwnPropertyDescriptor(obj, key);
        Object.defineProperty(obj, key, {
            set(value) {
                fn.call(this, value, () => Object.defineProperty(this, 'onmessage', {
                    ...scheme
                }));
            }
        });
    };

    hook(window.WebSocket.prototype, 'onmessage', function(callback, restore) {
        restore();
        this.onmessage = function(event) {
            if (!this.url.match(/^wss\:\/\/[a-zA-Z0-9-]+\.discord\.gg/)) return callback.call(this, event);

            if (!Client.ws || Client.ws.readyState == WebSocket.CLOSED) {
                Client.ws = this;
                initDecoder();
            }
            decodeInput(event.data);

            callback.call(this, event);
        };
    });

    // hook http message history
    (() => {
        const proto = window.XMLHttpRequest.prototype;

        if (!proto._open) proto._open = proto.open;
        proto.open = function () {
            const [method, url] = arguments;
            Object.assign(this, {method, url});
            return this._open.apply(this, arguments);
        };

        if (!proto._send) proto._send = proto.send;
        proto.send = function (body) {
            if (this.url.match(/discord\.com\/api\/v9\/channels\/[0-9]+\/messages\?/)) {
                this._lnd = this.onload;
                this.onload = function (e) {
                    try {
                        const list = JSON.parse(this.response);
                        list.map(Client.newMessage).forEach(m => Client.channel(m.channel_id).addMessage(m));
                    } catch (e) {logError(e);}
                    this._lnd?.call(this, e);
                };
            }
            return this._send.apply(this, arguments)
        };
    })();
})();





// 0vC4#7152