PatreonExpander

Simplify elements, expand contents and comments

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