tweetable 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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]