mangdown 0.20.8 → 0.21.0.beta1

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
  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