import { uploadAndReplace } from "./replace.ts"; import { upload } from "../../takker/scrapbox-file-uploader/mod.ts"; import { parse, Node } from "../../takker/scrapbox-parser/mod.ts"; import { Scrapbox } from "../../takker/scrapbox-jp%2Ftypes/userscript.ts"; import { getPage, exportPages, patch, Socket, makeSocket, disconnect } from "../../takker/scrapbox-userscript-std/mod.ts"; declare const scrapbox: Scrapbox; const getTargetedPages = async (project: string): Promise => { const result = await exportPages(scrapbox.Project.name, { metadata: false }); if (!result.ok) { alert(`${result.value.name} ${result.value.message}`); throw Error(result.value.name); } return result.value.pages.flatMap( (page) => page.lines.some((line) => line.includes("pbs.twimg.com/media")) ? [page.title] : [] ); }; const main = async () => { const project = scrapbox.Project.name; // 古いページから更新する const list = (await getTargetedPages(project)).reverse(); let socket: Socket | undefined; try { for (let i = 0; i < list.length; i++) { const title = list[i]; const result = await getPage(project, title); if (!result.ok) continue; const stack: [string, URL[]][] = []; const blocks = parse(result.value.lines.map((line) => line.text).join("\n"), { hasTitle: true }); let lineNo = 0; let total = 0; for (const block of blocks) { switch (block.type) { case "title": lineNo++; break; case "codeBlock": lineNo += block.content.split("\n").length + 1; break; case "table": lineNo += block.cells.length + 1; break; case "line": { const urls = block.nodes.flatMap((node) => getURLs(node)); if (urls.length > 0) { total += urls.length; stack.push([result.value.lines[lineNo].id, urls]); } lineNo++; break; } } } if (total === 0) continue; console.debug(`[${i}/${list.length}] replace ${total} links in "/${project}/${result.value.title}"`); const pairs: [string, string][] = []; let counter = 0; for (const [hash, urls] of stack) { for (const url of urls) { const permalink = await uploadAndReplace(project, result.value.title, hash, url); if (!permalink) continue; console.debug(`[${i}/${list.length}][${counter}/${total}] ${url} => ${permalink}`); pairs.push([`${url}`, permalink]); counter++; } } socket ??= await makeSocket(); await patch(project, result.value.title, (lines) => lines.map((line) => { let text = line.text; for (const [b, a] of pairs) { text = text.replaceAll(b, a); } return text; }), { socket } ); console.debug(`[${i}/${list.length}] replaced links in "/${project}/${result.value.title}"`); } } finally { if (socket) disconnect(socket); } }; const getURLs = (node: Node): URL[] => { switch (node.type) { case "decoration": case "quote": return node.nodes.flatMap((node) => getURLs(node)); case "image": { const url = new URL(node.src); if (url.hostname !== "pbs.twimg.com") return []; if (!url.pathname.startsWith("/media")) return []; return [url]; } default: return []; } }; await main();