import { GetParametersByPathCommand, SSMClient } from "@aws-sdk/client-ssm"; import { coverArtAlt, type BlueskyCredentials } from "./bluesky"; import { getCoverArt, type NavidromeCredentials, type Track } from "./navidrome"; const ssm = new SSMClient({}); export function requireEnv(name: string): string { const value = process.env[name]; if (!value) throw new Error(`Missing environment variable ${name}`); return value; } async function loadSecrets(path: string): Promise> { const { Parameters = [] } = await ssm.send( new GetParametersByPathCommand({ Path: path, Recursive: true, WithDecryption: true }), ); const secrets = Object.fromEntries( Parameters.map((parameter) => [parameter.Name!.slice(path.length), parameter.Value!]), ); for (const name of ["navidrome/username", "navidrome/password", "bluesky/identifier", "bluesky/app-password"]) { if (!secrets[name]) throw new Error(`Missing SSM parameter ${path}${name}`); } return secrets; } export async function loadCredentials(): Promise<{ navidrome: NavidromeCredentials; bluesky: BlueskyCredentials; }> { const secrets = await loadSecrets(requireEnv("SECRETS_PATH")); return { navidrome: { url: requireEnv("NAVIDROME_URL"), username: secrets["navidrome/username"], password: secrets["navidrome/password"], }, bluesky: { serviceUrl: requireEnv("BLUESKY_SERVICE_URL"), identifier: secrets["bluesky/identifier"], appPassword: secrets["bluesky/app-password"], }, }; } /** Every post carries cover art, so art that can't fit Bluesky's limit fails the attempt. */ export async function loadImage(navidrome: NavidromeCredentials, track: Track) { const art = await getCoverArt(navidrome, track.coverArt); if (!art) throw new Error(`Cover art for track ${track.id} is too large at every size`); return { ...art, alt: coverArtAlt(track) }; }