jlind-twitter 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/History +290 -0
  2. data/License +20 -0
  3. data/Notes +33 -0
  4. data/README.rdoc +19 -0
  5. data/Rakefile +40 -0
  6. data/VERSION.yml +5 -0
  7. data/examples/connect.rb +30 -0
  8. data/examples/friendship_existence.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 +156 -0
  21. data/lib/twitter/base.rb +390 -0
  22. data/lib/twitter/geo.rb +25 -0
  23. data/lib/twitter/httpauth.rb +53 -0
  24. data/lib/twitter/local_trends.rb +30 -0
  25. data/lib/twitter/oauth.rb +85 -0
  26. data/lib/twitter/request.rb +71 -0
  27. data/lib/twitter/search.rb +163 -0
  28. data/lib/twitter/trends.rb +55 -0
  29. data/test/fixtures/blocking.json +1632 -0
  30. data/test/fixtures/firehose.json +1 -0
  31. data/test/fixtures/follower_ids.json +1 -0
  32. data/test/fixtures/followers.json +1 -0
  33. data/test/fixtures/friend_ids.json +1 -0
  34. data/test/fixtures/friends_timeline.json +1 -0
  35. data/test/fixtures/friendship.json +1 -0
  36. data/test/fixtures/friendship_exists.json +1 -0
  37. data/test/fixtures/geo_place.json +1 -0
  38. data/test/fixtures/geo_reverse_geocode.json +1 -0
  39. data/test/fixtures/geo_reverse_geocode_granularity.json +1 -0
  40. data/test/fixtures/geo_reverse_geocode_limit.json +1 -0
  41. data/test/fixtures/geo_search.json +1 -0
  42. data/test/fixtures/geo_search_ip_address.json +1 -0
  43. data/test/fixtures/geo_search_query.json +1 -0
  44. data/test/fixtures/home_timeline.json +1 -0
  45. data/test/fixtures/ids.json +1 -0
  46. data/test/fixtures/list.json +1 -0
  47. data/test/fixtures/list_statuses.json +1 -0
  48. data/test/fixtures/list_statuses_1_1.json +1 -0
  49. data/test/fixtures/list_statuses_2_1.json +1 -0
  50. data/test/fixtures/list_subscriptions.json +1 -0
  51. data/test/fixtures/list_users.json +1 -0
  52. data/test/fixtures/lists.json +1 -0
  53. data/test/fixtures/memberships.json +1 -0
  54. data/test/fixtures/mentions.json +1 -0
  55. data/test/fixtures/not_found.json +1 -0
  56. data/test/fixtures/people_search.json +39 -0
  57. data/test/fixtures/rate_limit_exceeded.json +1 -0
  58. data/test/fixtures/report_spam.json +41 -0
  59. data/test/fixtures/retweet.json +1 -0
  60. data/test/fixtures/retweeted_by_me.json +1 -0
  61. data/test/fixtures/retweeted_to_me.json +1 -0
  62. data/test/fixtures/retweeters_of_tweet.json +166 -0
  63. data/test/fixtures/retweets.json +1 -0
  64. data/test/fixtures/retweets_of_me.json +1 -0
  65. data/test/fixtures/sample-image.png +0 -0
  66. data/test/fixtures/saved_search.json +7 -0
  67. data/test/fixtures/saved_searches.json +16 -0
  68. data/test/fixtures/search.json +1 -0
  69. data/test/fixtures/search_from_jnunemaker.json +1 -0
  70. data/test/fixtures/status.json +1 -0
  71. data/test/fixtures/status_show.json +1 -0
  72. data/test/fixtures/trends_available.json +253 -0
  73. data/test/fixtures/trends_current.json +1 -0
  74. data/test/fixtures/trends_current_exclude.json +1 -0
  75. data/test/fixtures/trends_daily.json +1925 -0
  76. data/test/fixtures/trends_daily_date.json +1 -0
  77. data/test/fixtures/trends_daily_exclude.json +1 -0
  78. data/test/fixtures/trends_location.json +57 -0
  79. data/test/fixtures/trends_weekly.json +1 -0
  80. data/test/fixtures/trends_weekly_date.json +1 -0
  81. data/test/fixtures/trends_weekly_exclude.json +1 -0
  82. data/test/fixtures/unauthorized.json +1 -0
  83. data/test/fixtures/update_profile_background_image.json +1 -0
  84. data/test/fixtures/update_profile_image.json +1 -0
  85. data/test/fixtures/user.json +1 -0
  86. data/test/fixtures/user_timeline.json +710 -0
  87. data/test/fixtures/users.json +1 -0
  88. data/test/test_helper.rb +47 -0
  89. data/test/twitter/base_test.rb +426 -0
  90. data/test/twitter/geo_test.rb +79 -0
  91. data/test/twitter/httpauth_test.rb +86 -0
  92. data/test/twitter/oauth_test.rb +127 -0
  93. data/test/twitter/request_test.rb +217 -0
  94. data/test/twitter/search_test.rb +208 -0
  95. data/test/twitter/trends_test.rb +112 -0
  96. data/test/twitter_test.rb +106 -0
  97. metadata +305 -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,156 @@
