Add user ID support for Baileys 7 LIDs and Signal UUIDs

Baileys 7 uses LIDs (Linked IDs) instead of phone numbers in remoteJid for
some messages. This caused messages to be matched to wrong tickets because
the LID was used as the sender identifier. This commit adds proper support
for both phone numbers and user IDs across WhatsApp and Signal channels.

Changes:

Database:
- Add migration for whatsapp_user_id and signal_user_id fields on users table

Zammad controllers:
- Update user lookup with 3-step fallback: phone → dedicated user_id field →
  user_id in phone field (legacy)
- Store user IDs in dedicated fields when available
- Update phone field when we receive actual phone number for legacy records
- Fix redundant condition in Signal controller

Bridge services:
- Extract both phone (from senderPn/participantPn) and LID (from remoteJid)
- Send both identifiers to Zammad via webhooks
- Use camelCase (userId) in bridge-whatsapp, convert to snake_case (user_id)
  in bridge-worker for Zammad compatibility

Baileys 7 compliance:
- Remove broken loadAllUnreadMessages() call (removed in Baileys 7)
- Return descriptive error directing users to use webhooks instead

Misc:
- Add docs/ to .gitignore
This commit is contained in:
Darren Clarke 2026-01-15 10:01:15 +01:00
parent 57d7173485
commit 3d8f794cab
8 changed files with 154 additions and 36 deletions

View file

@ -193,13 +193,14 @@ export default class WhatsappService extends Service {
return;
}
const { id, fromMe, remoteJid } = key;
logger.info("Message type debug");
for (const key in message) {
logger.info(
{ key, exists: !!message[key as keyof proto.IMessage] },
"Message field",
);
}
// Baileys 7 uses LIDs (Linked IDs) instead of phone numbers in some cases.
// senderPn contains the actual phone number when available.
const senderPn = (key as any).senderPn as string | undefined;
const participantPn = (key as any).participantPn as string | undefined;
logger.info(
{ remoteJid, senderPn, participantPn, fromMe },
"Processing incoming message",
);
const isValidMessage = message && remoteJid !== "status@broadcast" && !fromMe;
if (isValidMessage) {
const { audioMessage, documentMessage, imageMessage, videoMessage } = message;
@ -257,9 +258,27 @@ export default class WhatsappService extends Service {
videoMessage,
].find((text) => text && text !== "");
// Extract phone number and user ID (LID) separately
// remoteJid may contain LIDs (Baileys 7+) which are not phone numbers
const jidValue = remoteJid?.split("@")[0];
const isLidJid = remoteJid?.endsWith("@lid");
// Phone number: prefer senderPn/participantPn, fall back to remoteJid only if it's not a LID
const senderPhone = senderPn?.split("@")[0] || participantPn?.split("@")[0] || (!isLidJid ? jidValue : undefined);
// User ID (LID): extract from remoteJid if it's a LID format
const senderUserId = isLidJid ? jidValue : undefined;
// Must have at least one identifier
if (!senderPhone && !senderUserId) {
logger.warn({ remoteJid, senderPn, participantPn }, "Could not determine sender identity, skipping message");
return;
}
const payload = {
to: botID,
from: remoteJid?.split("@")[0],
from: senderPhone,
userId: senderUserId,
messageId: id,
sentAt: new Date((messageTimestamp as number) * 1000).toISOString(),
message: messageText,
@ -423,12 +442,17 @@ export default class WhatsappService extends Service {
}
async receive(
botID: string,
_botID: string,
_lastReceivedDate: Date,
): Promise<proto.IWebMessageInfo[]> {
const connection = this.connections[botID]?.socket;
const messages = await connection.loadAllUnreadMessages();
return messages;
// loadAllUnreadMessages() was removed in Baileys 7.x
// Messages are now delivered via events (messages.upsert, messaging-history.set)
// and forwarded to webhooks automatically.
// See: https://baileys.wiki/docs/migration/to-v7.0.0/
throw new Error(
"Message polling is no longer supported in Baileys 7.x. " +
"Please configure a webhook to receive messages instead. " +
"Messages are automatically forwarded to BRIDGE_FRONTEND_URL/api/whatsapp/bots/{id}/receive"
);
}
}