import fetch, {Response} from 'node-fetch'; import process from 'process'; import fs from 'fs'; type Post = { no: number; com?: string; tim?: number; filename?: string; ext?: string; } type Thread = { posts: Post[]; } type ThreadId = { board: string; id: number; } type ImageId = { board: string; tim: number; ext: string; } type ApiResource = { url: string; map: (response: Response) => Promise; } const mapJson = () => async (response: Response) => await response.json() as T; const mapBuffer = () => async (response: Response) => await response.buffer(); const apiFetch = async ({url, map}: ApiResource) => { const response = await fetch(url); return await map(response); }; const endpoints = { getThread: ({board, id}: ThreadId) => ({ url: `https://a.4cdn.org/${board}/thread/${id}.json`, map: mapJson(), }), getImage: ({board, tim, ext}: ImageId) => ({ url: `https://i.4cdn.org/${board}/${tim}${ext}`, map: mapBuffer(), }) }; const writeFile = (path: string, data: any) => new Promise((resolve, reject) => { fs.writeFile(path, data, (err) => { if (err) { reject(err); } resolve(); }); }); const saveThread = async (path: string, threadId: ThreadId, thread: Thread) => await writeFile(`${path}/${thread.posts[0].no}.json`, JSON.stringify(thread, null,2)); const saveImage = async (path: string, {tim, ext}: ImageId, image: Buffer) => await writeFile(`${path}/${tim}${ext}`, image); const saveImages = async (path: string, threadId: ThreadId, thread: Thread) => Promise.all(thread.posts.map(async ({tim, ext}) => { if (tim === undefined || ext === undefined) { return; } const id = {board: threadId.board, tim, ext}; await saveImage(path, id, await apiFetch(endpoints.getImage(id))); })); const archiveThread = async (path: string, threadId: ThreadId) => { const thread = await apiFetch(endpoints.getThread(threadId)); await saveThread(path, threadId, thread); await saveImages(path, threadId, thread); console.log(path, thread.posts.length); }; const main = () => { if (process.argv.length < 4) { console.log('usage: imb4 [board] [id]'); return; } const target = {board: process.argv[2], id: parseInt(process.argv[3])}; const path = `${target.board}/${target.id}`; fs.mkdirSync(path, {recursive: true}); archiveThread(path, target).then(console.log); }; main();