// ==UserScript== // @name 花火御用表情包面板 一键爆炸 // @namespace https://deepflood.com/ // @version 0.1 // @description 可自定义添加/删除表情,点击表情包直接插入并发送,带磨砂质感浮动面板与开关 + 发送提示 // @author Sparkle // @license MIT // @match *://www.deepflood.com/* // @match *://www.nodeseek.com/* // @grant none // @icon https://img.meituan.net/video/1f498ca05808be0e7a8a837d4e51e995233496.png // @downloadURL none // ==/UserScript==rrript== // @name 花火御用表情包面板 一键爆炸 // @namespace https://deepflood.com/ // @version 0.1 // @description 可自定义添加/删除表情,点击表情包直接插入并发送,带磨砂质感浮动面板与开关 + 发送提示 // @author Sparkle // @match *://www.deepflood.com/* // @match *://www.nodeseek.com/* // @grant none // @icon https://img.meituan.net/video/1f498ca05808be0e7a8a837d4e51e995233496.png // ==/UserScript== (function () { 'use strict'; // GitHub仓库配置 const REPO_BASE_URL = "https://cdn.jsdelivr.net/gh/1143520/doro@main/loop/"; const REPO_API_URL = "https://api.github.com/repos/1143520/doro/contents/loop"; // 图片处理配置 const USE_IMAGE_PROXY = true; // 是否使用图片处理服务来调整尺寸 const IMAGE_PROXY_URL = "https://wsrv.nl/?url="; // 图片处理服务(用于尺寸调整) const TARGET_SIZE = "60"; // 目标尺寸 // 默认表情列表 - 将在异步加载后填充 let defaultEmojiList = []; let allGifFiles = []; // 存储所有GIF文件名 let isLoading = true; // 从GitHub API获取所有GIF文件列表 async function fetchAllGifFiles() { try { console.log("🔄 开始从GitHub获取表情包列表..."); const response = await fetch(REPO_API_URL); const files = await response.json(); // 筛选出所有.gif文件 allGifFiles = files .filter(file => file.name.endsWith('.gif') && file.type === 'file') .map(file => file.name); console.log(`✅ 成功加载 ${allGifFiles.length} 个表情包`); // 随机选择20个 defaultEmojiList = getRandomEmojis(20); isLoading = false; // 渲染表情 renderEmojis(); } catch (error) { console.error("❌ 获取表情包列表失败:", error); // 如果API失败,使用备用列表 allGifFiles = [ "1735348712826.gif", "1735348724291.gif", "1735348726658.gif", "1735348736520.gif", "1735348738391.gif", "1735348747247.gif", "1735348751230.gif", "1735348761071.gif", "1735348763774.gif", "1735348770585.gif", "2314666038.gif", "2314666040.gif", "2314666044.gif", "2422329068.gif", "2422329071.gif", "2422329072.gif", "2437195856.gif", "2437195898.gif", "2437195910.gif", "2437195912.gif" ]; defaultEmojiList = getRandomEmojis(20); isLoading = false; renderEmojis(); } } // 随机选择表情包 function getRandomEmojis(count = 20) { if (allGifFiles.length === 0) return []; const shuffled = [...allGifFiles].sort(() => Math.random() - 0.5); const selected = shuffled.slice(0, Math.min(count, allGifFiles.length)); return selected.map(filename => REPO_BASE_URL + filename); } // 刷新表情包列表(重新随机选择) function refreshEmojis() { if (allGifFiles.length === 0) { showToast("❌ 表情包列表为空,无法刷新"); console.error("allGifFiles is empty"); return; } console.log(`🔄 刷新前: ${defaultEmojiList.length} 个表情`); console.log(`📦 表情池总数: ${allGifFiles.length} 个`); defaultEmojiList = getRandomEmojis(20); console.log(`✅ 刷新后: ${defaultEmojiList.length} 个表情`); console.log(`🎲 随机表情:`, defaultEmojiList.slice(0, 3).map(url => url.split('/').pop())); renderEmojis(); showToast(`🔄 已刷新!(共${allGifFiles.length}个表情池)`); } // --- 新增功能:全局变量 --- const STORAGE_KEY = 'hanabi_custom_emojis'; let isDeleteMode = false; let customEmojiList = []; // --- 新增功能:本地存储操作 --- function loadCustomEmojis() { try { const stored = localStorage.getItem(STORAGE_KEY); return stored ? JSON.parse(stored) : []; } catch (e) { console.error("加载自定义表情失败", e); return []; } } function saveCustomEmojis(emojis) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(emojis)); } catch (e) { console.error("保存自定义表情失败", e); } } function findInputElement() { const selectors = [ 'textarea[name="message"]', 'textarea[placeholder*="输入"]', 'textarea[placeholder*="回复"]', 'textarea[placeholder*="说点什么"]', 'input[type="text"][name="message"]', 'input[type="text"][placeholder*="输入"]', '.editor-input textarea', '.message-input textarea', '.chat-input textarea', '.reply-box textarea', '.comment-box textarea', 'textarea.form-control', 'textarea', 'input[type="text"]' ]; for (const selector of selectors) { const el = document.querySelector(selector); if (el && !el.disabled && !el.readOnly && el.offsetWidth > 0 && el.offsetHeight > 0) return el; } const focused = document.activeElement; return (focused && (focused.tagName === 'TEXTAREA' || (focused.tagName === 'INPUT' && focused.type === 'text'))) ? focused : null; } function insertTextAtCursor(el, text) { if (!el) return false; el.focus(); if (document.execCommand) document.execCommand('insertText', false, text); else if (el.setRangeText) { const s = el.selectionStart || 0, e = el.selectionEnd || 0; el.setRangeText(text, s, e, 'end'); } else { const s = el.selectionStart || el.value.length; const before = el.value.substring(0, s); const after = el.value.substring(el.selectionEnd || el.value.length); el.value = before + text + after; el.selectionStart = el.selectionEnd = s + text.length; } el.dispatchEvent(new Event('input', { bubbles: true })); return true; } function showToast(msg) { const toast = document.createElement("div"); toast.textContent = msg; Object.assign(toast.style, { position: "fixed", bottom: "90px", right: "20px", padding: "10px 20px", borderRadius: "12px", background: "rgba(255,255,255,0.3)", backdropFilter: "blur(10px) saturate(180%)", color: "#fff", fontWeight: "500", fontSize: "15px", boxShadow: "0 4px 12px rgba(0,0,0,0.2)", zIndex: "100000", opacity: "0", transition: "opacity 0.3s ease, transform 0.3s ease", transform: "translateY(10px)" }); document.body.appendChild(toast); requestAnimationFrame(() => { toast.style.opacity = "1"; toast.style.transform = "translateY(0)"; }); setTimeout(() => { toast.style.opacity = "0"; toast.style.transform = "translateY(10px)"; setTimeout(() => toast.remove(), 300); }, 1500); } // === 悬浮按钮 === const toggleBtn = document.createElement("img"); toggleBtn.src = "https://img.meituan.net/video/1f498ca05808be0e7a8a837d4e51e995233496.png"; Object.assign(toggleBtn.style, { position: "fixed", right: "15px", bottom: "15px", width: "60px", height: "60px", borderRadius: "50%", cursor: "pointer", zIndex: "99998", background: "rgba(255,255,255,0.4)", backdropFilter: "blur(10px) saturate(180%)", border: "1px solid rgba(255,255,255,0.5)", boxShadow: "0 4px 18px rgba(0,0,0,0.25)", transition: "transform 0.25s ease, box-shadow 0.25s ease" }); toggleBtn.addEventListener("mouseenter", () => { toggleBtn.style.transform = "scale(1.1)"; toggleBtn.style.boxShadow = "0 6px 20px rgba(0,0,0,0.35)"; }); toggleBtn.addEventListener("mouseleave", () => { toggleBtn.style.transform = "scale(1)"; toggleBtn.style.boxShadow = "0 4px 18px rgba(0,0,0,0.25)"; }); document.body.appendChild(toggleBtn); // === 主面板 === const panel = document.createElement("div"); panel.id = "emoji-panel"; Object.assign(panel.style, { position: "fixed", right: "80px", bottom: "80px", width: "240px", height: "auto", maxHeight: "50vh", display: "flex", flexDirection: "column", background: "rgba(255, 255, 255, 0.15)", border: "1px solid rgba(255, 255, 255, 0.4)", borderRadius: "16px", backdropFilter: "blur(12px) saturate(180%)", boxShadow: "0 10px 30px rgba(0,0,0,0.25)", zIndex: "99999", padding: "10px", color: "#222", display: "none", transition: "opacity 0.3s ease, transform 0.3s ease", transform: "translateY(10px)", }); const style = document.createElement("style"); style.textContent = ` #emoji-panel * { box-sizing: border-box; } #emoji-panel-grid::-webkit-scrollbar { width: 6px; } #emoji-panel-grid::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.4); border-radius: 3px; } #emoji-panel-grid::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.6); } .emoji-item img:hover { transform: scale(1.08); box-shadow: 0 4px 10px rgba(0,0,0,0.2); } /* 新增:删除模式样式 */ #emoji-panel.delete-mode .emoji-item[data-is-custom="true"] > img { border: 2px dashed #ff4757; opacity: 0.8; cursor: pointer; } #emoji-panel.delete-mode .emoji-item[data-is-custom="true"]:hover > img { opacity: 1; box-shadow: 0 0 10px #ff4757; } #emoji-panel.delete-mode .emoji-item:not([data-is-custom="true"]) { filter: grayscale(80%); opacity: 0.5; pointer-events: none; } .control-button { background: rgba(255,255,255,0.3); border: none; padding: 4px 8px; font-size: 12px; border-radius: 6px; color: white; cursor: pointer; transition: background 0.2s ease; } .control-button:hover { background: rgba(255,255,255,0.5); } `; document.head.appendChild(style); const header = document.createElement("div"); Object.assign(header.style, { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "8px", color: "#fff", fontWeight: "600", textShadow: "0 1px 3px rgba(0,0,0,0.4)", cursor: "move", flexShrink: "0" }); header.innerHTML = `🌸 花火表情包面板✖`; header.querySelector("span:last-child").onclick = () => { panel.style.display = "none"; }; panel.appendChild(header); const grid = document.createElement("div"); grid.id = "emoji-panel-grid"; Object.assign(grid.style, { display: "flex", flexWrap: "wrap", justifyContent: "flex-start", overflowY: "auto", flexGrow: "1" }); panel.appendChild(grid); // --- 新增功能:控制区 --- const controls = document.createElement("div"); controls.style.marginTop = "8px"; controls.style.flexShrink = "0"; const urlInput = document.createElement("input"); Object.assign(urlInput.style, { width: "100%", padding: "6px", borderRadius: "6px", border: "1px solid rgba(255,255,255,0.4)", background: "rgba(0,0,0,0.1)", color: "white", marginBottom: "6px" }); urlInput.placeholder = "粘贴图片链接..."; const buttonContainer = document.createElement("div"); Object.assign(buttonContainer.style, { display: "flex", justifyContent: "space-between", gap: "4px" }); const addButton = document.createElement("button"); addButton.textContent = "✓ 添加"; addButton.className = "control-button"; const deleteModeButton = document.createElement("button"); deleteModeButton.textContent = "🗑️ 删除"; deleteModeButton.className = "control-button"; const refreshButton = document.createElement("button"); refreshButton.textContent = "🔄 换一批"; refreshButton.className = "control-button"; refreshButton.title = "随机更换20个表情包"; buttonContainer.append(addButton, deleteModeButton, refreshButton); controls.append(urlInput, buttonContainer); panel.appendChild(controls); document.body.appendChild(panel); // --- 核心功能重构:渲染所有表情 --- function renderEmojis() { grid.innerHTML = ''; // 清空 const createEmojiItem = (url, isCustom) => { const item = document.createElement("div"); item.className = "emoji-item"; if (isCustom) item.dataset.isCustom = "true"; const img = document.createElement("img"); img.src = url; img.loading = "lazy"; Object.assign(img.style, { width: "60px", height: "60px", borderRadius: "10px", margin: "4px", objectFit: "cover", cursor: "pointer", transition: "transform 0.2s ease, box-shadow 0.2s ease" }); img.onclick = () => { // 删除模式逻辑 if (isDeleteMode && isCustom) { if (confirm("确定要删除这个自定义表情吗?")) { customEmojiList = customEmojiList.filter(e => e !== url); saveCustomEmojis(customEmojiList); renderEmojis(); showToast("🗑️ 表情已删除!"); } return; } // 发送模式逻辑 let finalUrl = url; // 如果启用图片代理服务,使用wsrv.nl来调整图片尺寸 if (USE_IMAGE_PROXY) { // wsrv.nl 参数: w=宽度, h=高度, fit=contain, n=-1(保持所有GIF帧) finalUrl = `${IMAGE_PROXY_URL}${encodeURIComponent(url)}&w=${TARGET_SIZE}&h=${TARGET_SIZE}&fit=contain&n=-1`; } const markdown = `  `; const input = findInputElement(); if (input && insertTextAtCursor(input, markdown)) { setTimeout(() => { const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true }); input.dispatchEvent(enterEvent); }, 50); showToast("✨ 表情包已发送!"); } }; item.appendChild(img); grid.appendChild(item); }; defaultEmojiList.forEach(url => createEmojiItem(url, false)); customEmojiList.forEach(url => createEmojiItem(url, true)); } // --- 新增功能:按钮事件监听 --- addButton.onclick = () => { const url = urlInput.value.trim(); if (!url || !url.startsWith('http')) { showToast("❌ 请输入有效的图片链接!"); return; } if (customEmojiList.includes(url)) { showToast("😅 这个表情已经添加过啦!"); return; } customEmojiList.push(url); saveCustomEmojis(customEmojiList); renderEmojis(); urlInput.value = ''; showToast("✅ 自定义表情已添加!"); grid.scrollTop = grid.scrollHeight; // 滚动到底部 }; deleteModeButton.onclick = () => { isDeleteMode = !isDeleteMode; panel.classList.toggle('delete-mode', isDeleteMode); deleteModeButton.textContent = isDeleteMode ? "✓ 完成" : "🗑️ 删除"; deleteModeButton.style.background = isDeleteMode ? "rgba(255, 71, 87, 0.5)" : "rgba(255,255,255,0.3)"; }; refreshButton.onclick = () => { console.log("🔄 点击刷新按钮"); console.log(`📊 当前状态: allGifFiles.length = ${allGifFiles.length}, isLoading = ${isLoading}`); if (allGifFiles.length > 0) { refreshEmojis(); } else { showToast("⏳ 表情包列表加载中..."); console.warn("⚠️ allGifFiles 为空,可能API加载失败"); } }; toggleBtn.onclick = () => { const show = panel.style.display === "none" || !panel.style.display; panel.style.display = show ? "flex" : "none"; panel.style.opacity = show ? "1" : "0"; panel.style.transform = show ? "translateY(0)" : "translateY(10px)"; // 退出时,自动关闭删除模式 if (!show && isDeleteMode) { isDeleteMode = false; panel.classList.remove('delete-mode'); deleteModeButton.textContent = "🗑️ 删除"; deleteModeButton.style.background = "rgba(255,255,255,0.3)"; } }; // --- 初始化 --- customEmojiList = loadCustomEmojis(); // 显示加载提示 grid.innerHTML = '