mbbx6spp-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/client/account.rb +24 -0
  6. data/lib/twitter/client/auth.rb +27 -0
  7. data/lib/twitter/client/base.rb +87 -0
  8. data/lib/twitter/client/blocks.rb +35 -0
  9. data/lib/twitter/client/favorites.rb +53 -0
  10. data/lib/twitter/client/friendship.rb +35 -0
  11. data/lib/twitter/client/messaging.rb +79 -0
  12. data/lib/twitter/client/status.rb +46 -0
  13. data/lib/twitter/client/timeline.rb +72 -0
  14. data/lib/twitter/client/user.rb +65 -0
  15. data/lib/twitter/client.rb +21 -0
  16. data/lib/twitter/config.rb +71 -0
  17. data/lib/twitter/console.rb +28 -0
  18. data/lib/twitter/core.rb +137 -0
  19. data/lib/twitter/ext/stdlib.rb +51 -0
  20. data/lib/twitter/ext.rb +2 -0
  21. data/lib/twitter/extras.rb +39 -0
  22. data/lib/twitter/meta.rb +56 -0
  23. data/lib/twitter/model.rb +348 -0
  24. data/lib/twitter/rails.rb +92 -0
  25. data/lib/twitter/version.rb +19 -0
  26. data/lib/twitter.rb +31 -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 +108 -0
