mangadex 5.3.3.1 → 5.3.3.2

Sign up to get free protection for your applications and to get access to all the features.
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