jlind-twitter 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- data/History +290 -0
- data/License +20 -0
- data/Notes +33 -0
- data/README.rdoc +19 -0
- data/Rakefile +40 -0
- data/VERSION.yml +5 -0
- data/examples/connect.rb +30 -0
- data/examples/friendship_existence.rb +13 -0
- data/examples/helpers/config_store.rb +38 -0
- data/examples/httpauth.rb +11 -0
- data/examples/ids.rb +13 -0
- data/examples/lists.rb +11 -0
- data/examples/oauth.rb +27 -0
- data/examples/search.rb +15 -0
- data/examples/timeline.rb +19 -0
- data/examples/tumblr.rb +9 -0
- data/examples/unauthorized.rb +16 -0
- data/examples/update.rb +11 -0
- data/examples/user.rb +5 -0
- data/lib/twitter.rb +156 -0
- data/lib/twitter/base.rb +390 -0
- data/lib/twitter/geo.rb +25 -0
- data/lib/twitter/httpauth.rb +53 -0
- data/lib/twitter/local_trends.rb +30 -0
- data/lib/twitter/oauth.rb +85 -0
- data/lib/twitter/request.rb +71 -0
- data/lib/twitter/search.rb +163 -0
- data/lib/twitter/trends.rb +55 -0
- data/test/fixtures/blocking.json +1632 -0
- data/test/fixtures/firehose.json +1 -0
- data/test/fixtures/follower_ids.json +1 -0
- data/test/fixtures/followers.json +1 -0
- data/test/fixtures/friend_ids.json +1 -0
- data/test/fixtures/friends_timeline.json +1 -0
- data/test/fixtures/friendship.json +1 -0
- data/test/fixtures/friendship_exists.json +1 -0
- data/test/fixtures/geo_place.json +1 -0
- data/test/fixtures/geo_reverse_geocode.json +1 -0
- data/test/fixtures/geo_reverse_geocode_granularity.json +1 -0
- data/test/fixtures/geo_reverse_geocode_limit.json +1 -0
- data/test/fixtures/geo_search.json +1 -0
- data/test/fixtures/geo_search_ip_address.json +1 -0
- data/test/fixtures/geo_search_query.json +1 -0
- data/test/fixtures/home_timeline.json +1 -0
- data/test/fixtures/ids.json +1 -0
- data/test/fixtures/list.json +1 -0
- data/test/fixtures/list_statuses.json +1 -0
- data/test/fixtures/list_statuses_1_1.json +1 -0
- data/test/fixtures/list_statuses_2_1.json +1 -0
- data/test/fixtures/list_subscriptions.json +1 -0
- data/test/fixtures/list_users.json +1 -0
- data/test/fixtures/lists.json +1 -0
- data/test/fixtures/memberships.json +1 -0
- data/test/fixtures/mentions.json +1 -0
- data/test/fixtures/not_found.json +1 -0
- data/test/fixtures/people_search.json +39 -0
- data/test/fixtures/rate_limit_exceeded.json +1 -0
- data/test/fixtures/report_spam.json +41 -0
- data/test/fixtures/retweet.json +1 -0
- data/test/fixtures/retweeted_by_me.json +1 -0
- data/test/fixtures/retweeted_to_me.json +1 -0
- data/test/fixtures/retweeters_of_tweet.json +166 -0
- data/test/fixtures/retweets.json +1 -0
- data/test/fixtures/retweets_of_me.json +1 -0
- data/test/fixtures/sample-image.png +0 -0
- data/test/fixtures/saved_search.json +7 -0
- data/test/fixtures/saved_searches.json +16 -0
- data/test/fixtures/search.json +1 -0
- data/test/fixtures/search_from_jnunemaker.json +1 -0
- data/test/fixtures/status.json +1 -0
- data/test/fixtures/status_show.json +1 -0
- data/test/fixtures/trends_available.json +253 -0
- data/test/fixtures/trends_current.json +1 -0
- data/test/fixtures/trends_current_exclude.json +1 -0
- data/test/fixtures/trends_daily.json +1925 -0
- data/test/fixtures/trends_daily_date.json +1 -0
- data/test/fixtures/trends_daily_exclude.json +1 -0
- data/test/fixtures/trends_location.json +57 -0
- data/test/fixtures/trends_weekly.json +1 -0
- data/test/fixtures/trends_weekly_date.json +1 -0
- data/test/fixtures/trends_weekly_exclude.json +1 -0
- data/test/fixtures/unauthorized.json +1 -0
- data/test/fixtures/update_profile_background_image.json +1 -0
- data/test/fixtures/update_profile_image.json +1 -0
- data/test/fixtures/user.json +1 -0
- data/test/fixtures/user_timeline.json +710 -0
- data/test/fixtures/users.json +1 -0
- data/test/test_helper.rb +47 -0
- data/test/twitter/base_test.rb +426 -0
- data/test/twitter/geo_test.rb +79 -0
- data/test/twitter/httpauth_test.rb +86 -0
- data/test/twitter/oauth_test.rb +127 -0
- data/test/twitter/request_test.rb +217 -0
- data/test/twitter/search_test.rb +208 -0
- data/test/twitter/trends_test.rb +112 -0
- data/test/twitter_test.rb +106 -0
- 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
|
data/examples/search.rb
ADDED
@@ -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
|
data/examples/tumblr.rb
ADDED
@@ -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
|
+
|
data/examples/update.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
|
+
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
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")
|
data/lib/twitter/base.rb
ADDED
@@ -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
|