Moved to current channel api.

This commit is contained in:
Martin Edenhofer 2015-12-14 10:23:14 +01:00
parent 5852dc6d8c
commit 1d4e1310d1
9 changed files with 178 additions and 83 deletions

View file

@ -15,6 +15,6 @@ process emails from STDIN
msg = ARGF.read msg = ARGF.read
process( {}, msg ) process({}, msg)
end end
end end

View file

@ -6,11 +6,15 @@ class Channel::Driver::Smtp
instance = Channel::Driver::Smtp.new instance = Channel::Driver::Smtp.new
instance.send( instance.send(
host: 'some.host', {
port: 25, host: 'some.host',
enable_starttls_auto: true, # optional port: 25,
user: 'someuser', enable_starttls_auto: true, # optional
password: 'somepass' user: 'someuser',
password: 'somepass'
},
mail_attributes,
notification
) )
=end =end

View file

@ -1,12 +1,55 @@
# Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/
=begin
fetch tweets from twitter account
options = {
adapter: 'twitter',
auth: {
consumer_key: consumer_key,
consumer_secret: consumer_secret,
oauth_token: armin_theo_token,
oauth_token_secret: armin_theo_token_secret,
},
sync: {
search: [
{
term: '#citheo42',
group_id: 2,
},
{
term: '#citheo24',
group_id: 1,
},
],
mentions: {
group_id: 2,
},
direct_messages: {
group_id: 2,
}
}
}
instance = Channel::Driver::Twitter.new
result = instance.fetch(options, channel)
returns
{
result: 'ok',
}
=end
class Channel::Driver::Twitter class Channel::Driver::Twitter
def fetch (_adapter_options, channel) def fetch (options, channel)
@tweet = Tweet.new(options[:auth])
@sync = options[:sync]
@channel = channel @channel = channel
@tweet = Tweet.new(@channel[:options][:auth])
@sync = @channel[:options][:sync]
Rails.logger.debug 'twitter fetch started' Rails.logger.debug 'twitter fetch started'
@ -17,16 +60,39 @@ class Channel::Driver::Twitter
disconnect disconnect
Rails.logger.debug 'twitter fetch completed' Rails.logger.debug 'twitter fetch completed'
{
result: 'ok',
}
end end
def send(article, _notification = false) =begin
@channel = Channel.find_by(area: 'Twitter::Account', active: true) instance = Channel::Driver::Twitter.new
@tweet = Tweet.new(@channel[:options][:auth]) instance.send(
{
adapter: 'twitter',
auth: {
consumer_key: consumer_key,
consumer_secret: consumer_secret,
oauth_token: armin_theo_token,
oauth_token_secret: armin_theo_token_secret,
},
},
twitter_attributes,
notification
)
tweet = @tweet.from_article(article) =end
def send(options, article, _notification = false)
# return if we run import mode
return if Setting.get('import_mode')
@tweet = Tweet.new(options[:auth])
tweet = @tweet.from_article(article)
disconnect disconnect
tweet tweet
end end
@ -54,7 +120,7 @@ class Channel::Driver::Twitter
break if search[:limit] && search[:limit] <= counter break if search[:limit] && search[:limit] <= counter
break if Ticket::Article.find_by(message_id: tweet.id) break if Ticket::Article.find_by(message_id: tweet.id)
@tweet.to_group(tweet, search[:group_id]) @tweet.to_group(tweet, search[:group_id], @channel)
counter += 1 counter += 1
} }
@ -74,7 +140,7 @@ class Channel::Driver::Twitter
break if @sync[:mentions][:limit] && @sync[:mentions][:limit] <= counter break if @sync[:mentions][:limit] && @sync[:mentions][:limit] <= counter
break if Ticket::Article.find_by(message_id: tweet.id) break if Ticket::Article.find_by(message_id: tweet.id)
@tweet.to_group(tweet, @sync[:mentions][:group_id]) @tweet.to_group(tweet, @sync[:mentions][:group_id], @channel)
counter += 1 counter += 1
} }
@ -93,7 +159,7 @@ class Channel::Driver::Twitter
break if @sync[:direct_messages][:limit] && @sync[:direct_messages][:limit] <= counter break if @sync[:direct_messages][:limit] && @sync[:direct_messages][:limit] <= counter
break if Ticket::Article.find_by(message_id: tweet.id) break if Ticket::Article.find_by(message_id: tweet.id)
@tweet.to_group(tweet, @sync[:direct_messages][:group_id]) @tweet.to_group(tweet, @sync[:direct_messages][:group_id], @channel)
counter += 1 counter += 1
} }

