craigtmackenzie-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 (52) hide show
  1. data/CHANGES +129 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +37 -0
  4. data/TODO +7 -0
  5. data/bin/t4rsh +80 -0
  6. data/lib/twitter.rb +34 -0
  7. data/lib/twitter/client.rb +24 -0
  8. data/lib/twitter/client/account.rb +24 -0
  9. data/lib/twitter/client/auth.rb +27 -0
  10. data/lib/twitter/client/base.rb +93 -0
  11. data/lib/twitter/client/blocks.rb +35 -0
  12. data/lib/twitter/client/favorites.rb +53 -0
  13. data/lib/twitter/client/friendship.rb +35 -0
  14. data/lib/twitter/client/graph.rb +37 -0
  15. data/lib/twitter/client/messaging.rb +79 -0
  16. data/lib/twitter/client/profile.rb +29 -0
  17. data/lib/twitter/client/search.rb +27 -0
  18. data/lib/twitter/client/status.rb +46 -0
  19. data/lib/twitter/client/timeline.rb +72 -0
  20. data/lib/twitter/client/user.rb +65 -0
  21. data/lib/twitter/config.rb +77 -0
  22. data/lib/twitter/console.rb +31 -0
  23. data/lib/twitter/core.rb +137 -0
  24. data/lib/twitter/ext.rb +2 -0
  25. data/lib/twitter/ext/stdlib.rb +52 -0
  26. data/lib/twitter/extras.rb +39 -0
  27. data/lib/twitter/meta.rb +56 -0
  28. data/lib/twitter/model.rb +356 -0
  29. data/lib/twitter/version.rb +19 -0
  30. data/spec/twitter/client/account_spec.rb +28 -0
  31. data/spec/twitter/client/auth_spec.rb +34 -0
  32. data/spec/twitter/client/base_spec.rb +242 -0
  33. data/spec/twitter/client/blocks_spec.rb +76 -0
  34. data/spec/twitter/client/favorites_spec.rb +183 -0
  35. data/spec/twitter/client/friendship_spec.rb +76 -0
  36. data/spec/twitter/client/graph_spec.rb +67 -0
  37. data/spec/twitter/client/messaging_spec.rb +135 -0
  38. data/spec/twitter/client/profile_spec.rb +91 -0
  39. data/spec/twitter/client/search_spec.rb +24 -0
  40. data/spec/twitter/client/status_spec.rb +92 -0
  41. data/spec/twitter/client/timeline_spec.rb +79 -0
  42. data/spec/twitter/client/user_spec.rb +203 -0
  43. data/spec/twitter/client_spec.rb +2 -0
  44. data/spec/twitter/config_spec.rb +86 -0
  45. data/spec/twitter/console_spec.rb +15 -0
  46. data/spec/twitter/core_spec.rb +127 -0
  47. data/spec/twitter/ext/stdlib_spec.rb +59 -0
  48. data/spec/twitter/extras_spec.rb +46 -0
  49. data/spec/twitter/meta_spec.rb +90 -0
  50. data/spec/twitter/model_spec.rb +487 -0
  51. data/spec/twitter/version_spec.rb +19 -0
  52. metadata +114 -0
