pagenotfound-cli/cli.mjs

142 lines
4.1 KiB
JavaScript
Raw Normal View History

2024-06-26 14:57:18 +02:00
#!/usr/bin/env -S npx nodejsscript
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */
const url_drops= "https://pagenotfound.cz/drop/";
2024-06-26 15:52:59 +02:00
const { version, description }= s.cat("package.json").xargs(JSON.parse);
2024-06-26 14:57:18 +02:00
/**
* @typedef {Object} Article
* @property {string} title
* @property {string} perex
* @property {string} author
* @property {string} loc
* @property {string} drop
* */
/**
* @typedef {Object} Drop
* @property {string} drop
* @property {string} date
* */
/**
* @typedef {Object} Sitemap
* @property {Article[]} articles
* @property {Drop[]} drops
* */
/**
* @typedef {Object} State
* @property {Sitemap} json
* @property {string[]} changed Changed files
* */
2024-06-26 14:57:18 +02:00
$.api()
.version(version)
.describe(description)
.command("pull", "Update article list")
2024-06-27 10:52:51 +02:00
.option("--git", "Update git repository")
.action(async function pull({ git: is_git= false }){
const { changed }= await sitemap().then(toRSS);
echo("Changed files:", changed.length ? changed.join(", ") : "—");
if(is_git) gitCommit(changed, "pull");
2024-06-26 14:57:18 +02:00
$.exit(0);
})
.parse();
2024-06-27 10:52:51 +02:00
function gitCommit(files, des= "not specified"){
if(!files.length || !s.run`git diff --numstat`.trim())
2024-06-27 10:52:51 +02:00
return echo("Nothig todo");
echo("Diff to save");
s.run`git config user.name "Bot"`;
s.run`git config user.email "${"zc.murtnec@naj.elrdna".split("").reverse().join("")}"`;
s.run`git add ${files}`;
s.run`git commit -m "Updated by bot ${des}"`;
s.run`git push`;
s.run`git config --remove-section user`;
}
/**
* @param {State} state
* @returns {State} state
* */
async function toRSS({ json, changed }){
if(!changed.length) return { json, changed };
const path= "rss.xml";
2024-06-26 14:57:18 +02:00
const host= "https://pagenotfound.cz";
const articles= json.articles.map(function({ title, perex, author, loc, drop }){
return [
"<item>",
...[
`<title>${title}</title>`,
`<link>${host+loc}</link>`,
`<description>${perex}</description>`,
`<author>${author}</author>`,
`<pubDate>${json.drops.find(d=> d.drop === drop).date}</pubDate>`,
`<category>${drop}</category>`,
].map(l=> "\t"+l),
"</item>"
].map(l=> "\t"+l).join("\n");
});
s.echo([
`<?xml version="1.0" encoding="UTF-8" ?>`,
`<rss version="2.0">`,
"<channel>",
` <title>Pagenotfound.cz</title>`,
` <link>${host}</link>`,
...articles,
"</channel>",
"</rss>"
].join("\n")).to(path);
return { json, changed: [...changed, path] };
2024-06-26 14:57:18 +02:00
}
2024-06-26 15:52:59 +02:00
import { JSDOM } from "jsdom";
/** @returns {Promise<State>} */
2024-06-26 14:57:18 +02:00
async function sitemap(){
const path= "sitemap.json";
2024-06-26 14:57:18 +02:00
/** @type {Sitemap} */
const json= s.test("-f", path) ? s.cat(path).xargs(JSON.parse) : { drops: [], articles: [] };
await syncDrops(json);
const [ { drop: drop_last } ]= json.drops;
const [ article_last= { drop: "" } ]= json.articles;
if(drop_last === article_last.drop) return { json, changed: [] };
2024-06-26 14:57:18 +02:00
const res= await fetch(url_drops+drop_last);
if(res.status !== 200) return { json, changed: [] };
2024-06-26 14:57:18 +02:00
const dom= new JSDOM(await res.text());
const diff= [];
for(const article of dom.window.document.querySelectorAll("article")){
diff.push({
title: article.querySelector("h2").textContent.trim(),
perex: article.querySelector("[class^=ArticleTile_perex]").textContent.trim(),
author: article.querySelector("[class^=ArticleTile_author]").textContent.trim(),
loc: article.querySelector("a").href,
drop: drop_last,
});
}
json.articles.unshift(...diff);
s.echo(JSON.stringify(json, null, "\t")).to(path);
return { json, changed: [ path ] };
2024-06-26 14:57:18 +02:00
}
/** @param {Sitemap} json */
async function syncDrops(json){
const [ { drop: drop_last } ]= json.drops;
const i_index= drop_last.search(/\d/);
const pre= drop_last.slice(0, i_index);
const index= pipe(
Number,
i=> i+1,
i=> i.toString().padStart(drop_last.length - i_index, "0"),
)(drop_last.slice(i_index));
const drop= pre+index;
const res= await fetch(url_drops+drop, { method: "HEAD" });
if(res.status !== 200) return json;
const date= pipe(
d=> new Date(d),
d=> d.toISOString(),
)(res.headers.get("date"));
json.drops.unshift({ drop, date });
return json;
}