invite/src/lib/smsReply.ts
chris 2c78112a4f Initial commit: E-Invite, a self-hosted Evite-lite
Docker-ready Next.js + Prisma/Postgres e-invitation app: themed invite
pages, RSVP, comments, share links, email + SMS invites with
reply-to-RSVP-by-text, co-hosts, photo dropbox, and browser push
notifications.
2026-07-11 10:11:12 -04:00

34 lines
1.3 KiB
TypeScript

import type { RsvpStatus } from '@prisma/client';
export type ParsedSmsRsvpReply = { status: RsvpStatus; numGuests: number };
const YES_WORDS = new Set(['yes', 'y', 'yep', 'yeah', 'yup', 'ya']);
const NO_WORDS = new Set(['no', 'n', 'nope', 'nah']);
const MAYBE_WORDS = new Set(['maybe', 'perhaps', 'unsure', 'tentative', 'possibly']);
/**
* Parses a guest's free-form SMS reply into an RSVP status plus an optional
* guest count (e.g. "YES 3", "no", "Maybe, 2 people"). Only the first word is
* used to determine status - guests reply in all kinds of phrasing, but the
* status word almost always leads. Returns null when no recognized status
* word is found, so the caller can ask the guest to try again.
*/
export function parseSmsRsvpReply(rawBody: string): ParsedSmsRsvpReply | null {
const body = rawBody.trim();
if (!body) return null;
const firstWord = body.toLowerCase().match(/[a-z']+/)?.[0];
if (!firstWord) return null;
let status: RsvpStatus;
if (YES_WORDS.has(firstWord)) status = 'YES';
else if (NO_WORDS.has(firstWord)) status = 'NO';
else if (MAYBE_WORDS.has(firstWord)) status = 'MAYBE';
else return null;
const countMatch = body.match(/\d+/);
const numGuests = countMatch ? Math.min(Math.max(parseInt(countMatch[0], 10), 1), 50) : 1;
return { status, numGuests };
}