2015-07-02 15:13:04 +00:00
|
|
|
# Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/
|
|
|
|
|
2016-01-10 13:24:54 +00:00
|
|
|
class Channel::Driver::Twitter
|
|
|
|
|
2015-12-14 09:23:14 +00:00
|
|
|
=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
|
|
|
|
|
2016-10-28 13:06:00 +00:00
|
|
|
def fetch(options, channel)
|
2015-07-02 15:13:04 +00:00
|
|
|
|
2015-12-21 00:48:49 +00:00
|
|
|
options = check_external_credential(options)
|
|
|
|
|
2016-01-08 14:31:47 +00:00
|
|
|
@rest_client = TweetRest.new(options[:auth])
|
|
|
|
@sync = options[:sync]
|
|
|
|
@channel = channel
|
2015-07-02 15:13:04 +00:00
|
|
|
|
2015-07-03 09:42:54 +00:00
|
|
|
Rails.logger.debug 'twitter fetch started'
|
2015-07-02 15:13:04 +00:00
|
|
|
|
|
|
|
fetch_mentions
|
2016-01-08 14:31:47 +00:00
|
|
|
fetch_search
|
2015-07-02 15:13:04 +00:00
|
|
|
fetch_direct_messages
|
|
|
|
|
|
|
|
disconnect
|
|
|
|
|
|
|
|
Rails.logger.debug 'twitter fetch completed'
|
2015-12-14 09:23:14 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
result: 'ok',
|
2016-01-08 14:31:47 +00:00
|
|
|
notice: '',
|
2015-12-14 09:23:14 +00:00
|
|
|
}
|
2015-07-02 15:13:04 +00:00
|
|
|
end
|
|
|
|
|
2016-01-10 13:24:54 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
instance = Channel::Driver::Twitter.new
|
|
|
|
instance.fetchable?(channel)
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def fetchable?(channel)
|
|
|
|
return true if Rails.env.test?
|
|
|
|
|
2016-02-12 07:48:33 +00:00
|
|
|
# only fetch once in 30 minutes
|
2016-01-10 13:24:54 +00:00
|
|
|
return true if !channel.preferences
|
|
|
|
return true if !channel.preferences[:last_fetch]
|
2017-06-01 06:23:53 +00:00
|
|
|
return false if channel.preferences[:last_fetch] > Time.zone.now - 20.minutes
|
2016-01-10 13:24:54 +00:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2015-12-14 09:23:14 +00:00
|
|
|
=begin
|
2015-07-02 15:13:04 +00:00
|
|
|
|
2015-12-14 09:23:14 +00:00
|
|
|
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
|
|
|
|
)
|
2015-07-02 15:13:04 +00:00
|
|
|
|
2015-12-14 09:23:14 +00:00
|
|
|
=end
|
2015-07-02 15:13:04 +00:00
|
|
|
|
2015-12-14 09:23:14 +00:00
|
|
|
def send(options, article, _notification = false)
|
|
|
|
|
|
|
|
# return if we run import mode
|
|
|
|
return if Setting.get('import_mode')
|
|
|
|
|
2015-12-21 00:48:49 +00:00
|
|
|
options = check_external_credential(options)
|
|
|
|
|
2016-01-08 14:31:47 +00:00
|
|
|
@rest_client = TweetRest.new(options[:auth])
|
|
|
|
tweet = @rest_client.from_article(article)
|
2015-12-14 09:23:14 +00:00
|
|
|
disconnect
|
2015-07-02 15:13:04 +00:00
|
|
|
tweet
|
|
|
|
end
|
|
|
|
|
|
|
|
def disconnect
|
2017-11-23 08:09:44 +00:00
|
|
|
@stream_client&.disconnect
|
|
|
|
@rest_client&.disconnect
|
2016-01-08 14:31:47 +00:00
|
|
|
end
|
|
|
|
|
2017-10-02 11:23:14 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
Channel::Driver::Twitter.streamable?
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
true|false
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.streamable?
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2016-01-09 12:23:11 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
create stream endpoint form 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
|
|
|
|
stream_instance = instance.stream_instance(channel)
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
instance_of_stream_handle
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2016-01-08 14:31:47 +00:00
|
|
|
def stream_instance(channel)
|
|
|
|
@channel = channel
|
|
|
|
options = @channel.options
|
|
|
|
@stream_client = TweetStream.new(options[:auth])
|
|
|
|
end
|
|
|
|
|
2016-01-09 12:23:11 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
stream tweets from twitter account
|
|
|
|
|
2016-10-28 13:06:00 +00:00
|
|
|
instance.stream
|
2016-01-09 12:23:11 +00:00
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
# endless loop
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2016-01-08 14:31:47 +00:00
|
|
|
def stream
|
2017-10-02 11:23:14 +00:00
|
|
|
sleep_on_unauthorized = 65
|
2017-10-01 12:25:52 +00:00
|
|
|
2.times do |loop_count|
|
2017-06-01 06:23:53 +00:00
|
|
|
begin
|
|
|
|
stream_start
|
|
|
|
rescue Twitter::Error::Unauthorized => e
|
|
|
|
Rails.logger.info "Unable to stream, try #{loop_count}, error #{e.inspect}"
|
2017-11-23 08:09:44 +00:00
|
|
|
if loop_count >= 2
|
2017-06-01 06:23:53 +00:00
|
|
|
raise "Unable to stream, try #{loop_count}, error #{e.inspect}"
|
|
|
|
end
|
2017-11-23 08:09:44 +00:00
|
|
|
Rails.logger.info "wait for #{sleep_on_unauthorized} sec. and try it again"
|
|
|
|
sleep sleep_on_unauthorized
|
2017-06-01 06:23:53 +00:00
|
|
|
end
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-06-01 06:23:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def stream_start
|
|
|
|
|
2016-02-12 07:48:33 +00:00
|
|
|
sync = @channel.options['sync']
|
2016-03-01 14:26:46 +00:00
|
|
|
raise 'Need channel.options[\'sync\'] for account, but no params found' if !sync
|
2016-02-12 07:48:33 +00:00
|
|
|
|
|
|
|
filter = {}
|
|
|
|
if sync['search']
|
|
|
|
hashtags = []
|
2017-10-01 12:25:52 +00:00
|
|
|
sync['search'].each do |item|
|
2017-10-16 13:43:48 +00:00
|
|
|
next if item['term'].blank?
|
2017-11-03 07:13:44 +00:00
|
|
|
next if item['term'] == '#'
|
2017-10-16 13:43:48 +00:00
|
|
|
next if item['group_id'].blank?
|
2016-02-12 07:48:33 +00:00
|
|
|
hashtags.push item['term']
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2016-02-12 07:48:33 +00:00
|
|
|
filter[:track] = hashtags.join(',')
|
|
|
|
end
|
|
|
|
if sync['mentions'] && sync['mentions']['group_id'] != ''
|
2016-01-08 14:31:47 +00:00
|
|
|
filter[:replies] = 'all'
|
|
|
|
end
|
|
|
|
|
2017-11-23 08:09:44 +00:00
|
|
|
return if filter.blank?
|
2016-02-12 07:48:33 +00:00
|
|
|
|
2016-01-08 14:31:47 +00:00
|
|
|
@stream_client.client.user(filter) do |tweet|
|
|
|
|
next if tweet.class != Twitter::Tweet && tweet.class != Twitter::DirectMessage
|
2017-03-06 11:18:04 +00:00
|
|
|
|
|
|
|
# wait until own posts are stored in local database to prevent importing own tweets
|
2017-06-01 06:23:53 +00:00
|
|
|
next if @stream_client.locale_sender?(tweet) && own_tweet_already_imported?(tweet)
|
|
|
|
|
2016-01-08 14:31:47 +00:00
|
|
|
next if Ticket::Article.find_by(message_id: tweet.id)
|
|
|
|
|
|
|
|
# check direct message
|
|
|
|
if tweet.class == Twitter::DirectMessage
|
2016-02-12 07:48:33 +00:00
|
|
|
if sync['direct_messages'] && sync['direct_messages']['group_id'] != ''
|
2017-06-01 06:23:53 +00:00
|
|
|
next if @stream_client.direct_message_limit_reached(tweet, 2)
|
2016-02-12 07:48:33 +00:00
|
|
|
@stream_client.to_group(tweet, sync['direct_messages']['group_id'], @channel)
|
2016-01-08 14:31:47 +00:00
|
|
|
end
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2017-02-16 16:04:59 +00:00
|
|
|
next if !track_retweets? && tweet.retweet?
|
2017-06-01 06:23:53 +00:00
|
|
|
next if @stream_client.tweet_limit_reached(tweet, 2)
|
2016-01-08 14:31:47 +00:00
|
|
|
|
|
|
|
# check if it's mention
|
2017-10-16 13:43:48 +00:00
|
|
|
if sync['mentions'] && sync['mentions']['group_id'].present?
|
2016-01-08 14:31:47 +00:00
|
|
|
hit = false
|
2017-11-23 08:09:44 +00:00
|
|
|
tweet.user_mentions&.each do |user|
|
|
|
|
if user.id.to_s == @channel.options['user']['id'].to_s
|
|
|
|
hit = true
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2016-01-08 14:31:47 +00:00
|
|
|
end
|
|
|
|
if hit
|
2016-02-12 07:48:33 +00:00
|
|
|
@stream_client.to_group(tweet, sync['mentions']['group_id'], @channel)
|
2016-01-08 14:31:47 +00:00
|
|
|
next
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# check hashtags
|
2016-02-12 07:48:33 +00:00
|
|
|
if sync['search'] && tweet.hashtags
|
2016-01-08 14:31:47 +00:00
|
|
|
hit = false
|
2017-10-01 12:25:52 +00:00
|
|
|
sync['search'].each do |item|
|
2017-10-16 13:43:48 +00:00
|
|
|
next if item['term'].blank?
|
2017-11-03 07:13:44 +00:00
|
|
|
next if item['term'] == '#'
|
2017-10-16 13:43:48 +00:00
|
|
|
next if item['group_id'].blank?
|
2017-10-01 12:25:52 +00:00
|
|
|
tweet.hashtags.each do |hashtag|
|
2016-01-08 14:31:47 +00:00
|
|
|
next if item['term'] !~ /^#/
|
|
|
|
if item['term'].sub(/^#/, '') == hashtag.text
|
|
|
|
hit = item
|
|
|
|
end
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
|
|
|
end
|
2016-01-08 14:31:47 +00:00
|
|
|
if hit
|
|
|
|
@stream_client.to_group(tweet, hit['group_id'], @channel)
|
|
|
|
next
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# check stings
|
2016-02-12 07:48:33 +00:00
|
|
|
if sync['search']
|
2016-01-08 14:31:47 +00:00
|
|
|
hit = false
|
|
|
|
body = tweet.text
|
2017-10-01 12:25:52 +00:00
|
|
|
sync['search'].each do |item|
|
2017-10-16 13:43:48 +00:00
|
|
|
next if item['term'].blank?
|
2017-11-03 07:13:44 +00:00
|
|
|
next if item['term'] == '#'
|
2017-10-16 13:43:48 +00:00
|
|
|
next if item['group_id'].blank?
|
2017-11-23 08:09:44 +00:00
|
|
|
if body.match?(/#{item['term']}/)
|
2016-01-08 14:31:47 +00:00
|
|
|
hit = item
|
|
|
|
end
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2016-01-08 14:31:47 +00:00
|
|
|
if hit
|
|
|
|
@stream_client.to_group(tweet, hit['group_id'], @channel)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
2015-07-02 15:13:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def fetch_search
|
2016-12-02 11:24:00 +00:00
|
|
|
return if @sync[:search].blank?
|
2017-10-01 12:25:52 +00:00
|
|
|
@sync[:search].each do |search|
|
2016-12-02 11:24:00 +00:00
|
|
|
next if search[:term].blank?
|
2017-11-03 07:13:44 +00:00
|
|
|
next if search[:term] == '#'
|
2016-12-02 11:24:00 +00:00
|
|
|
next if search[:group_id].blank?
|
2015-07-02 15:13:04 +00:00
|
|
|
result_type = search[:type] || 'mixed'
|
|
|
|
Rails.logger.debug " - searching for '#{search[:term]}'"
|
2016-10-28 13:06:00 +00:00
|
|
|
older_import = 0
|
|
|
|
older_import_max = 20
|
2017-10-01 12:25:52 +00:00
|
|
|
@rest_client.client.search(search[:term], result_type: result_type).collect do |tweet|
|
2017-01-19 15:23:41 +00:00
|
|
|
next if !track_retweets? && tweet.retweet?
|
2016-10-28 13:06:00 +00:00
|
|
|
|
|
|
|
# ignore older messages
|
2017-06-19 09:42:02 +00:00
|
|
|
if (@channel.created_at - 15.days) > tweet.created_at.dup.utc || older_import >= older_import_max
|
2016-10-28 13:06:00 +00:00
|
|
|
older_import += 1
|
|
|
|
Rails.logger.debug "tweet to old: #{tweet.id}/#{tweet.created_at}"
|
|
|
|
next
|
|
|
|
end
|
2017-06-01 06:23:53 +00:00
|
|
|
|
|
|
|
next if @rest_client.locale_sender?(tweet) && own_tweet_already_imported?(tweet)
|
2016-01-08 14:31:47 +00:00
|
|
|
next if Ticket::Article.find_by(message_id: tweet.id)
|
|
|
|
break if @rest_client.tweet_limit_reached(tweet)
|
|
|
|
@rest_client.to_group(tweet, search[:group_id], @channel)
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
|
|
|
end
|
2015-07-02 15:13:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def fetch_mentions
|
2016-12-02 11:24:00 +00:00
|
|
|
return if @sync[:mentions].blank?
|
|
|
|
return if @sync[:mentions][:group_id].blank?
|
2015-07-02 15:13:04 +00:00
|
|
|
Rails.logger.debug ' - searching for mentions'
|
2016-10-28 13:06:00 +00:00
|
|
|
older_import = 0
|
|
|
|
older_import_max = 20
|
2017-10-01 12:25:52 +00:00
|
|
|
@rest_client.client.mentions_timeline.each do |tweet|
|
2017-01-19 15:23:41 +00:00
|
|
|
next if !track_retweets? && tweet.retweet?
|
2016-10-28 13:06:00 +00:00
|
|
|
|
|
|
|
# ignore older messages
|
2017-06-19 09:42:02 +00:00
|
|
|
if (@channel.created_at - 15.days) > tweet.created_at.dup.utc || older_import >= older_import_max
|
2016-10-28 13:06:00 +00:00
|
|
|
older_import += 1
|
|
|
|
Rails.logger.debug "tweet to old: #{tweet.id}/#{tweet.created_at}"
|
|
|
|
next
|
|
|
|
end
|
2016-01-08 14:31:47 +00:00
|
|
|
next if Ticket::Article.find_by(message_id: tweet.id)
|
|
|
|
break if @rest_client.tweet_limit_reached(tweet)
|
|
|
|
@rest_client.to_group(tweet, @sync[:mentions][:group_id], @channel)
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2015-07-02 15:13:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def fetch_direct_messages
|
2016-12-02 11:24:00 +00:00
|
|
|
return if @sync[:direct_messages].blank?
|
|
|
|
return if @sync[:direct_messages][:group_id].blank?
|
2015-07-02 15:13:04 +00:00
|
|
|
Rails.logger.debug ' - searching for direct_messages'
|
2016-10-28 13:06:00 +00:00
|
|
|
older_import = 0
|
|
|
|
older_import_max = 20
|
2017-10-01 12:25:52 +00:00
|
|
|
@rest_client.client.direct_messages(full_text: 'true').each do |tweet|
|
2016-10-28 13:06:00 +00:00
|
|
|
|
|
|
|
# ignore older messages
|
2017-06-19 09:42:02 +00:00
|
|
|
if (@channel.created_at - 15.days) > tweet.created_at.dup.utc || older_import >= older_import_max
|
2016-10-28 13:06:00 +00:00
|
|
|
older_import += 1
|
|
|
|
Rails.logger.debug "tweet to old: #{tweet.id}/#{tweet.created_at}"
|
|
|
|
next
|
|
|
|
end
|
2016-01-08 14:31:47 +00:00
|
|
|
next if Ticket::Article.find_by(message_id: tweet.id)
|
|
|
|
break if @rest_client.direct_message_limit_reached(tweet)
|
|
|
|
@rest_client.to_group(tweet, @sync[:direct_messages][:group_id], @channel)
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2015-07-02 15:13:04 +00:00
|
|
|
end
|
2015-12-21 00:48:49 +00:00
|
|
|
|
|
|
|
def check_external_credential(options)
|
|
|
|
if options[:auth] && options[:auth][:external_credential_id]
|
|
|
|
external_credential = ExternalCredential.find_by(id: options[:auth][:external_credential_id])
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "No such ExternalCredential.find(#{options[:auth][:external_credential_id]})" if !external_credential
|
2015-12-21 00:48:49 +00:00
|
|
|
options[:auth][:consumer_key] = external_credential.credentials['consumer_key']
|
|
|
|
options[:auth][:consumer_secret] = external_credential.credentials['consumer_secret']
|
|
|
|
end
|
|
|
|
options
|
|
|
|
end
|
|
|
|
|
2017-01-19 15:23:41 +00:00
|
|
|
def track_retweets?
|
|
|
|
@channel.options && @channel.options['sync'] && @channel.options['sync']['track_retweets']
|
|
|
|
end
|
2017-06-01 06:23:53 +00:00
|
|
|
|
|
|
|
def own_tweet_already_imported?(tweet)
|
|
|
|
event_time = Time.zone.now
|
|
|
|
sleep 4
|
2017-10-01 12:25:52 +00:00
|
|
|
12.times do |loop_count|
|
2017-06-01 06:23:53 +00:00
|
|
|
if Ticket::Article.find_by(message_id: tweet.id)
|
|
|
|
Rails.logger.debug "Own tweet already imported, skipping tweet #{tweet.id}"
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
count = Delayed::Job.where('created_at < ?', event_time).count
|
|
|
|
break if count.zero?
|
|
|
|
sleep_time = 2 * count
|
|
|
|
sleep_time = 5 if sleep_time > 5
|
|
|
|
Rails.logger.debug "Delay importing own tweets - sleep #{sleep_time} (loop #{loop_count})"
|
|
|
|
sleep sleep_time
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-06-01 06:23:53 +00:00
|
|
|
|
|
|
|
if Ticket::Article.find_by(message_id: tweet.id)
|
|
|
|
Rails.logger.debug "Own tweet already imported, skipping tweet #{tweet.id}"
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2015-07-02 15:13:04 +00:00
|
|
|
end
|