twitter-jruby 0.9.5.2010050701

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 (88) hide show
  1. data/History +282 -0
  2. data/License +20 -0
  3. data/Notes +33 -0
  4. data/README.rdoc +27 -0
  5. data/Rakefile +39 -0
  6. data/VERSION.yml +5 -0
  7. data/examples/connect.rb +30 -0
  8. data/examples/friendship_existance.rb +13 -0
  9. data/examples/helpers/config_store.rb +38 -0
  10. data/examples/httpauth.rb +11 -0
  11. data/examples/ids.rb +13 -0
  12. data/examples/lists.rb +11 -0
  13. data/examples/oauth.rb +27 -0
  14. data/examples/search.rb +15 -0
  15. data/examples/timeline.rb +19 -0
  16. data/examples/tumblr.rb +9 -0
  17. data/examples/unauthorized.rb +16 -0
  18. data/examples/update.rb +11 -0
  19. data/examples/user.rb +5 -0
  20. data/lib/twitter.rb +146 -0
  21. data/lib/twitter/base.rb +390 -0
  22. data/lib/twitter/httpauth.rb +39 -0
  23. data/lib/twitter/local_trends.rb +15 -0
  24. data/lib/twitter/oauth.rb +58 -0
  25. data/lib/twitter/request.rb +71 -0
  26. data/lib/twitter/search.rb +159 -0
  27. data/lib/twitter/trends.rb +41 -0
  28. data/test/fixtures/blocking.json +1632 -0
  29. data/test/fixtures/firehose.json +1 -0
  30. data/test/fixtures/follower_ids.json +1 -0
  31. data/test/fixtures/followers.json +1 -0
  32. data/test/fixtures/friend_ids.json +1 -0
  33. data/test/fixtures/friends_timeline.json +1 -0
  34. data/test/fixtures/friendship.json +1 -0
  35. data/test/fixtures/friendship_exists.json +1 -0
  36. data/test/fixtures/home_timeline.json +1 -0
  37. data/test/fixtures/ids.json +1 -0
  38. data/test/fixtures/list.json +1 -0
  39. data/test/fixtures/list_statuses.json +1 -0
  40. data/test/fixtures/list_statuses_1_1.json +1 -0
  41. data/test/fixtures/list_statuses_2_1.json +1 -0
  42. data/test/fixtures/list_subscriptions.json +1 -0
  43. data/test/fixtures/list_users.json +1 -0
  44. data/test/fixtures/lists.json +1 -0
  45. data/test/fixtures/memberships.json +1 -0
  46. data/test/fixtures/mentions.json +1 -0
  47. data/test/fixtures/not_found.json +1 -0
  48. data/test/fixtures/people_search.json +39 -0
  49. data/test/fixtures/rate_limit_exceeded.json +1 -0
  50. data/test/fixtures/report_spam.json +41 -0
  51. data/test/fixtures/retweet.json +1 -0
  52. data/test/fixtures/retweeted_by_me.json +1 -0
  53. data/test/fixtures/retweeted_to_me.json +1 -0
  54. data/test/fixtures/retweeters_of_tweet.json +166 -0
  55. data/test/fixtures/retweets.json +1 -0
  56. data/test/fixtures/retweets_of_me.json +1 -0
  57. data/test/fixtures/sample-image.png +0 -0
  58. data/test/fixtures/saved_search.json +7 -0
  59. data/test/fixtures/saved_searches.json +16 -0
  60. data/test/fixtures/search.json +1 -0
  61. data/test/fixtures/search_from_jnunemaker.json +1 -0
  62. data/test/fixtures/status.json +1 -0
  63. data/test/fixtures/status_show.json +1 -0
  64. data/test/fixtures/trends_available.json +253 -0
  65. data/test/fixtures/trends_current.json +1 -0
  66. data/test/fixtures/trends_current_exclude.json +1 -0
  67. data/test/fixtures/trends_daily.json +1925 -0
  68. data/test/fixtures/trends_daily_date.json +1 -0
  69. data/test/fixtures/trends_daily_exclude.json +1 -0
  70. data/test/fixtures/trends_location.json +57 -0
  71. data/test/fixtures/trends_weekly.json +1 -0
  72. data/test/fixtures/trends_weekly_date.json +1 -0
  73. data/test/fixtures/trends_weekly_exclude.json +1 -0
  74. data/test/fixtures/unauthorized.json +1 -0
  75. data/test/fixtures/update_profile_background_image.json +1 -0
  76. data/test/fixtures/update_profile_image.json +1 -0
  77. data/test/fixtures/user.json +1 -0
  78. data/test/fixtures/user_timeline.json +710 -0
  79. data/test/fixtures/users.json +1 -0
  80. data/test/test_helper.rb +46 -0
  81. data/test/twitter/base_test.rb +426 -0
  82. data/test/twitter/httpauth_test.rb +76 -0
  83. data/test/twitter/oauth_test.rb +108 -0
  84. data/test/twitter/request_test.rb +217 -0
  85. data/test/twitter/search_test.rb +208 -0
  86. data/test/twitter/trends_test.rb +112 -0
  87. data/test/twitter_test.rb +106 -0
  88. metadata +280 -0
