jikanrb 0.1.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.
- checksums.yaml +7 -0
- data/.claude/settings.local.json +8 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +202 -0
- data/Rakefile +12 -0
- data/lib/jikanrb/client.rb +308 -0
- data/lib/jikanrb/configuration.rb +41 -0
- data/lib/jikanrb/errors.rb +48 -0
- data/lib/jikanrb/pagination.rb +137 -0
- data/lib/jikanrb/utils.rb +85 -0
- data/lib/jikanrb/version.rb +5 -0
- data/lib/jikanrb.rb +144 -0
- data/sig/jikanrb.rbs +4 -0
- metadata +105 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 322781c4fbc3373799633c17b00a1db8d7c0862e123d0cbade697542c30efbb0
|
|
4
|
+
data.tar.gz: e63b3b3bccd110e0a234beab9304c5eac563163b8885aa8bf91d403fd212fdaf
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9ed79ea3b2baa1f643d3e4369a34afad14da572ed36ba9dd5cc2176691f194e305dadecd5e7a40a663e8d32c9d0630eb6c4ff1e7c1fc288280a16bf8c4389c38
|
|
7
|
+
data.tar.gz: ecfc01bce3b8f605d221508b9a803fa1f0febd5719cef1c7d4c51c040e586dca194d4462792fc85459584371f1ac342b06852f774adcfbd80832a74cceca5fca
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.1.0
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"jikanrb" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["sergiobrocos@gmail.com"](mailto:"sergiobrocos@gmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergio Brocos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Jikanrb
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/jikanrb)
|
|
4
|
+
[](https://github.com/tuusuario/jikanrb/actions)
|
|
5
|
+
|
|
6
|
+
A modern Ruby client for the [Jikan API v4](https://jikan.moe/) - the unofficial MyAnimeList API.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem 'jikanrb'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
And then execute:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install it yourself:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
gem install jikanrb
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Quick Start
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require 'jikanrb'
|
|
34
|
+
|
|
35
|
+
# Get anime by ID
|
|
36
|
+
anime = Jikanrb.anime(1)
|
|
37
|
+
|
|
38
|
+
# Access data using strings or symbols (Indifferent Access)
|
|
39
|
+
puts anime["data"]["title"] # => "Cowboy Bebop"
|
|
40
|
+
puts anime[:data][:title] # => "Cowboy Bebop"
|
|
41
|
+
|
|
42
|
+
# Get full anime info (includes relations, theme songs, etc.)
|
|
43
|
+
anime = Jikanrb.anime(1, full: true)
|
|
44
|
+
|
|
45
|
+
# Search anime
|
|
46
|
+
results = Jikanrb.search_anime("Naruto")
|
|
47
|
+
results[:data].each do |anime|
|
|
48
|
+
puts "#{anime[:title]} - Score: #{anime[:score]}"
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Using a Client Instance
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
client = Jikanrb::Client.new do |config|
|
|
56
|
+
config.read_timeout = 15
|
|
57
|
+
config.max_retries = 5
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# All methods available
|
|
61
|
+
client.anime(1)
|
|
62
|
+
client.manga(1)
|
|
63
|
+
client.character(1)
|
|
64
|
+
client.person(1)
|
|
65
|
+
client.search_anime("One Piece", type: "tv", status: "airing")
|
|
66
|
+
client.top_anime(type: "tv", filter: "bypopularity")
|
|
67
|
+
client.season(2024, "winter")
|
|
68
|
+
client.season_now
|
|
69
|
+
client.schedules(day: "monday")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Configuration in Rails
|
|
73
|
+
|
|
74
|
+
You can configure the gem globally in an initializer (e.g., `config/initializers/jikanrb.rb`):
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
# config/initializers/jikanrb.rb
|
|
78
|
+
Jikanrb.configure do |config|
|
|
79
|
+
config.read_timeout = 20
|
|
80
|
+
config.max_retries = 3
|
|
81
|
+
# config.logger = Rails.logger # Use Rails logger
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Global Configuration
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
Jikanrb.configure do |config|
|
|
89
|
+
config.base_url = "https://api.jikan.moe/v4" # Default
|
|
90
|
+
config.open_timeout = 5 # Connection timeout (seconds)
|
|
91
|
+
config.read_timeout = 10 # Read timeout (seconds)
|
|
92
|
+
config.max_retries = 3 # Retry on rate limit/server errors
|
|
93
|
+
config.retry_interval = 1 # Initial retry delay (seconds)
|
|
94
|
+
config.logger = Logger.new($stdout) # Optional logging
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Available Methods
|
|
99
|
+
|
|
100
|
+
| Method | Description |
|
|
101
|
+
| :--- | :--- |
|
|
102
|
+
| `anime(id, full: false)` | Get anime by MAL ID |
|
|
103
|
+
| `manga(id, full: false)` | Get manga by MAL ID |
|
|
104
|
+
| `character(id, full: false)` | Get character by MAL ID |
|
|
105
|
+
| `person(id, full: false)` | Get person by MAL ID |
|
|
106
|
+
| `search_anime(query, **params)` | Search anime |
|
|
107
|
+
| `search_manga(query, **params)` | Search manga |
|
|
108
|
+
| `top_anime(type:, filter:, page:)` | Top anime list |
|
|
109
|
+
| `top_manga(type:, filter:, page:)` | Top manga list |
|
|
110
|
+
| `season(year, season, page:)` | Anime by season |
|
|
111
|
+
| `season_now(page:)` | Current season anime |
|
|
112
|
+
| `schedules(day:)` | Weekly schedule |
|
|
113
|
+
|
|
114
|
+
### Error Handling
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
begin
|
|
118
|
+
anime = Jikanrb.anime(999999999)
|
|
119
|
+
rescue Jikanrb::NotFoundError => e
|
|
120
|
+
puts "Anime not found: #{e.message}"
|
|
121
|
+
rescue Jikanrb::RateLimitError => e
|
|
122
|
+
puts "Rate limited! Retry after #{e.retry_after} seconds"
|
|
123
|
+
rescue Jikanrb::ConnectionError => e
|
|
124
|
+
puts "Connection failed: #{e.message}"
|
|
125
|
+
rescue Jikanrb::Error => e
|
|
126
|
+
puts "Something went wrong: #{e.message}"
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Pagination
|
|
131
|
+
|
|
132
|
+
The gem provides convenient pagination helpers for working with paginated endpoints:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# Automatic pagination - iterates through all pages
|
|
136
|
+
client = Jikanrb::Client.new
|
|
137
|
+
paginator = client.paginate(:top_anime, type: 'tv')
|
|
138
|
+
|
|
139
|
+
# Get all items (will fetch all pages)
|
|
140
|
+
all_anime = paginator.all
|
|
141
|
+
|
|
142
|
+
# Iterate through all pages lazily
|
|
143
|
+
paginator.each do |anime|
|
|
144
|
+
puts "#{anime['title']} - Score: #{anime['score']}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Get items from first 3 pages only
|
|
148
|
+
first_three_pages = paginator.take_pages(3)
|
|
149
|
+
|
|
150
|
+
# Manual pagination with pagination info
|
|
151
|
+
response = client.top_anime(page: 1)
|
|
152
|
+
pagination = client.pagination_info(response)
|
|
153
|
+
|
|
154
|
+
puts "Current page: #{pagination.current_page}"
|
|
155
|
+
puts "Total pages: #{pagination.total_pages}"
|
|
156
|
+
puts "Items per page: #{pagination.per_page}"
|
|
157
|
+
puts "Has next page: #{pagination.has_next_page?}"
|
|
158
|
+
puts "Has previous page: #{pagination.has_previous_page?}"
|
|
159
|
+
puts "Next page number: #{pagination.next_page}" if pagination.has_next_page?
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Rate Limiting
|
|
163
|
+
|
|
164
|
+
Jikan API allows **60 requests per minute**. This gem includes automatic retry with exponential backoff for rate limit errors (429).
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
169
|
+
|
|
170
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
171
|
+
|
|
172
|
+
### Useful Commands
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Install dependencies
|
|
176
|
+
bin/setup
|
|
177
|
+
|
|
178
|
+
# Run tests
|
|
179
|
+
bundle exec rspec
|
|
180
|
+
|
|
181
|
+
# Interactive console
|
|
182
|
+
bin/console
|
|
183
|
+
|
|
184
|
+
# Linting
|
|
185
|
+
bundle exec rubocop
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Acknowledgments
|
|
189
|
+
|
|
190
|
+
This gem is inspired by [jikan.rb](https://github.com/Zerocchi/jikan.rb) by Zerocchi, which wrapped the Jikan API v3.
|
|
191
|
+
|
|
192
|
+
## Contributing
|
|
193
|
+
|
|
194
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/[USERNAME]/jikanrb>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/jikanrb/blob/main/CODE_OF_CONDUCT.md).
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
199
|
+
|
|
200
|
+
## Code of Conduct
|
|
201
|
+
|
|
202
|
+
Everyone interacting in the Jikanrb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/jikanrb/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'faraday/retry'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module Jikanrb
|
|
8
|
+
# Main HTTP client for interacting with the Jikan API v4.
|
|
9
|
+
# Handles requests, rate limiting, retries, and error handling.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# client = Jikanrb::Client.new
|
|
13
|
+
# anime = client.anime(1)
|
|
14
|
+
#
|
|
15
|
+
# @example With custom configuration
|
|
16
|
+
# client = Jikanrb::Client.new do |config|
|
|
17
|
+
# config.read_timeout = 15
|
|
18
|
+
# config.max_retries = 5
|
|
19
|
+
# end
|
|
20
|
+
class Client
|
|
21
|
+
attr_reader :config
|
|
22
|
+
|
|
23
|
+
# Initializes the client with optional configuration
|
|
24
|
+
#
|
|
25
|
+
# @example With default configuration
|
|
26
|
+
# client = Jikanrb::Client.new
|
|
27
|
+
#
|
|
28
|
+
# @example With custom configuration
|
|
29
|
+
# client = Jikanrb::Client.new do |config|
|
|
30
|
+
# config.base_url = "https://api.jikan.moe/v4"
|
|
31
|
+
# config.read_timeout = 15
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @yield [config] Optional block to configure the client
|
|
35
|
+
# @yieldparam config [Configuration] Configuration object
|
|
36
|
+
def initialize
|
|
37
|
+
@config = Configuration.new
|
|
38
|
+
yield(@config) if block_given?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Performs a GET request
|
|
42
|
+
#
|
|
43
|
+
# @param path [String] Endpoint path (e.g., "/anime/1")
|
|
44
|
+
# @param params [Hash] Query string parameters
|
|
45
|
+
# @return [Hash] Response parsed as Hash
|
|
46
|
+
def get(path, params = {})
|
|
47
|
+
request(:get, path, params)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Anime information by ID
|
|
51
|
+
#
|
|
52
|
+
# @param id [Integer] Anime ID on MyAnimeList
|
|
53
|
+
# @param full [Boolean] If true, returns extended information
|
|
54
|
+
# @return [Hash] Anime data
|
|
55
|
+
def anime(id, full: false)
|
|
56
|
+
path = full ? "/anime/#{id}/full" : "/anime/#{id}"
|
|
57
|
+
get(path)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Manga information by ID
|
|
61
|
+
#
|
|
62
|
+
# @param id [Integer] Manga ID on MyAnimeList
|
|
63
|
+
# @param full [Boolean] If true, returns extended information
|
|
64
|
+
# @return [Hash] Manga data
|
|
65
|
+
def manga(id, full: false)
|
|
66
|
+
path = full ? "/manga/#{id}/full" : "/manga/#{id}"
|
|
67
|
+
get(path)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Character information by ID
|
|
71
|
+
#
|
|
72
|
+
# @param id [Integer] Character ID on MyAnimeList
|
|
73
|
+
# @param full [Boolean] If true, returns extended information
|
|
74
|
+
# @return [Hash] Character data
|
|
75
|
+
def character(id, full: false)
|
|
76
|
+
path = full ? "/characters/#{id}/full" : "/characters/#{id}"
|
|
77
|
+
get(path)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Person information by ID
|
|
81
|
+
#
|
|
82
|
+
# @param id [Integer] Person ID on MyAnimeList
|
|
83
|
+
# @param full [Boolean] If true, returns extended information
|
|
84
|
+
# @return [Hash] Person data
|
|
85
|
+
def person(id, full: false)
|
|
86
|
+
path = full ? "/people/#{id}/full" : "/people/#{id}"
|
|
87
|
+
get(path)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Search anime
|
|
91
|
+
#
|
|
92
|
+
# @param query [String] Search term
|
|
93
|
+
# @param params [Hash] Additional filters (type, score, status, etc.)
|
|
94
|
+
# @return [Hash] Search results
|
|
95
|
+
def search_anime(query, **params)
|
|
96
|
+
get('/anime', params.merge(q: query))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Search manga
|
|
100
|
+
#
|
|
101
|
+
# @param query [String] Search term
|
|
102
|
+
# @param params [Hash] Additional filters
|
|
103
|
+
# @return [Hash] Search results
|
|
104
|
+
def search_manga(query, **params)
|
|
105
|
+
get('/manga', params.merge(q: query))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Top anime
|
|
109
|
+
#
|
|
110
|
+
# @param type [String, nil] Filter: "tv", "movie", "ova", etc.
|
|
111
|
+
# @param filter [String, nil] Filter: "airing", "upcoming", "bypopularity", etc.
|
|
112
|
+
# @param page [Integer] Page number
|
|
113
|
+
# @return [Hash] List of top anime
|
|
114
|
+
def top_anime(type: nil, filter: nil, page: 1)
|
|
115
|
+
params = { page: page }
|
|
116
|
+
params[:type] = type if type
|
|
117
|
+
params[:filter] = filter if filter
|
|
118
|
+
get('/top/anime', params)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Top manga
|
|
122
|
+
#
|
|
123
|
+
# @param type [String, nil] Filter: "manga", "novel", "lightnovel", etc.
|
|
124
|
+
# @param filter [String, nil] Filter: "publishing", "upcoming", "bypopularity", etc.
|
|
125
|
+
# @param page [Integer] Page number
|
|
126
|
+
# @return [Hash] List of top manga
|
|
127
|
+
def top_manga(type: nil, filter: nil, page: 1)
|
|
128
|
+
params = { page: page }
|
|
129
|
+
params[:type] = type if type
|
|
130
|
+
params[:filter] = filter if filter
|
|
131
|
+
get('/top/manga', params)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Seasonal anime
|
|
135
|
+
#
|
|
136
|
+
# @param year [Integer] Year
|
|
137
|
+
# @param season [String] Season: "winter", "spring", "summer", "fall"
|
|
138
|
+
# @param page [Integer] Page number
|
|
139
|
+
# @return [Hash] Seasonal anime
|
|
140
|
+
def season(year, season, page: 1)
|
|
141
|
+
get("/seasons/#{year}/#{season}", page: page)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Current season
|
|
145
|
+
#
|
|
146
|
+
# @param page [Integer] Page number
|
|
147
|
+
# @return [Hash] Current season anime
|
|
148
|
+
def season_now(page: 1)
|
|
149
|
+
get('/seasons/now', page: page)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Weekly schedule
|
|
153
|
+
#
|
|
154
|
+
# @param day [String, nil] Day: "monday", "tuesday", etc.
|
|
155
|
+
# @return [Hash] Anime schedule
|
|
156
|
+
def schedules(day: nil)
|
|
157
|
+
path = day ? "/schedules/#{day}" : '/schedules'
|
|
158
|
+
get(path)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Create a paginator for iterating through all pages of a paginated endpoint
|
|
162
|
+
#
|
|
163
|
+
# @param method [Symbol] Method name to paginate (e.g., :top_anime, :search_anime)
|
|
164
|
+
# @param params [Hash] Parameters to pass to the method
|
|
165
|
+
# @return [Pagination::Paginator] Paginator instance
|
|
166
|
+
#
|
|
167
|
+
# @example Iterate through all top anime
|
|
168
|
+
# client.paginate(:top_anime, type: 'tv').each do |anime|
|
|
169
|
+
# puts anime['title']
|
|
170
|
+
# end
|
|
171
|
+
#
|
|
172
|
+
# @example Get all items as array
|
|
173
|
+
# all_anime = client.paginate(:search_anime, 'Naruto').all
|
|
174
|
+
#
|
|
175
|
+
# @example Get first 3 pages only
|
|
176
|
+
# anime = client.paginate(:top_anime).take_pages(3)
|
|
177
|
+
def paginate(method, **params)
|
|
178
|
+
Pagination::Paginator.new(self, method, **params)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Extract pagination information from a response
|
|
182
|
+
#
|
|
183
|
+
# @param response [Hash] API response with pagination data
|
|
184
|
+
# @return [Pagination::PaginationInfo] Pagination information
|
|
185
|
+
#
|
|
186
|
+
# @example
|
|
187
|
+
# result = client.top_anime(page: 1)
|
|
188
|
+
# pagination = client.pagination_info(result)
|
|
189
|
+
# puts "Page #{pagination.current_page} of #{pagination.total_pages}"
|
|
190
|
+
def pagination_info(response)
|
|
191
|
+
Pagination::PaginationInfo.new(response)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
# Faraday connection with configuration
|
|
197
|
+
def connection
|
|
198
|
+
@connection ||= Faraday.new(url: config.base_url) do |f|
|
|
199
|
+
configure_connection_options(f)
|
|
200
|
+
configure_connection_headers(f)
|
|
201
|
+
configure_connection_middleware(f)
|
|
202
|
+
f.adapter Faraday.default_adapter
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Configures connection timeouts
|
|
207
|
+
def configure_connection_options(faraday)
|
|
208
|
+
faraday.options.open_timeout = config.open_timeout
|
|
209
|
+
faraday.options.timeout = config.read_timeout
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Configures connection headers
|
|
213
|
+
def configure_connection_headers(faraday)
|
|
214
|
+
faraday.headers['User-Agent'] = config.user_agent
|
|
215
|
+
faraday.headers['Accept'] = 'application/json'
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Configures retry middleware and logger
|
|
219
|
+
def configure_connection_middleware(faraday)
|
|
220
|
+
configure_retry_middleware(faraday)
|
|
221
|
+
faraday.response :logger, config.logger if config.logger
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Configures retry middleware for rate limiting and transient errors
|
|
225
|
+
def configure_retry_middleware(faraday)
|
|
226
|
+
faraday.request :retry,
|
|
227
|
+
max: config.max_retries,
|
|
228
|
+
interval: config.retry_interval,
|
|
229
|
+
interval_randomness: 0.5,
|
|
230
|
+
backoff_factor: 2,
|
|
231
|
+
retry_statuses: [429, 500, 502, 503, 504],
|
|
232
|
+
retry_if: ->(env, _exception) { env.status == 429 }
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Executes the HTTP request and handles errors
|
|
236
|
+
def request(method, path, params = {})
|
|
237
|
+
response = connection.public_send(method, path, params)
|
|
238
|
+
handle_response(response)
|
|
239
|
+
rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
|
|
240
|
+
raise ConnectionError, "Connection failed: #{e.message}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Processes the HTTP response
|
|
244
|
+
def handle_response(response)
|
|
245
|
+
return parse_json(response.body) if response.status.between?(200, 299)
|
|
246
|
+
|
|
247
|
+
handle_error_response(response)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Handles error responses based on status code
|
|
251
|
+
def handle_error_response(response)
|
|
252
|
+
case response.status
|
|
253
|
+
when 400 then raise_bad_request_error(response)
|
|
254
|
+
when 404 then raise_not_found_error(response)
|
|
255
|
+
when 405 then raise_method_not_allowed_error(response)
|
|
256
|
+
when 429 then raise_rate_limit_error(response)
|
|
257
|
+
when 500..599 then raise_server_error(response)
|
|
258
|
+
else raise_unexpected_error(response)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Raises a BadRequestError
|
|
263
|
+
def raise_bad_request_error(response)
|
|
264
|
+
raise BadRequestError.new('Bad request', response: response, status: 400)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Raises a NotFoundError
|
|
268
|
+
def raise_not_found_error(response)
|
|
269
|
+
raise NotFoundError.new('Resource not found', response: response, status: 404)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Raises a MethodNotAllowedError
|
|
273
|
+
def raise_method_not_allowed_error(response)
|
|
274
|
+
raise MethodNotAllowedError.new('Method not allowed', response: response, status: 405)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Raises a RateLimitError with retry information
|
|
278
|
+
def raise_rate_limit_error(response)
|
|
279
|
+
retry_after = response.headers['Retry-After']&.to_i
|
|
280
|
+
raise RateLimitError.new(
|
|
281
|
+
"Rate limit exceeded. Retry after #{retry_after || 'unknown'} seconds",
|
|
282
|
+
response: response,
|
|
283
|
+
status: 429,
|
|
284
|
+
retry_after: retry_after
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Raises a ServerError
|
|
289
|
+
def raise_server_error(response)
|
|
290
|
+
raise ServerError.new("Server error (#{response.status})", response: response, status: response.status)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Raises a generic Error for unexpected status codes
|
|
294
|
+
def raise_unexpected_error(response)
|
|
295
|
+
raise Error.new("Unexpected response (#{response.status})", response: response, status: response.status)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Parses the JSON response
|
|
299
|
+
def parse_json(body)
|
|
300
|
+
return {} if body.nil? || body.empty?
|
|
301
|
+
|
|
302
|
+
parsed = JSON.parse(body)
|
|
303
|
+
Jikanrb::IndifferentHash.new(parsed)
|
|
304
|
+
rescue JSON::ParserError => e
|
|
305
|
+
raise ParseError, "Failed to parse JSON: #{e.message}"
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jikanrb
|
|
4
|
+
# Configuration class for Jikanrb client.
|
|
5
|
+
# Allows customization of timeouts, retries, and other HTTP client settings.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# config = Jikanrb::Configuration.new
|
|
9
|
+
# config.read_timeout = 15
|
|
10
|
+
# config.max_retries = 5
|
|
11
|
+
class Configuration
|
|
12
|
+
# Base URL for Jikan v4 API
|
|
13
|
+
DEFAULT_BASE_URL = 'https://api.jikan.moe/v4'
|
|
14
|
+
|
|
15
|
+
# Default timeouts (in seconds)
|
|
16
|
+
DEFAULT_OPEN_TIMEOUT = 5
|
|
17
|
+
DEFAULT_READ_TIMEOUT = 10
|
|
18
|
+
|
|
19
|
+
# Rate limit: Jikan allows 60 requests/minute
|
|
20
|
+
DEFAULT_MAX_RETRIES = 3
|
|
21
|
+
DEFAULT_RETRY_INTERVAL = 1
|
|
22
|
+
|
|
23
|
+
attr_accessor :base_url,
|
|
24
|
+
:open_timeout,
|
|
25
|
+
:read_timeout,
|
|
26
|
+
:max_retries,
|
|
27
|
+
:retry_interval,
|
|
28
|
+
:user_agent,
|
|
29
|
+
:logger
|
|
30
|
+
|
|
31
|
+
def initialize
|
|
32
|
+
@base_url = DEFAULT_BASE_URL
|
|
33
|
+
@open_timeout = DEFAULT_OPEN_TIMEOUT
|
|
34
|
+
@read_timeout = DEFAULT_READ_TIMEOUT
|
|
35
|
+
@max_retries = DEFAULT_MAX_RETRIES
|
|
36
|
+
@retry_interval = DEFAULT_RETRY_INTERVAL
|
|
37
|
+
@user_agent = "Jikanrb Ruby Gem/#{Jikanrb::VERSION}"
|
|
38
|
+
@logger = nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jikanrb
|
|
4
|
+
# Base error for the gem
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :response, :status
|
|
7
|
+
|
|
8
|
+
def initialize(message = nil, response: nil, status: nil)
|
|
9
|
+
@response = response
|
|
10
|
+
@status = status
|
|
11
|
+
super(message)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Configuration error
|
|
16
|
+
class ConfigurationError < Error; end
|
|
17
|
+
|
|
18
|
+
# Specific HTTP errors
|
|
19
|
+
class ClientError < Error; end
|
|
20
|
+
|
|
21
|
+
# 400 - Bad Request
|
|
22
|
+
class BadRequestError < ClientError; end
|
|
23
|
+
|
|
24
|
+
# 404 - Not Found
|
|
25
|
+
class NotFoundError < ClientError; end
|
|
26
|
+
|
|
27
|
+
# 405 - Method Not Allowed
|
|
28
|
+
class MethodNotAllowedError < ClientError; end
|
|
29
|
+
|
|
30
|
+
# 429 - Rate Limit Exceeded
|
|
31
|
+
class RateLimitError < ClientError
|
|
32
|
+
attr_reader :retry_after
|
|
33
|
+
|
|
34
|
+
def initialize(message = nil, response: nil, status: nil, retry_after: nil)
|
|
35
|
+
@retry_after = retry_after
|
|
36
|
+
super(message, response: response, status: status)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 5xx - Server errors
|
|
41
|
+
class ServerError < Error; end
|
|
42
|
+
|
|
43
|
+
# Connection error (timeout, network issues)
|
|
44
|
+
class ConnectionError < Error; end
|
|
45
|
+
|
|
46
|
+
# Error parsing JSON
|
|
47
|
+
class ParseError < Error; end
|
|
48
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jikanrb
|
|
4
|
+
# Pagination helper module for paginated API responses
|
|
5
|
+
#
|
|
6
|
+
# @example Iterating through all pages
|
|
7
|
+
# client = Jikanrb::Client.new
|
|
8
|
+
# all_anime = client.paginate(:top_anime, type: 'tv')
|
|
9
|
+
# all_anime.each do |anime|
|
|
10
|
+
# puts anime['title']
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# @example Manual pagination
|
|
14
|
+
# result = client.top_anime(page: 1)
|
|
15
|
+
# pagination = Jikanrb::PaginationInfo.new(result)
|
|
16
|
+
# puts "Page #{pagination.current_page} of #{pagination.total_pages}"
|
|
17
|
+
# puts "Has next? #{pagination.has_next_page?}"
|
|
18
|
+
module Pagination
|
|
19
|
+
# Represents pagination information from an API response
|
|
20
|
+
class PaginationInfo
|
|
21
|
+
attr_reader :current_page, :last_visible_page, :has_next_page, :items
|
|
22
|
+
|
|
23
|
+
# @param response [Hash] API response with pagination data
|
|
24
|
+
def initialize(response)
|
|
25
|
+
@pagination = response['pagination'] || {}
|
|
26
|
+
@current_page = @pagination['current_page'] || 1
|
|
27
|
+
@last_visible_page = @pagination['last_visible_page'] || 1
|
|
28
|
+
@has_next_page = @pagination['has_next_page'] || false
|
|
29
|
+
@items = @pagination['items'] || {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Check if there's a next page
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def has_next_page?
|
|
35
|
+
@has_next_page
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Check if there's a previous page
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def has_previous_page?
|
|
41
|
+
@current_page > 1
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get next page number
|
|
45
|
+
# @return [Integer, nil] Next page number or nil if no next page
|
|
46
|
+
def next_page
|
|
47
|
+
has_next_page? ? @current_page + 1 : nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get previous page number
|
|
51
|
+
# @return [Integer, nil] Previous page number or nil if no previous page
|
|
52
|
+
def previous_page
|
|
53
|
+
has_previous_page? ? @current_page - 1 : nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get total number of pages
|
|
57
|
+
# @return [Integer]
|
|
58
|
+
def total_pages
|
|
59
|
+
@last_visible_page
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get number of items per page
|
|
63
|
+
# @return [Integer]
|
|
64
|
+
def per_page
|
|
65
|
+
@items['per_page'] || 25
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get total number of items
|
|
69
|
+
# @return [Integer]
|
|
70
|
+
def total_items
|
|
71
|
+
@items['total'] || 0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get current item count
|
|
75
|
+
# @return [Integer]
|
|
76
|
+
def current_item_count
|
|
77
|
+
@items['count'] || 0
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Paginator class for iterating through all pages
|
|
82
|
+
class Paginator
|
|
83
|
+
include Enumerable
|
|
84
|
+
|
|
85
|
+
# @param client [Jikanrb::Client] Client instance
|
|
86
|
+
# @param method [Symbol] Method name to call (e.g., :top_anime)
|
|
87
|
+
# @param params [Hash] Additional parameters for the method
|
|
88
|
+
def initialize(client, method, **params)
|
|
89
|
+
@client = client
|
|
90
|
+
@method = method
|
|
91
|
+
@params = params
|
|
92
|
+
@current_page = params[:page] || 1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Iterate through all items across all pages
|
|
96
|
+
# @yield [Hash] Each item from the API response
|
|
97
|
+
def each(&block)
|
|
98
|
+
loop do
|
|
99
|
+
response = @client.public_send(@method, **@params, page: @current_page)
|
|
100
|
+
data = response['data'] || []
|
|
101
|
+
|
|
102
|
+
data.each(&block)
|
|
103
|
+
|
|
104
|
+
pagination = PaginationInfo.new(response)
|
|
105
|
+
break unless pagination.has_next_page?
|
|
106
|
+
|
|
107
|
+
@current_page = pagination.next_page
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get all items from all pages as an array
|
|
112
|
+
# @return [Array<Hash>] All items
|
|
113
|
+
def all
|
|
114
|
+
to_a
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Get items from the first N pages
|
|
118
|
+
# @param page_count [Integer] Number of pages to fetch
|
|
119
|
+
# @return [Array<Hash>] Items from the specified number of pages
|
|
120
|
+
def take_pages(page_count)
|
|
121
|
+
items = []
|
|
122
|
+
page_count.times do
|
|
123
|
+
response = @client.public_send(@method, **@params, page: @current_page)
|
|
124
|
+
data = response['data'] || []
|
|
125
|
+
|
|
126
|
+
items.concat(data)
|
|
127
|
+
|
|
128
|
+
pagination = PaginationInfo.new(response)
|
|
129
|
+
break unless pagination.has_next_page?
|
|
130
|
+
|
|
131
|
+
@current_page = pagination.next_page
|
|
132
|
+
end
|
|
133
|
+
items
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jikanrb
|
|
4
|
+
# A Hash that allows access with both Symbol and String keys.
|
|
5
|
+
# This class provides a recursive mechanism to ensure nested Hashes
|
|
6
|
+
# also behave indifferently, similar to ActiveSupport's HashWithIndifferentAccess.
|
|
7
|
+
class IndifferentHash < Hash
|
|
8
|
+
# Initializes a new IndifferentHash.
|
|
9
|
+
#
|
|
10
|
+
# @param hash [Hash] The initial hash to populate the IndifferentHash with.
|
|
11
|
+
def initialize(hash = {})
|
|
12
|
+
super()
|
|
13
|
+
hash.each { |key, value| self[key] = value }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Retrieves the value object corresponding to the key object.
|
|
17
|
+
# The key is automatically converted to a string.
|
|
18
|
+
#
|
|
19
|
+
# @param key [Symbol, String] The key to look up.
|
|
20
|
+
# @return [Object] The value associated with the key.
|
|
21
|
+
def [](key)
|
|
22
|
+
super(convert_key(key))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Associates the value given by value with the key given by key.
|
|
26
|
+
# The key is automatically converted to a string.
|
|
27
|
+
# The value is processed to ensure nested structures are also indifferent.
|
|
28
|
+
#
|
|
29
|
+
# @param key [Symbol, String] The key to store.
|
|
30
|
+
# @param value [Object] The value to store.
|
|
31
|
+
# @return [Object] The stored value.
|
|
32
|
+
def []=(key, value)
|
|
33
|
+
super(convert_key(key), convert_value(value))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns a key's value, or the default value if the key is not found.
|
|
37
|
+
# The key is automatically converted to a string.
|
|
38
|
+
#
|
|
39
|
+
# @param key [Symbol, String] The key to look up.
|
|
40
|
+
# @param args [Array] Optional default value or block.
|
|
41
|
+
# @return [Object] The value associated with the key.
|
|
42
|
+
def fetch(key, *args)
|
|
43
|
+
super(convert_key(key), *args)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns true if the given key is present in the hash.
|
|
47
|
+
# The key is automatically converted to a string.
|
|
48
|
+
#
|
|
49
|
+
# @param key [Symbol, String] The key to check.
|
|
50
|
+
# @return [Boolean] True if the key exists, false otherwise.
|
|
51
|
+
def key?(key)
|
|
52
|
+
super(convert_key(key))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
alias include? key?
|
|
56
|
+
alias has_key? key?
|
|
57
|
+
alias member? key?
|
|
58
|
+
|
|
59
|
+
protected
|
|
60
|
+
|
|
61
|
+
# Converts the key to a String if it is a Symbol.
|
|
62
|
+
#
|
|
63
|
+
# @param key [Object] The key to convert.
|
|
64
|
+
# @return [String, Object] The converted key.
|
|
65
|
+
def convert_key(key)
|
|
66
|
+
key.is_a?(Symbol) ? key.to_s : key
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Recursively converts Hash values to IndifferentHash.
|
|
70
|
+
# Also handles Arrays of Hashes.
|
|
71
|
+
#
|
|
72
|
+
# @param value [Object] The value to convert.
|
|
73
|
+
# @return [Object] The converted value.
|
|
74
|
+
def convert_value(value)
|
|
75
|
+
case value
|
|
76
|
+
when Hash
|
|
77
|
+
IndifferentHash.new(value)
|
|
78
|
+
when Array
|
|
79
|
+
value.map { |v| convert_value(v) }
|
|
80
|
+
else
|
|
81
|
+
value
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/jikanrb.rb
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'jikanrb/version'
|
|
4
|
+
require_relative 'jikanrb/configuration'
|
|
5
|
+
require_relative 'jikanrb/errors'
|
|
6
|
+
require_relative 'jikanrb/pagination'
|
|
7
|
+
require_relative 'jikanrb/client'
|
|
8
|
+
require_relative 'jikanrb/utils'
|
|
9
|
+
|
|
10
|
+
# Jikanrb is a modern Ruby wrapper for the Jikan REST API v4.
|
|
11
|
+
# Provides easy access to anime, manga, characters, and more from MyAnimeList.
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage
|
|
14
|
+
# client = Jikanrb::Client.new
|
|
15
|
+
# anime = client.anime(1) # Cowboy Bebop
|
|
16
|
+
#
|
|
17
|
+
# @example Using global configuration
|
|
18
|
+
# Jikanrb.configure do |config|
|
|
19
|
+
# config.read_timeout = 15
|
|
20
|
+
# end
|
|
21
|
+
# anime = Jikanrb.anime(1)
|
|
22
|
+
module Jikanrb
|
|
23
|
+
class << self
|
|
24
|
+
attr_writer :configuration
|
|
25
|
+
|
|
26
|
+
# Global configuration for the gem
|
|
27
|
+
#
|
|
28
|
+
# @return [Configuration] Current configuration
|
|
29
|
+
def configuration
|
|
30
|
+
@configuration ||= Configuration.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Configures the gem with a block
|
|
34
|
+
#
|
|
35
|
+
# @example
|
|
36
|
+
# Jikanrb.configure do |config|
|
|
37
|
+
# config.read_timeout = 15
|
|
38
|
+
# config.logger = Logger.new($stdout)
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# @yield [config] Configuration block
|
|
42
|
+
# @yieldparam config [Configuration] Configuration object
|
|
43
|
+
def configure
|
|
44
|
+
yield(configuration)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Resets configuration to default values
|
|
48
|
+
#
|
|
49
|
+
# @return [Configuration] New configuration
|
|
50
|
+
def reset_configuration!
|
|
51
|
+
@configuration = Configuration.new
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Default client using global configuration
|
|
55
|
+
#
|
|
56
|
+
# @return [Client] Configured client
|
|
57
|
+
def client
|
|
58
|
+
@client ||= Client.new { |config| configure_client(config) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Resets the client (useful after changing configuration)
|
|
62
|
+
#
|
|
63
|
+
# @return [nil]
|
|
64
|
+
def reset_client!
|
|
65
|
+
@client = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Convenience methods that delegate to the default client
|
|
69
|
+
# Allows using Jikanrb.anime(1) directly
|
|
70
|
+
|
|
71
|
+
# @see Client#anime
|
|
72
|
+
def anime(id, full: false)
|
|
73
|
+
client.anime(id, full: full)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @see Client#manga
|
|
77
|
+
def manga(id, full: false)
|
|
78
|
+
client.manga(id, full: full)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @see Client#character
|
|
82
|
+
def character(id, full: false)
|
|
83
|
+
client.character(id, full: full)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @see Client#person
|
|
87
|
+
def person(id, full: false)
|
|
88
|
+
client.person(id, full: full)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @see Client#search_anime
|
|
92
|
+
def search_anime(query, **params)
|
|
93
|
+
client.search_anime(query, **params)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @see Client#search_manga
|
|
97
|
+
def search_manga(query, **params)
|
|
98
|
+
client.search_manga(query, **params)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @see Client#top_anime
|
|
102
|
+
def top_anime(type: nil, filter: nil, page: 1)
|
|
103
|
+
client.top_anime(type: type, filter: filter, page: page)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @see Client#top_manga
|
|
107
|
+
def top_manga(type: nil, filter: nil, page: 1)
|
|
108
|
+
client.top_manga(type: type, filter: filter, page: page)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @see Client#season
|
|
112
|
+
def season(year, season, page: 1)
|
|
113
|
+
client.season(year, season, page: page)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @see Client#season_now
|
|
117
|
+
def season_now(page: 1)
|
|
118
|
+
client.season_now(page: page)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @see Client#schedules
|
|
122
|
+
def schedules(day: nil)
|
|
123
|
+
client.schedules(day: day)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
# Configures a client instance with global configuration settings
|
|
129
|
+
#
|
|
130
|
+
# @param config [Configuration] Client configuration object
|
|
131
|
+
# @return [void]
|
|
132
|
+
# rubocop:disable Metrics/AbcSize
|
|
133
|
+
def configure_client(config)
|
|
134
|
+
config.base_url = configuration.base_url
|
|
135
|
+
config.open_timeout = configuration.open_timeout
|
|
136
|
+
config.read_timeout = configuration.read_timeout
|
|
137
|
+
config.max_retries = configuration.max_retries
|
|
138
|
+
config.retry_interval = configuration.retry_interval
|
|
139
|
+
config.user_agent = configuration.user_agent
|
|
140
|
+
config.logger = configuration.logger
|
|
141
|
+
end
|
|
142
|
+
# rubocop:enable Metrics/AbcSize
|
|
143
|
+
end
|
|
144
|
+
end
|
data/sig/jikanrb.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jikanrb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sergio Brocos
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-01-09 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '2.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: faraday-retry
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '3.0'
|
|
43
|
+
type: :runtime
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '2.0'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '3.0'
|
|
53
|
+
description: A modern, well-documented Ruby wrapper for the Jikan REST API v4. Provides
|
|
54
|
+
easy access to anime, manga, characters, and more from MyAnimeList.
|
|
55
|
+
email:
|
|
56
|
+
- sergiobrocos@gmail.com
|
|
57
|
+
executables: []
|
|
58
|
+
extensions: []
|
|
59
|
+
extra_rdoc_files: []
|
|
60
|
+
files:
|
|
61
|
+
- ".claude/settings.local.json"
|
|
62
|
+
- ".ruby-version"
|
|
63
|
+
- CHANGELOG.md
|
|
64
|
+
- CODE_OF_CONDUCT.md
|
|
65
|
+
- LICENSE.txt
|
|
66
|
+
- README.md
|
|
67
|
+
- Rakefile
|
|
68
|
+
- lib/jikanrb.rb
|
|
69
|
+
- lib/jikanrb/client.rb
|
|
70
|
+
- lib/jikanrb/configuration.rb
|
|
71
|
+
- lib/jikanrb/errors.rb
|
|
72
|
+
- lib/jikanrb/pagination.rb
|
|
73
|
+
- lib/jikanrb/utils.rb
|
|
74
|
+
- lib/jikanrb/version.rb
|
|
75
|
+
- sig/jikanrb.rbs
|
|
76
|
+
homepage: https://github.com/tuusuario/jikanrb
|
|
77
|
+
licenses:
|
|
78
|
+
- MIT
|
|
79
|
+
metadata:
|
|
80
|
+
allowed_push_host: https://rubygems.org
|
|
81
|
+
homepage_uri: https://github.com/tuusuario/jikanrb
|
|
82
|
+
source_code_uri: https://github.com/tuusuario/jikanrb
|
|
83
|
+
changelog_uri: https://github.com/tuusuario/jikanrb/blob/main/CHANGELOG.md
|
|
84
|
+
documentation_uri: https://rubydoc.info/gems/jikanrb
|
|
85
|
+
rubygems_mfa_required: 'true'
|
|
86
|
+
post_install_message:
|
|
87
|
+
rdoc_options: []
|
|
88
|
+
require_paths:
|
|
89
|
+
- lib
|
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: 3.1.0
|
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '0'
|
|
100
|
+
requirements: []
|
|
101
|
+
rubygems_version: 3.3.3
|
|
102
|
+
signing_key:
|
|
103
|
+
specification_version: 4
|
|
104
|
+
summary: Ruby client for Jikan API v4 (Unofficial MyAnimeList API)
|
|
105
|
+
test_files: []
|