mirror of
https://github.com/nkoehring/250kb-club.git
synced 2025-04-19 21:59:04 +02:00
101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
import {
|
|
parse as tomlParse,
|
|
stringify as tomlStringify,
|
|
} from "https://deno.land/std@0.130.0/encoding/toml.ts";
|
|
|
|
const reFrontmatter = /^\+\+\+([\s\S]*)^\+\+\+$([\s\S]*)/m;
|
|
|
|
export function url2title(url: string): string {
|
|
return url
|
|
.replace(/^https?:\/\//, "") // remove leading http(s)://
|
|
.replace(/\/$/, ""); // remove trailing slash
|
|
}
|
|
|
|
// gets an URL like https://foo.bar and returns ./content/foo_baz.md
|
|
function url2filepath(url: string, output_path: string): string {
|
|
const filename = url2title(url).replaceAll(/[\.\/]/g, "_"); // replace dots and slashes with underscores
|
|
return `${output_path}/${filename}.md`;
|
|
}
|
|
|
|
// deprecated in deno std, but also simple to replicate
|
|
// see: https://deno.land/std@0.130.0/fs/exists.ts
|
|
async function exists(path: string): Promise<boolean> {
|
|
try {
|
|
return !!(await Deno.lstat(path));
|
|
} catch (err) {
|
|
if (err instanceof Deno.errors.NotFound) return false;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// checks if URL has a record already and returns time since last check or null
|
|
export async function getPageRecord(
|
|
url: string,
|
|
output_path: string
|
|
): Promise<PageRecord | null> {
|
|
const path = url2filepath(url, output_path);
|
|
const hasRecord = await exists(path);
|
|
|
|
if (!hasRecord) return null;
|
|
|
|
const fileContents = await Deno.readTextFile(path);
|
|
const match = fileContents.match(reFrontmatter);
|
|
if (!match) return null; // that should never happen but who knows
|
|
return tomlParse(match[1].trim()) as PageRecord;
|
|
}
|
|
|
|
export async function writeRecord(
|
|
record: PageRecord,
|
|
url: string,
|
|
output_path: string
|
|
): Promise<boolean> {
|
|
const path = url2filepath(url, output_path);
|
|
const toml = tomlStringify(record);
|
|
|
|
try {
|
|
await Deno.writeTextFile(path, `+++\n${toml}+++\n`);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function removeRecord(url: string, output_path: string) {
|
|
const path = url2filepath(url, output_path);
|
|
const hasRecord = await exists(path);
|
|
|
|
if (!hasRecord) return false;
|
|
|
|
try {
|
|
await Deno.remove(path);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function delay(ms: number): Promise<unknown> {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
export async function retryFetch(
|
|
url: string,
|
|
retries = 3,
|
|
msDelay = 1000
|
|
): Promise<any> {
|
|
try {
|
|
const response = await fetch(url);
|
|
if (!response.ok) return false;
|
|
const json = await response.json();
|
|
return json;
|
|
} catch (err) {
|
|
if (retries > 0) {
|
|
console.warn(`Failed to fetch ${url}, retrying in ${msDelay}ms.`);
|
|
await delay(msDelay);
|
|
return retryFetch(url, retries - 1, msDelay);
|
|
} else {
|
|
console.error(`Fetching ${url} failed too often. Giving up.`);
|
|
return false;
|
|
}
|
|
}
|
|
}
|