Greasy Fork is available in English.
可自定义添加/删除表情,点击表情包直接插入并发送,带磨砂质感浮动面板与开关 + 发送提示
当前为
// ==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
// ==/UserScript==rript==
// @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 = `<span>🌸 花火表情包面板</span><span style="cursor:pointer;font-size:16px;">✖</span>`;
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 = '<div style="width:100%;text-align:center;color:#fff;padding:20px;">⏳ 正在加载表情包...</div>';
// 异步加载GitHub表情包列表
fetchAllGifFiles();
console.log("🌸 花火表情包面板 已加载");
})();