Bilibili 动态筛选

Bilibili 动态筛选,快速找出感兴趣的动态

  1. // ==UserScript==
  2. // @name Bilibili 动态筛选
  3. // @namespace Schwi
  4. // @version 2.8
  5. // @description Bilibili 动态筛选,快速找出感兴趣的动态
  6. // @author Schwi
  7. // @match *://*.bilibili.com/*
  8. // @connect api.bilibili.com
  9. // @connect api.vc.bilibili.com
  10. // @grant GM.xmlHttpRequest
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @noframes
  15. // @supportURL https://github.com/cyb233/script
  16. // @icon https://www.bilibili.com/favicon.ico
  17. // @license GPL-3.0
  18. // ==/UserScript==
  19.  
  20. (function () {
  21. 'use strict';
  22.  
  23. // 将字符串转换回函数
  24. const serializeFilters = (filters) => {
  25. if (!filters) return null;
  26. for (const key in filters) {
  27. filters[key].filter = filters[key].filter.toString();
  28. }
  29. return filters;
  30. }
  31. // 将字符串转换回函数
  32. const deserializeFilters = (filters) => {
  33. if (!filters) return null;
  34. for (const key in filters) {
  35. filters[key].filter = new Function('return ' + filters[key].filter)();
  36. }
  37. return filters;
  38. }
  39.  
  40. // 初始化 自定义筛选规则,示例值:{全部: {type: "checkbox", filter: "(item, input) => true" }, ...}
  41. GM_setValue('customFilters', serializeFilters(deserializeFilters(GM_getValue('customFilters', null))));
  42.  
  43. // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/dynamic/dynamic_enum.md
  44. const DYNAMIC_TYPE = {
  45. DYNAMIC_TYPE_NONE: { key: "DYNAMIC_TYPE_NONE", name: "动态失效", filter: false },
  46. DYNAMIC_TYPE_FORWARD: { key: "DYNAMIC_TYPE_FORWARD", name: "转发", filter: false },
  47. DYNAMIC_TYPE_AV: { key: "DYNAMIC_TYPE_AV", name: "视频", filter: true },
  48. DYNAMIC_TYPE_PGC: { key: "DYNAMIC_TYPE_PGC", name: "剧集", filter: true },
  49. DYNAMIC_TYPE_COURSES: { key: "DYNAMIC_TYPE_COURSES", name: "课程", filter: true },
  50. DYNAMIC_TYPE_WORD: { key: "DYNAMIC_TYPE_WORD", name: "文本", filter: true },
  51. DYNAMIC_TYPE_DRAW: { key: "DYNAMIC_TYPE_DRAW", name: "图文", filter: true },
  52. DYNAMIC_TYPE_ARTICLE: { key: "DYNAMIC_TYPE_ARTICLE", name: "专栏", filter: true },
  53. DYNAMIC_TYPE_MUSIC: { key: "DYNAMIC_TYPE_MUSIC", name: "音乐", filter: true },
  54. DYNAMIC_TYPE_COMMON_SQUARE: { key: "DYNAMIC_TYPE_COMMON_SQUARE", name: "卡片", filter: true }, // 充电专属问答,收藏集等
  55. DYNAMIC_TYPE_COMMON_VERTICAL: { key: "DYNAMIC_TYPE_COMMON_VERTICAL", name: "竖屏", filter: true },
  56. DYNAMIC_TYPE_LIVE: { key: "DYNAMIC_TYPE_LIVE", name: "直播", filter: true },
  57. DYNAMIC_TYPE_MEDIALIST: { key: "DYNAMIC_TYPE_MEDIALIST", name: "收藏夹", filter: true },
  58. DYNAMIC_TYPE_COURSES_SEASON: { key: "DYNAMIC_TYPE_COURSES_SEASON", name: "课程合集", filter: true },
  59. DYNAMIC_TYPE_COURSES_BATCH: { key: "DYNAMIC_TYPE_COURSES_BATCH", name: "课程批次", filter: true },
  60. DYNAMIC_TYPE_AD: { key: "DYNAMIC_TYPE_AD", name: "广告", filter: true },
  61. DYNAMIC_TYPE_APPLET: { key: "DYNAMIC_TYPE_APPLET", name: "小程序", filter: true },
  62. DYNAMIC_TYPE_SUBSCRIPTION: { key: "DYNAMIC_TYPE_SUBSCRIPTION", name: "订阅", filter: true },
  63. DYNAMIC_TYPE_LIVE_RCMD: { key: "DYNAMIC_TYPE_LIVE_RCMD", name: "直播", filter: true }, // 被转发
  64. DYNAMIC_TYPE_BANNER: { key: "DYNAMIC_TYPE_BANNER", name: "横幅", filter: true },
  65. DYNAMIC_TYPE_UGC_SEASON: { key: "DYNAMIC_TYPE_UGC_SEASON", name: "合集", filter: true },
  66. DYNAMIC_TYPE_PGC_UNION: { key: "DYNAMIC_TYPE_PGC_UNION", name: "番剧影视", filter: true },
  67. DYNAMIC_TYPE_SUBSCRIPTION_NEW: { key: "DYNAMIC_TYPE_SUBSCRIPTION_NEW", name: "新订阅", filter: true },
  68. };
  69.  
  70. const MAJOR_TYPE = {
  71. MAJOR_TYPE_NONE: { key: "MAJOR_TYPE_NONE", name: "动态失效" },
  72. MAJOR_TYPE_OPUS: { key: "MAJOR_TYPE_OPUS", name: "动态" },
  73. MAJOR_TYPE_ARCHIVE: { key: "MAJOR_TYPE_ARCHIVE", name: "视频" },
  74. MAJOR_TYPE_PGC: { key: "MAJOR_TYPE_PGC", name: "番剧影视" },
  75. MAJOR_TYPE_COURSES: { key: "MAJOR_TYPE_COURSES", name: "课程" },
  76. MAJOR_TYPE_DRAW: { key: "MAJOR_TYPE_DRAW", name: "图文" },
  77. MAJOR_TYPE_ARTICLE: { key: "MAJOR_TYPE_ARTICLE", name: "专栏" },
  78. MAJOR_TYPE_MUSIC: { key: "MAJOR_TYPE_MUSIC", name: "音乐" },
  79. MAJOR_TYPE_COMMON: { key: "MAJOR_TYPE_COMMON", name: "卡片" },
  80. MAJOR_TYPE_LIVE: { key: "MAJOR_TYPE_LIVE", name: "直播" },
  81. MAJOR_TYPE_MEDIALIST: { key: "MAJOR_TYPE_MEDIALIST", name: "收藏夹" },
  82. MAJOR_TYPE_APPLET: { key: "MAJOR_TYPE_APPLET", name: "小程序" },
  83. MAJOR_TYPE_SUBSCRIPTION: { key: "MAJOR_TYPE_SUBSCRIPTION", name: "订阅" },
  84. MAJOR_TYPE_LIVE_RCMD: { key: "MAJOR_TYPE_LIVE_RCMD", name: "直播推荐" },
  85. MAJOR_TYPE_UGC_SEASON: { key: "MAJOR_TYPE_UGC_SEASON", name: "合集" },
  86. MAJOR_TYPE_SUBSCRIPTION_NEW: { key: "MAJOR_TYPE_SUBSCRIPTION_NEW", name: "新订阅" },
  87. MAJOR_TYPE_BLOCKED: { key: "MAJOR_TYPE_BLOCKED", name: "屏蔽(如未充电)" },
  88. };
  89.  
  90. const RICH_TEXT_NODE_TYPE = {
  91. RICH_TEXT_NODE_TYPE_NONE: { key: "RICH_TEXT_NODE_TYPE_NONE", name: "无效节点" },
  92. RICH_TEXT_NODE_TYPE_TEXT: { key: "RICH_TEXT_NODE_TYPE_TEXT", name: "文本" },
  93. RICH_TEXT_NODE_TYPE_AT: { key: "RICH_TEXT_NODE_TYPE_AT", name: "@用户" },
  94. RICH_TEXT_NODE_TYPE_LOTTERY: { key: "RICH_TEXT_NODE_TYPE_LOTTERY", name: "互动抽奖" },
  95. RICH_TEXT_NODE_TYPE_VOTE: { key: "RICH_TEXT_NODE_TYPE_VOTE", name: "投票" },
  96. RICH_TEXT_NODE_TYPE_TOPIC: { key: "RICH_TEXT_NODE_TYPE_TOPIC", name: "话题" },
  97. RICH_TEXT_NODE_TYPE_GOODS: { key: "RICH_TEXT_NODE_TYPE_GOODS", name: "商品链接" },
  98. RICH_TEXT_NODE_TYPE_BV: { key: "RICH_TEXT_NODE_TYPE_BV", name: "视频链接" },
  99. RICH_TEXT_NODE_TYPE_AV: { key: "RICH_TEXT_NODE_TYPE_AV", name: "视频" },
  100. RICH_TEXT_NODE_TYPE_EMOJI: { key: "RICH_TEXT_NODE_TYPE_EMOJI", name: "表情" },
  101. RICH_TEXT_NODE_TYPE_USER: { key: "RICH_TEXT_NODE_TYPE_USER", name: "用户" },
  102. RICH_TEXT_NODE_TYPE_CV: { key: "RICH_TEXT_NODE_TYPE_CV", name: "专栏" },
  103. RICH_TEXT_NODE_TYPE_VC: { key: "RICH_TEXT_NODE_TYPE_VC", name: "音频" },
  104. RICH_TEXT_NODE_TYPE_WEB: { key: "RICH_TEXT_NODE_TYPE_WEB", name: "网页链接" },
  105. RICH_TEXT_NODE_TYPE_TAOBAO: { key: "RICH_TEXT_NODE_TYPE_TAOBAO", name: "淘宝链接" },
  106. RICH_TEXT_NODE_TYPE_MAIL: { key: "RICH_TEXT_NODE_TYPE_MAIL", name: "邮箱地址" },
  107. RICH_TEXT_NODE_TYPE_OGV_SEASON: { key: "RICH_TEXT_NODE_TYPE_OGV_SEASON", name: "剧集信息" },
  108. RICH_TEXT_NODE_TYPE_OGV_EP: { key: "RICH_TEXT_NODE_TYPE_OGV_EP", name: "剧集" },
  109. RICH_TEXT_NODE_TYPE_SEARCH_WORD: { key: "RICH_TEXT_NODE_TYPE_SEARCH_WORD", name: "搜索词" },
  110. };
  111.  
  112. const ADDITIONAL_TYPE = {
  113. ADDITIONAL_TYPE_NONE: { key: "ADDITIONAL_TYPE_NONE", name: "无附加类型" },
  114. ADDITIONAL_TYPE_PGC: { key: "ADDITIONAL_TYPE_PGC", name: "番剧影视" },
  115. ADDITIONAL_TYPE_GOODS: { key: "ADDITIONAL_TYPE_GOODS", name: "商品信息" },
  116. ADDITIONAL_TYPE_VOTE: { key: "ADDITIONAL_TYPE_VOTE", name: "投票" },
  117. ADDITIONAL_TYPE_COMMON: { key: "ADDITIONAL_TYPE_COMMON", name: "一般类型" },
  118. ADDITIONAL_TYPE_MATCH: { key: "ADDITIONAL_TYPE_MATCH", name: "比赛" },
  119. ADDITIONAL_TYPE_UP_RCMD: { key: "ADDITIONAL_TYPE_UP_RCMD", name: "UP主推荐" },
  120. ADDITIONAL_TYPE_UGC: { key: "ADDITIONAL_TYPE_UGC", name: "视频跳转" },
  121. ADDITIONAL_TYPE_RESERVE: { key: "ADDITIONAL_TYPE_RESERVE", name: "直播预约" },
  122. ADDITIONAL_TYPE_UPOWER_LOTTERY: { key: "ADDITIONAL_TYPE_UPOWER_LOTTERY", name: "动态充电互动抽奖" },
  123. };
  124.  
  125. const STYPE = {
  126. 1: { key: 1, name: "视频更新预告" },
  127. 2: { key: 2, name: "直播预告" },
  128. };
  129.  
  130. const BUSINESS_TYPE = {
  131. 1: { key: 1, name: "直播预约抽奖" },
  132. 10: { key: 10, name: "动态互动抽奖" },
  133. 12: { key: 12, name: "充电动态互动抽奖" }
  134. }
  135.  
  136. // 添加全局变量
  137. let dynamicList = [];
  138. let collectedCount = 0;
  139. let userData = null;
  140.  
  141. // 获取用户UID
  142. const getUserData = async () => {
  143. if (!userData) {
  144. userData = (await apiRequest('https://api.bilibili.com/x/space/v2/myinfo')).data
  145. }
  146. return userData
  147. };
  148.  
  149. // 筛选按钮数据结构
  150. const defaultFilters = {
  151. // 全部: {type: "checkbox", filter: (item, input) => true },
  152. 只看自己: { type: "checkbox", filter: (item, input) => item.modules.module_author.mid === userData.profile.mid },
  153. 排除自己: { type: "checkbox", filter: (item, input) => !defaultFilters['只看自己'].filter(item, input) },
  154. 只看转发: { type: "checkbox", filter: (item, input) => item.type === DYNAMIC_TYPE.DYNAMIC_TYPE_FORWARD.key },
  155. 排除转发: { type: "checkbox", filter: (item, input) => !defaultFilters['只看转发'].filter(item, input) },
  156. 视频更新预告: { type: "checkbox", filter: (item, input) => (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item).modules.module_dynamic.additional?.reserve?.stype === 1 },
  157. 直播预告: { type: "checkbox", filter: (item, input) => (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item).modules.module_dynamic.additional?.reserve?.stype === 2 },
  158. 充电动态: { type: "checkbox", filter: (item, input) => (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item).modules.module_author.icon_badge?.text === '充电专属' },
  159. 有奖预约: { type: "checkbox", filter: (item, input) => defaultFilters['直播预告'].filter(item, input) && (item.type === DYNAMIC_TYPE.DYNAMIC_TYPE_FORWARD.key ? item.orig : item).modules.module_dynamic.additional?.reserve?.desc3?.text },
  160. 互动抽奖: {
  161. type: "checkbox", filter: (item, input) =>
  162. (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item)?.modules?.module_dynamic?.major?.opus?.summary?.rich_text_nodes?.some(n => n?.type === RICH_TEXT_NODE_TYPE.RICH_TEXT_NODE_TYPE_LOTTERY.key)
  163. ||
  164. (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item)?.modules?.module_dynamic?.desc?.rich_text_nodes?.some(n => n?.type === RICH_TEXT_NODE_TYPE.RICH_TEXT_NODE_TYPE_LOTTERY.key)
  165. },
  166. 充电互动抽奖: { type: "checkbox", filter: (item, input) => (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item)?.modules?.module_dynamic?.additional?.type === ADDITIONAL_TYPE.ADDITIONAL_TYPE_UPOWER_LOTTERY.key },
  167. 已参与: {
  168. type: "checkbox", filter: (item, input) => {
  169. return (defaultFilters['有奖预约'].filter(item) && item.reserve?.isFollow === 1)
  170. ||
  171. (defaultFilters['互动抽奖'].filter(item) && (item.reserveInfo?.followed && item.reserveInfo?.reposted))
  172. ||
  173. (defaultFilters['充电互动抽奖'].filter(item) && (item.reserveInfo?.has_charge_right && item.reserveInfo?.participated))
  174. }
  175. },
  176. 未参与: {
  177. type: "checkbox", filter: (item, input) => {
  178. return (defaultFilters['有奖预约'].filter(item) && item.reserve?.isFollow === 0)
  179. ||
  180. (defaultFilters['互动抽奖'].filter(item) && !(item.reserveInfo?.followed && item.reserveInfo?.reposted))
  181. ||
  182. (defaultFilters['充电互动抽奖'].filter(item) && !(item.reserveInfo?.has_charge_right && item.reserveInfo?.participated))
  183. }
  184. },
  185. 已开奖: { type: "checkbox", filter: (item, input) => item.reserveInfo?.lottery_result },
  186. 未开奖: { type: "checkbox", filter: (item, input) => item.reserveInfo && !item.reserveInfo.lottery_result },
  187. 我中奖的: {
  188. type: "checkbox", filter: (item, input) => {
  189. const lottery_result = item.reserveInfo?.lottery_result
  190. if (!lottery_result) {
  191. return false;
  192. }
  193. const prizeCategories = Object.keys(lottery_result);
  194. for (const category of prizeCategories) {
  195. const prizeList = lottery_result[category];
  196. if (prizeList.some(prize => prize.uid === userData.profile.mid)) {
  197. return true;
  198. }
  199. }
  200. return false;
  201. }
  202. },
  203. 未中奖: { type: "checkbox", filter: (item, input) => defaultFilters['已开奖'].filter(item, input) && !defaultFilters['我中奖的'].filter(item, input) },
  204. 搜索: {
  205. type: "text",
  206. filter: (item, input) => {
  207. const searchText = input.toLocaleUpperCase();
  208. const authorName = item.modules.module_author.name.toLocaleUpperCase();
  209. const authorMid = item.modules.module_author.mid.toString();
  210. const titleText = (item.modules.module_dynamic.major?.opus?.title || item.modules.module_dynamic.major?.archive?.title || '').toLocaleUpperCase();
  211. const descText = (item.modules.module_dynamic.major?.opus?.summary?.text || item.modules.module_dynamic.desc?.text || item.modules.module_dynamic.major?.archive?.desc || '').toLocaleUpperCase();
  212.  
  213. const forwardAuthorName = item.type === DYNAMIC_TYPE.DYNAMIC_TYPE_FORWARD.key ? item.orig.modules.module_author.name.toLocaleUpperCase() : '';
  214. const forwardAuthorMid = item.type === DYNAMIC_TYPE.DYNAMIC_TYPE_FORWARD.key ? item.orig.modules.module_author.mid.toString() : '';
  215. const forwardDescText = item.type === DYNAMIC_TYPE.DYNAMIC_TYPE_FORWARD.key ? (item.orig.modules.module_dynamic.major.opus?.summary?.text || '').toLocaleUpperCase() : '';
  216.  
  217. return authorName.includes(searchText) || authorMid.includes(searchText) || titleText.includes(searchText) || descText.includes(searchText) ||
  218. forwardAuthorName.includes(searchText) || forwardAuthorMid.includes(searchText) || forwardDescText.includes(searchText);
  219. }
  220. },
  221. };
  222.  
  223. const typeFilters = {};
  224. let customFilters;
  225.  
  226. // 工具函数:创建 dialog
  227. function createDialog(id, title, content) {
  228. let dialog = document.createElement('div');
  229. dialog.id = id;
  230. dialog.style.position = 'fixed';
  231. dialog.style.top = '5%';
  232. dialog.style.left = '5%';
  233. dialog.style.width = '90%';
  234. dialog.style.height = '90%';
  235. dialog.style.backgroundColor = '#fff';
  236. dialog.style.border = '1px solid #ccc';
  237. dialog.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
  238. dialog.style.zIndex = '9999';
  239. dialog.style.display = 'none';
  240. dialog.style.overflow = 'hidden'; // 添加 overflow: hidden
  241.  
  242. let header = document.createElement('div');
  243. header.style.display = 'flex';
  244. header.style.justifyContent = 'space-between';
  245. header.style.alignItems = 'center';
  246. header.style.padding = '10px';
  247. header.style.borderBottom = '1px solid #ccc';
  248. header.style.backgroundColor = '#f9f9f9';
  249.  
  250. let titleElement = document.createElement('span');
  251. titleElement.textContent = title;
  252. header.appendChild(titleElement);
  253.  
  254. let closeButton = document.createElement('button');
  255. closeButton.textContent = '关闭';
  256. closeButton.style.backgroundColor = '#ff4d4f'; // 修改背景颜色为红色
  257. closeButton.style.color = '#fff'; // 修改文字颜色为白色
  258. closeButton.style.border = 'none';
  259. closeButton.style.borderRadius = '5px';
  260. closeButton.style.cursor = 'pointer';
  261. closeButton.style.padding = '5px 10px';
  262. closeButton.style.transition = 'background-color 0.3s'; // 添加过渡效果
  263. closeButton.onmouseover = () => { closeButton.style.backgroundColor = '#d93637'; } // 添加悬停效果
  264. closeButton.onmouseout = () => { closeButton.style.backgroundColor = '#ff4d4f'; } // 恢复背景颜色
  265. closeButton.onclick = () => dialog.remove();
  266. header.appendChild(closeButton);
  267.  
  268. dialog.appendChild(header);
  269.  
  270. let contentArea = document.createElement('div');
  271. contentArea.innerHTML = content;
  272. contentArea.style.padding = '10px';
  273. dialog.appendChild(contentArea);
  274.  
  275. document.body.appendChild(dialog);
  276.  
  277. return {
  278. dialog: dialog,
  279. header: header,
  280. titleElement: titleElement,
  281. closeButton: closeButton,
  282. contentArea: contentArea
  283. };
  284. }
  285.  
  286. // 创建并显示时间选择器 dialog
  287. function showTimeSelector(callback, isSelf) {
  288. let yesterday = new Date();
  289. yesterday.setDate(yesterday.getDate() - 1);
  290. let today = new Date();
  291.  
  292. let dialogContent = `<div style='padding:20px; display: flex; flex-direction: column; align-items: center;'>
  293. <label for='startDate' style='font-size: 16px; margin-bottom: 10px;'>开始时间:</label>
  294. <input type='date' id='startDate' value='${yesterday.getFullYear()}-${(yesterday.getMonth() + 1) < 10 ? '0' + (yesterday.getMonth() + 1) : (yesterday.getMonth() + 1)}-${yesterday.getDate() < 10 ? '0' + yesterday.getDate() : yesterday.getDate()}' style='margin-bottom: 20px; padding: 10px; font-size: 16px; border: 1px solid #ccc; border-radius: 5px;'>
  295. <label for='endDate' style='font-size: 16px; margin-bottom: 10px;'>结束时间:</label>
  296. <input type='date' id='endDate' value='${today.getFullYear()}-${(today.getMonth() + 1) < 10 ? '0' + (today.getMonth() + 1) : (today.getMonth() + 1)}-${today.getDate() < 10 ? '0' + today.getDate() : today.getDate()}' style='margin-bottom: 20px; padding: 10px; font-size: 16px; border: 1px solid #ccc; border-radius: 5px;'>
  297. <button id='startTask' style='padding: 10px 20px; font-size: 16px; background-color: #00a1d6; color: white; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s;'>开始</button>
  298. </div>`;
  299.  
  300. const { dialog, contentArea } = createDialog('timeSelectorDialog', '选择时间', dialogContent);
  301. dialog.style.display = 'block';
  302.  
  303. contentArea.querySelector('#startTask').onclick = () => {
  304. const startDate = new Date(contentArea.querySelector('#startDate').value + ' 00:00:00').getTime() / 1000;
  305. const endDate = new Date(contentArea.querySelector('#endDate').value + ' 00:00:00').getTime() / 1000;
  306. dialog.style.display = 'none';
  307. callback(startDate, endDate, isSelf);
  308. };
  309. }
  310.  
  311. // API 请求函数
  312. async function apiRequest(url, retry = 3) {
  313. for (let attempt = 1; attempt <= retry; attempt++) {
  314. try {
  315. const response = await GM.xmlHttpRequest({
  316. method: 'GET',
  317. url: url,
  318. });
  319. const data = JSON.parse(response.responseText);
  320. return data;
  321. } catch (e) {
  322. console.error(`API ${url} 请求失败,正在重试...`, e);
  323. if (attempt === retry) {
  324. throw e;
  325. }
  326. }
  327. }
  328. }
  329.  
  330. // 显示结果 dialog
  331. function showResultsDialog() {
  332. const { dialog, titleElement, closeButton } = createDialog('resultsDialog', `动态结果(${dynamicList.length}/${dynamicList.length}) ${new Date(dynamicList[dynamicList.length - 1].modules.module_author.pub_ts * 1000).toLocaleString()} ~ ${new Date(dynamicList[0].modules.module_author.pub_ts * 1000).toLocaleString()}`, '');
  333.  
  334. let gridContainer = document.createElement('div');
  335. gridContainer.style.display = 'grid';
  336. gridContainer.style.gridTemplateColumns = 'repeat(auto-fill,minmax(200px,1fr))';
  337. gridContainer.style.gap = '10px';
  338. gridContainer.style.padding = '10px';
  339. gridContainer.style.height = 'calc(90% - 50px)'; // 设置高度以启用滚动
  340. gridContainer.style.overflowY = 'auto'; // 启用垂直滚动
  341. gridContainer.style.alignContent = 'flex-start';
  342.  
  343. // 添加全局切换按钮
  344. const toggleVisibilityButton = document.createElement('button');
  345. toggleVisibilityButton.textContent = "是否只看图片";
  346. toggleVisibilityButton.style.backgroundColor = "#00a1d6";
  347. toggleVisibilityButton.style.color = "#fff";
  348. toggleVisibilityButton.style.border = 'none';
  349. toggleVisibilityButton.style.borderRadius = '5px';
  350. toggleVisibilityButton.style.cursor = 'pointer';
  351. toggleVisibilityButton.style.padding = '5px 10px';
  352. toggleVisibilityButton.style.transition = 'background-color 0.3s'; // 添加过渡效果
  353. toggleVisibilityButton.style.marginLeft = "auto"; // 右对齐
  354. toggleVisibilityButton.style.marginRight = "10px";
  355. toggleVisibilityButton.onmouseover = () => { toggleVisibilityButton.style.backgroundColor = "#008ecf"; };
  356. toggleVisibilityButton.onmouseout = () => { toggleVisibilityButton.style.backgroundColor = "#00a1d6"; };
  357.  
  358. let isContentVisible = true; // 全局状态
  359.  
  360. toggleVisibilityButton.onclick = () => {
  361. isContentVisible = !isContentVisible;
  362. const contentContainers = document.querySelectorAll(".dynamic-content-container");
  363. contentContainers.forEach(container => {
  364. container.style.display = isContentVisible ? "flex" : "none";
  365. });
  366. };
  367. // 添加到倒数第二个
  368. closeButton.before(toggleVisibilityButton);
  369.  
  370. // 遍历 DYNAMIC_TYPE 生成 filters
  371. Object.values(DYNAMIC_TYPE).forEach(type => {
  372. if (type.filter) { // 根据 filter 判断是否纳入过滤条件
  373. if (!typeFilters[type.name]) {
  374. typeFilters[type.name] = { type: "checkbox", filter: (item, input) => item.baseType === type.key };
  375. } else {
  376. const existingFilter = typeFilters[type.name].filter;
  377. typeFilters[type.name].filter = (item, input) => existingFilter(item, input) || item.baseType === type.key;
  378. }
  379. }
  380. });
  381. // 用户自定义筛选条件
  382. customFilters = deserializeFilters(GM_getValue('customFilters', null));
  383.  
  384. const deal = (dynamicList) => {
  385. let checkedFilters = [];
  386. for (let key in defaultFilters) {
  387. const f = defaultFilters[key];
  388. const filter = filterButtonsContainer.querySelector(`#${key}`);
  389. let checkedFilter;
  390. switch (f.type) {
  391. case 'checkbox':
  392. checkedFilter = { ...f, value: filter.checked };
  393. break;
  394. case 'text':
  395. checkedFilter = { ...f, value: filter.value };
  396. break;
  397. }
  398. checkedFilters.push(checkedFilter);
  399. }
  400. for (let key in typeFilters) {
  401. const f = typeFilters[key];
  402. const filter = filterButtonsContainer.querySelector(`#${key}`);
  403. let checkedFilter;
  404. switch (f.type) {
  405. case 'checkbox':
  406. checkedFilter = { ...f, value: filter.checked };
  407. break;
  408. case 'text':
  409. checkedFilter = { ...f, value: filter.value };
  410. break;
  411. }
  412. checkedFilters.push(checkedFilter);
  413. }
  414. // 添加自定义筛选条件
  415. if (customFilters && Object.keys(customFilters).length > 0) {
  416. for (let key in customFilters) {
  417. const f = customFilters[key];
  418. const filter = filterButtonsContainer.querySelector(`#${key}`);
  419. let checkedFilter;
  420. switch (f.type) {
  421. case 'checkbox':
  422. checkedFilter = { ...f, value: filter.checked };
  423. break;
  424. case 'text':
  425. checkedFilter = { ...f, value: filter.value };
  426. break;
  427. }
  428. checkedFilters.push(checkedFilter);
  429. }
  430. }
  431. dynamicList.forEach(item => {
  432. item.display = checkedFilters.every(f => f.value ? f.filter(item, f.value) : true);
  433. });
  434. console.log(checkedFilters, dynamicList.filter(item => item.display));
  435.  
  436. // 更新标题显示筛选后的条数和总条数
  437. titleElement.textContent = `动态结果(${dynamicList.filter(item => item.display).length}/${dynamicList.length}) ${new Date(dynamicList[dynamicList.length - 1].modules.module_author.pub_ts * 1000).toLocaleString()} ~ ${new Date(dynamicList[0].modules.module_author.pub_ts * 1000).toLocaleString()}`;
  438.  
  439. // 重新初始化 IntersectionObserver
  440. observer.disconnect();
  441. renderedCount = 0;
  442. gridContainer.innerHTML = ''; // 清空 gridContainer 的内容
  443. renderBatch();
  444. };
  445.  
  446. // 封装生成筛选按钮的函数
  447. const createFilterButtons = (filters, dynamicList) => {
  448. let mainContainer = document.createElement('div');
  449. mainContainer.style.display = 'flex';
  450. mainContainer.style.flexWrap = 'wrap'; // 修改为换行布局
  451. mainContainer.style.width = '100%';
  452.  
  453. for (let key in filters) {
  454. let filter = filters[key];
  455. let input = document.createElement('input');
  456. input.type = filter.type;
  457. input.id = key;
  458. input.style.marginRight = '5px';
  459. // 添加边框样式
  460. if (filter.type === 'text') {
  461. input.style.border = '1px solid #ccc';
  462. input.style.padding = '5px';
  463. input.style.borderRadius = '5px';
  464. }
  465.  
  466. let label = document.createElement('label');
  467. label.htmlFor = key;
  468. label.textContent = `${key}${filter.note ? `(${filter.note})` : ''}`;
  469. label.style.display = 'flex'; // 确保 label 和 input 在同一行
  470. label.style.alignItems = 'center'; // 垂直居中对齐
  471. label.style.marginRight = '5px';
  472.  
  473. let container = document.createElement('div');
  474. container.style.display = 'flex';
  475. container.style.alignItems = 'center';
  476. container.style.marginRight = '10px';
  477.  
  478. if (['checkbox', 'radio'].includes(filter.type)) {
  479. (function (dynamicList, filter, input) {
  480. input.addEventListener('change', () => deal(dynamicList));
  481. })(dynamicList, filter, input);
  482. container.appendChild(input);
  483. container.appendChild(label);
  484. } else {
  485. let timeout;
  486. (function (dynamicList, filter, input) {
  487. input.addEventListener('input', () => {
  488. clearTimeout(timeout);
  489. timeout = setTimeout(() => deal(dynamicList), 1000); // 增加延迟处理
  490. });
  491. })(dynamicList, filter, input);
  492. container.appendChild(label);
  493. container.appendChild(input);
  494. }
  495.  
  496. mainContainer.appendChild(container);
  497. }
  498.  
  499. return mainContainer;
  500. };
  501.  
  502. // 生成筛选按钮
  503. let filterButtonsContainer = document.createElement('div');
  504. filterButtonsContainer.style.marginBottom = '10px';
  505. filterButtonsContainer.style.display = 'flex'; // 添加 flex 布局
  506. filterButtonsContainer.style.flexWrap = 'wrap'; // 添加换行
  507. filterButtonsContainer.style.gap = '10px'; // 添加间距
  508. filterButtonsContainer.style.padding = '10px';
  509. filterButtonsContainer.style.alignItems = 'center'; // 添加垂直居中对齐
  510.  
  511. filterButtonsContainer.appendChild(createFilterButtons(defaultFilters, dynamicList));
  512. filterButtonsContainer.appendChild(createFilterButtons(typeFilters, dynamicList));
  513.  
  514. // 添加自定义筛选按钮
  515. if (customFilters && Object.keys(customFilters).length > 0) {
  516. filterButtonsContainer.appendChild(createFilterButtons(customFilters, dynamicList));
  517. }
  518.  
  519. const getDescText = (dynamic, isForward) => {
  520. let titleText = dynamic.modules.module_dynamic.major?.opus?.title || dynamic.modules.module_dynamic.major?.archive?.title || ''
  521. let descText = dynamic.modules.module_dynamic.major?.opus?.summary?.text || dynamic.modules.module_dynamic.desc?.text || dynamic.modules.module_dynamic.major?.archive?.desc || ''
  522.  
  523. if (isForward) {
  524. const subDescText = getDescText(dynamic.orig)
  525. descText += `<hr />${subDescText}`
  526. }
  527.  
  528. return `${titleText ? '<h3>' + titleText + '</h3><br />' : ''}${descText}`
  529. }
  530.  
  531. const createDynamicItem = (dynamic) => {
  532. const isForward = dynamic.type === DYNAMIC_TYPE.DYNAMIC_TYPE_FORWARD.key;
  533. const baseDynamic = isForward ? dynamic.orig : dynamic;
  534. const type = baseDynamic.type;
  535. const authorName = dynamic.modules.module_author.name;
  536. const mid = dynamic.modules.module_author.mid;
  537. const dynamicUrl = `https://t.bilibili.com/${dynamic.id_str}`;
  538. const jumpUrl = (mid, dynamicType) => {
  539. if (dynamicType === DYNAMIC_TYPE.DYNAMIC_TYPE_UGC_SEASON.key) {
  540. return `https://www.bilibili.com/video/av${mid}/`
  541. }
  542. if (dynamicType === DYNAMIC_TYPE.DYNAMIC_TYPE_PGC_UNION.key) {
  543. return `https://bangumi.bilibili.com/anime/${mid}`
  544. }
  545. return `https://space.bilibili.com/${mid}`
  546. }
  547.  
  548. let backgroundImage = '';
  549. if (type === DYNAMIC_TYPE.DYNAMIC_TYPE_DRAW.key) {
  550. backgroundImage = baseDynamic.modules.module_dynamic.major.opus?.pics[0].url;
  551. }
  552.  
  553. let dynamicItem = document.createElement('div');
  554. dynamicItem.style.position = "relative";
  555. dynamicItem.style.border = "1px solid #ddd";
  556. dynamicItem.style.borderRadius = "10px";
  557. dynamicItem.style.overflow = "hidden";
  558. dynamicItem.style.height = "300px";
  559. dynamicItem.style.display = "flex";
  560. dynamicItem.style.flexDirection = "column";
  561. dynamicItem.style.justifyContent = "flex-start"; // 修改为 flex-start 以使内容从顶部开始
  562. dynamicItem.style.padding = "10px";
  563. dynamicItem.style.color = "#fff";
  564. dynamicItem.style.transition = "transform 0.3s, background-color 0.3s"; // 添加过渡效果
  565.  
  566. dynamicItem.onmouseover = () => {
  567. dynamicItem.style.transform = "scale(1.05)"; // 略微放大
  568. cardTitle.style.background = "rgba(0, 0, 0, 0.3)";
  569. publishTime.style.background = "rgba(0, 0, 0, 0.3)";
  570. typeComment.style.background = "rgba(0, 0, 0, 0.3)";
  571. describe.style.background = "rgba(0, 0, 0, 0.3)";
  572. viewDetailsButton.style.backgroundColor = "rgba(0, 0, 0, 0.3)";
  573. };
  574.  
  575. dynamicItem.onmouseout = () => {
  576. dynamicItem.style.transform = "scale(1)"; // 恢复原始大小
  577. cardTitle.style.background = "rgba(0, 0, 0, 0.5)";
  578. publishTime.style.background = "rgba(0, 0, 0, 0.5)";
  579. typeComment.style.background = "rgba(0, 0, 0, 0.5)";
  580. describe.style.background = "rgba(0, 0, 0, 0.5)";
  581. viewDetailsButton.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
  582. };
  583.  
  584. // 背景图片
  585. if (backgroundImage) {
  586. const img = document.createElement('img');
  587. img.src = backgroundImage;
  588. img.loading = "lazy";
  589. img.style.position = "absolute";
  590. img.style.top = "0";
  591. img.style.left = "0";
  592. img.style.width = "100%";
  593. img.style.height = "100%";
  594. img.style.objectFit = "cover";
  595. img.style.zIndex = "-1";
  596. dynamicItem.appendChild(img);
  597. }
  598.  
  599. // 创建内容容器
  600. const contentContainer = document.createElement('div');
  601. contentContainer.className = "dynamic-content-container";
  602. contentContainer.style.position = "relative";
  603. contentContainer.style.zIndex = "1"; // 确保内容在背景图之上
  604. contentContainer.style.width = "100%"; // 撑满 dynamicItem 的宽度
  605. contentContainer.style.height = "100%"; // 撑满 dynamicItem 的高度
  606. contentContainer.style.display = "flex";
  607. contentContainer.style.flexDirection = "column";
  608.  
  609. // 标题
  610. const cardTitle = document.createElement("div");
  611. cardTitle.style.fontWeight = "bold";
  612. cardTitle.style.textShadow = "0 2px 4px rgba(0, 0, 0, 0.8)";
  613. cardTitle.style.background = "rgba(0, 0, 0, 0.5)";
  614. cardTitle.style.backdropFilter = "blur(5px)";
  615. cardTitle.style.borderRadius = "5px";
  616. cardTitle.style.padding = "5px";
  617. cardTitle.style.marginBottom = "5px";
  618. cardTitle.style.textAlign = "center";
  619.  
  620. // 创建 authorName 和原作者的 a 标签
  621. const authorLink = document.createElement('a');
  622. authorLink.href = jumpUrl(mid, type);
  623. authorLink.target = "_blank";
  624. authorLink.textContent = authorName;
  625.  
  626. let originalAuthorLink;
  627. if (isForward) {
  628. originalAuthorLink = document.createElement('a');
  629. const originalMid = dynamic.orig.modules.module_author.mid;
  630. const originalType = dynamic.orig.type;
  631. originalAuthorLink.href = jumpUrl(originalMid, originalType);
  632. originalAuthorLink.target = "_blank";
  633. originalAuthorLink.textContent = dynamic.orig.modules.module_author.name;
  634. }
  635.  
  636. // 设置 cardTitle 的内容
  637. cardTitle.innerHTML = isForward ? `${authorLink.outerHTML} 转发了 ${originalAuthorLink.outerHTML} 的动态` : `${authorLink.outerHTML} 发布了动态`;
  638.  
  639. // 显示发布时间
  640. const publishTime = document.createElement("div");
  641. publishTime.style.fontSize = "12px";
  642. publishTime.style.marginTop = "2px";
  643. publishTime.style.background = "rgba(0, 0, 0, 0.5)";
  644. publishTime.style.backdropFilter = "blur(5px)";
  645. publishTime.style.borderRadius = "5px";
  646. publishTime.style.padding = "5px";
  647. publishTime.style.marginBottom = "5px";
  648. publishTime.style.textAlign = "center";
  649. publishTime.textContent = `发布时间: ${new Date(dynamic.modules.module_author.pub_ts * 1000).toLocaleString()}`;
  650.  
  651. // 显示 DYNAMIC_TYPE 对应的注释
  652. const typeComment = document.createElement("div");
  653. typeComment.style.fontSize = "12px";
  654. typeComment.style.marginTop = "2px";
  655. typeComment.style.background = "rgba(0, 0, 0, 0.5)";
  656. typeComment.style.backdropFilter = "blur(5px)";
  657. typeComment.style.borderRadius = "5px";
  658. typeComment.style.padding = "5px";
  659. typeComment.style.marginBottom = "5px";
  660. typeComment.style.textAlign = "center";
  661. typeComment.textContent = `类型: ${DYNAMIC_TYPE[dynamic.type]?.name || dynamic.type} ${isForward ? `(${DYNAMIC_TYPE[dynamic.orig.type]?.name || dynamic.orig.type})` : ''
  662. } ${(defaultFilters['有奖预约'].filter(dynamic) || defaultFilters['互动抽奖'].filter(dynamic) || defaultFilters['充电互动抽奖'].filter(dynamic)) ? '🎁' : ''
  663. }${defaultFilters['充电动态'].filter(dynamic) || defaultFilters['充电互动抽奖'].filter(dynamic) ? '🔋' : ''
  664. }`;
  665.  
  666. // 正文
  667. const describe = document.createElement("div");
  668. describe.style.fontSize = "14px";
  669. describe.style.marginTop = "2px";
  670. describe.style.background = "rgba(0, 0, 0, 0.5)";
  671. describe.style.backdropFilter = "blur(5px)";
  672. describe.style.borderRadius = "5px";
  673. describe.style.padding = "5px";
  674. describe.style.marginBottom = "5px";
  675. describe.style.textAlign = "center";
  676. describe.style.flexGrow = "1"; // 添加 flexGrow 以使描述占据剩余空间
  677. describe.style.overflowY = "auto";
  678. describe.style.textOverflow = "ellipsis";
  679. describe.innerHTML = getDescText(dynamic, isForward); // 修改为 innerHTML 以支持 HTML 标签
  680.  
  681. const viewDetailsButton = document.createElement("a");
  682. viewDetailsButton.href = dynamicUrl;
  683. viewDetailsButton.target = "_blank";
  684. viewDetailsButton.textContent = "查看详情";
  685. viewDetailsButton.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
  686. viewDetailsButton.style.color = "#fff";
  687. viewDetailsButton.style.padding = "5px 10px";
  688. viewDetailsButton.style.borderRadius = "5px";
  689. viewDetailsButton.style.textDecoration = "none";
  690. viewDetailsButton.style.textAlign = "center";
  691.  
  692. contentContainer.appendChild(cardTitle);
  693. contentContainer.appendChild(typeComment);
  694. contentContainer.appendChild(describe);
  695. contentContainer.appendChild(publishTime); // 添加发布时间
  696. contentContainer.appendChild(viewDetailsButton);
  697.  
  698. // 将内容容器添加到 dynamicItem
  699. dynamicItem.appendChild(contentContainer);
  700.  
  701. return dynamicItem;
  702. };
  703.  
  704. // 分批渲染
  705. const batchSize = 50; // 每次渲染的动态数量
  706. let renderedCount = 0;
  707.  
  708. const renderBatch = () => {
  709. const renderList = dynamicList.filter(item => item.display);
  710. for (let i = 0; i < batchSize && renderedCount < renderList.length; i++, renderedCount++) {
  711. const dynamicItem = createDynamicItem(renderList[renderedCount]);
  712. dynamicItem.style.display = renderList[renderedCount].display ? 'flex' : 'none'; // 根据 display 属性显示或隐藏
  713. const contentContainer = dynamicItem.querySelector(".dynamic-content-container");
  714. contentContainer.style.display = isContentVisible ? "flex" : "none"; // 根据全局状态设置可见性
  715. gridContainer.appendChild(dynamicItem);
  716. }
  717. // 检查是否还需要继续渲染
  718. if (renderedCount < renderList.length) {
  719. observer.observe(gridContainer.lastElementChild); // 观察最后一个 dynamicItem
  720. } else {
  721. observer.disconnect(); // 如果所有动态都已渲染,停止观察
  722. }
  723. };
  724.  
  725. const observer = new IntersectionObserver((entries) => {
  726. if (entries[0].isIntersecting) {
  727. observer.unobserve(entries[0].target); // 取消对当前目标的观察
  728. renderBatch();
  729. }
  730. });
  731.  
  732. renderBatch(); // 初始渲染一批
  733.  
  734. dialog.appendChild(filterButtonsContainer);
  735. dialog.appendChild(gridContainer);
  736. dialog.style.display = 'block';
  737. }
  738.  
  739. // 主任务函数
  740. async function collectDynamic(startTime, endTime, isSelf = false) {
  741. let offset = '';
  742. dynamicList = [];
  743. collectedCount = 0;
  744. let shouldContinue = true; // 引入标志位
  745.  
  746. let { dialog, contentArea } = createDialog('progressDialog', '任务进度', `<p>已收集动态数:<span id='collectedCount'>0</span>/<span id='totalCount'>0</span></p><p>已获取最早动态时间:<span id='earliestTime'>N/A</span></p>`);
  747. dialog.style.display = 'block';
  748.  
  749. // 添加样式优化
  750. dialog.querySelector('p').style.textAlign = 'center';
  751. dialog.querySelector('p').style.fontSize = '18px';
  752. dialog.querySelector('p').style.fontWeight = 'bold';
  753. dialog.querySelector('p').style.marginTop = '20px';
  754.  
  755. await getUserData()
  756.  
  757. let shouldInclude = false;
  758. while (shouldContinue) { // 使用标志位控制循环
  759. const api = isSelf ?
  760. `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?host_mid=${userData.profile.mid}&offset=${offset}&features=itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote,decorationCard,onlyfansAssetsV2,forwardListHidden,ugcDelete,onlyfansQaCard,commentsNewVersion` :
  761. `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?type=all&offset=${offset}&features=itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote,decorationCard,onlyfansAssetsV2,forwardListHidden,ugcDelete,onlyfansQaCard,commentsNewVersion`;
  762.  
  763. try {
  764. const data = await apiRequest(api);
  765. const items = data?.data?.items;
  766.  
  767. // 如果出错等原因导致没有,直接跳过
  768. if (!items) {
  769. continue;
  770. }
  771.  
  772. if (!shouldInclude) {
  773. shouldInclude = items.some(item => item.modules.module_author.pub_ts > 0 && item.modules.module_author.pub_ts < (endTime + 24 * 60 * 60));
  774. }
  775. for (let item of items) {
  776. if (item.type !== DYNAMIC_TYPE.DYNAMIC_TYPE_LIVE_RCMD.key) {
  777. // 直播动态可能不按时间顺序出现,不能用来判断时间要求
  778. if (item.modules.module_author.pub_ts > 0 && item.modules.module_author.pub_ts < startTime) {
  779. shouldContinue = false; // 设置标志位为 false 以结束循环
  780. }
  781. }
  782. item.baseType = item.type;
  783. if (item.type === DYNAMIC_TYPE.DYNAMIC_TYPE_FORWARD.key) {
  784. item.baseType = item.orig.type;
  785. }
  786. item.display = true;
  787.  
  788. // 如果是直播预约动态,获取预约信息
  789. let reserve = null;
  790. let reserveInfo = null;
  791. if (shouldInclude) {
  792. if (defaultFilters['直播预告'].filter(item)) {
  793. const rid = (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item).modules.module_dynamic?.additional?.reserve?.rid;
  794. if (rid) {
  795. reserveInfo = (await apiRequest(`https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice?business_id=${rid}&business_type=10`)).data;
  796. }
  797. if (reserveInfo?.business_id) {
  798. const business_id = reserveInfo.business_id;
  799. const reserveRelationInfo = (await apiRequest(`https://api.bilibili.com/x/activity/up/reserve/relation/info?ids=${business_id}`)).data;
  800. reserve = reserveRelationInfo?.list[business_id];
  801. }
  802. }
  803. // 如果是互动抽奖动态,获取预约信息
  804. if (defaultFilters['互动抽奖'].filter(item)) {
  805. const id_str = (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item).id_str
  806. if (id_str) {
  807. reserveInfo = (await apiRequest(`https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice?business_id=${id_str}&business_type=1`)).data;
  808. }
  809. }
  810. // 如果是充电互动抽奖动态,获取预约信息
  811. if (defaultFilters['充电互动抽奖'].filter(item)) {
  812. const id_str = (item.type === 'DYNAMIC_TYPE_FORWARD' ? item.orig : item).id_str
  813. if (id_str) {
  814. reserveInfo = (await apiRequest(`https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice?business_id=${id_str}&business_type=12`)).data;
  815. }
  816. }
  817. }
  818. item.reserve = reserve;
  819. item.reserveInfo = reserveInfo;
  820.  
  821. if (shouldInclude) {
  822. dynamicList.push(item);
  823. }
  824. collectedCount++;
  825. contentArea.querySelector('#collectedCount').textContent = dynamicList.length;
  826. contentArea.querySelector('#totalCount').textContent = collectedCount;
  827. contentArea.querySelector('#earliestTime').textContent = new Date(item.modules.module_author.pub_ts * 1000).toLocaleString();
  828. }
  829. offset = items[items.length - 1].id_str;
  830.  
  831. if (shouldContinue) { // 检查标志位
  832. if (!data.data.has_more) shouldContinue = false; // 没有更多数据时结束循环
  833. }
  834. } catch (e) {
  835. console.error(`Error fetching data: ${e.message}`, e);
  836. continue; // 出错时继续
  837. }
  838. }
  839. console.log(`${dynamicList.length}/${collectedCount}`);
  840. console.log(`${new Date(dynamicList[dynamicList.length - 1].modules.module_author.pub_ts * 1000).toLocaleString()} ~ ${new Date(dynamicList[0].modules.module_author.pub_ts * 1000).toLocaleString()}`);
  841. console.log(dynamicList);
  842. console.log(new Set(dynamicList.map(item => item.type).filter(item => item)));
  843. console.log(new Set(dynamicList.map(item => item.orig?.type).filter(item => item)));
  844.  
  845. dialog.style.display = 'none';
  846. showResultsDialog();
  847. }
  848.  
  849. // 注册(不可用)菜单项
  850. GM_registerMenuCommand("检查动态", () => {
  851. showTimeSelector(collectDynamic);
  852. });
  853. GM_registerMenuCommand("只看自己动态", async () => {
  854. showTimeSelector(collectDynamic, true);
  855. });
  856. })();

QingJ © 2025

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