Greasy Fork镜像 支持简体中文。

Lofter查看历史记录

在 Lofter 网页版查看App端浏览记录

  1. // ==UserScript==
  2. // @name Lofter查看历史记录
  3. // @license GPLv3
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.3.1
  6. // @description 在 Lofter 网页版查看App端浏览记录
  7. // @author SrakhiuMeow
  8. // @match https://www.lofter.com/
  9. // @grant GM.xmlHttpRequest
  10. // @connect api.lofter.com
  11. // // @run-at document-start
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. function getCookie(name) {
  18. const cookies = document.cookie.split('; ');
  19. for (const cookie of cookies) {
  20. const [key, value] = cookie.split('=');
  21. if (key === name) {
  22. return decodeURIComponent(value); // 解码 Cookie 值
  23. }
  24. }
  25. return null; // 如果未找到 Cookie,返回 null
  26. }
  27.  
  28. function getHistory(authkey, blogdomain, offset = 0, limit = 50) {
  29. const url = new URL("https://api.lofter.com/v2.0/history.api");
  30. const params = {
  31. 'method': 'getList',
  32. 'offset': offset,
  33. 'limit': limit,
  34. 'blogdomain': blogdomain,
  35. 'product': 'lofter-android-7.6.12'
  36. };
  37.  
  38. Object.keys(params).forEach(key =>
  39. url.searchParams.append(key, params[key])
  40. );
  41.  
  42. return new Promise((resolve, reject) => {
  43. GM.xmlHttpRequest({
  44. method: "GET",
  45. url: url.toString(),
  46. headers: {
  47. 'lofproduct': 'lofter-android-7.6.12',
  48. 'User-Agent': "LOFTER-Android 7.6.12 (V2272A; Android 13; null) WIFI",
  49. 'Accept-Encoding': "br,gzip",
  50. 'lofter-phone-login-auth': authkey,
  51. },
  52. onload: function(response) {
  53. try {
  54. // console.log(response);
  55. const data = JSON.parse(response.responseText);
  56. resolve(data.response);
  57. } catch (e) {
  58. reject(e);
  59. }
  60. },
  61. onerror: function(error) {
  62. reject(error);
  63. }
  64. });
  65. });
  66. }
  67.  
  68. function buildArticleElement(blogUrl, avatarUrl, publisher, imageUrl, digest, tags, postUrl, title, fullContent) {
  69. const avatar = document.createElement('div');
  70. avatar.className = 'mlistimg';
  71. avatar.innerHTML = `
  72. <div class="w-img" style="z-index:1;">
  73. <a href="${blogUrl}" target="_blank">
  74. <img src="${avatarUrl}?imageView&amp;thumbnail=64x64&amp;type=jpg">
  75. </a>
  76. </div>
  77. <div class="w-img" style="height:0px; padding:0; z-index:10;"></div>
  78. `;
  79.  
  80. const notDisplayImage = typeof imageUrl == "undefined" || imageUrl == null || imageUrl == "";
  81. const content = document.createElement('div');
  82. content.className = 'mlistcnt';
  83. content.innerHTML = `
  84. <div class="isay">
  85. <div class="isayt"> <a class="isayc" href="${postUrl}" title="查看全文" target="_blank">打开新页</a></div>
  86. <div class="isaym">
  87. <div class="w-who"><a href="${blogUrl}" class="publishernick"
  88. target="_blank">${publisher}</a>
  89. </div>
  90. <div>
  91. <div class="m-icnt">
  92. <h2 class="tit"> ${title}</h2>
  93. <div class="cnt">
  94. <div class="img" style="width: 164px; height: auto; display: ${notDisplayImage ? "none" : "block"}">
  95. <div class="imgc"> <a hidefocus="true"><img
  96. style="width:164px;"
  97. src="${imageUrl}?imageView&amp;thumbnail=1000x0&amp;type=jpg"></a>
  98. <div class="sphotolabels" style="display:none"></div>
  99. </div>
  100. <a class="w-zoom">查看原图</a>
  101. </div>
  102. <div class="txt full" style="display: none;">
  103. ${fullContent}
  104. </div>
  105. <div class="txt digest" style="display: block;">
  106. ${digest}
  107. </div>
  108. </div>
  109. <div class="more" style=""><a class="w-more w-more-open">展开</a></div>
  110.  
  111. </div>
  112.  
  113. </div>
  114.  
  115.  
  116. <div class="w-opt">
  117. <div class="opta" style="width: 132px;">
  118. ${tags}
  119. </div>
  120. <div class="optb"> <span class="opti" style="display: block;">
  121. <span class="opti">
  122. <a href="${postUrl}" target="_blank" hidefocus="true">查看全文</a>
  123. <span class="opticrt"></span>
  124. </span>
  125. </div>
  126. </div>
  127. </div>
  128. <div class="isayb"></div>
  129. </div>
  130. `;
  131.  
  132.  
  133. const article = document.createElement('div');
  134. article.className = 'm-mlist';
  135. article.appendChild(avatar);
  136. article.appendChild(content);
  137.  
  138. // 给展开按钮添加点击事件
  139. const more = article.querySelector('a.w-more');
  140. more.addEventListener('click', () => {
  141. const full = article.querySelector('.txt.full');
  142. const digest = article.querySelector('.txt.digest');
  143. if (more.classList.contains('w-more-open')) {
  144. more.classList.remove('w-more-open');
  145. more.classList.add('w-more-close');
  146. more.textContent = '收起';
  147. full.style.display = 'block';
  148. digest.style.display = 'none';
  149. }
  150. else {
  151.  
  152. more.classList.remove('w-more-close');
  153. more.classList.add('w-more-open');
  154. more.textContent = '展开';
  155. full.style.display = 'none';
  156. digest.style.display = 'block';
  157. digest.scrollIntoView({ behavior: 'smooth' });
  158. }
  159. });
  160.  
  161. // 给查看原图按钮添加点击事件
  162. const viewOriginal = article.querySelector('.w-zoom');
  163. const newViewOriginal = viewOriginal.cloneNode(true);
  164. viewOriginal.parentNode.replaceChild(newViewOriginal, viewOriginal);
  165.  
  166. newViewOriginal.href = imageUrl;
  167. newViewOriginal.target = '_blank';
  168.  
  169.  
  170. return article;
  171. }
  172.  
  173. function insertArticle(article) {
  174. const main = document.getElementById('main');
  175. main.insertBefore(article, main.children[7]);
  176. }
  177.  
  178. function insertArticles(articles) {
  179. const main = document.getElementById('main');
  180. // const firstArticle = main.children[7];
  181.  
  182. // 插入新元素(历史记录)
  183. articles.forEach(article => {
  184. const articleElement = buildArticleElement(
  185. article.post.publisherMainBlogInfo.homePageUrl,
  186. article.post.publisherMainBlogInfo.bigAvaImg,
  187. article.post.publisherMainBlogInfo.blogNickName,
  188. article.post.firstImageUrlForAnti,
  189. article.post.digest,
  190. article.post.tagList.map(tag => `<span class="opti"><a href="${tag.tagUrl}" target="_blank"><span>${tag}</span></a></span>`).join(' '),
  191. article.post.blogPageUrl,
  192. article.post.title,
  193. article.post.content
  194. );
  195. // main.insertBefore(articleElement, firstArticle);
  196. main.appendChild(articleElement);
  197. });
  198. }
  199.  
  200. var offset = 20;
  201.  
  202. function change2history() {
  203. // 变换按钮状态
  204. this.querySelector('span').textContent = '返回主页';
  205. this.removeEventListener('click', change2history);
  206. this.addEventListener('click', () => {
  207. location.reload();
  208. });
  209.  
  210. // 获取authkey
  211. const authkey = getCookie("LOFTER-PHONE-LOGIN-AUTH");
  212. if (authkey === null) {
  213. console.log('未登录(不可用)');
  214. throw new Error('未登录(不可用)');
  215. }
  216. // console.log('authkey:', authkey);
  217.  
  218. // 获取blogDomain
  219. let blogDomain = document.querySelector('span.lg2').textContent;
  220. // console.log('blogDomain:', blogDomain);
  221.  
  222. // 清除原有元素(关注更新)
  223. const divs = document.querySelectorAll('div.m-mlist');
  224. divs.forEach(div => {
  225. div.remove();
  226. });
  227.  
  228. const history = getHistory(authkey, blogDomain, 0, 20)
  229. .then(response => insertArticles(response.items))
  230. .catch(error => console.error(error));
  231. // console.log('hi');
  232. // console.log(history);
  233.  
  234. window.addEventListener('scroll', e => {
  235. e.stopImmediatePropagation(); // 先阻止原逻辑
  236. const threshold = 100;
  237. const isNearBottom = (window.innerHeight + window.scrollY) >=
  238. document.body.scrollHeight - threshold;
  239.  
  240. if (isNearBottom) {
  241. // console.log("触发自定义加载...");
  242. // 调用你的加载函数(例如从其他API获取数据)
  243.  
  244. const history = getHistory(authkey, blogDomain, offset, 20)
  245. .then(response => insertArticles(response.items))
  246. .catch(error => console.error(error));
  247. offset += 20;
  248. }
  249. }, true); // 必须在捕获阶段拦截!
  250. }
  251.  
  252. function initializeHistoryFeature() {
  253. const slideBar = document.getElementById('slide-bar')?.children[0]?.children[1];
  254. if (!slideBar) {
  255. console.error('无法找到侧边栏元素');
  256. return;
  257. }
  258. // 添加分割线
  259. const dividingLine = document.createElement('div');
  260. dividingLine.className = slideBar.children[3].children[0].className;
  261. slideBar.insertBefore(dividingLine, slideBar.children[0]);
  262.  
  263. // 添加历史记录按钮
  264. // 不知道为什么直接用<a>会有报错(
  265. const history = document.createElement('div');
  266. history.id = 'getHistory';
  267. history.innerHTML = `
  268. <div>
  269. <h5 class="LRlf1c3Y3+bO-foPi4wNjQ==">
  270. <span>历史记录</span>
  271. <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" class="mVWxtvI0CO9-BAyQYEFwKw=="><path d="M8 4.5l5.5 5.5L8 15.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path></svg>
  272. </h5>
  273. </div>
  274. `;
  275. history.style.cursor = 'pointer';
  276. history.addEventListener('click', change2history);
  277. slideBar.insertBefore(history, slideBar.children[0]);
  278.  
  279. }
  280.  
  281. // 监听 DOM 变化,等待 slidebar 加载完成
  282. function waitForElement(selector, callback) {
  283. const observer = new MutationObserver((mutations, obs) => {
  284. const element = document.querySelector(selector);
  285. if (element) {
  286. clearTimeout(timeoutId); // 清除超时定时器
  287. obs.disconnect(); // 停止观察
  288. callback(element);
  289. }
  290. });
  291.  
  292. const timeoutId = setTimeout(() => {
  293. observer.disconnect(); // 停止观察
  294. }, 2000); // 2秒超时
  295.  
  296. // 开始观察整个文档的变化
  297. observer.observe(document, {
  298. childList: true,
  299. subtree: true
  300. });
  301. }
  302.  
  303. // 避免脚本过早执行
  304. waitForElement('#slide-bar', (element) => {
  305. setTimeout(() => {
  306. // console.log('Slide bar loaded');
  307. initializeHistoryFeature();
  308. }, 50); // 等待50ms后执行
  309. });
  310.  
  311. })();

QingJ © 2025

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