Edit EVERY message!(discord)

Same code(from function to end) can also work on Discord Desktop if you open devtools and run it on Console

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Advertisement:

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

Advertisement:

// ==UserScript==
// @name         Edit EVERY message!(discord)
// @namespace    https://example.org
// @license      MIT
// @version      0.1.3
// @description  Same code(from function to end) can also work on Discord Desktop if you open devtools and run it on Console
// @author       Lio
// @match        https://discord.com/*
// @grant        none
// @noframes
// ==/UserScript==

(function () {
    "use strict";

    const EDIT_BUTTON = "local-edit-button";
    const CONTROL_BOX = "local-edit-controls";
    const STYLE_ID = "local-edit-styles";

    let observer = null;
    let editButtonsHidden = false;

    // Opens an inline textarea editor in place of `content`.
    // `compact` is used for the small quoted-reply snippet, which lives in
    // a narrow, height-clipped single-line row and breaks if given the
    // full-size editor without first relaxing that row's constraints.
    function startEdit(content, compact) {

        if (!content.dataset.originalText) {
            content.dataset.originalText = content.innerText;
        }

        // The reply row (and sometimes its parent too) is usually styled
        // with overflow:hidden / white-space:nowrap / a fixed height to
        // keep the quoted snippet to one truncated line. Save their
        // current inline styles and relax those constraints for as long
        // as we're editing, restoring them afterwards.
        const relaxedAncestors = [];

        if (compact) {
            let cur = content.parentElement;
            let levels = 0;

            while (cur && levels < 3) {
                relaxedAncestors.push({
                    el: cur,
                    cssText: cur.style.cssText
                });

                cur.style.overflow = "visible";
                cur.style.whiteSpace = "normal";
                cur.style.height = "auto";
                cur.style.maxHeight = "none";
                cur.style.textOverflow = "clip";
                cur.style.alignItems = "flex-start";

                cur = cur.parentElement;
                levels++;
            }
        }

        function restoreAncestors() {
            relaxedAncestors.forEach(({ el, cssText }) => {
                el.style.cssText = cssText;
            });
        }

        const textarea = document.createElement("textarea");
        textarea.value = content.innerText;

        textarea.style.cssText = compact ? `
            display:block;
            width:100%;
            min-width:0;
            min-height:20px;
            max-height:160px;
            background:#2b2d31;
            color:#dbdee1;
            font-size:13.5px;
            line-height:1.3;
            font-family:inherit;
            border:1px solid #5865F2;
            border-radius:4px;
            padding:2px 6px;
            resize:vertical;
            box-sizing:border-box;
        ` : `
            display:block;
            width:100%;
            min-width:0;
            min-height:45px;
            background:#2b2d31;
            color:white;
            border:1px solid #5865F2;
            border-radius:5px;
            padding:6px;
            resize:vertical;
            box-sizing:border-box;
        `;

        const save = document.createElement("button");
        save.textContent = "Save";

        const cancel = document.createElement("button");
        cancel.textContent = "Cancel";

        const btnStyle = compact ? `
            border:none;
            border-radius:4px;
            padding:1px 6px;
            font-size:11px;
            line-height:1.6;
            cursor:pointer;
            flex:0 0 auto;
        ` : `
            border:none;
            border-radius:4px;
            padding:5px 10px;
            cursor:pointer;
            flex:0 0 auto;
        `;

        save.style.cssText = `background:#5865F2; color:white; ${btnStyle}`;
        cancel.style.cssText = `margin-left:5px; background:#ed4245; color:white; ${btnStyle}`;

        const row = document.createElement("div");
        row.style.cssText = `
            margin-top:${compact ? "2px" : "5px"};
            display:flex;
            flex-wrap:wrap;
            align-items:center;
        `;

        row.append(save, cancel);

        const wrapper = document.createElement("div");
        wrapper.style.cssText = `
            display:block;
            width:100%;
            min-width:0;
            max-width:100%;
            box-sizing:border-box;
            position:relative;
        `;

        wrapper.append(textarea, row);

        // --- PREVENT DISCORD REPLY NAV JUMP / FOCUS INTERFERING ---
        // We block these in the bubble phase (false) so that the textarea itself still 
        // receives the events natively (allowing typing, cursor placement, text selection, etc.),
        // while completely preventing Discord's parent containers from detecting them.
        const stopEvents = (e) => {
            e.stopPropagation();
        };

        const eventsToBlock = ["click", "mousedown", "mouseup", "pointerdown", "pointerup", "keydown", "keyup", "keypress"];
        eventsToBlock.forEach(eventName => {
            wrapper.addEventListener(eventName, stopEvents, false);
        });

        content.replaceWith(wrapper);

        // Compact (reply snippet) editors start at content height and grow
        // with input, instead of carrying the full message editor's fixed
        // min-height which would blow out the narrow reply row.
        if (compact) {
            const fit = () => {
                textarea.style.height = "auto";
                textarea.style.height = Math.min(textarea.scrollHeight, 160) + "px";
            };

            fit();
            textarea.addEventListener("input", fit);
        }

        save.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
            
            // Defer replacement so the click/mousedown event safely finishes stopping first
            setTimeout(() => {
                content.textContent = textarea.value;
                restoreAncestors();
                wrapper.replaceWith(content);
            }, 0);
        };

        cancel.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
            
            // Defer replacement so the click/mousedown event safely finishes stopping first
            setTimeout(() => {
                restoreAncestors();
                wrapper.replaceWith(content);
            }, 0);
        };

        textarea.focus();
    }

    function makeEditIcon(icon, color, title, rightOffset) {
        const button = document.createElement("button");

        button.className = EDIT_BUTTON;
        button.textContent = icon;
        button.title = title;

        button.style.cssText = `
            position:absolute;
            top:3px;
            right:${rightOffset};
            width:18px;
            height:18px;
            display:flex;
            align-items:center;
            justify-content:center;
            padding:0;
            border:none;
            border-radius:4px;
            background:${color};
            color:white;
            font-size:12px;
            cursor:pointer;
            opacity:.5;
            z-index:20;
        `;

        return button;
    }

    function addEditButton(message) {
        if (message.dataset.localEditInjected)
            return;

        const container = message.querySelector(".message__5126c");

        // A reply renders the quoted snippet ABOVE the real message, and
        // both happen to use an id starting with "message-content-" (the
        // snippet uses the id of the message being replied to). Grabbing
        // the first match — like a plain querySelector would — picks the
        // quoted snippet instead of the actual message. The real message
        // is always the LAST one in DOM order.
        const contents = [...message.querySelectorAll('[id^="message-content-"]')];

        if (!container || contents.length === 0)
            return;

        message.dataset.localEditInjected = "true";
        container.style.position = "relative";

        const mainContent = contents[contents.length - 1];
        const repliedContent = contents.length > 1 ? contents[0] : null;

        const editButton = makeEditIcon(
            "✎",
            "#5865F2",
            "Edit locally",
            "3px"
        );

        editButton.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            // Defer showing the edit box to let the button click event finish bubbling away
            setTimeout(() => {
                startEdit(mainContent, false);
            }, 0);
        };

        container.appendChild(editButton);

        // Only present when this message is a reply: a second, separate
        // button that edits the quoted snippet instead of the real message.
        if (repliedContent) {
            const replyEditButton = makeEditIcon(
                "↩",
                "#3ba55c",
                "Edit replied message",
                "23px"
            );

            replyEditButton.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                // Defer showing the edit box to let the button click event finish bubbling away
                setTimeout(() => {
                    startEdit(repliedContent, true);
                }, 0);
            };

            container.appendChild(replyEditButton);
        }
    }

    // --- REMAINDER OF UTILITIES & OBSERVERS STAY UNCHANGED ---
    function scanMessages() {
        document.querySelectorAll('li[id^="chat-messages-"]').forEach(addEditButton);
    }

    function applyGlobalCSS() {
        let styleEl = document.getElementById(STYLE_ID);
        if (!styleEl) {
            styleEl = document.createElement("style");
            styleEl.id = STYLE_ID;
            document.head.appendChild(styleEl);
        }

        styleEl.textContent = editButtonsHidden ? `.${EDIT_BUTTON} { display: none !important; }` : '';
    }

    function clearGlobalCSS() {
        const styleEl = document.getElementById(STYLE_ID);
        if (styleEl) {
            styleEl.remove();
        }
    }

    function createControls() {
        const existing = document.querySelector("." + CONTROL_BOX);
        if (existing) return;

        // Targets the root wrapper holding the direct home element and guild container list
        const treeRoot = document.querySelector('nav[class*="guilds_"] [class*="tree_"]');
        if (!treeRoot) return;

        const box = document.createElement("div");
        box.className = CONTROL_BOX;

        // Clean layout styles: centered, slight margin bottom to create real space above home button
        box.style.cssText = `
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;
            gap: 5px;
            width: 100%;
            padding: 8px 0 4px 0;
            z-index: 9999;
            box-sizing: border-box;
        `;

        function makeButton(icon, color, title) {
            const b = document.createElement("button");
            b.textContent = icon;
            b.title = title;
            b.style.cssText = `
                width: 16px;
                height: 16px;
                display: flex;
                align-items: center;
                justify-content: center;
                border: none;
                border-radius: 50%;
                background: ${color};
                color: white;
                font-size: 9px;
                line-height: 1;
                cursor: pointer;
                box-shadow: 0 1px 3px rgba(0,0,0,.4);
                flex: 0 0 auto;
            `;
            return b;
        }

        const reset = makeButton("🔄", "#5865F2", "Reset edits");
        reset.onclick = () => {
            document.querySelectorAll("[data-original-text]").forEach(el => {
                el.textContent = el.dataset.originalText;
                delete el.dataset.originalText;
            });
        };

        const toggleVisibility = makeButton(
            editButtonsHidden ? "👁️" : "😑",
            "#23a55a",
            "Toggle edit buttons visibility"
        );

        toggleVisibility.onclick = () => {
            editButtonsHidden = !editButtonsHidden;
            toggleVisibility.textContent = editButtonsHidden ? "👁️" : "😑";
            applyGlobalCSS();
        };

        const remove = makeButton("✖", "#ed4245", "Remove editor");
        remove.onclick = () => {
            document.querySelectorAll("." + EDIT_BUTTON).forEach(e => e.remove());
            document.querySelectorAll('li[id^="chat-messages-"]').forEach(e => delete e.dataset.localEditInjected);
            if (observer) observer.disconnect();
            clearGlobalCSS();
            box.remove();
        };

        box.append(reset, toggleVisibility, remove);

        // Prepend directly into the structural root layout panel to natively offset everything below it
        treeRoot.insertBefore(box, treeRoot.firstChild);
        applyGlobalCSS();
    }

    scanMessages();
    createControls();

    observer = new MutationObserver(() => {
        scanMessages();
        createControls();
    });

    observer.observe(
        document.body,
        {
            childList: true,
            subtree: true
        }
    );

    console.log("Local editor loaded");

})();