Skip to content

Instantly share code, notes, and snippets.

@mattroseman
Last active February 23, 2023 19:07
Show Gist options
  • Save mattroseman/80e3113db46d385183fd0e062c28dc58 to your computer and use it in GitHub Desktop.
Save mattroseman/80e3113db46d385183fd0e062c28dc58 to your computer and use it in GitHub Desktop.
DGG Scripts: A catchall Tampermonkey script for messing around with adding stuff to destiny.gg chat
// ==UserScript==
// @name DGG Scripts
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Random scripts modifying DGG and DGG chat
// @author Matthew Roseman
// @match *://*.destiny.gg/embed/chat*
// @icon https://www.google.com/s2/favicons?sz=64&domain=destiny.gg
// @grant none
// @run-at document-start
// ==/UserScript==
/*
================================================
Shared Observers
================================================
*/
/**
* handleNewMessage is a shared handler for new DGG message elements being added to the chat
* it passes the new message to the various functions used by different parts of this general script
*/
function handleNewMessage(newMessage) {
addNewUserFlairToMessage(newMessage);
showRestrictedEmotes(newMessage);
}
document.addEventListener("DOMContentLoaded", function(event) {
const messageObserver = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) {
if (node.className?.includes('msg-user')) {
handleNewMessage(node);
}
}
}
}
});
// messageObserver should trigger for any new message added to the chat-lines div
messageObserver.observe(document.getElementsByClassName('chat-lines')[0], {childList: true});
});
/*
================================================
Show All Emotes, Regardless of Flair
================================================
*/
const RESTRICTED_EMOTES = [
'Aware',
];
function showRestrictedEmotes(newMessage) {
const messageText = newMessage.getElementsByClassName("text")[0];
let messageTextHTML = messageText.innerHTML;
for (const emote of RESTRICTED_EMOTES) {
messageTextHTML = messageTextHTML.replace(new RegExp(`(^|\\s)${emote}($|\\s)`, "gm"), `$1<div title='${emote}' class='emote ${emote}'>${emote}</div>$2`);
}
messageText.innerHTML = messageTextHTML;
}
/*
================================================
Show New-User Badge
================================================
*/
// code that will be added, inline, to the page's HTML in a <script> tag
const INJECTED_CODE = `
let OrigWebSocket = window.WebSocket; // store the original WebSocket for later reference
let wsAddListener = OrigWebSocket.prototype.addEventListener;
wsAddListener = wsAddListener.call.bind(wsAddListener); // rebind to the call function, as it gets lost when reassigned to a new variable
window.WebSocket = function WebSocket(url, protocols) {
var ws;
// mock the various construction situations
if (!(this instanceof WebSocket)) {
ws = OrigWebSocket.apply.bind(OrigWebSocket)(this, arguments);
} else if (arguments.length === 1) {
ws = new OrigWebSocket(url);
} else if (arguments.length >= 2) {
ws = new OrigWebSocket(url, protocols);
} else { // No arguments (browsers will throw an error)
ws = new OrigWebSocket();
}
// add a listener to 'message' events which will store the incoming messages
wsAddListener(ws, 'message', function(event) {
const users = JSON.parse(sessionStorage.getItem('users') || '{}');
const type = event.data.split(' ', 1)[0];
const data = event.data.split(' ').slice(1).join(' ');
if (type === 'NAMES') {
message = JSON.parse(data);
for (const user of message.users) {
users[user.nick.toLowerCase()] = user.createdDate;
}
} else if (type === 'JOIN') {
message = JSON.parse(data);
users[message.nick.toLowerCase()] = message.createdDate;
}
sessionStorage.setItem('users', JSON.stringify(users));
});
return ws;
}.bind();
window.WebSocket.prototype = OrigWebSocket.prototype;
window.WebSocket.prototype.constructor = window.WebSocket;
`;
// create a <script> tag, add the above websocket eavesdropping code, and insert in the page's HTML
const injectedScript = document.createElement('script');
injectedScript.textContent = INJECTED_CODE;
(document.head || document.documentElement).appendChild(injectedScript);
const LEAF_ICON_SVG = '' +
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="rgb(34, 197, 94)" aria-hidden="true" class="h-4 w-4 text-green-500" height="18px" width="18px">' +
'<path fill-rule="evenodd" d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 01-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zM12 2a1 1 0 01.967.744L14.146 7.2 17.5 9.134a1 1 0 010 1.732l-3.354 1.935-1.18 4.455a1 1 0 01-1.933 0L9.854 12.8 6.5 10.866a1 1 0 010-1.732l3.354-1.935 1.18-4.455A1 1 0 0112 2z" clip-rule="evenodd">' +
'</path>' +
'</svg>';
function addNewUserFlairToMessage(newMessage) {
// construct the leaf icon in preperation if this message is from a new user's account
const leafIcon = document.createElement('i');
leafIcon.className += 'flair new-user-flair';
leafIcon.style.height = '18px';
leafIcon.innerHTML = LEAF_ICON_SVG;
const username = newMessage.dataset.username;
// if username isn't in the current dictionary of users, re-read it from session storage to be sure
if (!(username in users)) {
updateUsers();
}
if (username in users) {
const userAge = (new Date() - new Date(users[username])) / 1000 / 60 / 60 / 24; // user's account's age in days
// console.log(`${username} is ${userAge} days old`);
if (userAge <= 7) {
// if the features span isn't in the HTML, no features span is added), add it
if (newMessage.getElementsByClassName('features').length === 0) {
const features = document.createElement('span');
features.className += 'features'
newMessage.insertBefore(features, newMessage.getElementsByClassName('user')[0])
}
// add the leaf icon before the username
newMessage.getElementsByClassName('features')[0].prepend(leafIcon);
}
}
}
function handleNewUser(newUser) {
if (newUser.getElementsByClassName('new-user-flair').length > 0) {
return;
}
// construct the leaf icon in preperation if this message is from a new user's account
const leafIcon = document.createElement('i');
leafIcon.className += 'flair new-user-flair';
leafIcon.style.height = '18px';
leafIcon.innerHTML = LEAF_ICON_SVG;
const username = newUser.dataset.username;
// if username isn't in the current dictionary of users, re-read it from session storage to be sure
if (!(username in users)) {
updateUsers();
}
if (username in users) {
const userAge = (new Date() - new Date(users[username])) / 1000 / 60 / 60 / 24; // user's account's age in days
if (userAge <= 7) {
newUser.prepend(leafIcon);
}
}
}
let users = {};
function updateUsers() {
users = JSON.parse(sessionStorage.getItem('users') || '{}');
}
document.addEventListener("DOMContentLoaded", function(event) {
updateUsers();
const userListObserver = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
for (const node of mutation.addedNodes) {
if (node.className?.includes('user-entry')) {
handleNewUser(node);
}
}
}
});
userListObserver.observe(document.getElementById('chat-user-list'), {childList: true, subtree: true});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment