comic_walker 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e501759d14c46245526e2b8cbff03a38b9f390f9
4
- data.tar.gz: a1eccbbfd4a9f823614adc8c1adf8b6a98bebc5b
3
+ metadata.gz: 6a5cddb9273f468c064ce300cbe66a9ca1f87680
4
+ data.tar.gz: a2f75342e60753047ab80894a5e4f52281a41396
5
5
  SHA512:
6
- metadata.gz: 1fe26f0cc27ad11cda330ff55e502e27b4a09a71a9f1c264026042765a0a311e68cec060b1e555fb9a5e58ca44319c8baa75e6e863e81fddf0adcfb37d0f6eed
7
- data.tar.gz: 2d2393961e1859b017c960e29deed845913df53c98472382785418ccd9b51cf04675e17570f2c95d439efec7f1af9d6557651b0d0417e4a7220daae1660c24df
6
+ metadata.gz: 3002a2c97d87584ef29cc9c620d23abb58095a2df5234058d6b5daef8287ce0a76ec5ed53c46d12f6934e858b59333818b955f25348089362ef425917267f713
7
+ data.tar.gz: f7fa296b48bd74e0781458e753d90405e7c884540429e7378be8f49b29ebe3832b676e8c26c1d723da8aeaa622e10febdbf0fba0554a63aac97585546a5142d3
@@ -0,0 +1,8 @@
1
+ # 0.2.0 (2014-04-13)
2
+ - Rename to `save` subcommand.
3
+ - Add `contents` subcommand.
4
+ - Add `save-all` subcommand.
5
+ - Ignore JSON::ParserError from EndLayerDecoder.
6
+
7
+ # 0.1.0 (2014-03-30)
8
+ - Initial release.
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # ComicWalker
2
+ [![Gem Version](https://badge.fury.io/rb/comic_walker.svg)](http://badge.fury.io/rb/comic_walker)
2
3
 
3
4
  Client library for [ComicWalker](http://comic-walker.com/).
4
5
 
@@ -21,44 +22,24 @@ Or install it yourself as:
21
22
  ### CLI
22
23
 
23
24
  ```
24
- % comic-walker KDCW_KS04000001010001_68
25
+ % comic-walker save KDCW_KS04000001010001_68
25
26
  item/xhtml/p-cover.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/000_p-cover.jpg
26
27
  item/xhtml/p-001.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/001_p-001.jpg
27
28
  item/xhtml/p-002.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/002_p-002.jpg
28
29
  item/xhtml/p-white.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/003_p-white.jpg
29
30
  item/xhtml/p-004.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/004_p-004.jpg
30
- item/xhtml/p-005.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/005_p-005.jpg
31
- item/xhtml/p-006.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/006_p-006.jpg
32
- item/xhtml/p-007.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/007_p-007.jpg
33
- item/xhtml/p-008.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/008_p-008.jpg
34
- item/xhtml/p-009.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/009_p-009.jpg
35
- item/xhtml/p-010.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/010_p-010.jpg
36
- item/xhtml/p-011.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/011_p-011.jpg
37
- item/xhtml/p-012.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/012_p-012.jpg
38
- item/xhtml/p-013.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/013_p-013.jpg
39
- item/xhtml/p-014.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/014_p-014.jpg
40
- item/xhtml/p-015.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/015_p-015.jpg
41
- item/xhtml/p-016.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/016_p-016.jpg
42
- item/xhtml/p-017.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/017_p-017.jpg
43
- item/xhtml/p-018.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/018_p-018.jpg
44
- item/xhtml/p-019.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/019_p-019.jpg
45
- item/xhtml/p-020.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/020_p-020.jpg
46
- item/xhtml/p-021.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/021_p-021.jpg
47
- item/xhtml/p-022.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/022_p-022.jpg
48
- item/xhtml/p-023.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/023_p-023.jpg
49
- item/xhtml/p-024.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/024_p-024.jpg
50
- item/xhtml/p-025.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/025_p-025.jpg
51
- item/xhtml/p-026.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/026_p-026.jpg
52
- item/xhtml/p-027.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/027_p-027.jpg
53
- item/xhtml/p-028.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/028_p-028.jpg
54
- item/xhtml/p-029.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/029_p-029.jpg
55
- item/xhtml/p-030.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/030_p-030.jpg
56
- item/xhtml/p-031.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/031_p-031.jpg
57
- item/xhtml/p-032.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/032_p-032.jpg
58
- item/xhtml/p-033.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/033_p-033.jpg
59
- item/xhtml/p-034.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/034_p-034.jpg
60
- item/xhtml/p-035.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/035_p-035.jpg
61
- item/xhtml/p-036.xhtml/0.dat -> Fate/kaleid liner プリズマ☆イリヤ ドライ!!/KDCW_KS04000001010001_68/036_p-036.jpg
31
+ (snip)
32
+ ```
33
+
34
+ ```
35
+ % comic-walker contents
36
+ 冴えない彼女の育てかた http://comic-walker.com/contents/detail/KDCW_FS02000006010000_68
37
+ Updated: 2014-03-24 13:39:24
38
+ Deliver: 2014-03-24 13:35:00 - 2064-03-12 17:54:00
39
+ Episodes:
40
+ 第1話 http://comic-walker.com/viewer/?cid=KDCW_FS02000006010001_68
41
+ Next: 2014-04-01 00:00:00
42
+ (snip)
62
43
  ```
63
44
 
64
45
  ## Contributing
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'comic_walker/cli'
3
3
 
4
- ComicWalker::CLI.new.run(ARGV)
4
+ ComicWalker::CLI.start(ARGV)
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency "http-cookie"
26
26
  spec.add_dependency "rmagick"
27
27
  spec.add_dependency "rubyzip"
28
+ spec.add_dependency "thor"
28
29
  end
@@ -1,19 +1,77 @@
1
1
  require 'json'
2
2
  require 'pathname'
3
3
  require 'pp'
4
+ require 'thor'
5
+ require 'yaml'
4
6
  require 'comic_walker/client'
5
7
  require 'comic_walker/end_layer_decoder'
6
8
  require 'comic_walker/item_decoder'
9
+ require 'comic_walker/v1/client'
7
10
 
8
11
  module ComicWalker
9
- class CLI
10
- def run(cids)
12
+ class CLI < Thor
13
+ package_name 'comic_walker'
14
+
15
+ CONFIG_DIR = Pathname.new(ENV['HOME']).join('.comic-walker')
16
+ CONFIG_PATH = CONFIG_DIR.join('config.yml')
17
+ COOKIE_PATH = CONFIG_DIR.join('cookie.yml')
18
+
19
+ desc 'save CID1 CID2...', 'Save contents'
20
+ def save(*cids)
11
21
  client = Client.new
12
22
  cids.each do |cid|
13
23
  save_content(client, cid)
14
24
  end
15
25
  end
16
26
 
27
+ desc 'save-all CID1 CID2...', 'Save all sub-contents'
28
+ def save_all(*parent_cids)
29
+ jar = HTTP::CookieJar.new
30
+ load_cookies(jar)
31
+ child_cids = find_sub_contents(V1::Client.new(jar, load_uuid), parent_cids)
32
+ save_cookies(jar)
33
+ save(*child_cids)
34
+ end
35
+
36
+ desc 'contents', 'List contents'
37
+ option :page,
38
+ desc: 'Page',
39
+ aliases: :p,
40
+ type: :numeric
41
+ option :per_page,
42
+ desc: 'Per page',
43
+ type: :numeric
44
+ def contents
45
+ jar = HTTP::CookieJar.new
46
+ load_cookies(jar)
47
+ client = V1::Client.new(jar, load_uuid)
48
+ json = client.start do
49
+ client.contents(per_page: options[:per_page], page: options[:page])
50
+ end
51
+ save_cookies(jar)
52
+
53
+ json['contents'].sort_by { |content| Time.parse(content['updated_at']) }.reverse_each do |content|
54
+ next unless content.has_key?('sub_contents')
55
+ puts "#{content['name']} http://comic-walker.com/contents/detail/#{content['content_id']}"
56
+ puts " Updated: #{format_time(content['updated_at'])}"
57
+ puts " Deliver: #{format_time(content['deliver_start_date'])} - #{format_time(content['deliver_stop_date'])}"
58
+ tags = content['tags']
59
+ puts " Episodes:"
60
+ tags['visible_episodes'].each_key do |episode|
61
+ _, desc, cid = *episode.split('|')
62
+ puts " #{desc} http://comic-walker.com/viewer/?cid=#{cid}"
63
+ end
64
+ if next_issue = tags['publication_date_of_next_issue']
65
+ next_issue = format_time(next_issue.keys.first)
66
+ else
67
+ next_issue = 'UNKNOWN'
68
+ end
69
+ puts " Next: #{next_issue}"
70
+ end
71
+ end
72
+
73
+ private
74
+
17
75
  def save_content(client, cid)
18
76
  license = client.get_license(cid)
19
77
  info = license.get_info.first
@@ -33,7 +91,11 @@ module ComicWalker
33
91
  pages[content['index']-1] = content['file']
34
92
  end
35
93
  when /end_layer\.json/
36
- pp EndLayerDecoder.new(license.key).decode(zip.read)
94
+ begin
95
+ pp EndLayerDecoder.new(license.key).decode(zip.read)
96
+ rescue JSON::ParserError
97
+ $stderr.puts "WARNING: #{cid}: Could not decode end_layer.json.enc"
98
+ end
37
99
  when %r{\Aitem/}
38
100
  items[entry.name] = zip.read
39
101
  end
@@ -50,5 +112,61 @@ module ComicWalker
50
112
  end
51
113
  end
52
114
  end
115
+
116
+ def load_cookies(jar)
117
+ if COOKIE_PATH.readable?
118
+ jar.load(COOKIE_PATH.to_s, session: true)
119
+ end
120
+ end
121
+
122
+ def save_cookies(jar)
123
+ CONFIG_DIR.mkpath
124
+ jar.save(COOKIE_PATH.to_s, session: true)
125
+ end
126
+
127
+ def load_uuid
128
+ CONFIG_DIR.mkpath
129
+ yaml = CONFIG_PATH.readable? && YAML.load_file(CONFIG_PATH.to_s)
130
+ uuid = yaml && yaml['uuid']
131
+ unless uuid
132
+ require 'securerandom'
133
+ uuid = SecureRandom.uuid.upcase
134
+ CONFIG_PATH.open('w') do |f|
135
+ YAML.dump({uuid: uuid}, f)
136
+ end
137
+ end
138
+ uuid
139
+ end
140
+
141
+ def format_time(str)
142
+ Time.parse(str).strftime('%F %X')
143
+ end
144
+
145
+ def find_sub_contents(client, parent_cids)
146
+ page = 1
147
+ per_page = 200
148
+ child_cids = {}
149
+ client.start do
150
+ until child_cids.size == parent_cids do
151
+ json = client.contents(page: page, per_page: per_page)
152
+ contents = json['contents']
153
+ if contents.empty?
154
+ break
155
+ end
156
+ contents.each do |c|
157
+ if parent_cids.include?(c['content_id'])
158
+ child_cids[c['content_id']] = c['sub_contents']
159
+ end
160
+ end
161
+ page += 1
162
+ end
163
+ end
164
+ parent_cids.each do |cid|
165
+ unless child_cids.has_key?(cid)
166
+ $stderr.puts "No such content: #{cid}"
167
+ end
168
+ end
169
+ child_cids.values.flatten
170
+ end
53
171
  end
54
172
  end
@@ -11,9 +11,21 @@ module ComicWalker
11
11
  @bid = '000000000000000000000NFBR'
12
12
  end
13
13
 
14
+ class Error < StandardError
15
+ attr_reader :code, :reason
16
+ def initialize(code, reason)
17
+ super("#{code}: #{reason}")
18
+ @code = code
19
+ @reason = reason
20
+ end
21
+ end
22
+
14
23
  def get_license(cid)
15
24
  u1 = get_u1(cid)
16
25
  json = get_li(cid, u1)
26
+ if error = json['error']
27
+ raise Error.new(error['code'], error['message'])
28
+ end
17
29
  license_b64 = json['license']
18
30
  License.new(JSON.parse(Cipher.decrypt_license(@bid, u1, license_b64)))
19
31
  end
@@ -0,0 +1,126 @@
1
+ require 'addressable/uri'
2
+ require 'http-cookie'
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ module ComicWalker
7
+ module V1
8
+ class Client
9
+ BASE_URI = Addressable::URI.parse("https://cnts.comic-walker.com")
10
+
11
+ def initialize(jar, uuid)
12
+ @https = Net::HTTP.new(BASE_URI.host, 443)
13
+ @https.use_ssl = true
14
+ @https.verify_mode = OpenSSL::SSL::VERIFY_PEER
15
+ @jar = jar
16
+ @uuid = uuid
17
+ end
18
+
19
+ def start(&block)
20
+ @https.start do
21
+ block.call
22
+ end
23
+ end
24
+
25
+ AID = 'KDCWI_JP'
26
+ AVER = '1.2.0'
27
+
28
+ class UnknownDeviceError < StandardError
29
+ end
30
+ class NoValidSessionError < StandardError
31
+ end
32
+
33
+ def create_session
34
+ retried = 0
35
+ begin
36
+ res = post('/user_sessions/create', {
37
+ DID: @uuid,
38
+ PIN: @uuid,
39
+ AID: AID,
40
+ AVER: AVER,
41
+ })
42
+ case res.body
43
+ when 'UnknownDeviceError'
44
+ raise UnknownDeviceError.new
45
+ when 'ValidSessionExistsError'
46
+ nil
47
+ else
48
+ JSON.parse(res.body)
49
+ 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
+ end
59
+ end
60
+
61
+ def create_user
62
+ post('/users/create', {
63
+ DID: @uuid,
64
+ PIN: @uuid,
65
+ AID: AID,
66
+ AVER: AVER,
67
+ })
68
+ end
69
+
70
+ def contents(params = {})
71
+ retried = 0
72
+ params = {
73
+ AID: AID,
74
+ AVER: AVER,
75
+ W: '320',
76
+ H: '480',
77
+ FORMATS: 'epub_pdf_fixedlayout',
78
+ include_hidden: 1,
79
+ include_meta: 1,
80
+ languages: 'ja',
81
+ }.merge(params)
82
+
83
+ begin
84
+ res = get('/v1/contents', params)
85
+ case res.body
86
+ when 'NoValidSessionError'
87
+ raise NoValidSessionError.new
88
+ else
89
+ JSON.parse(res.body)
90
+ 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
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def get(path, params = {})
105
+ uri = BASE_URI.join(path)
106
+ uri.query_values = params
107
+ req = Net::HTTP::Get.new(uri.request_uri)
108
+ request_with_cookie(uri, req)
109
+ end
110
+
111
+ def post(path, params = {})
112
+ uri = BASE_URI.join(path)
113
+ req = Net::HTTP::Post.new(uri.request_uri)
114
+ req.set_form_data(params)
115
+ request_with_cookie(uri, req)
116
+ end
117
+
118
+ def request_with_cookie(uri, req)
119
+ req['cookie'] = HTTP::Cookie.cookie_value(@jar.cookies(uri.to_s))
120
+ @https.request(req).tap do |res|
121
+ @jar.parse(res['set-cookie'], uri.to_s)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -1,5 +1,5 @@
1
1
  module ComicWalker
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
 
4
4
  VIEWER_VERSION = "1.0.2_2014-03-20"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: comic_walker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kohei Suzuki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-30 00:00:00.000000000 Z
11
+ date: 2014-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: thor
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  description: Client library for ComicWalker
112
126
  email:
113
127
  - eagletmt@gmail.com
@@ -117,6 +131,7 @@ extensions: []
117
131
  extra_rdoc_files: []
118
132
  files:
119
133
  - ".gitignore"
134
+ - CHANGELOG.md
120
135
  - Gemfile
121
136
  - LICENSE.txt
122
137
  - README.md
@@ -131,6 +146,7 @@ files:
131
146
  - lib/comic_walker/end_layer_decoder.rb
132
147
  - lib/comic_walker/item_decoder.rb
133
148
  - lib/comic_walker/item_decoder/unknown.rb
149
+ - lib/comic_walker/v1/client.rb
134
150
  - lib/comic_walker/version.rb
135
151
  homepage: https://github.com/eagletmt/comic_walker
136
152
  licenses: