my_manga 1.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +145 -0
- data/bin/my_manga +6 -0
- data/db/config.yml +19 -0
- data/db/connection.rb +44 -0
- data/db/environment.rb +30 -0
- data/db/migrate/001_add_tables.rb +20 -0
- data/lib/mangdown/adapters/manga_here.rb +116 -0
- data/lib/my_manga.rb +106 -0
- data/lib/my_manga/add.rb +21 -0
- data/lib/my_manga/cli.rb +42 -0
- data/lib/my_manga/command.rb +17 -0
- data/lib/my_manga/download.rb +46 -0
- data/lib/my_manga/env.rb +16 -0
- data/lib/my_manga/find.rb +28 -0
- data/lib/my_manga/list.rb +60 -0
- data/lib/my_manga/m_clean_up.rb +18 -0
- data/lib/my_manga/mark.rb +56 -0
- data/lib/my_manga/remove.rb +19 -0
- data/lib/my_manga/update.rb +32 -0
- data/lib/my_manga/version.rb +5 -0
- data/models/chapter.rb +28 -0
- data/models/manga.rb +33 -0
- data/test/adapters_test.rb +33 -0
- data/test/features_test.rb +188 -0
- data/test/test_helper.rb +14 -0
- metadata +154 -0
data/lib/my_manga/add.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyManga
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
# See desc
|
7
|
+
class Add < MyManga::CLI::Command
|
8
|
+
desc 'Add a manga to your library'
|
9
|
+
argument :url, required: true, desc: 'Manga url'
|
10
|
+
|
11
|
+
def call(url:)
|
12
|
+
manga = MyManga.add(url)
|
13
|
+
|
14
|
+
return unless manga
|
15
|
+
|
16
|
+
puts %("#{manga.name}" added to your library!)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/my_manga/cli.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './command'
|
4
|
+
require_relative './version'
|
5
|
+
require_relative './find'
|
6
|
+
require_relative './add'
|
7
|
+
require_relative './remove'
|
8
|
+
require_relative './list'
|
9
|
+
require_relative './download'
|
10
|
+
require_relative './update'
|
11
|
+
require_relative './mark'
|
12
|
+
require_relative './env'
|
13
|
+
require_relative './m_clean_up'
|
14
|
+
|
15
|
+
module MyManga
|
16
|
+
module CLI
|
17
|
+
# Registry for CLI commands
|
18
|
+
module Commands
|
19
|
+
extend Hanami::CLI::Registry
|
20
|
+
|
21
|
+
# see desc
|
22
|
+
class Version < MyManga::CLI::Command
|
23
|
+
desc 'Print version'
|
24
|
+
|
25
|
+
def call(*)
|
26
|
+
puts MyManga::VERSION
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
register 'version', Version, aliases: %w[v -v --version]
|
31
|
+
register 'find', Find, aliases: %w[search]
|
32
|
+
register 'add', Add
|
33
|
+
register 'remove', Remove, aliases: %w[delete]
|
34
|
+
register 'list', List, aliases: %w[library show]
|
35
|
+
register 'download', Download
|
36
|
+
register 'update', Update, aliases: %w[fetch]
|
37
|
+
register 'mark', Mark
|
38
|
+
register 'env', Env, aliases: %w[-e --environment]
|
39
|
+
register 'm:clean_up', MCleanUp, aliases: %w[purge]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module MyManga
|
2
|
+
module CLI
|
3
|
+
# Base class for CLI commands
|
4
|
+
class Command < Hanami::CLI::Command
|
5
|
+
def manga_names(input)
|
6
|
+
all = MyManga.names
|
7
|
+
names = Array(input.to_s.split(',')).map(&:strip)
|
8
|
+
|
9
|
+
names.empty? ? all : all & names
|
10
|
+
end
|
11
|
+
|
12
|
+
def pad(string, width, with: ' ')
|
13
|
+
string.ljust(width + 2, with)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyManga
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
# See desc
|
7
|
+
class Download < MyManga::CLI::Command
|
8
|
+
desc 'Download manga from your library'
|
9
|
+
argument :names, desc: 'Manga names (comma separated)'
|
10
|
+
option :all,
|
11
|
+
type: :boolean,
|
12
|
+
default: true,
|
13
|
+
desc: 'Download all unread'
|
14
|
+
option :list,
|
15
|
+
desc: 'List of chapters to download (comma separated)'
|
16
|
+
option :from,
|
17
|
+
desc: 'First chapter to download (must be used with TO)'
|
18
|
+
option :to,
|
19
|
+
desc: 'Last chapter to download (must be used with FROM)'
|
20
|
+
|
21
|
+
def call(names: nil, **options)
|
22
|
+
names = manga_names(names)
|
23
|
+
numbers = if options[:list]
|
24
|
+
options[:list].to_s.split(',').map(&:strip)
|
25
|
+
elsif options[:to]
|
26
|
+
(options.fetch(:from)..options.fetch(:to)).to_a
|
27
|
+
end
|
28
|
+
|
29
|
+
names.each do |name|
|
30
|
+
manga = MyManga[name]
|
31
|
+
chapters = numbers || manga.chapters_unread_numbers
|
32
|
+
count = chapters.length
|
33
|
+
|
34
|
+
next unless count.positive?
|
35
|
+
|
36
|
+
puts "Downloading #{count} Chapters from \"#{name}\""
|
37
|
+
MyManga.download(manga, chapters)
|
38
|
+
end
|
39
|
+
|
40
|
+
puts '...'
|
41
|
+
puts 'Finished Download!'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/my_manga/env.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyManga
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
# See desc
|
7
|
+
class Find < MyManga::CLI::Command
|
8
|
+
desc 'Search for a manga by name'
|
9
|
+
argument :search_term, required: true, desc: 'Manga name'
|
10
|
+
|
11
|
+
def call(search_term:)
|
12
|
+
results = MyManga.find(search_term)
|
13
|
+
|
14
|
+
puts "Manga found for \"#{search_term}\""
|
15
|
+
puts '=' * (18 + search_term.length)
|
16
|
+
|
17
|
+
return if results.empty?
|
18
|
+
|
19
|
+
column_width = results.map { |r| r[:name].length }.max
|
20
|
+
puts pad('Name', column_width) + 'Url'
|
21
|
+
results.each do |result|
|
22
|
+
puts pad(result[:name], column_width) + result[:uri]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyManga
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
# See desc
|
7
|
+
class List < MyManga::CLI::Command
|
8
|
+
desc 'View your manga list'
|
9
|
+
argument :names, desc: 'Manga names (comma separated)'
|
10
|
+
|
11
|
+
def call(names: nil)
|
12
|
+
names = manga_names(names)
|
13
|
+
names.one? ? list_detail(names.first) : list(names)
|
14
|
+
end
|
15
|
+
|
16
|
+
def list(names)
|
17
|
+
column_width = names.map(&:length).max || 10
|
18
|
+
|
19
|
+
puts 'Manga list'
|
20
|
+
puts '=========='
|
21
|
+
print pad('Name', column_width)
|
22
|
+
puts 'Chapters read/total (unread)'
|
23
|
+
|
24
|
+
names.sort.each do |name|
|
25
|
+
manga = MyManga[name]
|
26
|
+
read = manga.read_count
|
27
|
+
total = manga.total_count
|
28
|
+
unread = total - read
|
29
|
+
|
30
|
+
print pad(name, column_width)
|
31
|
+
puts "#{read}/#{total} (#{unread}) #{manga.uri}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def list_detail(name)
|
36
|
+
manga = MyManga[name]
|
37
|
+
header = %(Manga details for "#{manga.name}")
|
38
|
+
chapters = manga.chapters_read
|
39
|
+
read = manga.read_count
|
40
|
+
total = manga.total_count
|
41
|
+
unread = total - read
|
42
|
+
|
43
|
+
puts header
|
44
|
+
puts '=' * header.length
|
45
|
+
print pad('Name', name.length)
|
46
|
+
puts 'Chapters read/total (unread)'
|
47
|
+
puts "#{name} #{read}/#{total} (#{unread}) #{manga.uri}"
|
48
|
+
puts
|
49
|
+
puts 'Chapters Read'
|
50
|
+
puts '-------------'
|
51
|
+
chapters[0..2].each do |chapter|
|
52
|
+
puts chapter.name
|
53
|
+
end
|
54
|
+
puts '...' if chapters.length > 4
|
55
|
+
puts chapters[-1].name if chapters.length > 3
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyManga
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
# See desc
|
7
|
+
class MCleanUp < MyManga::CLI::Command
|
8
|
+
desc 'Remove the manga cache for M, the mangdown client'
|
9
|
+
|
10
|
+
def call(*)
|
11
|
+
return unless MyManga.mangdown_client_clean_up
|
12
|
+
|
13
|
+
puts 'M cache files removed'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyManga
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
# See desc
|
7
|
+
class Mark < MyManga::CLI::Command
|
8
|
+
desc 'Mark manga chapters as read/unread'
|
9
|
+
argument :flag, required: true, desc: '[read|unread]'
|
10
|
+
argument :names, desc: 'Manga names (comma separated)'
|
11
|
+
option :all,
|
12
|
+
type: :boolean,
|
13
|
+
default: true,
|
14
|
+
desc: 'Mark all'
|
15
|
+
option :list,
|
16
|
+
desc: 'List of chapters to mark (comma separated)'
|
17
|
+
option :from,
|
18
|
+
desc: 'First chapter to mark (must be used with TO)'
|
19
|
+
option :to,
|
20
|
+
desc: 'Last chapter to mark (must be used with FROM)'
|
21
|
+
|
22
|
+
def call(flag:, names: nil, **options)
|
23
|
+
names = manga_names(names)
|
24
|
+
if options[:list]
|
25
|
+
numbers = options[:list].to_s.split(',').map(&:strip)
|
26
|
+
output ||= numbers.join(', ')
|
27
|
+
elsif options[:to]
|
28
|
+
numbers = (options.fetch(:from)..options.fetch(:to)).to_a
|
29
|
+
output = [numbers.first, numbers.last].join('-')
|
30
|
+
end
|
31
|
+
output ||= '(all)'
|
32
|
+
|
33
|
+
names.each do |name|
|
34
|
+
manga = MyManga[name]
|
35
|
+
chapters = numbers || manga.chapters_numbers
|
36
|
+
count = chapters.length
|
37
|
+
|
38
|
+
next unless count.positive?
|
39
|
+
|
40
|
+
mark(manga, flag, chapters)
|
41
|
+
print %(Chapters #{output} from "#{name}" )
|
42
|
+
puts %(Marked as #{flag.capitalize})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def mark(manga, flag, chapters)
|
47
|
+
if flag == 'read'
|
48
|
+
MyManga.read!(manga, chapters)
|
49
|
+
else
|
50
|
+
MyManga.unread!(manga, chapters)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyManga
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
# See desc
|
7
|
+
class Remove < MyManga::CLI::Command
|
8
|
+
desc 'Remove a manga from your library'
|
9
|
+
argument :name, required: true, desc: 'Manga name'
|
10
|
+
|
11
|
+
def call(name:)
|
12
|
+
return unless MyManga.remove(name)
|
13
|
+
|
14
|
+
puts %("#{name}" removed from your library!)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyManga
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
# See desc
|
7
|
+
class Update < MyManga::CLI::Command
|
8
|
+
desc 'Update manga from your library'
|
9
|
+
argument :names, desc: 'Manga names (comma separated)'
|
10
|
+
|
11
|
+
def call(names: nil, **options)
|
12
|
+
names = manga_names(names)
|
13
|
+
|
14
|
+
puts 'Fetching Manga'
|
15
|
+
puts '...'
|
16
|
+
|
17
|
+
names.each do |name|
|
18
|
+
manga = MyManga[name]
|
19
|
+
old_total = manga.total_count
|
20
|
+
MyManga.update(manga)
|
21
|
+
new_total = MyManga[name].total_count
|
22
|
+
updated = new_total - old_total
|
23
|
+
|
24
|
+
next if updated.zero?
|
25
|
+
|
26
|
+
puts "Updated \"#{name}\": #{updated} new Chapters."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/models/chapter.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Chapter < ActiveRecord::Base
|
2
|
+
belongs_to :manga
|
3
|
+
|
4
|
+
scope :read, Proc.new { where(read: true) }
|
5
|
+
scope :unread, Proc.new { where(read: false) }
|
6
|
+
|
7
|
+
def self.from_md(md_hash)
|
8
|
+
uri = md_hash.uri
|
9
|
+
name = md_hash.name
|
10
|
+
site = md_hash.site
|
11
|
+
number = md_hash.chapter
|
12
|
+
|
13
|
+
unless number
|
14
|
+
adapter = Mangdown.adapter!(uri, site, nil, name)
|
15
|
+
number = adapter.chapter[:chapter]
|
16
|
+
end
|
17
|
+
|
18
|
+
find_or_create_by(name: name, uri: uri, number: number)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_md
|
22
|
+
::Mangdown::MDHash.new(name: name, uri: uri).to_chapter
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.numbers
|
26
|
+
pluck(:number)
|
27
|
+
end
|
28
|
+
end
|
data/models/manga.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
class Manga < ActiveRecord::Base
|
2
|
+
self.table_name = "manga"
|
3
|
+
|
4
|
+
has_many :chapters, dependent: :destroy
|
5
|
+
|
6
|
+
delegate :read, :unread, to: :chapters, prefix: true, allow_nil: false
|
7
|
+
delegate :numbers, to: :chapters_unread, prefix: true, allow_nil: false
|
8
|
+
delegate :numbers, to: :chapters, prefix: true, allow_nil: false
|
9
|
+
|
10
|
+
def self.from_md(md_manga)
|
11
|
+
manga = find_or_create_by(name: md_manga.name, uri: md_manga.uri)
|
12
|
+
manga.update_chapters(md_manga.chapters)
|
13
|
+
manga
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_chapters(fetched_chapters)
|
17
|
+
fetched_chapters.each do |md|
|
18
|
+
chapter = Chapter.from_md(md)
|
19
|
+
chapter.manga = self
|
20
|
+
chapter.save
|
21
|
+
end
|
22
|
+
self.total_count = chapters.count
|
23
|
+
save!
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_md
|
27
|
+
::Mangdown::MDHash.new(name: name, uri: uri).to_manga
|
28
|
+
end
|
29
|
+
|
30
|
+
def chapters
|
31
|
+
super.order(:number)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require_relative '../../mangdown/lib/mangdown/client'
|
3
|
+
require_relative '../lib/mangdown/adapters/manga_here'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
Mangdown.register_adapter(:manga_here, MangaHere)
|
7
|
+
|
8
|
+
class AdaptersTest < Minitest::Test
|
9
|
+
def setup
|
10
|
+
@manga_uri = 'http://www.mangahere.co/manga/bokutachi_wa_benkyou_ga_dekinai/'
|
11
|
+
end
|
12
|
+
|
13
|
+
def build_manga
|
14
|
+
Mangdown::MDHash.new(uri: @manga_uri).to_manga
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_creates_manga
|
18
|
+
assert_silent do
|
19
|
+
build_manga
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_chapter
|
24
|
+
manga = build_manga
|
25
|
+
chapter = nil
|
26
|
+
|
27
|
+
Timeout.timeout(10) do
|
28
|
+
chapter = manga.chapters[-1].to_chapter
|
29
|
+
end
|
30
|
+
|
31
|
+
assert chapter
|
32
|
+
end
|
33
|
+
end
|