Skip to content

Restored and updated the api toggle + added a download option for poster images #183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## 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, anime, manga, books, games, music and wiki articles, and import them into your vault.

### Features

Expand Down Expand Up @@ -36,6 +36,10 @@ Available variables that can be used in template tags are the same variables fro

I also published my own templates [here](https://github.com/mProjectsCode/obsidian-media-db-templates).

#### Download poster images

Allows you to automatically download the poster images for a new media, ensuring offline access. The images are saved as `type_title (year)` e.g. `movie_The Perfect Storm (2000)` with a user chosen save location.

#### Metadata field customization

Allows you to rename the metadata fields this plugin generates through mappings.
Expand Down Expand Up @@ -113,7 +117,6 @@ 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 |
Expand All @@ -124,7 +127,7 @@ Now you select the result you want and the plugin will cast it's magic and creat
| [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/). NOTE: As of September 2024 the API key is no longer free so consider using Giant Bomb or steam instead | 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 |
| [Giant Bomb](https://www.giantbomb.com) | The Giant Bomb API offers metadata for games for all platforms | games | Yes, by making an account [here](https://www.giantbomb.com/login-signup/) | API requests are limited to 200 requests per resource, per hour. In addition, they implement velocity detection to prevent malicious use. If too many requests are made per second, you may receive temporary blocks to resources. | No |
| Comic Vine | The Comic Vine API offers metadata for comic books | comicbooks | Yes, by making an account [here](https://comicvine.gamespot.com/login-signup/) and going to the [api section](https://comicvine.gamespot.com/api/) of the site | 200 requests per resource, per hour. There is also a velocity detection to prevent malicious use. If too many requests are made per second, you may receive temporary blocks to resources. | No
| Comic Vine | The Comic Vine API offers metadata for comic books | comicbooks | Yes, by making an account [here](https://comicvine.gamespot.com/login-signup/) and going to the [api section](https://comicvine.gamespot.com/api/) of the site | 200 requests per resource, per hour. There is also a velocity detection to prevent malicious use. If too many requests are made per second, you may receive temporary blocks to resources. | No |

#### Notes

Expand All @@ -149,6 +152,10 @@ Now you select the result you want and the plugin will cast it's magic and creat
- e.g. for "Rogue One" the URL looks like this `https://www.imdb.com/title/tt3748528/` so the ID is `tt3748528`
- [MusicBrainz](https://musicbrainz.org/)
- the id of a release is not easily accessible, you are better off just searching by title
- the search is generally for albums but you can have a more granular search like so:
- search for albums by a specific `artist:"Lady Gaga" AND primarytype:"album"`
- search for a specific album by a specific artist `artist:"Lady Gaga" AND primarytype:"album" AND releasegroup:"The Fame"`
- search for a specific entry (song or album) by a specific `artist:"Lady Gaga" AND releasegroup:"Poker face"`
- [Wikipedia](https://en.wikipedia.org/wiki/Main_Page)
- [here](https://en.wikipedia.org/wiki/Wikipedia:Finding_a_Wikidata_ID) is a guide to finding the Wikipedia ID for an article
- [Steam](https://store.steampowered.com/)
Expand Down
13 changes: 5 additions & 8 deletions src/api/APIModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,19 @@ export abstract class APIModel {
plugin!: MediaDbPlugin;

/**
* This function should query the api and return a list of matches. The matches should be caped at 20.
* This function should query the api and return a list of matches. The matches should be capped at 20.
*
* @param title the title to query for
*/
abstract searchByTitle(title: string): Promise<MediaTypeModel[]>;

abstract getById(id: string): Promise<MediaTypeModel>;

abstract getDisabledMediaTypes(): MediaType[];

hasType(type: MediaType): boolean {
// if (
// this.types.contains(type) &&
// (Boolean((this.plugin.settings.apiToggle as any)?.[this.apiName]?.[type]) === true || (this.plugin.settings.apiToggle as any)?.[this.apiName]?.[type] === undefined)
// ) {
// return true;
// }
return this.types.contains(type);
const disabledMediaTypes = this.getDisabledMediaTypes();
return this.types.includes(type) && !disabledMediaTypes.includes(type);
}

hasTypeOverlap(types: MediaType[]): boolean {
Expand Down
3 changes: 3 additions & 0 deletions src/api/apis/BoardGameGeekAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,7 @@ export class BoardGameGeekAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.BoardgameGeekAPI_disabledMediaTypes as MediaType[];
}
}
3 changes: 3 additions & 0 deletions src/api/apis/ComicVineAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,7 @@ export class ComicVineAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.ComicVineAPI_disabledMediaTypes as MediaType[];
}
}
3 changes: 3 additions & 0 deletions src/api/apis/GiantBombAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,7 @@ export class GiantBombAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.GiantBombAPI_disabledMediaTypes as MediaType[];
}
}
3 changes: 3 additions & 0 deletions src/api/apis/MALAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,7 @@ export class MALAPI extends APIModel {

throw new Error(`MDB | Unknown media type for id ${id}`);
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.MALAPI_disabledMediaTypes as MediaType[];
}
}
3 changes: 3 additions & 0 deletions src/api/apis/MALAPIManga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,7 @@ export class MALAPIManga extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.MALAPIManga_disabledMediaTypes as MediaType[];
}
}
3 changes: 3 additions & 0 deletions src/api/apis/MobyGamesAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,7 @@ export class MobyGamesAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.MobyGamesAPI_disabledMediaTypes as MediaType[];
}
}
3 changes: 3 additions & 0 deletions src/api/apis/MusicBrainzAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,7 @@ export class MusicBrainzAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.MusicBrainzAPI_disabledMediaTypes as MediaType[];
}
}
10 changes: 7 additions & 3 deletions src/api/apis/OMDbAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class OMDbAPI extends APIModel {
duration: result.Runtime ?? 'unknown',
onlineRating: Number.parseFloat(result.imdbRating ?? 0),
actors: result.Actors?.split(', ') ?? [],
image: result.Poster ?? '',
image: result.Poster ? result.Poster.replace('_SX300', '_SX600') : '',

released: true,
streamingServices: [],
Expand Down Expand Up @@ -181,7 +181,7 @@ export class OMDbAPI extends APIModel {
duration: result.Runtime ?? 'unknown',
onlineRating: Number.parseFloat(result.imdbRating ?? 0),
actors: result.Actors?.split(', ') ?? [],
image: result.Poster ?? '',
image: result.Poster ? result.Poster.replace('_SX300', '_SX600') : '',

released: true,
streamingServices: [],
Expand Down Expand Up @@ -209,7 +209,7 @@ export class OMDbAPI extends APIModel {
publishers: [],
genres: result.Genre?.split(', ') ?? [],
onlineRating: Number.parseFloat(result.imdbRating ?? 0),
image: result.Poster ?? '',
image: result.Poster ? result.Poster.replace('_SX300', '_SX600') : '',

released: true,
releaseDate: this.plugin.dateFormatter.format(result.Released, this.apiDateFormat) ?? 'unknown',
Expand All @@ -223,4 +223,8 @@ export class OMDbAPI extends APIModel {

throw new Error(`MDB | Unknown media type for id ${id}`);
}

getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.OMDbAPI_disabledMediaTypes as MediaType[];
}
}
3 changes: 3 additions & 0 deletions src/api/apis/OpenLibraryAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,7 @@ export class OpenLibraryAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.OpenLibraryAPI_disabledMediaTypes as MediaType[];
}
}
16 changes: 15 additions & 1 deletion src/api/apis/SteamAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
import { imageUrlExists } from '../../utils/Utils';

