Daily Bluesky bot for AT Mot. Invites players and congratulates yesterday's solvers.
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 3.2 kB View raw
1import { ORIGIN, type Lang, type YesterdayCounts } from './config.js'; 2import { linkFacet, tagFacets, type Facet } from './facets.js'; 3 4export interface ComposeInput { 5 lang: Lang; 6 todayN: number; 7 /** Yesterday's counts, or null on day 1 when there is no previous puzzle. */ 8 yesterday: YesterdayCounts | null; 9} 10 11export interface ComposedPost { 12 text: string; 13 facets: Facet[]; 14 langs: [Lang]; 15} 16 17/** Verbatim in the post text; also the idempotency marker. */ 18export function postMarker(lang: Lang, todayN: number): string { 19 return lang === 'fr' ? `AT Mot n°${todayN}` : `AT Mot #${todayN}`; 20} 21 22function inviteLine(lang: Lang, todayN: number): string { 23 const marker = postMarker(lang, todayN); 24 return lang === 'fr' 25 ? `🟩 ${marker} est en ligne ! Six essais pour deviner le mot de cinq lettres du jour.` 26 : `🟩 ${marker} is live! Six tries to guess today's five-letter word.`; 27} 28 29/** The congrats sentence, or null when there's nothing meaningful to say. */ 30function congratsLine(lang: Lang, c: YesterdayCounts): string | null { 31 const { players, solvers, sampled } = c; 32 if (players == null || players === 0) return null; 33 34 if (solvers === 0) { 35 // An incomplete sample can't prove nobody won — only claim it when we're sure. 36 if (sampled) return null; 37 return lang === 'fr' 38 ? `Personne n'a trouvé le mot d'hier. Nouveau départ aujourd'hui !` 39 : `Nobody cracked yesterday's word. Fresh start today!`; 40 } 41 42 if (sampled) { 43 return lang === 'fr' 44 ? `Bravo aux ${solvers}+ qui ont trouvé le mot d'hier. Saurez-vous les rejoindre aujourd'hui ?` 45 : `Congrats to the ${solvers}+ of you who cracked yesterday's word. Can you join them today?`; 46 } 47 48 const nonSolvers = Math.max(0, players - solvers); 49 50 if (lang === 'fr') { 51 const who = solvers === 1 ? 'au seul joueur' : `aux ${solvers}`; 52 const verb = solvers === 1 ? 'a' : 'ont'; 53 let line = `Bravo ${who} qui ${verb} trouvé le mot d'hier.`; 54 if (nonSolvers > 0) { 55 const ns = nonSolvers === 1 ? 'Et au seul qui a séché' : `Et aux ${nonSolvers} qui ont séché`; 56 line += ` ${ns}, meilleure chance aujourd'hui !`; 57 } 58 return line; 59 } 60 61 const who = solvers === 1 ? 'the one player' : `the ${solvers} of you`; 62 let line = `Congrats to ${who} who cracked yesterday's word.`; 63 if (nonSolvers > 0) { 64 const ns = nonSolvers === 1 ? "And to the one who didn't" : `And to the ${nonSolvers} who didn't`; 65 line += ` ${ns}, better luck today!`; 66 } 67 return line; 68} 69 70export function composePost(input: ComposeInput): ComposedPost { 71 const { lang, todayN, yesterday } = input; 72 const congrats = yesterday ? congratsLine(lang, yesterday) : null; 73 const playLine = lang === 'fr' ? `Jouez : ${ORIGIN}` : `Play: ${ORIGIN}`; 74 const tags = lang === 'fr' ? '#JeuDeMots #atproto' : '#WordGame #atproto'; 75 76 const text = [inviteLine(lang, todayN), congrats, playLine, tags].filter(Boolean).join('\n'); 77 // Homepage link + a #tag facet per hashtag, sorted by position so the byte 78 // ranges appear in document order. 79 const facets = [...linkFacet(text, ORIGIN), ...tagFacets(text)].sort( 80 (a, b) => a.index.byteStart - b.index.byteStart, 81 ); 82 return { text, facets, langs: [lang] }; 83}