// ==UserScript== // @name Irodori-TTS Emoji Palette (Invincible v2.8) // @namespace http://tampermonkey.net/ // @version 2.8 // @description 絶対に見切れない&画面更新で消えない最強版パレット // @author Gemini // @match http://127.0.0.1:7860/* // @match http://localhost:7860 // @grant none // ==/UserScript== (function() { 'use strict'; // 公式ドキュメントに基づいた正確な39種類 const CATEGORIES = [ { name: "感情・表情", emojis: [ { char: "😊", label: "楽しげ" }, { char: "😆", label: "喜び" }, { char: "🤭", label: "笑い" }, { char: "😏", label: "からかう" }, { char: "😌", label: "安堵" }, { char: "😭", label: "嗚咽" }, { char: "😠", label: "怒り" }, { char: "😱", label: "悲鳴" }, { char: "😲", label: "驚き" }, { char: "😰", label: "動揺" }, { char: "😒", label: "舌打ち" }, { char: "🤔", label: "疑問" }, { char: "🥴", label: "酔っ払い" }, { char: "🫣", label: "恥ずかしそう" }, { char: "🥺", label: "震える声" }, { char: "😪", label: "眠そうに" }, { char: "😖", label: "苦しげ" }, { char: "😟", label: "心配" }, { char: "🙄", label: "呆れ" }, { char: "🥵", label: "喘ぎ・うめき" }, { char: "🫶", label: "優しく" } ] }, { name: "呼吸・生理・発声", emojis: [ { char: "🌬️", label: "息切れ" }, { char: "😮‍💨", label: "吐息" }, { char: "😮", label: "息をのむ" }, { char: "🥱", label: "あくび" }, { char: "🤧", label: "咳・くしゃみ" }, { char: "🥤", label: "飲む音" }, { char: "👅", label: "舐める音" }, { char: "💋", label: "リップノイズ" }, { char: "🤐", label: "口を塞がれて" }, { char: "🎵", label: "鼻歌" } ] }, { name: "動作・記号・演出", emojis: [ { char: "👂", label: "囁き" }, { char: "📢", label: "エコー" }, { char: "📞", label: "電話越し" }, { char: "🐢", label: "ゆっくり" }, { char: "⏸️", label: "間・沈黙" }, { char: "⏩", label: "早口" }, { char: "👌", label: "相槌" }, { char: "🙏", label: "懇願" } ] } ]; let lastActiveTextarea = null; document.addEventListener('focusin', (e) => { if (e.target.tagName === 'TEXTAREA') lastActiveTextarea = e.target; }); function injectEmoji(emoji) { const textarea = lastActiveTextarea || document.querySelector('textarea[data-testid="textbox"]'); if (!textarea) return; const start = textarea.selectionStart; const text = textarea.value; const newValue = text.substring(0, start) + emoji + text.substring(textarea.selectionEnd); const descriptor = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value"); descriptor.set.call(textarea, newValue); textarea.dispatchEvent(new Event('input', { bubbles: true })); textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.focus(); setTimeout(() => { const pos = start + emoji.length; textarea.setSelectionRange(pos, pos); window.getSelection().removeAllRanges(); }, 20); } // パレットの開閉状態を記憶しておく変数 let isPaletteOpen = false; function createPalette() { if (document.getElementById('irodori-emoji-fixed-bar')) return; const wrapper = document.createElement('div'); wrapper.id = 'irodori-emoji-fixed-bar'; Object.assign(wrapper.style, { position: 'fixed', bottom: '0', left: '0', width: '100%', backgroundColor: '#262626', borderTop: '2px solid #ff9800', zIndex: '2147483647', // 絶対に他の要素に隠れない最強のz-index boxShadow: '0 -2px 10px rgba(0,0,0,0.8)', display: 'flex', flexDirection: 'column' }); const toggleBtn = document.createElement('div'); toggleBtn.innerText = isPaletteOpen ? '▼ Close Palette ▼' : '▲ Irodori Emoji Palette ▲'; Object.assign(toggleBtn.style, { height: '25px', backgroundColor: '#ff9800', color: '#000', fontSize: '12px', fontWeight: 'bold', textAlign: 'center', lineHeight: '25px', cursor: 'pointer', userSelect: 'none', flexShrink: '0' }); const content = document.createElement('div'); Object.assign(content.style, { padding: '10px 10px 20px 10px', maxHeight: '40vh', // 画面の高さの40%に制限(絶対に見切れない) overflowY: 'auto', display: isPaletteOpen ? 'block' : 'none' }); toggleBtn.onclick = () => { isPaletteOpen = !isPaletteOpen; content.style.display = isPaletteOpen ? 'block' : 'none'; toggleBtn.innerText = isPaletteOpen ? '▼ Close Palette ▼' : '▲ Irodori Emoji Palette ▲'; }; CATEGORIES.forEach(cat => { const catLabel = document.createElement('div'); catLabel.innerText = cat.name; Object.assign(catLabel.style, { color: '#ff9800', fontSize: '11px', margin: '8px 0 4px', borderBottom: '1px solid #444' }); content.appendChild(catLabel); const grid = document.createElement('div'); Object.assign(grid.style, { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(110px, 1fr))', gap: '6px' }); cat.emojis.forEach(item => { const btn = document.createElement('div'); Object.assign(btn.style, { display: 'flex', alignItems: 'center', gap: '5px', padding: '5px', backgroundColor: '#333', borderRadius: '4px', cursor: 'pointer', border: '1px solid #444' }); btn.innerHTML = `${item.char}${item.label}`; btn.onclick = () => injectEmoji(item.char); btn.onmouseover = () => { btn.style.backgroundColor = '#444'; btn.style.borderColor = '#ff9800'; }; btn.onmouseout = () => { btn.style.backgroundColor = '#333'; btn.style.borderColor = '#444'; }; grid.appendChild(btn); }); content.appendChild(grid); }); wrapper.appendChild(toggleBtn); wrapper.appendChild(content); document.body.appendChild(wrapper); } // 初回起動 setTimeout(createPalette, 1000); // 【重要】画面遷移などでパレットが消されても、2秒ごとに生存確認して自動復活させる setInterval(() => { if (!document.getElementById('irodori-emoji-fixed-bar')) { createPalette(); } }, 2000); })();