const fetch = require('node-fetch')
const fs = require('fs-extra')
const { basename, join } = require('path')
const progress = (str) => {
process.stdout.clearLine()
process.stdout.cursorTo(0)
process.stdout.write(str)
}
const pad = (str, max) => {
str = str.toString()
return str.length < max ? pad('0' + str, max) : str
}
const fetchLatest = () => {
return fetch(`https://xkcd.com/info.0.json`)
.then(res => res.json())
}
const fetchId = (id) => {
return fetch(`https://xkcd.com/${id}/info.0.json`)
.then(res => res.json())
}
const htmlPage = ({ alt, title, transcript, num, img }) => {
const btnClass = 'dib navy mh2 pa2 bg-light-blue bg-animate hover-bg-lightest-blue br2 ba bw1 b--navy no-underline'
return `
${num} - ${title}
${title} #${num}
${transcript}
`
}
const download = async (baseDir, i) => {
const num = pad(i, 4)
const res = await fetchId(i)
const dir = join(baseDir, num)
try {
await fs.outputJSON(join(dir, 'info.json'), res, { spaces: '\t' })
const img = await fetch(res.img)
const dest = fs.createWriteStream(join(dir, basename(res.img)))
await fs.outputFile(join(dir, 'index.html'), htmlPage(res))
img.body.pipe(dest)
} catch (err) {
await fs.remove(dir)
throw err
}
return {
id: i,
title: res.title,
num: num
}
}
const homePage = (list) => `
XKCD
XKCD
${list.map(({ id, title, num }) => `${id} - ${title}`).join('\n')}
`
const clone = async ({ baseDir, empty, onlyMissing }) => {
console.log(`😊 Going to clone XKCD to ${baseDir}`)
let errored = []
let added = []
try {
console.log(`🔍 Finding the latest comic`)
const latest = (await fetchLatest()).num
console.log(`😁 Found! Will download ${latest} comics 🥶`)
await fs.ensureDir(baseDir)
if (empty) await fs.emptyDir(baseDir)
let existent = []
if (onlyMissing) {
existent = fs.readdirSync(baseDir).map(v => Number(v.split('-')[0].trim()))
}
for (let i = 1; i <= 10 + 1; i++) {
if (existent.includes(i)) {
continue
} else if (i === 404) {
progress(`📦 404 not found 😵`)
continue
} else if (i === latest + 1) {
progress(`📦 All ${latest} comics fetched\n`)
continue
} else {
progress(`📦 Fetching ${i} out of ${latest}`)
}
try {
added.push(await download(baseDir, i))
} catch (err) {
errored.push(i)
}
}
} catch (err) {
console.log(`🐉 ${err.stack}`)
process.exit(1)
}
for (const num of errored) {
for (let i = 0; i < 3; i++) {
try {
added.push(await download(baseDir, i))
break
} catch (err) {
if (i === 2) {
console.log(`😢 ${num} could not be fetched: ${err.toString()}`)
}
}
}
}
added = added.sort((a, b) => a.num - b.num)
await fs.outputFile(join(baseDir, 'index.html'), homePage(added))
}
clone({
baseDir: './clone',
empty: true,
onlyMissing: false
})