├── .gitattributes ├── docs ├── assets │ ├── icons.png │ ├── icons@2x.png │ ├── widgets.png │ ├── widgets@2x.png │ └── highlight.css ├── .nojekyll ├── modules.html ├── modules │ ├── Mal.User.User.html │ └── Mal.Manga.MangaForList.html └── interfaces │ ├── Mal.User.User.Gender.html │ ├── Mal.User.User.Birthday.html │ ├── Mal.User.User.TimeZone.html │ ├── Mal.Common.WorkForList.CreatedAt.html │ ├── Mal.Common.WorkForList.UpdatedAt.html │ ├── Mal.User.User.AnimeStatistics.html │ ├── Mal.User.User.IsSupporter.html │ ├── Mal.Common.WorkForList.EndDate.html │ ├── Mal.Common.WorkForList.Popularity.html │ ├── Mal.Common.WorkForList.StartDate.html │ ├── Mal.Common.WorkForList.NumScoringUsers.html │ ├── Mal.Common.WorkForList.Rank.html │ ├── Mal.Common.WorkForList.Synopsis.html │ ├── Mal.Common.WorkForList.Mean.html │ ├── Mal.Common.WorkForList.Genres.html │ ├── Mal.Common.WorkForList.NumListUsers.html │ ├── Mal.Anime.AnimeListStatus.Tags.html │ ├── Mal.Manga.MangaListStatus.Tags.html │ └── Mal.Anime.AnimeListStatus.Priority.html ├── jikan_generator ├── README.md └── main.ts ├── test ├── mal │ ├── dummyData │ │ ├── index.ts │ │ └── user │ │ │ └── animelistDef.ts │ ├── util │ │ ├── types.ts │ │ └── index.ts │ ├── index.ts │ └── common.ts ├── jikan_online.ts └── mal.ts ├── typedoc.json ├── src ├── index.ts └── methods │ ├── malApi │ ├── api.ts │ ├── util.ts │ ├── user │ │ ├── types.ts │ │ ├── fields.ts │ │ └── index.ts │ ├── request.ts │ ├── common │ │ ├── index.ts │ │ └── work.ts │ ├── forum │ │ ├── types.ts │ │ └── index.ts │ ├── manga │ │ ├── types.ts │ │ └── index.ts │ ├── types.ts │ └── anime │ │ ├── index.ts │ │ └── types.ts │ └── jikan4 │ ├── magazines.ts │ ├── producers.ts │ ├── genres.ts │ ├── schedules.ts │ ├── reviews.ts │ ├── recommendations.ts │ ├── random.ts │ ├── watch.ts │ ├── season.ts │ ├── person.ts │ ├── character.ts │ ├── top.ts │ ├── club.ts │ ├── index.ts │ ├── manga.ts │ └── anime.ts ├── .vscode ├── settings.json └── tasks.json ├── .npmignore ├── .github └── workflows │ └── tests.yml ├── tsconfig.json ├── package.json ├── .gitignore └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /docs/assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PolyMeilex/node-myanimelist/HEAD/docs/assets/icons.png -------------------------------------------------------------------------------- /docs/assets/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PolyMeilex/node-myanimelist/HEAD/docs/assets/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PolyMeilex/node-myanimelist/HEAD/docs/assets/widgets.png -------------------------------------------------------------------------------- /docs/assets/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PolyMeilex/node-myanimelist/HEAD/docs/assets/widgets@2x.png -------------------------------------------------------------------------------- /jikan_generator/README.md: -------------------------------------------------------------------------------- 1 | # Quick and dirty TS generator for openapi 2 | 3 | ### Pleass don't use this for anything! 4 | -------------------------------------------------------------------------------- /test/mal/dummyData/index.ts: -------------------------------------------------------------------------------- 1 | export * as Manga from "./manga/search"; 2 | export * as Animelist from "./user/animelistDef"; 3 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "out": "docs", 3 | "entryPoints": ["./src/index.ts"], 4 | "categorizeByGroup": false, 5 | "categoryOrder": ["*", "Other"] 6 | } 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * as Jikan4 from "./methods/jikan4"; 2 | export * as Mal from "./methods/malApi"; 3 | 4 | export const { version } = require("../package.json"); 5 | -------------------------------------------------------------------------------- /src/methods/malApi/api.ts: -------------------------------------------------------------------------------- 1 | const apiUrl = "https://api.myanimelist.net/v2"; 2 | const secondaryApiUrl = "https://myanimelist.net/v1"; 3 | 4 | export { apiUrl, secondaryApiUrl }; 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.tabSize": 2, 4 | "[typescript]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # NPM 2 | node_modules 3 | .git 4 | .vscode 5 | .gitattributes 6 | .gitignore 7 | gulpfile.js 8 | tests/ 9 | test/ 10 | tsconfig.json 11 | readme.md 12 | src/ 13 | dataExamples/ 14 | docs/ 15 | jikan_generator -------------------------------------------------------------------------------- /src/methods/jikan4/magazines.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | export function magazines(): Promise { 6 | return jikanGet(urljoin(jikanUrl, "magazines")); 7 | } 8 | -------------------------------------------------------------------------------- /src/methods/jikan4/producers.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Producers*/ 6 | export function producers(): Promise { 7 | return jikanGet(urljoin(jikanUrl, "producers")); 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "gulp", 9 | "task": "build", 10 | "problemMatcher": [ 11 | "$gulp-tsc" 12 | ] 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | - master 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: npm install, build and test 16 | run: | 17 | npm install 18 | npm run build 19 | npm test 20 | -------------------------------------------------------------------------------- /test/mal/util/types.ts: -------------------------------------------------------------------------------- 1 | export class Type { 2 | private t: string; 3 | constructor(t) { 4 | this.t = t; 5 | } 6 | toString() { 7 | return this.t; 8 | } 9 | } 10 | 11 | export const string = new Type("string"); 12 | export const number = new Type("number"); 13 | export const boolean = new Type("boolean"); 14 | export const object = new Type("object"); 15 | -------------------------------------------------------------------------------- /src/methods/jikan4/genres.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Genres*/ 6 | export function animeGenres(): Promise { 7 | return jikanGet(urljoin(jikanUrl, "genres", "anime")); 8 | } 9 | 10 | /** @category Genres*/ 11 | export function mangaGenres(): Promise { 12 | return jikanGet(urljoin(jikanUrl, "genres", "manga")); 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ES2019", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "lib": ["ES2020", "DOM"], 9 | "declaration": true, 10 | "sourceMap": true, 11 | "removeComments": false, 12 | "outDir": "./dist/", 13 | "declarationDir": "./typings/", 14 | "experimentalDecorators": true, 15 | "resolveJsonModule": true 16 | }, 17 | "include": ["./src"] 18 | } 19 | -------------------------------------------------------------------------------- /src/methods/jikan4/schedules.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Schedules*/ 6 | export function schedules(params?: { 7 | page?: number; 8 | filter?: 9 | | "monday" 10 | | "tuesday" 11 | | "wednesday" 12 | | "thursday" 13 | | "friday" 14 | | "unknown" 15 | | "other"; 16 | }): Promise { 17 | const url = urljoin(jikanUrl, "schedules") + queryJoin({ ...params }); 18 | return jikanGet(url); 19 | } 20 | -------------------------------------------------------------------------------- /src/methods/jikan4/reviews.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Reviews*/ 6 | export function animeReviews(params?: { 7 | page?: number; 8 | }): Promise { 9 | const url = urljoin(jikanUrl, "reviews", "anime") + queryJoin({ ...params }); 10 | return jikanGet(url); 11 | } 12 | 13 | /** @category Reviews*/ 14 | export function mangaReviews(params?: { 15 | page?: number; 16 | }): Promise { 17 | const url = urljoin(jikanUrl, "reviews", "manga") + queryJoin({ ...params }); 18 | return jikanGet(url); 19 | } 20 | -------------------------------------------------------------------------------- /src/methods/malApi/util.ts: -------------------------------------------------------------------------------- 1 | export function queryEncode(obj: any) { 2 | return Object.keys(obj) 3 | .map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(obj[key])) 4 | .join("&"); 5 | } 6 | 7 | export function field( 8 | _target: any, 9 | propertyKey: string, 10 | descriptor: PropertyDescriptor 11 | ) { 12 | const after = descriptor.value; 13 | 14 | const real_name = propertyKey 15 | .split(/(?=[A-Z])/) 16 | .map((s) => s.toLocaleLowerCase()) 17 | .join("_"); 18 | 19 | descriptor.value = function (...args: any[]) { 20 | (this as any).fields[real_name] = true; 21 | 22 | return after.apply(this, args); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/methods/jikan4/recommendations.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Recommendations*/ 6 | export function animeRecommendations(params?: { 7 | page?: number; 8 | }): Promise<{ data: Types.Anime }> { 9 | const url = 10 | urljoin(jikanUrl, "recommendations", "anime") + queryJoin({ ...params }); 11 | return jikanGet(url); 12 | } 13 | 14 | /** @category Recommendations*/ 15 | export function mangaRecommendations(params?: { 16 | page?: number; 17 | }): Promise<{ data: Types.Manga }> { 18 | const url = 19 | urljoin(jikanUrl, "recommendations", "manga") + queryJoin({ ...params }); 20 | return jikanGet(url); 21 | } 22 | -------------------------------------------------------------------------------- /test/jikan_online.ts: -------------------------------------------------------------------------------- 1 | import { Jikan4 } from "../"; 2 | import axios from "axios"; 3 | import assert from "assert"; 4 | 5 | describe("Online Anime", () => { 6 | it("/anime/1", async () => { 7 | // Bring back oryginal get function 8 | // @ts-ignore 9 | global.__jikanGet = function(url: string): Promise { 10 | return new Promise((res, rej) => { 11 | axios 12 | .get(url) 13 | .then((r) => res(r.data)) 14 | .catch((err) => rej(err)); 15 | }); 16 | }; 17 | 18 | const data = (await Jikan4.anime(1).info()).data; 19 | 20 | assert.equal(1, data.mal_id); 21 | 22 | // Bring back test get function 23 | // @ts-ignore 24 | global.jikanGet = (s) => s; 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/methods/malApi/user/types.ts: -------------------------------------------------------------------------------- 1 | export interface UserBase { 2 | id: number; 3 | name: string; 4 | location: string | null; 5 | joined_at: string; 6 | picture: string; 7 | } 8 | 9 | export module User { 10 | export interface Gender { 11 | gender: string | null; 12 | } 13 | export interface Birthday { 14 | birthday: string | null; 15 | } 16 | export interface AnimeStatistics { 17 | anime_statistics: unknown | null; 18 | } 19 | export interface TimeZone { 20 | time_zone: string | null; 21 | } 22 | export interface IsSupporter { 23 | is_supporter: boolean | null; 24 | } 25 | } 26 | 27 | export interface AnimeListItem { 28 | node: T; 29 | list_status: S; 30 | } 31 | 32 | export interface MangaListItem { 33 | node: T; 34 | list_status: S; 35 | } 36 | -------------------------------------------------------------------------------- /src/methods/malApi/user/fields.ts: -------------------------------------------------------------------------------- 1 | import { field as f } from "../util"; 2 | import { UserBase, User } from "./types"; 3 | 4 | export class UserFields { 5 | fields: any = {}; 6 | 7 | type: T = null as any; 8 | 9 | /** Aka `I don't care mode` */ 10 | all() { 11 | return this.gender().birthday().animeStatistics().timeZone().isSupporter(); 12 | } 13 | 14 | @f gender() { 15 | return (this as any) as UserFields; 16 | } 17 | @f birthday() { 18 | return (this as any) as UserFields; 19 | } 20 | @f animeStatistics() { 21 | return (this as any) as UserFields; 22 | } 23 | @f timeZone() { 24 | return (this as any) as UserFields; 25 | } 26 | @f isSupporter() { 27 | return (this as any) as UserFields; 28 | } 29 | } 30 | 31 | export function fields() { 32 | return new UserFields(); 33 | } 34 | -------------------------------------------------------------------------------- /src/methods/malApi/request.ts: -------------------------------------------------------------------------------- 1 | // import { MalToken } from "."; 2 | import { AxiosRequestConfig, Method, AxiosError } from "axios"; 3 | import axios from "axios"; 4 | // import urljoin from "url-join"; 5 | 6 | export class MalRequest { 7 | // method: Method = "get"; 8 | // url: string; 9 | // headers: { [key: string]: string } = {}; 10 | // data: any; 11 | 12 | config: AxiosRequestConfig; 13 | 14 | constructor(config: AxiosRequestConfig) { 15 | this.config = config; 16 | } 17 | 18 | getUrl(): string { 19 | return axios.getUri(this.config); 20 | } 21 | 22 | async call(): Promise { 23 | return new Promise((res, rej) => { 24 | axios(this.config) 25 | .then((r) => res(r.data)) 26 | .catch((err) => rej(err)); 27 | }); 28 | } 29 | } 30 | export default MalRequest; 31 | 32 | export interface MalError { 33 | message?: string; 34 | error: string; 35 | } 36 | 37 | export type ResponseError = AxiosError; 38 | -------------------------------------------------------------------------------- /src/methods/jikan4/random.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Random*/ 6 | export function randomAnime(): Promise<{ data: Types.Anime }> { 7 | return jikanGet(urljoin(jikanUrl, "random", "anime")); 8 | } 9 | 10 | /** @category Random*/ 11 | export function randomManga(): Promise<{ data: Types.Manga }> { 12 | return jikanGet(urljoin(jikanUrl, "random", "manga")); 13 | } 14 | 15 | /** @category Random*/ 16 | export function randomCharacters(): Promise<{ data: Types.Character }> { 17 | return jikanGet(urljoin(jikanUrl, "random", "characters")); 18 | } 19 | 20 | /** @category Random*/ 21 | export function randomPerson(): Promise<{ data: Types.Person }> { 22 | return jikanGet(urljoin(jikanUrl, "random", "people")); 23 | } 24 | 25 | /** @category Random*/ 26 | export function randomUser(): Promise<{ data: Types.UserProfile }> { 27 | return jikanGet(urljoin(jikanUrl, "random", "users")); 28 | } 29 | -------------------------------------------------------------------------------- /test/mal/util/index.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import * as T from "./types"; 3 | 4 | export function nullable(src: T, cb: (t: T) => void) { 5 | if (src != null) { 6 | cb(src); 7 | } else { 8 | it("null", () => {}); 9 | } 10 | } 11 | 12 | export function nonNullable(src: T, cb: (t: T) => void) { 13 | if (src != null) { 14 | cb(src); 15 | } else { 16 | it("null", () => assert.fail("Null in non nullable field")); 17 | } 18 | } 19 | 20 | export function isOneOf(src: T, arr: T[]) { 21 | return arr.some((i) => src == i); 22 | } 23 | export function assertIsOneOf(src: T, arr: T[]) { 24 | if (!isOneOf(src, arr)) { 25 | assert.fail(`Expected one of ${arr.toString()}`); 26 | } 27 | } 28 | 29 | export function assertTypeof( 30 | src: U, 31 | type: T.Type, 32 | nullable: boolean = false 33 | ) { 34 | if (!nullable && src == null) { 35 | assert.fail("Null in non nullable field"); 36 | } else if (src != null) { 37 | assert.equal(type.toString(), typeof src); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-myanimelist", 3 | "version": "4.1.2", 4 | "description": "Node.js wrappers for MAL.", 5 | "main": "dist/index.js", 6 | "types": "typings/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "dev": "tsc --watch", 10 | "test": "./node_modules/ts-mocha/bin/ts-mocha test/*.ts", 11 | "doc": "typedoc", 12 | "postdoc": "touch docs/.nojekyll" 13 | }, 14 | "keywords": [ 15 | "MAL", 16 | "Myanimelist", 17 | "jikan", 18 | "API", 19 | "Wrapper" 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/PolyMagic/node-myanimelist.git" 24 | }, 25 | "author": "Poly", 26 | "license": "MIT", 27 | "dependencies": { 28 | "axios": "^0.25.0", 29 | "url-join": "^4.0.1" 30 | }, 31 | "devDependencies": { 32 | "@types/mocha": "^9.1.0", 33 | "@types/node": "^17.0.17", 34 | "@types/url-join": "^4.0.1", 35 | "i": "^0.3.6", 36 | "mocha": "^9.2.0", 37 | "npm": "^6.14.5", 38 | "pkce-challenge": "^2.2.0", 39 | "ts-mocha": "^9.0.2", 40 | "typedoc": "^0.22.11", 41 | "typescript": "^4.5.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Optional eslint cache 43 | .eslintcache 44 | 45 | # Optional REPL history 46 | .node_repl_history 47 | 48 | # Output of 'npm pack' 49 | *.tgz 50 | 51 | # Yarn Integrity file 52 | .yarn-integrity 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # Others 58 | dist/ 59 | 60 | typings/ 61 | 62 | gen/ -------------------------------------------------------------------------------- /src/methods/malApi/common/index.ts: -------------------------------------------------------------------------------- 1 | import { WorkBase } from "./work"; 2 | 3 | export * from "./work"; 4 | 5 | export interface Picture { 6 | large: string | null; 7 | medium: string; 8 | } 9 | 10 | export interface Paging { 11 | data: T[]; 12 | paging: { 13 | previous?: string | null; 14 | next?: string | null; 15 | }; 16 | } 17 | 18 | export interface RankingItem { 19 | ranking: { 20 | rank: number; 21 | previous_rank: number | null; 22 | }; 23 | } 24 | 25 | export interface PersonBase { 26 | id: number; 27 | first_name: string; 28 | last_name: string; 29 | } 30 | 31 | export interface PersonRoleEdge { 32 | node: PersonBase; 33 | role: string; 34 | } 35 | 36 | export interface RelatedEdge { 37 | node: WorkBase & T; 38 | /** The type of the relationship between this work and related work. */ 39 | relation_type: 40 | | "sequel" 41 | | "prequel" 42 | | "alternative_setting" 43 | | "alternative_version" 44 | | "side_story" 45 | | "parent_story" 46 | | "summary" 47 | | "full_story"; 48 | /** The format of relation_type for human like "Alternative version". */ 49 | relation_type_formatted: string; 50 | } 51 | 52 | export interface RecommendationEdge { 53 | node: WorkBase & T; 54 | num_recommendations: number; 55 | } 56 | -------------------------------------------------------------------------------- /src/methods/jikan4/watch.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Watch*/ 6 | export function watchRecentEpisodes(params?: { 7 | page?: number; 8 | limit?: number; 9 | }): Promise { 10 | const url = urljoin(jikanUrl, "watch", "episodes") + queryJoin({ ...params }); 11 | return jikanGet(url); 12 | } 13 | 14 | /** @category Watch*/ 15 | export function watchPopularEpisodes(params?: { 16 | page?: number; 17 | limit?: number; 18 | }): Promise { 19 | const url = 20 | urljoin(jikanUrl, "watch", "episodes", "popular") + 21 | queryJoin({ ...params }); 22 | return jikanGet(url); 23 | } 24 | 25 | /** @category Watch*/ 26 | export function watchRecentPromos(params?: { 27 | page?: number; 28 | limit?: number; 29 | }): Promise { 30 | const url = urljoin(jikanUrl, "watch", "promos") + queryJoin({ ...params }); 31 | return jikanGet(url); 32 | } 33 | 34 | /** @category Watch*/ 35 | export function watchPopularPromos(params?: { 36 | page?: number; 37 | limit?: number; 38 | }): Promise { 39 | const url = 40 | urljoin(jikanUrl, "watch", "promos", "popular") + queryJoin({ ...params }); 41 | return jikanGet(url); 42 | } 43 | -------------------------------------------------------------------------------- /src/methods/jikan4/season.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Season*/ 6 | export function season( 7 | year: number, 8 | season: "spring" | "summer" | "fall" | "winter", 9 | params?: { 10 | page?: number; 11 | limit?: number; 12 | } 13 | ): Promise<{ data: Types.Anime[] } & Types.Pagination> { 14 | const url = 15 | urljoin(jikanUrl, "seasons", year.toString(), season) + 16 | queryJoin({ ...params }); 17 | return jikanGet(url); 18 | } 19 | 20 | /** @category Season*/ 21 | export function seasonNow(params?: { 22 | page?: number; 23 | limit?: number; 24 | }): Promise<{ data: Types.Anime[] } & Types.Pagination> { 25 | const url = urljoin(jikanUrl, "seasons", "now") + queryJoin({ ...params }); 26 | return jikanGet(url); 27 | } 28 | 29 | /** @category Season*/ 30 | export function seasonsList(): Promise { 31 | const url = urljoin(jikanUrl, "seasons"); 32 | return jikanGet(url); 33 | } 34 | 35 | /** @category Season*/ 36 | export function seasonUpcoming(params?: { 37 | page?: number; 38 | limit?: number; 39 | }): Promise<{ data: Types.Anime[] } & Types.Pagination> { 40 | const url = 41 | urljoin(jikanUrl, "seasons", "upcoming") + queryJoin({ ...params }); 42 | return jikanGet(url); 43 | } 44 | -------------------------------------------------------------------------------- /test/mal.ts: -------------------------------------------------------------------------------- 1 | import { Mal } from "../"; 2 | import { main } from "./mal/index"; 3 | 4 | // import pkceChallenge from "pkce-challenge"; 5 | // const pkce = pkceChallenge(); 6 | const pkce = { 7 | code_challenge: "YGPBICGddpnGlq2bZxDL7GQtsNjKOld8Xv7eEFOJKYY", 8 | code_verifier: "YMIUGMD.QSZj4x4~84zEioVPHXY6eoUiQ~4A1MC2xyJ", 9 | }; 10 | 11 | import assert from "assert"; 12 | 13 | describe("MalAPI", async () => { 14 | const clientId = "6114d00ca681b7701d1e15fe11a4987e"; 15 | 16 | it("OAuth Url", () => { 17 | const mal = Mal.auth(clientId); 18 | 19 | let res = mal.getOAuthUrl(pkce.code_challenge); 20 | console.log(res); 21 | 22 | const base = "https://myanimelist.net/v1/oauth2"; 23 | const url = `${base}/authorize?response_type=code&client_id=${clientId}&code_challenge_method=plain&code_challenge=${pkce.code_challenge}`; 24 | 25 | assert.equal(url, res); 26 | }); 27 | 28 | it("Login With Code", async () => { 29 | const mal = Mal.auth(clientId); 30 | 31 | const code = process.env["MAL_CODE"]; 32 | 33 | if (code) { 34 | const res = await mal.authorizeWithCode(code, pkce.code_challenge); 35 | console.log(res); 36 | } 37 | }); 38 | 39 | it("Login Form Env", () => { 40 | // let acount = await mal.unstable.login(l, p); 41 | }); 42 | it("Token From Env", async () => 43 | await main( 44 | new Mal.MalToken("Bearer", process.env["MAL_TOKEN"], "...", 2678400) 45 | )); 46 | }); 47 | -------------------------------------------------------------------------------- /src/methods/jikan4/person.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Person*/ 6 | export class Person { 7 | /** @ignore */ 8 | private baseUrl: string; 9 | 10 | constructor(id: number) { 11 | this.baseUrl = `${jikanUrl}/people/${id}`; 12 | } 13 | 14 | info(): Promise<{ data: Types.Person }> { 15 | return jikanGet(this.baseUrl); 16 | } 17 | 18 | anime(): Promise { 19 | return jikanGet(urljoin(this.baseUrl, "anime")); 20 | } 21 | 22 | voices(): Promise { 23 | return jikanGet(urljoin(this.baseUrl, "voices")); 24 | } 25 | 26 | manga(): Promise { 27 | return jikanGet(urljoin(this.baseUrl, "manga")); 28 | } 29 | 30 | pictures(): Promise { 31 | return jikanGet(urljoin(this.baseUrl, "pictures")); 32 | } 33 | } 34 | 35 | /** @category Person*/ 36 | export function person(id: number): Person { 37 | return new Person(id); 38 | } 39 | 40 | /** @category Person*/ 41 | export function personSearch(params?: { 42 | page?: number; 43 | limit?: number; 44 | q?: string; 45 | order_by?: "mal_id" | "name" | "birthday" | "favorites"; 46 | sort?: "desc" | "asc"; 47 | letter?: string; 48 | }): Promise { 49 | const url = urljoin(jikanUrl, "people") + queryJoin({ ...params }); 50 | return jikanGet(url); 51 | } 52 | -------------------------------------------------------------------------------- /src/methods/jikan4/character.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Character*/ 6 | export class Character { 7 | /** @ignore */ 8 | private baseUrl: string; 9 | 10 | constructor(id: number) { 11 | this.baseUrl = `${jikanUrl}/characters/${id}`; 12 | } 13 | 14 | info(): Promise { 15 | return jikanGet(this.baseUrl); 16 | } 17 | 18 | anime(): Promise { 19 | return jikanGet(urljoin(this.baseUrl, "anime")); 20 | } 21 | 22 | manga(): Promise { 23 | return jikanGet(urljoin(this.baseUrl, "manga")); 24 | } 25 | 26 | voiceActors(): Promise { 27 | return jikanGet(urljoin(this.baseUrl, "voices")); 28 | } 29 | 30 | pictures(): Promise { 31 | return jikanGet(urljoin(this.baseUrl, "pictures")); 32 | } 33 | } 34 | 35 | /** @category Character*/ 36 | export function character(id: number): Character { 37 | return new Character(id); 38 | } 39 | 40 | /** @category Character*/ 41 | export function characterSearch(params?: { 42 | page?: number; 43 | limit?: number; 44 | q?: string; 45 | order_by?: "mal_id" | "name" | "favorites"; 46 | sort?: "desc" | "asc"; 47 | letter?: string; 48 | }): Promise { 49 | const url = urljoin(jikanUrl, "characters") + queryJoin({ ...params }); 50 | return jikanGet(url); 51 | } 52 | -------------------------------------------------------------------------------- /src/methods/malApi/forum/types.ts: -------------------------------------------------------------------------------- 1 | export interface ForumCategory { 2 | title: string; 3 | boards: ForumBoard[]; 4 | } 5 | 6 | export interface ForumBoard { 7 | id: number; 8 | title: string; 9 | description: string; 10 | subboards: ForumSubboard[]; 11 | } 12 | 13 | export interface ForumSubboard { 14 | id: number; 15 | title: string; 16 | } 17 | 18 | // Detail 19 | 20 | export interface ForumTopicData { 21 | title: string; 22 | posts: ForumTopicPost[]; 23 | poll: ForumTopicPoll | null; 24 | } 25 | 26 | export interface ForumTopicPost { 27 | id: number; 28 | number: number; 29 | created_at: string; 30 | created_by: ForumTopicPostCreatedBy; 31 | body: string; 32 | signature: string; 33 | } 34 | 35 | export interface ForumTopicPostCreatedBy { 36 | id: number; 37 | name: string; 38 | forum_avator: string; 39 | } 40 | 41 | export interface ForumTopicPoll { 42 | id: number; 43 | question: string; 44 | close: boolean; 45 | options: ForumTopicPollOption[]; 46 | } 47 | 48 | export interface ForumTopicPollOption { 49 | id: number; 50 | text: string; 51 | votes: number; 52 | } 53 | 54 | // Topics 55 | 56 | export interface ForumTopicsData { 57 | id: number; 58 | title: string; 59 | created_at: string; 60 | created_by: ForumTopicsCreatedBy; 61 | number_of_posts: number; 62 | last_post_created_at: string; 63 | last_post_created_by: ForumTopicsCreatedBy; 64 | is_locked: boolean; 65 | } 66 | 67 | export interface ForumTopicsCreatedBy { 68 | id: number; 69 | name: string; 70 | } 71 | -------------------------------------------------------------------------------- /src/methods/jikan4/top.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Top*/ 6 | export function topAnime(params?: { 7 | page?: number; 8 | limit?: number; 9 | }): Promise<{ data: Types.Anime[] } & Types.Pagination> { 10 | const url = urljoin(jikanUrl, "top", "anime") + queryJoin({ ...params }); 11 | return jikanGet(url); 12 | } 13 | 14 | /** @category Top*/ 15 | export function topManga(params?: { 16 | page?: number; 17 | limit?: number; 18 | }): Promise<{ data: Types.Manga[] } & Types.Pagination> { 19 | const url = urljoin(jikanUrl, "top", "manga") + queryJoin({ ...params }); 20 | return jikanGet(url); 21 | } 22 | 23 | /** @category Top*/ 24 | export function topPeople(params?: { 25 | page?: number; 26 | limit?: number; 27 | }): Promise<{ data: Types.Person[] } & Types.Pagination> { 28 | const url = urljoin(jikanUrl, "top", "people") + queryJoin({ ...params }); 29 | return jikanGet(url); 30 | } 31 | 32 | /** @category Top*/ 33 | export function topCharacters(params?: { 34 | page?: number; 35 | limit?: number; 36 | }): Promise<{ data: Types.Character[] } & Types.Pagination> { 37 | const url = urljoin(jikanUrl, "top", "characters") + queryJoin({ ...params }); 38 | return jikanGet(url); 39 | } 40 | 41 | /** @category Top*/ 42 | export function topReviews(params?: { 43 | page?: number; 44 | limit?: number; 45 | }): Promise { 46 | const url = urljoin(jikanUrl, "top", "characters") + queryJoin({ ...params }); 47 | return jikanGet(url); 48 | } 49 | -------------------------------------------------------------------------------- /src/methods/jikan4/club.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Club*/ 6 | export class Club { 7 | /** @ignore */ 8 | private baseUrl: string; 9 | 10 | constructor(id: number) { 11 | this.baseUrl = `${jikanUrl}/clubs/${id}`; 12 | } 13 | 14 | info(): Promise { 15 | return jikanGet(this.baseUrl); 16 | } 17 | 18 | members(params?: { page?: number }): Promise { 19 | const url = urljoin(this.baseUrl, "members") + queryJoin({ ...params }); 20 | return jikanGet(url); 21 | } 22 | 23 | staff(): Promise { 24 | return jikanGet(urljoin(this.baseUrl, "staff")); 25 | } 26 | 27 | relations(): Promise { 28 | return jikanGet(urljoin(this.baseUrl, "relations")); 29 | } 30 | } 31 | 32 | /** @category Club*/ 33 | export function club(id: number): Club { 34 | return new Club(id); 35 | } 36 | 37 | /** @category Club*/ 38 | export function clubSearch(params?: { 39 | page?: number; 40 | limit?: number; 41 | q?: string; 42 | type?: "public" | "private" | "secret"; 43 | category?: 44 | | "anime" 45 | | "manga" 46 | | "actors_and_artists" 47 | | "characters" 48 | | "cities_and_neighborhoods" 49 | | "companies" 50 | | "conventions" 51 | | "games" 52 | | "japan" 53 | | "music" 54 | | "other" 55 | | "schools"; 56 | order_by?: 57 | | "mal_id" 58 | | "title" 59 | | "members_count" 60 | | "pictures_count" 61 | | "created"; 62 | sort?: "desc" | "asc"; 63 | letter?: string; 64 | }): Promise { 65 | const url = urljoin(jikanUrl, "clubs") + queryJoin({ ...params }); 66 | return jikanGet(url); 67 | } 68 | -------------------------------------------------------------------------------- /src/methods/malApi/forum/index.ts: -------------------------------------------------------------------------------- 1 | import { MalAcount } from ".."; 2 | import { MalRequest } from "../request"; 3 | import { apiUrl } from "../api"; 4 | 5 | import { ForumCategory, ForumTopicData, ForumTopicsData } from "./types"; 6 | import { queryEncode } from "../util"; 7 | import { Paging } from "../common"; 8 | import { AxiosRequestConfig } from "axios"; 9 | 10 | export * from "./types"; 11 | 12 | export class MalForum { 13 | private acount: MalAcount; 14 | 15 | constructor(acount: MalAcount) { 16 | this.acount = acount; 17 | } 18 | 19 | boards(): MalRequest<{ categories: ForumCategory }> { 20 | const config: AxiosRequestConfig = { 21 | url: [apiUrl, "forum", "boards"].join("/"), 22 | headers: this.acount.getHttpHeaders(), 23 | }; 24 | 25 | return new MalRequest(config); 26 | } 27 | 28 | details( 29 | topic_id: number, 30 | limit?: number | null, 31 | offset?: number | null 32 | ): MalRequest> { 33 | const config: AxiosRequestConfig = { 34 | url: [apiUrl, "forum", "topic", topic_id.toString()].join("/"), 35 | headers: this.acount.getHttpHeaders(), 36 | params: {}, 37 | }; 38 | 39 | if (limit != null) config.params.limit = limit; 40 | if (offset != null) config.params.offset = offset; 41 | 42 | return new MalRequest(config); 43 | } 44 | 45 | topics(params: { 46 | board_id?: number; 47 | subboard_id?: number; 48 | limit?: number; 49 | offset?: number; 50 | sort?: string; 51 | q?: string; 52 | topic_user_name?: string; 53 | user_name?: string; 54 | }): MalRequest> { 55 | const config: AxiosRequestConfig = { 56 | url: [apiUrl, "forum", "topics"].join("/"), 57 | headers: this.acount.getHttpHeaders(), 58 | params: params, 59 | }; 60 | 61 | return new MalRequest(config); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/methods/jikan4/index.ts: -------------------------------------------------------------------------------- 1 | export * as Types from "./types"; 2 | 3 | export { anime, animeSearch, Anime } from "./anime"; 4 | export { manga, mangaSearch, Manga } from "./manga"; 5 | export { character, characterSearch, Character } from "./character"; 6 | export { club, clubSearch, Club } from "./club"; 7 | export { animeGenres, mangaGenres } from "./genres"; 8 | export { magazines } from "./magazines"; 9 | export { person, personSearch, Person } from "./person"; 10 | export { producers } from "./producers"; 11 | export { 12 | randomAnime, 13 | randomManga, 14 | randomCharacters, 15 | randomPerson, 16 | randomUser, 17 | } from "./random"; 18 | export { animeRecommendations, mangaRecommendations } from "./recommendations"; 19 | export { animeReviews, mangaReviews } from "./reviews"; 20 | export { schedules } from "./schedules"; 21 | export { season, seasonNow, seasonsList, seasonUpcoming } from "./season"; 22 | export { 23 | topAnime, 24 | topManga, 25 | topPeople, 26 | topCharacters, 27 | topReviews, 28 | } from "./top"; 29 | export { 30 | watchRecentEpisodes, 31 | watchPopularEpisodes, 32 | watchRecentPromos, 33 | watchPopularPromos, 34 | } from "./watch"; 35 | 36 | import axios from "axios"; 37 | 38 | /** @ignore */ 39 | export function queryJoin(params: { [key: string]: any }): string { 40 | const url_params = Object.entries(params) 41 | .filter(([_, value]) => value != null && value != undefined) 42 | .map(([key, value]) => `${key}=${value}`); 43 | 44 | if (url_params.length != 0) { 45 | return "?" + url_params.join("&"); 46 | } else { 47 | return ""; 48 | } 49 | } 50 | 51 | // @ts-ignore 52 | global.__jikanGet = async (url: string) => { 53 | const res = await axios.get(url); 54 | return res.data; 55 | }; 56 | 57 | /** @ignore */ 58 | export function jikanGet(url: string): Promise { 59 | // @ts-ignore 60 | return global.__jikanGet(url); 61 | } 62 | 63 | export const jikanUrl = `https://api.jikan.moe/v4`; 64 | -------------------------------------------------------------------------------- /src/methods/malApi/common/work.ts: -------------------------------------------------------------------------------- 1 | import { Picture } from "."; 2 | 3 | export interface WorkBase { 4 | id: number; 5 | title: string; 6 | main_picture: Picture | null; 7 | } 8 | 9 | export module WorkForList { 10 | export interface AlternativeTitles { 11 | /** 12 | * "synonyms" or ISO 639-1 13 | */ 14 | alternative_titles: { 15 | synonyms: string[]; 16 | en: string | null; 17 | ja: string | null; 18 | } | null; 19 | } 20 | 21 | export interface StartDate { 22 | start_date: string | null; 23 | } 24 | 25 | export interface EndDate { 26 | end_date: string | null; 27 | } 28 | 29 | export interface Synopsis { 30 | /** 31 | * Synopsis. 32 | * 33 | * The API strips BBCode tags from the result. 34 | */ 35 | synopsis: string | null; 36 | } 37 | 38 | export interface Mean { 39 | /** 40 | * Mean score. 41 | * 42 | * When the `mean` can not be calculated, such as when the number of user scores is small, the result does not include this field. 43 | */ 44 | mean: number | null; 45 | } 46 | 47 | export interface Rank { 48 | /** 49 | * When the `rank` can not be calculated, such as when the number of user scores is small, the result does not include this field. 50 | */ 51 | rank: number | null; 52 | } 53 | 54 | export interface Popularity { 55 | popularity: number | null; 56 | } 57 | 58 | export interface NumListUsers { 59 | /** 60 | * Number of users who have this work in their list. 61 | */ 62 | num_list_users: number; 63 | } 64 | 65 | export interface NumScoringUsers { 66 | num_scoring_users: number; 67 | } 68 | 69 | export interface Nsfw { 70 | /** 71 | * | Value | Description| 72 | * | :------- | :-------------------------------- | 73 | * | white | This work is safe for work | 74 | * | gray | This work may be not safe for work | 75 | * | black | This work is not safe for work | 76 | */ 77 | nsfw: "white" | "gray" | "black" | null; 78 | } 79 | 80 | export interface Genres { 81 | genres: { id: number; name: string }[]; 82 | } 83 | 84 | export interface CreatedAt { 85 | created_at: string; 86 | } 87 | 88 | export interface UpdatedAt { 89 | updated_at: string; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #000000; 3 | --dark-hl-0: #D4D4D4; 4 | --light-hl-1: #0000FF; 5 | --dark-hl-1: #569CD6; 6 | --light-hl-2: #0070C1; 7 | --dark-hl-2: #4FC1FF; 8 | --light-hl-3: #795E26; 9 | --dark-hl-3: #DCDCAA; 10 | --light-hl-4: #A31515; 11 | --dark-hl-4: #CE9178; 12 | --light-hl-5: #008000; 13 | --dark-hl-5: #6A9955; 14 | --light-hl-6: #AF00DB; 15 | --dark-hl-6: #C586C0; 16 | --light-hl-7: #001080; 17 | --dark-hl-7: #9CDCFE; 18 | --light-code-background: #F5F5F5; 19 | --dark-code-background: #1E1E1E; 20 | } 21 | 22 | @media (prefers-color-scheme: light) { :root { 23 | --hl-0: var(--light-hl-0); 24 | --hl-1: var(--light-hl-1); 25 | --hl-2: var(--light-hl-2); 26 | --hl-3: var(--light-hl-3); 27 | --hl-4: var(--light-hl-4); 28 | --hl-5: var(--light-hl-5); 29 | --hl-6: var(--light-hl-6); 30 | --hl-7: var(--light-hl-7); 31 | --code-background: var(--light-code-background); 32 | } } 33 | 34 | @media (prefers-color-scheme: dark) { :root { 35 | --hl-0: var(--dark-hl-0); 36 | --hl-1: var(--dark-hl-1); 37 | --hl-2: var(--dark-hl-2); 38 | --hl-3: var(--dark-hl-3); 39 | --hl-4: var(--dark-hl-4); 40 | --hl-5: var(--dark-hl-5); 41 | --hl-6: var(--dark-hl-6); 42 | --hl-7: var(--dark-hl-7); 43 | --code-background: var(--dark-code-background); 44 | } } 45 | 46 | body.light { 47 | --hl-0: var(--light-hl-0); 48 | --hl-1: var(--light-hl-1); 49 | --hl-2: var(--light-hl-2); 50 | --hl-3: var(--light-hl-3); 51 | --hl-4: var(--light-hl-4); 52 | --hl-5: var(--light-hl-5); 53 | --hl-6: var(--light-hl-6); 54 | --hl-7: var(--light-hl-7); 55 | --code-background: var(--light-code-background); 56 | } 57 | 58 | body.dark { 59 | --hl-0: var(--dark-hl-0); 60 | --hl-1: var(--dark-hl-1); 61 | --hl-2: var(--dark-hl-2); 62 | --hl-3: var(--dark-hl-3); 63 | --hl-4: var(--dark-hl-4); 64 | --hl-5: var(--dark-hl-5); 65 | --hl-6: var(--dark-hl-6); 66 | --hl-7: var(--dark-hl-7); 67 | --code-background: var(--dark-code-background); 68 | } 69 | 70 | .hl-0 { color: var(--hl-0); } 71 | .hl-1 { color: var(--hl-1); } 72 | .hl-2 { color: var(--hl-2); } 73 | .hl-3 { color: var(--hl-3); } 74 | .hl-4 { color: var(--hl-4); } 75 | .hl-5 { color: var(--hl-5); } 76 | .hl-6 { color: var(--hl-6); } 77 | .hl-7 { color: var(--hl-7); } 78 | pre, code { background: var(--code-background); } 79 | -------------------------------------------------------------------------------- /test/mal/index.ts: -------------------------------------------------------------------------------- 1 | import { Mal } from "../../"; 2 | 3 | import { manga } from "./manga"; 4 | import * as Data from "./dummyData"; 5 | 6 | import assert from "assert"; 7 | 8 | const mal = Mal.auth("6114d00ca681b7701d1e15fe11a4987e"); 9 | 10 | function forum(acount: Mal.MalAcount) { 11 | let forum = acount.forum; 12 | 13 | describe("Forum", async () => { 14 | { 15 | let boards = forum.boards(); 16 | it("/forum/boards", () => 17 | assert.equal( 18 | "https://api.myanimelist.net/v2/forum/boards", 19 | boards.getUrl() 20 | )); 21 | } 22 | 23 | { 24 | let details = forum.details(5); 25 | it("/forum/topic/5", () => 26 | assert.equal( 27 | "https://api.myanimelist.net/v2/forum/topic/5", 28 | details.getUrl() 29 | )); 30 | } 31 | 32 | { 33 | let details = forum.details(5, 50); 34 | it("/forum/topic/5?limit=50", () => 35 | assert.equal( 36 | "https://api.myanimelist.net/v2/forum/topic/5?limit=50", 37 | details.getUrl() 38 | )); 39 | } 40 | 41 | { 42 | let details = forum.details(5, 50, 1); 43 | it("/forum/topic/5?limit=50&offset=1", () => 44 | assert.equal( 45 | "https://api.myanimelist.net/v2/forum/topic/5?limit=50&offset=1", 46 | details.getUrl() 47 | )); 48 | } 49 | 50 | { 51 | let details = forum.topics({ q: "Official" }); 52 | it("/forum/topics?q=Official", () => 53 | assert.equal( 54 | "https://api.myanimelist.net/v2/forum/topics?q=Official", 55 | details.getUrl() 56 | )); 57 | } 58 | 59 | { 60 | let details = forum.topics({ q: "Official", limit: 1 }); 61 | it("/forum/topics?q=Official&limit=1", () => 62 | assert.equal( 63 | "https://api.myanimelist.net/v2/forum/topics?q=Official&limit=1", 64 | details.getUrl() 65 | )); 66 | } 67 | }); 68 | } 69 | 70 | async function animelist(acount: Mal.MalAcount) { 71 | const req = acount.user.animelist( 72 | "@me", 73 | Mal.Anime.fields(), 74 | Mal.Anime.listStatusFields() 75 | ); 76 | 77 | req.call = () => Data.Animelist.def as any; 78 | 79 | const data = await req.call(); 80 | 81 | // let item = data.data[0]; 82 | } 83 | 84 | export async function main(t: Mal.MalToken) { 85 | let acount = mal.loadToken(t); 86 | 87 | forum(acount); 88 | await animelist(acount); 89 | await manga(acount); 90 | } 91 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![NodeMal API Banner](https://i.imgur.com/IcBShyO.png) 2 | 3 | # Node-MyAnimeList 4 | Node-MyAnimeList is a small promise based package for downloading information from MyAnimeList. 5 | Node-MyAnimeList is using Jikan.moe API and flew methods created by me specially for this package 6 | 7 | ## Intellisens Support (Typescript) 8 | ![Gif](https://i.imgur.com/J1dUQf2.gif) 9 | 10 | # Instalation 11 | [![NPM](https://nodei.co/npm/node-myanimelist.png)](https://nodei.co/npm/node-myanimelist/) 12 | ```sh 13 | npm i node-myanimelist 14 | ``` 15 | 16 | ### Import 17 | ```js 18 | const { Mal, Jikan } = require("node-myanimelist"); 19 | // Or 20 | import { Mal, Jikan } from 'node-myanimelist'; 21 | ``` 22 | ### MalAPI Example 23 | ```ts 24 | const auth = Mal.auth("6114d00ca681b7701d1e15fe11a4987e" /* app_id */ ); 25 | 26 | // Unoffical way to login (not recomended) 27 | const acount = await auth.Unstable.login("username","password"); 28 | 29 | // Offical way to login (recomended) 30 | // import pkceChallenge from "pkce-challenge"; 31 | // const pkce = pkceChallenge(); 32 | 33 | const url = auth.getOAuthUrl(pkce.code_challenge) 34 | // Open returned url, accept oauth and use returned code to authorize 35 | const acount = await auth.authorizeWithCode(code,pkce.code_challenge); 36 | 37 | let search = await acount.manga.search( 38 | "Sakurasou", 39 | Mal.Manga.fields() 40 | .alternativeTitles() 41 | .startDate() 42 | .endDate() 43 | .synopsis() 44 | .mean() 45 | .rank() 46 | .popularity() 47 | .numListUsers() 48 | .numScoringUsers() 49 | .nsfw() 50 | .genres() 51 | .createdAt() 52 | .updatedAt() 53 | .mediaType() 54 | .status() 55 | .myListStatus( 56 | Mal.Manga.listStatusFields() 57 | .startDate() 58 | .finishDate() 59 | .priority() 60 | .numTimesReread() 61 | .rereadValue() 62 | .tags() 63 | .comments() 64 | ) 65 | .numVolumes() 66 | .numChapters() 67 | .authors() 68 | ).call(); 69 | 70 | // Alternative if you don't care about choosing fields 71 | let searchIDC = await acount.manga.search( 72 | "Sakurasou", 73 | Mal.Manga.fields().all() 74 | ).call(); 75 | ``` 76 | # List of functions 77 | For more detalis visit [doc](https://polymeilex.github.io/node-myanimelist/) 78 | * [MalApi Methods](https://polymeilex.github.io/node-myanimelist/modules/Mal.html) 79 | * [JikanApi Methods](https://polymeilex.github.io/node-myanimelist/modules/Jikan4.html) 80 | -------------------------------------------------------------------------------- /src/methods/jikan4/manga.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Manga*/ 6 | export class Manga { 7 | /** @ignore */ 8 | private baseUrl: string; 9 | 10 | constructor(id: number) { 11 | this.baseUrl = `${jikanUrl}/manga/${id}`; 12 | } 13 | 14 | info(): Promise<{ data: Types.Manga }> { 15 | return jikanGet(this.baseUrl); 16 | } 17 | 18 | characters(): Promise { 19 | return jikanGet(urljoin(this.baseUrl, "characters")); 20 | } 21 | 22 | news(params?: { page?: number }): Promise { 23 | const url = urljoin(this.baseUrl, "news") + queryJoin({ ...params }); 24 | return jikanGet(url); 25 | } 26 | 27 | forum(params?: { 28 | topic?: "all" | "episode" | "other"; 29 | }): Promise { 30 | const url = urljoin(this.baseUrl, "forum") + queryJoin({ ...params }); 31 | return jikanGet(url); 32 | } 33 | 34 | pictures(): Promise { 35 | return jikanGet(urljoin(this.baseUrl, "pictures")); 36 | } 37 | 38 | statistics(): Promise { 39 | return jikanGet(urljoin(this.baseUrl, "statistics")); 40 | } 41 | 42 | moreInfo(): Promise { 43 | return jikanGet(urljoin(this.baseUrl, "moreinfo")); 44 | } 45 | 46 | recommendations(): Promise { 47 | return jikanGet(urljoin(this.baseUrl, "recommendations")); 48 | } 49 | 50 | userUpdates(params?: { page?: number }): Promise { 51 | const url = urljoin(this.baseUrl, "userupdates") + queryJoin({ ...params }); 52 | return jikanGet(url); 53 | } 54 | 55 | reviews(params?: { page?: number }): Promise { 56 | const url = urljoin(this.baseUrl, "reviews") + queryJoin({ ...params }); 57 | return jikanGet(url); 58 | } 59 | 60 | relations(): Promise { 61 | return jikanGet(urljoin(this.baseUrl, "relations")); 62 | } 63 | 64 | external(): Promise { 65 | return jikanGet(urljoin(this.baseUrl, "external")); 66 | } 67 | } 68 | 69 | /** @category Manga*/ 70 | export function manga(id: number): Manga { 71 | return new Manga(id); 72 | } 73 | 74 | /** @category Manga*/ 75 | export function mangaSearch(params?: { 76 | page?: number; 77 | limit?: number; 78 | q?: string; 79 | type?: 80 | | "manga" 81 | | "novel" 82 | | "lightnovel" 83 | | "oneshot" 84 | | "doujin" 85 | | "manhwa" 86 | | "manhua"; 87 | score?: number; 88 | min_score?: number; 89 | max_score?: number; 90 | status?: "publishing" | "complete" | "hiatus" | "discontinued" | "upcoming"; 91 | sfw?: boolean; 92 | genres?: string; 93 | genres_exclude?: string; 94 | order_by?: 95 | | "mal_id" 96 | | "title" 97 | | "start_date" 98 | | "end_date" 99 | | "chapters" 100 | | "volumes" 101 | | "score" 102 | | "scored_by" 103 | | "rank" 104 | | "popularity" 105 | | "members" 106 | | "favorites"; 107 | sort?: "desc" | "asc"; 108 | letter?: string; 109 | magazine?: string; 110 | }): Promise { 111 | const url = urljoin(jikanUrl, "manga") + queryJoin({ ...params }); 112 | return jikanGet(url); 113 | } 114 | -------------------------------------------------------------------------------- /src/methods/malApi/user/index.ts: -------------------------------------------------------------------------------- 1 | import { MalAcount } from ".."; 2 | import MalRequest from "../request"; 3 | 4 | import { apiUrl } from "../api"; 5 | import { AnimeField } from "../types"; 6 | import { AnimeFields, AnimeListStatusFields } from "../anime"; 7 | import { Paging, WorkBase } from "../common"; 8 | import { UserFields } from "./fields"; 9 | import { AnimeListStatusBase } from "../anime/types"; 10 | import { 11 | MangaFields, 12 | MangaListStatusBase, 13 | MangaListStatusFields, 14 | } from "../manga"; 15 | import { UserBase, AnimeListItem, MangaListItem } from "./types"; 16 | import { AxiosRequestConfig } from "axios"; 17 | 18 | export * from "./fields"; 19 | export * from "./types"; 20 | 21 | export class MalUser { 22 | private acount: MalAcount; 23 | 24 | constructor(acount: MalAcount) { 25 | this.acount = acount; 26 | } 27 | 28 | info(fields?: UserFields): MalRequest { 29 | const config: AxiosRequestConfig = { 30 | url: [apiUrl, "users", "@me"].join("/"), 31 | headers: this.acount.getHttpHeaders(), 32 | params: {}, 33 | }; 34 | 35 | if (fields) config.params.fields = fields.toString(); 36 | 37 | return new MalRequest(config); 38 | } 39 | 40 | animelist( 41 | name: string = "@me", 42 | fields?: AnimeFields | null, 43 | listStatusFields?: AnimeListStatusFields | null, 44 | args?: { status?: string; sort?: string; limit?: number; offset?: number, includeNsfw?: boolean } 45 | ): MalRequest>> { 46 | const config: AxiosRequestConfig = { 47 | url: [apiUrl, "users", name, "animelist"].join("/"), 48 | headers: this.acount.getHttpHeaders(), 49 | params: { 50 | nsfw: args?.includeNsfw ? "1" : "0", 51 | }, 52 | }; 53 | 54 | const serializedFields: string[] = [] 55 | if (fields != null) { 56 | serializedFields.push(fields.toString()); 57 | } 58 | if (listStatusFields != null) { 59 | serializedFields.push(`list_status{${listStatusFields.toString()}}`); 60 | } else { 61 | serializedFields.push("list_status"); 62 | } 63 | config.params.fields = serializedFields.join(',') 64 | 65 | if (args) { 66 | if (args.status != null) config.params.status = args.status; 67 | if (args.sort != null) config.params.sort = args.sort; 68 | if (args.limit != null) config.params.limit = args.limit; 69 | if (args.offset != null) config.params.offset = args.offset; 70 | } 71 | 72 | return new MalRequest(config); 73 | } 74 | 75 | mangalist( 76 | name: string = "@me", 77 | fields?: MangaFields | null, 78 | listStatusFields?: MangaListStatusFields | null, 79 | args?: { status?: string; sort?: string; limit?: number; offset?: number } 80 | ): MalRequest>> { 81 | const config: AxiosRequestConfig = { 82 | url: [apiUrl, "users", name, "mangalist"].join("/"), 83 | headers: this.acount.getHttpHeaders(), 84 | params: {}, 85 | }; 86 | 87 | const serializedFields: string[] = [] 88 | if (fields != null) { 89 | serializedFields.push(fields.toString()); 90 | } 91 | if (listStatusFields != null) { 92 | serializedFields.push(`list_status{${listStatusFields.toString()}}`); 93 | } else { 94 | serializedFields.push("list_status"); 95 | } 96 | config.params.fields = serializedFields.join(',') 97 | 98 | if (args) { 99 | if (args.status != null) config.params.status = args.status; 100 | if (args.sort != null) config.params.sort = args.sort; 101 | if (args.limit != null) config.params.limit = args.limit; 102 | if (args.offset != null) config.params.offset = args.offset; 103 | } 104 | 105 | return new MalRequest(config); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/methods/malApi/manga/types.ts: -------------------------------------------------------------------------------- 1 | import * as Common from "../common"; 2 | 3 | export interface MangaItem { 4 | node: T; 5 | } 6 | 7 | export module MangaForList { 8 | export interface MediaType { 9 | media_type: 10 | | "unknown" 11 | | "manga" 12 | | "novel" 13 | | "one_shot" 14 | | "doujinshi" 15 | | "manhwa" 16 | | "manhua" 17 | | "oel"; 18 | } 19 | 20 | export interface Status { 21 | /** 22 | * Publishing status. 23 | */ 24 | status: "finished" | "currently_publishing" | "not_yet_published"; 25 | } 26 | 27 | export interface MyListStatus { 28 | /** 29 | * Status of user's manga list. If there is no access token, the API excludes this field. 30 | */ 31 | my_list_status: T | null; 32 | } 33 | 34 | export interface NumVolumes { 35 | /** 36 | * If unknown, it is 0. 37 | */ 38 | num_volumes: number; 39 | } 40 | 41 | export interface NumChapters { 42 | /** 43 | * If unknown, it is 0. 44 | */ 45 | num_chapters: number; 46 | } 47 | 48 | export interface Authors { 49 | authors: Common.PersonRoleEdge[]; 50 | } 51 | } 52 | 53 | export module MangaForDetails { 54 | export interface Pictures { 55 | pictures: Array; 56 | } 57 | export interface Background { 58 | background: string | null; 59 | } 60 | export interface RelatedAnime { 61 | related_anime: Array>; 62 | } 63 | export interface RelatedManga { 64 | related_manga: Array>; 65 | } 66 | export interface Recommendations { 67 | recommendations: Array>; 68 | } 69 | export interface Serialization { 70 | serialization: Array; 71 | } 72 | } 73 | 74 | export interface MangaMagazineRelationEdge { 75 | node: { 76 | id: number; 77 | name: string; 78 | }; 79 | role: string; 80 | } 81 | 82 | export interface MangaListStatusBase { 83 | status: 84 | | "reading" 85 | | "completed" 86 | | "on_hold" 87 | | "dropped" 88 | | "plan_to_read" 89 | | null; 90 | /** 91 | * 0-10 92 | */ 93 | score: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; 94 | 95 | /** 96 | * 0 or the number of read volumes. 97 | */ 98 | num_volumes_read: number; 99 | 100 | /** 101 | * 0 or the number of read chapters. 102 | */ 103 | num_chapters_read: number; 104 | 105 | /** 106 | * If authorized user reads an manga again after completion, this field value is true. 107 | * 108 | * In this case, MyAnimeList treats the manga as 'reading' in the user's manga list. 109 | */ 110 | is_rereading: boolean; 111 | 112 | updated_at: string; 113 | } 114 | 115 | export module MangaListStatus { 116 | export interface StartDate { 117 | start_date: string | null; 118 | } 119 | export interface FinishDate { 120 | finish_date: string | null; 121 | } 122 | export interface Priority { 123 | priority: number; 124 | } 125 | export interface NumTimesReread { 126 | num_times_reread: number; 127 | } 128 | export interface RereadValue { 129 | reread_value: number; 130 | } 131 | export interface Tags { 132 | tags: string[]; 133 | } 134 | export interface Comments { 135 | /** 136 | * You cannot contain this field in a list. 137 | */ 138 | comments: string; 139 | } 140 | } 141 | 142 | export interface UpdateMangaParams { 143 | status?: "reading" | "completed" | "on_hold" | "dropped" | "plan_to_read"; 144 | is_rereading?: boolean; 145 | /** 146 | * 0-10 147 | */ 148 | score?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | number; 149 | 150 | num_volumes_read?: number; 151 | num_chapters_read?: number; 152 | 153 | priority?: 0 | 1 | 2 | number; 154 | num_times_reread?: number; 155 | rewatch_value?: 0 | 1 | 2 | 3 | 4 | 5 | number; 156 | tags?: string; 157 | comments?: string; 158 | } 159 | -------------------------------------------------------------------------------- /src/methods/jikan4/anime.ts: -------------------------------------------------------------------------------- 1 | import { jikanGet, jikanUrl, queryJoin } from "./index"; 2 | import urljoin from "url-join"; 3 | import * as Types from "./types"; 4 | 5 | /** @category Anime*/ 6 | export class Anime { 7 | /** @ignore */ 8 | private baseUrl: string; 9 | 10 | constructor(id: number) { 11 | this.baseUrl = `${jikanUrl}/anime/${id}`; 12 | } 13 | 14 | info(): Promise<{ data: Types.Anime }> { 15 | return jikanGet(this.baseUrl); 16 | } 17 | 18 | characters(): Promise { 19 | return jikanGet(urljoin(this.baseUrl, "characters")); 20 | } 21 | 22 | staff(): Promise { 23 | return jikanGet(urljoin(this.baseUrl, "staff")); 24 | } 25 | 26 | episodes(params?: { page?: number }): Promise { 27 | const url = urljoin(this.baseUrl, "episodes") + queryJoin({ ...params }); 28 | return jikanGet(url); 29 | } 30 | 31 | episode(id: number): Promise { 32 | return jikanGet(urljoin(this.baseUrl, "episodes", id.toString())); 33 | } 34 | 35 | news(params?: { page?: number }): Promise { 36 | const url = urljoin(this.baseUrl, "news") + queryJoin({ ...params }); 37 | return jikanGet(url); 38 | } 39 | 40 | forum(params?: { 41 | topic?: "all" | "episode" | "other"; 42 | }): Promise { 43 | const url = urljoin(this.baseUrl, "forum") + queryJoin({ ...params }); 44 | return jikanGet(url); 45 | } 46 | 47 | videos(): Promise { 48 | return jikanGet(urljoin(this.baseUrl, "videos")); 49 | } 50 | 51 | pictures(): Promise { 52 | return jikanGet(urljoin(this.baseUrl, "pictures")); 53 | } 54 | 55 | statistics(): Promise { 56 | return jikanGet(urljoin(this.baseUrl, "statistics")); 57 | } 58 | 59 | moreInfo(): Promise { 60 | return jikanGet(urljoin(this.baseUrl, "moreinfo")); 61 | } 62 | 63 | recommendations(): Promise { 64 | return jikanGet(urljoin(this.baseUrl, "recommendations")); 65 | } 66 | 67 | userUpdates(params?: { page?: number }): Promise { 68 | const url = urljoin(this.baseUrl, "userupdates") + queryJoin({ ...params }); 69 | return jikanGet(url); 70 | } 71 | 72 | reviews(params?: { page?: number }): Promise { 73 | const url = urljoin(this.baseUrl, "reviews") + queryJoin({ ...params }); 74 | return jikanGet(url); 75 | } 76 | 77 | relations(): Promise { 78 | return jikanGet(urljoin(this.baseUrl, "relations")); 79 | } 80 | 81 | themes(): Promise { 82 | return jikanGet(urljoin(this.baseUrl, "themes")); 83 | } 84 | 85 | external(): Promise { 86 | return jikanGet(urljoin(this.baseUrl, "external")); 87 | } 88 | } 89 | 90 | /** @category Anime*/ 91 | export function anime(id: number): Anime { 92 | return new Anime(id); 93 | } 94 | 95 | /** @category Anime*/ 96 | export function animeSearch(params?: { 97 | page?: number; 98 | limit?: number; 99 | q?: string; 100 | type?: "tv" | "movie" | "ova" | "special" | "ona" | "music"; 101 | score?: number; 102 | min_score?: number; 103 | max_score?: number; 104 | status?: "airing" | "complete" | "upcoming"; 105 | ranking?: "g" | "pg" | "pg13" | "r17" | "r" | "rx"; 106 | sfw?: boolean; 107 | genres?: string; 108 | genres_exclude?: string; 109 | order_by?: 110 | | "mal_id" 111 | | "title" 112 | | "type" 113 | | "rating" 114 | | "start_date" 115 | | "end_date" 116 | | "episodes" 117 | | "score" 118 | | "scored_by" 119 | | "rank" 120 | | "popularity" 121 | | "members" 122 | | "favorites"; 123 | sort?: "desc" | "asc"; 124 | letter?: string; 125 | producer?: string; 126 | }): Promise { 127 | const url = urljoin(jikanUrl, "anime") + queryJoin({ ...params }); 128 | return jikanGet(url); 129 | } 130 | -------------------------------------------------------------------------------- /src/methods/malApi/manga/index.ts: -------------------------------------------------------------------------------- 1 | import { MalAcount } from ".."; 2 | import { MalRequest } from "../request"; 3 | import { apiUrl } from "../api"; 4 | 5 | import { MangaFields, MangaDetailsFields } from "./fields"; 6 | import { MangaItem, UpdateMangaParams, MangaListStatusBase } from "./types"; 7 | import { WorkBase, Paging, RankingItem } from "../common"; 8 | import { queryEncode } from "../util"; 9 | import { AxiosRequestConfig } from "axios"; 10 | 11 | export * from "./fields"; 12 | export * from "./types"; 13 | 14 | export class MalManga { 15 | private acount: MalAcount; 16 | 17 | constructor(acount: MalAcount) { 18 | this.acount = acount; 19 | } 20 | 21 | search( 22 | q: string, 23 | fields?: MangaFields | null, 24 | limit?: number | null, 25 | offset?: number | null, 26 | nsfw?: boolean | null 27 | ): MalRequest>> { 28 | const config: AxiosRequestConfig = { 29 | url: [apiUrl, "manga"].join("/"), 30 | headers: this.acount.getHttpHeaders(), 31 | params: { 32 | q, 33 | }, 34 | }; 35 | 36 | if (fields != null) config.params.fields = fields.toString(); 37 | if (limit != null) config.params.limit = limit; 38 | if (offset != null) config.params.offset = offset; 39 | if (nsfw != null) config.params.nsfw = nsfw; 40 | 41 | return new MalRequest(config); 42 | } 43 | 44 | details(id: number, fields?: MangaDetailsFields) { 45 | const config: AxiosRequestConfig = { 46 | url: [apiUrl, "manga", id.toString()].join("/"), 47 | headers: this.acount.getHttpHeaders(), 48 | params: {}, 49 | }; 50 | 51 | if (fields) config.params.fields = fields.toString(); 52 | 53 | return new MalRequest(config); 54 | } 55 | 56 | /** 57 | * | value | | 58 | * | ---- | ---- | 59 | * | all | Top Anime Series | 60 | * | airing | Top Airing Anime | 61 | * | upcoming | Top Upcoming Anime | 62 | * | tv | Top Anime TV Series | 63 | * | ova | Top Anime OVA Series | 64 | * | movie | Top Anime Movies | 65 | * | special | Top Anime Specials | 66 | * | bypopularity | Top Anime by Popularity | 67 | * | favorite | Top Favorited Anime | 68 | */ 69 | ranking( 70 | rankingType: 71 | | "all" 72 | | "airing" 73 | | "upcoming" 74 | | "tv" 75 | | "ova" 76 | | "movie" 77 | | "special" 78 | | "bypopularity" 79 | | "favorite", 80 | fields?: MangaFields | null, 81 | limit?: number | null, 82 | offset?: number | null 83 | ): MalRequest>> { 84 | const config: AxiosRequestConfig = { 85 | url: [apiUrl, "manga", "ranking"].join("/"), 86 | headers: this.acount.getHttpHeaders(), 87 | params: { 88 | ranking_type: rankingType, 89 | }, 90 | }; 91 | 92 | if (fields != null) config.params.fields = fields.toString(); 93 | if (limit != null) config.params.limit = limit; 94 | if (offset != null) config.params.offset = offset; 95 | 96 | return new MalRequest(config); 97 | } 98 | 99 | updateMyManga( 100 | id: number, 101 | params?: UpdateMangaParams 102 | ): MalRequest { 103 | const config: AxiosRequestConfig = { 104 | method: "PATCH", 105 | url: [apiUrl, "manga", id.toString(), "my_list_status"].join("/"), 106 | headers: { 107 | ...this.acount.getHttpHeaders(), 108 | "content-type": "application/x-www-form-urlencoded", 109 | }, 110 | params: {}, 111 | data: queryEncode(params), 112 | }; 113 | 114 | return new MalRequest(config); 115 | } 116 | 117 | deleteMyManga(id: number): MalRequest { 118 | const config: AxiosRequestConfig = { 119 | method: "DELETE", 120 | url: [apiUrl, "manga", id.toString(), "my_list_status"].join("/"), 121 | headers: this.acount.getHttpHeaders(), 122 | }; 123 | 124 | return new MalRequest(config); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

