// ==UserScript== // @name 小红书复制链接净化 // @namespace https://github.com/LeonYew-Ley/xiaohongshu-clean-share-link // @version 1.1.0 // @description 点击「复制链接」后,剪贴板写成【标题】干净链接,去掉 token 与追踪参数 // @author LeonYew-Ley // @license MIT // @match *://www.xiaohongshu.com/* // @match *://xiaohongshu.com/* // @icon https://www.xiaohongshu.com/favicon.ico // @homepageURL https://github.com/LeonYew-Ley/xiaohongshu-clean-share-link // @supportURL https://github.com/LeonYew-Ley/xiaohongshu-clean-share-link/issues // @run-at document-start // @grant none // @inject-into page // ==/UserScript== (() => { "use strict"; const COPY_LABEL = "复制链接"; const NOTE_ID_RE = /(?:explore|discovery\/item|item)\/([0-9a-f]{24})/i; const PROFILE_NOTE_RE = /\/user\/profile\/[0-9a-f]+\/([0-9a-f]{24})/i; const URL_IN_TEXT_RE = /https?:\/\/(?:www\.)?(?:xiaohongshu\.com|xhslink\.com)[^\s]+/gi; const SHARE_HINT_RE = /打开【小红书】|复制后打开|发布了一篇小红书笔记|xhsshare=|source=webshare|xsec_source=|xhslink\.com/i; let armedUntil = 0; let toastUntil = 0; function armCopy() { armedUntil = Date.now() + 2000; } function isArmed() { return Date.now() < armedUntil; } function getState() { try { return window.__INITIAL_STATE__ || null; } catch { return null; } } function unwrap(value) { if (value && typeof value === "object" && "value" in value) { return value.value; } return value; } function getNoteIdFromHref(href) { if (!href) return ""; const fromExplore = href.match(NOTE_ID_RE); if (fromExplore) return fromExplore[1]; const fromProfile = href.match(PROFILE_NOTE_RE); return fromProfile ? fromProfile[1] : ""; } function getCurrentNoteId() { return getNoteIdFromHref(location.href); } function getNoteDetail(noteId) { const state = getState(); if (!state || !noteId) return null; const map = unwrap(state.note?.noteDetailMap) || {}; const entry = unwrap(map[noteId]) || {}; return unwrap(entry.note) || null; } function firstText(selectors) { for (const selector of selectors) { const el = document.querySelector(selector); const text = el?.textContent?.trim(); if (text) return text; } return ""; } function stripSiteSuffix(text) { return (text || "") .replace(/\s*[-–—|]\s*小红书\s*$/, "") .replace(/\s*[-–—|]\s*Xiaohongshu\s*$/i, "") .trim(); } function getNoteTitle(shareText) { const noteId = getCurrentNoteId(); const note = getNoteDetail(noteId); const fromState = (note?.title || note?.displayTitle || note?.display_title || "").trim(); if (fromState) return fromState; const fromDom = firstText([ "#detail-title", ".note-content .title", ".interaction-container .title", ".note-scroller .title", '[class*="note-content"] [class*="title"]', ]); if (fromDom) return fromDom; const og = document.querySelector('meta[property="og:title"]')?.content; const fromOg = stripSiteSuffix(og); if (fromOg && fromOg !== "小红书") return fromOg; const fromDoc = stripSiteSuffix(document.title); if (fromDoc && fromDoc !== "小红书") return fromDoc; if (shareText) { const firstLine = shareText .split(/\r?\n/) .map((line) => line.trim()) .find((line) => line && !/^https?:\/\//i.test(line) && !SHARE_HINT_RE.test(line)); if (firstLine) return firstLine.replace(/[😀-🙏🌀-🗿]+$/u, "").trim(); } return ""; } function looksLikeOfficialShare(text) { if (!text) return false; return SHARE_HINT_RE.test(text); } function extractUrlFromText(text) { const matches = String(text || "").match(URL_IN_TEXT_RE); return matches ? matches[matches.length - 1].replace(/[),.;]+$/, "") : ""; } function cleanNoteUrl(rawUrl, shareText) { const noteId = getNoteIdFromHref(rawUrl) || getCurrentNoteId() || getNoteIdFromHref(extractUrlFromText(shareText)); if (!noteId) { return rawUrl ? stripQuery(rawUrl) : location.href.split("?")[0]; } return `https://www.xiaohongshu.com/explore/${noteId}`; } function stripQuery(rawUrl) { try { const url = new URL(rawUrl); url.search = ""; url.hash = ""; return url.toString(); } catch { return rawUrl.split("?")[0]; } } function buildCleanText(shareText) { const title = getNoteTitle(shareText); const rawUrl = extractUrlFromText(shareText) || location.href; const url = cleanNoteUrl(rawUrl, shareText); return title ? `【${title}】${url}` : url; } function writeClipboard(text) { if (!text) return Promise.resolve(); if (navigator.clipboard?.writeText) { return navigator.clipboard.writeText(text).catch(() => fallbackCopy(text)); } return Promise.resolve(fallbackCopy(text)); } function fallbackCopy(text) { const input = document.createElement("textarea"); input.value = text; input.setAttribute("readonly", ""); input.style.cssText = "position:fixed;top:-9999px;left:-9999px;opacity:0"; document.body.appendChild(input); input.select(); try { document.execCommand("copy"); } finally { input.remove(); } } function overwriteClipboard(shareText) { const text = buildCleanText(shareText); writeClipboard(text); [60, 180, 450].forEach((delay) => { setTimeout(() => writeClipboard(text), delay); }); showToast("已复制标题和干净链接"); } function isCopyLinkItem(node) { const item = node?.closest?.(".xhs-note-share-popup-action-item"); if (!item) return false; return (item.textContent || "").replace(/\s+/g, "").includes(COPY_LABEL); } function hookClipboard() { const proto = window.Clipboard?.prototype; if (!proto?.writeText || proto.writeText.__xhsCleanCopy) return; const original = proto.writeText; proto.writeText = function patchedWriteText(data) { const text = typeof data === "string" ? data : String(data ?? ""); if (isArmed() || looksLikeOfficialShare(text)) { armedUntil = 0; data = buildCleanText(text); showToast("已复制标题和干净链接"); } return original.call(this, data); }; proto.writeText.__xhsCleanCopy = true; } function showToast(message) { if (Date.now() < toastUntil) return; toastUntil = Date.now() + 1500; const old = document.getElementById("xhs-clean-copy-toast"); old?.remove(); const toast = document.createElement("div"); toast.id = "xhs-clean-copy-toast"; toast.textContent = message; toast.style.cssText = [ "position:fixed", "left:50%", "bottom:24%", "transform:translateX(-50%)", "z-index:2147483647", "padding:10px 16px", "border-radius:8px", "background:rgba(0,0,0,.78)", "color:#fff", "font-size:14px", "line-height:1.4", "pointer-events:none", "box-shadow:0 6px 20px rgba(0,0,0,.25)", ].join(";"); document.documentElement.appendChild(toast); setTimeout(() => toast.remove(), 1600); } hookClipboard(); document.addEventListener( "click", (event) => { if (!isCopyLinkItem(event.target)) return; armCopy(); // 站点可能异步写入剪贴板;先生成一份,再在短时间内覆盖官方带追踪文案 overwriteClipboard(""); }, true ); document.addEventListener( "copy", (event) => { if (!isArmed() && !isCopyLinkItem(event.target)) return; const original = event.clipboardData?.getData("text/plain") || ""; const cleaned = buildCleanText(original); event.clipboardData?.setData("text/plain", cleaned); event.preventDefault(); armedUntil = 0; showToast("已复制标题和干净链接"); }, true ); })();