Greasy Fork 支持简体中文。

Discord DM Sender with Input Box and Toggle

Send DM messages via Discord API with toggle visibility, customizable button names, and instructions. Supports "Shift + Enter" for new line and "Enter" for sending messages.

目前為 2025-04-17 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Discord DM Sender with Input Box and Toggle
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.2
  5. // @description Send DM messages via Discord API with toggle visibility, customizable button names, and instructions. Supports "Shift + Enter" for new line and "Enter" for sending messages.
  6. // @author Your Name
  7. // @match https://discord.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @license You can modify as long as you credit me
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. let isBoxVisible = GM_getValue('isBoxVisible', true);
  18. let isTokenVisible = GM_getValue('isTokenVisible', true);
  19. let areChannelsVisible = GM_getValue('areChannelsVisible', true);
  20. let channelId = '';
  21.  
  22. const initialWidth = '280px';
  23. const initialHeight = '500px';
  24.  
  25. const container = document.createElement('div');
  26. container.style.position = 'fixed';
  27. container.style.bottom = '10px';
  28. container.style.left = '10px';
  29. container.style.backgroundColor = '#2f3136';
  30. container.style.color = '#ffffff';
  31. container.style.padding = '10px';
  32. container.style.borderRadius = '5px';
  33. container.style.zIndex = '1000';
  34. container.style.width = initialWidth;
  35. container.style.height = initialHeight;
  36. container.style.maxHeight = '90vh';
  37. container.style.overflow = 'auto';
  38. container.style.display = isBoxVisible ? 'block' : 'none';
  39. document.body.appendChild(container);
  40.  
  41. makeElementDraggable(container);
  42.  
  43. const hideTokenButton = document.createElement('button');
  44. hideTokenButton.innerText = isTokenVisible ? 'Hide Token' : 'View Token';
  45. hideTokenButton.style.marginBottom = '10px';
  46. hideTokenButton.style.width = '100%';
  47. hideTokenButton.style.backgroundColor = '#575757';
  48. hideTokenButton.style.color = '#ffffff';
  49. hideTokenButton.style.border = 'none';
  50. hideTokenButton.style.borderRadius = '3px';
  51. hideTokenButton.style.cursor = 'pointer';
  52. hideTokenButton.addEventListener('click', () => {
  53. isTokenVisible = !isTokenVisible;
  54. GM_setValue('isTokenVisible', isTokenVisible);
  55. tokenBox.style.display = isTokenVisible ? 'block' : 'none';
  56. hideTokenButton.innerText = isTokenVisible ? 'Hide Token' : 'View Token';
  57. });
  58. container.appendChild(hideTokenButton);
  59.  
  60. const tokenBox = document.createElement('textarea');
  61. tokenBox.placeholder = 'Enter your token';
  62. tokenBox.style.width = '100%';
  63. tokenBox.style.height = '40px';
  64. tokenBox.style.resize = 'none';
  65. tokenBox.style.backgroundColor = '#000000';
  66. tokenBox.style.color = '#00FF00';
  67. tokenBox.style.display = isTokenVisible ? 'block' : 'none';
  68. tokenBox.value = GM_getValue('tokenBoxValue', '');
  69. tokenBox.addEventListener('input', () => {
  70. GM_setValue('tokenBoxValue', tokenBox.value);
  71. });
  72. container.appendChild(tokenBox);
  73.  
  74. const toggleChannelsButton = document.createElement('button');
  75. toggleChannelsButton.innerText = areChannelsVisible ? 'Hide Channel IDs' : 'View Channel IDs';
  76. toggleChannelsButton.style.marginTop = '10px';
  77. toggleChannelsButton.style.width = '100%';
  78. toggleChannelsButton.style.backgroundColor = '#575757';
  79. toggleChannelsButton.style.color = '#ffffff';
  80. toggleChannelsButton.style.border = 'none';
  81. toggleChannelsButton.style.borderRadius = '3px';
  82. toggleChannelsButton.style.cursor = 'pointer';
  83. toggleChannelsButton.addEventListener('click', () => {
  84. areChannelsVisible = !areChannelsVisible;
  85. GM_setValue('areChannelsVisible', areChannelsVisible);
  86. channelBoxes.forEach((channelBox) => {
  87. channelBox.style.display = areChannelsVisible ? 'block' : 'none';
  88. });
  89. toggleChannelsButton.innerText = areChannelsVisible ? 'Hide Channel IDs' : 'View Channel IDs';
  90. });
  91. container.appendChild(toggleChannelsButton);
  92.  
  93. const channelBoxes = [];
  94. const channelBoxPlaceholders = [
  95. '荒らし雑談用匿名BOTのDMチャンネルIDを入力',
  96. '情勢雑談用BOTの匿名BOTのDMチャンネルIDを入力',
  97. '依頼支部用匿名BOTのDMチャンネルIDを入力'
  98. ];
  99.  
  100. channelBoxPlaceholders.forEach((placeholder, index) => {
  101. const channelBox = document.createElement('textarea');
  102. channelBox.placeholder = placeholder;
  103. channelBox.style.width = '100%';
  104. channelBox.style.height = '40px';
  105. channelBox.style.resize = 'none';
  106. channelBox.style.backgroundColor = '#000000';
  107. channelBox.style.color = '#00FF00';
  108. channelBox.style.display = areChannelsVisible ? 'block' : 'none';
  109. channelBox.value = GM_getValue(`channelBox${index + 1}Value`, '');
  110. channelBox.addEventListener('input', () => {
  111. GM_setValue(`channelBox${index + 1}Value`, channelBox.value);
  112. });
  113. channelBoxes.push(channelBox);
  114. container.appendChild(channelBox);
  115. });
  116.  
  117. const inputBox = document.createElement('textarea');
  118. inputBox.placeholder = 'Enter message (⚠️初回送信時はbotに1回手動でDMを送ってから使用してください。DMチャンネルIDはBOT IDではありません)';
  119. inputBox.style.width = '100%';
  120. inputBox.style.height = '100px';
  121. inputBox.style.resize = 'none';
  122. inputBox.style.backgroundColor = '#000000';
  123. inputBox.style.color = '#00FF00';
  124. inputBox.style.marginTop = '10px';
  125. inputBox.value = GM_getValue('inputBoxValue', '');
  126. inputBox.addEventListener('input', () => {
  127. GM_setValue('inputBoxValue', inputBox.value);
  128. });
  129. inputBox.addEventListener('keydown', (event) => {
  130. if (event.key === 'Enter' && !event.shiftKey) {
  131. event.preventDefault(); // Prevent the default Enter behavior (new line)
  132. const token = tokenBox.value.trim();
  133. if (!token) {
  134. alert('Token is required');
  135. return;
  136. }
  137.  
  138. const message = inputBox.value.trim();
  139. if (!message) {
  140. alert('Message cannot be empty');
  141. return;
  142. }
  143.  
  144. sendMessage(channelId, message, token).then((success) => {
  145. if (success) {
  146. inputBox.value = '';
  147. GM_setValue('inputBoxValue', '');
  148. } else {
  149. alert('Failed to send message');
  150. }
  151. });
  152. }
  153. });
  154. container.appendChild(inputBox);
  155.  
  156. const selectChannelsLabel = document.createElement('div');
  157. selectChannelsLabel.innerText = 'Select channels';
  158. selectChannelsLabel.style.marginTop = '10px';
  159. selectChannelsLabel.style.marginBottom = '5px';
  160. selectChannelsLabel.style.fontSize = '14px';
  161. selectChannelsLabel.style.textAlign = 'left';
  162. container.appendChild(selectChannelsLabel);
  163.  
  164. const buttonContainer = document.createElement('div');
  165. buttonContainer.style.display = 'flex';
  166. buttonContainer.style.justifyContent = 'space-between';
  167.  
  168. const buttonNames = ['荒らし雑談', '情勢雑談', '依頼支部'];
  169.  
  170. channelBoxes.forEach((channelBox, index) => {
  171. const channelButton = document.createElement('button');
  172. channelButton.innerText = buttonNames[index];
  173. channelButton.style.width = '30%';
  174. channelButton.style.backgroundColor = '#575757';
  175. channelButton.style.color = '#ffffff';
  176. channelButton.style.border = 'none';
  177. channelButton.style.borderRadius = '3px';
  178. channelButton.style.cursor = 'pointer';
  179. channelButton.addEventListener('click', () => {
  180. channelId = channelBox.value.trim();
  181. updateButtonStyles(channelButton);
  182. });
  183. buttonContainer.appendChild(channelButton);
  184. });
  185.  
  186. function updateButtonStyles(activeButton) {
  187. Array.from(buttonContainer.children).forEach((button) => {
  188. button.style.backgroundColor = button === activeButton ? '#047500' : '#575757';
  189. });
  190. }
  191.  
  192. container.appendChild(buttonContainer);
  193.  
  194. const sendButton = document.createElement('button');
  195. sendButton.innerText = 'Send DM';
  196. sendButton.style.marginTop = '10px';
  197. sendButton.style.width = '100%';
  198. sendButton.style.backgroundColor = '#575757';
  199. sendButton.style.color = '#ffffff';
  200. sendButton.style.border = 'none';
  201. sendButton.style.borderRadius = '3px';
  202. sendButton.style.cursor = 'pointer';
  203. sendButton.addEventListener('click', async () => {
  204. const token = tokenBox.value.trim();
  205. if (!token) {
  206. alert('Token is required');
  207. return;
  208. }
  209.  
  210. const message = inputBox.value.trim();
  211. if (!message) {
  212. alert('Message cannot be empty');
  213. return;
  214. }
  215.  
  216. const success = await sendMessage(channelId, message, token);
  217. if (success) {
  218. inputBox.value = '';
  219. GM_setValue('inputBoxValue', '');
  220. } else {
  221. alert('Failed to send message');
  222. }
  223. });
  224. container.appendChild(sendButton);
  225.  
  226. const toggleImage = document.createElement('img');
  227. toggleImage.src = 'https://i.imgur.com/FL6WD8a.png';
  228. toggleImage.style.position = 'fixed';
  229. toggleImage.style.width = '30px';
  230. toggleImage.style.height = '30px';
  231. toggleImage.style.cursor = 'pointer';
  232. toggleImage.style.zIndex = '1001';
  233. toggleImage.style.left = '75px';
  234. toggleImage.style.bottom = '222px';
  235. document.body.appendChild(toggleImage);
  236.  
  237. toggleImage.addEventListener('click', () => {
  238. isBoxVisible = !isBoxVisible;
  239. GM_setValue('isBoxVisible', isBoxVisible);
  240. container.style.display = isBoxVisible ? 'block' : 'none';
  241. });
  242.  
  243. function makeElementDraggable(el) {
  244. el.onmousedown = function(event) {
  245. if (event.target.tagName === 'TEXTAREA') return;
  246.  
  247. event.preventDefault();
  248.  
  249. let shiftX = event.clientX - el.getBoundingClientRect().left;
  250. let shiftY = event.clientY - el.getBoundingClientRect().top;
  251.  
  252. function moveAt(pageX, pageY) {
  253. const newLeft = Math.min(Math.max(0, pageX - shiftX), window.innerWidth - el.offsetWidth);
  254. const newTop = Math.min(Math.max(0, pageY - shiftY), window.innerHeight - el.offsetHeight);
  255.  
  256. el.style.left = `${newLeft}px`;
  257. el.style.top = `${newTop}px`;
  258. }
  259.  
  260. function stopDragging() {
  261. document.removeEventListener('mousemove', onMouseMove);
  262. document.removeEventListener('mouseup', stopDragging);
  263. }
  264.  
  265. function onMouseMove(event) {
  266. moveAt(event.pageX, event.pageY);
  267. }
  268.  
  269. document.addEventListener('mousemove', onMouseMove);
  270. document.addEventListener('mouseup', stopDragging);
  271. };
  272.  
  273. el.ondragstart = function() {
  274. return false;
  275. };
  276. }
  277.  
  278. async function sendMessage(channelId, message, token) {
  279. const nonce = generateNonce();
  280. return new Promise((resolve) => {
  281. GM_xmlhttpRequest({
  282. method: 'POST',
  283. url: `https://discord.com/api/v9/channels/${channelId}/messages`,
  284. headers: {
  285. 'Content-Type': 'application/json',
  286. 'Authorization': token
  287. },
  288. data: JSON.stringify({
  289. content: message,
  290. flags: 0,
  291. nonce: nonce,
  292. tts: false
  293. }),
  294. onload: (response) => {
  295. resolve(response.status === 200);
  296. },
  297. onerror: () => resolve(false)
  298. });
  299. });
  300. }
  301.  
  302. function generateNonce() {
  303. const now = Date.now();
  304. return `${now}${Math.floor(Math.random() * 1000)}`;
  305. }
  306. })();