node-myanimelist

Index

Namespaces

Variables

Variables

version: any

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /src/methods/malApi/types.ts: -------------------------------------------------------------------------------- 1 | export type AnimeField = 2 | | "alternative_titles" 3 | | "media_type" 4 | | "num_episodes" 5 | | "status" 6 | | "start_date" 7 | | "end_date" 8 | | "average_episode_duration" 9 | | "synopsis" 10 | | "mean" 11 | | "rank" 12 | | "popularity" 13 | | "num_list_users" 14 | | "num_favorites" 15 | | "num_scoring_users" 16 | | "start_season" 17 | | "broadcast" 18 | | "my_list_status{start_date,finish_date}" 19 | | "nsfw" 20 | | "created_at" 21 | | "updated_at" 22 | | string; 23 | 24 | // interface MyListStatus { 25 | // status: any; 26 | // score: any; 27 | // } 28 | 29 | // class Fields { 30 | // constructor(a: T) {} 31 | // } 32 | 33 | // let a: MyListStatus = { 34 | // status: {}, 35 | // score: {}, 36 | // }; 37 | 38 | // let f = new Fields(a); 39 | 40 | // interface AnimeResponse { 41 | // id: number; 42 | // title: string; 43 | // main_picture: { medium: string; large: string | null }; 44 | // } 45 | 46 | // class AnimeBase { 47 | // fields: any = {}; 48 | 49 | // type: T = null as any; 50 | 51 | // alternativeTitle(): AnimeBase { 52 | // this.fields["alternative_titles"] = true; 53 | // return this as any; 54 | // } 55 | // status(): AnimeBase { 56 | // this.fields["status"] = true; 57 | // return this as any; 58 | // } 59 | 60 | // parse(): T { 61 | // return null as any; 62 | // } 63 | // } 64 | // function animeBase() { 65 | // return new AnimeBase(); 66 | // } 67 | // class AnimeAlter extends AnimeBase { 68 | // alternative_titles = true; 69 | // } 70 | // class AnimeStatus extends AnimeBase { 71 | // alternative_titles = true; 72 | // } 73 | 74 | // interface AnimeAlternativeTitles { 75 | // alternative_titles: { 76 | // synonyms: string[]; 77 | // en: string | null; 78 | // ja: string | null; 79 | // }; 80 | // } 81 | 82 | // interface AnimeStatus { 83 | // status: String; 84 | // } 85 | 86 | // let b = animeBase().alternativeTitle().status(); 87 | 88 | // let t = b.type; 89 | 90 | // console.log(b); 91 | 92 | // let data = b.parse(); 93 | 94 | // let id = data.id; 95 | 96 | // let base: AnimeResponse = { 97 | // id: 5, 98 | // title: "test", 99 | // main_picture: { medium: "test", large: "test" }, 100 | // }; 101 | 102 | // let res: AnimeResponse | AnimeAlternativeTitles = { 103 | // id: 5, 104 | // title: "test", 105 | // main_picture: { medium: "test", large: "test" }, 106 | // alternative_titles: { 107 | // synonyms: [], 108 | // en: "test", 109 | // ja: "test", 110 | // }, 111 | // }; 112 | 113 | // export type MyListStatusField = "start_date" | "finish_date"; 114 | 115 | // export class MyListStatusFields { 116 | // fields: MyListStatusField[] = []; 117 | 118 | // constructor(fields: MyListStatusField[]) { 119 | // this.fields = fields; 120 | // } 121 | // } 122 | 123 | // MyListStatusFields.prototype.toString = function () { 124 | // return "my_list_status{" + this.fields.join(",") + "}"; 125 | // }; 126 | 127 | // export type RecommendationsField = 128 | // | "alternative_titles" 129 | // | "media_type" 130 | // | "num_episodes" 131 | // | "status" 132 | // | "start_date" 133 | // | "end_date" 134 | // | "average_episode_duration" 135 | // | "synopsis" 136 | // | "mean" 137 | // | "rank" 138 | // | "popularity" 139 | // | "num_list_users" 140 | // | "num_favorites" 141 | // | "num_scoring_users" 142 | // | "start_season" 143 | // | "broadcast" 144 | // | "my_list_status{start_date,finish_date}" 145 | // | MyListStatusFields 146 | // | "nsfw" 147 | // | "created_at" 148 | // | "updated_at"; 149 | 150 | // export type RelatedAnimeField = 151 | // | "alternative_titles" 152 | // | "media_type" 153 | // | "num_episodes" 154 | // | "status" 155 | // | "start_date" 156 | // | "end_date" 157 | // | "average_episode_duration" 158 | // | "synopsis" 159 | // | "mean" 160 | // | "rank" 161 | // | "popularity" 162 | // | "num_list_users" 163 | // | "num_favorites" 164 | // | "num_scoring_users" 165 | // | "start_season" 166 | // | "broadcast" 167 | // | "my_list_status{start_date,finish_date}" 168 | // | MyListStatusFields 169 | // | "nsfw 170 | // | "created_at" 171 | // | "updated_at"; 172 | -------------------------------------------------------------------------------- /jikan_generator/main.ts: -------------------------------------------------------------------------------- 1 | import json from "./api-docs.json"; 2 | const schemas = json.components.schemas; 3 | 4 | type Node = 5 | | { 6 | type: "allOf"; 7 | nodes: Node[]; 8 | } 9 | | { 10 | type: "anyOf"; 11 | nodes: Node[]; 12 | } 13 | | { 14 | type: "object"; 15 | properties: { [key: string]: Node }; 16 | } 17 | | { 18 | type: "array"; 19 | items: Node; 20 | } 21 | | { 22 | type: "ref"; 23 | ref: string; 24 | } 25 | | { type: "string" } 26 | | { type: "integer" } 27 | | { type: "boolean" }; 28 | 29 | class Schema { 30 | name: string; 31 | node: Node; 32 | 33 | constructor(key: string, node: Node) { 34 | this.name = toUpperCamelCase(key); 35 | this.node = node; 36 | } 37 | } 38 | 39 | class Generator { 40 | map: { [ref: string]: Schema } = {}; 41 | } 42 | 43 | const generator = new Generator(); 44 | 45 | function toUpperCamelCase(name: string): string { 46 | const text = name.replace(/[-_\s.]+(.)?/g, (_, c) => 47 | c ? c.toUpperCase() : "" 48 | ); 49 | return text.substring(0, 1).toUpperCase() + text.substring(1); 50 | } 51 | 52 | function refToName(ref: string): string { 53 | const split = ref.split("/"); 54 | return toUpperCamelCase(split[split.length - 1]); 55 | } 56 | 57 | function readNode(root: string, elm: any): Node { 58 | let allOf: any[] = elm["allOf"]; 59 | let anyOf: any[] = elm["anyOf"]; 60 | let ref: string | undefined = elm["$ref"]; 61 | 62 | if (ref) { 63 | return { 64 | type: "ref", 65 | ref, 66 | }; 67 | } else if (allOf) { 68 | const nodes: Node[] = allOf.map((elm) => readNode(root, elm)); 69 | return { 70 | type: "allOf", 71 | nodes, 72 | }; 73 | } else if (anyOf) { 74 | const nodes: Node[] = anyOf.map((elm) => readNode(root, elm)); 75 | return { 76 | type: "anyOf", 77 | nodes, 78 | }; 79 | } else { 80 | let type = elm["type"]; 81 | 82 | if (type) { 83 | if (type === "object") { 84 | const properties: { [key: string]: Node } = {}; 85 | 86 | let elm_props = elm.properties ? elm.properties : {}; 87 | Object.entries(elm_props).forEach(([key, elm]) => { 88 | properties[key] = readNode(root + "/" + key, elm); 89 | }); 90 | 91 | return { 92 | type: "object", 93 | properties, 94 | }; 95 | } else if (type === "array") { 96 | return { 97 | type: "array", 98 | items: readNode(root, elm.items), 99 | }; 100 | } else if (type === "string" || type === "String") { 101 | return { 102 | type: "string", 103 | }; 104 | } else if (type === "integer" || type === "number") { 105 | return { 106 | type: "integer", 107 | }; 108 | } else if (type === "boolean") { 109 | return { 110 | type: "boolean", 111 | }; 112 | } 113 | } 114 | } 115 | 116 | throw Error(root); 117 | } 118 | 119 | Object.entries(schemas).forEach(([key, elm]: any) => { 120 | const schema = new Schema(key, readNode(key, elm)); 121 | 122 | generator.map[key] = schema; 123 | }); 124 | 125 | function nodeToTs(root: string, node: Node): string { 126 | if (node == null) { 127 | console.log(root, node); 128 | } 129 | switch (node.type) { 130 | case "allOf": 131 | return node.nodes.map((node) => nodeToTs(root, node)).join("&"); 132 | case "anyOf": 133 | return node.nodes.map((node) => nodeToTs(root, node)).join("|"); 134 | case "object": 135 | let array_mode: string | null = null; 136 | 137 | let props = Object.entries(node.properties).map(([key, node]) => { 138 | // Definition is broken look at clubs relations for example 139 | if (key.length == 0) { 140 | array_mode = nodeToTs(root + "/" + key, node); 141 | } 142 | 143 | let optional = 144 | node.type !== "object" && 145 | node.type !== "array" && 146 | node.type !== "ref" && 147 | key !== "score"; 148 | return `${key}${optional ? "?" : ""}: ${nodeToTs( 149 | root + "/" + key, 150 | node 151 | )},`; 152 | }); 153 | 154 | if (array_mode) { 155 | return array_mode; 156 | } else { 157 | return `{${props.join("\n")}}`; 158 | } 159 | case "array": 160 | return `(${nodeToTs(root, node.items)})[]`; 161 | case "ref": 162 | return refToName(node.ref); 163 | case "string": 164 | return "string"; 165 | case "integer": 166 | return "number"; 167 | case "boolean": 168 | return "boolean"; 169 | default: 170 | throw Error("Unknown node"); 171 | break; 172 | } 173 | } 174 | 175 | function generate() { 176 | let map = generator.map; 177 | 178 | Object.entries(map).forEach(([key, schema]) => { 179 | const typeAlias = ` 180 | export type ${schema.name} = ${nodeToTs(schema.name, schema.node)}; 181 | `; 182 | console.log(typeAlias); 183 | }); 184 | } 185 | 186 | generate(); 187 | -------------------------------------------------------------------------------- /docs/modules/Mal.User.User.html: -------------------------------------------------------------------------------- 1 | User | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /src/methods/malApi/anime/index.ts: -------------------------------------------------------------------------------- 1 | import { MalAcount } from ".."; 2 | import { MalRequest } from "../request"; 3 | import { apiUrl } from "../api"; 4 | 5 | import { AnimeFields, AnimeDetailsFields } from "./fields"; 6 | import { AnimeItem, UpdateAnimeParams, AnimeListStatusBase } from "./types"; 7 | import { WorkBase, Paging, RankingItem } from "../common"; 8 | import { queryEncode } from "../util"; 9 | import axios, { AxiosRequestConfig } from "axios"; 10 | 11 | export * from "./fields"; 12 | export * from "./types"; 13 | 14 | export class MalAnime { 15 | private acount: MalAcount; 16 | 17 | constructor(acount: MalAcount) { 18 | this.acount = acount; 19 | } 20 | 21 | search( 22 | q: string, 23 | fields?: AnimeFields | null, 24 | limit?: number | null, 25 | offset?: number | null, 26 | nsfw? : boolean | null 27 | ): MalRequest>> { 28 | const config: AxiosRequestConfig = { 29 | url: [apiUrl, "anime"].join("/"), 30 | headers: this.acount.getHttpHeaders(), 31 | params: { 32 | q, 33 | }, 34 | }; 35 | 36 | if (fields != null) config.params.fields = fields.toString(); 37 | if (limit != null) config.params.limit = limit; 38 | if (offset != null) config.params.offset = offset; 39 | if (nsfw != null) config.params.nsfw = nsfw; 40 | 41 | return new MalRequest(config); 42 | } 43 | 44 | details(id: number, fields?: AnimeDetailsFields) { 45 | const config: AxiosRequestConfig = { 46 | url: [apiUrl, "anime", id.toString()].join("/"), 47 | headers: this.acount.getHttpHeaders(), 48 | params: {}, 49 | }; 50 | 51 | if (fields != null) config.params.fields = fields.toString(); 52 | 53 | return new MalRequest(config); 54 | } 55 | 56 | /** 57 | * | value | | 58 | * | ---- | ---- | 59 | * | all | Top Anime Series | 60 | * | airing | Top Airing Anime | 61 | * | upcoming | Top Upcoming Anime | 62 | * | tv | Top Anime TV Series | 63 | * | ova | Top Anime OVA Series | 64 | * | movie | Top Anime Movies | 65 | * | special | Top Anime Specials | 66 | * | bypopularity | Top Anime by Popularity | 67 | * | favorite | Top Favorited Anime | 68 | */ 69 | ranking( 70 | rankingType: 71 | | "all" 72 | | "airing" 73 | | "upcoming" 74 | | "tv" 75 | | "ova" 76 | | "movie" 77 | | "special" 78 | | "bypopularity" 79 | | "favorite", 80 | fields?: AnimeFields | null, 81 | limit?: number | null, 82 | offset?: number | null 83 | ): MalRequest>> { 84 | const config: AxiosRequestConfig = { 85 | url: [apiUrl, "anime", "ranking"].join("/"), 86 | headers: this.acount.getHttpHeaders(), 87 | params: { 88 | ranking_type: rankingType, 89 | }, 90 | }; 91 | 92 | if (fields != null) config.params.fields = fields.toString(); 93 | if (limit != null) config.params.limit = limit; 94 | if (offset != null) config.params.offset = offset; 95 | 96 | return new MalRequest(config); 97 | } 98 | 99 | seasonal( 100 | year: number, 101 | season: string, 102 | fields?: AnimeFields | null, 103 | sort?: "anime_score" | "anime_num_list_users" | null, 104 | limit?: number | null, 105 | offset?: number | null 106 | ): MalRequest>> { 107 | const config: AxiosRequestConfig = { 108 | url: [apiUrl, "anime", "season", year.toString(), season].join("/"), 109 | headers: this.acount.getHttpHeaders(), 110 | params: {}, 111 | }; 112 | 113 | if (fields != null) config.params.fields = fields.toString(); 114 | if (sort != null) config.params.sort = sort; 115 | if (limit != null) config.params.limit = limit; 116 | if (offset != null) config.params.offset = offset; 117 | 118 | return new MalRequest(config); 119 | } 120 | 121 | suggested( 122 | fields?: AnimeFields | null, 123 | limit?: number | null, 124 | offset?: number | null 125 | ): MalRequest>> { 126 | const config: AxiosRequestConfig = { 127 | url: [apiUrl, "anime", "suggestions"].join("/"), 128 | headers: this.acount.getHttpHeaders(), 129 | params: {}, 130 | }; 131 | 132 | if (fields != null) config.params.fields = fields.toString(); 133 | if (limit != null) config.params.limit = limit; 134 | if (offset != null) config.params.offset = offset; 135 | 136 | return new MalRequest(config); 137 | } 138 | 139 | updateMyAnime( 140 | id: number, 141 | params: UpdateAnimeParams = {} 142 | ): MalRequest { 143 | const config: AxiosRequestConfig = { 144 | method: "PATCH", 145 | url: [apiUrl, "anime", id.toString(), "my_list_status"].join("/"), 146 | headers: { 147 | ...this.acount.getHttpHeaders(), 148 | "content-type": "application/x-www-form-urlencoded", 149 | }, 150 | params: {}, 151 | data: queryEncode(params), 152 | }; 153 | 154 | return new MalRequest(config); 155 | } 156 | 157 | deleteMyAnime(id: number): MalRequest { 158 | const config: AxiosRequestConfig = { 159 | method: "DELETE", 160 | url: [apiUrl, "anime", id.toString(), "my_list_status"].join("/"), 161 | headers: this.acount.getHttpHeaders(), 162 | }; 163 | 164 | return new MalRequest(config); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /test/mal/common.ts: -------------------------------------------------------------------------------- 1 | import { Mal } from "../../"; 2 | import { assertTypeof, nullable, nonNullable, assertIsOneOf } from "./util"; 3 | import * as T from "./util/types"; 4 | 5 | export module Common { 6 | export function picture(p: Mal.Common.Picture) { 7 | describe("Picture", () => { 8 | it("large", () => assertTypeof(p.large, T.string, true)); 9 | it("medium", () => assertTypeof(p.medium, T.string)); 10 | }); 11 | } 12 | export function personBase(p: Mal.Common.PersonBase) { 13 | describe(`PersonBase: ${p.id}`, () => { 14 | it("id", () => assertTypeof(p.id, T.number)); 15 | it("first_name", () => assertTypeof(p.first_name, T.string)); 16 | it("last_name", () => assertTypeof(p.last_name, T.string)); 17 | }); 18 | } 19 | export function personRoleEdge(p: Mal.Common.PersonRoleEdge) { 20 | describe("PersonRoleEdge", () => { 21 | describe(`node`, () => nonNullable(p.node, (p) => personBase(p))); 22 | it("role", () => assertTypeof(p.role, T.string)); 23 | }); 24 | } 25 | export function workBase(wb: Mal.Common.WorkBase) { 26 | describe("WorkBase", () => { 27 | it("id", () => assertTypeof(wb.id, T.number)); 28 | it("title", () => assertTypeof(wb.title, T.string)); 29 | describe("main_picture", () => 30 | nullable(wb.main_picture, (p) => picture(p))); 31 | }); 32 | } 33 | export module WorkForList { 34 | export function all(data: any) { 35 | describe("WorkForList", () => { 36 | Common.WorkForList.alternativeTitles(data); 37 | Common.WorkForList.startDate(data); 38 | Common.WorkForList.endDate(data); 39 | Common.WorkForList.synopsis(data); 40 | Common.WorkForList.mean(data); 41 | Common.WorkForList.rank(data); 42 | Common.WorkForList.popularity(data); 43 | Common.WorkForList.numListUsers(data); 44 | Common.WorkForList.numScoringUsers(data); 45 | Common.WorkForList.nsfw(data); 46 | Common.WorkForList.genres(data); 47 | Common.WorkForList.createdAt(data); 48 | Common.WorkForList.UpdatedAt(data); 49 | }); 50 | } 51 | export function alternativeTitles( 52 | t: Mal.Common.WorkForList.AlternativeTitles 53 | ) { 54 | describe("AlternativeTitles", () => 55 | nullable(t.alternative_titles, (t) => { 56 | it("synonyms", () => 57 | nonNullable(t.synonyms, (t) => 58 | t.forEach((s) => assertTypeof(s, T.string)) 59 | )); 60 | it("en", () => assertTypeof(t.en, T.string, true)); 61 | it("ja", () => assertTypeof(t.ja, T.string, true)); 62 | })); 63 | } 64 | export function startDate(t: Mal.Common.WorkForList.StartDate) { 65 | describe("StartDate", () => { 66 | it("start_date", () => assertTypeof(t.start_date, T.string, true)); 67 | }); 68 | } 69 | export function endDate(t: Mal.Common.WorkForList.EndDate) { 70 | describe("EndDate", () => { 71 | it("end_date", () => assertTypeof(t.end_date, T.string, true)); 72 | }); 73 | } 74 | export function synopsis(t: Mal.Common.WorkForList.Synopsis) { 75 | describe("EndDate", () => { 76 | it("synopsis", () => assertTypeof(t.synopsis, T.string, true)); 77 | }); 78 | } 79 | export function mean(t: Mal.Common.WorkForList.Mean) { 80 | describe("Mean", () => { 81 | it("mean", () => assertTypeof(t.mean, T.number, true)); 82 | }); 83 | } 84 | export function rank(t: Mal.Common.WorkForList.Rank) { 85 | describe("Rank", () => { 86 | it("rank", () => assertTypeof(t.rank, T.number, true)); 87 | }); 88 | } 89 | export function popularity(t: Mal.Common.WorkForList.Popularity) { 90 | describe("Popularity", () => { 91 | it("popularity", () => assertTypeof(t.popularity, T.number, true)); 92 | }); 93 | } 94 | export function numListUsers(t: Mal.Common.WorkForList.NumListUsers) { 95 | describe("NumListUsers", () => { 96 | it("num_list_users", () => assertTypeof(t.num_list_users, T.number)); 97 | }); 98 | } 99 | export function numScoringUsers(t: Mal.Common.WorkForList.NumScoringUsers) { 100 | describe("NumScoringUsers", () => { 101 | it("num_scoring_users", () => 102 | assertTypeof(t.num_scoring_users, T.number)); 103 | }); 104 | } 105 | export function nsfw(t: Mal.Common.WorkForList.Nsfw) { 106 | describe("Nsfw", () => { 107 | nullable(t.nsfw, (t) => { 108 | it(`nsfw: ${t}`, () => { 109 | const opt = ["white", "gray", "black"]; 110 | assertIsOneOf(t, opt); 111 | }); 112 | }); 113 | }); 114 | } 115 | export function genres(t: Mal.Common.WorkForList.Genres) { 116 | describe("Genres", () => { 117 | describe("genres", () => { 118 | nonNullable(t.genres, (g) => { 119 | g.forEach((g) => { 120 | it(`id: ${g.id}`, () => assertTypeof(g.id, T.number)); 121 | it(`name: ${g.name}`, () => assertTypeof(g.name, T.string)); 122 | }); 123 | }); 124 | }); 125 | }); 126 | } 127 | export function createdAt(t: Mal.Common.WorkForList.CreatedAt) { 128 | describe("CreatedAt", () => { 129 | it("created_at", () => assertTypeof(t.created_at, T.string)); 130 | }); 131 | } 132 | export function UpdatedAt(t: Mal.Common.WorkForList.UpdatedAt) { 133 | describe("UpdatedAt", () => { 134 | it("updated_at", () => assertTypeof(t.updated_at, T.string)); 135 | }); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/methods/malApi/anime/types.ts: -------------------------------------------------------------------------------- 1 | import * as Common from "../common"; 2 | import { WorkBase } from "../common"; 3 | 4 | export interface AnimeItem { 5 | node: T; 6 | } 7 | 8 | export module AnimeForList { 9 | export interface MediaType { 10 | media_type: 11 | | "unknown" 12 | | "tv" 13 | | "ova" 14 | | "movie" 15 | | "special" 16 | | "ona" 17 | | "music"; 18 | } 19 | 20 | export interface Status { 21 | /** 22 | * Airing status. 23 | */ 24 | status: "finished_airing" | "currently_airing" | "not_yet_aired"; 25 | } 26 | 27 | export interface MyListStatus { 28 | my_list_status: T | null; 29 | } 30 | 31 | export interface NumEpisodes { 32 | /** 33 | * The total number of episodes of this series. If unknown, it is 0. 34 | */ 35 | num_episodes: number; 36 | } 37 | 38 | export interface StartSeason { 39 | start_season: { 40 | year: number; 41 | season: "winter" | "spring" | "summer" | "fall"; 42 | } | null; 43 | } 44 | 45 | export interface Broadcast { 46 | /** 47 | * Broadcast date. 48 | */ 49 | broadcast: { 50 | /** 51 | * Day of the week broadcast in Japan time. 52 | * 53 | * Day of the week or `other` 54 | */ 55 | day_of_the_week: string; 56 | /** 57 | * for example: "01:25" 58 | */ 59 | start_time: string | null; 60 | } | null; 61 | } 62 | 63 | export interface Source { 64 | /** 65 | * Original work. 66 | */ 67 | source: 68 | | "other" 69 | | "original" 70 | | "manga" 71 | | "4_koma_manga" 72 | | "web_manga" 73 | | "digital_manga" 74 | | "novel" 75 | | "light_novel" 76 | | "visual_novel" 77 | | "game" 78 | | "card_game" 79 | | "book" 80 | | "picture_book" 81 | | "radio" 82 | | "music" 83 | | null; 84 | } 85 | 86 | export interface AverageEpisodeDuration { 87 | /** 88 | * Average length of episode in seconds. 89 | */ 90 | average_episode_duration: number | null; 91 | } 92 | 93 | export interface Rating { 94 | /** 95 | * | Value | Description| 96 | * | :---- | :--------- | 97 | * | g | G - All Ages | 98 | * | pg | PG - Children | 99 | * | pg_13 | pg_13 - Teens 13 and Older | 100 | * | r | R - 17+ (violence & profanity) | 101 | * | r+ | R+ - Profanity & Mild Nudity | 102 | * | rx | Rx - Hentai | 103 | */ 104 | rating: "g" | "pg" | "pg_13" | "r" | "r+" | "rx" | null; 105 | } 106 | 107 | export interface Studios { 108 | studios: { 109 | id: number; 110 | name: string; 111 | }[]; 112 | } 113 | } 114 | 115 | export module AnimeForDetails { 116 | export interface Pictures { 117 | pictures: Array; 118 | } 119 | export interface Background { 120 | background: string | null; 121 | } 122 | export interface RelatedAnime { 123 | related_anime: Array>; 124 | } 125 | export interface RelatedManga { 126 | related_manga: Array>; 127 | } 128 | export interface Recommendations { 129 | recommendations: Array>; 130 | } 131 | export interface Statistics { 132 | statistics: AnimeStatistics | null; 133 | } 134 | } 135 | 136 | export interface AnimeStatistics { 137 | num_list_users: number; 138 | status: { 139 | watching: number; 140 | completed: number; 141 | on_hold: number; 142 | dropped: number; 143 | plan_to_watch: number; 144 | }; 145 | } 146 | 147 | export interface AnimeListStatusBase { 148 | status: 149 | | "watching" 150 | | "completed" 151 | | "on_hold" 152 | | "dropped" 153 | | "plan_to_watch" 154 | | null; 155 | /** 156 | * 0-10 157 | */ 158 | score: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; 159 | /** 160 | * 0 or the number of watched episodes. 161 | * 162 | * There is mistake in MAL v2 API it should be num_watched_episodes 163 | */ 164 | num_episodes_watched: number; 165 | // num_watched_episodes: number; 166 | 167 | /** 168 | * If authorized user watches an anime again after completion, this field value is true. 169 | * 170 | * In this case, MyAnimeList treats the anime as 'watching' in the user's anime list. 171 | */ 172 | is_rewatching: boolean; 173 | 174 | updated_at: string; 175 | } 176 | 177 | export module AnimeListStatus { 178 | export interface StartDate { 179 | start_date: string | null; 180 | } 181 | export interface FinishDate { 182 | finish_date: string | null; 183 | } 184 | export interface Priority { 185 | priority: number; 186 | } 187 | export interface NumTimesRewatched { 188 | num_times_rewatched: number; 189 | } 190 | export interface RewatchValue { 191 | rewatch_value: number; 192 | } 193 | export interface Tags { 194 | tags: string[]; 195 | } 196 | export interface Comments { 197 | /** 198 | * You cannot contain this field in a list. 199 | */ 200 | comments: string; 201 | } 202 | } 203 | 204 | export interface UpdateAnimeParams { 205 | status?: "watching" | "completed" | "on_hold" | "dropped" | "plan_to_watch"; 206 | is_rewatching?: boolean; 207 | /** 208 | * 0-10 209 | */ 210 | score?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | number; 211 | /** 212 | * 0 or the number of watched episodes. 213 | */ 214 | num_watched_episodes?: number; 215 | 216 | priority?: 0 | 1 | 2 | number; 217 | num_times_rewatched?: number; 218 | rewatch_value?: 0 | 1 | 2 | 3 | 4 | 5 | number; 219 | tags?: string; 220 | comments?: string; 221 | } 222 | -------------------------------------------------------------------------------- /test/mal/dummyData/user/animelistDef.ts: -------------------------------------------------------------------------------- 1 | export const def = { 2 | data: [ 3 | { 4 | node: { 5 | id: 31646, 6 | title: "3-gatsu no Lion", 7 | main_picture: { 8 | medium: "https://api-cdn.myanimelist.net/images/anime/6/82898.jpg", 9 | large: "https://api-cdn.myanimelist.net/images/anime/6/82898l.jpg", 10 | }, 11 | }, 12 | list_status: { 13 | status: "dropped", 14 | score: 0, 15 | num_episodes_watched: 2, 16 | is_rewatching: false, 17 | updated_at: "2018-11-02T14:39:35+00:00", 18 | }, 19 | }, 20 | { 21 | node: { 22 | id: 36793, 23 | title: "3D Kanojo: Real Girl", 24 | main_picture: { 25 | medium: "https://api-cdn.myanimelist.net/images/anime/1327/93616.jpg", 26 | large: "https://api-cdn.myanimelist.net/images/anime/1327/93616l.jpg", 27 | }, 28 | }, 29 | list_status: { 30 | status: "completed", 31 | score: 7, 32 | num_episodes_watched: 12, 33 | is_rewatching: false, 34 | updated_at: "2018-06-25T19:35:07+00:00", 35 | }, 36 | }, 37 | { 38 | node: { 39 | id: 37956, 40 | title: "3D Kanojo: Real Girl 2nd Season", 41 | main_picture: { 42 | medium: "https://api-cdn.myanimelist.net/images/anime/1941/97219.jpg", 43 | large: "https://api-cdn.myanimelist.net/images/anime/1941/97219l.jpg", 44 | }, 45 | }, 46 | list_status: { 47 | status: "plan_to_watch", 48 | score: 0, 49 | num_episodes_watched: 1, 50 | is_rewatching: false, 51 | updated_at: "2020-05-11T01:43:31+00:00", 52 | }, 53 | }, 54 | { 55 | node: { 56 | id: 36039, 57 | title: "A.I.C.O.: Incarnation", 58 | main_picture: { 59 | medium: "https://api-cdn.myanimelist.net/images/anime/1921/91085.jpg", 60 | large: "https://api-cdn.myanimelist.net/images/anime/1921/91085l.jpg", 61 | }, 62 | }, 63 | list_status: { 64 | status: "completed", 65 | score: 7, 66 | num_episodes_watched: 12, 67 | is_rewatching: false, 68 | updated_at: "2020-06-20T08:22:20+00:00", 69 | }, 70 | }, 71 | { 72 | node: { 73 | id: 11759, 74 | title: "Accel World", 75 | main_picture: { 76 | medium: "https://api-cdn.myanimelist.net/images/anime/8/38155.jpg", 77 | large: "https://api-cdn.myanimelist.net/images/anime/8/38155l.jpg", 78 | }, 79 | }, 80 | list_status: { 81 | status: "completed", 82 | score: 8, 83 | num_episodes_watched: 24, 84 | is_rewatching: false, 85 | updated_at: "2017-08-02T18:29:07+00:00", 86 | }, 87 | }, 88 | { 89 | node: { 90 | id: 12291, 91 | title: "Acchi Kocchi (TV)", 92 | main_picture: { 93 | medium: "https://api-cdn.myanimelist.net/images/anime/5/46489.jpg", 94 | large: "https://api-cdn.myanimelist.net/images/anime/5/46489l.jpg", 95 | }, 96 | }, 97 | list_status: { 98 | status: "on_hold", 99 | score: 7, 100 | num_episodes_watched: 3, 101 | is_rewatching: false, 102 | updated_at: "2018-11-10T22:45:39+00:00", 103 | }, 104 | }, 105 | { 106 | node: { 107 | id: 34881, 108 | title: "Aho Girl", 109 | main_picture: { 110 | medium: "https://api-cdn.myanimelist.net/images/anime/7/86665.jpg", 111 | large: "https://api-cdn.myanimelist.net/images/anime/7/86665l.jpg", 112 | }, 113 | }, 114 | list_status: { 115 | status: "dropped", 116 | score: 0, 117 | num_episodes_watched: 2, 118 | is_rewatching: false, 119 | updated_at: "2018-06-14T20:01:59+00:00", 120 | }, 121 | }, 122 | { 123 | node: { 124 | id: 101, 125 | title: "Air", 126 | main_picture: { 127 | medium: "https://api-cdn.myanimelist.net/images/anime/8/22059.jpg", 128 | large: "https://api-cdn.myanimelist.net/images/anime/8/22059l.jpg", 129 | }, 130 | }, 131 | list_status: { 132 | status: "dropped", 133 | score: 0, 134 | num_episodes_watched: 2, 135 | is_rewatching: false, 136 | updated_at: "2020-07-12T02:07:30+00:00", 137 | }, 138 | }, 139 | { 140 | node: { 141 | id: 31580, 142 | title: "Ajin", 143 | main_picture: { 144 | medium: "https://api-cdn.myanimelist.net/images/anime/13/77968.jpg", 145 | large: "https://api-cdn.myanimelist.net/images/anime/13/77968l.jpg", 146 | }, 147 | }, 148 | list_status: { 149 | status: "completed", 150 | score: 8, 151 | num_episodes_watched: 13, 152 | is_rewatching: false, 153 | updated_at: "2018-07-03T21:25:20+00:00", 154 | }, 155 | }, 156 | { 157 | node: { 158 | id: 33253, 159 | title: "Ajin 2nd Season", 160 | main_picture: { 161 | medium: "https://api-cdn.myanimelist.net/images/anime/12/81858.jpg", 162 | large: "https://api-cdn.myanimelist.net/images/anime/12/81858l.jpg", 163 | }, 164 | }, 165 | list_status: { 166 | status: "completed", 167 | score: 8, 168 | num_episodes_watched: 13, 169 | is_rewatching: false, 170 | updated_at: "2018-07-05T04:15:33+00:00", 171 | }, 172 | }, 173 | ], 174 | paging: { 175 | next: 176 | "https://api.myanimelist.net/v2/users/@me/animelist?offset=10&fields=list_status%7B%7D", 177 | }, 178 | }; 179 | -------------------------------------------------------------------------------- /docs/interfaces/Mal.User.User.Gender.html: -------------------------------------------------------------------------------- 1 | Gender | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Gender

