bloveless_grackle 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ == 0.1.10 (2010-6-13)
2
+ * Changed :v1 (api.twitter.com/1) to be the default API instead of REST. :rest is now deprecated
3
+ * Fixed issue with DELETE requests not being able to have form encoded body parameters. This was causing the list membership delete method to fail. As a side note, it appears that Twitter is actually violating the HTTP 1.1 spec on this one since RFC 2616 states that a DELETE "requests that the origin server delete the resource identified by the Request-URI" (note no mention of any "enclosed entity").
4
+
5
+ == 0.1.9 (2010-2-28)
6
+ * Added support for a boolean option called auto_append_ids that controls whether parameters named "id" are automatically appended to the URL or used as request parameters. It's true by default so there's no change to existing behavior unless you explicitly set it to false.
7
+ * Updated the README with changes describing how to use custom handlers (thanks akahn!)
8
+
9
+ == 0.1.8 (2010-2-2)
10
+ * Added proxy support to Grackle::Transport via proxy attribute. Can specify a Proc that returns a Net::HTTP subclass or a Net::HTTP subclass directly. Typically this will be from Net::HTTP.Proxy.
11
+
12
+ == 0.1.7 (2009-12-13)
13
+ * Fixed a bug in Ruby 1.9 where the client was incorrectly handling request method parameters
14
+ * Fixed a bug in the _ method that wasn't correctly appending numeric path elements
15
+ * Provided a way to specify SSL Certificate Authority certs for correct SSL validation
16
+ * The only thing that doesn't work in 1.9 in this version is OAuth because that gem is not up to date
17
+
18
+ == 0.1.6 (2009-10-29)
19
+ * Added support for HTTP methods beside GET and POST using block syntax
20
+ * Added method for appending to the request path something that's not a valid ruby method
21
+
22
+ == 0.1.5 (2009-10-28)
23
+ Added support for the Twitter version 1 API as a API symbol :v1
24
+
25
+ == 0.1.4 (2009-08-09)
26
+ Added additional check for 3xx responses that don't have location headers.
27
+
28
+ == 0.1.3 (2009-08-09)
29
+ Merged in changes from gotwalt for timeouts and redirects. Revised 30x
30
+ redirect handling to support a limit that prevents infinite redirects.
31
+
32
+ == 0.1.2 (2009-05-11)
33
+ Changed :site param used by OAuth to be determined dynamically unless
34
+ explicitly specified as part of the :auth param to the Client constructor.
35
+ This param needs to match the scheme and authority of the request using
36
+ OAuth or the signing will not validate.
37
+
38
+ == 0.1.1 (2009-05-10)
39
+ Fixed issue where SSL setting wasn't being applied correctly to Net:HTTP
40
+ which was preventing SSL-enabled requests from working correctly.
41
+
42
+ == 0.1.0 (2009-04-12)
43
+ * Added OAuth authentication
44
+ * Deprecated :username and :password Grackle::Client constructor params
45
+ * Changed multipart upload implementation and removed dependency on httpclient gem
46
+ * Added dependency on mime-types gem
@@ -0,0 +1,217 @@
1
+ =grackle
2
+ by Hayes Davis
3
+ - http://twitter.com/hayesdavis
4
+ - hayes [at] unionmetrics [dot] com
5
+ - http://unionmetrics.com
6
+ - http://hayesdavis.net
7
+
8
+ == DESCRIPTION
9
+ Grackle is a lightweight Ruby wrapper around the Twitter REST and Search APIs.
10
+ It's based on my experience using the Twitter API to build http://cheaptweet.com
11
+ and http://tweetreach.com. The main goal of Grackle is to never require a
12
+ release when the Twitter API changes (which it often does) or in the face of a
13
+ particular Twitter API bug. As such it's somewhat different from other Twitter
14
+ API libraries. It doesn't try to hide the Twitter "methods" under an access
15
+ layer nor does it introduce concrete classes for the various objects returned by
16
+ Twitter. Instead, calls to the Grackle client map directly to Twitter API URLs.
17
+ The objects returned by API calls are generated as OpenStructs on the fly and
18
+ make no assumptions about the presence or absence of any particular attributes.
19
+ Taking this approach means that changes to URLs used by Twitter, parameters
20
+ required by those URLs or return values will not require a new release. It will
21
+ potentially require, however, some modifications to your code that uses Grackle.
22
+
23
+ === Support and Announcements
24
+ The preferred forum for questions and discussions is the Google group at
25
+ http://groups.google.com/group/gracklerb. You can email me directly or @reply me
26
+ on Twitter, but the group is better since the questions and responses will be
27
+ available to everyone. I'll also make announcements there. There are some
28
+ examples on the wiki at http://wiki.github.com/hayesdavis/grackle. If you prefer
29
+ your information in 140 characters, follow @gracklerb[http://twitter.com/gracklerb].
30
+
31
+ ==USING GRACKLE
32
+
33
+ Before you do anything else, you'll need to
34
+ require 'grackle'
35
+
36
+ ===Creating a Grackle::Client
37
+ ====Using OAuth
38
+ client = Grackle::Client.new(:auth=>{
39
+ :type=>:oauth,
40
+ :consumer_key=>'SOMECONSUMERKEYFROMTWITTER', :consumer_secret=>'SOMECONSUMERTOKENFROMTWITTER',
41
+ :token=>'ACCESSTOKENACQUIREDONUSERSBEHALF', :token_secret=>'SUPERSECRETACCESSTOKENSECRET'
42
+ })
43
+
44
+ OAuth can be a bit complicated. See the wiki[http://wiki.github.com/hayesdavis/grackle/grackle-and-oauth]
45
+ for more information on acquiring the keys, tokens and secrets needed to
46
+ successfully authenticate with OAuth.
47
+
48
+ ====Using No Auth
49
+ client = Grackle::Client.new
50
+
51
+ ====Using Basic Auth (DEPRECATED)
52
+ As of August 31st, 2010, Twitter has deprecated basic authentication in favor of OAuth. Please refer to the section on OAuth authentication.
53
+ client = Grackle::Client.new(:auth=>{:type=>:basic,:username=>'your_user',:password=>'yourpass'})
54
+
55
+ See Grackle::Client for more information about valid arguments to the constructor. It's quite configurable. Among other things, you can turn on ssl and specify custom headers. The calls below are pretty much as simple as it gets.
56
+
57
+ ===Grackle Method Syntax
58
+ Grackle uses a method syntax that corresponds to the Twitter API URLs with a few twists. Where you would have a slash in
59
+ a Twitter URL, that becomes a "." in a chained set of Grackle method calls. Each call in the method chain is used to build
60
+ Twitter URL path until a particular call is encountered which causes the request to be sent. Methods which will cause a
61
+ request to be execute include:
62
+ - A method call ending in "?" will cause an HTTP GET to be executed
63
+ - A method call ending in "!" will cause an HTTP POST to be executed
64
+ - If a valid format such as .json, .xml, .rss or .atom is encounted, a get will be executed with that format
65
+ - A format method can also include a ? or ! to determine GET or POST in that format respectively
66
+
67
+ ===GETting Data
68
+ The preferred and simplest way of executing a GET is to use the "?" method notation. This will use the default client
69
+ format (usually JSON, but see Formats section below):
70
+ client.users.show? :screen_name=>'some_user' #http://twitter.com/users/show.json?screen_name=some_user
71
+
72
+ You can force XML format by doing:
73
+ client.users.show.xml? :screen_name=>'some_user' #http://twitter.com/users/show.xml?screen_name=some_user
74
+
75
+ You can force JSON:
76
+ client.users.show.json? :screen_name=>'some_user' #http://twitter.com/users/show.json?screen_name=some_user
77
+
78
+ Or, since Twitter also allows certain ids/screen_names to be part of their URLs, this works:
79
+ client.users.show.some_user? #http://twitter.com/users/show/some_user.json
80
+
81
+ If you use an explicit format, you can leave off the "?" like so:
82
+ client.users.show.xml :screen_name=>'some_user' #http://twitter.com/users/show.xml?screen_name=some_user
83
+
84
+ ===POSTing data
85
+ To use Twitter API methods that require an HTTP POST, you need to end your method chain with a bang (!)
86
+
87
+ The preferred way is to use the Client's default format (usually JSON, but see Formats section below):
88
+ client.statuses.update! :status=>'this status is from grackle' #POST to http://twitter.com/statuses/update.json
89
+
90
+ You can force a format. To update the authenticated user's status using the XML format:
91
+ client.statuses.update.xml! :status=>'this status is from grackle' #POST to http://twitter.com/statuses/update.xml
92
+
93
+ Or, with JSON
94
+ client.statuses.update.json! :status=>'this status is from grackle' #POST to http://twitter.com/statuses/update.json
95
+
96
+ ===Using Other HTTP Verbs
97
+ To use HTTP verbs like DELETE or PUT, Grackle provides a slightly different syntax:
98
+ client.put{ hayesdavis.lists.my_list :name=>'New Name' } #HTTP PUT
99
+ client.delete{ direct_messages.destroy :id=>1 } #HTTP DELETE
100
+
101
+ You may specify any method chain you wish in the block. Note that if your method chain inside the block
102
+ ends in a ! or ?, that the HTTP verb for the block will still be used. This means that
103
+ client.delete{ direct_messages.destroy! :id=>1 } #Uses HTTP DELETE, not POST
104
+ client.direct_messages.destroy! :id=>1 #Uses HTTP POST
105
+
106
+ If for some reason you don't like the preferred block syntax above, you may specify a
107
+ parameter to your method chain called :__method (note the double underscores) to specify the HTTP verb:
108
+ client.direct_messages.destroy! :id=>1, :__method=>:delete #HTTP DELETE
109
+
110
+ ===Toggling APIs
111
+ DEPRECATION NOTICE: The :rest API key (pointing to twitter.com) has been
112
+ deprecated in favor of the versioned API :v1 (pointing to api.twitter.com/1).
113
+ :v1 is now the default if you do not specify an API. Please do not use :rest
114
+ in your code.
115
+
116
+ By default, the Grackle::Client sends all requests to the versioned Twitter REST API. If you want to send requests to
117
+ the Twitter Search API, just set Grackle::Client.api to :search. To toggle back, set it to be :v1. All requests made
118
+ after setting this attribute will go to that API.
119
+
120
+ If you want to make a specific request to one API and not change the Client's overall api setting beyond that request, you can use the
121
+ bracket syntax like so:
122
+ client[:search].trends.daily? :exclude=>'hashtags'
123
+ client[:v1].users.show? :id=>'hayesdavis'
124
+
125
+ Search and REST requests are all built using the same method chaining and termination conventions.
126
+
127
+ ===Parameter handling
128
+ - All parameters are URL encoded as necessary.
129
+ - If you use a File object as a parameter it will be POSTed to Twitter in a multipart request.
130
+ - If you use a Time object as a parameter, .httpdate will be called on it and that value will be used
131
+
132
+ ===Return Values
133
+ Regardless of the format used, Grackle returns an OpenStruct (actually a Grackle::TwitterStruct) of data. The attributes
134
+ available on these structs correspond to the data returned by Twitter.
135
+
136
+ ===Dealing with Errors
137
+ If the request to Twitter does not return a status code of 200, then a TwitterError is thrown. This contains the HTTP method used,
138
+ the full request URI, the response status, the response body in text and a response object build by parsing the formatted error
139
+ returned by Twitter. It's a good idea to wrap your API calls with rescue clauses for Grackle::TwitterError.
140
+
141
+ If there is an unexpected connection error or Twitter returns data in the wrong format (which it can do), you'll still get a TwitterError.
142
+
143
+ ===Formats
144
+ Twitter allows you to request data in particular formats. Grackle automatically
145
+ parses JSON and XML formatted responses and returns an OpenStruct. The
146
+ Grackle::Client has a default_format you can specify. By default, the
147
+ default_format is :json. If you don't include a named format in your method
148
+ chain as described above, but use a "?" or "!" then the
149
+ Grackle::Client.default_format is used.
150
+
151
+ If you specify a format that Grackle doesn't parse for you, you'll receive a
152
+ string containing the raw response body. If you want to receive the raw
153
+ response body even for XML or JSON formatted responses, tell the Grackle client
154
+ to use the +StringHandler+ handler. For example, the following code sets the
155
+ Grackle client to return JSON instead of an OpenStruct:
156
+
157
+ client = Grackle::Client.new(:handlers=>{:json=>Grackle::Handlers::StringHandler.new })
158
+
159
+ ===Odds and Ends
160
+ If you need to append something to the request path that isn't a valid ruby method, e.g.
161
+ /1user/lists.json #1user isn't a valid Ruby method
162
+ you can use the Grackle::Client#_ method like so:
163
+ client._('1user').lists.json
164
+
165
+ == REQUIREMENTS
166
+
167
+ You'll need the following gems to use all features of Grackle:
168
+ - json
169
+ - oauth
170
+ - mime-types
171
+
172
+ === Ruby Version Support
173
+ Grackle works just fine on Ruby 1.8.x. It is also known to work on 1.9.1 with
174
+ the exception of OAuth. The OAuth gem used by Grackle has not been updated fully
175
+ to support 1.9. Please see this thread[http://groups.google.com/group/oauth-ruby/browse_thread/thread/d0851a907878cd22]
176
+ for more information.
177
+
178
+ Once the OAuth gem has been updated, Grackle will work fully on 1.9. If you
179
+ aren't using OAuth it should be fine for use on 1.9 as is.
180
+
181
+ == INSTALL
182
+ The grackle gem is now hosted at http://gemcutter.org. If you've already setup gemcutter
183
+ in your sources, you can do the following:
184
+ sudo gem install grackle
185
+
186
+ If you haven't yet setup gemcutter in your sources, go to http://gemcutter.org and follow the instructions there.
187
+ They will likely tell you to do the following:
188
+ sudo gem install gemcutter
189
+ sudo gem tumble
190
+
191
+ Once you've done that you can do:
192
+ sudo gem install grackle
193
+
194
+ == LICENSE
195
+
196
+ (The MIT License)
197
+
198
+ Copyright (c) 2009
199
+
200
+ Permission is hereby granted, free of charge, to any person obtaining
201
+ a copy of this software and associated documentation files (the
202
+ 'Software'), to deal in the Software without restriction, including
203
+ without limitation the rights to use, copy, modify, merge, publish,
204
+ distribute, sublicense, and/or sell copies of the Software, and to
205
+ permit persons to whom the Software is furnished to do so, subject to
206
+ the following conditions:
207
+
208
+ The above copyright notice and this permission notice shall be
209
+ included in all copies or substantial portions of the Software.
210
+
211
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
212
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
213
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
214
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
215
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
216
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
217
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{bloveless_grackle}
5
+ s.version = "0.1.10"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Hayes Davis", "Brennon Loveless"]
9
+ #s.date = %q{2010-6-13}
10
+ s.description = %q{Grackle is a lightweight library for the Twitter REST and Search API. Brennon Loveless added the upload api endpoint.}
11
+ s.email = %q{hayes@appozite.com}
12
+ s.files = ["CHANGELOG.rdoc", "README.rdoc", "grackle.gemspec", "lib/grackle.rb", "lib/grackle/client.rb", "lib/grackle/handlers.rb", "lib/grackle/transport.rb", "lib/grackle/utils.rb", "test/test_grackle.rb", "test/test_helper.rb", "test/test_client.rb", "test/test_handlers.rb"]
13
+ s.has_rdoc = true
14
+ s.homepage = %q{http://github.com/hayesdavis/grackle}
15
+ s.rdoc_options = ["--inline-source", "--charset=UTF-8","--main=README.rdoc"]
16
+ s.extra_rdoc_files = ['README.rdoc']
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{grackle}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Grackle is a library for the Twitter REST and Search API designed to not require a new release in the face Twitter API changes or errors. It supports both basic and OAuth authentication mechanisms.}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<json>, [">= 0"])
28
+ s.add_dependency(%q<mime-types>, [">= 0"])
29
+ s.add_dependency(%q<oauth>, [">= 0"])
30
+ else
31
+ s.add_dependency(%q<json>, [">= 0"])
32
+ s.add_dependency(%q<mime-types>, [">= 0"])
33
+ s.add_dependency(%q<oauth>, [">= 0"])
34
+ end
35
+ else
36
+ s.add_dependency(%q<json>, [">= 0"])
37
+ s.add_dependency(%q<mime-types>, [">= 0"])
38
+ s.add_dependency(%q<oauth>, [">= 0"])
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ module Grackle
2
+
3
+ # :stopdoc:
4
+ VERSION = '0.1.10'
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ def self.version
11
+ VERSION
12
+ end
13
+
14
+ end # module Grackle
15
+
16
+ $:.unshift File.dirname(__FILE__)
17
+
18
+ require 'ostruct'
19
+ require 'open-uri'
20
+ require 'net/http'
21
+ require 'time'
22
+ require 'rexml/document'
23
+ require 'json'
24
+ require 'oauth'
25
+ require 'oauth/client'
26
+ require 'mime/types'
27
+
28
+ require 'grackle/utils'
29
+ require 'grackle/transport'
30
+ require 'grackle/handlers'
31
+ require 'grackle/client'
@@ -0,0 +1,295 @@
1
+ module Grackle
2
+
3
+ #Returned by methods which retrieve data from the API
4
+ class TwitterStruct < OpenStruct
5
+ attr_accessor :id
6
+ end
7
+
8
+ #Raised by methods which call the API if a non-200 response status is received
9
+ class TwitterError < StandardError
10
+ attr_accessor :method, :request_uri, :status, :response_body, :response_object
11
+
12
+ def initialize(method, request_uri, status, response_body, msg=nil)
13
+ self.method = method
14
+ self.request_uri = request_uri
15
+ self.status = status
16
+ self.response_body = response_body
17
+ super(msg||"#{self.method} #{self.request_uri} => #{self.status}: #{self.response_body}")
18
+ end
19
+ end
20
+
21
+ # The Client is the public interface to Grackle. You build Twitter API calls using method chains. See the README for details
22
+ # and new for information on valid options.
23
+ #
24
+ # ==Authentication
25
+ # Twitter is migrating to OAuth as the preferred mechanism for authentication (over HTTP basic auth). Grackle supports both methods.
26
+ # Typically you will supply Grackle with authentication information at the time you create your Grackle::Client via the :auth parameter.
27
+ # ===Basic Auth
28
+ # client = Grackle.Client.new(:auth=>{:type=>:basic,:username=>'twitteruser',:password=>'secret'})
29
+ # Please note that the original way of specifying basic authentication still works but is deprecated
30
+ # client = Grackle.Client.new(:username=>'twitteruser',:password=>'secret') #deprecated
31
+ #
32
+ # ===OAuth
33
+ # OAuth is a relatively complex topic. For more information on OAuth applications see the official OAuth site at http://oauth.net and the
34
+ # OAuth specification at http://oauth.net/core/1.0. For authentication using OAuth, you will need do the following:
35
+ # - Acquire a key and token for your application ("Consumer" in OAuth terms) from Twitter. Learn more here: http://apiwiki.twitter.com/OAuth-FAQ
36
+ # - Acquire an access token and token secret for the user that will be using OAuth to authenticate into Twitter
37
+ # The process of acquiring the access token and token secret are outside the scope of Grackle and will need to be coded on a per-application
38
+ # basis. Grackle comes into play once you've acquired all of the above pieces of information. To create a Grackle::Client that uses OAuth once
39
+ # you've got all the necessary tokens and keys:
40
+ # client = Grackle::Client.new(:auth=>{
41
+ # :type=>:oauth,
42
+ # :consumer_key=>'SOMECONSUMERKEYFROMTWITTER, :consumer_secret=>'SOMECONSUMERTOKENFROMTWITTER',
43
+ # :token=>'ACCESSTOKENACQUIREDONUSERSBEHALF', :token_secret=>'SUPERSECRETACCESSTOKENSECRET'
44
+ # })
45
+ class Client
46
+
47
+ class Request #:nodoc:
48
+ attr_accessor :client, :path, :method, :api, :ssl, :params
49
+
50
+ def initialize(client,api=:rest,ssl=true)
51
+ self.client = client
52
+ self.api = api
53
+ self.ssl = ssl
54
+ self.path = ''
55
+ end
56
+
57
+ def <<(path)
58
+ self.path << path
59
+ end
60
+
61
+ def path?
62
+ path.length > 0
63
+ end
64
+
65
+ def url
66
+ "#{scheme}://#{host}#{path}"
67
+ end
68
+
69
+ def host
70
+ client.api_hosts[api]
71
+ end
72
+
73
+ def scheme
74
+ ssl ? 'https' :'http'
75
+ end
76
+
77
+ def params
78
+ @params ||= {}
79
+ end
80
+ end
81
+
82
+ VALID_FORMATS = [:json,:xml,:atom,:rss]
83
+
84
+ # Contains the mapping of API name symbols to actual host (and path)
85
+ # prefixes to use with requests. You can add your own to this hash and
86
+ # refer to it wherever Grackle::Client uses an API symbol. You may wish
87
+ # to do this when Twitter introduces API versions greater than 1.
88
+ TWITTER_API_HOSTS = {
89
+ :search=>'search.twitter.com', :v1=>'api.twitter.com/1', :upload=>'upload.twitter.com/1'
90
+ }
91
+ TWITTER_API_HOSTS[:rest] = TWITTER_API_HOSTS[:v1]
92
+
93
+ #Basic OAuth information needed to communicate with Twitter
94
+ TWITTER_OAUTH_SPEC = {
95
+ :request_token_path=>'/oauth/request_token',
96
+ :access_token_path=>'/oauth/access_token',
97
+ :authorize_path=>'/oauth/authorize'
98
+ }
99
+
100
+ attr_accessor :auth, :handlers, :default_format, :headers, :ssl, :api,
101
+ :transport, :request, :api_hosts, :timeout, :auto_append_ids, :uri_extension
102
+
103
+ # Arguments (all are optional):
104
+ # - :username - twitter username to authenticate with (deprecated in favor of :auth arg)
105
+ # - :password - twitter password to authenticate with (deprecated in favor of :auth arg)
106
+ # - :handlers - Hash of formats to Handler instances (e.g. {:json=>CustomJSONHandler.new})
107
+ # - :default_format - Symbol of format to use when no format is specified in an API call (e.g. :json, :xml)
108
+ # - :headers - Hash of string keys and values for headers to pass in the HTTP request to twitter
109
+ # - :ssl - true or false to turn SSL on or off. Default is off (i.e. http://)
110
+ # - :api - one of :rest, :search or :v1. :v1 is the default and :rest is now deprecated
111
+ # - :auth - Hash of authentication type and credentials. Must have :type key with value one of :basic or :oauth
112
+ # - :type=>:basic - Include :username and :password keys
113
+ # - :type=>:oauth - Include :consumer_key, :consumer_secret, :token and :token_secret keys
114
+ # - :uri_extension - true or false to include format in URI (e.g. /test.json). Default is true
115
+ def initialize(options={})
116
+ self.transport = Transport.new
117
+ self.handlers = {:json=>Handlers::JSONHandler.new,:xml=>Handlers::XMLHandler.new,:unknown=>Handlers::StringHandler.new}
118
+ self.handlers.merge!(options[:handlers]||{})
119
+ self.default_format = options[:default_format] || :json
120
+ self.headers = {"User-Agent"=>"Grackle/#{Grackle::VERSION}"}.merge!(options[:headers]||{})
121
+ self.ssl = options[:ssl] == true
122
+ self.api = options[:api] || :v1
123
+ self.api_hosts = TWITTER_API_HOSTS.clone
124
+ self.timeout = options[:timeout] || 60
125
+ self.auto_append_ids = options[:auto_append_ids] == false ? false : true
126
+ self.auth = {}
127
+ self.uri_extension = options[:uri_extension] == false ? false : true
128
+ if options.has_key?(:username) || options.has_key?(:password)
129
+ #Use basic auth if :username and :password args are passed in
130
+ self.auth.merge!({:type=>:basic,:username=>options[:username],:password=>options[:password]})
131
+ end
132
+ #Handle auth mechanism that permits basic or oauth
133
+ if options.has_key?(:auth)
134
+ self.auth = options[:auth]
135
+ if auth[:type] == :oauth
136
+ self.auth = TWITTER_OAUTH_SPEC.merge(auth)
137
+ end
138
+ end
139
+ end
140
+
141
+ def method_missing(name,*args,&block)
142
+ if block_given?
143
+ return request_with_http_method_block(name,&block)
144
+ end
145
+ append(name,*args)
146
+ end
147
+
148
+ # Used to toggle APIs for a particular request without setting the Client's default API
149
+ # client[:rest].users.show.hayesdavis?
150
+ def [](api_name)
151
+ request.api = api_name
152
+ self
153
+ end
154
+
155
+ #Clears any pending request built up by chained methods but not executed
156
+ def clear
157
+ self.request = nil
158
+ end
159
+
160
+ #Deprecated in favor of using the auth attribute.
161
+ def username
162
+ if auth[:type] == :basic
163
+ auth[:username]
164
+ end
165
+ end
166
+
167
+ #Deprecated in favor of using the auth attribute.
168
+ def username=(value)
169
+ unless auth[:type] == :basic
170
+ auth[:type] = :basic
171
+ end
172
+ auth[:username] = value
173
+ end
174
+
175
+ #Deprecated in favor of using the auth attribute.
176
+ def password
177
+ if auth[:type] == :basic
178
+ auth[:password]
179
+ end
180
+ end
181
+
182
+ #Deprecated in favor of using the auth attribute.
183
+ def password=(value)
184
+ unless auth[:type] == :basic
185
+ auth[:type] = :basic
186
+ end
187
+ auth[:password] = value
188
+ end
189
+
190
+ def append(name,*args)
191
+ name = name.to_s.to_sym
192
+ #The args will be a hash, store them if they're specified
193
+ self.request.params = args.first
194
+ #If method is a format name, execute using that format
195
+ if format_invocation?(name)
196
+ return call_with_format(name)
197
+ end
198
+ #If method ends in ! or ? use that to determine post or get
199
+ if name.to_s =~ /^(.*)(!|\?)$/
200
+ name = $1.to_sym
201
+ #! is a post, ? is a get - only set this if the method hasn't been set
202
+ self.request.method ||= ($2 == '!' ? :post : :get)
203
+ if format_invocation?(name)
204
+ return call_with_format(name)
205
+ else
206
+ self.request << "/#{$1}"
207
+ return call_with_format(self.default_format)
208
+ end
209
+ end
210
+ #Else add to the request path
211
+ self.request << "/#{name}"
212
+ self
213
+ end
214
+
215
+ alias_method :_, :append
216
+
217
+ protected
218
+ def call_with_format(format)
219
+ if auto_append_ids
220
+ id = request.params.delete(:id)
221
+ request << "/#{id}" if id
222
+ end
223
+ if uri_extension
224
+ request << ".#{format}"
225
+ end
226
+ res = send_request
227
+ process_response(format,res)
228
+ ensure
229
+ clear
230
+ end
231
+
232
+ def send_request
233
+ begin
234
+ http_method = (
235
+ request.params.delete(:__method) or request.method or :get
236
+ )
237
+ transport.request(
238
+ http_method, request.url,
239
+ :auth=>auth,:headers=>headers,
240
+ :params=>request.params,:timeout => timeout
241
+ )
242
+ rescue => e
243
+ puts e
244
+ raise TwitterError.new(request.method,request.url,nil,nil,"Unexpected failure making request: #{e}")
245
+ end
246
+ end
247
+
248
+ def process_response(format,res)
249
+ fmt_handler = handler(format)
250
+ begin
251
+ unless res.status == 200
252
+ handle_error_response(res,fmt_handler)
253
+ else
254
+ fmt_handler.decode_response(res.body)
255
+ end
256
+ rescue TwitterError => e
257
+ raise e
258
+ rescue => e
259
+ raise TwitterError.new(res.method,res.request_uri,res.status,res.body,"Unable to decode response: #{e}")
260
+ end
261
+ end
262
+
263
+ def request
264
+ @request ||= Request.new(self,api,ssl)
265
+ end
266
+
267
+ def handler(format)
268
+ handlers[format] || handlers[:unknown]
269
+ end
270
+
271
+ def handle_error_response(res,handler)
272
+ err = TwitterError.new(res.method,res.request_uri,res.status,res.body)
273
+ err.response_object = handler.decode_response(err.response_body)
274
+ raise err
275
+ end
276
+
277
+ def format_invocation?(name)
278
+ self.request.path? && VALID_FORMATS.include?(name)
279
+ end
280
+
281
+ def pending_request?
282
+ !@request.nil?
283
+ end
284
+
285
+ def request_with_http_method_block(method,&block)
286
+ request.method = method
287
+ response = instance_eval(&block)
288
+ if pending_request?
289
+ call_with_format(self.default_format)
290
+ else
291
+ response
292
+ end
293
+ end
294
+ end
295
+ end