(Firefox) Discord fix: devmode copy ID & copy message link

Fix broken Discord channel/thread/message devmode "Copy ID" and "Copy Message Link" buttons in Firefox.

当前为 2025-05-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name (Firefox) Discord fix: devmode copy ID & copy message link
  3. // @description Fix broken Discord channel/thread/message devmode "Copy ID" and "Copy Message Link" buttons in Firefox.
  4. // @author You
  5. // @version 0.6
  6. // @namespace https://greasyfork.org/users/1376767
  7. // @match https://discord.com/*
  8. // @grant GM_setClipboard
  9. // @noframes
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. function addClickEventCopyId(node) {
  16. const match = node.id.match(/(\d+)$/); // id="channel-context-devmode-copy-id-123456" > extract end digits
  17. if (match) {
  18. const channelId = match[1];
  19. node.addEventListener(
  20. 'click',
  21. function(event) { GM_setClipboard(channelId); },
  22. { once: true }
  23. );
  24. }
  25. }
  26.  
  27.  
  28. var selectedLink = null;
  29. function addClickEventCopyMessageLink(node) {
  30. if (selectedLink) {
  31. // Extract guild id from href
  32. const guildIdRegex = /^.*?discord\.com\/channels\/(\d+)\//;
  33. const guildIdMatch = window.location.href.match(guildIdRegex);
  34. let guildId = null;
  35. if (guildIdMatch) {
  36. guildId = guildIdMatch[1]
  37.  
  38. // Extract channel+message id from selected__ div
  39. const listItemId = selectedLink.getAttribute('data-list-item-id');
  40. if (listItemId) {
  41. const listItemIdRegex = /-(\d+)-(\d+)$/; // data-list-item-id="messages___chat-messages-12345-135464"
  42. const listItemIdMatch = listItemId.match(listItemIdRegex);
  43. if (listItemIdMatch) {
  44. const channelId = listItemIdMatch[1];
  45. const messageId = listItemIdMatch[2];
  46.  
  47. const fullDiscordURL = `https://discord.com/channels/${guildId}/${channelId}/${messageId}`;
  48. // console.log("==> discord url: ", fullDiscordURL)
  49.  
  50. node.addEventListener(
  51. 'click',
  52. function(event) { GM_setClipboard(fullDiscordURL); },
  53. { once: true }
  54. );
  55. }
  56. }
  57. }
  58. }
  59. }
  60.  
  61. const observer = new MutationObserver(mutations => {
  62. mutations.forEach(mutation => {
  63. // Process added nodes
  64. mutation.addedNodes.forEach(node => {
  65. if (node.nodeType === Node.ELEMENT_NODE) {
  66. // console.log("==> mutation: ", node)
  67.  
  68. if (/clickTrapContainer_.*trapClicks_/.test(node.className)
  69. || (node.className.toString().startsWith("clickTrapContainer_") && node.firstChild?.id?.toString().startsWith("popout_")))
  70. {
  71. const menu = node;
  72. menu.querySelectorAll('[role="menuitem"][id*="-devmode-copy-id-"]').forEach(menuItem => {
  73. setTimeout(() => addClickEventCopyId(menuItem), 100);
  74. });
  75. menu.querySelectorAll('[role="menuitem"][id$="-copy-link"]').forEach(menuItem => {
  76. setTimeout(() => addClickEventCopyMessageLink(menuItem), 100);
  77. });
  78. }
  79. }
  80. });
  81.  
  82. // Process attribute changes (specifically class changes)
  83. if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
  84. const target = mutation.target;
  85. if (target instanceof HTMLElement) {
  86. for (const className of target.classList) {
  87. if (className.startsWith('selected__')) {
  88. // console.log("'selected__' div:", target);
  89. selectedLink = target;
  90. break;
  91. }
  92. }
  93. }
  94. }
  95.  
  96. });
  97. });
  98.  
  99. observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });
  100. })();