jphastings-dlc 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. data/dlc.rb +329 -0
  2. metadata +72 -0
data/dlc.rb ADDED
@@ -0,0 +1,329 @@
1
+ # = The DLC API in ruby!
2
+ # Visit http://jdownloader.org for more information on why this might be useful!
3
+ #
4
+ # My thanks go out to the JDownloader staff for making such an excellent application! I've spoken with them and this script is now available for download at the github page (http://github.com/jphastings/ruby-DLC) or you can install the gem with:
5
+ # gem sources -a http://gems.github.com
6
+ # sudo gem install jphastings-dlc
7
+ #
8
+ # == How to Use
9
+ # You can use this library from the irb, or in any script you like. Examples below are for irb, I'm sure people using this as a
10
+ # library will be able to figure out how to use it in that way.
11
+ # === Set Up
12
+ # You'll need to set the settings file before you do anything else. Open up irb in the directory where this file is located:
13
+ # >> require 'dlc'
14
+ # => true
15
+ # >> s = DLC::Settings.new
16
+ # No settings file exists. You should set them with the follwing in irb:
17
+ # require 'dlc'
18
+ # s = DLC::Settings.new
19
+ # s.name = "Your Name"
20
+ # s.url = "http://yourdomain.com"
21
+ # s.email = "your.name@yourdomain.com"
22
+ # You need to specify a name, url and email for the DLC generator. See the documentation
23
+ # =>
24
+ # >> s.name = "Your Name"
25
+ # => "Your Name"
26
+ # >> s.url = "http://your.domain.com"
27
+ # => "http://your.domain.com"
28
+ # >> s.email = "your.name@your.domain.com"
29
+ # => "your.name@your.domain.com"
30
+ #
31
+ # Now your settings file has been created you can go about making DLCs!
32
+ #
33
+ # === Creating a Package and DLC
34
+ # The following irb example shows the variety of options you have while creating a package
35
+ # >> p = DLC::Package.new
36
+ # => Unnamed package (0 links, 0 passwords)
37
+ # >> p.name = "My Package"
38
+ # => "My Package"
39
+ # >> p.comment = "An exciting package!"
40
+ # => "An exciting package!"
41
+ # >> p.category = "Nothing useful"
42
+ # => "Nothing useful"
43
+ # >> p.add_link("http://google.com/")
44
+ # => true
45
+ # >> p.add_link(["http://bbc.co.uk","http://slashdot.org"])
46
+ # => true
47
+ # >> p.add_password("I don't really need one of these")
48
+ # => true
49
+ # >> p p
50
+ # "My Package" (3 links, 1 passwords)
51
+ # # An exciting package!
52
+ # => nil
53
+ # >> p.dlc
54
+ #
55
+ # That final command will give you the string for your DLC. If you want to put it into a file you should do:
56
+ # open("my_dlc.dlc","w") do |f|
57
+ # f.write p.dlc
58
+ # end
59
+ #
60
+ # This will ensure the file gets closed after you've written your DLC to it.
61
+ #
62
+ # == Problems?
63
+ # Found a bug? Leave me an issue report on the githib page: http://github.com/jphastings/ruby-DLC/issues -
64
+ # I'll get onto it as soon as I can and see if I can fix it.
65
+ #
66
+ # == More Information
67
+ # I'm JP, you can find my things at http://kedakai.co.uk. Any questions can be sent to me via twitter: @jphastings.
68
+ #
69
+ # I did this entirely for fun, please take that into account if/when you ask for help or before you get in touch. If you have any code improvements
70
+ # please do let me know! I hope you enjoy this!
71
+
72
+ require 'yaml'
73
+ require 'time'
74
+ require 'net/http'
75
+ require 'digest/md5'
76
+
77
+ require 'rubygems'
78
+ require 'builder'
79
+ require 'ruby-aes'
80
+
81
+ # A hack to make the AES module accept string keys when they look like hex!
82
+ def Aes.check_iv(iv)
83
+ return iv
84
+ end
85
+
86
+ # The DLC module, this is the container for the Settings and Package classes. It also contains some private helper functions
87
+ module DLC
88
+ Api = {
89
+ :version => 1.0,
90
+ :pair_expires_after => 3600,
91
+ :service_urls => ["http://service.jdownloader.org/dlcrypt/service.php"],
92
+ }
93
+
94
+ # Settings is a class that deals with information about the group using the DLC api.
95
+ #
96
+ # The class contains code to write its own settings file. Before using this ruby DLC api
97
+ # you should require it in irb and set the details:
98
+ # require 'dlc'
99
+ # s = DLC::Settings.new
100
+ # s.name = "Your name"
101
+ # s.email = "you@yourdomain.com"
102
+ # s.url = "http://yourdomain.com/why_i_use_dlc.html"
103
+ # p s
104
+ class Settings
105
+ attr_accessor :email,:name,:url
106
+
107
+ # I may allow this to be changed in later versions
108
+ Settings = "dlc_settings.yml"
109
+
110
+ def initialize
111
+ if File.exists? Settings
112
+ begin
113
+ s = YAML.load(open(Settings))
114
+ @email = s[:email]
115
+ @name = s[:name]
116
+ @url = s[:url]
117
+ @keycache = (s[:keycache][:expires].nil? or s[:keycache][:expires] < Time.now.to_i) ? {} : s[:keycache]
118
+ rescue
119
+ raise SettingsNotValidError, "Your settings file is not valid. Please remove it."
120
+ end
121
+ else
122
+ $stderr.puts "No settings file exists. Read the documentation to find out how to make one."
123
+ @keycache = {}
124
+ end
125
+ end
126
+
127
+ # Validate email address entry
128
+ def email=(email)
129
+ if email =~ /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i
130
+ @email = email
131
+ write_settings
132
+ else
133
+ $stderr.puts "That is an invalid email address"
134
+ end
135
+ end
136
+ # Must have a Name to make DLCs
137
+ def name=(name)
138
+ if not name.nil? and name.length != 0
139
+ @name = name
140
+ write_settings
141
+ else
142
+ $stderr.puts "You must use a full name"
143
+ end
144
+ end
145
+ # Must have a URL (starting in http:// or https://) to make DLCs
146
+ def url=(url)
147
+ if url =~ /^http(s?)\:\/\//i
148
+ @url = url
149
+ write_settings
150
+ else
151
+ $stderr.puts "Please include a leading http://"
152
+ end
153
+ end
154
+
155
+ # Allows the cache of the key/encoded key pairs
156
+ def set_keycache(key,encoded_key,expires = 3600)
157
+ @keycache = {
158
+ :expires => Time.now.to_i + expires,
159
+ :key => key,
160
+ :encoded_key => encoded_key
161
+ }
162
+ write_settings
163
+ end
164
+
165
+ # Retrieve the key from the cache, if there is one there.
166
+ # This will raise a +NoKeyCachedError+ if there is no key.
167
+ def get_keycache
168
+ if @keycache[:expires].nil? or @keycache[:expires] < Time.now.to_i
169
+ @keycache = {}
170
+ raise NoKeyCachedError, "There is no key in the cache"
171
+ else
172
+ return @keycache[:key],@keycache[:encoded_key]
173
+ end
174
+ end
175
+
176
+ # A helper for irb people, and hands-on developlers
177
+ def inspect
178
+ if @name.nil? or @email.nil? or @url.nil?
179
+ $stderr.puts "You need to specify a name, url and email for the DLC generator. See the documentation"
180
+ return nil
181
+ end
182
+ "DLC API owner: #{@name} <#{@email}> (#{@url})"
183
+ end
184
+
185
+ private
186
+ def write_settings
187
+ open(Settings,"w") do |f|
188
+ p @keycache
189
+ f.write YAML.dump({:name => @name, :email => @email, :url => @url,:keycache => @keycache})
190
+ end
191
+ end
192
+ end
193
+
194
+ # The DLC package handler class. Make a new one of these for each package you want to create.
195
+ class Package
196
+ attr_reader :links,:passwords
197
+ attr_accessor :comment, :name, :category
198
+
199
+ # Makes sure all the defaults are set (this would make a valid, if useless, package)
200
+ def initialize
201
+ @links = []
202
+ @passwords = []
203
+ @category = "various"
204
+ @comment = ""
205
+ end
206
+
207
+ # Adds a link to the package
208
+ # Will take an array of links too
209
+ # I will, at some point, include the ability to specify filename and size.
210
+ def add_link(url)
211
+ if url.is_a?(Array)
212
+ url.each do |u|
213
+ self.add_link(u)
214
+ end
215
+ return true
216
+ end
217
+ if url.is_a?(String) and url =~ /^http(s)?\:\/\//
218
+ @links.push({:url=>url,:filename=>nil,:size=>0})
219
+ return true
220
+ end
221
+ $stderr.puts "Invalid URL: #{url}"
222
+ return false
223
+ end
224
+
225
+ # Adds a password to the package
226
+ # Also accepts an array of passwords
227
+ def add_password(password)
228
+ if password.is_a?(Array)
229
+ password.each do |p|
230
+ self.add_password(p)
231
+ end
232
+ return true
233
+ end
234
+ if password.is_a?(String)
235
+ @passwords.push(password)
236
+ return true
237
+ end
238
+ $stderr.puts "Invalid password: #{password}"
239
+ return false
240
+ end
241
+
242
+ # Gives you the DLC of the package you've created. First run (every hour) will take longer than the others while
243
+ # the jdownloader service is queried for information.
244
+ def dlc
245
+ settings = DLC::Settings.new
246
+ if settings.inspect.nil?
247
+ raise NoGeneratorDetailsError, "You must enter a name, url and email for the generator. See the documentation."
248
+ end
249
+
250
+ xml = Builder::XmlMarkup.new(:indent=>0)
251
+ xml.dlc {
252
+ xml.header {
253
+ xml.generator {
254
+ xml.app(DLC.encode("Ruby DLC API (kedakai)"))
255
+ xml.version(DLC.encode(DLC::Api[:version]))
256
+ xml.url(DLC.encode(settings.url))
257
+ }
258
+ xml.tribute {
259
+ xml.name(DLC.encode(settings.name))
260
+ }
261
+ xml.dlcxmlversion(DLC.encode('20_02_2008'))
262
+ }
263
+ xml.content {
264
+ package = {:name => DLC.encode(@name)}
265
+ package[:passwords] = DLC.encode(@passwords.collect{|pw| "\"#{pw}\""}.join(",")) if @passwords.length != 0
266
+ package[:comment] = DLC.encode(@comment) if @comment != ""
267
+ package[:category] = DLC.encode(@category) if @category != ""
268
+ xml.package(package) {
269
+ @links.each do |link|
270
+ xml.file {
271
+ xml.url(DLC.encode(link[:url]))
272
+ xml.filename(DLC.encode(link[:filename]))
273
+ xml.size(DLC.encode(link[:size]))
274
+ }
275
+ end
276
+ }
277
+ }
278
+ }
279
+
280
+ # Lets get a key/encoded key pair
281
+ begin
282
+ key, encoded_key = settings.get_keycache
283
+ rescue NoKeyCachedError
284
+ # Generate a key
285
+ expires = 3600
286
+ key = Digest::MD5.hexdigest(Time.now.to_i.to_s+"salty salty"+rand(100000).to_s)[0..15]
287
+ begin
288
+ if Net::HTTP.post_form(URI.parse(DLC::Api[:service_urls][rand(DLC::Api[:service_urls].length)]),{
289
+ :data => key, # A random key
290
+ :lid => DLC.encode([settings.url,settings.email,expires].join("_")), # Details about the generator of the DLC
291
+ :version => DLC::Api[:version],
292
+ :client => "rubydlc"
293
+ }).body =~ /^<rc>(.+)<\/rc><rcp>(.+)<\/rcp>$/
294
+ encoded_key = $1
295
+ # What is the second part?!
296
+ settings.set_keycache(key, encoded_key, expires)
297
+ else
298
+ raise ServerNotRespondingError
299
+ end
300
+ rescue
301
+ raise ServerNotRespondingError, "The DLC service is not responding in the expected way. Try again later."
302
+ end
303
+ end
304
+
305
+ b64 = DLC.encode(xml.target!)
306
+ DLC.encode(Aes.encrypt_buffer(128,"CBC",key,key,b64.ljust((b64.length/16).ceil*16,"\000")))+encoded_key
307
+ end
308
+
309
+ # Gives some useful information when people use the library from irb
310
+ def inspect
311
+ ((@name.nil?) ? "Unnamed package" : "\"#{@name}\"" )+" (#{@links.length} links, #{@passwords.length} passwords)"+((@comment == "") ? "" : "\n# #{@comment}")
312
+ end
313
+ end
314
+
315
+ # For when the settings file is invalid
316
+ class SettingsNotValidError < StandardError; end
317
+ # For when a DLC is requested without settings set
318
+ class NoGeneratorDetailsError < StandardError; end
319
+ # For when the keycache is accessed and no valid key is available
320
+ class NoKeyCachedError < StandardError; end
321
+ # For when the service is not responding in the expectde manner
322
+ class ServerNotRespondingError < StandardError; end
323
+
324
+ private
325
+ def self.encode(string)
326
+ string = "n.A." if string.nil?
327
+ return [string.to_s].pack("m").gsub("\n","")
328
+ end
329
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jphastings-dlc
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - JP Hastings-Spital
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-08 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: builder
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: ruby-aes
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: Allows the generation of DLC container files (of JDownloader fame) from ruby
36
+ email: rubydlc@projects.kedakai.co.uk
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - dlc.rb
45
+ has_rdoc: true
46
+ homepage: http://projects.kedakai.co.uk/rubydlc/
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - .
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.2.0
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: Allows the generation of DLC container files (of JDownloader fame) from ruby
71
+ test_files: []
72
+