refactor(imessage): share target parsing helpers

This commit is contained in:
Peter Steinberger
2026-02-15 00:56:43 +00:00
parent f835eb32f3
commit 461ead8ceb
4 changed files with 228 additions and 163 deletions

View File

@@ -0,0 +1,132 @@
export type ServicePrefix<TService extends string> = { prefix: string; service: TService };
export type ChatTargetPrefixesParams = {
trimmed: string;
lower: string;
chatIdPrefixes: string[];
chatGuidPrefixes: string[];
chatIdentifierPrefixes: string[];
};
export type ParsedChatTarget =
| { kind: "chat_id"; chatId: number }
| { kind: "chat_guid"; chatGuid: string }
| { kind: "chat_identifier"; chatIdentifier: string };
function stripPrefix(value: string, prefix: string): string {
return value.slice(prefix.length).trim();
}
export function resolveServicePrefixedTarget<TService extends string, TTarget>(params: {
trimmed: string;
lower: string;
servicePrefixes: Array<ServicePrefix<TService>>;
isChatTarget: (remainderLower: string) => boolean;
parseTarget: (remainder: string) => TTarget;
}): ({ kind: "handle"; to: string; service: TService } | TTarget) | null {
for (const { prefix, service } of params.servicePrefixes) {
if (!params.lower.startsWith(prefix)) {
continue;
}
const remainder = stripPrefix(params.trimmed, prefix);
if (!remainder) {
throw new Error(`${prefix} target is required`);
}
const remainderLower = remainder.toLowerCase();
if (params.isChatTarget(remainderLower)) {
return params.parseTarget(remainder);
}
return { kind: "handle", to: remainder, service };
}
return null;
}
export function parseChatTargetPrefixesOrThrow(
params: ChatTargetPrefixesParams,
): ParsedChatTarget | null {
for (const prefix of params.chatIdPrefixes) {
if (params.lower.startsWith(prefix)) {
const value = stripPrefix(params.trimmed, prefix);
const chatId = Number.parseInt(value, 10);
if (!Number.isFinite(chatId)) {
throw new Error(`Invalid chat_id: ${value}`);
}
return { kind: "chat_id", chatId };
}
}
for (const prefix of params.chatGuidPrefixes) {
if (params.lower.startsWith(prefix)) {
const value = stripPrefix(params.trimmed, prefix);
if (!value) {
throw new Error("chat_guid is required");
}
return { kind: "chat_guid", chatGuid: value };
}
}
for (const prefix of params.chatIdentifierPrefixes) {
if (params.lower.startsWith(prefix)) {
const value = stripPrefix(params.trimmed, prefix);
if (!value) {
throw new Error("chat_identifier is required");
}
return { kind: "chat_identifier", chatIdentifier: value };
}
}
return null;
}
export function resolveServicePrefixedAllowTarget<TAllowTarget>(params: {
trimmed: string;
lower: string;
servicePrefixes: Array<{ prefix: string }>;
parseAllowTarget: (remainder: string) => TAllowTarget;
}): (TAllowTarget | { kind: "handle"; handle: string }) | null {
for (const { prefix } of params.servicePrefixes) {
if (!params.lower.startsWith(prefix)) {
continue;
}
const remainder = stripPrefix(params.trimmed, prefix);
if (!remainder) {
return { kind: "handle", handle: "" };
}
return params.parseAllowTarget(remainder);
}
return null;
}
export function parseChatAllowTargetPrefixes(
params: ChatTargetPrefixesParams,
): ParsedChatTarget | null {
for (const prefix of params.chatIdPrefixes) {
if (params.lower.startsWith(prefix)) {
const value = stripPrefix(params.trimmed, prefix);
const chatId = Number.parseInt(value, 10);
if (Number.isFinite(chatId)) {
return { kind: "chat_id", chatId };
}
}
}
for (const prefix of params.chatGuidPrefixes) {
if (params.lower.startsWith(prefix)) {
const value = stripPrefix(params.trimmed, prefix);
if (value) {
return { kind: "chat_guid", chatGuid: value };
}
}
}
for (const prefix of params.chatIdentifierPrefixes) {
if (params.lower.startsWith(prefix)) {
const value = stripPrefix(params.trimmed, prefix);
if (value) {
return { kind: "chat_identifier", chatIdentifier: value };
}
}
}
return null;
}