Son aktivite 1745295964

Revizyon e891f208eb42834dce5d6d4136b020f53111bd20

discord_msg_webhook.user.js Ham
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.4
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 const webhookField = {};
321 if (field.rawName) webhookField.name = field.name;
322 if (field.rawValue) webhookField.value = field.value;
323 if (field.inline !== undefined) webhookField.inline = field.inline;
324 return webhookField;
325 });
326 }
327
328 return webhookEmbed;
329 }).filter(embed => Object.keys(embed).length > 0);
330 }
331
332 return webhookJson;
333 }
334
335 // --- JSON Viewer Functions ---
336 function createJsonDisplayBox() {
337 jsonDisplayBox = document.createElement('div');
338 jsonDisplayBox.id = 'discord-message-json-display';
339 jsonDisplayBox.style.display = 'none'; // Initially hidden
340
341 const header = document.createElement('div');
342 header.className = 'header';
343 // Create a text node as the first child
344 header.appendChild(document.createTextNode('Discord Message JSON Data'));
345
346 const controls = document.createElement('div');
347 controls.style.cssText = 'display: flex; align-items: center;';
348
349 // Webhook Toggle
350 const webhookToggleLabel = document.createElement('label');
351 webhookToggleLabel.style.cssText = 'font-weight: normal; font-size: 10px; margin-right: 10px; display: flex; align-items: center; cursor: pointer;';
352 webhookToggleLabel.textContent = 'Webhook Format';
353
354 const webhookToggle = document.createElement('input');
355 webhookToggle.type = 'checkbox';
356 webhookToggle.style.cssText = 'margin-right: 5px; cursor: pointer;';
357 webhookToggle.addEventListener('change', function() {
358 isWebhookFormat = this.checked;
359 if (currentMessage) {
360 // Only update content, not the entire structure
361 const jsonToDisplay = isWebhookFormat ? convertMessageToWebhookJson(currentMessage) : currentMessage;
362 const jsonString = jsonToDisplay ? JSON.stringify(jsonToDisplay, null, 2) : 'Could not retrieve message data.';
363 const contentElement = document.getElementById('discord-message-json-content');
364 contentElement.textContent = jsonString;
365
366 // Apply our own highlighting
367 highlightJsonContent();
368 }
369 // Update header text but preserve the DOM structure
370 const headerText = document.querySelector('#discord-message-json-display .header');
371 const headerTitle = headerText.childNodes[0];
372 headerTitle.textContent = isWebhookFormat ? 'Discord Webhook JSON Data' : 'Discord Message JSON Data';
373 });
374
375 webhookToggleLabel.prepend(webhookToggle);
376 controls.appendChild(webhookToggleLabel);
377
378 // Copy Button
379 const copyButton = document.createElement('div');
380 copyButton.style.cssText = 'font-weight: normal; font-size: 10px; margin-right: 10px; cursor: pointer; display: flex; align-items: center;';
381 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>';
382 copyButton.innerHTML += '<span style="margin-left: 4px;">Copy</span>';
383
384 copyButton.onclick = () => {
385 const jsonContent = document.getElementById('discord-message-json-content');
386
387 // Get the plain text content, not the highlighted HTML
388 let textToCopy = '';
389 if (currentMessage) {
390 const jsonToDisplay = isWebhookFormat ? convertMessageToWebhookJson(currentMessage) : currentMessage;
391 textToCopy = JSON.stringify(jsonToDisplay, null, 2);
392 } else {
393 textToCopy = jsonContent.textContent;
394 }
395
396 // Copy to clipboard
397 navigator.clipboard.writeText(textToCopy)
398 .then(() => {
399 // Visual feedback
400 const originalText = copyButton.querySelector('span').textContent;
401 copyButton.querySelector('span').textContent = 'Copied!';
402 setTimeout(() => {
403 copyButton.querySelector('span').textContent = originalText;
404 }, 1000);
405 })
406 .catch(err => {
407 console.error('Failed to copy JSON: ', err);
408 });
409 };
410
411 controls.appendChild(copyButton);
412
413 const closeButton = document.createElement('span');
414 closeButton.style.cssText = 'margin-left: 10px; cursor: pointer; font-size: 16px; color: var(--interactive-normal);';
415 closeButton.textContent = '✕';
416 closeButton.onclick = () => {
417 jsonDisplayBox.style.display = 'none';
418 };
419
420 controls.appendChild(closeButton);
421 header.appendChild(controls);
422 jsonDisplayBox.appendChild(header);
423
424 const content = document.createElement('pre');
425 content.id = 'discord-message-json-content';
426 jsonDisplayBox.appendChild(content);
427
428 document.body.appendChild(jsonDisplayBox);
429
430 // Make the box draggable
431 header.addEventListener('mousedown', (e) => {
432 if (e.button !== 0) return;
433 isDragging = true;
434 dragOffsetX = e.clientX - jsonDisplayBox.getBoundingClientRect().left;
435 dragOffsetY = e.clientY - jsonDisplayBox.getBoundingClientRect().top;
436 jsonDisplayBox.style.cursor = 'grabbing';
437 document.body.style.userSelect = 'none';
438 });
439
440 document.addEventListener('mousemove', (e) => {
441 if (!isDragging) return;
442 jsonDisplayBox.style.left = (e.clientX - dragOffsetX) + 'px';
443 jsonDisplayBox.style.top = (e.clientY - dragOffsetY) + 'px';
444 });
445
446 document.addEventListener('mouseup', () => {
447 isDragging = false;
448 if (jsonDisplayBox) jsonDisplayBox.style.cursor = '';
449 document.body.style.userSelect = '';
450 });
451 }
452
453 function highlightJsonContent() {
454 const codeElement = document.getElementById('discord-message-json-content');
455 if (!codeElement) return;
456
457 // Get the text content
458 const text = codeElement.textContent;
459
460 // JSON syntax highlighting with colons preserved
461 let highlighted = text.replace(
462 /"(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
463 function (match) {
464 let cls = 'json-number';
465 if (/^"/.test(match)) {
466 if (/:$/.test(match)) {
467 cls = 'json-key';
468 // Don't remove the colon
469 } else {
470 cls = 'json-string';
471 }
472 } else if (/true|false/.test(match)) {
473 cls = 'json-boolean';
474 } else if (/null/.test(match)) {
475 cls = 'json-null';
476 }
477
478 return `<span class="${cls}">${match}</span>`;
479 }
480 );
481
482 // Also highlight brackets and punctuation (but not colons as they're already handled)
483 highlighted = highlighted.replace(/[{}\[\],]/g, function(match) {
484 return `<span class="json-punctuation">${match}</span>`;
485 });
486
487 codeElement.innerHTML = highlighted;
488 }
489
490 function displayMessageJson(message) {
491 if (!jsonDisplayBox) {
492 createJsonDisplayBox();
493 }
494
495 currentMessage = message;
496
497 let jsonToDisplay = null;
498 if (currentMessage) {
499 if (isWebhookFormat) {
500 jsonToDisplay = convertMessageToWebhookJson(currentMessage);
501 } else {
502 jsonToDisplay = currentMessage;
503 }
504 }
505
506 const jsonString = jsonToDisplay ? JSON.stringify(jsonToDisplay, null, 2) : 'Could not retrieve message data.';
507 const contentElement = document.getElementById('discord-message-json-content');
508 contentElement.textContent = jsonString;
509
510 // Apply our custom syntax highlighting
511 highlightJsonContent();
512
513 jsonDisplayBox.style.display = 'flex';
514 }
515
516 // Handle message clicks for JSON viewing
517 function handleMessageClick(event) {
518 if (!isScriptActive) return;
519
520 const target = event.target;
521 const messageElement = target.closest('[id^="message-"]');
522
523 if (messageElement) {
524 // Skip if clicking interactive elements
525 const interactiveElements = target.closest('a, button, .markup-2BOw-j');
526 if (interactiveElements && !interactiveElements.classList.contains('embedWrapper-lXp9gn')) {
527 return;
528 }
529
530 const reactInstance = findReactInstance(messageElement);
531 if (reactInstance) {
532 const message = getMessageFromReactInstance(reactInstance);
533 if (message) {
534 displayMessageJson(message);
535 return;
536 }
537 }
538
539 // log.warn("Could not find React instance for message element.");
540 if (jsonDisplayBox && document.getElementById('discord-message-json-content')) {
541 document.getElementById('discord-message-json-content').textContent = 'Could not retrieve message data.';
542 currentMessage = null;
543 }
544 }
545 }
546
547 // --- Button Creation and Mounting ---
548 let jsonViewerBtn = null;
549
550 function createJsonViewerButton() {
551 // Create button container
552 jsonViewerBtn = document.createElement('div');
553 jsonViewerBtn.id = 'json-viewer-btn';
554 jsonViewerBtn.setAttribute('role', 'button');
555 jsonViewerBtn.setAttribute('aria-label', 'View Message JSON');
556 jsonViewerBtn.setAttribute('tabindex', '0');
557 jsonViewerBtn.title = 'View Message JSON';
558 jsonViewerBtn.style.cssText = `
559 position: relative;
560 width: auto;
561 height: 24px;
562 margin: 0 8px;
563 cursor: pointer;
564 color: var(--interactive-normal);
565 flex: 0 0 auto;
566 display: flex;
567 align-items: center;
568 justify-content: center;
569 `;
570 jsonViewerBtn.innerHTML = jsonSvgIcon;
571
572 // Add click handler
573 jsonViewerBtn.onclick = () => {
574 if (jsonDisplayBox) {
575 jsonDisplayBox.style.display = jsonDisplayBox.style.display === 'none' ? 'flex' : 'none';
576 } else {
577 createJsonDisplayBox();
578 jsonDisplayBox.style.display = 'flex';
579 }
580 };
581
582 // Add hover effects
583 jsonViewerBtn.onmouseover = () => {
584 jsonViewerBtn.style.color = 'var(--interactive-hover)';
585 };
586
587 jsonViewerBtn.onmouseout = () => {
588 jsonViewerBtn.style.color = 'var(--interactive-normal)';
589 };
590
591 return jsonViewerBtn;
592 }
593
594 function mountJsonViewerButton() {
595 const toolbar = document.querySelector('#app-mount [class^=toolbar]');
596 if (toolbar) {
597 // Check if button already exists
598 if (!document.getElementById('json-viewer-btn')) {
599 if (!jsonViewerBtn) {
600 jsonViewerBtn = createJsonViewerButton();
601 }
602 toolbar.appendChild(jsonViewerBtn);
603 console.log('Mounted JSON Viewer button');
604 }
605 }
606 }
607
608 function initJsonViewer() {
609 // Insert CSS
610 insertCss(themeCss);
611
612 // Create button
613 createJsonViewerButton();
614
615 // Create display box (initially hidden)
616 createJsonDisplayBox();
617
618 // Mount button
619 mountJsonViewerButton();
620
621 // Add message click listener
622 document.addEventListener('click', handleMessageClick, true);
623
624 // Setup observer to re-mount button if needed
625 const discordElm = document.querySelector('#app-mount');
626 if (discordElm) {
627 const observer = new MutationObserver(() => {
628 if (observerThrottle) return;
629 observerThrottle = setTimeout(() => {
630 observerThrottle = null;
631 if (!document.body.contains(jsonViewerBtn)) {
632 mountJsonViewerButton();
633 }
634 }, 3000);
635 });
636
637 observer.observe(discordElm, { childList: true, subtree: true });
638 }
639
640 console.log('JSON Viewer initialized successfully');
641 }
642
643 // Initialize when the page loads
644 if (document.readyState === 'loading') {
645 window.addEventListener('DOMContentLoaded', initJsonViewer);
646 } else {
647 initJsonViewer();
648 }
649})();
650