jikanrb 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb528e6db6413f8aa74e076189001f63fd5bb1180e20a6b65341ea36dbb0f6e6
4
- data.tar.gz: 03dc1aa0638e29902313138e0598df2e310b29c6814672fa490eb7bcde0269b3
3
+ metadata.gz: cf12793ef80ef81581dbad79ebe5d15266029db927c4e4ab5f741323b84f7d6f
4
+ data.tar.gz: 4c28905ede783f3047996a6c1bb0a74f88a8529b0d76d0658d546c9d89af6164
5
5
  SHA512:
6
- metadata.gz: 531160fb13899e4a6499f7209a8371352e5473e24879ae7dae4d6643452af72b11725e9fdd33d18e65dc568569afe1f4f5d303b10df9abe6b9a7a74ccdef52d7
7
- data.tar.gz: 40f0284cb2b8d2a1508f09ee30d28587219a9c7cfe2484a0a4d9e0c7b7f7ad21aed92140e9396a7d0875fc3ac5339aa23172534b3464976bb321ae0996d6cf24
6
+ metadata.gz: 1beb1a905b77c37a189dc6dfed33bd42923b412a9eef9aeeb81be74f484df4ed06626ad1209de68a5f92b2a150550c4cc2d234832ad372d616aed56faa8c08c5
7
+ data.tar.gz: e578ca35e574134812424ddb696ac2166e6ea216e7be343bcc5663ccbf10858b56c8d8255f2fab830a250b34c2c57d61133ee14ab45b3231b520e657c07c71c4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-01-31
4
+
5
+ ### Added
6
+ - **Fluent API** for accessing sub-resources with chainable methods
7
+ - `BaseResource` class providing common functionality for all resources
8
+ - `AnimeResource` with 14 sub-resource methods:
9
+ - `info(full: false)` - Basic/full anime details
10
+ - `characters` - Characters and voice actors
11
+ - `staff` - Staff members
12
+ - `episodes(page: nil)` - Episodes with pagination
13
+ - `news` - Related news articles
14
+ - `forum` - Forum topics
15
+ - `videos` - PVs, episodes, music videos
16
+ - `pictures` - Anime pictures
17
+ - `statistics` - User statistics
18
+ - `recommendations` - User recommendations
19
+ - `relations` - Related anime/manga
20
+ - `themes` - Opening/ending themes
21
+ - `external` - External links
22
+ - `streaming` - Streaming platform links
23
+ - `MangaResource` with 9 sub-resource methods:
24
+ - `info(full: false)` - Basic/full manga details
25
+ - `characters` - Characters
26
+ - `news` - Related news articles
27
+ - `forum` - Forum topics
28
+ - `pictures` - Manga pictures
29
+ - `statistics` - User statistics
30
+ - `recommendations` - User recommendations
31
+ - `relations` - Related anime/manga
32
+ - `external` - External links
33
+ - `CharacterResource` with 5 sub-resource methods:
34
+ - `info(full: false)` - Basic/full character details
35
+ - `animes` - Anime appearances
36
+ - `mangas` - Manga appearances
37
+ - `voices` - Voice actors
38
+ - `pictures` - Character pictures
39
+ - `PersonResource` with 5 sub-resource methods:
40
+ - `info(full: false)` - Basic/full person details
41
+ - `animes` - Anime staff positions
42
+ - `mangas` - Manga work
43
+ - `voices` - Voice acting roles
44
+ - `pictures` - Person pictures
45
+
46
+ ### Changed
47
+ - `Client#anime(id)` now returns `AnimeResource` instead of `Hash`
48
+ - `Client#manga(id)` now returns `MangaResource` instead of `Hash`
49
+ - `Client#character(id)` now returns `CharacterResource` instead of `Hash`
50
+ - `Client#person(id)` now returns `PersonResource` instead of `Hash`
51
+
52
+ ### Breaking Changes
53
+ - Removed `full:` parameter from `anime`, `manga`, `character`, and `person` methods
54
+ - Use `.info` or `.info(full: true)` for the previous behavior:
55
+ ```ruby
56
+ # Before (v0.2.x)
57
+ client.anime(1)
58
+ client.anime(1, full: true)
59
+
60
+ # After (v0.3.0+)
61
+ client.anime(1).info
62
+ client.anime(1).info(full: true)
63
+ ```
64
+
65
+ ### Documentation
66
+ - Complete YARD documentation for all resource classes
67
+ - Updated README with fluent API examples
68
+ - Added migration guide for breaking changes
69
+
3
70
  ## [0.2.1] - 2026-01-10
4
71
 
