import { createGroup, sendGroupMessage } from '@/modules/signal'; import { getGroupId, register } from '@/modules/store'; import { withAuth } from '@/utils/auth'; import { logError, logVerbose } from '@/utils/log'; const handleNtfyPublish = async (req: Request) => { try { const url = new URL(req.url); const topic = decodeURIComponent(url.pathname.slice(1)); if (!topic || topic.includes('/')) { return new Response('Invalid topic', { status: 400 }); } let message = await req.text(); if (!message) { return new Response('Message required', { status: 400 }); } const contentType = req.headers.get('content-type') || ''; let title = req.headers.get('X-Title') || req.headers.get('Title') || req.headers.get('t'); let androidPackage = req.headers.get('X-Package') || req.headers.get('Package') || req.headers.get('p'); if (contentType.includes('application/x-www-form-urlencoded')) { const params = new URLSearchParams(message); message = params.get('message') || message; title = title || params.get('title') || params.get('t'); androidPackage = androidPackage || params.get('package') || params.get('p'); } const topicKey = `ntfy-${topic}`; let groupId = getGroupId(topicKey); if (!groupId) { groupId = await createGroup(topic); register(topicKey, groupId, topic); logVerbose(`Created new group for ntfy topic: ${topic}`); } await sendGroupMessage(groupId, message, { androidPackage: androidPackage || undefined, title: title || undefined, }); logVerbose(`Sent ntfy message to topic ${topic}: ${title || message.substring(0, 50)}`); return new Response( JSON.stringify({ id: Date.now().toString(), time: Math.floor(Date.now() / 1000), event: 'message', topic, message, }), { status: 200, headers: { 'Content-Type': 'application/json' }, }, ); } catch (error) { logError('Failed to handle ntfy publish:', error); return new Response('Internal server error', { status: 500 }); } }; export const ntfyRoutes = { '/:topic': { POST: withAuth(handleNtfyPublish), }, };