Merge pull request #183 from ltctceplrm/api-toggle

Restored and updated the api toggle + added a download option for poster images
This commit is contained in:
Moritz Jung 2025-05-20 12:05:56 +02:00 committed by GitHub
commit 917f7ab411
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 219 additions and 133 deletions

View file

@ -10,7 +10,7 @@ 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
*/
@ -18,14 +18,11 @@ export abstract class APIModel {
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 {

View file

@ -117,4 +117,7 @@ export class BoardGameGeekAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.BoardgameGeekAPI_disabledMediaTypes as MediaType[];
}
}

View file

@ -95,4 +95,7 @@ export class ComicVineAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.ComicVineAPI_disabledMediaTypes as MediaType[];
}
}

View file

@ -106,4 +106,7 @@ export class GiantBombAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.GiantBombAPI_disabledMediaTypes as MediaType[];
}
}

View file

@ -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[];
}
}

View file

@ -126,4 +126,7 @@ export class MALAPIManga extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.MALAPIManga_disabledMediaTypes as MediaType[];
}
}

View file

@ -107,4 +107,7 @@ export class MobyGamesAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.MobyGamesAPI_disabledMediaTypes as MediaType[];
}
}

View file

@ -99,4 +99,7 @@ export class MusicBrainzAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.MusicBrainzAPI_disabledMediaTypes as MediaType[];
}
}

View file

@ -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: [],
@ -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: [],
@ -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',
@ -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[];
}
}

View file

@ -89,4 +89,7 @@ export class OpenLibraryAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.OpenLibraryAPI_disabledMediaTypes as MediaType[];
}
}

View file

@ -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;
@ -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,
@ -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',
@ -109,4 +120,7 @@ export class SteamAPI extends APIModel {
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.SteamAPI_disabledMediaTypes as MediaType[];
}
}

View file

@ -79,4 +79,7 @@ export class WikipediaAPI extends APIModel {
userData: {},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.WikipediaAPI_disabledMediaTypes as MediaType[];
}
}

View file