@@ -0,0 +1,77 @@
1
+ # config.rb contains classes, methods and extends existing Twitter4R classes
2
+ # to provide easy configuration facilities.
3
+
4
+ module Twitter
5
+ # Represents global configuration for Twitter::Client.
6
+ # Can override the following configuration options:
7
+ # * <tt>protocol</tt> - <tt>:http</tt>, <tt>:https</tt> or <tt>:ssl</tt> supported. <tt>:ssl</tt> is an alias for <tt>:https</tt>. Defaults to <tt>:ssl</tt>
8
+ # * <tt>host</tt> - hostname to connect to for the Twitter service. Defaults to <tt>'twitter.com'</tt>.
9
+ # * <tt>port</tt> - port to connect to for the Twitter service. Defaults to <tt>443</tt>.
10
+ # * <tt>proxy_host</tt> - proxy host to use. Defaults to nil.
11
+ # * <tt>proxy_port</tt> - proxy host to use. Defaults to nil.
12
+ # * <tt>proxy_user</tt> - proxy username to use. Defaults to nil.
13
+ # * <tt>proxy_pass</tt> - proxy password to use. Defaults to nil.
14
+ # * <tt>user_agent</tt> - user agent string to use for each request of the HTTP header.
15
+ # * <tt>application_name</tt> - name of your client application. Defaults to 'Twitter4R'
16
+ # * <tt>application_version</tt> - version of your client application. Defaults to current <tt>Twitter::Version.to_version</tt>.
17
+ # * <tt>application_url</tt> - URL of your client application. Defaults to http://twitter4r.rubyforge.org.
18
+ # * <tt>source</tt> - the source id given to you by Twitter to identify your application in their web interface. Note: you must contact Twitter.com developer directly so they can configure their servers appropriately.
19
+ class Config
20
+ include ClassUtilMixin
21
+ @@ATTRIBUTES = [
22
+ :protocol,
23
+ :host,
24
+ :port,
25
+ :search_protocol,
26
+ :search_host,
27
+ :search_port,
28
+ :proxy_host,
29
+ :proxy_port,
30
+ :proxy_user,
31
+ :proxy_pass,
32
+ :user_agent,
33
+ :application_name,
34
+ :application_version,
35
+ :application_url,
36
+ :source,
37
+ ]
38
+ attr_accessor *@@ATTRIBUTES
39
+
40
+ # Override of Object#eql? to ensure RSpec specifications run
41
+ # correctly. Also done to follow Ruby best practices.
42
+ def eql?(other)
43
+ return true if self == other
44
+ @@ATTRIBUTES.each do |att|
45
+ return false unless self.send(att).eql?(other.send(att))
46
+ end
47
+ true
48
+ end
49
+ end
50
+
51
+ class Client
52
+ @@defaults = { :host => 'twitter.com',
53
+ :port => 443,
54
+ :protocol => :ssl,
55
+ :search_host => 'search.twitter.com',
56
+ :search_port => 80,
57
+ :search_protocol => :http,
58
+ :proxy_host => nil,
59
+ :proxy_port => nil,
60
+ :user_agent => "default",
61
+ :application_name => 'Twitter4R',
62
+ :application_version => Twitter::Version.to_version,
63
+ :application_url => 'http://twitter4r.rubyforge.org',
64
+ :source => 'twitter4r',
65
+ }
66
+ @@config = Twitter::Config.new(@@defaults)
67
+
68
+ # Twitter::Client class methods
69
+ class << self
70
+ # Yields to given <tt>block</tt> to configure the Twitter4R API.
71
+ def configure(&block)
72
+ raise ArgumentError, "Block must be provided to configure" unless block_given?
73
+ yield @@config
74
+ end # configure
75
+ end # class << self
76
+ end # Client class
77
+ end # Twitter module
@@ -0,0 +1,31 @@
1
+ # Contains hooks for the twitter console
2
+
3
+ require('optparse')
4
+
5
+ module Twitter
6
+ class Client
7
+ class << self
8
+ # Helper method mostly for irb shell prototyping.
9
+ #
10
+ # Reads in login/password Twitter credentials from YAML file
11
+ # found at the location given by <tt>config_file</tt> that has
12
+ # the following format:
13
+ # envname:
14
+ # login: mytwitterlogin
15
+ # password: mytwitterpassword
16
+ #
17
+ # Where <tt>envname</tt> is the name of the environment like 'test',
18
+ # 'dev' or 'prod'. The <tt>env</tt> argument defaults to 'test'.
19
+ #
20
+ # To use this in the shell you would do something like the following
21
+ # examples:
22
+ # twitter = Twitter::Client.from_config('config/twitter.yml', 'dev')
23
+ # twitter = Twitter::Client.from_config('config/twitter.yml')
24
+ def from_config(config_file, env = 'test')
25
+ yaml_hash = YAML.load(File.read(config_file))
26
+ self.new yaml_hash[env]
27
+ end
28
+ end # class << self
29
+ end
30
+ end
31
+
@@ -0,0 +1,137 @@
1
+ # The Twitter4R API provides a nicer Ruby object API to work with
2
+ # instead of coding around the REST API.
3
+
4
+ # Module to encapsule the Twitter4R API.
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
14
+ module ClassUtilMixin #:nodoc:
15
+ def self.included(base) #:nodoc:
16
+ base.send(:include, InstanceMethods)
17
+ end
18
+
19
+ # Instance methods defined for <tt>Twitter::ModelMixin</tt> module.
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).
71
+ def initialize(params = {})
72
+ params.each do |key,val|
73
+ self.send("#{key}=", val) if self.respond_to? key
74
+ end
75
+ self.send(:init) if self.respond_to? :init
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
84
+ end
85
+ end # ClassUtilMixin
86
+
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
102
+ class RESTError < RuntimeError
103
+ include ClassUtilMixin
104
+ @@ATTRIBUTES = [:code, :message, :uri]
105
+ attr_accessor :code, :message, :uri
106
+
107
+ # Returns string in following format:
108
+ # "HTTP #{@code}: #{@message} at #{@uri}"
109
+ # For example,
110
+ # "HTTP 404: Resource Not Found at /i_am_crap.json"
111
+ def to_s
112
+ "HTTP #{@code}: #{@message} at #{@uri}"
113
+ end
114
+ end # RESTError
115
+
116
+ # Remote REST API interface representation
117
+ #
118
+ class RESTInterfaceSpec
119
+ include ClassUtilMixin
120
+
121
+ end
122
+
123
+ # Remote REST API method representation
124
+ #
125
+ class RESTMethodSpec
126
+ include ClassUtilMixin
127
+ attr_accessor :uri, :method, :parameters
128
+ end
129
+
130
+ # Remote REST API method parameter representation
131
+ #
132
+ class RESTParameterSpec
133
+ include ClassUtilMixin
134
+ attr_accessor :name, :type, :required
135
+ def required?; @required; end
136
+ end
137
+ end
@@ -0,0 +1,2 @@
1
+
2
+ require_local('twitter/ext/stdlib')
@@ -0,0 +1,52 @@
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 = !!(version ? gem(gem_name, version) : gem(gem_name))
46
+ rescue Gem::LoadError => le
47
+ present = false
48
+ warn("Gem load error: Couldn't load #{gem_name} #{version ? "with version requirement #{version}: #{le.to_s}": ""}")
49
+ end
50
+ present
51
+ end
52
+ 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,356 @@
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,
162
+ :protected, :profile_image_url, :profile_background_color,
163
+ :profile_text_color, :profile_link_color, :profile_sidebar_fill_color,
164
+ :profile_sidebar_border_color, :profile_background_image_url,
165
+ :profile_background_tile, :utc_offset, :time_zone,
166
+ :following, :notifications, :favourites_count, :followers_count,
167
+ :friends_count, :statuses_count, :created_at, ]
168
+ attr_accessor *@@ATTRIBUTES
169
+
170
+ class << self
171
+ # Used as factory method callback
172
+ def attributes; @@ATTRIBUTES; end
173
+
174
+ # Returns user model object with given <tt>id</tt> using the configuration
175
+ # and credentials of the <tt>client</tt> object passed in.
176
+ #
177
+ # You can pass in either the user's unique integer ID or the user's
178
+ # screen name.
179
+ def find(id, client)
180
+ client.user(id)
181
+ end
182
+ end
183
+
184
+ # Override of ModelMixin#bless method.
185
+ #
186
+ # Adds #followers instance method when user object represents
187
+ # authenticated user. Otherwise just do basic bless.
188
+ #
189
+ # This permits applications using <tt>Twitter4R</tt> to write
190
+ # Rubyish code like this:
191
+ # followers = user.followers if user.is_me?
192
+ # Or:
193
+ # followers = user.followers if user.respond_to?(:followers)
194
+ def bless(client)
195
+ basic_bless(client)
196
+ self.instance_eval(%{
197
+ self.class.send(:include, Twitter::AuthenticatedUserMixin)
198
+ }) if self.is_me? and not self.respond_to?(:followers)
199
+ self
200
+ end
201
+
202
+ # Returns whether this <tt>Twitter::User</tt> model object
203
+ # represents the authenticated user of the <tt>client</tt>
204
+ # that blessed it.
205
+ def is_me?
206
+ # TODO: Determine whether we should cache this or not?
207
+ # Might be dangerous to do so, but do we want to support
208
+ # the edge case where this would cause a problem? i.e.
209
+ # changing authenticated user after initial use of
210
+ # authenticated API.
211
+ # TBD: To cache or not to cache. That is the question!
212
+ # Since this is an implementation detail we can leave this for
213
+ # subsequent 0.2.x releases. It doesn't have to be decided before
214
+ # the 0.2.0 launch.
215
+ @screen_name == @client.instance_eval("@login")
216
+ end
217
+
218
+ # Returns an Array of user objects that represents the authenticated
219
+ # user's friends on Twitter.
220
+ def friends
221
+ @client.user(@id, :friends)
222
+ end
223
+ end # User
224
+
225
+ # Represents a status posted to <tt>Twitter</tt> by a <tt>Twitter</tt> user.
226
+ class Status
227
+ include ModelMixin
228
+ @@ATTRIBUTES = [:id, :text, :source, :truncated, :created_at, :user,
229
+ :favorited, :in_reply_to_status_id, :in_reply_to_user_id,
230
+ :in_reply_to_screen_name]
231
+ attr_accessor *@@ATTRIBUTES
232
+
233
+ class << self
234
+ # Used as factory method callback
235
+ def attributes; @@ATTRIBUTES; end
236
+
237
+ # Returns status model object with given <tt>status</tt> using the
238
+ # configuration and credentials of the <tt>client</tt> object passed in.
239
+ def find(id, client)
240
+ client.status(:get, id)
241
+ end
242
+
243
+ # Creates a new status for the authenticated user of the given
244
+ # <tt>client</tt> context.
245
+ #
246
+ # You MUST include a valid/authenticated <tt>client</tt> context
247
+ # in the given <tt>params</tt> argument.
248
+ #
249
+ # For example:
250
+ # status = Twitter::Status.create(
251
+ # :text => 'I am shopping for flip flops',
252
+ # :client => client)
253
+ #
254
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
255
+ # is given in the <tt>params</tt> Hash. For example,
256
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
257
+ # The above line of code will raise an <tt>ArgumentError</tt>.
258
+ #
259
+ # The same is true when you do not provide a <tt>:text</tt> key-value
260
+ # pair in the <tt>params</tt> argument given.
261
+ #
262
+ # The Twitter::Status object returned after the status successfully
263
+ # updates on the Twitter server side is returned from this method.
264
+ def create(params)
265
+ client, text = params[:client], params[:text]
266
+ raise ArgumentError, 'Valid client context must be provided' unless client.is_a?(Twitter::Client)
267
+ raise ArgumentError, 'Must provide text for the status to update' unless text.is_a?(String)
268
+ client.status(:post, text)
269
+ end
270
+ end
271
+
272
+ def reply?
273
+ !!@in_reply_to_status_id
274
+ end
275
+
276
+ protected
277
+ # Constructor callback
278
+ def init
279
+ @user = User.new(@user) if @user.is_a?(Hash)
280
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
281
+ end
282
+ end # Status
283
+
284
+ # Represents a direct message on <tt>Twitter</tt> between <tt>Twitter</tt> users.
285
+ class Message
286
+ include ModelMixin
287
+ @@ATTRIBUTES = [:id, :recipient, :sender, :text, :created_at]
288
+ attr_accessor *@@ATTRIBUTES
289
+
290
+ class << self
291
+ # Used as factory method callback
292
+ def attributes; @@ATTRIBUTES; end
293
+
294
+ # Raises <tt>NotImplementedError</tt> because currently
295
+ # <tt>Twitter</tt> doesn't provide a facility to retrieve
296
+ # one message by unique ID.
297
+ def find(id, client)
298
+ raise NotImplementedError, 'Twitter has yet to implement a REST API for this. This is not a Twitter4R library limitation.'
299
+ end
300
+
301
+ # Creates a new direct message from the authenticated user of the
302
+ # given <tt>client</tt> context.
303
+ #
304
+ # You MUST include a valid/authenticated <tt>client</tt> context
305
+ # in the given <tt>params</tt> argument.
306
+ #
307
+ # For example:
308
+ # status = Twitter::Message.create(
309
+ # :text => 'I am shopping for flip flops',
310
+ # :receipient => 'anotherlogin',
311
+ # :client => client)
312
+ #
313
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
314
+ # is given in the <tt>params</tt> Hash. For example,
315
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
316
+ # The above line of code will raise an <tt>ArgumentError</tt>.
317
+ #
318
+ # The same is true when you do not provide any of the following
319
+ # key-value pairs in the <tt>params</tt> argument given:
320
+ # * <tt>text</tt> - the String that will be the message text to send to <tt>user</tt>
321
+ # * <tt>recipient</tt> - the user ID, screen_name or Twitter::User object representation of the recipient of the direct message
322
+ #
323
+ # The Twitter::Message object returned after the direct message is
324
+ # successfully sent on the Twitter server side is returned from
325
+ # this method.
326
+ def create(params)
327
+ client, text, recipient = params[:client], params[:text], params[:recipient]
328
+ raise ArgumentError, 'Valid client context must be given' unless client.is_a?(Twitter::Client)
329
+ raise ArgumentError, 'Message text must be supplied to send direct message' unless text.is_a?(String)
330
+ raise ArgumentError, 'Recipient user must be specified to send direct message' unless [Twitter::User, Integer, String].member?(recipient.class)
331
+ client.message(:post, text, recipient)
332
+ end
333
+ end
334
+
335
+ protected
336
+ # Constructor callback
337
+ def init
338
+ @sender = User.new(@sender) if @sender.is_a?(Hash)
339
+ @recipient = User.new(@recipient) if @recipient.is_a?(Hash)
340
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
341
+ end
342
+ end # Message
343
+
344
+ # RateLimitStatus provides information about how many requests you have left
345
+ # and when you can resume more requests if your remaining_hits count is zero.
346
+ class RateLimitStatus
347
+ include ModelMixin
348
+ @@ATTRIBUTES = [:remaining_hits, :hourly_limit, :reset_time_in_seconds, :reset_time]
349
+ attr_accessor *@@ATTRIBUTES
350
+
351
+ class << self
352
+ # Used as factory method callback
353
+ def attributes; @@ATTRIBUTES; end
354
+ end
355
+ end
356
+ end # Twitter