WhatsApp coexistence replies can carry a BSUID-scoped `context.id` even when Chatwoot stored the original message with a phone-scoped WAMID. Although both identifiers refer to the same message, their complete values differ, so incoming replies retained the external reference but did not populate the internal `in_reply_to` relationship. Agents consequently saw the reply text without the quoted-message preview. This resolves the original message within the selected conversation and stores the internal reply relationship. Exact WAMID matches remain the primary path; scoped identifiers fall back to the unique decoded message token. Fixed https://linear.app/chatwoot/issue/CW-7663/whatsapp-quoted-replies-are-not-linked-across-scoped-wamids and https://github.com/chatwoot/chatwoot/issues/14953 ## How to reproduce 1. Use a WhatsApp Cloud inbox with coexistence enabled. 2. Send a message whose source ID is stored using the phone-scoped WAMID. 3. Reply to it from WhatsApp when the webhook carries a BSUID-scoped `context.id` for the same message. 4. Before this change, the incoming message appears without its quoted-message preview. 5. After this change, the reply references and displays the original message. ## What changed - Resolve incoming reply context IDs against messages in the selected conversation. - Keep exact source-ID matching as the first lookup path. - Decode scoped WAMIDs and match only a unique 20- or 32-character message token. - Populate `content_attributes.in_reply_to` while preserving `in_reply_to_external_id`. - Leave malformed, unmatched, or ambiguous identifiers unlinked. ## How to test 1. Open a WhatsApp Cloud conversation and send a message to the contact. 2. Reply to that message from WhatsApp through a coexistence identity. 3. Confirm the incoming message displays the original message as a quoted preview. 4. Confirm ordinary exact-ID replies continue to resolve. 5. Confirm an unknown or malformed context ID does not attach to another message. ## Things to know Meta documents `context.id` as the replied-to message identifier, but does not document the internal WAMID encoding or the phone-versus-BSUID scope transformation. The fallback is therefore limited to the selected conversation and succeeds only when one stored message has the decoded token. --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
24 lines
1.5 KiB
Ruby
24 lines
1.5 KiB
Ruby
module RegexHelper
|
|
# user https://rubular.com/ to quickly validate your regex
|
|
|
|
# the following regext needs atleast one character which should be
|
|
# valid unicode letter, unicode number, underscore, hyphen
|
|
# shouldn't start with a underscore or hyphen
|
|
UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE = Regexp.new('\A[\p{L}\p{N}]+[\p{L}\p{N}_-]+\Z')
|
|
# Regex to match mention markdown links and extract display names
|
|
# Matches: [@display name](mention://user|team/id/url_encoded_name)
|
|
# Captures: 1) @display name (including emojis), 2) url_encoded_name
|
|
# Uses [^]]+ to match any characters except ] in display name to support emojis
|
|
# NOTE: Still used by Slack integration (lib/integrations/slack/send_on_slack_service.rb)
|
|
# while notifications use CommonMarker for better markdown processing
|
|
MENTION_REGEX = Regexp.new('\[(@[^\\]]+)\]\(mention://(?:user|team)/\d+/([^)]+)\)')
|
|
|
|
TWILIO_CHANNEL_SMS_REGEX = Regexp.new('\A\+\d{1,15}\z')
|
|
WHATSAPP_BSUID_PATTERN = '[A-Z]{2}\.(?:ENT\.)?[A-Za-z0-9]{1,128}'.freeze
|
|
WHATSAPP_WAMID_TOKEN_PATTERN = '(?<![0-9a-f])(?:[0-9a-f]{32}|[0-9a-f]{20})(?![0-9a-f])'.freeze
|
|
WHATSAPP_BSUID_REGEX = Regexp.new("\\A#{WHATSAPP_BSUID_PATTERN}\\z")
|
|
WHATSAPP_WAMID_TOKEN_REGEX = Regexp.new(WHATSAPP_WAMID_TOKEN_PATTERN, Regexp::IGNORECASE)
|
|
TWILIO_CHANNEL_WHATSAPP_REGEX = Regexp.new("\\A(?:whatsapp:\\+\\d{1,15}|whatsapp:#{WHATSAPP_BSUID_PATTERN})\\z")
|
|
WHATSAPP_CHANNEL_REGEX = Regexp.new("\\A(?:\\d{1,15}|#{WHATSAPP_BSUID_PATTERN})\\z")
|
|
end
|