multiple fixes

- updated api docs
- fixed top reviews endpoint
- fixed reviews parsing
- added "contextual" boolean query string parameters, so params without value can be interpreted as "boolean". E.g. ?sfw or ?kid in the url would add "true" value to their corresponding field in the DTO
- fixed typesense issues
This commit is contained in:
pushrbx 2023-01-30 20:41:39 +00:00
parent bcb87084c5
commit 0211fc4128
35 changed files with 571 additions and 368 deletions

View File

@ -211,7 +211,7 @@ class Anime extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'start_date' => $this->convertToTimestamp($this->aired['from']),
'end_date' => $this->convertToTimestamp($this->aired['to']),
'title' => $this->title,

View File

@ -0,0 +1,25 @@
<?php
namespace App\Casts;
use Spatie\LaravelData\Casts\Cast;
use Spatie\LaravelData\Support\DataProperty;
/**
* This class ensures that "?sfw" and "?kids" boolean type query string parameters in the url would be interpreted as "true"
*/
final class ContextualBooleanCast implements Cast
{
public function cast(DataProperty $property, mixed $value, array $context): mixed
{
$propertyName = $property->name;
if (array_key_exists($propertyName, $context) && $context[$propertyName] === "")
{
return true;
}
return $value;
}
}

View File

