// ==UserScript==
// @name 小红书优化
// @namespace https://github.com/WhiteSevs/TamperMonkeyScript
// @version 2025.8.18
// @author WhiteSevs
// @description 屏蔽登录弹窗、屏蔽广告、优化评论浏览、优化图片浏览、允许复制、禁止唤醒App、禁止唤醒弹窗、修复正确跳转等
// @license GPL-3.0-only
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAx9JREFUWEfNl09MU0EQxn/beFTDDRI41FAMcNGbBw62oPEGid6UULxg1EhEEzExgdBEEzRqlKDxZCHgDZJ6U8TWAyaQGIsHMQLSA0S8VYQT2NXp9tnX0vKnpi2TNH1vd3bmm5lv9+0o0kQ73SXsc7QCx1EcjU9rnOl6O3pXRNAqCjqCIsB6LKQioYh9rbK/6MMnWojFHgElO3KwWyUBBD1q9q3fWvoPgHY1dIHu2a3N3PRVt5ob98naOABdVd+K5nluxnJc5dBe9TU4qHS128lvRzDnOufoH4iyETukihJ9EnSH0i5PAFRj7oH8z0r9UmlXw0fQZrsVWhQRKcFCEepvQo0DcNXrQgeechDtbQAVpbCyBiurqUmqqYSD+2FyOnPyZE50ln7A4vKWCc5egvIyCA3DzV4YeZ00UlEGQ/eN88670HsjOTczZ8bbvXCiDqbC8HkeBkahuhLE5sBICqDdAzh9yjh1n4OlZZgdTxqcDEPfIAw9SI1aMjg1DVrDpe5tAIRewOJ36LyXzIAgv+IFz1ljXN5FJAOjrwwIcd583YwfO2L0JHvW2qqGjKXYnAExJkYfDyYBaGWibmyDGhe0t/z9bikDSMQO4NZlEO5YJTggfHCBf8SUIo0TqQCEPB8C0Ddg6m5xQIj4xAcXu+DLPASHjY5/1BDUDkAyWF6amXjCkcYLW5Sg1gWBZ3C7H6Y+mWdJ48y35LiQ0HvGGLHzIFsJLAJLSSQzssYmmzMg0TVfM9vMqqMYkcwIejEiv59rhliy3URP2H6n3/zXJsbsO+ipz+huCUCQSb2E3eJQRNL+ZsIQS/a1ALQIKDtCxu0i4EUs8GPvk7YEXFPbNrvAmj5ZJ3dB49wSYbTlUIgqANJFzoFfq4aE8izBiC0h49iEmctagszUyevoHvgYFf1zXEwA6PBeuJLVXwUe5pVp2Yyr2HmVaMUW8tYNZXWuI6xrT6IxcbeiHYVtTCT62ZDf1pp5ekB1FaYU2qfmgvGLQWpzKi0adOfxlhxF0ZGxObUiT7RqbjRNoJ0oVZIzINMNy5Eehtg7NvCrSChqz/IfgUZkW/BhLsQAAAAASUVORK5CYII=
// @supportURL https://github.com/WhiteSevs/TamperMonkeyScript/issues
// @match *://www.xiaohongshu.com/*
// @require https://fastly.jsdelivr.net/gh/WhiteSevs/TamperMonkeyScript@86be74b83fca4fa47521cded28377b35e1d7d2ac/lib/CoverUMD/index.js
// @require https://fastly.jsdelivr.net/npm/@whitesev/utils@2.7.3/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/@whitesev/domutils@1.6.3/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/@whitesev/pops@2.3.3/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/qmsg@1.4.0/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/viewerjs@1.11.7/dist/viewer.min.js
// @resource ViewerCSS https://fastly.jsdelivr.net/npm/viewerjs@1.11.7/dist/viewer.min.css
// @connect edith.xiaohongshu.com
// @grant GM_deleteValue
// @grant GM_getResourceText
// @grant GM_getValue
// @grant GM_info
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_unregisterMenuCommand
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(function (Qmsg, DOMUtils, Utils, pops, Viewer) {
'use strict';
var _GM_deleteValue = /* @__PURE__ */ (() => typeof GM_deleteValue != "undefined" ? GM_deleteValue : void 0)();
var _GM_getResourceText = /* @__PURE__ */ (() => typeof GM_getResourceText != "undefined" ? GM_getResourceText : void 0)();
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
var _GM_unregisterMenuCommand = /* @__PURE__ */ (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)();
var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
var _monkeyWindow = /* @__PURE__ */ (() => window)();
const blockCSS$2 = "/* 用户主页 */\r\n/* 底部的-App内打开 */\r\n.launch-app-container.bottom-bar,\r\n/* 顶部的-打开看看 */\r\n.main-container > .scroll-view-container > .launch-app-container:first-child,\r\n/* 底部的-打开小红书看更多精彩内容 */\r\n.bottom-launch-app-tip.show-bottom-bar,\r\n/* 首页-顶部横幅 */\r\n#app .launch-app-container,\r\n/* 笔记-顶部横幅 */\r\n.note-view-container .nav-bar-box-expand ,\r\n.note-view-container .nav-bar-box-expand+.placeholder-expand,\r\n/* 404页面 顶部的打开看看 */\r\n.not-found-container .nav-bar-box-expand:has(.share-info-box):has(.launch-btn),\r\n/* 404页面 底部的-App内打开 */\r\n.not-found-container #fmp {\r\n display: none !important;\r\n}\r\n";
const XHSRouter = {
/**
* 判断是否是笔记页面
*/
isArticle() {
return globalThis.location.pathname.startsWith("/discovery/item/") || globalThis.location.pathname.startsWith("/explore/");
},
/**
* 判断是否是用户主页页面
*/
isUserHome() {
return globalThis.location.pathname.startsWith("/user/profile/");
},
/**
* 判断是否是主页
*/
isHome() {
return globalThis.location.href === "https://www.xiaohongshu.com/" || globalThis.location.href === "https://www.xiaohongshu.com";
},
/**
* 判断是否是搜索页面
*/
isSearch() {
return globalThis.location.pathname.startsWith("/search_result/");
}
};
const KEY = "GM_Panel";
const ATTRIBUTE_INIT = "data-init";
const ATTRIBUTE_KEY = "data-key";
const ATTRIBUTE_DEFAULT_VALUE = "data-default-value";
const ATTRIBUTE_INIT_MORE_VALUE = "data-init-more-value";
const PROPS_STORAGE_API = "data-storage-api";
const PanelUISize = {
/**
* 一般设置界面的尺寸
*/
setting: {
get width() {
if (window.innerWidth < 550) {
return "88vw";
} else if (window.innerWidth < 700) {
return "550px";
} else {
return "700px";
}
},
get height() {
if (window.innerHeight < 450) {
return "70vh";
} else if (window.innerHeight < 550) {
return "450px";
} else {
return "550px";
}
}
},
/**
* 中等的设置界面
*/
settingMiddle: {
get width() {
return window.innerWidth < 350 ? "88vw" : "350px";
}
},
/**
* 信息界面,一般用于提示信息之类
*/
info: {
get width() {
return window.innerWidth < 350 ? "88vw" : "350px";
},
get height() {
return window.innerHeight < 250 ? "88vh" : "250px";
}
}
};
class StorageUtils {
/** 存储的键名 */
storageKey;
listenerData;
/**
* 存储的键名,可以是多层的,如:a.b.c
*
* 那就是
* {
* "a": {
* "b": {
* "c": {
* ...你的数据
* }
* }
* }
* }
* @param key
*/
constructor(key) {
if (typeof key === "string") {
let trimKey = key.trim();
if (trimKey == "") {
throw new Error("key参数不能为空字符串");
}
this.storageKey = trimKey;
} else {
throw new Error("key参数类型错误,必须是字符串");
}
this.listenerData = new Utils.Dictionary();
}
/**
* 获取本地值
*/
getLocalValue() {
let localValue = _GM_getValue(this.storageKey);
if (localValue == null) {
localValue = {};
this.setLocalValue(localValue);
}
return localValue;
}
/**
* 设置本地值
* @param value
*/
setLocalValue(value) {
_GM_setValue(this.storageKey, value);
}
/**
* 设置值
* @param key 键
* @param value 值
*/
set(key, value) {
let oldValue = this.get(key);
let localValue = this.getLocalValue();
Reflect.set(localValue, key, value);
this.setLocalValue(localValue);
this.triggerValueChangeListener(key, oldValue, value);
}
/**
* 获取值
* @param key 键
* @param defaultValue 默认值
*/
get(key, defaultValue) {
let localValue = this.getLocalValue();
return Reflect.get(localValue, key) ?? defaultValue;
}
/**
* 获取所有值
*/
getAll() {
let localValue = this.getLocalValue();
return localValue;
}
/**
* 删除值
* @param key 键
*/
delete(key) {
let oldValue = this.get(key);
let localValue = this.getLocalValue();
Reflect.deleteProperty(localValue, key);
this.setLocalValue(localValue);
this.triggerValueChangeListener(key, oldValue, void 0);
}
/**
* 判断是否存在该值
*/
has(key) {
let localValue = this.getLocalValue();
return Reflect.has(localValue, key);
}
/**
* 获取所有键
*/
keys() {
let localValue = this.getLocalValue();
return Reflect.ownKeys(localValue);
}
/**
* 获取所有值
*/
values() {
let localValue = this.getLocalValue();
return Reflect.ownKeys(localValue).map(
(key) => Reflect.get(localValue, key)
);
}
/**
* 清空所有值
*/
clear() {
_GM_deleteValue(this.storageKey);
}
/**
* 监听值改变
* + .set
* + .delete
* @param key 监听的键
* @param callback 值改变的回调函数
*/
addValueChangeListener(key, callback) {
let listenerId = Math.random();
let listenerData = this.listenerData.get(key) || [];
listenerData.push({
id: listenerId,
key,
callback
});
this.listenerData.set(key, listenerData);
return listenerId;
}
/**
* 移除监听
* @param listenerId 监听的id或键名
*/
removeValueChangeListener(listenerId) {
let flag = false;
for (const [key, listenerData] of this.listenerData.entries()) {
for (let index = 0; index < listenerData.length; index++) {
const value = listenerData[index];
if (typeof listenerId === "string" && value.key === listenerId || typeof listenerId === "number" && value.id === listenerId) {
listenerData.splice(index, 1);
index--;
flag = true;
}
}
this.listenerData.set(key, listenerData);
}
return flag;
}
/**
* 主动触发监听器
* @param key 键
* @param oldValue (可选)旧值
* @param newValue (可选)新值
*/
triggerValueChangeListener(key, oldValue, newValue) {
if (!this.listenerData.has(key)) {
return;
}
let listenerData = this.listenerData.get(key);
for (let index = 0; index < listenerData.length; index++) {
const data = listenerData[index];
if (typeof data.callback === "function") {
let value = this.get(key);
let __newValue;
let __oldValue;
if (typeof oldValue !== "undefined" && arguments.length >= 2) {
__oldValue = oldValue;
} else {
__oldValue = value;
}
if (typeof newValue !== "undefined" && arguments.length > 2) {
__newValue = newValue;
} else {
__newValue = value;
}
data.callback(key, __oldValue, __newValue);
}
}
}
}
const PopsPanelStorageApi = new StorageUtils(KEY);
const PanelContent = {
$data: {
/**
* @private
*/
__contentConfig: null,
get contentConfig() {
if (this.__contentConfig == null) {
this.__contentConfig = new utils.Dictionary();
}
return this.__contentConfig;
}
},
/**
* 设置所有配置项,用于初始化默认的值
*
* 如果是第一组添加的话,那么它默认就是设置菜单打开的配置
* @param configList 配置项
*/
addContentConfig(configList) {
if (!Array.isArray(configList)) {
configList = [configList];
}
let index = this.$data.contentConfig.keys().length;
this.$data.contentConfig.set(index, configList);
},
/**
* 获取所有的配置内容,用于初始化默认的值
*/
getAllContentConfig() {
return this.$data.contentConfig.values().flat();
},
/**
* 获取配置内容
* @param index 配置索引
*/
getConfig(index = 0) {
return this.$data.contentConfig.get(index) ?? [];
},
/**
* 获取默认左侧底部的配置项
*/
getDefaultBottomContentConfig() {
return [
{
id: "script-version",
title: `版本:${_GM_info?.script?.version || "未知"}`,
isBottom: true,
forms: [],
clickFirstCallback(event, rightHeaderElement, rightContainerElement) {
let supportURL = _GM_info?.script?.supportURL || _GM_info?.script?.namespace;
if (typeof supportURL === "string" && utils.isNotNull(supportURL)) {
window.open(supportURL, "_blank");
}
return false;
}
}
];
}
};
const PanelMenu = {
$data: {
__menuOption: [
{
key: "show_pops_panel_setting",
text: "⚙ 设置",
autoReload: false,
isStoreValue: false,
showText(text) {
return text;
},
callback: () => {
Panel.showPanel(PanelContent.getConfig(0));
}
}
],
get menuOption() {
return this.__menuOption;
}
},
init() {
this.initExtensionsMenu();
},
/**
* 初始化菜单项
*/
initExtensionsMenu() {
if (!Panel.isTopWindow()) {
return;
}
GM_Menu.add(this.$data.menuOption);
},
/**
* 添加菜单项
* @param option 菜单配置
*/
addMenuOption(option) {
if (!Array.isArray(option)) {
option = [option];
}
this.$data.menuOption.push(...option);
},
/**
* 更新菜单项
* @param option 菜单配置
*/
updateMenuOption(option) {
if (!Array.isArray(option)) {
option = [option];
}
option.forEach((optionItem) => {
let findIndex = this.$data.menuOption.findIndex((it) => {
return it.key === optionItem.key;
});
if (findIndex !== -1) {
this.$data.menuOption[findIndex] = optionItem;
}
});
},
/**
* 获取菜单项
* @param [index=0] 索引
*/
getMenuOption(index = 0) {
return this.$data.menuOption[index];
},
/**
* 删除菜单项
* @param [index=0] 索引
*/
deleteMenuOption(index = 0) {
this.$data.menuOption.splice(index, 1);
}
};
const CommonUtil = {
/**
* 移除元素(未出现也可以等待出现)
* @param selector 元素选择器
*/
waitRemove(...args) {
args.forEach((selector) => {
if (typeof selector !== "string") {
return;
}
utils.waitNodeList(selector).then((nodeList) => {
nodeList.forEach(($el) => $el.remove());
});
});
},
/**
* 添加屏蔽CSS
* @param args
* @example
* addBlockCSS("")
* addBlockCSS("","")
* addBlockCSS(["",""])
*/
addBlockCSS(...args) {
let selectorList = [];
if (args.length === 0) {
return;
}
if (args.length === 1 && typeof args[0] === "string" && args[0].trim() === "") {
return;
}
args.forEach((selector) => {
if (Array.isArray(selector)) {
selectorList = selectorList.concat(selector);
} else {
selectorList.push(selector);
}
});
return addStyle(`${selectorList.join(",\n")}{display: none !important;}`);
},
/**
* 设置GM_getResourceText的style内容
* @param resourceMapData 资源数据
* @example
* setGMResourceCSS({
* keyName: "ViewerCSS",
* url: "https://example.com/example.css",
* })
*/
setGMResourceCSS(resourceMapData) {
let cssText = typeof _GM_getResourceText === "function" ? _GM_getResourceText(resourceMapData.keyName) : null;
if (typeof cssText === "string" && cssText) {
addStyle(cssText);
} else {
CommonUtil.loadStyleLink(resourceMapData.url);
}
},
/**
* 添加标签
* @param url
* @example
* loadStyleLink("https://example.com/example.css")
*/
async loadStyleLink(url) {
let $link = document.createElement("link");
$link.rel = "stylesheet";
$link.type = "text/css";
$link.href = url;
DOMUtils.ready(() => {
document.head.appendChild($link);
});
},
/**
* 添加