您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Backup a thread
当前为
- // ==UserScript==
- // @name Discourse Thread Backup
- // @namespace polv
- // @version 0.2
- // @description Backup a thread
- // @author polv
- // @match *://community.wanikani.com/*
- // @match *://forums.learnnatively.com/*
- // @license MIT
- // @supportURL https://community.wanikani.com/t/a-way-to-backup-discourse-threads/63679/9
- // @source https://github.com/patarapolw/wanikani-userscript/blob/master/userscripts/wk-com-backup.user.js
- // @icon https://www.google.com/s2/favicons?sz=64&domain=meta.discourse.org
- // @grant none
- // ==/UserScript==
- // @ts-check
- (function () {
- 'use strict';
- async function backupThread(thread_id = 0, x1000 = false) {
- if (typeof thread_id === 'boolean') {
- x1000 = thread_id;
- thread_id = 0;
- }
- let thread_slug = '';
- let thread_title = '';
- if (!thread_id) {
- const [pid, tid, slug] = location.pathname.split('/').reverse();
- thread_id = Number(tid);
- if (!thread_id) {
- thread_slug = tid;
- thread_id = Number(pid);
- } else {
- thread_slug = slug;
- }
- }
- if (!thread_id) return;
- const main = document.createElement('main');
- let cursor = 0;
- while (true) {
- let nextCursor = cursor;
- const jsonURL =
- location.origin +
- '/t/-/' +
- thread_id +
- (cursor ? '/' + cursor : '') +
- '.json' +
- (x1000 ? '?print=true' : '');
- const obj = await fetch(jsonURL).then((r) => r.json());
- if (x1000) {
- // TODO: ?print=true is rate limited. Not sure for how long.
- x1000 = false;
- setTimeout(() => {
- fetch(jsonURL);
- }, 1 * 60 * 1000);
- }
- if (!thread_slug) {
- thread_slug = obj.slug;
- }
- if (!thread_title) {
- thread_title = obj.unicode_title || obj.title;
- }
- obj.post_stream.posts.map((p) => {
- const { username, cooked, polls, post_number, actions_summary } = p;
- if (post_number > nextCursor) {
- nextCursor = post_number;
- const section = document.createElement('section');
- main.append(section);
- section.append(
- ((p) => {
- p.innerText = `#${post_number}: ${username} ${actions_summary
- .filter((a) => a.count)
- .map((a) => `❤️ ${a.count}`)
- .join(', ')}`;
- return p;
- })(document.createElement('p')),
- );
- if (polls?.length) {
- const details = document.createElement('details');
- section.append(details);
- const summary = document.createElement('summary');
- summary.innerText = 'Polls results';
- details.append(summary);
- polls.map((p) => {
- const pre = document.createElement('pre');
- pre.textContent = JSON.stringify(
- p,
- (k, v) => {
- if (/^(avatar|assign)_/.test(k)) return;
- if (v === null || v === '') return;
- return v;
- },
- 2,
- );
- details.append(p);
- });
- }
- section.append(
- ((div) => {
- div.className = 'cooked';
- div.innerHTML = cooked;
- return div;
- })(document.createElement('div')),
- );
- }
- });
- if (cursor >= nextCursor) {
- break;
- }
- cursor = nextCursor;
- }
- main.querySelectorAll('img').forEach((img) => {
- img.loading = 'lazy';
- });
- const url =
- location.origin + '/t/' + (thread_slug || '-') + '/' + thread_id;
- if (!thread_slug) {
- thread_slug = String(thread_id);
- }
- const html = document.createElement('html');
- const head = document.createElement('head');
- html.append(head);
- head.append(
- ...Array.from(
- document.querySelectorAll(
- 'meta[charset], link[rel="icon"], link[rel="stylesheet"], style',
- ),
- ).map((el) => el.cloneNode(true)),
- ((el) => {
- el.innerText = thread_title;
- return el;
- })(document.createElement('title')),
- ((el) => {
- el.textContent = /* css */ `
- main {max-width: 1000px; margin: 0 auto;}
- .cooked {margin: 2em;}
- .spoiler:not(:hover):not(:active) {filter:blur(5px);}
- `;
- return el;
- })(document.createElement('style')),
- );
- const body = document.createElement('body');
- html.append(body);
- body.append(
- ((el) => {
- el.innerText = thread_title;
- return el;
- })(document.createElement('h1')),
- ((el) => {
- const a1 = document.createElement('a');
- el.append(a1);
- a1.href = url;
- a1.innerText = decodeURI(url);
- const span = document.createElement('span');
- el.append(span);
- span.innerText = '・';
- const a2 = document.createElement('a');
- el.append(a2);
- a2.href = url + '.json';
- a2.innerText = 'JSON';
- return el;
- })(document.createElement('p')),
- main,
- );
- const a = document.createElement('a');
- a.href = URL.createObjectURL(
- new Blob([html.outerHTML], {
- type: 'text/html',
- }),
- );
- a.download = decodeURIComponent(thread_slug) + '.html';
- a.click();
- URL.revokeObjectURL(a.href);
- a.remove();
- html.remove();
- }
- Object.assign(window, { backupThread });
- })();