// ==UserScript==
// @name 小红书优化
// @namespace https://github.com/WhiteSevs/TamperMonkeyScript
// @version 2025.7.16
// @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.0/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/@whitesev/domutils@1.5.11/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/@whitesev/pops@2.2.5/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/qmsg@1.3.8/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";
}
}
},
/**
* 信息界面,一般用于提示信息之类
*/
info: {
get width() {
return window.innerWidth < 350 ? "350px" : "350px";
},
get height() {
return window.innerHeight < 250 ? "250px" : "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 Panel = {
/** 数据 */
$data: {
/**
* @private
*/
__contentConfigInitDefaultValue: null,
/**
* @private
*/
__onceExecMenuData: null,
/**
* @private
*/
__onceExecData: null,
/**
* @private
*/
__panelConfig: {},
$panel: null,
/**
* 菜单项初始化的默认值
*/
get contentConfigInitDefaultValue() {
if (this.__contentConfigInitDefaultValue == null) {
this.__contentConfigInitDefaultValue = new utils.Dictionary();
}
return this.__contentConfigInitDefaultValue;
},
/**
* 菜单项初始化时禁用的键
*/
contentConfigInitDisabledKeys: [],
/**
* 成功只执行了一次的项
*/
get onceExecMenuData() {
if (this.__onceExecMenuData == null) {
this.__onceExecMenuData = new utils.Dictionary();
}
return this.__onceExecMenuData;
},
/**
* 成功只执行了一次的项
*/
get onceExecData() {
if (this.__onceExecData == null) {
this.__onceExecData = new utils.Dictionary();
}
return this.__onceExecData;
},
/** 脚本名,一般用在设置的标题上 */
get scriptName() {
return SCRIPT_NAME;
},
/**
* pops.panel的默认配置
*/
get panelConfig() {
return this.__panelConfig;
},
set panelConfig(value) {
this.__panelConfig = value;
},
/** 菜单项的总值在本地数据配置的键名 */
key: KEY,
/** 菜单项在attributes上配置的菜单键 */
attributeKeyName: ATTRIBUTE_KEY,
/** 菜单项在attributes上配置的菜单默认值 */
attributeDefaultValueName: ATTRIBUTE_DEFAULT_VALUE
},
init() {
this.initContentDefaultValue();
PanelMenu.init();
},
/** 判断是否是顶层窗口 */
isTopWindow() {
return _unsafeWindow.top === _unsafeWindow.self;
},
/** 初始化菜单项的默认值保存到本地数据中 */
initContentDefaultValue() {
const initDefaultValue = (config) => {
if (!config.attributes) {
return;
}
if (config.type === "button" || config.type === "forms" || config.type === "deepMenu") {
return;
}
let menuDefaultConfig = /* @__PURE__ */ new Map();
let key = config.attributes[ATTRIBUTE_KEY];
if (key != null) {
const defaultValue = config.attributes[ATTRIBUTE_DEFAULT_VALUE];
menuDefaultConfig.set(key, defaultValue);
}
let moreMenuDefaultConfig = config.attributes[ATTRIBUTE_INIT_MORE_VALUE];
if (typeof moreMenuDefaultConfig === "object" && moreMenuDefaultConfig) {
Object.keys(moreMenuDefaultConfig).forEach((key2) => {
menuDefaultConfig.set(key2, moreMenuDefaultConfig[key2]);
});
}
if (!menuDefaultConfig.size) {
log.warn(["请先配置键", config]);
return;
}
let __attr_init__ = config.attributes[ATTRIBUTE_INIT];
if (typeof __attr_init__ === "function") {
let __attr_result__ = __attr_init__();
if (typeof __attr_result__ === "boolean" && !__attr_result__) {
return;
}
}
if (config.type === "switch") {
let disabled = typeof config.disabled === "function" ? config.disabled() : config.disabled;
if (typeof disabled === "boolean" && disabled) {
this.$data.contentConfigInitDisabledKeys.push(
...menuDefaultConfig.keys()
);
}
}
for (const [__key, __defaultValue] of menuDefaultConfig.entries()) {
this.setDefaultValue(__key, __defaultValue);
}
};
const loopInitDefaultValue = (configList) => {
for (let index = 0; index < configList.length; index++) {
let configItem = configList[index];
initDefaultValue(configItem);
let childForms = configItem.forms;
if (childForms && Array.isArray(childForms)) {
loopInitDefaultValue(childForms);
}
}
};
const contentConfigList = [...PanelContent.getAllContentConfig()];
for (let index = 0; index < contentConfigList.length; index++) {
let leftContentConfigItem = contentConfigList[index];
if (!leftContentConfigItem.forms) {
continue;
}
const rightContentConfigList = leftContentConfigItem.forms;
if (rightContentConfigList && Array.isArray(rightContentConfigList)) {
loopInitDefaultValue(rightContentConfigList);
}
}
this.$data.contentConfigInitDisabledKeys = [
...new Set(this.$data.contentConfigInitDisabledKeys)
];
},
/**
* 设置初始化使用的默认值
*/
setDefaultValue(key, defaultValue) {
if (this.$data.contentConfigInitDefaultValue.has(key)) {
log.warn("请检查该key(已存在): " + key);
}
this.$data.contentConfigInitDefaultValue.set(key, defaultValue);
},
/**
* 设置值
* @param key 键
* @param value 值
*/
setValue(key, value) {
PopsPanelStorageApi.set(key, value);
},
/**
* 获取值
* @param key 键
* @param defaultValue 默认值
*/
getValue(key, defaultValue) {
let localValue = PopsPanelStorageApi.get(key);
if (localValue == null) {
if (this.$data.contentConfigInitDefaultValue.has(key)) {
return this.$data.contentConfigInitDefaultValue.get(key);
}
return defaultValue;
}
return localValue;
},
/**
* 删除值
* @param key 键
*/
deleteValue(key) {
PopsPanelStorageApi.delete(key);
},
/**
* 判断该键是否存在
* @param key 键
*/
hasKey(key) {
return PopsPanelStorageApi.has(key);
},
/**
* 监听调用setValue、deleteValue
* @param key 需要监听的键
* @param callback
*/
addValueChangeListener(key, callback) {
let listenerId = PopsPanelStorageApi.addValueChangeListener(
key,
(__key, __newValue, __oldValue) => {
callback(key, __oldValue, __newValue);
}
);
return listenerId;
},
/**
* 移除监听
* @param listenerId 监听的id
*/
removeValueChangeListener(listenerId) {
PopsPanelStorageApi.removeValueChangeListener(listenerId);
},
/**
* 主动触发菜单值改变的回调
* @param key 菜单键
* @param newValue 想要触发的新值,默认使用当前值
* @param oldValue 想要触发的旧值,默认使用当前值
*/
triggerMenuValueChange(key, newValue, oldValue) {
PopsPanelStorageApi.triggerValueChangeListener(key, oldValue, newValue);
},
/**
* 移除已执行的仅执行一次的菜单
* @param key 键
*/
deleteExecMenuOnce(key) {
this.$data.onceExecMenuData.delete(key);
let flag = PopsPanelStorageApi.removeValueChangeListener(key);
return flag;
},
/**
* 移除已执行的仅执行一次的菜单
* @param key 键
*/
deleteOnceExec(key) {
this.$data.onceExecData.delete(key);
},
/**
* 执行菜单
*
* @param queryKey 判断的键,如果是字符串列表,那么它们的判断处理方式是与关系
* @param callback 执行的回调函数
* @param checkExec 判断是否执行回调
*
* (默认)如果想要每个菜单是`与`关系,即每个菜单都判断为开启,那么就判断它们的值&就行
*
* 如果想要任意菜单存在true再执行,那么判断它们的值|就行
*
* + 返回值都为`true`,执行回调,如果回调返回了
`
)
});
Comments.commentContainer = commentContainer;
Comments.init();
let totalElement = domUtils.createElement("div", {
className: "little-red-book-comments-total",
innerHTML: `共 ${Comments.commentData["commentCount"] ?? Comments.noteData["comments"]} 条评论`
});
commentContainer.appendChild(totalElement);
if (Comments.commentData && Comments.commentData["comments"]) {
log.info("从固定的评论中加载");
Comments.commentData["comments"].forEach((commentItem) => {
let commentItemElement = Comments.getCommentElement(commentItem);
commentContainer.appendChild(commentItemElement);
});
}
domUtils.append(noteViewContainer, commentContainer);
});
},
/**
* 优化图片浏览
*/
optimizeImageBrowsing() {
log.info("优化图片浏览");
CommonUtil.setGMResourceCSS(GM_RESOURCE_MAPPING.Viewer);
function viewIMG(imgSrcList = [], index = 0) {
let viewerULNodeHTML = "";
imgSrcList.forEach((item) => {
viewerULNodeHTML += `
`;
});
let viewerULNode = domUtils.createElement("ul", {
innerHTML: viewerULNodeHTML
});
let viewer = new __viewer(viewerULNode, {
inline: false,
url: "data-src",
zIndex: utils.getMaxZIndex() + 100,
hidden: () => {
viewer.destroy();
}
});
index = index < 0 ? 0 : index;
viewer.view(index);
viewer.zoomTo(1);
viewer.show();
}
domUtils.on(document, "click", ".note-image-box", function(event) {
let clickElement = event.target;
let imgElement = clickElement.querySelector("img");
let imgList = [];
let imgBoxList = [];
if (clickElement.closest(".onix-carousel-item")) {
imgBoxList = Array.from(
clickElement.closest(".onix-carousel-item").parentElement.querySelectorAll("img")
);
} else {
imgBoxList = [imgElement];
}
let index = imgBoxList.findIndex((value) => {
return value == imgElement;
});
imgBoxList.forEach((element) => {
let imgSrc = element.getAttribute("src") || element.getAttribute("data-src") || element.getAttribute("alt");
if (imgSrc) {
imgList.push(imgSrc);
}
});
log.success(["点击浏览图片👉", imgList[index]]);
viewIMG(imgList, index);
});
}
};
const M_XHSHome = {
init() {
domUtils.ready(() => {
Panel.execMenuOnce("little-red-book-repariClick", () => {
M_XHSHome.repariClick();
});
});
},
/**
* 修复正确的点击跳转-用户主页
* 点啥都不好使,都会跳转至下载页面
*/
repariClick() {
log.info("修复正确的点击跳转");
domUtils.on(
document,
"click",
void 0,
function(event) {
let clickElement = event.target;
log.info(["点击的按钮元素", clickElement]);
if (clickElement?.className?.includes("follow-btn")) {
log.success("点击-关注按钮");
} else if (clickElement?.closest("button.reds-button.message-btn")) {
log.success("点击-私信按钮");
} else if (clickElement?.closest("div.reds-tab-item")) {
log.success("点击-笔记/收藏按钮");
} else if (clickElement?.closest("section.reds-note-card")) {
log.success("点击-笔记卡片");
let sectionElement = clickElement?.closest(
"section.reds-note-card"
);
let note_id = sectionElement.getAttribute("id") || utils.toJSON(sectionElement.getAttribute("impression"))?.["noteTarget"]?.["value"]?.["noteId"];
if (note_id) {
window.open(
`https://www.xiaohongshu.com/discovery/item/${clickElement?.closest("section.reds-note-card")?.getAttribute("id")}`,
"_blank"
);
} else {
Qmsg.error("获取笔记note_id失败");
}
}
utils.preventEvent(event);
return false;
},
{
capture: true
}
);
}
};
const M_XHS = {
init() {
Panel.execMenuOnce("little-red-book-shieldAd", () => {
log.info("注入默认屏蔽CSS");
return addStyle(blockCSS$2);
});
Panel.execMenuOnce("little-red-book-allowCopy", () => {
return M_XHS.allowCopy();
});
if (XHSRouter.isArticle()) {
M_XHSArticle.init();
} else if (XHSRouter.isUserHome()) {
M_XHSHome.init();
}
},
/**
* 允许复制
*/
allowCopy() {
log.info("允许复制文字");
return addStyle(
/*css*/
`
*{
-webkit-user-select: unset !important;
user-select: unset !important;
}
`
);
}
};
const blockCSS = "";
const XHSBlock = {
init() {
Panel.execMenuOnce("pc-xhs-shieldAd", () => {
return addStyle(blockCSS);
});
Panel.execMenuOnce("pc-xhs-shield-select-text-search-position", () => {
return this.blockSelectTextVisibleSearchPosition();
});
Panel.execMenuOnce("pc-xhs-shield-topToolbar", () => {
return this.blockTopToolbar();
});
domUtils.ready(() => {
Panel.execMenuOnce("pc-xhs-shield-login-dialog", () => {
this.blockLoginContainer();
});
});
},
/**
* 屏蔽登录弹窗显示
*/
blockLoginContainer() {
log.info("添加屏蔽登录弹窗CSS,监听登录弹窗出现");
CommonUtil.addBlockCSS(".login-container");
utils.mutationObserver(document.body, {
config: {
subtree: true,
childList: true
},
callback: () => {
let $close = document.querySelector(
".login-container .icon-btn-wrapper"
);
if ($close) {
$close.click();
log.success("登录弹窗出现,关闭");
}
}
});
},
/**
* 屏蔽选择文字弹出的搜索提示
*/
blockSelectTextVisibleSearchPosition() {
log.info("屏蔽选择文字弹出的搜索提示");
return CommonUtil.addBlockCSS(".search-position");
},
/**
* 【屏蔽】顶部工具栏
*/
blockTopToolbar() {
log.info("【屏蔽】顶部工具栏");
return [
CommonUtil.addBlockCSS("#headerContainer", ".header-container"),
addStyle(
/*css*/
`
/* 主内容去除padding */
#mfContainer{
padding-top: 0 !important;
}
.outer-link-container{
margin-top: 0 !important;
height: 100vh !important;
padding: 30px 0;
}
#noteContainer{
height: 100%;
}
`
)
];
}
};
const XHSUrlApi = {
/**
* 获取搜索链接
* @param searchText
* @returns
*/
getSearchUrl(searchText) {
return `https://www.xiaohongshu.com/search_result?keyword=${searchText}&source=web_explore_feed`;
}
};
const VueUtils = {
/**
* 获取vue2实例
* @param $el
*/
getVue($el) {
if ($el == null) {
return;
}
return $el["__vue__"] || $el["__Ivue__"] || $el["__IVue__"];
},
/**
* 获取vue3实例
* @param $el
*/
getVue3($el) {
if ($el == null) {
return;
}
return $el["__vueParentComponent"];
},
/**
* 等待vue属性并进行设置
* @param $el 目标对象
* @param checkOption 需要设置的配置
*/
waitVuePropToSet($el, checkOption) {
if (!Array.isArray(checkOption)) {
checkOption = [checkOption];
}
function getTarget() {
let __target__ = null;
if (typeof $el === "string") {
__target__ = domUtils.selector($el);
} else if (typeof $el === "function") {
__target__ = $el();
} else if ($el instanceof HTMLElement) {
__target__ = $el;
}
return __target__;
}
checkOption.forEach((needSetOption) => {
if (typeof needSetOption.msg === "string") {
log.info(needSetOption.msg);
}
function checkTarget() {
let $targetEl = getTarget();
if ($targetEl == null) {
return {
status: false,
isTimeout: true,
inst: null,
$el: $targetEl
};
}
let vueInst = VueUtils.getVue($targetEl);
if (vueInst == null) {
return {
status: false,
isTimeout: false,
inst: null,
$el: $targetEl
};
}
let checkResult = needSetOption.check(vueInst, $targetEl);
checkResult = Boolean(checkResult);
return {
status: checkResult,
isTimeout: false,
inst: vueInst,
$el: $targetEl
};
}
utils.waitVueByInterval(
() => {
return getTarget();
},
() => checkTarget().status,
250,
1e4
).then((result) => {
let checkTargetResult = checkTarget();
if (checkTargetResult.status) {
let vueInst = checkTargetResult.inst;
needSetOption.set(vueInst, checkTargetResult.$el);
} else {
if (typeof needSetOption.failWait === "function") {
needSetOption.failWait(checkTargetResult.isTimeout);
}
}
});
});
},
/**
* 观察vue属性的变化
* @param $el 目标对象
* @param key 需要观察的属性
* @param callback 监听回调
* @param watchConfig 监听配置
* @param failWait 当检测失败/超时触发该回调
*/
watchVuePropChange($el, key, callback, watchConfig, failWait) {
let config = utils.assign(
{
immediate: true,
deep: false
},
watchConfig || {}
);
return new Promise((resolve) => {
VueUtils.waitVuePropToSet($el, {
check(vueInstance) {
return typeof vueInstance?.$watch === "function";
},
set(vueInstance) {
let removeWatch = null;
if (typeof key === "function") {
removeWatch = vueInstance.$watch(
() => {
return key(vueInstance);
},
(newValue, oldValue) => {
callback(vueInstance, newValue, oldValue);
},
config
);
} else {
removeWatch = vueInstance.$watch(
key,
(newValue, oldValue) => {
callback(vueInstance, newValue, oldValue);
},
config
);
}
resolve(removeWatch);
},
failWait
});
});
},
/**
* 前往网址
* @param $el 包含vue属性的元素
* @param path 需要跳转的路径
* @param [useRouter=false] 是否强制使用Vue的Router来进行跳转,默认false
*/
goToUrl($el, path, useRouter = false) {
if ($el == null) {
Qmsg.error("跳转Url: $vueNode为空");
log.error("跳转Url: $vueNode为空:" + path);
return;
}
let vueInstance = VueUtils.getVue($el);
if (vueInstance == null) {
Qmsg.error("获取vue属性失败", { consoleLogContent: true });
return;
}
let $router = vueInstance.$router;
let isBlank = true;
log.info("即将跳转URL:" + path);
if (useRouter) {
isBlank = false;
}
if (isBlank) {
window.open(path, "_blank");
} else {
if (path.startsWith("http") || path.startsWith("//")) {
if (path.startsWith("//")) {
path = window.location.protocol + path;
}
let urlObj = new URL(path);
if (urlObj.origin === window.location.origin) {
path = urlObj.pathname + urlObj.search + urlObj.hash;
} else {
log.info("不同域名,直接本页打开,不用Router:" + path);
window.location.href = path;
return;
}
}
log.info("$router push跳转Url:" + path);
$router.push(path);
}
},
/**
* 手势返回
* @param option 配置
*/
hookGestureReturnByVueRouter(option) {
function popstateEvent() {
log.success("触发popstate事件");
resumeBack(true);
}
function banBack() {
log.success("监听地址改变");
option.vueInst.$router.history.push(option.hash);
domUtils.on(_unsafeWindow, "popstate", popstateEvent);
}
async function resumeBack(isFromPopState = false) {
domUtils.off(_unsafeWindow, "popstate", popstateEvent);
let callbackResult = option.callback(isFromPopState);
if (callbackResult) {
return;
}
while (1) {
if (option.vueInst.$router.history.current.hash === option.hash) {
log.info("后退!");
option.vueInst.$router.back();
await utils.sleep(250);
} else {
return;
}
}
}
banBack();
return {
resumeBack
};
}
};
const XHSArticle = {
init() {
if (Panel.getValue("pc-xhs-search-open-blank-btn") || Panel.getValue("pc-xhs-search-open-blank-keyboard-enter")) {
this.optimizationSearch();
}
Panel.execMenuOnce("pc-xhs-article-fullWidth", () => {
return this.fullWidth();
});
},
/**
* 优化搜索
*/
optimizationSearch() {
function blankSearchText(searchText, isBlank = true) {
{
let $searchText = document.querySelector("#search-input");
if ($searchText) {
let searchText2 = $searchText.value;
let searchUrl = XHSUrlApi.getSearchUrl(searchText2);
log.info("搜索内容: " + searchText2);
window.open(searchUrl, isBlank ? "_blank" : "_self");
} else {
Qmsg.error("未找到搜索的输入框");
}
}
}
utils.waitNode("#search-input").then(($searchInput) => {
$searchInput.placeholder = "搜索小红书";
Panel.execMenu("pc-xhs-search-open-blank-keyboard-enter", () => {
domUtils.listenKeyboard(
$searchInput,
"keydown",
(keyName, keyValue, otherCodeList, event) => {
if (keyName === "Enter" && !otherCodeList.length) {
log.info("按下回车键");
utils.preventEvent(event);
$searchInput.blur();
blankSearchText();
}
}
);
});
});
utils.waitNode("#search-input + .input-button .search-icon").then(($searchIconBtn) => {
Panel.execMenu("pc-xhs-search-open-blank-btn", () => {
domUtils.on(
$searchIconBtn,
"click",
(event) => {
utils.preventEvent(event);
log.info("点击搜索按钮");
blankSearchText();
},
{
capture: true
}
);
});
});
},
/**
* 笔记宽屏
*/
fullWidth() {
log.info("笔记宽屏");
let noteContainerWidth = Panel.getValue(
"pc-xhs-article-fullWidth-widthSize",
90
);
return addStyle(
/*css*/
`
.main-container .main-content{
padding-left: 0 !important;
}
.outer-link-container{
width: 100% !important;
}
/* 隐藏左侧工具栏 */
.main-container .side-bar{
display: none !important;
}
#noteContainer{
width: ${noteContainerWidth}vw;
}
`
);
},
/**
* 转换笔记发布时间
*/
transformPublishTime() {
log.info(`转换笔记发布时间`);
let lockFn = new utils.LockFunction(() => {
$$(".note-content:not([data-edit-date])").forEach(
($noteContent) => {
let vueInstance = VueUtils.getVue($noteContent);
if (!vueInstance) {
return;
}
let note = vueInstance?._?.props?.note;
if (note == null) {
return;
}
let publishTime = note.time;
let lastUpdateTime = note.lastUpdateTime;
let ipLocation = note.ipLocation;
if (typeof publishTime === "number") {
let detailTimeLocationInfo = [];
detailTimeLocationInfo.push(
`发布:${utils.formatTime(publishTime)}`
);
if (typeof lastUpdateTime === "number") {
detailTimeLocationInfo.push(
`修改:${utils.formatTime(lastUpdateTime)}`
);
}
if (typeof ipLocation === "string" && utils.isNotNull(ipLocation)) {
detailTimeLocationInfo.push(ipLocation);
}
let $date = $noteContent.querySelector(".date");
domUtils.html($date, detailTimeLocationInfo.join("
"));
$noteContent.setAttribute("data-edit-date", "");
}
}
);
});
utils.mutationObserver(document, {
config: {
subtree: true,
childList: true
},
callback: () => {
lockFn.run();
}
});
}
};
const PanelComponents = {
$data: {
__storeApiFn: null,
get storeApiValue() {
if (!this.__storeApiFn) {
this.__storeApiFn = new Utils.Dictionary();
}
return this.__storeApiFn;
}
},
/**
* 获取自定义的存储接口
* @param type 组件类型
*/
getStorageApi(type) {
if (!this.hasStorageApi(type)) {
return;
}
return this.$data.storeApiValue.get(type);
},
/**
* 判断是否存在自定义的存储接口
* @param type 组件类型
*/
hasStorageApi(type) {
return this.$data.storeApiValue.has(type);
},
/**
* 设置自定义的存储接口
* @param type 组件类型
* @param storageApiValue 存储接口
*/
setStorageApi(type, storageApiValue) {
this.$data.storeApiValue.set(type, storageApiValue);
},
/**
* 初始化组件的存储接口属性
*
* @param type 组件类型
* @param config 组件配置,必须包含prop属性
* @param storageApiValue 存储接口
*/
initComponentsStorageApi(type, config, storageApiValue) {
let propsStorageApi;
if (this.hasStorageApi(type)) {
propsStorageApi = this.getStorageApi(type);
} else {
propsStorageApi = storageApiValue;
}
this.setComponentsStorageApiProperty(config, propsStorageApi);
},
/**
* 设置组件的存储接口属性
* @param config 组件配置,必须包含prop属性
* @param storageApiValue 存储接口
*/
setComponentsStorageApiProperty(config, storageApiValue) {
Reflect.set(config.props, PROPS_STORAGE_API, storageApiValue);
}
};
const UIInput = function(text, key, defaultValue, description, changeCallback, placeholder = "", isNumber, isPassword, afterAddToUListCallBack) {
let result = {
text,
type: "input",
isNumber: Boolean(isNumber),
isPassword: Boolean(isPassword),
attributes: {},
props: {},
description,
afterAddToUListCallBack,
getValue() {
let storageApiValue = this.props[PROPS_STORAGE_API];
return storageApiValue.get(key, defaultValue);
},
callback(event, value, valueAsNumber) {
let storageApiValue = this.props[PROPS_STORAGE_API];
storageApiValue.set(key, value);
},
placeholder
};
Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
PanelComponents.initComponentsStorageApi(
"input",
result,
{
get(key2, defaultValue2) {
return Panel.getValue(key2, defaultValue2);
},
set(key2, value) {
Panel.setValue(key2, value);
}
}
);
return result;
};
const UISwitch = function(text, key, defaultValue, clickCallback, description, afterAddToUListCallBack, disabled) {
let result = {
text,
type: "switch",
description,
disabled,
attributes: {},
props: {},
getValue() {
let storageApiValue = this.props[PROPS_STORAGE_API];
let value = storageApiValue.get(key, defaultValue);
return value;
},
callback(event, __value) {
let value = Boolean(__value);
log.success(`${value ? "开启" : "关闭"} ${text}`);
let storageApiValue = this.props[PROPS_STORAGE_API];
storageApiValue.set(key, value);
},
afterAddToUListCallBack
};
Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
PanelComponents.initComponentsStorageApi(
"switch",
result,
{
get(key2, defaultValue2) {
return Panel.getValue(key2, defaultValue2);
},
set(key2, value) {
Panel.setValue(key2, value);
}
}
);
return result;
};
const UISelectMultiple = function(text, key, defaultValue, data, changeCallback, description, placeholder = "请至少选择一个选项", selectConfirmDialogDetails) {
let selectData = [];
if (typeof data === "function") {
selectData = data();
} else {
selectData = data;
}
let result = {
text,
type: "select-multiple",
description,
placeholder,
attributes: {},
props: {},
getValue() {
let storageApiValue = this.props[PROPS_STORAGE_API];
return storageApiValue.get(key, defaultValue);
},
selectConfirmDialogDetails,
callback(selectInfo) {
let storageApiValue = this.props[PROPS_STORAGE_API];
let value = [];
selectInfo.forEach((selectedInfo) => {
value.push(selectedInfo.value);
});
log.info(`多选-选择:`, value);
storageApiValue.set(key, value);
},
data: selectData
};
Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
PanelComponents.initComponentsStorageApi(
"select-multiple",
result,
{
get(key2, defaultValue2) {
return Panel.getValue(key2, defaultValue2);
},
set(key2, value) {
Panel.setValue(key2, value);
}
}
);
return result;
};
const UITextArea = function(text, key, defaultValue, description, changeCallback, placeholder = "", disabled) {
let result = {
text,
type: "textarea",
attributes: {},
props: {},
description,
placeholder,
disabled,
getValue() {
let storageApiValue = this.props[PROPS_STORAGE_API];
let value = storageApiValue.get(key, defaultValue);
if (Array.isArray(value)) {
return value.join("\n");
}
return value;
},
callback(event, value) {
let storageApiValue = this.props[PROPS_STORAGE_API];
storageApiValue.set(key, value);
}
};
Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
PanelComponents.initComponentsStorageApi(
"switch",
result,
{
get(key2, defaultValue2) {
return Panel.getValue(key2, defaultValue2);
},
set(key2, value) {
Panel.setValue(key2, value);
}
}
);
return result;
};
class RuleStorage {
option;
constructor(option) {
this.option = option;
}
/**
* 获取所有规则
*/
getAllRule() {
let allRules = _GM_getValue(this.option.STORAGE_API_KEY, []);
return allRules;
}
/**
* 设置全部规则
*/
setAllRule(rules) {
_GM_setValue(this.option.STORAGE_API_KEY, rules);
}
/**
* 清空所有规则
*/
clearAllRule() {
this.setAllRule([]);
}
/**
* 获取规则
* @param uuid
*/
getRule(uuid) {
let allRules = this.getAllRule();
let findIndex = allRules.findIndex((item) => item.uuid === uuid);
if (findIndex !== -1) {
let rule = allRules[findIndex];
return rule;
}
}
/**
* 设置规则(覆盖规则)
* @param rule 规则
* @returns
* + true 成功覆盖
* + false 未找到规则
*/
setRule(rule) {
let allRules = this.getAllRule();
let findIndex = allRules.findIndex((item) => item.uuid === rule.uuid);
let updateFlag = false;
if (findIndex !== -1) {
allRules[findIndex] = rule;
this.setAllRule(allRules);
updateFlag = true;
}
return updateFlag;
}
/**
* 添加规则
*/
addRule(rule) {
let allRules = this.getAllRule();
let findIndex = allRules.findIndex((item) => item.uuid === rule.uuid);
let addFlag = false;
if (findIndex !== -1) ;
else {
allRules.push(rule);
this.setAllRule(allRules);
addFlag = true;
}
return addFlag;
}
/**
* 规则规则(有就更新,没有就添加)
* @param rule 规则
*/
updateRule(rule) {
let allRules = this.getAllRule();
let findIndex = allRules.findIndex((item) => item.uuid === rule.uuid);
if (findIndex !== -1) {
allRules[findIndex] = rule;
} else {
allRules.push(rule);
}
this.setAllRule(allRules);
}
/**
* 删除规则
* @param rule 规则/规则的uuid
*/
deleteRule(rule) {
let allRules = this.getAllRule();
let ruleUUID = typeof rule === "string" ? rule : rule.uuid;
let findIndex = allRules.findIndex((item) => item.uuid === ruleUUID);
if (findIndex !== -1) {
allRules.splice(findIndex, 1);
this.setAllRule(allRules);
return true;
} else {
return false;
}
}
/**
* 导入规则
* @param importEndCallBack 导入完毕后的回调
*/
importRules(importEndCallBack) {
let $alert = __pops.alert({
title: {
text: "请选择导入方式",
position: "center"
},
content: {
text: (
/*html*/
`
本地导入
网络导入
剪贴板导入
`
),
html: true
},
btn: {
ok: { enable: false },
close: {
enable: true,
callback(details, event) {
details.close();
}
}
},
mask: { enable: true },
drag: true,
width: PanelUISize.info.width,
height: PanelUISize.info.height,
style: (
/*css*/
`
.btn-control{
display: inline-block;
margin: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
cursor: pointer;
}
`
)
});
let $local = $alert.$shadowRoot.querySelector(
".btn-control[data-mode='local']"
);
let $network = $alert.$shadowRoot.querySelector(
".btn-control[data-mode='network']"
);
let $clipboard = $alert.$shadowRoot.querySelector(
".btn-control[data-mode='clipboard']"
);
let updateRuleToStorage = async (data) => {
let allData = this.getAllRule();
let addNewData = [];
let repeatData = [];
let isRepeat = false;
for (let index = 0; index < data.length; index++) {
const dataItem = data[index];
let findIndex = allData.findIndex((it) => it.uuid === dataItem.uuid);
if (findIndex !== -1) {
repeatData.push({
index: findIndex,
data: dataItem
});
} else {
addNewData.push(dataItem);
}
}
if (repeatData.length) {
let confirmRepeat = await new Promise((resolve) => {
__pops.alert({
title: {
text: "覆盖规则",
position: "center"
},
content: {
text: `存在相同的uuid的规则 ${repeatData.length}条,是否进行覆盖?`,
html: true
},
btn: {
close: {
callback(details, event) {
details.close();
resolve(false);
}
},
ok: {
text: "覆盖",
callback(details, event) {
details.close();
resolve(true);
}
}
},
width: PanelUISize.info.width,
height: PanelUISize.info.height,
mask: { enable: true },
drag: true
});
});
if (confirmRepeat) {
for (const repeatDataItem of repeatData) {
allData[repeatDataItem.index] = repeatDataItem.data;
}
isRepeat = true;
}
}
if (addNewData.length) {
allData = allData.concat(addNewData);
}
this.setAllRule(allData);
let message = `共 ${data.length} 条规则,新增 ${addNewData.length} 条,覆盖 ${isRepeat ? repeatData.length : 0} 条`;
Qmsg.success(message);
importEndCallBack?.();
};
let importFile = (subscribeText) => {
return new Promise(async (resolve) => {
let data = utils.toJSON(subscribeText);
if (!Array.isArray(data)) {
log.error(data);
Qmsg.error("导入失败,格式不符合(不是数组)", {
consoleLogContent: true
});
resolve(false);
return;
}
if (!data.length) {
Qmsg.error("导入失败,解析出的数据为空", {
consoleLogContent: true
});
resolve(false);
return;
}
await updateRuleToStorage(data);
resolve(true);
});
};
domUtils.on($local, "click", (event) => {
utils.preventEvent(event);
$alert.close();
let $input = domUtils.createElement("input", {
type: "file",
accept: ".json"
});
domUtils.on($input, ["propertychange", "input"], (event2) => {
if (!$input.files?.length) {
return;
}
let uploadFile = $input.files[0];
let fileReader = new FileReader();
fileReader.onload = () => {
importFile(fileReader.result);
};
fileReader.readAsText(uploadFile, "UTF-8");
});
$input.click();
});
domUtils.on($network, "click", (event) => {
utils.preventEvent(event);
$alert.close();
let $prompt = __pops.prompt({
title: {
text: "网络导入",
position: "center"
},
content: {
text: "",
placeholder: "请填写URL",
focus: true
},
btn: {
close: {
enable: true,
callback(details, event2) {
details.close();
}
},
ok: {
text: "导入",
callback: async (eventDetails, event2) => {
let url = eventDetails.text;
if (utils.isNull(url)) {
Qmsg.error("请填入完整的url");
return;
}
let $loading = Qmsg.loading("正在获取配置...");
let response = await httpx.get(url, {
allowInterceptConfig: false
});
$loading.close();
if (!response.status) {
log.error(response);
Qmsg.error("获取配置失败", { consoleLogContent: true });
return;
}
let flag = await importFile(response.data.responseText);
if (!flag) {
return;
}
eventDetails.close();
}
},
cancel: {
enable: false
}
},
mask: { enable: true },
drag: true,
width: PanelUISize.info.width,
height: "auto"
});
let $promptInput = $prompt.$shadowRoot.querySelector("input");
let $promptOk = $prompt.$shadowRoot.querySelector(
".pops-prompt-btn-ok"
);
domUtils.on($promptInput, ["input", "propertychange"], (event2) => {
let value = domUtils.val($promptInput);
if (value === "") {
domUtils.attr($promptOk, "disabled", "true");
} else {
domUtils.removeAttr($promptOk, "disabled");
}
});
domUtils.listenKeyboard(
$promptInput,
"keydown",
(keyName, keyValue, otherCodeList) => {
if (keyName === "Enter" && otherCodeList.length === 0) {
let value = domUtils.val($promptInput);
if (value !== "") {
utils.dispatchEvent($promptOk, "click");
}
}
}
);
utils.dispatchEvent($promptInput, "input");
});
domUtils.on($clipboard, "click", async (event) => {
utils.preventEvent(event);
$alert.close();
let clipboardInfo = await utils.getClipboardInfo();
if (clipboardInfo.error != null) {
Qmsg.error(clipboardInfo.error.toString());
return;
}
if (clipboardInfo.content.trim() === "") {
Qmsg.warning("获取到的剪贴板内容为空");
return;
}
let flag = await importFile(clipboardInfo.content);
if (!flag) {
return;
}
});
}
/**
* 导出规则
*/
exportRules(fileName = "rule.json") {
let allRules = this.getAllRule();
let blob = new Blob([JSON.stringify(allRules, null, 4)]);
let blobUrl = globalThis.URL.createObjectURL(blob);
let $a = document.createElement("a");
$a.href = blobUrl;
$a.download = fileName;
$a.click();
setTimeout(() => {
globalThis.URL.revokeObjectURL(blobUrl);
}, 1500);
}
}
class RuleEditView {
option;
constructor(option) {
this.option = option;
}
/**
* 显示视图
*/
async showView() {
let $dialog = __pops.confirm({
title: {
text: this.option.title,
position: "center"
},
content: {
text: (
/*html*/
`
`
),
html: true
},
btn: utils.assign(
{
ok: {
callback: async () => {
await submitSaveOption();
}
}
},
this.option.btn || {},
true
),
drag: true,
mask: {
enable: true
},
style: (
/*css*/
`
${__pops.config.cssText.panelCSS}
.rule-form-container {
}
.rule-form-container li{
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 20px;
gap: 10px;
}
.rule-form-ulist-dynamic{
--button-margin-top: 0px;
--button-margin-right: 0px;
--button-margin-bottom: 0px;
--button-margin-left: 0px;
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 5px 0px 5px 20px;
}
.rule-form-ulist-dynamic__inner{
width: 100%;
}
.rule-form-ulist-dynamic__inner-container{
display: flex;
align-items: center;
}
.dynamic-forms{
width: 100%;
}
.pops-panel-item-left-main-text{
max-width: 150px;
}
.pops-panel-item-right-text{
padding-left: 30px;
}
.pops-panel-item-right-text,
.pops-panel-item-right-main-text{
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.pops-panel-item-left-desc-text{
line-height: normal;
margin-top: 6px;
font-size: 0.8em;
color: rgb(108, 108, 108);
}
${this.option?.style ?? ""}
`
),
width: typeof this.option.width === "function" ? this.option.width() : window.innerWidth > 500 ? "500px" : "88vw",
height: typeof this.option.height === "function" ? this.option.height() : window.innerHeight > 500 ? "500px" : "80vh"
});
let $form = $dialog.$shadowRoot.querySelector(
".rule-form-container"
);
$dialog.$shadowRoot.querySelector(
"input[type=submit]"
);
let $ulist = $dialog.$shadowRoot.querySelector(".rule-form-ulist");
let view = await this.option.getView(await this.option.data());
$ulist.appendChild(view);
const submitSaveOption = async () => {
let result = await this.option.onsubmit($form, await this.option.data());
if (!result.success) {
return;
}
$dialog.close();
await this.option.dialogCloseCallBack(true);
};
}
}
class RuleFilterView {
option;
constructor(option) {
this.option = option;
}
showView() {
let $alert = __pops.alert({
title: {
text: this.option.title,
position: "center"
},
content: {
text: (
/*html*/
`
`
)
},
btn: {
ok: {
text: "关闭",
type: "default"
}
},
drag: true,
mask: {
enable: true
},
width: window.innerWidth > 500 ? "350px" : "80vw",
height: window.innerHeight > 500 ? "300px" : "70vh",
style: (
/*css*/
`
.filter-container{
height: 100%;
display: flex;
flex-direction: column;
gap: 20px;
}
.filter-container button{
text-wrap: wrap;
padding: 8px;
height: auto;
text-align: left;
}
`
)
});
let $filterContainer = $alert.$shadowRoot.querySelector(".filter-container");
let $fragment = document.createDocumentFragment();
this.option.filterOption.forEach((filterOption) => {
let $button = document.createElement("button");
$button.innerText = filterOption.name;
let execFilterAndCloseDialog = async () => {
let allRuleInfo = await this.option.getAllRuleInfo();
allRuleInfo.forEach(async (ruleInfo) => {
let filterResult = await filterOption.filterCallBack(ruleInfo.data);
if (!filterResult) {
domUtils.hide(ruleInfo.$el, false);
} else {
domUtils.show(ruleInfo.$el, false);
}
});
if (typeof this.option.execFilterCallBack === "function") {
await this.option.execFilterCallBack();
}
$alert.close();
};
domUtils.on($button, "click", async (event) => {
utils.preventEvent(event);
if (typeof filterOption.callback === "function") {
let result = await filterOption.callback(
event,
execFilterAndCloseDialog
);
if (!result) {
return;
}
}
await execFilterAndCloseDialog();
});
$fragment.appendChild($button);
});
$filterContainer.appendChild($fragment);
}
}
class RuleView {
option;
constructor(option) {
this.option = option;
}
/**
* 显示视图
* @param filterCallBack 返回值为false隐藏,true则不隐藏(不处理)
*/
async showView(filterCallBack) {
let $popsConfirm = __pops.confirm({
title: {
text: this.option.title,
position: "center"
},
content: {
text: (
/*html*/
`
`
),
html: true
},
btn: {
merge: true,
reverse: false,
position: "space-between",
ok: {
enable: this.option?.bottomControls?.add?.enable || true,
type: "primary",
text: "添加",
callback: async (event) => {
this.showEditView(
false,
await this.option.getAddData(),
$popsConfirm.$shadowRoot
);
}
},
close: {
enable: true,
callback(event) {
$popsConfirm.close();
}
},
cancel: {
enable: this.option?.bottomControls?.filter?.enable || false,
type: "default",
text: "过滤",
callback: (details, event) => {
if (typeof this.option?.bottomControls?.filter?.callback === "function") {
this.option.bottomControls.filter.callback();
}
let getAllRuleElement = () => {
return Array.from(
$popsConfirm.$shadowRoot.querySelectorAll(
".rule-view-container .rule-item"
)
);
};
let $button = event.target.closest(".pops-confirm-btn").querySelector(".pops-confirm-btn-cancel span");
if (domUtils.text($button).includes("取消")) {
getAllRuleElement().forEach(($el) => {
domUtils.show($el, false);
});
domUtils.text($button, "过滤");
} else {
let ruleFilterView = new RuleFilterView({
title: this.option.bottomControls?.filter?.title ?? "过滤规则",
filterOption: this.option.bottomControls?.filter?.option || [],
execFilterCallBack() {
domUtils.text($button, "取消过滤");
},
getAllRuleInfo: () => {
return getAllRuleElement().map(($el) => {
return {
data: this.parseRuleItemElement($el).data,
$el
};
});
}
});
ruleFilterView.showView();
}
}
},
other: {
enable: this.option?.bottomControls?.clear?.enable || true,
type: "xiaomi-primary",
text: `清空所有(${(await this.option.data()).length})`,
callback: (event) => {
let $askDialog = __pops.confirm({
title: {
text: "提示",
position: "center"
},
content: {
text: "确定清空所有的数据?",
html: false
},
btn: {
ok: {
enable: true,
callback: async (popsEvent) => {
log.success("清空所有");
if (typeof this.option?.bottomControls?.clear?.callback === "function") {
this.option.bottomControls.clear.callback();
}
let data = await this.option.data();
if (data.length) {
Qmsg.error("清理失败");
return;
} else {
Qmsg.success("清理成功");
}
await this.updateDeleteAllBtnText($popsConfirm.$shadowRoot);
this.clearContent($popsConfirm.$shadowRoot);
$askDialog.close();
}
},
cancel: {
text: "取消",
enable: true
}
},
mask: { enable: true },
width: "300px",
height: "200px"
});
}
}
},
mask: {
enable: true
},
width: window.innerWidth > 500 ? "500px" : "88vw",
height: window.innerHeight > 500 ? "500px" : "80vh",
style: (
/*css*/
`
${__pops.config.cssText.panelCSS}
.rule-item{
display: flex;
align-items: center;
line-height: normal;
font-size: 16px;
padding: 4px 8px;
gap: 8px;
}
.rule-name{
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.rule-controls{
display: flex;
align-items: center;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
gap: 8px;
padding: 0px;
}
.rule-controls-enable{
}
.rule-controls-edit{
}
.rule-controls-delete{
}
.rule-controls-edit,
.rule-controls-delete{
width: 16px;
height: 16px;
cursor: pointer;
}
`
)
});
let allData = await this.option.data();
let changeButtonText = false;
for (let index = 0; index < allData.length; index++) {
let item = allData[index];
let $ruleItemList = await this.appendRuleItemElement(
$popsConfirm.$shadowRoot,
item
);
let flag = typeof filterCallBack === "function" ? filterCallBack(item) : true;
if (!flag) {
changeButtonText = true;
$ruleItemList.forEach(($el) => {
domUtils.hide($el, false);
});
}
}
if (changeButtonText) {
let $button = $popsConfirm.$shadowRoot.querySelector(
".pops-confirm-btn-cancel span"
);
domUtils.text($button, "取消过滤");
}
}
/**
* 显示编辑视图
* @param isEdit 是否是编辑状态
* @param editData 编辑的数据
* @param $parentShadowRoot (可选)关闭弹窗后对ShadowRoot进行操作
* @param $editRuleItemElement (可选)关闭弹窗后对规则行进行更新数据
* @param updateDataCallBack (可选)关闭添加/编辑弹窗的回调(不更新数据)
* @param submitCallBack (可选)添加/修改提交的回调
*/
showEditView(isEdit, editData, $parentShadowRoot, $editRuleItemElement, updateDataCallBack, submitCallBack) {
let dialogCloseCallBack = async (isSubmit) => {
if (isSubmit) {
if (typeof submitCallBack === "function") {
let newData = await this.option.getData(editData);
submitCallBack(newData);
}
} else {
if (!isEdit) {
await this.option.deleteData(editData);
}
if (typeof updateDataCallBack === "function") {
let newData = await this.option.getData(editData);
updateDataCallBack(newData);
}
}
};
let editView = new RuleEditView({
title: isEdit ? "编辑" : "添加",
data: () => {
return editData;
},
dialogCloseCallBack,
getView: async (data) => {
return await this.option.itemControls.edit.getView(data, isEdit);
},
btn: {
ok: {
enable: true,
text: isEdit ? "修改" : "添加"
},
cancel: {
callback: async (detail, event) => {
detail.close();
await dialogCloseCallBack(false);
}
},
close: {
callback: async (detail, event) => {
detail.close();
await dialogCloseCallBack(false);
}
}
},
onsubmit: async ($form, data) => {
let result = await this.option.itemControls.edit.onsubmit(
$form,
isEdit,
data
);
if (result.success) {
if (isEdit) {
Qmsg.success("修改成功");
$parentShadowRoot && await this.updateRuleItemElement(
result.data,
$editRuleItemElement,
$parentShadowRoot
);
} else {
$parentShadowRoot && await this.appendRuleItemElement(
$parentShadowRoot,
result.data
);
}
} else {
if (isEdit) {
log.error("修改失败");
}
}
return result;
},
style: this.option.itemControls.edit.style,
width: this.option.itemControls.edit.width,
height: this.option.itemControls.edit.height
});
editView.showView();
}
/**
* 解析弹窗内的各个元素
*/
parseViewElement($shadowRoot) {
let $container = $shadowRoot.querySelector(
".rule-view-container"
);
let $deleteBtn = $shadowRoot.querySelector(
".pops-confirm-btn button.pops-confirm-btn-other"
);
return {
/** 容器 */
$container,
/** 左下角的清空按钮 */
$deleteBtn
};
}
/**
* 解析每一项的元素
*/
parseRuleItemElement($ruleElement) {
let $enable = $ruleElement.querySelector(
".rule-controls-enable"
);
let $enableSwitch = $enable.querySelector(".pops-panel-switch");
let $enableSwitchInput = $enable.querySelector(
".pops-panel-switch__input"
);
let $enableSwitchCore = $enable.querySelector(
".pops-panel-switch__core"
);
let $edit = $ruleElement.querySelector(".rule-controls-edit");
let $delete = $ruleElement.querySelector(
".rule-controls-delete"
);
return {
/** 启用开关 */
$enable,
/** 启用开关的container */
$enableSwitch,
/** 启用开关的input */
$enableSwitchInput,
/** 启用开关的core */
$enableSwitchCore,
/** 编辑按钮 */
$edit,
/** 删除按钮 */
$delete,
/** 存储在元素上的数据 */
data: Reflect.get($ruleElement, "data-rule")
};
}
/**
* 创建一条规则元素
*/
async createRuleItemElement(data, $shadowRoot) {
let name = await this.option.getDataItemName(data);
let $ruleItem = domUtils.createElement("div", {
className: "rule-item",
innerHTML: (
/*html*/
`
${name}
${__pops.config.iconSVG.edit}
${__pops.config.iconSVG.delete}
`
)
});
Reflect.set($ruleItem, "data-rule", data);
let switchCheckedClassName = "pops-panel-switch-is-checked";
const {
$enable,
$enableSwitch,
$enableSwitchCore,
$enableSwitchInput,
$delete,
$edit
} = this.parseRuleItemElement($ruleItem);
if (this.option.itemControls.enable.enable) {
domUtils.on($enableSwitchCore, "click", async (event) => {
let isChecked = false;
if ($enableSwitch.classList.contains(switchCheckedClassName)) {
$enableSwitch.classList.remove(switchCheckedClassName);
isChecked = false;
} else {
$enableSwitch.classList.add(switchCheckedClassName);
isChecked = true;
}
$enableSwitchInput.checked = isChecked;
await this.option.itemControls.enable.callback(data, isChecked);
});
if (await this.option.itemControls.enable.getEnable(data)) {
$enableSwitch.classList.add(switchCheckedClassName);
}
} else {
$enable.remove();
}
if (this.option.itemControls.edit.enable) {
domUtils.on($edit, "click", (event) => {
utils.preventEvent(event);
this.showEditView(true, data, $shadowRoot, $ruleItem, (newData) => {
data = null;
data = newData;
});
});
} else {
$edit.remove();
}
if (this.option.itemControls.delete.enable) {
domUtils.on($delete, "click", (event) => {
utils.preventEvent(event);
let $askDialog = __pops.confirm({
title: {
text: "提示",
position: "center"
},
content: {
text: "确定删除该条数据?",
html: false
},
btn: {
ok: {
enable: true,
callback: async (popsEvent) => {
log.success("删除数据");
let flag = await this.option.itemControls.delete.deleteCallBack(
data
);
if (flag) {
Qmsg.success("成功删除该数据");
$ruleItem.remove();
await this.updateDeleteAllBtnText($shadowRoot);
$askDialog.close();
} else {
Qmsg.error("删除该数据失败");
}
}
},
cancel: {
text: "取消",
enable: true
}
},
mask: {
enable: true
},
width: "300px",
height: "200px"
});
});
} else {
$delete.remove();
}
return $ruleItem;
}
/**
* 添加一个规则元素
*/
async appendRuleItemElement($shadowRoot, data) {
let { $container } = this.parseViewElement($shadowRoot);
let $ruleItem = [];
let iteratorData = Array.isArray(data) ? data : [data];
for (let index = 0; index < iteratorData.length; index++) {
let item = iteratorData[index];
let $item = await this.createRuleItemElement(item, $shadowRoot);
$container.appendChild($item);
$ruleItem.push($item);
}
await this.updateDeleteAllBtnText($shadowRoot);
return $ruleItem;
}
/**
* 更新弹窗内容的元素
*/
async updateRuleContaienrElement($shadowRoot) {
this.clearContent($shadowRoot);
const { $container } = this.parseViewElement($shadowRoot);
let data = await this.option.data();
await this.appendRuleItemElement($shadowRoot, data);
await this.updateDeleteAllBtnText($shadowRoot);
}
/**
* 更新规则元素
*/
async updateRuleItemElement(data, $oldRuleItem, $shadowRoot) {
let $newRuleItem = await this.createRuleItemElement(data, $shadowRoot);
$oldRuleItem.after($newRuleItem);
$oldRuleItem.remove();
}
/**
* 清空内容
*/
clearContent($shadowRoot) {
const { $container } = this.parseViewElement($shadowRoot);
domUtils.html($container, "");
}
/**
* 设置删除按钮的文字
*/
setDeleteBtnText($shadowRoot, text, isHTML = false) {
const { $deleteBtn } = this.parseViewElement($shadowRoot);
if (isHTML) {
domUtils.html($deleteBtn, text);
} else {
domUtils.text($deleteBtn, text);
}
}
/**
* 更新【清空所有】的按钮的文字
* @param $shadowRoot
*/
async updateDeleteAllBtnText($shadowRoot) {
let data = await this.option.data();
this.setDeleteBtnText($shadowRoot, `清空所有(${data.length})`);
}
}
const XHSNetworkHook = {
__ajaxHooker: null,
get ajaxHooker() {
if (this.__ajaxHooker == null) {
this.__ajaxHooker = utils.ajaxHooker();
}
return this.__ajaxHooker;
}
};
class XHSArticleFilterBase {
/**
* 解析信息转为规则过滤的字典
* @param info 数据信息
* @param showLog 是否显示日志输出
*/
parseInfoDictData(info, showLog = false) {
let note_card = info?.note_card;
let articleId = info.id;
let display_title = note_card.display_title;
let isLike = Boolean(note_card?.interact_info?.liked);
let liked_count = 0;
let liked_count_str = info?.note_card?.interact_info?.liked_count;
if (typeof liked_count_str === "string") {
liked_count = parseInt(liked_count_str);
if (isNaN(liked_count)) {
liked_count = 0;
}
}
let nick_name = note_card?.user?.nick_name || note_card?.user?.nickname;
let user_id = note_card?.user?.user_id;
let isVideo = note_card?.type === "video";
let videoDuration = note_card?.video?.capa?.duration || 0;
return {
articleId,
display_title,
isLike,
liked_count,
nick_name,
user_id,
isVideo,
videoDuration
};
}
/**
* 根据视频信息,判断是否需要屏蔽
*/
checkFilterWithRule(details) {
if (details.infoValue == null) {
return false;
}
if (details.ruleValue == null) {
return false;
}
if (typeof details.infoValue === "string") {
if (Boolean(details.infoValue.match(details.ruleValue))) {
return true;
}
} else if (typeof details.infoValue === "object") {
if (Array.isArray(details.infoValue)) {
let findValue = details.infoValue.find((infoDictValue) => {
if (typeof infoDictValue === "string" && details.ruleValue != null) {
return Boolean(infoDictValue.match(details.ruleValue));
} else {
return false;
}
});
if (findValue) {
return true;
}
}
} else if (typeof details.infoValue === "number") {
if (typeof details.ruleValue === "string") {
let ruleValue = details.ruleValue.trim();
let compareNumberMatch = ruleValue.match(/(\d+)/);
if (!compareNumberMatch) {
log.warn("过滤器-解析比较大小的数字失败: ", details);
return false;
}
let compareNumber = Number(compareNumberMatch[1]);
if (ruleValue.startsWith(">")) {
if (ruleValue.startsWith(">=")) {
if (details.infoValue >= compareNumber) {
return true;
}
} else {
if (details.infoValue > compareNumber) {
return true;
}
}
} else if (ruleValue.startsWith("<")) {
if (ruleValue.startsWith("<=")) {
if (details.infoValue <= compareNumber) {
return true;
}
} else {
if (details.infoValue < compareNumber) {
return true;
}
}
} else if (ruleValue.startsWith("=")) {
if (details.infoValue === compareNumber) {
return true;
}
} else {
log.warn("视频过滤器-未经允许的比较符号: ", details);
return false;
}
}
} else if (typeof details.infoValue === "boolean") {
if (typeof details.ruleValue === "string") {
let trimRuleValue = details.ruleValue.trim();
return details.infoValue.toString() === trimRuleValue;
}
}
return false;
}
/**
* 检测视频是否可以屏蔽,可以屏蔽返回true
* @param rule 规则
* @param info 视频信息结构
*/
checkInfoIsFilter(rule, info) {
let transformInfo = this.parseInfoDictData(info);
let flag = false;
let matchedFilterOption = null;
outerLoop: for (let index = 0; index < rule.length; index++) {
const filterOption = rule[index];
const ruleNameList = Array.isArray(filterOption.data.ruleName) ? filterOption.data.ruleName : [filterOption.data.ruleName];
for (let ruleNameIndex = 0; ruleNameIndex < ruleNameList.length; ruleNameIndex++) {
const ruleName = ruleNameList[ruleNameIndex];
if (!Reflect.has(transformInfo, ruleName)) {
continue;
}
let tagKey = ruleName;
let tagValue = transformInfo[tagKey];
let details = {
infoKey: tagKey,
infoValue: tagValue,
ruleKey: filterOption.data.ruleName,
ruleValue: filterOption.data.ruleValue
};
flag = this.checkFilterWithRule(details);
if (flag) {
if (Array.isArray(filterOption.dynamicData) && filterOption.dynamicData.length) {
let dynamicDetailsList = [];
for (let dynamicIndex = 0; dynamicIndex < filterOption.dynamicData.length; dynamicIndex++) {
const dynamicOption = filterOption.dynamicData[dynamicIndex];
let dynamicTagKey = dynamicOption.ruleName;
let dynamicTagValue = transformInfo[dynamicTagKey];
let dynamicDetails = {
infoKey: dynamicTagKey,
infoValue: dynamicTagValue,
ruleKey: dynamicOption.ruleName,
ruleValue: dynamicOption.ruleValue
};
dynamicDetailsList.push(dynamicDetails);
let dynamicCheckFlag = this.checkFilterWithRule(dynamicDetails);
flag = flag && dynamicCheckFlag;
if (!flag) {
break;
}
}
if (flag) {
log.success([
`视频过滤器-多组 ==> ${filterOption.name}`,
transformInfo,
details,
dynamicDetailsList,
info,
filterOption
]);
}
} else {
log.success([
`视频过滤器 ==> ${filterOption.name}`,
transformInfo,
details,
info,
filterOption
]);
}
}
if (flag) {
matchedFilterOption = filterOption;
break outerLoop;
}
}
}
return {
/** 是否允许过滤 */
isFilter: flag,
/** 命中的过滤规则 */
matchedFilterOption,
/** 解析出的视频信息 */
transformInfo,
/** 原始视频信息 */
info
};
}
removeArticle(...args) {
if (args.length === 1) {
let $el = args[0];
if ($el != null && $el instanceof Element) {
$el.remove();
}
} else if (args.length === 2) {
let infoList = args[0];
let deleteIndex = args[1];
if (typeof deleteIndex === "number") {
let item = infoList[deleteIndex];
if (item != null && item instanceof Element) {
item.remove();
}
infoList.splice(deleteIndex, 1);
}
}
}
}
const XHSArticleFilter = {
$key: {
ENABLE_KEY: "shieldVideo-exec-network-enable"
},
$data: {
/** 已经过滤的信息 */
isFilterAwemeInfoList: new Utils.Dictionary(),
/**
* 网络接口的视频信息字典
*/
articleInfoMap: new Utils.Dictionary(),
__videoFilterRuleStorage: null,
get videoFilterRuleStorage() {
if (this.__videoFilterRuleStorage == null) {
this.__videoFilterRuleStorage = new RuleStorage(
{
STORAGE_API_KEY: "xhs-article-filter-rule"
}
);
}
return this.__videoFilterRuleStorage;
},
/**
* 当命中过滤规则,如果开启了仅显示被过滤的视频,则修改isFilter值
*/
get isReverse() {
return Panel.getValue(
"xhs-article-filter-only-show-filtered-video"
);
}
},
init() {
this.execFilter();
},
/**
* 执行过滤
*/
execFilter() {
Panel.execMenuOnce(this.$key.ENABLE_KEY, async () => {
log.info(`执行笔记过滤器`);
let filterBase = new XHSArticleFilterBase();
let checkFilterCallBack = (filterTransformInfoResult) => {
if (this.$data.isReverse) {
filterTransformInfoResult.isFilter = !filterTransformInfoResult.isFilter;
if (typeof filterTransformInfoResult.transformInfo.articleId === "string" && filterTransformInfoResult.matchedFilterOption) {
let filterOptionList = this.$data.isFilterAwemeInfoList.get(
filterTransformInfoResult.transformInfo.articleId
) || [];
filterOptionList.push(
filterTransformInfoResult.matchedFilterOption
);
this.$data.isFilterAwemeInfoList.set(
filterTransformInfoResult.transformInfo.articleId,
filterOptionList
);
}
}
if (typeof filterTransformInfoResult.transformInfo.articleId === "string") {
this.$data.articleInfoMap.set(
filterTransformInfoResult.transformInfo.articleId,
{
articleInfo: filterTransformInfoResult.info,
transformArticleInfo: filterTransformInfoResult.transformInfo
}
);
}
};
let queryScopeFilterOptionList = (scopeName) => {
if (!Panel.getValue(this.$key.ENABLE_KEY)) {
return [];
}
let filterOptionList = this.$data.videoFilterRuleStorage.getAllRule();
if (!filterOptionList.length) {
return [];
}
let scopeNameList = Array.isArray(scopeName) ? scopeName : [scopeName];
let matchedFilterOptionList = filterOptionList.filter(
(it) => it.enable && (it.data.scope.includes("all") || Array.from(scopeNameList).findIndex(
(item) => it.data.scope.includes(
item
)
) !== -1)
);
return matchedFilterOptionList;
};
let xhr_hook_callback_1 = (scopeName, request) => {
request.response = (response) => {
let filterOptionList = queryScopeFilterOptionList(scopeName);
if (!filterOptionList.length) {
return;
}
let data = utils.toJSON(response.responseText);
let items = data?.["data"]?.["items"];
if (Array.isArray(items)) {
for (let index = 0; index < items.length; index++) {
let awemeInfo = items[index] || {};
let filterResult = filterBase.checkInfoIsFilter(
filterOptionList,
awemeInfo
);
checkFilterCallBack(filterResult);
if (filterResult.isFilter) {
filterBase.removeArticle(items, index--);
}
}
response.responseText = JSON.stringify(data);
}
};
};
XHSNetworkHook.ajaxHooker.hook((request) => {
let url = CommonUtil.fixUrl(request.url);
let urlInst = new URL(url);
if (urlInst.pathname.startsWith("/api/sns/web/v1/homefeed")) {
xhr_hook_callback_1("xhr-explore", request);
}
});
});
},
/**
* 获取模板数据
*/
getTemplateData() {
return {
uuid: utils.generateUUID(),
enable: true,
name: "",
data: {
scope: [],
ruleName: "display_title",
ruleValue: "",
remarks: ""
},
dynamicData: []
};
},
/**
* 显示视图
*/
showView() {
let ruleView = this.getRuleViewInstance();
ruleView.showView();
},
/**
* 获取规则视图实例
*/
getRuleViewInstance() {
const that = this;
let panelHandlerComponents = __pops.config.PanelHandlerComponents();
function generateStorageApi(data) {
return {
get(key, defaultValue) {
return data[key] ?? defaultValue;
},
set(key, value) {
data[key] = value;
}
};
}
let ruleView = new RuleView({
title: "笔记过滤器",
data: () => {
return this.$data.videoFilterRuleStorage.getAllRule();
},
getAddData: () => {
return this.getTemplateData();
},
getDataItemName: (data) => {
return data["name"];
},
updateData: (data) => {
return this.$data.videoFilterRuleStorage.setRule(data);
},
deleteData: (data) => {
return this.$data.videoFilterRuleStorage.deleteRule(data);
},
getData: (data) => {
let allData = this.$data.videoFilterRuleStorage.getAllRule();
let findValue = allData.find((item) => item.uuid === data.uuid);
return findValue ?? data;
},
itemControls: {
enable: {
enable: true,
getEnable(data) {
return data.enable;
},
callback: (data, enable) => {
data.enable = enable;
this.$data.videoFilterRuleStorage.setRule(data);
}
},
edit: {
enable: true,
getView: (data, isEdit) => {
let $fragment = document.createDocumentFragment();
if (!isEdit) {
data = this.getTemplateData();
}
let enable_template = UISwitch("启用", "enable", true);
Reflect.set(
enable_template.props,
PROPS_STORAGE_API,
generateStorageApi(data)
);
let $enable = panelHandlerComponents.createSectionContainerItem_switch(
enable_template
);
let name_template = UIInput(
"规则名称",
"name",
"",
"",
void 0,
"必填"
);
Reflect.set(
name_template.props,
PROPS_STORAGE_API,
generateStorageApi(data)
);
let $name = panelHandlerComponents.createSectionContainerItem_input(
name_template
);
let scope_template = UISelectMultiple(
"作用域",
"scope",
[],
[
{
text: "所有",
value: "all"
},
{
text: "发现",
value: "xhr-explore"
}
].map((it) => {
let result = {
...it,
value: it.value
};
return result;
}),
void 0,
"选择需要在xxx上生效的作用域"
);
Reflect.set(
scope_template.props,
PROPS_STORAGE_API,
generateStorageApi(data.data)
);
let $scope = panelHandlerComponents.createSectionContainerItem_select_multiple_new(
scope_template
);
let keyNameHandlerInfo = [
"display_title",
"isLike",
"liked_count",
"nick_name",
"user_id",
"isVideo",
"videoDuration"
];
let getDynamicProp = (storageData) => {
let ruleNameDefaultValue = Array.isArray(storageData["ruleName"]) ? storageData["ruleName"] : [storageData["ruleName"]];
let ruleName_template = UISelectMultiple(
"属性名",
"ruleName",
ruleNameDefaultValue,
keyNameHandlerInfo.map((item) => {
return {
text: item,
value: item
};
}),
void 0,
"选择需要的属性名 "
);
Reflect.set(
ruleName_template.props,
PROPS_STORAGE_API,
generateStorageApi(storageData)
);
let $ruleName2 = panelHandlerComponents.createSectionContainerItem_select_multiple_new(
ruleName_template
);
let ruleValue_template = UITextArea(
"属性值",
"ruleValue",
"",
"如果是字符串,可正则,注意转义"
);
Reflect.set(
ruleValue_template.props,
PROPS_STORAGE_API,
generateStorageApi(storageData)
);
let $ruleValue2 = panelHandlerComponents.createSectionContainerItem_textarea(
ruleValue_template
);
let remarks_template = UITextArea(
"备注",
"remarks",
"",
""
);
Reflect.set(
remarks_template.props,
PROPS_STORAGE_API,
generateStorageApi(storageData)
);
let $remarks2 = panelHandlerComponents.createSectionContainerItem_textarea(
remarks_template
);
return {
$ruleName: $ruleName2,
$ruleValue: $ruleValue2,
$remarks: $remarks2
};
};
let $dynamicContainer = domUtils.createElement("div", {
className: "rule-form-ulist-dynamic",
innerHTML: (
/*html*/
`
`
)
});
let $dynamicInner = $dynamicContainer.querySelector(
".rule-form-ulist-dynamic__inner"
);
let $addDynamicButton = $dynamicContainer.querySelector(
".pops-panel-button"
);
let addDynamicElementItem = (dynamicData = {
ruleName: [],
ruleValue: "",
remarks: ""
}) => {
let $dynamicUListContainer = domUtils.createElement("div", {
className: "rule-form-ulist-dynamic__inner-container",
innerHTML: (
/*html*/
`
`
)
});
let $dynamicDelete = $dynamicUListContainer.querySelector(
".dynamic-control-delete"
);
domUtils.on($dynamicDelete, "click", (event) => {
utils.preventEvent(event);
$dynamicUListContainer.remove();
if (Array.isArray(data.dynamicData)) {
let findIndex = data.dynamicData.findIndex(
(it) => it == dynamicData
);
if (findIndex !== -1) {
data.dynamicData.splice(findIndex, 1);
}
}
});
let $dynamicUList = $dynamicUListContainer.querySelector(
".dynamic-forms"
);
let {
$ruleName: $dynamic_ruleName,
$ruleValue: $dynamic_ruleValue,
$remarks: $dynamic_remarks
} = getDynamicProp(dynamicData);
$dynamicUList.appendChild($dynamic_ruleName);
$dynamicUList.appendChild($dynamic_ruleValue);
$dynamicUList.appendChild($dynamic_remarks);
$dynamicInner.appendChild($dynamicUListContainer);
};
domUtils.on($addDynamicButton, "click", (event) => {
utils.preventEvent(event);
addDynamicElementItem();
});
if (Array.isArray(data.dynamicData)) {
for (let index = 0; index < data.dynamicData.length; index++) {
const moreDataItem = data.dynamicData[index];
addDynamicElementItem(moreDataItem);
}
}
let { $ruleName, $ruleValue, $remarks } = getDynamicProp(data.data);
$fragment.append(
$enable,
$name,
$scope,
// $autoSendDisLikeRequest,
$ruleName,
$ruleValue,
$remarks,
$dynamicContainer
);
return $fragment;
},
onsubmit: ($form, isEdit, editData) => {
let $ulist_li = $form.querySelectorAll(
".rule-form-ulist > li"
);
let data = this.getTemplateData();
if (isEdit) {
data.uuid = editData.uuid;
}
$ulist_li.forEach(($li) => {
let formConfig = Reflect.get($li, "__formConfig__");
if (!formConfig) {
return;
}
let attrs = Reflect.get(formConfig, "attributes");
if (!attrs) {
return;
}
let storageApi = Reflect.get($li, PROPS_STORAGE_API);
let key = Reflect.get(attrs, ATTRIBUTE_KEY);
let defaultValue = Reflect.get(attrs, ATTRIBUTE_DEFAULT_VALUE);
let value = storageApi.get(key, defaultValue);
if (Reflect.has(data, key)) {
Reflect.set(data, key, value);
} else if (Reflect.has(data.data, key)) {
Reflect.set(data.data, key, value);
} else {
log.error(`${key}不在数据中`);
}
});
$form.querySelectorAll(
".rule-form-ulist-dynamic__inner-container"
).forEach(($inner) => {
let dynamicData = {};
$inner.querySelectorAll(".dynamic-forms > li").forEach(($li) => {
let formConfig = Reflect.get($li, "__formConfig__");
if (!formConfig) {
return;
}
let attrs = Reflect.get(formConfig, "attributes");
if (!attrs) {
return;
}
let storageApi = Reflect.get($li, PROPS_STORAGE_API);
let key = Reflect.get(attrs, ATTRIBUTE_KEY);
let defaultValue = Reflect.get(
attrs,
ATTRIBUTE_DEFAULT_VALUE
);
let value = storageApi.get(key, defaultValue);
Reflect.set(dynamicData, key, value);
});
data.dynamicData.push(dynamicData);
});
if (data.name.trim() === "") {
Qmsg.error("规则名称不能为空");
return {
success: false,
data
};
}
if (data.data.scope.length === 0) {
Qmsg.error("请选择作用域");
return {
success: false,
data
};
}
if (data.data.ruleName.length === 0) {
Qmsg.error("请选择属性名");
return {
success: false,
data
};
}
if (data.data.ruleValue.trim() === "") {
Qmsg.error("属性值不能为空");
return {
success: false,
data
};
}
if (isEdit) {
return {
success: that.$data.videoFilterRuleStorage.setRule(data),
data
};
} else {
return {
success: that.$data.videoFilterRuleStorage.addRule(data),
data
};
}
},
style: (
/*css*/
`
.pops-panel-textarea textarea{
height: 150px;
}
.pops-panel-item-left-desc-text{
line-height: normal;
margin-top: 6px;
font-size: 0.8em;
color: rgb(108, 108, 108);
}
.rule-form-ulist-dynamic{
--button-margin-top: 0px;
--button-margin-right: 0px;
--button-margin-bottom: 0px;
--button-margin-left: 0px;
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 5px 20px;
}
.rule-form-ulist-dynamic__inner{
width: 100%;
}
.rule-form-ulist-dynamic__inner-container{
display: flex;
align-items: center;
}
.dynamic-forms{
width: 100%;
}
.pops-panel-textarea textarea{
height: 60px;
min-height: 60px;
width: 250px;
max-width: 400px;
min-width: 250px;
resize: auto;
transition: unset;
}
`
),
width: () => {
return window.innerWidth > 700 ? "700px" : "88vw";
}
},
delete: {
enable: true,
deleteCallBack: (data) => {
return that.$data.videoFilterRuleStorage.deleteRule(data);
}
}
},
bottomControls: {
filter: {
enable: true,
option: [
{
name: "过滤-已启用",
filterCallBack(data) {
return data.enable;
}
},
{
name: "过滤-未启用",
filterCallBack(data) {
return !data.enable;
}
}
]
},
clear: {
enable: true,
callback: () => {
that.$data.videoFilterRuleStorage.clearAllRule();
}
}
}
});
return ruleView;
}
};
const XHS = {
init() {
XHSArticleFilter.init();
Panel.execMenuOnce("pc-xhs-hook-vue", () => {
XHSHook.hookVue();
});
Panel.execMenuOnce("pc-xhs-allowCopy", () => {
XHS.allowPCCopy();
});
Panel.execMenuOnce("pc-xhs-open-blank-article", () => {
XHS.openBlankArticle();
});
XHSBlock.init();
Panel.execMenuOnce("pc-xhs-article-showPubsliushTime", () => {
XHSArticle.transformPublishTime();
});
if (XHSRouter.isArticle()) {
log.info("Router: 笔记页面");
XHSArticle.init();
}
},
/**
* 允许复制
*/
allowPCCopy() {
log.success("允许复制文字");
domUtils.on(
_unsafeWindow,
"copy",
void 0,
function(event) {
utils.preventEvent(event);
let selectText = _unsafeWindow.getSelection();
if (selectText) {
utils.setClip(selectText.toString());
} else {
log.error("未选中任何内容");
}
return false;
},
{
capture: true
}
);
},
/**
* 新标签页打开文章
*/
openBlankArticle() {
log.success("新标签页打开文章");
domUtils.on(
document,
"click",
".feeds-container .note-item",
function(event) {
utils.preventEvent(event);
let $click = event.target;
let $url = $click.querySelector("a.cover[href]");
let url = $url?.href;
if (url) {
log.info("跳转文章: " + url);
let urlInstance = new URL(url);
urlInstance.pathname = urlInstance.pathname.replace(
/^\/user\/profile\/[a-z0-9A-Z]+\//i,
"/discovery/item/"
);
url = urlInstance.toString();
window.open(url, "_blank");
} else {
Qmsg.error("未找到文章链接");
}
},
{
capture: true
}
);
}
};
const UISelect = function(text, key, defaultValue, data, changeCallback, description) {
let selectData = [];
if (typeof data === "function") {
selectData = data();
} else {
selectData = data;
}
let result = {
text,
type: "select",
description,
attributes: {},
props: {},
getValue() {
let storageApiValue = this.props[PROPS_STORAGE_API];
return storageApiValue.get(key, defaultValue);
},
callback(event, isSelectedValue, isSelectedText) {
let value = isSelectedValue;
log.info(`选择:${isSelectedText}`);
if (typeof changeCallback === "function") {
let result2 = changeCallback(event, value, isSelectedText);
if (result2) {
return;
}
}
let storageApiValue = this.props[PROPS_STORAGE_API];
storageApiValue.set(key, value);
},
data: selectData
};
Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
PanelComponents.initComponentsStorageApi(
"select",
result,
{
get(key2, defaultValue2) {
return Panel.getValue(key2, defaultValue2);
},
set(key2, value) {
Panel.setValue(key2, value);
}
}
);
return result;
};
const UIButton = function(text, description, buttonText, buttonIcon, buttonIsRightIcon, buttonIconIsLoading, buttonType, clickCallBack, afterAddToUListCallBack, disable) {
let result = {
text,
type: "button",
attributes: {},
props: {},
description,
buttonIcon,
buttonIsRightIcon,
buttonIconIsLoading,
buttonType,
buttonText,
callback(event) {
if (typeof clickCallBack === "function") {
clickCallBack(event);
}
},
afterAddToUListCallBack
};
Reflect.set(result.attributes, ATTRIBUTE_INIT, () => {
result.disable = Boolean(
disable
);
});
return result;
};
const SettingUI_Common = {
id: "xhs-panel-config-common",
title: "通用",
forms: [
{
type: "forms",
text: "",
forms: [
{
text: "Toast配置",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISelect(
"Toast位置",
"qmsg-config-position",
"bottom",
[
{
value: "topleft",
text: "左上角"
},
{
value: "top",
text: "顶部"
},
{
value: "topright",
text: "右上角"
},
{
value: "left",
text: "左边"
},
{
value: "center",
text: "中间"
},
{
value: "right",
text: "右边"
},
{
value: "bottomleft",
text: "左下角"
},
{
value: "bottom",
text: "底部"
},
{
value: "bottomright",
text: "右下角"
}
],
(event, isSelectValue, isSelectText) => {
log.info("设置当前Qmsg弹出位置" + isSelectText);
},
"Toast显示在页面九宫格的位置"
),
UISelect(
"最多显示的数量",
"qmsg-config-maxnums",
3,
[
{
value: 1,
text: "1"
},
{
value: 2,
text: "2"
},
{
value: 3,
text: "3"
},
{
value: 4,
text: "4"
},
{
value: 5,
text: "5"
}
],
void 0,
"限制Toast显示的数量"
),
UISwitch(
"逆序弹出",
"qmsg-config-showreverse",
false,
void 0,
"修改Toast弹出的顺序"
)
]
}
]
}
]
},
{
text: "",
type: "forms",
forms: [
{
text: "功能",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"允许复制",
"pc-xhs-allowCopy",
true,
void 0,
"可以选择文字并复制"
),
UISwitch(
"新标签页打开文章",
"pc-xhs-open-blank-article",
false,
void 0,
"点击文章不会在本页展开,会打开新标签页"
)
]
}
]
},
{
text: "搜索",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"新标签页打开-搜索按钮",
"pc-xhs-search-open-blank-btn",
false,
void 0,
"点击右边的搜索按钮直接新标签页打开搜索内容"
),
UISwitch(
"新标签页打开-回车键",
"pc-xhs-search-open-blank-keyboard-enter",
false,
void 0,
"按下回车键直接新标签页打开搜索内容"
)
]
}
]
},
{
text: "屏蔽",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"【屏蔽】广告",
"pc-xhs-shieldAd",
true,
void 0,
"屏蔽元素"
),
UISwitch(
"【屏蔽】登录弹窗",
"pc-xhs-shield-login-dialog",
true,
void 0,
"屏蔽会自动弹出的登录弹窗"
),
UISwitch(
"【屏蔽】选择文字弹出的搜索提示",
"pc-xhs-shield-select-text-search-position",
false,
void 0,
"屏蔽元素"
),
UISwitch(
"【屏蔽】顶部工具栏",
"pc-xhs-shield-topToolbar",
false,
void 0,
"屏蔽元素"
)
]
}
]
},
{
type: "deepMenu",
text: "笔记过滤器",
forms: [
{
text: '点击查看规则',
type: "forms",
forms: [
UISwitch(
"启用",
"shieldVideo-exec-network-enable",
true,
void 0,
"开启后以下功能才会生效"
),
UISwitch(
"仅显示被过滤的笔记",
"xhs-article-filter-only-show-filtered-video",
false,
void 0,
"只会显示过滤规则命中的笔记"
),
UIButton(
"笔记过滤规则",
"可过滤笔记",
"自定义",
void 0,
false,
false,
"primary",
() => {
XHSArticleFilter.showView();
}
)
]
},
{
type: "forms",
text: "",
forms: [
UIButton(
"数据导入",
"导入自定义规则数据",
"导入",
void 0,
false,
false,
"primary",
() => {
XHSArticleFilter.$data.videoFilterRuleStorage.importRules();
}
),
UIButton(
"数据导出",
"导出自定义规则数据",
"导出",
void 0,
false,
false,
"primary",
() => {
XHSArticleFilter.$data.videoFilterRuleStorage.exportRules(
_SCRIPT_NAME_ + "-视频过滤规则.json"
);
}
)
]
}
]
},
{
text: "劫持/拦截",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"劫持Vue",
"pc-xhs-hook-vue",
true,
void 0,
"恢复__vue__属性"
)
]
}
]
}
]
}
]
};
const UISlider = function(text, key, defaultValue, min, max, changeCallback, getToolTipContent, description, step) {
let result = {
text,
type: "slider",
description,
attributes: {},
props: {},
getValue() {
let storageApiValue = this.props[PROPS_STORAGE_API];
return storageApiValue.get(key, defaultValue);
},
getToolTipContent(value) {
if (typeof getToolTipContent === "function") {
return getToolTipContent(value);
} else {
return `${value}`;
}
},
callback(event, value) {
if (typeof changeCallback === "function") {
let result2 = changeCallback(event, value);
if (result2) {
return;
}
}
let storageApiValue = this.props[PROPS_STORAGE_API];
storageApiValue.set(key, value);
},
min,
max,
step
};
Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
PanelComponents.initComponentsStorageApi(
"slider",
result,
{
get(key2, defaultValue2) {
return Panel.getValue(key2, defaultValue2);
},
set(key2, value) {
Panel.setValue(key2, value);
}
}
);
return result;
};
const SettingUI_Article = {
id: "xhs-panel-config-article",
title: "笔记",
forms: [
{
type: "forms",
text: "功能",
forms: [
UISwitch(
"显示发布、修改的绝对时间",
"pc-xhs-article-showPubsliushTime",
false,
void 0,
"注:需要开启通用
-劫持/拦截
-劫持Vue
"
)
]
},
{
text: "笔记宽屏",
type: "forms",
forms: [
UISwitch(
"启用",
"pc-xhs-article-fullWidth",
false,
void 0,
`让笔记占据宽屏,当页面可视宽度>=960px时才会触发该功能,当前页面可视宽度: ${window.innerWidth}px`
),
UISlider(
"占据范围",
"pc-xhs-article-fullWidth-widthSize",
90,
30,
100,
(event, value) => {
let $noteContainer = document.querySelector("#noteContainer");
if (!$noteContainer) {
log.error("未找到笔记容器");
return;
}
$noteContainer.style.width = `${value}vw`;
},
(value) => {
return `${value}%,默认:90%`;
},
"调整笔记页面占据的页面范围"
)
]
}
]
};
const MSettingUI_Common = {
id: "little-red-book-panel-config-common",
title: "通用",
forms: [
{
text: "",
type: "forms",
forms: [
{
text: "Toast配置",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISelect(
"Toast位置",
"qmsg-config-position",
"bottom",
[
{
value: "topleft",
text: "左上角"
},
{
value: "top",
text: "顶部"
},
{
value: "topright",
text: "右上角"
},
{
value: "left",
text: "左边"
},
{
value: "center",
text: "中间"
},
{
value: "right",
text: "右边"
},
{
value: "bottomleft",
text: "左下角"
},
{
value: "bottom",
text: "底部"
},
{
value: "bottomright",
text: "右下角"
}
],
(event, isSelectValue, isSelectText) => {
log.info("设置当前Qmsg弹出位置" + isSelectText);
},
"Toast显示在页面九宫格的位置"
),
UISelect(
"最多显示的数量",
"qmsg-config-maxnums",
3,
[
{
value: 1,
text: "1"
},
{
value: 2,
text: "2"
},
{
value: 3,
text: "3"
},
{
value: 4,
text: "4"
},
{
value: 5,
text: "5"
}
],
void 0,
"限制Toast显示的数量"
),
UISwitch(
"逆序弹出",
"qmsg-config-showreverse",
false,
void 0,
"修改Toast弹出的顺序"
)
]
}
]
}
]
},
{
text: "",
type: "forms",
forms: [
{
text: "屏蔽",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"【屏蔽】广告",
"little-red-book-shieldAd",
true,
void 0,
"如:App内打开"
),
UISwitch(
"【屏蔽】底部搜索发现",
"little-red-book-shieldBottomSearchFind",
true,
void 0,
"建议开启"
),
UISwitch(
"【屏蔽】底部工具栏",
"little-red-book-shieldBottomToorBar",
true,
void 0,
"建议开启"
)
]
}
]
}
// {
// text: "劫持/拦截",
// type: "deepMenu",
// forms: [
// {
// text: "",
// type: "forms",
// forms: [
// UISwitch(
// "劫持Vue",
// "little-red-book-hijack-vue",
// true,
// void 0,
// "恢复__vue__属性"
// ),
// ],
// },
// ],
// },
]
}
]
};
const MSettingUI_Home = {
id: "little-red-book-panel-config-home",
title: "主页",
forms: [
{
text: "",
type: "forms",
forms: [
{
text: "劫持/拦截",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"劫持点击事件",
"little-red-book-repariClick",
true,
void 0,
"可阻止点击跳转至下载页面"
)
]
}
]
}
]
}
]
};
const MSettingUI_Notes = {
id: "little-red-book-panel-config-note",
title: "笔记",
forms: [
{
text: "",
type: "forms",
forms: [
{
text: "视频笔记",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"优化视频描述",
"little-red-book-optimizeVideoNoteDesc",
true,
void 0,
"让视频描述可以滚动显示更多"
),
UISwitch(
"【屏蔽】作者热门笔记",
"little-red-book-shieldAuthorHotNote",
true,
void 0,
"建议开启"
),
UISwitch(
"【屏蔽】热门推荐",
"little-red-book-shieldHotRecommendNote",
true,
void 0,
"建议开启"
)
]
}
]
}
]
},
{
text: "",
type: "forms",
forms: [
{
text: "功能",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"优化评论浏览",
"little-red-book-optimizeCommentBrowsing",
true,
void 0,
"目前仅可加载部分评论"
),
UISwitch(
"优化图片浏览",
"little-red-book-optimizeImageBrowsing",
true,
void 0,
"更方便的浏览图片"
),
UISwitch(
"允许复制",
"little-red-book-allowCopy",
true,
void 0,
"可以复制笔记的内容"
)
]
}
]
},
{
text: "劫持/拦截",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"劫持webpack-弹窗",
"little-red-book-hijack-webpack-mask",
true,
void 0,
"如:打开App弹窗、登录弹窗"
),
UISwitch(
"劫持webpack-唤醒App",
"little-red-book-hijack-webpack-scheme",
true,
void 0,
"禁止跳转商店小红书详情页/小红书"
)
]
}
]
}
]
}
]
};
addStyle(
/*css*/
`
.qmsg svg.animate-turn {
fill: none;
}
`
);
PanelContent.addContentConfig([SettingUI_Common, SettingUI_Article]);
PanelContent.addContentConfig([
MSettingUI_Common,
MSettingUI_Home,
MSettingUI_Notes
]);
const defaultMenuOption = PanelMenu.getMenuOption();
defaultMenuOption.text = "⚙ PC-设置";
PanelMenu.updateMenuOption(defaultMenuOption);
PanelMenu.addMenuOption({
key: "show_mobile_setting",
text: "⚙ 移动端-设置",
autoReload: false,
isStoreValue: false,
showText(text) {
return text;
},
callback: () => {
Panel.showPanel(PanelContent.getConfig(1), `${_SCRIPT_NAME_}-移动端设置`);
}
});
Panel.init();
let isMobile = utils.isPhone();
let CHANGE_ENV_SET_KEY = "change_env_set";
let chooseMode = _GM_getValue(CHANGE_ENV_SET_KEY);
GM_Menu.add({
key: CHANGE_ENV_SET_KEY,
text: `⚙ 自动: ${isMobile ? "移动端" : "PC端"}`,
autoReload: false,
isStoreValue: false,
showText(text) {
if (chooseMode == null) {
return text;
}
return text + ` 手动: ${chooseMode == 1 ? "移动端" : chooseMode == 2 ? "PC端" : "未知"}`;
},
callback: () => {
let allowValue = [0, 1, 2];
let chooseText = window.prompt(
"请输入当前脚本环境判定\n\n自动判断: 0\n移动端: 1\nPC端: 2",
"0"
);
if (!chooseText) {
return;
}
let chooseMode2 = parseInt(chooseText);
if (isNaN(chooseMode2)) {
Qmsg.error("输入的不是规范的数字");
return;
}
if (!allowValue.includes(chooseMode2)) {
Qmsg.error("输入的值必须是0或1或2");
return;
}
if (chooseMode2 == 0) {
_GM_deleteValue(CHANGE_ENV_SET_KEY);
} else {
_GM_setValue(CHANGE_ENV_SET_KEY, chooseMode2);
}
}
});
if (chooseMode != null) {
log.info(`手动判定为${chooseMode === 1 ? "移动端" : "PC端"}`);
if (chooseMode == 1) {
M_XHS.init();
} else if (chooseMode == 2) {
XHS.init();
} else {
Qmsg.error("意外,手动判定的值不在范围内");
_GM_deleteValue(CHANGE_ENV_SET_KEY);
}
} else {
if (isMobile) {
log.info("自动判定为移动端");
M_XHS.init();
} else {
log.info("自动判定为PC端");
XHS.init();
}
}
})(Qmsg, DOMUtils, Utils, pops, Viewer);