dambalah-twitter4r 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/CHANGES +124 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +32 -0
  4. data/TODO +9 -0
  5. data/lib/twitter.rb +31 -0
  6. data/lib/twitter/client.rb +21 -0
  7. data/lib/twitter/client/account.rb +24 -0
  8. data/lib/twitter/client/auth.rb +27 -0
  9. data/lib/twitter/client/base.rb +87 -0
  10. data/lib/twitter/client/blocks.rb +35 -0
  11. data/lib/twitter/client/favorites.rb +53 -0
  12. data/lib/twitter/client/friendship.rb +47 -0
  13. data/lib/twitter/client/messaging.rb +79 -0
  14. data/lib/twitter/client/status.rb +46 -0
  15. data/lib/twitter/client/timeline.rb +72 -0
  16. data/lib/twitter/client/user.rb +87 -0
  17. data/lib/twitter/config.rb +71 -0
  18. data/lib/twitter/console.rb +28 -0
  19. data/lib/twitter/core.rb +137 -0
  20. data/lib/twitter/ext.rb +2 -0
  21. data/lib/twitter/ext/stdlib.rb +51 -0
  22. data/lib/twitter/extras.rb +39 -0
  23. data/lib/twitter/meta.rb +56 -0
  24. data/lib/twitter/model.rb +344 -0
  25. data/lib/twitter/rails.rb +92 -0
  26. data/lib/twitter/version.rb +19 -0
  27. data/spec/twitter/client/auth_spec.rb +34 -0
  28. data/spec/twitter/client/base_spec.rb +242 -0
  29. data/spec/twitter/client/blocks_spec.rb +76 -0
  30. data/spec/twitter/client/favorites_spec.rb +183 -0
  31. data/spec/twitter/client/friendship_spec.rb +76 -0
  32. data/spec/twitter/client/messaging_spec.rb +135 -0
  33. data/spec/twitter/client/status_spec.rb +92 -0
  34. data/spec/twitter/client/timeline_spec.rb +79 -0
  35. data/spec/twitter/client/user_spec.rb +203 -0
  36. data/spec/twitter/client_spec.rb +2 -0
  37. data/spec/twitter/config_spec.rb +86 -0
  38. data/spec/twitter/console_spec.rb +15 -0
  39. data/spec/twitter/core_spec.rb +127 -0
  40. data/spec/twitter/ext/stdlib_spec.rb +42 -0
  41. data/spec/twitter/extras_spec.rb +46 -0
  42. data/spec/twitter/meta_spec.rb +90 -0
  43. data/spec/twitter/model_spec.rb +464 -0
  44. data/spec/twitter/rails_spec.rb +110 -0
  45. data/spec/twitter/version_spec.rb +19 -0
  46. metadata +109 -0