Index

Properties

Properties

gender: null | string

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.User.User.Birthday.html: -------------------------------------------------------------------------------- 1 | Birthday | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Birthday

Index

Properties

Properties

birthday: null | string

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.User.User.TimeZone.html: -------------------------------------------------------------------------------- 1 | TimeZone | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • TimeZone

Index

Properties

Properties

time_zone: null | string

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.CreatedAt.html: -------------------------------------------------------------------------------- 1 | CreatedAt | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • CreatedAt

Index

Properties

Properties

created_at: string

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.UpdatedAt.html: -------------------------------------------------------------------------------- 1 | UpdatedAt | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • UpdatedAt

Index

Properties

Properties

updated_at: string

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.User.User.AnimeStatistics.html: -------------------------------------------------------------------------------- 1 | AnimeStatistics | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • AnimeStatistics

Index

Properties

anime_statistics: unknown

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.User.User.IsSupporter.html: -------------------------------------------------------------------------------- 1 | IsSupporter | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • IsSupporter

Index

Properties

Properties

is_supporter: null | boolean

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.EndDate.html: -------------------------------------------------------------------------------- 1 | EndDate | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • EndDate

Index

Properties

Properties

end_date: null | string

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.Popularity.html: -------------------------------------------------------------------------------- 1 | Popularity | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Popularity

