twitter4r 0.1.1 → 0.2.0
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.
- 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
|