Discord DM Sender with Input Box and Toggle

Send DM messages via Discord API with toggle visibility, customizable button names, and instructions

As of 17. 04. 2025. See the latest version.

// ==UserScript==
// @name         Discord DM Sender with Input Box and Toggle
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Send DM messages via Discord API with toggle visibility, customizable button names, and instructions
// @author       Your Name
// @match        https://discord.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @license      You can modify as long as you credit me
// ==/UserScript==

(function() {
    'use strict';

    let isBoxVisible = GM_getValue('isBoxVisible', true);
    let isTokenVisible = GM_getValue('isTokenVisible', true);
    let areChannelsVisible = GM_getValue('areChannelsVisible', true);
    let channelId = '';

    const initialWidth = '280px';
    const initialHeight = '500px';

   
    const container = document.createElement('div');
    container.style.position = 'fixed';
    container.style.bottom = '10px';
    container.style.left = '10px';
    container.style.backgroundColor = '#2f3136';
    container.style.color = '#ffffff';
    container.style.padding = '10px';
    container.style.borderRadius = '5px';
    container.style.zIndex = '1000';
    container.style.width = initialWidth;
    container.style.height = initialHeight;
    container.style.maxHeight = '90vh';
    container.style.overflow = 'auto';
    container.style.display = isBoxVisible ? 'block' : 'none';
    document.body.appendChild(container);

    makeElementDraggable(container);

    
    const hideTokenButton = document.createElement('button');
    hideTokenButton.innerText = isTokenVisible ? 'Hide Token' : 'View Token';
    hideTokenButton.style.marginBottom = '10px';
    hideTokenButton.style.width = '100%';
    hideTokenButton.style.backgroundColor = '#575757';
    hideTokenButton.style.color = '#ffffff';
    hideTokenButton.style.border = 'none';
    hideTokenButton.style.borderRadius = '3px';
    hideTokenButton.style.cursor = 'pointer';
    hideTokenButton.addEventListener('click', () => {
        isTokenVisible = !isTokenVisible;
        GM_setValue('isTokenVisible', isTokenVisible);
        tokenBox.style.display = isTokenVisible ? 'block' : 'none';
        hideTokenButton.innerText = isTokenVisible ? 'Hide Token' : 'View Token';
    });
    container.appendChild(hideTokenButton);

    
    const tokenBox = document.createElement('textarea');
    tokenBox.placeholder = 'Enter your token';
    tokenBox.style.width = '100%';
    tokenBox.style.height = '40px';
    tokenBox.style.resize = 'none';
    tokenBox.style.backgroundColor = '#000000';
    tokenBox.style.color = '#00FF00';
    tokenBox.style.display = isTokenVisible ? 'block' : 'none';
    tokenBox.value = GM_getValue('tokenBoxValue', '');
    tokenBox.addEventListener('input', () => {
        GM_setValue('tokenBoxValue', tokenBox.value);
    });
    container.appendChild(tokenBox);

    
    const toggleChannelsButton = document.createElement('button');
    toggleChannelsButton.innerText = areChannelsVisible ? 'Hide Channel IDs' : 'View Channel IDs';
    toggleChannelsButton.style.marginTop = '10px';
    toggleChannelsButton.style.width = '100%';
    toggleChannelsButton.style.backgroundColor = '#575757';
    toggleChannelsButton.style.color = '#ffffff';
    toggleChannelsButton.style.border = 'none';
    toggleChannelsButton.style.borderRadius = '3px';
    toggleChannelsButton.style.cursor = 'pointer';
    toggleChannelsButton.addEventListener('click', () => {
        areChannelsVisible = !areChannelsVisible;
        GM_setValue('areChannelsVisible', areChannelsVisible);
        channelBoxes.forEach((channelBox) => {
            channelBox.style.display = areChannelsVisible ? 'block' : 'none';
        });
        toggleChannelsButton.innerText = areChannelsVisible ? 'Hide Channel IDs' : 'View Channel IDs';
    });
    container.appendChild(toggleChannelsButton);

   
    const channelBoxes = [];
    const channelBoxPlaceholders = [
        '荒らし雑談用匿名BOTのDMチャンネルIDを入力',
        '情勢雑談用BOTの匿名BOTのDMチャンネルIDを入力',
        '依頼支部用匿名BOTのDMチャンネルIDを入力'
    ];

    channelBoxPlaceholders.forEach((placeholder, index) => {
        const channelBox = document.createElement('textarea');
        channelBox.placeholder = placeholder;
        channelBox.style.width = '100%';
        channelBox.style.height = '40px';
        channelBox.style.resize = 'none';
        channelBox.style.backgroundColor = '#000000';
        channelBox.style.color = '#00FF00';
        channelBox.style.display = areChannelsVisible ? 'block' : 'none';
        channelBox.value = GM_getValue(`channelBox${index + 1}Value`, '');
        channelBox.addEventListener('input', () => {
            GM_setValue(`channelBox${index + 1}Value`, channelBox.value);
        });
        channelBoxes.push(channelBox);
        container.appendChild(channelBox);
    });

   
    const inputBox = document.createElement('textarea');
    inputBox.placeholder = 'Enter message (⚠️初回送信時はbotに1回手動でDMを送ってから使用してください。DMチャンネルIDはBOT IDではありません)';
    inputBox.style.width = '100%';
    inputBox.style.height = '100px';
    inputBox.style.resize = 'none';
    inputBox.style.backgroundColor = '#000000';
    inputBox.style.color = '#00FF00';
    inputBox.style.marginTop = '10px';
    inputBox.value = GM_getValue('inputBoxValue', '');
    inputBox.addEventListener('input', () => {
        GM_setValue('inputBoxValue', inputBox.value);
    });
    container.appendChild(inputBox);

    
    const selectChannelsLabel = document.createElement('div');
    selectChannelsLabel.innerText = 'Select channels';
    selectChannelsLabel.style.marginTop = '10px';
    selectChannelsLabel.style.marginBottom = '5px';
    selectChannelsLabel.style.fontSize = '14px';
    selectChannelsLabel.style.textAlign = 'left'; 
    container.appendChild(selectChannelsLabel);

 
    const buttonContainer = document.createElement('div');
    buttonContainer.style.display = 'flex';
    buttonContainer.style.justifyContent = 'space-between';


    const buttonNames = ['荒らし雑談', '情勢雑談', '依頼支部'];

    channelBoxes.forEach((channelBox, index) => {
        const channelButton = document.createElement('button');
        channelButton.innerText = buttonNames[index]; 
        channelButton.style.width = '30%';
        channelButton.style.backgroundColor = '#575757';
        channelButton.style.color = '#ffffff';
        channelButton.style.border = 'none';
        channelButton.style.borderRadius = '3px';
        channelButton.style.cursor = 'pointer';
        channelButton.addEventListener('click', () => {
            channelId = channelBox.value.trim();
            updateButtonStyles(channelButton);
        });
        buttonContainer.appendChild(channelButton);
    });

    function updateButtonStyles(activeButton) {
        Array.from(buttonContainer.children).forEach((button) => {
            button.style.backgroundColor = button === activeButton ? '#047500' : '#575757';
        });
    }

    container.appendChild(buttonContainer);

    
    const sendButton = document.createElement('button');
    sendButton.innerText = 'Send DM';
    sendButton.style.marginTop = '10px';
    sendButton.style.width = '100%';
    sendButton.style.backgroundColor = '#575757';
    sendButton.style.color = '#ffffff';
    sendButton.style.border = 'none';
    sendButton.style.borderRadius = '3px';
    sendButton.style.cursor = 'pointer';
    sendButton.addEventListener('click', async () => {
        const token = tokenBox.value.trim();
        if (!token) {
            alert('Token is required');
            return;
        }

        const message = inputBox.value.trim();
        if (!message) {
            alert('Message cannot be empty');
            return;
        }

        const success = await sendMessage(channelId, message, token);
        if (success) {
            inputBox.value = '';
            GM_setValue('inputBoxValue', '');
        } else {
            alert('Failed to send message');
        }
    });
    container.appendChild(sendButton);

   
    const warningNote = document.createElement('div');
    warningNote.innerText = '';
    warningNote.style.marginTop = '10px';
    warningNote.style.fontSize = '12px';
    warningNote.style.textAlign = 'left'; 
    container.appendChild(warningNote);

   
    const toggleImage = document.createElement('img');
    toggleImage.src = 'https://i.imgur.com/FL6WD8a.png'; 
    toggleImage.style.position = 'fixed';
    toggleImage.style.width = '30px';
    toggleImage.style.height = '30px';
    toggleImage.style.cursor = 'pointer';
    toggleImage.style.zIndex = '1001';
    toggleImage.style.left = '75px';
    toggleImage.style.bottom = '222px';
    document.body.appendChild(toggleImage);

    toggleImage.addEventListener('click', () => {
        isBoxVisible = !isBoxVisible;
        GM_setValue('isBoxVisible', isBoxVisible);
        container.style.display = isBoxVisible ? 'block' : 'none';
    });

    function makeElementDraggable(el) {
        el.onmousedown = function(event) {
            if (event.target.tagName === 'TEXTAREA') return;

            event.preventDefault();

            let shiftX = event.clientX - el.getBoundingClientRect().left;
            let shiftY = event.clientY - el.getBoundingClientRect().top;

            function moveAt(pageX, pageY) {
                el.style.left = Math.min(Math.max(0, pageX - shiftX), window.innerWidth - el.offsetWidth) + 'px';
                el.style.top = Math.min(Math.max(0, pageY - shiftY), window.innerHeight - el.offsetHeight) + 'px';
            }

            function onMouseMove(event) {
                moveAt(event.pageX, event.pageY);
            }

            document.addEventListener('mousemove', onMouseMove);

            el.onmouseup = function() {
                document.removeEventListener('mousemove', onMouseMove);
                el.onmouseup = null;
            };
        };

        el.ondragstart = function() {
            return false;
        };
    }

    async function sendMessage(channelId, message, token) {
        const nonce = generateNonce();
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: `https://discord.com/api/v9/channels/${channelId}/messages`,
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': token
                },
                data: JSON.stringify({
                    content: message,
                    flags: 0,
                    nonce: nonce,
                    tts: false
                }),
                onload: (response) => {
                    resolve(response.status === 200);
                },
                onerror: () => resolve(false)
            });
        });
    }

    function generateNonce() {
        const now = Date.now();
        return `${now}${Math.floor(Math.random() * 1000)}`;
    }
})();