View file

@ -9,7 +9,7 @@ class Channel::EmailParser
=begin =begin
mail = parse( msg_as_string ) mail = parse(msg_as_string)
mail = { mail = {
from: 'Some Name <some@example.com>', from: 'Some Name <some@example.com>',
@ -60,9 +60,9 @@ class Channel::EmailParser
=end =end
def parse (msg) def parse(msg)
data = {} data = {}
mail = Mail.new( msg ) mail = Mail.new(msg)
# set all headers # set all headers
mail.header.fields.each { |field| mail.header.fields.each { |field|
@ -70,7 +70,7 @@ class Channel::EmailParser
next if !field.name next if !field.name
# full line, encode, ready for storage # full line, encode, ready for storage
data[field.name.to_s.downcase.to_sym] = Encode.conv( 'utf8', field.to_s ) data[field.name.to_s.downcase.to_sym] = Encode.conv('utf8', field.to_s)
# if we need to access the lines by objects later again # if we need to access the lines by objects later again
data[ "raw-#{field.name.downcase}".to_sym ] = field data[ "raw-#{field.name.downcase}".to_sym ] = field
@ -101,11 +101,11 @@ class Channel::EmailParser
# set extra headers # set extra headers
begin begin
data[:from_email] = Mail::Address.new( from ).address data[:from_email] = Mail::Address.new(from).address
data[:from_local] = Mail::Address.new( from ).local data[:from_local] = Mail::Address.new(from).local
data[:from_domain] = Mail::Address.new( from ).domain data[:from_domain] = Mail::Address.new(from).domain
data[:from_display_name] = Mail::Address.new( from ).display_name || data[:from_display_name] = Mail::Address.new(from).display_name ||
( Mail::Address.new( from ).comments && Mail::Address.new( from ).comments[0] ) (Mail::Address.new(from).comments && Mail::Address.new(from).comments[0])
rescue rescue
data[:from_email] = from data[:from_email] = from
data[:from_local] = from data[:from_local] = from
@ -113,7 +113,7 @@ class Channel::EmailParser
end end
# do extra decoding because we needed to use field.value # do extra decoding because we needed to use field.value
data[:from_display_name] = Mail::Field.new( 'X-From', data[:from_display_name] ).to_s data[:from_display_name] = Mail::Field.new('X-From', data[:from_display_name]).to_s
# compat headers # compat headers
data[:message_id] = data['message-id'.to_sym] data[:message_id] = data['message-id'.to_sym]
@ -129,7 +129,7 @@ class Channel::EmailParser
# text attachment/body exists # text attachment/body exists
if mail.text_part if mail.text_part
data[:body] = mail.text_part.body.decoded data[:body] = mail.text_part.body.decoded
data[:body] = Encode.conv( mail.text_part.charset, data[:body] ) data[:body] = Encode.conv(mail.text_part.charset, data[:body])
if !data[:body].valid_encoding? if !data[:body].valid_encoding?
data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
@ -142,7 +142,7 @@ class Channel::EmailParser
if mail.html_part && mail.html_part.body if mail.html_part && mail.html_part.body
filename = 'message.html' filename = 'message.html'
data[:body] = mail.html_part.body.to_s data[:body] = mail.html_part.body.to_s
data[:body] = Encode.conv( mail.html_part.charset.to_s, data[:body] ) data[:body] = Encode.conv(mail.html_part.charset.to_s, data[:body])
data[:body] = data[:body].html2text.to_s.force_encoding('utf-8') data[:body] = data[:body].html2text.to_s.force_encoding('utf-8')
if !data[:body].force_encoding('UTF-8').valid_encoding? if !data[:body].force_encoding('UTF-8').valid_encoding?
@ -181,11 +181,11 @@ class Channel::EmailParser
# protect process to work fine with spam emails, see test/fixtures/mail15.box # protect process to work fine with spam emails, see test/fixtures/mail15.box
begin begin
attachs = _get_attachment( part, data[:attachments], mail ) attachs = _get_attachment(part, data[:attachments], mail)
data[:attachments].concat( attachs ) data[:attachments].concat(attachs)
rescue rescue
attachs = _get_attachment( part, data[:attachments], mail ) attachs = _get_attachment(part, data[:attachments], mail)
data[:attachments].concat( attachs ) data[:attachments].concat(attachs)
end end
} }
end end
@ -196,7 +196,7 @@ class Channel::EmailParser
# text part only # text part only
if !mail.mime_type || mail.mime_type.to_s == '' || mail.mime_type.to_s.downcase == 'text/plain' if !mail.mime_type || mail.mime_type.to_s == '' || mail.mime_type.to_s.downcase == 'text/plain'
data[:body] = mail.body.decoded data[:body] = mail.body.decoded
data[:body] = Encode.conv( mail.charset, data[:body] ) data[:body] = Encode.conv(mail.charset, data[:body])
if !data[:body].force_encoding('UTF-8').valid_encoding? if !data[:body].force_encoding('UTF-8').valid_encoding?
data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
@ -208,7 +208,7 @@ class Channel::EmailParser
if mail.mime_type.to_s.downcase == 'text/html' if mail.mime_type.to_s.downcase == 'text/html'
filename = 'message.html' filename = 'message.html'
data[:body] = mail.body.decoded data[:body] = mail.body.decoded
data[:body] = Encode.conv( mail.charset, data[:body] ) data[:body] = Encode.conv(mail.charset, data[:body])
data[:body] = data[:body].html2text.to_s.force_encoding('utf-8') data[:body] = data[:body].html2text.to_s.force_encoding('utf-8')
if !data[:body].valid_encoding? if !data[:body].valid_encoding?
@ -240,9 +240,9 @@ class Channel::EmailParser
end end
# strip not wanted chars # strip not wanted chars
data[:body].gsub!( /\n\r/, "\n" ) data[:body].gsub!(/\n\r/, "\n")
data[:body].gsub!( /\r\n/, "\n" ) data[:body].gsub!(/\r\n/, "\n")
data[:body].gsub!( /\r/, "\n" ) data[:body].gsub!(/\r/, "\n")
# remember original mail instance # remember original mail instance
data[:mail_instance] = mail data[:mail_instance] = mail
@ -250,14 +250,14 @@ class Channel::EmailParser
data data
end end
def _get_attachment( file, attachments, mail ) def _get_attachment(file, attachments, mail)
# check if sub parts are available # check if sub parts are available
if !file.parts.empty? if !file.parts.empty?
a = [] a = []
file.parts.each {|p| file.parts.each {|p|
attachment = _get_attachment( p, attachments, mail ) attachment = _get_attachment(p, attachments, mail)
a.concat( attachment ) a.concat(attachment)
} }
return a return a
end end
@ -341,7 +341,7 @@ retrns
=end =end
def process(channel, msg) def process(channel, msg)
mail = parse( msg ) mail = parse(msg)
# run postmaster pre filter # run postmaster pre filter
filters = { filters = {
@ -353,7 +353,7 @@ retrns
'1000' => Channel::Filter::Database, '1000' => Channel::Filter::Database,
} }
# filter( channel, mail ) # filter(channel, mail)
filters.each {|_prio, backend| filters.each {|_prio, backend|
begin begin
backend.run(channel, mail) backend.run(channel, mail)
@ -379,10 +379,10 @@ retrns
# create sender # create sender
if mail[ 'x-zammad-customer-login'.to_sym ] if mail[ 'x-zammad-customer-login'.to_sym ]
user = User.find_by( login: mail[ 'x-zammad-customer-login'.to_sym ] ) user = User.find_by(login: mail[ 'x-zammad-customer-login'.to_sym ])
end end
if !user if !user
user = User.find_by( email: mail[ 'x-zammad-customer-email'.to_sym ] || mail[:from_email] ) user = User.find_by(email: mail[ 'x-zammad-customer-email'.to_sym ] || mail[:from_email])
end end
if !user if !user
user = user_create( user = user_create(
@ -422,8 +422,8 @@ retrns
# set ticket state to open if not new # set ticket state to open if not new
if ticket if ticket
state = Ticket::State.find( ticket.state_id ) state = Ticket::State.find(ticket.state_id)
state_type = Ticket::StateType.find( state.state_type_id ) state_type = Ticket::StateType.find(state.state_type_id)
# if tickte is merged, find linked ticket # if tickte is merged, find linked ticket
if state_type.name == 'merged' if state_type.name == 'merged'
@ -440,16 +440,23 @@ retrns
# create new ticket # create new ticket
if !ticket if !ticket
# set attributes preferences = {}
if channel[:id]
preferences = {
channel_id: channel[:id]
}
end
ticket = Ticket.new( ticket = Ticket.new(
group_id: channel[:group_id] || 1, group_id: channel[:group_id] || 1,
customer_id: user.id, customer_id: user.id,
title: mail[:subject] || '', title: mail[:subject] || '',
state_id: Ticket::State.find_by( name: 'new' ).id, state_id: Ticket::State.find_by(name: 'new').id,
priority_id: Ticket::Priority.find_by( name: '2 normal' ).id, priority_id: Ticket::Priority.find_by(name: '2 normal').id,
preferences: preferences,
) )
set_attributes_by_x_headers( ticket, 'ticket', mail ) set_attributes_by_x_headers(ticket, 'ticket', mail)
# create ticket # create ticket
ticket.save ticket.save
@ -460,8 +467,8 @@ retrns
# set attributes # set attributes
article = Ticket::Article.new( article = Ticket::Article.new(
ticket_id: ticket.id, ticket_id: ticket.id,
type_id: Ticket::Article::Type.find_by( name: 'email' ).id, type_id: Ticket::Article::Type.find_by(name: 'email').id,
sender_id: Ticket::Article::Sender.find_by( name: 'Customer' ).id, sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
body: mail[:body], body: mail[:body],
from: mail[:from], from: mail[:from],
to: mail[:to], to: mail[:to],
@ -472,7 +479,7 @@ retrns
) )
# x-headers lookup # x-headers lookup
set_attributes_by_x_headers( article, 'article', mail ) set_attributes_by_x_headers(article, 'article', mail)
# create article # create article
article.save article.save
@ -505,13 +512,13 @@ retrns
# run postmaster post filter # run postmaster post filter
filters = { filters = {
# '0010' => Channel::Filter::Trusted, # '0010' => Channel::Filter::Trusted,
} }
# filter( channel, mail ) # filter( channel, mail )
filters.each {|_prio, backend| filters.each {|_prio, backend|
begin begin
backend.run( channel, mail, ticket, article, user ) backend.run(channel, mail, ticket, article, user)
rescue => e rescue => e
Rails.logger.error "can't run postmaster post filter #{backend}" Rails.logger.error "can't run postmaster post filter #{backend}"
Rails.logger.error e.inspect Rails.logger.error e.inspect
@ -525,11 +532,11 @@ retrns
def user_create(data) def user_create(data)
# return existing # return existing
user = User.find_by( login: data[:email].downcase ) user = User.find_by(login: data[:email].downcase)
return user if user return user if user
# create new user # create new user
roles = Role.where( name: 'Customer' ) roles = Role.where(name: 'Customer')
# fillup # fillup
%w(firstname lastname).each { |item| %w(firstname lastname).each { |item|
@ -551,7 +558,7 @@ retrns
user user
end end
def set_attributes_by_x_headers( item_object, header_name, mail ) def set_attributes_by_x_headers(item_object, header_name, mail)
# loop all x-zammad-hedaer-* headers # loop all x-zammad-hedaer-* headers
item_object.attributes.each {|key, _value| item_object.attributes.each {|key, _value|
@ -577,12 +584,12 @@ retrns
item = assoc.class_name.constantize item = assoc.class_name.constantize
if item.respond_to?(:name) if item.respond_to?(:name)
if item.lookup( name: mail[ header.to_sym ] ) if item.lookup(name: mail[ header.to_sym ])
item_object[key] = item.lookup( name: mail[ header.to_sym ] ).id item_object[key] = item.lookup(name: mail[ header.to_sym ]).id
end end
elsif item.respond_to?(:login) elsif item.respond_to?(:login)
if item.lookup( login: mail[ header.to_sym ] ) if item.lookup(login: mail[ header.to_sym ])
item_object[key] = item.lookup( login: mail[ header.to_sym ] ).id item_object[key] = item.lookup(login: mail[ header.to_sym ]).id
end end
end end
} }

View file

@ -12,22 +12,27 @@ class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer
return if Setting.get('import_mode') return if Setting.get('import_mode')
# if sender is customer, do not communication # if sender is customer, do not communication
sender = Ticket::Article::Sender.lookup( id: record.sender_id ) sender = Ticket::Article::Sender.lookup(id: record.sender_id)
return if sender.nil? return if sender.nil?
return if sender['name'] == 'Customer' return if sender['name'] == 'Customer'
# only apply on tweets # only apply on tweets
type = Ticket::Article::Type.lookup( id: record.type_id ) type = Ticket::Article::Type.lookup(id: record.type_id)
return if type['name'] !~ /\Atwitter/ return if type['name'] !~ /\Atwitter/i
twitter = Channel::Driver::Twitter.new ticket = Ticket.lookup(id: record.ticket_id)
tweet = twitter.send({ fail "Can't find ticket.preferences for Ticket.find(#{record.ticket_id})" if !ticket.preferences
type: type['name'], fail "Can't find ticket.preferences['channel_id'] for Ticket.find(#{record.ticket_id})" if !ticket.preferences['channel_id']
to: record.to, channel = Channel.lookup(id: ticket.preferences['channel_id'])
body: record.body, fail "Channel.find(#{channel.id}) isn't a twitter channel!" if channel.options[:adapter] !~ /\Atwitter/i
in_reply_to: record.in_reply_to tweet = channel.deliver(
}) type: type['name'],
to: record.to,
body: record.body,
in_reply_to: record.in_reply_to
)
record.message_id = tweet.id record.message_id = tweet.id
record.save record.save
end end
end end

View file

@ -14,6 +14,7 @@ class Ticket < ApplicationModel
include Ticket::SearchIndex include Ticket::SearchIndex
extend Ticket::Search extend Ticket::Search
store :preferences
before_create :check_generate, :check_defaults, :check_title before_create :check_generate, :check_defaults, :check_title
before_update :check_defaults, :check_title, :reset_pending_time before_update :check_defaults, :check_title, :reset_pending_time
before_destroy :destroy_dependencies before_destroy :destroy_dependencies

View file

@ -0,0 +1,5 @@
class UpdateTicketPreferences < ActiveRecord::Migration
def up
add_column :tickets, :preferences, :text, limit: 500.kilobytes + 1, null: true
end
end

View file

@ -86,7 +86,7 @@ class Tweet
user user
end end
def to_ticket(tweet, user, group_id) def to_ticket(tweet, user, group_id, channel)
Rails.logger.debug 'Create ticket from tweet...' Rails.logger.debug 'Create ticket from tweet...'
Rails.logger.debug tweet.inspect Rails.logger.debug tweet.inspect
@ -118,6 +118,9 @@ class Tweet
group_id: group_id, group_id: group_id,
state_id: Ticket::State.find_by(name: 'new').id, state_id: Ticket::State.find_by(name: 'new').id,
priority_id: Ticket::Priority.find_by(name: '2 normal').id, priority_id: Ticket::Priority.find_by(name: '2 normal').id,
preferences: {
channel_id: channel.id
},
) )
end end
@ -164,7 +167,7 @@ class Tweet
) )
end end
def to_group(tweet, group_id) def to_group(tweet, group_id, channel)
Rails.logger.debug 'import tweet' Rails.logger.debug 'import tweet'
@ -177,19 +180,19 @@ class Tweet
# check if parent exists # check if parent exists
user = to_user(tweet) user = to_user(tweet)
if tweet.class == Twitter::DirectMessage if tweet.class == Twitter::DirectMessage
ticket = to_ticket(tweet, user, group_id) ticket = to_ticket(tweet, user, group_id, channel)
to_article(tweet, user, ticket) to_article(tweet, user, ticket)
elsif tweet.class == Twitter::Tweet elsif tweet.class == Twitter::Tweet
if tweet.in_reply_to_status_id if tweet.in_reply_to_status_id && tweet.in_reply_to_status_id.to_s != ''
existing_article = Ticket::Article.find_by(message_id: tweet.in_reply_to_status_id) existing_article = Ticket::Article.find_by(message_id: tweet.in_reply_to_status_id)
if existing_article if existing_article
ticket = existing_article.ticket ticket = existing_article.ticket
else else
parent_tweet = @client.status(tweet.in_reply_to_status_id) parent_tweet = @client.status(tweet.in_reply_to_status_id)
ticket = to_group(parent_tweet, group_id) ticket = to_group(parent_tweet, group_id, channel)
end end
else else
ticket = to_ticket(tweet, user, group_id) ticket = to_ticket(tweet, user, group_id, channel)
end end
to_article(tweet, user, ticket) to_article(tweet, user, ticket)
else else

View file

@ -48,7 +48,7 @@ class TwitterTest < ActiveSupport::TestCase
# add channel # add channel
current = Channel.where(area: 'Twitter::Account') current = Channel.where(area: 'Twitter::Account')
current.each(&:destroy) current.each(&:destroy)
Channel.create( channel = Channel.create(
area: 'Twitter::Account', area: 'Twitter::Account',
options: { options: {
adapter: 'twitter', adapter: 'twitter',
@ -93,6 +93,9 @@ class TwitterTest < ActiveSupport::TestCase
group_id: 2, group_id: 2,
state: Ticket::State.find_by(name: 'new'), state: Ticket::State.find_by(name: 'new'),
priority: Ticket::Priority.find_by(name: '2 normal'), priority: Ticket::Priority.find_by(name: '2 normal'),
preferences: {
channel_id: channel.id,
},
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
@ -173,7 +176,7 @@ class TwitterTest < ActiveSupport::TestCase
tweet = client.update( tweet = client.update(
text, text,
) )
sleep 10 sleep 15
# fetch check system account # fetch check system account
article = nil article = nil
@ -243,7 +246,7 @@ class TwitterTest < ActiveSupport::TestCase
text, text,
) )
assert(dm, "dm with ##{hash} created") assert(dm, "dm with ##{hash} created")
sleep 10 sleep 15
# fetch check system account # fetch check system account
article = nil article = nil
@ -288,7 +291,7 @@ class TwitterTest < ActiveSupport::TestCase
text, text,
) )
assert(dm, "second dm with ##{hash} created") assert(dm, "second dm with ##{hash} created")
sleep 10 sleep 15
# fetch check system account # fetch check system account
article = nil article = nil
@ -321,6 +324,7 @@ class TwitterTest < ActiveSupport::TestCase
text, text,
) )
assert(dm, "third dm with ##{hash} created") assert(dm, "third dm with ##{hash} created")
sleep 15
# fetch check system account # fetch check system account
article = nil article = nil