mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 14:30:22 +00:00
224 lines
6.8 KiB
TypeScript
224 lines
6.8 KiB
TypeScript
import { isAllowedParsedChatSender } from "../../../src/plugin-sdk/allow-from.js";
|
|
|
|
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 };
|
|
|
|
export type ParsedChatAllowTarget = ParsedChatTarget | { kind: "handle"; handle: string };
|
|
|
|
export type ChatSenderAllowParams = {
|
|
allowFrom: Array<string | number>;
|
|
sender: string;
|
|
chatId?: number | null;
|
|
chatGuid?: string | null;
|
|
chatIdentifier?: string | null;
|
|
};
|
|
|
|
function stripPrefix(value: string, prefix: string): string {
|
|
return value.slice(prefix.length).trim();
|
|
}
|
|
|
|
function startsWithAnyPrefix(value: string, prefixes: readonly string[]): boolean {
|
|
return prefixes.some((prefix) => value.startsWith(prefix));
|
|
}
|
|
|
|
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 resolveServicePrefixedChatTarget<TService extends string, TTarget>(params: {
|
|
trimmed: string;
|
|
lower: string;
|
|
servicePrefixes: Array<ServicePrefix<TService>>;
|
|
chatIdPrefixes: string[];
|
|
chatGuidPrefixes: string[];
|
|
chatIdentifierPrefixes: string[];
|
|
extraChatPrefixes?: string[];
|
|
parseTarget: (remainder: string) => TTarget;
|
|
}): ({ kind: "handle"; to: string; service: TService } | TTarget) | null {
|
|
const chatPrefixes = [
|
|
...params.chatIdPrefixes,
|
|
...params.chatGuidPrefixes,
|
|
...params.chatIdentifierPrefixes,
|
|
...(params.extraChatPrefixes ?? []),
|
|
];
|
|
return resolveServicePrefixedTarget({
|
|
trimmed: params.trimmed,
|
|
lower: params.lower,
|
|
servicePrefixes: params.servicePrefixes,
|
|
isChatTarget: (remainderLower) => startsWithAnyPrefix(remainderLower, chatPrefixes),
|
|
parseTarget: params.parseTarget,
|
|
});
|
|
}
|
|
|
|
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 resolveServicePrefixedOrChatAllowTarget<
|
|
TAllowTarget extends ParsedChatAllowTarget,
|
|
>(params: {
|
|
trimmed: string;
|
|
lower: string;
|
|
servicePrefixes: Array<{ prefix: string }>;
|
|
parseAllowTarget: (remainder: string) => TAllowTarget;
|
|
chatIdPrefixes: string[];
|
|
chatGuidPrefixes: string[];
|
|
chatIdentifierPrefixes: string[];
|
|
}): TAllowTarget | null {
|
|
const servicePrefixed = resolveServicePrefixedAllowTarget({
|
|
trimmed: params.trimmed,
|
|
lower: params.lower,
|
|
servicePrefixes: params.servicePrefixes,
|
|
parseAllowTarget: params.parseAllowTarget,
|
|
});
|
|
if (servicePrefixed) {
|
|
return servicePrefixed as TAllowTarget;
|
|
}
|
|
|
|
const chatTarget = parseChatAllowTargetPrefixes({
|
|
trimmed: params.trimmed,
|
|
lower: params.lower,
|
|
chatIdPrefixes: params.chatIdPrefixes,
|
|
chatGuidPrefixes: params.chatGuidPrefixes,
|
|
chatIdentifierPrefixes: params.chatIdentifierPrefixes,
|
|
});
|
|
if (chatTarget) {
|
|
return chatTarget as TAllowTarget;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function createAllowedChatSenderMatcher<TParsed extends ParsedChatAllowTarget>(params: {
|
|
normalizeSender: (sender: string) => string;
|
|
parseAllowTarget: (entry: string) => TParsed;
|
|
}): (input: ChatSenderAllowParams) => boolean {
|
|
return (input) =>
|
|
isAllowedParsedChatSender({
|
|
allowFrom: input.allowFrom,
|
|
sender: input.sender,
|
|
chatId: input.chatId,
|
|
chatGuid: input.chatGuid,
|
|
chatIdentifier: input.chatIdentifier,
|
|
normalizeSender: params.normalizeSender,
|
|
parseAllowTarget: params.parseAllowTarget,
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|