// ── The Discord announce leg ─────────────────────────────────────────────── // // CORE content — the Discord bot has no game logic (MODULE_SYSTEM.md §1.10), so // this leg stays in core when module-uo leaves with the town crier. It is written // in the same shape as a module's leg and registered through the same function // (modules/registries.js registerAnnounceLeg), because a registry only core's // hardcoded base bypasses is not exercised until a module arrives. const botInternalClient = require('./botInternalClient') const { articleUrl, baseUrl, legError } = require('../model/announceJobs/announceJobs.logic') // Deliver. Returns the raw client result ({ ok, status, data, error }) — the // client never throws, and classification is `classify`'s job. async function dispatch(post) { const base = baseUrl() // Stored image paths are relative ("/uploads/x.png"); Discord embeds need an // absolute URL. const imageUrl = post.image_url ? new URL(post.image_url, base).toString() : null return botInternalClient.announce({ title: post.title, excerpt: post.excerpt, url: articleUrl(base), imageUrl, }) } // The bot's /internal/announce collapses failures (503 = not connected, // 400 = no news channel configured) without surfacing Discord's own retry_after, // so there is no reliable terminal signal to key on here. Retry every failure on // the shared backoff; a genuine config problem simply exhausts its attempts and // lands as `failed` in the admin panel, where the per-leg retry button re-runs it // after the channel is set. function classify(result) { if (result && result.ok) return { outcome: 'done' } return { outcome: 'retry', error: legError(result) } } const leg = { leg: 'discord', label: 'Discord #news', dispatch, classify, } module.exports = { leg, dispatch, classify }