comic_walker 0.2.2 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/comic_walker.gemspec +2 -1
- data/lib/comic_walker/cli.rb +13 -59
- data/lib/comic_walker/client/license.rb +15 -24
- data/lib/comic_walker/content_downloader.rb +27 -0
- data/lib/comic_walker/item_decoder.rb +0 -24
- data/lib/comic_walker/item_decoder/unknown.rb +0 -41
- data/lib/comic_walker/v1/client.rb +9 -31
- data/lib/comic_walker/version.rb +1 -1
- metadata +18 -4
- data/lib/comic_walker/end_layer_decoder.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7fe84087441847fad172320a68b0a4849d5ccb0f
|
|
4
|
+
data.tar.gz: ad4627cd74295d5e64c457d74729cd379dedb10f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ca5b14ea61e4a6118ee906b64e7dff21b95ec07b9c9e70827b765d6952fb8d824973bb1ba50ffe8ed918a40030d2f625d29621d41a69d2380cadc43d4bedbcf
|
|
7
|
+
data.tar.gz: 62076faf1ff081f17c5f42e5e9cba25949ad523eb87c3e2b54fda226524cfdd7b9e4be0bd6db98670776eca38666ea4a63c8b9c576c05020650989c1ca68a14c
|
data/CHANGELOG.md
CHANGED
data/comic_walker.gemspec
CHANGED
|
@@ -23,7 +23,8 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
spec.add_development_dependency "yard"
|
|
24
24
|
spec.add_dependency "addressable"
|
|
25
25
|
spec.add_dependency "http-cookie"
|
|
26
|
+
spec.add_dependency "net-http-persistent"
|
|
27
|
+
spec.add_dependency "retryable"
|
|
26
28
|
spec.add_dependency "rmagick"
|
|
27
|
-
spec.add_dependency "rubyzip"
|
|
28
29
|
spec.add_dependency "thor"
|
|
29
30
|
end
|
data/lib/comic_walker/cli.rb
CHANGED
|
@@ -4,7 +4,7 @@ require 'pp'
|
|
|
4
4
|
require 'thor'
|
|
5
5
|
require 'yaml'
|
|
6
6
|
require 'comic_walker/client'
|
|
7
|
-
require 'comic_walker/
|
|
7
|
+
require 'comic_walker/content_downloader'
|
|
8
8
|
require 'comic_walker/item_decoder'
|
|
9
9
|
require 'comic_walker/v1/client'
|
|
10
10
|
|
|
@@ -20,7 +20,7 @@ module ComicWalker
|
|
|
20
20
|
def save(*cids)
|
|
21
21
|
client = Client.new
|
|
22
22
|
cids.each do |cid|
|
|
23
|
-
|
|
23
|
+
ContentDownloader.new(client, cid).save
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -45,9 +45,7 @@ module ComicWalker
|
|
|
45
45
|
jar = HTTP::CookieJar.new
|
|
46
46
|
load_cookies(jar)
|
|
47
47
|
client = V1::Client.new(jar, load_uuid)
|
|
48
|
-
json = client.
|
|
49
|
-
client.contents(per_page: options[:per_page], page: options[:page])
|
|
50
|
-
end
|
|
48
|
+
json = client.contents(per_page: options[:per_page], page: options[:page])
|
|
51
49
|
save_cookies(jar)
|
|
52
50
|
|
|
53
51
|
json['contents'].sort_by { |content| Time.parse(content['updated_at']) }.reverse_each do |content|
|
|
@@ -72,48 +70,6 @@ module ComicWalker
|
|
|
72
70
|
|
|
73
71
|
private
|
|
74
72
|
|
|
75
|
-
def save_content(client, cid)
|
|
76
|
-
license = client.get_license(cid)
|
|
77
|
-
info = license.get_info.first
|
|
78
|
-
last_update = Time.strptime(info['last_update'], '%Y%m%d%H%M')
|
|
79
|
-
title = info['issues'].first['content_name']
|
|
80
|
-
|
|
81
|
-
license.with_jar do |zip|
|
|
82
|
-
pages = []
|
|
83
|
-
items = {}
|
|
84
|
-
decoder = nil
|
|
85
|
-
while entry = zip.get_next_entry
|
|
86
|
-
case entry.name
|
|
87
|
-
when 'configuration_pack.json'
|
|
88
|
-
config = JSON.parse(zip.read)
|
|
89
|
-
decoder = ItemDecoder.new(config)
|
|
90
|
-
when /end_layer\.json/
|
|
91
|
-
begin
|
|
92
|
-
pp EndLayerDecoder.new(license.key).decode(zip.read)
|
|
93
|
-
rescue JSON::ParserError
|
|
94
|
-
$stderr.puts "WARNING: #{cid}: Could not decode end_layer.json.enc"
|
|
95
|
-
end
|
|
96
|
-
when %r{\Aitem/}
|
|
97
|
-
items[entry.name] = zip.read
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
img_dir = Pathname.new(title).join(cid).tap(&:mkpath)
|
|
102
|
-
decoder.pages.each.with_index do |file, i|
|
|
103
|
-
dat_path = Pathname.new(file).join('0.dat')
|
|
104
|
-
img_fname = dat_path.parent.basename.sub_ext('.jpg')
|
|
105
|
-
img_path = img_dir.join(sprintf('%03d_%s', i, img_fname))
|
|
106
|
-
|
|
107
|
-
if data = items[dat_path.to_s]
|
|
108
|
-
decoder.decode_b64(file, dat_path, img_path, data)
|
|
109
|
-
else
|
|
110
|
-
decoder.decode(file, dat_path, img_path, license.get_jpeg(file))
|
|
111
|
-
end
|
|
112
|
-
puts "#{dat_path} -> #{img_path}"
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
73
|
def load_cookies(jar)
|
|
118
74
|
if COOKIE_PATH.readable?
|
|
119
75
|
jar.load(COOKIE_PATH.to_s, session: true)
|
|
@@ -147,20 +103,18 @@ module ComicWalker
|
|
|
147
103
|
page = 1
|
|
148
104
|
per_page = 200
|
|
149
105
|
child_cids = {}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
child_cids[c['content_id']] = c['sub_contents']
|
|
160
|
-
end
|
|
106
|
+
until child_cids.size == parent_cids do
|
|
107
|
+
json = client.contents(page: page, per_page: per_page)
|
|
108
|
+
contents = json['contents']
|
|
109
|
+
if contents.empty?
|
|
110
|
+
break
|
|
111
|
+
end
|
|
112
|
+
contents.each do |c|
|
|
113
|
+
if parent_cids.include?(c['content_id'])
|
|
114
|
+
child_cids[c['content_id']] = c['sub_contents']
|
|
161
115
|
end
|
|
162
|
-
page += 1
|
|
163
116
|
end
|
|
117
|
+
page += 1
|
|
164
118
|
end
|
|
165
119
|
parent_cids.each do |cid|
|
|
166
120
|
unless child_cids.has_key?(cid)
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'uri'
|
|
2
2
|
require 'base64'
|
|
3
|
-
require 'net/http'
|
|
4
|
-
require 'zip'
|
|
3
|
+
require 'net/http/persistent'
|
|
5
4
|
|
|
6
5
|
module ComicWalker
|
|
7
6
|
class Client
|
|
8
7
|
class License
|
|
9
8
|
def initialize(json)
|
|
10
9
|
@json = json
|
|
10
|
+
@http = Net::HTTP::Persistent.new('comic_walker')
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def agreement
|
|
14
14
|
@json['agreement']
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def jar_url
|
|
18
|
-
url_prefix + agreement['jar_file_name']
|
|
19
|
-
end
|
|
20
|
-
|
|
21
17
|
def info_url
|
|
22
18
|
url_prefix + agreement['info_file_name']
|
|
23
19
|
end
|
|
@@ -26,31 +22,26 @@ module ComicWalker
|
|
|
26
22
|
agreement['url_prefix']
|
|
27
23
|
end
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
CONFIGURATION_PACK_FILENAME = 'configuration_pack.json'
|
|
26
|
+
|
|
27
|
+
def configuration_pack_url
|
|
28
|
+
url_prefix + CONFIGURATION_PACK_FILENAME
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get(url)
|
|
32
|
+
@http.request(URI.parse(url))
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
body = Net::HTTP.start(uri.host) do |http|
|
|
36
|
-
http.get(uri.request_uri).body
|
|
37
|
-
end
|
|
38
|
-
Zip::InputStream.open(StringIO.new(body), &block)
|
|
35
|
+
def get_configuration_pack
|
|
36
|
+
JSON.parse(get(configuration_pack_url).body)
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
def get_info
|
|
42
|
-
|
|
43
|
-
body = Net::HTTP.start(uri.host) do |http|
|
|
44
|
-
http.get(uri.request_uri).body
|
|
45
|
-
end
|
|
46
|
-
JSON.parse(body)
|
|
40
|
+
JSON.parse(get(info_url).body)
|
|
47
41
|
end
|
|
48
42
|
|
|
49
43
|
def get_jpeg(file)
|
|
50
|
-
|
|
51
|
-
Net::HTTP.start(uri.host) do |http|
|
|
52
|
-
http.get(uri.request_uri).body
|
|
53
|
-
end
|
|
44
|
+
get(url_prefix + file + '/0.jpeg').body
|
|
54
45
|
end
|
|
55
46
|
end
|
|
56
47
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module ComicWalker
|
|
2
|
+
class ContentDownloader
|
|
3
|
+
def initialize(client, cid)
|
|
4
|
+
@client = client
|
|
5
|
+
@cid = cid
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def save
|
|
9
|
+
license = @client.get_license(@cid)
|
|
10
|
+
decoder = ItemDecoder.new(license.get_configuration_pack)
|
|
11
|
+
img_dir = image_dir(license)
|
|
12
|
+
decoder.pages.each.with_index do |file, i|
|
|
13
|
+
dat_path = Pathname.new(file).join('0.dat')
|
|
14
|
+
img_fname = dat_path.parent.basename.sub_ext('.jpg')
|
|
15
|
+
img_path = img_dir.join(sprintf('%03d_%s', i, img_fname))
|
|
16
|
+
decoder.decode(file, dat_path, img_path, license.get_jpeg(file))
|
|
17
|
+
puts "#{dat_path} -> #{img_path}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def image_dir(license)
|
|
22
|
+
info = license.get_info.first
|
|
23
|
+
title = info['issues'].first['content_name']
|
|
24
|
+
Pathname.new(title).join(@cid).tap(&:mkpath)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -5,18 +5,7 @@ module ComicWalker
|
|
|
5
5
|
class ItemDecoder
|
|
6
6
|
attr_reader :pages
|
|
7
7
|
|
|
8
|
-
DEFAULT_CT = '20000101000000'
|
|
9
|
-
DEFAULT_ST = '20000101000000'
|
|
10
|
-
DEFAULT_ET = '99991231235959'
|
|
11
|
-
|
|
12
8
|
def initialize(configuration_pack)
|
|
13
|
-
fname = '0.dat'
|
|
14
|
-
ct = configuration_pack['ct'] || DEFAULT_CT
|
|
15
|
-
st = configuration_pack['st'] || DEFAULT_ST
|
|
16
|
-
et = configuration_pack['et'] || DEFAULT_ET
|
|
17
|
-
@key1 = (ct + st + fname).unpack('C*')
|
|
18
|
-
@key2 = (ct + fname + et).unpack('C*')
|
|
19
|
-
@key3 = (fname + st + et).unpack('C*')
|
|
20
9
|
@pages = []
|
|
21
10
|
configuration_pack['configuration']['contents'].each do |content|
|
|
22
11
|
pages[content['index']-1] = content['file']
|
|
@@ -27,19 +16,6 @@ module ComicWalker
|
|
|
27
16
|
end
|
|
28
17
|
end
|
|
29
18
|
|
|
30
|
-
def decode_b64(file, dat_path, img_path, b64data)
|
|
31
|
-
bs = 128
|
|
32
|
-
hs = 1024
|
|
33
|
-
blob = Unknown.finish(@key1, hs,
|
|
34
|
-
Unknown.decrypt(@key2, bs,
|
|
35
|
-
Unknown.prepare(@key3,
|
|
36
|
-
Base64.decode64(b64data).unpack('C*')
|
|
37
|
-
)
|
|
38
|
-
)
|
|
39
|
-
).pack('C*')
|
|
40
|
-
decode(file, dat_path, img_path, blob)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
19
|
def decode(file, dat_path, img_path, blob)
|
|
44
20
|
src = Magick::Image.from_blob(blob).first
|
|
45
21
|
width = src.columns
|
|
@@ -5,47 +5,6 @@ module ComicWalker
|
|
|
5
5
|
module Unknown
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
|
-
# @param [Array<Fixnum>] key
|
|
9
|
-
# @param [Array<Fixnum>] data Encrypted data
|
|
10
|
-
# @return [Array<Fixnum>]
|
|
11
|
-
def prepare(key, data)
|
|
12
|
-
s = Cipher.gen_rc4_table(key)
|
|
13
|
-
data.map.with_index do |b, i|
|
|
14
|
-
b ^ s[i % 256]
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# @param [Array<Fixnum>] key
|
|
19
|
-
# @param [Integer] bsize block size?
|
|
20
|
-
# @param [Array<Fixnum>] data Encrypted data
|
|
21
|
-
# @return [Array<Fixnum>] Chunked decrypted data
|
|
22
|
-
def decrypt(key, bsize, data)
|
|
23
|
-
s = []
|
|
24
|
-
0.step(data.size-1, bsize) do |i|
|
|
25
|
-
s << data[i]
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
c = Cipher.decrypt_rc4(key, s)
|
|
29
|
-
# s.size == c.size
|
|
30
|
-
|
|
31
|
-
0.step(data.size-1, bsize) do |i|
|
|
32
|
-
data[i] = c.shift
|
|
33
|
-
end
|
|
34
|
-
data
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# @param [Array<Fixnum>] key
|
|
38
|
-
# @param [Integer] hsize header size?
|
|
39
|
-
# @param [Array<Fixnum>] data Encrypted data
|
|
40
|
-
# @return [Array<Fixnum>] data
|
|
41
|
-
def finish(key, hsize, data)
|
|
42
|
-
hsize = [hsize, data.size].min
|
|
43
|
-
Cipher.decrypt_rc4(key, data.slice(0, hsize)).each.with_index do |x, i|
|
|
44
|
-
data[i] = x
|
|
45
|
-
end
|
|
46
|
-
data
|
|
47
|
-
end
|
|
48
|
-
|
|
49
8
|
# Calculate moves.
|
|
50
9
|
# @param [Integer] width Width of the image
|
|
51
10
|
# @param [Integer] height Height of the image
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
require 'addressable/uri'
|
|
2
2
|
require 'http-cookie'
|
|
3
3
|
require 'json'
|
|
4
|
-
require 'net/http'
|
|
4
|
+
require 'net/http/persistent'
|
|
5
|
+
require 'retryable'
|
|
6
|
+
require 'uri'
|
|
5
7
|
|
|
6
8
|
module ComicWalker
|
|
7
9
|
module V1
|
|
@@ -9,19 +11,11 @@ module ComicWalker
|
|
|
9
11
|
BASE_URI = Addressable::URI.parse("https://cnts.comic-walker.com")
|
|
10
12
|
|
|
11
13
|
def initialize(jar, uuid)
|
|
12
|
-
@https = Net::HTTP.new(
|
|
13
|
-
@https.use_ssl = true
|
|
14
|
-
@https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
14
|
+
@https = Net::HTTP::Persistent.new('comic_walker')
|
|
15
15
|
@jar = jar
|
|
16
16
|
@uuid = uuid
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def start(&block)
|
|
20
|
-
@https.start do
|
|
21
|
-
block.call
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
19
|
AID = 'KDCWI_JP'
|
|
26
20
|
AVER = '1.2.0'
|
|
27
21
|
|
|
@@ -31,8 +25,8 @@ module ComicWalker
|
|
|
31
25
|
end
|
|
32
26
|
|
|
33
27
|
def create_session
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
on_exception = lambda { |exception| create_user }
|
|
29
|
+
retryable(tries: 2, on: UnknownDeviceError, sleep: 0, exception_cb: on_exception) do
|
|
36
30
|
res = post('/user_sessions/create', {
|
|
37
31
|
DID: @uuid,
|
|
38
32
|
PIN: @uuid,
|
|
@@ -47,14 +41,6 @@ module ComicWalker
|
|
|
47
41
|
else
|
|
48
42
|
JSON.parse(res.body)
|
|
49
43
|
end
|
|
50
|
-
rescue UnknownDeviceError => e
|
|
51
|
-
if retried == 0
|
|
52
|
-
retried += 1
|
|
53
|
-
create_user
|
|
54
|
-
retry
|
|
55
|
-
else
|
|
56
|
-
raise e
|
|
57
|
-
end
|
|
58
44
|
end
|
|
59
45
|
end
|
|
60
46
|
|
|
@@ -68,7 +54,6 @@ module ComicWalker
|
|
|
68
54
|
end
|
|
69
55
|
|
|
70
56
|
def contents(params = {})
|
|
71
|
-
retried = 0
|
|
72
57
|
params = {
|
|
73
58
|
AID: AID,
|
|
74
59
|
AVER: AVER,
|
|
@@ -80,7 +65,8 @@ module ComicWalker
|
|
|
80
65
|
languages: 'ja',
|
|
81
66
|
}.merge(params)
|
|
82
67
|
|
|
83
|
-
|
|
68
|
+
on_exception = lambda { |exception| create_session }
|
|
69
|
+
retryable(tries: 2, on: NoValidSessionError, sleep: 0, exception_cb: on_exception) do
|
|
84
70
|
res = get('/v1/contents', params)
|
|
85
71
|
case res.body
|
|
86
72
|
when 'NoValidSessionError'
|
|
@@ -88,14 +74,6 @@ module ComicWalker
|
|
|
88
74
|
else
|
|
89
75
|
JSON.parse(res.body)
|
|
90
76
|
end
|
|
91
|
-
rescue NoValidSessionError => e
|
|
92
|
-
if retried == 0
|
|
93
|
-
retried += 1
|
|
94
|
-
create_session
|
|
95
|
-
retry
|
|
96
|
-
else
|
|
97
|
-
raise e
|
|
98
|
-
end
|
|
99
77
|
end
|
|
100
78
|
end
|
|
101
79
|
|
|
@@ -117,7 +95,7 @@ module ComicWalker
|
|
|
117
95
|
|
|
118
96
|
def request_with_cookie(uri, req)
|
|
119
97
|
req['cookie'] = HTTP::Cookie.cookie_value(@jar.cookies(uri.to_s))
|
|
120
|
-
@https.request(req).tap do |res|
|
|
98
|
+
@https.request(URI.parse(uri.to_s), req).tap do |res|
|
|
121
99
|
@jar.parse(res['set-cookie'], uri.to_s)
|
|
122
100
|
end
|
|
123
101
|
end
|
data/lib/comic_walker/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: comic_walker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kohei Suzuki
|
|
@@ -81,7 +81,7 @@ dependencies:
|
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
84
|
+
name: net-http-persistent
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
87
|
- - ">="
|
|
@@ -95,7 +95,21 @@ dependencies:
|
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: '0'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
98
|
+
name: retryable
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rmagick
|
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
|
100
114
|
requirements:
|
|
101
115
|
- - ">="
|
|
@@ -143,7 +157,7 @@ files:
|
|
|
143
157
|
- lib/comic_walker/cli.rb
|
|
144
158
|
- lib/comic_walker/client.rb
|
|
145
159
|
- lib/comic_walker/client/license.rb
|
|
146
|
-
- lib/comic_walker/
|
|
160
|
+
- lib/comic_walker/content_downloader.rb
|
|
147
161
|
- lib/comic_walker/item_decoder.rb
|
|
148
162
|
- lib/comic_walker/item_decoder/unknown.rb
|
|
149
163
|
- lib/comic_walker/v1/client.rb
|