mirror of
https://github.com/tiennm99/miti99.git
synced 2026-09-19 10:20:40 +00:00
Translates the seven-subcommand engine to Node ESM, one module per former Go file, invoked as `node scripts/newsletter <command>` from the repo root. The Go implementation stays in place for now so parity can be measured against it. Hand-rolled HTML and XML regexes give way to cheerio, which removes the manual string surgery in caption extraction and covers RSS and sitemap XML through xmlMode without a second parser. fetch-via-defuddle gains a local extraction stage ahead of the defuddle.md proxy; the proxy stays, because fetching from a third IP is the whole point when this machine's IP is the blocked one. Both stages now emit the same YAML frontmatter plus body, and an extraction that looks like a bot challenge counts as a local failure so the proxy still runs. Behaviour is preserved where it is load-bearing rather than where it is merely idiomatic: query strings are rebuilt by string surgery so surviving parameters keep their original order and encoding, duplicate detection stays an index loop with byte-offset boundary checks so a prefix of a stored URL is not a false match, empty optional fields are omitted rather than emitted as "", a deep-crawl miss reports cutoff as null rather than dropping the key, bullet filtering counts code points, tag counts keep their six-column alignment, and a malformed percent sequence falls back to the raw substring. The printer lives in its own module so a command module never imports the dispatcher: index.js runs main() at module scope, so that cycle would execute the CLI as a side effect of any import. Missing dependencies report one actionable line instead of a module-resolution stack trace, and a reader that closes early exits quietly instead of raising EPIPE.
67 lines
2.1 KiB
JavaScript
67 lines
2.1 KiB
JavaScript
// Detect whether an image URL is Substack-hosted and extract its S3 image UUID.
|
|
// Usage: node scripts/newsletter detect-image-source "<image-url>"
|
|
// Output: JSON { original_url, clean_url, isSubstack, uuid?, innerUrl? }
|
|
//
|
|
// Substack images are usually served via a CDN wrapper:
|
|
//
|
|
// https://substackcdn.com/image/fetch/$s_!x!,.../https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F<uuid>_WxH.png
|
|
//
|
|
// The publication is NOT encoded in the URL — only the image identity (uuid) is.
|
|
|
|
import { printJson } from "./json-out.js";
|
|
import { cleanUrl, isSubstackImage, substackImageUuid } from "./url-utils.js";
|
|
|
|
/**
|
|
* extractInnerUrl pulls the inner S3 URL out of a substackcdn /image/fetch/
|
|
* wrapper (if present). A malformed percent sequence falls back to the raw
|
|
* substring rather than throwing.
|
|
* @param {string} target
|
|
* @returns {string}
|
|
*/
|
|
export function extractInnerUrl(target) {
|
|
const marker = target.indexOf("/https%3A%2F%2F");
|
|
if (marker !== -1) {
|
|
const raw = target.slice(marker + 1);
|
|
try {
|
|
return decodeURIComponent(raw);
|
|
} catch {
|
|
return raw;
|
|
}
|
|
}
|
|
// Some forms embed a plain (already-decoded) inner https URL.
|
|
if (target.length > 8) {
|
|
const plain = target.slice(8).indexOf("/https://");
|
|
if (plain !== -1) return target.slice(8 + plain + 1);
|
|
}
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* @param {string[]} args
|
|
* @returns {Promise<void>}
|
|
*/
|
|
export async function runDetectImageSource(args) {
|
|
if (args.length < 1 || args[0] === "") {
|
|
process.stderr.write("Usage: node scripts/newsletter detect-image-source <image-url>\n");
|
|
process.exit(1);
|
|
}
|
|
const target = args[0];
|
|
const isSubstack = isSubstackImage(target);
|
|
|
|
// Empty optional fields are omitted, not emitted as "": the skills branch on
|
|
// the key being present.
|
|
/** @type {Record<string, unknown>} */
|
|
const out = {
|
|
original_url: target,
|
|
clean_url: cleanUrl(target),
|
|
isSubstack,
|
|
};
|
|
if (isSubstack) {
|
|
const uuid = substackImageUuid(target);
|
|
const innerUrl = extractInnerUrl(target);
|
|
if (uuid !== "") out.uuid = uuid;
|
|
if (innerUrl !== "") out.innerUrl = innerUrl;
|
|
}
|
|
printJson(out);
|
|
}
|