discord_msg_webhook.user.js
· 26 KiB · JavaScript
Sin formato
// ==UserScript==
// @name Discord Message to Webhook
// @description Combined tool for viewing JSON data of Discord messages, based off undiscord
// @version 1.0.5
// @author Original scripts by various authors, edited by shayne
// @match https://*.discord.com/app
// @match https://*.discord.com/channels/*
// @match https://*.discord.com/login
// @license none
// @grant none
// @downloadURL https://gist.yorgei.dev/yorgei22/1a8dc2789b484f879d271c90885b1026/raw/HEAD/discord_msg_webhook.user.js
// ==/UserScript==
(function () {
'use strict';
// --- Configuration ---
let logFn = null;
let observerThrottle = null;
// --- CSS ---
const themeCss = `
/* JSON Viewer Box */
#discord-message-json-display {
position: fixed;
top: 50px;
right: 50px;
width: 450px;
height: 300px;
background-color: var(--background-secondary);
border: 1px solid var(--background-tertiary);
border-radius: 5px;
z-index: 1000;
overflow: hidden;
display: flex;
flex-direction: column;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 12px;
color: var(--text-normal);
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2);
resize: both;
overflow: auto;
min-width: 250px;
min-height: 150px;
}
#discord-message-json-display .header {
padding: 10px;
background-color: var(--background-tertiary);
cursor: grab;
font-weight: bold;
border-bottom: 1px solid var(--background-tertiary);
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
}
#discord-message-json-content {
flex-grow: 1;
padding: 10px;
margin: 0;
overflow: auto;
white-space: pre-wrap;
word-wrap: break-word;
background-color: var(--background-primary);
}
#discord-message-json-display::-webkit-resizer {
background-color: var(--brand-experiment);
}
.format-toggle-container {
display: flex;
align-items: center;
font-weight: normal;
font-size: 10px;
margin-right: 10px;
cursor: pointer;
}
.format-toggle-container input {
margin-right: 5px;
cursor: pointer;
}
/* Custom JSON syntax highlighting */
.json-string { color: #a8ff60; }
.json-number { color: #d8a0df; }
.json-boolean { color: #b285fe; }
.json-null { color: #70a0d8; }
.json-key { color: #f08d49; }
.json-punctuation { color: #7a82da; }
`;
// --- HTML Elements ---
// SVG Data for the JSON icon
const jsonSvgIcon = `<svg width="18px" height="18px" viewBox="-10 -5 1034 1034" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M482 226h-1l-10 2q-33 4 -64.5 18.5t-55.5 38.5q-41 37 -57 91q-9 30 -8 63t12 63q17 45 52 78l13 12l-83 135q-26 -1 -45 7q-30 13 -45 40q-7 15 -9 31t2 32q8 30 33 48q15 10 33 14.5t36 2t34.5 -12.5t27.5 -25q12 -17 14.5 -39t-5.5 -41q-1 -5 -7 -14l-3 -6l118 -192 q6 -9 8 -14l-10 -3q-9 -2 -13 -4q-23 -10 -41.5 -27.5t-28.5 -39.5q-17 -36 -9 -75q4 -23 17 -43t31 -34q37 -27 82 -27q27 -1 52.5 9.5t44.5 30.5q17 16 26.5 38.5t10.5 45.5q0 17 -6 42l70 19l8 1q14 -43 7 -86q-4 -33 -19.5 -63.5t-39.5 -53.5q-42 -42 -103 -56 q-6 -2 -18 -4l-14 -2h-37zM500 350q-17 0 -34 7t-30.5 20.5t-19.5 31.5q-8 20 -4 44q3 18 14 34t28 25q24 15 56 13q3 4 5 8l112 191q3 6 6 9q27 -26 58.5 -35.5t65 -3.5t58.5 26q32 25 43.5 61.5t0.5 73.5q-8 28 -28.5 50t-48.5 33q-31 13 -66.5 8.5t-63.5 -24.5 q-4 -3 -13 -10l-5 -6q-4 3 -11 10l-47 46q23 23 52 38.5t61 21.5l22 4h39l28 -5q64 -13 110 -60q22 -22 36.5 -50.5t19.5 -59.5q5 -36 -2 -71.5t-25 -64.5t-44 -51t-57 -35q-34 -14 -70.5 -16t-71.5 7l-17 5l-81 -137q13 -19 16 -37q5 -32 -13 -60q-16 -25 -44 -35 q-17 -6 -35 -6zM218 614q-58 13 -100 53q-47 44 -61 105l-4 24v37l2 11q2 13 4 20q7 31 24.5 59t42.5 49q50 41 115 49q38 4 76 -4.5t70 -28.5q53 -34 78 -91q7 -17 14 -45q6 -1 18 0l125 2q14 0 20 1q11 20 25 31t31.5 16t35.5 4q28 -3 50 -20q27 -21 32 -54 q2 -17 -1.5 -33t-13.5 -30q-16 -22 -41 -32q-17 -7 -35.5 -6.5t-35.5 7.5q-28 12 -43 37l-3 6q-14 0 -42 -1l-113 -1q-15 -1 -43 -1l-50 -1l3 17q8 43 -13 81q-14 27 -40 45t-57 22q-35 6 -70 -7.5t-57 -42.5q-28 -35 -27 -79q1 -37 23 -69q13 -19 32 -32t41 -19l9 -3z"/>
</svg>`;
// --- JSON Viewer Variables ---
let jsonDisplayBox = null;
let isDragging = false;
let dragOffsetX, dragOffsetY;
let isScriptActive = true;
let currentMessage = null;
let isWebhookFormat = false;
// --- Utility Functions ---
const $ = s => document.querySelector(s);
const log = {
debug() { return logFn ? logFn('debug', arguments) : console.debug.apply(console, arguments); },
info() { return logFn ? logFn('info', arguments) : console.info.apply(console, arguments); },
verb() { return logFn ? logFn('verb', arguments) : console.log.apply(console, arguments); },
warn() { return logFn ? logFn('warn', arguments) : console.warn.apply(console, arguments); },
error() { return logFn ? logFn('error', arguments) : console.error.apply(console, arguments); },
success() { return logFn ? logFn('success', arguments) : console.info.apply(console, arguments); }
};
function hslaToHex(hsla) {
// Handle the specific calc variable case by replacing it with 100%
const processedHsla = hsla.replace(/calc\(var\(--saturation-factor,\s*1\)\s*\*\s*100%\)/g, '100%');
// Parse the HSLA values
const parts = processedHsla.match(/hsla?\((\d+),\s*(\d+(\.\d+)?%),\s*(\d+(\.\d+)?%),\s*(\d?(\.\d+)?)\)/);
if (!parts) {
// If parsing fails, return a default color or handle the error
console.error("Failed to parse HSLA string:", hsla);
return null; // Or return a default hex color like 0
}
const h = parseInt(parts[1], 10);
const s = parseFloat(parts[2]) / 100;
const l = parseFloat(parts[4]) / 100;
const a = parseFloat(parts[7] !== undefined ? parts[7] : 1); // Default alpha to 1 if not present
// Convert HSL to RGB
let r, g, b;
if (s === 0) {
r = g = b = l; // Achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h / 360 + 1 / 3);
g = hue2rgb(p, q, h / 360);
b = hue2rgb(p, q, h / 360 - 1 / 3);
}
// Convert RGB to Hex
const toHex = x => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
const hex = `${toHex(r)}${toHex(g)}${toHex(b)}`;
// Handle Alpha (optional, as webhook colors are typically RGB hex)
// If you need to include alpha in the hex (RRGGBBAA), uncomment the following:
/*
const toHexAlpha = x => {
const hexA = Math.round(x * 255).toString(16);
return hexA.length === 1 ? '0' + hexA : hexA;
};
return parseInt(hex + toHexAlpha(a), 16);
*/
return parseInt(hex, 16);
}
const setLogFn = (fn) => logFn = fn;
function createElm(html) {
const temp = document.createElement('div');
temp.innerHTML = html;
return temp.removeChild(temp.firstElementChild);
}
function insertCss(css) {
const style = document.createElement('style');
style.innerHTML = css;
document.head.appendChild(style);
return style;
}
// --- React Instance Finders ---
function findReactInstance(element) {
for (const key in element) {
if (key.startsWith('__reactFiber$') || key.startsWith('__reactProps$')) {
return element[key];
}
}
return null;
}
function getMessageFromReactInstance(reactInstance) {
if (!reactInstance) return null;
let current = reactInstance;
while (current) {
if (current.memoizedProps && current.memoizedProps.message) {
return current.memoizedProps.message;
}
current = current.return;
}
return null;
}
// --- JSON Conversion Function ---
function convertMessageToWebhookJson(message) {
if (!message) return null;
const webhookJson = {};
// Basic message properties
if (message.content) {
webhookJson.content = message.content;
}
// console.log(message);
// Username and Avatar
if (message.author) {
webhookJson.username = message.author.username;
if (message.author.avatar) {
const avatarHash = message.author.avatar;
const userId = message.author.id;
const isGif = avatarHash.startsWith('a_');
webhookJson.avatar_url = `https://cdn.discordapp.com/avatars/${userId}/${avatarHash}.${isGif ? 'gif' : 'png'}`;
} else {
// Default avatar if none is set
const discriminator = message.author.discriminator;
let defaultAvatarId;
if (discriminator === '0') {
defaultAvatarId = (BigInt(message.author.id) >> 22n) % 6n;
webhookJson.avatar_url = `https://cdn.discordapp.com/assets/${defaultAvatarId}.png`;
} else {
defaultAvatarId = parseInt(discriminator) % 5;
webhookJson.avatar_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarId}.png`;
}
}
}
// Embeds
if (message.embeds) {
webhookJson.embeds = message.embeds.map(embed => {
const webhookEmbed = {};
// console.log(embed);
if (embed.rawTitle) webhookEmbed.title = embed.rawTitle;
if (embed.title) webhookEmbed.title = embed.title;
if (embed.rawDescription) webhookEmbed.description = embed.rawDescription;
if (embed.description) webhookEmbed.description = embed.description;
if (embed.url) webhookEmbed.url = embed.url;
// Color
// Original snippet with the added HSLA handling
if (embed.color !== undefined) {
if (typeof embed.color === 'string') {
try {
// Check if the color string is an HSLA value
if (embed.color.startsWith('hsl')) {
const hexColor = hslaToHex(embed.color);
if (hexColor !== null) {
webhookEmbed.color = hexColor;
} else {
// Handle the case where HSLA parsing failed
console.error("Could not convert HSLA color to hex:", embed.color);
// Optionally set a default color or leave it undefined
webhookEmbed.color = 0; // Example: set to black
}
} else {
// Assume it's a hex string if not HSLA
webhookEmbed.color = parseInt(embed.color.replace('#', ''), 16);
}
} catch (e) {
console.error("Failed to process embed color string:", embed.color, e);
}
} else if (typeof embed.color === 'number') {
webhookEmbed.color = embed.color;
}
}
if (embed.timestamp) webhookEmbed.timestamp = embed.timestamp;
if (embed.footer) {
const webhookFooter = {};
if (embed.footer.text) webhookFooter.text = embed.footer.text;
if (embed.footer.icon_url) webhookFooter.icon_url = embed.footer.icon_url;
if (Object.keys(webhookFooter).length > 0) webhookEmbed.footer = webhookFooter;
}
if (embed.image) {
const webhookImage = {};
if (embed.image.url) webhookImage.url = embed.image.url;
if (Object.keys(webhookImage).length > 0) webhookEmbed.image = webhookImage;
}
if (embed.thumbnail) {
const webhookThumbnail = {};
if (embed.thumbnail.url) webhookThumbnail.url = embed.thumbnail.url;
if (Object.keys(webhookThumbnail).length > 0) webhookEmbed.thumbnail = webhookThumbnail;
}
if (embed.author) {
const webhookAuthor = {};
if (embed.author.name) webhookAuthor.name = embed.author.name;
if (embed.author.url) webhookAuthor.url = embed.author.url;
if (embed.author.icon_url) webhookAuthor.icon_url = embed.author.icon_url;
if (Object.keys(webhookAuthor).length > 0) webhookEmbed.author = webhookAuthor;
}
if (embed.fields && embed.fields.length > 0) {
webhookEmbed.fields = embed.fields.map(field => {
console.log(field);
const webhookField = {};
if (field.rawName) webhookField.name = field.rawName;
if (field.rawValue) webhookField.value = field.rawValue;
if (field.inline !== undefined) webhookField.inline = field.inline;
return webhookField;
});
}
return webhookEmbed;
}).filter(embed => Object.keys(embed).length > 0);
}
return webhookJson;
}
// --- JSON Viewer Functions ---
function createJsonDisplayBox() {
jsonDisplayBox = document.createElement('div');
jsonDisplayBox.id = 'discord-message-json-display';
jsonDisplayBox.style.display = 'none'; // Initially hidden
const header = document.createElement('div');
header.className = 'header';
// Create a text node as the first child
header.appendChild(document.createTextNode('Discord Message JSON Data'));
const controls = document.createElement('div');
controls.style.cssText = 'display: flex; align-items: center;';
// Webhook Toggle
const webhookToggleLabel = document.createElement('label');
webhookToggleLabel.style.cssText = 'font-weight: normal; font-size: 10px; margin-right: 10px; display: flex; align-items: center; cursor: pointer;';
webhookToggleLabel.textContent = 'Webhook Format';
const webhookToggle = document.createElement('input');
webhookToggle.type = 'checkbox';
webhookToggle.style.cssText = 'margin-right: 5px; cursor: pointer;';
webhookToggle.addEventListener('change', function() {
isWebhookFormat = this.checked;
if (currentMessage) {
// Only update content, not the entire structure
const jsonToDisplay = isWebhookFormat ? convertMessageToWebhookJson(currentMessage) : currentMessage;
const jsonString = jsonToDisplay ? JSON.stringify(jsonToDisplay, null, 2) : 'Could not retrieve message data.';
const contentElement = document.getElementById('discord-message-json-content');
contentElement.textContent = jsonString;
// Apply our own highlighting
highlightJsonContent();
}
// Update header text but preserve the DOM structure
const headerText = document.querySelector('#discord-message-json-display .header');
const headerTitle = headerText.childNodes[0];
headerTitle.textContent = isWebhookFormat ? 'Discord Webhook JSON Data' : 'Discord Message JSON Data';
});
webhookToggleLabel.prepend(webhookToggle);
controls.appendChild(webhookToggleLabel);
// Copy Button
const copyButton = document.createElement('div');
copyButton.style.cssText = 'font-weight: normal; font-size: 10px; margin-right: 10px; cursor: pointer; display: flex; align-items: center;';
copyButton.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z" fill="currentColor"/></svg>';
copyButton.innerHTML += '<span style="margin-left: 4px;">Copy</span>';
copyButton.onclick = () => {
const jsonContent = document.getElementById('discord-message-json-content');
// Get the plain text content, not the highlighted HTML
let textToCopy = '';
if (currentMessage) {
const jsonToDisplay = isWebhookFormat ? convertMessageToWebhookJson(currentMessage) : currentMessage;
textToCopy = JSON.stringify(jsonToDisplay, null, 2);
} else {
textToCopy = jsonContent.textContent;
}
// Copy to clipboard
navigator.clipboard.writeText(textToCopy)
.then(() => {
// Visual feedback
const originalText = copyButton.querySelector('span').textContent;
copyButton.querySelector('span').textContent = 'Copied!';
setTimeout(() => {
copyButton.querySelector('span').textContent = originalText;
}, 1000);
})
.catch(err => {
console.error('Failed to copy JSON: ', err);
});
};
controls.appendChild(copyButton);
const closeButton = document.createElement('span');
closeButton.style.cssText = 'margin-left: 10px; cursor: pointer; font-size: 16px; color: var(--interactive-normal);';
closeButton.textContent = '✕';
closeButton.onclick = () => {
jsonDisplayBox.style.display = 'none';
};
controls.appendChild(closeButton);
header.appendChild(controls);
jsonDisplayBox.appendChild(header);
const content = document.createElement('pre');
content.id = 'discord-message-json-content';
jsonDisplayBox.appendChild(content);
document.body.appendChild(jsonDisplayBox);
// Make the box draggable
header.addEventListener('mousedown', (e) => {
if (e.button !== 0) return;
isDragging = true;
dragOffsetX = e.clientX - jsonDisplayBox.getBoundingClientRect().left;
dragOffsetY = e.clientY - jsonDisplayBox.getBoundingClientRect().top;
jsonDisplayBox.style.cursor = 'grabbing';
document.body.style.userSelect = 'none';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
jsonDisplayBox.style.left = (e.clientX - dragOffsetX) + 'px';
jsonDisplayBox.style.top = (e.clientY - dragOffsetY) + 'px';
});
document.addEventListener('mouseup', () => {
isDragging = false;
if (jsonDisplayBox) jsonDisplayBox.style.cursor = '';
document.body.style.userSelect = '';
});
}
function highlightJsonContent() {
const codeElement = document.getElementById('discord-message-json-content');
if (!codeElement) return;
// Get the text content
const text = codeElement.textContent;
// JSON syntax highlighting with colons preserved
let highlighted = text.replace(
/"(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
function (match) {
let cls = 'json-number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'json-key';
// Don't remove the colon
} else {
cls = 'json-string';
}
} else if (/true|false/.test(match)) {
cls = 'json-boolean';
} else if (/null/.test(match)) {
cls = 'json-null';
}
return `<span class="${cls}">${match}</span>`;
}
);
// Also highlight brackets and punctuation (but not colons as they're already handled)
highlighted = highlighted.replace(/[{}\[\],]/g, function(match) {
return `<span class="json-punctuation">${match}</span>`;
});
codeElement.innerHTML = highlighted;
}
function displayMessageJson(message) {
if (!jsonDisplayBox) {
createJsonDisplayBox();
}
currentMessage = message;
let jsonToDisplay = null;
if (currentMessage) {
if (isWebhookFormat) {
jsonToDisplay = convertMessageToWebhookJson(currentMessage);
} else {
jsonToDisplay = currentMessage;
}
}
const jsonString = jsonToDisplay ? JSON.stringify(jsonToDisplay, null, 2) : 'Could not retrieve message data.';
const contentElement = document.getElementById('discord-message-json-content');
contentElement.textContent = jsonString;
// Apply our custom syntax highlighting
highlightJsonContent();
jsonDisplayBox.style.display = 'flex';
}
// Handle message clicks for JSON viewing
function handleMessageClick(event) {
if (!isScriptActive) return;
const target = event.target;
const messageElement = target.closest('[id^="message-"]');
if (messageElement) {
// Skip if clicking interactive elements
const interactiveElements = target.closest('a, button, .markup-2BOw-j');
if (interactiveElements && !interactiveElements.classList.contains('embedWrapper-lXp9gn')) {
return;
}
const reactInstance = findReactInstance(messageElement);
if (reactInstance) {
const message = getMessageFromReactInstance(reactInstance);
if (message) {
displayMessageJson(message);
return;
}
}
// log.warn("Could not find React instance for message element.");
if (jsonDisplayBox && document.getElementById('discord-message-json-content')) {
document.getElementById('discord-message-json-content').textContent = 'Could not retrieve message data.';
currentMessage = null;
}
}
}
// --- Button Creation and Mounting ---
let jsonViewerBtn = null;
function createJsonViewerButton() {
// Create button container
jsonViewerBtn = document.createElement('div');
jsonViewerBtn.id = 'json-viewer-btn';
jsonViewerBtn.setAttribute('role', 'button');
jsonViewerBtn.setAttribute('aria-label', 'View Message JSON');
jsonViewerBtn.setAttribute('tabindex', '0');
jsonViewerBtn.title = 'View Message JSON';
jsonViewerBtn.style.cssText = `
position: relative;
width: auto;
height: 24px;
margin: 0 8px;
cursor: pointer;
color: var(--interactive-normal);
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
`;
jsonViewerBtn.innerHTML = jsonSvgIcon;
// Add click handler
jsonViewerBtn.onclick = () => {
if (jsonDisplayBox) {
jsonDisplayBox.style.display = jsonDisplayBox.style.display === 'none' ? 'flex' : 'none';
} else {
createJsonDisplayBox();
jsonDisplayBox.style.display = 'flex';
}
};
// Add hover effects
jsonViewerBtn.onmouseover = () => {
jsonViewerBtn.style.color = 'var(--interactive-hover)';
};
jsonViewerBtn.onmouseout = () => {
jsonViewerBtn.style.color = 'var(--interactive-normal)';
};
return jsonViewerBtn;
}
function mountJsonViewerButton() {
const toolbar = document.querySelector('#app-mount [class^=toolbar]');
if (toolbar) {
// Check if button already exists
if (!document.getElementById('json-viewer-btn')) {
if (!jsonViewerBtn) {
jsonViewerBtn = createJsonViewerButton();
}
toolbar.appendChild(jsonViewerBtn);
console.log('Mounted JSON Viewer button');
}
}
}
function initJsonViewer() {
// Insert CSS
insertCss(themeCss);
// Create button
createJsonViewerButton();
// Create display box (initially hidden)
createJsonDisplayBox();
// Mount button
mountJsonViewerButton();
// Add message click listener
document.addEventListener('click', handleMessageClick, true);
// Setup observer to re-mount button if needed
const discordElm = document.querySelector('#app-mount');
if (discordElm) {
const observer = new MutationObserver(() => {
if (observerThrottle) return;
observerThrottle = setTimeout(() => {
observerThrottle = null;
if (!document.body.contains(jsonViewerBtn)) {
mountJsonViewerButton();
}
}, 3000);
});
observer.observe(discordElm, { childList: true, subtree: true });
}
console.log('JSON Viewer initialized successfully');
}
// Initialize when the page loads
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', initJsonViewer);
} else {
initJsonViewer();
}
})();
1 | // ==UserScript== |
2 | // @name Discord Message to Webhook |
3 | // @description Combined tool for viewing JSON data of Discord messages, based off undiscord |
4 | // @version 1.0.5 |
5 | // @author Original scripts by various authors, edited by shayne |
6 | // @match https://*.discord.com/app |
7 | // @match https://*.discord.com/channels/* |
8 | // @match https://*.discord.com/login |
9 | // @license none |
10 | // @grant none |
11 | // @downloadURL https://gist.yorgei.dev/yorgei22/1a8dc2789b484f879d271c90885b1026/raw/HEAD/discord_msg_webhook.user.js |
12 | // ==/UserScript== |
13 | |
14 | (function () { |
15 | 'use strict'; |
16 | |
17 | // --- Configuration --- |
18 | let logFn = null; |
19 | let observerThrottle = null; |
20 | |
21 | // --- CSS --- |
22 | const themeCss = ` |
23 | /* JSON Viewer Box */ |
24 | #discord-message-json-display { |
25 | position: fixed; |
26 | top: 50px; |
27 | right: 50px; |
28 | width: 450px; |
29 | height: 300px; |
30 | background-color: var(--background-secondary); |
31 | border: 1px solid var(--background-tertiary); |
32 | border-radius: 5px; |
33 | z-index: 1000; |
34 | overflow: hidden; |
35 | display: flex; |
36 | flex-direction: column; |
37 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; |
38 | font-size: 12px; |
39 | color: var(--text-normal); |
40 | box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2); |
41 | resize: both; |
42 | overflow: auto; |
43 | min-width: 250px; |
44 | min-height: 150px; |
45 | } |
46 | #discord-message-json-display .header { |
47 | padding: 10px; |
48 | background-color: var(--background-tertiary); |
49 | cursor: grab; |
50 | font-weight: bold; |
51 | border-bottom: 1px solid var(--background-tertiary); |
52 | display: flex; |
53 | justify-content: space-between; |
54 | align-items: center; |
55 | user-select: none; |
56 | } |
57 | #discord-message-json-content { |
58 | flex-grow: 1; |
59 | padding: 10px; |
60 | margin: 0; |
61 | overflow: auto; |
62 | white-space: pre-wrap; |
63 | word-wrap: break-word; |
64 | background-color: var(--background-primary); |
65 | } |
66 | #discord-message-json-display::-webkit-resizer { |
67 | background-color: var(--brand-experiment); |
68 | } |
69 | .format-toggle-container { |
70 | display: flex; |
71 | align-items: center; |
72 | font-weight: normal; |
73 | font-size: 10px; |
74 | margin-right: 10px; |
75 | cursor: pointer; |
76 | } |
77 | .format-toggle-container input { |
78 | margin-right: 5px; |
79 | cursor: pointer; |
80 | } |
81 | |
82 | /* Custom JSON syntax highlighting */ |
83 | .json-string { color: #a8ff60; } |
84 | .json-number { color: #d8a0df; } |
85 | .json-boolean { color: #b285fe; } |
86 | .json-null { color: #70a0d8; } |
87 | .json-key { color: #f08d49; } |
88 | .json-punctuation { color: #7a82da; } |
89 | `; |
90 | |
91 | // --- HTML Elements --- |
92 | // SVG Data for the JSON icon |
93 | const jsonSvgIcon = `<svg width="18px" height="18px" viewBox="-10 -5 1034 1034" xmlns="http://www.w3.org/2000/svg"> |
94 | <path fill="currentColor" d="M482 226h-1l-10 2q-33 4 -64.5 18.5t-55.5 38.5q-41 37 -57 91q-9 30 -8 63t12 63q17 45 52 78l13 12l-83 135q-26 -1 -45 7q-30 13 -45 40q-7 15 -9 31t2 32q8 30 33 48q15 10 33 14.5t36 2t34.5 -12.5t27.5 -25q12 -17 14.5 -39t-5.5 -41q-1 -5 -7 -14l-3 -6l118 -192 q6 -9 8 -14l-10 -3q-9 -2 -13 -4q-23 -10 -41.5 -27.5t-28.5 -39.5q-17 -36 -9 -75q4 -23 17 -43t31 -34q37 -27 82 -27q27 -1 52.5 9.5t44.5 30.5q17 16 26.5 38.5t10.5 45.5q0 17 -6 42l70 19l8 1q14 -43 7 -86q-4 -33 -19.5 -63.5t-39.5 -53.5q-42 -42 -103 -56 q-6 -2 -18 -4l-14 -2h-37zM500 350q-17 0 -34 7t-30.5 20.5t-19.5 31.5q-8 20 -4 44q3 18 14 34t28 25q24 15 56 13q3 4 5 8l112 191q3 6 6 9q27 -26 58.5 -35.5t65 -3.5t58.5 26q32 25 43.5 61.5t0.5 73.5q-8 28 -28.5 50t-48.5 33q-31 13 -66.5 8.5t-63.5 -24.5 q-4 -3 -13 -10l-5 -6q-4 3 -11 10l-47 46q23 23 52 38.5t61 21.5l22 4h39l28 -5q64 -13 110 -60q22 -22 36.5 -50.5t19.5 -59.5q5 -36 -2 -71.5t-25 -64.5t-44 -51t-57 -35q-34 -14 -70.5 -16t-71.5 7l-17 5l-81 -137q13 -19 16 -37q5 -32 -13 -60q-16 -25 -44 -35 q-17 -6 -35 -6zM218 614q-58 13 -100 53q-47 44 -61 105l-4 24v37l2 11q2 13 4 20q7 31 24.5 59t42.5 49q50 41 115 49q38 4 76 -4.5t70 -28.5q53 -34 78 -91q7 -17 14 -45q6 -1 18 0l125 2q14 0 20 1q11 20 25 31t31.5 16t35.5 4q28 -3 50 -20q27 -21 32 -54 q2 -17 -1.5 -33t-13.5 -30q-16 -22 -41 -32q-17 -7 -35.5 -6.5t-35.5 7.5q-28 12 -43 37l-3 6q-14 0 -42 -1l-113 -1q-15 -1 -43 -1l-50 -1l3 17q8 43 -13 81q-14 27 -40 45t-57 22q-35 6 -70 -7.5t-57 -42.5q-28 -35 -27 -79q1 -37 23 -69q13 -19 32 -32t41 -19l9 -3z"/> |
95 | </svg>`; |
96 | |
97 | // --- JSON Viewer Variables --- |
98 | let jsonDisplayBox = null; |
99 | let isDragging = false; |
100 | let dragOffsetX, dragOffsetY; |
101 | let isScriptActive = true; |
102 | let currentMessage = null; |
103 | let isWebhookFormat = false; |
104 | |
105 | // --- Utility Functions --- |
106 | const $ = s => document.querySelector(s); |
107 | |
108 | const log = { |
109 | debug() { return logFn ? logFn('debug', arguments) : console.debug.apply(console, arguments); }, |
110 | info() { return logFn ? logFn('info', arguments) : console.info.apply(console, arguments); }, |
111 | verb() { return logFn ? logFn('verb', arguments) : console.log.apply(console, arguments); }, |
112 | warn() { return logFn ? logFn('warn', arguments) : console.warn.apply(console, arguments); }, |
113 | error() { return logFn ? logFn('error', arguments) : console.error.apply(console, arguments); }, |
114 | success() { return logFn ? logFn('success', arguments) : console.info.apply(console, arguments); } |
115 | }; |
116 | |
117 | function hslaToHex(hsla) { |
118 | // Handle the specific calc variable case by replacing it with 100% |
119 | const processedHsla = hsla.replace(/calc\(var\(--saturation-factor,\s*1\)\s*\*\s*100%\)/g, '100%'); |
120 | |
121 | // Parse the HSLA values |
122 | const parts = processedHsla.match(/hsla?\((\d+),\s*(\d+(\.\d+)?%),\s*(\d+(\.\d+)?%),\s*(\d?(\.\d+)?)\)/); |
123 | if (!parts) { |
124 | // If parsing fails, return a default color or handle the error |
125 | console.error("Failed to parse HSLA string:", hsla); |
126 | return null; // Or return a default hex color like 0 |
127 | } |
128 | |
129 | const h = parseInt(parts[1], 10); |
130 | const s = parseFloat(parts[2]) / 100; |
131 | const l = parseFloat(parts[4]) / 100; |
132 | const a = parseFloat(parts[7] !== undefined ? parts[7] : 1); // Default alpha to 1 if not present |
133 | |
134 | // Convert HSL to RGB |
135 | let r, g, b; |
136 | |
137 | if (s === 0) { |
138 | r = g = b = l; // Achromatic |
139 | } else { |
140 | const hue2rgb = (p, q, t) => { |
141 | if (t < 0) t += 1; |
142 | if (t > 1) t -= 1; |
143 | if (t < 1 / 6) return p + (q - p) * 6 * t; |
144 | if (t < 1 / 2) return q; |
145 | if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; |
146 | return p; |
147 | }; |
148 | |
149 | const q = l < 0.5 ? l * (1 + s) : l + s - l * s; |
150 | const p = 2 * l - q; |
151 | |
152 | r = hue2rgb(p, q, h / 360 + 1 / 3); |
153 | g = hue2rgb(p, q, h / 360); |
154 | b = hue2rgb(p, q, h / 360 - 1 / 3); |
155 | } |
156 | |
157 | // Convert RGB to Hex |
158 | const toHex = x => { |
159 | const hex = Math.round(x * 255).toString(16); |
160 | return hex.length === 1 ? '0' + hex : hex; |
161 | }; |
162 | |
163 | const hex = `${toHex(r)}${toHex(g)}${toHex(b)}`; |
164 | |
165 | // Handle Alpha (optional, as webhook colors are typically RGB hex) |
166 | // If you need to include alpha in the hex (RRGGBBAA), uncomment the following: |
167 | /* |
168 | const toHexAlpha = x => { |
169 | const hexA = Math.round(x * 255).toString(16); |
170 | return hexA.length === 1 ? '0' + hexA : hexA; |
171 | }; |
172 | return parseInt(hex + toHexAlpha(a), 16); |
173 | */ |
174 | |
175 | return parseInt(hex, 16); |
176 | } |
177 | |
178 | const setLogFn = (fn) => logFn = fn; |
179 | |
180 | function createElm(html) { |
181 | const temp = document.createElement('div'); |
182 | temp.innerHTML = html; |
183 | return temp.removeChild(temp.firstElementChild); |
184 | } |
185 | |
186 | function insertCss(css) { |
187 | const style = document.createElement('style'); |
188 | style.innerHTML = css; |
189 | document.head.appendChild(style); |
190 | return style; |
191 | } |
192 | |
193 | // --- React Instance Finders --- |
194 | function findReactInstance(element) { |
195 | for (const key in element) { |
196 | if (key.startsWith('__reactFiber$') || key.startsWith('__reactProps$')) { |
197 | return element[key]; |
198 | } |
199 | } |
200 | return null; |
201 | } |
202 | |
203 | function getMessageFromReactInstance(reactInstance) { |
204 | if (!reactInstance) return null; |
205 | |
206 | let current = reactInstance; |
207 | while (current) { |
208 | if (current.memoizedProps && current.memoizedProps.message) { |
209 | return current.memoizedProps.message; |
210 | } |
211 | current = current.return; |
212 | } |
213 | return null; |
214 | } |
215 | |
216 | // --- JSON Conversion Function --- |
217 | function convertMessageToWebhookJson(message) { |
218 | if (!message) return null; |
219 | |
220 | const webhookJson = {}; |
221 | |
222 | // Basic message properties |
223 | if (message.content) { |
224 | webhookJson.content = message.content; |
225 | } |
226 | // console.log(message); |
227 | // Username and Avatar |
228 | if (message.author) { |
229 | webhookJson.username = message.author.username; |
230 | if (message.author.avatar) { |
231 | const avatarHash = message.author.avatar; |
232 | const userId = message.author.id; |
233 | const isGif = avatarHash.startsWith('a_'); |
234 | webhookJson.avatar_url = `https://cdn.discordapp.com/avatars/${userId}/${avatarHash}.${isGif ? 'gif' : 'png'}`; |
235 | } else { |
236 | // Default avatar if none is set |
237 | const discriminator = message.author.discriminator; |
238 | let defaultAvatarId; |
239 | if (discriminator === '0') { |
240 | defaultAvatarId = (BigInt(message.author.id) >> 22n) % 6n; |
241 | webhookJson.avatar_url = `https://cdn.discordapp.com/assets/${defaultAvatarId}.png`; |
242 | } else { |
243 | defaultAvatarId = parseInt(discriminator) % 5; |
244 | webhookJson.avatar_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarId}.png`; |
245 | } |
246 | } |
247 | } |
248 | |
249 | // Embeds |
250 | if (message.embeds) { |
251 | |
252 | webhookJson.embeds = message.embeds.map(embed => { |
253 | const webhookEmbed = {}; |
254 | // console.log(embed); |
255 | if (embed.rawTitle) webhookEmbed.title = embed.rawTitle; |
256 | if (embed.title) webhookEmbed.title = embed.title; |
257 | if (embed.rawDescription) webhookEmbed.description = embed.rawDescription; |
258 | if (embed.description) webhookEmbed.description = embed.description; |
259 | if (embed.url) webhookEmbed.url = embed.url; |
260 | |
261 | // Color |
262 | // Original snippet with the added HSLA handling |
263 | if (embed.color !== undefined) { |
264 | if (typeof embed.color === 'string') { |
265 | try { |
266 | // Check if the color string is an HSLA value |
267 | if (embed.color.startsWith('hsl')) { |
268 | const hexColor = hslaToHex(embed.color); |
269 | if (hexColor !== null) { |
270 | webhookEmbed.color = hexColor; |
271 | } else { |
272 | // Handle the case where HSLA parsing failed |
273 | console.error("Could not convert HSLA color to hex:", embed.color); |
274 | // Optionally set a default color or leave it undefined |
275 | webhookEmbed.color = 0; // Example: set to black |
276 | } |
277 | } else { |
278 | // Assume it's a hex string if not HSLA |
279 | webhookEmbed.color = parseInt(embed.color.replace('#', ''), 16); |
280 | } |
281 | } catch (e) { |
282 | console.error("Failed to process embed color string:", embed.color, e); |
283 | } |
284 | } else if (typeof embed.color === 'number') { |
285 | webhookEmbed.color = embed.color; |
286 | } |
287 | } |
288 | |
289 | if (embed.timestamp) webhookEmbed.timestamp = embed.timestamp; |
290 | |
291 | if (embed.footer) { |
292 | const webhookFooter = {}; |
293 | if (embed.footer.text) webhookFooter.text = embed.footer.text; |
294 | if (embed.footer.icon_url) webhookFooter.icon_url = embed.footer.icon_url; |
295 | if (Object.keys(webhookFooter).length > 0) webhookEmbed.footer = webhookFooter; |
296 | } |
297 | |
298 | if (embed.image) { |
299 | const webhookImage = {}; |
300 | if (embed.image.url) webhookImage.url = embed.image.url; |
301 | if (Object.keys(webhookImage).length > 0) webhookEmbed.image = webhookImage; |
302 | } |
303 | |
304 | if (embed.thumbnail) { |
305 | const webhookThumbnail = {}; |
306 | if (embed.thumbnail.url) webhookThumbnail.url = embed.thumbnail.url; |
307 | if (Object.keys(webhookThumbnail).length > 0) webhookEmbed.thumbnail = webhookThumbnail; |
308 | } |
309 | |
310 | if (embed.author) { |
311 | const webhookAuthor = {}; |
312 | if (embed.author.name) webhookAuthor.name = embed.author.name; |
313 | if (embed.author.url) webhookAuthor.url = embed.author.url; |
314 | if (embed.author.icon_url) webhookAuthor.icon_url = embed.author.icon_url; |
315 | if (Object.keys(webhookAuthor).length > 0) webhookEmbed.author = webhookAuthor; |
316 | } |
317 | |
318 | if (embed.fields && embed.fields.length > 0) { |
319 | webhookEmbed.fields = embed.fields.map(field => { |
320 | console.log(field); |
321 | const webhookField = {}; |
322 | if (field.rawName) webhookField.name = field.rawName; |
323 | if (field.rawValue) webhookField.value = field.rawValue; |
324 | if (field.inline !== undefined) webhookField.inline = field.inline; |
325 | return webhookField; |
326 | }); |
327 | } |
328 | |
329 | return webhookEmbed; |
330 | }).filter(embed => Object.keys(embed).length > 0); |
331 | } |
332 | |
333 | return webhookJson; |
334 | } |
335 | |
336 | // --- JSON Viewer Functions --- |
337 | function createJsonDisplayBox() { |
338 | jsonDisplayBox = document.createElement('div'); |
339 | jsonDisplayBox.id = 'discord-message-json-display'; |
340 | jsonDisplayBox.style.display = 'none'; // Initially hidden |
341 | |
342 | const header = document.createElement('div'); |
343 | header.className = 'header'; |
344 | // Create a text node as the first child |
345 | header.appendChild(document.createTextNode('Discord Message JSON Data')); |
346 | |
347 | const controls = document.createElement('div'); |
348 | controls.style.cssText = 'display: flex; align-items: center;'; |
349 | |
350 | // Webhook Toggle |
351 | const webhookToggleLabel = document.createElement('label'); |
352 | webhookToggleLabel.style.cssText = 'font-weight: normal; font-size: 10px; margin-right: 10px; display: flex; align-items: center; cursor: pointer;'; |
353 | webhookToggleLabel.textContent = 'Webhook Format'; |
354 | |
355 | const webhookToggle = document.createElement('input'); |
356 | webhookToggle.type = 'checkbox'; |
357 | webhookToggle.style.cssText = 'margin-right: 5px; cursor: pointer;'; |
358 | webhookToggle.addEventListener('change', function() { |
359 | isWebhookFormat = this.checked; |
360 | if (currentMessage) { |
361 | // Only update content, not the entire structure |
362 | const jsonToDisplay = isWebhookFormat ? convertMessageToWebhookJson(currentMessage) : currentMessage; |
363 | const jsonString = jsonToDisplay ? JSON.stringify(jsonToDisplay, null, 2) : 'Could not retrieve message data.'; |
364 | const contentElement = document.getElementById('discord-message-json-content'); |
365 | contentElement.textContent = jsonString; |
366 | |
367 | // Apply our own highlighting |
368 | highlightJsonContent(); |
369 | } |
370 | // Update header text but preserve the DOM structure |
371 | const headerText = document.querySelector('#discord-message-json-display .header'); |
372 | const headerTitle = headerText.childNodes[0]; |
373 | headerTitle.textContent = isWebhookFormat ? 'Discord Webhook JSON Data' : 'Discord Message JSON Data'; |
374 | }); |
375 | |
376 | webhookToggleLabel.prepend(webhookToggle); |
377 | controls.appendChild(webhookToggleLabel); |
378 | |
379 | // Copy Button |
380 | const copyButton = document.createElement('div'); |
381 | copyButton.style.cssText = 'font-weight: normal; font-size: 10px; margin-right: 10px; cursor: pointer; display: flex; align-items: center;'; |
382 | copyButton.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z" fill="currentColor"/></svg>'; |
383 | copyButton.innerHTML += '<span style="margin-left: 4px;">Copy</span>'; |
384 | |
385 | copyButton.onclick = () => { |
386 | const jsonContent = document.getElementById('discord-message-json-content'); |
387 | |
388 | // Get the plain text content, not the highlighted HTML |
389 | let textToCopy = ''; |
390 | if (currentMessage) { |
391 | const jsonToDisplay = isWebhookFormat ? convertMessageToWebhookJson(currentMessage) : currentMessage; |
392 | textToCopy = JSON.stringify(jsonToDisplay, null, 2); |
393 | } else { |
394 | textToCopy = jsonContent.textContent; |
395 | } |
396 | |
397 | // Copy to clipboard |
398 | navigator.clipboard.writeText(textToCopy) |
399 | .then(() => { |
400 | // Visual feedback |
401 | const originalText = copyButton.querySelector('span').textContent; |
402 | copyButton.querySelector('span').textContent = 'Copied!'; |
403 | setTimeout(() => { |
404 | copyButton.querySelector('span').textContent = originalText; |
405 | }, 1000); |
406 | }) |
407 | .catch(err => { |
408 | console.error('Failed to copy JSON: ', err); |
409 | }); |
410 | }; |
411 | |
412 | controls.appendChild(copyButton); |
413 | |
414 | const closeButton = document.createElement('span'); |
415 | closeButton.style.cssText = 'margin-left: 10px; cursor: pointer; font-size: 16px; color: var(--interactive-normal);'; |
416 | closeButton.textContent = '✕'; |
417 | closeButton.onclick = () => { |
418 | jsonDisplayBox.style.display = 'none'; |
419 | }; |
420 | |
421 | controls.appendChild(closeButton); |
422 | header.appendChild(controls); |
423 | jsonDisplayBox.appendChild(header); |
424 | |
425 | const content = document.createElement('pre'); |
426 | content.id = 'discord-message-json-content'; |
427 | jsonDisplayBox.appendChild(content); |
428 | |
429 | document.body.appendChild(jsonDisplayBox); |
430 | |
431 | // Make the box draggable |
432 | header.addEventListener('mousedown', (e) => { |
433 | if (e.button !== 0) return; |
434 | isDragging = true; |
435 | dragOffsetX = e.clientX - jsonDisplayBox.getBoundingClientRect().left; |
436 | dragOffsetY = e.clientY - jsonDisplayBox.getBoundingClientRect().top; |
437 | jsonDisplayBox.style.cursor = 'grabbing'; |
438 | document.body.style.userSelect = 'none'; |
439 | }); |
440 | |
441 | document.addEventListener('mousemove', (e) => { |
442 | if (!isDragging) return; |
443 | jsonDisplayBox.style.left = (e.clientX - dragOffsetX) + 'px'; |
444 | jsonDisplayBox.style.top = (e.clientY - dragOffsetY) + 'px'; |
445 | }); |
446 | |
447 | document.addEventListener('mouseup', () => { |
448 | isDragging = false; |
449 | if (jsonDisplayBox) jsonDisplayBox.style.cursor = ''; |
450 | document.body.style.userSelect = ''; |
451 | }); |
452 | } |
453 | |
454 | function highlightJsonContent() { |
455 | const codeElement = document.getElementById('discord-message-json-content'); |
456 | if (!codeElement) return; |
457 | |
458 | // Get the text content |
459 | const text = codeElement.textContent; |
460 | |
461 | // JSON syntax highlighting with colons preserved |
462 | let highlighted = text.replace( |
463 | /"(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, |
464 | function (match) { |
465 | let cls = 'json-number'; |
466 | if (/^"/.test(match)) { |
467 | if (/:$/.test(match)) { |
468 | cls = 'json-key'; |
469 | // Don't remove the colon |
470 | } else { |
471 | cls = 'json-string'; |
472 | } |
473 | } else if (/true|false/.test(match)) { |
474 | cls = 'json-boolean'; |
475 | } else if (/null/.test(match)) { |
476 | cls = 'json-null'; |
477 | } |
478 | |
479 | return `<span class="${cls}">${match}</span>`; |
480 | } |
481 | ); |
482 | |
483 | // Also highlight brackets and punctuation (but not colons as they're already handled) |
484 | highlighted = highlighted.replace(/[{}\[\],]/g, function(match) { |
485 | return `<span class="json-punctuation">${match}</span>`; |
486 | }); |
487 | |
488 | codeElement.innerHTML = highlighted; |
489 | } |
490 | |
491 | function displayMessageJson(message) { |
492 | if (!jsonDisplayBox) { |
493 | createJsonDisplayBox(); |
494 | } |
495 | |
496 | currentMessage = message; |
497 | |
498 | let jsonToDisplay = null; |
499 | if (currentMessage) { |
500 | if (isWebhookFormat) { |
501 | jsonToDisplay = convertMessageToWebhookJson(currentMessage); |
502 | } else { |
503 | jsonToDisplay = currentMessage; |
504 | } |
505 | } |
506 | |
507 | const jsonString = jsonToDisplay ? JSON.stringify(jsonToDisplay, null, 2) : 'Could not retrieve message data.'; |
508 | const contentElement = document.getElementById('discord-message-json-content'); |
509 | contentElement.textContent = jsonString; |
510 | |
511 | // Apply our custom syntax highlighting |
512 | highlightJsonContent(); |
513 | |
514 | jsonDisplayBox.style.display = 'flex'; |
515 | } |
516 | |
517 | // Handle message clicks for JSON viewing |
518 | function handleMessageClick(event) { |
519 | if (!isScriptActive) return; |
520 | |
521 | const target = event.target; |
522 | const messageElement = target.closest('[id^="message-"]'); |
523 | |
524 | if (messageElement) { |
525 | // Skip if clicking interactive elements |
526 | const interactiveElements = target.closest('a, button, .markup-2BOw-j'); |
527 | if (interactiveElements && !interactiveElements.classList.contains('embedWrapper-lXp9gn')) { |
528 | return; |
529 | } |
530 | |
531 | const reactInstance = findReactInstance(messageElement); |
532 | if (reactInstance) { |
533 | const message = getMessageFromReactInstance(reactInstance); |
534 | if (message) { |
535 | displayMessageJson(message); |
536 | return; |
537 | } |
538 | } |
539 | |
540 | // log.warn("Could not find React instance for message element."); |
541 | if (jsonDisplayBox && document.getElementById('discord-message-json-content')) { |
542 | document.getElementById('discord-message-json-content').textContent = 'Could not retrieve message data.'; |
543 | currentMessage = null; |
544 | } |
545 | } |
546 | } |
547 | |
548 | // --- Button Creation and Mounting --- |
549 | let jsonViewerBtn = null; |
550 | |
551 | function createJsonViewerButton() { |
552 | // Create button container |
553 | jsonViewerBtn = document.createElement('div'); |
554 | jsonViewerBtn.id = 'json-viewer-btn'; |
555 | jsonViewerBtn.setAttribute('role', 'button'); |
556 | jsonViewerBtn.setAttribute('aria-label', 'View Message JSON'); |
557 | jsonViewerBtn.setAttribute('tabindex', '0'); |
558 | jsonViewerBtn.title = 'View Message JSON'; |
559 | jsonViewerBtn.style.cssText = ` |
560 | position: relative; |
561 | width: auto; |
562 | height: 24px; |
563 | margin: 0 8px; |
564 | cursor: pointer; |
565 | color: var(--interactive-normal); |
566 | flex: 0 0 auto; |
567 | display: flex; |
568 | align-items: center; |
569 | justify-content: center; |
570 | `; |
571 | jsonViewerBtn.innerHTML = jsonSvgIcon; |
572 | |
573 | // Add click handler |
574 | jsonViewerBtn.onclick = () => { |
575 | if (jsonDisplayBox) { |
576 | jsonDisplayBox.style.display = jsonDisplayBox.style.display === 'none' ? 'flex' : 'none'; |
577 | } else { |
578 | createJsonDisplayBox(); |
579 | jsonDisplayBox.style.display = 'flex'; |
580 | } |
581 | }; |
582 | |
583 | // Add hover effects |
584 | jsonViewerBtn.onmouseover = () => { |
585 | jsonViewerBtn.style.color = 'var(--interactive-hover)'; |
586 | }; |
587 | |
588 | jsonViewerBtn.onmouseout = () => { |
589 | jsonViewerBtn.style.color = 'var(--interactive-normal)'; |
590 | }; |
591 | |
592 | return jsonViewerBtn; |
593 | } |
594 | |
595 | function mountJsonViewerButton() { |
596 | const toolbar = document.querySelector('#app-mount [class^=toolbar]'); |
597 | if (toolbar) { |
598 | // Check if button already exists |
599 | if (!document.getElementById('json-viewer-btn')) { |
600 | if (!jsonViewerBtn) { |
601 | jsonViewerBtn = createJsonViewerButton(); |
602 | } |
603 | toolbar.appendChild(jsonViewerBtn); |
604 | console.log('Mounted JSON Viewer button'); |
605 | } |
606 | } |
607 | } |
608 | |
609 | function initJsonViewer() { |
610 | // Insert CSS |
611 | insertCss(themeCss); |
612 | |
613 | // Create button |
614 | createJsonViewerButton(); |
615 | |
616 | // Create display box (initially hidden) |
617 | createJsonDisplayBox(); |
618 | |
619 | // Mount button |
620 | mountJsonViewerButton(); |
621 | |
622 | // Add message click listener |
623 | document.addEventListener('click', handleMessageClick, true); |
624 | |
625 | // Setup observer to re-mount button if needed |
626 | const discordElm = document.querySelector('#app-mount'); |
627 | if (discordElm) { |
628 | const observer = new MutationObserver(() => { |
629 | if (observerThrottle) return; |
630 | observerThrottle = setTimeout(() => { |
631 | observerThrottle = null; |
632 | if (!document.body.contains(jsonViewerBtn)) { |
633 | mountJsonViewerButton(); |
634 | } |
635 | }, 3000); |
636 | }); |
637 | |
638 | observer.observe(discordElm, { childList: true, subtree: true }); |
639 | } |
640 | |
641 | console.log('JSON Viewer initialized successfully'); |
642 | } |
643 | |
644 | // Initialize when the page loads |
645 | if (document.readyState === 'loading') { |
646 | window.addEventListener('DOMContentLoaded', initJsonViewer); |
647 | } else { |
648 | initJsonViewer(); |
649 | } |
650 | })(); |
651 |