DeepCo Morale Elevator

Toggle emoji picker appended to chat input

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         DeepCo Morale Elevator
// @namespace    deepco
// @version      2026.04.16
// @description  Toggle emoji picker appended to chat input
// @author       M3P / ChatGPT
// @match        https://deepco.app/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=deepco.app
// @license      MIT
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function () {
    'use strict';

    //--------------------------------------------------
    // Default emoji string
    const DEFAULT_EMOJIS = "⚙︎😁👍🥳😉😂🤣😛🤔🤨🙄🧐😱🤯😭😢🐸";

    //--------------------------------------------------
    // Grapheme segmentation
    const segmenter =
          typeof Intl !== 'undefined' && Intl.Segmenter
    ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
    : null;

    function splitEmojis(str) {
        if (!str) return [];
        if (segmenter) {
            return Array.from(segmenter.segment(str), s => s.segment);
        }
        return Array.from(str);
    }

    //--------------------------------------------------
    // Emoji detection
    const EXTENDED_PICTOGRAPHIC_RE = /\p{Extended_Pictographic}/u;
    const REGIONAL_INDICATOR_RE = /^\p{Regional_Indicator}{2}$/u;
    const KEYCAP_RE = /^[0-9#*]\uFE0F?\u20E3$/u;

    function isEmojiLike(grapheme) {
        return (
            EXTENDED_PICTOGRAPHIC_RE.test(grapheme) ||
            REGIONAL_INDICATOR_RE.test(grapheme) ||
            KEYCAP_RE.test(grapheme)
        );
    }

    function uniqueEmojiListFromString(str) {
        const seen = new Set();
        const out = [];

        for (const g of splitEmojis(str)) {
            if (isEmojiLike(g) && !seen.has(g)) {
                seen.add(g);
                out.push(g);
            }
        }

        return out;
    }

    //--------------------------------------------------
    // Storage-backed emoji list
    function loadEmojiList() {
        const raw = GM_getValue('emoji_string', DEFAULT_EMOJIS);

        if (Array.isArray(raw)) {
            return uniqueEmojiListFromString(raw.join(''));
        }

        if (typeof raw === 'string') {
            const list = uniqueEmojiListFromString(raw);
            return list.length ? list : uniqueEmojiListFromString(DEFAULT_EMOJIS);
        }

        return uniqueEmojiListFromString(DEFAULT_EMOJIS);
    }

    let emojiList = loadEmojiList();

    function saveEmojiList() {
        GM_setValue('emoji_string', emojiList.join(''));
    }

    // Optional helper for updating emoji string from console
    window.setEmojiString = function (str) {
        emojiList = uniqueEmojiListFromString(String(str || ''));
        saveEmojiList();
        renderPickerGrid(document.querySelector('#tm-emoji-grid'));
    };

    //--------------------------------------------------
    function isVisible(el) {
        return !!(el && el.offsetParent !== null);
    }

    function renderPickerGrid(grid) {
        if (!grid) return;

        const frag = document.createDocumentFragment();

        for (const emoji of emojiList) {
            const btn = document.createElement('button');
            btn.textContent = emoji;
            btn.type = 'button';
            btn.style.fontSize = '18px';
            btn.style.cursor = 'pointer';
            btn.style.padding = '2px 4px';

            btn.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();

                insertEmoji(emoji);
                promoteEmoji(emoji);
            });
            frag.appendChild(btn);
        }

        grid.replaceChildren(frag);
    }

    function promoteEmoji(emoji) {
        const idx = emojiList.indexOf(emoji);
        if (idx === 0) return false;

        if (idx > -1) {
            emojiList.splice(idx, 1);
        }

        emojiList.unshift(emoji);
        saveEmojiList();
        renderPickerGrid(document.querySelector('#tm-emoji-grid'));
        return true;
    }

    function extractEmojis(text) {
        const found = [];
        const seen = new Set();

        for (const g of splitEmojis(text)) {
            if (isEmojiLike(g) && !seen.has(g)) {
                seen.add(g);
                found.push(g);
            }
        }

        return found;
    }

    function ingestEmojisFromMessage(text) {
        const found = extractEmojis(text);
        if (!found.length) return;

        let changed = false;

        // Reverse the per-message list so the order in the typed message
        // is preserved when items are unshifted onto the front.
        for (const emoji of found.slice().reverse()) {
            const idx = emojiList.indexOf(emoji);

            if (idx === 0) {
                continue;
            }

            if (idx > -1) {
                emojiList.splice(idx, 1);
            }

            emojiList.unshift(emoji);
            changed = true;

            if (idx === -1) {
                console.log(`New Emoji detected! ${emoji}`);
            }
        }

        if (changed) {
            saveEmojiList();
            renderPickerGrid(document.querySelector('#tm-emoji-grid'));
        }
    }

    //--------------------------------------------------
    function insertEmoji(emoji) {
        const el = document.querySelector('#message');
        if (!el) return;

        if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
            const start = el.selectionStart ?? el.value.length;
            const end = el.selectionEnd ?? el.value.length;
            el.value = el.value.slice(0, start) + emoji + el.value.slice(end);
            el.selectionStart = el.selectionEnd = start + emoji.length;
            el.dispatchEvent(new Event('input', { bubbles: true }));
            return;
        }

        if (el.isContentEditable) {
            document.execCommand('insertText', false, emoji);
        }
    }

    //--------------------------------------------------
    function buildPicker() {
        const picker = document.createElement('div');
        picker.id = 'tm-emoji-picker';
        Object.assign(picker.style, {
            display: 'none',
            marginTop: '6px',
            padding: '6px',
            border: '1px solid #555',
            borderRadius: '6px',
            background: '#222',
        });

        const grid = document.createElement('div');
        grid.id = 'tm-emoji-grid';
        Object.assign(grid.style, {
            display: 'flex',
            flexWrap: 'wrap',
            gap: '4px',
        });

        picker.appendChild(grid);
        renderPickerGrid(grid);


        return picker;
    }

    function bindSubmissionTracking(input) {
        if (input.dataset.tmEmojiHooked) return;
        input.dataset.tmEmojiHooked = '1';

        const processCurrentMessage = () => {
            ingestEmojisFromMessage(input.value);
        };

        const form = input.closest('form');
        if (form) {
            form.addEventListener(
                'submit',
                () => {
                    processCurrentMessage();
                },
                true
            );
            return;
        }

        // Fallback for non-form submit flows
        input.addEventListener('keydown', e => {
            if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
                setTimeout(processCurrentMessage, 0);
            }
        });
    }

    //--------------------------------------------------
    function inject() {
        const input = document.querySelector('#message');
        if (input) {
            bindSubmissionTracking(input);
        }

        const panel = document.querySelector('#chat');
        if (!isVisible(panel)) return;

        const title = [...panel.querySelectorAll('h2')].find(
            h => h.textContent.trim() === 'Comm Terminal'
        );
        if (!title) return;

        const headerContainer = title.parentElement?.parentElement;
        if (!headerContainer) return;

        if (!document.querySelector('#tm-emoji-toggle')) {
            const toggle = document.createElement('button');
            toggle.id = 'tm-emoji-toggle';
            toggle.textContent = '😁';
            toggle.type = 'button';
            toggle.style.marginLeft = '8px';
            toggle.style.fontSize = '20px';
            toggle.style.cursor = 'pointer';

            toggle.addEventListener("click", (e)=>{
                e.preventDefault();
                e.stopPropagation();

                const picker = document.querySelector('#tm-emoji-picker');
                if (!picker) return;

                picker.style.display =
                    picker.style.display === "none" ? "block" : "none";
            });

            headerContainer.style.display = 'flex';
            headerContainer.style.alignItems = 'center';

            title.parentElement.insertAdjacentElement('afterend', toggle);

            const chatpanel = document.querySelector('#chat-messages');
            if (chatpanel?.parentElement && !document.querySelector('#tm-emoji-picker')) {
                chatpanel.parentElement.prepend(buildPicker());
            }
        }
    }

    //--------------------------------------------------
    const observer = new MutationObserver(() => {
        inject();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['class', 'style'],
    });

    inject();
})();