olly-iplayer-dl 0.15.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/.gitignore +1 -0
- data/COPYING +26 -0
- data/README +14 -0
- data/Rakefile +67 -0
- data/VERSION.yml +4 -0
- data/application.ico +0 -0
- data/bin/iplayer-dl +152 -0
- data/bin/iplayer-dl-gui +26 -0
- data/init.rb +12 -0
- data/iplayer-dl.gemspec +67 -0
- data/lib/iplayer.rb +5 -0
- data/lib/iplayer/browser.rb +69 -0
- data/lib/iplayer/downloader.rb +132 -0
- data/lib/iplayer/errors.rb +53 -0
- data/lib/iplayer/gui/app.rb +84 -0
- data/lib/iplayer/gui/main_frame.rb +123 -0
- data/lib/iplayer/metadata.rb +63 -0
- data/lib/iplayer/preferences.rb +70 -0
- data/lib/iplayer/subtitles.rb +33 -0
- data/lib/iplayer/version.rb +4 -0
- data/setup.rb +1596 -0
- data/share/pixmaps/iplayer-dl/icon128.png +0 -0
- data/share/pixmaps/iplayer-dl/icon16.png +0 -0
- data/share/pixmaps/iplayer-dl/icon32.png +0 -0
- data/share/pixmaps/iplayer-dl/icon48.png +0 -0
- data/share/pixmaps/iplayer-dl/icon64.png +0 -0
- data/test/test_metadata.rb +75 -0
- data/test/test_preferences.rb +175 -0
- data/test/test_subtitles.rb +57 -0
- metadata +84 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/COPYING
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2008 Paul Battley <pbattley@gmail.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
21
|
+
If you have received this program as a self-executing bundle, the following
|
22
|
+
licences apply to individual components:
|
23
|
+
|
24
|
+
ruby: GNU General Public License (GPL), version 2; Ruby License
|
25
|
+
rubyscript2exe: GNU General Public License (GPL), version 2
|
26
|
+
wxruby: wxWindows Licence
|
data/README
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
I've refactored iplayer-dl in order to make it easier to extend and wrap. As a
|
2
|
+
direct result, it's no longer just one script. As a result, you'll need to
|
3
|
+
install the libraries and command-line script in order to use it.
|
4
|
+
|
5
|
+
The good news is that this is as easy as typing:
|
6
|
+
|
7
|
+
ruby setup.rb config
|
8
|
+
sudo ruby setup.rb install
|
9
|
+
|
10
|
+
Windows users should omit 'sudo' in the second line.
|
11
|
+
|
12
|
+
You can then run the downloader just by typing:
|
13
|
+
|
14
|
+
iplayer-dl
|
data/Rakefile
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/packagetask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake'
|
5
|
+
require 'find'
|
6
|
+
require 'lib/iplayer/version'
|
7
|
+
|
8
|
+
# Globals
|
9
|
+
|
10
|
+
PKG_NAME = 'iplayer-dl'
|
11
|
+
|
12
|
+
PKG_FILES = %w[ COPYING README setup.rb Rakefile ]
|
13
|
+
Find.find('lib/', 'test/', 'bin/', 'share/') do |f|
|
14
|
+
if FileTest.directory?(f) and f =~ /\.svn/
|
15
|
+
Find.prune
|
16
|
+
else
|
17
|
+
PKG_FILES << f
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
EXE_FILES = PKG_FILES + %w[ application.ico init.rb ]
|
22
|
+
|
23
|
+
# Tasks
|
24
|
+
|
25
|
+
task :default => :test
|
26
|
+
|
27
|
+
Rake::TestTask.new do |t|
|
28
|
+
t.libs << "test"
|
29
|
+
t.test_files = FileList['test/test_*.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
Rake::PackageTask.new(PKG_NAME, IPlayer::VERSION) do |p|
|
33
|
+
p.need_tar_gz = true
|
34
|
+
p.package_files = PKG_FILES
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Build a Windows executable. Needs rubyscript2exe.rb in the current path and the wx gem installed."
|
38
|
+
task :exe do |t|
|
39
|
+
mkdir_p 'tmp'
|
40
|
+
build_dir = File.join('tmp', "ipdl-#{IPlayer::GUI_VERSION}")
|
41
|
+
rm_rf build_dir
|
42
|
+
mkdir build_dir
|
43
|
+
EXE_FILES.each do |file|
|
44
|
+
next if File.directory?(file)
|
45
|
+
loc = File.join(build_dir, File.dirname(file))
|
46
|
+
mkdir_p loc
|
47
|
+
cp_r file, loc
|
48
|
+
end
|
49
|
+
sh "ruby rubyscript2exe.rb #{build_dir} --rubyscript2exe-verbose --rubyscript2exe-rubyw"
|
50
|
+
rm_rf build_dir
|
51
|
+
mkdir_p 'pkg'
|
52
|
+
mv "ipdl-#{IPlayer::GUI_VERSION}.exe", "pkg"
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
require 'rubygems'
|
57
|
+
require 'jeweler'
|
58
|
+
Jeweler::Tasks.new do |gemspec|
|
59
|
+
gemspec.name = "iplayer-dl"
|
60
|
+
gemspec.summary = "Downloads DRM-free video (h.264) and audio (MP3) files from the BBC iPlayer service by pretending to be an iPhone."
|
61
|
+
gemspec.homepage = "http://po-ru.com/projects/iplayer-downloader/"
|
62
|
+
gemspec.description = "Downloads DRM-free video (h.264) and audio (MP3) files from the BBC iPlayer service by pretending to be an iPhone."
|
63
|
+
gemspec.authors = ["Paul Battley"]
|
64
|
+
end
|
65
|
+
rescue LoadError
|
66
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
67
|
+
end
|
data/VERSION.yml
ADDED
data/application.ico
ADDED
Binary file
|
data/bin/iplayer-dl
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Download iPlayer programmes by spoofing an iPhone
|
4
|
+
# Paul Battley - http://po-ru.com/
|
5
|
+
#
|
6
|
+
# Get the latest version via subversion:
|
7
|
+
# svn co http://paulbattley.googlecode.com/svn/iplayer-dl
|
8
|
+
|
9
|
+
require 'iplayer'
|
10
|
+
require 'optparse'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'iplayer/version'
|
13
|
+
require 'iplayer/preferences'
|
14
|
+
|
15
|
+
include IPlayer
|
16
|
+
include IPlayer::Errors
|
17
|
+
|
18
|
+
preferences = IPlayer::Preferences.new
|
19
|
+
filenames = []
|
20
|
+
pid_list = nil
|
21
|
+
dry_run = false
|
22
|
+
|
23
|
+
opts = ARGV.options{ |o|
|
24
|
+
o.banner << ' IDENTIFIER [IDENTIFIER [...]]'
|
25
|
+
o.define_head 'Download DRM-free videos from the BBC iPlayer, courtesy of their iPhone interface.'
|
26
|
+
o.separator 'IDENTIFIER is the iPlayer viewing page URL or the PID of the programme.'
|
27
|
+
o.separator ''
|
28
|
+
o.on(
|
29
|
+
'-t', '--type-preference=VERSION', String,
|
30
|
+
'Video types in order of preference.',
|
31
|
+
"Default is '#{preferences.type_preference.join(',')}'."
|
32
|
+
) { |s| preferences.type_preference = s.split(/,\s*/) }
|
33
|
+
o.on(
|
34
|
+
'-d', '--download-path=PATH', String,
|
35
|
+
'Location into which downloaded files will be saved.',
|
36
|
+
'Default is current working directory.'
|
37
|
+
) { |d| preferences.download_path = d }
|
38
|
+
o.on(
|
39
|
+
'-s', '--title-subdir',
|
40
|
+
'Place downloaded files in a sub-directory named after the title of the programme.'
|
41
|
+
) { preferences.subdirs = true }
|
42
|
+
o.on(
|
43
|
+
'-u', '--subtitles',
|
44
|
+
'Also download subtitles.'
|
45
|
+
) { preferences.subtitles = true }
|
46
|
+
o.on(
|
47
|
+
'-f', '--filename=FILENAME', String,
|
48
|
+
'Manually specify a name for the downloaded file.',
|
49
|
+
'The default is constructed from the programme metadata.',
|
50
|
+
'You can specify this multiple times for each download in order.'
|
51
|
+
) { |f| filenames << f }
|
52
|
+
o.on(
|
53
|
+
'-p', '--http-proxy=HOST:PORT', String,
|
54
|
+
'Specify an HTTP proxy.',
|
55
|
+
'Default is taken from the http_proxy environment variable.'
|
56
|
+
) { |p| preferences.http_proxy = p }
|
57
|
+
o.on(
|
58
|
+
'-l', '--pid-list=FILENAME', String,
|
59
|
+
'List PIDs to be downloaded in a file, one per line.'
|
60
|
+
) { |l| pid_list = l }
|
61
|
+
o.on(
|
62
|
+
'-n', '--dry-run', String,
|
63
|
+
'Parse the iPlayer page and output the filename but don\'t actually download (useful for scripting).'
|
64
|
+
) { |n| dry_run = true }
|
65
|
+
o.on(
|
66
|
+
'-v', '--version',
|
67
|
+
'Show the software version.'
|
68
|
+
) { puts IPlayer::VERSION; exit }
|
69
|
+
o.on_tail(
|
70
|
+
'-h', '--help',
|
71
|
+
'Show this help message.'
|
72
|
+
) { puts o; exit }
|
73
|
+
}
|
74
|
+
|
75
|
+
begin
|
76
|
+
opts.parse!
|
77
|
+
pids = ARGV
|
78
|
+
if pid_list
|
79
|
+
pids += File.read(pid_list).strip.split(/\s*\r?\n\s*/)
|
80
|
+
end
|
81
|
+
raise 'no programme identifier specified' if pids.empty?
|
82
|
+
rescue => exception
|
83
|
+
$stderr.puts 'Error: '+exception, ''
|
84
|
+
puts opts
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
|
88
|
+
if http_proxy = preferences.http_proxy
|
89
|
+
http_proxy = 'http://' + http_proxy unless http_proxy =~ %r{^http://}
|
90
|
+
u = URI.parse(http_proxy)
|
91
|
+
$stderr.puts "Using proxy #{u.host}:#{u.port}"
|
92
|
+
http = Net::HTTP::Proxy(u.host, u.port)
|
93
|
+
else
|
94
|
+
http = Net::HTTP
|
95
|
+
end
|
96
|
+
|
97
|
+
pids.each_with_index do |pid, i|
|
98
|
+
browser = Browser.new(http)
|
99
|
+
|
100
|
+
begin
|
101
|
+
pid = Downloader.extract_pid(pid)
|
102
|
+
downloader = Downloader.new(browser, pid)
|
103
|
+
|
104
|
+
available_versions = downloader.available_versions
|
105
|
+
raise MP4Unavailable if available_versions.empty?
|
106
|
+
version = available_versions.sort_by{ |v|
|
107
|
+
preferences.type_preference.index(v.name) || 100
|
108
|
+
}.first
|
109
|
+
|
110
|
+
filename = filenames[i]
|
111
|
+
if filename.nil? || preferences.subdirs
|
112
|
+
metadata = downloader.metadata
|
113
|
+
filename ||= "#{ metadata.full_title }.#{ metadata.filetype }".gsub(/[^a-z0-9 \-\.]+/i, '')
|
114
|
+
end
|
115
|
+
if preferences.subdirs
|
116
|
+
subdir = metadata.title.gsub(/[^a-z0-9 \-\.]+/i, '')
|
117
|
+
path = File.expand_path( File.join( preferences.download_path, subdir ))
|
118
|
+
FileUtils.makedirs(path)
|
119
|
+
path = File.join( path, filename )
|
120
|
+
else
|
121
|
+
path = File.expand_path( File.join( preferences.download_path, filename ))
|
122
|
+
end
|
123
|
+
|
124
|
+
if dry_run
|
125
|
+
$stdout.puts filename
|
126
|
+
else
|
127
|
+
old_percentage = nil
|
128
|
+
first_chunk = true
|
129
|
+
|
130
|
+
$stderr.puts "#{ filename } (version: #{ version.name })"
|
131
|
+
download_options = {:subtitles => preferences.subtitles}
|
132
|
+
downloader.download(version.pid, path, download_options) do |position, max|
|
133
|
+
if first_chunk
|
134
|
+
$stderr.puts "Resuming download at #{position} bytes." if position > 0
|
135
|
+
first_chunk = false
|
136
|
+
end
|
137
|
+
|
138
|
+
percentage = "%.1f" % [((1000 * position) / max) / 10.0]
|
139
|
+
if percentage != old_percentage
|
140
|
+
old_percentage = percentage
|
141
|
+
$stderr.print "\r#{ percentage }%"
|
142
|
+
$stderr.flush
|
143
|
+
end
|
144
|
+
end
|
145
|
+
$stderr.puts
|
146
|
+
end
|
147
|
+
|
148
|
+
rescue RecognizedError => error
|
149
|
+
$stderr.puts(error.to_str)
|
150
|
+
next
|
151
|
+
end
|
152
|
+
end
|
data/bin/iplayer-dl-gui
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Download iPlayer programmes by spoofing an iPhone
|
4
|
+
# WxWidgets GUI interface
|
5
|
+
# Paul Battley - http://po-ru.com/
|
6
|
+
#
|
7
|
+
# Get the latest version via subversion:
|
8
|
+
# svn co http://paulbattley.googlecode.com/svn/iplayer-dl
|
9
|
+
|
10
|
+
require 'iplayer'
|
11
|
+
require 'iplayer/gui/app'
|
12
|
+
require 'iplayer/gui/main_frame'
|
13
|
+
require 'iplayer/version'
|
14
|
+
|
15
|
+
options = {
|
16
|
+
:type_preference => %w[original signed],
|
17
|
+
:http_proxy => ENV['http_proxy']
|
18
|
+
}
|
19
|
+
about = {
|
20
|
+
:name => 'iPlayer Downloader',
|
21
|
+
:version => "#{IPlayer::GUI_VERSION} (library #{IPlayer::VERSION})",
|
22
|
+
:developers => ['Paul Battley'],
|
23
|
+
:description => "Download programmes from the BBC iPlayer.\nVisit http://po-ru.com/projects/iplayer-downloader/\nfor more information."
|
24
|
+
}
|
25
|
+
app = IPlayer::GUI::App.new(IPlayer::GUI::MainFrame, about, options)
|
26
|
+
app.main_loop
|
data/init.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
if defined?(RUBYSCRIPT2EXE)
|
2
|
+
app_root = File.expand_path(File.dirname(__FILE__))
|
3
|
+
$:.unshift(File.join(app_root, 'lib'))
|
4
|
+
if RUBYSCRIPT2EXE.respond_to?(:is_compiling?) && RUBYSCRIPT2EXE.is_compiling?
|
5
|
+
require 'iplayer'
|
6
|
+
require 'iplayer/gui/app'
|
7
|
+
require 'iplayer/gui/main_frame'
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
load File.join(app_root, 'bin', 'iplayer-dl-gui')
|
data/iplayer-dl.gemspec
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{iplayer-dl}
|
5
|
+
s.version = "0.15.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Paul Battley"]
|
9
|
+
s.date = %q{2009-05-18}
|
10
|
+
s.description = %q{Downloads DRM-free video (h.264) and audio (MP3) files from the BBC iPlayer service by pretending to be an iPhone.}
|
11
|
+
s.executables = ["iplayer-dl", "iplayer-dl-gui"]
|
12
|
+
s.extra_rdoc_files = [
|
13
|
+
"README"
|
14
|
+
]
|
15
|
+
s.files = [
|
16
|
+
".gitignore",
|
17
|
+
"COPYING",
|
18
|
+
"README",
|
19
|
+
"Rakefile",
|
20
|
+
"VERSION.yml",
|
21
|
+
"application.ico",
|
22
|
+
"bin/iplayer-dl",
|
23
|
+
"bin/iplayer-dl-gui",
|
24
|
+
"init.rb",
|
25
|
+
"iplayer-dl.gemspec",
|
26
|
+
"lib/iplayer.rb",
|
27
|
+
"lib/iplayer/browser.rb",
|
28
|
+
"lib/iplayer/downloader.rb",
|
29
|
+
"lib/iplayer/errors.rb",
|
30
|
+
"lib/iplayer/gui/app.rb",
|
31
|
+
"lib/iplayer/gui/main_frame.rb",
|
32
|
+
"lib/iplayer/metadata.rb",
|
33
|
+
"lib/iplayer/preferences.rb",
|
34
|
+
"lib/iplayer/subtitles.rb",
|
35
|
+
"lib/iplayer/version.rb",
|
36
|
+
"setup.rb",
|
37
|
+
"share/pixmaps/iplayer-dl/icon128.png",
|
38
|
+
"share/pixmaps/iplayer-dl/icon16.png",
|
39
|
+
"share/pixmaps/iplayer-dl/icon32.png",
|
40
|
+
"share/pixmaps/iplayer-dl/icon48.png",
|
41
|
+
"share/pixmaps/iplayer-dl/icon64.png",
|
42
|
+
"test/test_metadata.rb",
|
43
|
+
"test/test_preferences.rb",
|
44
|
+
"test/test_subtitles.rb"
|
45
|
+
]
|
46
|
+
s.has_rdoc = true
|
47
|
+
s.homepage = %q{http://po-ru.com/projects/iplayer-downloader/}
|
48
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
49
|
+
s.require_paths = ["lib"]
|
50
|
+
s.rubygems_version = %q{1.3.2}
|
51
|
+
s.summary = %q{Downloads DRM-free video (h.264) and audio (MP3) files from the BBC iPlayer service by pretending to be an iPhone.}
|
52
|
+
s.test_files = [
|
53
|
+
"test/test_metadata.rb",
|
54
|
+
"test/test_preferences.rb",
|
55
|
+
"test/test_subtitles.rb"
|
56
|
+
]
|
57
|
+
|
58
|
+
if s.respond_to? :specification_version then
|
59
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
60
|
+
s.specification_version = 3
|
61
|
+
|
62
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
63
|
+
else
|
64
|
+
end
|
65
|
+
else
|
66
|
+
end
|
67
|
+
end
|
data/lib/iplayer.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class Net::HTTPResponse # Monkey-patch in some 21st-century functionality
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def cookies
|
8
|
+
inject([]){ |acc, (key, value)|
|
9
|
+
key == 'set-cookie' ? acc << value.split(/;/).first : acc
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_hash
|
14
|
+
@to_hash ||= inject({}){ |hash, (key, value)|
|
15
|
+
hash[key] = value
|
16
|
+
hash
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module IPlayer
|
22
|
+
class Browser
|
23
|
+
|
24
|
+
# Used by Safari Mobile
|
25
|
+
IPHONE_UA = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ '+
|
26
|
+
'(KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3'
|
27
|
+
|
28
|
+
# Used by Quicktime
|
29
|
+
QT_UA = 'Apple iPhone v1.1.4 CoreMedia v1.0.0.4A102'
|
30
|
+
|
31
|
+
# Safari, for no good reason
|
32
|
+
DESKTOP_UA = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_4; en-gb) '+
|
33
|
+
'AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1'
|
34
|
+
|
35
|
+
DEFAULT_HEADERS = {
|
36
|
+
'Accept' => '*/*',
|
37
|
+
'Accept-Language' => 'en',
|
38
|
+
'Connection' => 'keep-alive',
|
39
|
+
'Pragma' => 'no-cache'
|
40
|
+
}
|
41
|
+
|
42
|
+
def initialize(http_class = Net::HTTP)
|
43
|
+
@http_class = http_class
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(location, headers={}, &blk)
|
47
|
+
url = URI.parse(location)
|
48
|
+
http = @http_class.new(url.host, url.port)
|
49
|
+
path = url.path
|
50
|
+
if url.query
|
51
|
+
path << '?' << url.query
|
52
|
+
end
|
53
|
+
if defined? DEBUG
|
54
|
+
puts path
|
55
|
+
DEFAULT_HEADERS.merge(headers).each do |k,v|
|
56
|
+
puts " -> #{k}: #{v}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
response = http.request_get(path, DEFAULT_HEADERS.merge(headers), &blk)
|
60
|
+
if defined? DEBUG
|
61
|
+
response.each do |k,v|
|
62
|
+
puts "<- #{k}: #{v}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
response
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|