Something went wrong. Try again.
ranger bot. Selections from music archive.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103import { formatPost, publishPost, type BlueskyCredentials } from "./bluesky";import { claimTrack, recordPostUri, releaseTrack } from "./history";import { getRandomTracks, trackKey, type NavidromeCredentials } from "./navidrome";import { beginAttempt, markFailed, markPosted } from "./runs";import { easternDate, isPostingDay } from "./schedule";import { loadCredentials, loadImage, requireEnv } from "./shared";
const SAMPLE_SIZE = 50;const MAX_SAMPLES = 3;
/** * The claim is written before publishing, so a crash can only cost a track its turn. * The reverse order could post the same track twice on the next attempt. */async function postRandomTrack(navidrome: NavidromeCredentials, bluesky: BlueskyCredentials) { const table = requireEnv("HISTORY_TABLE"); const cooldownDays = Number(requireEnv("COOLDOWN_DAYS"));
for (let sample = 0; sample < MAX_SAMPLES; sample++) { for (const track of await getRandomTracks(navidrome, SAMPLE_SIZE)) { const key = trackKey(track); if (!(await claimTrack(table, key, track.title, track.artist, cooldownDays))) continue;
const text = formatPost(track); let uri: string; try { uri = await publishPost(bluesky, text, await loadImage(navidrome, track)); } catch (error) { await releaseTrack(table, key); throw error; }
// The post is live, so nothing after this point may count as a failed attempt. await recordPostUri(table, key, uri).catch((error) => console.error("Posted, but could not record the post URI", error), ); console.log(JSON.stringify({ posted: key, uri })); return { uri, text }; } }
throw new Error(`No eligible track found in ${MAX_SAMPLES} samples of ${SAMPLE_SIZE}`);}
/** Shows a sample post without publishing or recording anything. */async function dryRun() { const { navidrome } = await loadCredentials(); const [track] = await getRandomTracks(navidrome, 5); if (!track) throw new Error("Navidrome returned no usable track"); const image = await loadImage(navidrome, track); const result = { dryRun: true, text: formatPost(track), coverArtBytes: image.bytes.length }; console.log(JSON.stringify(result)); return result;}
/** * Fires hourly through the retry window every day, and only acts on a posting day. * * {"dryRun": true} shows a sample post. {"force": true} posts now, ignoring the * schedule and the day's attempts, though the track cooldown still applies. */export async function handler(event: { dryRun?: boolean; force?: boolean } = {}) { if (event.dryRun) return dryRun(); if (event.force) { const { navidrome, bluesky } = await loadCredentials(); return postRandomTrack(navidrome, bluesky); }
const now = new Date(); const runDate = easternDate(now); const interval = Number(requireEnv("POST_INTERVAL_DAYS")); if (!isPostingDay(runDate, requireEnv("FIRST_POST_DATE"), interval)) { return { skipped: "not a posting day", runDate }; }
const table = requireEnv("RUNS_TABLE"); const maxAttempts = Number(requireEnv("MAX_ATTEMPTS")); const attempt = await beginAttempt(table, runDate, maxAttempts, now); if (attempt === null) { console.log(JSON.stringify({ skipped: "no attempt due", runDate })); return { skipped: "no attempt due", runDate }; }
let result: { uri: string; text: string }; try { const { navidrome, bluesky } = await loadCredentials(); result = await postRandomTrack(navidrome, bluesky); } catch (error) { const message = error instanceof Error ? error.message : String(error); const status = attempt >= maxAttempts ? "abandoned" : "failed"; console.error(JSON.stringify({ runDate, attempt, maxAttempts, status, message })); await markFailed(table, runDate, status, message).catch((writeError) => console.error("Could not record the failed attempt", writeError), ); throw error; }
await markPosted(table, runDate, result.uri, result.text).catch((error) => console.error("Posted, but could not mark the run as posted", error), ); return { ...result, runDate, attempt };}