feat: create route to return title information
Summary: Test Plan:
This commit is contained in:
@@ -13,3 +13,7 @@ pre-commit hook for Sapling (`.sl/config`):
|
|||||||
[hooks]
|
[hooks]
|
||||||
precommit = echo $HG_PARENT1 && bun prettier $(sl show -T "{file_adds} {file_mods}\n\n" $HG_PARENT1 --stat | head -n 1) --write
|
precommit = echo $HG_PARENT1 && bun prettier $(sl show -T "{file_adds} {file_mods}\n\n" $HG_PARENT1 --stat | head -n 1) --write
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
If a route is internal-only or doesn't need to appear on the OpenAPI spec (that's autogenerated by Hono), use the `Hono` class. Otherwise, use the `OpenAPIHono` class from `@hono/zod-openapi`.
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/zod-openapi": "^0.12.0",
|
"@hono/zod-openapi": "^0.12.0",
|
||||||
|
"gql.tada": "^1.7.4",
|
||||||
|
"graphql-request": "^7.0.1",
|
||||||
"hono": "^4.3.6",
|
"hono": "^4.3.6",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@0no-co/graphqlsp": "^1.12.3",
|
||||||
"@cloudflare/workers-types": "^4.20240403.0",
|
"@cloudflare/workers-types": "^4.20240403.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/bun": "^1.1.2",
|
"@types/bun": "^1.1.2",
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ const app = new OpenAPIHono();
|
|||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: "/",
|
path: "/",
|
||||||
|
summary: "Health check",
|
||||||
|
operationId: "healthCheck",
|
||||||
|
tags: ["aniplay"],
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
@@ -14,7 +17,7 @@ const route = createRoute({
|
|||||||
schema: SuccessResponseSchema(),
|
schema: SuccessResponseSchema(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: "Retrieve the user",
|
description: "Server is up and running!",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
76
src/controllers/title/amvstrm.ts
Normal file
76
src/controllers/title/amvstrm.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { Title } from "~/types/title";
|
||||||
|
|
||||||
|
export async function fetchTitleFromAmvstrm(
|
||||||
|
aniListId: number,
|
||||||
|
): Promise<Title | undefined> {
|
||||||
|
return Promise.all([
|
||||||
|
fetch(`https://api-amvstrm.nyt92.eu.org/api/v2/info/${aniListId}`).then(
|
||||||
|
(res) => res.json() as Promise<any>,
|
||||||
|
),
|
||||||
|
fetchMissingInformationFromAnify(aniListId).catch((err) => {
|
||||||
|
console.error("Failed to get missing information from Anify", err);
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
]).then(
|
||||||
|
async ([
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
idMal,
|
||||||
|
title: { english: englishTitle, userPreferred: userPreferredTitle },
|
||||||
|
description,
|
||||||
|
episodes,
|
||||||
|
genres,
|
||||||
|
status,
|
||||||
|
bannerImage,
|
||||||
|
coverImage: {
|
||||||
|
extraLarge: extraLargeCoverImage,
|
||||||
|
large: largeCoverImage,
|
||||||
|
medium: mediumCoverImage,
|
||||||
|
},
|
||||||
|
countryOfOrigin,
|
||||||
|
nextair: nextAiringEpisode,
|
||||||
|
score: { averageScore },
|
||||||
|
},
|
||||||
|
anifyInfo,
|
||||||
|
]) => {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
idMal,
|
||||||
|
title: {
|
||||||
|
userPreferred: userPreferredTitle,
|
||||||
|
english: englishTitle,
|
||||||
|
},
|
||||||
|
description,
|
||||||
|
episodes,
|
||||||
|
genres,
|
||||||
|
status,
|
||||||
|
averageScore,
|
||||||
|
bannerImage: bannerImage ?? anifyInfo?.bannerImage,
|
||||||
|
coverImage: {
|
||||||
|
extraLarge: extraLargeCoverImage,
|
||||||
|
large: largeCoverImage,
|
||||||
|
medium: mediumCoverImage,
|
||||||
|
},
|
||||||
|
countryOfOrigin: countryOfOrigin ?? anifyInfo?.countryOfOrigin,
|
||||||
|
nextAiringEpisode,
|
||||||
|
mediaListEntry: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnifyInformation = {
|
||||||
|
bannerImage: string | null;
|
||||||
|
countryOfOrigin: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function fetchMissingInformationFromAnify(
|
||||||
|
aniListId: number,
|
||||||
|
): Promise<AnifyInformation> {
|
||||||
|
return fetch(`https://api.anify.tv/info?id=${aniListId}`)
|
||||||
|
.then((res) => res.json() as Promise<AnifyInformation>)
|
||||||
|
.then(({ bannerImage, countryOfOrigin }) => ({
|
||||||
|
bannerImage,
|
||||||
|
countryOfOrigin,
|
||||||
|
}));
|
||||||
|
}
|
||||||
32
src/controllers/title/anilist.ts
Normal file
32
src/controllers/title/anilist.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { graphql } from "gql.tada";
|
||||||
|
import { GraphQLClient } from "graphql-request";
|
||||||
|
|
||||||
|
import type { Title } from "~/types/title";
|
||||||
|
|
||||||
|
import { MediaFragment } from "./mediaFragment";
|
||||||
|
|
||||||
|
const GetTitleQuery = graphql(
|
||||||
|
`
|
||||||
|
query GetTitle($id: Int!) {
|
||||||
|
Media(id: $id) {
|
||||||
|
...Media
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
[MediaFragment],
|
||||||
|
);
|
||||||
|
|
||||||
|
export async function fetchTitleFromAnilist(
|
||||||
|
id: number,
|
||||||
|
token: string | undefined,
|
||||||
|
): Promise<Title | undefined> {
|
||||||
|
const client = new GraphQLClient("https://graphql.anilist.co/");
|
||||||
|
const headers = new Headers();
|
||||||
|
if (token) {
|
||||||
|
headers.append("Authorization", `Bearer ${token}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
.request(GetTitleQuery, { id }, headers)
|
||||||
|
.then((data) => data?.Media ?? undefined);
|
||||||
|
}
|
||||||
127
src/controllers/title/index.spec.ts
Normal file
127
src/controllers/title/index.spec.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { describe, expect, it } from "bun:test";
|
||||||
|
|
||||||
|
import app from "~/index";
|
||||||
|
import { server } from "~/mocks";
|
||||||
|
|
||||||
|
server.listen();
|
||||||
|
|
||||||
|
describe('requests the "/title" route', () => {
|
||||||
|
it("with a valid id & token", async () => {
|
||||||
|
const response = await app.request("/title?id=10", {
|
||||||
|
headers: new Headers({ "x-anilist-token": "asd" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.json()).resolves.toEqual({
|
||||||
|
success: true,
|
||||||
|
result: {
|
||||||
|
nextAiringEpisode: null,
|
||||||
|
mediaListEntry: {
|
||||||
|
status: "CURRENT",
|
||||||
|
progress: 1,
|
||||||
|
id: 402665918,
|
||||||
|
},
|
||||||
|
countryOfOrigin: "JP",
|
||||||
|
coverImage: {
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/medium/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
extraLarge:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
},
|
||||||
|
averageScore: 66,
|
||||||
|
bannerImage:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/banner/135643-cmQZCR3z9dB5.jpg",
|
||||||
|
status: "FINISHED",
|
||||||
|
genres: ["Fantasy", "Thriller"],
|
||||||
|
episodes: 6,
|
||||||
|
description:
|
||||||
|
'Once upon a time, brothers Jacob and Wilhelm collected fairy tales from across the land and made them into a book. They also had a much younger sister, the innocent and curious Charlotte, who they loved very much. One day, while the brothers were telling Charlotte a fairy tale like usual, they saw that she had a somewhat melancholy look on her face. She asked them, "Do you suppose they really lived happily ever after?"\n<br><br>\nThe pages of Grimms\' Fairy Tales, written by Jacob and Wilhelm, are now presented from the unique perspective of Charlotte, who sees the stories quite differently from her brothers.\n<br><br>\n(Source: Netflix Anime)',
|
||||||
|
title: {
|
||||||
|
userPreferred: "The Grimm Variations",
|
||||||
|
english: "The Grimm Variations",
|
||||||
|
},
|
||||||
|
idMal: 49210,
|
||||||
|
id: 135643,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with a valid id but no token", async () => {
|
||||||
|
const response = await app.request("/title?id=10");
|
||||||
|
|
||||||
|
expect(response.json()).resolves.toEqual({
|
||||||
|
success: true,
|
||||||
|
result: {
|
||||||
|
nextAiringEpisode: null,
|
||||||
|
mediaListEntry: null,
|
||||||
|
countryOfOrigin: "JP",
|
||||||
|
coverImage: {
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/medium/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
extraLarge:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
},
|
||||||
|
averageScore: 66,
|
||||||
|
bannerImage:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/banner/135643-cmQZCR3z9dB5.jpg",
|
||||||
|
status: "FINISHED",
|
||||||
|
genres: ["Fantasy", "Thriller"],
|
||||||
|
episodes: 6,
|
||||||
|
description:
|
||||||
|
'Once upon a time, brothers Jacob and Wilhelm collected fairy tales from across the land and made them into a book. They also had a much younger sister, the innocent and curious Charlotte, who they loved very much. One day, while the brothers were telling Charlotte a fairy tale like usual, they saw that she had a somewhat melancholy look on her face. She asked them, "Do you suppose they really lived happily ever after?"\n<br><br>\nThe pages of Grimms\' Fairy Tales, written by Jacob and Wilhelm, are now presented from the unique perspective of Charlotte, who sees the stories quite differently from her brothers.\n<br><br>\n(Source: Netflix Anime)',
|
||||||
|
title: {
|
||||||
|
userPreferred: "The Grimm Variations",
|
||||||
|
english: "The Grimm Variations",
|
||||||
|
},
|
||||||
|
idMal: 49210,
|
||||||
|
id: 135643,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with an unknown title from anilist but valid title from amvstrm", async () => {
|
||||||
|
const response = await app.request("/title?id=50");
|
||||||
|
|
||||||
|
expect(response.json()).resolves.toEqual({
|
||||||
|
success: true,
|
||||||
|
result: {
|
||||||
|
nextAiringEpisode: null,
|
||||||
|
mediaListEntry: null,
|
||||||
|
coverImage: {
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx151807-yxY3olrjZH4k.png",
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/medium/bx151807-yxY3olrjZH4k.png",
|
||||||
|
},
|
||||||
|
averageScore: 83,
|
||||||
|
bannerImage:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/banner/151807-37yfQA3ym8PA.jpg",
|
||||||
|
status: "FINISHED",
|
||||||
|
genres: ["Action", "Adventure", "Fantasy"],
|
||||||
|
episodes: 12,
|
||||||
|
description:
|
||||||
|
"They say whatever doesn’t kill you makes you stronger, but that’s not the case for the world’s weakest hunter Sung Jinwoo. After being brutally slaughtered by monsters in a high-ranking dungeon, Jinwoo came back with the System, a program only he could see, that’s leveling him up in every way. Now, he’s inspired to discover the secrets behind his powers and the dungeon that spawned them.<br>\n<br>\n(Source: Crunchyroll) <br><br>",
|
||||||
|
title: {
|
||||||
|
userPreferred: "Ore dake Level Up na Ken",
|
||||||
|
english: "Solo Leveling",
|
||||||
|
},
|
||||||
|
idMal: 52299,
|
||||||
|
id: 151807,
|
||||||
|
countryOfOrigin: "JP",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with an unknown title from all sources", async () => {
|
||||||
|
const response = await app.request("/title?id=-1");
|
||||||
|
|
||||||
|
expect(response.json()).resolves.toEqual({ success: false });
|
||||||
|
expect(response.status).toBe(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
61
src/controllers/title/index.ts
Normal file
61
src/controllers/title/index.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
||||||
|
|
||||||
|
import { fetchFromMultipleSources } from "~/libs/fetchFromMultipleSources";
|
||||||
|
import {
|
||||||
|
AniListIdSchema,
|
||||||
|
ErrorResponseSchema,
|
||||||
|
SuccessResponseSchema,
|
||||||
|
} from "~/types/schema";
|
||||||
|
import { Title } from "~/types/title";
|
||||||
|
|
||||||
|
import { fetchTitleFromAmvstrm } from "./amvstrm";
|
||||||
|
import { fetchTitleFromAnilist } from "./anilist";
|
||||||
|
|
||||||
|
const app = new OpenAPIHono();
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
tags: ["aniplay", "title"],
|
||||||
|
operationId: "fetchTitle",
|
||||||
|
summary: "Fetch title information",
|
||||||
|
method: "get",
|
||||||
|
path: "/",
|
||||||
|
request: {
|
||||||
|
query: z.object({ id: AniListIdSchema }),
|
||||||
|
headers: z.object({ "x-anilist-token": z.string().nullish() }),
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: SuccessResponseSchema(Title),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Returns title information",
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: ErrorResponseSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Title could not be found",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.openapi(route, async (c) => {
|
||||||
|
const aniListId = Number(c.req.query("id"));
|
||||||
|
const aniListToken = c.req.header("X-AniList-Token");
|
||||||
|
|
||||||
|
const title = await fetchFromMultipleSources([
|
||||||
|
() => fetchTitleFromAnilist(aniListId, aniListToken ?? undefined),
|
||||||
|
() => fetchTitleFromAmvstrm(aniListId),
|
||||||
|
]);
|
||||||
|
if (!title) {
|
||||||
|
return c.json({ success: false }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.json({ success: true, result: title }, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
||||||
35
src/controllers/title/mediaFragment.ts
Normal file
35
src/controllers/title/mediaFragment.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { graphql } from "gql.tada";
|
||||||
|
|
||||||
|
export const MediaFragment = graphql(`
|
||||||
|
fragment Media on Media {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
type
|
||||||
|
description
|
||||||
|
episodes
|
||||||
|
genres
|
||||||
|
status
|
||||||
|
bannerImage
|
||||||
|
averageScore
|
||||||
|
coverImage {
|
||||||
|
extraLarge
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
countryOfOrigin
|
||||||
|
mediaListEntry {
|
||||||
|
id
|
||||||
|
progress
|
||||||
|
status
|
||||||
|
}
|
||||||
|
nextAiringEpisode {
|
||||||
|
timeUntilAiring
|
||||||
|
airingAt
|
||||||
|
episode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
@@ -8,6 +8,10 @@ app.route(
|
|||||||
(controller) => controller.default,
|
(controller) => controller.default,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
app.route(
|
||||||
|
"/title",
|
||||||
|
await import("~/controllers/title").then((controller) => controller.default),
|
||||||
|
);
|
||||||
|
|
||||||
// The OpenAPI documentation will be available at /doc
|
// The OpenAPI documentation will be available at /doc
|
||||||
app.doc("/doc", {
|
app.doc("/doc", {
|
||||||
|
|||||||
602
src/mocks/amvstrm/title.ts
Normal file
602
src/mocks/amvstrm/title.ts
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
import { HttpResponse, http } from "msw";
|
||||||
|
|
||||||
|
export function getAmvstrmTitle() {
|
||||||
|
return http.get(
|
||||||
|
"https://api-amvstrm.nyt92.eu.org/api/v2/info/:aniListId",
|
||||||
|
({ params }) => {
|
||||||
|
const aniListId = Number(params["aniListId"] as string);
|
||||||
|
|
||||||
|
if (aniListId == -1) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
code: 404,
|
||||||
|
message:
|
||||||
|
"The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aniListId == 50) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
code: 200,
|
||||||
|
message: "success",
|
||||||
|
id: 151807,
|
||||||
|
idMal: 52299,
|
||||||
|
id_provider: {
|
||||||
|
idGogo: "ore-dake-level-up-na-ken",
|
||||||
|
idGogoDub: "ore-dake-level-up-na-ken-korean-dub",
|
||||||
|
idZoro: "solo-leveling-18718",
|
||||||
|
id9anime: "solo-leveling.3rpv2",
|
||||||
|
idPahe: "5421",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
romaji: "Ore dake Level Up na Ken",
|
||||||
|
english: "Solo Leveling",
|
||||||
|
native: "俺だけレベルアップな件",
|
||||||
|
userPreferred: "Ore dake Level Up na Ken",
|
||||||
|
},
|
||||||
|
dub: true,
|
||||||
|
description:
|
||||||
|
"They say whatever doesn’t kill you makes you stronger, but that’s not the case for the world’s weakest hunter Sung Jinwoo. After being brutally slaughtered by monsters in a high-ranking dungeon, Jinwoo came back with the System, a program only he could see, that’s leveling him up in every way. Now, he’s inspired to discover the secrets behind his powers and the dungeon that spawned them.<br>\n<br>\n(Source: Crunchyroll) <br><br>",
|
||||||
|
coverImage: {
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/medium/bx151807-yxY3olrjZH4k.png",
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx151807-yxY3olrjZH4k.png",
|
||||||
|
color: "#35bbf1",
|
||||||
|
},
|
||||||
|
bannerImage:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/banner/151807-37yfQA3ym8PA.jpg",
|
||||||
|
genres: ["Action", "Adventure", "Fantasy"],
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: 604,
|
||||||
|
name: "Dungeon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 82,
|
||||||
|
name: "Male Protagonist",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 321,
|
||||||
|
name: "Urban Fantasy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 66,
|
||||||
|
name: "Super Power",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 29,
|
||||||
|
name: "Magic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1243,
|
||||||
|
name: "Necromancy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 326,
|
||||||
|
name: "Cultivation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 111,
|
||||||
|
name: "War",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 104,
|
||||||
|
name: "Anti-Hero",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 94,
|
||||||
|
name: "Gore",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 636,
|
||||||
|
name: "Cosmic Horror",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 85,
|
||||||
|
name: "Tragedy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 43,
|
||||||
|
name: "Swordplay",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 56,
|
||||||
|
name: "Shounen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 146,
|
||||||
|
name: "Alternate Universe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 96,
|
||||||
|
name: "Time Manipulation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1068,
|
||||||
|
name: "Angels",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 93,
|
||||||
|
name: "Post-Apocalyptic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 217,
|
||||||
|
name: "Dystopian",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1310,
|
||||||
|
name: "Travel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1045,
|
||||||
|
name: "Heterosexual",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 488,
|
||||||
|
name: "Age Regression",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 244,
|
||||||
|
name: "Isekai",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 171,
|
||||||
|
name: "Bullying",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 224,
|
||||||
|
name: "Dragons",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 255,
|
||||||
|
name: "Ninja",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: "FINISHED",
|
||||||
|
format: "TV",
|
||||||
|
episodes: 12,
|
||||||
|
year: 2024,
|
||||||
|
season: "WINTER",
|
||||||
|
duration: 24,
|
||||||
|
startIn: {
|
||||||
|
year: 2024,
|
||||||
|
month: 1,
|
||||||
|
day: 7,
|
||||||
|
},
|
||||||
|
endIn: {
|
||||||
|
year: 2024,
|
||||||
|
month: 3,
|
||||||
|
day: 31,
|
||||||
|
},
|
||||||
|
nextair: null,
|
||||||
|
score: {
|
||||||
|
averageScore: 83,
|
||||||
|
decimalScore: 8.3,
|
||||||
|
},
|
||||||
|
popularity: 196143,
|
||||||
|
siteUrl: "https://anilist.co/anime/151807",
|
||||||
|
trailer: {
|
||||||
|
id: "HkIKAnwLZCw",
|
||||||
|
site: "youtube",
|
||||||
|
thumbnail: "https://i.ytimg.com/vi/HkIKAnwLZCw/hqdefault.jpg",
|
||||||
|
},
|
||||||
|
studios: [
|
||||||
|
{
|
||||||
|
name: "A-1 Pictures",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Aniplex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Netmarble",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "D&C MEDIA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Kakao piccoma",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Crunchyroll",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relation: [
|
||||||
|
{
|
||||||
|
id: 105398,
|
||||||
|
idMal: 121496,
|
||||||
|
title: {
|
||||||
|
romaji: "Na Honjaman Level Up",
|
||||||
|
english: "Solo Leveling",
|
||||||
|
native: "나 혼자만 레벨업",
|
||||||
|
userPreferred: "Na Honjaman Level Up",
|
||||||
|
},
|
||||||
|
coverImage: {
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/manga/cover/medium/bx105398-b673Vt5ZSuz3.jpg",
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/manga/cover/small/bx105398-b673Vt5ZSuz3.jpg",
|
||||||
|
color: null,
|
||||||
|
},
|
||||||
|
bannerImage:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/manga/banner/105398-4UrEhdqZukrg.jpg",
|
||||||
|
genres: ["Action", "Adventure", "Fantasy"],
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: 604,
|
||||||
|
name: "Dungeon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 82,
|
||||||
|
name: "Male Protagonist",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 111,
|
||||||
|
name: "War",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 66,
|
||||||
|
name: "Super Power",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 207,
|
||||||
|
name: "Full Color",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 29,
|
||||||
|
name: "Magic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1243,
|
||||||
|
name: "Necromancy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 321,
|
||||||
|
name: "Urban Fantasy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 253,
|
||||||
|
name: "Gods",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 109,
|
||||||
|
name: "Primarily Adult Cast",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 103,
|
||||||
|
name: "Politics",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 93,
|
||||||
|
name: "Post-Apocalyptic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: "Demons",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 85,
|
||||||
|
name: "Tragedy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 308,
|
||||||
|
name: "Video Games",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 365,
|
||||||
|
name: "Memory Manipulation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 96,
|
||||||
|
name: "Time Manipulation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 198,
|
||||||
|
name: "Foreign",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1564,
|
||||||
|
name: "Estranged Family",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 171,
|
||||||
|
name: "Bullying",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 488,
|
||||||
|
name: "Age Regression",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 104,
|
||||||
|
name: "Anti-Hero",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 322,
|
||||||
|
name: "Assassins",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 774,
|
||||||
|
name: "Chimera",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1045,
|
||||||
|
name: "Heterosexual",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 516,
|
||||||
|
name: "Language Barrier",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 153,
|
||||||
|
name: "Time Skip",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "MANGA",
|
||||||
|
format: "MANGA",
|
||||||
|
status: "FINISHED",
|
||||||
|
episodes: null,
|
||||||
|
duration: null,
|
||||||
|
averageScore: 85,
|
||||||
|
season: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 176496,
|
||||||
|
idMal: 58567,
|
||||||
|
title: {
|
||||||
|
romaji:
|
||||||
|
"Ore dake Level Up na Ken: Season 2 - Arise from the Shadow",
|
||||||
|
english: "Solo Leveling Season 2 -Arise from the Shadow-",
|
||||||
|
native:
|
||||||
|
"俺だけレベルアップな件 Season 2 -Arise from the Shadow-",
|
||||||
|
userPreferred:
|
||||||
|
"Ore dake Level Up na Ken: Season 2 - Arise from the Shadow",
|
||||||
|
},
|
||||||
|
coverImage: {
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/medium/bx176496-r6oXxEqdZL0n.jpg",
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx176496-r6oXxEqdZL0n.jpg",
|
||||||
|
color: "#a1bbe4",
|
||||||
|
},
|
||||||
|
bannerImage: null,
|
||||||
|
genres: ["Action", "Adventure", "Fantasy"],
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: 1243,
|
||||||
|
name: "Necromancy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 604,
|
||||||
|
name: "Dungeon",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "ANIME",
|
||||||
|
format: "TV",
|
||||||
|
status: "NOT_YET_RELEASED",
|
||||||
|
episodes: null,
|
||||||
|
duration: null,
|
||||||
|
averageScore: null,
|
||||||
|
season: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
code: 200,
|
||||||
|
message: "success",
|
||||||
|
id: 135643,
|
||||||
|
idMal: 49210,
|
||||||
|
id_provider: {
|
||||||
|
idGogo: "grimm-kumikyoku-dub",
|
||||||
|
idGogoDub: "grimm-kumikyoku",
|
||||||
|
idZoro: "the-grimm-variations-19092",
|
||||||
|
id9anime: "grimm-kumikyoku.qxvzn",
|
||||||
|
idPahe: "",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
romaji: "Grimm Kumikyoku",
|
||||||
|
english: "The Grimm Variations",
|
||||||
|
native: "グリム組曲",
|
||||||
|
userPreferred: "Grimm Kumikyoku",
|
||||||
|
},
|
||||||
|
dub: true,
|
||||||
|
description:
|
||||||
|
'Once upon a time, brothers Jacob and Wilhelm collected fairy tales from across the land and made them into a book. They also had a much younger sister, the innocent and curious Charlotte, who they loved very much. One day, while the brothers were telling Charlotte a fairy tale like usual, they saw that she had a somewhat melancholy look on her face. She asked them, "Do you suppose they really lived happily ever after?"\n<br><br>\nThe pages of Grimms\' Fairy Tales, written by Jacob and Wilhelm, are now presented from the unique perspective of Charlotte, who sees the stories quite differently from her brothers.\n<br><br>\n(Source: Netflix Anime)',
|
||||||
|
coverImage: {
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/medium/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
color: "#fea150",
|
||||||
|
},
|
||||||
|
bannerImage:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/banner/135643-cmQZCR3z9dB5.jpg",
|
||||||
|
genres: ["Fantasy", "Thriller"],
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: 400,
|
||||||
|
name: "Fairy Tale",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 193,
|
||||||
|
name: "Episodic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 471,
|
||||||
|
name: "Anthology",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 227,
|
||||||
|
name: "Classic Literature",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 179,
|
||||||
|
name: "Witch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1219,
|
||||||
|
name: "Disability",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 93,
|
||||||
|
name: "Post-Apocalyptic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 94,
|
||||||
|
name: "Gore",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 25,
|
||||||
|
name: "Historical",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 250,
|
||||||
|
name: "Rural",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 394,
|
||||||
|
name: "Writing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 29,
|
||||||
|
name: "Magic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 161,
|
||||||
|
name: "Bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1578,
|
||||||
|
name: "Arranged Marriage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 654,
|
||||||
|
name: "Denpa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 217,
|
||||||
|
name: "Dystopian",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 598,
|
||||||
|
name: "Elf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 456,
|
||||||
|
name: "Conspiracy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 63,
|
||||||
|
name: "Space",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 364,
|
||||||
|
name: "Augmented Reality",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 112,
|
||||||
|
name: "Virtual World",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 639,
|
||||||
|
name: "Body Horror",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 163,
|
||||||
|
name: "Yandere",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 154,
|
||||||
|
name: "Body Swapping",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 100,
|
||||||
|
name: "Nudity",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: "FINISHED",
|
||||||
|
format: "ONA",
|
||||||
|
episodes: 6,
|
||||||
|
year: 2024,
|
||||||
|
season: "SPRING",
|
||||||
|
duration: 44,
|
||||||
|
startIn: {
|
||||||
|
year: 2024,
|
||||||
|
month: 4,
|
||||||
|
day: 17,
|
||||||
|
},
|
||||||
|
endIn: {
|
||||||
|
year: 2024,
|
||||||
|
month: 4,
|
||||||
|
day: 17,
|
||||||
|
},
|
||||||
|
nextair: null,
|
||||||
|
score: {
|
||||||
|
averageScore: 66,
|
||||||
|
decimalScore: 6.6,
|
||||||
|
},
|
||||||
|
popularity: 8486,
|
||||||
|
siteUrl: "https://anilist.co/anime/135643",
|
||||||
|
trailer: {
|
||||||
|
id: "bTU3detmX_I",
|
||||||
|
site: "youtube",
|
||||||
|
thumbnail: "https://i.ytimg.com/vi/bTU3detmX_I/hqdefault.jpg",
|
||||||
|
},
|
||||||
|
studios: [
|
||||||
|
{
|
||||||
|
name: "Netflix",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Wit Studio",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relation: [
|
||||||
|
{
|
||||||
|
id: 177039,
|
||||||
|
idMal: 169338,
|
||||||
|
title: {
|
||||||
|
romaji: "Grimm Kumikyoku",
|
||||||
|
english: null,
|
||||||
|
native: "グリム組曲",
|
||||||
|
userPreferred: "Grimm Kumikyoku",
|
||||||
|
},
|
||||||
|
coverImage: {
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/manga/cover/medium/bx177039-672FYniIpHIL.jpg",
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/manga/cover/small/bx177039-672FYniIpHIL.jpg",
|
||||||
|
color: "#865028",
|
||||||
|
},
|
||||||
|
bannerImage: null,
|
||||||
|
genres: ["Fantasy", "Thriller"],
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: 400,
|
||||||
|
name: "Fairy Tale",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 94,
|
||||||
|
name: "Gore",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 85,
|
||||||
|
name: "Tragedy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 63,
|
||||||
|
name: "Space",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "MANGA",
|
||||||
|
format: "MANGA",
|
||||||
|
status: "RELEASING",
|
||||||
|
episodes: null,
|
||||||
|
duration: null,
|
||||||
|
averageScore: null,
|
||||||
|
season: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/mocks/anify/title.ts
Normal file
12
src/mocks/anify/title.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { HttpResponse, http } from "msw";
|
||||||
|
|
||||||
|
export function getAnifyTitle() {
|
||||||
|
return http.get(`https://api.anify.tv/info`, ({ request }) => {
|
||||||
|
// Construct a URL instance out of the intercepted request.
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const id = url.searchParams.get("id");
|
||||||
|
|
||||||
|
// TODO: Actually return a response
|
||||||
|
return HttpResponse.json({ bannerImage: null, countryOfOrigin: "JP" });
|
||||||
|
});
|
||||||
|
}
|
||||||
70
src/mocks/anilist/title.ts
Normal file
70
src/mocks/anilist/title.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { HttpResponse, graphql } from "msw";
|
||||||
|
|
||||||
|
export function getAnilistTitle() {
|
||||||
|
return graphql.query(
|
||||||
|
"GetTitle",
|
||||||
|
({ variables: { id }, request: { headers } }) => {
|
||||||
|
console.log(
|
||||||
|
`Intercepting GetTitle query with ID ${id} and Authorization header ${headers.get("authorization")}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (id === -1 || id === 50) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
message: "Not Found.",
|
||||||
|
status: 404,
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
line: 2,
|
||||||
|
column: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
data: {
|
||||||
|
Media: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
data: {
|
||||||
|
Media: {
|
||||||
|
id: 135643,
|
||||||
|
idMal: 49210,
|
||||||
|
title: {
|
||||||
|
english: "The Grimm Variations",
|
||||||
|
userPreferred: "The Grimm Variations",
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'Once upon a time, brothers Jacob and Wilhelm collected fairy tales from across the land and made them into a book. They also had a much younger sister, the innocent and curious Charlotte, who they loved very much. One day, while the brothers were telling Charlotte a fairy tale like usual, they saw that she had a somewhat melancholy look on her face. She asked them, "Do you suppose they really lived happily ever after?"\n<br><br>\nThe pages of Grimms\' Fairy Tales, written by Jacob and Wilhelm, are now presented from the unique perspective of Charlotte, who sees the stories quite differently from her brothers.\n<br><br>\n(Source: Netflix Anime)',
|
||||||
|
episodes: 6,
|
||||||
|
genres: ["Fantasy", "Thriller"],
|
||||||
|
status: "FINISHED",
|
||||||
|
bannerImage:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/banner/135643-cmQZCR3z9dB5.jpg",
|
||||||
|
averageScore: 66,
|
||||||
|
coverImage: {
|
||||||
|
extraLarge:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
large:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/medium/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
medium:
|
||||||
|
"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx135643-2kJt86K9Db9P.jpg",
|
||||||
|
},
|
||||||
|
countryOfOrigin: "JP",
|
||||||
|
mediaListEntry: headers.has("authorization")
|
||||||
|
? {
|
||||||
|
id: 402665918,
|
||||||
|
progress: 1,
|
||||||
|
status: "CURRENT",
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
nextAiringEpisode: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1 +1,5 @@
|
|||||||
export const handlers = [];
|
import { getAmvstrmTitle } from "./amvstrm/title";
|
||||||
|
import { getAnifyTitle } from "./anify/title";
|
||||||
|
import { getAnilistTitle } from "./anilist/title";
|
||||||
|
|
||||||
|
export const handlers = [getAnilistTitle(), getAmvstrmTitle(), getAnifyTitle()];
|
||||||
|
|||||||
@@ -8,3 +8,11 @@ export const SuccessResponseSchema = <T extends ZodSchema>(schema?: T) => {
|
|||||||
|
|
||||||
return z.object({ success: z.literal(true), result: schema });
|
return z.object({ success: z.literal(true), result: schema });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ErrorResponseSchema = z.object({
|
||||||
|
success: z.literal(false),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AniListIdSchema = z
|
||||||
|
.number({ coerce: true })
|
||||||
|
.openapi({ type: "integer" });
|
||||||
|
|||||||
253
src/types/title/countryCodes.ts
Normal file
253
src/types/title/countryCodes.ts
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const countryCodeSchema = z.enum([
|
||||||
|
"AD",
|
||||||
|
"AE",
|
||||||
|
"AF",
|
||||||
|
"AG",
|
||||||
|
"AI",
|
||||||
|
"AL",
|
||||||
|
"AM",
|
||||||
|
"AO",
|
||||||
|
"AQ",
|
||||||
|
"AR",
|
||||||
|
"AS",
|
||||||
|
"AT",
|
||||||
|
"AU",
|
||||||
|
"AW",
|
||||||
|
"AX",
|
||||||
|
"AZ",
|
||||||
|
"BA",
|
||||||
|
"BB",
|
||||||
|
"BD",
|
||||||
|
"BE",
|
||||||
|
"BF",
|
||||||
|
"BG",
|
||||||
|
"BH",
|
||||||
|
"BI",
|
||||||
|
"BJ",
|
||||||
|
"BL",
|
||||||
|
"BM",
|
||||||
|
"BN",
|
||||||
|
"BO",
|
||||||
|
"BQ",
|
||||||
|
"BR",
|
||||||
|
"BS",
|
||||||
|
"BT",
|
||||||
|
"BV",
|
||||||
|
"BW",
|
||||||
|
"BY",
|
||||||
|
"BZ",
|
||||||
|
"CA",
|
||||||
|
"CC",
|
||||||
|
"CD",
|
||||||
|
"CF",
|
||||||
|
"CG",
|
||||||
|
"CH",
|
||||||
|
"CI",
|
||||||
|
"CK",
|
||||||
|
"CL",
|
||||||
|
"CM",
|
||||||
|
"CN",
|
||||||
|
"CO",
|
||||||
|
"CR",
|
||||||
|
"CU",
|
||||||
|
"CV",
|
||||||
|
"CW",
|
||||||
|
"CX",
|
||||||
|
"CY",
|
||||||
|
"CZ",
|
||||||
|
"DE",
|
||||||
|
"DJ",
|
||||||
|
"DK",
|
||||||
|
"DM",
|
||||||
|
"DO",
|
||||||
|
"DZ",
|
||||||
|
"EC",
|
||||||
|
"EE",
|
||||||
|
"EG",
|
||||||
|
"EH",
|
||||||
|
"ER",
|
||||||
|
"ES",
|
||||||
|
"ET",
|
||||||
|
"FI",
|
||||||
|
"FJ",
|
||||||
|
"FK",
|
||||||
|
"FM",
|
||||||
|
"FO",
|
||||||
|
"FR",
|
||||||
|
"GA",
|
||||||
|
"GB",
|
||||||
|
"GD",
|
||||||
|
"GE",
|
||||||
|
"GF",
|
||||||
|
"GG",
|
||||||
|
"GH",
|
||||||
|
"GI",
|
||||||
|
"GL",
|
||||||
|
"GM",
|
||||||
|
"GN",
|
||||||
|
"GP",
|
||||||
|
"GQ",
|
||||||
|
"GR",
|
||||||
|
"GS",
|
||||||
|
"GT",
|
||||||
|
"GU",
|
||||||
|
"GW",
|
||||||
|
"GY",
|
||||||
|
"HK",
|
||||||
|
"HM",
|
||||||
|
"HN",
|
||||||
|
"HR",
|
||||||
|
"HT",
|
||||||
|
"HU",
|
||||||
|
"ID",
|
||||||
|
"IE",
|
||||||
|
"IL",
|
||||||
|
"IM",
|
||||||
|
"IN",
|
||||||
|
"IO",
|
||||||
|
"IQ",
|
||||||
|
"IR",
|
||||||
|
"IS",
|
||||||
|
"IT",
|
||||||
|
"JE",
|
||||||
|
"JM",
|
||||||
|
"JO",
|
||||||
|
"JP",
|
||||||
|
"KE",
|
||||||
|
"KG",
|
||||||
|
"KH",
|
||||||
|
"KI",
|
||||||
|
"KM",
|
||||||
|
"KN",
|
||||||
|
"KP",
|
||||||
|
"KR",
|
||||||
|
"KW",
|
||||||
|
"KY",
|
||||||
|
"KZ",
|
||||||
|
"LA",
|
||||||
|
"LB",
|
||||||
|
"LC",
|
||||||
|
"LI",
|
||||||
|
"LK",
|
||||||
|
"LR",
|
||||||
|
"LS",
|
||||||
|
"LT",
|
||||||
|
"LU",
|
||||||
|
"LV",
|
||||||
|
"LY",
|
||||||
|
"MA",
|
||||||
|
"MC",
|
||||||
|
"MD",
|
||||||
|
"ME",
|
||||||
|
"MF",
|
||||||
|
"MG",
|
||||||
|
"MH",
|
||||||
|
"MK",
|
||||||
|
"ML",
|
||||||
|
"MM",
|
||||||
|
"MN",
|
||||||
|
"MO",
|
||||||
|
"MP",
|
||||||
|
"MQ",
|
||||||
|
"MR",
|
||||||
|
"MS",
|
||||||
|
"MT",
|
||||||
|
"MU",
|
||||||
|
"MV",
|
||||||
|
"MW",
|
||||||
|
"MX",
|
||||||
|
"MY",
|
||||||
|
"MZ",
|
||||||
|
"NA",
|
||||||
|
"NC",
|
||||||
|
"NE",
|
||||||
|
"NF",
|
||||||
|
"NG",
|
||||||
|
"NI",
|
||||||
|
"NL",
|
||||||
|
"NO",
|
||||||
|
"NP",
|
||||||
|
"NR",
|
||||||
|
"NU",
|
||||||
|
"NZ",
|
||||||
|
"OM",
|
||||||
|
"PA",
|
||||||
|
"PE",
|
||||||
|
"PF",
|
||||||
|
"PG",
|
||||||
|
"PH",
|
||||||
|
"PK",
|
||||||
|
"PL",
|
||||||
|
"PM",
|
||||||
|
"PN",
|
||||||
|
"PR",
|
||||||
|
"PS",
|
||||||
|
"PT",
|
||||||
|
"PW",
|
||||||
|
"PY",
|
||||||
|
"QA",
|
||||||
|
"RE",
|
||||||
|
"RO",
|
||||||
|
"RS",
|
||||||
|
"RU",
|
||||||
|
"RW",
|
||||||
|
"SA",
|
||||||
|
"SB",
|
||||||
|
"SC",
|
||||||
|
"SD",
|
||||||
|
"SE",
|
||||||
|
"SG",
|
||||||
|
"SH",
|
||||||
|
"SI",
|
||||||
|
"SJ",
|
||||||
|
"SK",
|
||||||
|
"SL",
|
||||||
|
"SM",
|
||||||
|
"SN",
|
||||||
|
"SO",
|
||||||
|
"SR",
|
||||||
|
"SS",
|
||||||
|
"ST",
|
||||||
|
"SV",
|
||||||
|
"SX",
|
||||||
|
"SY",
|
||||||
|
"SZ",
|
||||||
|
"TC",
|
||||||
|
"TD",
|
||||||
|
"TF",
|
||||||
|
"TG",
|
||||||
|
"TH",
|
||||||
|
"TJ",
|
||||||
|
"TK",
|
||||||
|
"TL",
|
||||||
|
"TM",
|
||||||
|
"TN",
|
||||||
|
"TO",
|
||||||
|
"TR",
|
||||||
|
"TT",
|
||||||
|
"TV",
|
||||||
|
"TW",
|
||||||
|
"TZ",
|
||||||
|
"UA",
|
||||||
|
"UG",
|
||||||
|
"UM",
|
||||||
|
"US",
|
||||||
|
"UY",
|
||||||
|
"UZ",
|
||||||
|
"VA",
|
||||||
|
"VC",
|
||||||
|
"VE",
|
||||||
|
"VG",
|
||||||
|
"VI",
|
||||||
|
"VN",
|
||||||
|
"VU",
|
||||||
|
"WF",
|
||||||
|
"WS",
|
||||||
|
"YE",
|
||||||
|
"YT",
|
||||||
|
"ZA",
|
||||||
|
"ZM",
|
||||||
|
"ZW",
|
||||||
|
]);
|
||||||
60
src/types/title/index.ts
Normal file
60
src/types/title/index.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { countryCodeSchema } from "./countryCodes";
|
||||||
|
|
||||||
|
export type Title = z.infer<typeof Title>;
|
||||||
|
export const Title = z.object({
|
||||||
|
nextAiringEpisode: z.nullable(
|
||||||
|
z.object({
|
||||||
|
episode: z.number(),
|
||||||
|
airingAt: z.number(),
|
||||||
|
timeUntilAiring: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
mediaListEntry: z.nullable(
|
||||||
|
z.object({
|
||||||
|
status: z.nullable(
|
||||||
|
z.enum([
|
||||||
|
"CURRENT",
|
||||||
|
"PLANNING",
|
||||||
|
"COMPLETED",
|
||||||
|
"DROPPED",
|
||||||
|
"PAUSED",
|
||||||
|
"REPEATING",
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
progress: z.number().nullable(),
|
||||||
|
id: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
countryOfOrigin: z.optional(countryCodeSchema),
|
||||||
|
coverImage: z.nullable(
|
||||||
|
z.object({
|
||||||
|
medium: z.nullable(z.string()).optional(),
|
||||||
|
large: z.nullable(z.string()).optional(),
|
||||||
|
extraLarge: z.nullable(z.string()).optional(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
averageScore: z.number().nullable(),
|
||||||
|
bannerImage: z.nullable(z.string()),
|
||||||
|
status: z.nullable(
|
||||||
|
z.enum([
|
||||||
|
"FINISHED",
|
||||||
|
"RELEASING",
|
||||||
|
"NOT_YET_RELEASED",
|
||||||
|
"CANCELLED",
|
||||||
|
"HIATUS",
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
genres: z.nullable(z.array(z.nullable(z.string()))),
|
||||||
|
episodes: z.number().nullable(),
|
||||||
|
description: z.nullable(z.string()),
|
||||||
|
title: z.nullable(
|
||||||
|
z.object({
|
||||||
|
userPreferred: z.nullable(z.string()),
|
||||||
|
english: z.nullable(z.string()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
idMal: z.number().nullable(),
|
||||||
|
id: z.number(),
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["@cloudflare/workers-types"],
|
"types": ["@cloudflare/workers-types", "@types/bun"],
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["src/*"]
|
"~/*": ["src/*"]
|
||||||
@@ -25,6 +25,15 @@
|
|||||||
// Some stricter flags
|
// Some stricter flags
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": true
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
|
||||||
|
// plugins
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "@0no-co/graphqlsp",
|
||||||
|
"schema": "https://graphql.anilist.co",
|
||||||
|
"tadaOutputLocation": "./src/types/anilist-graphql.d.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user