mbbx6spp-twitter4r 0.3.1

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.
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