// rms.wavelog.cat // // Publishes the station's current frequency/mode to a Wavelog logbook via its // CAT radio API (`POST /index.php/api/radio`). The caller passes the // per-operator Wavelog API key, so each operator's frequency lands in their own // Wavelog account (cat.user_id = key owner). // // Generic by design (public repo): the Wavelog base URL and radio name come from // plugin settings or runtime env, never hardcoded. No secrets are stored here. const ADIF_MODE_BY_STARTMOD = { usb: "SSB", lsb: "SSB", ssb: "SSB", cw: "CW", cwr: "CW", fm: "FM", nfm: "FM", wfm: "FM", am: "AM", sam: "AM", dmr: "DMR", rtty: "RTTY", ft8: "FT8", ft4: "FT4", psk: "PSK", psk31: "PSK" }; function toAdifMode(mode) { const key = String(mode || "").trim().toLowerCase(); if (!key) { return ""; } if (ADIF_MODE_BY_STARTMOD[key]) { return ADIF_MODE_BY_STARTMOD[key]; } // Fall back to the raw value in upper case; Wavelog maps common rig aliases itself. return String(mode).trim().toUpperCase(); } async function createPlugin(ctx) { return { async execute(action, input) { if (action !== "publish") { throw new Error(`Unknown action: ${action}`); } return publish(ctx, input || {}); }, async health() { const baseUrl = resolveBaseUrl(ctx, {}); return { ok: true, configured: Boolean(baseUrl) }; } }; } function resolveBaseUrl(ctx, input) { return String(input.baseUrl || ctx.getSetting("baseUrl", ctx.env.WAVELOG_BASE_URL || "")) .trim() .replace(/\/+$/, ""); } async function publish(ctx, input) { const baseUrl = resolveBaseUrl(ctx, input); const apiKey = String(input.apiKey || "").trim(); const radio = String(input.radioName || ctx.getSetting("radioName", ctx.env.WAVELOG_RADIO_NAME || "RMS Remote")).trim(); const frequencyHz = Math.round(Number(input.frequencyHz)); const mode = toAdifMode(input.mode); if (!baseUrl) { return { ok: false, skipped: true, reason: "wavelog.baseUrl_missing" }; } if (!apiKey) { return { ok: false, skipped: true, reason: "wavelog.apiKey_missing" }; } if (!Number.isFinite(frequencyHz) || frequencyHz <= 0) { return { ok: false, skipped: true, reason: "wavelog.frequency_missing" }; } const payload = { key: apiKey, radio: radio || "RMS Remote", frequency: frequencyHz }; if (mode) { payload.mode = mode; } const timeoutMs = Number(ctx.getSetting("timeoutMs", ctx.env.WAVELOG_TIMEOUT_MS || 8000)); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), Number.isFinite(timeoutMs) ? timeoutMs : 8000); try { const resp = await fetch(`${baseUrl}/index.php/api/radio`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), signal: controller.signal }); const text = await resp.text().catch(() => ""); if (!resp.ok) { // Never surface the API key; only status + a short body excerpt. return { ok: false, status: resp.status, body: String(text).slice(0, 200) }; } return { ok: true, status: resp.status, radio: payload.radio, frequency: frequencyHz, mode: mode || null }; } catch (error) { return { ok: false, error: String(error && error.message ? error.message : error) }; } finally { clearTimeout(timer); } } module.exports = { createPlugin, // exported for unit tests toAdifMode };