mangdown 0.20.8 → 0.21.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a09d979891cb14a1db38340293795b8d599d99cdb5cbba07f9d8ca05f5530fc0
4
- data.tar.gz: 26099c3ea529e2a0528b20b54b7c9c7ebe2f15e40c547081864e46a031883f9b
3
+ metadata.gz: 9dd954ee70668eaf60bdb7666da399247cab3b12e5149bc58cabb1e1e69907d1
4
+ data.tar.gz: 4e6f7bac16b8537de8a54a80afd60c0b86fcb72c3e72e9c125106ca2fa7f7aa9
5
5
  SHA512:
6
- metadata.gz: 468c99af8dc3353a445e7d0b3e27758cd65f75dbfdd4d6689e641b602b62b8d4a9483c5fb81e2ae21097de59f53f17dca527f3b6e7cf2d0466371c8252547eaa
7
- data.tar.gz: 31362635a8d659da879c2a2c36a810f93b3ecbf2d760689ad0a19e6283a01bc20f94877d3f15b512d9b42b5f457d003b8409e02442a46408d44f09ec965ad982
6
+ metadata.gz: abbeef647bd90011460b32610a71ff2d0023450f50e64f9d2a0a1f31c5a5a0e78a7287ffafecd24cea3f421bd0738ca48828b5b70933983412af86945f6067a6
7
+ data.tar.gz: c9ee0906dbb2e26bb84be9f7a760cdbcf511420b7234bdbeffe601fbe70c93e229067aa3c16ee86649ef8da873f9682777b84f94f2be716609914eee36294c1b
data/README.md CHANGED
@@ -1,33 +1,35 @@
1
- ## Adapters [New]
2
- Check out lib/mangdown/adapter.rb and lib/mangdown/adapters/mangareader.rb for examples of how to build an adapter.
1
+ ## Adapters
2
+ Check out lib/mangdown/adapters/mangareader.rb and lib/mangdown/adapters/mangabat.rb for examples of how to build an adapter.
3
3
 
4
4
  ### Register an adapter
5
5
 
6
6
  ```
7
7
  # Register an adapter (AdapterClass) with the name :name
8
- Mangdown.register_adapter(:name, AdpaterClass)
8
+ Mangdown.register_adapter(:name, AdpaterClass.new)
9
9
  ```
10
10
 
11
11
  ### Bundled adapters
12
- There is only one adapter bundled with mangdown, but it is fairly simple to create one, so go ahead and give it a try. Feel free to file an issue if you have any problems.
12
+ There are only two adapters bundled with mangdown, but it is fairly simple to create one, so go ahead and give it a try. Feel free to file an issue if you have any problems.
13
13
 
14
- There is a simple built-in client, "M", that you can use for finding manga:
14
+ There is a simple built-in client, "Mangdown::Client", that you can use for finding manga:
15
15
 
