您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fix broken Discord channel/thread/message devmode "Copy ID" and "Copy Message Link" buttons in Firefox.
// ==UserScript== // @name (Firefox) Discord fix: devmode copy ID & copy message link // @description Fix broken Discord channel/thread/message devmode "Copy ID" and "Copy Message Link" buttons in Firefox. // @author You // @version 0.6 // @namespace https://greasyfork.org/users/1376767 // @match https://discord.com/* // @grant GM_setClipboard // @noframes // ==/UserScript== (function() { 'use strict'; function addClickEventCopyId(node) { const match = node.id.match(/(\d+)$/); // id="channel-context-devmode-copy-id-123456" > extract end digits if (match) { const channelId = match[1]; node.addEventListener( 'click', function(event) { GM_setClipboard(channelId); }, { once: true } ); } } var selectedLink = null; function addClickEventCopyMessageLink(node) { if (selectedLink) { // Extract guild id from href const guildIdRegex = /^.*?discord\.com\/channels\/(\d+)\//; const guildIdMatch = window.location.href.match(guildIdRegex); let guildId = null; if (guildIdMatch) { guildId = guildIdMatch[1] // Extract channel+message id from selected__ div const listItemId = selectedLink.getAttribute('data-list-item-id'); if (listItemId) { const listItemIdRegex = /-(\d+)-(\d+)$/; // data-list-item-id="messages___chat-messages-12345-135464" const listItemIdMatch = listItemId.match(listItemIdRegex); if (listItemIdMatch) { const channelId = listItemIdMatch[1]; const messageId = listItemIdMatch[2]; const fullDiscordURL = `https://discord.com/channels/${guildId}/${channelId}/${messageId}`; // console.log("==> discord url: ", fullDiscordURL) node.addEventListener( 'click', function(event) { GM_setClipboard(fullDiscordURL); }, { once: true } ); } } } } } const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { // Process added nodes mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { // console.log("==> mutation: ", node) if (/clickTrapContainer_.*trapClicks_/.test(node.className) || (node.className.toString().startsWith("clickTrapContainer_") && node.firstChild?.id?.toString().startsWith("popout_"))) { const menu = node; menu.querySelectorAll('[role="menuitem"][id*="-devmode-copy-id-"]').forEach(menuItem => { setTimeout(() => addClickEventCopyId(menuItem), 100); }); menu.querySelectorAll('[role="menuitem"][id$="-copy-link"]').forEach(menuItem => { setTimeout(() => addClickEventCopyMessageLink(menuItem), 100); }); } } }); // Process attribute changes (specifically class changes) if (mutation.type === 'attributes' && mutation.attributeName === 'class') { const target = mutation.target; if (target instanceof HTMLElement) { for (const className of target.classList) { if (className.startsWith('selected__')) { // console.log("'selected__' div:", target); selectedLink = target; break; } } } } }); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); })();