mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-04-30 22:12:32 +08:00
Fix Matrix media alias normalization
This commit is contained in:
@@ -93,7 +93,7 @@ describe("matrixMessageActions", () => {
|
||||
expect(actions).toContain(profileAction);
|
||||
expect(supportsAction({ action: profileAction } as never)).toBe(true);
|
||||
expect(discovery.mediaSourceParams).toEqual({
|
||||
"set-profile": ["avatarUrl", "avatar_url", "avatarPath", "avatar_path"],
|
||||
"set-profile": ["avatarUrl", "avatarPath"],
|
||||
});
|
||||
expect(properties.displayName).toBeDefined();
|
||||
expect(properties.avatarUrl).toBeDefined();
|
||||
|
||||
@@ -57,9 +57,7 @@ const MATRIX_PROFILE_MEDIA_PROPERTIES = {
|
||||
}),
|
||||
),
|
||||
} as const;
|
||||
const MATRIX_PROFILE_MEDIA_SOURCE_PARAMS = Object.freeze(
|
||||
Object.keys(MATRIX_PROFILE_MEDIA_PROPERTIES),
|
||||
);
|
||||
const MATRIX_PROFILE_MEDIA_SOURCE_PARAMS = Object.freeze(["avatarUrl", "avatarPath"]);
|
||||
|
||||
function createMatrixExposedActions(params: {
|
||||
gate: ReturnType<typeof createActionGate>;
|
||||
|
||||
@@ -13,12 +13,7 @@ import {
|
||||
|
||||
const cfg = {} as OpenClawConfig;
|
||||
const maybeIt = process.platform === "win32" ? it.skip : it;
|
||||
const matrixMediaSourceParamKeys = [
|
||||
"avatarPath",
|
||||
"avatar_path",
|
||||
"avatarUrl",
|
||||
"avatar_url",
|
||||
] as const;
|
||||
const matrixMediaSourceParamKeys = ["avatarPath", "avatarUrl"] as const;
|
||||
|
||||
describe("message action media helpers", () => {
|
||||
it("prefers sandbox media policy when sandbox roots are non-blank", () => {
|
||||
@@ -184,6 +179,36 @@ describe("message action media helpers", () => {
|
||||
}
|
||||
});
|
||||
|
||||
maybeIt("prefers canonical Matrix media params over invalid snake_case aliases", async () => {
|
||||
const sandboxRoot = await fs.mkdtemp(path.join(os.tmpdir(), "msg-params-avatar-canonical-"));
|
||||
try {
|
||||
const args: Record<string, unknown> = {
|
||||
avatarUrl: "https://example.com/avatars/profile.png",
|
||||
avatar_url: "data:text/plain;base64,QQ==",
|
||||
avatarPath: "/workspace/avatars/profile.png",
|
||||
avatar_path: "data:text/plain;base64,QQ==",
|
||||
};
|
||||
|
||||
await normalizeSandboxMediaParams({
|
||||
args,
|
||||
mediaPolicy: {
|
||||
mode: "sandbox",
|
||||
sandboxRoot,
|
||||
},
|
||||
extraParamKeys: matrixMediaSourceParamKeys,
|
||||
});
|
||||
|
||||
expect(args).toMatchObject({
|
||||
avatarUrl: "https://example.com/avatars/profile.png",
|
||||
avatarPath: path.join(sandboxRoot, "avatars", "profile.png"),
|
||||
avatar_url: "data:text/plain;base64,QQ==",
|
||||
avatar_path: "data:text/plain;base64,QQ==",
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(sandboxRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
maybeIt("keeps remote HTTP avatarUrl unchanged under sandbox normalization", async () => {
|
||||
const sandboxRoot = await fs.mkdtemp(path.join(os.tmpdir(), "msg-params-avatar-remote-"));
|
||||
try {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "../../media/load-options.js";
|
||||
import { extensionForMime } from "../../media/mime.js";
|
||||
import { loadWebMedia } from "../../media/web-media.js";
|
||||
import { resolveSnakeCaseParamKey } from "../../param-key.js";
|
||||
import { readBooleanParam as readBooleanParamShared } from "../../plugin-sdk/boolean-param.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
|
||||
@@ -31,6 +32,24 @@ function readMediaParam(args: Record<string, unknown>, key: string): string | un
|
||||
return readStringParam(args, key, { trim: false });
|
||||
}
|
||||
|
||||
function resolveMediaParamEntry(
|
||||
args: Record<string, unknown>,
|
||||
key: string,
|
||||
): { key: string; value: string } | undefined {
|
||||
const resolvedKey = resolveSnakeCaseParamKey(args, key);
|
||||
if (!resolvedKey) {
|
||||
return undefined;
|
||||
}
|
||||
const value = readMediaParam(args, key);
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
key: resolvedKey,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
function buildActionMediaSourceParamKeys(extraParamKeys?: readonly string[]): string[] {
|
||||
const keys = new Set<string>(BASE_ACTION_MEDIA_SOURCE_PARAM_KEYS);
|
||||
extraParamKeys?.forEach((key) => keys.add(key));
|
||||
@@ -67,9 +86,9 @@ export function collectActionMediaSourceHints(
|
||||
): string[] {
|
||||
const sources: string[] = [];
|
||||
for (const key of buildActionMediaSourceParamKeys(extraParamKeys)) {
|
||||
const source = typeof args[key] === "string" ? args[key] : undefined;
|
||||
if (source && normalizeOptionalString(source)) {
|
||||
sources.push(source);
|
||||
const entry = resolveMediaParamEntry(args, key);
|
||||
if (entry && normalizeOptionalString(entry.value)) {
|
||||
sources.push(entry.value);
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
@@ -277,17 +296,17 @@ export async function normalizeSandboxMediaParams(params: {
|
||||
const sandboxRoot =
|
||||
params.mediaPolicy.mode === "sandbox" ? params.mediaPolicy.sandboxRoot.trim() : undefined;
|
||||
for (const key of buildActionMediaSourceParamKeys(params.extraParamKeys)) {
|
||||
const raw = readMediaParam(params.args, key);
|
||||
if (!raw) {
|
||||
const entry = resolveMediaParamEntry(params.args, key);
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
assertMediaNotDataUrl(raw);
|
||||
assertMediaNotDataUrl(entry.value);
|
||||
if (!sandboxRoot) {
|
||||
continue;
|
||||
}
|
||||
const normalized = await resolveSandboxedMediaSource({ media: raw, sandboxRoot });
|
||||
if (normalized !== raw) {
|
||||
params.args[key] = normalized;
|
||||
const normalized = await resolveSandboxedMediaSource({ media: entry.value, sandboxRoot });
|
||||
if (normalized !== entry.value) {
|
||||
params.args[entry.key] = normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,24 @@ function toSnakeCaseKey(key: string): string {
|
||||
return lowercasePreservingWhitespace(snakeKey);
|
||||
}
|
||||
|
||||
export function readSnakeCaseParamRaw(params: Record<string, unknown>, key: string): unknown {
|
||||
export function resolveSnakeCaseParamKey(
|
||||
params: Record<string, unknown>,
|
||||
key: string,
|
||||
): string | undefined {
|
||||
if (Object.hasOwn(params, key)) {
|
||||
return params[key];
|
||||
return key;
|
||||
}
|
||||
const snakeKey = toSnakeCaseKey(key);
|
||||
if (snakeKey !== key && Object.hasOwn(params, snakeKey)) {
|
||||
return params[snakeKey];
|
||||
return snakeKey;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function readSnakeCaseParamRaw(params: Record<string, unknown>, key: string): unknown {
|
||||
const resolvedKey = resolveSnakeCaseParamKey(params, key);
|
||||
if (resolvedKey) {
|
||||
return params[resolvedKey];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user