diff --git a/Gemfile b/Gemfile
index 0bc92366e..6fc764e0c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -42,6 +42,7 @@ gem 'omniauth-linkedin-oauth2'
gem 'omniauth-twitter'
gem 'twitter'
+gem 'telegramAPI'
gem 'koala'
gem 'mail'
gem 'email_verifier'
diff --git a/Gemfile.lock b/Gemfile.lock
index 8de6cbc06..b2d2e72c8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -198,6 +198,7 @@ GEM
nenv (0.3.0)
nestful (1.1.1)
net-ldap (0.15.0)
+ netrc (0.11.0)
nokogiri (1.6.8)
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
@@ -293,6 +294,10 @@ GEM
rb-inotify (0.9.7)
ffi (>= 0.5.0)
ref (2.0.0)
+ rest-client (2.0.0)
+ http-cookie (>= 1.0.2, < 2.0)
+ mime-types (>= 1.16, < 4.0)
+ netrc (~> 0.8)
retriable (2.1.0)
rspec-core (3.5.4)
rspec-support (~> 3.5.0)
@@ -358,6 +363,8 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.11)
+ telegramAPI (1.2.2)
+ rest-client (~> 2.0, >= 1.7.3)
term-ansicolor (1.3.2)
tins (~> 1.0)
test-unit (3.2.1)
@@ -467,6 +474,7 @@ DEPENDENCIES
spring-commands-rspec
sprockets
sqlite3
+ telegramAPI
test-unit
therubyracer
twitter
diff --git a/app/assets/javascripts/app/controllers/_channel/email.coffee b/app/assets/javascripts/app/controllers/_channel/email.coffee
index 586caf4a7..f37c3e2fa 100644
--- a/app/assets/javascripts/app/controllers/_channel/email.coffee
+++ b/app/assets/javascripts/app/controllers/_channel/email.coffee
@@ -243,7 +243,7 @@ class App.ChannelEmailAccountOverview extends App.Controller
@ajax(
id: 'email_index'
type: 'GET'
- url: @apiPath + '/channels/email_index'
+ url: "#{@apiPath}/channels_email"
processData: true
success: (data, status, xhr) =>
@stopLoading()
@@ -324,34 +324,44 @@ class App.ChannelEmailAccountOverview extends App.Controller
delete: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- new App.ControllerGenericDestroyConfirm(
- item: item
+ new App.ControllerConfirm(
+ message: 'Sure?'
+ callback: =>
+ @ajax(
+ id: 'email_delete'
+ type: 'DELETE'
+ url: "#{@apiPath}/channels_email"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
+ @load()
+ )
container: @el.closest('.content')
- callback: @load
)
disable: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- item.active = false
- item.save(
- done: =>
- @load()
- fail: =>
+ @ajax(
+ id: 'email_disable'
+ type: 'POST'
+ url: "#{@apiPath}/channels_email_disable"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
@load()
)
enable: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- item.active = true
- item.save(
- done: =>
- @load()
- fail: =>
+ @ajax(
+ id: 'email_enable'
+ type: 'POST'
+ url: "#{@apiPath}/channels_email_enable"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
@load()
)
@@ -441,7 +451,7 @@ class App.ChannelEmailEdit extends App.ControllerModal
# show errors in form
if errors
@log 'error', errors
- @formValidate( form: e.target, errors: errors )
+ @formValidate(form: e.target, errors: errors)
return false
# disable form
@@ -449,16 +459,18 @@ class App.ChannelEmailEdit extends App.ControllerModal
# update
@ajax(
- id: 'channel_group_update'
+ id: 'channel_email_group'
type: 'POST'
- url: "#{@apiPath}/channels/group/#{@item.id}"
- data: JSON.stringify( params )
+ url: "#{@apiPath}/channels_email_group/#{@item.id}"
+ data: JSON.stringify(params)
processData: true
success: (data, status, xhr) =>
@callback()
@close()
- fail: =>
+ error: (xhr) =>
+ data = JSON.parse(xhr.responseText)
@formEnable(e)
+ @el.find('.alert').removeClass('hidden').text(data.error || 'Unable to save changes.')
)
class App.ChannelEmailAccountWizard extends App.WizardModal
@@ -651,7 +663,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
@ajax(
id: 'email_probe'
type: 'POST'
- url: @apiPath + '/channels/email_probe'
+ url: "#{@apiPath}/channels_email_probe"
data: JSON.stringify(params)
processData: true
success: (data, status, xhr) =>
@@ -683,7 +695,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
@$('.js-inbound [name="options::password"]').val(@account['meta']['password'])
@enable(e)
- fail: =>
+ error: =>
@enable(e)
@showSlide('js-intro')
)
@@ -705,7 +717,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
@ajax(
id: 'email_inbound'
type: 'POST'
- url: @apiPath + '/channels/email_inbound'
+ url: "#{@apiPath}/channels_email_inbound"
data: JSON.stringify(params)
processData: true
success: (data, status, xhr) =>
@@ -738,9 +750,10 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
@showAlert('js-inbound', data.message_human || data.message)
@showInvalidField('js-inbound', data.invalid_field)
@enable(e)
- fail: =>
+ error: (xhr) =>
+ data = JSON.parse(xhr.responseText)
@showSlide('js-inbound')
- @showAlert('js-inbound', data.message_human || data.message)
+ @showAlert('js-inbound', data.message_human || data.message || data.error)
@showInvalidField('js-inbound', data.invalid_field)
@enable(e)
)
@@ -768,8 +781,8 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
@ajax(
id: 'email_outbound'
type: 'POST'
- url: @apiPath + '/channels/email_outbound'
- data: JSON.stringify( params )
+ url: "#{@apiPath}/channels_email_outbound"
+ data: JSON.stringify(params)
processData: true
success: (data, status, xhr) =>
if data.result is 'ok'
@@ -783,9 +796,10 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
@showAlert('js-outbound', data.message_human || data.message)
@showInvalidField('js-outbound', data.invalid_field)
@enable(e)
- fail: =>
+ error: (xhr) =>
+ data = JSON.parse(xhr.responseText)
@showSlide('js-outbound')
- @showAlert('js-outbound', data.message_human || data.message)
+ @showAlert('js-outbound', data.message_human || data.message || data.error)
@showInvalidField('js-outbound', data.invalid_field)
@enable(e)
)
@@ -810,7 +824,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
@ajax(
id: 'email_verify'
type: 'POST'
- url: @apiPath + '/channels/email_verify'
+ url: "#{@apiPath}/channels_email_verify"
data: JSON.stringify(account)
processData: true
success: (data, status, xhr) =>
@@ -835,7 +849,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
if data.subject && @account
@account.subject = data.subject
@verify(@account, count + 1)
- fail: =>
+ error: =>
@showSlide('js-intro')
@showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.')
)
@@ -946,7 +960,7 @@ class App.ChannelEmailNotificationWizard extends App.WizardModal
@ajax(
id: 'email_outbound'
type: 'POST'
- url: "#{@apiPath}/channels/email_notification"
+ url: "#{@apiPath}/channels_email_notification"
data: JSON.stringify(params)
processData: true
success: (data, status, xhr) =>
@@ -957,9 +971,10 @@ class App.ChannelEmailNotificationWizard extends App.WizardModal
@showAlert('js-outbound', data.message_human || data.message)
@showInvalidField('js-outbound', data.invalid_field)
@enable(e)
- fail: =>
+ error: (xhr) =>
+ data = JSON.parse(xhr.responseText)
@showSlide('js-outbound')
- @showAlert('js-outbound', data.message_human || data.message)
+ @showAlert('js-outbound', data.message_human || data.message || data.error)
@showInvalidField('js-outbound', data.invalid_field)
@enable(e)
)
diff --git a/app/assets/javascripts/app/controllers/_channel/facebook.coffee b/app/assets/javascripts/app/controllers/_channel/facebook.coffee
index 53634bde9..10e1eabfb 100644
--- a/app/assets/javascripts/app/controllers/_channel/facebook.coffee
+++ b/app/assets/javascripts/app/controllers/_channel/facebook.coffee
@@ -20,7 +20,7 @@ class Index extends App.ControllerSubContent
@ajax(
id: 'facebook_index'
type: 'GET'
- url: "#{@apiPath}/channels/facebook_index"
+ url: "#{@apiPath}/channels_facebook"
processData: true
success: (data, status, xhr) =>
@stopLoading()
@@ -62,9 +62,6 @@ class Index extends App.ControllerSubContent
@html App.view('facebook/list')(
channels: channels
)
- # accounts: accounts
- # showDescription: showDescription
- # description: description
if @channel_id
@edit(undefined, @channel_id)
@@ -103,41 +100,45 @@ class Index extends App.ControllerSubContent
delete: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- new App.ControllerGenericDestroyConfirm(
- item: item
+ new App.ControllerConfirm(
+ message: 'Sure?'
+ callback: =>
+ @ajax(
+ id: 'facebook_delete'
+ type: 'DELETE'
+ url: "#{@apiPath}/channels_facebook"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
+ @load()
+ )
container: @el.closest('.content')
- callback: @load
)
disable: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- item.active = false
- item.save(
- done: =>
- @load()
- fail: =>
+ @ajax(
+ id: 'facebook_disable'
+ type: 'POST'
+ url: "#{@apiPath}/channels_facebook_disable"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
@load()
)
enable: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- item.active = true
- item.save(
- done: =>
+ @ajax(
+ id: 'facebook_enable'
+ type: 'POST'
+ url: "#{@apiPath}/channels_facebook_enable"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
@load()
- fail: =>
- @load()
- )
-
- description: (e) =>
- new App.ControllerGenericDescription(
- description: App.Twitter.description
- container: @el.closest('.content')
)
class AppConfig extends App.ControllerModal
@@ -182,7 +183,7 @@ class AppConfig extends App.ControllerModal
done: =>
@isChanged = true
@close()
- fail: ->
+ fail: =>
@el.find('.alert').removeClass('hidden').text('Unable to create entry.')
)
return
@@ -241,14 +242,16 @@ class AccountEdit extends App.ControllerModal
@ajax(
id: 'channel_facebook_update'
type: 'POST'
- url: "#{@apiPath}/channels/facebook_verify/#{@channel.id}"
+ url: "#{@apiPath}/channels_facebook/#{@channel.id}"
data: JSON.stringify(@channel.attributes())
processData: true
success: (data, status, xhr) =>
@isChanged = true
@close()
- fail: =>
+ error: (xhr) =>
+ data = JSON.parse(xhr.responseText)
@formEnable(e)
+ @el.find('.alert').removeClass('hidden').text(data.error || 'Unable to save changes.')
)
App.Config.set('Facebook', { prio: 5100, name: 'Facebook', parent: '#channels', target: '#channels/facebook', controller: Index, permission: ['admin.channel_facebook'] }, 'NavBarAdmin')
diff --git a/app/assets/javascripts/app/controllers/_channel/telegram.coffee b/app/assets/javascripts/app/controllers/_channel/telegram.coffee
new file mode 100644
index 000000000..39992ae48
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_channel/telegram.coffee
@@ -0,0 +1,202 @@
+class Index extends App.ControllerSubContent
+ requiredPermission: 'admin.channel_telegram'
+ events:
+ 'click .js-new': 'new'
+ 'click .js-edit': 'edit'
+ 'click .js-delete': 'delete'
+ 'click .js-disable': 'disable'
+ 'click .js-enable': 'enable'
+
+ constructor: ->
+ super
+
+ #@interval(@load, 60000)
+ @load()
+
+ load: =>
+ @startLoading()
+ @ajax(
+ id: 'telegram_index'
+ type: 'GET'
+ url: "#{@apiPath}/channels_telegram"
+ processData: true
+ success: (data) =>
+ @stopLoading()
+ App.Collection.loadAssets(data.assets)
+ @render(data)
+ )
+
+ render: (data) =>
+
+ channels = []
+ for channel_id in data.channel_ids
+ channel = App.Channel.find(channel_id)
+ if channel && channel.options
+ displayName = '-'
+ if channel.group_id
+ group = App.Group.find(channel.group_id)
+ displayName = group.displayName()
+ channel.options.groupName = displayName
+ channels.push channel
+ @html App.view('telegram/index')(
+ channels: channels
+ )
+
+ new: (e) =>
+ e.preventDefault()
+ new BotAdd(
+ container: @el.parents('.content')
+ load: @load
+ )
+
+ edit: (e) =>
+ e.preventDefault()
+ id = $(e.target).closest('.action').data('id')
+ channel = App.Channel.find(id)
+ new BotEdit(
+ channel: channel
+ container: @el.parents('.content')
+ load: @load
+ )
+
+ delete: (e) =>
+ e.preventDefault()
+ id = $(e.target).closest('.action').data('id')
+ new App.ControllerConfirm(
+ message: 'Sure?'
+ callback: =>
+ @ajax(
+ id: 'telegram_delete'
+ type: 'DELETE'
+ url: "#{@apiPath}/channels_telegram"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
+ @load()
+ )
+ container: @el.closest('.content')
+ )
+
+ disable: (e) =>
+ e.preventDefault()
+ id = $(e.target).closest('.action').data('id')
+ @ajax(
+ id: 'telegram_disable'
+ type: 'POST'
+ url: "#{@apiPath}/channels_telegram_disable"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
+ @load()
+ )
+
+ enable: (e) =>
+ e.preventDefault()
+ id = $(e.target).closest('.action').data('id')
+ @ajax(
+ id: 'telegram_enable'
+ type: 'POST'
+ url: "#{@apiPath}/channels_telegram_enable"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
+ @load()
+ )
+
+class BotAdd extends App.ControllerModal
+ head: 'Add Telegram Bot'
+ shown: true
+ button: 'Add'
+ buttonCancel: true
+ small: true
+
+ content: ->
+ content = $(App.view('telegram/bot_add')())
+ createGroupSelection = (selected_id) ->
+ return App.UiElement.select.render(
+ name: 'group_id'
+ multiple: false
+ limit: 100
+ null: false
+ relation: 'Group'
+ nulloption: true
+ value: selected_id
+ class: 'form-control--small'
+ )
+
+ content.find('.js-select').on('click', (e) =>
+ @selectAll(e)
+ )
+ content.find('.js-messagesGroup').replaceWith createGroupSelection(1)
+ content
+
+ onClosed: =>
+ return if !@isChanged
+ @isChanged = false
+ @load()
+
+ onSubmit: (e) =>
+ @formDisable(e)
+ @ajax(
+ id: 'telegram_app_verify'
+ type: 'POST'
+ url: "#{@apiPath}/channels_telegram"
+ data: JSON.stringify(@formParams())
+ processData: true
+ success: =>
+ @isChanged = true
+ @close()
+ error: (xhr) =>
+ data = JSON.parse(xhr.responseText)
+ @formEnable(e)
+ @el.find('.alert').removeClass('hidden').text(data.error || 'Unable to save Bot.')
+ )
+
+class BotEdit extends App.ControllerModal
+ head: 'Telegram Account'
+ shown: true
+ buttonCancel: true
+
+ content: ->
+ content = $(App.view('telegram/bot_edit')(channel: @channel))
+
+ createGroupSelection = (selected_id) ->
+ return App.UiElement.select.render(
+ name: 'group_id'
+ multiple: false
+ limit: 100
+ null: false
+ relation: 'Group'
+ nulloption: true
+ value: selected_id
+ class: 'form-control--small'
+ )
+
+ content.find('.js-messagesGroup').replaceWith createGroupSelection(@channel.group_id)
+ content
+
+ onClosed: =>
+ return if !@isChanged
+ @isChanged = false
+ @load()
+
+ onSubmit: (e) =>
+ @formDisable(e)
+ params = @formParams()
+ @channel.options = params
+ @ajax(
+ id: 'channel_telegram_update'
+ type: 'PUT'
+ url: "#{@apiPath}/channels_telegram/#{@channel.id}"
+ data: JSON.stringify(@formParams())
+ processData: true
+ success: =>
+ @isChanged = true
+ @close()
+ error: (xhr) =>
+ data = JSON.parse(xhr.responseText)
+ @formEnable(e)
+ @el.find('.alert').removeClass('hidden').text(data.error || 'Unable to save changes.')
+ )
+
+App.Config.set('Telegram', { prio: 5100, name: 'Telegram', parent: '#channels', target: '#channels/telegram', controller: Index, permission: ['admin.channel_telegram'] }, 'NavBarAdmin')
diff --git a/app/assets/javascripts/app/controllers/_channel/twitter.coffee b/app/assets/javascripts/app/controllers/_channel/twitter.coffee
index 77243b001..2e756c0f1 100644
--- a/app/assets/javascripts/app/controllers/_channel/twitter.coffee
+++ b/app/assets/javascripts/app/controllers/_channel/twitter.coffee
@@ -19,7 +19,7 @@ class Index extends App.ControllerSubContent
@ajax(
id: 'twitter_index'
type: 'GET'
- url: "#{@apiPath}/channels/twitter_index"
+ url: "#{@apiPath}/channels_twitter"
processData: true
success: (data, status, xhr) =>
@stopLoading()
@@ -61,9 +61,6 @@ class Index extends App.ControllerSubContent
@html App.view('twitter/list')(
channels: channels
)
- # accounts: accounts
- # showDescription: showDescription
- # description: description
if @channel_id
@edit(undefined, @channel_id)
@@ -102,41 +99,45 @@ class Index extends App.ControllerSubContent
delete: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- new App.ControllerGenericDestroyConfirm(
- item: item
+ new App.ControllerConfirm(
+ message: 'Sure?'
+ callback: =>
+ @ajax(
+ id: 'twitter_delete'
+ type: 'DELETE'
+ url: "#{@apiPath}/channels_twitter"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
+ @load()
+ )
container: @el.closest('.content')
- callback: @load
)
disable: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- item.active = false
- item.save(
- done: =>
- @load()
- fail: =>
+ @ajax(
+ id: 'twitter_disable'
+ type: 'POST'
+ url: "#{@apiPath}/channels_twitter_disable"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
@load()
)
enable: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
- item = App.Channel.find(id)
- item.active = true
- item.save(
- done: =>
+ @ajax(
+ id: 'twitter_enable'
+ type: 'POST'
+ url: "#{@apiPath}/channels_twitter_enable"
+ data: JSON.stringify(id: id)
+ processData: true
+ success: =>
@load()
- fail: =>
- @load()
- )
-
- description: (e) =>
- new App.ControllerGenericDescription(
- description: App.Twitter.description
- container: @el.closest('.content')
)
class AppConfig extends App.ControllerModal
@@ -277,14 +278,16 @@ class AccountEdit extends App.ControllerModal
@ajax(
id: 'channel_twitter_update'
type: 'POST'
- url: "#{@apiPath}/channels/twitter_verify/#{@channel.id}"
+ url: "#{@apiPath}/channels_twitter/#{@channel.id}"
data: JSON.stringify(@channel.attributes())
processData: true
success: (data, status, xhr) =>
@isChanged = true
@close()
- fail: =>
+ error: (xhr) =>
+ data = JSON.parse(xhr.responseText)
@formEnable(e)
+ @el.find('.alert').removeClass('hidden').text(data.error || 'Unable to save changes.')
)
App.Config.set('Twitter', { prio: 5000, name: 'Twitter', parent: '#channels', target: '#channels/twitter', controller: Index, permission: ['admin.channel_twitter'] }, 'NavBarAdmin')
diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_actions.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_actions.coffee
index 1f01e188b..081d9ad7b 100644
--- a/app/assets/javascripts/app/controllers/ticket_zoom/article_actions.coffee
+++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_actions.coffee
@@ -1,13 +1,14 @@
class App.TicketZoomArticleActions extends App.Controller
events:
- 'click [data-type=public]': 'publicInternal'
- 'click [data-type=internal]': 'publicInternal'
- 'click [data-type=emailReply]': 'emailReply'
- 'click [data-type=emailReplyAll]': 'emailReplyAll'
- 'click [data-type=twitterStatusReply]': 'twitterStatusReply'
- 'click [data-type=twitterDirectMessageReply]': 'twitterDirectMessageReply'
- 'click [data-type=facebookFeedReply]': 'facebookFeedReply'
- 'click [data-type=delete]': 'delete'
+ 'click [data-type=public]': 'publicInternal'
+ 'click [data-type=internal]': 'publicInternal'
+ 'click [data-type=emailReply]': 'emailReply'
+ 'click [data-type=emailReplyAll]': 'emailReplyAll'
+ 'click [data-type=twitterStatusReply]': 'twitterStatusReply'
+ 'click [data-type=twitterDirectMessageReply]': 'twitterDirectMessageReply'
+ 'click [data-type=facebookFeedReply]': 'facebookFeedReply'
+ 'click [data-type=telegramPersonalMessageReply]': 'telegramPersonalMessageReply'
+ 'click [data-type=delete]': 'delete'
constructor: ->
super
@@ -151,6 +152,13 @@ class App.TicketZoomArticleActions extends App.Controller
icon: 'reply'
href: '#'
}
+ if article.sender.name is 'Customer' && article.type.name is 'telegram personal-message'
+ actions.push {
+ name: 'reply'
+ type: 'telegramPersonalMessageReply'
+ icon: 'reply'
+ href: '#'
+ }
actions.push {
name: 'split'
@@ -399,6 +407,34 @@ class App.TicketZoomArticleActions extends App.Controller
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew } )
+ telegramPersonalMessageReply: (e) =>
+ e.preventDefault()
+
+ # get reference article
+ article_id = $(e.target).parents('[data-id]').data('id')
+ article = App.TicketArticle.fullLocal(article_id)
+ sender = App.TicketArticleSender.find(article.sender_id)
+ type = App.TicketArticleType.find(article.type_id)
+ customer = App.User.find(article.created_by_id)
+
+ @scrollToCompose()
+
+ # empty form
+ articleNew = {
+ to: ''
+ cc: ''
+ body: ''
+ in_reply_to: ''
+ }
+
+ if article.message_id
+ articleNew.in_reply_to = article.message_id
+
+ # get current body
+ articleNew.body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
+
+ App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew, position: 'end' } )
+
delete: (e) =>
e.preventDefault()
diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
index 7566674b3..0782d2f3f 100644
--- a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
+++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
@@ -42,6 +42,8 @@ class App.TicketZoomArticleNew extends App.Controller
possibleArticleType['email'] = true
else if articleTypeCreate is 'facebook feed post'
possibleArticleType['facebook feed comment'] = true
+ else if articleTypeCreate is 'telegram personal-message'
+ possibleArticleType['telegram personal-message'] = true
if @ticket && @ticket.customer_id
customer = App.User.find(@ticket.customer_id)
if customer.email
@@ -105,6 +107,16 @@ class App.TicketZoomArticleNew extends App.Controller
internal: false,
features: ['attachment']
}
+ if possibleArticleType['telegram personal-message']
+ @articleTypes.push {
+ name: 'telegram personal-message'
+ icon: 'telegram'
+ attributes: []
+ internal: false,
+ features: ['attachment']
+ maxTextLength: 10000
+ warningTextLength: 5000
+ }
if @permissionCheck('ticket.customer')
@type = 'note'
@@ -335,6 +347,11 @@ class App.TicketZoomArticleNew extends App.Controller
params.content_type = 'text/plain'
params.body = App.Utils.html2text(params.body, true)
+ if params.type is 'telegram personal-message'
+ App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false)
+ params.content_type = 'text/plain'
+ params.body = App.Utils.html2text(params.body, true)
+
params
validate: =>
@@ -499,7 +516,7 @@ class App.TicketZoomArticleNew extends App.Controller
@$('[data-name=body] [data-signature=true]').remove()
# remove richtext
- if @type is 'twitter status' || @type is 'twitter direct-message'
+ if @type is 'twitter status' || @type is 'twitter direct-message' || @type is 'telegram personal-message'
rawHTML = @$('[data-name=body]').html()
cleanHTML = App.Utils.htmlRemoveRichtext(rawHTML)
if cleanHTML && cleanHTML.html() != rawHTML
diff --git a/app/assets/javascripts/app/views/telegram/bot_add.jst.eco b/app/assets/javascripts/app/views/telegram/bot_add.jst.eco
new file mode 100644
index 000000000..4626b023f
--- /dev/null
+++ b/app/assets/javascripts/app/views/telegram/bot_add.jst.eco
@@ -0,0 +1,32 @@
+
+
+ <%- @T('The tutorial on how to manage a %s is hosted on our [online documentation](https://zammad.org/documentation/channel/telegram).', 'Telegram Bot') %>
+
+
+ <%- @T('Enter your %s App Keys', 'Telegram') %>
+
+ <%- @T('Settings') %>
+
+
+
diff --git a/app/assets/javascripts/app/views/telegram/bot_edit.jst.eco b/app/assets/javascripts/app/views/telegram/bot_edit.jst.eco
new file mode 100644
index 000000000..07b6de1c2
--- /dev/null
+++ b/app/assets/javascripts/app/views/telegram/bot_edit.jst.eco
@@ -0,0 +1,29 @@
+
+
+ <%- @T('Enter your %s App Keys', 'Telegram') %>
+
+ <%- @T('Settings') %>
+
+
+
diff --git a/app/assets/javascripts/app/views/telegram/index.jst.eco b/app/assets/javascripts/app/views/telegram/index.jst.eco
new file mode 100644
index 000000000..b88d9506c
--- /dev/null
+++ b/app/assets/javascripts/app/views/telegram/index.jst.eco
@@ -0,0 +1,48 @@
+
+
+
+
+<% if _.isEmpty(@channels): %>
+
+
<%- @T('You have no configured %s right now.', 'Telegram Bot') %>
+
+<% else: %>
+
+<% for channel in @channels: %>
+
+
+
<%- @Icon('status', 'supergood-color inline') %> <%= channel.options.bot.first_name %> @<%= channel.options.bot.username %>
+
+
+
+
<%- @T('Messages') %>
+ @<%= channel.options.bot.username %>
+
+ <%- @Icon('arrow-right', 'action-flow-icon') %>
+
+
<%- @T('Group') %>
+ <% if channel.options: %>
+ <%= channel.options.groupName %>
+ <% end %>
+
+
+
+
<%- @T('Delete') %>
+ <% if channel.active is true: %>
+
<%- @T('Disable') %>
+ <% else: %>
+
<%- @T('Enable') %>
+ <% end %>
+
<%- @T('Edit') %>
+
+
+<% end %>
+
diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_email_controller.rb
similarity index 74%
rename from app/controllers/channels_controller.rb
rename to app/controllers/channels_email_controller.rb
index 818ac9525..8dbc68224 100644
--- a/app/controllers/channels_controller.rb
+++ b/app/controllers/channels_email_controller.rb
@@ -1,99 +1,9 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
-class ChannelsController < ApplicationController
+class ChannelsEmailController < ApplicationController
before_action :authentication_check
-=begin
-
-Resource:
-POST /api/v1/channels/group/{id}.json
-
-Response:
-{}
-
-Test:
-curl http://localhost/api/v1/group/channels.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST '{group_id:123}'
-
-=end
-
- def group_update
- permission_check('admin')
- check_access
-
- channel = Channel.find(params[:id])
- channel.group_id = params[:group_id]
- channel.save
- render json: {}
- end
-
-=begin
-
-Resource:
-DELETE /api/v1/channels/{id}.json
-
-Response:
-{}
-
-Test:
-curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X DELETE
-
-=end
-
- def destroy
- permission_check('admin')
- check_access
- model_destroy_render(Channel, params)
- end
-
- def twitter_index
- permission_check('admin.channel_twitter')
- assets = {}
- ExternalCredential.where(name: 'twitter').each { |external_credential|
- assets = external_credential.assets(assets)
- }
- channel_ids = []
- Channel.order(:id).each { |channel|
- next if channel.area != 'Twitter::Account'
- assets = channel.assets(assets)
- channel_ids.push channel.id
- }
- render json: {
- assets: assets,
- channel_ids: channel_ids,
- callback_url: ExternalCredential.callback_url('twitter'),
- }
- end
-
- def twitter_verify
- permission_check('admin.channel_twitter')
- model_update_render(Channel, params)
- end
-
- def facebook_index
- permission_check('admin.channel_facebook')
- assets = {}
- ExternalCredential.where(name: 'facebook').each { |external_credential|
- assets = external_credential.assets(assets)
- }
- channel_ids = []
- Channel.order(:id).each { |channel|
- next if channel.area != 'Facebook::Account'
- assets = channel.assets(assets)
- channel_ids.push channel.id
- }
- render json: {
- assets: assets,
- channel_ids: channel_ids,
- callback_url: ExternalCredential.callback_url('facebook'),
- }
- end
-
- def facebook_verify
- permission_check('admin.channel_facebook')
- model_update_render(Channel, params)
- end
-
- def email_index
+ def index
permission_check('admin.channel_email')
system_online_service = Setting.get('system_online_service')
account_channel_ids = []
@@ -142,7 +52,7 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten
}
end
- def email_probe
+ def probe
# check admin permissions
permission_check('admin.channel_email')
@@ -156,13 +66,13 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten
# verify if user+host already exists
if result[:result] == 'ok'
- return if email_account_duplicate?(result)
+ return if account_duplicate?(result)
end
render json: result
end
- def email_outbound
+ def outbound
# check admin permissions
permission_check('admin.channel_email')
@@ -174,7 +84,7 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten
render json: EmailHelper::Probe.outbound(params, params[:email])
end
- def email_inbound
+ def inbound
# check admin permissions
permission_check('admin.channel_email')
@@ -186,12 +96,12 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten
result = EmailHelper::Probe.inbound(params)
# check account duplicate
- return if email_account_duplicate?({ setting: { inbound: params } }, params[:channel_id])
+ return if account_duplicate?({ setting: { inbound: params } }, params[:channel_id])
render json: result
end
- def email_verify
+ def verify
# check admin permissions
permission_check('admin.channel_email')
@@ -204,7 +114,7 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten
return if channel_id && !check_access(channel_id)
# check account duplicate
- return if email_account_duplicate?({ setting: { inbound: params[:inbound] } }, channel_id)
+ return if account_duplicate?({ setting: { inbound: params[:inbound] } }, channel_id)
# check delivery for 30 sek.
result = EmailHelper::Verify.email(
@@ -284,7 +194,38 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten
render json: result
end
- def email_notification
+ def enable
+ permission_check('admin.channel_email')
+ channel = Channel.find_by(id: params[:id], area: 'Email::Account')
+ channel.active = true
+ channel.save!
+ render json: {}
+ end
+
+ def disable
+ permission_check('admin.channel_email')
+ channel = Channel.find_by(id: params[:id], area: 'Email::Account')
+ channel.active = false
+ channel.save!
+ render json: {}
+ end
+
+ def destroy
+ permission_check('admin.channel_email')
+ channel = Channel.find_by(id: params[:id], area: 'Email::Account')
+ channel.destroy
+ render json: {}
+ end
+
+ def group
+ check_access
+ channel = Channel.find_by(id: params[:id], area: 'Email::Account')
+ channel.group_id = params[:group_id]
+ channel.save!
+ render json: {}
+ end
+
+ def notification
check_online_service
@@ -323,7 +264,7 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten
private
- def email_account_duplicate?(result, channel_id = nil)
+ def account_duplicate?(result, channel_id = nil)
Channel.where(area: 'Email::Account').each { |channel|
next if !channel.options
next if !channel.options[:inbound]
diff --git a/app/controllers/channels_facebook_controller.rb b/app/controllers/channels_facebook_controller.rb
new file mode 100644
index 000000000..b4254a5a1
--- /dev/null
+++ b/app/controllers/channels_facebook_controller.rb
@@ -0,0 +1,47 @@
+# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
+
+class ChannelsFacebookController < ApplicationController
+ before_action { authentication_check(permission: 'admin.channel_facebook') }
+
+ def index
+ assets = {}
+ ExternalCredential.where(name: 'facebook').each { |external_credential|
+ assets = external_credential.assets(assets)
+ }
+ channel_ids = []
+ Channel.where(area: 'Facebook::Account').order(:id).each { |channel|
+ assets = channel.assets(assets)
+ channel_ids.push channel.id
+ }
+ render json: {
+ assets: assets,
+ channel_ids: channel_ids,
+ callback_url: ExternalCredential.callback_url('facebook'),
+ }
+ end
+
+ def update
+ model_update_render(Channel, params)
+ end
+
+ def enable
+ channel = Channel.find_by(id: params[:id], area: 'Facebook::Account')
+ channel.active = true
+ channel.save!
+ render json: {}
+ end
+
+ def disable
+ channel = Channel.find_by(id: params[:id], area: 'Facebook::Account')
+ channel.active = false
+ channel.save!
+ render json: {}
+ end
+
+ def destroy
+ channel = Channel.find_by(id: params[:id], area: 'Facebook::Account')
+ channel.destroy
+ render json: {}
+ end
+
+end
diff --git a/app/controllers/channels_telegram_controller.rb b/app/controllers/channels_telegram_controller.rb
new file mode 100644
index 000000000..cd40fc68c
--- /dev/null
+++ b/app/controllers/channels_telegram_controller.rb
@@ -0,0 +1,74 @@
+# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
+
+class ChannelsTelegramController < ApplicationController
+ before_action -> { authentication_check(permission: 'admin.channel_telegram') }, except: [:webhook]
+
+ def index
+ assets = {}
+ channel_ids = []
+ Channel.where(area: 'Telegram::Bot').order(:id).each { |channel|
+ assets = channel.assets(assets)
+ channel_ids.push channel.id
+ }
+ render json: {
+ assets: assets,
+ channel_ids: channel_ids
+ }
+ end
+
+ def add
+ begin
+ channel = Telegram.create_or_update_channel(params[:api_token], params)
+ rescue => e
+ raise Exceptions::UnprocessableEntity, e.message
+ end
+ render json: channel
+ end
+
+ def update
+ channel = Channel.find_by(id: params[:id], area: 'Telegram::Bot')
+ begin
+ channel = Telegram.create_or_update_channel(params[:api_token], params, channel)
+ rescue => e
+ raise Exceptions::UnprocessableEntity, e.message
+ end
+ render json: channel
+ end
+
+ def enable
+ channel = Channel.find_by(id: params[:id], area: 'Telegram::Bot')
+ channel.active = true
+ channel.save!
+ render json: {}
+ end
+
+ def disable
+ channel = Channel.find_by(id: params[:id], area: 'Telegram::Bot')
+ channel.active = false
+ channel.save!
+ render json: {}
+ end
+
+ def destroy
+ channel = Channel.find_by(id: params[:id], area: 'Telegram::Bot')
+ channel.destroy
+ render json: {}
+ end
+
+ def webhook
+ raise Exceptions::UnprocessableEntity, 'bot param missing' if params['bid'].blank?
+
+ channel = Telegram.bot_by_bot_id(params['bid'])
+ raise Exceptions::UnprocessableEntity, 'bot not found' if !channel
+
+ if channel.options[:callback_token] != params['callback_token']
+ raise Exceptions::UnprocessableEntity, 'invalid callback token'
+ end
+
+ telegram = Telegram.new(channel.options[:api_token])
+ telegram.to_group(params, channel.group_id, channel)
+
+ render json: {}, status: :ok
+ end
+
+end
diff --git a/app/controllers/channels_twitter_controller.rb b/app/controllers/channels_twitter_controller.rb
new file mode 100644
index 000000000..b4bf0ec90
--- /dev/null
+++ b/app/controllers/channels_twitter_controller.rb
@@ -0,0 +1,47 @@
+# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
+
+class ChannelsTwitterController < ApplicationController
+ before_action { authentication_check(permission: 'admin.channel_twitter') }
+
+ def index
+ assets = {}
+ ExternalCredential.where(name: 'twitter').each { |external_credential|
+ assets = external_credential.assets(assets)
+ }
+ channel_ids = []
+ Channel.where(area: 'Twitter::Account').order(:id).each { |channel|
+ assets = channel.assets(assets)
+ channel_ids.push channel.id
+ }
+ render json: {
+ assets: assets,
+ channel_ids: channel_ids,
+ callback_url: ExternalCredential.callback_url('twitter'),
+ }
+ end
+
+ def update
+ model_update_render(Channel, params)
+ end
+
+ def enable
+ channel = Channel.find_by(id: params[:id], area: 'Twitter::Account')
+ channel.active = true
+ channel.save!
+ render json: {}
+ end
+
+ def disable
+ channel = Channel.find_by(id: params[:id], area: 'Twitter::Account')
+ channel.active = false
+ channel.save!
+ render json: {}
+ end
+
+ def destroy
+ channel = Channel.find_by(id: params[:id], area: 'Twitter::Account')
+ channel.destroy
+ render json: {}
+ end
+
+end
diff --git a/app/models/channel/driver/telegram.rb b/app/models/channel/driver/telegram.rb
new file mode 100644
index 000000000..ffa24fc67
--- /dev/null
+++ b/app/models/channel/driver/telegram.rb
@@ -0,0 +1,44 @@
+# Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/
+
+class Channel::Driver::Telegram
+
+=begin
+
+ instance = Channel::Driver::Telegram.new
+ instance.send(
+ {
+ adapter: 'telegram',
+ auth: {
+ api_key: api_key
+ },
+ },
+ telegram_attributes,
+ notification
+ )
+
+=end
+
+ def send(options, article, _notification = false)
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
+ options = check_external_credential(options)
+
+ @client = Telegram.new(options[:auth][:api_key])
+ message = @client.from_article(article)
+ message
+ end
+
+ private
+
+ def check_external_credential(options)
+ if options[:auth] && options[:auth][:external_credential_id]
+ external_credential = ExternalCredential.find_by(id: options[:auth][:external_credential_id])
+ raise "No such ExternalCredential.find(#{options[:auth][:external_credential_id]})" if !external_credential
+ options[:auth][:api_key] = external_credential.credentials['api_key']
+ end
+ options
+ end
+
+end
diff --git a/app/models/observer/ticket/article/communicate_telegram.rb b/app/models/observer/ticket/article/communicate_telegram.rb
new file mode 100644
index 000000000..9995763fb
--- /dev/null
+++ b/app/models/observer/ticket/article/communicate_telegram.rb
@@ -0,0 +1,25 @@
+# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
+
+class Observer::Ticket::Article::CommunicateTelegram < ActiveRecord::Observer
+ observe 'ticket::_article'
+
+ def after_create(record)
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
+ # if sender is customer, do not communicate
+ return if !record.sender_id
+ sender = Ticket::Article::Sender.lookup(id: record.sender_id)
+ return if sender.nil?
+ return if sender['name'] == 'Customer'
+
+ # only apply on telegram messages
+ return if !record.type_id
+ type = Ticket::Article::Type.lookup(id: record.type_id)
+ return if type['name'] !~ /\Atelegram/i
+
+ Delayed::Job.enqueue(Observer::Ticket::Article::CommunicateTelegram::BackgroundJob.new(record.id))
+ end
+
+end
diff --git a/app/models/observer/ticket/article/communicate_telegram/background_job.rb b/app/models/observer/ticket/article/communicate_telegram/background_job.rb
new file mode 100644
index 000000000..0922b639a
--- /dev/null
+++ b/app/models/observer/ticket/article/communicate_telegram/background_job.rb
@@ -0,0 +1,108 @@
+class Observer::Ticket::Article::CommunicateTelegram::BackgroundJob
+ def initialize(id)
+ @article_id = id
+ end
+
+ def perform
+ article = Ticket::Article.find(@article_id)
+
+ # set retry count
+ if !article.preferences['delivery_retry']
+ article.preferences['delivery_retry'] = 0
+ end
+ article.preferences['delivery_retry'] += 1
+
+ ticket = Ticket.lookup(id: article.ticket_id)
+ log_error(article, "Can't find ticket.preferences for Ticket.find(#{article.ticket_id})") if !ticket.preferences
+ log_error(article, "Can't find ticket.preferences['telegram'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['telegram']
+ log_error(article, "Can't find ticket.preferences['telegram']['chat_id'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['telegram']['chat_id']
+ if ticket.preferences['telegram'] && ticket.preferences['telegram']['bid']
+ channel = Telegram.bot_by_bot_id(ticket.preferences['telegram']['bid'])
+ end
+ if !channel
+ channel = Channel.lookup(id: ticket.preferences['channel_id'])
+ end
+ log_error(article, "No such channel for bot #{ticket.preferences['bid']} or channel id #{ticket.preferences['channel_id']}") if !channel
+ #log_error(article, "Channel.find(#{channel.id}) isn't a telegram channel!") if channel.options[:adapter] !~ /\Atelegram/i
+ log_error(article, "Channel.find(#{channel.id}) has not telegram api token!") if channel.options[:api_token].blank?
+
+ begin
+ api = TelegramAPI.new(channel.options[:api_token])
+ chat_id = ticket.preferences[:telegram][:chat_id]
+ result = api.sendMessage(chat_id, article.body)
+ article.attachments.each { |file|
+ parts = file.filename.split(/^(.*)(\..+?)$/)
+ t = Tempfile.new([parts[1], parts[2]])
+ t.binmode
+ t.write(file.content)
+ t.rewind
+ api.sendDocument(chat_id, t.path.to_s)
+ }
+ rescue => e
+ log_error(article, e.message)
+ return
+ end
+
+ # fill article with message info
+ article.from = "@#{result['from']['username']}"
+ article.to = "@#{result['chat']['username']}"
+
+ article.preferences['telegram'] = {
+ date: result['date'],
+ from_id: result['from']['id'],
+ chat_id: result['chat']['id'],
+ message_id: result['message_id']
+ }
+
+ # set delivery status
+ article.preferences['delivery_status_message'] = nil
+ article.preferences['delivery_status'] = 'success'
+ article.preferences['delivery_status_date'] = Time.zone.now
+
+ article.message_id = "telegram.#{result['message_id']}.#{result['chat']['id']}"
+
+ article.save!
+
+ Rails.logger.info "Send telegram message to: '#{article.to}' (from #{article.from})"
+
+ article
+ end
+
+ def log_error(local_record, message)
+ local_record.preferences['delivery_status'] = 'fail'
+ local_record.preferences['delivery_status_message'] = message
+ local_record.preferences['delivery_status_date'] = Time.zone.now
+ local_record.save
+ Rails.logger.error message
+
+ if local_record.preferences['delivery_retry'] > 3
+ Ticket::Article.create(
+ ticket_id: local_record.ticket_id,
+ content_type: 'text/plain',
+ body: "Unable to send telegram message: #{message}",
+ internal: true,
+ sender: Ticket::Article::Sender.find_by(name: 'System'),
+ type: Ticket::Article::Type.find_by(name: 'note'),
+ preferences: {
+ delivery_article_id_related: local_record.id,
+ delivery_message: true,
+ },
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ end
+
+ raise message
+ end
+
+ def max_attempts
+ 4
+ end
+
+ def reschedule_at(current_time, attempts)
+ if Rails.env.production?
+ return current_time + attempts * 120.seconds
+ end
+ current_time + 5.seconds
+ end
+end
diff --git a/config/application.rb b/config/application.rb
index 5b8a77bc5..32fed4b20 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -30,6 +30,7 @@ module Zammad
'observer::_ticket::_article::_communicate_email',
'observer::_ticket::_article::_communicate_facebook',
'observer::_ticket::_article::_communicate_twitter',
+ 'observer::_ticket::_article::_communicate_telegram',
'observer::_ticket::_reset_new_state',
'observer::_ticket::_ref_object_touch',
'observer::_ticket::_online_notification_seen',
diff --git a/config/routes/channel.rb b/config/routes/channel.rb
deleted file mode 100644
index 0bc9e246a..000000000
--- a/config/routes/channel.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-Zammad::Application.routes.draw do
- api_path = Rails.configuration.api_path
-
- # email helper
- match api_path + '/channels/email_index', to: 'channels#email_index', via: :get
- match api_path + '/channels/email_probe', to: 'channels#email_probe', via: :post
- match api_path + '/channels/email_outbound', to: 'channels#email_outbound', via: :post
- match api_path + '/channels/email_inbound', to: 'channels#email_inbound', via: :post
- match api_path + '/channels/email_verify', to: 'channels#email_verify', via: :post
- match api_path + '/channels/email_notification', to: 'channels#email_notification', via: :post
-
- # twitter helper
- match api_path + '/channels/twitter_index', to: 'channels#twitter_index', via: :get
- match api_path + '/channels/twitter_verify/:id', to: 'channels#twitter_verify', via: :post
-
- # facebook helper
- match api_path + '/channels/facebook_index', to: 'channels#facebook_index', via: :get
- match api_path + '/channels/facebook_verify/:id', to: 'channels#facebook_verify', via: :post
-
- # channels
- match api_path + '/channels/group/:id', to: 'channels#group_update', via: :post
- match api_path + '/channels/:id', to: 'channels#destroy', via: :delete
-
-end
diff --git a/config/routes/channel_email.rb b/config/routes/channel_email.rb
new file mode 100644
index 000000000..4226128cb
--- /dev/null
+++ b/config/routes/channel_email.rb
@@ -0,0 +1,15 @@
+Zammad::Application.routes.draw do
+ api_path = Rails.configuration.api_path
+
+ match api_path + '/channels_email', to: 'channels_email#index', via: :get
+ match api_path + '/channels_email_probe', to: 'channels_email#probe', via: :post
+ match api_path + '/channels_email_outbound', to: 'channels_email#outbound', via: :post
+ match api_path + '/channels_email_inbound', to: 'channels_email#inbound', via: :post
+ match api_path + '/channels_email_verify', to: 'channels_email#verify', via: :post
+ match api_path + '/channels_email_notification', to: 'channels_email#notification', via: :post
+ match api_path + '/channels_email_disable', to: 'channels_email#disable', via: :post
+ match api_path + '/channels_email_enable', to: 'channels_email#enable', via: :post
+ match api_path + '/channels_email', to: 'channels_email#destroy', via: :delete
+ match api_path + '/channels_email_group/:id', to: 'channels_email#group', via: :post
+
+end
diff --git a/config/routes/channel_facebook.rb b/config/routes/channel_facebook.rb
new file mode 100644
index 000000000..32098a861
--- /dev/null
+++ b/config/routes/channel_facebook.rb
@@ -0,0 +1,9 @@
+Zammad::Application.routes.draw do
+ api_path = Rails.configuration.api_path
+
+ match api_path + '/channels_facebook', to: 'channels_facebook#index', via: :get
+ match api_path + '/channels_facebook/:id', to: 'channels_facebook#update', via: :post
+ match api_path + '/channels_facebook_disable', to: 'channels_facebook#disable', via: :post
+ match api_path + '/channels_facebook_enable', to: 'channels_facebook#enable', via: :post
+ match api_path + '/channels_facebook', to: 'channels_facebook#destroy', via: :delete
+end
diff --git a/config/routes/channel_telegram.rb b/config/routes/channel_telegram.rb
new file mode 100644
index 000000000..bf0845167
--- /dev/null
+++ b/config/routes/channel_telegram.rb
@@ -0,0 +1,12 @@
+Zammad::Application.routes.draw do
+ api_path = Rails.configuration.api_path
+
+ match api_path + '/channels_telegram', to: 'channels_telegram#index', via: :get
+ match api_path + '/channels_telegram', to: 'channels_telegram#add', via: :post
+ match api_path + '/channels_telegram/:id', to: 'channels_telegram#update', via: :put
+ match api_path + '/channels_telegram_webhook/:callback_token', to: 'channels_telegram#webhook', via: :post
+ match api_path + '/channels_telegram_disable', to: 'channels_telegram#disable', via: :post
+ match api_path + '/channels_telegram_enable', to: 'channels_telegram#enable', via: :post
+ match api_path + '/channels_telegram', to: 'channels_telegram#destroy', via: :delete
+
+end
diff --git a/config/routes/channel_twitter.rb b/config/routes/channel_twitter.rb
new file mode 100644
index 000000000..202ac157a
--- /dev/null
+++ b/config/routes/channel_twitter.rb
@@ -0,0 +1,9 @@
+Zammad::Application.routes.draw do
+ api_path = Rails.configuration.api_path
+
+ match api_path + '/channels_twitter', to: 'channels_twitter#index', via: :get
+ match api_path + '/channels_twitter/:id', to: 'channels_twitter#update', via: :post
+ match api_path + '/channels_twitter_disable', to: 'channels_twitter#disable', via: :post
+ match api_path + '/channels_twitter_enable', to: 'channels_twitter#enable', via: :post
+ match api_path + '/channels_twitter', to: 'channels_twitter#destroy', via: :delete
+end
diff --git a/db/migrate/20170215000001_telegram_support.rb b/db/migrate/20170215000001_telegram_support.rb
new file mode 100644
index 000000000..8bd689ca5
--- /dev/null
+++ b/db/migrate/20170215000001_telegram_support.rb
@@ -0,0 +1,22 @@
+class TelegramSupport < ActiveRecord::Migration
+ def up
+
+ # return if it's a new setup
+ return if !Setting.find_by(name: 'system_init_done')
+
+ Permission.create_if_not_exists(
+ name: 'admin.channel_telegram',
+ note: 'Manage %s',
+ preferences: {
+ translations: ['Channel - Telegram']
+ },
+ )
+
+ Ticket::Article::Type.create_if_not_exists(
+ id: 12,
+ name: 'telegram personal-message',
+ communication: true,
+ )
+
+ end
+end
diff --git a/db/seeds.rb b/db/seeds.rb
index 435f7d8ea..ddceeecd3 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -2894,6 +2894,13 @@ Permission.create_if_not_exists(
translations: ['Channel - Facebook']
},
)
+Permission.create_if_not_exists(
+ name: 'admin.channel_telegram',
+ note: 'Manage %s',
+ preferences: {
+ translations: ['Channel - Telegram']
+ },
+)
Permission.create_if_not_exists(
name: 'admin.channel_chat',
note: 'Manage %s',
@@ -3241,6 +3248,7 @@ Ticket::Article::Type.create_if_not_exists(id: 8, name: 'facebook feed post', co
Ticket::Article::Type.create_if_not_exists(id: 9, name: 'facebook feed comment', communication: true)
Ticket::Article::Type.create_if_not_exists(id: 10, name: 'note', communication: false)
Ticket::Article::Type.create_if_not_exists(id: 11, name: 'web', communication: true)
+Ticket::Article::Type.create_if_not_exists(id: 12, name: 'telegram personal-message', communication: true)
Ticket::Article::Sender.create_if_not_exists(id: 1, name: 'Agent')
Ticket::Article::Sender.create_if_not_exists(id: 2, name: 'Customer')
diff --git a/lib/telegram.rb b/lib/telegram.rb
new file mode 100644
index 000000000..f16da7d64
--- /dev/null
+++ b/lib/telegram.rb
@@ -0,0 +1,573 @@
+# Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/
+
+class Telegram
+
+ attr_accessor :client
+
+=begin
+
+check token and return bot attributes of token
+
+ bot = Telegram.check_token('token')
+
+=end
+
+ def self.check_token(token)
+ api = TelegramAPI.new(token)
+ begin
+ bot = api.getMe()
+ rescue
+ raise 'invalid api token'
+ end
+ bot
+ end
+
+=begin
+
+set webhool for bot
+
+ success = Telegram.set_webhook('token', callback_url)
+
+returns
+
+ true|false
+
+=end
+
+ def self.set_webhook(token, callback_url)
+ if callback_url =~ /^http:\/\//i
+ raise 'webhook url need to start with https://, you use http://'
+ end
+ api = TelegramAPI.new(token)
+ begin
+ api.setWebhook(callback_url)
+ rescue
+ raise 'Unable to set webhook at Telegram, seems to be a invalid url.'
+ end
+ true
+ end
+
+=begin
+
+create or update channel, store bot attributes and verify token
+
+ channel = Telegram.create_or_update_channel('token', params)
+
+returns
+
+ channel # instance of Channel
+
+=end
+
+ def self.create_or_update_channel(token, params, channel = nil)
+
+ # verify token
+ bot = Telegram.check_token(token)
+
+ if !channel
+ if Telegram.bot_duplicate?(bot['id'])
+ raise 'Bot already exists!'
+ end
+ end
+
+ if params[:group_id].blank?
+ raise 'Group needed!'
+ else
+ group = Group.find_by(id: params[:group_id])
+ end
+ if !group
+ raise 'Group invalid!'
+ end
+
+ # generate randam callback token
+ callback_token = SecureRandom.urlsafe_base64(10)
+
+ # set webhook / callback url for this bot @ telegram
+ callback_url = "#{Setting.get('http_type')}://#{Setting.get('fqdn')}/api/v1/channels_telegram_webhook/#{callback_token}?bid=#{bot['id']}"
+ Telegram.set_webhook(token, callback_url)
+
+ if !channel
+ channel = Telegram.bot_by_bot_id(bot['id'])
+ if !channel
+ channel = Channel.new
+ end
+ end
+ channel.area = 'Telegram::Bot'
+ channel.options = {
+ bot: {
+ id: bot['id'],
+ username: bot['username'],
+ first_name: bot['first_name'],
+ last_name: bot['last_name'],
+ },
+ callback_token: callback_token,
+ callback_url: callback_url,
+ api_token: token,
+ welcome: params[:welcome],
+ }
+ channel.group_id = group.id
+ channel.active = true
+ channel.save!
+ channel
+ end
+
+=begin
+
+check if bot already exists as channel
+
+ success = Telegram.bot_duplicate?(bot_id)
+
+returns
+
+ channel # instance of Channel
+
+=end
+
+ def self.bot_duplicate?(bot_id, channel_id = nil)
+ Channel.where(area: 'Telegram::Bot').each { |channel|
+ next if !channel.options
+ next if !channel.options[:bot]
+ next if !channel.options[:bot][:id]
+ next if channel.options[:bot][:id] != bot_id
+ next if channel.id.to_s == channel_id.to_s
+ return true
+ }
+ false
+ end
+
+=begin
+
+get channel by bot_id
+
+ channel = Telegram.bot_by_bot_id(bot_id)
+
+returns
+
+ true|false
+
+=end
+
+ def self.bot_by_bot_id(bot_id)
+ Channel.where(area: 'Telegram::Bot').each { |channel|
+ next if !channel.options
+ next if !channel.options[:bot]
+ next if !channel.options[:bot][:id]
+ return channel if channel.options[:bot][:id].to_s == bot_id.to_s
+ }
+ nil
+ end
+
+=begin
+
+generate message_id for message
+
+ message_id = Telegram.message_id(message)
+
+returns
+
+ message_id # 123456@telegram
+
+=end
+
+ def self.message_id(params)
+ message_id = nil
+ [:message, :edited_message].each { |key|
+ next if !params[key]
+ next if !params[key][:message_id]
+ message_id = params[key][:message_id]
+ break
+ }
+ if !message_id
+ message_id = params[:update_id]
+ end
+ "#{message_id}@telegram"
+ end
+
+=begin
+
+ client = Telegram.new('token')
+
+=end
+
+ def initialize(token)
+ @token = token
+ @api = TelegramAPI.new(token)
+ end
+
+=begin
+
+ client.message(chat_id, 'some message')
+
+=end
+
+ def message(chat_id, message)
+ return if Rails.env.test?
+ @api.sendMessage(chat_id, message)
+ end
+
+ def user(params)
+ {
+ id: params[:message][:from][:id],
+ username: params[:message][:from][:username],
+ first_name: params[:message][:from][:first_name],
+ last_name: params[:message][:from][:last_name]
+ }
+ end
+
+ def to_user(params)
+ Rails.logger.debug 'Create user from message...'
+ Rails.logger.debug params.inspect
+
+ # do message_user lookup
+ message_user = user(params)
+
+ auth = Authorization.find_by(uid: message_user[:id], provider: 'telegram')
+
+ # create or update user
+ user_data = {
+ login: message_user[:username],
+ firstname: message_user[:first_name],
+ lastname: message_user[:last_name],
+ }
+ if auth
+ user = User.find(auth.user_id)
+ user.update_attributes(user_data)
+ else
+ user_data[:note] = "Telegram @#{message_user[:username]}"
+ user_data[:active] = true
+ user_data[:role_ids] = Role.signup_role_ids
+ user = User.create(user_data)
+ end
+
+ # create or update authorization
+ auth_data = {
+ uid: message_user[:id],
+ username: message_user[:username],
+ user_id: user.id,
+ provider: 'telegram'
+ }
+ if auth
+ auth.update_attributes(auth_data)
+ else
+ Authorization.create(auth_data)
+ end
+
+ user
+ end
+
+ def to_ticket(params, user, group_id, channel)
+ UserInfo.current_user_id = user.id
+
+ Rails.logger.debug 'Create ticket from message...'
+ Rails.logger.debug params.inspect
+ Rails.logger.debug user.inspect
+ Rails.logger.debug group_id.inspect
+
+ # find ticket or create one
+ state_ids = Ticket::State.where(name: %w(closed merged removed)).pluck(:id)
+ ticket = Ticket.where(customer_id: user.id).where.not(state_id: state_ids).order(:updated_at).first
+ if ticket
+ new_state = Ticket::State.find_by(name: 'new')
+ if ticket.state_id != new_state.id
+ ticket.state = Ticket::State.find_by(name: 'open')
+ end
+ ticket.save!
+ return ticket
+ end
+
+ # prepare title
+ title = params[:message][:text]
+ if title.length > 60
+ title = "#{title[0, 60]}..."
+ end
+
+ ticket = Ticket.new(
+ group_id: group_id,
+ title: title,
+ state_id: Ticket::State.find_by(name: 'new').id,
+ priority_id: Ticket::Priority.find_by(name: '2 normal').id,
+ customer_id: user.id,
+ preferences: {
+ channel_id: channel.id,
+ telegram: {
+ bid: params['bid'],
+ chat_id: params[:message][:chat][:id]
+ }
+ },
+ )
+ ticket.save!
+ ticket
+ end
+
+ def to_article(params, user, ticket, channel, article = nil)
+
+ if article
+ Rails.logger.debug 'Update article from message...'
+ else
+ Rails.logger.debug 'Create article from message...'
+ end
+ Rails.logger.debug params.inspect
+ Rails.logger.debug user.inspect
+ Rails.logger.debug ticket.inspect
+
+ UserInfo.current_user_id = user.id
+
+ if article
+ article.preferences[:edited_message] = {
+ message: {
+ created_at: params[:message][:date],
+ message_id: params[:message][:message_id],
+ from: params[:message][:from],
+ },
+ update_id: params[:update_id],
+ }
+ else
+ article = Ticket::Article.new(
+ ticket_id: ticket.id,
+ type_id: Ticket::Article::Type.find_by(name: 'telegram personal-message').id,
+ sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
+ from: user(params)[:username],
+ to: "@#{channel[:options][:bot][:username]}",
+ message_id: Telegram.message_id(params),
+ internal: false,
+ preferences: {
+ message: {
+ created_at: params[:message][:date],
+ message_id: params[:message][:message_id],
+ from: params[:message][:from],
+ },
+ update_id: params[:update_id],
+ }
+ )
+ end
+
+ # add article
+ if params[:message][:photo]
+
+ # find photo with best resolution for us
+ photo = nil
+ max_width = 650 * 2
+ last_width = 0
+ last_height = 0
+ params[:message][:photo].each { |file|
+ if !photo
+ photo = file
+ last_width = file['width'].to_i
+ last_height = file['height'].to_i
+ end
+ if file['width'].to_i < max_width && last_width < file['width'].to_i
+ photo = file
+ last_width = file['width'].to_i
+ last_height = file['height'].to_i
+ end
+ }
+ if last_width > 650
+ last_width = (last_width / 2).to_i
+ last_height = (last_height / 2).to_i
+ end
+
+ # download image
+ result = download_file(photo['file_id'])
+ if !result.success? || !result.body
+ raise "Unable for download image from telegram: #{result.code}"
+ end
+ body = " "
+ if params[:message][:caption]
+ body += " #{params[:message][:caption].text2html}"
+ end
+ article.content_type = 'text/html'
+ article.body = body
+ article.save!
+ return article
+ end
+
+ # add document
+ if params[:message][:document]
+ thump = params[:message][:document][:thumb]
+ body = ' '
+ if thump
+ width = thump[:width]
+ height = thump[:height]
+ result = download_file(thump['file_id'])
+ if !result.success? || !result.body
+ raise "Unable for download image from telegram: #{result.code}"
+ end
+ body = " "
+ end
+ document_result = download_file(params[:message][:document][:file_id])
+ article.content_type = 'text/html'
+ article.body = body
+ article.save!
+ Store.remove(
+ object: 'Ticket::Article',
+ o_id: article.id,
+ )
+ Store.add(
+ object: 'Ticket::Article',
+ o_id: article.id,
+ data: document_result.body,
+ filename: params[:message][:document][:file_name],
+ preferences: {
+ 'Mime-Type' => params[:message][:document][:mime_type],
+ },
+ )
+ return article
+ end
+
+ # voice
+ if params[:message][:voice]
+ body = ' '
+ if params[:message][:caption]
+ body = " #{params[:message][:caption].text2html}"
+ end
+ document_result = download_file(params[:message][:voice][:file_id])
+ article.content_type = 'text/html'
+ article.body = body
+ article.save!
+ Store.remove(
+ object: 'Ticket::Article',
+ o_id: article.id,
+ )
+ Store.add(
+ object: 'Ticket::Article',
+ o_id: article.id,
+ data: document_result.body,
+ filename: params[:message][:voice][:file_path] || 'audio',
+ preferences: {
+ 'Mime-Type' => params[:message][:voice][:mime_type],
+ },
+ )
+ return article
+ end
+
+ # text
+ if params[:message][:text]
+ article.content_type = 'text/plain'
+ article.body = params[:message][:text]
+ article.save!
+ return article
+ end
+ raise 'invalid action'
+ end
+
+ def to_group(params, group_id, channel)
+ Rails.logger.debug 'import message'
+
+ # prevent multible update
+ if !params[:edited_message]
+ return if Ticket::Article.find_by(message_id: Telegram.message_id(params))
+ end
+
+ # update article
+ if params[:edited_message]
+ article = Ticket::Article.find_by(message_id: Telegram.message_id(params))
+ return if !article
+ params[:message] = params[:edited_message]
+ user = to_user(params)
+ to_article(params, user, article.ticket, channel, article)
+ return article
+ end
+
+ # send welcome message and don't create ticket
+ text = params[:message][:text]
+ if text.present? && text =~ /^\/start/
+ message(params[:message][:chat][:id], channel.options[:welcome] || 'You are welcome! Just ask me something!')
+ return
+
+ # find ticket and close it
+ elsif text.present? && text =~ /^\/end/
+ user = to_user(params)
+ ticket = Ticket.where(customer_id: user.id).order(:updated_at).first
+ ticket.state = Ticket::State.find_by(name: 'closed')
+ ticket.save!
+ return
+ end
+
+ ticket = nil
+
+ # use transaction
+ Transaction.execute(reset_user_id: true) do
+ user = to_user(params)
+ ticket = to_ticket(params, user, group_id, channel)
+ to_article(params, user, ticket, channel)
+ end
+
+ ticket
+ end
+
+ def from_article(article)
+
+ message = nil
+ Rails.logger.debug "Create telegram personal message from article to '#{article[:to]}'..."
+
+ message = {}
+ # TODO: create telegram message here
+
+ Rails.logger.debug message.inspect
+ message
+ end
+
+ def get_state(channel, telegram_update, ticket = nil)
+ message = telegram_update['message']
+ message_user = user(message)
+
+ # no changes in post is from page user it self
+ if channel.options[:bot][:id].to_s == message_user[:id].to_s
+ if !ticket
+ return Ticket::State.find_by(name: 'closed') if !ticket
+ end
+ return ticket.state
+ end
+
+ state = Ticket::State.find_by(name: 'new')
+ return state if !ticket
+ return ticket.state if ticket.state.name == 'new'
+ Ticket::State.find_by(name: 'open')
+ end
+
+ def download_file(file_id)
+ if Rails.env.test?
+ result = Result.new(
+ success: true,
+ body: 'ok',
+ data: 'ok',
+ code: 200,
+ content_type: 'application/stream',
+ )
+ return result
+ end
+ document = @api.getFile(file_id)
+ url = "https://api.telegram.org/file/bot#{@token}/#{document['file_path']}"
+ UserAgent.get(
+ url,
+ {},
+ {
+ open_timeout: 20,
+ read_timeout: 40,
+ },
+ )
+ end
+
+ class Result
+
+ attr_reader :error
+ attr_reader :body
+ attr_reader :data
+ attr_reader :code
+ attr_reader :content_type
+
+ def initialize(options)
+ @success = options[:success]
+ @body = options[:body]
+ @data = options[:data]
+ @code = options[:code]
+ @content_type = options[:content_type]
+ @error = options[:error]
+ end
+
+ def success?
+ return true if @success
+ false
+ end
+ end
+end
diff --git a/test/controllers/channels_controller_test.rb b/test/controllers/channels_controller_test.rb
new file mode 100644
index 000000000..841ba56d1
--- /dev/null
+++ b/test/controllers/channels_controller_test.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+require 'test_helper'
+
+class ChannelsControllerTest < ActionDispatch::IntegrationTest
+ setup do
+
+ # set accept header
+ @headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
+
+ # create agent
+ roles = Role.where(name: %w(Admin Agent))
+ groups = Group.all
+
+ UserInfo.current_user_id = 1
+ @admin = User.create_or_update(
+ login: 'packages-admin',
+ firstname: 'Packages',
+ lastname: 'Admin',
+ email: 'packages-admin@example.com',
+ password: 'adminpw',
+ active: true,
+ roles: roles,
+ groups: groups,
+ )
+
+ # create agent
+ roles = Role.where(name: 'Agent')
+ @agent = User.create_or_update(
+ login: 'packages-agent@example.com',
+ firstname: 'Rest',
+ lastname: 'Agent',
+ email: 'packages-agent@example.com',
+ password: 'agentpw',
+ active: true,
+ roles: roles,
+ groups: groups,
+ )
+
+ # create customer without org
+ roles = Role.where(name: 'Customer')
+ @customer_without_org = User.create_or_update(
+ login: 'packages-customer1@example.com',
+ firstname: 'Packages',
+ lastname: 'Customer1',
+ email: 'packages-customer1@example.com',
+ password: 'customer1pw',
+ active: true,
+ roles: roles,
+ )
+
+ end
+
+ test '01 telegram_webhook creates ticket' do
+ json = File.read('test/fixtures/telegram/personal_message_content.json')
+ post '/api/v1/channels/telegram_webhook', json, @headers
+ puts JSON.parse(@response.body).inspect
+
+ assert_response(200)
+
+ result = JSON.parse(@response.body)
+ assert_equal(Hash, result.class)
+ assert_equal({ 'ok' => 'ok' }, result)
+ end
+
+ test '0x telegram_webhook with existing ticket adds ticket_article'
+ test '0x telegram_webhook sends welcome message on /start'
+ test '0x telegram_webhook closes the ticket on /stop'
+end
diff --git a/test/fixtures/telegram/personal1_message_content1.json b/test/fixtures/telegram/personal1_message_content1.json
new file mode 100644
index 000000000..8c722e2c8
--- /dev/null
+++ b/test/fixtures/telegram/personal1_message_content1.json
@@ -0,0 +1,21 @@
+{
+ "update_id":10001,
+ "message":{
+ "date":1441645532,
+ "chat":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "type": "private",
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "message_id":1365,
+ "from":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "text":"Hello, I need your Help"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal1_message_content2.json b/test/fixtures/telegram/personal1_message_content2.json
new file mode 100644
index 000000000..3a930b7e1
--- /dev/null
+++ b/test/fixtures/telegram/personal1_message_content2.json
@@ -0,0 +1,21 @@
+{
+ "update_id":10002,
+ "message":{
+ "date":1441645535,
+ "chat":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "type": "private",
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "message_id":1366,
+ "from":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "text":"Hello, I need your Help 2"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal1_message_end.json b/test/fixtures/telegram/personal1_message_end.json
new file mode 100644
index 000000000..2a459cf03
--- /dev/null
+++ b/test/fixtures/telegram/personal1_message_end.json
@@ -0,0 +1,21 @@
+{
+ "update_id":10003,
+ "message":{
+ "date":1441645532,
+ "chat":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "type": "private",
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "message_id":1367,
+ "from":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "text":"/end"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal1_message_start.json b/test/fixtures/telegram/personal1_message_start.json
new file mode 100644
index 000000000..8c06a2387
--- /dev/null
+++ b/test/fixtures/telegram/personal1_message_start.json
@@ -0,0 +1,21 @@
+{
+ "update_id":10000,
+ "message":{
+ "date":1441645532,
+ "chat":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "type": "private",
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "message_id":1365,
+ "from":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "text":"/start"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal2_message_content1.json b/test/fixtures/telegram/personal2_message_content1.json
new file mode 100644
index 000000000..f5d249f21
--- /dev/null
+++ b/test/fixtures/telegram/personal2_message_content1.json
@@ -0,0 +1,21 @@
+{
+ "update_id":20001,
+ "message":{
+ "date":1441645532,
+ "chat":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "type": "private",
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "message_id":2365,
+ "from":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "text":"Can you help me with my feature?"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal2_message_content2.json b/test/fixtures/telegram/personal2_message_content2.json
new file mode 100644
index 000000000..92f09956a
--- /dev/null
+++ b/test/fixtures/telegram/personal2_message_content2.json
@@ -0,0 +1,21 @@
+{
+ "update_id":20002,
+ "message":{
+ "date":1441645536,
+ "chat":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "type": "private",
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "message_id":2366,
+ "from":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "text":"Yes of course! lalal "
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal2_message_start.json b/test/fixtures/telegram/personal2_message_start.json
new file mode 100644
index 000000000..9851f1eee
--- /dev/null
+++ b/test/fixtures/telegram/personal2_message_start.json
@@ -0,0 +1,21 @@
+{
+ "update_id":20000,
+ "message":{
+ "date":1441645532,
+ "chat":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "type": "private",
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "message_id":2364,
+ "from":{
+ "last_name":"Test Lastname",
+ "id":1111111,
+ "first_name":"Test Firstname",
+ "username":"Testusername"
+ },
+ "text":"/start"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal3_message_content1.json b/test/fixtures/telegram/personal3_message_content1.json
new file mode 100644
index 000000000..63f2b4090
--- /dev/null
+++ b/test/fixtures/telegram/personal3_message_content1.json
@@ -0,0 +1,21 @@
+{
+ "update_id":30001,
+ "message":{
+ "date":1441645532,
+ "chat":{
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "type": "private",
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "message_id":3365,
+ "from":{
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "text":"Can you help me with my feature?"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal3_message_content2.json b/test/fixtures/telegram/personal3_message_content2.json
new file mode 100644
index 000000000..ba6f9feec
--- /dev/null
+++ b/test/fixtures/telegram/personal3_message_content2.json
@@ -0,0 +1,42 @@
+{
+ "update_id": 30002,
+ "message": {
+ "message_id": 3366,
+ "from": {
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "type": "private",
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "chat": {
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "first_name":"Test Firstname2",
+ "username":"Testusername2",
+ "type": "private"
+ },
+ "date": 1486036832,
+ "photo": [
+ {
+ "file_id": "ABC-123VabcOcv123w0ABBL_aoY-F849YYABC",
+ "file_size": 1016,
+ "width": 90,
+ "height": 82
+ },
+ {
+ "file_id": "ABC-123VabcOcv123w0ABPlhIiVSfO9TYoABC",
+ "file_size": 7378,
+ "width": 320,
+ "height": 291
+ },
+ {
+ "file_id": "ABC-123VabcOcv123w0ABHywrcPqfrbAYIABC",
+ "file_size": 16433,
+ "width": 720,
+ "height": 654
+ }
+ ],
+ "caption": "caption 123abc "
+ }
+}
diff --git a/test/fixtures/telegram/personal3_message_content3.json b/test/fixtures/telegram/personal3_message_content3.json
new file mode 100644
index 000000000..6aafb33ec
--- /dev/null
+++ b/test/fixtures/telegram/personal3_message_content3.json
@@ -0,0 +1,33 @@
+{
+ "update_id": 30003,
+ "message": {
+ "message_id": 3367,
+ "from": {
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "type": "private",
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "chat": {
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "first_name":"Test Firstname2",
+ "username":"Testusername2",
+ "type": "private"
+ },
+ "date": 1486036832,
+ "document": {
+ "file_name": "blockposter-162412.pdf",
+ "mime_type": "application/pdf",
+ "thumb": {
+ "file_id": "AAQCABO0I4INAATATQAB5HWPq4XgxQACAg",
+ "file_size": 8752,
+ "width": 200,
+ "height": 200
+ },
+ "file_id": "BQADAgADDgAD7x6ZSC_-1LMkOEmoAg",
+ "file_size": 3622849
+ }
+ }
+}
diff --git a/test/fixtures/telegram/personal3_message_content4.json b/test/fixtures/telegram/personal3_message_content4.json
new file mode 100644
index 000000000..fc0606e16
--- /dev/null
+++ b/test/fixtures/telegram/personal3_message_content4.json
@@ -0,0 +1,22 @@
+{
+ "update_id":30004,
+ "edited_message": {
+ "message_id":3365,
+ "from": {
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "chat": {
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "type": "private",
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "date": 1487116688,
+ "edit_date": 1487116889,
+ "text": "UPDATE: 1231444"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal3_message_content5.json b/test/fixtures/telegram/personal3_message_content5.json
new file mode 100644
index 000000000..e1816ce3e
--- /dev/null
+++ b/test/fixtures/telegram/personal3_message_content5.json
@@ -0,0 +1,26 @@
+{
+ "update_id":30005,
+ "message":{
+ "message_id":3368,
+ "from":{
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "chat":{
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "type": "private",
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "date":1487119496,
+ "voice":{
+ "duration":1,
+ "mime_type":"audio/ogg",
+ "file_id":"AwADAgADVQADCEIYSZwyOmSZK9iZAg",
+ "file_size":6030
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/telegram/personal3_message_start.json b/test/fixtures/telegram/personal3_message_start.json
new file mode 100644
index 000000000..889635e1b
--- /dev/null
+++ b/test/fixtures/telegram/personal3_message_start.json
@@ -0,0 +1,21 @@
+{
+ "update_id":30000,
+ "message":{
+ "date":1441645532,
+ "chat":{
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "type": "private",
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "message_id":3364,
+ "from":{
+ "last_name":"Test Lastname2",
+ "id":1111112,
+ "first_name":"Test Firstname2",
+ "username":"Testusername2"
+ },
+ "text":"/start start"
+ }
+}
\ No newline at end of file
diff --git a/test/integration/telegram_controller_test.rb b/test/integration/telegram_controller_test.rb
new file mode 100644
index 000000000..45128bfcb
--- /dev/null
+++ b/test/integration/telegram_controller_test.rb
@@ -0,0 +1,196 @@
+# encoding: utf-8
+require 'test_helper'
+require 'rexml/document'
+
+class TelegramControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
+
+ # configure telegram channel
+ token = ENV['TELEGRAM_TOKEN']
+ group_id = Group.find_by(name: 'Users').id
+ #bot = Telegram.check_token(token)
+ #Setting.set('http_type', 'http')
+ Setting.set('http_type', 'https')
+ Setting.set('fqdn', 'me.zammad.com')
+ Channel.where(area: 'Telegram::Bot').destroy_all
+ @channel = Telegram.create_or_update_channel(token, { group_id: group_id, welcome: 'hi!' })
+
+ groups = Group.where(name: 'Users')
+ roles = Role.where(name: %w(Agent))
+ agent = User.create_or_update(
+ login: 'telegram-agent@example.com',
+ firstname: 'E',
+ lastname: 'S',
+ email: 'telegram-agent@example.com',
+ password: 'agentpw',
+ active: true,
+ roles: roles,
+ groups: groups,
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+
+ end
+
+ test 'basic call' do
+ Ticket.destroy_all
+
+ # start communication #1
+ post '/api/v1/channels/telegram_webhook', read_messaage('personal1_message_start'), @headers
+ assert_response(404)
+ result = JSON.parse(@response.body)
+
+ post '/api/v1/channels_telegram_webhook/not_existing', read_messaage('personal1_message_start'), @headers
+ assert_response(422)
+ result = JSON.parse(@response.body)
+ assert_equal('bot param missing', result['error'])
+
+ callback_url = "/api/v1/channels_telegram_webhook/not_existing?bid=#{@channel.options[:bot][:id]}"
+ post callback_url, read_messaage('personal1_message_start'), @headers
+ assert_response(422)
+ result = JSON.parse(@response.body)
+ assert_equal('invalid callback token', result['error'])
+
+ callback_url = "/api/v1/channels_telegram_webhook/#{@channel.options[:callback_token]}?bid=#{@channel.options[:bot][:id]}"
+ post callback_url, read_messaage('personal1_message_start'), @headers
+ assert_response(200)
+
+ # send message1
+ post callback_url, read_messaage('personal1_message_content1'), @headers
+ assert_response(200)
+ assert_equal(1, Ticket.count)
+ ticket = Ticket.last
+ assert_equal('Hello, I need your Help', ticket.title)
+ assert_equal('new', ticket.state.name)
+ assert_equal(1, ticket.articles.count)
+ assert_equal('Hello, I need your Help', ticket.articles.first.body)
+ assert_equal('text/plain', ticket.articles.first.content_type)
+
+ # send same message again, ignore it
+ post callback_url, read_messaage('personal1_message_content1'), @headers
+ assert_response(200)
+ ticket = Ticket.last
+ assert_equal('Hello, I need your Help', ticket.title)
+ assert_equal('new', ticket.state.name)
+ assert_equal(1, ticket.articles.count)
+ assert_equal('Hello, I need your Help', ticket.articles.first.body)
+ assert_equal('text/plain', ticket.articles.first.content_type)
+
+ # send message2
+ post callback_url, read_messaage('personal1_message_content2'), @headers
+ assert_response(200)
+ ticket = Ticket.last
+ assert_equal('Hello, I need your Help', ticket.title)
+ assert_equal('new', ticket.state.name)
+ assert_equal(2, ticket.articles.count)
+ assert_equal('Hello, I need your Help 2', ticket.articles.last.body)
+ assert_equal('text/plain', ticket.articles.last.content_type)
+
+ # send end message
+ post callback_url, read_messaage('personal1_message_end'), @headers
+ assert_response(200)
+ ticket = Ticket.last
+ assert_equal('Hello, I need your Help', ticket.title)
+ assert_equal('closed', ticket.state.name)
+ assert_equal(2, ticket.articles.count)
+ assert_equal('Hello, I need your Help 2', ticket.articles.last.body)
+ assert_equal('text/plain', ticket.articles.last.content_type)
+
+ # start communication #2
+ post callback_url, read_messaage('personal2_message_start'), @headers
+ assert_response(200)
+
+ # send message1
+ post callback_url, read_messaage('personal2_message_content1'), @headers
+ assert_response(200)
+ assert_equal(2, Ticket.count)
+ ticket = Ticket.last
+ assert_equal('Can you help me with my feature?', ticket.title)
+ assert_equal('new', ticket.state.name)
+ assert_equal(1, ticket.articles.count)
+ assert_equal('Can you help me with my feature?', ticket.articles.first.body)
+ assert_equal('text/plain', ticket.articles.first.content_type)
+
+ # send message2
+ post callback_url, read_messaage('personal2_message_content2'), @headers
+ assert_response(200)
+ assert_equal(2, Ticket.count)
+ ticket = Ticket.last
+ assert_equal('Can you help me with my feature?', ticket.title)
+ assert_equal('new', ticket.state.name)
+ assert_equal(2, ticket.articles.count)
+ assert_equal('Yes of course! lalal ', ticket.articles.last.body)
+ assert_equal('text/plain', ticket.articles.last.content_type)
+
+ # start communication #3
+ post callback_url, read_messaage('personal3_message_start'), @headers
+ assert_response(200)
+
+ # send message1
+ post callback_url, read_messaage('personal3_message_content1'), @headers
+ assert_response(200)
+ assert_equal(3, Ticket.count)
+ ticket = Ticket.last
+ assert_equal('Can you help me with my feature?', ticket.title)
+ assert_equal('new', ticket.state.name)
+ assert_equal(1, ticket.articles.count)
+ assert_equal('Can you help me with my feature?', ticket.articles.last.body)
+ assert_equal('text/plain', ticket.articles.last.content_type)
+
+ # send message2
+ post callback_url, read_messaage('personal3_message_content2'), @headers
+ assert_response(200)
+ assert_equal(3, Ticket.count)
+ ticket = Ticket.last
+ assert_equal('Can you help me with my feature?', ticket.title)
+ assert_equal('new', ticket.state.name)
+ assert_equal(2, ticket.articles.count)
+ assert_match(/