Skip to content

Commit eb28e96

Browse files
Added MAL Manga as a second Jikan API
1 parent 2d2e380 commit eb28e96

File tree

7 files changed

+335
-1
lines changed

7 files changed

+335
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ data.json
2020

2121
# Exclude macOS Finder (System Explorer) View States
2222
.DS_Store
23+
24+
src/**/*.js
25+
__mocks__/*.js

src/api/apis/MALAPIManga.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { APIModel } from '../APIModel';
2+
import { MediaTypeModel } from '../../models/MediaTypeModel';
3+
// import { MovieModel } from '../../models/MovieModel';
4+
import MediaDbPlugin from '../../main';
5+
import { MangaModel } from '../../models/MangaModel';
6+
import { MediaType } from '../../utils/MediaType';
7+
8+
export class MALAPIManga extends APIModel {
9+
plugin: MediaDbPlugin;
10+
typeMappings: Map<string, string>;
11+
12+
constructor(plugin: MediaDbPlugin) {
13+
super();
14+
15+
this.plugin = plugin;
16+
this.apiName = 'MALAPI Manga';
17+
this.apiDescription = 'A free API for Manga. Some results may take a long time to load.';
18+
this.apiUrl = 'https://jikan.moe/';
19+
this.types = [MediaType.Manga];
20+
this.typeMappings = new Map<string, string>();
21+
this.typeMappings.set('manga', 'manga');
22+
this.typeMappings.set('light novel', 'lnovel');
23+
}
24+
25+
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
26+
console.log(`MDB | api "${this.apiName}" queried by Title`);
27+
28+
const searchUrl = `https://api.jikan.moe/v4/manga?q=${encodeURIComponent(title)}&limit=20${this.plugin.settings.sfwFilter ? '&sfw' : ''}`;
29+
30+
const fetchData = await fetch(searchUrl);
31+
console.debug(fetchData);
32+
if (fetchData.status !== 200) {
33+
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
34+
}
35+
const data = await fetchData.json();
36+
37+
console.debug(data);
38+
39+
const ret: MediaTypeModel[] = [];
40+
41+
for (const result of data.data) {
42+
const type = this.typeMappings.get(result.type?.toLowerCase());
43+
if (type === undefined) {
44+
ret.push(
45+
new MangaModel({
46+
subType: type,
47+
title: result.title,
48+
synopsis: result.synopsis,
49+
englishTitle: result.title_english ?? result.title,
50+
alternateTitles: result.titles?.map((x: any) => x.title) ?? [],
51+
year: result.year ?? result.published?.prop?.from?.year ?? '',
52+
dataSource: this.apiName,
53+
url: result.url,
54+
id: result.mal_id,
55+
56+
genres: result.genres?.map((x: any) => x.name) ?? [],
57+
authors: result.authors?.map((x: any) => x.name) ?? [],
58+
chapters: result.chapters,
59+
volumes: result.volumes,
60+
onlineRating: result.score ?? 0,
61+
image: result.images?.jpg?.image_url ?? '',
62+
63+
released: true,
64+
publishedFrom: new Date(result.published?.from).toLocaleDateString() ?? 'unknown',
65+
publishedTo: new Date(result.published?.to).toLocaleDateString() ?? 'unknown',
66+
status: result.status,
67+
68+
userData: {
69+
watched: false,
70+
lastWatched: '',
71+
personalRating: 0,
72+
},
73+
} as MangaModel)
74+
);
75+
}
76+
if (type === 'manga' || type === "lnovel") {
77+
ret.push(
78+
new MangaModel({
79+
subType: type,
80+
title: result.title,
81+
synopsis: result.synopsis,
82+
englishTitle: result.title_english ?? result.title,
83+
alternateTitles: result.titles?.map((x: any) => x.title) ?? [],
84+
year: result.year ?? result.published?.prop?.from?.year ?? '',
85+
dataSource: this.apiName,
86+
url: result.url,
87+
id: result.mal_id,
88+
89+
genres: result.genres?.map((x: any) => x.name) ?? [],
90+
authors: result.authors?.map((x: any) => x.name) ?? [],
91+
chapters: result.chapters,
92+
volumes: result.volumes,
93+
onlineRating: result.score ?? 0,
94+
image: result.images?.jpg?.image_url ?? '',
95+
96+
released: true,
97+
publishedFrom: new Date(result.published?.from).toLocaleDateString() ?? 'unknown',
98+
publishedTo: new Date(result.published?.to).toLocaleDateString() ?? 'unknown',
99+
status: result.status,
100+
101+
userData: {
102+
watched: false,
103+
lastWatched: '',
104+
personalRating: 0,
105+
},
106+
} as MangaModel)
107+
);
108+
}
109+
}
110+
111+
return ret;
112+
}
113+
114+
async getById(id: string): Promise<MediaTypeModel> {
115+
console.log(`MDB | api "${this.apiName}" queried by ID`);
116+
117+
const searchUrl = `https://api.jikan.moe/v4/manga/${encodeURIComponent(id)}/full`;
118+
const fetchData = await fetch(searchUrl);
119+
120+
if (fetchData.status !== 200) {
121+
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
122+
}
123+
124+
const data = await fetchData.json();
125+
console.debug(data);
126+
const result = data.data;
127+
128+
const type = this.typeMappings.get(result.type?.toLowerCase());
129+
if (type === undefined) {
130+
const model = new MangaModel({
131+
subType: type,
132+
title: result.title,
133+
synopsis: result.synopsis,
134+
englishTitle: result.title_english ?? result.title,
135+
alternateTitles: result.titles?.map((x: any) => x.title) ?? [],
136+
year: result.year ?? result.published?.prop?.from?.year ?? '',
137+
dataSource: this.apiName,
138+
url: result.url,
139+
id: result.mal_id,
140+
141+
genres: result.genres?.map((x: any) => x.name) ?? [],
142+
authors: result.authors?.map((x: any) => x.name) ?? [],
143+
chapters: result.chapters,
144+
volumes: result.volumes,
145+
onlineRating: result.score ?? 0,
146+
image: result.images?.jpg?.image_url ?? '',
147+
148+
released: true,
149+
publishedFrom: new Date(result.published?.from).toLocaleDateString() ?? 'unknown',
150+
publishedTo: new Date(result.published?.to).toLocaleDateString() ?? 'unknown',
151+
status: result.status,
152+
153+
userData: {
154+
watched: false,
155+
lastWatched: '',
156+
personalRating: 0,
157+
},
158+
} as MangaModel);
159+
160+
return model;
161+
}
162+
163+
if (type === 'manga' || type === 'lnovel') {
164+
const model = new MangaModel({
165+
subType: type,
166+
title: result.title,
167+
synopsis: result.synopsis,
168+
englishTitle: result.title_english ?? result.title,
169+
alternateTitles: result.titles?.map((x: any) => x.title) ?? [],
170+
year: result.year ?? result.published?.prop?.from?.year ?? '',
171+
dataSource: this.apiName,
172+
url: result.url,
173+
id: result.mal_id,
174+
175+
genres: result.genres?.map((x: any) => x.name) ?? [],
176+
authors: result.authors?.map((x: any) => x.name) ?? [],
177+
chapters: result.chapters,
178+
volumes: result.volumes,
179+
onlineRating: result.score ?? 0,
180+
image: result.images?.jpg?.image_url ?? '',
181+
182+
released: true,
183+
publishedFrom: new Date(result.published?.from).toLocaleDateString() ?? 'unknown',
184+
publishedTo: new Date(result.published?.to).toLocaleDateString() ?? 'unknown',
185+
status: result.status,
186+
187+
userData: {
188+
watched: false,
189+
lastWatched: '',
190+
personalRating: 0,
191+
},
192+
} as MangaModel);
193+
194+
return model;
195+
}
196+
197+
return;
198+
}
199+
}

src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { MediaTypeModel } from './models/MediaTypeModel';
55
import { CreateNoteOptions, dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, unCamelCase } from './utils/Utils';
66
import { OMDbAPI } from './api/apis/OMDbAPI';
77
import { MALAPI } from './api/apis/MALAPI';
8+
import { MALAPIManga } from './api/apis/MALAPIManga';
89
import { WikipediaAPI } from './api/apis/WikipediaAPI';
910
import { MusicBrainzAPI } from './api/apis/MusicBrainzAPI';
1011
import { MEDIA_TYPES, MediaTypeManager } from './utils/MediaTypeManager';
@@ -30,6 +31,7 @@ export default class MediaDbPlugin extends Plugin {
3031
// register APIs
3132
this.apiManager.registerAPI(new OMDbAPI(this));
3233
this.apiManager.registerAPI(new MALAPI(this));
34+
this.apiManager.registerAPI(new MALAPIManga(this));
3335
this.apiManager.registerAPI(new WikipediaAPI(this));
3436
this.apiManager.registerAPI(new MusicBrainzAPI(this));
3537
this.apiManager.registerAPI(new SteamAPI(this));

src/models/MangaModel.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { MediaTypeModel } from './MediaTypeModel';
2+
import { mediaDbTag, migrateObject } from '../utils/Utils';
3+
import { MediaType } from '../utils/MediaType';
4+
5+
export class MangaModel extends MediaTypeModel {
6+
type: string;
7+
subType: string;
8+
title: string;
9+
synopsis: string;
10+
englishTitle: string;
11+
alternateTitles: string[];
12+
year: string;
13+
dataSource: string;
14+
url: string;
15+
id: string;
16+
17+
genres: string[];
18+
authors: string[];
19+
chapters: number;
20+
volumes: number;
21+
onlineRating: number;
22+
image: string;
23+
24+
released: boolean;
25+
status: string;
26+
publishedFrom: string;
27+
publishedTo: string;
28+
29+
userData: {
30+
watched: boolean;
31+
lastWatched: string;
32+
personalRating: number;
33+
};
34+
35+
constructor(obj: any = {}) {
36+
super();
37+
38+
this.genres = undefined;
39+
this.authors = undefined;
40+
this.alternateTitles = undefined;
41+
this.chapters = undefined;
42+
this.volumes = undefined;
43+
this.onlineRating = undefined;
44+
this.image = undefined;
45+
46+
this.released = undefined;
47+
this.status = undefined;
48+
this.publishedFrom = undefined;
49+
this.publishedTo = undefined;
50+
51+
this.userData = {
52+
watched: undefined,
53+
lastWatched: undefined,
54+
personalRating: undefined,
55+
};
56+
57+
migrateObject(this, obj, this);
58+
59+
if (!obj.hasOwnProperty('userData')) {
60+
migrateObject(this.userData, obj, this.userData);
61+
}
62+
63+
this.type = this.getMediaType();
64+
}
65+
66+
getTags(): string[] {
67+
return [mediaDbTag, 'manga', 'lnovel'];
68+
}
69+
70+
getMediaType(): MediaType {
71+
return MediaType.Manga;
72+
}
73+
74+
getSummary(): string {
75+
return this.title + ' (' + this.year + ')';
76+
}
77+
}

0 commit comments

Comments
 (0)