scribd-rscribd 1.0.2

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.
data/History.txt ADDED
@@ -0,0 +1,56 @@
1
+ === 1.0.2 / 2009-7-9
2
+
3
+ * Added support for getting information about the API user when no other user
4
+ has been logged in.
5
+ * Files to upload can now be specified as both file paths and File streams.
6
+
7
+ === 1.0.1 / 2009-3-4
8
+
9
+ * Fixed a bug that would cause some API parameters to not be sent when uploading
10
+ a document.
11
+
12
+ === 1.0.0 / 2008-11-26
13
+
14
+ * First release version.
15
+ * Comprehensive RSpec of all code.
16
+ * read_attributes method now works correctly.
17
+ * Uploading a document as a not-yet-created user no longer uploads it into the
18
+ default API account.
19
+ * Finding documents owned by a not-yet-created user now returns nil.
20
+ * Symbol#to_proc definedd for RScribd use in non-Rails projects.
21
+ * Minor code style improvements.
22
+
23
+ === 0.1.2 / 2008-11-26
24
+
25
+ * Happy Thanksgiving!
26
+ * Potential security issue resolved by having the document's privacy setting set
27
+ in the same request as the upload.
28
+ * Bug fixed where some files with uppercase extensions would not convert.
29
+
30
+ === 0.1.1 / 2008-10-6
31
+
32
+ * Fixed bug that occurs when parsing empty elements.
33
+
34
+ === 0.1.0 / 2008-10-1
35
+
36
+ * Changed Scribd::Document.find method to take an ActiveRecord-style scope
37
+ variable (which can be :all, :first, etc.) as its first parameter. Options are
38
+ now specified as the second parameter.
39
+ * Scribd::Document.find can also be used with a single integer to find a
40
+ document by ID.
41
+ * Scribd::Document.find now takes offset and limit parameters (for scopes that
42
+ return arrays of documents).
43
+ * Added a Scribd::Document#download_url method.
44
+ * Should no longer raise an exception when logging in.
45
+
46
+ === 0.0.5 / 2008-09-25
47
+
48
+ * Updated RScribd to work with server-side API changes.
49
+
50
+ === 0.0.4 / 2008-02-20
51
+
52
+ * Fixed a bug that caused errors when Rails models shared the same name as Scribd objects.
53
+
54
+ === 0.0.3 / 2008-02-18
55
+
56
+ * Initial RubyForge project
data/Manifest.txt ADDED
@@ -0,0 +1,14 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/scribdapi.rb
6
+ lib/scribddoc.rb
7
+ lib/scribderrors.rb
8
+ lib/scribdmultiparthack.rb
9
+ lib/scribdresource.rb
10
+ lib/rscribd.rb
11
+ lib/scribduser.rb
12
+ sample/01_upload.rb
13
+ sample/02_user.rb
14
+ sample/test.txt
data/README.txt ADDED
@@ -0,0 +1,84 @@
1
+ = rscribd
2
+
3
+ * 1.0.2 (July 9, 2009)
4
+
5
+ == DESCRIPTION:
6
+
7
+ This gem provides a simple and powerful library for the Scribd API, allowing you
8
+ to write Ruby applications or Ruby on Rails websites that upload, convert,
9
+ display, search, and control documents in many formats. For more information on
10
+ the Scribd platform, visit http://www.scribd.com/publisher
11
+
12
+ == FEATURES:
13
+
14
+ * Upload your documents to Scribd's servers and access them using the gem
15
+ * Upload local files or from remote web sites
16
+ * Search, tag, and organize documents
17
+ * Associate documents with your users' accounts
18
+
19
+ == SYNOPSIS:
20
+
21
+ This API allows you to use Scribd's Flash viewer on your website. You'll be able
22
+ to take advantage of Scribd's scalable document conversion system to convert
23
+ your documents into platform-independent formats. You can leverage Scribd's
24
+ storage system to store your documents in accessible manner. Scribd's ad system
25
+ will help you monetize your documents easily.
26
+
27
+ First, you'll need to get a Scribd API account. Visit
28
+ http://www.scribd.com/publisher/api to apply for a platform account.
29
+
30
+ On the Platform site you will be given an API key and secret. The API object
31
+ will need these to authenticate you:
32
+
33
+ require 'rscribd'
34
+ Scribd::API.instance.key = 'your API key'
35
+ Scribd::API.instance.secret = 'your API secret'
36
+
37
+ Next, log into the Scribd website:
38
+
39
+ Scribd::User.login 'username', 'password'
40
+
41
+ You are now ready to use Scribd to manage your documents. For instance, to
42
+ upload a document:
43
+
44
+ doc = Scribd::Document.upload(:file => 'your-file.pdf')
45
+
46
+ For more information, please see the documentation for the Scribd::API,
47
+ Scribd::User, and Scribd::Document classes. (You can also check out the docs for
48
+ the other classes for a more in-depth look at this gem's features).
49
+
50
+ == REQUIREMENTS:
51
+
52
+ * A Scribd API account
53
+ * mime-types gem
54
+
55
+ == INSTALL:
56
+
57
+ * The client library is a RubyGem called *rscribd*. To install, type:
58
+
59
+ sudo gem install rscribd
60
+
61
+ == LICENSE:
62
+
63
+ (The MIT License)
64
+
65
+ Copyright (c) 2007-2008 The Scribd Team
66
+
67
+ Permission is hereby granted, free of charge, to any person obtaining
68
+ a copy of this software and associated documentation files (the
69
+ 'Software'), to deal in the Software without restriction, including
70
+ without limitation the rights to use, copy, modify, merge, publish,
71
+ distribute, sublicense, and/or sell copies of the Software, and to
72
+ permit persons to whom the Software is furnished to do so, subject to
73
+ the following conditions:
74
+
75
+ The above copyright notice and this permission notice shall be
76
+ included in all copies or substantial portions of the Software.
77
+
78
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
79
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
80
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
81
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
82
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
83
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
84
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require 'spec/rake/spectask'
4
+
5
+ Hoe.spec('rscribd') do |p|
6
+ p.version = '1.0.2'
7
+ p.rubyforge_name = 'rscribd'
8
+ p.author = 'Jared Friedman, Tim Morgan'
9
+ p.email = 'api@scribd.com'
10
+ p.summary = 'Ruby client library for the Scribd API'
11
+ p.description = p.paragraphs_of('README.txt', 3).join("\n\n")
12
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
13
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ p.extra_deps << [ 'mime-types', '>0.0.0' ]
15
+ p.remote_rdoc_dir = ''
16
+ end
17
+
18
+ # desc "Verify gem specs"
19
+ # Spec::Rake::SpecTask.new do |t|
20
+ # t.spec_files = FileList['spec/*.rb']
21
+ # t.spec_opts = [ '-cfs' ]
22
+ # end
23
+
24
+ namespace :github do
25
+ desc "Prepare for GitHub gem packaging"
26
+ task :prepare do
27
+ `rake debug_gem > rscribd.gemspec`
28
+ end
29
+ end
data/lib/rscribd.rb ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Scribd # :nodoc:
4
+ end
5
+
6
+ class Symbol
7
+ def to_proc
8
+ Proc.new { |*args| args.shift.__send__(self, *args) }
9
+ end unless method_defined?(:to_proc)
10
+ end
11
+
12
+ class Hash #:nodoc:
13
+ # Taken from Rails, with appreciation to DHH
14
+ def stringify_keys
15
+ inject({}) do |options, (key, value)|
16
+ options[key.to_s] = value
17
+ options
18
+ end
19
+ end unless method_defined?(:stringify_keys)
20
+ end
21
+
22
+ class Array #:nodoc:
23
+ def to_hsh
24
+ h = Hash.new
25
+ each { |k, v| h[k] = v }
26
+ h
27
+ end
28
+ end
29
+
30
+ # The reason these files have such terrible names is so they don't conflict with
31
+ # files in Rails's app/models directory; Rails seems to prefer loading those
32
+ # when require is called.
33
+ require 'scribdmultiparthack'
34
+ require 'scribderrors'
35
+ require 'scribdapi'
36
+ require 'scribdresource'
37
+ require 'scribddoc'
38
+ require 'scribduser'
data/lib/scribdapi.rb ADDED
@@ -0,0 +1,218 @@
1
+ require 'singleton'
2
+ require 'digest/md5'
3
+ require 'rexml/document'
4
+
5
+ module Scribd
6
+
7
+ # This class acts as the top-level interface between Scribd and your
8
+ # application. Before you can begin using the Scribd API, you must specify for
9
+ # this object your API key and API secret. They are available on your
10
+ # Platform home page.
11
+ #
12
+ # This class is a singleton. Its only instance is accessed using the
13
+ # +instance+ class method.
14
+ #
15
+ # To begin, first specify your API key and secret:
16
+ #
17
+ # Scribd::API.instance.key = 'your API key here'
18
+ # Scribd::API.instance.secret = 'your API secret here'
19
+ #
20
+ # (If you set the +SCRIBD_API_KEY+ and +SCRIBD_API_SECRET+ Ruby environment
21
+ # variables before loading the gem, these values will be set automatically for
22
+ # you.)
23
+ #
24
+ # Next, you should log in to Scribd, or create a new account through the gem.
25
+ #
26
+ # user = Scribd::User.login 'login', 'password'
27
+ #
28
+ # You are now free to use the Scribd::User or Scribd::Document classes to work
29
+ # with Scribd documents or your user account.
30
+ #
31
+ # If you need the Scribd::User instance for the currently logged in user at a
32
+ # later point in time, you can retrieve it using the +user+ attribute:
33
+ #
34
+ # user = Scribd::API.instance.user
35
+ #
36
+ # In addition, you can save and restore sessions by simply storing this user
37
+ # instance and assigning it to the API at a later time. For example, to
38
+ # restore the session retrieved in the previous example:
39
+ #
40
+ # Scribd::API.instance.user = user
41
+ #
42
+ # In addition to working with Scribd users, you can also work with your own
43
+ # website's user accounts. To do this, set the Scribd API user to a string
44
+ # containing a unique identifier for that user (perhaps a login name or a user
45
+ # ID):
46
+ #
47
+ # Scribd::API.instance.user = my_user_object.mangled_user_id
48
+ #
49
+ # A "phantom" Scribd user will be set up with that ID, so you any documents
50
+ # you upload will be associated with that account.
51
+ #
52
+ # For more hints on what you can do with the Scribd API, please see the
53
+ # Scribd::Document class.
54
+
55
+ class API
56
+ include Singleton
57
+
58
+ HOST = 'api.scribd.com' #:nodoc:
59
+ PORT = 80 #:nodoc:
60
+ REQUEST_PATH = '/api' #:nodoc:
61
+ TRIES = 3 #:nodoc:
62
+
63
+ attr_accessor :key # The API key you were given when you created a Platform account.
64
+ attr_accessor :secret # The API secret used to validate your key (also provided with your account).
65
+ attr_accessor :user # The currently logged in user.
66
+ attr_accessor :asynchronous # If true, requests are processed asynchronously. If false, requests are blocking.
67
+ attr_accessor :debug # If true, extended debugging information is printed
68
+
69
+ def initialize #:nodoc:
70
+ @asychronous = false
71
+ @key = ENV['SCRIBD_API_KEY']
72
+ @secret = ENV['SCRIBD_API_SECRET']
73
+ @user = User.new
74
+ end
75
+
76
+ def send_request(method, fields={}) #:nodoc:
77
+ raise NotReadyError unless @key and @secret
78
+ # See if method is given
79
+ raise ArgumentError, "Method should be given" if method.nil? || method.empty?
80
+
81
+ debug("** Remote method call: #{method}; fields: #{fields.inspect}")
82
+
83
+ # replace pesky hashes to prevent accidents
84
+ fields = fields.stringify_keys
85
+
86
+ # Complete fields with the method name
87
+ fields['method'] = method
88
+ fields['api_key'] = @key
89
+
90
+ if fields['session_key'].nil? and fields['my_user_id'].nil? then
91
+ if @user.kind_of? Scribd::User then
92
+ fields['session_key'] = @user.session_key
93
+ elsif @user.kind_of? String then
94
+ fields['my_user_id'] = @user
95
+ end
96
+ end
97
+
98
+ fields.reject! { |k, v| v.nil? }
99
+
100
+ # Don't include file in parameters to calculate signature
101
+ sign_fields = fields.dup
102
+ sign_fields.delete 'file'
103
+
104
+ fields['api_sig'] = sign(sign_fields)
105
+ debug("** POST parameters: #{fields.inspect}")
106
+
107
+ # Create the connection
108
+ http = Net::HTTP.new(HOST, PORT)
109
+ # TODO configure timeouts through the properties
110
+
111
+ # API methods can be SLOW. Make sure this is set to something big to prevent spurious timeouts
112
+ http.read_timeout = 15*60
113
+
114
+ request = Net::HTTP::Post.new(REQUEST_PATH)
115
+ request.multipart_params = fields
116
+
117
+ tries = TRIES
118
+ begin
119
+ tries -= 1
120
+ res = http.request(request)
121
+ rescue Exception
122
+ $stderr.puts "Request encountered error, will retry #{tries} more."
123
+ if tries > 0
124
+ # Retrying several times with sleeps is recommended.
125
+ # This will prevent temporary downtimes at Scribd from breaking API applications
126
+ sleep(20)
127
+ retry
128
+ end
129
+ raise $!
130
+ end
131
+
132
+ debug "** Response:"
133
+ debug(res.body)
134
+ debug "** End response"
135
+
136
+ # Convert response into XML
137
+ xml = REXML::Document.new(res.body)
138
+ raise MalformedResponseError, "The response received from the remote host could not be interpreted" unless xml.elements['/rsp']
139
+
140
+ # See if there was an error and raise an exception
141
+ if xml.elements['/rsp'].attributes['stat'] == 'fail'
142
+ # Load default code and error
143
+ code, message = -1, "Unidentified error:\n#{res.body}"
144
+
145
+ # Get actual error code and message
146
+ err = xml.elements['/rsp/error']
147
+ code, message = err.attributes['code'], err.attributes['message'] if err
148
+
149
+ # Add more data
150
+ message = "Method: #{method} Response: code=#{code} message=#{message}"
151
+
152
+ raise ResponseError.new(code), message
153
+ end
154
+
155
+ return xml
156
+ end
157
+
158
+ private
159
+
160
+ # FIXME: Since we don't need XMLRPC, the exception could be different
161
+ # TODO: It would probably be better if we wrapped the fault
162
+ # in something more meaningful. At the very least, a broad
163
+ # division of errors, such as retryable and fatal.
164
+ def error(el) #:nodoc:
165
+ att = el.attributes
166
+ fe = XMLRPC::FaultException.new(att['code'].to_i, att['msg'])
167
+ $stderr.puts "ERR: #{fe.faultString} (#{fe.faultCode})"
168
+ raise fe
169
+ end
170
+
171
+ # Checks if a string parameter is given and not empty.
172
+ #
173
+ # Parameters:
174
+ # name - parameter name for an error message.
175
+ # value - value.
176
+ #
177
+ # Raises:
178
+ # ArgumentError if the value is nil, or empty.
179
+ #
180
+ def check_not_empty(name, value) #:nodoc:
181
+ check_given(name, value)
182
+ raise ArgumentError, "#{name} must not be empty" if value.to_s.empty?
183
+ end
184
+
185
+ # Checks if the value is given.
186
+ #
187
+ # Parameters:
188
+ # name - parameter name for an error message.
189
+ # value - value.
190
+ #
191
+ # Raises:
192
+ # ArgumentError if the value is nil.
193
+ #
194
+ def check_given(name, value) #:nodoc:
195
+ raise ArgumentError, "#{name} must be given" if value.nil?
196
+ end
197
+
198
+ # Sign the arguments hash with our very own signature.
199
+ #
200
+ # Parameters:
201
+ # args - method arguments to be sent to the server API
202
+ #
203
+ # Returns:
204
+ # signature
205
+ #
206
+ def sign(args)
207
+ return Digest::MD5.hexdigest(@secret + args.sort.flatten.join).to_s
208
+ end
209
+
210
+ # Outputs whatever is given into the $stderr if debugging is enabled.
211
+ #
212
+ # Parameters:
213
+ # args - content to output
214
+ def debug(str)
215
+ $stderr.puts(str) if @debug
216
+ end
217
+ end
218
+ end
data/lib/scribddoc.rb ADDED
@@ -0,0 +1,282 @@
1
+ require 'uri'
2
+
3
+ module Scribd
4
+
5
+ # A document as shown on the Scribd website. API programs can upload documents
6
+ # from files or URL's, tag them, and change their settings. An API program can
7
+ # access any document, but it can only modify documents owned by the logged-in
8
+ # user.
9
+ #
10
+ # To upload a new document to Scribd, you must create a new Document instance,
11
+ # set the +file+ attribute to the file's path, and then save the document:
12
+ #
13
+ # doc = Scribd::Document.new
14
+ # doc.file = '/path/or/URL/of/file.txt'
15
+ # doc.save
16
+ #
17
+ # You can do this more simply with one line of code:
18
+ #
19
+ # doc = Scribd::Document.create :file => '/path/or/URL/of/file.txt'
20
+ #
21
+ # If you are uploading a file that does not have an extension (like ".txt"),
22
+ # you need to specify the +type+ attribute as well:
23
+ #
24
+ # doc = Scribd::Document.upload :file => 'CHANGELOG', :type => 'txt'
25
+ #
26
+ # Aside from these two attributes, you can set other attributes that affect
27
+ # how the file is displayed on Scribd. See the API documentation online for a
28
+ # list of attributes, at
29
+ # http://www.scribd.com/publisher/api?method_name=docs.search (consult the
30
+ # "Result explanation" section).
31
+ #
32
+ # These attributes can be accessed or changed directly
33
+ # (<tt>doc.title = 'Title'</tt>). You must save a document after changing its
34
+ # attributes in order for those changes to take effect. Not all attributes can
35
+ # be modified; see the API documentation online for details.
36
+ #
37
+ # A document can be associated with a Scribd::User via the +owner+ attribute.
38
+ # This is not always the case, however. Documents retrieved from the find
39
+ # method will not be associated with their owners.
40
+ #
41
+ # The +owner+ attribute is read/write, however, changes made to it only apply
42
+ # _before_ the document is saved. Once it is saved, the owner is set in stone
43
+ # and cannot be modified:
44
+ #
45
+ # doc = Scribd::Document.new :file => 'test.txt'
46
+ # doc.user = Scribd::User.signup(:username => 'newuser', :password => 'newpass', :email => 'your@email.com')
47
+ # doc.save #=> Uploads the document as "newuser", regardless of who the Scribd API user is
48
+ # doc.user = Scribd::API.instance.user #=> raises NotImplementedError
49
+
50
+ class Document < Resource
51
+
52
+ # Creates a new, unsaved document with the given attributes. The document
53
+ # must be saved before it will appear on the website.
54
+
55
+ def initialize(options={})
56
+ super
57
+ @download_urls = Hash.new
58
+ if options[:xml] then
59
+ load_attributes(options[:xml])
60
+ @attributes[:owner] = options[:owner]
61
+ @saved = true
62
+ @created = true
63
+ else
64
+ @attributes = options
65
+ end
66
+ end
67
+
68
+ # For document objects that have not been saved for the first time, uploads
69
+ # the document, sets its attributes, and saves it. Otherwise, updates any
70
+ # changed attributes and saves it. Returns true if the save completed
71
+ # successfully. Throws an exception if save fails.
72
+ #
73
+ # For first-time saves, you must have specified a +file+ attribute. This can
74
+ # either be a local path to a file, or an HTTP, HTTPS, or FTP URL. In either
75
+ # case, the file at that location will be uploaded to create the document.
76
+ #
77
+ # If you create a document, specify the +file+ attribute again, and then
78
+ # save it, Scribd replaces the existing document with the file given, while
79
+ # keeping all other properties (title, description, etc.) the same, in a
80
+ # process called _revisioning_.
81
+ #
82
+ # This method can throw a +Timeout+ exception if the connection is slow or
83
+ # inaccessible. A Scribd::ResponseError will be thrown if a remote problem
84
+ # occurs. A Scribd::PrivilegeError will be thrown if you try to upload a new
85
+ # revision for a document with no associated user (i.e., one retrieved from
86
+ # the find method).
87
+ #
88
+ # You must specify the +type+ attribute alongside the +file+ attribute if
89
+ # the file's type cannot be determined from its name.
90
+
91
+ def save
92
+ if not created? and @attributes[:file].nil? then
93
+ raise "'file' attribute must be specified for new documents"
94
+ return false
95
+ end
96
+
97
+ if created? and @attributes[:file] and (@attributes[:owner].nil? or @attributes[:owner].session_key.nil?) then
98
+ raise PrivilegeError, "The current API user is not the owner of this document"
99
+ end
100
+
101
+ # Make a request form
102
+ fields = @attributes.dup
103
+ if file = @attributes[:file] then
104
+ fields.delete :file
105
+ is_file_object = file.is_a?(File)
106
+ file_path = is_file_object ? file.path : file
107
+ ext = File.extname(file_path).gsub(/^\./, '')
108
+ ext = nil if ext == ''
109
+ fields[:doc_type] = fields.delete(:type)
110
+ fields[:doc_type] ||= ext
111
+ fields[:doc_type].downcase! if fields[:doc_type]
112
+ fields[:rev_id] = fields.delete(:doc_id)
113
+ end
114
+ fields[:session_key] = fields.delete(:owner).session_key if fields[:owner]
115
+ response = nil
116
+
117
+ if @attributes[:file] then
118
+ uri = nil
119
+ begin
120
+ uri = URI.parse @attributes[:file]
121
+ rescue URI::InvalidURIError
122
+ uri = nil # Some valid file paths are not valid URI's (but some are)
123
+ end
124
+ if uri.kind_of? URI::HTTP or uri.kind_of? URI::HTTPS or uri.kind_of? URI::FTP then
125
+ fields[:url] = @attributes[:file]
126
+ response = API.instance.send_request 'docs.uploadFromUrl', fields
127
+ elsif uri.kind_of? URI::Generic or uri.nil? then
128
+ file_obj = is_file_object ? file : File.open(file, 'r')
129
+ fields[:file] = file_obj
130
+ response = API.instance.send_request 'docs.upload', fields
131
+ file_obj.close unless is_file_object
132
+ end
133
+ end
134
+
135
+ fields = @attributes.dup # fields is what we send to the server
136
+
137
+ if response then
138
+ # Extract our response
139
+ xml = response.get_elements('/rsp')[0]
140
+ load_attributes(xml)
141
+ @created = true
142
+ end
143
+
144
+ fields.delete :file
145
+ fields.delete :type
146
+ fields.delete :access
147
+
148
+ changed_attributes = fields.dup # changed_attributes is what we will stick into @attributes once we update remotely
149
+
150
+ fields[:session_key] = fields[:owner].session_key if fields[:owner]
151
+ changed_attributes[:owner] ||= API.instance.user
152
+ fields[:doc_ids] = self.id
153
+
154
+ API.instance.send_request('docs.changeSettings', fields)
155
+
156
+ @attributes.update(changed_attributes)
157
+
158
+ @saved = true
159
+ return true
160
+ end
161
+
162
+ # Quickly updates an array of documents with the given attributes. The
163
+ # documents can have different owners, but all of them must be modifiable.
164
+
165
+ def self.update_all(docs, options)
166
+ raise ArgumentError, "docs must be an array" unless docs.kind_of? Array
167
+ raise ArgumentError, "docs must consist of Scribd::Document objects" unless docs.all? { |doc| doc.kind_of? Document }
168
+ raise ArgumentError, "You can't modify one or more documents" if docs.any? { |doc| doc.owner.nil? }
169
+ raise ArgumentError, "options must be a hash" unless options.kind_of? Hash
170
+
171
+ docs_by_user = docs.inject(Hash.new { |hash, key| hash[key] = Array.new }) { |hash, doc| hash[doc.owner] << doc; hash }
172
+ docs_by_user.each { |user, doc_list| API.instance.send_request 'docs.changeSettings', options.merge(:doc_ids => doc_list.collect(&:id).join(','), :session_key => user.session_key) }
173
+ end
174
+
175
+ # === Finding by query
176
+ #
177
+ # This method is called with a scope and a hash of options to documents by
178
+ # their content. You must at a minimum supply a +query+ option, with a
179
+ # string that will become the full-text search query. For a list of other
180
+ # supported options, please see the online API documentation at
181
+ # http://www.scribd.com/publisher/api?method_name=docs.search
182
+ #
183
+ # The scope can be any value given for the +scope+ parameter in the above
184
+ # website, or <tt>:first</tt> to return the first result only (not an array
185
+ # of results).
186
+ #
187
+ # The +num_results+ option has been aliased as +limit+, and the +num_start+
188
+ # option has been aliased as +offset+.
189
+ #
190
+ # Documents returned from this method will have their +owner+ attributes set
191
+ # to nil.
192
+ #
193
+ # Scribd::Document.find(:all, :query => 'cats and dogs', :limit => 10)
194
+ #
195
+ # === Finding by ID
196
+ #
197
+ # Passing in simply a numerical ID loads the document with that ID. You can
198
+ # pass additional options as defined at
199
+ # httphttp://www.scribd.com/publisher/api?method_name=docs.getSettings
200
+ #
201
+ # Scribd::Document.find(108196)
202
+ #
203
+ # For now only documents that belong to the current user can be accessed in
204
+ # this manner.
205
+
206
+ def self.find(scope, options={})
207
+ doc_id = scope if scope.kind_of?(Integer)
208
+ raise ArgumentError, "You must specify a query or document ID" unless options[:query] or doc_id
209
+
210
+ if doc_id then
211
+ options[:doc_id] = doc_id
212
+ response = API.instance.send_request('docs.getSettings', options)
213
+ return Document.new(:xml => response.elements['/rsp'])
214
+ else
215
+ options[:scope] = scope == :first ? 'all' : scope.to_s
216
+ options[:num_results] = options[:limit]
217
+ options[:num_start] = options[:offset]
218
+ response = API.instance.send_request('docs.search', options)
219
+ documents = []
220
+ response.elements['/rsp/result_set'].elements.each do |doc|
221
+ documents << Document.new(:xml => doc)
222
+ end
223
+ return scope == :first ? documents.first : documents
224
+ end
225
+ end
226
+
227
+ class << self
228
+ alias_method :upload, :create
229
+ end
230
+
231
+ # Returns the conversion status of this document. When a document is
232
+ # uploaded it must be converted before it can be used. The conversion is
233
+ # non-blocking; you can query this method to determine whether the document
234
+ # is ready to be displayed.
235
+ #
236
+ # The conversion status is returned as a string. For a full list of
237
+ # conversion statuses, see the online API documentation at
238
+ # http://www.scribd.com/publisher/api?method_name=docs.getConversionStatus
239
+ #
240
+ # Unlike other properties of a document, this is retrieved from the server
241
+ # every time it's queried.
242
+
243
+ def conversion_status
244
+ response = API.instance.send_request('docs.getConversionStatus', :doc_id => self.id)
245
+ response.elements['/rsp/conversion_status'].text
246
+ end
247
+
248
+ # Deletes a document. Returns true if successful.
249
+
250
+ def destroy
251
+ response = API.instance.send_request('docs.delete', :doc_id => self.id)
252
+ return response.elements['/rsp'].attributes['stat'] == 'ok'
253
+ end
254
+
255
+ # Returns the +doc_id+ attribute.
256
+
257
+ def id
258
+ self.doc_id
259
+ end
260
+
261
+ # Ensures that the +owner+ attribute cannot be set once the document is
262
+ # saved.
263
+
264
+ def owner=(newuser) #:nodoc:
265
+ saved? ? raise(NotImplementedError, "Cannot change a document's owner once the document has been saved") : super
266
+ end
267
+
268
+ # Retrieves a document's download URL. You can provide a format for the
269
+ # download. Valid formats are listed at
270
+ # http://www.scribd.com/publisher/api?method_name=docs.getDownloadUrl
271
+ #
272
+ # If you do not provide a format, the link will be for the document's
273
+ # original format.
274
+
275
+ def download_url(format='original')
276
+ @download_urls[format] ||= begin
277
+ response = API.instance.send_request('docs.getDownloadUrl', :doc_id => self.id, :doc_type => format)
278
+ response.elements['/rsp/download_link'].cdatas.first.to_s
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,34 @@
1
+ module Scribd
2
+
3
+ # Raised when API calls are made before a key and secret are specified.
4
+
5
+ class NotReadyError < StandardError; end
6
+
7
+ # Raised when the XML returned by Scribd is malformed.
8
+
9
+ class MalformedResponseError < StandardError; end
10
+
11
+ # Raised when trying to perform an action that isn't allowed for the current
12
+ # active user. Note that this exception is thrown only if the error originates
13
+ # locally. If the request must go out to the Scribd server before the
14
+ # privilege error occurs, a Scribd::ResponseError will be thrown. Unless a
15
+ # method's documentation indicates otherwise, assume that the error will
16
+ # originate remotely and a Scribd::ResponseError will be thrown.
17
+
18
+ class PrivilegeError < StandardError; end
19
+
20
+ # Raised when a remote error occurs. Remote errors are referenced by numerical
21
+ # code. The online API documentation has a list of possible error codes and
22
+ # their descriptions for each API method.
23
+
24
+ class ResponseError < RuntimeError
25
+ # The error code.
26
+ attr_reader :code
27
+
28
+ # Initializes the error with a given code.
29
+
30
+ def initialize(code)
31
+ @code = code
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ # This is based on a great snippet from Pivot Labs, used with permission:
2
+ # http://pivots.pivotallabs.com/users/damon/blog/articles/227-standup-04-27-07-testing-file-uploads
3
+
4
+ require 'net/https'
5
+ require 'rubygems'
6
+ require 'mime/types' # Requires gem install mime-types
7
+ require 'cgi'
8
+
9
+ module Net #:nodoc:all
10
+ class HTTP::Post
11
+ def multipart_params=(param_hash={})
12
+ boundary_token = [Array.new(8) {rand(256)}].join
13
+ self.content_type = "multipart/form-data; boundary=#{boundary_token}"
14
+ boundary_marker = "--#{boundary_token}\r\n"
15
+ self.body = param_hash.map { |param_name, param_value|
16
+ boundary_marker + case param_value
17
+ when File
18
+ file_to_multipart(param_name, param_value)
19
+ else
20
+ text_to_multipart(param_name, param_value.to_s)
21
+ end
22
+ }.join('') + "--#{boundary_token}--\r\n"
23
+ end
24
+
25
+ protected
26
+ def file_to_multipart(key,file)
27
+ filename = File.basename(file.path)
28
+ mime_types = MIME::Types.of(filename)
29
+ mime_type = mime_types.empty? ? "application/octet-stream" : mime_types.first.content_type
30
+ part = %Q|Content-Disposition: form-data; name="#{key}"; filename="#{filename}"\r\n|
31
+ part += "Content-Transfer-Encoding: binary\r\n"
32
+ part += "Content-Type: #{mime_type}\r\n\r\n#{file.read}\r\n"
33
+ end
34
+
35
+ def text_to_multipart(key,value)
36
+ "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n#{value}\r\n"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,164 @@
1
+ module Scribd
2
+
3
+ # Describes a remote object that the Scribd API lets you interact with. All
4
+ # such objects are modeled after the <tt>ActiveRecord</tt> ORM approach.
5
+ #
6
+ # The Resource superclass is never directly used; you will interact with
7
+ # actual Scribd entities like Document and User, which inherit functionality
8
+ # from this superclass.
9
+ #
10
+ # Objects have one or more attributes (also called fields) that can be
11
+ # accessed directly through synonymous methods. For instance, if your resource
12
+ # has an attribute +title+, you can get and set the title like so:
13
+ #
14
+ # obj.title #=> "Title"
15
+ # obj.title = "New Title"
16
+ #
17
+ # The specific attributes that a Document or a User or any other resource has
18
+ # are not stored locally. They are downloaded remotely whenever a resource is
19
+ # loaded from the remote server. Thus, you can modify any attribute you want,
20
+ # though it may or may not have any effect:
21
+ #
22
+ # doc = Scribd::Document.find(:text => 'foo').first
23
+ # doc.self_destruct_in = 5.seconds #=> Does not produce error
24
+ # doc.save #=> Has no effect, since that attribute doesn't exist. Your document does not explode.
25
+ #
26
+ # As shown above, when you make changes to an attribute, these changes are not
27
+ # immediately reflected remotely. They are only stored locally until such time
28
+ # as save is called. When you call save, the remote object is updated to
29
+ # reflect the changes you made in its API instance.
30
+
31
+ class Resource
32
+
33
+ # Initializes instance variables.
34
+
35
+ def initialize(options={})
36
+ @saved = false
37
+ @created = false
38
+ @attributes = Hash.new
39
+ end
40
+
41
+ # Creates a new instance with the given attributes, saves it immediately,
42
+ # and returns it. You should call its created? method if you need to verify
43
+ # that the object was saved successfully.
44
+
45
+ def self.create(options={})
46
+ obj = new(options)
47
+ obj.save
48
+ obj
49
+ end
50
+
51
+ # Throws NotImplementedError by default.
52
+
53
+ def save
54
+ raise NotImplementedError, "Cannot save #{self.class.to_s} objects"
55
+ end
56
+
57
+ # Throws NotImplementedError by default.
58
+
59
+ def self.find(options)
60
+ raise NotImplementedError, "Cannot find #{self.class.to_s} objects"
61
+ end
62
+
63
+ # Throws NotImplementedError by default.
64
+
65
+ def destroy
66
+ raise NotImplementedError, "Cannot destroy #{self.class.to_s} objects"
67
+ end
68
+
69
+ # Returns true if this document's attributes have been updated remotely, and
70
+ # thus their local values reflect the remote values.
71
+
72
+ def saved?
73
+ @saved
74
+ end
75
+
76
+ # Returns true if this document has been created remotely, and corresponds
77
+ # to a document on the Scribd website.
78
+
79
+ def created?
80
+ @created
81
+ end
82
+
83
+ # Returns the value of an attribute, referenced by string or symbol, or nil
84
+ # if the attribute cannot be read.
85
+
86
+ def read_attribute(attribute)
87
+ raise ArgumentError, "Attribute must respond to to_sym" unless attribute.respond_to? :to_sym
88
+ @attributes[attribute.to_sym]
89
+ end
90
+
91
+ # Returns a map of attributes to their values, given an array of attributes,
92
+ # referenced by string or symbol. Attributes that cannot be read are
93
+ # ignored.
94
+
95
+ def read_attributes(attributes)
96
+ raise ArgumentError, "Attributes must be listed in an Enumeration" unless attributes.kind_of?(Enumerable)
97
+ raise ArgumentError, "All attributes must respond to to_sym" unless attributes.all? { |a| a.respond_to? :to_sym }
98
+ keys = attributes.map(&:to_sym)
99
+ values = @attributes.values_at(*keys)
100
+ keys.zip(values).to_hsh
101
+ end
102
+
103
+ # Assigns values to attributes. Takes a hash that specifies the
104
+ # attribute-value pairs to update. Does not perform a save. Non-writeable
105
+ # attributes are ignored.
106
+
107
+ def write_attributes(values)
108
+ raise ArgumentError, "Values must be specified through a hash of attributes" unless values.kind_of? Hash
109
+ raise ArgumentError, "All attributes must respond to to_sym" unless values.keys.all? { |a| a.respond_to? :to_sym }
110
+ @attributes.update values.map { |k,v| [ k.to_sym, v ] }.to_hsh
111
+ end
112
+
113
+ # Gets or sets attributes for the resource. Any named attribute can be
114
+ # retrieved for changed through a method call, even if it doesn't exist.
115
+ # Such attributes will be ignored and purged when the document is saved:
116
+ #
117
+ # doc = Scribd::Document.new
118
+ # doc.foobar #=> Returns nil
119
+ # doc.foobar = 12
120
+ # doc.foobar #=> Returns 12
121
+ # doc.save
122
+ # doc.foobar #=> Returns nil
123
+ #
124
+ # Because of this, no Scribd resource will ever raise NoMethodError.
125
+
126
+ def method_missing(meth, *args)
127
+ if meth.to_s =~ /(\w+)=/ then
128
+ raise ArgumentError, "Only one parameter can be passed to attribute=" unless args.size == 1
129
+ @attributes[$1.to_sym] = args[0]
130
+ else
131
+ @attributes[meth]
132
+ end
133
+ end
134
+
135
+ # Pretty-print for debugging output of Scribd resources.
136
+
137
+ def inspect #:nodoc:
138
+ "#<#{self.class.to_s} #{@attributes.select { |k, v| not v.nil? }.collect { |k,v| k.to_s + '=' + v.to_s }.join(', ')}>"
139
+ end
140
+
141
+ private
142
+
143
+ def load_attributes(xml)
144
+ @attributes.clear
145
+ xml.each_element do |element|
146
+ text = if element.text.nil? or element.text.chomp.strip.empty? then
147
+ (element.cdatas and not element.cdatas.empty?) ? element.cdatas.first.value : nil
148
+ else
149
+ element.text
150
+ end
151
+
152
+ @attributes[element.name.to_sym] = if element.attributes['type'] == 'integer' then
153
+ text.to_i
154
+ elsif element.attributes['type'] == 'float' then
155
+ text.to_f
156
+ elsif element.attributes['type'] == 'symbol' then
157
+ text.to_sym
158
+ else
159
+ text
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
data/lib/scribduser.rb ADDED
@@ -0,0 +1,143 @@
1
+ module Scribd
2
+
3
+ # A user of the Scribd website. API programs can use this class to log in as a
4
+ # Scribd user, create new user accounts, and get information about the current
5
+ # user.
6
+ #
7
+ # An API program begins by logging into Scribd:
8
+ #
9
+ # user = Scribd::User.login 'login', 'pass'
10
+ #
11
+ # You can now access information about this user through direct method calls:
12
+ #
13
+ # user.name #=> 'Real Name'
14
+ #
15
+ # If, at any time, you would like to retrieve the Scribd::User instance for
16
+ # the currently logged-in user, simply call:
17
+ #
18
+ # user = Scribd::API.instance.user
19
+ #
20
+ # For information on a user's attributes, please consult the online API
21
+ # documentation at http://www.scribd.com/publisher/api?method_name=user.login
22
+ #
23
+ # You can create a new account with the signup (a.k.a. create) method:
24
+ #
25
+ # user = Scribd::User.signup :username => 'testuser', :password => 'testpassword', :email => your@email.com
26
+
27
+ class User < Resource
28
+
29
+ # Creates a new, unsaved user with the given attributes. You can eventually
30
+ # use this record to create a new Scribd account.
31
+
32
+ def initialize(options={})
33
+ super
34
+ if options[:xml] then
35
+ load_attributes(options[:xml])
36
+ @saved = true
37
+ @created = true
38
+ else
39
+ @attributes = options
40
+ end
41
+ end
42
+
43
+ # For new, unsaved records, creates a new Scribd user with the provided
44
+ # attributes, then logs in as that user. Currently modification of existing
45
+ # Scribd users is not supported. Throws a ResponseError if a remote error
46
+ # occurs.
47
+
48
+ def save
49
+ if not created? then
50
+ response = API.instance.send_request('user.signup', @attributes)
51
+ xml = response.get_elements('/rsp')[0]
52
+ load_attributes(xml)
53
+ API.instance.user = self
54
+ else
55
+ raise NotImplementedError, "Cannot update a user once that user's been saved"
56
+ end
57
+ end
58
+
59
+ # Returns a list of all documents owned by this user. This list is _not_
60
+ # backed by the server, so if you add or remove items from it, it will not
61
+ # make those changes server-side. This also has some tricky consequences
62
+ # when modifying a list of documents while iterating over it:
63
+ #
64
+ # docs = user.documents
65
+ # docs.each(&:destroy)
66
+ # docs #=> Still populated, because it hasn't been updated
67
+ # docs = user.documents #=> Now it's empty
68
+ #
69
+ # Scribd::Document instances returned through this method have more
70
+ # attributes than those returned by the Scribd::Document.find method. The
71
+ # additional attributes are documented online at
72
+ # http://www.scribd.com/publisher/api?method_name=docs.getSettings
73
+
74
+ def documents
75
+ response = API.instance.send_request('docs.getList', { :session_key => @attributes[:session_key] })
76
+ documents = Array.new
77
+ response.elements['/rsp/resultset'].elements.each do |doc|
78
+ documents << Document.new(:xml => doc, :owner => self)
79
+ end
80
+ return documents
81
+ end
82
+
83
+ # Finds documents owned by this user matching a given query. The parameters
84
+ # provided to this method are identical to those provided to
85
+ # Scribd::Document.find.
86
+
87
+ def find_documents(options)
88
+ return nil unless @attributes[:session_key]
89
+ Document.find options.merge(:scope => 'user', :session_key => @attributes[:session_key])
90
+ end
91
+
92
+ # Loads a Scribd::Document by ID. You can only load such documents if they
93
+ # belong to this user.
94
+
95
+ def find_document(document_id)
96
+ return nil unless @attributes[:session_key]
97
+ response = API.instance.send_request('docs.getSettings', { :doc_id => document_id, :session_key => @attributes[:session_key] })
98
+ Document.new :xml => response.elements['/rsp'], :owner => self
99
+ end
100
+
101
+ # Uploads a document to a user's document list. This method takes the
102
+ # following options:
103
+ #
104
+ # +file+:: The location of a file on disk or the URL to a file on the Web
105
+ # +type+:: The file's type (e.g., "txt" or "ppt"). Optional if the file has
106
+ # an extension (like "file.txt").
107
+ #
108
+ # There are other options you can specify. For more information, see the
109
+ # Scribd::Document.save method.
110
+
111
+ def upload(options)
112
+ raise NotReadyError, "User hasn't been created yet" unless created?
113
+ Document.create options.merge(:owner => self)
114
+ end
115
+
116
+ class << self
117
+ alias_method :signup, :create
118
+ end
119
+
120
+ # Logs into Scribd using the given username and password. This user will be
121
+ # used for all subsequent Scribd API calls. You must log in before you can
122
+ # use protected API functions. Returns the Scribd::User instance for the
123
+ # logged in user.
124
+
125
+ def self.login(username, password)
126
+ response = API.instance.send_request('user.login', { :username => username, :password => password })
127
+ xml = response.get_elements('/rsp')[0]
128
+ user = User.new(:xml => xml)
129
+ API.instance.user = user
130
+ return user
131
+ end
132
+
133
+ # Returns the +user_id+ attribute.
134
+
135
+ def id
136
+ self.user_id
137
+ end
138
+
139
+ def to_s #:nodoc:
140
+ @attributes[:username]
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,47 @@
1
+ # Example 1 - Uploading a test text file and removing it afterwards.
2
+
3
+ require 'rubygems'
4
+ require 'rscribd'
5
+
6
+ # Use your API key / secret here
7
+ api_key = ''
8
+ api_secret = ''
9
+
10
+ # Create a scribd object
11
+ Scribd::API.instance.key = api_key
12
+ Scribd::API.instance.secret = api_secret
13
+ #Scribd::API.instance.debug = true
14
+
15
+ begin
16
+ Scribd::User.login 'LOGIN', 'PASSWORD'
17
+ # Upload the document from a file
18
+ print "Uploading a document ... "
19
+
20
+ doc = Scribd::Document.upload(:file => 'test.txt')
21
+ puts "Done doc_id=#{doc.id}, doc_access_key=#{doc.access_key}"
22
+
23
+ # Poll API until conversion is complete
24
+ while (doc.conversion_status == 'PROCESSING')
25
+ puts "Document conversion is processing"
26
+ sleep(2) # Very important to sleep to prevent a runaway loop that will get your app blocked
27
+ end
28
+ puts "Document conversion is complete"
29
+
30
+ # Edit various options of the document
31
+ # Note you can also edit options before your doc is done converting
32
+ doc.title = 'This is a test doc!'
33
+ doc.description = "I'm testing out the Scribd API!"
34
+ doc.access = 'private'
35
+ doc.language = 'en'
36
+ doc.license = 'c'
37
+ doc.tags = 'test,api'
38
+ doc.show_ads = true
39
+ doc.save
40
+
41
+ # Delete the uploaded document
42
+ print "Deleting a document ... "
43
+ doc.destroy
44
+ puts "Done doc_id=#{doc.id}"
45
+ rescue Scribd::ResponseError => e
46
+ puts "failed code=#{e.code} error='#{e.message}'"
47
+ end
data/sample/02_user.rb ADDED
@@ -0,0 +1,44 @@
1
+ # Example 2 - Signing in as a user, and accessing a user's files.
2
+
3
+ require 'rubygems'
4
+ require 'rscribd'
5
+
6
+ # Use your API key / secret here
7
+ api_key = ''
8
+ api_secret = ''
9
+
10
+ # Edit these to a real Scribd username/password pair
11
+ username = ''
12
+ password = ''
13
+
14
+ # Create a scribd object
15
+ Scribd::API.instance.key = api_key
16
+ Scribd::API.instance.secret = api_secret
17
+ #Scribd::API.instance.debug = true
18
+
19
+ begin
20
+
21
+ # Login your Scribd API object as a particular user
22
+ # NOTE: Edit this to the username and password of a real Scribd user
23
+ user = Scribd::User.login username, password
24
+
25
+ docs = user.documents
26
+
27
+ puts "User #{user.username} has #{docs.size} docs"
28
+ if docs.size > 0
29
+ puts "User's docs:"
30
+ for doc in docs
31
+ puts "#{doc.title}"
32
+ end
33
+ end
34
+
35
+ results = Scribd::Document.find(:all, :query => 'checklist') # Search over the user's docs for the string 'checklist'
36
+ puts "Search through docs turned up #{results.size} results:"
37
+ for doc in results
38
+ puts "#{doc.title}"
39
+ end
40
+
41
+
42
+ rescue Scribd::ResponseError => e
43
+ puts "failed code=#{e.code} error='#{e.message}'"
44
+ end
data/sample/test.txt ADDED
@@ -0,0 +1 @@
1
+ This is a simple test text file to try out the neat Scribd API.
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scribd-rscribd
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jared Friedman, Tim Morgan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mime-types
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.2
34
+ version:
35
+ description: This gem provides a simple and powerful library for the Scribd API, allowing you to write Ruby applications or Ruby on Rails websites that upload, convert, display, search, and control documents in many formats. For more information on the Scribd platform, visit http://www.scribd.com/publisher
36
+ email: api@scribd.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - History.txt
43
+ - Manifest.txt
44
+ - README.txt
45
+ files:
46
+ - History.txt
47
+ - Manifest.txt
48
+ - README.txt
49
+ - Rakefile
50
+ - lib/scribdapi.rb
51
+ - lib/scribddoc.rb
52
+ - lib/scribderrors.rb
53
+ - lib/scribdmultiparthack.rb
54
+ - lib/scribdresource.rb
55
+ - lib/rscribd.rb
56
+ - lib/scribduser.rb
57
+ - sample/01_upload.rb
58
+ - sample/02_user.rb
59
+ - sample/test.txt
60
+ has_rdoc: true
61
+ homepage: http://github.com/scribd/rscribd
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --main
65
+ - README.txt
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ requirements: []
81
+
82
+ rubyforge_project: rscribd
83
+ rubygems_version: 1.2.0
84
+ signing_key:
85
+ specification_version: 2
86
+ summary: Ruby client library for the Scribd API
87
+ test_files: []
88
+