rscribd 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/scribd/base.rb DELETED
@@ -1,381 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'md5'
4
- require 'rexml/document'
5
- require 'net/http'
6
- require 'parsedate'
7
-
8
- # Main Scribd API class
9
- class Scribd
10
- # Default API key
11
- API_KEY = ''
12
-
13
- # Default API signature
14
- API_SIG = ''
15
-
16
- # Default API endpoint
17
- ENDPOINT = 'http://tikhon.com/api'
18
-
19
- # Number of tries to call a method
20
- TRIES = 3
21
-
22
- attr_reader :api_key, :host, :port, :path
23
- attr_accessor :async, :debug
24
-
25
- # Outputs whatever is given into the $stderr if debugging is enabled.
26
- #
27
- # Parameters:
28
- # args - content to output
29
- def debug(*args) $stderr.puts(sprintf(*args)) if @debug end
30
-
31
- # Initializes API
32
- #
33
- # Parameters:
34
- # api_key - API key to use
35
- # api_sig - API signature to use
36
- # endpoint - API endpoint,
37
- def initialize(api_key = API_KEY, api_sig = API_SIG, endpoint = ENDPOINT)
38
- @async = false
39
-
40
- @api_key = api_key
41
- @api_sig = api_sig
42
- @endpoint = endpoint
43
-
44
- proto, @host, @port, @path, user, pass = parse_url(@endpoint)
45
- raise ProtoUnknownError.new("Unhandled protocol '#{proto}'") if proto.downcase != 'http'
46
- end
47
-
48
- # Returns Docs API interface.
49
- def docs() @docs ||= Docs.new(self) end
50
-
51
- # Returns Search API interface.
52
- def search() @search || Search.new(self) end
53
-
54
- # Sends the HTTP form to the server API with the given fields.
55
- #
56
- # Parameters:
57
- # method - method name.
58
- # fields - the Hash of field names to field values. If a field requires
59
- # its mime-type set, you specify its value in array where the
60
- # first element is value, the second is mime-type, and the third
61
- # is file name, like this:
62
- # Simple value: :user => 'john', :pass => 'nhoj'
63
- # Typed value : :user => 'john', :file => [data, type, name]
64
- # or : ... :file => { :data => data, :mimetype => type, :filename => 'test.txt' }
65
- #
66
- # Returns:
67
- # REXML::Document with the response of the server.
68
- #
69
- # Raises:
70
- # Scribd::ResponseError upon getting the 'fail' code from the server API.
71
- # Timeout::Error if unable to connect to the server.
72
- #
73
- def send_form(method, fields)
74
- # See if method is given
75
- raise ArgumentError, "Method should be given" if method.nil? || method.empty?
76
-
77
- # Complete fields with the method name
78
- fields = {} if fields.nil?
79
- fields['method'] = method
80
-
81
- # Create a multi-part form from given fields
82
- form = Scribd::MultiPartForm.new
83
- form.parts = prepare_parts(fields)
84
-
85
- debug(form.to_s)
86
-
87
- # Configure proper boundary in the headers
88
- headers = { "Content-Type" => "multipart/form-data; boundary=" + form.boundary }
89
-
90
- # Create the connection
91
- http = Net::HTTP.new(host, port)
92
- # TODO configure timeouts through the properties
93
- # http.read_timeout = 900 # 15 minutes max upload time
94
-
95
- tries = TRIES
96
- begin
97
- tries -= 1
98
- res = http.post(path, form.to_s, headers)
99
- rescue Timeout::Error => err
100
- $stderr.puts "Timed out, will retry #{tries} more."
101
- retry if tries > 0
102
- raise err
103
- end
104
-
105
- debug(res.body)
106
-
107
- # Convert response into XML
108
- xml = REXML::Document.new(res.body)
109
-
110
- # See if there was an error and raise an exception
111
- if xml.elements['/rsp'].attributes['stat'] == 'fail'
112
- # Load default code and error
113
- code, message = -1, "Unidentified error:\n#{res.body}"
114
-
115
- # Get actual error code and message
116
- err = xml.elements['/rsp/error']
117
- code, message = err.attributes['code'], err.attributes['message'] if err
118
-
119
- # Add more data
120
- message = "Method: #{method} Response: code=#{code} message=#{message}"
121
-
122
- raise Scribd::ResponseError.new(code), message
123
- end
124
-
125
- return xml
126
- end
127
-
128
- private
129
-
130
- # Sign the arguments hash with our very own signature.
131
- #
132
- # Parameters:
133
- # args - method arguments to be sent to the server API
134
- #
135
- # Returns:
136
- # signature
137
- #
138
- def sign(args)
139
- return MD5.md5(@api_sig + args.sort.flatten.join).to_s
140
- end
141
-
142
- # Parses given URL into pieces
143
- #
144
- # Parameters:
145
- # url - URL to parse
146
- #
147
- # Returns:
148
- # array of protocol, host, port, path, user, and password
149
- #
150
- def parse_url(url)
151
- url =~ /([^:]+):\/\/([^\/]*)(.*)/
152
- proto = $1.to_s
153
- hostplus = $2.to_s
154
- path = $3.to_s
155
-
156
- hostplus =~ /(?:(.*)@)?(.*)/
157
- userpass = $1
158
- hostport = $2
159
- user, pass = userpass.to_s.split(':', 2)
160
- host, port = hostport.to_s.split(':', 2)
161
- port = port ? port.to_i : 80
162
-
163
- return proto, host, port, path, user, pass
164
- end
165
-
166
- # Prepares a list of parts for a multi-part form.
167
- # The signature is added to the end of the list as a part.
168
- # Fields with mime-type set are ignored during the signature calculation.
169
- #
170
- # Parameters:
171
- # fields - the Hash of field names to field values. If a field requires
172
- # its mime-type set, you specify its value in array where the
173
- # first element is value, the second is mime-type, and the third
174
- # is file name, like this:
175
- # Simple value: :user => 'john', :pass => 'nhoj'
176
- # Typed value : :user => 'john', :file => [data, type, name]
177
- # or : ... :file => { :data => data, :mimetype => type, :filename => 'test.txt' }
178
- #
179
- # Returns:
180
- # array of Scribd::FormPart objects
181
- #
182
- def prepare_parts(fields)
183
- # Parameter check
184
- raise "Fields must be a Hash" unless fields.nil? || fields.class == Hash
185
-
186
- # Build the list of form parts and the list of non-typed
187
- # arguments in parallel. The list of arguments will be used
188
- # for the signature calculation.
189
- parts = []
190
- args = {}
191
- fields.each do |k, v|
192
- if v.class == Array
193
- data, mimetype, filename = v
194
- elsif v.class == Hash
195
- data, mimetype, filename = v[:data], v[:mimetype], v[:filename]
196
- else
197
- data = v.to_s
198
- end
199
-
200
- parts << Scribd::FormPart.new(k.to_s, data, mimetype, filename)
201
-
202
- # We don't place fields with mime-type set into the array
203
- # as they usually are files and other binary data we don't
204
- # want to hash.
205
- args[k.to_s] = v.to_s if mimetype.nil?
206
- end
207
- parts << Scribd::FormPart.new('api_key', api_key)
208
-
209
- # Add the key and calculate the signature
210
- args['api_key'] = api_key
211
- parts << Scribd::FormPart.new('api_sig', sign(args))
212
-
213
- return parts
214
- end
215
- end
216
-
217
- # Base class for API groups
218
- class Scribd::APIBase
219
- attr_reader :scribd
220
-
221
- # Initializes API group
222
- def initialize(scribd) @scribd = scribd end
223
-
224
- protected
225
-
226
- # FIXME: Since we don't need XMLRPC, the exception could be different
227
- # TODO: It would probably be better if we wrapped the fault
228
- # in something more meaningful. At the very least, a broad
229
- # division of errors, such as retryable and fatal.
230
- def error(el)
231
- att = el.attributes
232
- fe = XMLRPC::FaultException.new(att['code'].to_i, att['msg'])
233
- $stderr.puts "ERR: #{fe.faultString} (#{fe.faultCode})"
234
- raise fe
235
- end
236
-
237
- # Checks if a string parameter is given and not empty.
238
- #
239
- # Parameters:
240
- # name - parameter name for an error message.
241
- # value - value.
242
- #
243
- # Raises:
244
- # ArgumentError if the value is nil, or empty.
245
- #
246
- def check_not_empty(name, value)
247
- check_given(name, value)
248
- raise ArgumentError, "#{name} must not be empty" if value.to_s.empty?
249
- end
250
-
251
- # Checks an integer parameter for being given, being a number,
252
- # and being within the range.
253
- #
254
- # Parameters:
255
- # name - parameter name for an error message.
256
- # value - value.
257
- # min - range minimum.
258
- # max - range maximum.
259
- #
260
- # Raises:
261
- # ArgumentError if the value is nil, not Fixnum, or outside the [min, max] range.
262
- #
263
- def check_in_range(name, value, min, max)
264
- check_given(name, value)
265
- raise ArgumentError, "#{name} is not a number" if value.class != Fixnum
266
- raise ArgumentError, "#{name} is out of range [#{min}, #{max}]" if value < min || value > max
267
- end
268
-
269
- # Checks the value for being given, and being mentioned in the list.
270
- #
271
- # Parameters:
272
- # name - parameter name for an error message.
273
- # value - value.
274
- # allowed - list of allowed values.
275
- #
276
- # Raises:
277
- # ArgumentError if the value is nil, or not among the allowed values.
278
- #
279
- def check_allowed(name, value, allowed)
280
- check_given(name, value)
281
- raise ArgumentError, "#{name} is out of range #{allowed.inspect}" unless allowed.include?(value.to_sym)
282
- end
283
-
284
- # Checks if the value is given.
285
- #
286
- # Parameters:
287
- # name - parameter name for an error message.
288
- # value - value.
289
- #
290
- # Raises:
291
- # ArgumentError if the value is nil.
292
- #
293
- def check_given(name, value)
294
- raise ArgumentError, "#{name} must be given" if value.nil?
295
- end
296
- end
297
-
298
- # Form part class represents a single block of attributes, or
299
- # file or other part of the form.
300
- class Scribd::FormPart
301
- attr_reader :data, :mime_type, :attributes
302
-
303
- # Initializes the part.
304
- #
305
- # Parameters:
306
- # name - name
307
- # data - data
308
- # mime_type - optional type of the form part
309
- # filename - optional name of the file (use with mime-type)
310
- #
311
- def initialize(name, data, mime_type = nil, filename = nil)
312
- @attributes = {}
313
- @attributes['name'] = name
314
- @attributes['filename'] = filename unless filename.nil?
315
- @data = data
316
- @mime_type = mime_type
317
- end
318
-
319
- # Returns string representation.
320
- def to_s
321
- mime_present = @mime_type.nil? || @mime_type.empty?
322
-
323
- # Disposition
324
- fld_disposition = "Content-Disposition: form-data"
325
- attributes.each { |k, v| fld_disposition += "; #{k}=\"#{v}\"" }
326
-
327
- # Fields
328
- fields = [ fld_disposition ]
329
- unless mime_present
330
- fields << "Content-Type: #{@mime_type}"
331
- fields << "Content-Transfer-Encoding: binary"
332
- end
333
-
334
- # Add empty line and data block
335
- fields << ''
336
- fields << data
337
-
338
- return fields.join("\r\n")
339
- end
340
- end
341
-
342
- # Multi-part form object.
343
- class Scribd::MultiPartForm
344
- attr_accessor :boundary, :parts
345
-
346
- # Creates the form object.
347
- #
348
- # Parameters:
349
- # boundary - optional boundary to use for parts separation
350
- #
351
- def initialize(boundary = nil)
352
- @boundary = boundary || "----------------------------Ruby#{rand(1000000000000)}"
353
- @parts = []
354
- end
355
-
356
- # Returns string representation.
357
- def to_s
358
- "--#@boundary\r\n" +
359
- parts.map { |p| p.to_s }.join("\r\n--#@boundary\r\n") +
360
- "\r\n--#@boundary--\r\n"
361
- end
362
- end
363
-
364
- # Response error that indicates there was a problem during
365
- # processing your request to the server, which resulted in
366
- # the response with the 'fail' code.
367
- #
368
- # You can find the code and the message inside.
369
- #
370
- class Scribd::ResponseError < RuntimeError
371
- attr_reader :code
372
-
373
- # Initializes the error.
374
- #
375
- # Parameters:
376
- # code - error code.
377
- #
378
- def initialize(code)
379
- @code = code
380
- end
381
- end
data/lib/scribd/docs.rb DELETED
@@ -1,246 +0,0 @@
1
- require 'rubygems'
2
- require 'scribd/base'
3
- require 'mime/types'
4
-
5
- # Interface to the Docs server API
6
- class Scribd::Docs < Scribd::APIBase
7
-
8
- DISPLAY_FORMAT = [:html, :flash]
9
- PARENTAL_ADVISORY = [:safe, :adult]
10
- BOOLEAN = [:true, :false]
11
- ACCESS = [:public, :private]
12
- COMMENTING = [:none, :allow]
13
-
14
- # Settings with their allowed values
15
- SETTINGS = {
16
- :display_format => DISPLAY_FORMAT,
17
- :parental_advisory => PARENTAL_ADVISORY,
18
- :access => ACCESS,
19
- :commenting => COMMENTING,
20
- :allow_word_download => BOOLEAN,
21
- :allow_text_download => BOOLEAN,
22
- :allow_pdf_download => BOOLEAN
23
- }
24
-
25
- # Uploads a file specified by its name (path) and returns the
26
- # pair of document ID and GUID as an array. In addition to the
27
- # file name, you need to specify document and access types.
28
- #
29
- # Parameters:
30
- # filename - name of the file to upload.
31
- # doc_type - optional type of the document:
32
- # "pdf", "doc", "txt", "ppt", "pps", "xls", "ps", "htm",
33
- # "html", "rtf", "odt", "odp", "ods", "odg", "odf", "sxw",
34
- # "sxc", "sxi", "sxd", "jpg", "jpeg", "gif", "png"
35
- # public - optional flag showing if the document is public (default)
36
- # or private.
37
- #
38
- # Returns:
39
- # array with two elements: doc_id and doc_guid
40
- #
41
- # Raises:
42
- # ArgumentError if file name isn't given.
43
- # Scribd::ResponseError upon getting the 'fail' code from the server API.
44
- # Timeout::Error if unable to connect to the server.
45
- #
46
- def upload(filename, doc_type = nil, public = nil)
47
- # See if URL is given
48
- check_not_empty('filename', filename)
49
-
50
- # Get file type and load its data
51
- mimetype = MIME::Types.of(filename)
52
- mimetype = 'application/octet-stream' if mimetype.nil? || mimetype.empty?
53
- data = File.open(filename, 'rb').read
54
-
55
- # Make a request form
56
- fields = {}
57
- fields[:file] = { :data => data, :mimetype => mimetype,
58
- :filename => File.basename(filename) }
59
-
60
- return upload_impl('docs.upload', fields, doc_type, public)
61
- end
62
-
63
- # Uploads a file specified by an URL and returns the
64
- # pair of document ID and GUID as an array. In addition to the
65
- # file name, you need to specify document and access types.
66
- #
67
- # Parameters:
68
- # url - URL of the file to upload.
69
- # doc_type - optional type of the document:
70
- # "pdf", "doc", "txt", "ppt", "pps", "xls", "ps", "htm",
71
- # "html", "rtf", "odt", "odp", "ods", "odg", "odf", "sxw",
72
- # "sxc", "sxi", "sxd", "jpg", "jpeg", "gif", "png"
73
- # public - optional flag showing if the document is public (default)
74
- # or private.
75
- #
76
- # Returns:
77
- # array with two elements: doc_id and doc_guid
78
- #
79
- # Raises:
80
- # ArgumentError if URL isn't given.
81
- # Scribd::ResponseError upon getting the 'fail' code from the server API.
82
- # Timeout::Error if unable to connect to the server.
83
- #
84
- def upload_from_url(url, doc_type = nil, public = nil)
85
- # See if URL is given
86
- check_not_empty('url', url)
87
-
88
- # Make a request form
89
- fields = {}
90
- fields[:url] = url
91
-
92
- return upload_impl('docs.uploadFromUrl', fields, doc_type, public)
93
- end
94
-
95
- # Deletes a document from the repository by its ID.
96
- #
97
- # Parameters:
98
- # doc_id - document ID.
99
- #
100
- # Returns:
101
- # id of the deleted document.
102
- #
103
- # Raises:
104
- # ArgumentError if document ID isn't given.
105
- # Scribd::ResponseError upon getting the 'fail' code from the server API.
106
- # Timeout::Error if unable to connect to the server.
107
- #
108
- def delete(doc_id)
109
- # See if document ID is given
110
- raise ArgumentError, "Document ID must be given" if doc_id.nil?
111
-
112
- # Send form and return the result
113
- res = @scribd.send_form('docs.delete', :doc_id => doc_id)
114
- return res.elements['/rsp/doc_id'].text
115
- end
116
-
117
- # Returns the status of the document conversion.
118
- #
119
- # Parameters:
120
- # doc_id - document ID.
121
- #
122
- # Returns:
123
- # status string: "CONVERSION_ERROR", "CONVERTING", "ENCRYPTED_ERROR",
124
- # "FAILED_TO_CONVERT", "IN_QUEUE", "PUBLISHED", "UPLOADED".
125
- #
126
- # Raises:
127
- # ArgumentError if document ID isn't given.
128
- # Scribd::ResponseError upon getting the 'fail' code from the server API.
129
- # Timeout::Error if unable to connect to the server.
130
- #
131
- def get_conversion_status(doc_id)
132
- # See if document ID is given
133
- check_given('doc_id', doc_id)
134
-
135
- # Send form and return the result
136
- res = @scribd.send_form('docs.getConversionStatus', :doc_id => doc_id)
137
- return res.elements['/rsp/conversion_status'].text
138
- end
139
-
140
- # Changes the settings of the document specified by its ID. The settings
141
- # is a Hash with any number of settings key-pairs from the list below. If
142
- # no settings are given, the method call isn't relayed to the server for
143
- # the performance reasons.
144
- #
145
- # Settings keys and values you can update with this method:
146
- # title - document title.
147
- # description - document description.
148
- # display_format - "html" (for plain text, HTML and images),
149
- # "flash" (for everything else).
150
- # access - "public" or "private"
151
- # language - "ar", "bg", "da", "de", "en", "es", "fr", "he",
152
- # "hu", "it", "ja", "ko", "nl", "no", "pt", "ro",
153
- # "ru", "unknown" (default), "zh"
154
- # license - "by", "by-nc" (Creative Commons), "by-nc-nd",
155
- # "by-nc-sa", "by-nd", "by-sa",
156
- # "c" (traditional copyright),
157
- # "pd" (public domain)
158
- # allow_word_download - "true" or "false". Allow others to download a
159
- # MS Word version of this document. Default: "true".
160
- # allow_text_download - "true" or "false". Allow others to download a
161
- # plain text version of this document. Default: "true".
162
- # allow_pdf_download - "true" or "false". Allow others to download a
163
- # PDF version of this document. Default: "true".
164
- # commenting - "none" or "allow". Allow others to comment. Default: "allow".
165
- # paretal_advisory - "safe" or "adult". Default: "safe".
166
- # tags - comma-separated list of tags. Tags cannot contain whitespace.
167
- #
168
- # Parameters:
169
- # doc_id - document ID.
170
- # settings - hash with any combination of above settings.
171
- #
172
- # Returns:
173
- # id of the updated document.
174
- #
175
- # Raises:
176
- # ArgumentError if document ID isn't given.
177
- # Scribd::ResponseError upon getting the 'fail' code from the server API.
178
- # Timeout::Error if unable to connect to the server.
179
- #
180
- def change_settings(doc_id, settings = nil)
181
- # See if document ID is given
182
- check_given('doc_id', doc_id)
183
- check_settings(settings)
184
-
185
- # If settings aren't given, return. Otherwise see if they are given in a Hash
186
- return doc_id if settings.nil? || (settings.class == Hash && settings.empty?)
187
- raise ArgumentError, "Settings must be given in a Hash" if settings.class != Hash
188
-
189
- # Copy the settings as we'll be adding more keys to it
190
- fields = settings.dup
191
- fields[:doc_id] = doc_id
192
-
193
- # Send the form and return the result
194
- res = @scribd.send_form('docs.changeSettings', fields)
195
- return res.elements['/rsp/doc_id'].text
196
- end
197
-
198
- private
199
-
200
- # Checks if known settings are inside their allowed lists.
201
- #
202
- # Parameters:
203
- # settings - Hash of settings to check, as given to #change_settings.
204
- #
205
- # Raises:
206
- # ArgumentError if some setting is not given a value, or
207
- # falls out of allowed set.
208
- #
209
- def check_settings(settings)
210
- settings.each do |name, value|
211
- allowed = SETTINGS[name.to_sym]
212
-
213
- # If it's the setting we check, do it
214
- unless allowed.nil?
215
- check_given(name, value)
216
- check_allowed(name, value, allowed)
217
- end
218
- end
219
- end
220
-
221
- # Uploads the file.
222
- #
223
- # Parameters:
224
- # method - method name.
225
- # fields - method arguments.
226
- # doc_type - document type (see concrete methods for description).
227
- # public - public / private boolean flag.
228
- #
229
- # Returns:
230
- # array with two elements: doc_id and doc_guid
231
- #
232
- def upload_impl(method, fields, doc_type, public)
233
- fields = {} if fields.nil?
234
- fields[:doc_type] = doc_type unless doc_type.nil?
235
- fields[:access] = (public ? 'public' : 'private') unless public.nil?
236
-
237
- # Send it and wait for the result
238
- res = @scribd.send_form(method, fields)
239
-
240
- # Extract our response
241
- doc_id = res.elements['/rsp/doc_id'].text
242
- doc_guid = res.elements['/rsp/doc_guid'].text
243
-
244
- return doc_id, doc_guid
245
- end
246
- end
data/lib/scribd/search.rb DELETED
@@ -1,85 +0,0 @@
1
- require 'scribd/base'
2
-
3
- # Interface to the Search server API
4
- class Scribd::Search < Scribd::APIBase
5
-
6
- SORT_BY = [:popularity, :relevance, :scribds, :time, :views]
7
- SCOPE = [:all, :api]
8
- PARENTAL_ADVISORY = [:safe, :adult]
9
-
10
- # Looks for documents matching a given query. Additional optional
11
- # parameters can be used to control the output.
12
- #
13
- # Parameters:
14
- # query - query to pass to the server.
15
- # num_results - desired maximum number of records to return [1-1000].
16
- # num_start - first record in the result set to start from [1-1000].
17
- # sort_by - order of sorting, default :relevance. See SORT_BY
18
- # constant for the list of allowed values.
19
- # scope - scope of search:
20
- # :all - all of Scribd
21
- # :api - only user documents
22
- # parental_advisory - filter for adult content:
23
- # :safe - no adult content
24
- # :adult - everything
25
- #
26
- # Returns:
27
- # list of Scribd::Document objects.
28
- #
29
- # Raises:
30
- # ArgumentError if query is not given, or
31
- # num_results is out of range, or
32
- # num_start is out of range, or
33
- # sort_by is not one of allowed, or
34
- # scope is not one of allowed, or
35
- # parental_advisory is not one of allowed.
36
- # Scribd::ResponseError upon getting the 'fail' code from the server API.
37
- # Timeout::Error if unable to connect to the server.
38
- #
39
- def docs(query, num_results = 10, num_start = 1, sort_by = :relevance,
40
- scope = :all, parental_advisory = :safe)
41
-
42
- # Verify the parameters
43
- check_not_empty('query', query)
44
- check_in_range('num_results', num_results, 1, 1000)
45
- check_in_range('num_start', num_start, 1, 1000)
46
- check_allowed('sort_by', sort_by, SORT_BY)
47
- check_allowed('scope', scope, SCOPE)
48
- check_allowed('parental_advisory', parental_advisory, PARENTAL_ADVISORY)
49
-
50
- # Send the form
51
- res = @scribd.send_form('search.docs', fields)
52
-
53
- # Get documents
54
- documents = []
55
- res.elements['/response/result_set/result'].each do |doc|
56
- documents << Scribd::Document.new(
57
- doc.elements['title'],
58
- doc.elements['description'],
59
- doc.elements['scribd_url'],
60
- doc.elements['doc_id'])
61
- end
62
-
63
- return documents
64
- end
65
- end
66
-
67
- # Document returned from the search
68
- class Scribd::Document
69
- attr_reader :title, :description, :scribd_url, :doc_id
70
-
71
- # Initializes a document.
72
- #
73
- # Parameters:
74
- # title - document title.
75
- # description - description.
76
- # scribd_url - Scribd URL.
77
- # doc_id - document ID.
78
- #
79
- def initialize(title, description, scribd_url, doc_id)
80
- @title = title
81
- @description = description
82
- @scribd_url = scribd_url
83
- @doc_id = doc_id
84
- end
85
- end