Index

Properties

Properties

popularity: null | number

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.StartDate.html: -------------------------------------------------------------------------------- 1 | StartDate | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • StartDate

Index

Properties

Properties

start_date: null | string

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.NumScoringUsers.html: -------------------------------------------------------------------------------- 1 | NumScoringUsers | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • NumScoringUsers

Index

Properties

num_scoring_users: number

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/modules/Mal.Manga.MangaForList.html: -------------------------------------------------------------------------------- 1 | MangaForList | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.Rank.html: -------------------------------------------------------------------------------- 1 | Rank | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Rank

Index

Properties

Properties

rank: null | number
2 |

When the rank can not be calculated, such as when the number of user scores is small, the result does not include this field.

3 |

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.Synopsis.html: -------------------------------------------------------------------------------- 1 | Synopsis | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Synopsis

Index

Properties

Properties

synopsis: null | string
2 |

Synopsis.

3 |

The API strips BBCode tags from the result.

4 |

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.Mean.html: -------------------------------------------------------------------------------- 1 | Mean | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Mean

Index

Properties

Properties

mean: null | number
2 |

Mean score.

3 |

When the mean can not be calculated, such as when the number of user scores is small, the result does not include this field.

4 |

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.Genres.html: -------------------------------------------------------------------------------- 1 | Genres | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Genres

Index

Properties

Properties

genres: { id: number; name: string }[]

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Common.WorkForList.NumListUsers.html: -------------------------------------------------------------------------------- 1 | NumListUsers | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • NumListUsers

Index

Properties

Properties

num_list_users: number
2 |

Number of users who have this work in their list.

3 |

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Anime.AnimeListStatus.Tags.html: -------------------------------------------------------------------------------- 1 | Tags | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Manga.MangaListStatus.Tags.html: -------------------------------------------------------------------------------- 1 | Tags | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Property

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Mal.Anime.AnimeListStatus.Priority.html: -------------------------------------------------------------------------------- 1 | Priority | node-myanimelist
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Property

Settings

Theme

Generated using TypeDoc

--------------------------------------------------------------------------------