Skip to content

Commit 846f7de

Browse files
Merge pull request #99 from PhantomOffKanagawa/master
MAL Manga Support Through Second Jikan
2 parents ab69497 + 67825ae commit 846f7de

File tree

8 files changed

+272
-3
lines changed

8 files changed

+272
-3
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

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Obsidian Media DB Plugin
22

3-
A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.
3+
A plugin that can query multiple APIs for movies, series, anime, manga, games, music and wiki articles, and import them into your vault.
44

55
### Features
66

@@ -113,7 +113,7 @@ Now you select the result you want and the plugin will cast it's magic and creat
113113
114114
| Name | Description | Supported formats | Authentification | Rate limiting | SFW filter support |
115115
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------- | ------------------------------ | ------------------ |
116-
| [Jikan](https://jikan.moe/) | Jikan is an API that uses [My Anime List](https://myanimelist.net) and offers metadata for anime. | series, movies, specials, OVAs | No | 60 per minute and 3 per second | Yes |
116+
| [Jikan](https://jikan.moe/) | Jikan is an API that uses [My Anime List](https://myanimelist.net) and offers metadata for anime. | series, movies, specials, OVAs, manga, manwha, novels | No | 60 per minute and 3 per second | Yes |
117117
| [OMDb](https://www.omdbapi.com/) | OMDb is an API that offers metadata for movie, series and games. | series, movies, games | Yes, you can get a free key here [here](https://www.omdbapi.com/apikey.aspx) | 1000 per day | No |
118118
| [MusicBrainz](https://musicbrainz.org/) | MusicBrainz is an API that offers information about music releases. | music releases | No | 50 per second | No |
119119
| [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) | The Wikipedia API allows access to all Wikipedia articles. | wiki articles | No | None | No |
@@ -134,6 +134,10 @@ Now you select the result you want and the plugin will cast it's magic and creat
134134
- the ID you need is the ID of the anime on [My Anime List](https://myanimelist.net)
135135
- you can find this ID in the URL
136136
- e.g. for "Beyond the Boundary" the URL looks like this `https://myanimelist.net/anime/18153/Kyoukai_no_Kanata` so the ID is `18153`
137+
- [Jikan Manga](https://jikan.moe/)
138+
- the ID you need is the ID of the manga on [My Anime List](https://myanimelist.net)
139+
- you can find this ID in the URL
140+
- e.g. for "All You Need Is Kill" the URL looks like this `https://myanimelist.net/manga/62887/All_You_Need_Is_Kill` so the ID is `62887`
137141
- [OMDb](https://www.omdbapi.com/)
138142
- the ID you need is the ID of the movie or show on [IMDb](https://www.imdb.com)
139143
- you can find this ID in the URL

src/api/apis/MALAPIManga.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { APIModel } from '../APIModel';
2+
import { MediaTypeModel } from '../../models/MediaTypeModel';
3+
import MediaDbPlugin from '../../main';
4+
import { MangaModel } from '../../models/MangaModel';
5+
import { MediaType } from '../../utils/MediaType';
6+
7+
export class MALAPIManga extends APIModel {
8+
plugin: MediaDbPlugin;
9+
typeMappings: Map<string, string>;
10+
11+
constructor(plugin: MediaDbPlugin) {
12+
super();
13+
14+
this.plugin = plugin;
15+
this.apiName = 'MALAPI Manga';
16+
this.apiDescription = 'A free API for Manga. Some results may take a long time to load.';
17+
this.apiUrl = 'https://jikan.moe/';
18+
this.types = [MediaType.Manga];
19+
this.typeMappings = new Map<string, string>();
20+
this.typeMappings.set('manga', 'manga');
21+
this.typeMappings.set('manhwa', 'manhwa');
22+
this.typeMappings.set('doujinshi', 'doujin');
23+
this.typeMappings.set('one-shot', 'oneshot');
24+
this.typeMappings.set('manhua', 'manhua');
25+
this.typeMappings.set('light novel', 'light-novel');
26+
this.typeMappings.set('novel', 'novel');
27+
}
28+
29+
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
30+
console.log(`MDB | api "${this.apiName}" queried by Title`);
31+
32+
const searchUrl = `https://api.jikan.moe/v4/manga?q=${encodeURIComponent(title)}&limit=20${this.plugin.settings.sfwFilter ? '&sfw' : ''}`;
33+
34+
const fetchData = await fetch(searchUrl);
35+
console.debug(fetchData);
36+
if (fetchData.status !== 200) {
37+
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
38+
}
39+
const data = await fetchData.json();
40+
41+
console.debug(data);
42+
43+
const ret: MediaTypeModel[] = [];
44+
45+
for (const result of data.data) {
46+
const type = this.typeMappings.get(result.type?.toLowerCase());
47+
ret.push(
48+
new MangaModel({
49+
subType: type,
50+
title: result.title,
51+
synopsis: result.synopsis,
52+
englishTitle: result.title_english ?? result.title,
53+
alternateTitles: result.titles?.map((x: any) => x.title) ?? [],
54+
year: result.year ?? result.published?.prop?.from?.year ?? '',
55+
dataSource: this.apiName,
56+
url: result.url,
57+
id: result.mal_id,
58+
59+
genres: result.genres?.map((x: any) => x.name) ?? [],
60+
authors: result.authors?.map((x: any) => x.name) ?? [],
61+
chapters: result.chapters,
62+
volumes: result.volumes,
63+
onlineRating: result.score ?? 0,
64+
image: result.images?.jpg?.image_url ?? '',
65+
66+
released: true,
67+
publishedFrom: new Date(result.published?.from).toLocaleDateString() ?? 'unknown',
68+
publishedTo: new Date(result.published?.to).toLocaleDateString() ?? 'unknown',
69+
status: result.status,
70+
71+
userData: {
72+
watched: false,
73+
lastWatched: '',
74+
personalRating: 0,
75+
},
76+
} as MangaModel)
77+
)
78+
}
79+
80+
return ret;
81+
}
82+
83+
async getById(id: string): Promise<MediaTypeModel> {
84+
console.log(`MDB | api "${this.apiName}" queried by ID`);
85+
86+
const searchUrl = `https://api.jikan.moe/v4/manga/${encodeURIComponent(id)}/full`;
87+
const fetchData = await fetch(searchUrl);
88+
89+
if (fetchData.status !== 200) {
90+
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
91+
}
92+
93+
const data = await fetchData.json();
94+
console.debug(data);
95+
const result = data.data;
96+
97+
const type = this.typeMappings.get(result.type?.toLowerCase());
98+
const model = new MangaModel({
99+
subType: type,
100+
title: result.title,
101+
synopsis: result.synopsis,
102+
englishTitle: result.title_english ?? result.title,
103+
alternateTitles: result.titles?.map((x: any) => x.title) ?? [],
104+
year: result.year ?? result.published?.prop?.from?.year ?? '',
105+
dataSource: this.apiName,
106+
url: result.url,
107+
id: result.mal_id,
108+
109+
genres: result.genres?.map((x: any) => x.name) ?? [],
110+
authors: result.authors?.map((x: any) => x.name) ?? [],
111+
chapters: result.chapters,
112+
volumes: result.volumes,
113+
onlineRating: result.score ?? 0,
114+
image: result.images?.jpg?.image_url ?? '',
115+
116+
released: true,
117+
publishedFrom: new Date(result.published?.from).toLocaleDateString() ?? 'unknown',
118+
publishedTo: new Date(result.published?.to).toLocaleDateString() ?? 'unknown',
119+
status: result.status,
120+
121+
userData: {
122+
watched: false,
123+
lastWatched: '',
124+
personalRating: 0,
125+
},
126+
} as MangaModel);
127+
128+
return model;
129+
}
130+
}

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';
@@ -33,6 +34,7 @@ export default class MediaDbPlugin extends Plugin {
3334
// register APIs
3435
this.apiManager.registerAPI(new OMDbAPI(this));
3536
this.apiManager.registerAPI(new MALAPI(this));
37+
this.apiManager.registerAPI(new MALAPIManga(this));
3638
this.apiManager.registerAPI(new WikipediaAPI(this));
3739
this.apiManager.registerAPI(new MusicBrainzAPI(this));
3840
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', 'light-novel'];
68+
}
69+
70+
getMediaType(): MediaType {
71+
return MediaType.Manga;
72+
}
73+
74+
getSummary(): string {
75+
return this.title + ' (' + this.year + ')';
76+
}
77+
}

src/settings/Settings.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface MediaDbPluginSettings {
1818

1919
movieTemplate: string;
2020
seriesTemplate: string;
21+
mangaTemplate: string;
2122
gameTemplate: string;
2223
wikiTemplate: string;
2324
musicReleaseTemplate: string;
@@ -26,6 +27,7 @@ export interface MediaDbPluginSettings {
2627

2728
movieFileNameTemplate: string;
2829
seriesFileNameTemplate: string;
30+
mangaFileNameTemplate: string;
2931
gameFileNameTemplate: string;
3032
wikiFileNameTemplate: string;
3133
musicReleaseFileNameTemplate: string;
@@ -34,6 +36,7 @@ export interface MediaDbPluginSettings {
3436

3537
moviePropertyConversionRules: string;
3638
seriesPropertyConversionRules: string;
39+
mangaPropertyConversionRules: string;
3740
gamePropertyConversionRules: string;
3841
wikiPropertyConversionRules: string;
3942
musicReleasePropertyConversionRules: string;
@@ -42,6 +45,7 @@ export interface MediaDbPluginSettings {
4245

4346
movieFolder: string;
4447
seriesFolder: string;
48+
mangaFolder: string;
4549
gameFolder: string;
4650
wikiFolder: string;
4751
musicReleaseFolder: string;
@@ -60,6 +64,7 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
6064

6165
movieTemplate: '',
6266
seriesTemplate: '',
67+
mangaTemplate: '',
6368
gameTemplate: '',
6469
wikiTemplate: '',
6570
musicReleaseTemplate: '',
@@ -68,6 +73,7 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
6873

6974
movieFileNameTemplate: '{{ title }} ({{ year }})',
7075
seriesFileNameTemplate: '{{ title }} ({{ year }})',
76+
mangaFileNameTemplate: '{{ title }} ({{ year }})',
7177
gameFileNameTemplate: '{{ title }} ({{ year }})',
7278
wikiFileNameTemplate: '{{ title }}',
7379
musicReleaseFileNameTemplate: '{{ title }} (by {{ ENUM:artists }} - {{ year }})',
@@ -76,6 +82,7 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
7682

7783
moviePropertyConversionRules: '',
7884
seriesPropertyConversionRules: '',
85+
mangaPropertyConversionRules: '',
7986
gamePropertyConversionRules: '',
8087
wikiPropertyConversionRules: '',
8188
musicReleasePropertyConversionRules: '',
@@ -84,6 +91,7 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
8491

8592
movieFolder: 'Media DB/movies',
8693
seriesFolder: 'Media DB/series',
94+
mangaFolder: 'Media DB/manga',
8795
gameFolder: 'Media DB/games',
8896
wikiFolder: 'Media DB/wiki',
8997
musicReleaseFolder: 'Media DB/music',
@@ -226,6 +234,19 @@ export class MediaDbSettingTab extends PluginSettingTab {
226234
});
227235
});
228236

237+
new Setting(containerEl)
238+
.setName('Manga Folder')
239+
.setDesc('Where newly imported manga should be placed.')
240+
.addSearch(cb => {
241+
new FolderSuggest(this.app, cb.inputEl);
242+
cb.setPlaceholder(DEFAULT_SETTINGS.mangaFolder)
243+
.setValue(this.plugin.settings.mangaFolder)
244+
.onChange(data => {
245+
this.plugin.settings.mangaFolder = data;
246+
this.plugin.saveSettings();
247+
});
248+
});
249+
229250
new Setting(containerEl)
230251
.setName('Game Folder')
231252
.setDesc('Where newly imported games should be placed.')
@@ -319,6 +340,19 @@ export class MediaDbSettingTab extends PluginSettingTab {
319340
});
320341
});
321342

343+
new Setting(containerEl)
344+
.setName('Manga template')
345+
.setDesc('Template file to be used when creating a new note for a manga.')
346+
.addSearch(cb => {
347+
new FileSuggest(this.app, cb.inputEl);
348+
cb.setPlaceholder('Example: mangaTemplate.md')
349+
.setValue(this.plugin.settings.mangaTemplate)
350+
.onChange(data => {
351+
this.plugin.settings.mangaTemplate = data;
352+
this.plugin.saveSettings();
353+
});
354+
});
355+
322356
new Setting(containerEl)
323357
.setName('Game template')
324358
.setDesc('Template file to be used when creating a new note for a game.')
@@ -411,6 +445,18 @@ export class MediaDbSettingTab extends PluginSettingTab {
411445
});
412446
});
413447

448+
new Setting(containerEl)
449+
.setName('Manga file name template')
450+
.setDesc('Template for the file name used when creating a new note for a manga.')
451+
.addText(cb => {
452+
cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.mangaFileNameTemplate}`)
453+
.setValue(this.plugin.settings.mangaFileNameTemplate)
454+
.onChange(data => {
455+
this.plugin.settings.mangaFileNameTemplate = data;
456+
this.plugin.saveSettings();
457+
});
458+
});
459+
414460
new Setting(containerEl)
415461
.setName('Game file name template')
416462
.setDesc('Template for the file name used when creating a new note for a game.')

src/utils/MediaType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export enum MediaType {
22
Movie = 'movie',
33
Series = 'series',
4+
Manga = 'manga',
45
Game = 'game',
56
MusicRelease = 'musicRelease',
67
Wiki = 'wiki',

0 commit comments

Comments
 (0)