mangadex 5.3.3.1 → 5.3.3.2

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: cdf99fecbef4c256aa1f0b739a7051d68f7f79a864f2525d7a56d5ee6a2cca79
4
- data.tar.gz: 7250ded43bef9892e3d13323a1cea91c8f45378df330534f1a4f229575a1ebee
3
+ metadata.gz: 1d01b0fac553138d7ac86d1313737d86ba5415ee88e2ca6de08787402be7e3fb
4
+ data.tar.gz: 643923fe081fb046f6feec8ab4ce945a358a2fc9d66681d732a272b9bad2223d
5
5
  SHA512:
6
- metadata.gz: ef23c65119a28a69e2c060b69361be24e3558cdce80b6bed93e1a0fc7463e7f87bca81b59c770c4fb378d68368c0da1713723523f4fee445d829ac9d4e0a973b
7
- data.tar.gz: 58b0f5ee9763465a0801ebfbaf825c0009b12138bd20996ac34d5183c68a16ed6d515c8555e02b669c29cdbcb5e12916bb7adfec4d0769edbc7e793597d97334
6
+ metadata.gz: a196f878697f4c7f1a65178cee78a6d36b1c23c9466d0b997ee3ece51bbc0497db0fc0a3f6f16361a1d7c7518733afe7357bbc2a5cb785deaabe07d45df1aec5
7
+ data.tar.gz: b657aa48b952ae4b3136bc52c6e5f96f7d3287ae26d08089a0aaf294313a9662d1dfa2ca35477702b4269ad42af53f2b97e7e9eee575142b0c27b295c789de78
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mangadex (5.3.3.1)
4
+ mangadex (5.3.3.2)
5
5
  activesupport (~> 6.1)
6
6
  psych (~> 4.0.1)
7
7
  rest-client (~> 2.1)
@@ -82,7 +82,7 @@ GEM
82
82
  smart_properties (1.16.3)
83
83
  sorbet (0.5.9152)
84
84
  sorbet-static (= 0.5.9152)
85
- sorbet-runtime (0.5.9161)
85
+ sorbet-runtime (0.5.9189)
86
86
  sorbet-static (0.5.9152-universal-darwin-20)
87
87
  sorbet-static (0.5.9152-x86_64-linux)
88
88
  tilt (2.0.10)
data/bin/console CHANGED
@@ -3,13 +3,14 @@
3
3
  require "bundler/setup"
4
4
  require "mangadex"
5
5
 
