PatreonExpander

Simplify elements, expand contents and comments

  1. // ==UserScript==
  2. // @name PatreonExpander
  3. // @namespace https://github.com/frosn0w/iOSscripts
  4. // @version 2.25.324
  5. // @description Simplify elements, expand contents and comments
  6. // @author frosn0w
  7. // @match *://*.patreon.com/*
  8. // @run-at document-end
  9. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MzYgNDc2Ij48dGl0bGU+UGF0cmVvbiBsb2dvPC90aXRsZT48cGF0aCBkYXRhLWZpbGw9IjEiIGQ9Ik00MzYgMTQzYy0uMDg0LTYwLjc3OC00Ny41Ny0xMTAuNTkxLTEwMy4yODUtMTI4LjU2NUMyNjMuNTI4LTcuODg0IDE3Mi4yNzktNC42NDkgMTA2LjIxNCAyNi40MjQgMjYuMTQyIDY0LjA4OS45ODggMTQ2LjU5Ni4wNTEgMjI4Ljg4M2MtLjc3IDY3LjY1MyA2LjAwNCAyNDUuODQxIDEwNi44MyAyNDcuMTEgNzQuOTE3Ljk0OCA4Ni4wNzItOTUuMjc5IDEyMC43MzctMTQxLjYyMyAyNC42NjItMzIuOTcyIDU2LjQxNy00Mi4yODUgOTUuNTA3LTUxLjkyOUMzOTAuMzA5IDI2NS44NjUgNDM2LjA5NyAyMTMuMDExIDQzNiAxNDNaIj48L3BhdGg+PC9zdmc+
  10. // @grant none
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. // 常量统一管理
  15. const CONFIG = {
  16. MAX_EXECUTIONS: 50,
  17. INTERVAL_DELAY: 2888,
  18. REMAIN_DAYS: 1,
  19. STYLE_ID: "patreon-expander-styles",
  20. TARGET_SELECTORS: {
  21. POST_TITLE: 'span[data-tag="post-title"]',
  22. COMMENT_ROW: 'div[data-tag="comment-row"]',
  23. // 添加更多常用选择器...
  24. },
  25. };
  26.  
  27. // 全局样式(合并所有样式,避免重复注入)
  28. const globalStyles = `
  29. p { line-height: 1.4 !important; }
  30. div[data-tag="comment-row"]::before,
  31. div[data-tag="comment-row"]::after {
  32. width: 0 !important;
  33. border-left: 0 !important;
  34. }
  35. .TAI-body-div p {
  36. margin: 8px 0 !important;
  37. }
  38. .TAI-body-div ul {
  39. margin: 6px 0 !important;
  40. }
  41. `;
  42. // 日期处理器(避免重复计算)
  43. const DateFormatter = {
  44. currentDate: new Date(),
  45.  
  46. refresh() {
  47. this.currentDate = new Date();
  48. },
  49.  
  50. get formattedDate() {
  51. return `${this.currentMonth}月${this.currentDay}日`;
  52. },
  53.  
  54. get yesterdayDate() {
  55. const date = new Date(this.currentDate);
  56. date.setDate(date.getDate() - 1);
  57. return `${date.getMonth() + 1}月${date.getDate()}日`;
  58. },
  59.  
  60. get currentMonth() {
  61. return this.currentDate.getMonth() + 1;
  62. },
  63.  
  64. get currentDay() {
  65. return this.currentDate.getDate();
  66. },
  67. };
  68. // 安全移除函数
  69. const safeRemove = (() => {
  70. const removedCache = new WeakSet();
  71. return (element, selector = null, levels = 0) => {
  72. if (!element || !element?.parentElement || removedCache.has(element)) {
  73. return false;
  74. }
  75. try {
  76. const target = selector ? element.closest(selector) : element;
  77. if (!target) return false;
  78. let parent = target;
  79. for (let i = 0; i < levels; i++) parent = parent?.parentElement;
  80. if (!parent) return false;
  81. parent.remove();
  82. removedCache.add(element);
  83. return true;
  84. } catch (error) {
  85. console.error("Removal failed:", error);
  86. return false;
  87. }
  88. };
  89. })();
  90. // 移除过期发布
  91. function shouldRemovePost(text) {
  92. const { currentMonth, currentDay } = DateFormatter;
  93. return (
  94. text.includes(" 天前") ||
  95. (text.includes(`${currentMonth}月`) &&
  96. parseInt(text.split(`${currentMonth}月`)[1]) <
  97. currentDay - CONFIG.REMAIN_DAYS) ||
  98. (text.includes(`${currentMonth}月`) &&
  99. parseInt(text.split(`${currentMonth}月`)[0]) === currentMonth - 1)
  100. );
  101. }
  102. // 处理 link
  103. function processLinks(element) {
  104. const { textContent, href, dataset } = element;
  105. switch (true) {
  106. case /小时前|分钟前/.test(textContent):
  107. element.textContent = DateFormatter.formattedDate;
  108. break;
  109. case textContent.includes("昨天"):
  110. element.textContent = DateFormatter.yesterdayDate;
  111. break;
  112. case dataset.tag === "post-published-at" && shouldRemovePost(textContent):
  113. safeRemove(element, 'div[data-tag="post-card"]', 2);
  114. break;
  115. // 删除赠送卡片
  116. case href === "https://www.patreon.com/user/gift?u=80821958":
  117. safeRemove(element, null, 4);
  118. break;
  119. case textContent === "Skip navigation":
  120. element.remove();
  121. break;
  122. case dataset.tag === "comment-avatar-wrapper":
  123. safeRemove(element, null, 1);
  124. break;
  125. }
  126. }
  127. // 处理 button
  128. function processButtons(button) {
  129. const { textContent, ariaExpanded, ariaLabel, dataset } = button;
  130. const BUTTON_INTERVALS = {
  131. 展开: 2888,
  132. 加载更多留言: 1688,
  133. 加载回复: 1888,
  134. };
  135. switch (true) {
  136. case ariaExpanded === "false" && ariaLabel === "打开导航":
  137. safeRemove(button, "header");
  138. break;
  139. case ["收起", "收起回复"].includes(textContent):
  140. safeRemove(button, null, 1);
  141. break;
  142. case textContent === "展开":
  143. case textContent === "加载更多留言":
  144. case textContent === "加载回复":
  145. if (!button.autoClicker) {
  146. button.autoClicker = setInterval(() => {
  147. document.contains(button)
  148. ? button.click()
  149. : clearInterval(button.autoClicker);
  150. }, BUTTON_INTERVALS[textContent]);
  151. }
  152. break;
  153. case dataset.tag === "commenter-name" &&
  154. textContent === "贝乐斯 Think Analyze Invest":
  155. safeRemove(button, '[data-tag="commenter-name"]');
  156. break;
  157. }
  158. }
  159. // 处理 Div
  160. function processDivs(element) {
  161. const { dataset, id, ariaExpanded, textContent } = element;
  162. switch (true) {
  163. case id === "main-app-navigation":
  164. safeRemove(element, null, 1);
  165. break;
  166. // 移除导航栏
  167. case ariaExpanded === "false" && textContent.includes("我的会籍"):
  168. safeRemove(element, "nav", 3);
  169. break;
  170. // 移除头图
  171. case dataset.tag === "creation-name" &&
  172. textContent.includes("Love & Peace !"):
  173. safeRemove(element, null, 4);
  174. break;
  175. // 移除搜索框
  176. case dataset.tag === "search-input-box":
  177. safeRemove(element, null, 5);
  178. break;
  179. case dataset.tag === "chip-container":
  180. safeRemove(element, null, 2);
  181. break;
  182. case dataset.tag === "post-details":
  183. safeRemove(element);
  184. break;
  185. // 移除已删除留言区域
  186. case dataset.tag === "comment-body" &&
  187. textContent.includes("此留言已被删除。"):
  188. safeRemove(element, null, 3);
  189. break;
  190. // 移除评论相关功能组件
  191. case dataset.tag === "comment-actions":
  192. safeRemove(element);
  193. break;
  194. case dataset.tag === "comment-field-box":
  195. safeRemove(element, null, 3);
  196. break;
  197. // 缩窄页边距
  198. case dataset.tag === "post-stream-container":
  199. element.parentNode?.style.setProperty("padding-left", "4px");
  200. element.parentNode?.style.setProperty("padding-right", "4px");
  201. break;
  202. }
  203. }
  204. // 通过span插入CSS标签,控制正文部分格式
  205. function processSpans(element) {
  206. if (element.getAttribute("data-tag") === "post-title") {
  207. element.parentNode?.parentNode?.parentNode?.parentNode?.classList.add(
  208. "TAI-body-div"
  209. );
  210. }
  211. }
  212. // 主逻辑
  213. (function init() {
  214. const style = document.createElement("style");
  215. style.id = CONFIG.STYLE_ID;
  216. style.textContent = globalStyles;
  217. document.head.appendChild(style);
  218.  
  219. let executionCount = 0;
  220. const interval = setInterval(() => {
  221. if (executionCount >= CONFIG.MAX_EXECUTIONS) return clearInterval(interval);
  222.  
  223. DateFormatter.refresh();
  224. document.querySelectorAll("a").forEach(processLinks);
  225. document.querySelectorAll("button").forEach(processButtons);
  226. document.querySelectorAll("div").forEach(processDivs);
  227. document.querySelectorAll("span").forEach(processSpans);
  228.  
  229. executionCount++;
  230. }, CONFIG.INTERVAL_DELAY);
  231. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址