// src/commands/voiceStatus.js // // Chat + HTTP entry point for /voicestatus. The heavy rendering lives in // services/renderers/phoneStatusRenderer.js so the Jira poller can emit // the same markdown (see services/jiraPollerService.js). This handler // stays thin: parse args, call the collector, hand data to the renderer, // respond. import { randomUUID } from 'node:crypto'; import { collectPhoneStatus } from '../services/phoneService.js'; import { renderPhoneStatusMarkdown } from '../services/renderers/phoneStatusRenderer.js'; import { buildIgmpFixCard } from './igmpFix.js'; import { pendingIgmpFixes } from '../utils/pendingIgmpFixes.js'; import { extractRequester } from '../utils/requester.js'; import { logger } from '../utils/logger.js'; export async function handleVoiceStatus(bot, trigger) { logger('voice:status', 'Handler entered', 'debug'); const query = trigger.query || {}; const args = trigger.args || []; let storeNum = args[0]?.trim() || query.storeNum || query.store || query.s; const isDetailed = argIncludes(args, 'detailed') || (query.mode === 'detailed') || (query.detailed === 'true' || query.detailed === true); if (!storeNum || !/^\d{2,4}$/.test(storeNum)) { const errorMsg = 'Please provide a 2–4 digit store number.\n' + 'Example: `/voicestatus 782` (or `detailed` / `?detailed=true` for more; includes Meraki links) or `https://.../voicestatus?storeNum=782`'; await bot.say('markdown', errorMsg); return; } logger('voice:status', `Collecting voice status for store ${storeNum}`, 'debug'); try { const data = await collectPhoneStatus(storeNum); if (!data) throw new Error('collectPhoneStatus returned undefined'); if (query.format === 'json' || (args[1] && args[1].toLowerCase() === 'json')) { const jsonPayload = { store: storeNum, mainNumber: data.locationMainNumber, timezone: (data.telephonyProfile && data.telephonyProfile.timeZone) || null, person: data.person ? { displayName: data.person.displayName, phoneNumbers: data.person.phoneNumbers } : null, timestamp: new Date().toISOString(), }; await bot.say('markdown', '```json\n' + JSON.stringify(jsonPayload, null, 2) + '\n```'); return; } const reply = renderPhoneStatusMarkdown(data, { storeNum, detailed: isDetailed, footer: true, }); await bot.say('markdown', reply || 'No data available.'); if (data.multicast?.needsFix && data.multicast.networkId && trigger.person) { const cardId = randomUUID(); const requester = extractRequester(trigger); pendingIgmpFixes.set(cardId, { networkId: data.multicast.networkId, networkName: data.multicast.networkName, storeNum, requester, summary: { defaultSnoopOn: data.multicast.defaultSnoopOn, defaultFloodOff: data.multicast.defaultFloodOff, deviatingOverrides: data.multicast.deviatingOverrides || [], }, }); const card = buildIgmpFixCard({ storeNum, networkName: data.multicast.networkName, summary: { defaultSnoopOn: data.multicast.defaultSnoopOn, defaultFloodOff: data.multicast.defaultFloodOff, deviatingOverrides: data.multicast.deviatingOverrides || [], }, cardId, }); await bot.say({ markdown: `Multicast policy on store ${storeNum}'s network deviates from DECT-safe defaults. Review and confirm:`, attachments: [{ contentType: 'application/vnd.microsoft.card.adaptive', content: card, }], }); } } catch (err) { logger('voice:status', `Error collecting voice status for store ${storeNum}: ${err.message}`, 'error'); await bot.say('markdown', `Error collecting voice status: ${err.message}`); } } function argIncludes(args, token) { const needle = String(token).toLowerCase(); return (args || []).some((a) => String(a).toLowerCase() === needle); }