// ==UserScript==
// @name DRDL
// @description Discord Recent Deletions Logger - view recent edits and deletions in console.
// @author 0vC4
// @version 1.0
// @namespace https://gf.qytechs.cn/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://gf.qytechs.cn/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