← Public packages
@kentcdodds/grok-bot
Wake a named Grok Bot from Kody by posting a prompt to that bot's own Cursor webhook.
src/plus-alias.ts
185 lines · 5.5 KB · TypeScriptexport const INBOX_DOMAINS = new Set([
'inbox.kody.codes',
'inbox.heykody.app',
'inbox.heykody.dev',
])
export const INBOX_LOCAL = 'kentcdodds'
export const PRIMARY_INBOX_DOMAIN = 'inbox.kody.codes'
export const KENT_FROM_ADDRESSES = new Set([
'kentcdodds@gmail.com',
'kent.c.dodds@gmail.com',
'me@kentcdodds.com',
])
export type PlusAliasHit = {
address: string
tag: string
domain: string
}
export function extractEmail(raw: unknown): string {
if (typeof raw !== 'string') return ''
const trimmed = raw.trim()
if (!trimmed) return ''
const angle = trimmed.match(/<([^>]+)>/)
return (angle?.[1] ?? trimmed).trim().toLowerCase()
}
export function isKentAddress(raw: unknown): boolean {
const address = extractEmail(raw)
return Boolean(address) && KENT_FROM_ADDRESSES.has(address)
}
export function isKentFrom(message: {
from_address?: unknown
envelope_from?: unknown
}): boolean {
return isKentAddress(message.from_address) || isKentAddress(message.envelope_from)
}
function listEmails(value: unknown): string[] {
if (typeof value === 'string') return [extractEmail(value)].filter(Boolean)
if (!Array.isArray(value)) return []
return value.map((item) => extractEmail(item)).filter(Boolean)
}
/** Kent is on this message as a participant, not only as the plus-alias inbox. */
export function kentOnThisMessage(message: {
to_addresses?: unknown
cc_addresses?: unknown
bcc_addresses?: unknown
reply_to_addresses?: unknown
}): boolean {
const addresses = [
...listEmails(message.to_addresses),
...listEmails(message.cc_addresses),
...listEmails(message.bcc_addresses),
...listEmails(message.reply_to_addresses),
]
return addresses.some((address) => KENT_FROM_ADDRESSES.has(address))
}
export function plusAddressFor(tag: string): string {
const normalized = tag.trim().toLowerCase()
return `${INBOX_LOCAL}+${normalized}@${PRIMARY_INBOX_DOMAIN}`
}
export function plusAliasFromAddress(raw: unknown): PlusAliasHit | null {
const email = extractEmail(raw)
const at = email.lastIndexOf('@')
if (at <= 0) return null
const local = email.slice(0, at)
const domain = email.slice(at + 1)
if (!INBOX_DOMAINS.has(domain)) return null
const prefix = `${INBOX_LOCAL}+`
if (!local.startsWith(prefix)) return null
const tag = local.slice(prefix.length).trim()
if (!tag) return null
return { address: email, tag, domain }
}
export function plusAliasesFromAddresses(addresses: unknown): PlusAliasHit[] {
const list = Array.isArray(addresses) ? addresses : []
const hits: PlusAliasHit[] = []
const seen = new Set<string>()
for (const item of list) {
const hit = plusAliasFromAddress(item)
if (!hit) continue
const key = `${hit.tag}@${hit.domain}`
if (seen.has(key)) continue
seen.add(key)
hits.push(hit)
}
return hits
}
export function plusAliasesFromMessage(message: {
to_addresses?: unknown
cc_addresses?: unknown
bcc_addresses?: unknown
}): PlusAliasHit[] {
return plusAliasesFromAddresses([
...(Array.isArray(message.to_addresses) ? message.to_addresses : []),
...(Array.isArray(message.cc_addresses) ? message.cc_addresses : []),
...(Array.isArray(message.bcc_addresses) ? message.bcc_addresses : []),
])
}
export function aliasCandidates(tag: string): string[] {
const normalized = tag.trim().toLowerCase()
if (!normalized) return []
const first = normalized.split('+')[0]?.trim() ?? ''
const unique = [normalized]
if (first && first !== normalized) unique.push(first)
return unique
}
export function threadOpenKey(threadId: string): string {
return `email-thread-open:${threadId}`
}
export function threadRateKey(threadId: string): string {
return `email-wake-rate:${threadId}`
}
export const NON_KENT_WAKE_WINDOW_MS = 10 * 60 * 1000
export const NON_KENT_WAKE_MAX = 3
export const NON_KENT_WAKE_LOCKOUT_MS = 30 * 60 * 1000
export type ThreadRateState = {
times?: string[]
lockedUntil?: string
}
export type ThrottleResult =
| { ok: true; state: ThreadRateState }
| { ok: false; reason: 'ignored-rate-limit'; lockedUntil: string; state: ThreadRateState }
function asRateState(value: unknown): ThreadRateState {
let parsed: unknown = value
if (typeof parsed === 'string') {
try {
parsed = JSON.parse(parsed)
} catch {
return {}
}
}
if (!parsed || typeof parsed !== 'object') return {}
const row = parsed as ThreadRateState & { at?: unknown }
const times = Array.isArray(row.times)
? row.times.filter((item): item is string => typeof item === 'string')
: typeof row.at === 'string'
? [row.at]
: []
return {
times,
lockedUntil: typeof row.lockedUntil === 'string' ? row.lockedUntil : undefined,
}
}
/** Allow a few quick non-Kent follow-ups, then lock the thread out. */
export function consumeNonKentThreadThrottle(
stored: unknown,
now = Date.now(),
): ThrottleResult {
const state = asRateState(stored)
const lockedUntilMs = state.lockedUntil ? Date.parse(state.lockedUntil) : NaN
if (Number.isFinite(lockedUntilMs) && lockedUntilMs > now) {
return { ok: false, reason: 'ignored-rate-limit', lockedUntil: state.lockedUntil as string, state }
}
const cutoff = now - NON_KENT_WAKE_WINDOW_MS
const times = (state.times || []).filter((stamp) => {
const ms = Date.parse(stamp)
return Number.isFinite(ms) && ms > cutoff
})
if (times.length >= NON_KENT_WAKE_MAX) {
const lockedUntil = new Date(now + NON_KENT_WAKE_LOCKOUT_MS).toISOString()
const next = { times, lockedUntil }
return { ok: false, reason: 'ignored-rate-limit', lockedUntil, state: next }
}
times.push(new Date(now).toISOString())
return { ok: true, state: { times, lockedUntil: undefined } }
}