diff --git a/app/models/channel/driver/mail_stdin.rb b/app/models/channel/driver/mail_stdin.rb index cefc2649d..cdfd250f0 100644 --- a/app/models/channel/driver/mail_stdin.rb +++ b/app/models/channel/driver/mail_stdin.rb @@ -15,6 +15,6 @@ process emails from STDIN msg = ARGF.read - process( {}, msg ) + process({}, msg) end end diff --git a/app/models/channel/driver/smtp.rb b/app/models/channel/driver/smtp.rb index 1aac4bbc1..671e39d4c 100644 --- a/app/models/channel/driver/smtp.rb +++ b/app/models/channel/driver/smtp.rb @@ -6,11 +6,15 @@ class Channel::Driver::Smtp instance = Channel::Driver::Smtp.new instance.send( - host: 'some.host', - port: 25, - enable_starttls_auto: true, # optional - user: 'someuser', - password: 'somepass' + { + host: 'some.host', + port: 25, + enable_starttls_auto: true, # optional + user: 'someuser', + password: 'somepass' + }, + mail_attributes, + notification ) =end diff --git a/app/models/channel/driver/twitter.rb b/app/models/channel/driver/twitter.rb index 3d5135d36..dee8e0879 100644 --- a/app/models/channel/driver/twitter.rb +++ b/app/models/channel/driver/twitter.rb @@ -1,12 +1,55 @@ # 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 - def fetch (_adapter_options, channel) + def fetch (options, channel) + @tweet = Tweet.new(options[:auth]) + @sync = options[:sync] @channel = channel - @tweet = Tweet.new(@channel[:options][:auth]) - @sync = @channel[:options][:sync] Rails.logger.debug 'twitter fetch started' @@ -17,16 +60,39 @@ class Channel::Driver::Twitter disconnect Rails.logger.debug 'twitter fetch completed' + + { + result: 'ok', + } end - def send(article, _notification = false) +=begin - @channel = Channel.find_by(area: 'Twitter::Account', active: true) - @tweet = Tweet.new(@channel[:options][:auth]) + instance = Channel::Driver::Twitter.new + 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 - tweet end @@ -54,7 +120,7 @@ class Channel::Driver::Twitter break if search[:limit] && search[:limit] <= counter 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 } @@ -74,7 +140,7 @@ class Channel::Driver::Twitter break if @sync[:mentions][:limit] && @sync[:mentions][:limit] <= counter 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 } @@ -93,7 +159,7 @@ class Channel::Driver::Twitter break if @sync[:direct_messages][:limit] && @sync[:direct_messages][:limit] <= counter 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 } diff --git a/app/models/channel/email_parser.rb b/app/models/channel/email_parser.rb index fb3fe64aa..0bc41d001 100644 --- a/app/models/channel/email_parser.rb +++ b/app/models/channel/email_parser.rb @@ -9,7 +9,7 @@ class Channel::EmailParser =begin - mail = parse( msg_as_string ) + mail = parse(msg_as_string) mail = { from: 'Some Name ', @@ -60,9 +60,9 @@ class Channel::EmailParser =end - def parse (msg) + def parse(msg) data = {} - mail = Mail.new( msg ) + mail = Mail.new(msg) # set all headers mail.header.fields.each { |field| @@ -70,7 +70,7 @@ class Channel::EmailParser next if !field.name # 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 data[ "raw-#{field.name.downcase}".to_sym ] = field @@ -101,11 +101,11 @@ class Channel::EmailParser # set extra headers begin - data[:from_email] = Mail::Address.new( from ).address - data[:from_local] = Mail::Address.new( from ).local - data[:from_domain] = Mail::Address.new( from ).domain - data[:from_display_name] = Mail::Address.new( from ).display_name || - ( Mail::Address.new( from ).comments && Mail::Address.new( from ).comments[0] ) + data[:from_email] = Mail::Address.new(from).address + data[:from_local] = Mail::Address.new(from).local + data[:from_domain] = Mail::Address.new(from).domain + data[:from_display_name] = Mail::Address.new(from).display_name || + (Mail::Address.new(from).comments && Mail::Address.new(from).comments[0]) rescue data[:from_email] = from data[:from_local] = from @@ -113,7 +113,7 @@ class Channel::EmailParser end # 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 data[:message_id] = data['message-id'.to_sym] @@ -129,7 +129,7 @@ class Channel::EmailParser # text attachment/body exists if mail.text_part 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? 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 filename = 'message.html' 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') 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 begin - attachs = _get_attachment( part, data[:attachments], mail ) - data[:attachments].concat( attachs ) + attachs = _get_attachment(part, data[:attachments], mail) + data[:attachments].concat(attachs) rescue - attachs = _get_attachment( part, data[:attachments], mail ) - data[:attachments].concat( attachs ) + attachs = _get_attachment(part, data[:attachments], mail) + data[:attachments].concat(attachs) end } end @@ -196,7 +196,7 @@ class Channel::EmailParser # text part only if !mail.mime_type || mail.mime_type.to_s == '' || mail.mime_type.to_s.downcase == 'text/plain' 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? 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' filename = 'message.html' 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') if !data[:body].valid_encoding? @@ -240,9 +240,9 @@ class Channel::EmailParser end # strip not wanted chars - data[:body].gsub!( /\n\r/, "\n" ) - data[:body].gsub!( /\r\n/, "\n" ) - data[:body].gsub!( /\r/, "\n" ) + data[:body].gsub!(/\n\r/, "\n") + data[:body].gsub!(/\r\n/, "\n") + data[:body].gsub!(/\r/, "\n") # remember original mail instance data[:mail_instance] = mail @@ -250,14 +250,14 @@ class Channel::EmailParser data end - def _get_attachment( file, attachments, mail ) + def _get_attachment(file, attachments, mail) # check if sub parts are available if !file.parts.empty? a = [] file.parts.each {|p| - attachment = _get_attachment( p, attachments, mail ) - a.concat( attachment ) + attachment = _get_attachment(p, attachments, mail) + a.concat(attachment) } return a end @@ -341,7 +341,7 @@ retrns =end def process(channel, msg) - mail = parse( msg ) + mail = parse(msg) # run postmaster pre filter filters = { @@ -353,7 +353,7 @@ retrns '1000' => Channel::Filter::Database, } - # filter( channel, mail ) + # filter(channel, mail) filters.each {|_prio, backend| begin backend.run(channel, mail) @@ -379,10 +379,10 @@ retrns # create sender 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 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 if !user user = user_create( @@ -422,8 +422,8 @@ retrns # set ticket state to open if not new if ticket - state = Ticket::State.find( ticket.state_id ) - state_type = Ticket::StateType.find( state.state_type_id ) + state = Ticket::State.find(ticket.state_id) + state_type = Ticket::StateType.find(state.state_type_id) # if tickte is merged, find linked ticket if state_type.name == 'merged' @@ -440,16 +440,23 @@ retrns # create new ticket if !ticket - # set attributes + preferences = {} + if channel[:id] + preferences = { + channel_id: channel[:id] + } + end + ticket = Ticket.new( group_id: channel[:group_id] || 1, customer_id: user.id, title: mail[:subject] || '', - state_id: Ticket::State.find_by( name: 'new' ).id, - priority_id: Ticket::Priority.find_by( name: '2 normal' ).id, + state_id: Ticket::State.find_by(name: 'new').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 ticket.save @@ -460,8 +467,8 @@ retrns # set attributes article = Ticket::Article.new( ticket_id: ticket.id, - type_id: Ticket::Article::Type.find_by( name: 'email' ).id, - sender_id: Ticket::Article::Sender.find_by( name: 'Customer' ).id, + type_id: Ticket::Article::Type.find_by(name: 'email').id, + sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id, body: mail[:body], from: mail[:from], to: mail[:to], @@ -472,7 +479,7 @@ retrns ) # x-headers lookup - set_attributes_by_x_headers( article, 'article', mail ) + set_attributes_by_x_headers(article, 'article', mail) # create article article.save @@ -505,13 +512,13 @@ retrns # run postmaster post filter filters = { - # '0010' => Channel::Filter::Trusted, + # '0010' => Channel::Filter::Trusted, } # filter( channel, mail ) filters.each {|_prio, backend| begin - backend.run( channel, mail, ticket, article, user ) + backend.run(channel, mail, ticket, article, user) rescue => e Rails.logger.error "can't run postmaster post filter #{backend}" Rails.logger.error e.inspect @@ -525,11 +532,11 @@ retrns def user_create(data) # return existing - user = User.find_by( login: data[:email].downcase ) + user = User.find_by(login: data[:email].downcase) return user if user # create new user - roles = Role.where( name: 'Customer' ) + roles = Role.where(name: 'Customer') # fillup %w(firstname lastname).each { |item| @@ -551,7 +558,7 @@ retrns user 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 item_object.attributes.each {|key, _value| @@ -577,12 +584,12 @@ retrns item = assoc.class_name.constantize if item.respond_to?(:name) - if item.lookup( name: mail[ header.to_sym ] ) - item_object[key] = item.lookup( name: mail[ header.to_sym ] ).id + if item.lookup(name: mail[ header.to_sym ]) + item_object[key] = item.lookup(name: mail[ header.to_sym ]).id end elsif item.respond_to?(:login) - if item.lookup( login: mail[ header.to_sym ] ) - item_object[key] = item.lookup( login: mail[ header.to_sym ] ).id + if item.lookup(login: mail[ header.to_sym ]) + item_object[key] = item.lookup(login: mail[ header.to_sym ]).id end end } diff --git a/app/models/observer/ticket/article/communicate_twitter.rb b/app/models/observer/ticket/article/communicate_twitter.rb index c809e5783..68eba6067 100644 --- a/app/models/observer/ticket/article/communicate_twitter.rb +++ b/app/models/observer/ticket/article/communicate_twitter.rb @@ -12,22 +12,27 @@ class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer return if Setting.get('import_mode') # 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['name'] == 'Customer' # only apply on tweets - type = Ticket::Article::Type.lookup( id: record.type_id ) - return if type['name'] !~ /\Atwitter/ + type = Ticket::Article::Type.lookup(id: record.type_id) + return if type['name'] !~ /\Atwitter/i - twitter = Channel::Driver::Twitter.new - tweet = twitter.send({ - type: type['name'], - to: record.to, - body: record.body, - in_reply_to: record.in_reply_to - }) + ticket = Ticket.lookup(id: record.ticket_id) + fail "Can't find ticket.preferences for Ticket.find(#{record.ticket_id})" if !ticket.preferences + fail "Can't find ticket.preferences['channel_id'] for Ticket.find(#{record.ticket_id})" if !ticket.preferences['channel_id'] + channel = Channel.lookup(id: ticket.preferences['channel_id']) + fail "Channel.find(#{channel.id}) isn't a twitter channel!" if channel.options[:adapter] !~ /\Atwitter/i + 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.save end + end diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 84d7d2268..0224277f2 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -14,6 +14,7 @@ class Ticket < ApplicationModel include Ticket::SearchIndex extend Ticket::Search + store :preferences before_create :check_generate, :check_defaults, :check_title before_update :check_defaults, :check_title, :reset_pending_time before_destroy :destroy_dependencies diff --git a/db/migrate/20151213000001_update_ticket_preferences.rb b/db/migrate/20151213000001_update_ticket_preferences.rb new file mode 100644 index 000000000..24da07c97 --- /dev/null +++ b/db/migrate/20151213000001_update_ticket_preferences.rb @@ -0,0 +1,5 @@ +class UpdateTicketPreferences < ActiveRecord::Migration + def up + add_column :tickets, :preferences, :text, limit: 500.kilobytes + 1, null: true + end +end diff --git a/lib/tweet.rb b/lib/tweet.rb index 53b109249..289546091 100644 --- a/lib/tweet.rb +++ b/lib/tweet.rb @@ -86,7 +86,7 @@ class Tweet user 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 tweet.inspect @@ -118,6 +118,9 @@ class Tweet group_id: group_id, state_id: Ticket::State.find_by(name: 'new').id, priority_id: Ticket::Priority.find_by(name: '2 normal').id, + preferences: { + channel_id: channel.id + }, ) end @@ -164,7 +167,7 @@ class Tweet ) end - def to_group(tweet, group_id) + def to_group(tweet, group_id, channel) Rails.logger.debug 'import tweet' @@ -177,19 +180,19 @@ class Tweet # check if parent exists user = to_user(tweet) 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) 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) if existing_article ticket = existing_article.ticket else 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 else - ticket = to_ticket(tweet, user, group_id) + ticket = to_ticket(tweet, user, group_id, channel) end to_article(tweet, user, ticket) else diff --git a/test/integration/twitter_test.rb b/test/integration/twitter_test.rb index f0f496ed5..fa90d5e8c 100644 --- a/test/integration/twitter_test.rb +++ b/test/integration/twitter_test.rb @@ -48,7 +48,7 @@ class TwitterTest < ActiveSupport::TestCase # add channel current = Channel.where(area: 'Twitter::Account') current.each(&:destroy) - Channel.create( + channel = Channel.create( area: 'Twitter::Account', options: { adapter: 'twitter', @@ -93,6 +93,9 @@ class TwitterTest < ActiveSupport::TestCase group_id: 2, state: Ticket::State.find_by(name: 'new'), priority: Ticket::Priority.find_by(name: '2 normal'), + preferences: { + channel_id: channel.id, + }, updated_by_id: 1, created_by_id: 1, ) @@ -173,7 +176,7 @@ class TwitterTest < ActiveSupport::TestCase tweet = client.update( text, ) - sleep 10 + sleep 15 # fetch check system account article = nil @@ -243,7 +246,7 @@ class TwitterTest < ActiveSupport::TestCase text, ) assert(dm, "dm with ##{hash} created") - sleep 10 + sleep 15 # fetch check system account article = nil @@ -288,7 +291,7 @@ class TwitterTest < ActiveSupport::TestCase text, ) assert(dm, "second dm with ##{hash} created") - sleep 10 + sleep 15 # fetch check system account article = nil @@ -321,6 +324,7 @@ class TwitterTest < ActiveSupport::TestCase text, ) assert(dm, "third dm with ##{hash} created") + sleep 15 # fetch check system account article = nil