Greasy Fork

LOFTER合集一键导出txt文档

基于真实文章链接选择器,自动滚动+关键词筛选+导出txt

// ==UserScript==
// @name         LOFTER合集一键导出txt文档
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  基于真实文章链接选择器,自动滚动+关键词筛选+导出txt
// @match        *://*.lofter.com/view*
// @grant        none
// @license MIT
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(async function() {
  'use strict';
  const $ = window.jQuery;

  const delay = ms => new Promise(r => setTimeout(r, ms));

  // 自动滚动到底部,等待文章加载,检测文章数稳定
  async function autoScroll(maxAttempts = 40, interval = 1500) {
    let lastCount = 0, stableCount = 0;
    for(let i = 0; i < maxAttempts; i++) {
      window.scrollTo(0, document.body.scrollHeight);
      await delay(interval);
      const count = $("a[href*='/post/']").length;
      if(count === lastCount) {
        stableCount++;
        if(stableCount >= 3) break;
      } else {
        stableCount = 0;
        lastCount = count;
      }
    }
    return lastCount;
  }

  // 提取文章列表,去重
  function extractArticles() {
    const articles = [];
    $("a[href*='/post/']").each(function(){
      const url = $(this).attr("href");
      let title = $(this).text().replace(/\s+/g, " ").trim();
      if(url && title) {
        articles.push({url, title});
      }
    });
    // 去重
    return [...new Map(articles.map(a => [a.url, a])).values()];
  }

  // 根据文章页面尝试抓正文,尝试多个常用选择器
  async function fetchContent(url) {
    try {
      const html = await $.get(url);
      const doc = new DOMParser().parseFromString(html, "text/html");
      const selectors = [".post-text", ".text", ".post-content", ".article-desc", ".article-content"];
      let content = "";
      for(const sel of selectors) {
        const elems = doc.querySelectorAll(sel);
        if(elems.length) {
          content = Array.from(elems).map(e => e.textContent.trim()).join("\n\n");
          if(content.length > 30) break; // 找到长度合理的正文就用
        }
      }
      return content || "";
    } catch(e) {
      console.warn("抓取正文失败:", url, e);
      return "";
    }
  }

  // 导出文本
  function exportTxt(data,keyword) {
    data.sort((a,b) => 0); // 若需时间排序,可加时间字段再排序
    data.reverse();
    let text = "";
    data.forEach(item => {
      text += item.content.trim() + "\n\n---------------------------------\n\n";
    });
    const blob = new Blob([text], {type:"text/plain;charset=utf-8"});
    const link = document.createElement("a");
    link.href = URL.createObjectURL(blob);
    link.download = keyword ? `lofter_${keyword}.txt` : "lofter_文章导出.txt";
    link.click();
    URL.revokeObjectURL(link.href);
  }

  // 插入按钮UI
  function injectButton() {
    const btn = $('<button style="position:fixed;top:15px;right:15px;z-index:999999;padding:10px;background:#1e90ff;color:#fff;border:none;border-radius:6px;cursor:pointer;">输入文章标题并导出</button>');
    $("body").append(btn);
    btn.on("click", async () => {
      const keyword = prompt("请输入关键词(留空导出全部):") || "";
      btn.text("自动滚动加载中...");
      const totalCount = await autoScroll();
      btn.text(`加载到${totalCount}篇文章,开始抓正文...`);
      const articles = extractArticles();
      const results = [];
      for(let i=0; i<articles.length; i++) {
        btn.text(`抓取正文 ${i+1}/${articles.length} ...`);
        const content = await fetchContent(articles[i].url);
        if(keyword === "" || content.includes(keyword) || articles[i].title.includes(keyword)) {
          results.push({...articles[i], content});
        }
        await delay(800);
      }
      btn.text(`完成,匹配${results.length}篇,导出中...`);
      exportTxt(results,keyword);
      btn.text("输入文章标题并导出");
      alert(`导出完成,共导出${results.length}篇文章`);
    });
  }

  $(window).on("load", () => setTimeout(injectButton, 2000));
})();