@@ -0,0 +1,2 @@
1
+
2
+ require_local('twitter/ext/stdlib')
@@ -0,0 +1,51 @@
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}=#{CGI.escape(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
+
25
+ # Returns RFC2822 compliant string for <tt>Time</tt> object.
26
+ # For example,
27
+ # # Tony Blair's last day in office (hopefully)
28
+ # best_day_ever = Time.local(2007, 6, 27)
29
+ # best_day_ever.to_s # => "Wed, 27 Jun 2007 00:00:00 +0100"
30
+ # You can also pass in an option <tt>format</tt> argument that
31
+ # corresponds to acceptable values according to ActiveSupport's
32
+ # +Time#to_formatted_s+ method.
33
+ def to_s(format = nil)
34
+ format ? self.to_formatted_s(format) : self.rfc2822
35
+ end
36
+ end
37
+
38
+ # Extension to Kernel to add #gem_present? without any exceptions raised
39
+ module Kernel
40
+
41
+ # Returns whether or not a gem exists without raising a Gem::LoadError exception
42
+ def gem_present?(gem_name, version = nil)
43
+ present = false
44
+ begin
45
+ present = gem(gem_name, version)
46
+ rescue Gem::LoadError => le
47
+ warn("Gem load error: Couldn't load #{gem_name} with version requirement #{version}: #{le.to_s}")
48
+ end
49
+ present
50
+ end
51
+ end
@@ -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
@@ -0,0 +1,56 @@
1
+ # meta.rb contains <tt>Twitter::Meta</tt> and related classes that
2
+ # help define the metadata of the <tt>Twitter4R</tt> project.
3
+
4
+ require('rubygems')
5
+ require('erb')
6
+
7
+ class Twitter::Meta #:nodoc:
8
+ attr_accessor :root_dir
9
+ attr_reader :gem_spec, :project_files, :spec_files
10
+
11
+ # Initializer for Twitter::Meta class. Takes <tt>root_dir</tt> as parameter.
12
+ def initialize(root_dir)
13
+ @root_dir = root_dir
14
+ end
15
+
16
+ # Returns package information defined in <tt>root_dir</tt>/pkg-info.yml
17
+ def pkg_info
18
+ yaml_file = File.join(@root_dir, 'pkg-info.yml')
19
+ ryaml = ERB.new(File.read(yaml_file), 0)
20
+ s = ryaml.result(binding)
21
+ YAML.load(s)
22
+ end
23
+
24
+ # Returns RubyGems spec information
25
+ def spec_info
26
+ self.pkg_info['spec'] if self.pkg_info
27
+ end
28
+
29
+ # Returns list of project files
30
+ def project_files
31
+ @project_files ||= Dir.glob(File.join(@root_dir, 'lib/**/*.rb'))
32
+ @project_files
33
+ end
34
+
35
+ # Returns list of specification files
36
+ def spec_files
37
+ @spec_files ||= Dir.glob(File.join(@root_dir, 'spec/**/*_spec.rb'))
38
+ @spec_files
39
+ end
40
+
41
+ # Returns RubyGem specification for Twitter4R project
42
+ def gem_spec
43
+ @gem_spec ||= Gem::Specification.new do |spec|
44
+ self.spec_info.each do |key, val|
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
52
+ end
53
+ end
54
+ @gem_spec
55
+ end
56
+ end
@@ -0,0 +1,344 @@
1
+ # Contains Twitter4R Model API.
2
+
3
+ module Twitter
4
+ # Mixin module for model classes. Includes generic class methods like
5
+ # unmarshal.
6
+ #
7
+ # To create a new model that includes this mixin's features simply:
8
+ # class NewModel
9
+ # include Twitter::ModelMixin
10
+ # end
11
+ #
12
+ # This mixin module automatically includes <tt>Twitter::ClassUtilMixin</tt>
13
+ # features.
14
+ #
15
+ # The contract for models to use this mixin correctly is that the class
16
+ # including this mixin must provide an class method named <tt>attributes</tt>
17
+ # that will return an Array of attribute symbols that will be checked
18
+ # in #eql? override method. The following would be sufficient:
19
+ # def self.attributes; @@ATTRIBUTES; end
20
+ module ModelMixin #:nodoc:
21
+ def self.included(base) #:nodoc:
22
+ base.send(:include, Twitter::ClassUtilMixin)
23
+ base.send(:include, InstanceMethods)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ # Class methods defined for <tt>Twitter::ModelMixin</tt> module.
28
+ module ClassMethods #:nodoc:
29
+ # Unmarshal object singular or plural array of model objects
30
+ # from JSON serialization. Currently JSON is only supported
31
+ # since this is all <tt>Twitter4R</tt> needs.
32
+ def unmarshal(raw)
33
+ input = JSON.parse(raw)
34
+ def unmarshal_model(hash)
35
+ self.new(hash)
36
+ end
37
+ return unmarshal_model(input) if input.is_a?(Hash) # singular case
38
+ result = []
39
+ input.each do |hash|
40
+ model = unmarshal_model(hash) if hash.is_a?(Hash)
41
+ result << model
42
+ end if input.is_a?(Array)
43
+ result # plural case
44
+ end
45
+ end
46
+
47
+ # Instance methods defined for <tt>Twitter::ModelMixin</tt> module.
48
+ module InstanceMethods #:nodoc:
49
+ attr_accessor :client
50
+ # Equality method override of Object#eql? default.
51
+ #
52
+ # Relies on the class using this mixin to provide a <tt>attributes</tt>
53
+ # class method that will return an Array of attributes to check are
54
+ # equivalent in this #eql? override.
55
+ #
56
+ # It is by design that the #eql? method will raise a NoMethodError
57
+ # if no <tt>attributes</tt> class method exists, to alert you that
58
+ # you must provide it for a meaningful result from this #eql? override.
59
+ # Otherwise this will return a meaningless result.
60
+ def eql?(other)
61
+ attrs = self.class.attributes
62
+ attrs.each do |att|
63
+ return false unless self.send(att).eql?(other.send(att))
64
+ end
65
+ true
66
+ end
67
+
68
+ # Returns integer representation of model object instance.
69
+ #
70
+ # For example,
71
+ # status = Twitter::Status.new(:id => 234343)
72
+ # status.to_i #=> 234343
73
+ def to_i
74
+ @id
75
+ end
76
+
77
+ # Returns string representation of model object instance.
78
+ #
79
+ # For example,
80
+ # status = Twitter::Status.new(:text => 'my status message')
81
+ # status.to_s #=> 'my status message'
82
+ #
83
+ # If a model class doesn't have a @text attribute defined
84
+ # the default Object#to_s will be returned as the result.
85
+ def to_s
86
+ self.respond_to?(:text) ? @text : super.to_s
87
+ end
88
+
89
+ # Returns hash representation of model object instance.
90
+ #
91
+ # For example,
92
+ # u = Twitter::User.new(:id => 2342342, :screen_name => 'tony_blair_is_the_devil')
93
+ # u.to_hash #=> {:id => 2342342, :screen_name => 'tony_blair_is_the_devil'}
94
+ #
95
+ # This method also requires that the class method <tt>attributes</tt> be
96
+ # defined to return an Array of attributes for the class.
97
+ def to_hash
98
+ attrs = self.class.attributes
99
+ result = {}
100
+ attrs.each do |att|
101
+ value = self.send(att)
102
+ value = value.to_hash if value.respond_to?(:to_hash)
103
+ result[att] = value if value
104
+ end
105
+ result
106
+ end
107
+
108
+ # "Blesses" model object.
109
+ #
110
+ # Should be overridden by model class if special behavior is expected
111
+ #
112
+ # Expected to return blessed object (usually <tt>self</tt>)
113
+ def bless(client)
114
+ self.basic_bless(client)
115
+ end
116
+
117
+ protected
118
+ # Basic "blessing" of model object
119
+ def basic_bless(client)
120
+ self.client = client
121
+ self
122
+ end
123
+ end
124
+ end
125
+
126
+ module AuthenticatedUserMixin
127
+ def self.included(base)
128
+ base.send(:include, InstanceMethods)
129
+ end
130
+
131
+ module InstanceMethods
132
+ # Returns an Array of user objects that represents the authenticated
133
+ # user's friends on Twitter.
134
+ def followers(options = {})
135
+ @client.my(:followers, options)
136
+ end
137
+
138
+ # Adds given user as a friend. Returns user object as given by
139
+ # <tt>Twitter</tt> REST server response.
140
+ #
141
+ # For <tt>user</tt> argument you may pass in the unique integer
142
+ # user ID, screen name or Twitter::User object representation.
143
+ def befriend(user)
144
+ @client.friend(:add, user)
145
+ end
146
+
147
+ # Removes given user as a friend. Returns user object as given by
148
+ # <tt>Twitter</tt> REST server response.
149
+ #
150
+ # For <tt>user</tt> argument you may pass in the unique integer
151
+ # user ID, screen name or Twitter::User object representation.
152
+ def defriend(user)
153
+ @client.friend(:remove, user)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Represents a <tt>Twitter</tt> user
159
+ class User
160
+ include ModelMixin
161
+ @@ATTRIBUTES = [:id, :name, :description, :location, :screen_name, :url, :profile_image_url, :protected]
162
+ attr_accessor *@@ATTRIBUTES
163
+
164
+ class << self
165
+ # Used as factory method callback
166
+ def attributes; @@ATTRIBUTES; end
167
+
168
+ # Returns user model object with given <tt>id</tt> using the configuration
169
+ # and credentials of the <tt>client</tt> object passed in.
170
+ #
171
+ # You can pass in either the user's unique integer ID or the user's
172
+ # screen name.
173
+ def find(id, client)
174
+ client.user(id)
175
+ end
176
+ end
177
+
178
+ # Override of ModelMixin#bless method.
179
+ #
180
+ # Adds #followers instance method when user object represents
181
+ # authenticated user. Otherwise just do basic bless.
182
+ #
183
+ # This permits applications using <tt>Twitter4R</tt> to write
184
+ # Rubyish code like this:
185
+ # followers = user.followers if user.is_me?
186
+ # Or:
187
+ # followers = user.followers if user.respond_to?(:followers)
188
+ def bless(client)
189
+ basic_bless(client)
190
+ self.instance_eval(%{
191
+ self.class.send(:include, Twitter::AuthenticatedUserMixin)
192
+ }) if self.is_me? and not self.respond_to?(:followers)
193
+ self
194
+ end
195
+
196
+ # Returns whether this <tt>Twitter::User</tt> model object
197
+ # represents the authenticated user of the <tt>client</tt>
198
+ # that blessed it.
199
+ def is_me?
200
+ # TODO: Determine whether we should cache this or not?
201
+ # Might be dangerous to do so, but do we want to support
202
+ # the edge case where this would cause a problem? i.e.
203
+ # changing authenticated user after initial use of
204
+ # authenticated API.
205
+ # TBD: To cache or not to cache. That is the question!
206
+ # Since this is an implementation detail we can leave this for
207
+ # subsequent 0.2.x releases. It doesn't have to be decided before
208
+ # the 0.2.0 launch.
209
+ @screen_name == @client.instance_eval("@login")
210
+ end
211
+
212
+ # Returns an Array of user objects that represents the authenticated
213
+ # user's friends on Twitter.
214
+ def friends
215
+ @client.user(@id, :friends)
216
+ end
217
+ end # User
218
+
219
+ # Represents a status posted to <tt>Twitter</tt> by a <tt>Twitter</tt> user.
220
+ class Status
221
+ include ModelMixin
222
+ @@ATTRIBUTES = [:id, :text, :created_at, :user]
223
+ attr_accessor *@@ATTRIBUTES
224
+
225
+ class << self
226
+ # Used as factory method callback
227
+ def attributes; @@ATTRIBUTES; end
228
+
229
+ # Returns status model object with given <tt>status</tt> using the
230
+ # configuration and credentials of the <tt>client</tt> object passed in.
231
+ def find(id, client)
232
+ client.status(:get, id)
233
+ end
234
+
235
+ # Creates a new status for the authenticated user of the given
236
+ # <tt>client</tt> context.
237
+ #
238
+ # You MUST include a valid/authenticated <tt>client</tt> context
239
+ # in the given <tt>params</tt> argument.
240
+ #
241
+ # For example:
242
+ # status = Twitter::Status.create(
243
+ # :text => 'I am shopping for flip flops',
244
+ # :client => client)
245
+ #
246
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
247
+ # is given in the <tt>params</tt> Hash. For example,
248
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
249
+ # The above line of code will raise an <tt>ArgumentError</tt>.
250
+ #
251
+ # The same is true when you do not provide a <tt>:text</tt> key-value
252
+ # pair in the <tt>params</tt> argument given.
253
+ #
254
+ # The Twitter::Status object returned after the status successfully
255
+ # updates on the Twitter server side is returned from this method.
256
+ def create(params)
257
+ client, text = params[:client], params[:text]
258
+ raise ArgumentError, 'Valid client context must be provided' unless client.is_a?(Twitter::Client)
259
+ raise ArgumentError, 'Must provide text for the status to update' unless text.is_a?(String)
260
+ client.status(:post, text)
261
+ end
262
+ end
263
+
264
+ protected
265
+ # Constructor callback
266
+ def init
267
+ @user = User.new(@user) if @user.is_a?(Hash)
268
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
269
+ end
270
+ end # Status
271
+
272
+ # Represents a direct message on <tt>Twitter</tt> between <tt>Twitter</tt> users.
273
+ class Message
274
+ include ModelMixin
275
+ @@ATTRIBUTES = [:id, :recipient, :sender, :text, :created_at]
276
+ attr_accessor *@@ATTRIBUTES
277
+
278
+ class << self
279
+ # Used as factory method callback
280
+ def attributes; @@ATTRIBUTES; end
281
+
282
+ # Raises <tt>NotImplementedError</tt> because currently
283
+ # <tt>Twitter</tt> doesn't provide a facility to retrieve
284
+ # one message by unique ID.
285
+ def find(id, client)
286
+ raise NotImplementedError, 'Twitter has yet to implement a REST API for this. This is not a Twitter4R library limitation.'
287
+ end
288
+
289
+ # Creates a new direct message from the authenticated user of the
290
+ # given <tt>client</tt> context.
291
+ #
292
+ # You MUST include a valid/authenticated <tt>client</tt> context
293
+ # in the given <tt>params</tt> argument.
294
+ #
295
+ # For example:
296
+ # status = Twitter::Message.create(
297
+ # :text => 'I am shopping for flip flops',
298
+ # :receipient => 'anotherlogin',
299
+ # :client => client)
300
+ #
301
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
302
+ # is given in the <tt>params</tt> Hash. For example,
303
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
304
+ # The above line of code will raise an <tt>ArgumentError</tt>.
305
+ #
306
+ # The same is true when you do not provide any of the following
307
+ # key-value pairs in the <tt>params</tt> argument given:
308
+ # * <tt>text</tt> - the String that will be the message text to send to <tt>user</tt>
309
+ # * <tt>recipient</tt> - the user ID, screen_name or Twitter::User object representation of the recipient of the direct message
310
+ #
311
+ # The Twitter::Message object returned after the direct message is
312
+ # successfully sent on the Twitter server side is returned from
313
+ # this method.
314
+ def create(params)
315
+ client, text, recipient = params[:client], params[:text], params[:recipient]
316
+ raise ArgumentError, 'Valid client context must be given' unless client.is_a?(Twitter::Client)
317
+ raise ArgumentError, 'Message text must be supplied to send direct message' unless text.is_a?(String)
318
+ raise ArgumentError, 'Recipient user must be specified to send direct message' unless [Twitter::User, Integer, String].member?(recipient.class)
319
+ client.message(:post, text, recipient)
320
+ end
321
+ end
322
+
323
+ protected
324
+ # Constructor callback
325
+ def init
326
+ @sender = User.new(@sender) if @sender.is_a?(Hash)
327
+ @recipient = User.new(@recipient) if @recipient.is_a?(Hash)
328
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
329
+ end
330
+ end # Message
331
+
332
+ # RateLimitStatus provides information about how many requests you have left
333
+ # and when you can resume more requests if your remaining_hits count is zero.
334
+ class RateLimitStatus
335
+ include ModelMixin
336
+ @@ATTRIBUTES = [:remaining_hits, :hourly_limit, :reset_time_in_seconds, :reset_time]
337
+ attr_accessor *@@ATTRIBUTES
338
+
339
+ class << self
340
+ # Used as factory method callback
341
+ def attributes; @@ATTRIBUTES; end
342
+ end
343
+ end
344
+ end # Twitter