/** * [Gitea #203] Build and send an inline reply typed into a notification's * text action. Lives outside sw.ts so it can be unit-tested; the SW passes in * its newest known session and the global fetch. */ export type SwReplySession = { accessToken: string; baseUrl: string }; export const buildReplyContent = (text: string, threadId?: string): Record => { const content: Record = { msgtype: 'm.text', body: text, 'm.mentions': {} }; if (threadId) { content['m.relates_to'] = { rel_type: 'm.thread', event_id: threadId, is_falling_back: true, 'm.in_reply_to': { event_id: threadId }, }; } return content; }; export const replySendUrl = (baseUrl: string, roomId: string, txnId: string): string => new URL( `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`, baseUrl, ).href; export async function sendNotificationReply( session: SwReplySession | undefined, roomId: string, threadId: string | undefined, text: string, fetchFn: typeof fetch = fetch, ): Promise { if (!session?.accessToken || !session.baseUrl) return false; const txn = `swreply-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; try { const res = await fetchFn(replySendUrl(session.baseUrl, roomId, txn), { method: 'PUT', headers: { Authorization: `Bearer ${session.accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify(buildReplyContent(text, threadId)), }); return res.ok; } catch { return false; } }