jnunemaker-twitter 0.2.8

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 (64) hide show
  1. data/History.txt +76 -0
  2. data/License.txt +19 -0
  3. data/Manifest.txt +63 -0
  4. data/README.txt +75 -0
  5. data/Rakefile +11 -0
  6. data/bin/twitter +15 -0
  7. data/config/hoe.rb +73 -0
  8. data/config/requirements.rb +17 -0
  9. data/examples/blocks.rb +15 -0
  10. data/examples/direct_messages.rb +26 -0
  11. data/examples/favorites.rb +20 -0
  12. data/examples/friends_followers.rb +25 -0
  13. data/examples/friendships.rb +13 -0
  14. data/examples/location.rb +8 -0
  15. data/examples/replies.rb +26 -0
  16. data/examples/sent_messages.rb +26 -0
  17. data/examples/timeline.rb +33 -0
  18. data/examples/twitter.rb +27 -0
  19. data/examples/verify_credentials.rb +13 -0
  20. data/lib/twitter/base.rb +249 -0
  21. data/lib/twitter/cli/config.rb +9 -0
  22. data/lib/twitter/cli/helpers.rb +46 -0
  23. data/lib/twitter/cli/migrations/20080722194500_create_accounts.rb +13 -0
  24. data/lib/twitter/cli/migrations/20080722194508_create_tweets.rb +16 -0
  25. data/lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb +9 -0
  26. data/lib/twitter/cli/migrations/20080722214606_create_configurations.rb +13 -0
  27. data/lib/twitter/cli/models/account.rb +21 -0
  28. data/lib/twitter/cli/models/configuration.rb +13 -0
  29. data/lib/twitter/cli/models/tweet.rb +20 -0
  30. data/lib/twitter/cli.rb +297 -0
  31. data/lib/twitter/direct_message.rb +22 -0
  32. data/lib/twitter/easy_class_maker.rb +43 -0
  33. data/lib/twitter/rate_limit_status.rb +19 -0
  34. data/lib/twitter/status.rb +22 -0
  35. data/lib/twitter/user.rb +37 -0
  36. data/lib/twitter/version.rb +9 -0
  37. data/lib/twitter.rb +20 -0
  38. data/script/destroy +14 -0
  39. data/script/generate +14 -0
  40. data/script/txt2html +74 -0
  41. data/setup.rb +1585 -0
  42. data/spec/base_spec.rb +109 -0
  43. data/spec/direct_message_spec.rb +35 -0
  44. data/spec/fixtures/followers.xml +706 -0
  45. data/spec/fixtures/friends.xml +609 -0
  46. data/spec/fixtures/friends_for.xml +584 -0
  47. data/spec/fixtures/friends_lite.xml +192 -0
  48. data/spec/fixtures/friends_timeline.xml +66 -0
  49. data/spec/fixtures/public_timeline.xml +148 -0
  50. data/spec/fixtures/rate_limit_status.xml +7 -0
  51. data/spec/fixtures/status.xml +25 -0
  52. data/spec/fixtures/user.xml +38 -0
  53. data/spec/fixtures/user_timeline.xml +465 -0
  54. data/spec/spec.opts +1 -0
  55. data/spec/spec_helper.rb +8 -0
  56. data/spec/status_spec.rb +40 -0
  57. data/spec/user_spec.rb +42 -0
  58. data/tasks/deployment.rake +34 -0
  59. data/tasks/environment.rake +7 -0
  60. data/tasks/website.rake +17 -0
  61. data/twitter.gemspec +23 -0
  62. data/website/css/common.css +47 -0
  63. data/website/index.html +135 -0
  64. metadata +136 -0
