Skip to content
← Public packages

@kentcdodds/exchange-threads

Open kody.exchange threads from Kody. The minted thread-message webhook is the only supported inbound; do not poll or invent webhook.site.

src/reply.ts

65 lines · 1.9 KB · TypeScript
import {
	connectUrl,
	exchangeFetch,
	getThread,
	readString,
	store,
	upsertThread,
} from './shared.ts'
import { parseReplyInput } from './reply-input.ts'

/**
 * Send a message on a thread you own (OAuth) or a registered guest thread (token).
 *
 * Maps threadId aliases (`thread_id` / `thread` / `id`) and body aliases
 * (`message` / `text` / `content`). Unknown keys are rejected. Pass
 * `dryRun: true` to preview without sending.
 *
 * @example
 * import reply from 'kody:@kentcdodds/exchange-threads/reply'
 * const preview = await reply({ threadId: 'th_1', body: 'ack', dryRun: true })
 * // => { dryRun: true, threadId: 'th_1', body: 'ack', requiresConfirm: false }
 * const result = await reply({ threadId: 'th_1', body: 'ack' })
 * // => { ok: true, threadId: 'th_1' }
 */
export default async function reply(params: Record<string, unknown> = {}) {
	const input = parseReplyInput(params)
	const threadId = input.threadId
	if (input.dryRun) {
		return {
			dryRun: true,
			action: 'reply',
			threadId,
			body: input.body,
			hasToken: Boolean(input.token),
			note: 'dryRun only — re-call without dryRun to send. This export does not require confirm:true.',
		}
	}
	const bucket = await store()
	const stored = await getThread(bucket, threadId)
	const token = readString(input.token) ?? readString(stored?.token) ?? undefined
	const response = await exchangeFetch({
		path: token ? '/v1/messages' : `/api/threads/${threadId}/messages`,
		method: 'POST',
		body: { body: input.body },
		token,
	})
	if (!response.ok) {
		return {
			ok: false,
			status: response.status,
			error: response.json,
			connectUrl: token ? undefined : connectUrl(),
			threadId,
		}
	}
	if (token) {
		await upsertThread(bucket, {
			id: threadId,
			token,
			registeredAt: stored?.registeredAt ?? new Date().toISOString(),
			lastAt: new Date().toISOString(),
		})
	}
	return { ok: true, threadId }
}