mangadex 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +7 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +81 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +42 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +23 -0
  13. data/bin/setup +8 -0
  14. data/lib/extensions.rb +12 -0
  15. data/lib/mangadex/README.md +93 -0
  16. data/lib/mangadex/api/context.rb +53 -0
  17. data/lib/mangadex/api/response.rb +104 -0
  18. data/lib/mangadex/api/user.rb +48 -0
  19. data/lib/mangadex/api/version.rb +21 -0
  20. data/lib/mangadex/api.rb +5 -0
  21. data/lib/mangadex/artist.rb +13 -0
  22. data/lib/mangadex/auth.rb +56 -0
  23. data/lib/mangadex/author.rb +101 -0
  24. data/lib/mangadex/chapter.rb +105 -0
  25. data/lib/mangadex/content_rating.rb +75 -0
  26. data/lib/mangadex/cover_art.rb +93 -0
  27. data/lib/mangadex/custom_list.rb +127 -0
  28. data/lib/mangadex/internal/definition.rb +162 -0
  29. data/lib/mangadex/internal/request.rb +121 -0
  30. data/lib/mangadex/internal/with_attributes.rb +120 -0
  31. data/lib/mangadex/internal.rb +3 -0
  32. data/lib/mangadex/manga.rb +188 -0
  33. data/lib/mangadex/mangadex_object.rb +62 -0
  34. data/lib/mangadex/relationship.rb +46 -0
  35. data/lib/mangadex/report_reason.rb +39 -0
  36. data/lib/mangadex/scanlation_group.rb +97 -0
  37. data/lib/mangadex/sorbet.rb +42 -0
  38. data/lib/mangadex/tag.rb +10 -0
  39. data/lib/mangadex/types.rb +24 -0
  40. data/lib/mangadex/upload.rb +78 -0
  41. data/lib/mangadex/user.rb +103 -0
  42. data/lib/mangadex/version.rb +4 -0
  43. data/lib/mangadex.rb +35 -0
  44. data/mangadex.gemspec +35 -0
  45. data/sorbet/config +3 -0
  46. data/sorbet/rbi/gems/activesupport.rbi +1267 -0
  47. data/sorbet/rbi/gems/coderay.rbi +285 -0
  48. data/sorbet/rbi/gems/concurrent-ruby.rbi +1662 -0
  49. data/sorbet/rbi/gems/domain_name.rbi +52 -0
  50. data/sorbet/rbi/gems/http-accept.rbi +101 -0
  51. data/sorbet/rbi/gems/http-cookie.rbi +119 -0
  52. data/sorbet/rbi/gems/i18n.rbi +133 -0
  53. data/sorbet/rbi/gems/method_source.rbi +64 -0
  54. data/sorbet/rbi/gems/mime-types-data.rbi +17 -0
  55. data/sorbet/rbi/gems/mime-types.rbi +218 -0
  56. data/sorbet/rbi/gems/netrc.rbi +51 -0
  57. data/sorbet/rbi/gems/pry.rbi +1898 -0
  58. data/sorbet/rbi/gems/psych.rbi +471 -0
  59. data/sorbet/rbi/gems/rake.rbi +660 -0
  60. data/sorbet/rbi/gems/rest-client.rbi +454 -0
  61. data/sorbet/rbi/gems/rspec-core.rbi +1939 -0
  62. data/sorbet/rbi/gems/rspec-expectations.rbi +1150 -0
  63. data/sorbet/rbi/gems/rspec-mocks.rbi +1100 -0
  64. data/sorbet/rbi/gems/rspec-support.rbi +280 -0
  65. data/sorbet/rbi/gems/rspec.rbi +15 -0
  66. data/sorbet/rbi/gems/tzinfo.rbi +586 -0
  67. data/sorbet/rbi/gems/unf.rbi +19 -0
  68. data/sorbet/rbi/hidden-definitions/errors.txt +3942 -0
  69. data/sorbet/rbi/hidden-definitions/hidden.rbi +8210 -0
  70. data/sorbet/rbi/sorbet-typed/lib/activesupport/>=6/activesupport.rbi +37 -0
  71. data/sorbet/rbi/sorbet-typed/lib/activesupport/all/activesupport.rbi +1850 -0
  72. data/sorbet/rbi/sorbet-typed/lib/minitest/all/minitest.rbi +108 -0
  73. data/sorbet/rbi/sorbet-typed/lib/rake/all/rake.rbi +645 -0
  74. data/sorbet/rbi/sorbet-typed/lib/rspec-core/all/rspec-core.rbi +1891 -0
  75. data/sorbet/rbi/todo.rbi +7 -0
  76. metadata +243 -0
@@ -0,0 +1,56 @@
1
+ # typed: false
2
+ module Mangadex
3
+ class Auth
4
+ class << self
5
+ def login(username, password)
6
+ response = Mangadex::Internal::Request.post(
7
+ '/auth/login',
8
+ payload: {
9
+ username: username,
10
+ password: password,
11
+ },
12
+ )
13
+ return response if response.is_a?(Mangadex::Api::Response) && response.errored?
14
+
15
+ session = response.dig('token', 'session')
16
+ refresh = response.dig('token', 'refresh')
17
+
18
+ mangadex_user = Mangadex::Internal::Request.get('/user/me', headers: { Authorization: session })
19
+
20
+ user = Mangadex::Api::User.new(
21
+ mangadex_user.data.id,
22
+ session: session,
23
+ refresh: refresh,
24
+ data: mangadex_user.data,
25
+ )
26
+ Mangadex::Api::Context.user = user
27
+ !user.session_expired?
28
+ end
29
+
30
+ def check_token
31
+ JSON.parse(
32
+ Mangadex::Internal::Request.get(
33
+ '/auth/check',
34
+ raw: true,
35
+ )
36
+ )
37
+ end
38
+
39
+ def logout
40
+ return true if Mangadex::Api::Context.user.nil?
41
+
42
+ response = Mangadex::Internal::Request.post(
43
+ '/auth/logout',
44
+ )
45
+ return reponse if response.is_a?(Mangadex::Api::Response) && response.errored?
46
+
47
+ Mangadex::Api::Context.user = nil
48
+ true
49
+ end
50
+
51
+ def refresh_token
52
+ !(Mangadex::Api::Context.user&.refresh!).nil?
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,101 @@
1
+ # typed: true
2
+
3
+ require_relative 'mangadex_object'
4
+
5
+ module Mangadex
6
+ class Author < MangadexObject
7
+ has_attributes :name, :image_url, :biography, :version, :created_at, :updated_at
8
+
9
+ # List all authors.
10
+ # Path: +GET /author+
11
+ # Reference: https://api.mangadex.org/docs.html#operation/get-author
12
+ #
13
+ # @return [Mangadex::Api::Response] with a collection of authors
14
+ sig { params(args: T::Api::Arguments).returns(Mangadex::Api::Response[Author]) }
15
+ def self.list(**args)
16
+ Mangadex::Internal::Request.get(
17
+ '/author',
18
+ Mangadex::Internal::Definition.validate(args, {
19
+ limit: { accepts: Integer },
20
+ offset: { accepts: Integer },
21
+ ids: { accepts: [String] },
22
+ name: { accepts: String },
23
+ order: { accepts: Hash },
24
+ includes: { accepts: [String] },
25
+ })
26
+ )
27
+ end
28
+
29
+ # Create an author.
30
+ # Path: +POST /author+
31
+ # Reference: https://api.mangadex.org/docs.html#operation/post-author
32
+ #
33
+ # @return [Mangadex::Api::Response] with newly created author
34
+ sig { params(args: T::Api::Arguments).returns(Mangadex::Api::Response[Author]) }
35
+ def self.create(**args)
36
+ Mangadex::Internal::Request.post(
37
+ '/author',
38
+ payload: Mangadex::Internal::Definition.validate(args, {
39
+ name: { accepts: String, required: true },
40
+ version: { accepts: Integer },
41
+ })
42
+ )
43
+ end
44
+
45
+ # Get an author by ID.
46
+ # Path: +GET /author/:id+
47
+ # Reference: https://api.mangadex.org/docs.html#operation/get-author-id
48
+ #
49
+ # @return [Mangadex::Api::Response] with a entity of author
50
+ sig { params(id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[Author]) }
51
+ def self.get(id, **args)
52
+ Mangadex::Internal::Request.get(
53
+ format('/author/%{id}', id: id),
54
+ Mangadex::Internal::Definition.validate(args, {
55
+ includes: { accepts: [String] },
56
+ })
57
+ )
58
+ end
59
+
60
+ # Update an author.
61
+ # Path: +POST /author/:id+
62
+ # Reference: https://api.mangadex.org/docs.html#operation/put-author-id
63
+ #
64
+ # @return [Mangadex::Api::Response] with a entity of author
65
+ sig { params(id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[Author]) }
66
+ def self.update(id, **args)
67
+ Mangadex::Internal::Request.put(
68
+ format('/author/%{id}', id: id),
69
+ payload: Mangadex::Internal::Definition.validate(args, {
70
+ name: { accepts: String },
71
+ version: { accepts: Integer, required: true },
72
+ })
73
+ )
74
+ end
75
+
76
+ # Delete an author.
77
+ # Path: +DELETE /author/:id+
78
+ # Reference: https://api.mangadex.org/docs.html#operation/delete-author-id
79
+ #
80
+ # @param id Author's ID
81
+ # @return [Hash]
82
+ sig { params(id: String).returns(Hash) }
83
+ def self.delete(id)
84
+ Mangadex::Internal::Request.delete(
85
+ format('/author/%{id}', id: id)
86
+ )
87
+ end
88
+
89
+ def self.inspect_attributes
90
+ [:name]
91
+ end
92
+
93
+ # Indicates if this is an artist
94
+ #
95
+ # @return [Boolean] whether this is an artist or not.
96
+ sig { returns(T::Boolean) }
97
+ def artist?
98
+ false
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,105 @@
1
+ # typed: false
2
+ require_relative "mangadex_object"
3
+
4
+ module Mangadex
5
+ class Chapter < MangadexObject
6
+ include Internal::WithAttributes
7
+
8
+ has_attributes \
9
+ :title,
10
+ :volume,
11
+ :chapter,
12
+ :translated_language,
13
+ :hash,
14
+ :data,
15
+ :data_saver,
16
+ :last_chapter,
17
+ :uploader,
18
+ :external_url,
19
+ :version,
20
+ :created_at,
21
+ :updated_at,
22
+ :publish_at
23
+
24
+ sig { params(args: T::Api::Arguments).returns(Mangadex::Api::Response[Chapter]) }
25
+ def self.list(**args)
26
+ Mangadex::Internal::Request.get(
27
+ '/chapter',
28
+ Mangadex::Internal::Definition.chapter_list(args),
29
+ )
30
+ end
31
+
32
+ sig { params(id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[Chapter]) }
33
+ def self.get(id, **args)
34
+ Mangadex::Internal::Request.get(
35
+ '/chapter/%{id}' % {id: id},
36
+ Mangadex::Internal::Definition.validate(args, {
37
+ includes: { accepts: [String] },
38
+ }),
39
+ )
40
+ end
41
+
42
+ sig { params(id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[Chapter]) }
43
+ def self.update(id, **args)
44
+ Mangadex::Internal::Request.put(
45
+ '/chapter/%{id}' % {id: id},
46
+ payload: Mangadex::Internal::Definition.validate(args, {
47
+ title: { accepts: String },
48
+ volume: { accepts: String },
49
+ chapter: { accepts: String },
50
+ translated_language: { accepts: %r{^[a-zA-Z\-]{2,5}$} },
51
+ groups: { accepts: [String] },
52
+ version: { accepts: Integer, required: true },
53
+ }),
54
+ )
55
+ end
56
+
57
+ sig { params(id: String).returns(Hash) }
58
+ def self.delete(id)
59
+ Mangadex::Internal::Request.delete(
60
+ '/chapter/%{id}' % {id: id},
61
+ )
62
+ end
63
+
64
+ sig { returns(String) }
65
+ def title
66
+ attributes&.title.presence || chapter.presence && "Chapter #{chapter}" || "N/A"
67
+ end
68
+
69
+ sig { returns(T.nilable(String)) }
70
+ def locale
71
+ found_locale = translated_language.split('-').first
72
+ return if found_locale.nil?
73
+
74
+ ISO_639.find(found_locale)
75
+ end
76
+
77
+ sig { returns(T.nilable(String)) }
78
+ def locale_name
79
+ locale&.english_name
80
+ end
81
+
82
+ sig { returns(Integer) }
83
+ def page_count
84
+ Array(data).count
85
+ end
86
+
87
+ sig { returns(T.nilable(String)) }
88
+ def preview_image_url
89
+ return if data_saver.empty?
90
+
91
+ "https://uploads.mangadex.org/data-saver/#{attributes.hash}/#{data_saver.first}"
92
+ end
93
+
94
+ def as_json(*)
95
+ super.merge({
96
+ locale_name: locale_name,
97
+ preview_image_url: preview_image_url,
98
+ })
99
+ end
100
+
101
+ def self.attributes_to_inspect
102
+ [:id, :type, :title, :volume, :chapter, :page_count, :publish_at]
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,75 @@
1
+ # typed: false
2
+ require "active_support/string_inquirer"
3
+ require "active_support/core_ext/module/delegation"
4
+
5
+ module Mangadex
6
+ class ContentRating
7
+ extend T::Sig
8
+ include Comparable
9
+
10
+ VALUES = [
11
+ SAFE = 'safe',
12
+ SUGGESTIVE = 'suggestive',
13
+ EROTICA = 'erotica',
14
+ PORNOGRAPHIC = 'pornographic',
15
+ ].freeze
16
+
17
+ SCORES = {
18
+ SAFE => 0,
19
+ SUGGESTIVE => 1,
20
+ EROTICA => 2,
21
+ PORNOGRAPHIC => 3,
22
+ }.freeze
23
+
24
+ delegate_missing_to :value
25
+
26
+ sig { params(content_rating: T::Api::ContentRating).returns(T::Array[ContentRating]) }
27
+ def self.anything_below(content_rating)
28
+ SCORES.keys.map { |key| ContentRating.new(key) }.select { |record| record <= content_rating }.sort
29
+ end
30
+
31
+ sig { params(value: T.any(T::Api::Text, T::Api::ContentRating)).void }
32
+ def initialize(value)
33
+ @value = ensure_value!(value.to_s)
34
+ end
35
+
36
+ sig { returns(ActiveSupport::StringInquirer) }
37
+ def value
38
+ ActiveSupport::StringInquirer.new(@value)
39
+ end
40
+
41
+ sig { params(other: T.any(ContentRating, String, Symbol)).returns(Integer) }
42
+ def <=>(other)
43
+ other_score = if other.is_a?(ContentRating)
44
+ other.score
45
+ else
46
+ ContentRating.new(other).score
47
+ end
48
+
49
+ score <=> other_score
50
+ end
51
+
52
+ alias_method :safer_than?, :<
53
+ alias_method :spicier_than?, :>
54
+
55
+ sig { returns(Integer) }
56
+ def score
57
+ SCORES[value]
58
+ end
59
+
60
+ sig { returns(String) }
61
+ def to_s
62
+ value.to_s
63
+ end
64
+
65
+ private
66
+
67
+ sig { params(value: T.any(T::Api::Text, T::Api::ContentRating)).void }
68
+ def ensure_value!(value)
69
+ return value if value.is_a?(ContentRating)
70
+ return value if VALUES.include?(value)
71
+
72
+ raise ArgumentError, "Invalid content rating: '#{value}'. Must be one of #{VALUES}"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,93 @@
1
+ # typed: false
2
+ require_relative "mangadex_object"
3
+
4
+ module Mangadex
5
+ class CoverArt < MangadexObject
6
+ has_attributes \
7
+ :description,
8
+ :volume,
9
+ :file_name,
10
+ :created_at,
11
+ :updated_at,
12
+ :version
13
+
14
+ sig { params(args: T::Api::Arguments).returns(Mangadex::Api::Response[CoverArt]) }
15
+ def self.list(**args)
16
+ Mangadex::Internal::Request.get(
17
+ '/cover',
18
+ Mangadex::Internal::Definition.validate(args, {
19
+ limit: { accepts: Integer },
20
+ offset: { accepts: Integer },
21
+ manga: { accepts: [String] },
22
+ ids: { accepts: [String] },
23
+ uploaders: { accepts: [String] },
24
+ order: { accepts: Hash },
25
+ includes: { accepts: [String] },
26
+ })
27
+ )
28
+ end
29
+
30
+ sig do
31
+ params(
32
+ file: String,
33
+ volume: T.nilable(T.any(String, Integer)),
34
+ manga_id: String,
35
+ ).returns(Mangadex::Api::Response[CoverArt])
36
+ end
37
+ def self.upload(file, volume=nil, manga_id:)
38
+ args = { file: file, volume: volume }
39
+ Mangadex::Internal::Request.post(
40
+ '/cover/%{manga_id}' % {manga_id: manga_id},
41
+ payload: Mangadex::Internal::Definition.validate(args, {
42
+ file: { accepts: String, required: true },
43
+ volume: { accepts: %r{^(0|[1-9]\\d*)((\\.\\d+){1,2})?[a-z]?$} } # todo: double check regexp here
44
+ })
45
+ )
46
+ end
47
+
48
+ sig { params(id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[CoverArt]) }
49
+ def self.get(id, **args)
50
+ Mangadex::Internal::Request.get(
51
+ '/cover/%{id}' % {id: id},
52
+ Mangadex::Internal::Definition.validate(args, {
53
+ includes: { accepts: [String] },
54
+ })
55
+ )
56
+ end
57
+
58
+ sig { params(id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[CoverArt]) }
59
+ def self.edit(id, **args)
60
+ Mangadex::Internal::Request.put(
61
+ '/cover/%{id}' % {id: id},
62
+ Mangadex::Internal::Definition.validate(args, {
63
+ volume: { accepts: String },
64
+ description: { accepts: String },
65
+ version: { accepts: Integer, required: true }
66
+ })
67
+ )
68
+ end
69
+
70
+ sig { params(id: String).returns(Hash) }
71
+ def self.delete(id)
72
+ Mangadex::Internal::Request.delete(
73
+ '/cover/%{id}' % {id: id},
74
+ )
75
+ end
76
+
77
+ sig { params(size: T::Api::Text).returns(T.nilable(String)) }
78
+ def image_url(size: :small)
79
+ return unless manga.present?
80
+
81
+ extension = case size.to_sym
82
+ when :original
83
+ ''
84
+ when :medium
85
+ '.512.jpg'
86
+ else # :small by default
87
+ '.256.jpg'
88
+ end
89
+
90
+ "https://uploads.mangadex.org/covers/#{manga.id}/#{file_name}#{extension}"
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,127 @@
1
+ # typed: false
2
+ require_relative "mangadex_object"
3
+
4
+ module Mangadex
5
+ class CustomList < MangadexObject
6
+ has_attributes \
7
+ :name,
8
+ :visibility,
9
+ :version
10
+
11
+ sig { params(args: T::Api::Arguments).returns(Mangadex::Api::Response[CustomList]) }
12
+ def self.create(**args)
13
+ Mangadex::Internal::Request.post(
14
+ '/list',
15
+ payload: Mangadex::Internal::Definition.validate(args, {
16
+ name: { accepts: String, required: true },
17
+ visibility: { accepts: %w(public private), converts: :to_s },
18
+ manga: { accepts: String },
19
+ version: { accepts: Integer },
20
+ }),
21
+ auth: true,
22
+ )
23
+ end
24
+
25
+ sig { params(id: String).returns(Mangadex::Api::Response[CustomList]) }
26
+ def self.get(id)
27
+ Mangadex::Internal::Request.get(
28
+ '/list/%{id}' % {id: id},
29
+ )
30
+ end
31
+
32
+ sig { params(id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[CustomList]) }
33
+ def self.update(id, **args)
34
+ Mangadex::Internal::Request.put(
35
+ '/list/%{id}' % {id: id},
36
+ payload: Mangadex::Internal::Definition.validate(args, {
37
+ name: { accepts: String },
38
+ visibility: { accepts: %w(private public) },
39
+ manga: { accepts: String },
40
+ version: { accepts: Integer, required: true },
41
+ })
42
+ )
43
+ end
44
+
45
+ sig { params(id: String).returns(T::Boolean) }
46
+ def self.delete(id)
47
+ Mangadex::Internal::Request.delete(
48
+ '/list/%{id}' % {id: id},
49
+ )
50
+ end
51
+
52
+ sig { params(id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[Chapter]) }
53
+ def self.feed(id, **args)
54
+ Mangadex::Internal::Request.get(
55
+ '/list/%{id}/feed' % {id: id},
56
+ Mangadex::Internal::Definition.chapter_list(args),
57
+ )
58
+ end
59
+
60
+ sig { params(id: String, list_id: String).returns(T::Boolean) }
61
+ def self.add_manga(id, list_id:)
62
+ response = Mangadex::Internal::Request.post(
63
+ '/manga/%{id}/list/%{list_id}' % {id: id, list_id: list_id},
64
+ )
65
+ if response.is_a?(Hash)
66
+ response['result'] == 'ok'
67
+ else
68
+ !response.errored?
69
+ end
70
+ end
71
+
72
+ sig { params(id: String, list_id: String).returns(T::Boolean) }
73
+ def self.remove_manga(id, list_id:)
74
+ response = Mangadex::Internal::Request.delete(
75
+ '/manga/%{id}/list/%{list_id}' % {id: id, list_id: list_id},
76
+ )
77
+ if response.is_a?(Hash)
78
+ response['result'] == 'ok'
79
+ else
80
+ !response.errored?
81
+ end
82
+ end
83
+
84
+ sig { params(args: T::Api::Arguments).returns(Mangadex::Api::Response[CustomList]) }
85
+ def self.list(**args)
86
+ Mangadex::Internal::Request.get(
87
+ '/user/list',
88
+ Mangadex::Internal::Definition.validate(args, {
89
+ limit: { accepts: Integer },
90
+ offset: { accepts: Integer },
91
+ }),
92
+ )
93
+ end
94
+
95
+ sig { params(user_id: String, args: T::Api::Arguments).returns(Mangadex::Api::Response[CustomList]) }
96
+ def self.user_list(user_id, **args)
97
+ Mangadex::Internal::Request.get(
98
+ '/user/%{id}/list' % {id: user_id},
99
+ Mangadex::Internal::Definition.validate(args, {
100
+ limit: { accepts: Integer },
101
+ offset: { accepts: Integer },
102
+ }),
103
+ )
104
+ end
105
+
106
+ sig { params(id: String).returns(T::Boolean) }
107
+ def add_manga(id)
108
+ Mangadex::CustomList.add_manga(id, list_id: self.id)
109
+ end
110
+
111
+ sig { params(id: String).returns(T::Boolean) }
112
+ def remove_manga(id)
113
+ Mangadex::CustomList.remove_manga(id, list_id: self.id)
114
+ end
115
+
116
+ sig { params(args: T::Api::Arguments).returns(T.nilable(Mangadex::Api::Response[Manga])) }
117
+ def manga_details(**args)
118
+ ids = mangas.map(&:id)
119
+ ids.any? ? Mangadex::Manga.list(**args.merge(ids: ids)) : nil
120
+ end
121
+
122
+ def self.inspect_attributes
123
+ [:name, :visibility]
124
+ end
125
+ end
126
+ end
127
+