1
+ require "forwardable"
2
+ require "oauth"
3
+ require "hashie"
4
+ require "httparty"
5
+ require "yajl"
6
+
7
+ module Twitter
8
+ include HTTParty
9
+ API_VERSION = "1".freeze
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.api_endpoint
30
+ @api_endpoint ||= "api.twitter.com/#{API_VERSION}"
31
+ end
32
+
33
+ def self.api_endpoint=(value)
34
+ @api_endpoint = value
35
+ end
36
+
37
+ def self.firehose(options = {})
38
+ perform_get("/statuses/public_timeline.json")
39
+ end
40
+
41
+ def self.user(id,options={})
42
+ perform_get("/users/show/#{id}.json")
43
+ end
44
+
45
+ def self.status(id,options={})
46
+ perform_get("/statuses/show/#{id}.json")
47
+ end
48
+
49
+ def self.friend_ids(id,options={})
50
+ perform_get("/friends/ids/#{id}.json")
51
+ end
52
+
53
+ def self.follower_ids(id,options={})
54
+ perform_get("/followers/ids/#{id}.json")
55
+ end
56
+
57
+ def self.timeline(id, options={})
58
+ perform_get("/statuses/user_timeline/#{id}.json", :query => options)
59
+ end
60
+
61
+ # :per_page = max number of statues to get at once
62
+ # :page = which page of tweets you wish to get
63
+ def self.list_timeline(list_owner_username, slug, query = {})
64
+ perform_get("/#{list_owner_username}/lists/#{slug}/statuses.json", :query => query)
65
+ end
66
+
67
+ private
68
+
69
+ def self.perform_get(uri, options = {})
70
+ base_uri self.api_endpoint
71
+ make_friendly(get(uri, options))
72
+ end
73
+
74
+ def self.make_friendly(response)
75
+ raise_errors(response)
76
+ data = parse(response)
77
+ # Don't mash arrays of integers
78
+ if data && data.is_a?(Array) && data.first.is_a?(Integer)
79
+ data
80
+ else
81
+ mash(data)
82
+ end
83
+ end
84
+
85
+ def self.raise_errors(response)
86
+ case response.code.to_i
87
+ when 400
88
+ data = parse(response)
89
+ raise RateLimitExceeded.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
90
+ when 401
91
+ data = parse(response)
92
+ raise Unauthorized.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
93
+ when 403
94
+ data = parse(response)
95
+ raise General.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
96
+ when 404
97
+ raise NotFound, "(#{response.code}): #{response.message}"
98
+ when 500
99
+ raise InformTwitter, "Twitter had an internal error. Please let them know in the group. (#{response.code}): #{response.message}"
100
+ when 502..503
101
+ raise Unavailable, "(#{response.code}): #{response.message}"
102
+ end
103
+ end
104
+
105
+ def self.parse(response)
106
+ Yajl::Parser.parse(response.body)
107
+ end
108
+
109
+ def self.mash(obj)
110
+ if obj.is_a?(Array)
111
+ obj.map{|item| make_mash_with_consistent_hash(item)}
112
+ elsif obj.is_a?(Hash)
113
+ make_mash_with_consistent_hash(obj)
114
+ else
115
+ obj
116
+ end
117
+ end
118
+
119
+ # Lame workaround for the fact that mash doesn't hash correctly
120
+ def self.make_mash_with_consistent_hash(obj)
121
+ m = Hashie::Mash.new(obj)
122
+ def m.hash
123
+ inspect.hash
124
+ end
125
+ return m
126
+ end
127
+
128
+ end
129
+
130
+ module Hashie
131
+ class Mash
132
+
133
+ # Converts all of the keys to strings, optionally formatting key name
134
+ def rubyify_keys!
135
+ keys.each{|k|
136
+ v = delete(k)
137
+ new_key = k.to_s.underscore
138
+ self[new_key] = v
139
+ v.rubyify_keys! if v.is_a?(Hash)
140
+ v.each{|p| p.rubyify_keys! if p.is_a?(Hash)} if v.is_a?(Array)
141
+ }
142
+ self
143
+ end
144
+
145
+ end
146
+ end
147
+
148
+ directory = File.expand_path(File.dirname(__FILE__))
149
+
150
+ require File.join(directory, "twitter", "oauth")
151
+ require File.join(directory, "twitter", "httpauth")
152
+ require File.join(directory, "twitter", "request")
153
+ require File.join(directory, "twitter", "base")
154
+ require File.join(directory, "twitter", "search")
155
+ require File.join(directory, "twitter", "trends")
156
+ require File.join(directory, "twitter", "geo")
@@ -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("/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("/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("/statuses/user_timeline.json", :query => query)
26
+ end
27
+
28
+ def status(id)
29
+ perform_get("/statuses/show/#{id}.json")
30
+ end
31
+
32
+ # Options: count
33
+ def retweets(id, query={})
34
+ perform_get("/statuses/retweets/#{id}.json", :query => query)
35
+ end
36
+
37
+ # Options: in_reply_to_status_id
38
+ def update(status, query={})
39
+ perform_post("/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("/statuses/replies.json", :query => query)
48
+ end
49
+
50
+ # Options: since_id, max_id, count, page
51
+ def mentions(query={})
52
+ perform_get("/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("/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("/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("/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("/statuses/#{id}/retweeted_by#{"/ids" if ids_only}.json", :query => options)
74
+ end
75
+
76
+ def status_destroy(id)
77
+ perform_post("/statuses/destroy/#{id}.json")
78
+ end
79
+
80
+ def retweet(id)
81
+ perform_post("/statuses/retweet/#{id}.json")
82
+ end
83
+
84
+ # Options: id, user_id, screen_name, page
85
+ def friends(query={})
86
+ perform_get("/statuses/friends.json", :query => query)
87
+ end
88
+
89
+ # Options: id, user_id, screen_name, page
90
+ def followers(query={})
91
+ perform_get("/statuses/followers.json", :query => query)
92
+ end
93
+
94
+ def user(id, query={})
95
+ perform_get("/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("/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("/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("/direct_messages.json", :query => query)
122
+ end
123
+
124
+ # Options: since, since_id, page
125
+ def direct_messages_sent(query={})
126
+ perform_get("/direct_messages/sent.json", :query => query)
127
+ end
128
+
129
+ def direct_message_create(user, text)
130
+ perform_post("/direct_messages/new.json", :body => {:user => user, :text => text})
131
+ end
132
+
133
+ def direct_message_destroy(id)
134
+ perform_post("/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("/friendships/create/#{id}.json", :body => body)
141
+ end
142
+
143
+ def friendship_destroy(id)
144
+ perform_post("/friendships/destroy/#{id}.json")
145
+ end
146
+
147
+ def friendship_exists?(a, b)
148
+ perform_get("/friendships/exists.json", :query => {:user_a => a, :user_b => b})
149
+ end
150
+
151
+ def friendship_show(query)
152
+ perform_get("/friendships/show.json", :query => query)
153
+ end
154
+
155
+ # Options: id, user_id, screen_name
156
+ def friend_ids(query={})
157
+ perform_get("/friends/ids.json", :query => query)
158
+ end
159
+
160
+ # Options: id, user_id, screen_name
161
+ def follower_ids(query={})
162
+ perform_get("/followers/ids.json", :query => query)
163
+ end
164
+
165
+ def verify_credentials
166
+ perform_get("/account/verify_credentials.json")
167
+ end
168
+
169
+ # Device must be sms, im or none
170
+ def update_delivery_device(device)
171
+ perform_post("/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("/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("/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("/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("/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("/account/update_profile.json", :body => body)
199
+ end
200
+
201
+ # Options: id, page
202
+ def favorites(query={})
203
+ perform_get("/favorites.json", :query => query)
204
+ end
205
+
206
+ def favorite_create(id)
207
+ perform_post("/favorites/create/#{id}.json")
208
+ end
209
+
210
+ def favorite_destroy(id)
211
+ perform_post("/favorites/destroy/#{id}.json")
212
+ end
213
+
214
+ def enable_notifications(id)
215
+ perform_post("/notifications/follow/#{id}.json")
216
+ end
217
+
218
+ def disable_notifications(id)
219
+ perform_post("/notifications/leave/#{id}.json")
220
+ end
221
+
222
+ def block(id)
223
+ perform_post("/blocks/create/#{id}.json")
224
+ end
225
+
226
+ def unblock(id)
227
+ perform_post("/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("/report_spam.json", :body => options)
233
+ end
234
+
235
+ def help
236
+ perform_get("/help/test.json")
237
+ end
238
+
239
+ def list_create(list_owner_username, options)
240
+ perform_post("/#{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("/#{list_owner_username}/lists/#{slug}.json", :body => options)
245
+ end
246
+
247
+ def list_delete(list_owner_username, slug)
248
+ perform_delete("/#{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
+ "/lists.json"
256
+ else
257
+ "/#{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("/#{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("/#{list_owner_username}/lists/#{slug}/statuses.json", :query => query)
270
+ end
271
+
272
+ def memberships(list_owner_username, query={})
273
+ perform_get("/#{list_owner_username}/lists/memberships.json", :query => query)
274
+ end
275
+
276
+ def subscriptions(list_owner_username, query = {})
277
+ perform_get("/#{list_owner_username}/lists/subscriptions.json", :query => query)
278
+ end
279
+
280
+ def list_members(list_owner_username, slug, query = {})
281
+ perform_get("/#{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("/#{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("/#{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("/#{list_owner_username}/#{slug}/members/#{id}.json").error.nil?
294
+ end
295
+
296
+ def list_subscribers(list_owner_username, slug)
297
+ perform_get("/#{list_owner_username}/#{slug}/subscribers.json")
298
+ end
299
+
300
+ def list_subscribe(list_owner_username, slug)
301
+ perform_post("/#{list_owner_username}/#{slug}/subscribers.json")
302
+ end
303
+
304
+ def list_unsubscribe(list_owner_username, slug)
305
+ perform_delete("/#{list_owner_username}/#{slug}/subscribers.json")
306
+ end
307
+
308
+ def blocked_ids
309
+ perform_get("/blocks/blocking/ids.json", :mash => false)
310
+ end
311
+
312
+ def blocking(options={})
313
+ perform_get("/blocks/blocking.json", options)
314
+ end
315
+
316
+ def saved_searches
317
+ perform_get("/saved_searches.json")
318
+ end
319
+
320
+ def saved_search(id)
321
+ perform_get("/saved_searches/show/#{id}.json")
322
+ end
323
+
324
+ def saved_search_create(query)
325
+ perform_post("/saved_searches/create.json", :body => {:query => query})
326
+ end
327
+
328
+ def saved_search_destroy(id)
329
+ perform_delete("/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