Merge pull request #45 from albertsun/twitter_user_agent

Twitter user agent

Andrew Cantino 12 jaren geleden
bovenliggende
commit
a0a074c96f
3 gewijzigde bestanden met toevoegingen van 184 en 0 verwijderingen
  1. 155 0
      app/models/agents/twitter_user_agent.rb
  2. 1 0
      spec/data_fixtures/user_tweets.json
  3. 28 0
      spec/models/agents/twitter_user_agent_spec.rb

+ 155 - 0
app/models/agents/twitter_user_agent.rb

@@ -0,0 +1,155 @@
1
+require "twitter"
2
+
3
+module Agents
4
+  class TwitterUserAgent < Agent
5
+    cannot_receive_events!
6
+
7
+    description <<-MD
8
+      The TwitterUserAgent follows the timeline of a specified Twitter user.
9
+
10
+      You must set up a Twitter app and provide it's `consumer_key`, `consumer_secret`, `oauth_token` and `oauth_token_secret`, (Also shown as "Access token" on the Twitter developer's site.) along with the `username` of the Twitter user to monitor.
11
+
12
+      Set `expected_update_period_in_days` to the maximum amount of time that you'd expect to pass between Events being created by this Agent.
13
+    MD
14
+
15
+    event_description <<-MD
16
+      Events are the raw JSON provided by the Twitter API. Should look something like:
17
+
18
+        {
19
+         :created_at=>"Thu Apr 04 13:27:48 +0000 2013",
20
+         :id=>319803490421596160,
21
+         :id_str=>"319803490421596160",
22
+         :text=>
23
+          "In which @jeresig goes to an art gallery and is \"the JavaScript programmer\". http://t.co/gt3PT1d3G1",
24
+         :source=>
25
+          "<a href=\"http://itunes.apple.com/us/app/twitter/id409789998?mt=12\" rel=\"nofollow\">Twitter for Mac</a>",
26
+         :truncated=>false,
27
+         :in_reply_to_status_id=>nil,
28
+         :in_reply_to_status_id_str=>nil,
29
+         :in_reply_to_user_id=>nil,
30
+         :in_reply_to_user_id_str=>nil,
31
+         :in_reply_to_screen_name=>nil,
32
+         :user=>
33
+          {:id=>2341001,
34
+           :id_str=>"2341001",
35
+           :name=>"Albert Sun",
36
+           :screen_name=>"albertsun",
37
+           :location=>"New York, NY",
38
+           :description=>
39
+            "News apps developer at NYT, formerly WSJ. graduated Penn 2010, geek, journalist, data-viz, nlp, gis, digital economics =)",
40
+           :url=>"http://albertsun.info",
41
+           :entities=>
42
+            {:url=>
43
+              {:urls=>
44
+                [{:url=>"http://albertsun.info",
45
+                  :expanded_url=>nil,
46
+                  :indices=>[0, 21]}]},
47
+             :description=>{:urls=>[]}},
48
+           :protected=>false,
49
+           :followers_count=>1857,
50
+           :friends_count=>798,
51
+           :listed_count=>115,
52
+           :created_at=>"Mon Mar 26 19:22:05 +0000 2007",
53
+           :favourites_count=>9,
54
+           :utc_offset=>-18000,
55
+           :time_zone=>"Eastern Time (US & Canada)",
56
+           :geo_enabled=>false,
57
+           :verified=>false,
58
+           :statuses_count=>2572,
59
+           :lang=>"en",
60
+           :contributors_enabled=>false,
61
+           :is_translator=>false,
62
+           :profile_background_color=>"1B2A2B",
63
+           :profile_background_image_url=>
64
+            "http://a0.twimg.com/profile_background_images/2802438/twitterbg.jpg",
65
+           :profile_background_image_url_https=>
66
+            "https://si0.twimg.com/profile_background_images/2802438/twitterbg.jpg",
67
+           :profile_background_tile=>false,
68
+           :profile_image_url=>
69
+            "http://a0.twimg.com/profile_images/110500205/profile-square_normal.jpg",
70
+           :profile_image_url_https=>
71
+            "https://si0.twimg.com/profile_images/110500205/profile-square_normal.jpg",
72
+           :profile_link_color=>"0000FF",
73
+           :profile_sidebar_border_color=>"87BC44",
74
+           :profile_sidebar_fill_color=>"E0FF92",
75
+           :profile_text_color=>"000000",
76
+           :profile_use_background_image=>true,
77
+           :default_profile=>false,
78
+           :default_profile_image=>false,
79
+           :following=>false,
80
+           :follow_request_sent=>false,
81
+           :notifications=>false},
82
+         :geo=>nil,
83
+         :coordinates=>nil,
84
+         :place=>nil,
85
+         :contributors=>nil,
86
+         :retweet_count=>0,
87
+         :favorite_count=>0,
88
+         :entities=>
89
+          {:hashtags=>[],
90
+           :urls=>
91
+            [{:url=>"http://t.co/gt3PT1d3G1",
92
+              :expanded_url=>
93
+               "http://www.nytimes.com/2013/04/04/fashion/art-and-techology-a-clash-of-cultures.html?pagewanted=all",
94
+              :display_url=>"nytimes.com/2013/04/04/fas",
95
+              :indices=>[77, 99]}],
96
+           :user_mentions=>
97
+            [{:screen_name=>"jeresig",
98
+              :name=>"John Resig",
99
+              :id=>752673,
100
+              :id_str=>"752673",
101
+              :indices=>[9, 17]}]},
102
+         :favorited=>false,
103
+         :retweeted=>false,
104
+         :possibly_sensitive=>false,
105
+         :lang=>"en"
106
+        }
107
+    MD
108
+
109
+    default_schedule "every_1h"
110
+
111
+    def validate_options
112
+      unless options[:username].present? && options[:expected_update_period_in_days].present? && options[:consumer_key].present? && options[:consumer_secret].present? && options[:oauth_token].present? && options[:oauth_token_secret].present?
113
+        errors.add(:base, "expected_update_period_in_days, username, consumer_key, consumer_secret, oauth_token and oauth_token_secret are required")
114
+      end
115
+    end
116
+
117
+    def working?
118
+      (event = event_created_within(options[:expected_update_period_in_days].to_i.days)) && event.payload.present?
119
+    end
120
+
121
+    def default_options
122
+      {
123
+          :username => "tectonic",
124
+          :expected_update_period_in_days => "2",
125
+          :consumer_key => "---",
126
+          :consumer_secret => "---",
127
+          :oauth_token => "---",
128
+          :oauth_token_secret => "---"
129
+      }
130
+    end
131
+
132
+    def check
133
+      Twitter.configure do |config|
134
+        config.consumer_key = options[:consumer_key]
135
+        config.consumer_secret = options[:consumer_secret]
136
+        config.oauth_token = options[:oauth_token]
137
+        config.oauth_token_secret = options[:oauth_token_secret]
138
+      end
139
+
140
+      since_id = memory[:since_id] || nil
141
+      opts = {:count => 200, :include_rts => true, :exclude_replies => false, :include_entities => true, :contributor_details => true}
142
+      opts.merge! :since_id => since_id unless since_id.nil?
143
+
144
+      tweets = Twitter.user_timeline(options[:username], opts)
145
+
146
+      tweets.each do |tweet|
147
+        memory[:since_id] = tweet.id if !memory[:since_id] || (tweet.id > memory[:since_id])
148
+
149
+        create_event :payload => tweet.attrs
150
+      end
151
+
152
+      save!
153
+    end
154
+  end
155
+end

