xkcd/bin/index.js
2022-08-13 20:58:41 +02:00

135 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
import fs from 'fs-extra'
import yargs from 'yargs'
import * as url from 'url'
import { hideBin } from 'yargs/helpers'
import { basename, extname, join } from 'path'
import { getLatestId, getComic } from '../lib/xkcd.js'
import { homePage, comicPage } from '../lib/html.js'
import { pad, progress } from '../lib/helpers.js'
import { createRequire } from 'module'
const argv = yargs(hideBin(process.argv))
.usage('$0', 'Clones XKCD comics. By default it only downloads the missing comics.')
.scriptName('xkcd-clone')
.option('dir', {
alias: 'd',
describe: 'Output directory',
type: 'string',
demandOption: true
}).option('empty', {
alias: 'e',
describe: 'Redownload all comics',
type: 'boolean'
})
.help()
.argv
async function write ({ data, img }, dir, latest) {
const hasImage = img !== null
try {
await fs.outputJSON(join(dir, 'info.json'), data, { spaces: '\t' })
await fs.outputFile(join(dir, 'index.html'), comicPage(data, latest, hasImage))
if (hasImage) {
await fs.outputFile(join(dir, basename(data.img)), Buffer.from(img))
await fs.outputFile(join(dir, `image${extname(data.img)}`), Buffer.from(img))
}
} catch (err) {
await fs.remove(dir)
throw err
}
}
async function run () {
console.log(`😊 Going to clone XKCD to ${argv.dir}`)
let added = []
const errored = []
let latest = null
try {
console.log('🔍 Finding the latest comic')
latest = await getLatestId()
console.log(`😁 Found! We're on comic number ${latest}!`)
await fs.ensureDir(argv.dir)
if (argv.empty) {
await fs.emptyDir(argv.dir)
}
for (let i = 1; i <= latest; i++) {
const num = pad(i, 4)
const dir = join(argv.dir, num)
progress(`📦 Fetching ${i} out of ${latest}`)
if (await fs.pathExists(dir)) {
const data = await fs.readJSON(join(dir, 'info.json'))
added.push({ id: i, title: data.title, num })
await fs.outputFile(join(dir, 'index.html'), comicPage(data, latest))
continue
} else if (i === 404) {
continue
}
let comic = null
const info = {
id: i,
dir: dir,
num: num
}
try {
comic = await getComic(i)
info.title = comic.data.title
await write(comic, dir, latest)
added.push(info)
} catch (err) {
progress(`😢 Could not fetch ${i}, will try again later\n`)
errored.push(info)
}
}
} catch (err) {
console.log(`🐉 ${err.stack}`)
process.exit(1)
}
for (const info of errored) {
const { id, dir, num } = info
for (let i = 0; i < 3; i++) {
try {
const comic = await getComic(id)
await write(comic, dir, latest)
added.push(info)
break
} catch (err) {
if (i === 2) {
console.log(`😢 ${num} could not be fetched: ${err.toString()}`)
}
}
}
}
if (errored.length === 0) {
progress('📦 All comics fetched\n')
} else {
progress('📦 Some comics fetched\n')
}
const require = createRequire(import.meta.url)
added = added.sort((a, b) => a.num - b.num)
await fs.remove(join(argv.dir, 'latest'))
await fs.copy(join(argv.dir, pad(latest, 4)), join(argv.dir, 'latest'))
await fs.copyFile(join(require.resolve('tachyons'), '../tachyons.min.css'), join(argv.dir, 'tachyons.css'))
await fs.copyFile(join(require.resolve('tachyons-columns'), '../../css/tachyons-columns.min.css'), join(argv.dir, 'tachyons-columns.css'))
await fs.outputFile(join(argv.dir, 'index.html'), homePage(added))
}
run()