NodeSeekEmoji 一个功能+颜值+格式支持达到95%完美度的表情包脚本!!!

本脚本基于 ez9l 的源码基础上行进升级与改造。再次感谢 ez9l 制作了这个完美表情包脚本!
 

原贴:https://www.nodeseek.com/post-418576-1
作者:@ez9l
 


脚本功能:
支持添加、删除自定义表情
支持添加组、删除组操作
支持同时导入多个表情
支持动画webp,gif表情
支持鼠标悬停预览原始尺寸
自动适配 Nodeseek站与DeepFlood站的日间与夜间主题
 


更新日志:
 
2.5.3 更新内容:

  • 增加了许多动态效果(弹出菜单,渐显渐隐等),不会像之前那么生硬,毕竟现在是css3时代
  • 增加了自动适配 NS站 与 DF站 的日间与夜间主题
  • 列表中的组件会自动适配宽度
  • 修复页面第一加载脚本需要点击两次“🐤 表情包”按键才能激活的bug
  • 优化了表情预览框背景
     

2.4.9 更新内容:

  • 增加鼠标指向预览功能
  • 增加删除整组功能,收藏夹(默认分组)不会被删除
     

2.4.1 修改内容:

  • 添加DeepFlood支持
  • 调整表情列表到组列表下面
  • 添加只有在发新贴与回贴界面才会显示“表情包”按键
  • 调整了面板宽度,尽量做到左右边距平衡
  • “功能键”做了小修正,显示在同一行
     
     

功能演示:
弹出表情面板与原始表情预览,自动切换日间夜间演示:

 

添加组与删除组功能演示:

 


脚本源代码:
 
使用方法:直接在油猴篡改猴里新建空白脚本,复制粘贴后保存即可)

