mirror of https://github.com/openclaw/openclaw.git
169 lines
4.6 KiB
TypeScript
169 lines
4.6 KiB
TypeScript
export const FEISHU_CARD_INTERACTION_VERSION = "ocf1";
|
|
|
|
export type FeishuCardInteractionKind = "button" | "quick" | "meta";
|
|
export type FeishuCardInteractionReason =
|
|
| "malformed"
|
|
| "stale"
|
|
| "wrong_user"
|
|
| "wrong_conversation";
|
|
|
|
export type FeishuCardInteractionMetadata = Record<
|
|
string,
|
|
string | number | boolean | null | undefined
|
|
>;
|
|
|
|
export type FeishuCardInteractionEnvelope = {
|
|
oc: typeof FEISHU_CARD_INTERACTION_VERSION;
|
|
k: FeishuCardInteractionKind;
|
|
a: string;
|
|
q?: string;
|
|
m?: FeishuCardInteractionMetadata;
|
|
c?: {
|
|
u?: string;
|
|
h?: string;
|
|
s?: string;
|
|
e?: number;
|
|
t?: "p2p" | "group";
|
|
};
|
|
};
|
|
|
|
export type FeishuCardActionEventLike = {
|
|
operator: {
|
|
open_id?: string;
|
|
};
|
|
action: {
|
|
value: unknown;
|
|
};
|
|
context: {
|
|
chat_id?: string;
|
|
};
|
|
};
|
|
|
|
export type DecodedFeishuCardAction =
|
|
| {
|
|
kind: "structured";
|
|
envelope: FeishuCardInteractionEnvelope;
|
|
}
|
|
| {
|
|
kind: "legacy";
|
|
text: string;
|
|
}
|
|
| {
|
|
kind: "invalid";
|
|
reason: FeishuCardInteractionReason;
|
|
};
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null;
|
|
}
|
|
|
|
function isInteractionKind(value: unknown): value is FeishuCardInteractionKind {
|
|
return value === "button" || value === "quick" || value === "meta";
|
|
}
|
|
|
|
function isMetadataValue(value: unknown): value is string | number | boolean | null | undefined {
|
|
return (
|
|
value === null ||
|
|
value === undefined ||
|
|
typeof value === "string" ||
|
|
typeof value === "number" ||
|
|
typeof value === "boolean"
|
|
);
|
|
}
|
|
|
|
export function createFeishuCardInteractionEnvelope(
|
|
envelope: Omit<FeishuCardInteractionEnvelope, "oc">,
|
|
): FeishuCardInteractionEnvelope {
|
|
return {
|
|
oc: FEISHU_CARD_INTERACTION_VERSION,
|
|
...envelope,
|
|
};
|
|
}
|
|
|
|
export function buildFeishuCardActionTextFallback(event: FeishuCardActionEventLike): string {
|
|
const actionValue = event.action.value;
|
|
if (isRecord(actionValue)) {
|
|
if (typeof actionValue.text === "string") {
|
|
return actionValue.text;
|
|
}
|
|
if (typeof actionValue.command === "string") {
|
|
return actionValue.command;
|
|
}
|
|
return JSON.stringify(actionValue);
|
|
}
|
|
return String(actionValue);
|
|
}
|
|
|
|
export function decodeFeishuCardAction(params: {
|
|
event: FeishuCardActionEventLike;
|
|
now?: number;
|
|
}): DecodedFeishuCardAction {
|
|
const { event, now = Date.now() } = params;
|
|
const actionValue = event.action.value;
|
|
if (!isRecord(actionValue) || actionValue.oc !== FEISHU_CARD_INTERACTION_VERSION) {
|
|
return {
|
|
kind: "legacy",
|
|
text: buildFeishuCardActionTextFallback(event),
|
|
};
|
|
}
|
|
|
|
if (!isInteractionKind(actionValue.k) || typeof actionValue.a !== "string" || !actionValue.a) {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
|
|
if (actionValue.q !== undefined && typeof actionValue.q !== "string") {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
|
|
if (actionValue.m !== undefined) {
|
|
if (!isRecord(actionValue.m)) {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
for (const value of Object.values(actionValue.m)) {
|
|
if (!isMetadataValue(value)) {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
}
|
|
}
|
|
|
|
if (actionValue.c !== undefined) {
|
|
if (!isRecord(actionValue.c)) {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
if (actionValue.c.u !== undefined && typeof actionValue.c.u !== "string") {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
if (actionValue.c.h !== undefined && typeof actionValue.c.h !== "string") {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
if (actionValue.c.s !== undefined && typeof actionValue.c.s !== "string") {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
if (actionValue.c.e !== undefined && !Number.isFinite(actionValue.c.e)) {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
if (actionValue.c.t !== undefined && actionValue.c.t !== "p2p" && actionValue.c.t !== "group") {
|
|
return { kind: "invalid", reason: "malformed" };
|
|
}
|
|
|
|
if (typeof actionValue.c.e === "number" && actionValue.c.e < now) {
|
|
return { kind: "invalid", reason: "stale" };
|
|
}
|
|
|
|
const expectedUser = actionValue.c.u?.trim();
|
|
if (expectedUser && expectedUser !== (event.operator.open_id ?? "").trim()) {
|
|
return { kind: "invalid", reason: "wrong_user" };
|
|
}
|
|
|
|
const expectedChat = actionValue.c.h?.trim();
|
|
if (expectedChat && expectedChat !== (event.context.chat_id ?? "").trim()) {
|
|
return { kind: "invalid", reason: "wrong_conversation" };
|
|
}
|
|
}
|
|
|
|
return {
|
|
kind: "structured",
|
|
envelope: actionValue as FeishuCardInteractionEnvelope,
|
|
};
|
|
}
|