+ 1 - 0
spec/data_fixtures/user_tweets.json

@@ -0,0 +1 @@
1
+[{"created_at":"Sun Apr 07 20:34:17 +0000 2013","id":320997982096990208,"id_str":"320997982096990208","text":"@christophe971 Cool project!","source":"web","truncated":false,"in_reply_to_status_id":320990180205002752,"in_reply_to_status_id_str":"320990180205002752","in_reply_to_user_id":12574212,"in_reply_to_user_id_str":"12574212","in_reply_to_screen_name":"christophe971","user":{"id":9813372,"id_str":"9813372","name":"Andrew Cantino","screen_name":"tectonic","location":"San Francisco, CA","description":"Experimentalist, web developer, and VP of Engineering at @Mavenlink.","url":"http://andrewcantino.com","entities":{"url":{"urls":[{"url":"http://andrewcantino.com","expanded_url":null,"indices":[0,24]}]},"description":{"urls":[]}},"protected":false,"followers_count":999,"friends_count":492,"listed_count":39,"created_at":"Wed Oct 31 03:16:39 +0000 2007","favourites_count":132,"utc_offset":-28800,"time_zone":"Pacific Time (US & Canada)","geo_enabled":true,"verified":false,"statuses_count":3433,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"352726","profile_background_image_url":"http://a0.twimg.com/images/themes/theme5/bg.gif","profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme5/bg.gif","profile_background_tile":false,"profile_image_url":"http://a0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_image_url_https":"https://si0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_link_color":"D02B55","profile_sidebar_border_color":"829D5E","profile_sidebar_fill_color":"99CC33","profile_text_color":"3E4415","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":false,"notifications":null},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"favorite_count":0,"entities":{"hashtags":[],"urls":[],"user_mentions":[{"screen_name":"christophe971","name":"Christophe Maximin","id":12574212,"id_str":"12574212","indices":[0,14]}]},"favorited":false,"retweeted":false,"lang":"en"},{"created_at":"Sun Apr 07 20:34:06 +0000 2013","id":320997936773349378,"id_str":"320997936773349378","text":"@christophe971 Fair enough.  I've previously worked on something very similar and wondered if you're looking for collaborators.","source":"web","truncated":false,"in_reply_to_status_id":320990180205002752,"in_reply_to_status_id_str":"320990180205002752","in_reply_to_user_id":12574212,"in_reply_to_user_id_str":"12574212","in_reply_to_screen_name":"christophe971","user":{"id":9813372,"id_str":"9813372","name":"Andrew Cantino","screen_name":"tectonic","location":"San Francisco, CA","description":"Experimentalist, web developer, and VP of Engineering at @Mavenlink.","url":"http://andrewcantino.com","entities":{"url":{"urls":[{"url":"http://andrewcantino.com","expanded_url":null,"indices":[0,24]}]},"description":{"urls":[]}},"protected":false,"followers_count":999,"friends_count":492,"listed_count":39,"created_at":"Wed Oct 31 03:16:39 +0000 2007","favourites_count":132,"utc_offset":-28800,"time_zone":"Pacific Time (US & Canada)","geo_enabled":true,"verified":false,"statuses_count":3433,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"352726","profile_background_image_url":"http://a0.twimg.com/images/themes/theme5/bg.gif","profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme5/bg.gif","profile_background_tile":false,"profile_image_url":"http://a0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_image_url_https":"https://si0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_link_color":"D02B55","profile_sidebar_border_color":"829D5E","profile_sidebar_fill_color":"99CC33","profile_text_color":"3E4415","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":false,"notifications":null},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"favorite_count":0,"entities":{"hashtags":[],"urls":[],"user_mentions":[{"screen_name":"christophe971","name":"Christophe Maximin","id":12574212,"id_str":"12574212","indices":[0,14]}]},"favorited":false,"retweeted":false,"lang":"en"},{"created_at":"Sun Apr 07 19:59:21 +0000 2013","id":320989192488382464,"id_str":"320989192488382464","text":"@christophe971 Is denisthebot open source?","source":"web","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":12574212,"in_reply_to_user_id_str":"12574212","in_reply_to_screen_name":"christophe971","user":{"id":9813372,"id_str":"9813372","name":"Andrew Cantino","screen_name":"tectonic","location":"San Francisco, CA","description":"Experimentalist, web developer, and VP of Engineering at @Mavenlink.","url":"http://andrewcantino.com","entities":{"url":{"urls":[{"url":"http://andrewcantino.com","expanded_url":null,"indices":[0,24]}]},"description":{"urls":[]}},"protected":false,"followers_count":999,"friends_count":492,"listed_count":39,"created_at":"Wed Oct 31 03:16:39 +0000 2007","favourites_count":132,"utc_offset":-28800,"time_zone":"Pacific Time (US & Canada)","geo_enabled":true,"verified":false,"statuses_count":3433,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"352726","profile_background_image_url":"http://a0.twimg.com/images/themes/theme5/bg.gif","profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme5/bg.gif","profile_background_tile":false,"profile_image_url":"http://a0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_image_url_https":"https://si0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_link_color":"D02B55","profile_sidebar_border_color":"829D5E","profile_sidebar_fill_color":"99CC33","profile_text_color":"3E4415","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":false,"notifications":null},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"favorite_count":0,"entities":{"hashtags":[],"urls":[],"user_mentions":[{"screen_name":"christophe971","name":"Christophe Maximin","id":12574212,"id_str":"12574212","indices":[0,14]}]},"favorited":false,"retweeted":false,"lang":"nl"},{"created_at":"Sun Apr 07 00:18:42 +0000 2013","id":320692069385527296,"id_str":"320692069385527296","text":"Netflix won’t allow a password longer than 10 characters.  Fail.","source":"<a href=\"http://tapbots.com/software/tweetbot/mac\" rel=\"nofollow\">Tweetbot for Mac</a>","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":9813372,"id_str":"9813372","name":"Andrew Cantino","screen_name":"tectonic","location":"San Francisco, CA","description":"Experimentalist, web developer, and VP of Engineering at @Mavenlink.","url":"http://andrewcantino.com","entities":{"url":{"urls":[{"url":"http://andrewcantino.com","expanded_url":null,"indices":[0,24]}]},"description":{"urls":[]}},"protected":false,"followers_count":999,"friends_count":492,"listed_count":39,"created_at":"Wed Oct 31 03:16:39 +0000 2007","favourites_count":132,"utc_offset":-28800,"time_zone":"Pacific Time (US & Canada)","geo_enabled":true,"verified":false,"statuses_count":3433,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"352726","profile_background_image_url":"http://a0.twimg.com/images/themes/theme5/bg.gif","profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme5/bg.gif","profile_background_tile":false,"profile_image_url":"http://a0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_image_url_https":"https://si0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_link_color":"D02B55","profile_sidebar_border_color":"829D5E","profile_sidebar_fill_color":"99CC33","profile_text_color":"3E4415","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":false,"notifications":null},"geo":null,"coordinates":null,"place":{"id":"8d2591b0d50daf32","url":"https://api.twitter.com/1.1/geo/id/8d2591b0d50daf32.json","place_type":"neighborhood","name":"Lower Haight","full_name":"Lower Haight, San Francisco","country_code":"US","country":"United States","polylines":[],"bounding_box":{"type":"Polygon","coordinates":[[[-122.437382,37.769551],[-122.421482,37.769551],[-122.421482,37.77602],[-122.437382,37.77602]]]},"attributes":{}},"contributors":null,"retweet_count":2,"favorite_count":0,"entities":{"hashtags":[],"urls":[],"user_mentions":[]},"favorited":false,"retweeted":false,"lang":"en"},{"created_at":"Sat Apr 06 18:42:44 +0000 2013","id":320607521532432384,"id_str":"320607521532432384","text":"@ryanmaxwell @draconisnz let me know if you use it, or have suggestions!","source":"<a href=\"http://twitter.com/download/iphone\" rel=\"nofollow\">Twitter for iPhone</a>","truncated":false,"in_reply_to_status_id":320454615885807616,"in_reply_to_status_id_str":"320454615885807616","in_reply_to_user_id":14658141,"in_reply_to_user_id_str":"14658141","in_reply_to_screen_name":"ryanmaxwell","user":{"id":9813372,"id_str":"9813372","name":"Andrew Cantino","screen_name":"tectonic","location":"San Francisco, CA","description":"Experimentalist, web developer, and VP of Engineering at @Mavenlink.","url":"http://andrewcantino.com","entities":{"url":{"urls":[{"url":"http://andrewcantino.com","expanded_url":null,"indices":[0,24]}]},"description":{"urls":[]}},"protected":false,"followers_count":999,"friends_count":492,"listed_count":39,"created_at":"Wed Oct 31 03:16:39 +0000 2007","favourites_count":132,"utc_offset":-28800,"time_zone":"Pacific Time (US & Canada)","geo_enabled":true,"verified":false,"statuses_count":3433,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"352726","profile_background_image_url":"http://a0.twimg.com/images/themes/theme5/bg.gif","profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme5/bg.gif","profile_background_tile":false,"profile_image_url":"http://a0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_image_url_https":"https://si0.twimg.com/profile_images/1694984565/me-right_normal.jpg","profile_link_color":"D02B55","profile_sidebar_border_color":"829D5E","profile_sidebar_fill_color":"99CC33","profile_text_color":"3E4415","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":false,"notifications":null},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"favorite_count":0,"entities":{"hashtags":[],"urls":[],"user_mentions":[{"screen_name":"ryanmaxwell","name":"Ryan Maxwell","id":14658141,"id_str":"14658141","indices":[0,12]},{"screen_name":"draconisNZ","name":"Dan","id":14979092,"id_str":"14979092","indices":[13,24]}]},"favorited":false,"retweeted":false,"lang":"en"}]

+ 28 - 0
spec/models/agents/twitter_user_agent_spec.rb

@@ -0,0 +1,28 @@
1
+require 'spec_helper'
2
+
3
+describe Agents::TwitterUserAgent do
4
+  before do
5
+    # intercept the twitter API request for @tectonic's user profile
6
+    stub_request(:any, /tectonic/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/user_tweets.json")), :status => 200)
7
+  
8
+    @opts = {
9
+      :username => "tectonic",
10
+      :expected_update_period_in_days => "2",
11
+      :consumer_key => "---",
12
+      :consumer_secret => "---",
13
+      :oauth_token => "---",
14
+      :oauth_token_secret => "---"
15
+    }
16
+
17
+    @checker = Agents::TwitterUserAgent.new(:name => "tectonic", :options => @opts)
18
+    @checker.user = users(:bob)
19
+    @checker.save!
20
+  end
21
+
22
+  describe "#check" do
23
+    it "should check for changes" do
24
+      lambda { @checker.check }.should change { Event.count }.by(5)
25
+    end
26
+  end
27
+
28
+end