mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-10 05:20:45 +08:00
fix(whatsapp): respond to linked-account group messages
This commit is contained in:
parent
9657107a21
commit
2cc3a42d45
11 changed files with 150 additions and 34 deletions
|
|
@ -119,11 +119,13 @@ test('plain Slack, Telegram, Discord, and WhatsApp receipts retain every split m
|
|||
|
||||
let whatsappId = 0;
|
||||
const remembered = [];
|
||||
const reserved = [];
|
||||
const whatsapp = new WhatsappBotClient({
|
||||
sendPresenceUpdate: async () => {},
|
||||
sendMessage: async () => ({ key: { id: `whatsapp-${++whatsappId}` } }),
|
||||
}, {
|
||||
remember: (messageId) => remembered.push(messageId),
|
||||
reserve: (messageId) => reserved.push(messageId),
|
||||
});
|
||||
const whatsappReceipt = await textReceipt({
|
||||
key: 'whatsapp',
|
||||
|
|
@ -133,6 +135,9 @@ test('plain Slack, Telegram, Discord, and WhatsApp receipts retain every split m
|
|||
});
|
||||
assert.deepEqual(whatsappReceipt.providerMessageIds, ['whatsapp-1', 'whatsapp-2']);
|
||||
assert.deepEqual(remembered, ['whatsapp-1', 'whatsapp-2']);
|
||||
assert.equal(reserved.length, 2);
|
||||
assert.equal(reserved.every((messageId) => /^[0-9A-F]{20}$/.test(messageId)), true);
|
||||
assert.notEqual(reserved[0], reserved[1]);
|
||||
});
|
||||
|
||||
test('Slack, Telegram, and Discord stream receipts retain the initial and remainder ids', async () => {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ test('WhatsApp account card uses the unified compact channel layout', () => {
|
|||
assert.match(markup, /仅自己模式(默认)/);
|
||||
assert.match(markup, /指定联系人模式/);
|
||||
assert.match(markup, /开放响应模式/);
|
||||
assert.match(markup, /已绑定账号自己发出的群聊消息/);
|
||||
assert.match(markup, /role="status"[^>]*>测试消息已发送/);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ test('WhatsApp media downloader supplies Baileys reupload context', async () =>
|
|||
assert.deepEqual(reuploaded, [{ key: { id: 'expired-media' } }]);
|
||||
});
|
||||
|
||||
test('WhatsApp normalizes direct and explicitly mentioned group messages', () => {
|
||||
test('WhatsApp normalizes direct, linked-account, and explicitly mentioned group messages', () => {
|
||||
const direct = normalizeWhatsappMessage({
|
||||
key: { remoteJid: '16505550999@s.whatsapp.net', id: 'direct-1', fromMe: false },
|
||||
message: { conversation: 'hello' },
|
||||
|
|
@ -358,6 +358,20 @@ test('WhatsApp normalizes direct and explicitly mentioned group messages', () =>
|
|||
}, ACCOUNT_JID);
|
||||
assert.equal(selfChat.selfChat, true);
|
||||
assert.equal(selfChat.addressed, true);
|
||||
|
||||
const linkedAccountGroup = normalizeWhatsappMessage({
|
||||
key: {
|
||||
remoteJid: '120363000000000001@g.us',
|
||||
id: 'owner-group-1',
|
||||
fromMe: true,
|
||||
},
|
||||
message: { conversation: 'message from linked account in a group' },
|
||||
}, ACCOUNT_JID);
|
||||
assert.equal(linkedAccountGroup.kind, 'group');
|
||||
assert.equal(linkedAccountGroup.senderId, ACCOUNT_JID);
|
||||
assert.equal(linkedAccountGroup.addressed, true);
|
||||
assert.equal(linkedAccountGroup.selfChat, false);
|
||||
|
||||
assert.equal(normalizeWhatsappMessage({
|
||||
key: { remoteJid: '16505550999@s.whatsapp.net', id: 'outbound-1', fromMe: true },
|
||||
message: { conversation: 'ordinary outbound message' },
|
||||
|
|
@ -387,10 +401,19 @@ test('WhatsApp access modes allow self-chat, selected contacts, or the existing
|
|||
key: { remoteJid: ACCOUNT_JID, id: 'access-self', fromMe: true },
|
||||
message: { conversation: 'hello' },
|
||||
}, ACCOUNT_JID);
|
||||
const linkedAccountGroup = normalizeWhatsappMessage({
|
||||
key: {
|
||||
remoteJid: '120363000000000001@g.us',
|
||||
id: 'access-owner-group',
|
||||
fromMe: true,
|
||||
},
|
||||
message: { conversation: 'hello from the linked account' },
|
||||
}, ACCOUNT_JID);
|
||||
|
||||
assert.equal(whatsappInboundAllowed(selfChat), true);
|
||||
assert.equal(whatsappInboundAllowed(direct), false);
|
||||
assert.equal(whatsappInboundAllowed(group), false);
|
||||
assert.equal(whatsappInboundAllowed(linkedAccountGroup), false);
|
||||
assert.equal(whatsappInboundAllowed(direct, {
|
||||
accessMode: WHATSAPP_ACCESS_MODES.privateAllowlist,
|
||||
allowedNumbers: new Set(['16505550999']),
|
||||
|
|
@ -402,6 +425,9 @@ test('WhatsApp access modes allow self-chat, selected contacts, or the existing
|
|||
assert.equal(whatsappInboundAllowed(group, {
|
||||
accessMode: WHATSAPP_ACCESS_MODES.open,
|
||||
}), true);
|
||||
assert.equal(whatsappInboundAllowed(linkedAccountGroup, {
|
||||
accessMode: WHATSAPP_ACCESS_MODES.open,
|
||||
}), true);
|
||||
});
|
||||
|
||||
test('WhatsApp keeps native and document images as images and exposes ordinary documents as files', async () => {
|
||||
|
|
@ -623,6 +649,64 @@ test('WhatsApp runtime filters messages before the bridge and applies policy upd
|
|||
await runtime.stop();
|
||||
});
|
||||
|
||||
test('WhatsApp open mode answers linked-account group messages without processing reply echoes', async (t) => {
|
||||
const groupJid = '120363000000000001@g.us';
|
||||
let callbacks;
|
||||
let replyEchoTask;
|
||||
let askCount = 0;
|
||||
const sent = [];
|
||||
const socket = {
|
||||
sendPresenceUpdate: async () => {},
|
||||
readMessages: async () => {},
|
||||
sendMessage: async (jid, content, options = {}) => {
|
||||
sent.push({ jid, content, options });
|
||||
replyEchoTask = callbacks.onMessage({
|
||||
key: { remoteJid: groupJid, id: options.messageId, fromMe: true },
|
||||
message: { conversation: content.text },
|
||||
});
|
||||
return { key: { id: options.messageId } };
|
||||
},
|
||||
};
|
||||
const runtime = new WhatsappRuntime({
|
||||
config: linkedConfig({ accessMode: WHATSAPP_ACCESS_MODES.open }),
|
||||
authDir: '/tmp/test-whatsapp-linked-account-group',
|
||||
harness: {
|
||||
ensureRunning: async () => {},
|
||||
sessionExists: async () => true,
|
||||
ask: async () => {
|
||||
askCount += 1;
|
||||
return 'Harness group answer';
|
||||
},
|
||||
},
|
||||
state: artifactState('session-linked-account-group'),
|
||||
createSession: async (options) => {
|
||||
callbacks = options;
|
||||
return {
|
||||
socket,
|
||||
ready: Promise.resolve({ accountJid: ACCOUNT_JID, name: 'Harness WhatsApp' }),
|
||||
close: async () => {},
|
||||
logout: async () => {},
|
||||
};
|
||||
},
|
||||
});
|
||||
t.after(() => runtime.stop());
|
||||
await runtime.start();
|
||||
|
||||
const inbound = {
|
||||
key: { remoteJid: groupJid, id: 'linked-account-group-1', fromMe: true },
|
||||
message: { conversation: 'hello from my group' },
|
||||
};
|
||||
await callbacks.onMessage(inbound);
|
||||
await replyEchoTask;
|
||||
|
||||
assert.equal(askCount, 1);
|
||||
assert.equal(sent.length, 1);
|
||||
assert.equal(sent[0].jid, groupJid);
|
||||
assert.equal(sent[0].content.text, 'Harness group answer');
|
||||
assert.equal(sent[0].options.quoted, inbound);
|
||||
assert.match(sent[0].options.messageId, /^[0-9A-F]{20}$/);
|
||||
});
|
||||
|
||||
test('WhatsApp runtime sends result files with native metadata, quote, stable id, and upload timeout', async (t) => {
|
||||
const { artifact, deliveryKey } = await committedArtifact(t, {
|
||||
suffix: 'native-file',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue