tweetable 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/Gemfile +8 -0
  2. data/History.txt +4 -0
  3. data/Manifest.txt +33 -0
  4. data/PostInstall.txt +7 -0
  5. data/README.rdoc +37 -0
  6. data/Rakefile +19 -0
  7. data/VERSION +1 -0
  8. data/lib/tweetable/authorization.rb +21 -0
  9. data/lib/tweetable/collection.rb +54 -0
  10. data/lib/tweetable/link.rb +30 -0
  11. data/lib/tweetable/message.rb +119 -0
  12. data/lib/tweetable/persistable.rb +32 -0
  13. data/lib/tweetable/photo.rb +6 -0
  14. data/lib/tweetable/queue.rb +121 -0
  15. data/lib/tweetable/search.rb +77 -0
  16. data/lib/tweetable/twitter_client.rb +36 -0
  17. data/lib/tweetable/twitter_streaming_client.rb +67 -0
  18. data/lib/tweetable/url.rb +39 -0
  19. data/lib/tweetable/user.rb +104 -0
  20. data/lib/tweetable.rb +82 -0
  21. data/pkg/tweetable-0.1.7.gem +0 -0
  22. data/script/console +10 -0
  23. data/script/destroy +14 -0
  24. data/script/generate +14 -0
  25. data/spec/collection_spec.rb +55 -0
  26. data/spec/fixtures/blank.json +1 -0
  27. data/spec/fixtures/flippyhead.json +1 -0
  28. data/spec/fixtures/follower_ids.json +1 -0
  29. data/spec/fixtures/friend_ids.json +1 -0
  30. data/spec/fixtures/friends_timeline.json +1 -0
  31. data/spec/fixtures/link_blank.json +1 -0
  32. data/spec/fixtures/link_exists.json +1 -0
  33. data/spec/fixtures/rate_limit_status.json +1 -0
  34. data/spec/fixtures/search.json +1 -0
  35. data/spec/fixtures/user_timeline.json +1 -0
  36. data/spec/fixtures/verify_credentials.json +1 -0
  37. data/spec/link_spec.rb +35 -0
  38. data/spec/message_spec.rb +148 -0
  39. data/spec/persistable_spec.rb +53 -0
  40. data/spec/queue_spec.rb +29 -0
  41. data/spec/search_spec.rb +60 -0
  42. data/spec/spec.opts +5 -0
  43. data/spec/spec_helper.rb +55 -0
  44. data/spec/tweetable_spec.rb +19 -0
  45. data/spec/twitter_client_spec.rb +41 -0
  46. data/spec/twitter_streaming_client_spec.rb +18 -0
  47. data/spec/user_spec.rb +143 -0
  48. data/tweetable.gemspec +106 -0
  49. metadata +165 -0
@@ -0,0 +1,39 @@
1
+ # require 'yajl'
2
+ require 'crack'
3
+ require 'crack/json'
4
+ require 'net/http'
5
+ require 'cgi'
6
+
7
+ module Tweetable
8
+ class URL
9
+ LONG_URL_API_URL = 'http://www.longurlplease.com/api/v1.1'
10
+ LONG_URL_TIMEOUT = 5
11
+
12
+ def self.headers
13
+ { "Content-Type" => 'application/json' }
14
+ end
15
+
16
+ # the api can handle multiple url queries and response, but this does just 1
17
+ def self.lookup_long_url(search)
18
+ url = URI.parse(LONG_URL_API_URL)
19
+ url.query = "q=#{CGI.escape(search)}"
20
+
21
+ http = Net::HTTP.new(url.host, url.port)
22
+ http.read_timeout = LONG_URL_TIMEOUT
23
+
24
+ long_url = nil
25
+ begin
26
+ json = http.get(url.to_s, headers).body
27
+ urls = Crack::JSON.parse(json)
28
+ long_url = urls.values[0]
29
+ rescue Crack::ParseError
30
+ raise TweetableError.new("Error parsing json trying to get long URL: #{json.to_s}")
31
+ rescue Timeout::Error => e
32
+ raise TweetableError.new("Timeout trying to get long URL: #{e}")
33
+ rescue Exception => e
34
+ raise TweetableError.new("Error trying to get long URL: #{e}")
35
+ end
36
+ long_url.nil? ? search : long_url
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,104 @@
1
+ module Tweetable
2
+ class User < Persistable
3
+ attribute :created_at
4
+ attribute :updated_at
5
+ attribute :screen_name
6
+ attribute :profile_image_url
7
+ attribute :user_id
8
+ attribute :friends_count
9
+ attribute :followers_count
10
+
11
+ index :screen_name
12
+ index :user_id
13
+
14
+ list :messages, Message
15
+ list :friend_messages, Message
16
+
17
+ set :friend_ids
18
+ set :follower_ids
19
+ set :tags
20
+
21
+ def self.create_from_timeline(user)
22
+ u = User.find_or_create(:screen_name, user.screen_name.downcase)
23
+ u.update(
24
+ :user_id => user[:id],
25
+ :profile_image_url => user.profile_image_url,
26
+ :followers_count => user.followers_count,
27
+ :friends_count => user.friends_count)
28
+ u
29
+ end
30
+
31
+ def update_all(force = false)
32
+ return unless needs_update?(force)
33
+ update_info if self.config[:include_on_update].include?(:info)
34
+ update_friend_ids if self.config[:include_on_update].include?(:friend_ids)
35
+ update_follower_ids if self.config[:include_on_update].include?(:follower_ids)
36
+ update_friend_messages if self.config[:include_on_update].include?(:friend_messages)
37
+ self.update(:updated_at => Time.now.utc.to_s)
38
+ self.config[:include_on_update].include?(:messages) ? update_messages : [] # return newly found messages
39
+ end
40
+
41
+ def update_info
42
+ uid = self.user_id.blank? ? self.screen_name : self.user_id
43
+ info = self.client.user(uid)
44
+
45
+ self.user_id = info[:id]
46
+ self.screen_name = info[:screen_name].downcase
47
+ self.profile_image_url = info[:profile_image_url]
48
+ self.friends_count = info[:friends_count]
49
+ self.followers_count = info[:followers_count]
50
+ self.save
51
+ end
52
+
53
+ def update_friend_ids
54
+ fids = self.client.friend_ids(:screen_name => self.screen_name, :page => 1) # limit to 5000 friend ids
55
+ fids.each{|fid| self.friend_ids << fid}
56
+ end
57
+
58
+ def update_follower_ids
59
+ fids = self.client.follower_ids(:screen_name => self.screen_name, :page => 1) # limit to 5000 friend ids
60
+ fids.each{|fid| self.follower_ids << fid}
61
+ end
62
+
63
+ def update_messages(options = {})
64
+ most_recent_message = self.messages.first(:order => 'DESC', :by => :message_id)
65
+ options.merge!(:count => self.config[:max_message_count], :screen_name => self.screen_name)
66
+ options[:since_id] = most_recent_message.message_id if most_recent_message
67
+
68
+ timeline = self.client.user_timeline(options)
69
+ build_messages(timeline, self.messages)
70
+ end
71
+
72
+ def update_friend_messages(options = {})
73
+ most_recent_message = self.friend_messages.first(:order => 'DESC', :by => :message_id)
74
+ options.merge!(:count => self.config[:max_message_count], :screen_name => self.screen_name)
75
+ options[:since_id] = most_recent_message.message_id if most_recent_message
76
+
77
+ timeline = self.client.friends_timeline(options)
78
+ build_messages(timeline, self.friend_messages, :create_user => true)
79
+ end
80
+
81
+ def twitter_link
82
+ "http://twitter.com/#{screen_name}"
83
+ end
84
+
85
+ private
86
+ def build_messages(timeline, collection, options = {})
87
+ new_messages = []
88
+
89
+ timeline.each do |message|
90
+ m = Message.create_from_timeline(message, options[:create_user])
91
+ next if !m.valid?
92
+ collection << m
93
+ new_messages << m
94
+ end
95
+
96
+ new_messages # return just the newly created messages
97
+ end
98
+
99
+ def validate
100
+ super
101
+ assert_unique :screen_name
102
+ end
103
+ end
104
+ end
data/lib/tweetable.rb ADDED
@@ -0,0 +1,82 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'logging'
5
+ require 'twitter'
6
+
7
+ module Tweetable
8
+ DEFAULT_CONFIG = {
9
+ :max_message_count => 10,
10
+ :include_on_update => [:info, :friend_ids, :follower_ids, :messages], # skipping :friend_messages
11
+ :update_delay => 60*10
12
+ }
13
+
14
+ # Generic exception class.
15
+ class TweetableError < StandardError
16
+ end
17
+
18
+ class TweetableAuthError < StandardError
19
+ end
20
+
21
+ # Raised when there is a temporary problem like Twitter::Unavailable
22
+ class TemporaryPullFromQueueError < TweetableError
23
+ end
24
+
25
+ # Raised when there is a problem pulling from the queue
26
+ class PullFromQueueError < TweetableError
27
+ end
28
+
29
+ autoload :Persistable, File.join(File.dirname(__FILE__), *%w[tweetable persistable.rb])
30
+ autoload :TwitterClient, File.join(File.dirname(__FILE__), *%w[tweetable twitter_client.rb])
31
+ autoload :TwitterStreamingClient, File.join(File.dirname(__FILE__), *%w[tweetable twitter_streaming_client.rb])
32
+ autoload :Link, File.join(File.dirname(__FILE__), *%w[tweetable link.rb])
33
+ autoload :Photo, File.join(File.dirname(__FILE__), *%w[tweetable photo.rb])
34
+ autoload :Message, File.join(File.dirname(__FILE__), *%w[tweetable message.rb])
35
+ autoload :Search, File.join(File.dirname(__FILE__), *%w[tweetable search.rb])
36
+ autoload :User, File.join(File.dirname(__FILE__), *%w[tweetable user.rb])
37
+ autoload :Collection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
38
+ autoload :UserCollection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
39
+ autoload :MessageCollection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
40
+ autoload :SearchCollection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
41
+ autoload :LinkCollection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
42
+ autoload :Authorization, File.join(File.dirname(__FILE__), *%w[tweetable authorization.rb])
43
+ autoload :URL, File.join(File.dirname(__FILE__), *%w[tweetable url.rb])
44
+
45
+ def client
46
+ Thread.current[:twitter_client] ||= options.nil? ? TwitterClient.new.login(*credentials) : TwitterClient.new.authorize(*options)
47
+ end
48
+
49
+ def streaming_client
50
+ Thread.current[:twitter_streaming_client] ||= TwitterStreamingClient.new.authorize(*options)
51
+ end
52
+
53
+ def login(*credentials)
54
+ @credentials = credentials
55
+ end
56
+
57
+ def authorize(*options)
58
+ @options = options
59
+ end
60
+
61
+ def options
62
+ @options
63
+ end
64
+
65
+ def config(config = nil)
66
+ @config ||= DEFAULT_CONFIG
67
+ @config.merge!(config) unless config.nil?
68
+ @config
69
+ end
70
+
71
+ def credentials
72
+ @credentials
73
+ end
74
+
75
+ def log
76
+ @log = Logging.logger(STDOUT)
77
+ @log.level = :debug
78
+ @log
79
+ end
80
+
81
+ module_function :client, :options, :authorize, :login, :credentials, :log, :config
82
+ end
Binary file
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/tweetable.rb'}"
9
+ puts "Loading tweetable gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,55 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe Tweetable::Collection, 'when building lists' do
4
+ before do
5
+ RedisSpecHelper.reset
6
+ end
7
+
8
+ it "should dissallow collections without a name" do
9
+ Tweetable::Collection.create.should_not be_valid
10
+ end
11
+
12
+ it "should build new MessageCollection" do
13
+ Tweetable::MessageCollection.new.should_not be_nil
14
+ end
15
+
16
+ it "should build new UserCollection" do
17
+ Tweetable::UserCollection.new.should_not be_nil
18
+ end
19
+
20
+ it "should build new SearchCollection" do
21
+ Tweetable::SearchCollection.new.should_not be_nil
22
+ end
23
+
24
+ it "should find or create a new SearchCollection" do
25
+ Tweetable::SearchCollection.find_or_create(:name, 'some-name').should be_instance_of Tweetable::SearchCollection
26
+ end
27
+
28
+ it "should find an existing collection" do
29
+ Tweetable::SearchCollection.create(:name => 'some-name')
30
+ Tweetable::SearchCollection.find(:name => 'some-name').first.should be_instance_of Tweetable::SearchCollection
31
+ end
32
+ end
33
+
34
+ describe Tweetable::Collection, 'when building sets' do
35
+ before do
36
+ RedisSpecHelper.reset
37
+ end
38
+
39
+ it "should add user to user set" do
40
+ collection = Tweetable::UserCollection.find_or_create(:name, 'some-name')
41
+ collection.user_set.add(Tweetable::User.create(:screen_name => 'this'))
42
+ collection.user_set.size.should == 1
43
+ end
44
+
45
+ it "should only add unique users to set" do
46
+ collection = Tweetable::UserCollection.find_or_create(:name, 'some-name')
47
+ user = Tweetable::User.create(:screen_name => 'this', :created_at => Time.now.utc.to_s)
48
+ user2 = Tweetable::User.create(:screen_name => 'this2', :created_at => Time.now.utc.to_s)
49
+ collection.user_set.add(user)
50
+ collection.user_set.add(user2)
51
+ collection.user_set.add(user)
52
+ collection.user_set.size.should == 2
53
+ end
54
+
55
+ end
@@ -0,0 +1 @@
1
+ []
@@ -0,0 +1 @@
1
+ {"profile_background_image_url":"http://s.twimg.com/a/1274144130/images/themes/theme12/bg.gif","profile_link_color":"FF0000","url":"http://wagglelabs.com","description":"Haha made ya read!","status":{"contributors":null,"coordinates":null,"in_reply_to_screen_name":null,"geo":null,"favorited":false,"truncated":false,"in_reply_to_status_id":null,"source":"<a href=\"http://twitterrific.com\" rel=\"nofollow\">Twitterrific</a>","in_reply_to_user_id":null,"created_at":"Sat May 29 03:21:34 +0000 2010","id":14951172829,"place":null,"text":"Watering mytwee !"},"profile_background_tile":false,"profile_sidebar_fill_color":"FFF7CC","location":"Capitol Hill, Washington","statuses_count":262,"notifications":null,"profile_image_url":"http://a3.twimg.com/profile_images/74792235/screen-capture-2_normal.png","profile_sidebar_border_color":"F2E195","followers_count":233,"contributors_enabled":false,"lang":"en","screen_name":"Flippyhead","friends_count":114,"favourites_count":2,"profile_background_color":"BADFCD","protected":false,"following":null,"geo_enabled":true,"time_zone":"Pacific Time (US & Canada)","created_at":"Wed Feb 20 04:05:51 +0000 2008","name":"Peter T. Brown","profile_text_color":"0C3E53","id":13705052,"verified":false,"utc_offset":-28800}
@@ -0,0 +1 @@
1
+ [103136961,12767492,38488854,43330332,117481827,114770454,87265151,7315022,14470427,15661935,118054255,116260600,58822253,31340126,17296974,32503163,108098467,60002553,79313634,20254889,82887533,12101862,39617233,15878021,95752657,97296470,22343218,94655742,91197034,15140257,14305034,87716554,87618379,85689666,56425680,16607043,1699751,45283949,3452941,84318041,82091551,16753165,59543723,36585916,79669240,78114832,15759497,32241975,76542156,16337900,14814522,16598422,14166535,64881807,46293477,41241684,17724900,40162052,34364391,25577085,31231869,67645971,19942432,46725587,62416583,3778581,2856031,16030970,15443687,16987565,33893844,43183984,24562033,15665281,11334,17304123,20104538,16388638,17663086,53480097,16230239,58598857,46236831,58579307,58587952,24718118,23096363,18348762,25846278,23047941,19990886,19159761,13656362,2656,5702262,15283419,51553499,17629947,18229081,25030269,16004418,17413507,46757793,41849734,18457047,2654,809512,54078161,15575798,46715253,21538391,14446704,7597362,14762678,17093876,32327090,43418805,25539943,24018995,9022272,38533218,20123484,31560075,27731367,22996821,20351687,30688103,44668855,17813503,16438680,18041530,21681207,29260057,44681603,43753948,43012556,16072309,17627869,23178979,20395561,51585617,15399031,15618016,18285296,29571801,14838820,14137771,24251591,18584722,19207519,19089507,16460227,17089852,14352556,49594418,14385806,10475492,49239945,23456819,15103961,15522944,47891060,1612021,37533608,42924484,14207966,41479415,26565471,37879230,983011,33337061,36267489,17181629,33389895,22900167,34986068,34904138,34686817,34423762,18811351,32664150,32649092,32630299,32607874,14069658,32235388,32227031,32222310,32216021,32211757,32204651,31294847,31237665,18810879,24166272,20255102,6726182,22071150,6140,8938362,16820481,18973843,24576385,16100658,21701416,1442541,19121958,22966270,15069767,17158244,17218814,21015551,780886,20402566,15555883,1173281,15841044,15552710,3093981,802799,19239032,14334195,19943055,10395332,2078,15909918,13357,3734051,3567281]
@@ -0,0 +1 @@
1
+ [17333183,14305100,12685,3452941,60493684,21547828,1442541,14305034,20608910,36082429,3778581,19035252,30495974,14032852,9580822,21066103,21944280,18789983,6170,25577085,13445732,14677720,9066762,3302921,1192,16004418,38975663,21159411,14278978,9420652,5832712,14166535,17242834,18457047,3244801,2729061,16393990,14939200,17304123,13895242,16316680,1235521,13035,11907952,24726388,7255652,10168952,15629200,7787522,14805814,16669075,30313925,35270768,51886564,5493,11263102,48381958,12691112,19159761,53256107,8071082,1916151,23832616,11397012,20734538,17093876,7597362,16735575,32241975,27339792,41478837,35395379,17065232,14446704,16129920,639643,12854372,14319981,16753165,24737795,33187261,16793850,15347711,8693202,14589771,22132568,42678765,50089613,20092241,29566363,16072309,17290254,18772339,19121773,30270211,41479415,3093981,17203250,649,14814522,7706092,2029851,43753948,6140,16112339,17663086,15126376,5651,47891060,9504712,15522944,6118342,12111,45384353,6583802,3585151,31136692,20123484,17086635,43012556,46725587,43916629,15618016,13134132,18492538,24258295,24251591,30903701,49258589,29327220,30688103,16426042,16055631,7017692,148373,14137771,31825923,15610682,45769183,20104538,8248752,17875633,3370001,44274687,20210157,15741618,25181690,25694758,31231869,17813503,37678947,29260057,14091396,45607634,46293477,13983,18492596,30340010,28286020,24827930,16230239,19089507,17089852,23519997,21361624,23642374,62903,20037216,16115979,29571801,32327090,17627869,14818663,17205623,18584722,23879834,19128879,14782042,14852457,14838820,16120957,18041530,19897797,22996563,21400763,20216202,18091654,41008177,16607043,23178979,18285296,12650182,18152775,20735406,19040380,25369931,31560075,24156110,6839892,21842077,34988327,17368984,20395561,22952101,12258422,24018995,19452490,21630899,22821231,22409250,16460227,18999188,24960841,32684446,9076552,23721204,41439789,6429402,27033107,44681603,22961177,25846278,18999061,21591962,29915826,19207519,19216123,15463366,31226467,19012200,3019081,7378312,19782239,26394794,7076432,19786525,17724900,21994180,15200655,22221963,26704718,17671951,17545756,21891183,17645701,32175337,34991746,49108215,25030269,10944522,16438680,22966270,16099148,18131862,15399031,14769689,756278,15846932,14470427,14385806,790368,4044361,6025032,5500632,1249191,809512,11334,1173281,9338012,14256894,14326765,14232711,820661,15876737,786818,9022272,16095572,10058162,21538391,19094626,28921689,36132718,1053901,15240977,25032607,50057976,23899200,15685878,16975038,763489,14324794,17464103,20254889,5702262,23456819,15103961,48160844,2185,18673,13357,12428572,14575000,23393638,17870251,22028135,33337061,9676672,586,32216021,32211757,428333,32649092,32664150,14069658,1338321,734493,15552710,97933,6726182,4557111,14170620,24576385,16100658,21701416,780886,8938362,18973843,11132462,15909918,2061681,10395332,14388729,2078,16359539,4644261,15841044,15069767,802799,3567281]