diff --git a/.rubocop.yml b/.rubocop.yml
index c05cbb9bf..76ef5fcfc 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -100,6 +100,7 @@ Metrics/AbcSize:
- 'app/controllers/concerns/auth_helper.rb'
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
+ - 'app/controllers/api/v1/accounts/inboxes_controller.rb'
Metrics/CyclomaticComplexity:
Max: 7
Exclude:
diff --git a/Gemfile b/Gemfile
index 37dba6a19..7f0933b37 100644
--- a/Gemfile
+++ b/Gemfile
@@ -78,7 +78,6 @@ gem 'wisper', '2.0.0'
##--- gems for channels ---##
# TODO: bump up gem to 2.0
gem 'facebook-messenger'
-gem 'telegram-bot-ruby'
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 c11e09a14..31b9f7bbc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -98,10 +98,6 @@ GEM
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.4)
aws-eventstream (~> 1, >= 1.0.2)
- axiom-types (0.1.1)
- descendants_tracker (~> 0.0.4)
- ice_nine (~> 0.11.0)
- thread_safe (~> 0.3, >= 0.3.1)
azure-storage-blob (2.0.1)
azure-storage-common (~> 2.0)
nokogiri (~> 1.11.0.rc2)
@@ -130,8 +126,6 @@ GEM
thor (~> 1.0)
byebug (11.1.3)
coderay (1.1.3)
- coercible (1.0.0)
- descendants_tracker (~> 0.0.1)
commonmarker (0.22.0)
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
@@ -152,8 +146,6 @@ GEM
ffi (~> 1.0)
msgpack
declarative (0.0.20)
- descendants_tracker (0.0.4)
- thread_safe (~> 0.3, >= 0.3.1)
devise (4.8.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
@@ -179,7 +171,6 @@ GEM
railties (>= 3.2)
down (5.2.3)
addressable (~> 2.8)
- dry-inflector (0.2.1)
ecma-re-validator (0.3.0)
regexp_parser (~> 2.0)
erubi (1.10.0)
@@ -292,7 +283,6 @@ GEM
httpclient (2.8.3)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
- ice_nine (0.11.2)
image_processing (1.12.1)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
@@ -564,13 +554,8 @@ GEM
sprockets (>= 3.0.0)
squasher (0.6.2)
statsd-ruby (1.5.0)
- telegram-bot-ruby (0.16.0)
- dry-inflector
- faraday
- virtus (~> 2.0)
telephone_number (1.4.12)
thor (1.1.0)
- thread_safe (0.3.6)
tilt (2.0.10)
time_diff (0.3.0)
activesupport
@@ -598,10 +583,6 @@ GEM
valid_email2 (4.0.0)
activemodel (>= 3.2)
mail (~> 2.5)
- virtus (2.0.0)
- axiom-types (~> 0.1)
- coercible (~> 1.0)
- descendants_tracker (~> 0.0, >= 0.0.3)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.1.0)
@@ -716,7 +697,6 @@ DEPENDENCIES
spring
spring-watcher-listen
squasher
- telegram-bot-ruby
telephone_number
time_diff
twilio-ruby (~> 5.32.0)
diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb
index 59ab107dc..3b21b70bb 100644
--- a/app/controllers/api/v1/accounts/inboxes_controller.rb
+++ b/app/controllers/api/v1/accounts/inboxes_controller.rb
@@ -27,22 +27,23 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
ActiveRecord::Base.transaction do
channel = create_channel
@inbox = Current.account.inboxes.build(
- name: permitted_params[:name],
- greeting_message: permitted_params[:greeting_message],
- greeting_enabled: permitted_params[:greeting_enabled],
- channel: channel
+ {
+ name: inbox_name(channel),
+ channel: channel
+ }.merge(
+ permitted_params.except(:channel)
+ )
)
- @inbox.avatar.attach(permitted_params[:avatar])
@inbox.save!
end
end
def update
- @inbox.update(inbox_update_params.except(:channel))
+ @inbox.update(permitted_params.except(:channel))
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
- return unless @inbox.channel.is_a?(Channel::WebWidget) && inbox_update_params[:channel].present?
- @inbox.channel.update!(inbox_update_params[:channel])
+ channel_attributes = get_channel_attributes(@inbox.channel_type)
+ @inbox.channel.update!(permitted_params(channel_attributes)[:channel])
update_channel_feature_flags
end
@@ -77,43 +78,52 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
@agent_bot = AgentBot.find(params[:agent_bot]) if params[:agent_bot]
end
+ def inbox_name(channel)
+ return channel.try(:bot_name) if channel.is_a?(Channel::Telegram)
+
+ permitted_params[:name]
+ end
+
def create_channel
case permitted_params[:channel][:type]
when 'web_widget'
- Current.account.web_widgets.create!(permitted_params[:channel].except(:type))
+ Current.account.web_widgets.create!(permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].except(:type))
when 'api'
- Current.account.api_channels.create!(permitted_params[:channel].except(:type))
+ Current.account.api_channels.create!(permitted_params(Channel::Api::EDITABLE_ATTRS)[:channel].except(:type))
when 'email'
- Current.account.email_channels.create!(permitted_params[:channel].except(:type))
+ Current.account.email_channels.create!(permitted_params(Channel::Email::EDITABLE_ATTRS)[:channel].except(:type))
+ when 'telegram'
+ Current.account.telegram_channels.create!(permitted_params(Channel::Telegram::EDITABLE_ATTRS)[:channel].except(:type))
end
end
def update_channel_feature_flags
- return unless inbox_update_params[:channel].key? :selected_feature_flags
+ return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags
- @inbox.channel.selected_feature_flags = inbox_update_params[:channel][:selected_feature_flags]
+ @inbox.channel.selected_feature_flags = permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel][:selected_feature_flags]
@inbox.channel.save!
end
- def permitted_params
- params.permit(:id, :avatar, :name, :greeting_message, :greeting_enabled, :enable_email_collect, :csat_survey_enabled, channel:
- [:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email, :reply_time])
+ def permitted_params(channel_attributes = [])
+ params.permit(
+ :name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
+ :enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone,
+ channel: [:type, *channel_attributes]
+ )
end
- def inbox_update_params
- params.permit(:enable_auto_assignment, :enable_email_collect, :name, :avatar, :greeting_message, :greeting_enabled, :csat_survey_enabled,
- :working_hours_enabled, :out_of_office_message, :timezone,
- channel: [
- :website_url,
- :widget_color,
- :welcome_title,
- :welcome_tagline,
- :webhook_url,
- :email,
- :reply_time,
- :pre_chat_form_enabled,
- { pre_chat_form_options: [:pre_chat_message, :require_email] },
- { selected_feature_flags: [] }
- ])
+ def get_channel_attributes(channel_type)
+ case channel_type
+ when 'Channel::WebWidget'
+ Channel::WebWidget::EDITABLE_ATTRS
+ when 'Channel::Api'
+ Channel::Api::EDITABLE_ATTRS
+ when 'Channel::Email'
+ Channel::Email::EDITABLE_ATTRS
+ when 'Channel::Telegram'
+ Channel::Telegram::EDITABLE_ATTRS
+ else
+ []
+ end
end
end
diff --git a/app/controllers/webhooks/telegram_controller.rb b/app/controllers/webhooks/telegram_controller.rb
new file mode 100644
index 000000000..bc65061e7
--- /dev/null
+++ b/app/controllers/webhooks/telegram_controller.rb
@@ -0,0 +1,6 @@
+class Webhooks::TelegramController < ActionController::API
+ def process_payload
+ Webhooks::TelegramEventsJob.perform_later(params.to_unsafe_hash)
+ head :ok
+ end
+end
diff --git a/app/javascript/dashboard/components/widgets/ChannelItem.vue b/app/javascript/dashboard/components/widgets/ChannelItem.vue
index e612fa14b..819d912bd 100644
--- a/app/javascript/dashboard/components/widgets/ChannelItem.vue
+++ b/app/javascript/dashboard/components/widgets/ChannelItem.vue
@@ -76,7 +76,7 @@ export default {
if (key === 'email') {
return this.enabledFeatures.channel_email;
}
- return ['website', 'twilio', 'api', 'whatsapp', 'sms'].includes(key);
+ return ['website', 'twilio', 'api', 'whatsapp', 'sms', 'telegram'].includes(key);
},
},
methods: {
diff --git a/app/javascript/dashboard/components/widgets/Thumbnail.vue b/app/javascript/dashboard/components/widgets/Thumbnail.vue
index 2d1c3e200..e43d48a4b 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"
/>
+
Email
+
+ Telegram
+
{{ globalConfig.apiChannelName || 'API' }}
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 0cf37f148..db7fb0c43 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 Telegram from './channels/Telegram';
const channelViewList = {
facebook: Facebook,
@@ -14,6 +15,7 @@ const channelViewList = {
email: Email,
sms: Sms,
whatsapp: Whatsapp,
+ telegram: Telegram,
};
export default {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Telegram.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Telegram.vue
new file mode 100644
index 000000000..710f12f86
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Telegram.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
diff --git a/app/jobs/send_reply_job.rb b/app/jobs/send_reply_job.rb
index 6d0d171c5..77d17cc22 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::Telegram'
+ ::Telegram::SendOnTelegramService.new(message: message).perform
end
end
end
diff --git a/app/jobs/webhooks/telegram_events_job.rb b/app/jobs/webhooks/telegram_events_job.rb
new file mode 100644
index 000000000..defdb02b0
--- /dev/null
+++ b/app/jobs/webhooks/telegram_events_job.rb
@@ -0,0 +1,12 @@
+class Webhooks::TelegramEventsJob < ApplicationJob
+ queue_as :default
+
+ def perform(params = {})
+ return unless params[:bot_token]
+
+ channel = Channel::Telegram.find_by(bot_token: params[:bot_token])
+ return unless channel
+
+ Telegram::IncomingMessageService.new(inbox: channel.inbox, params: params['telegram'].with_indifferent_access).perform
+ end
+end
diff --git a/app/models/account.rb b/app/models/account.rb
index 0cfc14293..817013373 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 :telegram_channels, dependent: :destroy, class_name: '::Channel::Telegram'
has_many :canned_responses, dependent: :destroy
has_many :webhooks, dependent: :destroy
has_many :labels, dependent: :destroy
diff --git a/app/models/channel/api.rb b/app/models/channel/api.rb
index 7899da789..f3d1a9b82 100644
--- a/app/models/channel/api.rb
+++ b/app/models/channel/api.rb
@@ -19,6 +19,7 @@
class Channel::Api < ApplicationRecord
self.table_name = 'channel_api'
+ EDITABLE_ATTRS = [:webhook_url].freeze
validates :account_id, presence: true
belongs_to :account
diff --git a/app/models/channel/email.rb b/app/models/channel/email.rb
index d68136b76..806ae0f08 100644
--- a/app/models/channel/email.rb
+++ b/app/models/channel/email.rb
@@ -17,6 +17,7 @@
class Channel::Email < ApplicationRecord
self.table_name = 'channel_email'
+ EDITABLE_ATTRS = [:email].freeze
validates :account_id, presence: true
belongs_to :account
diff --git a/app/models/channel/telegram.rb b/app/models/channel/telegram.rb
new file mode 100644
index 000000000..3dd66dfd3
--- /dev/null
+++ b/app/models/channel/telegram.rb
@@ -0,0 +1,89 @@
+# == Schema Information
+#
+# Table name: channel_telegram
+#
+# id :bigint not null, primary key
+# bot_name :string
+# bot_token :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# account_id :integer not null
+#
+# Indexes
+#
+# index_channel_telegram_on_bot_token (bot_token) UNIQUE
+#
+
+class Channel::Telegram < ApplicationRecord
+ 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
+
+ def name
+ 'Telegram'
+ end
+
+ def has_24_hour_messaging_window?
+ false
+ end
+
+ def telegram_api_url
+ "https://api.telegram.org/bot#{bot_token}"
+ end
+
+ def send_message_on_telegram(message, chat_id)
+ response = HTTParty.post("#{telegram_api_url}/sendMessage",
+ body: {
+ chat_id: chat_id,
+ text: message
+ })
+
+ response.parsed_response['result']['message_id'] if response.success?
+ end
+
+ def get_telegram_profile_image(user_id)
+ # get profile image from telegram
+ response = HTTParty.get("#{telegram_api_url}/getUserProfilePhotos", query: { user_id: user_id })
+ return nil unless response.success?
+
+ photos = response.parsed_response['result']['photos']
+ return if photos.blank?
+
+ get_telegram_file_path(photos.first.last['file_id'])
+ end
+
+ def get_telegram_file_path(file_id)
+ response = HTTParty.get("#{telegram_api_url}/getFile", query: { file_id: file_id })
+ return nil unless response.success?
+
+ "https://api.telegram.org/file/bot#{bot_token}/#{response.parsed_response['result']['file_path']}"
+ end
+
+ private
+
+ def ensure_valid_bot_token
+ response = HTTParty.get("#{telegram_api_url}/getMe")
+ unless response.success?
+ errors.add(:bot_token, 'invalid token')
+ return
+ end
+
+ self.bot_name = response.parsed_response['result']['username']
+ end
+
+ def setup_telegram_webhook
+ HTTParty.post("#{telegram_api_url}/deleteWebhook")
+ response = HTTParty.post("#{telegram_api_url}/setWebhook",
+ body: {
+ url: "#{ENV['FRONTEND_URL']}/webhooks/telegram/#{bot_token}"
+ })
+ errors.add(:bot_token, 'error setting up the webook') unless response.success?
+ end
+end
diff --git a/app/models/channel/web_widget.rb b/app/models/channel/web_widget.rb
index 02b85f75a..58f9b9ec3 100644
--- a/app/models/channel/web_widget.rb
+++ b/app/models/channel/web_widget.rb
@@ -26,8 +26,10 @@
class Channel::WebWidget < ApplicationRecord
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] },
+ { selected_feature_flags: [] }].freeze
validates :website_url, presence: true
validates :widget_color, presence: true
diff --git a/app/models/inbox.rb b/app/models/inbox.rb
index ea4a1faec..e6f89ab21 100644
--- a/app/models/inbox.rb
+++ b/app/models/inbox.rb
@@ -78,6 +78,10 @@ class Inbox < ApplicationRecord
channel_type == 'Channel::Api'
end
+ def email?
+ channel_type == 'Channel::Email'
+ end
+
def inbox_type
channel.name
end
diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb
new file mode 100644
index 000000000..0e47f2813
--- /dev/null
+++ b/app/services/telegram/incoming_message_service.rb
@@ -0,0 +1,106 @@
+class Telegram::IncomingMessageService
+ include ::FileTypeHelper
+ pattr_initialize [:inbox!, :params!]
+
+ def perform
+ set_contact
+ update_contact_avatar
+ set_conversation
+ @message = @conversation.messages.create(
+ content: params[:message][:text],
+ account_id: @inbox.account_id,
+ inbox_id: @inbox.id,
+ message_type: :incoming,
+ sender: @contact,
+ source_id: (params[:message][:message_id]).to_s
+ )
+ attach_files
+ @message.save!
+ end
+
+ private
+
+ def account
+ @account ||= inbox.account
+ end
+
+ def set_contact
+ contact_inbox = ::ContactBuilder.new(
+ source_id: params[:message][:from][:id],
+ inbox: inbox,
+ contact_attributes: contact_attributes
+ ).perform
+
+ @contact_inbox = contact_inbox
+ @contact = contact_inbox.contact
+ end
+
+ def update_contact_avatar
+ return if @contact.avatar.attached?
+
+ avatar_url = inbox.channel.get_telegram_profile_image(params[:message][:from][:id])
+ ::ContactAvatarJob.perform_later(@contact, avatar_url) if avatar_url
+ end
+
+ def conversation_params
+ {
+ account_id: @inbox.account_id,
+ inbox_id: @inbox.id,
+ contact_id: @contact.id,
+ contact_inbox_id: @contact_inbox.id,
+ additional_attributes: conversation_additional_attributes
+ }
+ end
+
+ def set_conversation
+ @conversation = @contact_inbox.conversations.first
+ return if @conversation
+
+ @conversation = ::Conversation.create!(conversation_params)
+ end
+
+ def contact_attributes
+ {
+ name: "#{params[:message][:from][:first_name]} #{params[:message][:from][:last_name]}",
+ additional_attributes: additional_attributes
+ }
+ end
+
+ def additional_attributes
+ {
+ username: params[:message][:from][:username],
+ language_code: params[:message][:from][:language_code]
+ }
+ end
+
+ def conversation_additional_attributes
+ {
+ chat_id: params[:message][:chat][:id]
+ }
+ end
+
+ def file_content_type
+ params[:message][:photo].present? ? :image : file_type(params[:message][:document][:mime_type])
+ end
+
+ def attach_files
+ file = params[:message][:document]
+ file ||= params[:message][:photo]&.last
+
+ return unless file
+
+ attachment_file = Down.download(
+ inbox.channel.get_telegram_file_path(file[:file_id])
+ )
+
+ @message.attachments.new(
+ account_id: @message.account_id,
+ file_type: file_content_type,
+ file: {
+ io: attachment_file,
+ filename: attachment_file.original_filename,
+ content_type: attachment_file.content_type
+ }
+ )
+ end
+end
diff --git a/app/services/telegram/send_on_telegram_service.rb b/app/services/telegram/send_on_telegram_service.rb
new file mode 100644
index 000000000..e6e98ff39
--- /dev/null
+++ b/app/services/telegram/send_on_telegram_service.rb
@@ -0,0 +1,22 @@
+class Telegram::SendOnTelegramService < Base::SendOnChannelService
+ private
+
+ def channel_class
+ Channel::Telegram
+ end
+
+ def perform_reply
+ ## send reply to telegram message api
+ # https://core.telegram.org/bots/api#sendmessage
+ message_id = channel.send_message_on_telegram(message.content, conversation[:additional_attributes]['chat_id'])
+ message.update!(source_id: message_id) if message_id.present?
+ end
+
+ def inbox
+ @inbox ||= message.inbox
+ end
+
+ def channel
+ @channel ||= inbox.channel
+ end
+end
diff --git a/app/views/api/v1/models/_inbox.json.jbuilder b/app/views/api/v1/models/_inbox.json.jbuilder
index 55ea31e3c..139831b1e 100644
--- a/app/views/api/v1/models/_inbox.json.jbuilder
+++ b/app/views/api/v1/models/_inbox.json.jbuilder
@@ -29,4 +29,6 @@ if resource.web_widget?
json.pre_chat_form_enabled resource.channel.try(:pre_chat_form_enabled)
json.pre_chat_form_options resource.channel.try(:pre_chat_form_options)
end
+json.email resource.channel.try(:email) if resource.email?
+json.webhook_url resource.channel.try(:webhook_url) if resource.api?
json.inbox_identifier resource.channel.try(:identifier) if resource.api?
diff --git a/config/routes.rb b/config/routes.rb
index 836acee04..a3e543074 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -242,6 +242,7 @@ Rails.application.routes.draw do
mount Facebook::Messenger::Server, at: 'bot'
get 'webhooks/twitter', to: 'api/v1/webhooks#twitter_crc'
post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events'
+ post 'webhooks/telegram/:bot_token', to: 'webhooks/telegram#process_payload'
namespace :twitter do
resource :callback, only: [:show]
diff --git a/db/migrate/20210828124043_add_telegram_channel.rb b/db/migrate/20210828124043_add_telegram_channel.rb
new file mode 100644
index 000000000..58466550c
--- /dev/null
+++ b/db/migrate/20210828124043_add_telegram_channel.rb
@@ -0,0 +1,10 @@
+class AddTelegramChannel < ActiveRecord::Migration[6.1]
+ def change
+ create_table :channel_telegram do |t|
+ t.string :bot_name
+ t.integer :account_id, null: false
+ t.string :bot_token, null: false, index: { unique: true }
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 86bb50653..5b0e0366b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2021_08_27_120929) do
+ActiveRecord::Schema.define(version: 2021_08_28_124043) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
@@ -185,6 +185,15 @@ ActiveRecord::Schema.define(version: 2021_08_27_120929) do
t.index ["page_id"], name: "index_channel_facebook_pages_on_page_id"
end
+ create_table "channel_telegram", force: :cascade do |t|
+ t.string "bot_name"
+ t.integer "account_id", null: false
+ t.string "bot_token", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["bot_token"], name: "index_channel_telegram_on_bot_token", unique: true
+ end
+
create_table "channel_twilio_sms", force: :cascade do |t|
t.string "phone_number", null: false
t.string "auth_token", null: false
diff --git a/spec/controllers/api/v1/accounts/inboxes_controller_spec.rb b/spec/controllers/api/v1/accounts/inboxes_controller_spec.rb
index d769302a2..b4ac8c70c 100644
--- a/spec/controllers/api/v1/accounts/inboxes_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/inboxes_controller_spec.rb
@@ -255,16 +255,6 @@ RSpec.describe 'Inboxes API', type: :request do
let(:admin) { create(:user, account: account, role: :administrator) }
let(:valid_params) { { name: 'test', channel: { type: 'web_widget', website_url: 'test.com' } } }
- it 'creates inbox' do
- post "/api/v1/accounts/#{account.id}/inboxes",
- headers: admin.create_new_auth_token,
- params: valid_params,
- as: :json
-
- expect(response).to have_http_status(:success)
- expect(response.body).to include('test.com')
- end
-
it 'will not create inbox for agent' do
agent = create(:user, account: account, role: :agent)
@@ -275,6 +265,26 @@ RSpec.describe 'Inboxes API', type: :request do
expect(response).to have_http_status(:unauthorized)
end
+
+ it 'creates a webwidget inbox when administrator' do
+ post "/api/v1/accounts/#{account.id}/inboxes",
+ headers: admin.create_new_auth_token,
+ params: valid_params,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include('test.com')
+ end
+
+ it 'creates a email inbox when administrator' do
+ post "/api/v1/accounts/#{account.id}/inboxes",
+ headers: admin.create_new_auth_token,
+ params: { name: 'test', channel: { type: 'email', email: 'test@test.com' } },
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include('test@test.com')
+ end
end
end
@@ -314,6 +324,34 @@ RSpec.describe 'Inboxes API', type: :request do
expect(inbox.reload.enable_auto_assignment).to be_falsey
end
+ it 'updates api inbox when administrator' do
+ api_channel = create(:channel_api, account: account)
+ api_inbox = create(:inbox, channel: api_channel, account: account)
+
+ patch "/api/v1/accounts/#{account.id}/inboxes/#{api_inbox.id}",
+ headers: admin.create_new_auth_token,
+ params: { enable_auto_assignment: false, channel: { webhook_url: 'webhook.test' } },
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(api_inbox.reload.enable_auto_assignment).to be_falsey
+ expect(api_channel.reload.webhook_url).to eq('webhook.test')
+ end
+
+ it 'updates email inbox when administrator' do
+ email_channel = create(:channel_email, account: account)
+ email_inbox = create(:inbox, channel: email_channel, account: account)
+
+ patch "/api/v1/accounts/#{account.id}/inboxes/#{email_inbox.id}",
+ headers: admin.create_new_auth_token,
+ params: { enable_auto_assignment: false, channel: { email: 'emailtest@email.test' } },
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(email_inbox.reload.enable_auto_assignment).to be_falsey
+ expect(email_channel.reload.email).to eq('emailtest@email.test')
+ end
+
it 'updates avatar when administrator' do
# no avatar before upload
expect(inbox.avatar.attached?).to eq(false)
diff --git a/spec/controllers/webhooks/telegram_controller_spec.rb b/spec/controllers/webhooks/telegram_controller_spec.rb
new file mode 100644
index 000000000..5366b467a
--- /dev/null
+++ b/spec/controllers/webhooks/telegram_controller_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+RSpec.describe 'Webhooks::TelegramController', type: :request do
+ describe 'POST /webhooks/telegram/{:bot_token}' do
+ it 'call the telegram events job with the params' do
+ allow(Webhooks::TelegramEventsJob).to receive(:perform_later)
+ expect(Webhooks::TelegramEventsJob).to receive(:perform_later)
+ post '/webhooks/telegram/random_bot_token', params: { content: 'hello' }
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/factories/channel/channel_telegram.rb b/spec/factories/channel/channel_telegram.rb
new file mode 100644
index 000000000..99f332c4d
--- /dev/null
+++ b/spec/factories/channel/channel_telegram.rb
@@ -0,0 +1,16 @@
+FactoryBot.define do
+ factory :channel_telegram, class: 'Channel::Telegram' do
+ bot_token { '2324234324' }
+ account
+
+ before(:create) do |channel_telegram|
+ # we are skipping some of the validation methods
+ channel_telegram.define_singleton_method(:ensure_valid_bot_token) { return }
+ channel_telegram.define_singleton_method(:setup_telegram_webhook) { return }
+ end
+
+ after(:create) do |channel_telegram|
+ create(:inbox, channel: channel_telegram, account: channel_telegram.account)
+ end
+ end
+end
diff --git a/spec/jobs/send_reply_job_spec.rb b/spec/jobs/send_reply_job_spec.rb
new file mode 100644
index 000000000..b7c27974b
--- /dev/null
+++ b/spec/jobs/send_reply_job_spec.rb
@@ -0,0 +1,59 @@
+require 'rails_helper'
+
+RSpec.describe SendReplyJob, type: :job do
+ subject(:job) { described_class.perform_later(message) }
+
+ let(:message) { create(:message) }
+
+ it 'enqueues the job' do
+ expect { job }.to have_enqueued_job(described_class)
+ .with(message)
+ .on_queue('high')
+ end
+
+ context 'when the job is triggered on a new message' do
+ let(:process_service) { double }
+
+ before do
+ allow(process_service).to receive(:perform)
+ end
+
+ it 'calls Facebook::SendOnFacebookService when its facebook message' do
+ facebook_channel = create(:channel_facebook_page)
+ facebook_inbox = create(:inbox, channel: facebook_channel)
+ message = create(:message, conversation: create(:conversation, inbox: facebook_inbox))
+ allow(Facebook::SendOnFacebookService).to receive(:new).with(message: message).and_return(process_service)
+ expect(Facebook::SendOnFacebookService).to receive(:new).with(message: message)
+ expect(process_service).to receive(:perform)
+ described_class.perform_now(message.id)
+ end
+
+ it 'calls ::Twitter::SendOnTwitterService when its twitter message' do
+ twitter_channel = create(:channel_twitter_profile)
+ twitter_inbox = create(:inbox, channel: twitter_channel)
+ message = create(:message, conversation: create(:conversation, inbox: twitter_inbox))
+ allow(::Twitter::SendOnTwitterService).to receive(:new).with(message: message).and_return(process_service)
+ expect(::Twitter::SendOnTwitterService).to receive(:new).with(message: message)
+ expect(process_service).to receive(:perform)
+ described_class.perform_now(message.id)
+ end
+
+ it 'calls ::Twilio::SendOnTwilioService when its twilio message' do
+ twilio_channel = create(:channel_twilio_sms)
+ message = create(:message, conversation: create(:conversation, inbox: twilio_channel.inbox))
+ allow(::Twilio::SendOnTwilioService).to receive(:new).with(message: message).and_return(process_service)
+ expect(::Twilio::SendOnTwilioService).to receive(:new).with(message: message)
+ expect(process_service).to receive(:perform)
+ described_class.perform_now(message.id)
+ end
+
+ it 'calls ::Telegram::SendOnTelegramService when its telegram message' do
+ telegram_channel = create(:channel_telegram)
+ message = create(:message, conversation: create(:conversation, inbox: telegram_channel.inbox))
+ allow(::Telegram::SendOnTelegramService).to receive(:new).with(message: message).and_return(process_service)
+ expect(::Telegram::SendOnTelegramService).to receive(:new).with(message: message)
+ expect(process_service).to receive(:perform)
+ described_class.perform_now(message.id)
+ end
+ end
+end
diff --git a/spec/jobs/webhooks/telegram_events_job_spec.rb b/spec/jobs/webhooks/telegram_events_job_spec.rb
new file mode 100644
index 000000000..9f45955e6
--- /dev/null
+++ b/spec/jobs/webhooks/telegram_events_job_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+RSpec.describe Webhooks::TelegramEventsJob, type: :job do
+ subject(:job) { described_class.perform_later(params) }
+
+ let!(:telegram_channel) { create(:channel_telegram) }
+ let!(:params) { { bot_token: telegram_channel.bot_token, 'telegram' => { test: 'test' } } }
+
+ it 'enqueues the job' do
+ expect { job }.to have_enqueued_job(described_class)
+ .with(params)
+ .on_queue('default')
+ end
+
+ context 'when invalid params' do
+ it 'returns nil when no bot_token' do
+ expect(described_class.perform_now({})).to be_nil
+ end
+
+ it 'returns nil when invalid bot_token' do
+ expect(described_class.perform_now({ bot_token: 'invalid' })).to be_nil
+ end
+ end
+
+ context 'when valid params' do
+ it 'calls Telegram::IncomingMessageService' do
+ process_service = double
+ allow(Telegram::IncomingMessageService).to receive(:new).and_return(process_service)
+ allow(process_service).to receive(:perform)
+ expect(Telegram::IncomingMessageService).to receive(:new).with(inbox: telegram_channel.inbox,
+ params: params['telegram'].with_indifferent_access)
+ expect(process_service).to receive(:perform)
+ described_class.perform_now(params)
+ end
+ end
+end
diff --git a/spec/services/telegram/incoming_message_service_spec.rb b/spec/services/telegram/incoming_message_service_spec.rb
new file mode 100644
index 000000000..79e361c79
--- /dev/null
+++ b/spec/services/telegram/incoming_message_service_spec.rb
@@ -0,0 +1,27 @@
+require 'rails_helper'
+
+describe Telegram::IncomingMessageService do
+ let!(:telegram_channel) { create(:channel_telegram) }
+
+ describe '#perform' do
+ context 'when valid text message params' do
+ it 'creates appropriate conversations, message and contacts' do
+ params = {
+ 'update_id' => 2_342_342_343_242,
+ 'message' => {
+ 'message_id' => 1,
+ 'from' => {
+ 'id' => 23, 'is_bot' => false, 'first_name' => 'Sojan', 'last_name' => 'Jose', 'username' => 'sojan', 'language_code' => 'en'
+ },
+ 'chat' => { 'id' => 23, 'first_name' => 'Sojan', 'last_name' => 'Jose', 'username' => 'sojan', 'type' => 'private' },
+ 'date' => 1_631_132_077, 'text' => 'test'
+ }
+ }.with_indifferent_access
+ described_class.new(inbox: telegram_channel.inbox, params: params).perform
+ expect(telegram_channel.inbox.conversations).not_to eq(0)
+ expect(Contact.all.first.name).to eq('Sojan Jose')
+ expect(telegram_channel.inbox.messages.first.content).to eq('test')
+ end
+ end
+ end
+end
diff --git a/spec/services/telegram/send_on_telegram_service_spec.rb b/spec/services/telegram/send_on_telegram_service_spec.rb
new file mode 100644
index 000000000..6dbb3f7c6
--- /dev/null
+++ b/spec/services/telegram/send_on_telegram_service_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+describe Telegram::SendOnTelegramService do
+ describe '#perform' do
+ context 'when a valid message' do
+ it 'calls channel.send_message_on_telegram' do
+ telegram_request = double
+ telegram_channel = create(:channel_telegram)
+ message = create(:message, message_type: :outgoing, content: 'test',
+ conversation: create(:conversation, inbox: telegram_channel.inbox, additional_attributes: { 'chat_id' => '123' }))
+ allow(HTTParty).to receive(:post).and_return(telegram_request)
+ allow(telegram_request).to receive(:success?).and_return(true)
+ allow(telegram_request).to receive(:parsed_response).and_return({ 'result' => { 'message_id' => 'telegram_123' } })
+ described_class.new(message: message).perform
+ expect(message.source_id).to eq('telegram_123')
+ end
+ end
+ end
+end