16
16
  ```ruby
17
17
  require 'mangdown/client'
18
18
 
19
19
  # Search for an exact match
20
- results = M.find("Dragon Ball")
20
+ results = Mangdown::Client.find("Dragon Ball")
21
21
 
22
22
  # Or if you need more flexibilty when searching for a manga,
23
- # use are Regex
24
- results = M.find(/dragon ball(\ssd)?$/i)
23
+ # use the db models directly
24
+ results = Mangdown::DB::Manga.where(name: 'Bleach').map do |record|
25
+ Mangdown.manga(record.url)
26
+ end
25
27
 
26
28
  # Get a Mangdown::Manga object
27
- manga = results.first.to_manga
29
+ manga = results.first
28
30
 
29
31
  # Get a chapter count
30
- manga.count
32
+ manga.chapter.length
31
33
 
32
34
  # Download everything
33
35
  manga.download
@@ -35,6 +37,9 @@ manga.download
35
37
  # Download a specific range
36
38
  manga.download(0, 99)
37
39
 
40
+ # Download to a specific dir
41
+ manga.download_to('path/to/downloads', 0, 99)
42
+
38
43
  # Convert all downloaded chapters to CBZ
39
44
  manga.cbz
40
45
 
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:manga) do
6
+ primary_key :id
7
+ String :adapter, null: false
8
+ String :url, null: false
9
+ String :name, null: false
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'forwardable'
3
4
  require 'addressable/uri'
4
5
  require 'mimemagic'
5
6
  require 'nokogiri'
@@ -10,47 +11,94 @@ require_relative 'mangdown/error'
10
11
 
11
12
  require_relative 'mangdown/support/logging'
12
13
  require_relative 'mangdown/support/equality'
13
- require_relative 'mangdown/support/properties'
14
14
  require_relative 'mangdown/support/tools'
15
15
  require_relative 'mangdown/support/cbz'
16
16
  require_relative 'mangdown/page'
17
17
  require_relative 'mangdown/chapter'
18
18
  require_relative 'mangdown/manga'
19
- require_relative 'mangdown/manga_list.rb'
20
- require_relative 'mangdown/md_hash'
21
19
 
22
- require_relative 'mangdown/adapter'
23
- require_relative 'mangdown/adapter/proxy'
24
20
  require_relative 'mangdown/adapter/no_adapter_error'
25
21
  require_relative 'mangdown/adapter/not_implemented_error'
26
- require_relative 'mangdown/adapter/mangareader.rb'
22
+ require_relative 'mangdown/adapter/mangareader'
23
+ require_relative 'mangdown/adapter/manga_bat'
27
24
 
28
25
  # Find, download and package manga from the web
29
26
  module Mangdown
30
- DOWNLOAD_DIR ||= Dir.home + '/manga'
27
+ class <<self
28
+ include Logging
29
+ end
31
30
 
32
- module_function
31
+ DOWNLOAD_DIR ||= Dir.home + '/manga'
33
32
 
34
- def register_adapter(name, adapter)
33
+ def self.register_adapter(name, adapter)
35
34
  adapters[name] = adapter
36
35
  end
37
36
 
38
- def adapter(name)
39
- adapters[name]
37
+ def self.adapters
38
+ @adapters ||= {}
40
39
  end
41
40
 
42
- def adapter!(uri, site = nil, doc = nil, name = nil)
43
- adapter_name = (uri || site).to_s
44
- klass = adapters.values.find { |adapter| adapter.for?(adapter_name) }
41
+ def self.manga(uri_or_instance)
42
+ with_adapter(uri_or_instance, :manga) do |instance|
43
+ Mangdown::Manga.new(instance)
44
+ end
45
+ end
45
46
 
46
- raise Adapter::NoAdapterError, adapter_name unless klass
47
+ def self.chapter(uri_or_instance)
48
+ with_adapter(uri_or_instance, :chapter) do |instance|
49
+ Mangdown::Chapter.new(instance)
50
+ end
51
+ end
47
52
 
48
- Adapter::Proxy.new(klass.new(uri, doc, name))
53
+ def self.page(uri_or_instance)
54
+ with_adapter(uri_or_instance, :page) do |instance|
55
+ Mangdown::Page.new(instance)
56
+ end
49
57
  end
50
58
 
51
- def adapters
52
- @adapters ||= {}
59
+ # rubocop:disable Metrics/AbcSize
60
+ # rubocop:disable Metrics/MethodLength
61
+ def self.with_adapter(instance, instance_constructor)
62
+ if instance.is_a?(String)
63
+ adapter = adapter(instance)
64
+ instance = adapter.public_send(instance_constructor, instance)
65
+ else
66
+ adapter = adapter(instance.url)
67
+ klass = adapter.class.const_get(instance_constructor.to_s.capitalize)
68
+ instance = klass.new(instance.attributes)
69
+ end
70
+ yield(instance)
71
+ rescue Adapter::NoAdapterError
72
+ raise
73
+ rescue StandardError => error
74
+ logger.error(debug_error(error, adapter, instance))
75
+ raise Mangdown::Error, "Adapter failed: #{error.message}"
53
76
  end
54
- end
77
+ private_class_method :with_adapter
78
+ # rubocop:enable Metrics/AbcSize
79
+ # rubocop:enable Metrics/MethodLength
55
80
 
56
- Mangdown.register_adapter(:mangareader, Mangdown::Mangareader)
81
+ def self.adapter(uri)
82
+ adapter = adapters.values.find { |a| a.for?(uri) }
83
+
84
+ raise Adapter::NoAdapterError, uri unless adapter
85
+
86
+ adapter
87
+ end
88
+ private_class_method :adapter
89
+
90
+ def self.debug_error(error, adapter, instance)
91
+ {
92
+ msg: 'Adapter method failed',
93
+ adapter: adapter.class,
94
+ instance: instance,
95
+ error: error,
96
+ error_msg: error.message,
97
+ backtrace: error.backtrace
98
+ }.to_s
99
+ end
100
+ private_class_method :debug_error
101
+
102
+ register_adapter :mangareader, Mangdown::Mangareader.new
103
+ register_adapter :manga_bat, Mangdown::MangaBat.new
104
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scrapework'
4
+
5
+ module Mangdown
6
+ # Adapter for mangabat
7
+ class MangaBat
8
+ ROOT = 'https://mangabat.com/'
9
+ CDNS = [
10
+ %r{^https://s\d.mkklcdnv\d.com/mangakakalot}
11
+ ].freeze
12
+
13
+ def for?(uri)
14
+ uri.to_s.start_with?(ROOT) || cdn_uri?(uri)
15
+ end
16
+
17
+ def cdn_uri?(uri)
18
+ CDNS.any? { |cdn| uri.match?(cdn) }
19
+ end
20
+
21
+ def manga_list
22
+ MangaList.load('https://mangabat.com/manga_list')
23
+ end
24
+
25
+ def manga(url)
26
+ Manga.load(url)
27
+ end
28
+
29
+ def chapter(url)
30
+ Chapter.load(url)
31
+ end
32
+
33
+ def page(url)
34
+ Page.load(url)
35
+ end
36
+
37
+ # A manga list web page
38
+ class MangaList < Scrapework::Object
39
+ has_many :manga, class: 'Mangdown::MangaBat::Manga'
40
+
41
+ def each(*args, &block)
42
+ to_enum.each(*args, &block)
43
+ end
44
+
45
+ def to_enum
46
+ Enumerator.new do |yielder|
47
+ page = self
48
+
49
+ while page
50
+ page.manga.each { |manga| yielder << manga }
51
+
52
+ page = page.next_page
53
+ end
54
+ end
55
+ end
56
+
57
+ map 'manga' do |html|
58
+ html.css('.update_item h3 a').map do |a|
59
+ uri = URI.join(ROOT, a[:href]).to_s
60
+
61
+ { url: uri, name: a.text.strip }
62
+ end
63
+ end
64
+
65
+ paginate do |html|
66
+ pages = html.css('.group-page a').to_a[1..-2]
67
+ current = pages.find_index { |p| p['class'] == 'pageselect' }
68
+
69
+ prev_page_link = pages[current - 1] if current
70
+ next_page_link = pages[current + 1] if current
71
+
72
+ prev_page = { url: prev_page_link['href'] } if prev_page_link
73
+ next_page = { url: next_page_link['href'] } if next_page_link
74
+
75
+ [prev_page, next_page]
76
+ end
77
+ end
78
+
79
+ # A manga web page
80
+ class Manga < Scrapework::Object
81
+ attribute :name
82
+
83
+ has_many :chapters, class: 'Mangdown::MangaBat::Chapter'
84
+
85
+ map :name do |html|
86
+ html.css('h1.entry-title').text.strip
87
+ end
88
+
89
+ map :chapters do |html|
90
+ html.css('.chapter-list .row a').reverse.map.with_index do |chapter, i|
91
+ i += 1
92
+ padded_number = i.to_s.rjust(5, '0')
93
+ chapter_name = "#{name} #{padded_number}"
94
+
95
+ { url: chapter['href'], name: chapter_name, number: i }
96
+ end
97
+ end
98
+ end
99
+
100
+ # A manga chapter web page
101
+ class Chapter < Scrapework::Object
102
+ attribute :name
103
+ attribute :number, type: Integer
104
+
105
+ belongs_to :manga, class: 'Mangdown::MangaBat::Manga'
106
+ has_many :pages, class: 'Mangdown::MangaBat::Page'
107
+
108
+ map :name do |html|
109
+ name = html.at_css('h1.entry-title').text.strip
110
+ name.sub(/Chapter (\d+)/) { Regexp.last_match[1].rjust(5, '0') }
111
+ end
112
+
113
+ map :number do
114
+ _mapped_name.slice(/Chapter (\d+)/, 1)
115
+ end
116
+
117
+ map :manga do |html|
118
+ manga = html.at_css('.breadcrumbs_doc p span:nth-child(3) a')
119
+
120
+ { url: manga['href'], name: manga.text.strip }
121
+ end
122
+
123
+ map :pages do |html|
124
+ html.css('.vung_doc img').map.with_index do |page, i|
125
+ i += 1
126
+ url = page['src']
127
+ padded_number = i.to_s.rjust(3, '0')
128
+ padded_chapter = number.to_s.rjust(5, '0')
129
+ name = "#{manga.name} #{padded_chapter}-#{padded_number}"
130
+
131
+ { url: url, name: name, number: i }
132
+ end
133
+ end
134
+
135
+ def hydra_opts
136
+ { max_concurrency: 10 }
137
+ end
138
+ end
139
+
140
+ # A manga page image
141
+ class Page < Scrapework::Object
142
+ attribute :name
143
+ attribute :number, type: Integer
144
+
145
+ belongs_to :chapter, class: 'Mangdown::MangaBat::Chapter'
146
+ end
147
+ end
148
+ end
@@ -1,134 +1,162 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cgi'
3
+ require 'scrapework'
4
4
 
5
5
  module Mangdown
6
- # Mangdown adapter for mangareader
7
- class Mangareader < Adapter::Base
8
- site :mangareader
9
-
10
- attr_reader :root
11
-
12
- def initialize(uri, doc, name)
13
- super
14
-
15
- @root = 'https://www.mangareader.net'
6
+ # Adapter for mangareader
7
+ class Mangareader
8
+ ROOT = 'https://www.mangareader.net'
9
+
10
+ def for?(uri)
11
+ URI.parse(uri).host&.end_with?('mangareader.net')
12
+ rescue URI::Error
13
+ false
16
14
  end
17
15
 
18
- def is_manga_list?(uri = @uri)
19
- uri == "#{root}/alphabetical"
16
+ def manga_list
17
+ MangaList.load('https://www.mangareader.net/alphabetical')
20
18
  end
21
19
 
22
- def is_manga?(uri = @uri)
23
- uri.slice(%r{#{root}(/\d+)?(/[^/]+)(\.html)?}i) == uri
20
+ def manga(url)
21
+ Manga.load(url)
24
22
  end
25
23
 
26
- def is_chapter?(uri = @uri)
27
- uri.slice(%r{#{root}(/[^/]+){1,2}/(\d+|chapter-\d+\.html)}i) == uri
24
+ def chapter(url)
25
+ Chapter.load(url)
28
26
  end
29
27
 
30
- def is_page?(uri = @uri)
31
- uri.slice(/.+\.(png|jpg|jpeg)$/i) == uri
28
+ def page(url)
29
+ Page.load(url)
32
30
  end
33
31
 
34
- # Only valid mangas should be returned (using is_manga?(uri))
35
- def manga_list
36
- doc.css('ul.series_alpha li a').map do |a|
37
- uri = "#{root}#{a[:href]}"
38
- manga = { uri: uri, name: a.text.strip.tr('/', ''), site: site }
32
+ # A mangareader manga list
33
+ class MangaList < Scrapework::Object
34
+ has_many :manga, class: 'Mangdown::Mangareader::Manga'
39
35
 
40
- manga if is_manga?(uri)
41
- end.compact
42
- end
36
+ map :manga do |html|
37
+ html.css('ul.series_alpha li a').map do |a|
38
+ uri = "#{ROOT}#{a[:href]}"
43
39
 
44
- def manga
45
- { uri: uri, name: manga_name, site: site }
46
- end
40
+ { url: uri, name: a.text.strip }
41
+ end
42
+ end
47
43
 
48
- # Only valid chapters should be returned (using is_chapter?(uri))
49
- def chapter_list
50
- doc.css('div#chapterlist td a').map do |a|
51
- uri = root + a[:href].sub(root, '')
52
- chapter = { uri: uri, name: a.text.strip.tr('/', ''), site: site }
44
+ def each(&block)
45
+ manga.each(&block)
46
+ end
53
47
 
54
- chapter if is_chapter?(uri)
55
- end.compact
48
+ def to_enum
49
+ manga.to_enum
50
+ end
56
51
  end
57
52
 
58
- def chapter
59
- { uri: uri,
60
- manga: manga_name,
61
- name: chapter_name,
62
- chapter: chapter_number,
63
- site: site }
64
- end
53
+ # A mangareader manga
54
+ class Manga < Scrapework::Object
55
+ attribute :name
56
+
57
+ has_many :chapters, class: 'Mangdown::Mangareader::Chapter'
65
58
 
66
- def page_list
67
- last_page = doc.css('select')[1].css('option').length
68
- (1..last_page).map do |page|
69
- slug = manga_name.tr(' ', '-').gsub(%r{[:,!'&/]}, '')
70
- uri = "#{root}/#{slug}/#{chapter_number}/#{page}"
71
- uri = Addressable::URI.escape(uri).downcase
72
- { uri: uri, name: page, site: site }
59
+ map :name do |html|
60
+ html.at_css('h2.aname').text.strip
61
+ end
62
+
63
+ map :chapters do |html|
64
+ html.css('div#chapterlist td a').map.with_index do |a, i|
65
+ uri = ROOT + a[:href]
66
+
67
+ { url: uri, name: a.text.strip, number: i + 1 }
68
+ end
73
69
  end
74
70
  end
75
71
 
76
- def page
77
- page_image = doc.css('img')[0]
78
- uri = page_image[:src]
79
- name = page_image[:alt].sub(/([^\d]*)(\d+)(\.\w+)?$/) do
80
- Regexp.last_match[1].to_s + Regexp.last_match[2].to_s.rjust(3, '0')
72
+ # A mangareader chapter
73
+ class Chapter < Scrapework::Object
74
+ attribute :name
75
+ attribute :number, type: Integer
76
+
77
+ belongs_to :manga, class: 'Mangdown::Mangareader::Manga'
78
+ has_many :page_views, class: 'Mangdown::Mangareader::PageView'
79
+
80
+ map :name do |html|
81
+ name = html.at_css('#mangainfo h1').text.strip
82
+ name.sub(/(\d+)$/) { Regexp.last_match[1].rjust(5, '0') }
81
83
  end
82
84
 
83
- { uri: uri, name: name.to_s.tr('/', ''), site: site }
84
- end
85
+ map :number do |html|
86
+ _mapped_name.slice(/(\d+)$/, 1)
87
+ end
85
88
 
86
- private
89
+ map :manga do |html|
90
+ manga = html.at_css('#mangainfo h2.c2 a')
87
91
 
88
- def manga_name
89
- if is_manga?
90
- name = doc.css('h2.aname').text
91
- elsif is_chapter?
92
- name = chapter_manga_name
92
+ {
93
+ url: "#{ROOT}#{manga['href']}",
94
+ name: manga.text.strip.sub(/ Manga$/, '')
95
+ }
93
96
  end
94
97
 
95
- return unless name
98
+ map :page_views do |html|
99
+ html.css('#selectpage select#pageMenu option').map.with_index do |op, i|
100
+ i += 1
101
+ uri = "#{ROOT}#{op['value']}"
102
+ padded_number = i.to_s.rjust(3, '0')
103
+ padded_chapter = number.to_s.rjust(5, '0')
104
+ name = "#{manga.name} #{padded_chapter}-#{padded_number}"
96
105
 
97
- name = name.gsub(%r{[/]}, '')
98
- CGI.unescapeHTML(name)
99
- end
106
+ { url: uri, name: name, number: i }
107
+ end
108
+ end
109
+
110
+ def hydra_opts
111
+ {}
112
+ end
100
113
 
101
- def chapter_name
102
- name = if @name
103
- @name.sub(/\s(\d+)$/) { |num| ' ' + num.to_i.to_s.rjust(5, '0') }
104
- else
105
- doc.css('').text # Not implimented
106
- end
114
+ def pages
115
+ return @pages if defined?(@pages)
107
116
 
108
- return unless name
117
+ threads = []
118
+ page_views.each do |page_view|
119
+ threads << Thread.new(page_view, &:page)
120
+ end
121
+ threads.each(&:join)
109
122
 
110
- name = name.gsub(%r{[/]}, '')
111
- CGI.unescapeHTML(name)
123
+ @pages = page_views.map(&:page)
124
+ end
112
125
  end
113
126
 
114
- def chapter_manga_name
115
- name = if @name
116
- @name.slice(/(^.+)\s/, 1)
117
- else
118
- doc.css('').text # Not implimented
119
- end
127
+ # A mangareader page
128
+ class PageView < Scrapework::Object
129
+ attribute :name
130
+ attribute :number, type: Integer
120
131
 
121
- return unless name
132
+ belongs_to :chapter, class: 'Mangdown::Mangareader::Chapter'
133
+ has_one :page, class: 'Mangdown::Mangareader::Page'
122
134
 
123
- name = name.gsub(%r{[/]}, '')
124
- CGI.unescapeHTML(name)
135
+ alias uri url
136
+
137
+ map :chapter do |html|
138
+ name = html.at_css('.mangainfo h1').text.strip
139
+ op = html.css('#selectpage select#pageMenu option').first
140
+
141
+ { url: "#{ROOT}#{op['href']}", name: name }
142
+ end
143
+
144
+ map :page do |html|
145
+ img = html.at_css('#imgholder img#img')
146
+
147
+ { url: img['src'], name: name, number: number }
148
+ end
125
149
  end
126
150
 
127
- def chapter_number
128
- if @name
129
- @name.slice(/\d+\z/).to_i
130
- else
131
- doc.css('').text # Not implimented
151
+ # A mangareader page
152
+ class Page < Scrapework::Object
153
+ attribute :name
154
+ attribute :number, type: Integer
155
+
156
+ belongs_to :page_view, class: 'Mangdown::Mangareader::PageView'
157
+
158
+ def chapter
159
+ page_view.chapter
132
160
  end
133
161
  end
134
162
  end