diff --git a/Gemfile b/Gemfile
index 7f0933b37..334602aad 100644
--- a/Gemfile
+++ b/Gemfile
@@ -78,6 +78,7 @@ gem 'wisper', '2.0.0'
##--- gems for channels ---##
# TODO: bump up gem to 2.0
gem 'facebook-messenger'
+gem 'line-bot-api'
gem 'twilio-ruby', '~> 5.32.0'
# twitty will handle subscription of twitter account events
# gem 'twitty', git: 'https://github.com/chatwoot/twitty'
diff --git a/Gemfile.lock b/Gemfile.lock
index 31b9f7bbc..8e102838d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -322,6 +322,7 @@ GEM
addressable (~> 2.7)
letter_opener (1.7.0)
launchy (~> 2.2)
+ line-bot-api (1.21.0)
liquid (5.0.1)
listen (3.6.0)
rb-fsevent (~> 0.10, >= 0.10.3)
@@ -661,6 +662,7 @@ DEPENDENCIES
kaminari
koala
letter_opener
+ line-bot-api
liquid
listen
maxminddb
diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb
index 3b21b70bb..f9a6ce154 100644
--- a/app/controllers/api/v1/accounts/inboxes_controller.rb
+++ b/app/controllers/api/v1/accounts/inboxes_controller.rb
@@ -92,6 +92,8 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
Current.account.api_channels.create!(permitted_params(Channel::Api::EDITABLE_ATTRS)[:channel].except(:type))
when 'email'
Current.account.email_channels.create!(permitted_params(Channel::Email::EDITABLE_ATTRS)[:channel].except(:type))
+ when 'line'
+ Current.account.line_channels.create!(permitted_params(Channel::Line::EDITABLE_ATTRS)[:channel].except(:type))
when 'telegram'
Current.account.telegram_channels.create!(permitted_params(Channel::Telegram::EDITABLE_ATTRS)[:channel].except(:type))
end
@@ -122,6 +124,8 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
Channel::Email::EDITABLE_ATTRS
when 'Channel::Telegram'
Channel::Telegram::EDITABLE_ATTRS
+ when 'Channel::Line'
+ Channel::Line::EDITABLE_ATTRS
else
[]
end
diff --git a/app/controllers/webhooks/line_controller.rb b/app/controllers/webhooks/line_controller.rb
new file mode 100644
index 000000000..74e22f119
--- /dev/null
+++ b/app/controllers/webhooks/line_controller.rb
@@ -0,0 +1,6 @@
+class Webhooks::LineController < ActionController::API
+ def process_payload
+ Webhooks::LineEventsJob.perform_later(params: params.to_unsafe_hash, signature: request.headers['x-line-signature'], post_body: request.raw_post)
+ head :ok
+ end
+end
diff --git a/app/javascript/dashboard/components/widgets/ChannelItem.vue b/app/javascript/dashboard/components/widgets/ChannelItem.vue
index 819d912bd..183f42c10 100644
--- a/app/javascript/dashboard/components/widgets/ChannelItem.vue
+++ b/app/javascript/dashboard/components/widgets/ChannelItem.vue
@@ -76,7 +76,16 @@ export default {
if (key === 'email') {
return this.enabledFeatures.channel_email;
}
- return ['website', 'twilio', 'api', 'whatsapp', 'sms', 'telegram'].includes(key);
+
+ return [
+ 'website',
+ 'twilio',
+ 'api',
+ 'whatsapp',
+ 'sms',
+ 'telegram',
+ 'line',
+ ].includes(key);
},
},
methods: {
diff --git a/app/javascript/dashboard/components/widgets/Thumbnail.vue b/app/javascript/dashboard/components/widgets/Thumbnail.vue
index e43d48a4b..6fe83720b 100644
--- a/app/javascript/dashboard/components/widgets/Thumbnail.vue
+++ b/app/javascript/dashboard/components/widgets/Thumbnail.vue
@@ -35,6 +35,13 @@
:style="badgeStyle"
src="~dashboard/assets/images/channels/whatsapp.png"
/>
+
+
+
+
+
@@ -75,6 +83,9 @@ export default {
isAEmailInbox() {
return this.currentInbox.channel_type === 'Channel::Email';
},
+ isALineInbox() {
+ return this.currentInbox.channel_type === 'Channel::Line';
+ },
message() {
if (this.isATwilioInbox) {
return `${this.$t('INBOX_MGMT.FINISH.MESSAGE')}. ${this.$t(
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channel-factory.js b/app/javascript/dashboard/routes/dashboard/settings/inbox/channel-factory.js
index db7fb0c43..0842f9f6b 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channel-factory.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channel-factory.js
@@ -5,6 +5,7 @@ import Api from './channels/Api';
import Email from './channels/Email';
import Sms from './channels/Sms';
import Whatsapp from './channels/Whatsapp';
+import Line from './channels/Line';
import Telegram from './channels/Telegram';
const channelViewList = {
@@ -15,6 +16,7 @@ const channelViewList = {
email: Email,
sms: Sms,
whatsapp: Whatsapp,
+ line: Line,
telegram: Telegram,
};
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Line.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Line.vue
new file mode 100644
index 000000000..339ded821
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Line.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
diff --git a/app/javascript/shared/mixins/configMixin.js b/app/javascript/shared/mixins/configMixin.js
index a4e6c62b2..acbe98511 100644
--- a/app/javascript/shared/mixins/configMixin.js
+++ b/app/javascript/shared/mixins/configMixin.js
@@ -3,9 +3,6 @@ export default {
hostURL() {
return window.chatwootConfig.hostURL;
},
- twilioCallbackURL() {
- return `${this.hostURL}/twilio/callback`;
- },
vapidPublicKey() {
return window.chatwootConfig.vapidPublicKey;
},
diff --git a/app/jobs/send_reply_job.rb b/app/jobs/send_reply_job.rb
index 77d17cc22..b834c41ea 100644
--- a/app/jobs/send_reply_job.rb
+++ b/app/jobs/send_reply_job.rb
@@ -11,6 +11,8 @@ class SendReplyJob < ApplicationJob
::Twitter::SendOnTwitterService.new(message: message).perform
when 'Channel::TwilioSms'
::Twilio::SendOnTwilioService.new(message: message).perform
+ when 'Channel::Line'
+ ::Line::SendOnLineService.new(message: message).perform
when 'Channel::Telegram'
::Telegram::SendOnTelegramService.new(message: message).perform
end
diff --git a/app/jobs/webhooks/line_events_job.rb b/app/jobs/webhooks/line_events_job.rb
new file mode 100644
index 000000000..3c17eddb3
--- /dev/null
+++ b/app/jobs/webhooks/line_events_job.rb
@@ -0,0 +1,24 @@
+class Webhooks::LineEventsJob < ApplicationJob
+ queue_as :default
+
+ def perform(params: {}, signature: '', post_body: '')
+ @params = params
+ return unless valid_event_payload?
+ return unless valid_post_body?(post_body, signature)
+
+ Line::IncomingMessageService.new(inbox: @channel.inbox, params: @params['line'].with_indifferent_access).perform
+ end
+
+ private
+
+ def valid_event_payload?
+ @channel = Channel::Line.find_by(line_channel_id: @params[:line_channel_id]) if @params[:line_channel_id]
+ end
+
+ # https://developers.line.biz/en/reference/messaging-api/#signature-validation
+ # validate the line payload
+ def valid_post_body?(post_body, signature)
+ hash = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), @channel.line_channel_secret, post_body)
+ Base64.strict_encode64(hash) == signature
+ end
+end
diff --git a/app/models/account.rb b/app/models/account.rb
index 817013373..0b8287be5 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -51,6 +51,7 @@ class Account < ApplicationRecord
has_many :web_widgets, dependent: :destroy, class_name: '::Channel::WebWidget'
has_many :email_channels, dependent: :destroy, class_name: '::Channel::Email'
has_many :api_channels, dependent: :destroy, class_name: '::Channel::Api'
+ has_many :line_channels, dependent: :destroy, class_name: '::Channel::Line'
has_many :telegram_channels, dependent: :destroy, class_name: '::Channel::Telegram'
has_many :canned_responses, dependent: :destroy
has_many :webhooks, dependent: :destroy
diff --git a/app/models/channel/api.rb b/app/models/channel/api.rb
index f3d1a9b82..fd8abb594 100644
--- a/app/models/channel/api.rb
+++ b/app/models/channel/api.rb
@@ -18,22 +18,15 @@
#
class Channel::Api < ApplicationRecord
+ include Channelable
+
self.table_name = 'channel_api'
EDITABLE_ATTRS = [:webhook_url].freeze
- validates :account_id, presence: true
- belongs_to :account
-
has_secure_token :identifier
has_secure_token :hmac_token
- has_one :inbox, as: :channel, dependent: :destroy
-
def name
'API'
end
-
- def has_24_hour_messaging_window?
- false
- end
end
diff --git a/app/models/channel/email.rb b/app/models/channel/email.rb
index 806ae0f08..fe711b01e 100644
--- a/app/models/channel/email.rb
+++ b/app/models/channel/email.rb
@@ -16,25 +16,20 @@
#
class Channel::Email < ApplicationRecord
+ include Channelable
+
self.table_name = 'channel_email'
EDITABLE_ATTRS = [:email].freeze
- validates :account_id, presence: true
- belongs_to :account
validates :email, uniqueness: true
validates :forward_to_email, uniqueness: true
- has_one :inbox, as: :channel, dependent: :destroy
before_validation :ensure_forward_to_email, on: :create
def name
'Email'
end
- def has_24_hour_messaging_window?
- false
- end
-
private
def ensure_forward_to_email
diff --git a/app/models/channel/facebook_page.rb b/app/models/channel/facebook_page.rb
index ade13990e..d564d0048 100644
--- a/app/models/channel/facebook_page.rb
+++ b/app/models/channel/facebook_page.rb
@@ -17,15 +17,12 @@
#
class Channel::FacebookPage < ApplicationRecord
- self.table_name = 'channel_facebook_pages'
-
+ include Channelable
include Reauthorizable
- validates :account_id, presence: true
- validates :page_id, uniqueness: { scope: :account_id }
- belongs_to :account
+ self.table_name = 'channel_facebook_pages'
- has_one :inbox, as: :channel, dependent: :destroy
+ validates :page_id, uniqueness: { scope: :account_id }
after_create_commit :subscribe
before_destroy :unsubscribe
diff --git a/app/models/channel/line.rb b/app/models/channel/line.rb
new file mode 100644
index 000000000..a417dbf64
--- /dev/null
+++ b/app/models/channel/line.rb
@@ -0,0 +1,39 @@
+# == Schema Information
+#
+# Table name: channel_line
+#
+# id :bigint not null, primary key
+# line_channel_secret :string not null
+# line_channel_token :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# account_id :integer not null
+# line_channel_id :string not null
+#
+# Indexes
+#
+# index_channel_line_on_line_channel_id (line_channel_id) UNIQUE
+#
+
+class Channel::Line < ApplicationRecord
+ include Channelable
+
+ self.table_name = 'channel_line'
+ EDITABLE_ATTRS = [:line_channel_id, :line_channel_secret, :line_channel_token].freeze
+
+ validates :line_channel_id, uniqueness: true, presence: true
+ validates :line_channel_secret, presence: true
+ validates :line_channel_token, presence: true
+
+ def name
+ 'LINE'
+ end
+
+ def client
+ @client ||= Line::Bot::Client.new do |config|
+ config.channel_id = line_channel_id
+ config.channel_secret = line_channel_secret
+ config.channel_token = line_channel_token
+ end
+ end
+end
diff --git a/app/models/channel/telegram.rb b/app/models/channel/telegram.rb
index 3dd66dfd3..e0d020f2f 100644
--- a/app/models/channel/telegram.rb
+++ b/app/models/channel/telegram.rb
@@ -15,14 +15,12 @@
#
class Channel::Telegram < ApplicationRecord
+ include Channelable
+
self.table_name = 'channel_telegram'
EDITABLE_ATTRS = [:bot_token].freeze
- has_one :inbox, as: :channel, dependent: :destroy
- belongs_to :account
-
before_validation :ensure_valid_bot_token, on: :create
- validates :account_id, presence: true
validates :bot_token, presence: true, uniqueness: true
before_save :setup_telegram_webhook
@@ -30,10 +28,6 @@ class Channel::Telegram < ApplicationRecord
'Telegram'
end
- def has_24_hour_messaging_window?
- false
- end
-
def telegram_api_url
"https://api.telegram.org/bot#{bot_token}"
end
diff --git a/app/models/channel/twilio_sms.rb b/app/models/channel/twilio_sms.rb
index 8f25fec2e..89e9cbdc8 100644
--- a/app/models/channel/twilio_sms.rb
+++ b/app/models/channel/twilio_sms.rb
@@ -17,19 +17,16 @@
#
class Channel::TwilioSms < ApplicationRecord
+ include Channelable
+
self.table_name = 'channel_twilio_sms'
- validates :account_id, presence: true
validates :account_sid, presence: true
validates :auth_token, presence: true
validates :phone_number, uniqueness: { scope: :account_id }, presence: true
enum medium: { sms: 0, whatsapp: 1 }
- belongs_to :account
-
- has_one :inbox, as: :channel, dependent: :destroy
-
def name
medium == 'sms' ? 'Twilio SMS' : 'Whatsapp'
end
diff --git a/app/models/channel/twitter_profile.rb b/app/models/channel/twitter_profile.rb
index 1b1011014..ff2b7c19d 100644
--- a/app/models/channel/twitter_profile.rb
+++ b/app/models/channel/twitter_profile.rb
@@ -16,13 +16,11 @@
#
class Channel::TwitterProfile < ApplicationRecord
+ include Channelable
+
self.table_name = 'channel_twitter_profiles'
- validates :account_id, presence: true
validates :profile_id, uniqueness: { scope: :account_id }
- belongs_to :account
-
- has_one :inbox, as: :channel, dependent: :destroy
before_destroy :unsubscribe
@@ -30,10 +28,6 @@ class Channel::TwitterProfile < ApplicationRecord
'Twitter'
end
- def has_24_hour_messaging_window?
- false
- end
-
def create_contact_inbox(profile_id, name, additional_attributes)
ActiveRecord::Base.transaction do
contact = inbox.account.contacts.create!(additional_attributes: additional_attributes, name: name)
diff --git a/app/models/channel/web_widget.rb b/app/models/channel/web_widget.rb
index 58f9b9ec3..38f6e2eb5 100644
--- a/app/models/channel/web_widget.rb
+++ b/app/models/channel/web_widget.rb
@@ -25,7 +25,9 @@
#
class Channel::WebWidget < ApplicationRecord
+ include Channelable
include FlagShihTzu
+
self.table_name = 'channel_web_widgets'
EDITABLE_ATTRS = [:website_url, :widget_color, :welcome_title, :welcome_tagline, :reply_time, :pre_chat_form_enabled,
{ pre_chat_form_options: [:pre_chat_message, :require_email] },
@@ -34,8 +36,6 @@ class Channel::WebWidget < ApplicationRecord
validates :website_url, presence: true
validates :widget_color, presence: true
- belongs_to :account
- has_one :inbox, as: :channel, dependent: :destroy
has_secure_token :website_token
has_secure_token :hmac_token
@@ -50,10 +50,6 @@ class Channel::WebWidget < ApplicationRecord
'Website'
end
- def has_24_hour_messaging_window?
- false
- end
-
def web_widget_script
"