diff --git a/README.md b/README.md index 37138fc..276138d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ ## Obsidian Media DB Plugin -A plugin that can query multiple APIs for movies, series, anime, manga, games, music and wiki articles, and import them into your vault. +A plugin that can query multiple APIs for movies, series, asian dramas, anime, manga, games, music and wiki articles, and import them into your vault. ### Features #### Search by Title -Search a movie, series, anime, game, music release or wiki article by its name across multiple APIs. +Search a movie, series, asian dramas, anime, game, music release or wiki article by its name across multiple APIs. #### Search by ID @@ -104,6 +104,7 @@ Now you select the result you want and the plugin will cast it's magic and creat - movies (including specials) - series (including OVAs) +- Asian Dramas and Movies (including specials) - games - music releases - wiki articles @@ -111,23 +112,29 @@ Now you select the result you want and the plugin will cast it's magic and creat ### Currently supported APIs: -| Name | Description | Supported formats | Authentification | Rate limiting | SFW filter support | -| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| [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 | -| [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 | -| [MusicBrainz](https://musicbrainz.org/) | MusicBrainz is an API that offers information about music releases. | music releases | No | 50 per second | No | -| [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) | The Wikipedia API allows access to all Wikipedia articles. | wiki articles | No | None | No | -| [Steam](https://store.steampowered.com/) | The Steam API offers information on all steam games. | games | No | 10000 per day | No | -| [Open Library](https://openlibrary.org) | The OpenLibrary API offers metadata for books | books | No | Cover access is rate-limited when not using CoverID or OLID by max 100 requests/IP every 5 minutes. This plugin uses OLID so there shouldn't be a rate limit. | No | -| [Moby Games](https://www.mobygames.com) | The Moby Games API offers metadata for games for all platforms | games | Yes, by making an account [here](https://www.mobygames.com/user/register/) | API requests are limited to 360 per hour (one every ten seconds). In addition, requests should be made no more frequently than one per second. | No | +| Name | Description | Supported formats | Authentification | Rate limiting | SFW filter support | +| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| [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 | +| [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 | +| [MyDramaList](https://mydramalist.com/) | MyDramaList offers metadata for Asian Dramas, movies, specials, etc. [Unofficial API](https://github.com/tbdsux/kuryana) is being used. | series, movies, specials | No | No | No | +| [MusicBrainz](https://musicbrainz.org/) | MusicBrainz is an API that offers information about music releases. | music releases | No | 50 per second | No | +| [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) | The Wikipedia API allows access to all Wikipedia articles. | wiki articles | No | None | No | +| [Steam](https://store.steampowered.com/) | The Steam API offers information on all steam games. | games | No | 10000 per day | No | +| [Open Library](https://openlibrary.org) | The OpenLibrary API offers metadata for books | books | No | Cover access is rate-limited when not using CoverID or OLID by max 100 requests/IP every 5 minutes. This plugin uses OLID so there shouldn't be a rate limit. | No | +| [Moby Games](https://www.mobygames.com) | The Moby Games API offers metadata for games for all platforms | games | Yes, by making an account [here](https://www.mobygames.com/user/register/) | API requests are limited to 360 per hour (one every ten seconds). In addition, requests should be made no more frequently than one per second. | No | #### Notes - [Jikan](https://jikan.moe/) + - sometimes the api is very slow, this is normal - you need to use the title the anime has on [My Anime List](https://myanimelist.net), which is in most cases the japanese title - e.g. instead of "Demon Slayer" you have to search "Kimetsu no Yaiba" +- [MyDramaList](https://mydramalist.com/) + - [Unofficial API](https://github.com/tbdsux/kuryana/) is being used + - Ratelimit is unknown + #### Search by ID - [Jikan](https://jikan.moe/) @@ -142,6 +149,9 @@ Now you select the result you want and the plugin will cast it's magic and creat - the ID you need is the ID of the movie or show on [IMDb](https://www.imdb.com) - you can find this ID in the URL - e.g. for "Rogue One" the URL looks like this `https://www.imdb.com/title/tt3748528/` so the ID is `tt3748528` +- [MyDramaList](https://mydramalist.com/) + - Id is in the URL + - e.g. for "The Roundup" the URL looks like this `https://mydramalist.com/29612-the-outlaws-2/` so the ID is `29612-the-outlaws-2` - [MusicBrainz](https://musicbrainz.org/) - the id of a release is not easily accessible, you are better off just searching by title - [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) diff --git a/src/api/apis/MyDramaListAPI.ts b/src/api/apis/MyDramaListAPI.ts new file mode 100644 index 0000000..e5e8a42 --- /dev/null +++ b/src/api/apis/MyDramaListAPI.ts @@ -0,0 +1,180 @@ +import MediaDbPlugin from 'src/main'; +import { APIModel } from '../APIModel'; +import { MediaType } from 'src/utils/MediaType'; +import { MediaTypeModel } from 'src/models/MediaTypeModel'; +import { MovieModel } from 'src/models/MovieModel'; +import { SeriesModel } from 'src/models/SeriesModel'; + +export class MyDramaListAPI extends APIModel { + plugin: MediaDbPlugin; + apiDateFormat: string = 'DD MMM YYYY'; + + constructor(plugin: MediaDbPlugin) { + super(); + + this.plugin = plugin; + this.apiName = 'MyDramaListAPI'; + this.apiDescription = 'A free API for Asian Movies and Dramas'; + this.apiUrl = 'https://kuryana.vercel.app/'; + this.types = [MediaType.Movie, MediaType.Series]; + } + + async searchByTitle(title: string): Promise { + console.log(`MDB | api "${this.apiName}" queried by Title`); + + const searchUrl = `https://kuryana.vercel.app/search/q/${encodeURIComponent(title)}`; + const fetchData = await fetch(searchUrl); + // console.debug(fetchData); + if (fetchData.status !== 200) { + throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`); + } + + const data = await fetchData.json(); + //console.debug(data); + + const ret: MediaTypeModel[] = []; + for (const result of data.results.dramas) { + const type = result.type.toLowerCase(); + if (type.contains('movie')) { + ret.push( + new MovieModel({ + type: 'Movie', + subType: result.type, + title: result.title, + englishTitle: result.title, + year: result.year, + dataSource: this.apiName, + id: result.slug, + } as MovieModel), + ); + } else if (type.contains('series') || type.contains('show') || type.contains('drama') || type.contains('special')) { + ret.push( + new SeriesModel({ + type: 'Series', + subType: result.type, + title: result.title, + englishTitle: result.title, + year: result.year, + dataSource: this.apiName, + id: result.slug, + } as SeriesModel), + ); + } else { + console.debug(`MDB | api "${this.apiName}" unsupported Type "${result.type}"`); + } + } + return ret; + } + + async getById(id: string): Promise { + console.log(`MDB | api "${this.apiName}" queried by ID`); + + const searchUrl = `https://kuryana.vercel.app/id/${encodeURIComponent(id)}`; + const fetchData = await fetch(searchUrl); + // console.debug(fetchData); + + if (fetchData.status !== 200) { + throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`); + } + + const data = await fetchData.json(); + // console.debug(data); + + if (data.error) { + throw Error(`MDB | Received error from ${this.apiName}: ${data.title} ${data.info}`); + } + + const result = data.data; + const type = result.details.type.toLowerCase(); + if (type.contains('movie')) { + return new MovieModel({ + type: 'Movie', + subType: result.details.type, + title: result.title, + englishTitle: result.title, + nativeTitle: result?.others?.native_title ?? '', + aliases: result?.others?.also_known_as ?? [], + year: result.details?.release_date?.split(',')[1]?.trim(), + dataSource: 'My Drama List', + url: `https://mydramalist.com/${id}`, + id: id, + country: result?.details?.country ?? '', + content_rating: result?.details?.content_rating ?? '', + + plot: result.synopsis ?? '', + genres: result.others.genres ?? [], + mediaTags: result?.others?.tags ? result.others.tags.map((tag: string) => tag.replace(' (Vote or add tags)', '')) : [], + relatedContent: result.others.related_content ?? '', + director: result.others.director ? result.others.director : [], + writer: result.others.screenwriter ?? [], + studio: result.details.original_network ? [result.details.original_network] : [], + duration: result.details.duration?.replace(/[. ]/g, '') ?? '', + onlineRating: result.rating ? Number.parseFloat(result.rating) : '', + votes: Number.parseInt(result.details.score.split('(')[1].split(' ')[2].replace(',', '')) ?? '', + ranked: Number.parseInt(result.details.ranked.replace('#', '')) ?? '', + popularity: Number.parseInt(result.details.popularity.replace('#', '')) ?? '', + watchers: result.details.watchers ? Number.parseInt(result.details.watchers) : '', + actors: result.casts.map((cast: any) => cast.name) ?? [], + image: result.poster ?? '', + + released: true, + streamingServices: [], + premiere: this.plugin.dateFormatter.format(result.details.release_date, this.apiDateFormat) ?? '', + + userData: { + watched: false, + lastWatched: '', + personalRating: 0, + }, + } as MovieModel); + } else if (type.contains('series') || type.contains('show') || type.contains('drama') || type.contains('special')) { + return new SeriesModel({ + type: 'Series', + subType: result.details.type, + title: result.title, + nativeTitle: result?.others?.native_title ?? '', + aliases: result?.others?.also_known_as ?? [], + englishTitle: result.title, + year: result.details.aired?.split(',')[1]?.split('-')[0]?.trim(), + dataSource: 'My Drama List', + url: `https://mydramalist.com/${id}`, + id: id, + country: result?.details?.country ?? '', + content_rating: result?.details?.content_rating ?? '', + + plot: result.synopsis ?? '', + genres: result.others.genres ?? [], + mediaTags: result?.others?.tags ? result.others.tags.map((tag: string) => tag.replace(' (Vote or add tags)', '')) : [], + relatedContent: result.others.related_content ?? '', + director: result.others.director ? result.others.director : [], + writer: result.others.screenwriter ?? [], + studio: [result.details.original_network], + episodes: result.details.episodes ?? 0, + duration: result.details.duration?.replace(/[. ]/g, '') ?? '', + onlineRating: result.rating ? Number.parseFloat(result.rating) : '', + actors: result.casts.map((cast: any) => cast.name) ?? [], + votes: Number.parseInt(result.details.score.split('(')[1].split(' ')[2].replace(',', '')) ?? '', + ranked: Number.parseInt(result.details.ranked.replace('#', '')) ?? '', + popularity: Number.parseInt(result.details.popularity.replace('#', '')) ?? '', + watchers: result.details.watchers ? Number.parseInt(result.details.watchers) : '', + image: result.poster ?? '', + + released: true, + streamingServices: [], + airing: false, + airedFrom: this.plugin.dateFormatter.format(result.details.aired?.split('-')[0]?.trim(), this.apiDateFormat) ?? '', + airedTo: this.plugin.dateFormatter.format(result.details.aired?.split('-')[1]?.trim(), this.apiDateFormat) ?? '', + + userData: { + watched: false, + lastWatched: '', + personalRating: 0, + }, + } as SeriesModel); + } else { + console.debug(`MDB | api "${this.apiName}" unsupported Type "${result.details.type}"`); + } + + return; + } +} diff --git a/src/main.ts b/src/main.ts index 678505a..805db9f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,6 +27,7 @@ import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMappin import { ModalHelper, ModalResultCode, SearchModalOptions } from './utils/ModalHelper'; import { DateFormatter } from './utils/DateFormatter'; import { MediaType } from 'src/utils/MediaType'; +import { MyDramaListAPI } from './api/apis/MyDramaListAPI'; export type Metadata = Record; @@ -58,6 +59,7 @@ export default class MediaDbPlugin extends Plugin { this.apiManager.registerAPI(new BoardGameGeekAPI(this)); this.apiManager.registerAPI(new OpenLibraryAPI(this)); this.apiManager.registerAPI(new MobyGamesAPI(this)); + this.apiManager.registerAPI(new MyDramaListAPI(this)); // this.apiManager.registerAPI(new LocGovAPI(this)); // TODO: parse data this.mediaTypeManager = new MediaTypeManager(); diff --git a/src/models/MovieModel.ts b/src/models/MovieModel.ts index a4db8a7..5607f84 100644 --- a/src/models/MovieModel.ts +++ b/src/models/MovieModel.ts @@ -3,15 +3,25 @@ import { mediaDbTag, migrateObject } from '../utils/Utils'; import { MediaType } from '../utils/MediaType'; export class MovieModel extends MediaTypeModel { + nativeTitle: string; + aliases: string[]; plot: string; genres: string[]; + mediaTags: string[]; + relatedContent: string[]; director: string[]; writer: string[]; studio: string[]; duration: string; onlineRating: number; + votes: number; + ranked: number; + popularity: number; + watchers: number; actors: string[]; image: string; + country: string; + content_rating: string; released: boolean; streamingServices: string[]; @@ -26,15 +36,25 @@ export class MovieModel extends MediaTypeModel { constructor(obj: any = {}) { super(); + this.nativeTitle = undefined; + this.aliases = undefined; this.plot = undefined; this.genres = undefined; + this.mediaTags = undefined; + this.relatedContent = undefined; this.director = undefined; this.writer = undefined; this.studio = undefined; this.duration = undefined; this.onlineRating = undefined; + this.votes = undefined; + this.ranked = undefined; + this.popularity = undefined; + this.watchers = undefined; this.actors = undefined; this.image = undefined; + this.country = undefined; + this.content_rating = undefined; this.released = undefined; this.streamingServices = undefined; diff --git a/src/models/SeriesModel.ts b/src/models/SeriesModel.ts index edc222e..a931f6b 100644 --- a/src/models/SeriesModel.ts +++ b/src/models/SeriesModel.ts @@ -6,19 +6,30 @@ export class SeriesModel extends MediaTypeModel { type: string; subType: string; title: string; + nativeTitle: string; englishTitle: string; + aliases: string[]; year: string; dataSource: string; url: string; id: string; + country: string; + content_rating: string; plot: string; genres: string[]; + mediaTags: string[]; + relatedContent: string[]; + director: string[]; writer: string[]; studio: string[]; episodes: number; duration: string; onlineRating: number; + votes: number; + ranked: number; + popularity: number; + watchers: number; actors: string[]; image: string; @@ -37,15 +48,26 @@ export class SeriesModel extends MediaTypeModel { constructor(obj: any = {}) { super(); + this.nativeTitle = undefined; + this.aliases = undefined; this.plot = undefined; this.genres = undefined; + this.mediaTags = undefined; + this.relatedContent = undefined; + this.director = undefined; this.writer = undefined; this.studio = undefined; this.episodes = undefined; this.duration = undefined; this.onlineRating = undefined; + this.votes = undefined; + this.ranked = undefined; + this.popularity = undefined; + this.watchers = undefined; this.actors = undefined; this.image = undefined; + this.country = undefined; + this.content_rating = undefined; this.released = undefined; this.streamingServices = undefined;