export class SteamAPI extends APIModel {
plugin: MediaDbPlugin;
Expand Down Expand Up @@ -85,6 +86,16 @@ export class SteamAPI extends APIModel {

// console.debug(result);

// Check if a poster version of the image exists, else use the header image
const imageUrl = `https://steamcdn-a.akamaihd.net/steam/apps/${result.steam_appid}/library_600x900_2x.jpg`;
const exists = await imageUrlExists(imageUrl);
let finalimageurl;
if (exists) {
finalimageurl = imageUrl;
} else {
finalimageurl = result.header_image ?? '';
}

return new GameModel({
type: MediaType.Game,
title: result.name,
Expand All @@ -98,7 +109,7 @@ export class SteamAPI extends APIModel {
publishers: result.publishers,
genres: result.genres?.map((x: any) => x.description) ?? [],
onlineRating: Number.parseFloat(result.metacritic?.score ?? 0),
image: result.header_image ?? '',
image: finalimageurl ?? '',

released: !result.release_date?.coming_soon,
releaseDate: this.plugin.dateFormatter.format(result.release_date?.date, this.apiDateFormat) ?? 'unknown',
Expand All @@ -109,4 +120,7 @@ export class SteamAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.SteamAPI_disabledMediaTypes as MediaType[];
}
}
3 changes: 3 additions & 0 deletions src/api/apis/WikipediaAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,7 @@ export class WikipediaAPI extends APIModel {
userData: {},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.WikipediaAPI_disabledMediaTypes as MediaType[];
}
}
44 changes: 40 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MarkdownView, Notice, parseYaml, Plugin, stringifyYaml, TFile, TFolder } from 'obsidian';
import { requestUrl, normalizePath } from 'obsidian'; // Add requestUrl import
import type { MediaType } from 'src/utils/MediaType';
import { APIManager } from './api/APIManager';
import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI';
Expand Down Expand Up @@ -218,13 +219,17 @@ export default class MediaDbPlugin extends Plugin {
(await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
return await this.queryDetails(selectModalData.selected);
})) ?? [];
if (!selectResults) {
if (!selectResults || selectResults.length < 1) {
return;
}

proceed = await this.modalHelper.openPreviewModal({ elements: selectResults }, async previewModalData => {
const confirmed = await this.modalHelper.openPreviewModal({ elements: selectResults }, async previewModalData => {
return previewModalData.confirmed;
});
if (!confirmed) {
return;
}
break;
}

await this.createMediaDbNotes(selectResults!);
Expand All @@ -248,13 +253,17 @@ export default class MediaDbPlugin extends Plugin {
(await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
return await this.queryDetails(selectModalData.selected);
})) ?? [];
if (!selectResults) {
if (!selectResults || selectResults.length < 1) {
return;
}

proceed = await this.modalHelper.openPreviewModal({ elements: selectResults }, async previewModalData => {
const confirmed = await this.modalHelper.openPreviewModal({ elements: selectResults }, async previewModalData => {
return previewModalData.confirmed;
});
if (!confirmed) {
return;
}
break;
}

await this.createMediaDbNotes(selectResults!);
Expand Down Expand Up @@ -306,6 +315,33 @@ export default class MediaDbPlugin extends Plugin {

options.openNote = this.settings.openNoteInNewTab;

if (mediaTypeModel.image && typeof mediaTypeModel.image === 'string' && mediaTypeModel.image.startsWith('http')) {
if (this.settings.imageDownload) {
try {
const imageurl = mediaTypeModel.image;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would extract this into a separate method.

const imageext = imageurl.split('.').pop()?.split(/\#|\?/)[0] || 'jpg';
const imagefileName = `${replaceIllegalFileNameCharactersInString(`${mediaTypeModel.type}_${mediaTypeModel.title} (${mediaTypeModel.year})`)}.${imageext}`;
const imagepath = normalizePath(`${this.settings.imageFolder}/${imagefileName}`);

if (!this.app.vault.getAbstractFileByPath(this.settings.imageFolder)) {
await this.app.vault.createFolder(this.settings.imageFolder);
}

if (!this.app.vault.getAbstractFileByPath(imagepath)) {
const response = await requestUrl({ url: imageurl, method: 'GET' });
await this.app.vault.createBinary(imagepath, response.arrayBuffer);
}

// Update model to use local image path
mediaTypeModel.image = `[[${imagepath}]]`;
} catch (e) {
console.warn('MDB | Failed to download image:', e);
}
} else {
mediaTypeModel.image = mediaTypeModel.image;
}
}

const fileContent = await this.generateMediaDbNoteContents(mediaTypeModel, options);

if (!options.folder) {
Expand Down
3 changes: 3 additions & 0 deletions src/models/MediaTypeModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export abstract class MediaTypeModel {
dataSource: string;
url: string;
id: string;
image?: string;

userData: object;

Expand All @@ -21,6 +22,8 @@ export abstract class MediaTypeModel {
this.dataSource = '';
this.url = '';
this.id = '';
this.image = '';

this.userData = {};
}

Expand Down
Loading