feat(baileys): upgrade to v7 (#143)
* feat(baileys): upgrade to v7 (#139) * feat(baileys): v7 * chore(zapi): match baileys changes * test: fix send on whatsapp spec * chore: simplify logic and minor fixes * fix: ensure contact avatar updates when profile picture changes * chore: add beta release workflow * feat: enhance contact update logic to handle conflicts in inbox updates (#140) * feat: update GitHub Actions workflow to trigger on release events and adjust GIT_REF handling * fix: prevent unique constraint error when LID contact inbox already exists (#141) * fix: prevent unique constraint error when LID contact inbox already exists * feat(baileys): handle ephemeral messages
This commit is contained in:
parent
17149f3dca
commit
c041090675
17
.github/workflows/publish_ee_github_docker.yml
vendored
17
.github/workflows/publish_ee_github_docker.yml
vendored
@ -5,9 +5,8 @@ permissions:
|
||||
packages: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
release:
|
||||
types: [released]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@ -25,10 +24,12 @@ jobs:
|
||||
runner: ubuntu-22.04-arm
|
||||
runs-on: ${{ matrix.runner }}
|
||||
env:
|
||||
GIT_REF: ${{ github.head_ref || github.ref_name || github.sha }}
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.release.tag_name || github.ref }}
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@ -61,7 +62,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@ -75,7 +76,7 @@ jobs:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
|
||||
push: ${{ github.event_name == 'release' || github.event_name == 'workflow_dispatch' }}
|
||||
tags: |
|
||||
${{ env.GITHUB_REPO }}:${{ env.SANITIZED_REF }}-ee
|
||||
${{ env.GITHUB_REPO }}:latest-ee
|
||||
@ -119,7 +120,7 @@ jobs:
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
env:
|
||||
GIT_REF: ${{ github.head_ref || github.ref_name || github.sha }}
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
run: |
|
||||
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
|
||||
docker buildx imagetools create \
|
||||
@ -129,7 +130,7 @@ jobs:
|
||||
|
||||
- name: Inspect image
|
||||
env:
|
||||
GIT_REF: ${{ github.head_ref || github.ref_name || github.sha }}
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
run: |
|
||||
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
|
||||
REPO="ghcr.io/${{ github.repository }}"
|
||||
|
||||
17
.github/workflows/publish_github_docker.yml
vendored
17
.github/workflows/publish_github_docker.yml
vendored
@ -5,9 +5,8 @@ permissions:
|
||||
packages: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
release:
|
||||
types: [released]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@ -25,10 +24,12 @@ jobs:
|
||||
runner: ubuntu-22.04-arm
|
||||
runs-on: ${{ matrix.runner }}
|
||||
env:
|
||||
GIT_REF: ${{ github.head_ref || github.ref_name }}
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.release.tag_name || github.ref }}
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@ -62,7 +63,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@ -76,7 +77,7 @@ jobs:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
|
||||
push: ${{ github.event_name == 'release' || github.event_name == 'workflow_dispatch' }}
|
||||
tags: |
|
||||
${{ env.GITHUB_REPO }}:${{ env.SANITIZED_REF }}
|
||||
${{ env.GITHUB_REPO }}:latest
|
||||
@ -120,7 +121,7 @@ jobs:
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
env:
|
||||
GIT_REF: ${{ github.head_ref || github.ref_name }}
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
run: |
|
||||
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
|
||||
docker buildx imagetools create \
|
||||
@ -130,7 +131,7 @@ jobs:
|
||||
|
||||
- name: Inspect image
|
||||
env:
|
||||
GIT_REF: ${{ github.head_ref || github.ref_name }}
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
run: |
|
||||
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
|
||||
REPO="ghcr.io/${{ github.repository }}"
|
||||
|
||||
139
.github/workflows/publish_github_docker_beta.yml
vendored
Normal file
139
.github/workflows/publish_github_docker_beta.yml
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
name: Publish Chatwoot beta docker images to GitHub
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [prereleased]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GITHUB_REPO: ghcr.io/${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-22.04-arm
|
||||
runs-on: ${{ matrix.runner }}
|
||||
env:
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.release.tag_name || github.ref }}
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Strip enterprise code
|
||||
run: |
|
||||
rm -rf enterprise
|
||||
rm -rf spec/enterprise
|
||||
|
||||
- name: Update version in app.yml
|
||||
run: |
|
||||
if [[ "$GIT_REF" =~ ^v(.+)$ ]]; then
|
||||
VERSION="${BASH_REMATCH[1]}"
|
||||
echo "Updating version to: $VERSION"
|
||||
sed -i "s/version: '.*'/version: '$VERSION'/" config/app.yml
|
||||
else
|
||||
echo "No version tag found, keeping existing version"
|
||||
fi
|
||||
|
||||
- name: Set Docker Tags
|
||||
run: |
|
||||
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
|
||||
echo "SANITIZED_REF=${SANITIZED_REF}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to GitHub Container Registry
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
push: ${{ github.event_name == 'release' || github.event_name == 'workflow_dispatch' }}
|
||||
tags: |
|
||||
${{ env.GITHUB_REPO }}:${{ env.SANITIZED_REF }}
|
||||
${{ env.GITHUB_REPO }}:beta
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}/digests
|
||||
digest="${{ steps.build-ghcr.outputs.digest }}"
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
env:
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
run: |
|
||||
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
|
||||
docker buildx imagetools create \
|
||||
-t ghcr.io/${{ github.repository }}:${SANITIZED_REF} \
|
||||
-t ghcr.io/${{ github.repository }}:beta \
|
||||
$(printf 'ghcr.io/${{ github.repository }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
env:
|
||||
GIT_REF: ${{ github.event.release.tag_name || github.ref_name }}
|
||||
run: |
|
||||
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
|
||||
REPO="ghcr.io/${{ github.repository }}"
|
||||
docker buildx imagetools inspect ${REPO}:${SANITIZED_REF}
|
||||
docker buildx imagetools inspect ${REPO}:beta
|
||||
@ -86,8 +86,9 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
def toggle_typing_status(typing_status, conversation:)
|
||||
return unless provider_service.respond_to?(:toggle_typing_status)
|
||||
|
||||
recipient_id = conversation.contact.identifier || conversation.contact.phone_number
|
||||
last_message = conversation.messages.last
|
||||
provider_service.toggle_typing_status(typing_status, last_message: last_message, phone_number: conversation.contact.phone_number)
|
||||
provider_service.toggle_typing_status(typing_status, last_message: last_message, recipient_id: recipient_id)
|
||||
end
|
||||
|
||||
def update_presence(status)
|
||||
@ -101,7 +102,13 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
# NOTE: This is the default behavior, so `mark_as_read` being `nil` is the same as `true`.
|
||||
return if provider_config&.dig('mark_as_read') == false
|
||||
|
||||
provider_service.read_messages(messages, phone_number: conversation.contact.phone_number)
|
||||
recipient_id = if provider == 'zapi'
|
||||
conversation.contact.phone_number
|
||||
else
|
||||
conversation.contact.identifier || conversation.contact.phone_number
|
||||
end
|
||||
|
||||
provider_service.read_messages(messages, recipient_id: recipient_id)
|
||||
end
|
||||
|
||||
def unread_conversation(conversation)
|
||||
@ -122,7 +129,8 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
def received_messages(messages, conversation)
|
||||
return unless provider_service.respond_to?(:received_messages)
|
||||
|
||||
provider_service.received_messages(conversation.contact.phone_number, messages)
|
||||
recipient_id = conversation.contact.identifier || conversation.contact.phone_number
|
||||
provider_service.received_messages(recipient_id, messages)
|
||||
end
|
||||
|
||||
def on_whatsapp(phone_number)
|
||||
|
||||
@ -3,14 +3,12 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
|
||||
private
|
||||
|
||||
def raw_message_id
|
||||
@raw_message[:key][:id]
|
||||
def unwrap_ephemeral_message(msg)
|
||||
msg.key?(:ephemeralMessage) ? msg.dig(:ephemeralMessage, :message) : msg
|
||||
end
|
||||
|
||||
def sender_lid
|
||||
return @raw_message[:key][:senderLid] if @raw_message[:key].key?(:senderLid)
|
||||
|
||||
@raw_message[:key][:remoteJid] if jid_type == 'lid'
|
||||
def raw_message_id
|
||||
@raw_message[:key][:id]
|
||||
end
|
||||
|
||||
def incoming?
|
||||
@ -42,7 +40,7 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
end
|
||||
|
||||
def message_type # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength,Metrics/AbcSize
|
||||
msg = @raw_message[:message]
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
if msg.key?(:conversation) || msg.dig(:extendedTextMessage, :text).present?
|
||||
'text'
|
||||
elsif msg.key?(:imageMessage)
|
||||
@ -72,22 +70,23 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
end
|
||||
|
||||
def message_content # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
case message_type
|
||||
when 'text'
|
||||
@raw_message.dig(:message, :conversation) || @raw_message.dig(:message, :extendedTextMessage, :text)
|
||||
msg[:conversation] || msg.dig(:extendedTextMessage, :text)
|
||||
when 'image'
|
||||
@raw_message.dig(:message, :imageMessage, :caption)
|
||||
msg.dig(:imageMessage, :caption)
|
||||
when 'video'
|
||||
@raw_message.dig(:message, :videoMessage, :caption)
|
||||
msg.dig(:videoMessage, :caption)
|
||||
when 'file'
|
||||
@raw_message.dig(:message, :documentMessage, :caption).presence ||
|
||||
@raw_message.dig(:message, :documentWithCaptionMessage, :message, :documentMessage, :caption)
|
||||
msg.dig(:documentMessage, :caption).presence ||
|
||||
msg.dig(:documentWithCaptionMessage, :message, :documentMessage, :caption)
|
||||
when 'reaction'
|
||||
@raw_message.dig(:message, :reactionMessage, :text)
|
||||
msg.dig(:reactionMessage, :text)
|
||||
when 'contact'
|
||||
# FIXME: Missing specs
|
||||
display_name = @raw_message.dig(:message, :contactMessage, :displayName)
|
||||
vcard = @raw_message.dig(:message, :contactMessage, :vcard)
|
||||
display_name = msg.dig(:contactMessage, :displayName)
|
||||
vcard = msg.dig(:contactMessage, :vcard)
|
||||
match_phone_number = vcard&.match(/waid=(\d+)/)
|
||||
|
||||
return display_name unless match_phone_number
|
||||
@ -97,6 +96,24 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
end
|
||||
end
|
||||
|
||||
def reply_to_message_id # rubocop:disable Metrics/CyclomaticComplexity
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
message_key = case message_type
|
||||
when 'text' then :extendedTextMessage
|
||||
when 'image' then :imageMessage
|
||||
when 'sticker' then :stickerMessage
|
||||
when 'audio' then :audioMessage
|
||||
when 'video' then :videoMessage
|
||||
when 'contact' then :contactMessage
|
||||
when 'file'
|
||||
context_info = msg.dig(:documentMessage, :contextInfo).presence ||
|
||||
msg.dig(:documentWithCaptionMessage, :message, :documentMessage, :contextInfo)
|
||||
return context_info&.dig(:stanzaId)
|
||||
end
|
||||
|
||||
msg.dig(message_key, :contextInfo, :stanzaId) if message_key
|
||||
end
|
||||
|
||||
def file_content_type
|
||||
return :image if message_type.in?(%w[image sticker])
|
||||
return :video if message_type.in?(%w[video video_note])
|
||||
@ -106,28 +123,31 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
end
|
||||
|
||||
def message_mimetype
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
case message_type
|
||||
when 'image'
|
||||
@raw_message.dig(:message, :imageMessage, :mimetype)
|
||||
msg.dig(:imageMessage, :mimetype)
|
||||
when 'sticker'
|
||||
@raw_message.dig(:message, :stickerMessage, :mimetype)
|
||||
msg.dig(:stickerMessage, :mimetype)
|
||||
when 'video'
|
||||
@raw_message.dig(:message, :videoMessage, :mimetype)
|
||||
msg.dig(:videoMessage, :mimetype)
|
||||
when 'audio'
|
||||
@raw_message.dig(:message, :audioMessage, :mimetype)
|
||||
msg.dig(:audioMessage, :mimetype)
|
||||
when 'file'
|
||||
@raw_message.dig(:message, :documentMessage, :mimetype).presence ||
|
||||
@raw_message.dig(:message, :documentWithCaptionMessage, :message, :documentMessage, :mimetype)
|
||||
msg.dig(:documentMessage, :mimetype).presence ||
|
||||
msg.dig(:documentWithCaptionMessage, :message, :documentMessage, :mimetype)
|
||||
end
|
||||
end
|
||||
|
||||
def phone_number_from_jid
|
||||
reference_field = jid_type == 'lid' ? :senderPn : :remoteJid
|
||||
def extract_from_jid(type:)
|
||||
addressing_mode = @raw_message[:key][:addressingMode]
|
||||
reference_field = addressing_mode && addressing_mode != type ? :remoteJidAlt : :remoteJid
|
||||
|
||||
jid = @raw_message[:key][reference_field]
|
||||
return unless jid
|
||||
|
||||
# NOTE: jid shape is `<user>_<agent>:<device>@<server>`
|
||||
# https://github.com/WhiskeySockets/Baileys/blob/v6.7.16/src/WABinary/jid-utils.ts#L19
|
||||
# https://github.com/WhiskeySockets/Baileys/blob/v7.0.0-rc.6/src/WABinary/jid-utils.ts#L52
|
||||
jid.split('@').first.split(':').first.split('_').first
|
||||
end
|
||||
|
||||
@ -136,12 +156,17 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
name = @raw_message[:verifiedBizName].presence || @raw_message[:pushName]
|
||||
return name if name.presence && (self_message? || incoming?)
|
||||
|
||||
phone_number_from_jid
|
||||
extract_from_jid(type: 'pn') || extract_from_jid(type: 'lid')
|
||||
end
|
||||
|
||||
def self_message?
|
||||
# TODO: Handle denormalized Brazilian phone numbers
|
||||
phone_number_from_jid == inbox.channel.phone_number.delete('+')
|
||||
normalize_phone_number(extract_from_jid(type: 'pn')) == normalize_phone_number(inbox.channel.phone_number.delete('+'))
|
||||
end
|
||||
|
||||
def normalize_phone_number(phone_number)
|
||||
return unless phone_number
|
||||
|
||||
Whatsapp::PhoneNormalizers::BrazilPhoneNormalizer.new.normalize(phone_number)
|
||||
end
|
||||
|
||||
def ignore_message?
|
||||
@ -162,22 +187,23 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
# TODO: Current logic will never update the contact avatar if their profile picture changes on WhatsApp.
|
||||
return if @contact.avatar.attached?
|
||||
|
||||
profile_pic_url = fetch_profile_picture_url(phone_number_from_jid)
|
||||
phone = extract_from_jid(type: 'pn')
|
||||
profile_pic_url = fetch_profile_picture_url(phone) if phone
|
||||
::Avatar::AvatarFromUrlJob.perform_later(@contact, profile_pic_url) if profile_pic_url
|
||||
end
|
||||
|
||||
def message_under_process?
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: raw_message_id)
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
|
||||
Redis::Alfred.get(key)
|
||||
end
|
||||
|
||||
def cache_message_source_id_in_redis
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: raw_message_id)
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
|
||||
::Redis::Alfred.setex(key, true)
|
||||
end
|
||||
|
||||
def clear_message_source_id_from_redis
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: raw_message_id)
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
|
||||
::Redis::Alfred.delete(key)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module Whatsapp::BaileysHandlers::MessagesUpsert
|
||||
module Whatsapp::BaileysHandlers::MessagesUpsert # rubocop:disable Metrics/ModuleLength
|
||||
include Whatsapp::BaileysHandlers::Helpers
|
||||
include BaileysHelper
|
||||
|
||||
@ -20,9 +20,9 @@ module Whatsapp::BaileysHandlers::MessagesUpsert
|
||||
end
|
||||
end
|
||||
|
||||
def handle_message # rubocop:disable Metrics/CyclomaticComplexity
|
||||
def handle_message
|
||||
return unless %w[lid user].include?(jid_type)
|
||||
return if jid_type == 'lid' && !phone_number_from_jid
|
||||
return unless extract_from_jid(type: 'lid')
|
||||
return if ignore_message?
|
||||
return if find_message_by_source_id(raw_message_id) || message_under_process?
|
||||
|
||||
@ -42,27 +42,51 @@ module Whatsapp::BaileysHandlers::MessagesUpsert
|
||||
end
|
||||
|
||||
def set_contact
|
||||
phone = extract_from_jid(type: 'pn')
|
||||
source_id = extract_from_jid(type: 'lid')
|
||||
identifier = "#{source_id}@lid"
|
||||
|
||||
update_existing_contact_inbox(phone, source_id, identifier) if phone
|
||||
|
||||
contact_inbox = ::ContactInboxWithContactBuilder.new(
|
||||
# FIXME: update the source_id to use sender LID
|
||||
source_id: phone_number_from_jid,
|
||||
source_id: source_id,
|
||||
inbox: inbox,
|
||||
contact_attributes: { name: contact_name, phone_number: "+#{phone_number_from_jid}", identifier: sender_lid }
|
||||
contact_attributes: { name: contact_name, phone_number: ("+#{phone}" if phone), identifier: identifier }
|
||||
).perform
|
||||
|
||||
@contact_inbox = contact_inbox
|
||||
@contact = contact_inbox.contact
|
||||
|
||||
update_contact_information
|
||||
update_contact_info(phone, source_id, identifier)
|
||||
end
|
||||
|
||||
def update_contact_information
|
||||
updates = {}
|
||||
updates[:identifier] = sender_lid if @contact.identifier.blank?
|
||||
updates[:phone_number] = "+#{phone_number_from_jid}" if @contact.phone_number.blank?
|
||||
updates[:name] = contact_name if @contact.name == phone_number_from_jid || @contact.name == sender_lid
|
||||
def update_existing_contact_inbox(phone, source_id, identifier)
|
||||
# NOTE: This is useful when we create a new contact manually, so we don't have information about contact LID;
|
||||
# With this, when we receive a message from that contact, we can link it properly.
|
||||
existing_contact_inbox = inbox.contact_inboxes.find_by(source_id: phone)
|
||||
return unless existing_contact_inbox
|
||||
return if inbox.contact_inboxes.exists?(source_id: source_id)
|
||||
|
||||
@contact.update!(updates) if updates.present?
|
||||
existing_contact = existing_contact_inbox.contact
|
||||
conflicting_identifier = inbox.account.contacts.find_by(identifier: identifier)
|
||||
conflicting_phone = inbox.account.contacts.find_by(phone_number: "+#{phone}")
|
||||
|
||||
return if conflicting_identifier && conflicting_identifier.id != existing_contact.id
|
||||
return if conflicting_phone && conflicting_phone.id != existing_contact.id
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
existing_contact_inbox.update!(source_id: source_id)
|
||||
existing_contact.update!(identifier: identifier, phone_number: "+#{phone}")
|
||||
end
|
||||
end
|
||||
|
||||
def update_contact_info(phone, source_id, identifier)
|
||||
update_params = {}
|
||||
update_params[:phone_number] = "+#{phone}" if phone
|
||||
update_params[:identifier] = identifier
|
||||
update_params[:name] = contact_name if @contact.name.in?([phone, source_id, identifier])
|
||||
|
||||
@contact.update!(update_params) if update_params.present?
|
||||
try_update_contact_avatar
|
||||
end
|
||||
|
||||
@ -91,10 +115,13 @@ module Whatsapp::BaileysHandlers::MessagesUpsert
|
||||
|
||||
def message_content_attributes
|
||||
type = message_type
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
content_attributes = { external_created_at: baileys_extract_message_timestamp(@raw_message[:messageTimestamp]) }
|
||||
if type == 'reaction'
|
||||
content_attributes[:in_reply_to_external_id] = @raw_message.dig(:message, :reactionMessage, :key, :id)
|
||||
content_attributes[:in_reply_to_external_id] = msg.dig(:reactionMessage, :key, :id)
|
||||
content_attributes[:is_reaction] = true
|
||||
elsif reply_to_message_id
|
||||
content_attributes[:in_reply_to_external_id] = reply_to_message_id
|
||||
elsif type == 'unsupported'
|
||||
content_attributes[:is_unsupported] = true
|
||||
end
|
||||
@ -104,13 +131,14 @@ module Whatsapp::BaileysHandlers::MessagesUpsert
|
||||
|
||||
def handle_attach_media
|
||||
attachment_file = download_attachment_file
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
|
||||
attachment = @message.attachments.build(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_content_type.to_s,
|
||||
file: { io: attachment_file, filename: filename, content_type: message_mimetype }
|
||||
)
|
||||
attachment.meta = { is_recorded_audio: true } if @raw_message.dig(:message, :audioMessage, :ptt)
|
||||
attachment.meta = { is_recorded_audio: true } if msg.dig(:audioMessage, :ptt)
|
||||
rescue Down::Error => e
|
||||
@message.update!(is_unsupported: true)
|
||||
|
||||
@ -122,7 +150,8 @@ module Whatsapp::BaileysHandlers::MessagesUpsert
|
||||
end
|
||||
|
||||
def filename
|
||||
filename = @raw_message.dig(:message, :documentMessage, :fileName)
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
filename = msg.dig(:documentMessage, :fileName)
|
||||
return filename if filename.present?
|
||||
|
||||
ext = ".#{message_mimetype.split(';').first.split('/').last}" if message_mimetype.present?
|
||||
|
||||
@ -55,9 +55,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
true
|
||||
end
|
||||
|
||||
def send_message(phone_number, message)
|
||||
def send_message(recipient_id, message)
|
||||
@message = message
|
||||
@phone_number = phone_number
|
||||
@recipient_id = recipient_id
|
||||
|
||||
if message.content_attributes[:is_reaction]
|
||||
@message_content = reaction_message_content
|
||||
@ -94,8 +94,8 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def toggle_typing_status(typing_status, phone_number:, **)
|
||||
@phone_number = phone_number
|
||||
def toggle_typing_status(typing_status, recipient_id:, **)
|
||||
@recipient_id = recipient_id
|
||||
status_map = {
|
||||
Events::Types::CONVERSATION_TYPING_ON => 'composing',
|
||||
Events::Types::CONVERSATION_RECORDING => 'recording',
|
||||
@ -136,8 +136,8 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
true
|
||||
end
|
||||
|
||||
def read_messages(messages, phone_number:, **)
|
||||
@phone_number = phone_number
|
||||
def read_messages(messages, recipient_id:, **)
|
||||
@recipient_id = recipient_id
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/read-messages",
|
||||
@ -158,8 +158,8 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
true
|
||||
end
|
||||
|
||||
def unread_message(phone_number, message) # rubocop:disable Metrics/MethodLength
|
||||
@phone_number = phone_number
|
||||
def unread_message(recipient_id, message) # rubocop:disable Metrics/MethodLength
|
||||
@recipient_id = recipient_id
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/chat-modify",
|
||||
@ -185,8 +185,8 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
true
|
||||
end
|
||||
|
||||
def received_messages(phone_number, messages)
|
||||
@phone_number = phone_number
|
||||
def received_messages(recipient_id, messages)
|
||||
@recipient_id = recipient_id
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/send-receipts",
|
||||
@ -220,8 +220,8 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
response.parsed_response
|
||||
end
|
||||
|
||||
def on_whatsapp(phone_number)
|
||||
@phone_number = phone_number
|
||||
def on_whatsapp(recipient_id)
|
||||
@recipient_id = recipient_id
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/on-whatsapp",
|
||||
@ -233,7 +233,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
|
||||
raise ProviderUnavailableError unless process_response(response)
|
||||
|
||||
response.parsed_response&.first || { 'jid' => remote_jid, 'exists' => false, 'lid' => nil }
|
||||
response.parsed_response&.first || { 'jid' => remote_jid, 'exists' => false }
|
||||
end
|
||||
|
||||
private
|
||||
@ -303,9 +303,10 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
response.success?
|
||||
end
|
||||
|
||||
# FIXME: Make this a function with argument, instead of using instance variable
|
||||
def remote_jid
|
||||
"#{@phone_number.delete('+')}@s.whatsapp.net"
|
||||
return @recipient_id if @recipient_id.ends_with?('@lid')
|
||||
|
||||
"#{@recipient_id.delete('+')}@s.whatsapp.net"
|
||||
end
|
||||
|
||||
def update_external_created_at(response)
|
||||
|
||||
@ -80,7 +80,7 @@ class Whatsapp::Providers::WhatsappZapiService < Whatsapp::Providers::BaseServic
|
||||
response.parsed_response['value']
|
||||
end
|
||||
|
||||
def read_messages(messages, phone_number:, **)
|
||||
def read_messages(messages, recipient_id:, **)
|
||||
# NOTE: Z-API will handle marking previous messages as read.
|
||||
last_message = messages.last
|
||||
|
||||
@ -88,7 +88,7 @@ class Whatsapp::Providers::WhatsappZapiService < Whatsapp::Providers::BaseServic
|
||||
"#{api_instance_path_with_token}/read-message",
|
||||
headers: api_headers,
|
||||
body: {
|
||||
phone: phone_number.delete('+'),
|
||||
phone: recipient_id.delete('+'),
|
||||
messageId: last_message.source_id
|
||||
}.to_json
|
||||
)
|
||||
|
||||
@ -51,14 +51,10 @@ class Whatsapp::SendOnWhatsappService < Base::SendOnChannelService
|
||||
end
|
||||
|
||||
def recipient_id
|
||||
return message.conversation.contact_inbox.source_id unless channel.provider == 'zapi'
|
||||
return message.conversation.contact_inbox.source_id unless %w[baileys zapi].include?(channel.provider)
|
||||
|
||||
if message.conversation.contact.phone_number.present?
|
||||
message.conversation.contact.phone_number.gsub(/[^\d]/, '')
|
||||
else
|
||||
# NOTE: `identifier` is the WhatsApp LID format. See `Whatsapp::ZapiHandlers::ReceivedCallback#set_contact`
|
||||
message.conversation.contact.identifier
|
||||
end
|
||||
# NOTE: `identifier` must be in the WhatsApp LID format
|
||||
message.conversation.contact.phone_number&.gsub(/[^\d]/, '') || message.conversation.contact.identifier
|
||||
end
|
||||
|
||||
def template_params
|
||||
|
||||
@ -12,17 +12,17 @@ module Whatsapp::ZapiHandlers::Helpers
|
||||
end
|
||||
|
||||
def cache_message_source_id_in_redis
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: raw_message_id)
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
|
||||
Redis::Alfred.setex(key, true)
|
||||
end
|
||||
|
||||
def clear_message_source_id_from_redis
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: raw_message_id)
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
|
||||
Redis::Alfred.delete(key)
|
||||
end
|
||||
|
||||
def message_under_process?
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: raw_message_id)
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
|
||||
Redis::Alfred.get(key)
|
||||
end
|
||||
end
|
||||
|
||||
@ -184,9 +184,10 @@ RSpec.describe Channel::Whatsapp do
|
||||
let(:conversation) { create(:conversation) }
|
||||
|
||||
it 'calls provider service method' do
|
||||
message = create(:message, conversation: conversation)
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, toggle_typing_status: nil)
|
||||
allow(provider_double).to receive(:toggle_typing_status)
|
||||
.with(Events::Types::CONVERSATION_TYPING_ON, phone_number: conversation.contact.phone_number)
|
||||
.with(Events::Types::CONVERSATION_TYPING_ON, recipient_id: conversation.contact.identifier, last_message: message)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||
.with(whatsapp_channel: channel)
|
||||
.and_return(provider_double)
|
||||
@ -242,11 +243,11 @@ RSpec.describe Channel::Whatsapp do
|
||||
create(:channel_whatsapp, provider: 'baileys', provider_config: { mark_as_read: true }, validate_provider_config: false, sync_templates: false)
|
||||
end
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:message) { create(:message) }
|
||||
let(:message) { create(:message, conversation: conversation) }
|
||||
|
||||
it 'calls provider service method' do
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
||||
allow(provider_double).to receive(:read_messages).with([message], phone_number: conversation.contact.phone_number)
|
||||
allow(provider_double).to receive(:read_messages).with([message], recipient_id: conversation.contact.identifier)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||
.with(whatsapp_channel: channel)
|
||||
.and_return(provider_double)
|
||||
@ -259,7 +260,7 @@ RSpec.describe Channel::Whatsapp do
|
||||
it 'call method when the provider config mark_as_read is nil' do
|
||||
channel.update!(provider_config: {})
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
||||
allow(provider_double).to receive(:read_messages).with([message], phone_number: conversation.contact.phone_number)
|
||||
allow(provider_double).to receive(:read_messages).with([message], recipient_id: conversation.contact.identifier)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||
.with(whatsapp_channel: channel)
|
||||
.and_return(provider_double)
|
||||
@ -332,7 +333,7 @@ RSpec.describe Channel::Whatsapp do
|
||||
|
||||
it 'calls provider service method' do
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, received_messages: nil)
|
||||
allow(provider_double).to receive(:received_messages).with(conversation.contact.phone_number, messages)
|
||||
allow(provider_double).to receive(:received_messages).with(conversation.contact.identifier, messages)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||
.with(whatsapp_channel: channel)
|
||||
.and_return(provider_double)
|
||||
@ -415,4 +416,51 @@ RSpec.describe Channel::Whatsapp do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_connection_data' do
|
||||
let(:channel) do
|
||||
create(:channel_whatsapp, provider: 'baileys', validate_provider_config: false, sync_templates: false,
|
||||
provider_connection: {
|
||||
'connection' => 'open',
|
||||
'qr_data_url' => 'data:image/png;base64,test',
|
||||
'error' => 'some_error'
|
||||
})
|
||||
end
|
||||
|
||||
context 'when user is an administrator' do
|
||||
it 'includes qr_data_url and error in the response' do
|
||||
account_user = create(:account_user, account: channel.account, role: :administrator)
|
||||
allow(Current).to receive(:account_user).and_return(account_user)
|
||||
|
||||
data = channel.provider_connection_data
|
||||
|
||||
expect(data).to eq({
|
||||
connection: 'open',
|
||||
qr_data_url: 'data:image/png;base64,test',
|
||||
error: 'some_error'
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not an administrator' do
|
||||
it 'excludes qr_data_url and error from the response' do
|
||||
account_user = create(:account_user, account: channel.account, role: :agent)
|
||||
allow(Current).to receive(:account_user).and_return(account_user)
|
||||
|
||||
data = channel.provider_connection_data
|
||||
|
||||
expect(data).to eq({ connection: 'open' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Current.account_user is nil' do
|
||||
it 'excludes qr_data_url and error from the response' do
|
||||
allow(Current).to receive(:account_user).and_return(nil)
|
||||
|
||||
data = channel.provider_connection_data
|
||||
|
||||
expect(data).to eq({ connection: 'open' })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
319
spec/services/whatsapp/baileys_handlers/messages_upsert_spec.rb
Normal file
319
spec/services/whatsapp/baileys_handlers/messages_upsert_spec.rb
Normal file
@ -0,0 +1,319 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::BaileysHandlers::MessagesUpsert do
|
||||
let(:webhook_verify_token) { 'valid_token' }
|
||||
let!(:whatsapp_channel) do
|
||||
create(:channel_whatsapp,
|
||||
provider: 'baileys',
|
||||
provider_config: { webhook_verify_token: webhook_verify_token },
|
||||
validate_provider_config: false,
|
||||
received_messages: false)
|
||||
end
|
||||
let(:inbox) { whatsapp_channel.inbox }
|
||||
let(:timestamp) { Time.current.to_i }
|
||||
|
||||
before do
|
||||
stub_request(:get, /profile-picture-url/)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { data: { profilePictureUrl: nil } }.to_json
|
||||
)
|
||||
end
|
||||
|
||||
describe '#update_existing_contact_inbox' do
|
||||
context 'when updating contact inbox with LID information' do
|
||||
let(:phone) { '5511912345678' }
|
||||
let(:lid) { '12345678' }
|
||||
let(:source_id) { lid }
|
||||
let(:identifier) { "#{lid}@lid" }
|
||||
|
||||
context 'when there is no conflict' do
|
||||
it 'updates existing contact_inbox source_id from phone to LID' do
|
||||
contact = create(:contact, account: inbox.account, phone_number: "+#{phone}", identifier: nil)
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: phone)
|
||||
|
||||
raw_message = {
|
||||
key: { id: 'msg_123', remoteJid: "#{lid}@lid", remoteJidAlt: "#{phone}@s.whatsapp.net", fromMe: false, addressingMode: 'lid' },
|
||||
pushName: 'John Doe',
|
||||
messageTimestamp: timestamp,
|
||||
message: { conversation: 'Hello' }
|
||||
}
|
||||
params = {
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: { type: 'notify', messages: [raw_message] }
|
||||
}
|
||||
|
||||
expect do
|
||||
Whatsapp::IncomingMessageBaileysService.new(inbox: inbox, params: params).perform
|
||||
end.not_to raise_error
|
||||
|
||||
expect(contact_inbox.reload.source_id).to eq(source_id)
|
||||
expect(contact.reload.identifier).to eq(identifier)
|
||||
expect(contact.phone_number).to eq("+#{phone}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when identifier is already taken by a different contact (race condition)' do
|
||||
it 'does not raise validation error and skips the update' do
|
||||
original_contact = create(:contact, account: inbox.account, phone_number: nil, identifier: nil, name: 'Original Contact')
|
||||
original_contact_inbox = create(:contact_inbox, inbox: inbox, contact: original_contact, source_id: phone)
|
||||
|
||||
conflicting_contact = create(:contact, account: inbox.account, phone_number: "+#{phone}", identifier: identifier,
|
||||
name: 'Conflicting Contact')
|
||||
create(:contact_inbox, inbox: inbox, contact: conflicting_contact, source_id: source_id)
|
||||
|
||||
raw_message = {
|
||||
key: { id: 'msg_123', remoteJid: "#{lid}@lid", remoteJidAlt: "#{phone}@s.whatsapp.net", fromMe: false, addressingMode: 'lid' },
|
||||
pushName: 'John Doe',
|
||||
messageTimestamp: timestamp,
|
||||
message: { conversation: 'Hello' }
|
||||
}
|
||||
params = {
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: { type: 'notify', messages: [raw_message] }
|
||||
}
|
||||
|
||||
expect do
|
||||
Whatsapp::IncomingMessageBaileysService.new(inbox: inbox, params: params).perform
|
||||
end.not_to raise_error
|
||||
|
||||
expect(original_contact_inbox.reload.source_id).to eq(phone)
|
||||
expect(original_contact.reload.identifier).to be_nil
|
||||
|
||||
message = inbox.messages.last
|
||||
expect(message).to be_present
|
||||
expect(message.sender).to eq(conflicting_contact)
|
||||
expect(message.conversation.contact).to eq(conflicting_contact)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when phone number is already taken by a different contact (race condition)' do
|
||||
it 'does not raise validation error and skips the update' do
|
||||
original_contact = create(:contact, account: inbox.account, phone_number: nil, identifier: nil)
|
||||
create(:contact_inbox, inbox: inbox, contact: original_contact, source_id: phone)
|
||||
|
||||
different_lid = '87654321'
|
||||
different_identifier = "#{different_lid}@lid"
|
||||
conflicting_contact = create(:contact, account: inbox.account, phone_number: "+#{phone}", identifier: different_identifier)
|
||||
create(:contact_inbox, inbox: inbox, contact: conflicting_contact, source_id: different_lid)
|
||||
|
||||
raw_message = {
|
||||
key: { id: 'msg_123', remoteJid: "#{lid}@lid", remoteJidAlt: "#{phone}@s.whatsapp.net", fromMe: false, addressingMode: 'lid' },
|
||||
pushName: 'John Doe',
|
||||
messageTimestamp: timestamp,
|
||||
message: { conversation: 'Hello' }
|
||||
}
|
||||
params = {
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: { type: 'notify', messages: [raw_message] }
|
||||
}
|
||||
|
||||
expect do
|
||||
Whatsapp::IncomingMessageBaileysService.new(inbox: inbox, params: params).perform
|
||||
end.not_to raise_error
|
||||
|
||||
expect(original_contact.reload.phone_number).to be_nil
|
||||
expect(original_contact.identifier).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when LID contact_inbox already exists (race condition)' do
|
||||
it 'does not raise unique constraint error and skips the update' do
|
||||
original_contact = create(:contact, account: inbox.account, phone_number: nil, identifier: nil)
|
||||
create(:contact_inbox, inbox: inbox, contact: original_contact, source_id: phone)
|
||||
|
||||
lid_contact = create(:contact, account: inbox.account, phone_number: "+#{phone}", identifier: identifier)
|
||||
create(:contact_inbox, inbox: inbox, contact: lid_contact, source_id: source_id)
|
||||
|
||||
raw_message = {
|
||||
key: { id: 'msg_123', remoteJid: "#{lid}@lid", remoteJidAlt: "#{phone}@s.whatsapp.net", fromMe: false, addressingMode: 'lid' },
|
||||
pushName: 'John Doe',
|
||||
messageTimestamp: timestamp,
|
||||
message: { conversation: 'Hello' }
|
||||
}
|
||||
params = {
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: { type: 'notify', messages: [raw_message] }
|
||||
}
|
||||
|
||||
expect do
|
||||
Whatsapp::IncomingMessageBaileysService.new(inbox: inbox, params: params).perform
|
||||
end.not_to raise_error
|
||||
|
||||
message = inbox.messages.last
|
||||
expect(message).to be_present
|
||||
expect(message.sender).to eq(lid_contact)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating the same contact (no conflict)' do
|
||||
it 'successfully updates the contact' do
|
||||
contact = create(:contact, account: inbox.account, phone_number: "+#{phone}", identifier: nil)
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: phone)
|
||||
|
||||
raw_message = {
|
||||
key: { id: 'msg_123', remoteJid: "#{lid}@lid", remoteJidAlt: "#{phone}@s.whatsapp.net", fromMe: false, addressingMode: 'lid' },
|
||||
pushName: 'John Doe',
|
||||
messageTimestamp: timestamp,
|
||||
message: { conversation: 'Hello' }
|
||||
}
|
||||
params = {
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: { type: 'notify', messages: [raw_message] }
|
||||
}
|
||||
|
||||
expect do
|
||||
Whatsapp::IncomingMessageBaileysService.new(inbox: inbox, params: params).perform
|
||||
end.not_to raise_error
|
||||
|
||||
expect(contact_inbox.reload.source_id).to eq(source_id)
|
||||
expect(contact.reload.identifier).to eq(identifier)
|
||||
expect(contact.phone_number).to eq("+#{phone}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'ephemeral message handling' do
|
||||
let(:phone) { '5511912345678' }
|
||||
let(:lid) { '12345678' }
|
||||
|
||||
context 'when receiving an ephemeral text message' do
|
||||
it 'correctly unwraps and processes the message' do
|
||||
raw_message = {
|
||||
key: { id: 'msg_ephemeral_123', remoteJid: "#{phone}@s.whatsapp.net", remoteJidAlt: "#{lid}@lid", fromMe: false,
|
||||
addressingMode: 'pn' },
|
||||
pushName: 'Gabriel',
|
||||
messageTimestamp: timestamp,
|
||||
message: {
|
||||
messageContextInfo: {
|
||||
deviceListMetadata: {},
|
||||
deviceListMetadataVersion: 2
|
||||
},
|
||||
ephemeralMessage: {
|
||||
message: {
|
||||
extendedTextMessage: {
|
||||
text: 'This is a disappearing message',
|
||||
contextInfo: {
|
||||
expiration: 604_800,
|
||||
disappearingMode: { initiator: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params = {
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: { type: 'notify', messages: [raw_message] }
|
||||
}
|
||||
|
||||
expect do
|
||||
Whatsapp::IncomingMessageBaileysService.new(inbox: inbox, params: params).perform
|
||||
end.to change(inbox.messages, :count).by(1)
|
||||
|
||||
message = inbox.messages.last
|
||||
expect(message.content).to eq('This is a disappearing message')
|
||||
expect(message.message_type).to eq('incoming')
|
||||
expect(message.is_unsupported).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when receiving an ephemeral image message' do
|
||||
it 'correctly unwraps and processes the message with media' do
|
||||
raw_message = {
|
||||
key: { id: 'msg_ephemeral_image_123', remoteJid: "#{phone}@s.whatsapp.net", remoteJidAlt: "#{lid}@lid", fromMe: false,
|
||||
addressingMode: 'pn' },
|
||||
pushName: 'Gabriel',
|
||||
messageTimestamp: timestamp,
|
||||
message: {
|
||||
messageContextInfo: {
|
||||
deviceListMetadata: {},
|
||||
deviceListMetadataVersion: 2
|
||||
},
|
||||
ephemeralMessage: {
|
||||
message: {
|
||||
imageMessage: {
|
||||
caption: 'Check this out',
|
||||
mimetype: 'image/jpeg',
|
||||
url: 'https://example.com/image.jpg'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params = {
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: { type: 'notify', messages: [raw_message] }
|
||||
}
|
||||
|
||||
stub_request(:get, whatsapp_channel.media_url('msg_ephemeral_image_123'))
|
||||
.to_return(status: 200, body: 'fake image data')
|
||||
|
||||
expect do
|
||||
Whatsapp::IncomingMessageBaileysService.new(inbox: inbox, params: params).perform
|
||||
end.to change(inbox.messages, :count).by(1)
|
||||
|
||||
message = inbox.messages.last
|
||||
expect(message.content).to eq('Check this out')
|
||||
expect(message.message_type).to eq('incoming')
|
||||
expect(message.is_unsupported).to be_falsey
|
||||
expect(message.attachments.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when receiving an ephemeral reaction message' do
|
||||
it 'correctly unwraps and processes the reaction' do
|
||||
# First create the original message
|
||||
contact = create(:contact, account: inbox.account, phone_number: "+#{phone}", identifier: "#{lid}@lid")
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: lid)
|
||||
conversation = create(:conversation, inbox: inbox, contact_inbox: contact_inbox)
|
||||
original_message = create(:message, inbox: inbox, conversation: conversation, source_id: 'original_msg_id')
|
||||
|
||||
raw_message = {
|
||||
key: { id: 'msg_ephemeral_reaction_123', remoteJid: "#{phone}@s.whatsapp.net", remoteJidAlt: "#{lid}@lid", fromMe: false,
|
||||
addressingMode: 'pn' },
|
||||
pushName: 'Gabriel',
|
||||
messageTimestamp: timestamp,
|
||||
message: {
|
||||
messageContextInfo: {
|
||||
deviceListMetadata: {},
|
||||
deviceListMetadataVersion: 2
|
||||
},
|
||||
ephemeralMessage: {
|
||||
message: {
|
||||
reactionMessage: {
|
||||
text: '👍',
|
||||
key: { id: 'original_msg_id' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params = {
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: { type: 'notify', messages: [raw_message] }
|
||||
}
|
||||
|
||||
expect do
|
||||
Whatsapp::IncomingMessageBaileysService.new(inbox: inbox, params: params).perform
|
||||
end.to change(conversation.messages, :count).by(1)
|
||||
|
||||
reaction = conversation.messages.last
|
||||
expect(reaction.content).to eq('👍')
|
||||
expect(reaction.message_type).to eq('incoming')
|
||||
expect(reaction.content_attributes['is_reaction']).to be_truthy
|
||||
expect(reaction.in_reply_to).to eq(original_message.id)
|
||||
expect(reaction.in_reply_to_external_id).to eq(original_message.source_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -171,7 +171,7 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
let(:timestamp) { Time.current.to_i }
|
||||
let(:raw_message) do
|
||||
{
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false, senderLid: '12345678@lid' },
|
||||
key: { id: 'msg_123', remoteJid: '12345678@lid', remoteJidAlt: '5511912345678@s.whatsapp.net', fromMe: false, addressingMode: 'lid' },
|
||||
pushName: 'John Doe',
|
||||
messageTimestamp: timestamp,
|
||||
message: { conversation: 'Hello from Baileys' }
|
||||
@ -360,23 +360,6 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
expect(message.message_type).to eq('outgoing')
|
||||
end
|
||||
|
||||
it 'creates an outgoing self message' do
|
||||
self_jid = "#{whatsapp_channel.phone_number.delete('+')}@s.whatsapp.net"
|
||||
raw_message[:key][:remoteJid] = self_jid
|
||||
raw_message[:key][:fromMe] = true
|
||||
create(:account_user, account: inbox.account)
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
conversation = inbox.conversations.last
|
||||
message = conversation.messages.last
|
||||
|
||||
expect(message).to be_present
|
||||
expect(message.content).to eq('Hello from Baileys')
|
||||
expect(conversation.contact.name).to eq('John Doe')
|
||||
expect(message.message_type).to eq('outgoing')
|
||||
end
|
||||
|
||||
it 'updates the contact name when name is the phone number and message has a pushName' do
|
||||
create(:contact, account: inbox.account, name: '5511912345678')
|
||||
|
||||
@ -396,6 +379,23 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
expect(conversation.contact.name).to eq('Verified John')
|
||||
end
|
||||
|
||||
it 'creates contact with LID only when phone number is not present' do
|
||||
raw_message[:key][:remoteJid] = '87654321@lid'
|
||||
raw_message[:key][:remoteJidAlt] = nil
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
conversation = inbox.conversations.last
|
||||
contact = conversation.contact
|
||||
contact_inbox = conversation.contact_inbox
|
||||
|
||||
expect(contact).to be_present
|
||||
expect(contact.phone_number).to be_nil
|
||||
expect(contact.identifier).to eq('87654321@lid')
|
||||
expect(contact.name).to eq('John Doe')
|
||||
expect(contact_inbox.source_id).to eq('87654321')
|
||||
end
|
||||
|
||||
it 'creates contact with phone number as name on outgoing message' do
|
||||
raw_message[:key][:fromMe] = true
|
||||
create(:account_user, account: inbox.account)
|
||||
@ -416,9 +416,23 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
expect(conversation.contact.name).to eq('5511912345678')
|
||||
end
|
||||
|
||||
it 'migrates existing phone-based contact inbox to LID-based when receiving message with LID' do
|
||||
contact = create(:contact, account: inbox.account, name: 'Existing Contact', phone_number: '+5511912345678')
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
contact_inbox.reload
|
||||
contact.reload
|
||||
|
||||
expect(contact_inbox.source_id).to eq('12345678')
|
||||
expect(contact.identifier).to eq('12345678@lid')
|
||||
expect(contact.phone_number).to eq('+5511912345678')
|
||||
end
|
||||
|
||||
it 'creates a message on an existing conversation' do
|
||||
contact = create(:contact, account: inbox.account, name: 'John Doe')
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '12345678')
|
||||
existing_conversation = create(:conversation, inbox: inbox, contact_inbox: contact_inbox)
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
@ -476,7 +490,7 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
context 'when message type is reaction' do
|
||||
let!(:message) do
|
||||
contact = create(:contact, account: inbox.account, name: '5511912345678')
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '12345678')
|
||||
conversation = create(:conversation, inbox: inbox, contact_inbox: contact_inbox)
|
||||
create(:message, inbox: inbox, conversation: conversation, source_id: 'msg_123')
|
||||
end
|
||||
@ -485,7 +499,7 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
raw_message[:key][:id] = 'reaction_123'
|
||||
raw_message[:message] = {
|
||||
reactionMessage: {
|
||||
key: { remoteJid: '5511912345678@s.whatsapp.net', fromMe: true, id: 'msg_123' },
|
||||
key: { remoteJid: '12345678@lid', fromMe: true, id: 'msg_123' },
|
||||
text: '👍'
|
||||
}
|
||||
}
|
||||
@ -502,7 +516,7 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
raw_message[:key][:id] = 'reaction_123'
|
||||
raw_message[:message] = {
|
||||
reactionMessage: {
|
||||
key: { remoteJid: '5511912345678@s.whatsapp.net', fromMe: true, id: 'msg_123' },
|
||||
key: { remoteJid: '12345678@lid', fromMe: true, id: 'msg_123' },
|
||||
text: ''
|
||||
}
|
||||
}
|
||||
@ -522,7 +536,8 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
type: 'notify',
|
||||
messages: [
|
||||
{
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
|
||||
key: { id: 'msg_123', remoteJid: '12345678@lid', remoteJidAlt: '5511912345678@s.whatsapp.net', fromMe: false,
|
||||
addressingMode: 'lid' },
|
||||
message: { imageMessage: { caption: 'Hello from Baileys', mimetype: 'image/png' } },
|
||||
pushName: 'John Doe'
|
||||
}
|
||||
@ -574,7 +589,8 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
type: 'notify',
|
||||
messages: [
|
||||
{
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
|
||||
key: { id: 'msg_123', remoteJid: '12345678@lid', remoteJidAlt: '5511912345678@s.whatsapp.net', fromMe: false,
|
||||
addressingMode: 'lid' },
|
||||
message: { videoMessage: { caption: 'Hello from Baileys', mimetype: 'video/mp4' } },
|
||||
pushName: 'John Doe'
|
||||
}
|
||||
@ -617,7 +633,8 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
type: 'notify',
|
||||
messages: [
|
||||
{
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
|
||||
key: { id: 'msg_123', remoteJid: '12345678@lid', remoteJidAlt: '5511912345678@s.whatsapp.net', fromMe: false,
|
||||
addressingMode: 'lid' },
|
||||
message: { documentMessage: { fileName: filename } },
|
||||
pushName: 'John Doe'
|
||||
}
|
||||
@ -668,7 +685,8 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
type: 'notify',
|
||||
messages: [
|
||||
{
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
|
||||
key: { id: 'msg_123', remoteJid: '12345678@lid', remoteJidAlt: '5511912345678@s.whatsapp.net', fromMe: false,
|
||||
addressingMode: 'lid' },
|
||||
message: { audioMessage: { mimetype: 'audio/opus' } },
|
||||
pushName: 'John Doe'
|
||||
}
|
||||
@ -699,7 +717,8 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
type: 'notify',
|
||||
messages: [
|
||||
{
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
|
||||
key: { id: 'msg_123', remoteJid: '12345678@lid', remoteJidAlt: '5511912345678@s.whatsapp.net', fromMe: false,
|
||||
addressingMode: 'lid' },
|
||||
message: { stickerMessage: { mimetype: 'image/png' } },
|
||||
pushName: 'John Doe'
|
||||
}
|
||||
@ -724,13 +743,13 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
context 'when processing multiple messages' do
|
||||
it 'creates separate contacts and conversations for each message' do
|
||||
raw_message1 = {
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', remoteJidAlt: '12345678@lid', fromMe: false, addressingMode: 'pn' },
|
||||
pushName: 'John Doe',
|
||||
messageTimestamp: timestamp,
|
||||
message: { conversation: 'Hello from Baileys' }
|
||||
}
|
||||
raw_message2 = {
|
||||
key: { id: 'msg_124', remoteJid: '5511987654321@s.whatsapp.net', fromMe: false },
|
||||
key: { id: 'msg_124', remoteJid: '5511987654321@s.whatsapp.net', remoteJidAlt: '87654321@lid', fromMe: false, addressingMode: 'pn' },
|
||||
pushName: 'Jane Doe',
|
||||
messageTimestamp: timestamp,
|
||||
message: { conversation: 'Hello from another user' }
|
||||
@ -764,9 +783,10 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
end
|
||||
|
||||
context 'when jid type is lid' do
|
||||
it 'processes the message with phone number from lid jid type' do
|
||||
raw_message[:key][:remoteJid] = '12345678@lid'
|
||||
raw_message[:key][:senderPn] = '5511912345678@s.whatsapp.net'
|
||||
it 'processes the message with phone number from addressingMode pn' do
|
||||
raw_message[:key][:remoteJid] = '5511912345678@s.whatsapp.net'
|
||||
raw_message[:key][:remoteJidAlt] = '12345678@lid'
|
||||
raw_message[:key][:addressingMode] = 'pn'
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
@ -778,82 +798,90 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
end
|
||||
end
|
||||
|
||||
it 'skips message if senderPn is not present' do
|
||||
raw_message[:key][:remoteJid] = '12345678@lid'
|
||||
it 'skips message if LID cannot be extracted' do
|
||||
raw_message[:key][:remoteJid] = 'invalid_jid'
|
||||
raw_message[:key][:remoteJidAlt] = nil
|
||||
raw_message[:key][:addressingMode] = 'lid'
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(inbox.conversations).to be_empty
|
||||
end
|
||||
|
||||
context 'when handling senderLid field' do
|
||||
it 'creates contact with identifier from senderLid' do
|
||||
context 'when handling LID field' do
|
||||
it 'creates contact with LID as source_id and identifier' do
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
contact = inbox.contacts.last
|
||||
contact_inbox = inbox.contact_inboxes.last
|
||||
|
||||
expect(contact.identifier).to eq('12345678@lid')
|
||||
expect(contact_inbox.source_id).to eq('12345678')
|
||||
end
|
||||
|
||||
it 'updates contact identifier for existing contacts without identifier' do
|
||||
it 'updates existing contact_inbox from phone to LID source_id' do
|
||||
contact = create(:contact, account: inbox.account, phone_number: '+5511912345678', identifier: nil)
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
contact_inbox = inbox.contact_inboxes.find_by(contact: contact)
|
||||
expect(contact_inbox.source_id).to eq('12345678')
|
||||
expect(contact.reload.identifier).to eq('12345678@lid')
|
||||
expect(contact.phone_number).to eq('+5511912345678')
|
||||
end
|
||||
|
||||
it 'does not update contact identifier if already present' do
|
||||
contact = create(:contact, account: inbox.account, phone_number: '+5511912345678', identifier: 'existing@lid')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
it 'does not update contact_inbox if source_id is already LID' do
|
||||
contact = create(:contact, account: inbox.account, phone_number: '+5511912345678', identifier: '12345678@lid')
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '12345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(contact.reload.identifier).to eq('existing@lid')
|
||||
expect(contact_inbox.reload.source_id).to eq('12345678')
|
||||
expect(contact.reload.identifier).to eq('12345678@lid')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating contact information' do
|
||||
it 'updates contact phone number if blank' do
|
||||
contact = create(:contact, account: inbox.account, phone_number: nil, identifier: '12345678@lid')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '12345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(contact.reload.phone_number).to eq('+5511912345678')
|
||||
end
|
||||
|
||||
it 'does not update contact phone number if already present' do
|
||||
contact = create(:contact, account: inbox.account, phone_number: '+9999999999', identifier: '12345678@lid')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(contact.reload.phone_number).to eq('+9999999999')
|
||||
end
|
||||
|
||||
it 'updates contact name if it matches phone number' do
|
||||
contact = create(:contact, account: inbox.account, name: '5511912345678', phone_number: '+5511912345678', identifier: '12345678@lid')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '12345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(contact.reload.name).to eq('John Doe')
|
||||
end
|
||||
|
||||
it 'updates contact name if it matches sender LID' do
|
||||
it 'updates contact name if it matches LID source_id' do
|
||||
contact = create(:contact, account: inbox.account, name: '12345678', phone_number: '+5511912345678', identifier: '12345678@lid')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '12345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(contact.reload.name).to eq('John Doe')
|
||||
end
|
||||
|
||||
it 'updates contact name if it matches identifier' do
|
||||
contact = create(:contact, account: inbox.account, name: '12345678@lid', phone_number: '+5511912345678', identifier: '12345678@lid')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '12345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(contact.reload.name).to eq('John Doe')
|
||||
end
|
||||
|
||||
it 'does not update contact name if it is different from phone number and sender LID' do
|
||||
it 'does not update contact name if it is different from phone number, source_id, and identifier' do
|
||||
contact = create(:contact, account: inbox.account, name: 'Existing Name', phone_number: '+5511912345678', identifier: '12345678@lid')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
create(:contact_inbox, inbox: inbox, contact: contact, source_id: '12345678')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
@ -958,7 +986,7 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
end
|
||||
|
||||
def format_message_source_key(message_id)
|
||||
format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: message_id)
|
||||
format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{message_id}")
|
||||
end
|
||||
|
||||
def stub_download
|
||||
|
||||
@ -477,7 +477,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
body: { keys: [{ id: message.source_id, remoteJid: test_send_jid, fromMe: false }] }.to_json
|
||||
).to_return(status: 200, body: '', headers: {})
|
||||
|
||||
result = service.read_messages([message], phone_number: test_send_phone_number)
|
||||
result = service.read_messages([message], recipient_id: test_send_phone_number)
|
||||
|
||||
expect(result).to be(true)
|
||||
end
|
||||
@ -495,7 +495,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
.to_return(status: 200)
|
||||
|
||||
expect do
|
||||
service.read_messages([message], phone_number: test_send_phone_number)
|
||||
service.read_messages([message], recipient_id: test_send_phone_number)
|
||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||
end
|
||||
end
|
||||
@ -605,7 +605,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
)
|
||||
.to_return(status: 200)
|
||||
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, recipient_id: test_send_phone_number)
|
||||
|
||||
expect(request).to have_been_requested
|
||||
end
|
||||
@ -621,7 +621,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
)
|
||||
.to_return(status: 200)
|
||||
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_RECORDING, phone_number: test_send_phone_number)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_RECORDING, recipient_id: test_send_phone_number)
|
||||
|
||||
expect(request).to have_been_requested
|
||||
end
|
||||
@ -637,7 +637,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
)
|
||||
.to_return(status: 200)
|
||||
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_OFF, phone_number: test_send_phone_number)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_OFF, recipient_id: test_send_phone_number)
|
||||
|
||||
expect(request).to have_been_requested
|
||||
end
|
||||
@ -664,7 +664,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
allow(Rails.logger).to receive(:error)
|
||||
|
||||
expect do
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, recipient_id: test_send_phone_number)
|
||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||
|
||||
expect(Rails.logger).to have_received(:error).with('error message')
|
||||
@ -730,12 +730,12 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
.to_return(
|
||||
status: 200,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: [{ jid: "#{phone_number.delete('+')}@s.whatsapp.net", exists: true, lid: '123@lid' }].to_json
|
||||
body: [{ jid: "#{phone_number.delete('+')}@s.whatsapp.net", exists: true }].to_json
|
||||
)
|
||||
|
||||
response = service.on_whatsapp(phone_number)
|
||||
|
||||
expect(response).to eq({ 'jid' => "#{phone_number.delete('+')}@s.whatsapp.net", 'exists' => true, 'lid' => '123@lid' })
|
||||
expect(response).to eq({ 'jid' => "#{phone_number.delete('+')}@s.whatsapp.net", 'exists' => true })
|
||||
end
|
||||
|
||||
it 'returns default check response' do
|
||||
@ -749,7 +749,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
|
||||
response = service.on_whatsapp(phone_number)
|
||||
|
||||
expect(response).to eq({ 'jid' => "#{phone_number.delete('+')}@s.whatsapp.net", 'exists' => false, 'lid' => nil })
|
||||
expect(response).to eq({ 'jid' => "#{phone_number.delete('+')}@s.whatsapp.net", 'exists' => false })
|
||||
end
|
||||
end
|
||||
|
||||
@ -916,7 +916,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
|
||||
|
||||
expect do
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, recipient_id: test_send_phone_number)
|
||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||
|
||||
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
|
||||
|
||||
@ -208,7 +208,7 @@ describe Whatsapp::Providers::WhatsappZapiService do
|
||||
)
|
||||
.to_return(status: 200)
|
||||
|
||||
result = service.read_messages(messages, phone_number: "+#{test_send_phone_number}")
|
||||
result = service.read_messages(messages, recipient_id: "+#{test_send_phone_number}")
|
||||
|
||||
expect(result).to be(true)
|
||||
end
|
||||
@ -223,7 +223,7 @@ describe Whatsapp::Providers::WhatsappZapiService do
|
||||
allow(Rails.logger).to receive(:error)
|
||||
|
||||
expect do
|
||||
service.read_messages(messages, phone_number: "+#{test_send_phone_number}")
|
||||
service.read_messages(messages, recipient_id: "+#{test_send_phone_number}")
|
||||
end.to raise_error(Whatsapp::Providers::WhatsappZapiService::ProviderUnavailableError)
|
||||
end
|
||||
end
|
||||
|
||||
@ -397,8 +397,9 @@ describe Whatsapp::SendOnWhatsappService do
|
||||
end
|
||||
|
||||
it 'calls channel.send_message if channel is not locked on outgoing message' do
|
||||
conversation.contact.update!(phone_number: '+123456789')
|
||||
message = create(:message, message_type: :outgoing, content: 'test', conversation: conversation)
|
||||
allow(whatsapp_channel).to receive(:send_message).with(conversation.contact_inbox.source_id, message).and_return('123456789')
|
||||
allow(whatsapp_channel).to receive(:send_message).with('123456789', message).and_return('123456789')
|
||||
|
||||
described_class.new(message: message).perform
|
||||
|
||||
|
||||
@ -1217,6 +1217,6 @@ describe Whatsapp::ZapiHandlers::ReceivedCallback do
|
||||
private
|
||||
|
||||
def format_message_source_key(message_id)
|
||||
format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: message_id)
|
||||
format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{message_id}")
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user