@@ -0,0 +1,249 @@
1
+ # This is the base class for the twitter library. It makes all the requests
2
+ # to twitter, parses the xml (using hpricot) and returns ruby objects to play with.
3
+ #
4
+ # For complete documentation on the options, check out the twitter api docs.
5
+ # http://groups.google.com/group/twitter-development-talk/web/api-documentation
6
+ module Twitter
7
+ class Base
8
+ # Twitter's url, duh!
9
+ @@api_url = 'twitter.com'
10
+
11
+ # Initializes the configuration for making requests to twitter
12
+ def initialize(email, password)
13
+ @config, @config[:email], @config[:password] = {}, email, password
14
+ end
15
+
16
+ # Returns an array of statuses for a timeline; Defaults to your friends timeline.
17
+ def timeline(which=:friends, options={})
18
+ raise UnknownTimeline unless [:friends, :public, :user].include?(which)
19
+ auth = which.to_s.include?('public') ? false : true
20
+ statuses(call("#{which}_timeline", :auth => auth, :since => options[:since], :args => parse_options(options)))
21
+ end
22
+
23
+ # Returns an array of users who are in your friends list
24
+ def friends(options={})
25
+ users(call(:friends, {:args => parse_options(options)}))
26
+ end
27
+
28
+ # Returns an array of users who are friends for the id or username passed in
29
+ def friends_for(id, options={})
30
+ friends(options.merge({:id => id}))
31
+ end
32
+
33
+ # Returns an array of users who are following you
34
+ def followers(options={})
35
+ users(call(:followers, {:args => parse_options(options)}))
36
+ end
37
+
38
+ def followers_for(id, options={})
39
+ followers(options.merge({:id => id}))
40
+ end
41
+
42
+ # Returns a single status for a given id
43
+ def status(id)
44
+ statuses(call("show/#{id}")).first
45
+ end
46
+
47
+ # returns all the profile information and the last status for a user
48
+ def user(id_or_screenname)
49
+ users(request("users/show/#{id_or_screenname}.xml", :auth => true)).first
50
+ end
51
+
52
+ # Returns an array of statuses that are replies
53
+ def replies(options={})
54
+ statuses(call(:replies, :since => options[:since], :args => parse_options(options)))
55
+ end
56
+
57
+ # Destroys a status by id
58
+ def destroy(id)
59
+ call("destroy/#{id}")
60
+ end
61
+
62
+ def rate_limit_status
63
+ RateLimitStatus.new_from_xml request("account/rate_limit_status.xml", :auth => true)
64
+ end
65
+
66
+ # waiting for twitter to correctly implement this in the api as it is documented
67
+ def featured
68
+ users(call(:featured))
69
+ end
70
+
71
+ # Returns an array of all the direct messages for the authenticated user
72
+ def direct_messages(options={})
73
+ doc = request(build_path('direct_messages.xml', parse_options(options)), {:auth => true, :since => options[:since]})
74
+ (doc/:direct_message).inject([]) { |dms, dm| dms << DirectMessage.new_from_xml(dm); dms }
75
+ end
76
+ alias :received_messages :direct_messages
77
+
78
+ # Returns direct messages sent by auth user
79
+ def sent_messages(options={})
80
+ doc = request(build_path('direct_messages/sent.xml', parse_options(options)), {:auth => true, :since => options[:since]})
81
+ (doc/:direct_message).inject([]) { |dms, dm| dms << DirectMessage.new_from_xml(dm); dms }
82
+ end
83
+
84
+ # destroys a give direct message by id if the auth user is a recipient
85
+ def destroy_direct_message(id)
86
+ request("direct_messages/destroy/#{id}.xml", :auth => true)
87
+ end
88
+
89
+ # Sends a direct message <code>text</code> to <code>user</code>
90
+ def d(user, text)
91
+ url = URI.parse("http://#{@@api_url}/direct_messages/new.xml")
92
+ req = Net::HTTP::Post.new(url.path)
93
+ req.basic_auth(@config[:email], @config[:password])
94
+ req.set_form_data({'text' => text, 'user' => user})
95
+ response = Net::HTTP.new(url.host, url.port).start { |http| http.request(req) }
96
+ DirectMessage.new_from_xml(parse(response.body).at('direct_message'))
97
+ end
98
+
99
+ # Befriends id_or_screenname for the auth user
100
+ def create_friendship(id_or_screenname)
101
+ users(request("friendships/create/#{id_or_screenname}.xml", :auth => true)).first
102
+ end
103
+
104
+ # Defriends id_or_screenname for the auth user
105
+ def destroy_friendship(id_or_screenname)
106
+ users(request("friendships/destroy/#{id_or_screenname}.xml", :auth => true)).first
107
+ end
108
+
109
+ # Returns true if friendship exists, false if it doesn't.
110
+ def friendship_exists?(user_a, user_b)
111
+ doc = request(build_path("friendships/exists.xml", {:user_a => user_a, :user_b => user_b}), :auth => true)
112
+ doc.at('friends').innerHTML == 'true' ? true : false
113
+ end
114
+
115
+ # Updates your location and returns Twitter::User object
116
+ def update_location(location)
117
+ users(request(build_path('account/update_location.xml', {'location' => location}), :auth => true)).first
118
+ end
119
+
120
+ # Updates your deliver device and returns Twitter::User object
121
+ def update_delivery_device(device)
122
+ users(request(build_path('account/update_delivery_device.xml', {'device' => device}), :auth => true)).first
123
+ end
124
+
125
+ # Turns notifications by id_or_screenname on for auth user.
126
+ def follow(id_or_screenname)
127
+ users(request("notifications/follow/#{id_or_screenname}.xml", :auth => true)).first
128
+ end
129
+
130
+ # Turns notifications by id_or_screenname off for auth user.
131
+ def leave(id_or_screenname)
132
+ users(request("notifications/leave/#{id_or_screenname}.xml", :auth => true)).first
133
+ end
134
+
135
+ # Returns the most recent favorite statuses for the autenticating user
136
+ def favorites(options={})
137
+ statuses(request(build_path('favorites.xml', parse_options(options)), :auth => true))
138
+ end
139
+
140
+ # Favorites the status specified by id for the auth user
141
+ def create_favorite(id)
142
+ statuses(request("favorites/create/#{id}.xml", :auth => true)).first
143
+ end
144
+
145
+ # Un-favorites the status specified by id for the auth user
146
+ def destroy_favorite(id)
147
+ statuses(request("favorites/destroy/#{id}.xml", :auth => true)).first
148
+ end
149
+
150
+ # Blocks the user specified by id for the auth user
151
+ def block(id)
152
+ users(request("blocks/create/#{id}.xml", :auth => true)).first
153
+ end
154
+
155
+ # Unblocks the user specified by id for the auth user
156
+ def unblock(id)
157
+ users(request("blocks/destroy/#{id}.xml", :auth => true)).first
158
+ end
159
+
160
+ # Posts a new update to twitter for auth user.
161
+ def post(status, options={})
162
+ form_data = {'status' => status}
163
+ form_data.merge({'source' => options[:source]}) if options[:source]
164
+ url = URI.parse("http://#{@@api_url}/statuses/update.xml")
165
+ req = Net::HTTP::Post.new(url.path)
166
+ req.basic_auth(@config[:email], @config[:password])
167
+ req.set_form_data(form_data)
168
+ response = Net::HTTP.new(url.host, url.port).start { |http| http.request(req) }
169
+ Status.new_from_xml(parse(response.body).at('status'))
170
+ end
171
+ alias :update :post
172
+
173
+ # Verifies the credentials for the auth user.
174
+ # raises Twitter::CantConnect on failure.
175
+ def verify_credentials
176
+ request('account/verify_credentials', :auth => true)
177
+ end
178
+
179
+ private
180
+ # Converts an hpricot doc to an array of statuses
181
+ def statuses(doc)
182
+ (doc/:status).inject([]) { |statuses, status| statuses << Status.new_from_xml(status); statuses }
183
+ end
184
+
185
+ # Converts an hpricot doc to an array of users
186
+ def users(doc)
187
+ (doc/:user).inject([]) { |users, user| users << User.new_from_xml(user); users }
188
+ end
189
+
190
+ # Calls whatever api method requested that deals with statuses
191
+ #
192
+ # ie: call(:public_timeline, :auth => false)
193
+ def call(method, options={})
194
+ options.reverse_merge!({ :auth => true, :args => {} })
195
+ # Following line needed as lite=false doesn't work in the API: http://tinyurl.com/yo3h5d
196
+ options[:args].delete(:lite) unless options[:args][:lite]
197
+ args = options.delete(:args)
198
+ request(build_path("statuses/#{method.to_s}.xml", args), options)
199
+ end
200
+
201
+ # Makes a request to twitter.
202
+ def request(path, options={})
203
+ options.reverse_merge!({:headers => { "User-Agent" => @config[:email] }})
204
+ unless options[:since].blank?
205
+ since = options[:since].kind_of?(Date) ? options[:since].strftime('%a, %d-%b-%y %T GMT') : options[:since].to_s
206
+ options[:headers]["If-Modified-Since"] = since
207
+ end
208
+
209
+ begin
210
+ response = Net::HTTP.start(@@api_url, 80) do |http|
211
+ req = Net::HTTP::Get.new('/' + path, options[:headers])
212
+ req.basic_auth(@config[:email], @config[:password]) if options[:auth]
213
+ http.request(req)
214
+ end
215
+ rescue => error
216
+ raise CantConnect, error.message
217
+ end
218
+
219
+ if %w[200 304].include?(response.code)
220
+ response = parse(response.body)
221
+ raise RateExceeded if (response/:hash/:error).text =~ /Rate limit exceeded/
222
+ response
223
+ elsif response.code == '503'
224
+ raise Unavailable, response.message
225
+ elsif response.code == '401'
226
+ raise CantConnect, 'Authentication failed. Check your username and password'
227
+ else
228
+ raise CantConnect, "Twitter is returning a #{response.code}: #{response.message}"
229
+ end
230
+ end
231
+
232
+ # Given a path and a hash, build a full path with the hash turned into a query string
233
+ def build_path(path, options)
234
+ path += "?#{options.to_query}" unless options.blank?
235
+ path
236
+ end
237
+
238
+ # Tries to get all the options in the correct format before making the request
239
+ def parse_options(options)
240
+ options[:since] = options[:since].kind_of?(Date) ? options[:since].strftime('%a, %d-%b-%y %T GMT') : options[:since].to_s if options[:since]
241
+ options
242
+ end
243
+
244
+ # Converts a string response into an Hpricot xml element.
245
+ def parse(response)
246
+ Hpricot.XML(response)
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,9 @@
1
+ module Twitter
2
+ module CLI
3
+ Config = {
4
+ :adapter => 'sqlite3',
5
+ :database => File.join(ENV['HOME'], '.twitter.db'),
6
+ :timeout => 5000
7
+ }
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module Twitter
2
+ module CLI
3
+ module Helpers
4
+ def base(username=current_account.username, password=current_account.password)
5
+ @base ||= Twitter::Base.new(username, password)
6
+ end
7
+
8
+ def current_account
9
+ @current_account ||= Account.active
10
+ exit('No current account.') if @current_account.blank?
11
+ @current_account
12
+ end
13
+
14
+ def do_work(&block)
15
+ connect
16
+ begin
17
+ block.call
18
+ rescue Twitter::RateExceeded
19
+ say("Twitter says you've been making too many requests. Wait for a bit and try again.")
20
+ rescue Twitter::Unavailable
21
+ say("Twitter is unavailable right now. Try again later.")
22
+ rescue Twitter::CantConnect => msg
23
+ say("Can't connect to twitter because: #{msg}")
24
+ end
25
+ end
26
+
27
+ def connect
28
+ ActiveRecord::Base.logger = Logger.new('/tmp/twitter_ar_logger.log')
29
+ ActiveRecord::Base.establish_connection(Twitter::CLI::Config)
30
+ ActiveRecord::Base.connection
31
+ end
32
+
33
+ def migrate
34
+ connect
35
+ ActiveRecord::Migrator.migrate("#{CLI_ROOT}/migrations/")
36
+ end
37
+
38
+ def connect_and_migrate
39
+ say('Attempting to establish connection...')
40
+ connect
41
+ say('Connection established...migrating database...')
42
+ migrate
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ class CreateAccounts < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :accounts do |t|
4
+ t.string :username, :password
5
+ t.boolean :current
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :accounts
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ class CreateTweets < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tweets do |t|
4
+ t.datetime :occurred_at
5
+ t.boolean :truncated, :favorited, :user_protected, :default => false
6
+ t.integer :twitter_id, :user_id, :in_reply_to_status_id, :in_reply_to_user_id, :user_followers_count
7
+ t.text :body
8
+ t.string :source, :user_name, :user_screen_name, :user_location, :user_description, :user_profile_image_url, :user_url
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :tweets
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ class AddAccountIdToTweets < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :tweets, :account_id, :integer
4
+ end
5
+
6
+ def self.down
7
+ remove_column :tweets, :account_id
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ class CreateConfigurations < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :configurations do |t|
4
+ t.string :key
5
+ t.text :data
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :accounts
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ class Account < ActiveRecord::Base
2
+ named_scope :current, :conditions => {:current => true}
3
+
4
+ has_many :tweets, :dependent => :destroy
5
+
6
+ def self.active
7
+ current.first
8
+ end
9
+
10
+ def self.set_current(account_or_id)
11
+ account = account_or_id.is_a?(Account) ? account_or_id : find(account_or_id)
12
+ account.update_attribute :current, true
13
+ Account.update_all "current = 0", "id != #{account.id}"
14
+ account
15
+ end
16
+
17
+ def to_s
18
+ "#{current? ? '*' : ' '} #{username}"
19
+ end
20
+ alias to_str to_s
21
+ end
@@ -0,0 +1,13 @@
1
+ class Configuration < ActiveRecord::Base
2
+ serialize :data
3
+
4
+ def self.[](key)
5
+ key = find_by_key(key.to_s)
6
+ key.nil? ? nil : key.data
7
+ end
8
+
9
+ def self.[]=(key, data)
10
+ c = find_or_create_by_key(key.to_s)
11
+ c.update_attribute :data, data
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ class Tweet < ActiveRecord::Base
2
+ belongs_to :account
3
+
4
+ def self.create_from_tweet(account, s)
5
+ tweet = account.tweets.find_or_initialize_by_twitter_id(s.id)
6
+ tweet.body = s.text
7
+ tweet.occurred_at = s.created_at
8
+
9
+ %w[truncated favorited in_reply_to_status_id in_reply_to_user_id source].each do |m|
10
+ tweet.send("#{m}=", s.send(m))
11
+ end
12
+
13
+ %w[id followers_count name screen_name location description
14
+ profile_image_url url protected].each do |m|
15
+ tweet.send("user_#{m}=", s.user.send(m))
16
+ end
17
+ tweet.save!
18
+ tweet
19
+ end
20
+ end