diff --git a/.github/dashboard-screen.png b/.github/dashboard-screen.png
deleted file mode 100644
index 847ae1582..000000000
Binary files a/.github/dashboard-screen.png and /dev/null differ
diff --git a/.github/screenshots/dashboard-dark.png b/.github/screenshots/dashboard-dark.png
new file mode 100644
index 000000000..4d08b52b9
Binary files /dev/null and b/.github/screenshots/dashboard-dark.png differ
diff --git a/.github/screenshots/dashboard.png b/.github/screenshots/dashboard.png
new file mode 100644
index 000000000..b8b99be49
Binary files /dev/null and b/.github/screenshots/dashboard.png differ
diff --git a/.github/screenshots/header-dark.png b/.github/screenshots/header-dark.png
new file mode 100644
index 000000000..84931aee3
Binary files /dev/null and b/.github/screenshots/header-dark.png differ
diff --git a/.github/screenshots/header.png b/.github/screenshots/header.png
new file mode 100644
index 000000000..f10ca0faf
Binary files /dev/null and b/.github/screenshots/header.png differ
diff --git a/.github/workflows/frontend-fe.yml b/.github/workflows/frontend-fe.yml
index 5af4857e0..45ff25203 100644
--- a/.github/workflows/frontend-fe.yml
+++ b/.github/workflows/frontend-fe.yml
@@ -10,7 +10,7 @@ on:
jobs:
test:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/nightly_installer.yml b/.github/workflows/nightly_installer.yml
index a01ba1093..beef5727c 100644
--- a/.github/workflows/nightly_installer.yml
+++ b/.github/workflows/nightly_installer.yml
@@ -2,7 +2,7 @@
# #
# # Linux nightly installer action
# # This action will try to install and setup
-# # chatwoot on an Ubuntu 20.04 machine using
+# # chatwoot on an Ubuntu 22.04 machine using
# # the linux installer script.
# #
# # This is set to run daily at midnight.
diff --git a/.github/workflows/run_foss_spec.yml b/.github/workflows/run_foss_spec.yml
index 5a9d35d0b..385feddfc 100644
--- a/.github/workflows/run_foss_spec.yml
+++ b/.github/workflows/run_foss_spec.yml
@@ -9,7 +9,7 @@ on:
jobs:
test:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
services:
postgres:
image: pgvector/pgvector:pg15
diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml
index 724f69ec3..9be79da05 100644
--- a/.github/workflows/size-limit.yml
+++ b/.github/workflows/size-limit.yml
@@ -7,7 +7,7 @@ on:
jobs:
test:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
diff --git a/.gitignore b/.gitignore
index 77c4a4740..53deb62a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -91,3 +91,6 @@ yarn-debug.log*
# Vite uses dotenv and suggests to ignore local-only env files. See
# https://vitejs.dev/guide/env-and-mode.html#env-files
*.local
+
+# Claude.ai config file
+CLAUDE.md
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 1cc029441..2477dfffb 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -283,3 +283,4 @@ exclude:
- 'app/javascript/widget/assets/scss/sdk.css'
- 'app/assets/stylesheets/administrate/reset/_normalize.scss'
- 'app/javascript/shared/assets/stylesheets/*.scss'
+ - 'app/javascript/dashboard/assets/scss/_woot.scss'
diff --git a/Gemfile b/Gemfile
index fc1e483f6..fe180439f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -173,8 +173,11 @@ gem 'pgvector'
# Convert Website HTML to Markdown
gem 'reverse_markdown'
+gem 'iso-639'
gem 'ruby-openai'
+gem 'shopify_api'
+
### Gems required only in specific deployment environments ###
##############################################################
diff --git a/Gemfile.lock b/Gemfile.lock
index 729319f31..2baf17aa1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -371,6 +371,7 @@ GEM
ruby2ruby (~> 2.4)
ruby_parser (~> 3.10)
hana (1.3.7)
+ hash_diff (1.1.1)
hashdiff (1.1.2)
hashie (5.0.0)
html2text (0.4.0)
@@ -401,6 +402,8 @@ GEM
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
+ iso-639 (0.3.8)
+ csv
jbuilder (2.13.0)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
@@ -552,6 +555,9 @@ GEM
rack (>= 1.2, < 4)
snaky_hash (~> 2.0)
version_gem (~> 1.1)
+ oj (3.16.10)
+ bigdecimal (>= 3.0)
+ ostruct (>= 0.2)
omniauth (2.1.3)
hashie (>= 3.4.6)
rack (>= 2.2.3)
@@ -784,6 +790,17 @@ GEM
sentry-ruby (~> 5.22.4)
sidekiq (>= 3.0)
sexp_processor (4.17.3)
+ shopify_api (14.9.0)
+ activesupport
+ concurrent-ruby
+ hash_diff
+ httparty
+ jwt
+ oj
+ openssl
+ securerandom
+ sorbet-runtime
+ zeitwerk (~> 2.5)
shoulda-matchers (6.4.0)
activesupport (>= 5.2.0)
sidekiq (8.0.1)
@@ -818,6 +835,7 @@ GEM
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
+ sorbet-runtime (0.5.12024)
spring (4.2.1)
spring-watcher-listen (2.1.0)
listen (>= 2.7, < 4.0)
@@ -962,6 +980,7 @@ DEPENDENCIES
hashie
html2text
image_processing
+ iso-639
jbuilder
json_refs
json_schemer
@@ -1018,6 +1037,7 @@ DEPENDENCIES
sentry-rails (>= 5.19.0)
sentry-ruby
sentry-sidekiq (>= 5.19.0)
+ shopify_api
shoulda-matchers
sidekiq (>= 7.3.1)
sidekiq-cron (>= 1.12.0)
diff --git a/README.md b/README.md
index f09bd5698..21316b422 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,11 @@
-## 🚨 Note: This branch is unstable. For the stable branch's source code, please use the branch [3.x](https://github.com/chatwoot/chatwoot/tree/3.x)
-
-
-
-
+
+
___
# Chatwoot
-Customer engagement suite, an open-source alternative to Intercom, Zendesk, Salesforce Service Cloud etc.
-
@@ -31,41 +20,71 @@ Customer engagement suite, an open-source alternative to Intercom, Zendesk, Sale
-
-Chatwoot is an open-source, self-hosted customer engagement suite. Chatwoot lets you view and manage your customer data, communicate with them irrespective of which medium they use, and re-engage them based on their profile.
+
-## Features
+
+
-Chatwoot supports the following conversation channels:
+---
- - **Website**: Talk to your customers using our live chat widget and make use of our SDK to identify a user and provide contextual support.
- - **Facebook**: Connect your Facebook pages and start replying to the direct messages to your page.
- - **Instagram**: Connect your Instagram profile and start replying to the direct messages.
- - **Twitter**: Connect your Twitter profiles and reply to direct messages or the tweets where you are mentioned.
- - **Telegram**: Connect your Telegram bot and reply to your customers right from a single dashboard.
- - **WhatsApp**: Connect your WhatsApp business account and manage the conversation in Chatwoot.
- - **Line**: Connect your Line account and manage the conversations in Chatwoot.
- - **SMS**: Connect your Twilio SMS account and reply to the SMS queries in Chatwoot.
- - **API Channel**: Build custom communication channels using our API channel.
- - **Email**: Forward all your email queries to Chatwoot and view it in our integrated dashboard.
+Chatwoot is the modern, open-source, and self-hosted customer support platform designed to help businesses deliver exceptional customer support experience. Built for scale and flexibility, Chatwoot gives you full control over your customer data while providing powerful tools to manage conversations across channels.
-And more.
+### ✨ Captain – AI Agent for Support
-Other features include:
+Supercharge your support with Captain, Chatwoot’s AI agent. Captain helps automate responses, handle common queries, and reduce agent workload—ensuring customers get instant, accurate answers. With Captain, your team can focus on complex conversations while routine questions are resolved automatically. Read more about Captain [here](https://chwt.app/captain-docs).
+
+### 💬 Omnichannel Support Desk
+
+Chatwoot centralizes all customer conversations into one powerful inbox, no matter where your customers reach out from. It supports live chat on your website, email, Facebook, Instagram, Twitter, WhatsApp, Telegram, Line, SMS etc.
+
+### 📚 Help center portal
+
+Publish help articles, FAQs, and guides through the built-in Help Center Portal. Enable customers to find answers on their own, reduce repetitive queries, and keep your support team focused on more complex issues.
+
+### 🗂️ Other features
+
+#### Collaboration & Productivity
+
+- Private Notes and @mentions for internal team discussions.
+- Labels to organize and categorize conversations.
+- Keyboard Shortcuts and a Command Bar for quick navigation.
+- Canned Responses to reply faster to frequently asked questions.
+- Auto-Assignment to route conversations based on agent availability.
+- Multi-lingual Support to serve customers in multiple languages.
+- Custom Views and Filters for better inbox organization.
+- Business Hours and Auto-Responders to manage response expectations.
+- Teams and Automation tools for scaling support workflows.
+- Agent Capacity Management to balance workload across the team.
+
+#### Customer Data & Segmentation
+- Contact Management with profiles and interaction history.
+- Contact Segments and Notes for targeted communication.
+- Campaigns to proactively engage customers.
+- Custom Attributes for storing additional customer data.
+- Pre-Chat Forms to collect user information before starting conversations.
+
+#### Integrations
+- Slack Integration to manage conversations directly from Slack.
+- Dialogflow Integration for chatbot automation.
+- Dashboard Apps to embed internal tools within Chatwoot.
+- Shopify Integration to view and manage customer orders right within Chatwoot.
+- Use Google Translate to translate messages from your customers in realtime.
+- Create and manage Linear tickets within Chatwoot.
+
+#### Reports & Insights
+- Live View of ongoing conversations for real-time monitoring.
+- Conversation, Agent, Inbox, Label, and Team Reports for operational visibility.
+- CSAT Reports to measure customer satisfaction.
+- Downloadable Reports for offline analysis and reporting.
-- **CRM**: Save all your customer information right inside Chatwoot, use contact notes to log emails, phone calls, or meeting notes.
-- **Custom Attributes**: Define custom attribute attributes to store information about a contact or a conversation and extend the product to match your workflow.
-- **Shared multi-brand inboxes**: Manage multiple brands or pages using a shared inbox.
-- **Private notes**: Use @mentions and private notes to communicate internally about a conversation.
-- **Canned responses (Saved replies)**: Improve the response rate by adding saved replies for frequently asked questions.
-- **Conversation Labels**: Use conversation labels to create custom workflows.
-- **Auto assignment**: Chatwoot intelligently assigns a ticket to the agents who have access to the inbox depending on their availability and load.
-- **Conversation continuity**: If the user has provided an email address through the chat widget, Chatwoot will send an email to the customer under the agent name so that the user can continue the conversation over the email.
-- **Multi-lingual support**: Chatwoot supports 10+ languages.
-- **Powerful API & Webhooks**: Extend the capability of the software using Chatwoot’s webhooks and APIs.
-- **Integrations**: Chatwoot natively integrates with Slack right now. Manage your conversations in Slack without logging into the dashboard.
## Documentation
@@ -107,13 +126,11 @@ For other supported options, checkout our [deployment page](https://chatwoot.com
Looking to report a vulnerability? Please refer our [SECURITY.md](./SECURITY.md) file.
-
-## Community? Questions? Support ?
+## Community
If you need help or just want to hang out, come, say hi on our [Discord](https://discord.gg/cJXdrwS) server.
-
-## Contributors ✨
+## Contributors
Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contributors):
diff --git a/app/assets/stylesheets/administrate/components/_buttons.scss b/app/assets/stylesheets/administrate/components/_buttons.scss
index a0c3699ba..1fceddc1f 100644
--- a/app/assets/stylesheets/administrate/components/_buttons.scss
+++ b/app/assets/stylesheets/administrate/components/_buttons.scss
@@ -9,7 +9,7 @@ input[type='submit']:not(.reset-base),
border-radius: $base-border-radius;
color: $white;
cursor: pointer;
- display: inline-block;
+ display: inline-flex;
font-size: $font-size-small;
-webkit-font-smoothing: antialiased;
font-weight: $font-weight-medium;
diff --git a/app/builders/contact_inbox_builder.rb b/app/builders/contact_inbox_builder.rb
index 8fcd2b158..ffa45db2e 100644
--- a/app/builders/contact_inbox_builder.rb
+++ b/app/builders/contact_inbox_builder.rb
@@ -64,5 +64,40 @@ class ContactInboxBuilder
inbox_id: @inbox.id,
source_id: @source_id
)
+ rescue ActiveRecord::RecordNotUnique
+ Rails.logger.info("[ContactInboxBuilder] RecordNotUnique #{@source_id} #{@contact.id} #{@inbox.id}")
+ update_old_contact_inbox
+ retry
+ end
+
+ def update_old_contact_inbox
+ # The race condition occurs when there’s a contact inbox with the
+ # same source ID but linked to a different contact. This can happen
+ # if the agent updates the contact’s email or phone number, or
+ # if the contact is merged with another.
+ #
+ # We update the old contact inbox source_id to a random value to
+ # avoid disrupting the current flow. However, the root cause of
+ # this issue is a flaw in the contact inbox model design.
+ # Contact inbox is essentially tracking a session and is not
+ # needed for non-live chat channels.
+ raise ActiveRecord::RecordNotUnique unless allowed_channels?
+
+ contact_inbox = ::ContactInbox.find_by(inbox_id: @inbox.id, source_id: @source_id)
+ return if contact_inbox.blank?
+
+ contact_inbox.update!(source_id: new_source_id)
+ end
+
+ def new_source_id
+ if @inbox.whatsapp? || @inbox.sms? || @inbox.twilio?
+ "whatsapp:#{@source_id}#{rand(100)}"
+ else
+ "#{rand(10)}#{@source_id}"
+ end
+ end
+
+ def allowed_channels?
+ @inbox.email? || @inbox.sms? || @inbox.twilio? || @inbox.whatsapp?
end
end
diff --git a/app/builders/contact_inbox_with_contact_builder.rb b/app/builders/contact_inbox_with_contact_builder.rb
index 2f30093db..2c0e6087e 100644
--- a/app/builders/contact_inbox_with_contact_builder.rb
+++ b/app/builders/contact_inbox_with_contact_builder.rb
@@ -63,9 +63,33 @@ class ContactInboxWithContactBuilder
contact = find_contact_by_identifier(contact_attributes[:identifier])
contact ||= find_contact_by_email(contact_attributes[:email])
contact ||= find_contact_by_phone_number(contact_attributes[:phone_number])
+ contact ||= find_contact_by_instagram_source_id(source_id) if instagram_channel?
+
contact
end
+ def instagram_channel?
+ inbox.channel_type == 'Channel::Instagram'
+ end
+
+ # There might be existing contact_inboxes created through Channel::FacebookPage
+ # with the same Instagram source_id. New Instagram interactions should create fresh contact_inboxes
+ # while still reusing contacts if found in Facebook channels so that we can create
+ # new conversations with the same contact.
+ def find_contact_by_instagram_source_id(instagram_id)
+ return if instagram_id.blank?
+
+ existing_contact_inbox = ContactInbox.joins(:inbox)
+ .where(source_id: instagram_id)
+ .where(
+ 'inboxes.channel_type = ? AND inboxes.account_id = ?',
+ 'Channel::FacebookPage',
+ account.id
+ ).first
+
+ existing_contact_inbox&.contact
+ end
+
def find_contact_by_identifier(identifier)
return if identifier.blank?
diff --git a/app/builders/messages/instagram/base_message_builder.rb b/app/builders/messages/instagram/base_message_builder.rb
new file mode 100644
index 000000000..767115bc6
--- /dev/null
+++ b/app/builders/messages/instagram/base_message_builder.rb
@@ -0,0 +1,178 @@
+class Messages::Instagram::BaseMessageBuilder < Messages::Messenger::MessageBuilder
+ attr_reader :messaging
+
+ def initialize(messaging, inbox, outgoing_echo: false)
+ super()
+ @messaging = messaging
+ @inbox = inbox
+ @outgoing_echo = outgoing_echo
+ end
+
+ def perform
+ return if @inbox.channel.reauthorization_required?
+
+ ActiveRecord::Base.transaction do
+ build_message
+ end
+ rescue StandardError => e
+ handle_error(e)
+ end
+
+ private
+
+ def attachments
+ @messaging[:message][:attachments] || {}
+ end
+
+ def message_type
+ @outgoing_echo ? :outgoing : :incoming
+ end
+
+ def message_identifier
+ message[:mid]
+ end
+
+ def message_source_id
+ @outgoing_echo ? recipient_id : sender_id
+ end
+
+ def message_is_unsupported?
+ message[:is_unsupported].present? && @messaging[:message][:is_unsupported] == true
+ end
+
+ def sender_id
+ @messaging[:sender][:id]
+ end
+
+ def recipient_id
+ @messaging[:recipient][:id]
+ end
+
+ def message
+ @messaging[:message]
+ end
+
+ def contact
+ @contact ||= @inbox.contact_inboxes.find_by(source_id: message_source_id)&.contact
+ end
+
+ def conversation
+ @conversation ||= set_conversation_based_on_inbox_config
+ end
+
+ def set_conversation_based_on_inbox_config
+ if @inbox.lock_to_single_conversation
+ find_conversation_scope.order(created_at: :desc).first || build_conversation
+ else
+ find_or_build_for_multiple_conversations
+ end
+ end
+
+ def find_conversation_scope
+ Conversation.where(conversation_params)
+ end
+
+ def find_or_build_for_multiple_conversations
+ last_conversation = find_conversation_scope.where.not(status: :resolved).order(created_at: :desc).first
+ return build_conversation if last_conversation.nil?
+
+ last_conversation
+ end
+
+ def message_content
+ @messaging[:message][:text]
+ end
+
+ def story_reply_attributes
+ message[:reply_to][:story] if message[:reply_to].present? && message[:reply_to][:story].present?
+ end
+
+ def message_reply_attributes
+ message[:reply_to][:mid] if message[:reply_to].present? && message[:reply_to][:mid].present?
+ end
+
+ def build_message
+ # Duplicate webhook events may be sent for the same message
+ # when a user is connected to the Instagram account through both Messenger and Instagram login.
+ # There is chance for echo events to be sent for the same message.
+ # Therefore, we need to check if the message already exists before creating it.
+ return if message_already_exists?
+
+ return if message_content.blank? && all_unsupported_files?
+
+ @message = conversation.messages.create!(message_params)
+ save_story_id
+
+ attachments.each do |attachment|
+ process_attachment(attachment)
+ end
+ end
+
+ def save_story_id
+ return if story_reply_attributes.blank?
+
+ @message.save_story_info(story_reply_attributes)
+ end
+
+ def build_conversation
+ @contact_inbox ||= contact.contact_inboxes.find_by!(source_id: message_source_id)
+ Conversation.create!(conversation_params.merge(
+ contact_inbox_id: @contact_inbox.id,
+ additional_attributes: additional_conversation_attributes
+ ))
+ end
+
+ def additional_conversation_attributes
+ {}
+ end
+
+ def conversation_params
+ {
+ account_id: @inbox.account_id,
+ inbox_id: @inbox.id,
+ contact_id: contact.id
+ }
+ end
+
+ def message_params
+ params = {
+ account_id: conversation.account_id,
+ inbox_id: conversation.inbox_id,
+ message_type: message_type,
+ source_id: message_identifier,
+ content: message_content,
+ sender: @outgoing_echo ? nil : contact,
+ content_attributes: {
+ in_reply_to_external_id: message_reply_attributes
+ }
+ }
+
+ params[:content_attributes][:is_unsupported] = true if message_is_unsupported?
+ params
+ end
+
+ def message_already_exists?
+ cw_message = conversation.messages.where(
+ source_id: @messaging[:message][:mid]
+ ).first
+
+ cw_message.present?
+ end
+
+ def all_unsupported_files?
+ return if attachments.empty?
+
+ attachments_type = attachments.pluck(:type).uniq.first
+ unsupported_file_type?(attachments_type)
+ end
+
+ def handle_error(error)
+ ChatwootExceptionTracker.new(error, account: @inbox.account).capture_exception
+ true
+ end
+
+ # Abstract methods to be implemented by subclasses
+ def get_story_object_from_source_id(source_id)
+ raise NotImplementedError
+ end
+end
diff --git a/app/builders/messages/instagram/message_builder.rb b/app/builders/messages/instagram/message_builder.rb
index 7f1b9cab2..4e7150894 100644
--- a/app/builders/messages/instagram/message_builder.rb
+++ b/app/builders/messages/instagram/message_builder.rb
@@ -1,200 +1,42 @@
-# This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
-# Assumptions
-# 1. Incase of an outgoing message which is echo, source_id will NOT be nil,
-# based on this we are showing "not sent from chatwoot" message in frontend
-# Hence there is no need to set user_id in message for outgoing echo messages.
-
-class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
- attr_reader :messaging
-
+class Messages::Instagram::MessageBuilder < Messages::Instagram::BaseMessageBuilder
def initialize(messaging, inbox, outgoing_echo: false)
- super()
- @messaging = messaging
- @inbox = inbox
- @outgoing_echo = outgoing_echo
- end
-
- def perform
- return if @inbox.channel.reauthorization_required?
-
- ActiveRecord::Base.transaction do
- build_message
- end
- rescue Koala::Facebook::AuthenticationError => e
- Rails.logger.warn("Instagram authentication error for inbox: #{@inbox.id} with error: #{e.message}")
- Rails.logger.error e
- @inbox.channel.authorization_error!
- raise
- rescue StandardError => e
- ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
- true
+ super(messaging, inbox, outgoing_echo: outgoing_echo)
end
private
- def attachments
- @messaging[:message][:attachments] || {}
+ def get_story_object_from_source_id(source_id)
+ url = "#{base_uri}/#{source_id}?fields=story,from&access_token=#{@inbox.channel.access_token}"
+
+ response = HTTParty.get(url)
+
+ return JSON.parse(response.body).with_indifferent_access if response.success?
+
+ # Create message first if it doesn't exist
+ @message ||= conversation.messages.create!(message_params)
+ handle_error_response(response)
+ nil
end
- def message_type
- @outgoing_echo ? :outgoing : :incoming
- end
+ def handle_error_response(response)
+ parsed_response = JSON.parse(response.body)
+ error_code = parsed_response.dig('error', 'code')
- def message_identifier
- message[:mid]
- end
+ # https://developers.facebook.com/docs/messenger-platform/error-codes
+ # Access token has expired or become invalid.
+ channel.authorization_error! if error_code == 190
- def message_source_id
- @outgoing_echo ? recipient_id : sender_id
- end
-
- def message_is_unsupported?
- message[:is_unsupported].present? && @messaging[:message][:is_unsupported] == true
- end
-
- def sender_id
- @messaging[:sender][:id]
- end
-
- def recipient_id
- @messaging[:recipient][:id]
- end
-
- def message
- @messaging[:message]
- end
-
- def contact
- @contact ||= @inbox.contact_inboxes.find_by(source_id: message_source_id)&.contact
- end
-
- def conversation
- @conversation ||= set_conversation_based_on_inbox_config
- end
-
- def instagram_direct_message_conversation
- Conversation.where(conversation_params)
- .where("additional_attributes ->> 'type' = 'instagram_direct_message'")
- end
-
- def set_conversation_based_on_inbox_config
- if @inbox.lock_to_single_conversation
- instagram_direct_message_conversation.order(created_at: :desc).first || build_conversation
- else
- find_or_build_for_multiple_conversations
+ # There was a problem scraping data from the provided link.
+ # https://developers.facebook.com/docs/graph-api/guides/error-handling/ search for error code 1609005
+ if error_code == 1_609_005
+ @message.attachments.destroy_all
+ @message.update(content: I18n.t('conversations.messages.instagram_deleted_story_content'))
end
+
+ Rails.logger.error("[InstagramStoryFetchError]: #{parsed_response.dig('error', 'message')} #{error_code}")
end
- def find_or_build_for_multiple_conversations
- last_conversation = instagram_direct_message_conversation.where.not(status: :resolved).order(created_at: :desc).first
-
- return build_conversation if last_conversation.nil?
-
- last_conversation
+ def base_uri
+ "https://graph.instagram.com/#{GlobalConfigService.load('INSTAGRAM_API_VERSION', 'v22.0')}"
end
-
- def message_content
- @messaging[:message][:text]
- end
-
- def story_reply_attributes
- message[:reply_to][:story] if message[:reply_to].present? && message[:reply_to][:story].present?
- end
-
- def message_reply_attributes
- message[:reply_to][:mid] if message[:reply_to].present? && message[:reply_to][:mid].present?
- end
-
- def build_message
- return if @outgoing_echo && already_sent_from_chatwoot?
- return if message_content.blank? && all_unsupported_files?
-
- @message = conversation.messages.create!(message_params)
- save_story_id
-
- attachments.each do |attachment|
- process_attachment(attachment)
- end
- end
-
- def save_story_id
- return if story_reply_attributes.blank?
-
- @message.save_story_info(story_reply_attributes)
- end
-
- def build_conversation
- @contact_inbox ||= contact.contact_inboxes.find_by!(source_id: message_source_id)
-
- Conversation.create!(conversation_params.merge(
- contact_inbox_id: @contact_inbox.id,
- additional_attributes: { type: 'instagram_direct_message' }
- ))
- end
-
- def conversation_params
- {
- account_id: @inbox.account_id,
- inbox_id: @inbox.id,
- contact_id: contact.id
- }
- end
-
- def message_params
- params = {
- account_id: conversation.account_id,
- inbox_id: conversation.inbox_id,
- message_type: message_type,
- source_id: message_identifier,
- content: message_content,
- sender: @outgoing_echo ? nil : contact,
- content_attributes: {
- in_reply_to_external_id: message_reply_attributes
- }
- }
-
- params[:content_attributes][:is_unsupported] = true if message_is_unsupported?
- params
- end
-
- def already_sent_from_chatwoot?
- cw_message = conversation.messages.where(
- source_id: @messaging[:message][:mid]
- ).first
-
- cw_message.present?
- end
-
- def all_unsupported_files?
- return if attachments.empty?
-
- attachments_type = attachments.pluck(:type).uniq.first
- unsupported_file_type?(attachments_type)
- end
-
- ### Sample response
- # {
- # "object": "instagram",
- # "entry": [
- # {
- # "id": "