Something went wrong. Try again.
ranger bot. Selections from music archive.
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677import { createPost, formatPost, listMentions, login, type Mention } from "./bluesky";import { getRandomTracks } from "./navidrome";import { claimReply, recordReplyUri, releaseReply } from "./replies";import { loadCredentials, loadImage, requireEnv } from "./shared";
const DAY_MS = 86_400_000;/** Far enough back that a ping survives a long outage. */const LOOKBACK_DAYS = 3;/** Limits the damage from a burst of new accounts. Any extra people wait for the next run. */const MAX_REPLIES_PER_RUN = 20;/** A few tracks, in case some are dropped for missing details or cover art. */const SAMPLE_SIZE = 5;
/** Takes oldest-first mentions and keeps each person's newest, since they get one reply. */function newestPerPerson(mentions: Mention[]): Mention[] { const newest = new Map<string, Mention>(); for (const mention of mentions) { newest.delete(mention.authorDid); newest.set(mention.authorDid, mention); } return [...newest.values()];}
/** * Replies to new pings with a random track, once per person every 6 hours. * These tracks skip the post history, so pings can't use up the scheduled posts' choices. */export async function handler() { const table = requireEnv("REPLIES_TABLE"); const { navidrome, bluesky } = await loadCredentials(); const session = await login(bluesky); const mentions = await listMentions(session, new Date(Date.now() - LOOKBACK_DAYS * DAY_MS)); const people = newestPerPerson(mentions);
let replied = 0; let failures = 0; for (const mention of people) { if (replied >= MAX_REPLIES_PER_RUN) break; const claimed = await claimReply( table, mention.authorDid, mention.authorHandle, mention.parent.uri, mention.indexedAt, ); if (!claimed) continue;
let uri: string; try { const [track] = await getRandomTracks(navidrome, SAMPLE_SIZE); if (!track) throw new Error("Navidrome returned no usable track"); uri = await createPost(session, formatPost(track), await loadImage(navidrome, track), { root: mention.root, parent: mention.parent, }); } catch (error) { // Release the claim so the next run can try again, and keep going for everyone else. failures++; console.error(`Could not reply to ${mention.parent.uri}`, error); await releaseReply(table, mention.authorDid).catch((releaseError) => console.error("Could not release the reply claim", releaseError), ); continue; }
replied++; await recordReplyUri(table, mention.authorDid, uri).catch((error) => console.error("Replied, but could not record the reply URI", error), ); }
const summary = { mentions: mentions.length, people: people.length, replied, failures }; console.log(JSON.stringify(summary)); if (failures > 0) throw new Error(`${failures} of ${people.length} replies failed`); return summary;}