@ -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';
@ -219,13 +220,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!);
@ -249,13 +254,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!);
@ -307,6 +316,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;
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) {

View file

@ -9,6 +9,7 @@ export abstract class MediaTypeModel {
dataSource: string;
url: string;
id: string;
image?: string;
userData: object;
@ -21,6 +22,8 @@ export abstract class MediaTypeModel {
this.dataSource = '';
this.url = '';
this.id = '';
this.image = '';
this.userData = {};
}

View file

@ -4,11 +4,12 @@ import { mount } from 'svelte';
import type MediaDbPlugin from '../main';
import type { MediaTypeModel } from '../models/MediaTypeModel';
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
import { fragWithHTML } from '../utils/Utils';
import { fragWithHTML, unCamelCase } from '../utils/Utils';
import { PropertyMapping, PropertyMappingModel, PropertyMappingOption } from './PropertyMapping';
import PropertyMappingModelsComponent from './PropertyMappingModelsComponent.svelte';
import { FileSuggest } from './suggesters/FileSuggest';
import { FolderSuggest } from './suggesters/FolderSuggest';
import type { MediaType } from 'src/utils/MediaType';
export interface MediaDbPluginSettings {
OMDbKey: string;
@ -21,24 +22,17 @@ export interface MediaDbPluginSettings {
openNoteInNewTab: boolean;
useDefaultFrontMatter: boolean;
enableTemplaterIntegration: boolean;
// TODO: disabled for now, as i currently don't have the time to fix this from the original PR that introduced it (#133)
// apiToggle: {
// OMDbAPI: {
// movie: boolean;
// series: boolean;
// game: boolean;
// };
// MALAPI: {
// movie: boolean;
// series: boolean;
// };
// SteamAPI: {
// game: boolean;
// };
// MobyGamesAPI: {
// game: boolean;
// };
// };
OMDbAPI_disabledMediaTypes: MediaType[];
MALAPI_disabledMediaTypes: MediaType[];
MALAPIManga_disabledMediaTypes: MediaType[];
ComicVineAPI_disabledMediaTypes: MediaType[];
SteamAPI_disabledMediaTypes: MediaType[];
MobyGamesAPI_disabledMediaTypes: MediaType[];
GiantBombAPI_disabledMediaTypes: MediaType[];
WikipediaAPI_disabledMediaTypes: MediaType[];
BoardgameGeekAPI_disabledMediaTypes: MediaType[];
MusicBrainzAPI_disabledMediaTypes: MediaType[];
OpenLibraryAPI_disabledMediaTypes: MediaType[];
movieTemplate: string;
seriesTemplate: string;
mangaTemplate: string;
@ -75,6 +69,8 @@ export interface MediaDbPluginSettings {
boardgameFolder: string;
bookFolder: string;
imageDownload: boolean;
imageFolder: string;
propertyMappingModels: PropertyMappingModel[];
}
@ -89,23 +85,17 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
openNoteInNewTab: true,
useDefaultFrontMatter: true,
enableTemplaterIntegration: false,
// apiToggle: {
// OMDbAPI: {
// movie: true,
// series: true,
// game: true,
// },
// MALAPI: {
// movie: true,
// series: true,
// },
// SteamAPI: {
// game: true,
// },
// MobyGamesAPI: {
// game: true,
// },
// },
OMDbAPI_disabledMediaTypes: [],
MALAPI_disabledMediaTypes: [],
MALAPIManga_disabledMediaTypes: [],
ComicVineAPI_disabledMediaTypes: [],
SteamAPI_disabledMediaTypes: [],
MobyGamesAPI_disabledMediaTypes: [],
GiantBombAPI_disabledMediaTypes: [],
WikipediaAPI_disabledMediaTypes: [],
BoardgameGeekAPI_disabledMediaTypes: [],
MusicBrainzAPI_disabledMediaTypes: [],
OpenLibraryAPI_disabledMediaTypes: [],
movieTemplate: '',
seriesTemplate: '',
mangaTemplate: '',
@ -142,6 +132,8 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
boardgameFolder: 'Media DB/boardgames',
bookFolder: 'Media DB/books',
imageDownload: false,
imageFolder: 'Media DB/images',
propertyMappingModels: [],
};
@ -310,82 +302,72 @@ export class MediaDbSettingTab extends PluginSettingTab {
});
});
// containerEl.createEl('h3', { text: 'APIs per media type' });
// containerEl.createEl('h5', { text: 'Movies' });
// new Setting(containerEl)
// .setName('OMDb API')
// .setDesc('Use OMDb API for movies.')
// .addToggle(cb => {
// cb.setValue(this.plugin.settings.apiToggle.OMDbAPI.movie).onChange(data => {
// this.plugin.settings.apiToggle.OMDbAPI.movie = data;
// void this.plugin.saveSettings();
// });
// });
// new Setting(containerEl)
// .setName('MAL API')
// .setDesc('Use MAL API for movies.')
// .addToggle(cb => {
// cb.setValue(this.plugin.settings.apiToggle.MALAPI.movie).onChange(data => {
// this.plugin.settings.apiToggle.MALAPI.movie = data;
// void this.plugin.saveSettings();
// });
// });
// containerEl.createEl('h5', { text: 'Series' });
// new Setting(containerEl)
// .setName('OMDb API')
// .setDesc('Use OMDb API for series.')
// .addToggle(cb => {
// cb.setValue(this.plugin.settings.apiToggle.OMDbAPI.series).onChange(data => {
// this.plugin.settings.apiToggle.OMDbAPI.series = data;
// void this.plugin.saveSettings();
// });
// });
// new Setting(containerEl)
// .setName('MAL API')
// .setDesc('Use MAL API for series.')
// .addToggle(cb => {
// cb.setValue(this.plugin.settings.apiToggle.MALAPI.series).onChange(data => {
// this.plugin.settings.apiToggle.MALAPI.series = data;
// void this.plugin.saveSettings();
// });
// });
// containerEl.createEl('h5', { text: 'Games' });
// new Setting(containerEl)
// .setName('OMDb API')
// .setDesc('Use OMDb API for games.')
// .addToggle(cb => {
// cb.setValue(this.plugin.settings.apiToggle.OMDbAPI.game).onChange(data => {
// this.plugin.settings.apiToggle.OMDbAPI.game = data;
// void this.plugin.saveSettings();
// });
// });
// new Setting(containerEl)
// .setName('Steam API')
// .setDesc('Use OMDb API for games.')
// .addToggle(cb => {
// cb.setValue(this.plugin.settings.apiToggle.SteamAPI.game).onChange(data => {
// this.plugin.settings.apiToggle.SteamAPI.game = data;
// void this.plugin.saveSettings();
// });
// });
// new Setting(containerEl)
// .setName('MobyGames API')
// .setDesc('Use MobyGames API for games.')
// .addToggle(cb => {
// cb.setValue(this.plugin.settings.apiToggle.MobyGamesAPI.game).onChange(data => {
// this.plugin.settings.apiToggle.MobyGamesAPI.game = data;
// void this.plugin.saveSettings();
// });
// });
// new Setting(containerEl)
// .setName('Giantbomb API')
// .setDesc('Use Giantbomb API for games.')
// .addToggle(cb => {
// cb.setValue(this.plugin.settings.apiToggle.GiantBombAPI.game).onChange(data => {
// this.plugin.settings.apiToggle.GiantBombAPI.game = data;
// void this.plugin.saveSettings();
// });
// });
new Setting(containerEl)
.setName('Download images')
.setDesc('Downloads images for new notes in the folder below')
.addToggle(cb => {
cb.setValue(this.plugin.settings.imageDownload).onChange(data => {
this.plugin.settings.imageDownload = data;
void this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Image folder')
.setDesc('Where downloaded images should be stored.')
.addSearch(cb => {
new FolderSuggest(this.app, cb.inputEl);
cb.setPlaceholder(DEFAULT_SETTINGS.imageFolder)
.setValue(this.plugin.settings.imageFolder)
.onChange(data => {
this.plugin.settings.imageFolder = data;
void this.plugin.saveSettings();
});
});
// Create a map to store APIs for each media type
const mediaTypeApiMap = new Map<MediaType, string[]>();
// Populate the map with APIs for each media type dynamically
for (const api of this.plugin.apiManager.apis) {
for (const mediaType of api.types) {
if (!mediaTypeApiMap.has(mediaType)) {
mediaTypeApiMap.set(mediaType, []);
}
mediaTypeApiMap.get(mediaType)!.push(api.apiName);
}
}
// Filter out media types with only one API
const filteredMediaTypes = Array.from(mediaTypeApiMap.entries()).filter(([_, apis]) => apis.length > 1);
// Dynamically create settings based on the filtered media types and their APIs
for (const [mediaType, apis] of filteredMediaTypes) {
new Setting(containerEl).setName(`Select APIs for ${unCamelCase(mediaType)}`).setHeading();
for (const apiName of apis) {
const api = this.plugin.apiManager.apis.find(api => api.apiName === apiName);
if (api) {
const disabledMediaTypes = api.getDisabledMediaTypes();
new Setting(containerEl)
.setName(apiName)
.setDesc(`Use ${apiName} API for ${unCamelCase(mediaType)}.`)
.addToggle(cb => {
cb.setValue(!disabledMediaTypes.includes(mediaType)).onChange(data => {
if (data) {
const index = disabledMediaTypes.indexOf(mediaType);
if (index > -1) {
disabledMediaTypes.splice(index, 1);
}
} else {
disabledMediaTypes.push(mediaType);
}
void this.plugin.saveSettings();
});
});
}
}
}
new Setting(containerEl).setName('New file location').setHeading();
// region new file location
new Setting(containerEl)

View file

@ -505,12 +505,12 @@ export class ModalHelper {
console.warn(previewModalResult.error);
new Notice(previewModalResult.error.toString());
previewModal.close();
return true;
return false;
}
if (previewModalResult.code === ModalResultCode.CLOSE) {
// modal is already being closed
return true;
return false;
}
try {

View file

@ -234,3 +234,19 @@ export async function useTemplaterPluginInFile(app: App, file: TFile): Promise<v
export type ModelToData<T> = {
[K in keyof T as T[K] extends Function ? never : K]?: T[K];
};
// Checks if a given URL points to an existing image (status 200), or returns false for 404/other errors.
export async function imageUrlExists(url: string): Promise<boolean> {
try {
// @ts-ignore
const response = await requestUrl({
url,
method: 'HEAD',
throw: false,
});
return response.status === 200;
} catch {
return false;
}
}