6
- username, password = [
6
+ username, password, email = [
7
7
  ENV['MD_USERNAME'],
8
8
  ENV['MD_PASSWORD'],
9
+ ENV['MD_EMAIL'],
9
10
  ]
10
11
 
11
- if username && password
12
- Mangadex::Auth.login(username, password)
12
+ if (username || email) && password
13
+ Mangadex::Auth.login(username: username, email: email, password: password)
13
14
  end
14
15
 
15
16
  # You can add fixtures and/or initialization code here to make experimenting
@@ -0,0 +1,226 @@
1
+ # 🔒 Authenticating with Mangadex
2
+
3
+ ## Beforehand
4
+
5
+ Any actions that can be performed on the mangadex site as a non authenticated user will not require a user to be logged
6
+ in. Authentication on Mangadex, as per version
7
+ <a href="https://rubygems.org/gems/mangadex"><img src="https://badgen.net/rubygems/v/mangadex" /></a>
8
+ will need an `Authorization` HTTP header to be present.
9
+
10
+ You can check details on the Mangadex API here: https://api.mangadex.org/docs.html#section/Authentication
11
+
12
+ ## Authentication & Authorization flow
13
+
14
+ The authentication flow happens as such:
15
+
16
+ 1. You login with your email or username, and password.
17
+ 2. Upon successfully _authenticating_ your account, you will be given a `session` and a `refresh` tokens.
18
+ 3. You must use the `session` token to _authorize_ your account to perform certain actions (ie: create resources, etc.)
19
+ 4. You must use the `refresh` token to refresh the `session` when expired.
20
+
21
+ > - The `session` token expires **15 minutes** after been granted.
22
+ > - The `refresh` token refreshes **1 month** after been granted.
23
+
24
+ ## Authentication using `mangadex` gem
25
+
26
+ Now that the basics of authentication have been covered, let's go over how it's done on with the gem.
27
+
28
+ ### Logging in
29
+
30
+ It's simple to login, whether with your email address or your email address:
31
+
32
+ ```ruby
33
+ # With your username
34
+ Mangadex::Auth.login(username: username, password: password)
35
+
36
+ # With your email address
37
+ Mangadex::Auth.login(email: email, password: password)
38
+ ```
39
+
40
+ Upon successful authentication, an instance of `Mangadex::Api::User` will be returned:
41
+
42
+ ```ruby
43
+ user = Mangadex::Auth.login(...)
44
+
45
+ # The session token, valid for 15 minutes (String).
46
+ user.session
47
+
48
+ # The refresh token, valid for 1 month (String)
49
+ user.refresh
50
+
51
+ # The logged in user's ID (String) (formatted as a UUID)
52
+ user.mangadex_user_id
53
+
54
+ # Time at the which user.session becomes invalid (Time)
55
+ user.session_valid_until
56
+
57
+ # Miscellaneaous data. When logging in, it's an instance of Mangadex::User
58
+ # (response from the server)
59
+ user.data
60
+ ```
61
+
62
+ ```ruby
63
+ # Refreshes the tokens now (Boolean)
64
+ user.refresh!
65
+
66
+ # Refreshes the tokens if expired, then return user itself (Mangadex::Api::User)
67
+ user.with_valid_session
68
+
69
+ # Returns if user.session has expired (Boolean)
70
+ user.session_expired?
71
+ ```
72
+
73
+ If there's an error, `Mangadex::Errors::AuthenticationError` will be raised. Here's how to handle that scenario:
74
+
75
+ ```ruby
76
+ def login(email, password)
77
+ Mangadex::Auth.login(email: email, password: password)
78
+ rescue Mangadex::Errors::AuthenticationError => error
79
+ response = error.response
80
+
81
+ # A list of detailed errors from Mangadex. (Array of
82
+ # Mangadex::Api::Response::Error)
83
+ response.errors.each do |error|
84
+ puts error.id
85
+ puts error.status
86
+ puts error.title
87
+ puts error.detail
88
+ end
89
+ end
90
+ ```
91
+
92
+ ### Authenticating requests
93
+
94
+ When the user is logged in, all subsequent requests _should_ be authenticated. Here's an example to retrieve a list of manga that the logged in user is _reading_ at the moment:
95
+
96
+ ```ruby
97
+ user = Mangadex::Auth.login(...)
98
+ response = Mangadex::Manga.all_reading_status('reading')
99
+ manga_ids = response['statuses'].keys
100
+
101
+ reading_now = Mangadex::Manga.list(ids: manga_ids)
102
+ ```
103
+
104
+ If for whatever reason you want to a request not to be authenticated, you can do something like:
105
+
106
+ ```ruby
107
+ Mangadex.context.without_user do
108
+ # your mangadex request(s) here
109
+ end
110
+ ```
111
+
112
+ When logging in, the user's session information will be persisted in the storage. See below [for more details]().
113
+
114
+ ### Logging out
115
+
116
+ Logging the user out is very easy:
117
+
118
+ ```ruby
119
+ Mangadex::Auth.logout
120
+ ```
121
+
122
+ Here, the `user`'s session will be revoked on Mangadex. It will try to delete the user's session. `Mangadex::Auth.logout` outside of the `with_user` block will not do anything.
123
+
124
+ This action also clears the context's user and the storage info associated to this user.
125
+
126
+ ## Persisting the user session: storage stragegies
127
+
128
+ ### What is this?
129
+
130
+ Using this gem should help you a little bit managing tokens. By default, the gem stores the following information in memory:
131
+
132
+ - For a particular user ID:
133
+ - User session
134
+ - User refresh token
135
+ - User session expiry date
136
+
137
+ ### Why is this a thing?
138
+
139
+ Good question. We want to make session management with this gem as easy as possible. The session is used to retrieve a valid logged in user. Here's how it works:
140
+
141
+ - When the user logs in, the refresh token, the session token (as well as it's expired date) are stored for that user
142
+ - When requesting the tokens for the user, a `Mangadex::Api::User` is created with refreshed tokens (if expired).
143
+ - When logging out, if implemented by you, the users's session details are deleted.
144
+
145
+ Here's you retrieve a user from your storage at any point:
146
+
147
+ ```ruby
148
+ mangadex_user_id = '...'
149
+ Mangadex::Api::User.from_storage(mangadex_user_id)
150
+ ```
151
+
152
+ It's up to you to decide how you store the user ID. You don't need to worry about saving the storage, the gem takes care of that for you.
153
+
154
+ ### Ok, ok. How can I use my own strategy?
155
+
156
+ By default, this gem ships with `Mangagex::Storage::Memory` which corresponds to the in-memory storage. This should be fine if you don't care much about persisting the user session at any point.
157
+
158
+ No assumptions can be made on which storage service you use. That is 100% up to you how the information is stored. Let's say you want to use [redis](https://github.com/redis/redis) for session instead of the default memory storage stragery:
159
+
160
+ ```ruby
161
+ require 'redis'
162
+
163
+ class BasicRedisStragery < Mangadex::Storage::Basic
164
+ # Must be implemented
165
+ def get(mangadex_user_id, key)
166
+ client.hget(mangadex_user_id, key)
167
+ end
168
+
169
+ # Must be implemented
170
+ def set(mangadex_user_id, key, value)
171
+ client.hset(mangadex_user_id, key, value)
172
+ end
173
+
174
+ # Optional - It's a nice-to-have, especially for logging out.
175
+ def clear(mangadex_user_id)
176
+ client.del(mangadex_user_id)
177
+ end
178
+
179
+ private
180
+
181
+ def client
182
+ @client ||= Redis.new(url: 'redis://localhost')
183
+ end
184
+ end
185
+
186
+ # Let the gem know which strategy needs to be used
187
+ Mangadex.configuration.storage_class = BasicRedisStragery
188
+ ```
189
+
190
+ > On Rails, you can put this inside an initializer. Example: `config/initializers/mangadex.rb`.
191
+
192
+ The snippet of code is an example of how a storage strategy is implemented. It's important to make sure that neither `get` nor `set` raise exceptions.
193
+
194
+ > - We recommend using redis if you're developing a web app or a bot where authentication is involed.
195
+ > - You can even use a the filesystem if you're building a CLI (command line interface).
196
+ > - We **do not** recommend using SQL at the moment. This might be hard on your app's performance...
197
+
198
+ ### Can I opt-out?
199
+
200
+ Of course. Set `Mangadex::Storage::None` as the prefered strategy:
201
+
202
+ ```ruby
203
+ # Either
204
+ Mangadex.configure do |config|
205
+ config.storage_class = Mangadex::Storage::None
206
+ end
207
+
208
+ # Or
209
+ Mangadex.configuration.storage_class = Mangadex::Storage::None
210
+ ```
211
+
212
+ ## About content ratings
213
+
214
+ Each manga/chapter has a content rating (`safe`, `suggestive`, `erotica` and `pornographic`). It might be worth filtering certain titles depending on the audiance. By default, Mangadex filters out every `pornographic` entry.
215
+
216
+ Please note that content rating is not tied to the user at the moment on Mangadex. So it was decided **not** to add this responsiblity on this gem. Instead, the content ratings can be specified on context gem as well, like this:
217
+
218
+ ```ruby
219
+ # Everything but "suggestive" content - this is an example :p
220
+ mangas = Mangadex::Api::Content.allow_content_rating('safe', 'erotica', 'pornographic') do
221
+ response = Mangadex::Manga.list
222
+ response.data
223
+ end
224
+ ```
225
+
226
+ The advantage of this approach is that you don't have to set the `content_rating` param yourself everywhere.
data/docs/context.md ADDED
@@ -0,0 +1,93 @@
1
+ # Mangadex contexts
2
+
3
+ There is a concept of concepts in this gem. This is there for you to access certain variables at any point in your app.
4
+
5
+ ## User
6
+
7
+ ```ruby
8
+ Mangadex.context.user # => #<Mangadex::Api::User ...>
9
+ ```
10
+
11
+ This is set to `nil` before logging in.
12
+
13
+ When logging in, the user is stored in the context so that subsequent requests are set to be authenticated with this user.
14
+
15
+ ```ruby
16
+ Mangadex::Auth.login(...)
17
+ Mangadex.context.user.nil? # => false
18
+
19
+ custom_lists = Mangadex::CustomList.list
20
+ ```
21
+
22
+ If you're not logged in, `Mangadex::Errors::UnauthorizedError` will be raised for any request that requires you to be logged in and authorized to perform a certain account.
23
+
24
+ You can set the user in a temporary context:
25
+
26
+ ```ruby
27
+ Mangadex.context.user # => nil
28
+
29
+ temp_user = Mangadex::Api::User.new(mangadex_user_id: 'blabla')
30
+ Mangadex.context.with_user(temp_user) do
31
+ Mangadex.context.user # => #<Mangadex::Api::User mangadex_user_id="blabla">
32
+ end
33
+
34
+ Mangadex.context.user # => nil
35
+ ```
36
+
37
+ More info on authentication [here]().
38
+
39
+ ## Content rating
40
+
41
+ ```ruby
42
+ Mangadex.context.allowed_content_ratings # => [#<Mangadex::ContentRating ...>, ...]
43
+ ```
44
+
45
+ Content ratings are not tied to the user. When set, requests that accept a [`content_rating`](https://api.mangadex.org/docs.html#section/Static-data/Manga-content-rating) parameter, this parameter will be set to `Mangadex.context.allowed_content_ratings` if nothing is specified.
46
+
47
+ By default, `safe`, `suggestive` and `erotica` are used on Mangadex. But however, if you want to allow all content ratings, you could do something like:
48
+
49
+ ```ruby
50
+ Mangadex.context.allow_content_ratings('safe', 'suggestive', 'erotica', 'pornographic')
51
+ ```
52
+
53
+ Then, a query to fetch manga will make the following request:
54
+
55
+ ```ruby
56
+ Mangadex::Manga.list
57
+ # GET https://api.mangadex.org/manga?contentRating%5B%5D=safe&contentRating%5B%5D=suggestive&contentRating%5B%5D=erotica&contentRating%5B%5D=pornographic
58
+ ```
59
+
60
+ You can also use temporary content ratings:
61
+
62
+ ```ruby
63
+ # old content ratings
64
+ Mangadex.context.allow_content_ratings('safe', 'suggestive', 'erotica', 'pornographic') do
65
+ # temporary content ratings
66
+ Mangadex::Manga.list
67
+ end
68
+
69
+ # back to old content ratings
70
+ ```
71
+
72
+ ## Tags
73
+
74
+ Get the list of possible tags on Mangadex:
75
+
76
+ ```ruby
77
+ Mangadex.context.tags
78
+ ```
79
+
80
+ ### API version
81
+
82
+ Get the current Mangadex's latest API version
83
+
84
+ ```ruby
85
+ Mangadex.context.version
86
+ ```
87
+
88
+ A warning message will be printed if there's a mismatch between Mangadex's API version and the gem version. Example:
89
+
90
+ | Mangadex's API version | The gem's version | Result |
91
+ | ---------------------- | ----------------- | ------- |
92
+ | 5.3.3 | 5.3.3.1 | OK |
93
+ | 5.3.4 | 5.3.3.4 | Warning |
data/lib/config.rb ADDED
@@ -0,0 +1,50 @@
1
+ # typed: true
2
+
3
+ module Mangadex
4
+ class Config
5
+ extend T::Sig
6
+
7
+ # Class used to persist users
8
+ # Must respond to: :session, :refresh, :mangadex_user_id
9
+ sig { returns(Class) }
10
+ attr_accessor :user_class
11
+
12
+ # Persisting strategy. See Mangadex::Storage::Base for more details.
13
+ sig { returns(Class) }
14
+ attr_accessor :storage_class
15
+
16
+ sig { returns(T::Array[ContentRating]) }
17
+ attr_accessor :default_content_ratings
18
+
19
+ sig { void }
20
+ def initialize
21
+ @user_class = Api::User
22
+ @storage_class = Storage::Memory
23
+ @default_content_ratings = ContentRating.parse(['safe', 'suggestive', 'erotica'])
24
+ end
25
+
26
+ sig { params(klass: Class).void }
27
+ def user_class=(klass)
28
+ missing_methods = [:session, :refresh, :mangadex_user_id] - klass.instance_methods
29
+ if missing_methods.empty?
30
+ @user_class = klass
31
+ else
32
+ raise ArgumentError, 'user_class must respond to :session, :refresh, :mangadex_user_id'
33
+ end
34
+ end
35
+
36
+ sig { params(content_ratings: T::Array[T.any(String, ContentRating)]).void }
37
+ def default_content_ratings=(content_ratings)
38
+ @default_content_ratings = ContentRating.parse(content_ratings)
39
+ end
40
+
41
+ def storage_class=(klass)
42
+ @storage = nil
43
+ @storage_class = klass
44
+ end
45
+
46
+ def storage
47
+ @storage ||= storage_class.new
48
+ end
49
+ end
50
+ end
data/lib/errors.rb ADDED
@@ -0,0 +1,42 @@
1
+ # typed: true
2
+
3
+ module Mangadex
4
+ module Errors
5
+ # Standard error class for this gem.
6
+ #
7
+ # @author thedrummeraki
8
+ # @since 0.6.0
9
+ class StandardError < ::StandardError
10
+ extend T::Sig
11
+ end
12
+
13
+ class UserNotLoggedIn < StandardError
14
+ sig { returns(String) }
15
+ def message
16
+ "You are not logged in. Use [Mangadex::Auth.login] to log in."
17
+ end
18
+ end
19
+
20
+ class AuthenticationError < StandardError
21
+ sig { returns(Mangadex::Api::Response) }
22
+ attr_accessor :response
23
+
24
+ sig { params(response: Mangadex::Api::Response).void }
25
+ def initialize(response)
26
+ @response = response
27
+ end
28
+
29
+ sig { returns(String) }
30
+ def message
31
+ "Your username or password may not be correct."
32
+ end
33
+ end
34
+
35
+ class UnauthorizedError < AuthenticationError
36
+ sig { returns(String) }
37
+ def message
38
+ "Oops, you are not authorized to make this call. Make sure you log in with the right account."
39
+ end
40
+ end
41
+ end
42
+ end
@@ -3,12 +3,14 @@
3
3
  This is documentation for the `Mangadex` module.
4
4
 
5
5
  ### Directory
6
+
6
7
  #### Sub-modules
7
8
 
8
9
  - [`Mangadex::Api`](#)
9
10
  - [`Mangadex::Internal`](#)
10
11
 
11
12
  #### Fetchable/Resources
13
+
12
14
  - [`Mangadex::Artist`](#)
13
15
  - [`Mangadex::Auth`](#mangadexauth)
14
16
  - [`Mangadex::Author`](#)
@@ -25,6 +27,7 @@ This is documentation for the `Mangadex` module.
25
27
  - [`Mangadex::User`](#)
26
28
 
27
29
  #### Other classes
30
+
28
31
  - [`Mangadex::MangadexObject`](#)
29
32
  - [`Mangadex::Types`](#)
30
33
  - [`Mangadex::Version`](#)
@@ -51,7 +54,7 @@ Mangadex::Auth.login(username, password)
51
54
  ```
52
55
 
53
56
  Login with your username and password. Upon successful login, the user will be available in a context from
54
- `Mangadex::Api::Context.user`. This variable can be used anywhere in your application. More info [here](#).
57
+ `Mangadex.context.user`. This variable can be used anywhere in your application. More info [here](#).
55
58
 
56
59
  > - Returns `Mangadex::Api::Response` if request fails.
57
60
  > - Returns `true` if user is logged in.
@@ -86,8 +89,9 @@ Mangadex::Auth.refresh_token
86
89
  ```
87
90
 
88
91
  Manually cause a token refresh.
92
+
89
93
  > Please note that simply calling `Mangadex::Api::Content.user` ensures that the token is valid. More info [here](#).
90
94
 
91
95
  > - Returns `nil` if user is not logged (ie: `Mangadex::Api::Content.user` is `nil`)
92
96
  > - Returns `true` if the refresh is successful
93
- > - Returns `false` if the refresh is [not successful](#).
97
+ > - Returns `false` if the refresh is [not successful](#).
@@ -41,8 +41,11 @@ module Mangadex
41
41
  end
42
42
  end
43
43
 
44
- def errored?
45
- Array(errors).any?
44
+ def errored?(status=nil)
45
+ errored = Array(errors).any?
46
+ return errored if status.nil?
47
+
48
+ errors.select { |error| error.status.to_s == status.to_s }.any?
46
49
  end
47
50
 
48
51
  def as_json(*)
@@ -7,13 +7,13 @@ module Mangadex
7
7
  attr_accessor :mangadex_user_id, :session, :refresh, :session_valid_until
8
8
  attr_reader :data
9
9
 
10
- sig { params(mangadex_user_id: String, session: T.nilable(String), refresh: T.nilable(String), data: T.untyped).void }
11
- def initialize(mangadex_user_id, session: nil, refresh: nil, data: nil)
10
+ sig { params(mangadex_user_id: String, session: T.nilable(String), refresh: T.nilable(String), data: T.untyped, session_valid_until: T.nilable(Time)).void }
11
+ def initialize(mangadex_user_id:, session: nil, refresh: nil, data: nil, session_valid_until: nil)
12
12
  raise ArgumentError, 'Missing mangadex_user_id' if mangadex_user_id.to_s.empty?
13
13
 
14
14
  @mangadex_user_id = mangadex_user_id
15
15
  @session = session
16
- @session_valid_until = session ? Time.now + (14 * 60) : nil
16
+ @session_valid_until = session_valid_until ? session_valid_until : (session ? Time.now + (14 * 60) : nil)
17
17
  @refresh = refresh
18
18
  @data = data
19
19
  end
@@ -24,9 +24,7 @@ module Mangadex
24
24
  def refresh!
25
25
  return false if refresh.nil?
26
26
 
27
- response = Mangadex::Api::Context.without_user do
28
- Mangadex::Internal::Request.post('/auth/refresh', payload: { token: refresh })
29
- end
27
+ response = Mangadex::Internal::Request.post('/auth/refresh', payload: { token: refresh })
30
28
  return false unless response['token']
31
29
 
32
30
  @session_valid_until = Time.now + (14 * 60)
@@ -36,7 +34,7 @@ module Mangadex
36
34
  true
37
35
  end
38
36
 
39
- sig { returns(Mangadex::Api::User) }
37
+ sig { returns(User) }
40
38
  def with_valid_session
41
39
  session_expired? && refresh!
42
40
  self
@@ -48,6 +46,52 @@ module Mangadex
48
46
  def session_expired?
49
47
  @session_valid_until.nil? || @session_valid_until <= Time.now
50
48
  end
49
+
50
+ sig { returns(T::Boolean) }
51
+ def persist
52
+ return false unless valid?
53
+
54
+ Mangadex.storage.set(mangadex_user_id, 'session', session) if session
55
+ Mangadex.storage.set(mangadex_user_id, 'refresh', refresh) if refresh
56
+ if session_valid_until
57
+ Mangadex.storage.set(mangadex_user_id, 'session_valid_until', session_valid_until.to_s)
58
+ end
59
+
60
+ true
61
+ end
62
+
63
+ sig { returns(T::Boolean) }
64
+ def valid?
65
+ !mangadex_user_id.nil? && !mangadex_user_id.strip.empty?
66
+ end
67
+
68
+ sig { params(mangadex_user_id: T.nilable(String)).returns(T.nilable(User)) }
69
+ def self.from_storage(mangadex_user_id)
70
+ return if mangadex_user_id.nil?
71
+
72
+ session = Mangadex.storage.get(mangadex_user_id, 'session')
73
+ refresh = Mangadex.storage.get(mangadex_user_id, 'refresh')
74
+ session_valid_until = Mangadex.storage.get(mangadex_user_id, 'session_valid_until')
75
+
76
+ user = if session || refresh || session_valid_until
77
+ session_valid_until = session_valid_until ? Time.parse(session_valid_until) : nil
78
+
79
+ new(
80
+ mangadex_user_id: mangadex_user_id,
81
+ session: session,
82
+ refresh: refresh,
83
+ session_valid_until: session_valid_until,
84
+ ).with_valid_session
85
+ else
86
+ nil
87
+ end
88
+
89
+ if user
90
+ Mangadex.context.user = user
91
+ end
92
+
93
+ user
94
+ end
51
95
  end
52
96
  end
53
97
  end
data/lib/mangadex/api.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  # typed: strict
2
2
  require_relative "api/version_checker"
3
- require_relative "api/context"
4
3
  require_relative "api/response"
5
4
  require_relative "api/user"
data/lib/mangadex/auth.rb CHANGED
@@ -3,16 +3,20 @@ module Mangadex
3
3
  class Auth
4
4
  extend T::Sig
5
5
 
6
- sig { params(username: String, password: String).returns(T.any(T::Boolean, Mangadex::Api::Response)) }
7
- def self.login(username, password)
6
+ sig { params(username: T.nilable(String), email: T.nilable(String), password: String).returns(T.nilable(Mangadex::Api::User)) }
7
+ def self.login(username: nil, email: nil, password: nil)
8
+ args = { password: password }
9
+ args.merge!(email: email) if email
10
+ args.merge!(username: username) if username
11
+
8
12
  response = Mangadex::Internal::Request.post(
9
13
  '/auth/login',
10
- payload: {
11
- username: username,
12
- password: password,
13
- },
14
+ payload: Mangadex::Internal::Definition.validate(args, {
15
+ username: { accepts: String },
16
+ email: { accepts: String },
17
+ password: { accepts: String, required: true },
18
+ }),
14
19
  )
15
- return response if response.is_a?(Mangadex::Api::Response) && response.errored?
16
20
 
17
21
  session = response.dig('token', 'session')
18
22
  refresh = response.dig('token', 'refresh')
@@ -20,13 +24,19 @@ module Mangadex
20
24
  mangadex_user = Mangadex::Internal::Request.get('/user/me', headers: { Authorization: session })
21
25
 
22
26
  user = Mangadex::Api::User.new(
23
- mangadex_user.data.id,
27
+ mangadex_user_id: mangadex_user.data.id,
24
28
  session: session,
25
29
  refresh: refresh,
26
30
  data: mangadex_user.data,
27
31
  )
28
- Mangadex::Api::Context.user = user
29
- !user.session_expired?
32
+ return if user.session_expired?
33
+
34
+ Mangadex.context.user = user
35
+
36
+ user.persist
37
+ user
38
+ rescue Errors::UnauthorizedError => error
39
+ raise Errors::AuthenticationError.new(error.response)
30
40
  end
31
41
 
32
42
  sig { returns(Hash) }
@@ -41,20 +51,39 @@ module Mangadex
41
51
 
42
52
  sig { returns(T.any(T::Boolean, Mangadex::Api::Response)) }
43
53
  def self.logout
44
- return true if Mangadex::Api::Context.user.nil?
54
+ return true if Mangadex.context.user.nil?
45
55
 
46
56
  response = Mangadex::Internal::Request.post(
47
57
  '/auth/logout',
48
58
  )
49
59
  return reponse if response.is_a?(Mangadex::Api::Response) && response.errored?
50
60
 
51
- Mangadex::Api::Context.user = nil
61
+ clear_user
62
+ true
63
+ rescue Mangadex::Errors::UnauthorizedError
64
+ clear_user
52
65
  true
53
66
  end
54
67
 
55
68
  sig { returns(T::Boolean) }
56
69
  def self.refresh_token
57
- !(Mangadex::Api::Context.user&.refresh!).nil?
70
+ !(Mangadex.context.user&.refresh!).nil?
71
+ end
72
+
73
+ private
74
+
75
+ sig { void }
76
+ def self.clear_user
77
+ return if Mangadex.context.user.nil?
78
+
79
+ if Mangadex.context.user.respond_to?(:session=)
80
+ Mangadex.context.user.session = nil
81
+ end
82
+ if Mangadex.context.user.respond_to?(:refresh=)
83
+ Mangadex.context.user.refresh = nil
84
+ end
85
+ Mangadex.storage.clear(Mangadex.context.user.mangadex_user_id)
86
+ Mangadex.context.user = nil
58
87
  end
59
88
  end
60
89
  end
@@ -0,0 +1,141 @@
1
+ # typed: true
2
+ module Mangadex
3
+ module Internal
4
+ class Context
5
+ extend T::Sig
6
+
7
+ sig { returns(T::Array[Mangadex::ContentRating]) }
8
+ attr_accessor :allowed_content_ratings, :ignore_user
9
+
10
+ def initialize
11
+ @allowed_content_ratings = Mangadex.configuration.default_content_ratings
12
+ end
13
+
14
+ sig { returns(T.nilable(String)) }
15
+ def version
16
+ @version ||= Mangadex::Api::VersionChecker.check_mangadex_version
17
+ end
18
+
19
+ sig { returns(T.nilable(Mangadex::Api::User)) }
20
+ def user
21
+ @ignore_user ? nil : @user&.with_valid_session
22
+ rescue Mangadex::Errors::UnauthorizedError
23
+ warn("A user is present but not authenticated!")
24
+ nil
25
+ end
26
+
27
+ sig { returns(T::Array[Mangadex::Tag]) }
28
+ def tags
29
+ @tags ||= Mangadex::Tag.list.data
30
+ end
31
+
32
+ sig { params(user: T.nilable(T.any(Hash, Mangadex::Api::User, Mangadex::User)), block: T.proc.returns(T.untyped)).returns(T.untyped) }
33
+ def with_user(user, &block)
34
+ temp_set_value("user", user) do
35
+ yield
36
+ end
37
+ end
38
+
39
+ sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) }
40
+ def without_user(&block)
41
+ temp_set_value("ignore_user", true) do
42
+ yield
43
+ end
44
+ end
45
+
46
+ sig { params(user: T.nilable(T.untyped)).void }
47
+ def user=(user)
48
+ if user.is_a?(Mangadex::Api::User)
49
+ @user = user
50
+ elsif user.is_a?(Mangadex::User)
51
+ @user = Mangadex::Api::User.new(
52
+ mangadex_user_id: user.id,
53
+ data: user,
54
+ )
55
+ elsif user.is_a?(Hash)
56
+ user = Mangadex::Internal::Definition.validate(user, {
57
+ mangadex_user_id: { accepts: String, required: true },
58
+ session: { accepts: String },
59
+ refresh: { accepts: String },
60
+ })
61
+
62
+ @user = Mangadex::Api::User.new(
63
+ mangadex_user_id: user[:mangadex_user_id],
64
+ session: user[:session],
65
+ refresh: user[:refresh],
66
+ )
67
+ elsif user_object?(user)
68
+ @user = Mangadex::Api::User.new(
69
+ mangadex_user_id: user.mangadex_user_id.to_s,
70
+ session: user.session,
71
+ refresh: user.refresh,
72
+ data: user,
73
+ )
74
+ elsif user.nil?
75
+ @user = nil
76
+ else
77
+ raise TypeError, "Invalid user type."
78
+ end
79
+ end
80
+
81
+ def allow_content_ratings(*content_ratings, &block)
82
+ content_ratings = Mangadex::ContentRating.parse(Array(content_ratings))
83
+ if block_given?
84
+ content_ratings = Mangadex.context.allowed_content_ratings if content_ratings.empty?
85
+
86
+ # set temporarily
87
+ temp_set_value("allowed_content_ratings", content_ratings) do
88
+ yield
89
+ end
90
+ elsif content_ratings.any?
91
+ # set "permanently"
92
+ @allowed_content_ratings = content_ratings
93
+ else
94
+ # This is to throw an exception prompting to pass a block if there no params.
95
+ yield
96
+ end
97
+ end
98
+
99
+ def with_allowed_content_ratings(*other_content_ratings, &block)
100
+ T.unsafe(self).allow_content_ratings(*(allowed_content_ratings + other_content_ratings)) do
101
+ yield
102
+ end
103
+ end
104
+
105
+ # Only recommended for development and debugging only
106
+ def force_raw_requests(&block)
107
+ if block_given?
108
+ temp_set_value("force_raw_requests", true) do
109
+ yield
110
+ end
111
+ else
112
+ !!@force_raw_requests
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def user_object?(user)
119
+ return false if user.nil?
120
+
121
+ missing_methods = [:session, :refresh, :mangadex_user_id] - user.methods
122
+ return true if missing_methods.empty?
123
+
124
+ warn("Potential user object #{user} is missing #{missing_methods}")
125
+ false
126
+ end
127
+
128
+ def temp_set_value(name, value, &block)
129
+ setter_method_name = "#{name}="
130
+
131
+ current_value = send(name)
132
+ send(setter_method_name, value)
133
+ response = yield
134
+ send(setter_method_name, current_value)
135
+ response
136
+ ensure
137
+ send(setter_method_name, current_value) if current_value
138
+ end
139
+ end
140
+ end
141
+ end
@@ -54,7 +54,7 @@ module Mangadex
54
54
  payload_details = request_payload ? "Payload: #{request_payload}" : "{no-payload}"
55
55
  puts("[#{self.class.name}] #{method.to_s.upcase} #{request_url} #{payload_details}")
56
56
 
57
- raise Mangadex::UserNotLoggedIn.new if auth && Mangadex::Api::Context.user.nil?
57
+ raise Mangadex::Errors::UserNotLoggedIn.new if auth && Mangadex.context.user.nil?
58
58
 
59
59
  start_time = Time.now
60
60
 
@@ -63,11 +63,13 @@ module Mangadex
63
63
  elapsed_time = ((end_time - start_time) * 1000).to_i
64
64
  puts("[#{self.class.name}] took #{elapsed_time} ms")
65
65
 
66
- raw_request = raw || Mangadex::Api::Context.force_raw_requests
66
+ raw_request = raw || Mangadex.context.force_raw_requests
67
67
 
68
68
  if (body = @response.body)
69
69
  raw_request ? try_json(body) : Mangadex::Api::Response.coerce(try_json(body))
70
70
  end
71
+ rescue RestClient::Unauthorized => error
72
+ raise Errors::UnauthorizedError.new(Mangadex::Api::Response.coerce(try_json(error.response.body)))
71
73
  rescue RestClient::Exception => error
72
74
  if (body = error.response.body)
73
75
  raw_request ? try_json(body) : Mangadex::Api::Response.coerce(JSON.parse(body)) rescue raise error
@@ -90,8 +92,8 @@ module Mangadex
90
92
 
91
93
  def self.with_content_rating(data)
92
94
  content_rating = data.has_key?(:content_rating) ? data[:content_rating] : []
93
- Mangadex::Api::Context.allow_content_ratings(*content_rating) do
94
- data[:content_rating] = Mangadex::Api::Context.allowed_content_ratings
95
+ Mangadex.context.allow_content_ratings(*content_rating) do
96
+ data[:content_rating] = Mangadex.context.allowed_content_ratings
95
97
  end
96
98
  data
97
99
  end
@@ -108,10 +110,10 @@ module Mangadex
108
110
  end
109
111
 
110
112
  def request_headers
111
- return headers if Mangadex::Api::Context.user.nil?
113
+ return headers if Mangadex.context.user.nil?
112
114
 
113
115
  headers.merge({
114
- Authorization: Mangadex::Api::Context.user.with_valid_session.session,
116
+ Authorization: Mangadex.context.user.with_valid_session.session,
115
117
  })
116
118
  end
117
119
 
@@ -1,3 +1,4 @@
1
1
  # typed: strict
2
+ require_relative "internal/context"
2
3
  require_relative "internal/request"
3
4
  require_relative "internal/definition"
@@ -123,14 +123,16 @@ module Mangadex
123
123
  )
124
124
  end
125
125
 
126
- sig { params(args: T::Api::Arguments).returns(T::Api::GenericResponse) }
127
- def self.all_reading_status(**args)
126
+ sig { params(status: String).returns(T::Api::GenericResponse) }
127
+ def self.all_reading_status(status)
128
+ args = { status: status }
129
+
128
130
  Mangadex::Internal::Request.get(
129
131
  '/manga/status',
130
132
  Mangadex::Internal::Definition.validate(args, {
131
133
  status: {
132
134
  accepts: %w(reading on_hold dropped plan_to_read re_reading completed),
133
- converts: Mangadex::Internal::Definition.converts(:to_a),
135
+ converts: :to_s,
134
136
  },
135
137
  })
136
138
  )
@@ -0,0 +1,18 @@
1
+ module Mangadex
2
+ module Storage
3
+ class Basic
4
+ def get(_scope, _key)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def set(_scope, _key, _value)
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def clear(_scope)
13
+ warn("Don't know how to clear #{self.class} storage strategy! Skipping...")
14
+ nil
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Mangadex
2
+ module Storage
3
+ class Memory < BasicObject
4
+ def initialize
5
+ @storage = {}
6
+ end
7
+
8
+ def get(scope, key)
9
+ @storage.dig(scope.to_s, key.to_s)
10
+ end
11
+
12
+ def set(scope, key, value)
13
+ key = key.to_s
14
+ @storage[scope] = {} unless @storage.has_key?(scope)
15
+ @storage[scope][key] = value
16
+ end
17
+
18
+ def clear(scope)
19
+ @storage.delete(scope)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module Mangadex
2
+ module Storage
3
+ class None < Basic
4
+ def get(_scope, _key); end
5
+ def set(_scope, _key, _value); end
6
+ def clear(_scope); end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ require_relative "storage/basic"
2
+ require_relative "storage/none"
3
+ require_relative "storage/memory"
data/lib/mangadex/user.rb CHANGED
@@ -60,7 +60,7 @@ module Mangadex
60
60
  def self.follows_user(id)
61
61
  Mangadex::Internal::Definition.must(id)
62
62
 
63
- return if Mangadex::Api::Context.user.nil?
63
+ return if Mangadex.context.user.nil?
64
64
 
65
65
  data = Mangadex::Internal::Request.get(
66
66
  '/user/follows/user/%{id}' % {id: id},
@@ -90,7 +90,7 @@ module Mangadex
90
90
  def self.follows_manga(id)
91
91
  Mangadex::Internal::Definition.must(id)
92
92
 
93
- return if Mangadex::Api::Context.user.nil?
93
+ return if Mangadex.context.user.nil?
94
94
 
95
95
  data = Mangadex::Internal::Request.get(
96
96
  '/user/follows/manga/%{id}' % {id: id},
@@ -4,7 +4,7 @@ module Mangadex
4
4
  MAJOR = "5"
5
5
  MINOR = "3"
6
6
  TINY = "3"
7
- PATCH = "1"
7
+ PATCH = "2"
8
8
 
9
9
  STRING = [MAJOR, MINOR, TINY].compact.join('.')
10
10
  FULL = [MAJOR, MINOR, TINY, PATCH].compact.join('.')
data/lib/mangadex.rb CHANGED
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: true
2
2
  require 'sorbet-runtime'
3
3
 
4
4
  require 'active_support'
@@ -14,22 +14,35 @@ require "mangadex/types"
14
14
  # API, to interact with Mangadex
15
15
  require "mangadex/api"
16
16
 
17
+ # Persist strategies
18
+ require "mangadex/storage"
19
+
20
+ require_relative "config"
21
+ require_relative "errors"
22
+
17
23
  # Namespace for classes and modules for this gem.
18
24
  # @since 5.3.0
19
25
 
20
26
  module Mangadex
21
- # Standard error class for this gem.
22
- #
23
- # @author thedrummeraki
24
- # @since 0.6.0
25
- class Error < StandardError
26
- extend T::Sig
27
- end
27
+ class << self
28
+ def configuration
29
+ @configuration ||= Config.new
30
+ end
31
+
32
+ def context
33
+ @context ||= Internal::Context.new
34
+ end
35
+
36
+ def configure(&block)
37
+ yield(configuration)
38
+ end
39
+
40
+ def storage
41
+ configuration.storage
42
+ end
28
43
 
29
- class UserNotLoggedIn < Error
30
- sig { returns(String) }
31
- def message
32
- "You are not logged in. Use [Mangadex::Auth.login] to log in."
44
+ def api_version
45
+ context.version
33
46
  end
34
47
  end
35
48
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mangadex
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.3.1
4
+ version: 5.3.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akinyele Cafe-Febrissy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-27 00:00:00.000000000 Z
11
+ date: 2021-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: psych
@@ -170,11 +170,14 @@ files:
170
170
  - Rakefile
171
171
  - bin/console
172
172
  - bin/setup
173
+ - docs/authentication.md
174
+ - docs/context.md
175
+ - lib/config.rb
176
+ - lib/errors.rb
173
177
  - lib/extensions.rb
174
178
  - lib/mangadex.rb
175
179
  - lib/mangadex/README.md
176
180
  - lib/mangadex/api.rb
177
- - lib/mangadex/api/context.rb
178
181
  - lib/mangadex/api/response.rb
179
182
  - lib/mangadex/api/user.rb
180
183
  - lib/mangadex/api/version_checker.rb
@@ -186,6 +189,7 @@ files:
186
189
  - lib/mangadex/cover_art.rb
187
190
  - lib/mangadex/custom_list.rb
188
191
  - lib/mangadex/internal.rb
192
+ - lib/mangadex/internal/context.rb
189
193
  - lib/mangadex/internal/definition.rb
190
194
  - lib/mangadex/internal/request.rb
191
195
  - lib/mangadex/internal/with_attributes.rb
@@ -195,6 +199,10 @@ files:
195
199
  - lib/mangadex/report_reason.rb
196
200
  - lib/mangadex/scanlation_group.rb
197
201
  - lib/mangadex/sorbet.rb
202
+ - lib/mangadex/storage.rb
203
+ - lib/mangadex/storage/basic.rb
204
+ - lib/mangadex/storage/memory.rb
205
+ - lib/mangadex/storage/none.rb
198
206
  - lib/mangadex/tag.rb
199
207
  - lib/mangadex/types.rb
200
208
  - lib/mangadex/upload.rb
@@ -1,137 +0,0 @@
1
- # typed: true
2
- module Mangadex
3
- module Api
4
- class Context
5
- extend T::Sig
6
-
7
- DEFAULT_MANGADEX_CONTENT_RATING_VALUES = [
8
- ContentRating::SAFE,
9
- ContentRating::SUGGESTIVE,
10
- ContentRating::EROTICA,
11
- ].freeze
12
-
13
- @@user = nil
14
- @@version = nil
15
- @@force_raw_requests = nil
16
- @@tags = nil
17
- @@allowed_content_ratings = DEFAULT_MANGADEX_CONTENT_RATING_VALUES
18
-
19
- sig { returns(T.nilable(String)) }
20
- def self.version
21
- return @@version unless @@version.nil?
22
-
23
- @@version = Mangadex::Api::VersionChecker.check_mangadex_version
24
- end
25
-
26
- sig { returns(T.nilable(Mangadex::Api::User)) }
27
- def self.user
28
- @@user&.with_valid_session
29
- end
30
-
31
- sig { returns(T::Array[Mangadex::Tag]) }
32
- def self.tags
33
- return @@tags if @@tags
34
-
35
- @@tags = Mangadex::Tag.list.data
36
- end
37
-
38
- sig { returns(T::Array[Mangadex::ContentRating]) }
39
- def self.allowed_content_ratings
40
- @@allowed_content_ratings.map { |value| ContentRating.new(value) }
41
- end
42
-
43
- sig { params(user: T.nilable(T.any(Hash, Mangadex::Api::User, Mangadex::User))).void }
44
- def self.user=(user)
45
- if user.is_a?(Mangadex::Api::User)
46
- @@user = user
47
- elsif user.is_a?(Mangadex::User)
48
- @@user = Mangadex::Api::User.new(
49
- user.id,
50
- data: user,
51
- )
52
- elsif user.is_a?(Hash)
53
- user = Mangadex::Internal::Definition.validate(user, {
54
- mangadex_user_id: { accepts: String, required: true },
55
- session: { accepts: String },
56
- refresh: { accepts: String },
57
- })
58
-
59
- @@user = Mangadex::Api::User.new(
60
- user[:mangadex_user_id],
61
- session: user[:session],
62
- refresh: user[:refresh],
63
- )
64
- elsif user.nil?
65
- @@user = nil
66
- end
67
- end
68
-
69
- sig { params(user: T.nilable(T.any(Hash, Mangadex::Api::User, Mangadex::User)), block: T.proc.returns(T.untyped)).returns(T.untyped) }
70
- def self.with_user(user, &block)
71
- temp_set_value("user", user) do
72
- yield
73
- end
74
- end
75
-
76
- sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) }
77
- def self.without_user(&block)
78
- with_user(nil) do
79
- yield
80
- end
81
- end
82
-
83
- def self.force_raw_requests(&block)
84
- if block_given?
85
- temp_set_value("force_raw_requests", true) do
86
- yield
87
- end
88
- else
89
- !!@@force_raw_requests
90
- end
91
- end
92
-
93
- def self.force_raw_requests=(value)
94
- @@force_raw_requests = value
95
- end
96
-
97
- def self.allow_content_ratings(*content_ratings, &block)
98
- content_ratings = if content_ratings.empty?
99
- allowed_content_ratings
100
- else
101
- Mangadex::ContentRating.parse(content_ratings)
102
- end
103
- if block_given?
104
- # set temporarily
105
- temp_set_value("allowed_content_ratings", content_ratings) do
106
- yield
107
- end
108
- elsif content_ratings.any?
109
- # set "permanently"
110
- @@allowed_content_ratings = content_ratings
111
- else
112
- # This is to throw an exception prompting to pass a block if there no params.
113
- yield
114
- end
115
- end
116
-
117
- def self.with_allowed_content_ratings(*other_content_ratings, &block)
118
- T.unsafe(self).allow_content_ratings(*(allowed_content_ratings + other_content_ratings)) do
119
- yield
120
- end
121
- end
122
-
123
- private
124
-
125
- def self.temp_set_value(name, value, &block)
126
- var_name = "@@#{name}"
127
- current_value = class_variable_get(var_name)
128
- class_variable_set(var_name, value)
129
- response = yield
130
- class_variable_set(var_name, current_value)
131
- response
132
- ensure
133
- class_variable_set(var_name, current_value) if current_value
134
- end
135
- end
136
- end
137
- end