Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/morning-briefing

src/sources/inbox.ts

71 lines · 2.2 KB · TypeScript
import { getMessage, searchMessages } from 'kody:@kody/google/gmail'
import { DEFAULT_INBOX_MAX, DEFAULT_INBOX_QUERY, skippedSection } from '../format.ts'
import { googleInboxSetupError } from '../google-setup.ts'
import type { BriefingInput, BriefingItem, BriefingSection } from '../types.ts'

type GmailList = { messages?: Array<{ id?: string; threadId?: string }> }
type GmailHeader = { name?: string; value?: string }
type GmailMessage = {
	id?: string
	payload?: { headers?: GmailHeader[] }
	snippet?: string
}

function header(message: GmailMessage, name: string): string {
	const headers = message.payload?.headers || []
	return (
		headers.find((entry) => String(entry.name || '').toLowerCase() === name.toLowerCase())
			?.value || ''
	)
}

export async function inboxSection(input: BriefingInput): Promise<BriefingSection> {
	const account = input.inbox?.account?.trim() || 'personal'
	const query = input.inbox?.query?.trim() || DEFAULT_INBOX_QUERY
	const max = Math.min(Math.max(Number(input.inbox?.max) || DEFAULT_INBOX_MAX, 1), 20)
	if (!account) {
		return skippedSection(
			'inbox',
			'Pass inbox.account after connecting bring-your-own Google OAuth with Gmail read scopes.',
		)
	}

	try {
		const listed = (await searchMessages({
			account,
			query,
			maxResults: max,
		})) as GmailList
		const ids = (listed.messages || []).map((row) => row.id).filter((id): id is string => Boolean(id))

		const items: BriefingItem[] = []
		for (const id of ids.slice(0, max)) {
			const message = (await getMessage({
				account,
				id,
				format: 'metadata',
				metadataHeaders: ['Subject', 'From', 'Date'],
			})) as GmailMessage
			const subject = header(message, 'Subject') || '(no subject)'
			const from = header(message, 'From')
			items.push({
				title: subject,
				detail: from || message.snippet || undefined,
				label: 'unread',
				severity: 'notice',
			})
		}

		return {
			id: 'inbox',
			title: 'Inbox',
			status: items.length ? 'ok' : 'empty',
			summary: items.length
				? `${items.length} message${items.length === 1 ? '' : 's'} for \`${query}\``
				: `No messages for \`${query}\``,
			items,
		}
	} catch (error) {
		throw googleInboxSetupError(error, account)
	}
}