@@ -0,0 +1,38 @@
1
+ class ConfigStore
2
+ attr_reader :file
3
+
4
+ def initialize(file)
5
+ @file = file
6
+ end
7
+
8
+ def load
9
+ @config ||= YAML::load(open(file))
10
+ self
11
+ end
12
+
13
+ def [](key)
14
+ load
15
+ @config[key]
16
+ end
17
+
18
+ def []=(key, value)
19
+ @config[key] = value
20
+ end
21
+
22
+ def delete(*keys)
23
+ keys.each { |key| @config.delete(key) }
24
+ save
25
+ self
26
+ end
27
+
28
+ def update(c={})
29
+ @config.merge!(c)
30
+ save
31
+ self
32
+ end
33
+
34
+ def save
35
+ File.open(file, 'w') { |f| f.write(YAML.dump(@config)) }
36
+ self
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require File.join(File.dirname(__FILE__), 'helpers', 'config_store')
3
+ require 'pp'
4
+
5
+ config = ConfigStore.new("#{ENV['HOME']}/.twitter")
6
+
7
+ httpauth = Twitter::HTTPAuth.new(config['email'], config['password'])
8
+ base = Twitter::Base.new(httpauth)
9
+
10
+ pp base.user_timeline
11
+ pp base.verify_credentials
data/examples/ids.rb ADDED
@@ -0,0 +1,13 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require File.join(File.dirname(__FILE__), 'helpers', 'config_store')
3
+ require 'pp'
4
+
5
+ config = ConfigStore.new("#{ENV['HOME']}/.twitter")
6
+
7
+ oauth = Twitter::OAuth.new(config['token'], config['secret'])
8
+ oauth.authorize_from_access(config['atoken'], config['asecret'])
9
+
10
+ client = Twitter::Base.new(oauth)
11
+
12
+ puts client.friend_ids
13
+ puts client.follower_ids
data/examples/lists.rb ADDED
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require File.join(File.dirname(__FILE__), 'helpers', 'config_store')
3
+ require 'pp'
4
+
5
+ config = ConfigStore.new("#{ENV['HOME']}/.twitter")
6
+
7
+ httpauth = Twitter::HTTPAuth.new(config['email'], config['password'])
8
+ base = Twitter::Base.new(httpauth)
9
+
10
+ pp base.lists('pengwynn')
11
+ pp base.list_members('pengwynn', 'rubyists')
data/examples/oauth.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'pp'
2
+ require 'pathname'
3
+ dir = Pathname(__FILE__).dirname.expand_path
4
+ require (dir + '..' + 'lib' + 'twitter').expand_path
5
+ require dir + 'helpers' + 'config_store'
6
+
7
+ config = ConfigStore.new("#{ENV['HOME']}/.twitter")
8
+ oauth = Twitter::OAuth.new(config['token'], config['secret'])
9
+ rtoken = oauth.request_token.token
10
+ rsecret = oauth.request_token.secret
11
+
12
+ puts "> redirecting you to twitter to authorize..."
13
+ %x(open #{oauth.request_token.authorize_url})
14
+
15
+ print "> what was the PIN twitter provided you with? "
16
+ pin = gets.chomp
17
+
18
+ begin
19
+ oauth.authorize_from_request(rtoken, rsecret, pin)
20
+
21
+ twitter = Twitter::Base.new(oauth)
22
+ twitter.user_timeline.each do |tweet|
23
+ puts "#{tweet.user.screen_name}: #{tweet.text}"
24
+ end
25
+ rescue OAuth::Unauthorized
26
+ puts "> FAIL!"
27
+ end
@@ -0,0 +1,15 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require 'pp'
3
+
4
+ search = Twitter::Search.new.from('jnunemaker')
5
+
6
+ puts '*'*50, 'First Run', '*'*50
7
+ search.each { |result| pp result }
8
+
9
+ puts '*'*50, 'Second Run', '*'*50
10
+ search.each { |result| pp result }
11
+
12
+ puts '*'*50, 'Parameter Check', '*'*50
13
+ pp Twitter::Search.new('#austineats').fetch().results.first
14
+ pp Twitter::Search.new('#austineats').page(2).fetch().results.first
15
+ pp Twitter::Search.new('#austineats').since(1412737343).fetch().results.first
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require File.join(File.dirname(__FILE__), 'helpers', 'config_store')
3
+ require 'pp'
4
+
5
+ config = ConfigStore.new("#{ENV['HOME']}/.twitter")
6
+
7
+ oauth = Twitter::OAuth.new(config['token'], config['secret'])
8
+ oauth.authorize_from_access(config['atoken'], config['asecret'])
9
+
10
+ client = Twitter::Base.new(oauth)
11
+
12
+ pp client.friends_timeline
13
+ puts '*'*50
14
+
15
+ pp client.user_timeline
16
+ puts '*'*50
17
+
18
+ pp client.replies
19
+ puts '*'*50
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require 'pp'
3
+
4
+
5
+ httpauth = Twitter::HTTPAuth.new('email', 'password', :api_endpoint => 'tumblr.com')
6
+ base = Twitter::Base.new(httpauth)
7
+
8
+ pp base.user_timeline
9
+ pp base.verify_credentials
@@ -0,0 +1,16 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require 'pp'
3
+
4
+ puts 'User', '*'*50
5
+ pp Twitter.user('jnunemaker')
6
+ pp Twitter.user('snitch_test')
7
+
8
+ puts 'Status', '*'*50
9
+ pp Twitter.status(1533815199)
10
+
11
+ puts 'Friend Ids', '*'*50
12
+ pp Twitter.friend_ids('jnunemaker')
13
+
14
+ puts 'Follower Ids', '*'*50
15
+ pp Twitter.follower_ids('jnunemaker')
16
+
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require File.join(File.dirname(__FILE__), 'helpers', 'config_store')
3
+ require 'pp'
4
+
5
+ config = ConfigStore.new("#{ENV['HOME']}/.twitter")
6
+
7
+ oauth = Twitter::OAuth.new(config['token'], config['secret'])
8
+ oauth.authorize_from_access(config['atoken'], config['asecret'])
9
+
10
+ client = Twitter::Base.new(oauth)
11
+ pp client.update('This is an update from the twitter gem')
data/examples/user.rb ADDED
@@ -0,0 +1,5 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
2
+ require 'pp'
3
+
4
+ pp Twitter.user('jnunemaker')
5
+ pp Twitter.user('snitch_test')
data/lib/twitter.rb ADDED
@@ -0,0 +1,146 @@
1
+ require "forwardable"
2
+ require "oauth"
3
+ require "hashie"
4
+ require "httparty"
5
+
6
+ module Twitter
7
+ include HTTParty
8
+ API_VERSION = "1".freeze
9
+ base_uri "api.twitter.com/#{API_VERSION}"
10
+ format :json
11
+
12
+ class TwitterError < StandardError
13
+ attr_reader :data
14
+
15
+ def initialize(data)
16
+ @data = data
17
+ super
18
+ end
19
+ end
20
+
21
+ class RateLimitExceeded < TwitterError; end
22
+ class Unauthorized < TwitterError; end
23
+ class General < TwitterError; end
24
+
25
+ class Unavailable < StandardError; end
26
+ class InformTwitter < StandardError; end
27
+ class NotFound < StandardError; end
28
+
29
+ def self.firehose
30
+ perform_get("/statuses/public_timeline.json")
31
+ end
32
+
33
+ def self.user(id)
34
+ perform_get("/users/show/#{id}.json")
35
+ end
36
+
37
+ def self.status(id)
38
+ perform_get("/statuses/show/#{id}.json")
39
+ end
40
+
41
+ def self.friend_ids(id)
42
+ perform_get("/friends/ids/#{id}.json")
43
+ end
44
+
45
+ def self.follower_ids(id)
46
+ perform_get("/followers/ids/#{id}.json")
47
+ end
48
+
49
+ def self.timeline(id, options={})
50
+ perform_get("/statuses/user_timeline/#{id}.json", :query => options)
51
+ end
52
+
53
+ # :per_page = max number of statues to get at once
54
+ # :page = which page of tweets you wish to get
55
+ def self.list_timeline(list_owner_username, slug, query = {})
56
+ perform_get("/#{list_owner_username}/lists/#{slug}/statuses.json", :query => query)
57
+ end
58
+
59
+ private
60
+
61
+ def self.perform_get(uri, options = {})
62
+ make_friendly(get(uri, options))
63
+ end
64
+
65
+ def self.make_friendly(response)
66
+ raise_errors(response)
67
+ data = parse(response)
68
+ # Don't mash arrays of integers
69
+ if data && data.is_a?(Array) && data.first.is_a?(Integer)
70
+ data
71
+ else
72
+ mash(data)
73
+ end
74
+ end
75
+
76
+ def self.raise_errors(response)
77
+ case response.code.to_i
78
+ when 400
79
+ data = parse(response)
80
+ raise RateLimitExceeded.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
81
+ when 401
82
+ data = parse(response)
83
+ raise Unauthorized.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
84
+ when 403
85
+ data = parse(response)
86
+ raise General.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
87
+ when 404
88
+ raise NotFound, "(#{response.code}): #{response.message}"
89
+ when 500
90
+ raise InformTwitter, "Twitter had an internal error. Please let them know in the group. (#{response.code}): #{response.message}"
91
+ when 502..503
92
+ raise Unavailable, "(#{response.code}): #{response.message}"
93
+ end
94
+ end
95
+
96
+ def self.parse(response)
97
+ JSON.parse(response.body)
98
+ end
99
+
100
+ def self.mash(obj)
101
+ if obj.is_a?(Array)
102
+ obj.map{|item| make_mash_with_consistent_hash(item)}
103
+ elsif obj.is_a?(Hash)
104
+ make_mash_with_consistent_hash(obj)
105
+ else
106
+ obj
107
+ end
108
+ end
109
+
110
+ # Lame workaround for the fact that mash doesn't hash correctly
111
+ def self.make_mash_with_consistent_hash(obj)
112
+ m = Hashie::Mash.new(obj)
113
+ def m.hash
114
+ inspect.hash
115
+ end
116
+ return m
117
+ end
118
+
119
+ end
120
+
121
+ module Hashie
122
+ class Mash
123
+
124
+ # Converts all of the keys to strings, optionally formatting key name
125
+ def rubyify_keys!
126
+ keys.each{|k|
127
+ v = delete(k)
128
+ new_key = k.to_s.underscore
129
+ self[new_key] = v
130
+ v.rubyify_keys! if v.is_a?(Hash)
131
+ v.each{|p| p.rubyify_keys! if p.is_a?(Hash)} if v.is_a?(Array)
132
+ }
133
+ self
134
+ end
135
+
136
+ end
137
+ end
138
+
139
+ directory = File.expand_path(File.dirname(__FILE__))
140
+
141
+ require File.join(directory, "twitter", "oauth")
142
+ require File.join(directory, "twitter", "httpauth")
143
+ require File.join(directory, "twitter", "request")
144
+ require File.join(directory, "twitter", "base")
145
+ require File.join(directory, "twitter", "search")
146
+ require File.join(directory, "twitter", "trends")
@@ -0,0 +1,390 @@
1
+ module Twitter
2
+ class Base
3
+ extend Forwardable
4
+
5
+ def_delegators :client, :get, :post, :put, :delete
6
+
7
+ attr_reader :client
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ # Options: since_id, max_id, count, page
14
+ def home_timeline(query={})
15
+ perform_get("/#{API_VERSION}/statuses/home_timeline.json", :query => query)
16
+ end
17
+
18
+ # Options: since_id, max_id, count, page, since
19
+ def friends_timeline(query={})
20
+ perform_get("/#{API_VERSION}/statuses/friends_timeline.json", :query => query)
21
+ end
22
+
23
+ # Options: id, user_id, screen_name, since_id, max_id, page, since, count
24
+ def user_timeline(query={})
25
+ perform_get("/#{API_VERSION}/statuses/user_timeline.json", :query => query)
26
+ end
27
+
28
+ def status(id)
29
+ perform_get("/#{API_VERSION}/statuses/show/#{id}.json")
30
+ end
31
+
32
+ # Options: count
33
+ def retweets(id, query={})
34
+ perform_get("/#{API_VERSION}/statuses/retweets/#{id}.json", :query => query)
35
+ end
36
+
37
+ # Options: in_reply_to_status_id
38
+ def update(status, query={})
39
+ perform_post("/#{API_VERSION}/statuses/update.json", :body => {:status => status}.merge(query))
40
+ end
41
+
42
+ # DEPRECATED: Use #mentions instead
43
+ #
44
+ # Options: since_id, max_id, since, page
45
+ def replies(query={})
46
+ warn("DEPRECATED: #replies is deprecated by Twitter; use #mentions instead")
47
+ perform_get("/#{API_VERSION}/statuses/replies.json", :query => query)
48
+ end
49
+
50
+ # Options: since_id, max_id, count, page
51
+ def mentions(query={})
52
+ perform_get("/#{API_VERSION}/statuses/mentions.json", :query => query)
53
+ end
54
+
55
+ # Options: since_id, max_id, count, page
56
+ def retweeted_by_me(query={})
57
+ perform_get("/#{API_VERSION}/statuses/retweeted_by_me.json", :query => query)
58
+ end
59
+
60
+ # Options: since_id, max_id, count, page
61
+ def retweeted_to_me(query={})
62
+ perform_get("/#{API_VERSION}/statuses/retweeted_to_me.json", :query => query)
63
+ end
64
+
65
+ # Options: since_id, max_id, count, page
66
+ def retweets_of_me(query={})
67
+ perform_get("/#{API_VERSION}/statuses/retweets_of_me.json", :query => query)
68
+ end
69
+
70
+ # options: count, page, ids_only
71
+ def retweeters_of(id, options={})
72
+ ids_only = !!(options.delete(:ids_only))
73
+ perform_get("/#{API_VERSION}/statuses/#{id}/retweeted_by#{"/ids" if ids_only}.json", :query => options)
74
+ end
75
+
76
+ def status_destroy(id)
77
+ perform_post("/#{API_VERSION}/statuses/destroy/#{id}.json")
78
+ end
79
+
80
+ def retweet(id)
81
+ perform_post("/#{API_VERSION}/statuses/retweet/#{id}.json")
82
+ end
83
+
84
+ # Options: id, user_id, screen_name, page
85
+ def friends(query={})
86
+ perform_get("/#{API_VERSION}/statuses/friends.json", :query => query)
87
+ end
88
+
89
+ # Options: id, user_id, screen_name, page
90
+ def followers(query={})
91
+ perform_get("/#{API_VERSION}/statuses/followers.json", :query => query)
92
+ end
93
+
94
+ def user(id, query={})
95
+ perform_get("/#{API_VERSION}/users/show/#{id}.json", :query => query)
96
+ end
97
+
98
+ def users(*ids_or_usernames)
99
+ ids, usernames = [], []
100
+ ids_or_usernames.each do |id_or_username|
101
+ if id_or_username.is_a?(Integer)
102
+ ids << id_or_username
103
+ elsif id_or_username.is_a?(String)
104
+ usernames << id_or_username
105
+ end
106
+ end
107
+ query = {}
108
+ query[:user_id] = ids.join(",") unless ids.empty?
109
+ query[:screen_name] = usernames.join(",") unless usernames.empty?
110
+ perform_get("/#{API_VERSION}/users/lookup.json", :query => query)
111
+ end
112
+
113
+ # Options: page, per_page
114
+ def user_search(q, query={})
115
+ q = URI.escape(q)
116
+ perform_get("/#{API_VERSION}/users/search.json", :query => ({:q => q}.merge(query)))
117
+ end
118
+
119
+ # Options: since, since_id, page
120
+ def direct_messages(query={})
121
+ perform_get("/#{API_VERSION}/direct_messages.json", :query => query)
122
+ end
123
+
124
+ # Options: since, since_id, page
125
+ def direct_messages_sent(query={})
126
+ perform_get("/#{API_VERSION}/direct_messages/sent.json", :query => query)
127
+ end
128
+
129
+ def direct_message_create(user, text)
130
+ perform_post("/#{API_VERSION}/direct_messages/new.json", :body => {:user => user, :text => text})
131
+ end
132
+
133
+ def direct_message_destroy(id)
134
+ perform_post("/#{API_VERSION}/direct_messages/destroy/#{id}.json")
135
+ end
136
+
137
+ def friendship_create(id, follow=false)
138
+ body = {}
139
+ body.merge!(:follow => follow) if follow
140
+ perform_post("/#{API_VERSION}/friendships/create/#{id}.json", :body => body)
141
+ end
142
+
143
+ def friendship_destroy(id)
144
+ perform_post("/#{API_VERSION}/friendships/destroy/#{id}.json")
145
+ end
146
+
147
+ def friendship_exists?(a, b)
148
+ perform_get("/#{API_VERSION}/friendships/exists.json", :query => {:user_a => a, :user_b => b})
149
+ end
150
+
151
+ def friendship_show(query)
152
+ perform_get("/#{API_VERSION}/friendships/show.json", :query => query)
153
+ end
154
+
155
+ # Options: id, user_id, screen_name
156
+ def friend_ids(query={})
157
+ perform_get("/#{API_VERSION}/friends/ids.json", :query => query)
158
+ end
159
+
160
+ # Options: id, user_id, screen_name
161
+ def follower_ids(query={})
162
+ perform_get("/#{API_VERSION}/followers/ids.json", :query => query)
163
+ end
164
+
165
+ def verify_credentials
166
+ perform_get("/#{API_VERSION}/account/verify_credentials.json")
167
+ end
168
+
169
+ # Device must be sms, im or none
170
+ def update_delivery_device(device)
171
+ perform_post("/#{API_VERSION}/account/update_delivery_device.json", :body => {:device => device})
172
+ end
173
+
174
+ # One or more of the following must be present:
175
+ # profile_background_color, profile_text_color, profile_link_color,
176
+ # profile_sidebar_fill_color, profile_sidebar_border_color
177
+ def update_profile_colors(colors={})
178
+ perform_post("/#{API_VERSION}/account/update_profile_colors.json", :body => colors)
179
+ end
180
+
181
+ # file should respond to #read and #path
182
+ def update_profile_image(file)
183
+ perform_post("/#{API_VERSION}/account/update_profile_image.json", build_multipart_bodies(:image => file))
184
+ end
185
+
186
+ # file should respond to #read and #path
187
+ def update_profile_background(file, tile = false)
188
+ perform_post("/#{API_VERSION}/account/update_profile_background_image.json", build_multipart_bodies(:image => file).merge(:tile => tile))
189
+ end
190
+
191
+ def rate_limit_status
192
+ perform_get("/#{API_VERSION}/account/rate_limit_status.json")
193
+ end
194
+
195
+ # One or more of the following must be present:
196
+ # name, email, url, location, description
197
+ def update_profile(body={})
198
+ perform_post("/#{API_VERSION}/account/update_profile.json", :body => body)
199
+ end
200
+
201
+ # Options: id, page
202
+ def favorites(query={})
203
+ perform_get("/#{API_VERSION}/favorites.json", :query => query)
204
+ end
205
+
206
+ def favorite_create(id)
207
+ perform_post("/#{API_VERSION}/favorites/create/#{id}.json")
208
+ end
209
+
210
+ def favorite_destroy(id)
211
+ perform_post("/#{API_VERSION}/favorites/destroy/#{id}.json")
212
+ end
213
+
214
+ def enable_notifications(id)
215
+ perform_post("/#{API_VERSION}/notifications/follow/#{id}.json")
216
+ end
217
+
218
+ def disable_notifications(id)
219
+ perform_post("/#{API_VERSION}/notifications/leave/#{id}.json")
220
+ end
221
+
222
+ def block(id)
223
+ perform_post("/#{API_VERSION}/blocks/create/#{id}.json")
224
+ end
225
+
226
+ def unblock(id)
227
+ perform_post("/#{API_VERSION}/blocks/destroy/#{id}.json")
228
+ end
229
+
230
+ # When reporting a user for spam, specify one or more of id, screen_name, or user_id
231
+ def report_spam(options)
232
+ perform_post("/#{API_VERSION}/report_spam.json", :body => options)
233
+ end
234
+
235
+ def help
236
+ perform_get("/#{API_VERSION}/help/test.json")
237
+ end
238
+
239
+ def list_create(list_owner_username, options)
240
+ perform_post("/#{API_VERSION}/#{list_owner_username}/lists.json", :body => {:user => list_owner_username}.merge(options))
241
+ end
242
+
243
+ def list_update(list_owner_username, slug, options)
244
+ perform_put("/#{API_VERSION}/#{list_owner_username}/lists/#{slug}.json", :body => options)
245
+ end
246
+
247
+ def list_delete(list_owner_username, slug)
248
+ perform_delete("/#{API_VERSION}/#{list_owner_username}/lists/#{slug}.json")
249
+ end
250
+
251
+ def lists(list_owner_username = nil, query = {})
252
+ path = case list_owner_username
253
+ when nil, Hash
254
+ query = list_owner_username
255
+ "/#{API_VERSION}/lists.json"
256
+ else
257
+ "/#{API_VERSION}/#{list_owner_username}/lists.json"
258
+ end
259
+ perform_get(path, :query => query)
260
+ end
261
+
262
+ def list(list_owner_username, slug)
263
+ perform_get("/#{API_VERSION}/#{list_owner_username}/lists/#{slug}.json")
264
+ end
265
+
266
+ # :per_page = max number of statues to get at once
267
+ # :page = which page of tweets you wish to get
268
+ def list_timeline(list_owner_username, slug, query = {})
269
+ perform_get("/#{API_VERSION}/#{list_owner_username}/lists/#{slug}/statuses.json", :query => query)
270
+ end
271
+
272
+ def memberships(list_owner_username, query={})
273
+ perform_get("/#{API_VERSION}/#{list_owner_username}/lists/memberships.json", :query => query)
274
+ end
275
+
276
+ def subscriptions(list_owner_username, query = {})
277
+ perform_get("/#{API_VERSION}/#{list_owner_username}/lists/subscriptions.json", :query => query)
278
+ end
279
+
280
+ def list_members(list_owner_username, slug, query = {})
281
+ perform_get("/#{API_VERSION}/#{list_owner_username}/#{slug}/members.json", :query => query)
282
+ end
283
+
284
+ def list_add_member(list_owner_username, slug, new_id)
285
+ perform_post("/#{API_VERSION}/#{list_owner_username}/#{slug}/members.json", :body => {:id => new_id})
286
+ end
287
+
288
+ def list_remove_member(list_owner_username, slug, id)
289
+ perform_delete("/#{API_VERSION}/#{list_owner_username}/#{slug}/members.json", :query => {:id => id})
290
+ end
291
+
292
+ def is_list_member?(list_owner_username, slug, id)
293
+ perform_get("/#{API_VERSION}/#{list_owner_username}/#{slug}/members/#{id}.json").error.nil?
294
+ end
295
+
296
+ def list_subscribers(list_owner_username, slug)
297
+ perform_get("/#{API_VERSION}/#{list_owner_username}/#{slug}/subscribers.json")
298
+ end
299
+
300
+ def list_subscribe(list_owner_username, slug)
301
+ perform_post("/#{API_VERSION}/#{list_owner_username}/#{slug}/subscribers.json")
302
+ end
303
+
304
+ def list_unsubscribe(list_owner_username, slug)
305
+ perform_delete("/#{API_VERSION}/#{list_owner_username}/#{slug}/subscribers.json")
306
+ end
307
+
308
+ def blocked_ids
309
+ perform_get("/#{API_VERSION}/blocks/blocking/ids.json", :mash => false)
310
+ end
311
+
312
+ def blocking(options={})
313
+ perform_get("/#{API_VERSION}/blocks/blocking.json", options)
314
+ end
315
+
316
+ def saved_searches
317
+ perform_get("/#{API_VERSION}/saved_searches.json")
318
+ end
319
+
320
+ def saved_search(id)
321
+ perform_get("/#{API_VERSION}/saved_searches/show/#{id}.json")
322
+ end
323
+
324
+ def saved_search_create(query)
325
+ perform_post("/#{API_VERSION}/saved_searches/create.json", :body => {:query => query})
326
+ end
327
+
328
+ def saved_search_destroy(id)
329
+ perform_delete("/#{API_VERSION}/saved_searches/destroy/#{id}.json")
330
+ end
331
+
332
+ protected
333
+
334
+ def self.mime_type(file)
335
+ case
336
+ when file =~ /\.jpg/ then 'image/jpg'
337
+ when file =~ /\.gif$/ then 'image/gif'
338
+ when file =~ /\.png$/ then 'image/png'
339
+ else 'application/octet-stream'
340
+ end
341
+ end
342
+
343
+ def mime_type(f) self.class.mime_type(f) end
344
+
345
+ CRLF = "\r\n"
346
+
347
+ def self.build_multipart_bodies(parts)
348
+ boundary = Time.now.to_i.to_s(16)
349
+ body = ""
350
+ parts.each do |key, value|
351
+ esc_key = CGI.escape(key.to_s)
352
+ body << "--#{boundary}#{CRLF}"
353
+ if value.respond_to?(:read)
354
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"; filename=\"#{File.basename(value.path)}\"#{CRLF}"
355
+ body << "Content-Type: #{mime_type(value.path)}#{CRLF*2}"
356
+ body << value.read
357
+ else
358
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"#{CRLF*2}#{value}"
359
+ end
360
+ body << CRLF
361
+ end
362
+ body << "--#{boundary}--#{CRLF*2}"
363
+ {
364
+ :body => body,
365
+ :headers => {"Content-Type" => "multipart/form-data; boundary=#{boundary}"}
366
+ }
367
+ end
368
+
369
+ def build_multipart_bodies(parts) self.class.build_multipart_bodies(parts) end
370
+
371
+ private
372
+
373
+ def perform_get(path, options={})
374
+ Twitter::Request.get(self, path, options)
375
+ end
376
+
377
+ def perform_post(path, options={})
378
+ Twitter::Request.post(self, path, options)
379
+ end
380
+
381
+ def perform_put(path, options={})
382
+ Twitter::Request.put(self, path, options)
383
+ end
384
+
385
+ def perform_delete(path, options={})
386
+ Twitter::Request.delete(self, path, options)
387
+ end
388
+
389
+ end
390
+ end