@@ -0,0 +1,348 @@
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, :in_reply_to_status_id]
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
+ def reply?
265
+ !!@in_reply_to_status_id
266
+ end
267
+
268
+ protected
269
+ # Constructor callback
270
+ def init
271
+ @user = User.new(@user) if @user.is_a?(Hash)
272
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
273
+ end
274
+ end # Status
275
+
276
+ # Represents a direct message on <tt>Twitter</tt> between <tt>Twitter</tt> users.
277
+ class Message
278
+ include ModelMixin
279
+ @@ATTRIBUTES = [:id, :recipient, :sender, :text, :created_at]
280
+ attr_accessor *@@ATTRIBUTES
281
+
282
+ class << self
283
+ # Used as factory method callback
284
+ def attributes; @@ATTRIBUTES; end
285
+
286
+ # Raises <tt>NotImplementedError</tt> because currently
287
+ # <tt>Twitter</tt> doesn't provide a facility to retrieve
288
+ # one message by unique ID.
289
+ def find(id, client)
290
+ raise NotImplementedError, 'Twitter has yet to implement a REST API for this. This is not a Twitter4R library limitation.'
291
+ end
292
+
293
+ # Creates a new direct message from the authenticated user of the
294
+ # given <tt>client</tt> context.
295
+ #
296
+ # You MUST include a valid/authenticated <tt>client</tt> context
297
+ # in the given <tt>params</tt> argument.
298
+ #
299
+ # For example:
300
+ # status = Twitter::Message.create(
301
+ # :text => 'I am shopping for flip flops',
302
+ # :receipient => 'anotherlogin',
303
+ # :client => client)
304
+ #
305
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
306
+ # is given in the <tt>params</tt> Hash. For example,
307
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
308
+ # The above line of code will raise an <tt>ArgumentError</tt>.
309
+ #
310
+ # The same is true when you do not provide any of the following
311
+ # key-value pairs in the <tt>params</tt> argument given:
312
+ # * <tt>text</tt> - the String that will be the message text to send to <tt>user</tt>
313
+ # * <tt>recipient</tt> - the user ID, screen_name or Twitter::User object representation of the recipient of the direct message
314
+ #
315
+ # The Twitter::Message object returned after the direct message is
316
+ # successfully sent on the Twitter server side is returned from
317
+ # this method.
318
+ def create(params)
319
+ client, text, recipient = params[:client], params[:text], params[:recipient]
320
+ raise ArgumentError, 'Valid client context must be given' unless client.is_a?(Twitter::Client)
321
+ raise ArgumentError, 'Message text must be supplied to send direct message' unless text.is_a?(String)
322
+ raise ArgumentError, 'Recipient user must be specified to send direct message' unless [Twitter::User, Integer, String].member?(recipient.class)
323
+ client.message(:post, text, recipient)
324
+ end
325
+ end
326
+
327
+ protected
328
+ # Constructor callback
329
+ def init
330
+ @sender = User.new(@sender) if @sender.is_a?(Hash)
331
+ @recipient = User.new(@recipient) if @recipient.is_a?(Hash)
332
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
333
+ end
334
+ end # Message
335
+
336
+ # RateLimitStatus provides information about how many requests you have left
337
+ # and when you can resume more requests if your remaining_hits count is zero.
338
+ class RateLimitStatus
339
+ include ModelMixin
340
+ @@ATTRIBUTES = [:remaining_hits, :hourly_limit, :reset_time_in_seconds, :reset_time]
341
+ attr_accessor *@@ATTRIBUTES
342
+
343
+ class << self
344
+ # Used as factory method callback
345
+ def attributes; @@ATTRIBUTES; end
346
+ end
347
+ end
348
+ end # Twitter
@@ -0,0 +1,92 @@
1
+ # File that contains extensions to the Twitter4R library directly related to providing
2
+ # seamless Rails integration.
3
+
4
+ if gem_present?('rails', '<= 1.2.6')
5
+
6
+ require('active_record/xml_serialization')
7
+ require('active_support')
8
+
9
+ # Extend +String+ with +#xmlize+ method for convenience.
10
+ class String
11
+ def xmlize
12
+ # Forget module/namespace for now as this is totally broken in Rails 1.2.x
13
+ # (search for namespaced models on the Rails Trac site)
14
+ cls = self.split('::').pop
15
+ cls.dasherize.downcase
16
+ end
17
+ end
18
+
19
+ # Parent mixin module that defines +InstanceMethods+ for Twitter4R model classes.
20
+ #
21
+ # Defines/overrides the following methods for:
22
+ # * Twitter::RailsPatch::InstanceMethods#to_param
23
+ # * Twitter::RailsPatch::InstanceMethods#to_xml
24
+ # * Twitter::RailsPatch::InstanceMethods#to_json
25
+ module Twitter::RailsPatch
26
+ class << self
27
+ def included(base)
28
+ base.send(:include, InstanceMethods)
29
+ base.extend ClassMethods
30
+ end
31
+
32
+ module ClassMethods
33
+ # Dummy method to satisfy ActiveRecord's XmlSerializer.
34
+ def inheritance_column
35
+ nil
36
+ end
37
+
38
+ # Returns Hash of name-column pairs
39
+ def columns_hash
40
+ {}
41
+ end
42
+ end
43
+
44
+ module InstanceMethods
45
+ # Returns an Array of attribute names of the model
46
+ def attribute_names
47
+ self.class.class_eval("@@ATTRIBUTES").collect {|att| att.to_s }
48
+ end
49
+
50
+ # Override default +#to_param+ implementation.
51
+ def to_param
52
+ self.id.to_s
53
+ end
54
+
55
+ # Returns XML representation of model.
56
+ def to_xml(options = {})
57
+ ActiveRecord::XmlSerializer.new(self, options.merge(:root => self.class.name.xmlize)).to_s
58
+ end
59
+
60
+ # Returns JSON representation of model.
61
+ #
62
+ # The current implementation basically ignoring +options+, so reopen and override
63
+ # this implementation or live with it for the moment:)
64
+ def to_json(options = {})
65
+ JSON.unparse(self.to_hash)
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ class Twitter::User
72
+ include Twitter::RailsPatch
73
+ end
74
+
75
+ class Twitter::Status
76
+ include Twitter::RailsPatch
77
+ end
78
+
79
+ class Twitter::Message
80
+ include Twitter::RailsPatch
81
+ end
82
+
83
+ class Twitter::RESTError
84
+ include Twitter::RailsPatch
85
+ alias :id :code
86
+ def to_hash
87
+ { :code => @code, :message => @message, :uri => @uri }
88
+ end
89
+ end
90
+ else
91
+ warn("WARNING: Twiter4R patch for ActiveRecord defects only supported for Rails 1.2.x currently")
92
+ end
@@ -0,0 +1,19 @@
1
+ # version.rb contains <tt>Twitter::Version</tt> that provides helper
2
+ # methods related to versioning of the <tt>Twitter4R</tt> project.
3
+
4
+ module Twitter::Version #:nodoc:
5
+ MAJOR = 0
6
+ MINOR = 3
7
+ REVISION = 1
8
+ class << self
9
+ # Returns X.Y.Z formatted version string
10
+ def to_version
11
+ "#{MAJOR}.#{MINOR}.#{REVISION}"
12
+ end
13
+
14
+ # Returns X-Y-Z formatted version name
15
+ def to_name
16
+ "#{MAJOR}_#{MINOR}_#{REVISION}"
17
+ end
18
+ end
19
+ end
data/lib/twitter.rb ADDED
@@ -0,0 +1,31 @@
1
+ #
2
+
3
+ module Twitter; end
4
+
5
+ def require_local(suffix)
6
+ require(File.expand_path(File.join(File.dirname(__FILE__), suffix)))
7
+ end
8
+
9
+ # For better unicode support
10
+ $KCODE = 'u'
11
+ require 'jcode'
12
+
13
+ # External requires
14
+ require('yaml')
15
+ require('date')
16
+ require('time')
17
+ require('net/https')
18
+ require('uri')
19
+ require('cgi')
20
+ require('json')
21
+ require('yaml')
22
+
23
+ # Ordering matters...pay attention here!
24
+ require_local('twitter/ext')
25
+ require_local('twitter/version')
26
+ require_local('twitter/meta')
27
+ require_local('twitter/core')
28
+ require_local('twitter/model')
29
+ require_local('twitter/config')
30
+ require_local('twitter/client')
31
+ require_local('twitter/console')
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+
3
+ describe Twitter::Client, "#authenticate?" do
4
+ before(:each) do
5
+ @uri = '/account/verify_credentials.json'
6
+ @request = mas_net_http_get(:basic_auth => nil)
7
+ @twitter = client_context
8
+ @default_header = @twitter.send(:http_header)
9
+ @response = mas_net_http_response(:success)
10
+ @error_response = mas_net_http_response(404, "Resource Not Found")
11
+ @connection = mas_net_http(@response)
12
+ Net::HTTP.stub!(:new).and_return(@connection)
13
+ @login = "applestillsucks"
14
+ @password = "linuxstillrocks"
15
+ end
16
+
17
+ it "creates expected HTTP GET request" do
18
+ @twitter.should_receive(:create_http_get_request).with(@uri).and_return(@request)
19
+ @twitter.authenticate?(@login, @password)
20
+ end
21
+
22
+ it "should return true if HTTP response is 20X" do
23
+ @twitter.authenticate?(@login, @password).should be(true)
24
+ end
25
+
26
+ it "should return false if HTTP response is not 20X" do
27
+ Net::HTTP.stub!(:new).and_return(mas_net_http(@error_response))
28
+ @twitter.authenticate?(@login, @password).should be(false)
29
+ end
30
+
31
+ after(:each) do
32
+ nilize(@uri, @request, @twitter, @default_header, @response, @error_response, @connection, @login, @password)
33
+ end
34
+ end