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 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Discord DM Sender with Input Box and Toggle
// @namespace    http://tampermonkey.net/
// @version      2.2
// @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.
// @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);
    });
    inputBox.addEventListener('keydown', (event) => {
        if (event.key === 'Enter' && !event.shiftKey) {
            event.preventDefault(); // Prevent the default Enter behavior (new line)
            
            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;
            }

            sendMessage(channelId, message, token).then((success) => {
                if (success) {
                    inputBox.value = '';
                    GM_setValue('inputBoxValue', '');
                } else {
                    alert('Failed to send message');
                }
            });
        }
    });
    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 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) {
                const newLeft = Math.min(Math.max(0, pageX - shiftX), window.innerWidth - el.offsetWidth);
                const newTop = Math.min(Math.max(0, pageY - shiftY), window.innerHeight - el.offsetHeight);

                el.style.left = `${newLeft}px`;
                el.style.top = `${newTop}px`;
            }

            function stopDragging() {
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', stopDragging);
            }

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

            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', stopDragging);
        };

        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)}`;
    }
})();