dferranti-twitter4r 0.4.0.1

Sign up to get free protection for your applications and to get access to all the features.
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 +96 -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 +51 -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 +360 -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 +68 -0
  40. data/spec/twitter/client/status_spec.rb +119 -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 +508 -0
  51. data/spec/twitter/version_spec.rb +19 -0
  52. metadata +115 -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, :error]
105
+ attr_accessor :code, :message, :uri, :error
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,360 @@
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
+ def reply(status)
277
+ client.status(:reply, :status => status, :in_reply_to_status_id => @id)
278
+ end
279
+
280
+ protected
281
+ # Constructor callback
282
+ def init
283
+ @user = User.new(@user) if @user.is_a?(Hash)
284
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
285
+ end
286
+ end # Status
287
+
288
+ # Represents a direct message on <tt>Twitter</tt> between <tt>Twitter</tt> users.
289
+ class Message
290
+ include ModelMixin
291
+ @@ATTRIBUTES = [:id, :recipient, :sender, :text, :created_at]
292
+ attr_accessor *@@ATTRIBUTES
293
+
294
+ class << self
295
+ # Used as factory method callback
296
+ def attributes; @@ATTRIBUTES; end
297
+
298
+ # Raises <tt>NotImplementedError</tt> because currently
299
+ # <tt>Twitter</tt> doesn't provide a facility to retrieve
300
+ # one message by unique ID.
301
+ def find(id, client)
302
+ raise NotImplementedError, 'Twitter has yet to implement a REST API for this. This is not a Twitter4R library limitation.'
303
+ end
304
+
305
+ # Creates a new direct message from the authenticated user of the
306
+ # given <tt>client</tt> context.
307
+ #
308
+ # You MUST include a valid/authenticated <tt>client</tt> context
309
+ # in the given <tt>params</tt> argument.
310
+ #
311
+ # For example:
312
+ # status = Twitter::Message.create(
313
+ # :text => 'I am shopping for flip flops',
314
+ # :receipient => 'anotherlogin',
315
+ # :client => client)
316
+ #
317
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
318
+ # is given in the <tt>params</tt> Hash. For example,
319
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
320
+ # The above line of code will raise an <tt>ArgumentError</tt>.
321
+ #
322
+ # The same is true when you do not provide any of the following
323
+ # key-value pairs in the <tt>params</tt> argument given:
324
+ # * <tt>text</tt> - the String that will be the message text to send to <tt>user</tt>
325
+ # * <tt>recipient</tt> - the user ID, screen_name or Twitter::User object representation of the recipient of the direct message
326
+ #
327
+ # The Twitter::Message object returned after the direct message is
328
+ # successfully sent on the Twitter server side is returned from
329
+ # this method.
330
+ def create(params)
331
+ client, text, recipient = params[:client], params[:text], params[:recipient]
332
+ raise ArgumentError, 'Valid client context must be given' unless client.is_a?(Twitter::Client)
333
+ raise ArgumentError, 'Message text must be supplied to send direct message' unless text.is_a?(String)
334
+ raise ArgumentError, 'Recipient user must be specified to send direct message' unless [Twitter::User, Integer, String].member?(recipient.class)
335
+ client.message(:post, text, recipient)
336
+ end
337
+ end
338
+
339
+ protected
340
+ # Constructor callback
341
+ def init
342
+ @sender = User.new(@sender) if @sender.is_a?(Hash)
343
+ @recipient = User.new(@recipient) if @recipient.is_a?(Hash)
344
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
345
+ end
346
+ end # Message
347
+
348
+ # RateLimitStatus provides information about how many requests you have left
349
+ # and when you can resume more requests if your remaining_hits count is zero.
350
+ class RateLimitStatus
351
+ include ModelMixin
352
+ @@ATTRIBUTES = [:remaining_hits, :hourly_limit, :reset_time_in_seconds, :reset_time]
353
+ attr_accessor *@@ATTRIBUTES
354
+
355
+ class << self
356
+ # Used as factory method callback
357
+ def attributes; @@ATTRIBUTES; end
358
+ end
359
+ end
360
+ end # Twitter