Greasy Fork is available in English.
Toggle emoji picker appended to chat input
// ==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();
})();