@ -9,7 +9,7 @@ use Spatie\LaravelData\Exceptions\CannotCastEnum;
use Spatie\LaravelData\Support\DataProperty;
use Throwable;
class EnumCast implements Cast
final class EnumCast implements Cast
{
public function __construct(
protected ?string $type = null

View File

@ -71,7 +71,7 @@ class Character extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'name' => $this->name,
'name_kanji' => $this->name_kanji,
'member_favorites' => $this->member_favorites

View File

@ -78,7 +78,7 @@ class Club extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'name' => $this->name,
'category' => $this->category,
'created' => $this->convertToTimestamp($this->created),

View File

@ -2,7 +2,9 @@
namespace App\Dto;
use App\Casts\ContextualBooleanCast;
use App\Casts\EnumCast;
use App\Dto\Concerns\PreparesData;
use App\Enums\MediaReviewsSortEnum;
use App\Rules\Attributes\EnumValidation;
use Illuminate\Http\JsonResponse;
@ -17,15 +19,17 @@ use Spatie\LaravelData\Optional;
*/
final class AnimeReviewsLookupCommand extends LookupDataCommand
{
use PreparesData;
#[Numeric, Min(1)]
public int|Optional $page = 1;
#[WithCast(EnumCast::class, MediaReviewsSortEnum::class), EnumValidation(MediaReviewsSortEnum::class)]
public MediaReviewsSortEnum|Optional $sort;
#[BooleanType]
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $spoilers;
#[BooleanType]
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $preliminary;
}

View File

@ -9,7 +9,7 @@ use Spatie\LaravelData\Optional;
trait HasLimitParameter
{
use MapsDefaultLimitParameter;
use PreparesData;
#[IntegerType, Min(1), MaxLimitWithFallback]
public int|Optional $limit;

View File

@ -0,0 +1,16 @@
<?php
namespace App\Dto\Concerns;
use App\Casts\ContextualBooleanCast;
use Spatie\LaravelData\Attributes\Validation\BooleanType;
use Spatie\LaravelData\Attributes\WithCast;
use Spatie\LaravelData\Optional;
trait HasSfwParameter
{
use PreparesData;
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $sfw = false;
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Dto\Concerns;
use Illuminate\Support\Collection;
use Illuminate\Support\Env;
trait MapsDefaultLimitParameter
{
public static function prepareForPipeline(Collection $properties): Collection
{
if (!$properties->has("limit"))
{
/** @noinspection PhpUndefinedFieldInspection */
$properties->put("limit", Env::get("MAX_RESULTS_PER_PAGE",
property_exists(static::class, "defaultLimit") ? static::$defaultLimit : 25));
}
return $properties;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Dto\Concerns;
use Illuminate\Support\Collection;
use Illuminate\Support\Env;
use \ReflectionClass;
use Spatie\LaravelData\Support\DataConfig;
/**
* A trait for preparing the incoming data before passing it through the data pipeline.
* All preparation logic lives here.
* https://spatie.be/docs/laravel-data/v2/advanced-usage/pipeline
*/
trait PreparesData
{
public static function prepareForPipeline(Collection $properties): Collection
{
// let's always set the limit parameter to the globally configured default value
if (property_exists(static::class, "limit") && !$properties->has("limit")) {
/** @noinspection PhpUndefinedFieldInspection */
$properties->put("limit", Env::get("MAX_RESULTS_PER_PAGE",
property_exists(static::class, "defaultLimit") ? static::$defaultLimit : 25));
}
// we want to cast "true" and "false" string values to boolean before validation, so let's take all properties
// of the class which are bool or bool|Optional type, and using their name read the values from the incoming
// collection, and if they are present have such a value, convert them.
$dataClass = app(DataConfig::class)->getDataClass(static::class);
foreach ($dataClass->properties as $property) {
if (!$property->type->acceptsType("bool")) {
continue;
}
// the name can be different in the $properties variable, so let's check if there is an input name mapping
// for the property and use that instead if present.
$propertyRawName = $property->inputMappedName ?? $property->name;
if ($properties->has($propertyRawName)) {
$propertyVal = $properties->get($propertyRawName);
if ($propertyVal === "true") {
$propertyVal = true;
}
if ($propertyVal === "false") {
$propertyVal = false;
}
$properties->put($propertyRawName, $propertyVal);
}
}
return $properties;
}
}

View File

@ -3,7 +3,9 @@
namespace App\Dto;
use App\Casts\ContextualBooleanCast;
use App\Casts\EnumCast;
use App\Dto\Concerns\PreparesData;
use App\Enums\MediaReviewsSortEnum;
use App\Rules\Attributes\EnumValidation;
use Illuminate\Http\JsonResponse;
@ -18,15 +20,17 @@ use Spatie\LaravelData\Optional;
*/
final class MangaReviewsLookupCommand extends LookupDataCommand
{
use PreparesData;
#[Numeric, Min(1)]
public int|Optional $page = 1;
#[WithCast(EnumCast::class, MediaReviewsSortEnum::class), EnumValidation(MediaReviewsSortEnum::class)]
public MediaReviewsSortEnum|Optional $sort;
#[BooleanType]
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $spoilers;
#[BooleanType]
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $preliminary;
}

View File

@ -2,6 +2,7 @@
namespace App\Dto;
use App\Dto\Concerns\HasSfwParameter;
use Carbon\CarbonImmutable;
use Illuminate\Validation\Validator;
use Spatie\LaravelData\Attributes\MapInputName;
@ -22,6 +23,8 @@ use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer;
class MediaSearchCommand extends SearchCommand
{
use HasSfwParameter;
#[MapInputName("min_score"), MapOutputName("min_score"), Between(0.00, 10.00), Numeric]
public float|Optional $minScore;
@ -31,8 +34,6 @@ class MediaSearchCommand extends SearchCommand
#[Between(1.00, 9.99), Numeric, Prohibits(["min_score", "max_score"])]
public float|Optional $score;
public bool|Optional $sfw;
public string|Optional $genres;
#[MapInputName("genres_exclude"), MapOutputName("genres_exclude")]

View File

@ -3,11 +3,14 @@
namespace App\Dto;
use App\Casts\ContextualBooleanCast;
use App\Casts\EnumCast;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use App\Dto\Concerns\HasLimitParameter;
use App\Dto\Concerns\HasPageParameter;
use App\Dto\Concerns\HasSfwParameter;
use App\Dto\Concerns\PreparesData;
use App\Enums\AnimeScheduleFilterEnum;
use App\Rules\Attributes\EnumValidation;
use Illuminate\Http\JsonResponse;
@ -22,14 +25,11 @@ use Spatie\LaravelData\Optional;
*/
final class QueryAnimeSchedulesCommand extends Data implements DataRequest
{
use HasLimitParameter, HasRequestFingerprint, HasPageParameter;
use HasLimitParameter, HasRequestFingerprint, HasPageParameter, PreparesData, HasSfwParameter;
#[BooleanType]
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $kids = false;
#[BooleanType]
public bool|Optional $sfw = false;
#[WithCast(EnumCast::class, AnimeScheduleFilterEnum::class), EnumValidation(AnimeScheduleFilterEnum::class)]
public ?AnimeScheduleFilterEnum $filter;

View File

@ -3,16 +3,14 @@
namespace App\Dto;
use App\Contracts\DataRequest;
use App\Dto\Concerns\HasSfwParameter;
use App\Http\Resources\V4\AnimeResource;
use Spatie\LaravelData\Attributes\Validation\BooleanType;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Optional;
/**
* @implements DataRequest<AnimeResource>
*/
final class QueryRandomAnimeCommand extends Data implements DataRequest
{
#[BooleanType]
public bool|Optional $sfw;
use HasSfwParameter;
}

View File

@ -3,6 +3,7 @@
namespace App\Dto;
use App\Contracts\DataRequest;
use App\Dto\Concerns\HasSfwParameter;
use App\Http\Resources\V4\MangaResource;
use Spatie\LaravelData\Attributes\Validation\BooleanType;
use Spatie\LaravelData\Data;
@ -13,6 +14,5 @@ use Spatie\LaravelData\Optional;
*/
final class QueryRandomMangaCommand extends Data implements DataRequest
{
#[BooleanType]
public bool|Optional $sfw;
use HasSfwParameter;
}

View File

@ -3,10 +3,12 @@
namespace App\Dto;
use App\Casts\ContextualBooleanCast;
use App\Casts\EnumCast;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use App\Dto\Concerns\HasPageParameter;
use App\Dto\Concerns\PreparesData;
use App\Enums\MediaReviewsSortEnum;
use App\Http\Resources\V4\ResultsResource;
use App\Rules\Attributes\EnumValidation;
@ -20,14 +22,14 @@ use Spatie\LaravelData\Optional;
*/
abstract class QueryReviewsCommand extends Data implements DataRequest
{
use HasRequestFingerprint, HasPageParameter;
use HasRequestFingerprint, HasPageParameter, PreparesData;
#[WithCast(EnumCast::class, MediaReviewsSortEnum::class), EnumValidation(MediaReviewsSortEnum::class)]
public MediaReviewsSortEnum|Optional $sort;
#[BooleanType]
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $spoilers;
#[BooleanType]
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $preliminary;
}

View File

@ -2,14 +2,31 @@
namespace App\Dto;
use App\Casts\ContextualBooleanCast;
use App\Casts\EnumCast;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use App\Dto\Concerns\PreparesData;
use App\Enums\TopAnimeFilterEnum;
use App\Enums\TopReviewsTypeEnum;
use App\Rules\Attributes\EnumValidation;
use Illuminate\Http\JsonResponse;
use Spatie\LaravelData\Attributes\WithCast;
use Spatie\LaravelData\Optional;
/**
* @implements DataRequest<JsonResponse>
*/
final class QueryTopReviewsCommand extends QueryTopItemsCommand implements DataRequest
{
use HasRequestFingerprint;
use HasRequestFingerprint, PreparesData;
#[WithCast(EnumCast::class, TopAnimeFilterEnum::class), EnumValidation(TopReviewsTypeEnum::class)]
public TopReviewsTypeEnum|Optional $type;
#[WithCast(ContextualBooleanCast::class)]
public bool|Optional $spoilers;
#[WithCast(ContextualBooleanCast::class)]
public bool|Optional $preliminary;
}

View File

@ -14,7 +14,7 @@ use Spatie\Enum\Laravel\Enum;
* @method static self episodes()
* @method static self score()
* @method static self scored_by()
* @method static self rank
* @method static self rank()
* @method static self popularity()
* @method static self members()
* @method static self favorites()

View File

@ -3,14 +3,21 @@
namespace App\Enums;
use Jikan\Helper\Constants as JikanConstants;
use Spatie\Enum\Laravel\Enum;
/**
* @method static self any()
* @method static self male()
* @method static self female()
* @method static self nonbinary()
* @OA\Schema(
* schema="users_search_query_gender",
* description="Users Search Query Gender.",
* type="string",
* enum={"any","male","female","nonbinary"}
* )
*/
final class GenderEnum extends \Spatie\Enum\Laravel\Enum
final class GenderEnum extends Enum
{
protected static function labels(): array
{

View File

@ -0,0 +1,20 @@
<?php
namespace App\Enums;
use Spatie\Enum\Laravel\Enum;
/**
* @method static self anime()
* @method static self manga()
* @OA\Schema(
* schema="top_reviews_type_enum",
* description="The type of reviews to filter by. Defaults to anime.",
* type="string",
* enum={"anime","manga"}
* )
*/
final class TopReviewsTypeEnum extends Enum
{
}

View File

@ -3,12 +3,13 @@
namespace App\Features;
use App\Dto\QueryTopReviewsCommand;
use App\Enums\TopReviewsTypeEnum;
use App\Support\CachedData;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Jikan\Helper\Constants;
use Jikan\MyAnimeList\MalClient;
use Jikan\Request\Reviews\RecentReviewsRequest;
use Jikan\Request\Reviews\ReviewsRequest;
/**
* @extends RequestHandlerWithScraperCache<QueryTopReviewsCommand, JsonResponse>
@ -25,9 +26,12 @@ final class QueryTopReviewsHandler extends RequestHandlerWithScraperCache
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
{
$type = $requestParams->get("type", TopReviewsTypeEnum::anime()->value);
$spoilers = $requestParams->get("spoilers", true);
$preliminary = $requestParams->get("preliminary", true);
return $this->scraperService->findList(
$requestFingerPrint,
fn (MalClient $jikan, ?int $page = null) => $jikan->getRecentReviews(new RecentReviewsRequest(Constants::RECENT_REVIEW_BEST_VOTED, $page)),
fn (MalClient $jikan, ?int $page = null) => $jikan->getReviews(new ReviewsRequest($type, $page, $spoilers, $preliminary)),
$requestParams->get("page"));
}
}

View File

@ -60,7 +60,7 @@ class GenreAnime extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'name' => $this->name,
'count' => $this->count
];

View File

@ -59,7 +59,7 @@ class GenreManga extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'name' => $this->name,
'count' => $this->count
];

View File

@ -2,33 +2,12 @@
namespace App\Http\Controllers\V4DB;
use App\Anime;
use App\Character;
use App\Dto\QueryRandomAnimeCommand;
use App\Dto\QueryRandomCharacterCommand;
use App\Dto\QueryRandomMangaCommand;
use App\Dto\QueryRandomPersonCommand;
use App\Dto\QueryRandomUserCommand;
use App\Http\HttpHelper;
use App\Http\HttpResponse;
use App\Http\Resources\V4\AnimeCollection;
use App\Http\Resources\V4\AnimeResource;
use App\Http\Resources\V4\CharacterCollection;
use App\Http\Resources\V4\CharacterResource;
use App\Http\Resources\V4\CommonResource;
use App\Http\Resources\V4\MangaCollection;
use App\Http\Resources\V4\MangaResource;
use App\Http\Resources\V4\PersonCollection;
use App\Http\Resources\V4\PersonResource;
use App\Http\Resources\V4\ProfileResource;
use App\Http\Resources\V4\ResultsResource;
use App\Http\Resources\V4\UserCollection;
use App\Manga;
use App\Person;
use App\Profile;
use App\User;
use Illuminate\Http\Request;
use MongoDB\BSON\UTCDateTime;
class RandomController extends Controller
{

View File

@ -28,7 +28,7 @@ class TopController extends Controller
* name="filter",
* in="query",
* required=false,
* @OA\Schema(ref="#/components/schemas/top_anime_filter)
* @OA\Schema(ref="#/components/schemas/top_anime_filter")
* ),
*
* @OA\Parameter(
@ -82,7 +82,7 @@ class TopController extends Controller
* name="filter",
* in="query",
* required=false,
* @OA\Schema(ref="#/components/schemas/top_manga_filter)
* @OA\Schema(ref="#/components/schemas/top_manga_filter")
* ),
*
* @OA\Parameter(ref="#/components/parameters/page"),
@ -168,6 +168,29 @@ class TopController extends Controller
*
* @OA\Parameter(ref="#/components/parameters/page"),
*
* @OA\Parameter(
* name="type",
* in="query",
* required=false,
* @OA\Schema(ref="#/components/schemas/top_reviews_type_enum")
* ),
*
* @OA\Parameter(
* name="preliminary",
* in="query",
* required=false,
* description="Whether the results include preliminary reviews or not. Defaults to true.",
* @OA\Schema(type="boolean")
* ),
*
* @OA\Parameter(
* name="spoilers",
* in="query",
* required=false,
* description="Whether the results include reviews with spoilers or not. Defaults to true.",
* @OA\Schema(type="boolean")
* ),
*
* @OA\Response(
* response="200",
* description="Returns top reviews",

View File

@ -38,32 +38,16 @@ class HttpHelper
return (int) str_replace('v', '', $request->segment(1));
}
public static function serializeEmptyObjects(string $requestType, array $data)
public static function serializeEmptyObjects(string $requestType, array $data): array
{
if (!($requestType === 'anime' || $requestType === 'manga')) {
return $data;
}
if (isset($data['related']) && \count($data['related']) === 0) {
$data['related'] = new \stdClass();
return self::serializeEmptyObjectsControllerLevel($data);
}
if (isset($data['related'])) {
$related = $data['related'];
$data['related'] = [];
foreach ($related as $relation => $items) {
$data['related'][] = [
'relation' => $relation,
'entry' => $items
];
}
}
return $data;
}
public static function serializeEmptyObjectsControllerLevel(array $data)
public static function serializeEmptyObjectsControllerLevel(array $data): array
{
if (isset($data['related']) && \count($data['related']) === 0) {
$data['related'] = new \stdClass();

View File

@ -94,12 +94,12 @@ class ReviewsResource extends JsonResource
* ),
* @OA\Property (
* property="is_spoiler",
* type="bool",
* type="boolean",
* description="The review contains spoiler"
* ),
* @OA\Property (
* property="is_preliminary",
* type="bool",
* type="boolean",
* description="The review was made before the entry was completed"
* ),
* ),
@ -190,12 +190,12 @@ class ReviewsResource extends JsonResource
* ),
* @OA\Property (
* property="is_spoiler",
* type="bool",
* type="boolean",
* description="The review contains spoiler"
* ),
* @OA\Property (
* property="is_preliminary",
* type="bool",
* type="boolean",
* description="The review was made before the entry was completed"
* ),
* @OA\Property(

View File

@ -21,7 +21,7 @@ class JikanApiModel extends \Jenssegers\Mongodb\Eloquent\Model
/** @noinspection PhpUnused */
public function scopeRandom(Builder $query, int $numberOfRandomItems = 1): Collection
{
return $query->raw(fn(\MongoDB\Collection $collection) => $collection->aggregate([
return $query->raw(fn(\Jenssegers\Mongodb\Collection $collection) => $collection->aggregate([
['$sample' => ['size' => $numberOfRandomItems]]
]));
}

View File

@ -64,7 +64,7 @@ class Magazine extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'name' => $this->name,
'count' => $this->count
];

View File

@ -121,7 +121,7 @@ class Manga extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'start_date' => $this->convertToTimestamp($this->published['from']),
'end_date' => $this->convertToTimestamp($this->published['to']),
'title' => $this->title,

View File

@ -76,7 +76,7 @@ class Person extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'name' => $this->name,
'given_name' => $this->given_name,
'family_name' => $this->family_name,

View File

@ -59,9 +59,12 @@ class Producers extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'url' => !is_null($this->url) ? collect(explode('/', $this->url))->last() : '',
'titles' => !is_null($this->titles) ? $this->titles : ['']
'mal_id' => (int) $this->mal_id,
'url' => !is_null($this->url) ? $this->url : '',
'titles' => !is_null($this->titles) ? collect($this->titles)->map(fn ($x) => $x["title"])->toArray() : [''],
'established' => $this->convertToTimestamp($this->established),
'favorites' => $this->favorites,
'count' => $this->count
];
}

View File

@ -8,7 +8,7 @@ use Jikan\Request\User\UserProfileRequest;
class Profile extends JikanApiSearchableModel
{
use FilteredByLetter;
protected array $filters = ["order_by", "sort", "letter"];
protected array $filters = [];
/**
* The attributes that are mass assignable.
@ -61,7 +61,7 @@ class Profile extends JikanApiSearchableModel
{
return [
'id' => (string) $this->mal_id,
'mal_id' => (string) $this->mal_id,
'mal_id' => (int) $this->mal_id,
'username' => $this->username
];
}

View File

@ -39,3 +39,18 @@ if (!function_exists('rescue')) {
}
}
}
if (!function_exists('to_boolean')) {
/**
* Convert to boolean
*
* @param $booleable
* @return boolean
*/
function to_boolean($booleable): bool
{
return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
}

View File

@ -2982,6 +2982,53 @@
}
}
},
"/seasons/now": {
"get": {
"tags": [
"seasons"
],
"operationId": "getSeasonNow",
"parameters": [
{
"$ref": "#/components/parameters/page"
},
{
"$ref": "#/components/parameters/limit"
},
{
"name": "filter",
"in": "query",
"description": "Entry types",
"schema": {
"type": "string",
"enum": [
"tv",
"movie",
"ova",
"special",
"ona",
"music"
]
}
}
],
"responses": {
"200": {
"description": "Returns current seasonal anime",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/anime_search"
}
}
}
},
"400": {
"description": "Error: Bad request. When required parameters were not supplied."
}
}
}
},
"/seasons/{year}/{season}": {
"get": {
"tags": [
@ -3023,6 +3070,9 @@
},
{
"$ref": "#/components/parameters/page"
},
{
"$ref": "#/components/parameters/limit"
}
],
"responses": {
@ -3042,34 +3092,6 @@
}
}
},
"/seasons/now": {
"get": {
"tags": [
"seasons"
],
"operationId": "getSeasonNow",
"parameters": [
{
"$ref": "#/components/parameters/page"
}
],
"responses": {
"200": {
"description": "Returns current seasonal anime",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/anime_search"
}
}
}
},
"400": {
"description": "Error: Bad request. When required parameters were not supplied."
}
}
}
},
"/seasons": {
"get": {
"tags": [
@ -3118,6 +3140,9 @@
},
{
"$ref": "#/components/parameters/page"
},
{
"$ref": "#/components/parameters/limit"
}
],
"responses": {
@ -3157,13 +3182,22 @@
"in": "query",
"required": false,
"schema": {
"type": "string",
"enum": [
"airing",
"upcoming",
"bypopularity",
"favorite"
]
"$ref": "#/components/schemas/top_anime_filter"
}
},
{
"name": "rating",
"in": "query",
"schema": {
"$ref": "#/components/schemas/anime_search_query_rating"
}
},
{
"name": "sfw",
"in": "query",
"description": "Filter out Adult entries",
"schema": {
"type": "boolean"
}
},
{
@ -3210,13 +3244,7 @@
"in": "query",
"required": false,
"schema": {
"type": "string",
"enum": [
"publishing",
"upcoming",
"bypopularity",
"favorite"
]
"$ref": "#/components/schemas/top_manga_filter"
}
},
{
@ -3314,6 +3342,32 @@
"parameters": [
{
"$ref": "#/components/parameters/page"
},
{
"name": "type",
"in": "query",
"required": false,
"schema": {
"$ref": "#/components/schemas/top_reviews_type_enum"
}
},
{
"name": "preliminary",
"in": "query",
"description": "Whether the results include preliminary reviews or not. Defaults to true.",
"required": false,
"schema": {
"type": "boolean"
}
},
{
"name": "spoilers",
"in": "query",
"description": "Whether the results include reviews with spoilers or not. Defaults to true.",
"required": false,
"schema": {
"type": "boolean"
}
}
],
"responses": {
@ -3974,11 +4028,6 @@
"watch"
],
"operationId": "getWatchRecentEpisodes",
"parameters": [
{
"$ref": "#/components/parameters/limit"
}
],
"responses": {
"200": {
"description": "Returns Recently Added Episodes",
@ -4002,11 +4051,6 @@
"watch"
],
"operationId": "getWatchPopularEpisodes",
"parameters": [
{
"$ref": "#/components/parameters/limit"
}
],
"responses": {
"200": {
"description": "Returns Popular Episodes",
@ -4030,6 +4074,11 @@
"watch"
],
"operationId": "getWatchRecentPromos",
"parameters": [
{
"$ref": "#/components/parameters/page"
}
],
"responses": {
"200": {
"description": "Returns Recently Added Promotional Videos",
@ -4053,11 +4102,6 @@
"watch"
],
"operationId": "getWatchPopularPromos",
"parameters": [
{
"$ref": "#/components/parameters/limit"
}
],
"responses": {
"200": {
"description": "Returns Popular Promotional Videos",
@ -4078,6 +4122,231 @@
},
"components": {
"schemas": {
"anime_search_query_orderby": {
"description": "Available Anime order_by properties",
"type": "string",
"enum": [
"mal_id",
"title",
"type",
"rating",
"start_date",
"end_date",
"episodes",
"score",
"scored_by",
"rank",
"popularity",
"members",
"favorites"
]
},
"anime_search_query_rating": {
"description": "Available Anime audience ratings<br><br><b>Ratings</b><br><ul><li>G - All Ages</li><li>PG - Children</li><li>PG-13 - Teens 13 or older</li><li>R - 17+ (violence & profanity)</li><li>R+ - Mild Nudity</li><li>Rx - Hentai</li></ul>",
"type": "string",
"enum": [
"g",
"pg",
"pg13",
"r17",
"r",
"rx"
]
},
"anime_search_query_status": {
"description": "Available Anime statuses",
"type": "string",
"enum": [
"airing",
"complete",
"upcoming"
]
},
"anime_search_query_type": {
"description": "Available Anime types",
"type": "string",
"enum": [
"tv",
"movie",
"ova",
"special",
"ona",
"music"
]
},
"characters_search_query_orderby": {
"description": "Available Character order_by properties",
"type": "string",
"enum": [
"mal_id",
"name",
"favorites"
]
},
"club_search_query_category": {
"description": "Club Search Query Category",
"type": "string",
"enum": [
"anime",
"manga",
"actors_and_artists",
"characters",
"cities_and_neighborhoods",
"companies",
"conventions",
"games",
"japan",
"music",
"other",
"schools"
]
},
"club_search_query_orderby": {
"description": "Club Search Query OrderBy",
"type": "string",
"enum": [
"mal_id",
"name",
"members_count",
"created"
]
},
"club_search_query_type": {
"description": "Club Search Query Type",
"type": "string",
"enum": [
"public",
"private",
"secret"
]
},
"users_search_query_gender": {
"description": "Users Search Query Gender.",
"type": "string",
"enum": [
"any",
"male",
"female",
"nonbinary"
]
},
"genre_query_filter": {
"description": "Filter genres by type",
"type": "string",
"enum": [
"genres",
"explicit_genres",
"themes",
"demographics"
]
},
"magazines_query_orderby": {
"description": "Order by magazine data",
"type": "string",
"enum": [
"mal_id",
"name",
"count"
]
},
"manga_search_query_orderby": {
"description": "Available Manga order_by properties",
"type": "string",
"enum": [
"mal_id",
"title",
"start_date",
"end_date",
"chapters",
"volumes",
"score",
"scored_by",
"rank",
"popularity",
"members",
"favorites"
]
},
"manga_search_query_status": {
"description": "Available Manga statuses",
"type": "string",
"enum": [
"publishing",
"complete",
"hiatus",
"discontinued",
"upcoming"
]
},
"manga_search_query_type": {
"description": "Available Manga types",
"type": "string",
"enum": [
"manga",
"novel",
"lightnovel",
"oneshot",
"doujin",
"manhwa",
"manhua"
]
},
"people_search_query_orderby": {
"description": "Available People order_by properties",
"type": "string",
"enum": [
"mal_id",
"name",
"birthday",
"favorites"
]
},
"producers_query_orderby": {
"description": "Producers Search Query Order By",
"type": "string",
"enum": [
"mal_id",
"count",
"favorites",
"established"
]
},
"search_query_sort": {
"description": "Search query sort direction",
"type": "string",
"enum": [
"desc",
"asc"
]
},
"top_anime_filter": {
"description": "Top items filter types",
"type": "string",
"enum": [
"airing",
"upcoming",
"bypopularity",
"favorite"
]
},
"top_manga_filter": {
"description": "Top items filter types",
"type": "string",
"enum": [
"publishing",
"upcoming",
"bypopularity",
"favorite"
]
},
"top_reviews_type_enum": {
"description": "The type of reviews to filter by. Defaults to anime.",
"type": "string",
"enum": [
"anime",
"manga"
]
},
"anime_episodes": {
"description": "Anime Episodes Resource",
"allOf": [
@ -4231,15 +4500,6 @@
},
"type": "object"
},
"magazines_query_orderby": {
"description": "Order by magazine data",
"type": "string",
"enum": [
"mal_id",
"name",
"count"
]
},
"manga_news": {
"description": "Manga News Resource",
"allOf": [
@ -4320,14 +4580,6 @@
}
]
},
"search_query_sort": {
"description": "Characters Search Query Sort",
"type": "string",
"enum": [
"desc",
"asc"
]
},
"users_search": {
"description": "User Results",
"allOf": [
@ -4364,16 +4616,6 @@
}
]
},
"producers_query_orderby": {
"description": "Producers Search Query Order By",
"type": "string",
"enum": [
"mal_id",
"count",
"favorites",
"established"
]
},
"seasons": {
"description": "List of available seasons",
"properties": {
@ -4590,167 +4832,6 @@
}
]
},
"anime_search_query_type": {
"description": "Available Anime types",
"type": "string",
"enum": [
"tv",
"movie",
"ova",
"special",
"ona",
"music"
]
},
"anime_search_query_status": {
"description": "Available Anime statuses",
"type": "string",
"enum": [
"airing",
"complete",
"upcoming"
]
},
"anime_search_query_rating": {
"description": "Available Anime audience ratings<br><br><b>Ratings</b><br><ul><li>G - All Ages</li><li>PG - Children</li><li>PG-13 - Teens 13 or older</li><li>R - 17+ (violence & profanity)</li><li>R+ - Mild Nudity</li><li>Rx - Hentai</li></ul>",
"type": "string",
"enum": [
"g",
"pg",
"pg13",
"r17",
"r",
"rx"
]
},
"anime_search_query_orderby": {
"description": "Available Anime order_by properties",
"type": "string",
"enum": [
"mal_id",
"title",
"type",
"rating",
"start_date",
"end_date",
"episodes",
"score",
"scored_by",
"rank",
"popularity",
"members",
"favorites"
]
},
"characters_search_query_orderby": {
"description": "Available Character order_by properties",
"type": "string",
"enum": [
"mal_id",
"name",
"favorites"
]
},
"club_search_query_type": {
"description": "Club Search Query Type",
"type": "string",
"enum": [
"public",
"private",
"secret"
]
},
"club_search_query_category": {
"description": "Club Search Query Category",
"type": "string",
"enum": [
"anime",
"manga",
"actors_and_artists",
"characters",
"cities_and_neighborhoods",
"companies",
"conventions",
"games",
"japan",
"music",
"other",
"schools"
]
},
"club_search_query_orderby": {
"description": "Club Search Query OrderBy",
"type": "string",
"enum": [
"mal_id",
"title",
"members_count",
"pictures_count",
"created"
]
},
"manga_search_query_type": {
"description": "Available Manga types",
"type": "string",
"enum": [
"manga",
"novel",
"lightnovel",
"oneshot",
"doujin",
"manhwa",
"manhua"
]
},
"manga_search_query_status": {
"description": "Available Manga statuses",
"type": "string",
"enum": [
"publishing",
"complete",
"hiatus",
"discontinued",
"upcoming"
]
},
"manga_search_query_orderby": {
"description": "Available Manga order_by properties",
"type": "string",
"enum": [
"mal_id",
"title",
"start_date",
"end_date",
"chapters",
"volumes",
"score",
"scored_by",
"rank",
"popularity",
"members",
"favorites"
]
},
"people_search_query_orderby": {
"description": "Available People order_by properties",
"type": "string",
"enum": [
"mal_id",
"name",
"birthday",
"favorites"
]
},
"users_search_query_gender": {
"description": "Users Search Query Gender",
"type": "string",
"enum": [
"any",
"male",
"female",
"nonbinary"
]
},
"anime_characters": {
"description": "Anime Characters Resource",
"properties": {
@ -6649,16 +6730,6 @@
},
"type": "object"
},
"genre_query_filter": {
"description": "Filter genres by type",
"type": "string",
"enum": [
"genres",
"explicit_genres",
"themes",
"demographics"
]
},
"genre": {
"description": "Genre Resource",
"properties": {
@ -8564,11 +8635,11 @@
},
"is_spoiler": {
"description": "The review contains spoiler",
"type": "bool"
"type": "boolean"
},
"is_preliminary": {
"description": "The review was made before the entry was completed",
"type": "bool"
"type": "boolean"
}
},
"type": "object"
@ -8646,11 +8717,11 @@
},
"is_spoiler": {
"description": "The review contains spoiler",
"type": "bool"
"type": "boolean"
},
"is_preliminary": {
"description": "The review was made before the entry was completed",
"type": "bool"
"type": "boolean"
},
"episodes_watched": {
"description": "Number of episodes watched",