小红书增强

深色模式;更适合web大屏的三栏模式(图/文/评);支持显示emoji表情;Tag可点击;

  1. // ==UserScript==
  2. // @name 小红书增强
  3. // @name:zh-CN 小红书增强
  4. // @name:zh-TW 小紅書增強
  5. // @name:en Xiaohongshu enhancement
  6. // @version 1.1.3
  7. // @author DD2333
  8. // @description 深色模式;更适合web大屏的三栏模式(图/文/评);支持显示emoji表情;Tag可点击;
  9. // @description:zh-TW 深色模式;更適合web大屏的三欄模式(圖/文/評);支持顯示emoji表情;Tag可點擊;
  10. // @description:en This shit has non-chinese users Huh?
  11. // @run-at document-start
  12. // @match https://www.xiaohongshu.com/*
  13. // @icon https://www.xiaohongshu.com/favicon.ico
  14. // @grant GM_addStyle
  15. // @grant GM_registerMenuCommand
  16. // @license GPL-3.0 License
  17. // @supportURL https://github.com/Duoduo23333333/XiaoHongShu_UserScript
  18. // @homepageURL https://github.com/Duoduo23333333/XiaoHongShu_UserScript
  19. // @namespace https://greasyfork.org/users/1035963
  20. // ==/UserScript==
  21.  
  22.  
  23. // 4.21更新说明 4.21更新说明 4.21更新说明 4.21更新说明 4.21更新说明 4.21更新说明 4.21更新说明
  24. //
  25. // 网页又有变,想死,又重新适配了下深色模式样式。
  26.  
  27.  
  28. // 4.19更新说明 4.19更新说明 4.19更新说明 4.19更新说明 4.19更新说明 4.19更新说明 4.19更新说明
  29. //
  30. // 1. 右上角月亮图标:修复了月亮图标偶尔缺席的bug。
  31. // 2. 笔记独立页面(非悬浮窗):优化加载速度,修复了两栏变三栏闪烁的bug,增加了官方红薯表情在中间栏只能显示成文本的bug。
  32. // 3. 增加了脚本列表里的菜单选项,点击油猴图标可见。
  33. // 4. 这两天忙毕设,上面的问题我忙完了会尽快解决,有问题QQ联系我 1243738449 。
  34.  
  35.  
  36. // 4.14更新说明 4.14更新说明 4.14更新说明 4.14更新说明 4.14更新说明 4.14更新说明 4.14更新说明
  37. //
  38. // 1. 刚发现网页有变,深色模式/夜间模式有地方失效了。重新适配了下,更新完脚本记得刷新网页。
  39. // 2. 下次出问题/有什么需要的功能, github 或 greasyfork 给我留个言,或者QQ联系我都行 1243738449 。
  40.  
  41.  
  42. // emoji
  43. (function () {
  44.  
  45. var script = document.createElement("script");
  46. script.type = "text/javascript";
  47. script.src = "https://cdn.jsdelivr.net/npm/twemoji@14.0.2/dist/twemoji.min.js";
  48. script.async = false;
  49. document.body.appendChild(script);
  50. script.onload = function () {
  51. twemoji.parse(document.body, { className: "emoji" });
  52. var styles = document.createElement("style");
  53. styles.type = "text/css";
  54. styles.innerHTML = ".emoji { height: 1em; width: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; }";
  55. document.head.appendChild(styles);
  56. var observer = new MutationObserver(mutationHandler);
  57. observer.observe(document.body, { childList: true, subtree: true });
  58. };
  59.  
  60. function mutationHandler(mutations) {
  61. mutations.forEach(function (mutation) {
  62. mutation.addedNodes.forEach(function (node) {
  63. if (node.nodeType === Node.ELEMENT_NODE) {
  64. twemoji.parse(node, { className: "emoji" });
  65. }
  66. });
  67. });
  68. }
  69.  
  70. })();
  71.  
  72. // 其他增强
  73. (function () {
  74. "use strict";
  75. //fuck函数
  76. function fuck() {
  77. // 创建新div
  78. const newDiv = document.createElement("div");
  79. newDiv.setAttribute("id", "dd233");
  80. newDiv.style.width = "400px";
  81. newDiv.style.height = "100%";
  82. newDiv.style.overflow = "auto";
  83. newDiv.style.padding = "24px 35px 30px 40px";
  84. // 隐藏divB防止闪烁
  85. const divA = document.querySelector(".note-scroller");
  86. const divB = divA.querySelector(".note-content");
  87. divB.style.display = "none";
  88. // 日期样式
  89. const dateDiv = divB.querySelector(".date");
  90. dateDiv.style.fontSize = "16px";
  91. dateDiv.style.marginBottom = "16px";
  92. dateDiv.style.paddingBottom = "6px";
  93. dateDiv.style.borderBottom = "0.5px solid rgba(200,200,200,.1)";
  94. dateDiv.parentNode.removeChild(dateDiv);
  95. divB.insertBefore(dateDiv, divB.children[1]);
  96.  
  97. // 标题样式
  98. const titleDiv = divB.querySelector(".title");
  99. if (titleDiv) {
  100. titleDiv.style.marginBottom = "0";
  101. }
  102.  
  103. // tag
  104. const descDivs = divB.querySelectorAll(".desc");
  105. // tag区前置及样式
  106. if (descDivs.length >= 2) {
  107. const firstDescElem = descDivs[0];
  108. const secondDescElem = descDivs[1];
  109. firstDescElem.parentNode.insertBefore(secondDescElem, firstDescElem);
  110. secondDescElem.style.marginBottom = '16px';
  111. secondDescElem.style.paddingBottom = '6px';
  112. secondDescElem.style.borderBottom = '0.5px solid rgba(200, 200, 200, 0.1)';
  113. }
  114. descDivs.forEach((descDiv) => {
  115. const spans = descDiv.querySelectorAll("span");
  116.  
  117. spans.forEach((span) => {
  118. const text = span.textContent.trim();
  119.  
  120. if (text.startsWith("#")) {
  121. const keyword = text.substring(1);
  122.  
  123. // 把tag从span转成link
  124. const link = document.createElement("a");
  125. link.href = `https://www.xiaohongshu.com/search_result?keyword=${encodeURIComponent(keyword)}`;
  126. link.textContent = text;
  127. link.style.display = "inline-block";
  128. link.style.padding = "0.1em 0.5em";
  129. link.style.margin = "0.1px";
  130. link.style.fontSize = "85%";
  131. link.style.whiteSpace = "break-spaces";
  132. link.style.color = "#5e9ada";
  133. span.parentNode.replaceChild(link, span);
  134. }
  135. });
  136. });
  137.  
  138. // 将此时的divB放到新div中
  139. const container = document.querySelector(".note-container");
  140. container.insertBefore(newDiv, container.children[1]);
  141. if (x == 1) {
  142. //什么都不做
  143. } else {
  144. container.style.position = "absolute";
  145. container.style.left = "-14%";
  146. container.style.transition = "none";
  147. }
  148.  
  149. newDiv.innerHTML = divB.innerHTML;
  150. newDiv.style.display = "block";
  151.  
  152. // 检测desc是否存在"[",如果不存在则不需要检测图片
  153. let hasBracket = false;
  154. const descDiv = divB.querySelector(".desc");
  155. if (descDiv && descDiv.textContent.indexOf("[") !== -1) {
  156. hasBracket = true;
  157. }
  158.  
  159. if (!hasBracket) {
  160. // 不需要检测图片
  161. divB.parentNode.removeChild(divB);
  162. return;
  163. }
  164.  
  165. // xhs表情文本是否已经变成img标签
  166. let checkImgInterval = setInterval(() => {
  167. const img = divB.querySelector("img");
  168. if (img) {
  169. newDiv.innerHTML = divB.innerHTML;
  170. divB.parentNode.removeChild(divB);
  171.  
  172. clearInterval(checkImgInterval); // xhs表情转换完毕,清除定时器
  173. }
  174. }, 200);
  175. }
  176.  
  177. //执行函数
  178. var x = 0;
  179. if ((window.location.href.indexOf("https://www.xiaohongshu.com/explore/") != -1 ||
  180. window.location.href.indexOf("https://www.xiaohongshu.com/search_result/") != -1) &&
  181. window.location.href.indexOf("https://www.xiaohongshu.com/search_result/?ke") == -1) {
  182.  
  183.  
  184. document.addEventListener('DOMContentLoaded', () => {
  185. x=1;
  186. fuck();
  187. });
  188.  
  189.  
  190. return;
  191. }
  192. else {
  193. const observer = new MutationObserver((mutationsList) => {
  194. for (let mutation of mutationsList) {
  195. if (
  196. mutation.type === "childList" &&
  197. mutation.target.classList.contains("note-scroller")
  198. ) {
  199. fuck(); // 执行fuck函数
  200. console.log('112');
  201. console.log(window.location.href);
  202. observer.disconnect(); // 断开监听
  203. setTimeout(() => {
  204. observer.observe(document.body, { childList: true, subtree: true }); // 200毫秒后重新开始监听
  205. }, 200);
  206. return;
  207. }
  208. }
  209. });
  210. observer.observe(document.body, { childList: true, subtree: true });
  211. }
  212. })();
  213.  
  214. // 深浅色模式
  215. (function () {
  216. "use strict";
  217.  
  218. /* 判断浏览器是否支持localStorage */
  219. if (typeof localStorage === 'undefined') {
  220. alert('当前浏览器不支持localStorage\n无法切换颜色模式\n\n除以下浏览器外均支持localStorage\nIE7及更低版本 ;\nSafari 4.0及更低版本 ;\nOpera Mini 和 Opera Mobile的某些版本; \n\n换用其他浏览器即可');
  221. }
  222.  
  223. /* 记录当前的深浅色模式状态 */
  224. let isDarkMode = false;
  225.  
  226. /* 获取localStorage中存储的主题模式偏好 */
  227. const getThemePreference = () => {
  228. const theme = localStorage.getItem('xhs_theme_preference');
  229. return theme === 'dark' ? 'dark' : 'light';
  230. };
  231.  
  232. /* 存储主题模式偏好到localStorage */
  233. const setThemePreference = (theme) => {
  234. localStorage.setItem('xhs_theme_preference', theme);
  235. };
  236.  
  237. /* 应用指定的CSS文件 */
  238. const applyCss = (url) => {
  239. if (localStorage.getItem(url)) {
  240.  
  241. GM_addStyle(localStorage.getItem(url));
  242.  
  243. } else {
  244. fetch(url)
  245. .then((response) => response.text())
  246. .then((text) => {
  247. localStorage.setItem(url, text);
  248. GM_addStyle(text);
  249. })
  250. .catch((error) => console.error(`Error fetching CSS: ${error}`));
  251. }
  252. };
  253.  
  254. /* 切换深浅色模式 */
  255. const toggleTheme = () => {
  256. isDarkMode = !isDarkMode;
  257. const themePreference = isDarkMode ? 'dark' : 'light';
  258. if (themePreference === 'dark') {
  259. applyCss('https://cdn.jsdelivr.net/gh/Duoduo23333333/XiaoHongShu_UserScript@1.1.2/darkmode.css');
  260. } else {
  261. const existingLink = document.querySelector('link[href="https://cdn.jsdelivr.net/gh/Duoduo23333333/XiaoHongShu_UserScript@1.1.2/darkmode.css"]');
  262. if (existingLink) {
  263. existingLink.remove();
  264. }
  265. alert('已换回浅色模式,请手动刷新网页。');
  266.  
  267. }
  268. setThemePreference(themePreference);
  269. };
  270.  
  271. /* 主程序 */
  272. const main = () => {
  273.  
  274. const themePreference = getThemePreference();
  275. isDarkMode = themePreference === 'dark';
  276.  
  277. applyCss(isDarkMode ? 'https://cdn.jsdelivr.net/gh/Duoduo23333333/XiaoHongShu_UserScript@1.1.2/darkmode.css' : '');
  278.  
  279. /* 初次加载时,提示用户切换到深色模式 */
  280. if (typeof localStorage !== 'undefined' && !localStorage.getItem('xhs_theme_preference')) {
  281. alert('使用说明\n---------------------------------------\n· 右上角图标切换夜间模式\n· tag 可点击\n· 支持 emoji 显示\n· 三栏显示(图/文/评)\n· 如需调整比例,Ctrl+鼠标滚轮或键盘+-');
  282. setThemePreference('dark');
  283. }
  284.  
  285. /* 监听小红书切换主题事件,用来更新localStorage中的主题偏好 */
  286. const observeThemeChanges = () => {
  287. const body = document.querySelector('body');
  288. const observer = new MutationObserver((mutations) => {
  289. mutations.forEach((mutation) => {
  290. if (mutation.attributeName === 'class') {
  291. const themePreference = body.classList.contains('theme-dark') ? 'dark' : 'light';
  292. setThemePreference(themePreference);
  293. }
  294. });
  295. });
  296. observer.observe(body, { attributes: true });
  297. };
  298. observeThemeChanges();
  299.  
  300.  
  301.  
  302. };
  303.  
  304. /* 创建切换按钮并添加到指定元素 */
  305. const addbutton = () => {
  306. const createToggleButton = () => {
  307. const dropdownNav = document.querySelector('.dropdown-nav');
  308. if (!dropdownNav) {
  309. console.warn('未找到 class 为 dropdown-nav 的元素,无法添加深浅色模式切换按钮');
  310. return;
  311. }
  312. const button = document.createElement('button');
  313. button.setAttribute("id", "button");
  314. button.innerHTML = isDarkMode
  315. ? '<svg t="1677925762871" class="icon" style="pointer-events:none;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20150" width="18" height="18" style="transform: translateY(1.5px)"><path d="M399.61 49.951C399.61 22.353 377.257 0 349.659 0 274.607 0 0 209.845 0 499.512 0 789.18 234.82 1024 524.488 1024 814.155 1024 1024 747.47 1024 674.341c0-27.598-22.353-49.95-49.951-49.95s-301.181 67.683-472.014-103.15C331.202 350.408 399.61 77.549 399.61 49.951z" p-id="20151" data-spm-anchor-id="a313x.7781069.0.i59" class="selected" fill="#707070"></path></svg>'
  316. : '<svg t="1677925762871" class="icon" style="pointer-events:none;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20150" width="18" height="18" style="transform: translateY(1.5px)"><path d="M399.61 49.951C399.61 22.353 377.257 0 349.659 0 274.607 0 0 209.845 0 499.512 0 789.18 234.82 1024 524.488 1024 814.155 1024 1024 747.47 1024 674.341c0-27.598-22.353-49.95-49.951-49.95s-301.181 67.683-472.014-103.15C331.202 350.408 399.61 77.549 399.61 49.951z" p-id="20151" data-spm-anchor-id="a313x.7781069.0.i59" class="selected" fill="#707070"></path></svg>';
  317. button.style.cursor = "pointer";
  318. button.style.opacity = "0.9";
  319. button.style.transform = 'translateY(3px)';
  320. button.style.borderRadius = "50%";
  321. button.style.marginLeft = "16px";
  322. button.style.width = "40px";
  323. button.style.height = "40px";
  324.  
  325. button.addEventListener("mouseover", () => {
  326. button.style.opacity = "0.7";
  327. });
  328. button.addEventListener("mouseout", () => {
  329. button.style.opacity = "0.9";
  330. });
  331. button.addEventListener('click', toggleTheme);
  332. dropdownNav.appendChild(button);
  333. };
  334.  
  335. window.onload = function () {
  336. createToggleButton();
  337. };
  338.  
  339. };
  340.  
  341.  
  342.  
  343. addbutton();
  344. main();
  345.  
  346. })();
  347.  
  348.  
  349.  
  350. function showUpdateLog() {
  351. alert('******************\n4.21更新说明\n******************\n\n网页又有变,想死,又重新适配了下深色模式样式。');
  352. }
  353.  
  354. function huabing() {
  355. alert('******************\n未来更新计划\n******************\n\n1. 两栏or三栏可选\n2. 去除未登录状态下的登录弹窗提示\n3. 笔记一键分享按钮\n4. 无水印原视频下载\n5. 禁用原版过渡动画\n6. 想不到了');
  356. }
  357.  
  358. function contactMe() {
  359. alert('qq 1243738449');
  360. }
  361.  
  362.  
  363. GM_registerMenuCommand('本次更新日志', showUpdateLog);
  364. GM_registerMenuCommand('接下来的更新计划', huabing);
  365. GM_registerMenuCommand('出问题联系我 QQ 1243738449', contactMe);