mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-02 04:41:11 +00:00
fix(zalouser): fix outbound target routing between DM and group
Add explicit target parsing with group:/user: prefixes so the bot correctly routes outbound messages to groups vs DMs. Supports aliases (g:, g-, u:, dm:, zlu:, zalouser:) and passes isGroup flag to sendMessageZalouser.
This commit is contained in:
@@ -49,6 +49,22 @@ describe("zalouserPlugin outbound sendPayload", () => {
|
||||
expect(result).toMatchObject({ channel: "zalouser", messageId: "zlu-t1" });
|
||||
});
|
||||
|
||||
it("group target delegates with isGroup=true and stripped threadId", async () => {
|
||||
mockedSend.mockResolvedValue({ ok: true, messageId: "zlu-g1" });
|
||||
|
||||
const result = await zalouserPlugin.outbound!.sendPayload!({
|
||||
...baseCtx({ text: "hello group" }),
|
||||
to: "group:1471383327500481391",
|
||||
});
|
||||
|
||||
expect(mockedSend).toHaveBeenCalledWith(
|
||||
"1471383327500481391",
|
||||
"hello group",
|
||||
expect.objectContaining({ isGroup: true }),
|
||||
);
|
||||
expect(result).toMatchObject({ channel: "zalouser", messageId: "zlu-g1" });
|
||||
});
|
||||
|
||||
it("single media delegates to sendMedia", async () => {
|
||||
mockedSend.mockResolvedValue({ ok: true, messageId: "zlu-m1" });
|
||||
|
||||
@@ -115,3 +131,27 @@ describe("zalouserPlugin outbound sendPayload", () => {
|
||||
expect(result).toMatchObject({ channel: "zalouser" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("zalouserPlugin messaging target normalization", () => {
|
||||
it("normalizes user/group aliases to canonical targets", () => {
|
||||
const normalize = zalouserPlugin.messaging?.normalizeTarget;
|
||||
expect(normalize).toBeTypeOf("function");
|
||||
if (!normalize) {
|
||||
return;
|
||||
}
|
||||
expect(normalize("zlu:g-30003")).toBe("group:30003");
|
||||
expect(normalize("zalouser:u:20002")).toBe("user:20002");
|
||||
expect(normalize("20002")).toBe("20002");
|
||||
});
|
||||
|
||||
it("treats canonical user/group targets as direct IDs", () => {
|
||||
const looksLikeId = zalouserPlugin.messaging?.targetResolver?.looksLikeId;
|
||||
expect(looksLikeId).toBeTypeOf("function");
|
||||
if (!looksLikeId) {
|
||||
return;
|
||||
}
|
||||
expect(looksLikeId("user:20002")).toBe(true);
|
||||
expect(looksLikeId("group:30003")).toBe(true);
|
||||
expect(looksLikeId("Alice Nguyen")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,6 +61,60 @@ const meta = {
|
||||
quickstartAllowFrom: true,
|
||||
};
|
||||
|
||||
function stripZalouserTargetPrefix(raw: string): string {
|
||||
return raw
|
||||
.trim()
|
||||
.replace(/^(zalouser|zlu):/i, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function normalizePrefixedTarget(raw: string): string | undefined {
|
||||
const trimmed = stripZalouserTargetPrefix(raw);
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const groupAlias = trimmed.match(/^(group:|g:|g-)(.+)$/i);
|
||||
if (groupAlias) {
|
||||
const id = groupAlias[2]?.trim() ?? "";
|
||||
return id ? `group:${id}` : undefined;
|
||||
}
|
||||
|
||||
const userAlias = trimmed.match(/^(user:|dm:|u:|u-)(.+)$/i);
|
||||
if (userAlias) {
|
||||
const id = userAlias[2]?.trim() ?? "";
|
||||
return id ? `user:${id}` : undefined;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function parseZalouserOutboundTarget(raw: string): {
|
||||
threadId: string;
|
||||
isGroup: boolean;
|
||||
} {
|
||||
const normalized = normalizePrefixedTarget(raw);
|
||||
if (!normalized) {
|
||||
throw new Error("Zalouser target is required");
|
||||
}
|
||||
const lowered = normalized.toLowerCase();
|
||||
if (lowered.startsWith("group:")) {
|
||||
const threadId = normalized.slice("group:".length).trim();
|
||||
if (!threadId) {
|
||||
throw new Error("Zalouser group target is missing group id");
|
||||
}
|
||||
return { threadId, isGroup: true };
|
||||
}
|
||||
if (lowered.startsWith("user:")) {
|
||||
const threadId = normalized.slice("user:".length).trim();
|
||||
if (!threadId) {
|
||||
throw new Error("Zalouser user target is missing user id");
|
||||
}
|
||||
return { threadId, isGroup: false };
|
||||
}
|
||||
return { threadId: normalized, isGroup: false };
|
||||
}
|
||||
|
||||
function resolveZalouserQrProfile(accountId?: string | null): string {
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
if (!normalized || normalized === DEFAULT_ACCOUNT_ID) {
|
||||
@@ -387,22 +441,19 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
},
|
||||
},
|
||||
messaging: {
|
||||
normalizeTarget: (raw) => {
|
||||
const trimmed = raw?.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
return trimmed.replace(/^(zalouser|zlu):/i, "");
|
||||
},
|
||||
normalizeTarget: (raw) => normalizePrefixedTarget(raw),
|
||||
targetResolver: {
|
||||
looksLikeId: (raw) => {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
const normalized = normalizePrefixedTarget(raw);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return /^\d{3,}$/.test(trimmed);
|
||||
if (/^group:[^\s]+$/i.test(normalized) || /^user:[^\s]+$/i.test(normalized)) {
|
||||
return true;
|
||||
}
|
||||
return /^\d{3,}$/.test(normalized);
|
||||
},
|
||||
hint: "<threadId>",
|
||||
hint: "<threadId|user:id|group:id>",
|
||||
},
|
||||
},
|
||||
directory: {
|
||||
@@ -596,7 +647,11 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
},
|
||||
sendText: async ({ to, text, accountId, cfg }) => {
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
||||
const result = await sendMessageZalouser(to, text, { profile: account.profile });
|
||||
const target = parseZalouserOutboundTarget(to);
|
||||
const result = await sendMessageZalouser(target.threadId, text, {
|
||||
profile: account.profile,
|
||||
isGroup: target.isGroup,
|
||||
});
|
||||
return {
|
||||
channel: "zalouser",
|
||||
ok: result.ok,
|
||||
@@ -606,8 +661,10 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
},
|
||||
sendMedia: async ({ to, text, mediaUrl, accountId, cfg, mediaLocalRoots }) => {
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
||||
const result = await sendMessageZalouser(to, text, {
|
||||
const target = parseZalouserOutboundTarget(to);
|
||||
const result = await sendMessageZalouser(target.threadId, text, {
|
||||
profile: account.profile,
|
||||
isGroup: target.isGroup,
|
||||
mediaUrl,
|
||||
mediaLocalRoots,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user