5
72
  ### Changed
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Jikanrb
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/jikanrb.svg)](https://badge.fury.io/rb/jikanrb)
4
- [![CI](https://github.com/tuusuario/jikanrb/actions/workflows/main.yml/badge.svg)](https://github.com/tuusuario/jikanrb/actions)
4
+ [![CI](https://github.com/sbrocos/jikanrb/actions/workflows/main.yml/badge.svg)](https://github.com/sbrocos/jikanrb/actions)
5
5
 
6
6
  A modern Ruby client for the [Jikan API v4](https://jikan.moe/) - the unofficial MyAnimeList API.
7
7
 
@@ -10,7 +10,7 @@ A modern Ruby client for the [Jikan API v4](https://jikan.moe/) - the unofficial
10
10
  Add this line to your application's Gemfile:
11
11
 
12
12
  ```ruby
13
- gem 'jikanrb'
13
+ gem "jikanrb"
14
14
  ```
15
15
 
16
16
  And then execute:
@@ -30,17 +30,14 @@ gem install jikanrb
30
30
  ### Quick Start
31
31
 
32
32
  ```ruby
33
- require 'jikanrb'
33
+ require "jikanrb"
34
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"
35
+ # Get anime info using fluent API
36
+ anime = Jikanrb.anime(1).info
37
+ puts anime[:data][:title] # => "Cowboy Bebop"
41
38
 
42
39
  # Get full anime info (includes relations, theme songs, etc.)
43
- anime = Jikanrb.anime(1, full: true)
40
+ anime = Jikanrb.anime(1).info(full: true)
44
41
 
45
42
  # Search anime
46
43
  results = Jikanrb.search_anime("Naruto")
@@ -57,11 +54,13 @@ client = Jikanrb::Client.new do |config|
57
54
  config.max_retries = 5
58
55
  end
59
56
 
60
- # All methods available
61
- client.anime(1)
62
- client.manga(1)
63
- client.character(1)
64
- client.person(1)
57
+ # Fluent API for resources
58
+ client.anime(1).info
59
+ client.manga(1).info
60
+ client.character(1).info
61
+ client.person(1).info
62
+
63
+ # Search and listings
65
64
  client.search_anime("One Piece", type: "tv", status: "airing")
66
65
  client.top_anime(type: "tv", filter: "bypopularity")
67
66
  client.season(2024, "winter")
@@ -69,19 +68,75 @@ client.season_now
69
68
  client.schedules(day: "monday")
70
69
  ```
71
70
 
72
- ### Configuration in Rails
71
+ ## Fluent API (v1.1.0+)
72
+
73
+ Access sub-resources using the fluent interface for cleaner, chainable code.
73
74
 
74
- You can configure the gem globally in an initializer (e.g., `config/initializers/jikanrb.rb`):
75
+ ### Anime
75
76
 
76
77
  ```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
78
+ client = Jikanrb::Client.new
79
+
80
+ # Basic and full info
81
+ client.anime(1).info # GET /anime/1
82
+ client.anime(1).info(full: true) # GET /anime/1/full
83
+
84
+ # Sub-resources
85
+ client.anime(1).characters # Characters and voice actors
86
+ client.anime(1).staff # Staff members
87
+ client.anime(1).episodes # Episodes (first page)
88
+ client.anime(1).episodes(page: 2) # Episodes (page 2)
89
+ client.anime(1).news # News articles
90
+ client.anime(1).forum # Forum topics
91
+ client.anime(1).videos # PVs, episodes, music videos
92
+ client.anime(1).pictures # Pictures
93
+ client.anime(1).statistics # User statistics
94
+ client.anime(1).recommendations # User recommendations
95
+ client.anime(1).relations # Related anime/manga
96
+ client.anime(1).themes # Opening/ending themes
97
+ client.anime(1).external # External links
98
+ client.anime(1).streaming # Streaming platform links
83
99
  ```
84
100
 
101
+ ### Manga
102
+
103
+ ```ruby
104
+ client.manga(1).info # GET /manga/1
105
+ client.manga(1).info(full: true) # GET /manga/1/full
106
+ client.manga(1).characters # Characters
107
+ client.manga(1).news # News articles
108
+ client.manga(1).forum # Forum topics
109
+ client.manga(1).pictures # Pictures
110
+ client.manga(1).statistics # User statistics
111
+ client.manga(1).recommendations # User recommendations
112
+ client.manga(1).relations # Related anime/manga
113
+ client.manga(1).external # External links
114
+ ```
115
+
116
+ ### Characters
117
+
118
+ ```ruby
119
+ client.character(1).info # GET /characters/1
120
+ client.character(1).info(full: true)# GET /characters/1/full
121
+ client.character(1).animes # Anime appearances
122
+ client.character(1).mangas # Manga appearances
123
+ client.character(1).voices # Voice actors
124
+ client.character(1).pictures # Pictures
125
+ ```
126
+
127
+ ### People
128
+
129
+ ```ruby
130
+ client.person(1).info # GET /people/1
131
+ client.person(1).info(full: true) # GET /people/1/full
132
+ client.person(1).animes # Anime staff positions
133
+ client.person(1).mangas # Manga work
134
+ client.person(1).voices # Voice acting roles
135
+ client.person(1).pictures # Pictures
136
+ ```
137
+
138
+ ## Configuration
139
+
85
140
  ### Global Configuration
86
141
 
87
142
  ```ruby
@@ -95,14 +150,33 @@ Jikanrb.configure do |config|
95
150
  end
96
151
  ```
97
152
 
98
- ### Available Methods
153
+ ### Rails Configuration
154
+
155
+ Create an initializer (e.g., `config/initializers/jikanrb.rb`):
156
+
157
+ ```ruby
158
+ Jikanrb.configure do |config|
159
+ config.read_timeout = 20
160
+ config.max_retries = 3
161
+ config.logger = Rails.logger
162
+ end
163
+ ```
164
+
165
+ ## Available Methods
166
+
167
+ ### Resource Methods (Fluent API)
168
+
169
+ | Method | Returns | Description |
170
+ | :--- | :--- | :--- |
171
+ | `anime(id)` | `AnimeResource` | Anime resource for sub-resource access |
172
+ | `manga(id)` | `MangaResource` | Manga resource for sub-resource access |
173
+ | `character(id)` | `CharacterResource` | Character resource for sub-resource access |
174
+ | `person(id)` | `PersonResource` | Person resource for sub-resource access |
175
+
176
+ ### Direct Methods
99
177
 
100
178
  | Method | Description |
101
179
  | :--- | :--- |
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
180
  | `search_anime(query, **params)` | Search anime |
107
181
  | `search_manga(query, **params)` | Search manga |
108
182
  | `top_anime(type:, filter:, page:)` | Top anime list |
@@ -111,11 +185,11 @@ end
111
185
  | `season_now(page:)` | Current season anime |
112
186
  | `schedules(day:)` | Weekly schedule |
113
187
 
114
- ### Error Handling
188
+ ## Error Handling
115
189
 
116
190
  ```ruby
117
191
  begin
118
- anime = Jikanrb.anime(999999999)
192
+ anime = Jikanrb.anime(999999999).info
119
193
  rescue Jikanrb::NotFoundError => e
120
194
  puts "Anime not found: #{e.message}"
121
195
  rescue Jikanrb::RateLimitError => e
@@ -127,21 +201,22 @@ rescue Jikanrb::Error => e
127
201
  end
128
202
  ```
129
203
 
130
- ### Pagination
204
+ ## Pagination
131
205
 
132
206
  The gem provides convenient pagination helpers for working with paginated endpoints:
133
207
 
134
208
  ```ruby
135
- # Automatic pagination - iterates through all pages
136
209
  client = Jikanrb::Client.new
137
- paginator = client.paginate(:top_anime, type: 'tv')
210
+
211
+ # Automatic pagination - iterates through all pages
212
+ paginator = client.paginate(:top_anime, type: "tv")
138
213
 
139
214
  # Get all items (will fetch all pages)
140
215
  all_anime = paginator.all
141
216
 
142
217
  # Iterate through all pages lazily
143
218
  paginator.each do |anime|
144
- puts "#{anime['title']} - Score: #{anime['score']}"
219
+ puts "#{anime["title"]} - Score: #{anime["score"]}"
145
220
  end
146
221
 
147
222
  # Get items from first 3 pages only
@@ -153,24 +228,15 @@ pagination = client.pagination_info(response)
153
228
 
154
229
  puts "Current page: #{pagination.current_page}"
155
230
  puts "Total pages: #{pagination.total_pages}"
156
- puts "Items per page: #{pagination.per_page}"
157
231
  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
232
  ```
161
233
 
162
- ### Rate Limiting
234
+ ## Rate Limiting
163
235
 
164
236
  Jikan API allows **60 requests per minute**. This gem includes automatic retry with exponential backoff for rate limit errors (429).
165
237
 
166
238
  ## Development
167
239
 
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
240
  ```bash
175
241
  # Install dependencies
176
242
  bin/setup
@@ -178,11 +244,14 @@ bin/setup
178
244
  # Run tests
179
245
  bundle exec rspec
180
246
 
247
+ # Run linter
248
+ bundle exec rubocop
249
+
181
250
  # Interactive console
182
251
  bin/console
183
252
 
184
- # Linting
185
- bundle exec rubocop
253
+ # Generate documentation
254
+ bundle exec yard doc
186
255
  ```
187
256
 
188
257
  ## Acknowledgments
@@ -191,12 +260,8 @@ This gem is inspired by [jikan.rb](https://github.com/Zerocchi/jikan.rb) by Zero
191
260
 
192
261
  ## Contributing
193
262
 
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).
263
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sbrocos/jikanrb.
195
264
 
196
265
  ## License
197
266
 
198
267
  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).
@@ -47,44 +47,88 @@ module Jikanrb
47
47
  request(:get, path, params)
48
48
  end
49
49
 
50
- # Anime information by ID
50
+ # Returns an AnimeResource for fluent API access to anime sub-resources
51
51
  #
52
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)
53
+ # @return [Resources::AnimeResource] Anime resource for chaining
54
+ #
55
+ # @example Get anime info
56
+ # client.anime(1).info
57
+ # # => { data: { mal_id: 1, title: "Cowboy Bebop", ... } }
58
+ #
59
+ # @example Get full anime info
60
+ # client.anime(1).info(full: true)
61
+ #
62
+ # @example Get anime characters
63
+ # client.anime(1).characters
64
+ #
65
+ # @example Get anime episodes
66
+ # client.anime(1).episodes
67
+ def anime(id)
68
+ Resources::AnimeResource.new(self, id)
58
69
  end
59
70
 
60
- # Manga information by ID
71
+ # Returns a MangaResource for fluent API access to manga sub-resources
61
72
  #
62
73
  # @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)
74
+ # @return [Resources::MangaResource] Manga resource for chaining
75
+ #
76
+ # @example Get manga info
77
+ # client.manga(1).info
78
+ # # => { data: { mal_id: 1, title: "Monster", ... } }
79
+ #
80
+ # @example Get full manga info
81
+ # client.manga(1).info(full: true)
82
+ #
83
+ # @example Get manga characters
84
+ # client.manga(1).characters
85
+ #
86
+ # @example Get manga statistics
87
+ # client.manga(1).statistics
88
+ def manga(id)
89
+ Resources::MangaResource.new(self, id)
68
90
  end
69
91
 
70
- # Character information by ID
92
+ # Returns a CharacterResource for fluent API access to character sub-resources
71
93
  #
72
94
  # @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)
95
+ # @return [Resources::CharacterResource] Character resource for chaining
96
+ #
97
+ # @example Get character info
98
+ # client.character(1).info
99
+ # # => { data: { mal_id: 1, name: "Spike Spiegel", ... } }
100
+ #
101
+ # @example Get full character info
102
+ # client.character(1).info(full: true)
103
+ #
104
+ # @example Get character's anime appearances
105
+ # client.character(1).animes
106
+ #
107
+ # @example Get character's voice actors
108
+ # client.character(1).voices
109
+ def character(id)
110
+ Resources::CharacterResource.new(self, id)
78
111
  end
79
112
 
80
- # Person information by ID
113
+ # Returns a PersonResource for fluent API access to person sub-resources
81
114
  #
82
115
  # @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)
116
+ # @return [Resources::PersonResource] Person resource for chaining
117
+ #
118
+ # @example Get person info
119
+ # client.person(1).info
120
+ # # => { data: { mal_id: 1, name: "Tomokazu Seki", ... } }
121
+ #
122
+ # @example Get full person info
123
+ # client.person(1).info(full: true)
124
+ #
125
+ # @example Get person's anime staff positions
126
+ # client.person(1).animes
127
+ #
128
+ # @example Get person's voice acting roles
129
+ # client.person(1).voices
130
+ def person(id)
131
+ Resources::PersonResource.new(self, id)
88
132
  end
89
133
 
90
134
  # Search anime
@@ -93,7 +137,7 @@ module Jikanrb
93
137
  # @param params [Hash] Additional filters (type, score, status, etc.)
94
138
  # @return [Hash] Search results
95
139
  def search_anime(query, **params)
96
- get('/anime', params.merge(q: query))
140
+ get('anime', params.merge(q: query))
97
141
  end
98
142
 
99
143
  # Search manga
@@ -102,7 +146,7 @@ module Jikanrb
102
146
  # @param params [Hash] Additional filters
103
147
  # @return [Hash] Search results
104
148
  def search_manga(query, **params)
105
- get('/manga', params.merge(q: query))
149
+ get('manga', params.merge(q: query))
106
150
  end
107
151
 
108
152
  # Top anime
@@ -115,7 +159,7 @@ module Jikanrb
115
159
  params = { page: page }
116
160
  params[:type] = type if type
117
161
  params[:filter] = filter if filter
118
- get('/top/anime', params)
162
+ get('top/anime', params)
119
163
  end
120
164
 
121
165
  # Top manga
@@ -128,7 +172,7 @@ module Jikanrb
128
172
  params = { page: page }
129
173
  params[:type] = type if type
130
174
  params[:filter] = filter if filter
131
- get('/top/manga', params)
175
+ get('top/manga', params)
132
176
  end
133
177
 
134
178
  # Seasonal anime
@@ -138,7 +182,7 @@ module Jikanrb
138
182
  # @param page [Integer] Page number
139
183
  # @return [Hash] Seasonal anime
140
184
  def season(year, season, page: 1)
141
- get("/seasons/#{year}/#{season}", page: page)
185
+ get("seasons/#{year}/#{season}", page: page)
142
186
  end
143
187
 
144
188
  # Current season
@@ -146,7 +190,7 @@ module Jikanrb
146
190
  # @param page [Integer] Page number
147
191
  # @return [Hash] Current season anime
148
192
  def season_now(page: 1)
149
- get('/seasons/now', page: page)
193
+ get('seasons/now', page: page)
150
194
  end
151
195
 
152
196
  # Weekly schedule
@@ -154,7 +198,7 @@ module Jikanrb
154
198
  # @param day [String, nil] Day: "monday", "tuesday", etc.
155
199
  # @return [Hash] Anime schedule
156
200
  def schedules(day: nil)
157
- path = day ? "/schedules/#{day}" : '/schedules'
201
+ path = day ? "schedules/#{day}" : 'schedules'
158
202
  get(path)
159
203
  end
160
204
 
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jikanrb
4
+ module Resources
5
+ # Resource class for accessing Anime sub-resources from the Jikan API.
6
+ # Provides fluent API access to anime details, characters, staff, episodes,
7
+ # news, forum topics, videos, pictures, statistics, and more.
8
+ #
9
+ # @example Basic usage
10
+ # client = Jikanrb::Client.new
11
+ # anime = client.anime(1)
12
+ # anime.info # GET /anime/1
13
+ # anime.info(full: true) # GET /anime/1/full
14
+ # anime.characters # GET /anime/1/characters
15
+ # anime.episodes # GET /anime/1/episodes
16
+ #
17
+ # @see https://docs.api.jikan.moe/#tag/anime
18
+ class AnimeResource < BaseResource
19
+ # Returns anime details
20
+ #
21
+ # @param full [Boolean] If true, returns extended information including
22
+ # relations, themes, external links, and streaming links
23
+ # @return [Hash] Anime data as IndifferentHash
24
+ #
25
+ # @example Get basic info
26
+ # client.anime(1).info
27
+ # # => { data: { mal_id: 1, title: "Cowboy Bebop", ... } }
28
+ #
29
+ # @example Get full info
30
+ # client.anime(1).info(full: true)
31
+ # # => { data: { mal_id: 1, title: "...", relations: [...], themes: {...} } }
32
+ def info(full: false)
33
+ path = full ? "anime/#{@id}/full" : "anime/#{@id}"
34
+ get(path)
35
+ end
36
+
37
+ # Returns characters and their voice actors for this anime
38
+ #
39
+ # @return [Hash] List of characters as IndifferentHash
40
+ #
41
+ # @example
42
+ # client.anime(1).characters
43
+ # # => { data: [{ character: { mal_id: 1, name: "Spike Spiegel" }, role: "Main", voice_actors: [...] }, ...] }
44
+ def characters
45
+ get("anime/#{@id}/characters")
46
+ end
47
+
48
+ # Returns staff members for this anime
49
+ #
50
+ # @return [Hash] List of staff as IndifferentHash
51
+ #
52
+ # @example
53
+ # client.anime(1).staff
54
+ # # => { data: [{ person: { mal_id: 1, name: "..." }, positions: ["Director"] }, ...] }
55
+ def staff
56
+ get("anime/#{@id}/staff")
57
+ end
58
+
59
+ # Returns episodes for this anime
60
+ #
61
+ # @param page [Integer, nil] Page number for pagination
62
+ # @return [Hash] List of episodes as IndifferentHash
63
+ #
64
+ # @example Get first page of episodes
65
+ # client.anime(1).episodes
66
+ # # => { data: [{ mal_id: 1, title: "Asteroid Blues", ... }, ...], pagination: { ... } }
67
+ #
68
+ # @example Get specific page
69
+ # client.anime(1).episodes(page: 2)
70
+ def episodes(page: nil)
71
+ get("anime/#{@id}/episodes", { page: page }.compact)
72
+ end
73
+
74
+ # Returns news articles related to this anime
75
+ #
76
+ # @return [Hash] List of news articles as IndifferentHash
77
+ #
78
+ # @example
79
+ # client.anime(1).news
80
+ # # => { data: [{ mal_id: 1, title: "...", date: "...", author_username: "..." }, ...] }
81
+ def news
82
+ get("anime/#{@id}/news")
83
+ end
84
+
85
+ # Returns forum topics related to this anime
86
+ #
87
+ # @return [Hash] List of forum topics as IndifferentHash
88
+ #
89
+ # @example
90
+ # client.anime(1).forum
91
+ # # => { data: [{ mal_id: 1, title: "...", date: "...", author_username: "..." }, ...] }
92
+ def forum
93
+ get("anime/#{@id}/forum")
94
+ end
95
+
96
+ # Returns videos (PVs, episodes, music videos) for this anime
97
+ #
98
+ # @return [Hash] Video data as IndifferentHash
99
+ #
100
+ # @example
101
+ # client.anime(1).videos
102
+ # # => { data: { promo: [...], episodes: [...], music_videos: [...] } }
103
+ def videos
104
+ get("anime/#{@id}/videos")
105
+ end
106
+
107
+ # Returns pictures of this anime
108
+ #
109
+ # @return [Hash] List of pictures as IndifferentHash
110
+ #
111
+ # @example
112
+ # client.anime(1).pictures
113
+ # # => { data: [{ jpg: { image_url: "https://..." }, webp: { ... } }, ...] }
114
+ def pictures
115
+ get("anime/#{@id}/pictures")
116
+ end
117
+
118
+ # Returns statistics for this anime
119
+ #
120
+ # @return [Hash] Statistics data as IndifferentHash
121
+ #
122
+ # @example
123
+ # client.anime(1).statistics
124
+ # # => { data: { watching: 12345, completed: 67890, on_hold: 1234, ... } }
125
+ def statistics
126
+ get("anime/#{@id}/statistics")
127
+ end
128
+
129
+ # Returns user recommendations for this anime
130
+ #
131
+ # @return [Hash] List of recommendations as IndifferentHash
132
+ #
133
+ # @example
134
+ # client.anime(1).recommendations
135
+ # # => { data: [{ entry: { mal_id: 5, title: "..." }, votes: 123 }, ...] }
136
+ def recommendations
137
+ get("anime/#{@id}/recommendations")
138
+ end
139
+
140
+ # Returns related anime/manga entries
141
+ #
142
+ # @return [Hash] List of relations as IndifferentHash
143
+ #
144
+ # @example
145
+ # client.anime(1).relations
146
+ # # => { data: [{ relation: "Adaptation", entry: [{ mal_id: 173, type: "manga", name: "..." }] }, ...] }
147
+ def relations
148
+ get("anime/#{@id}/relations")
149
+ end
150
+
151
+ # Returns opening and ending themes for this anime
152
+ #
153
+ # @return [Hash] Theme songs as IndifferentHash
154
+ #
155
+ # @example
156
+ # client.anime(1).themes
157
+ # # => { data: { openings: ["Tank! by The Seatbelts"], endings: ["The Real Folk Blues by ..."] } }
158
+ def themes
159
+ get("anime/#{@id}/themes")
160
+ end
161
+
162
+ # Returns external links for this anime
163
+ #
164
+ # @return [Hash] List of external links as IndifferentHash
165
+ #
166
+ # @example
167
+ # client.anime(1).external
168
+ # # => { data: [{ name: "Official Site", url: "https://..." }, ...] }
169
+ def external
170
+ get("anime/#{@id}/external")
171
+ end
172
+
173
+ # Returns streaming links for this anime
174
+ #
175
+ # @return [Hash] List of streaming platforms as IndifferentHash
176
+ #
177
+ # @example
178
+ # client.anime(1).streaming
179
+ # # => { data: [{ name: "Crunchyroll", url: "https://..." }, ...] }
180
+ def streaming
181
+ get("anime/#{@id}/streaming")
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jikanrb
4
+ module Resources
5
+ # Base class for all resource classes.
6
+ # Provides common functionality for accessing sub-resources from the Jikan API.
7
+ #
8
+ # @abstract Subclass and implement specific resource methods
9
+ #
10
+ # @example Creating a custom resource
11
+ # class AnimeResource < BaseResource
12
+ # def characters
13
+ # get("anime/#{@id}/characters")
14
+ # end
15
+ # end
16
+ class BaseResource
17
+ # @return [Integer] The resource ID
18
+ attr_reader :id
19
+
20
+ # Creates a new resource instance
21
+ #
22
+ # @param client [Jikanrb::Client] The client instance to use for requests
23
+ # @param id [Integer] The resource ID on MyAnimeList
24
+ def initialize(client, id)
25
+ @client = client
26
+ @id = id
27
+ end
28
+
29
+ private
30
+
31
+ # Performs a GET request through the client
32
+ #
33
+ # @param path [String] The API endpoint path
34
+ # @param params [Hash] Optional query parameters
35
+ # @return [Hash] The API response as an IndifferentHash
36
+ def get(path, params = {})
37
+ @client.send(:request, :get, path, params)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jikanrb
4
+ module Resources
5
+ # Resource class for accessing Character sub-resources from the Jikan API.
6
+ # Provides fluent API access to character details, anime appearances,
7
+ # manga appearances, voice actors, and pictures.
8
+ #
9
+ # @example Basic usage
10
+ # client = Jikanrb::Client.new
11
+ # character = client.character(1)
12
+ # character.info # GET /characters/1
13
+ # character.info(full: true) # GET /characters/1/full
14
+ # character.anime # GET /characters/1/anime
15
+ # character.voices # GET /characters/1/voices
16
+ #
17
+ # @see https://docs.api.jikan.moe/#tag/characters
18
+ class CharacterResource < BaseResource
19
+ # Returns character details
20
+ #
21
+ # @param full [Boolean] If true, returns extended information including
22
+ # anime appearances, manga appearances, and voice actors
23
+ # @return [Hash] Character data as IndifferentHash
24
+ #
25
+ # @example Get basic info
26
+ # client.character(1).info
27
+ # # => { data: { mal_id: 1, name: "Spike Spiegel", ... } }
28
+ #
29
+ # @example Get full info
30
+ # client.character(1).info(full: true)
31
+ # # => { data: { mal_id: 1, name: "...", anime: [...], voices: [...] } }
32
+ def info(full: false)
33
+ path = full ? "characters/#{@id}/full" : "characters/#{@id}"
34
+ get(path)
35
+ end
36
+
37
+ # Returns anime appearances for this character
38
+ #
39
+ # @return [Hash] List of anime appearances as IndifferentHash
40
+ #
41
+ # @example
42
+ # client.character(1).anime
43
+ # # => { data: [{ role: "Main", anime: { mal_id: 1, title: "..." } }, ...] }
44
+ def animes
45
+ get("characters/#{@id}/anime")
46
+ end
47
+
48
+ # Returns manga appearances for this character
49
+ #
50
+ # @return [Hash] List of manga appearances as IndifferentHash
51
+ #
52
+ # @example
53
+ # client.character(1).manga
54
+ # # => { data: [{ role: "Main", manga: { mal_id: 1, title: "..." } }, ...] }
55
+ def mangas
56
+ get("characters/#{@id}/manga")
57
+ end
58
+
59
+ # Returns voice actors for this character
60
+ #
61
+ # @return [Hash] List of voice actors as IndifferentHash
62
+ #
63
+ # @example
64
+ # client.character(1).voices
65
+ # # => { data: [{ language: "Japanese", person: { mal_id: 11, name: "..." } }, ...] }
66
+ def voices
67
+ get("characters/#{@id}/voices")
68
+ end
69
+
70
+ # Returns pictures of this character
71
+ #
72
+ # @return [Hash] List of pictures as IndifferentHash
73
+ #
74
+ # @example
75
+ # client.character(1).pictures
76
+ # # => { data: [{ jpg: { image_url: "https://..." } }, ...] }
77
+ def pictures
78
+ get("characters/#{@id}/pictures")
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jikanrb
4
+ module Resources
5
+ # Resource class for accessing Manga sub-resources from the Jikan API.
6
+ # Provides fluent API access to manga details, characters, news,
7
+ # forum topics, pictures, statistics, and more.
8
+ #
9
+ # @example Basic usage
10
+ # client = Jikanrb::Client.new
11
+ # manga = client.manga(1)
12
+ # manga.info # GET /manga/1
13
+ # manga.info(full: true) # GET /manga/1/full
14
+ # manga.characters # GET /manga/1/characters
15
+ #
16
+ # @see https://docs.api.jikan.moe/#tag/manga
17
+ class MangaResource < BaseResource
18
+ # Returns manga details
19
+ #
20
+ # @param full [Boolean] If true, returns extended information including
21
+ # relations, external links, etc.
22
+ # @return [Hash] Manga data as IndifferentHash
23
+ #
24
+ # @example Get basic info
25
+ # client.manga(1).info
26
+ # # => { data: { mal_id: 1, title: "Monster", ... } }
27
+ #
28
+ # @example Get full info
29
+ # client.manga(1).info(full: true)
30
+ # # => { data: { mal_id: 1, title: "...", relations: [...], external: [...] } }
31
+ def info(full: false)
32
+ path = full ? "manga/#{@id}/full" : "manga/#{@id}"
33
+ get(path)
34
+ end
35
+
36
+ # Returns characters for this manga
37
+ #
38
+ # @return [Hash] List of characters as IndifferentHash
39
+ #
40
+ # @example
41
+ # client.manga(1).characters
42
+ # # => { data: [{ character: { mal_id: 1, name: "Kenzou Tenma" }, role: "Main" }, ...] }
43
+ def characters
44
+ get("manga/#{@id}/characters")
45
+ end
46
+
47
+ # Returns news articles related to this manga
48
+ #
49
+ # @return [Hash] List of news articles as IndifferentHash
50
+ #
51
+ # @example
52
+ # client.manga(1).news
53
+ # # => { data: [{ mal_id: 1, title: "...", date: "...", author_username: "..." }, ...] }
54
+ def news
55
+ get("manga/#{@id}/news")
56
+ end
57
+
58
+ # Returns forum topics related to this manga
59
+ #
60
+ # @return [Hash] List of forum topics as IndifferentHash
61
+ #
62
+ # @example
63
+ # client.manga(1).forum
64
+ # # => { data: [{ mal_id: 1, title: "...", date: "...", author_username: "..." }, ...] }
65
+ def forum
66
+ get("manga/#{@id}/forum")
67
+ end
68
+
69
+ # Returns pictures of this manga
70
+ #
71
+ # @return [Hash] List of pictures as IndifferentHash
72
+ #
73
+ # @example
74
+ # client.manga(1).pictures
75
+ # # => { data: [{ jpg: { image_url: "https://..." }, webp: { ... } }, ...] }
76
+ def pictures
77
+ get("manga/#{@id}/pictures")
78
+ end
79
+
80
+ # Returns statistics for this manga
81
+ #
82
+ # @return [Hash] Statistics data as IndifferentHash
83
+ #
84
+ # @example
85
+ # client.manga(1).statistics
86
+ # # => { data: { reading: 12345, completed: 67890, on_hold: 1234, ... } }
87
+ def statistics
88
+ get("manga/#{@id}/statistics")
89
+ end
90
+
91
+ # Returns user recommendations for this manga
92
+ #
93
+ # @return [Hash] List of recommendations as IndifferentHash
94
+ #
95
+ # @example
96
+ # client.manga(1).recommendations
97
+ # # => { data: [{ entry: { mal_id: 5, title: "..." }, votes: 123 }, ...] }
98
+ def recommendations
99
+ get("manga/#{@id}/recommendations")
100
+ end
101
+
102
+ # Returns related anime/manga entries
103
+ #
104
+ # @return [Hash] List of relations as IndifferentHash
105
+ #
106
+ # @example
107
+ # client.manga(1).relations
108
+ # # => { data: [{ relation: "Adaptation", entry: [{ mal_id: 19, type: "anime", name: "..." }] }, ...] }
109
+ def relations
110
+ get("manga/#{@id}/relations")
111
+ end
112
+
113
+ # Returns external links for this manga
114
+ #
115
+ # @return [Hash] List of external links as IndifferentHash
116
+ #
117
+ # @example
118
+ # client.manga(1).external
119
+ # # => { data: [{ name: "Official Site", url: "https://..." }, ...] }
120
+ def external
121
+ get("manga/#{@id}/external")
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jikanrb
4
+ module Resources
5
+ # Resource class for accessing Person sub-resources from the Jikan API.
6
+ # Provides fluent API access to person details, anime staff positions,
7
+ # manga work, voice acting roles, and pictures.
8
+ #
9
+ # @example Basic usage
10
+ # client = Jikanrb::Client.new
11
+ # person = client.person(1)
12
+ # person.info # GET /people/1
13
+ # person.info(full: true) # GET /people/1/full
14
+ # person.animes # GET /people/1/anime
15
+ # person.voices # GET /people/1/voices
16
+ #
17
+ # @see https://docs.api.jikan.moe/#tag/people
18
+ class PersonResource < BaseResource
19
+ # Returns person details
20
+ #
21
+ # @param full [Boolean] If true, returns extended information including
22
+ # anime, manga, and voice acting roles
23
+ # @return [Hash] Person data as IndifferentHash
24
+ #
25
+ # @example Get basic info
26
+ # client.person(1).info
27
+ # # => { data: { mal_id: 1, name: "Tomokazu Seki", ... } }
28
+ #
29
+ # @example Get full info
30
+ # client.person(1).info(full: true)
31
+ # # => { data: { mal_id: 1, name: "...", anime: [...], voices: [...] } }
32
+ def info(full: false)
33
+ path = full ? "people/#{@id}/full" : "people/#{@id}"
34
+ get(path)
35
+ end
36
+
37
+ # Returns anime staff positions for this person
38
+ #
39
+ # @return [Hash] List of anime staff positions as IndifferentHash
40
+ #
41
+ # @example
42
+ # client.person(1).animes
43
+ # # => { data: [{ position: "Director", anime: { mal_id: 1, title: "..." } }, ...] }
44
+ def animes
45
+ get("people/#{@id}/anime")
46
+ end
47
+
48
+ # Returns manga work positions for this person
49
+ #
50
+ # @return [Hash] List of manga work positions as IndifferentHash
51
+ #
52
+ # @example
53
+ # client.person(1).mangas
54
+ # # => { data: [{ position: "Story & Art", manga: { mal_id: 1, title: "..." } }, ...] }
55
+ def mangas
56
+ get("people/#{@id}/manga")
57
+ end
58
+
59
+ # Returns voice acting roles for this person
60
+ #
61
+ # @return [Hash] List of voice acting roles as IndifferentHash
62
+ #
63
+ # @example
64
+ # client.person(1).voices
65
+ # # => { data: [{ role: "Main", character: { ... }, anime: { ... } }, ...] }
66
+ def voices
67
+ get("people/#{@id}/voices")
68
+ end
69
+
70
+ # Returns pictures of this person
71
+ #
72
+ # @return [Hash] List of pictures as IndifferentHash
73
+ #
74
+ # @example
75
+ # client.person(1).pictures
76
+ # # => { data: [{ jpg: { image_url: "https://..." } }, ...] }
77
+ def pictures
78
+ get("people/#{@id}/pictures")
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jikanrb
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/jikanrb.rb CHANGED
@@ -7,6 +7,13 @@ require_relative 'jikanrb/pagination'
7
7
  require_relative 'jikanrb/client'
8
8
  require_relative 'jikanrb/utils'
9
9
 
10
+ # Resources module - Fluent API for accessing sub-resources
11
+ require_relative 'jikanrb/resources/base_resource'
12
+ require_relative 'jikanrb/resources/anime_resource'
13
+ require_relative 'jikanrb/resources/manga_resource'
14
+ require_relative 'jikanrb/resources/character_resource'
15
+ require_relative 'jikanrb/resources/person_resource'
16
+
10
17
  # Jikanrb is a modern Ruby wrapper for the Jikan REST API v4.
11
18
  # Provides easy access to anime, manga, characters, and more from MyAnimeList.
12
19
  #
@@ -69,23 +76,23 @@ module Jikanrb
69
76
  # Allows using Jikanrb.anime(1) directly
70
77
 
71
78
  # @see Client#anime
72
- def anime(id, full: false)
73
- client.anime(id, full: full)
79
+ def anime(id)
80
+ client.anime(id)
74
81
  end
75
82
 
76
83
  # @see Client#manga
77
- def manga(id, full: false)
78
- client.manga(id, full: full)
84
+ def manga(id)
85
+ client.manga(id)
79
86
  end
80
87
 
81
88
  # @see Client#character
82
- def character(id, full: false)
83
- client.character(id, full: full)
89
+ def character(id)
90
+ client.character(id)
84
91
  end
85
92
 
86
93
  # @see Client#person
87
- def person(id, full: false)
88
- client.person(id, full: full)
94
+ def person(id)
95
+ client.person(id)
89
96
  end
90
97
 
91
98
  # @see Client#search_anime
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jikanrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergio Brocos
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-10 00:00:00.000000000 Z
11
+ date: 2026-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -58,7 +58,6 @@ executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
60
60
  files:
61
- - ".claude/settings.local.json"
62
61
  - ".ruby-version"
63
62
  - CHANGELOG.md
64
63
  - CODE_OF_CONDUCT.md
@@ -70,6 +69,11 @@ files:
70
69
  - lib/jikanrb/configuration.rb
71
70
  - lib/jikanrb/errors.rb
72
71
  - lib/jikanrb/pagination.rb
72
+ - lib/jikanrb/resources/anime_resource.rb
73
+ - lib/jikanrb/resources/base_resource.rb
74
+ - lib/jikanrb/resources/character_resource.rb
75
+ - lib/jikanrb/resources/manga_resource.rb
76
+ - lib/jikanrb/resources/person_resource.rb
73
77
  - lib/jikanrb/utils.rb
74
78
  - lib/jikanrb/version.rb
75
79
  - sig/jikanrb.rbs
@@ -1,8 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(rubocop:*)",
5
- "Bash(bundle exec rubocop:*)"
6
- ]
7
- }
8
- }