mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	feat: Telegram Channel (#2901)
- Ability to configure telegram bots as a channel in chatwoot - Receive a message sent to the telegram bot in chatwoot - Ability to reply to telegram users from chatwoot - Receive attachment messages in chatwoot fixes: #1843
This commit is contained in:
		| @@ -100,6 +100,7 @@ Metrics/AbcSize: | |||||||
|     - 'app/controllers/concerns/auth_helper.rb' |     - 'app/controllers/concerns/auth_helper.rb' | ||||||
|     - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' |     - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' | ||||||
|     - 'db/migrate/20161123131628_devise_token_auth_create_users.rb' |     - 'db/migrate/20161123131628_devise_token_auth_create_users.rb' | ||||||
|  |     - 'app/controllers/api/v1/accounts/inboxes_controller.rb' | ||||||
| Metrics/CyclomaticComplexity: | Metrics/CyclomaticComplexity: | ||||||
|   Max: 7 |   Max: 7 | ||||||
|   Exclude: |   Exclude: | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -78,7 +78,6 @@ gem 'wisper', '2.0.0' | |||||||
| ##--- gems for channels ---## | ##--- gems for channels ---## | ||||||
| # TODO: bump up gem to 2.0 | # TODO: bump up gem to 2.0 | ||||||
| gem 'facebook-messenger' | gem 'facebook-messenger' | ||||||
| gem 'telegram-bot-ruby' |  | ||||||
| gem 'twilio-ruby', '~> 5.32.0' | gem 'twilio-ruby', '~> 5.32.0' | ||||||
| # twitty will handle subscription of twitter account events | # twitty will handle subscription of twitter account events | ||||||
| # gem 'twitty', git: 'https://github.com/chatwoot/twitty' | # gem 'twitty', git: 'https://github.com/chatwoot/twitty' | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								Gemfile.lock
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								Gemfile.lock
									
									
									
									
									
								
							| @@ -98,10 +98,6 @@ GEM | |||||||
|       aws-sigv4 (~> 1.1) |       aws-sigv4 (~> 1.1) | ||||||
|     aws-sigv4 (1.2.4) |     aws-sigv4 (1.2.4) | ||||||
|       aws-eventstream (~> 1, >= 1.0.2) |       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-blob (2.0.1) | ||||||
|       azure-storage-common (~> 2.0) |       azure-storage-common (~> 2.0) | ||||||
|       nokogiri (~> 1.11.0.rc2) |       nokogiri (~> 1.11.0.rc2) | ||||||
| @@ -130,8 +126,6 @@ GEM | |||||||
|       thor (~> 1.0) |       thor (~> 1.0) | ||||||
|     byebug (11.1.3) |     byebug (11.1.3) | ||||||
|     coderay (1.1.3) |     coderay (1.1.3) | ||||||
|     coercible (1.0.0) |  | ||||||
|       descendants_tracker (~> 0.0.1) |  | ||||||
|     commonmarker (0.22.0) |     commonmarker (0.22.0) | ||||||
|     concurrent-ruby (1.1.9) |     concurrent-ruby (1.1.9) | ||||||
|     connection_pool (2.2.5) |     connection_pool (2.2.5) | ||||||
| @@ -152,8 +146,6 @@ GEM | |||||||
|       ffi (~> 1.0) |       ffi (~> 1.0) | ||||||
|       msgpack |       msgpack | ||||||
|     declarative (0.0.20) |     declarative (0.0.20) | ||||||
|     descendants_tracker (0.0.4) |  | ||||||
|       thread_safe (~> 0.3, >= 0.3.1) |  | ||||||
|     devise (4.8.0) |     devise (4.8.0) | ||||||
|       bcrypt (~> 3.0) |       bcrypt (~> 3.0) | ||||||
|       orm_adapter (~> 0.1) |       orm_adapter (~> 0.1) | ||||||
| @@ -179,7 +171,6 @@ GEM | |||||||
|       railties (>= 3.2) |       railties (>= 3.2) | ||||||
|     down (5.2.3) |     down (5.2.3) | ||||||
|       addressable (~> 2.8) |       addressable (~> 2.8) | ||||||
|     dry-inflector (0.2.1) |  | ||||||
|     ecma-re-validator (0.3.0) |     ecma-re-validator (0.3.0) | ||||||
|       regexp_parser (~> 2.0) |       regexp_parser (~> 2.0) | ||||||
|     erubi (1.10.0) |     erubi (1.10.0) | ||||||
| @@ -292,7 +283,6 @@ GEM | |||||||
|     httpclient (2.8.3) |     httpclient (2.8.3) | ||||||
|     i18n (1.8.10) |     i18n (1.8.10) | ||||||
|       concurrent-ruby (~> 1.0) |       concurrent-ruby (~> 1.0) | ||||||
|     ice_nine (0.11.2) |  | ||||||
|     image_processing (1.12.1) |     image_processing (1.12.1) | ||||||
|       mini_magick (>= 4.9.5, < 5) |       mini_magick (>= 4.9.5, < 5) | ||||||
|       ruby-vips (>= 2.0.17, < 3) |       ruby-vips (>= 2.0.17, < 3) | ||||||
| @@ -564,13 +554,8 @@ GEM | |||||||
|       sprockets (>= 3.0.0) |       sprockets (>= 3.0.0) | ||||||
|     squasher (0.6.2) |     squasher (0.6.2) | ||||||
|     statsd-ruby (1.5.0) |     statsd-ruby (1.5.0) | ||||||
|     telegram-bot-ruby (0.16.0) |  | ||||||
|       dry-inflector |  | ||||||
|       faraday |  | ||||||
|       virtus (~> 2.0) |  | ||||||
|     telephone_number (1.4.12) |     telephone_number (1.4.12) | ||||||
|     thor (1.1.0) |     thor (1.1.0) | ||||||
|     thread_safe (0.3.6) |  | ||||||
|     tilt (2.0.10) |     tilt (2.0.10) | ||||||
|     time_diff (0.3.0) |     time_diff (0.3.0) | ||||||
|       activesupport |       activesupport | ||||||
| @@ -598,10 +583,6 @@ GEM | |||||||
|     valid_email2 (4.0.0) |     valid_email2 (4.0.0) | ||||||
|       activemodel (>= 3.2) |       activemodel (>= 3.2) | ||||||
|       mail (~> 2.5) |       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) |     warden (1.2.9) | ||||||
|       rack (>= 2.0.9) |       rack (>= 2.0.9) | ||||||
|     web-console (4.1.0) |     web-console (4.1.0) | ||||||
| @@ -716,7 +697,6 @@ DEPENDENCIES | |||||||
|   spring |   spring | ||||||
|   spring-watcher-listen |   spring-watcher-listen | ||||||
|   squasher |   squasher | ||||||
|   telegram-bot-ruby |  | ||||||
|   telephone_number |   telephone_number | ||||||
|   time_diff |   time_diff | ||||||
|   twilio-ruby (~> 5.32.0) |   twilio-ruby (~> 5.32.0) | ||||||
|   | |||||||
| @@ -27,22 +27,23 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController | |||||||
|     ActiveRecord::Base.transaction do |     ActiveRecord::Base.transaction do | ||||||
|       channel = create_channel |       channel = create_channel | ||||||
|       @inbox = Current.account.inboxes.build( |       @inbox = Current.account.inboxes.build( | ||||||
|         name: permitted_params[:name], |         { | ||||||
|         greeting_message: permitted_params[:greeting_message], |           name: inbox_name(channel), | ||||||
|         greeting_enabled: permitted_params[:greeting_enabled], |           channel: channel | ||||||
|         channel: channel |         }.merge( | ||||||
|  |           permitted_params.except(:channel) | ||||||
|  |         ) | ||||||
|       ) |       ) | ||||||
|       @inbox.avatar.attach(permitted_params[:avatar]) |  | ||||||
|       @inbox.save! |       @inbox.save! | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def update |   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] |     @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 |     update_channel_feature_flags | ||||||
|   end |   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] |     @agent_bot = AgentBot.find(params[:agent_bot]) if params[:agent_bot] | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   def inbox_name(channel) | ||||||
|  |     return channel.try(:bot_name) if channel.is_a?(Channel::Telegram) | ||||||
|  |  | ||||||
|  |     permitted_params[:name] | ||||||
|  |   end | ||||||
|  |  | ||||||
|   def create_channel |   def create_channel | ||||||
|     case permitted_params[:channel][:type] |     case permitted_params[:channel][:type] | ||||||
|     when 'web_widget' |     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' |     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' |     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 | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def update_channel_feature_flags |   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! |     @inbox.channel.save! | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def permitted_params |   def permitted_params(channel_attributes = []) | ||||||
|     params.permit(:id, :avatar, :name, :greeting_message, :greeting_enabled, :enable_email_collect, :csat_survey_enabled, channel: |     params.permit( | ||||||
|       [:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email, :reply_time]) |       :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 |   end | ||||||
|  |  | ||||||
|   def inbox_update_params |   def get_channel_attributes(channel_type) | ||||||
|     params.permit(:enable_auto_assignment, :enable_email_collect, :name, :avatar, :greeting_message, :greeting_enabled, :csat_survey_enabled, |     case channel_type | ||||||
|                   :working_hours_enabled, :out_of_office_message, :timezone, |     when 'Channel::WebWidget' | ||||||
|                   channel: [ |       Channel::WebWidget::EDITABLE_ATTRS | ||||||
|                     :website_url, |     when 'Channel::Api' | ||||||
|                     :widget_color, |       Channel::Api::EDITABLE_ATTRS | ||||||
|                     :welcome_title, |     when 'Channel::Email' | ||||||
|                     :welcome_tagline, |       Channel::Email::EDITABLE_ATTRS | ||||||
|                     :webhook_url, |     when 'Channel::Telegram' | ||||||
|                     :email, |       Channel::Telegram::EDITABLE_ATTRS | ||||||
|                     :reply_time, |     else | ||||||
|                     :pre_chat_form_enabled, |       [] | ||||||
|                     { pre_chat_form_options: [:pre_chat_message, :require_email] }, |     end | ||||||
|                     { selected_feature_flags: [] } |  | ||||||
|                   ]) |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								app/controllers/webhooks/telegram_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/controllers/webhooks/telegram_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | class Webhooks::TelegramController < ActionController::API | ||||||
|  |   def process_payload | ||||||
|  |     Webhooks::TelegramEventsJob.perform_later(params.to_unsafe_hash) | ||||||
|  |     head :ok | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -76,7 +76,7 @@ export default { | |||||||
|       if (key === 'email') { |       if (key === 'email') { | ||||||
|         return this.enabledFeatures.channel_email; |         return this.enabledFeatures.channel_email; | ||||||
|       } |       } | ||||||
|       return ['website', 'twilio', 'api', 'whatsapp', 'sms'].includes(key); |       return ['website', 'twilio', 'api', 'whatsapp', 'sms', 'telegram'].includes(key); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|   | |||||||
| @@ -35,6 +35,13 @@ | |||||||
|       :style="badgeStyle" |       :style="badgeStyle" | ||||||
|       src="~dashboard/assets/images/channels/whatsapp.png" |       src="~dashboard/assets/images/channels/whatsapp.png" | ||||||
|     /> |     /> | ||||||
|  |     <img | ||||||
|  |       v-if="badge === 'Channel::Telegram'" | ||||||
|  |       id="badge" | ||||||
|  |       class="source-badge" | ||||||
|  |       :style="badgeStyle" | ||||||
|  |       src="~dashboard/assets/images/channels/telegram.png" | ||||||
|  |     /> | ||||||
|     <div |     <div | ||||||
|       v-if="showStatusIndicator" |       v-if="showStatusIndicator" | ||||||
|       :class="`source-badge user-online-status user-online-status--${status}`" |       :class="`source-badge user-online-status user-online-status--${status}`" | ||||||
|   | |||||||
| @@ -171,6 +171,19 @@ | |||||||
|           "ERROR_MESSAGE": "We were not able to save the email channel" |           "ERROR_MESSAGE": "We were not able to save the email channel" | ||||||
|         }, |         }, | ||||||
|         "FINISH_MESSAGE": "Start forwarding your emails to the following email address." |         "FINISH_MESSAGE": "Start forwarding your emails to the following email address." | ||||||
|  |       }, | ||||||
|  |        "TELEGRAM_CHANNEL": { | ||||||
|  |         "TITLE": "Telegram Channel", | ||||||
|  |         "DESC": "Integrate with Telegram channel and start supporting your customers.", | ||||||
|  |         "BOT_TOKEN": { | ||||||
|  |           "LABEL": "Bot Token", | ||||||
|  |           "SUBTITLE": "Configure the bot token you have obtained from Telegram BotFather.", | ||||||
|  |           "PLACEHOLDER": "Bot Token" | ||||||
|  |         }, | ||||||
|  |         "SUBMIT_BUTTON": "Create Telegram Channel", | ||||||
|  |         "API": { | ||||||
|  |           "ERROR_MESSAGE": "We were not able to save the telegram channel" | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       "AUTH": { |       "AUTH": { | ||||||
|         "TITLE": "Choose a channel", |         "TITLE": "Choose a channel", | ||||||
|   | |||||||
| @@ -48,6 +48,9 @@ | |||||||
|                 <span v-if="item.channel_type === 'Channel::Email'"> |                 <span v-if="item.channel_type === 'Channel::Email'"> | ||||||
|                   Email |                   Email | ||||||
|                 </span> |                 </span> | ||||||
|  |                 <span v-if="item.channel_type === 'Channel::Telegram'"> | ||||||
|  |                   Telegram | ||||||
|  |                 </span> | ||||||
|                 <span v-if="item.channel_type === 'Channel::Api'"> |                 <span v-if="item.channel_type === 'Channel::Api'"> | ||||||
|                   {{ globalConfig.apiChannelName || 'API' }} |                   {{ globalConfig.apiChannelName || 'API' }} | ||||||
|                 </span> |                 </span> | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import Api from './channels/Api'; | |||||||
| import Email from './channels/Email'; | import Email from './channels/Email'; | ||||||
| import Sms from './channels/Sms'; | import Sms from './channels/Sms'; | ||||||
| import Whatsapp from './channels/Whatsapp'; | import Whatsapp from './channels/Whatsapp'; | ||||||
|  | import Telegram from './channels/Telegram'; | ||||||
|  |  | ||||||
| const channelViewList = { | const channelViewList = { | ||||||
|   facebook: Facebook, |   facebook: Facebook, | ||||||
| @@ -14,6 +15,7 @@ const channelViewList = { | |||||||
|   email: Email, |   email: Email, | ||||||
|   sms: Sms, |   sms: Sms, | ||||||
|   whatsapp: Whatsapp, |   whatsapp: Whatsapp, | ||||||
|  |   telegram: Telegram, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   | |||||||
| @@ -0,0 +1,93 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="wizard-body small-9 columns"> | ||||||
|  |     <page-header | ||||||
|  |       :header-title="$t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.TITLE')" | ||||||
|  |       :header-content="$t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.DESC')" | ||||||
|  |     /> | ||||||
|  |     <form class="row" @submit.prevent="createChannel()"> | ||||||
|  |       <div class="medium-8 columns"> | ||||||
|  |         <label :class="{ error: $v.botToken.$error }"> | ||||||
|  |           {{ $t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.BOT_TOKEN.LABEL') }} | ||||||
|  |           <input | ||||||
|  |             v-model.trim="botToken" | ||||||
|  |             type="text" | ||||||
|  |             :placeholder=" | ||||||
|  |               $t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.BOT_TOKEN.PLACEHOLDER') | ||||||
|  |             " | ||||||
|  |             @blur="$v.botToken.$touch" | ||||||
|  |           /> | ||||||
|  |         </label> | ||||||
|  |         <p class="help-text"> | ||||||
|  |           {{ $t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.BOT_TOKEN.SUBTITLE') }} | ||||||
|  |         </p> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <div class="medium-12 columns"> | ||||||
|  |         <woot-submit-button | ||||||
|  |           :loading="uiFlags.isCreating" | ||||||
|  |           :button-text="$t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.SUBMIT_BUTTON')" | ||||||
|  |         /> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import { mapGetters } from 'vuex'; | ||||||
|  | import alertMixin from 'shared/mixins/alertMixin'; | ||||||
|  | import { required } from 'vuelidate/lib/validators'; | ||||||
|  | import router from '../../../../index'; | ||||||
|  | import PageHeader from '../../SettingsSubPageHeader'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   components: { | ||||||
|  |     PageHeader, | ||||||
|  |   }, | ||||||
|  |   mixins: [alertMixin], | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       botToken: '', | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     ...mapGetters({ | ||||||
|  |       uiFlags: 'inboxes/getUIFlags', | ||||||
|  |     }), | ||||||
|  |   }, | ||||||
|  |   validations: { | ||||||
|  |     botToken: { required }, | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     async createChannel() { | ||||||
|  |       this.$v.$touch(); | ||||||
|  |       if (this.$v.$invalid) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       try { | ||||||
|  |         const telegramChannel = await this.$store.dispatch( | ||||||
|  |           'inboxes/createChannel', | ||||||
|  |           { | ||||||
|  |             channel: { | ||||||
|  |               type: 'telegram', | ||||||
|  |               bot_token: this.botToken, | ||||||
|  |             }, | ||||||
|  |           } | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         router.replace({ | ||||||
|  |           name: 'settings_inboxes_add_agents', | ||||||
|  |           params: { | ||||||
|  |             page: 'new', | ||||||
|  |             inbox_id: telegramChannel.id, | ||||||
|  |           }, | ||||||
|  |         }); | ||||||
|  |       } catch (error) { | ||||||
|  |         this.showAlert( | ||||||
|  |           this.$t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.API.ERROR_MESSAGE') | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
| @@ -11,6 +11,8 @@ class SendReplyJob < ApplicationJob | |||||||
|       ::Twitter::SendOnTwitterService.new(message: message).perform |       ::Twitter::SendOnTwitterService.new(message: message).perform | ||||||
|     when 'Channel::TwilioSms' |     when 'Channel::TwilioSms' | ||||||
|       ::Twilio::SendOnTwilioService.new(message: message).perform |       ::Twilio::SendOnTwilioService.new(message: message).perform | ||||||
|  |     when 'Channel::Telegram' | ||||||
|  |       ::Telegram::SendOnTelegramService.new(message: message).perform | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								app/jobs/webhooks/telegram_events_job.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/jobs/webhooks/telegram_events_job.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
| @@ -51,6 +51,7 @@ class Account < ApplicationRecord | |||||||
|   has_many :web_widgets, dependent: :destroy, class_name: '::Channel::WebWidget' |   has_many :web_widgets, dependent: :destroy, class_name: '::Channel::WebWidget' | ||||||
|   has_many :email_channels, dependent: :destroy, class_name: '::Channel::Email' |   has_many :email_channels, dependent: :destroy, class_name: '::Channel::Email' | ||||||
|   has_many :api_channels, dependent: :destroy, class_name: '::Channel::Api' |   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 :canned_responses, dependent: :destroy | ||||||
|   has_many :webhooks, dependent: :destroy |   has_many :webhooks, dependent: :destroy | ||||||
|   has_many :labels, dependent: :destroy |   has_many :labels, dependent: :destroy | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ | |||||||
|  |  | ||||||
| class Channel::Api < ApplicationRecord | class Channel::Api < ApplicationRecord | ||||||
|   self.table_name = 'channel_api' |   self.table_name = 'channel_api' | ||||||
|  |   EDITABLE_ATTRS = [:webhook_url].freeze | ||||||
|  |  | ||||||
|   validates :account_id, presence: true |   validates :account_id, presence: true | ||||||
|   belongs_to :account |   belongs_to :account | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|  |  | ||||||
| class Channel::Email < ApplicationRecord | class Channel::Email < ApplicationRecord | ||||||
|   self.table_name = 'channel_email' |   self.table_name = 'channel_email' | ||||||
|  |   EDITABLE_ATTRS = [:email].freeze | ||||||
|  |  | ||||||
|   validates :account_id, presence: true |   validates :account_id, presence: true | ||||||
|   belongs_to :account |   belongs_to :account | ||||||
|   | |||||||
							
								
								
									
										89
									
								
								app/models/channel/telegram.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								app/models/channel/telegram.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
| @@ -26,8 +26,10 @@ | |||||||
|  |  | ||||||
| class Channel::WebWidget < ApplicationRecord | class Channel::WebWidget < ApplicationRecord | ||||||
|   include FlagShihTzu |   include FlagShihTzu | ||||||
|  |  | ||||||
|   self.table_name = 'channel_web_widgets' |   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 :website_url, presence: true | ||||||
|   validates :widget_color, presence: true |   validates :widget_color, presence: true | ||||||
|   | |||||||
| @@ -78,6 +78,10 @@ class Inbox < ApplicationRecord | |||||||
|     channel_type == 'Channel::Api' |     channel_type == 'Channel::Api' | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   def email? | ||||||
|  |     channel_type == 'Channel::Email' | ||||||
|  |   end | ||||||
|  |  | ||||||
|   def inbox_type |   def inbox_type | ||||||
|     channel.name |     channel.name | ||||||
|   end |   end | ||||||
|   | |||||||
							
								
								
									
										106
									
								
								app/services/telegram/incoming_message_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								app/services/telegram/incoming_message_service.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										22
									
								
								app/services/telegram/send_on_telegram_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/services/telegram/send_on_telegram_service.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
| @@ -29,4 +29,6 @@ if resource.web_widget? | |||||||
|   json.pre_chat_form_enabled resource.channel.try(:pre_chat_form_enabled) |   json.pre_chat_form_enabled resource.channel.try(:pre_chat_form_enabled) | ||||||
|   json.pre_chat_form_options resource.channel.try(:pre_chat_form_options) |   json.pre_chat_form_options resource.channel.try(:pre_chat_form_options) | ||||||
| end | 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? | json.inbox_identifier resource.channel.try(:identifier) if resource.api? | ||||||
|   | |||||||
| @@ -242,6 +242,7 @@ Rails.application.routes.draw do | |||||||
|   mount Facebook::Messenger::Server, at: 'bot' |   mount Facebook::Messenger::Server, at: 'bot' | ||||||
|   get 'webhooks/twitter', to: 'api/v1/webhooks#twitter_crc' |   get 'webhooks/twitter', to: 'api/v1/webhooks#twitter_crc' | ||||||
|   post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events' |   post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events' | ||||||
|  |   post 'webhooks/telegram/:bot_token', to: 'webhooks/telegram#process_payload' | ||||||
|  |  | ||||||
|   namespace :twitter do |   namespace :twitter do | ||||||
|     resource :callback, only: [:show] |     resource :callback, only: [:show] | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								db/migrate/20210828124043_add_telegram_channel.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								db/migrate/20210828124043_add_telegram_channel.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										11
									
								
								db/schema.rb
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								db/schema.rb
									
									
									
									
									
								
							| @@ -10,7 +10,7 @@ | |||||||
| # | # | ||||||
| # It's strongly recommended that you check this file into your version control system. | # 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 |   # These are extensions that must be enabled in order to support this database | ||||||
|   enable_extension "pg_stat_statements" |   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" |     t.index ["page_id"], name: "index_channel_facebook_pages_on_page_id" | ||||||
|   end |   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| |   create_table "channel_twilio_sms", force: :cascade do |t| | ||||||
|     t.string "phone_number", null: false |     t.string "phone_number", null: false | ||||||
|     t.string "auth_token", null: false |     t.string "auth_token", null: false | ||||||
|   | |||||||
| @@ -255,16 +255,6 @@ RSpec.describe 'Inboxes API', type: :request do | |||||||
|       let(:admin) { create(:user, account: account, role: :administrator) } |       let(:admin) { create(:user, account: account, role: :administrator) } | ||||||
|       let(:valid_params) { { name: 'test', channel: { type: 'web_widget', website_url: 'test.com' } } } |       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 |       it 'will not create inbox for agent' do | ||||||
|         agent = create(:user, account: account, role: :agent) |         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) |         expect(response).to have_http_status(:unauthorized) | ||||||
|       end |       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 | ||||||
|   end |   end | ||||||
|  |  | ||||||
| @@ -314,6 +324,34 @@ RSpec.describe 'Inboxes API', type: :request do | |||||||
|         expect(inbox.reload.enable_auto_assignment).to be_falsey |         expect(inbox.reload.enable_auto_assignment).to be_falsey | ||||||
|       end |       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 |       it 'updates avatar when administrator' do | ||||||
|         # no avatar before upload |         # no avatar before upload | ||||||
|         expect(inbox.avatar.attached?).to eq(false) |         expect(inbox.avatar.attached?).to eq(false) | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								spec/controllers/webhooks/telegram_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								spec/controllers/webhooks/telegram_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										16
									
								
								spec/factories/channel/channel_telegram.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								spec/factories/channel/channel_telegram.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										59
									
								
								spec/jobs/send_reply_job_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								spec/jobs/send_reply_job_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										36
									
								
								spec/jobs/webhooks/telegram_events_job_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								spec/jobs/webhooks/telegram_events_job_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										27
									
								
								spec/services/telegram/incoming_message_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								spec/services/telegram/incoming_message_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										19
									
								
								spec/services/telegram/send_on_telegram_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								spec/services/telegram/send_on_telegram_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose