mangadex 5.3.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.
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
+