// ==UserScript==// @name         NodeSeekEmoji// @namespace    https://nodeseek.com/// @version      2.5.3// @author       ez9l (modified by Gemini)// @description  表情分组,带面板开关动画,暗色模式支持,以及鼠标悬停预览原图(带渐显渐隐动画)// @match        https://www.nodeseek.com/new*// @match        https://www.nodeseek.com/post*// @match        https://www.deepflood.com/new*// @match        https://www.deepflood.com/post*// @icon         https://www.google.com/s2/favicons?sz=64&domain=www.deepflood.com// @grant        GM_getValue// @grant        GM_setValue// ==/UserScript==(function () {  'use strict';  const KEY = "emojiGroupsData";  const TOOL_URL = "https://nodeseekimagetool.pages.dev/";  let groups = [];  let gi = 0;  let del = false;  let previewTimeout; // --- Timer for hiding preview ---  async function load() {    const s = await GM_getValue(KEY, "[]");    try { groups = JSON.parse(s); } catch { groups = []; }    if (!Array.isArray(groups) || groups.length === 0) groups = [{ name: "收藏夹", emojis: [] }];  }  async function save() { await GM_setValue(KEY, JSON.stringify(groups)); }  function btn(txt, fn) {    const b = document.createElement("button");    b.textContent = txt;    b.className = "ns-btn";    b.onclick = fn;    return b;  }  const css = document.createElement("style");  css.textContent = `  .ns *{box-sizing:border-box}  /* --- .ns-panel (z-index: 9999) --- */  /* 滚动条 */  ::-webkit-scrollbar {    width: 8px;    height: 8px;  }  ::-webkit-scrollbar-track {    background-color: transparent; /* 轨道设为透明,更具一体感 */    margin-top: 20px;    margin-bottom: 20px;  }  ::-webkit-scrollbar-thumb {    background-color: rgba(120, 120, 120, 0.5); /* 半透明的滑块 */    border-radius: 2em;    border: 2px solid transparent; /* 创建一个“边框”效果 */    background-clip: padding-box;  }  ::-webkit-scrollbar-thumb:hover {    background-color: rgba(200, 200, 200, 0.7);  }  .ns-panel{    position:fixed; bottom:150px; right:30px; z-index:9999;    width:342px; max-height:520px; overflow-y:auto; overflow-x:hidden;    background:linear-gradient(180deg,rgba(255,255,255,.18),rgba(255,255,255,.12));    backdrop-filter:blur(20px) saturate(180%); border:1px solid rgba(240, 240, 240, 0.4);    border-radius:24px; box-shadow: 0 0 30px 10px rgba(0, 0, 0, .3);    padding:14px;    opacity: 0;    visibility: hidden;    transform: translateY(30px);    transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease-in-out;    pointer-events: none;  }  .ns-panel.show {    opacity: 1;    visibility: visible;    transform: translateY(0);    pointer-events: auto;  }  .ns-bar,.ns-actions,.ns-grid{width:100%}  .ns-bar{display:flex; flex-wrap:wrap; gap:8px; margin-bottom:10px;justify-content: space-evenly}  .ns-btn{    background:rgba(255,255,255,.22); color:#111; border:1px solid rgba(255,255,255,.35);    border-radius:10px; padding:6px 12px; font-size:12px; cursor:pointer;    transition:all .2s ease; box-shadow:0 1px 2px rgba(0,0,0,.06)  }  .ns-btn:hover{background:rgba(0,0,0,.85); color:#fff}  .ns-btn.active{background:rgba(255,193,7,.9); color:#111; border-color:rgba(255,193,7,.9)}  .ns-input{    width:100%; min-height:96px; margin:12px 0 0 0; padding:12px 14px; display:block;    background:rgba(255,255,255,.18); backdrop-filter:blur(12px);    border:1px solid rgba(255,255,255,.35); border-radius:12px; color:#111; font-size:12px;    outline:none;  }  .ns-input::placeholder{color:rgba(0,0,0,.6)}  .ns-actions{display:flex; flex-wrap:wrap; gap:8px; margin-top:12px;justify-content: space-evenly}  .ns-grid{display:flex; flex-wrap:wrap; gap:8px;justify-content: space-evenly}  .ns-item{position:relative}  .ns-img{    width:60px; height:60px; object-fit:contain; border:1px solid #ddd; border-radius:8px; cursor:pointer; background:#fff  }  .ns-del{    position:absolute; top:-6px; right:-6px; width:20px; height:20px; line-height:20px;    text-align:center; font-size:12px; color:#fff; background:#f33; border-radius:50%; cursor:pointer  }  /* --- .ns-main (z-index: 10000) --- */  .ns-main{    position:fixed; bottom:100px; right:30px; z-index:10000;    background:rgba(0,0,0,.8); color:#fff; border:1px solid rgba(255,255,255,.35);    padding:10px 14px; border-radius:14px; font-size:14px; font-weight:700; cursor:pointer;    box-shadow:0 4px 12px rgba(0,0,0,.25); user-select:none  }  /* --- .ns-preview-hover (z-index: 10001) --- */  .ns-preview-hover{    position:fixed; z-index:10001;    opacity: 0;    visibility: hidden;    padding:4px;    background:linear-gradient(180deg,rgba(255,255,255,.18),rgba(255,255,255,.12));    backdrop-filter:blur(20px) saturate(180%); border:1px solid rgba(240, 240, 240, 0.4);    border-radius:14px; box-shadow: 0 0 30px 10px rgba(0, 0, 0, .3);    pointer-events:none;    transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;  }  .ns-preview-hover.show {    opacity: 1;    visibility: visible;  }  .ns-preview-hover-img{    display:block; max-width:300px; max-height:300px;    min-width:128px; min-height:128px;    border-radius:8px;  }  /* --- ⬇️ MODIFIED DARK MODE SUPPORT (USING body.dark-layout) ⬇️ --- */  body.dark-layout .ns-panel,  body.dark-layout .ns-preview-hover {    background:linear-gradient(180deg,rgba(0,0,0,.45),rgba(0,0,0,.40));    backdrop-filter:blur(20px) saturate(180%); border:1px solid rgba(0, 0, 0, 0.4);    border-radius:24px; box-shadow: 0 0 30px 10px rgba(0, 0, 0, .3);  }  body.dark-layout .ns-btn {      background: rgba(70, 70, 70, 0.35);      color: #f2f2f2; /* --ns-text-color */      border-color: rgba(58, 58, 58, 0.7); /* --ns-border-color */  }  body.dark-layout .ns-btn:hover {      background: #f2f2f2; /* --ns-text-color */      color: #181a1b; /* --ns-bg-color */  }  /* Active button yellow is fine, but ensure text color matches dark mode button text */  body.dark-layout .ns-btn.active {    background: rgba(255, 193, 7, .9);    color: #111;    border-color: rgba(255, 193, 7, .9);  }  body.dark-layout .ns-input {      background: rgba(30, 30, 30, 0.3);      border-color: rgba(58, 58, 58, 0.7); /* --ns-border-color */      color: #f2f2f2; /* --ns-text-color */  }  body.dark-layout .ns-input::placeholder {      color: rgba(242, 242, 242, 0.5);  }  body.dark-layout .ns-img {      background: #222; /* Dark bg for images */      border-color: rgba(58, 58, 58, 0.7); /* --ns-border-color */  }  body.dark-layout .ns-main {       background: rgba(24, 26, 27, 0.85); /* --ns-bg-color */       color: #f2f2f2; /* --ns-text-color */       border-color: rgba(58, 58, 58, 0.7);  }  `;  document.head.appendChild(css);  const main = document.createElement("div");  main.className = "ns ns-main";  main.textContent = "🐤 表情包";  const panel = document.createElement("div");  panel.className = "ns ns-panel"; // 初始没有 'show' 类  const bar = document.createElement("div");  bar.className = "ns-bar";  function drawBar() {    bar.innerHTML = "";    groups.forEach((g, i) => {      const b = btn(g.name, () => { gi = i; drawBar(); drawGrid(); });      if (i === gi) b.classList.add("active");      bar.appendChild(b);    });    const add = btn("+分组", async () => {      const name = prompt("输入新分组名:");      if (!name) return;      groups.push({ name, emojis: [] });      gi = groups.length - 1;      await save();      drawBar();      drawGrid();    });    bar.appendChild(add);    if (gi > 0) {      const delGroupBtn = btn("-分组", async () => {        const currentGroupName = groups[gi].name;        if (!confirm(`确定要删除分组 "${currentGroupName}" 吗?\n\n此操作将删除该分组及其包含的所有表情!`)) {          return;        }        groups.splice(gi, 1);        gi = 0;        await save();        drawBar();        drawGrid();      });      bar.appendChild(delGroupBtn);    }  }  const input = document.createElement("textarea");  input.className = "ns-input";  input.placeholder = "支持多行添加图片链接(每行一个)";  const actions = document.createElement("div");  actions.className = "ns-actions";  const addBtn = btn("添加图片", async () => {    const lines = input.value.split("\n").map(s => s.trim()).filter(s => /^https?:\/\//i.test(s));    if (!lines.length) return;    groups[gi].emojis.push(...lines);    await save(); input.value = ""; drawGrid();  });  const clrBtn = btn("清空本组", async () => {    if (!confirm("确定要清空本组所有图片?")) return;    groups[gi].emojis = []; await save(); drawGrid();  });  const delBtn = btn("删除:关闭", () => {    del = !del; delBtn.textContent = del ? "删除:开启" : "删除:关闭"; drawGrid();  });  const toolBtn = btn("图片工具", () => window.open(TOOL_URL, "_blank"));  actions.append(addBtn, clrBtn, delBtn, toolBtn);  const grid = document.createElement("div");  grid.className = "ns-grid";  // --- PREVIEW ELEMENTS AND LOGIC ---  const previewContainer = document.createElement("div");  previewContainer.className = "ns-preview-hover";  const previewImg = document.createElement("img");  previewImg.className = "ns-preview-hover-img";  previewContainer.appendChild(previewImg);  function positionPreview(e) {      const rect = e.target.getBoundingClientRect();      const previewWidth = previewContainer.offsetWidth;      const previewHeight = previewContainer.offsetHeight;      const margin = 10;      let top = rect.top;      let left = rect.right + margin;      if (left + previewWidth > window.innerWidth) {        left = rect.left - previewWidth - margin;      }      if (top + previewHeight > window.innerHeight) {        top = window.innerHeight - previewHeight - margin;      }      top = Math.max(margin, top);      previewContainer.style.top = `${top}px`;      previewContainer.style.left = `${left}px`;  }  function showHoverPreview(e, url) {    clearTimeout(previewTimeout);    previewImg.src = url;    previewImg.onload = () => {      positionPreview(e);      previewContainer.classList.add('show');    };    if (previewImg.complete && previewImg.naturalWidth !== 0) {      positionPreview(e);      previewContainer.classList.add('show');    }  }  function hideHoverPreview() {    previewTimeout = setTimeout(() => {      previewContainer.classList.remove('show');    }, 100);  }  // --- END OF PREVIEW LOGIC ---  function insert(url) {    const md = `![image|100x100](${url})\n`;    const run = () => {      const w = document.querySelector('#cm-editor-wrapper .CodeMirror');      if (w && w.CodeMirror) {        const cm = w.CodeMirror, d = cm.getDoc();        d.replaceRange(md, d.getCursor()); cm.focus(); return true;      }      const ta = document.querySelector(".d-editor-input");      if (ta) {        ta.value += md; ta.dispatchEvent(new Event("input", { bubbles: true })); ta.focus(); return true;      }      return false;    };    if (run()) return;    const r = document.querySelector("button.reply-to-post"); if (r) r.click();    let t = 0; const h = setInterval(() => { if (run() || ++t >= 20) clearInterval(h); }, 300);  }  function drawGrid() {    grid.innerHTML = "";    if (!groups[gi]) gi = 0;    if (!groups[gi]) return;    groups[gi].emojis.forEach((u, idx) => {      const item = document.createElement("div");      item.className = "ns-item";      const img = document.createElement("img");      img.className = "ns-img";      img.src = u;      img.onclick = () => insert(u);      img.addEventListener('mouseenter', (e) => showHoverPreview(e, u));      img.addEventListener('mouseleave', hideHoverPreview);      item.appendChild(img);      if (del) {        const x = document.createElement("div");        x.className = "ns-del"; x.textContent = "✕";        x.onclick = async (e) => { e.stopPropagation(); groups[gi].emojis.splice(idx, 1); await save(); drawGrid(); };        item.appendChild(x);      }      grid.appendChild(item);    });  }  // --- CLICK LISTENER FOR PANEL ANIMATION ---  main.addEventListener("click", () => panel.classList.toggle("show"));  (async function init() {    await load();    drawBar();    drawGrid();    panel.append(bar, grid, input, actions);    document.body.append(main, panel, previewContainer);  })();})();

 
 
希望有js大佬继续为此脚本增加更炫的功能与主题~!
 

欢迎提出bug与建议,让表情包脚本更加完美。