Última atividade 1745295964

Revisão ae5e97cf5383353f4fa3f8b4a8a702bf80006ec1

discord_msg_webhook.user.js Bruto
1// ==UserScript==
2// @name Discord Message to Webhook
3// @description Combined tool for viewing JSON data of Discord messages and bulk deletion
4// @version 1.0.1
5// @author Original scripts by various authors, edited by jirachi
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// ==/UserScript==
12
13(function () {
14 'use strict';
15
16 // --- Configuration ---
17 let logFn = null;
18 let observerThrottle = null;
19
20 // --- CSS ---
21 const themeCss = `
22/* JSON Viewer Box */
23#discord-message-json-display {
24 position: fixed;
25 top: 50px;
26 right: 50px;
27 width: 450px;
28 height: 300px;
29 background-color: var(--background-secondary);
30 border: 1px solid var(--background-tertiary);
31 border-radius: 5px;
32 z-index: 1000;
33 overflow: hidden;
34 display: flex;
35 flex-direction: column;
36 font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
37 font-size: 12px;
38 color: var(--text-normal);
39 box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2);
40 resize: both;
41 overflow: auto;
42 min-width: 250px;
43 min-height: 150px;
44}
45#discord-message-json-display .header {
46 padding: 10px;
47 background-color: var(--background-tertiary);
48 cursor: grab;
49 font-weight: bold;
50 border-bottom: 1px solid var(--background-tertiary);
51 display: flex;
52 justify-content: space-between;
53 align-items: center;
54 user-select: none;
55}
56#discord-message-json-content {
57 flex-grow: 1;
58 padding: 10px;
59 margin: 0;
60 overflow: auto;
61 white-space: pre-wrap;
62 word-wrap: break-word;
63 background-color: var(--background-primary);
64}
65#discord-message-json-display::-webkit-resizer {
66 background-color: var(--brand-experiment);
67}
68.format-toggle-container {
69 display: flex;
70 align-items: center;
71 font-weight: normal;
72 font-size: 10px;
73 margin-right: 10px;
74 cursor: pointer;
75}
76.format-toggle-container input {
77 margin-right: 5px;
78 cursor: pointer;
79}
80
81/* Custom JSON syntax highlighting */
82.json-string { color: #a8ff60; }
83.json-number { color: #d8a0df; }
84.json-boolean { color: #b285fe; }
85.json-null { color: #70a0d8; }
86.json-key { color: #f08d49; }
87.json-punctuation { color: #7a82da; }
88`;
89
90 // --- HTML Elements ---
91 // SVG Data for the JSON icon
92 const jsonSvgIcon = `<svg width="18px" height="18px" viewBox="-10 -5 1034 1034" xmlns="http://www.w3.org/2000/svg">
93 <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"/>
94</svg>`;
95
96 // --- JSON Viewer Variables ---
97 let jsonDisplayBox = null;
98 let isDragging = false;
99 let dragOffsetX, dragOffsetY;
100 let isScriptActive = true;
101 let currentMessage = null;
102 let isWebhookFormat = false;
103
104 // --- Utility Functions ---
105 const $ = s => document.querySelector(s);
106
107 const log = {
108 debug() { return logFn ? logFn('debug', arguments) : console.debug.apply(console, arguments); },
109 info() { return logFn ? logFn('info', arguments) : console.info.apply(console, arguments); },
110 verb() { return logFn ? logFn('verb', arguments) : console.log.apply(console, arguments); },
111 warn() { return logFn ? logFn('warn', arguments) : console.warn.apply(console, arguments); },
112 error() { return logFn ? logFn('error', arguments) : console.error.apply(console, arguments); },
113 success() { return logFn ? logFn('success', arguments) : console.info.apply(console, arguments); }
114 };
115
116 const setLogFn = (fn) => logFn = fn;
117
118 function createElm(html) {
119 const temp = document.createElement('div');
120 temp.innerHTML = html;
121 return temp.removeChild(temp.firstElementChild);
122 }
123
124 function insertCss(css) {
125 const style = document.createElement('style');
126 style.innerHTML = css;
127 document.head.appendChild(style);
128 return style;
129 }
130
131 // --- React Instance Finders ---
132 function findReactInstance(element) {
133 for (const key in element) {
134 if (key.startsWith('__reactFiber$') || key.startsWith('__reactProps$')) {
135 return element[key];
136 }
137 }
138 return null;
139 }
140
141 function getMessageFromReactInstance(reactInstance) {
142 if (!reactInstance) return null;
143
144 let current = reactInstance;
145 while (current) {
146 if (current.memoizedProps && current.memoizedProps.message) {
147 return current.memoizedProps.message;
148 }
149 current = current.return;
150 }
151 return null;
152 }
153
154 // --- JSON Conversion Function ---
155 function convertMessageToWebhookJson(message) {
156 if (!message) return null;
157
158 const webhookJson = {};
159
160 // Basic message properties
161 if (message.content) {
162 webhookJson.content = message.content;
163 }
164 // console.log(message);
165 // Username and Avatar
166 if (message.author) {
167 webhookJson.username = message.author.username;
168 if (message.author.avatar) {
169 const avatarHash = message.author.avatar;
170 const userId = message.author.id;
171 const isGif = avatarHash.startsWith('a_');
172 webhookJson.avatar_url = `https://cdn.discordapp.com/avatars/${userId}/${avatarHash}.${isGif ? 'gif' : 'png'}`;
173 } else {
174 // Default avatar if none is set
175 const discriminator = message.author.discriminator;
176 let defaultAvatarId;
177 if (discriminator === '0') {
178 defaultAvatarId = (BigInt(message.author.id) >> 22n) % 6n;
179 webhookJson.avatar_url = `https://cdn.discordapp.com/assets/${defaultAvatarId}.png`;
180 } else {
181 defaultAvatarId = parseInt(discriminator) % 5;
182 webhookJson.avatar_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarId}.png`;
183 }
184 }
185 }
186
187 // Embeds
188 if (message.embeds) {
189
190 webhookJson.embeds = message.embeds.map(embed => {
191 const webhookEmbed = {};
192 // console.log(embed);
193 if (embed.rawTitle) webhookEmbed.title = embed.rawTitle;
194 if (embed.title) webhookEmbed.title = embed.title;
195 if (embed.rawDescription) webhookEmbed.description = embed.rawDescription;
196 if (embed.description) webhookEmbed.description = embed.description;
197 if (embed.url) webhookEmbed.url = embed.url;
198
199 // Color
200 if (embed.color !== undefined) {
201 if (typeof embed.color === 'string') {
202 try {
203 webhookEmbed.color = parseInt(embed.color.replace('#', ''), 16);
204 } catch (e) {
205 console.error("Failed to parse embed color string:", embed.color, e);
206 }
207 } else if (typeof embed.color === 'number') {
208 webhookEmbed.color = embed.color;
209 }
210 }
211
212 if (embed.timestamp) webhookEmbed.timestamp = embed.timestamp;
213
214 if (embed.footer) {
215 const webhookFooter = {};
216 if (embed.footer.text) webhookFooter.text = embed.footer.text;
217 if (embed.footer.icon_url) webhookFooter.icon_url = embed.footer.icon_url;
218 if (Object.keys(webhookFooter).length > 0) webhookEmbed.footer = webhookFooter;
219 }
220
221 if (embed.image) {
222 const webhookImage = {};
223 if (embed.image.url) webhookImage.url = embed.image.url;
224 if (Object.keys(webhookImage).length > 0) webhookEmbed.image = webhookImage;
225 }
226
227 if (embed.thumbnail) {
228 const webhookThumbnail = {};
229 if (embed.thumbnail.url) webhookThumbnail.url = embed.thumbnail.url;
230 if (Object.keys(webhookThumbnail).length > 0) webhookEmbed.thumbnail = webhookThumbnail;
231 }
232
233 if (embed.author) {
234 const webhookAuthor = {};
235 if (embed.author.name) webhookAuthor.name = embed.author.name;
236 if (embed.author.url) webhookAuthor.url = embed.author.url;
237 if (embed.author.icon_url) webhookAuthor.icon_url = embed.author.icon_url;
238 if (Object.keys(webhookAuthor).length > 0) webhookEmbed.author = webhookAuthor;
239 }
240
241 if (embed.fields && embed.fields.length > 0) {
242 webhookEmbed.fields = embed.fields.map(field => {
243 const webhookField = {};
244 if (field.name) webhookField.name = field.name;
245 if (field.value) webhookField.value = field.value;
246 if (field.inline !== undefined) webhookField.inline = field.inline;
247 return webhookField;
248 });
249 }
250
251 return webhookEmbed;
252 }).filter(embed => Object.keys(embed).length > 0);
253 }
254
255 return webhookJson;
256 }
257
258 // --- JSON Viewer Functions ---
259 function createJsonDisplayBox() {
260 jsonDisplayBox = document.createElement('div');
261 jsonDisplayBox.id = 'discord-message-json-display';
262 jsonDisplayBox.style.display = 'none'; // Initially hidden
263
264 const header = document.createElement('div');
265 header.className = 'header';
266 // Create a text node as the first child
267 header.appendChild(document.createTextNode('Discord Message JSON Data'));
268
269 const controls = document.createElement('div');
270 controls.style.cssText = 'display: flex; align-items: center;';
271
272 // Webhook Toggle
273 const webhookToggleLabel = document.createElement('label');
274 webhookToggleLabel.style.cssText = 'font-weight: normal; font-size: 10px; margin-right: 10px; display: flex; align-items: center; cursor: pointer;';
275 webhookToggleLabel.textContent = 'Webhook Format';
276
277 const webhookToggle = document.createElement('input');
278 webhookToggle.type = 'checkbox';
279 webhookToggle.style.cssText = 'margin-right: 5px; cursor: pointer;';
280 webhookToggle.addEventListener('change', function() {
281 isWebhookFormat = this.checked;
282 if (currentMessage) {
283 // Only update content, not the entire structure
284 const jsonToDisplay = isWebhookFormat ? convertMessageToWebhookJson(currentMessage) : currentMessage;
285 const jsonString = jsonToDisplay ? JSON.stringify(jsonToDisplay, null, 2) : 'Could not retrieve message data.';
286 const contentElement = document.getElementById('discord-message-json-content');
287 contentElement.textContent = jsonString;
288
289 // Apply our own highlighting
290 highlightJsonContent();
291 }
292 // Update header text but preserve the DOM structure
293 const headerText = document.querySelector('#discord-message-json-display .header');
294 const headerTitle = headerText.childNodes[0];
295 headerTitle.textContent = isWebhookFormat ? 'Discord Webhook JSON Data' : 'Discord Message JSON Data';
296 });
297
298 webhookToggleLabel.prepend(webhookToggle);
299 controls.appendChild(webhookToggleLabel);
300
301 // Copy Button
302 const copyButton = document.createElement('div');
303 copyButton.style.cssText = 'font-weight: normal; font-size: 10px; margin-right: 10px; cursor: pointer; display: flex; align-items: center;';
304 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>';
305 copyButton.innerHTML += '<span style="margin-left: 4px;">Copy</span>';
306
307 copyButton.onclick = () => {
308 const jsonContent = document.getElementById('discord-message-json-content');
309
310 // Get the plain text content, not the highlighted HTML
311 let textToCopy = '';
312 if (currentMessage) {
313 const jsonToDisplay = isWebhookFormat ? convertMessageToWebhookJson(currentMessage) : currentMessage;
314 textToCopy = JSON.stringify(jsonToDisplay, null, 2);
315 } else {
316 textToCopy = jsonContent.textContent;
317 }
318
319 // Copy to clipboard
320 navigator.clipboard.writeText(textToCopy)
321 .then(() => {
322 // Visual feedback
323 const originalText = copyButton.querySelector('span').textContent;
324 copyButton.querySelector('span').textContent = 'Copied!';
325 setTimeout(() => {
326 copyButton.querySelector('span').textContent = originalText;
327 }, 1000);
328 })
329 .catch(err => {
330 console.error('Failed to copy JSON: ', err);
331 });
332 };
333
334 controls.appendChild(copyButton);
335
336 const closeButton = document.createElement('span');
337 closeButton.style.cssText = 'margin-left: 10px; cursor: pointer; font-size: 16px; color: var(--interactive-normal);';
338 closeButton.textContent = '✕';
339 closeButton.onclick = () => {
340 jsonDisplayBox.style.display = 'none';
341 };
342
343 controls.appendChild(closeButton);
344 header.appendChild(controls);
345 jsonDisplayBox.appendChild(header);
346
347 const content = document.createElement('pre');
348 content.id = 'discord-message-json-content';
349 jsonDisplayBox.appendChild(content);
350
351 document.body.appendChild(jsonDisplayBox);
352
353 // Make the box draggable
354 header.addEventListener('mousedown', (e) => {
355 if (e.button !== 0) return;
356 isDragging = true;
357 dragOffsetX = e.clientX - jsonDisplayBox.getBoundingClientRect().left;
358 dragOffsetY = e.clientY - jsonDisplayBox.getBoundingClientRect().top;
359 jsonDisplayBox.style.cursor = 'grabbing';
360 document.body.style.userSelect = 'none';
361 });
362
363 document.addEventListener('mousemove', (e) => {
364 if (!isDragging) return;
365 jsonDisplayBox.style.left = (e.clientX - dragOffsetX) + 'px';
366 jsonDisplayBox.style.top = (e.clientY - dragOffsetY) + 'px';
367 });
368
369 document.addEventListener('mouseup', () => {
370 isDragging = false;
371 if (jsonDisplayBox) jsonDisplayBox.style.cursor = '';
372 document.body.style.userSelect = '';
373 });
374 }
375
376 function highlightJsonContent() {
377 const codeElement = document.getElementById('discord-message-json-content');
378 if (!codeElement) return;
379
380 // Get the text content
381 const text = codeElement.textContent;
382
383 // JSON syntax highlighting with colons preserved
384 let highlighted = text.replace(
385 /"(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
386 function (match) {
387 let cls = 'json-number';
388 if (/^"/.test(match)) {
389 if (/:$/.test(match)) {
390 cls = 'json-key';
391 // Don't remove the colon
392 } else {
393 cls = 'json-string';
394 }
395 } else if (/true|false/.test(match)) {
396 cls = 'json-boolean';
397 } else if (/null/.test(match)) {
398 cls = 'json-null';
399 }
400
401 return `<span class="${cls}">${match}</span>`;
402 }
403 );
404
405 // Also highlight brackets and punctuation (but not colons as they're already handled)
406 highlighted = highlighted.replace(/[{}\[\],]/g, function(match) {
407 return `<span class="json-punctuation">${match}</span>`;
408 });
409
410 codeElement.innerHTML = highlighted;
411 }
412
413 function displayMessageJson(message) {
414 if (!jsonDisplayBox) {
415 createJsonDisplayBox();
416 }
417
418 currentMessage = message;
419
420 let jsonToDisplay = null;
421 if (currentMessage) {
422 if (isWebhookFormat) {
423 jsonToDisplay = convertMessageToWebhookJson(currentMessage);
424 } else {
425 jsonToDisplay = currentMessage;
426 }
427 }
428
429 const jsonString = jsonToDisplay ? JSON.stringify(jsonToDisplay, null, 2) : 'Could not retrieve message data.';
430 const contentElement = document.getElementById('discord-message-json-content');
431 contentElement.textContent = jsonString;
432
433 // Apply our custom syntax highlighting
434 highlightJsonContent();
435
436 jsonDisplayBox.style.display = 'flex';
437 }
438
439 // Handle message clicks for JSON viewing
440 function handleMessageClick(event) {
441 if (!isScriptActive) return;
442
443 const target = event.target;
444 const messageElement = target.closest('[id^="message-"]');
445
446 if (messageElement) {
447 // Skip if clicking interactive elements
448 const interactiveElements = target.closest('a, button, .markup-2BOw-j');
449 if (interactiveElements && !interactiveElements.classList.contains('embedWrapper-lXp9gn')) {
450 return;
451 }
452
453 const reactInstance = findReactInstance(messageElement);
454 if (reactInstance) {
455 const message = getMessageFromReactInstance(reactInstance);
456 if (message) {
457 displayMessageJson(message);
458 return;
459 }
460 }
461
462 // log.warn("Could not find React instance for message element.");
463 if (jsonDisplayBox && document.getElementById('discord-message-json-content')) {
464 document.getElementById('discord-message-json-content').textContent = 'Could not retrieve message data.';
465 currentMessage = null;
466 }
467 }
468 }
469
470 // --- Button Creation and Mounting ---
471 let jsonViewerBtn = null;
472
473 function createJsonViewerButton() {
474 // Create button container
475 jsonViewerBtn = document.createElement('div');
476 jsonViewerBtn.id = 'json-viewer-btn';
477 jsonViewerBtn.setAttribute('role', 'button');
478 jsonViewerBtn.setAttribute('aria-label', 'View Message JSON');
479 jsonViewerBtn.setAttribute('tabindex', '0');
480 jsonViewerBtn.title = 'View Message JSON';
481 jsonViewerBtn.style.cssText = `
482 position: relative;
483 width: auto;
484 height: 24px;
485 margin: 0 8px;
486 cursor: pointer;
487 color: var(--interactive-normal);
488 flex: 0 0 auto;
489 display: flex;
490 align-items: center;
491 justify-content: center;
492 `;
493 jsonViewerBtn.innerHTML = jsonSvgIcon;
494
495 // Add click handler
496 jsonViewerBtn.onclick = () => {
497 if (jsonDisplayBox) {
498 jsonDisplayBox.style.display = jsonDisplayBox.style.display === 'none' ? 'flex' : 'none';
499 } else {
500 createJsonDisplayBox();
501 jsonDisplayBox.style.display = 'flex';
502 }
503 };
504
505 // Add hover effects
506 jsonViewerBtn.onmouseover = () => {
507 jsonViewerBtn.style.color = 'var(--interactive-hover)';
508 };
509
510 jsonViewerBtn.onmouseout = () => {
511 jsonViewerBtn.style.color = 'var(--interactive-normal)';
512 };
513
514 return jsonViewerBtn;
515 }
516
517 function mountJsonViewerButton() {
518 const toolbar = document.querySelector('#app-mount [class^=toolbar]');
519 if (toolbar) {
520 // Check if button already exists
521 if (!document.getElementById('json-viewer-btn')) {
522 if (!jsonViewerBtn) {
523 jsonViewerBtn = createJsonViewerButton();
524 }
525 toolbar.appendChild(jsonViewerBtn);
526 console.log('Mounted JSON Viewer button');
527 }
528 }
529 }
530
531 function initJsonViewer() {
532 // Insert CSS
533 insertCss(themeCss);
534
535 // Create button
536 createJsonViewerButton();
537
538 // Create display box (initially hidden)
539 createJsonDisplayBox();
540
541 // Mount button
542 mountJsonViewerButton();
543
544 // Add message click listener
545 document.addEventListener('click', handleMessageClick, true);
546
547 // Setup observer to re-mount button if needed
548 const discordElm = document.querySelector('#app-mount');
549 if (discordElm) {
550 const observer = new MutationObserver(() => {
551 if (observerThrottle) return;
552 observerThrottle = setTimeout(() => {
553 observerThrottle = null;
554 if (!document.body.contains(jsonViewerBtn)) {
555 mountJsonViewerButton();
556 }
557 }, 3000);
558 });
559
560 observer.observe(discordElm, { childList: true, subtree: true });
561 }
562
563 console.log('JSON Viewer initialized successfully');
564 }
565
566 // Initialize when the page loads
567 if (document.readyState === 'loading') {
568 window.addEventListener('DOMContentLoaded', initJsonViewer);
569 } else {
570 initJsonViewer();
571 }
572})();
573