twitter4r 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/twitter/client/base.rb +81 -0
- data/lib/twitter/client/friendship.rb +34 -0
- data/lib/twitter/client/messaging.rb +61 -0
- data/lib/twitter/client/status.rb +39 -0
- data/lib/twitter/client/timeline.rb +62 -0
- data/lib/twitter/client/user.rb +33 -0
- data/lib/twitter/client.rb +18 -0
- data/lib/twitter/config.rb +69 -0
- data/lib/twitter/console.rb +28 -0
- data/lib/twitter/core.rb +109 -171
- data/lib/twitter/ext/stdlib.rb +32 -0
- data/lib/twitter/ext.rb +2 -0
- data/lib/twitter/extras.rb +39 -0
- data/lib/twitter/meta.rb +16 -1
- data/lib/twitter/model.rb +331 -0
- data/lib/twitter/version.rb +7 -2
- data/lib/twitter.rb +16 -5
- data/spec/spec_helper.rb +47 -4
- data/spec/twitter/client/base_spec.rb +232 -0
- data/spec/twitter/client/friendship_spec.rb +70 -0
- data/spec/twitter/client/messaging_spec.rb +92 -0
- data/spec/twitter/client/status_spec.rb +85 -0
- data/spec/twitter/client/timeline_spec.rb +69 -0
- data/spec/twitter/client/user_spec.rb +141 -0
- data/spec/twitter/client_spec.rb +2 -0
- data/spec/twitter/config_spec.rb +86 -0
- data/spec/twitter/console_spec.rb +15 -0
- data/spec/twitter/core_spec.rb +32 -353
- data/spec/twitter/deprecated_spec.rb +177 -0
- data/spec/twitter/ext/stdlib_spec.rb +42 -0
- data/spec/twitter/extras_spec.rb +46 -0
- data/spec/twitter/meta_spec.rb +5 -4
- data/spec/twitter/model_spec.rb +458 -0
- data/spec/twitter/version_spec.rb +2 -2
- metadata +46 -10
data/lib/twitter/core.rb
CHANGED
@@ -1,198 +1,136 @@
|
|
1
1
|
# The Twitter4R API provides a nicer Ruby object API to work with
|
2
2
|
# instead of coding around the REST API.
|
3
3
|
|
4
|
-
|
5
|
-
require('uri')
|
6
|
-
require('json')
|
7
|
-
|
8
|
-
# Encapsules the Twitter4R API.
|
4
|
+
# Module to encapsule the Twitter4R API.
|
9
5
|
module Twitter
|
6
|
+
# Mixin module for classes that need to have a constructor similar to
|
7
|
+
# Rails' models, where a <tt>Hash</tt> is provided to set attributes
|
8
|
+
# appropriately.
|
9
|
+
#
|
10
|
+
# To define a class that uses this mixin, use the following code:
|
11
|
+
# class FilmActor
|
12
|
+
# include ClassUtilMixin
|
13
|
+
# end
|
10
14
|
module ClassUtilMixin #:nodoc:
|
11
|
-
def self.included(base)
|
15
|
+
def self.included(base) #:nodoc:
|
12
16
|
base.send(:include, InstanceMethods)
|
13
17
|
end
|
14
18
|
|
19
|
+
# Instance methods defined for <tt>Twitter::ModelMixin</tt> module.
|
15
20
|
module InstanceMethods #:nodoc:
|
21
|
+
# Constructor/initializer that takes a hash of parameters that
|
22
|
+
# will initialize *members* or instance attributes to the
|
23
|
+
# values given. For example,
|
24
|
+
#
|
25
|
+
# class FilmActor
|
26
|
+
# include Twitter::ClassUtilMixin
|
27
|
+
# attr_accessor :name
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# class Production
|
31
|
+
# include Twitter::ClassUtilMixin
|
32
|
+
# attr_accessor :title, :year, :actors
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # Favorite actress...
|
36
|
+
# jodhi = FilmActor.new(:name => "Jodhi May")
|
37
|
+
# jodhi.name # => "Jodhi May"
|
38
|
+
#
|
39
|
+
# # Favorite actor...
|
40
|
+
# robert = FilmActor.new(:name => "Robert Lindsay")
|
41
|
+
# robert.name # => "Robert Lindsay"
|
42
|
+
#
|
43
|
+
# # Jane is also an excellent pick...gotta love her accent!
|
44
|
+
# jane = FilmActor.new(name => "Jane Horrocks")
|
45
|
+
# jane.name # => "Jane Horrocks"
|
46
|
+
#
|
47
|
+
# # Witty BBC series...
|
48
|
+
# mrs_pritchard = Production.new(:title => "The Amazing Mrs. Pritchard",
|
49
|
+
# :year => 2005,
|
50
|
+
# :actors => [jodhi, jane])
|
51
|
+
# mrs_pritchard.title # => "The Amazing Mrs. Pritchard"
|
52
|
+
# mrs_pritchard.year # => 2005
|
53
|
+
# mrs_pritchard.actors # => [#<FilmActor:0xb79d6bbc @name="Jodhi May">,
|
54
|
+
# <FilmActor:0xb79d319c @name="Jane Horrocks">]
|
55
|
+
# # Any Ros Pritchard's out there to save us from the Tony Blair
|
56
|
+
# # and Gordon Brown *New Labour* debacle? You've got my vote!
|
57
|
+
#
|
58
|
+
# jericho = Production.new(:title => "Jericho",
|
59
|
+
# :year => 2005,
|
60
|
+
# :actors => [robert])
|
61
|
+
# jericho.title # => "Jericho"
|
62
|
+
# jericho.year # => 2005
|
63
|
+
# jericho.actors # => [#<FilmActor:0xc95d3eec @name="Robert Lindsay">]
|
64
|
+
#
|
65
|
+
# Assuming class <tt>FilmActor</tt> includes
|
66
|
+
# <tt>Twitter::ClassUtilMixin</tt> in the class definition
|
67
|
+
# and has an attribute of <tt>name</tt>, then that instance
|
68
|
+
# attribute will be set to "Jodhi May" for the <tt>actress</tt>
|
69
|
+
# object during object initialization (aka construction for
|
70
|
+
# you Java heads).
|
16
71
|
def initialize(params = {})
|
17
72
|
params.each do |key,val|
|
18
73
|
self.send("#{key}=", val) if self.respond_to? key
|
19
74
|
end
|
75
|
+
self.send(:init) if self.respond_to? :init
|
20
76
|
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
# Helper method to provide an easy and terse way to require
|
80
|
+
# a block is provided to a method.
|
81
|
+
def require_block(block_given)
|
82
|
+
raise ArgumentError, "Must provide a block" unless block_given
|
83
|
+
end
|
21
84
|
end
|
22
85
|
end # ClassUtilMixin
|
23
86
|
|
24
|
-
#
|
25
|
-
# querying or posting to the REST API.
|
87
|
+
# Exception subclass raised when there is an error encountered upon
|
88
|
+
# querying or posting to the remote Twitter REST API.
|
89
|
+
#
|
90
|
+
# To consume and query any <tt>RESTError</tt> raised by Twitter4R:
|
91
|
+
# begin
|
92
|
+
# # Do something with your instance of <tt>Twitter::Client</tt>.
|
93
|
+
# # Maybe something like:
|
94
|
+
# timeline = twitter.timeline_for(:public)
|
95
|
+
# rescue RESTError => re
|
96
|
+
# puts re.code, re.message, re.uri
|
97
|
+
# end
|
98
|
+
# Which on the code raising a <tt>RESTError</tt> will output something like:
|
99
|
+
# 404
|
100
|
+
# Resource Not Found
|
101
|
+
# /i_am_crap.json
|
26
102
|
class RESTError < Exception
|
27
103
|
include ClassUtilMixin
|
28
104
|
attr_accessor :code, :message, :uri
|
29
105
|
|
106
|
+
# Returns string in following format:
|
107
|
+
# "HTTP #{@code}: #{@message} at #{@uri}"
|
108
|
+
# For example,
|
109
|
+
# "HTTP 404: Resource Not Found at /i_am_crap.json"
|
30
110
|
def to_s
|
31
|
-
"HTTP #{code}: #{message} at #{uri}"
|
111
|
+
"HTTP #{@code}: #{@message} at #{@uri}"
|
32
112
|
end
|
33
113
|
end # RESTError
|
34
|
-
|
35
|
-
# Represents a user of Twitter
|
36
|
-
class User
|
37
|
-
include ClassUtilMixin
|
38
|
-
attr_accessor :id, :name, :description, :location, :screen_name, :url
|
39
|
-
|
40
|
-
def eql?(obj)
|
41
|
-
[:id, :name, :description, :location,
|
42
|
-
:screen_name, :url].each do |att|
|
43
|
-
return false unless self.send(att).eql?(obj.send(att))
|
44
|
-
end
|
45
|
-
true
|
46
|
-
end
|
47
|
-
end # User
|
48
114
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
:port => 80,
|
70
|
-
:ssl => false,
|
71
|
-
}
|
72
|
-
@@URIS = {:public => '/statuses/public_timeline.json',
|
73
|
-
:friends => '/statuses/friends_timeline.json',
|
74
|
-
:friends_statues => '/statuses/friends.json',
|
75
|
-
:followers => '/statuses/followers.json',
|
76
|
-
:update => '/statuses/update.json',
|
77
|
-
:send_direct_message => '/direct_messages/new.json', }
|
78
|
-
|
79
|
-
class << self
|
80
|
-
# The following configuration options can be passed in a hash to this method:
|
81
|
-
# * <tt>host</tt> - host of the REST server, defaults to twitter.com. Should only be changed for integration testing.
|
82
|
-
# * <tt>port</tt> - port of the REST server, defaults to 443.
|
83
|
-
# * <tt>ssl</tt> - SSL flag, defaults to false. Do not use :use_ssl anymore.
|
84
|
-
# * <tt>proxy_host</tt> - proxy host. No default.
|
85
|
-
# * <tt>proxy_port</tt> - proxy port. No default.
|
86
|
-
# * <tt>proxy_user</tt> - proxy username. No default.
|
87
|
-
# * <tt>proxy_pass</tt> - proxy password. No default.
|
88
|
-
def config(conf)
|
89
|
-
@@CONF.merge!(conf)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Mostly helper method for irb shell prototyping
|
93
|
-
# TODO: move this out as class extension for twitter4r console script
|
94
|
-
def from_config(config_file, env = 'test')
|
95
|
-
yaml_hash = YAML.load(File.read(config_file))
|
96
|
-
self.new yaml_hash[env]
|
97
|
-
end
|
98
|
-
end # class << self
|
99
|
-
|
100
|
-
def timeline(type = :public)
|
101
|
-
http = create_http_connection
|
102
|
-
http.start do |http|
|
103
|
-
timeline_request(type, http)
|
104
|
-
end # http.start block
|
105
|
-
end # timeline
|
106
|
-
|
107
|
-
def public_timeline
|
108
|
-
timeline :public
|
109
|
-
end
|
110
|
-
|
111
|
-
def friend_timeline
|
112
|
-
timeline :friends
|
113
|
-
end
|
114
|
-
|
115
|
-
def friend_statuses
|
116
|
-
timeline :friends_statuses
|
117
|
-
end
|
118
|
-
|
119
|
-
def follower_statuses
|
120
|
-
timeline :followers
|
121
|
-
end
|
122
|
-
|
123
|
-
def update(message)
|
124
|
-
uri = @@URIS[:update]
|
125
|
-
http = create_http_connection
|
126
|
-
http.start do |http|
|
127
|
-
request = Net::HTTP::Post.new(uri)
|
128
|
-
request.basic_auth(@login, @password)
|
129
|
-
response = http.request(request, "status=#{URI.escape(message)}")
|
130
|
-
handle_rest_response(response, uri)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def send_direct_message(user, message)
|
135
|
-
login = user.respond_to?(:screen_name) ? user.screen_name : user
|
136
|
-
uri = @@URIS[:send_direct_message]
|
137
|
-
http = create_http_connection
|
138
|
-
http.start do |http|
|
139
|
-
request = Net::HTTP::Post.new(uri)
|
140
|
-
request.basic_auth(@login, @password)
|
141
|
-
response = http.request(request, "user=#{login}&text=#{URI.escape(message)}")
|
142
|
-
handle_rest_response(response, uri)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
protected
|
147
|
-
attr_accessor :login, :password, :host, :port
|
148
|
-
|
149
|
-
def unmarshall_statuses(status_array)
|
150
|
-
status_array.collect do |status|
|
151
|
-
Status.new(:id => status['id'],
|
152
|
-
:text => status['text'],
|
153
|
-
:user => unmarshall_user(status['user']),
|
154
|
-
:created_at => Time.parse(status['created_at'])
|
155
|
-
)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def unmarshall_user(map)
|
160
|
-
User.new(:id => map['id'], :name => map['name'],
|
161
|
-
:screen_name => map['screen_name'],
|
162
|
-
:description => map['description'],
|
163
|
-
:location => map['location'],
|
164
|
-
:url => map['url'])
|
165
|
-
end
|
166
|
-
|
167
|
-
def timeline_request(type, http)
|
168
|
-
uri = @@URIS[type]
|
169
|
-
request = Net::HTTP::Get.new(uri)
|
170
|
-
request.basic_auth(@login, @password)
|
171
|
-
response = http.request(request)
|
172
|
-
|
173
|
-
handle_rest_response(response, uri)
|
174
|
-
unmarshall_statuses(JSON.parse(response.body))
|
175
|
-
end
|
176
|
-
|
177
|
-
def create_http_connection
|
178
|
-
conn = Net::HTTP.new(@@CONF[:host], @@CONF[:port],
|
179
|
-
@@CONF[:proxy_host], @@CONF[:proxy_port],
|
180
|
-
@@CONF[:proxy_user], @@CONF[:proxy_pass])
|
181
|
-
conn.use_ssl = @@CONF[:ssl]
|
182
|
-
conn.verify_mode = OpenSSL::SSL::VERIFY_NONE if @@CONF[:ssl]
|
183
|
-
conn
|
184
|
-
end
|
185
|
-
|
186
|
-
def raise_rest_error(response, uri = nil)
|
187
|
-
raise RESTError.new(:code => response.code,
|
188
|
-
:message => response.message,
|
189
|
-
:uri => uri)
|
190
|
-
end
|
191
|
-
|
192
|
-
def handle_rest_response(response, uri)
|
193
|
-
unless ["200", "201"].member?(response.code)
|
194
|
-
raise_rest_error(response, uri)
|
195
|
-
end
|
196
|
-
end
|
115
|
+
# Remote REST API interface representation
|
116
|
+
#
|
117
|
+
class RESTInterfaceSpec
|
118
|
+
include ClassUtilMixin
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
# Remote REST API method representation
|
123
|
+
#
|
124
|
+
class RESTMethodSpec
|
125
|
+
include ClassUtilMixin
|
126
|
+
attr_accessor :uri, :method, :parameters
|
127
|
+
end
|
128
|
+
|
129
|
+
# Remote REST API method parameter representation
|
130
|
+
#
|
131
|
+
class RESTParameterSpec
|
132
|
+
include ClassUtilMixin
|
133
|
+
attr_accessor :name, :type, :required
|
134
|
+
def required?; @required; end
|
197
135
|
end
|
198
136
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Contains Ruby standard library extensions specific to <tt>Twitter4R</tt> library.
|
2
|
+
|
3
|
+
# Extension to Hash to create URL encoded string from key-values
|
4
|
+
class Hash
|
5
|
+
# Returns string formatted for HTTP URL encoded name-value pairs.
|
6
|
+
# For example,
|
7
|
+
# {:id => 'thomas_hardy'}.to_http_str
|
8
|
+
# # => "id=thomas_hardy"
|
9
|
+
# {:id => 23423, :since => Time.now}.to_http_str
|
10
|
+
# # => "since=Thu,%2021%20Jun%202007%2012:10:05%20-0500&id=23423"
|
11
|
+
def to_http_str
|
12
|
+
result = ''
|
13
|
+
return result if self.empty?
|
14
|
+
self.each do |key, val|
|
15
|
+
result << "#{key}=#{URI.encode(val.to_s)}&"
|
16
|
+
end
|
17
|
+
result.chop # remove the last '&' character, since it can be discarded
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Extension to Time that outputs RFC2822 compliant string on #to_s
|
22
|
+
class Time
|
23
|
+
alias :old_to_s :to_s
|
24
|
+
# Returns RFC2822 compliant string for <tt>Time</tt> object.
|
25
|
+
# For example,
|
26
|
+
# # Tony Blair's last day in office (hopefully)
|
27
|
+
# best_day_ever = Time.local(2007, 6, 27)
|
28
|
+
# best_day_ever.to_s # => "Wed, 27 Jun 2007 00:00:00 +0100"
|
29
|
+
def to_s
|
30
|
+
self.rfc2822
|
31
|
+
end
|
32
|
+
end
|
data/lib/twitter/ext.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# extra.rb contains features that are not considered part of the core library.
|
2
|
+
# This file is not imported by doing <tt>require('twitter')</tt>, so you will
|
3
|
+
# need to import this file separately like:
|
4
|
+
# require('twitter')
|
5
|
+
# require('twitter/extras')
|
6
|
+
|
7
|
+
require('twitter')
|
8
|
+
|
9
|
+
class Twitter::Client
|
10
|
+
@@FEATURED_URIS = {
|
11
|
+
:users => 'http://twitter.com/statuses/featured.json'
|
12
|
+
}
|
13
|
+
|
14
|
+
# Provides access to the Featured Twitter API.
|
15
|
+
#
|
16
|
+
# Currently the only value for <tt>type</tt> accepted is <tt>:users</tt>,
|
17
|
+
# which will return an Array of blessed Twitter::User objects that
|
18
|
+
# represent Twitter's featured users.
|
19
|
+
def featured(type)
|
20
|
+
uri = @@FEATURED_URIS[type]
|
21
|
+
response = http_connect {|conn| create_http_get_request(uri) }
|
22
|
+
bless_models(Twitter::User.unmarshal(response.body))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Twitter::User
|
27
|
+
class << self
|
28
|
+
# Provides access to the Featured Twitter API via the Twitter4R Model
|
29
|
+
# interface.
|
30
|
+
#
|
31
|
+
# The following lines of code are equivalent to each other:
|
32
|
+
# users1 = Twitter::User.features(client)
|
33
|
+
# users2 = client.featured(:users)
|
34
|
+
# where <tt>users1</tt> and <tt>users2</tt> would be logically equivalent.
|
35
|
+
def featured(client)
|
36
|
+
client.featured(:users)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/twitter/meta.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# meta.rb contains <tt>Twitter::Meta</tt> and related classes that
|
2
|
+
# help define the metadata of the <tt>Twitter4R</tt> project.
|
3
|
+
|
1
4
|
require('rubygems')
|
2
5
|
require('erb')
|
3
6
|
|
@@ -5,10 +8,12 @@ class Twitter::Meta #:nodoc:
|
|
5
8
|
attr_accessor :root_dir
|
6
9
|
attr_reader :gem_spec, :project_files, :spec_files
|
7
10
|
|
11
|
+
# Initializer for Twitter::Meta class. Takes <tt>root_dir</tt> as parameter.
|
8
12
|
def initialize(root_dir)
|
9
13
|
@root_dir = root_dir
|
10
14
|
end
|
11
15
|
|
16
|
+
# Returns package information defined in <tt>root_dir</tt>/pkg-info.yml
|
12
17
|
def pkg_info
|
13
18
|
yaml_file = File.join(@root_dir, 'pkg-info.yml')
|
14
19
|
ryaml = ERB.new(File.read(yaml_file), 0)
|
@@ -16,24 +21,34 @@ class Twitter::Meta #:nodoc:
|
|
16
21
|
YAML.load(s)
|
17
22
|
end
|
18
23
|
|
24
|
+
# Returns RubyGems spec information
|
19
25
|
def spec_info
|
20
26
|
self.pkg_info['spec'] if self.pkg_info
|
21
27
|
end
|
22
28
|
|
29
|
+
# Returns list of project files
|
23
30
|
def project_files
|
24
31
|
@project_files ||= Dir.glob(File.join(@root_dir, 'lib/**/*.rb'))
|
25
32
|
@project_files
|
26
33
|
end
|
27
34
|
|
35
|
+
# Returns list of specification files
|
28
36
|
def spec_files
|
29
37
|
@spec_files ||= Dir.glob(File.join(@root_dir, 'spec/**/*.rb'))
|
30
38
|
@spec_files
|
31
39
|
end
|
32
40
|
|
41
|
+
# Returns RubyGem specification for Twitter4R project
|
33
42
|
def gem_spec
|
34
43
|
@gem_spec ||= Gem::Specification.new do |spec|
|
35
44
|
self.spec_info.each do |key, val|
|
36
|
-
|
45
|
+
if val.is_a?(Hash)
|
46
|
+
val.each do |k, v|
|
47
|
+
spec.send(key, k, v)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
spec.send("#{key}=", val)
|
51
|
+
end
|
37
52
|
end
|
38
53
|
end
|
39
54
|
@gem_spec
|