Merge branch 'master' into feat/vndb

This commit is contained in:
Senyksia 2025-02-20 01:36:07 +11:00 committed by GitHub
commit 98619fb75e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 927 additions and 724 deletions

View file

@ -1,5 +1,6 @@
import { APIModel } from './APIModel';
import { MediaTypeModel } from '../models/MediaTypeModel';
import { Notice } from 'obsidian';
import type { MediaTypeModel } from '../models/MediaTypeModel';
import type { APIModel } from './APIModel';
export class APIManager {
apis: APIModel[];
@ -23,7 +24,10 @@ export class APIManager {
try {
return await api.searchByTitle(query);
} catch (e) {
new Notice(`Error querying ${api.apiName}: ${e}`);
console.warn(e);
return [];
}
});
@ -35,7 +39,7 @@ export class APIManager {
*
* @param item
*/
async queryDetailedInfo(item: MediaTypeModel): Promise<MediaTypeModel> {
async queryDetailedInfo(item: MediaTypeModel): Promise<MediaTypeModel | undefined> {
return await this.queryDetailedInfoById(item.id, item.dataSource);
}
@ -45,22 +49,31 @@ export class APIManager {
* @param id
* @param apiName
*/
async queryDetailedInfoById(id: string, apiName: string): Promise<MediaTypeModel> {
async queryDetailedInfoById(id: string, apiName: string): Promise<MediaTypeModel | undefined> {
for (const api of this.apis) {
if (api.apiName === apiName) {
return api.getById(id);
try {
return api.getById(id);
} catch (e) {
new Notice(`Error querying ${api.apiName}: ${e}`);
console.warn(e);
return undefined;
}
}
}
return undefined;
}
getApiByName(name: string): APIModel {
getApiByName(name: string): APIModel | undefined {
for (const api of this.apis) {
if (api.apiName === name) {
return api;
}
}
return null;
return undefined;
}
registerAPI(api: APIModel): void {

View file

@ -1,13 +1,13 @@
import { MediaTypeModel } from '../models/MediaTypeModel';
import { MediaType } from '../utils/MediaType';
import MediaDbPlugin from '../main';
import type MediaDbPlugin from '../main';
import type { MediaTypeModel } from '../models/MediaTypeModel';
import type { MediaType } from '../utils/MediaType';
export abstract class APIModel {
apiName: string;
apiUrl: string;
apiDescription: string;
types: MediaType[];
plugin: MediaDbPlugin;
apiName!: string;
apiUrl!: string;
apiDescription!: string;
types!: MediaType[];
plugin!: MediaDbPlugin;
/**
* This function should query the api and return a list of matches. The matches should be caped at 20.

View file

@ -1,9 +1,9 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { BoardGameModel } from 'src/models/BoardGameModel';
import { requestUrl } from 'obsidian';
import { BoardGameModel } from 'src/models/BoardGameModel';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class BoardGameGeekAPI extends APIModel {
plugin: MediaDbPlugin;
@ -38,8 +38,8 @@ export class BoardGameGeekAPI extends APIModel {
const ret: MediaTypeModel[] = [];
for (const boardgame of Array.from(response.querySelectorAll('boardgame'))) {
const id = boardgame.attributes.getNamedItem('objectid')!.value;
const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')!.textContent!;
const id = boardgame.attributes.getNamedItem('objectid')?.value;
const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')?.textContent ?? undefined;
const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
ret.push(
@ -49,7 +49,7 @@ export class BoardGameGeekAPI extends APIModel {
title,
englishTitle: title,
year,
} as BoardGameModel),
}),
);
}
@ -72,21 +72,29 @@ export class BoardGameGeekAPI extends APIModel {
const response = new window.DOMParser().parseFromString(data, 'text/xml');
// console.debug(response);
const boardgame = response.querySelector('boardgame')!;
const title = boardgame.querySelector('name[primary=true]')!.textContent!;
const boardgame = response.querySelector('boardgame');
if (!boardgame) {
throw Error(`MDB | Received invalid data from ${this.apiName}.`);
}
const title = boardgame.querySelector('name[primary=true]')?.textContent;
const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
const image = boardgame.querySelector('image')?.textContent ?? undefined;
const onlineRating = Number.parseFloat(boardgame.querySelector('statistics ratings average')?.textContent ?? '0');
const genres = Array.from(boardgame.querySelectorAll('boardgamecategory')).map(n => n!.textContent!);
const genres = Array.from(boardgame.querySelectorAll('boardgamecategory'))
.map(n => n.textContent)
.filter(n => n !== null);
const complexityRating = Number.parseFloat(boardgame.querySelector('averageweight')?.textContent ?? '0');
const minPlayers = Number.parseFloat(boardgame.querySelector('minplayers')?.textContent ?? '0');
const maxPlayers = Number.parseFloat(boardgame.querySelector('maxplayers')?.textContent ?? '0');
const playtime = (boardgame.querySelector('playingtime')?.textContent ?? 'unknown') + ' minutes';
const publishers = Array.from(boardgame.querySelectorAll('boardgamepublisher')).map(n => n!.textContent!);
const publishers = Array.from(boardgame.querySelectorAll('boardgamepublisher'))
.map(n => n.textContent)
.filter(n => n !== null);
return new BoardGameModel({
title: title,
englishTitle: title,
title: title ?? undefined,
englishTitle: title ?? undefined,
year: year === '0' ? '' : year,
dataSource: this.apiName,
url: `https://boardgamegeek.com/boardgame/${id}`,
@ -107,6 +115,6 @@ export class BoardGameGeekAPI extends APIModel {
played: false,
personalRating: 0,
},
} as BoardGameModel);
});
}
}

View file

@ -0,0 +1,109 @@
import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class GiantBombAPI extends APIModel {
plugin: MediaDbPlugin;
apiDateFormat: string = 'YYYY-MM-DD';
constructor(plugin: MediaDbPlugin) {
super();
this.plugin = plugin;
this.apiName = 'GiantBombAPI';
this.apiDescription = 'A free API for games.';
this.apiUrl = 'https://www.giantbomb.com/api';
this.types = [MediaType.Game];
}
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
console.log(`MDB | api "${this.apiName}" queried by Title`);
if (!this.plugin.settings.GiantBombKey) {
throw Error(`MDB | API key for ${this.apiName} missing.`);
}
const searchUrl = `${this.apiUrl}/games?api_key=${this.plugin.settings.GiantBombKey}&filter=name:${encodeURIComponent(title)}&format=json`;
const fetchData = await requestUrl({
url: searchUrl,
});
// console.debug(fetchData);
if (fetchData.status === 401) {
throw Error(`MDB | Authentication for ${this.apiName} failed. Check the API key.`);
}
if (fetchData.status === 429) {
throw Error(`MDB | Too many requests for ${this.apiName}, you've exceeded your API quota.`);
}
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) {
ret.push(
new GameModel({
type: MediaType.Game,
title: result.name,
englishTitle: result.name,
year: new Date(result.original_release_date).getFullYear().toString(),
dataSource: this.apiName,
id: result.guid,
}),
);
}
return ret;
}
async getById(id: string): Promise<MediaTypeModel> {
console.log(`MDB | api "${this.apiName}" queried by ID`);
if (!this.plugin.settings.GiantBombKey) {
throw Error(`MDB | API key for ${this.apiName} missing.`);
}
const searchUrl = `${this.apiUrl}/game/${encodeURIComponent(id)}/?api_key=${this.plugin.settings.GiantBombKey}&format=json`;
const fetchData = await requestUrl({
url: 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 result = data.results;
return new GameModel({
type: MediaType.Game,
title: result.name,
englishTitle: result.name,
year: new Date(result.original_release_date).getFullYear().toString(),
dataSource: this.apiName,
url: result.site_detail_url,
id: result.guid,
developers: result.developers?.map((x: any) => x.name) ?? [],
publishers: result.publishers?.map((x: any) => x.name) ?? [],
genres: result.genres?.map((x: any) => x.name) ?? [],
onlineRating: 0,
image: result.image?.super_url ?? '',
released: true,
releaseDate: result.original_release_date ?? 'unknown',
userData: {
played: false,
personalRating: 0,
},
});
}
}

View file

@ -1,9 +1,9 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MovieModel } from '../../models/MovieModel';
import MediaDbPlugin from '../../main';
import { SeriesModel } from '../../models/SeriesModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class MALAPI extends APIModel {
plugin: MediaDbPlugin;
@ -52,7 +52,7 @@ export class MALAPI extends APIModel {
year: result.year ?? result.aired?.prop?.from?.year ?? '',
dataSource: this.apiName,
id: result.mal_id,
} as MovieModel),
}),
);
}
if (type === 'movie' || type === 'special') {
@ -64,7 +64,7 @@ export class MALAPI extends APIModel {
year: result.year ?? result.aired?.prop?.from?.year ?? '',
dataSource: this.apiName,
id: result.mal_id,
} as MovieModel),
}),
);
} else if (type === 'series' || type === 'ova') {
ret.push(
@ -75,7 +75,7 @@ export class MALAPI extends APIModel {
year: result.year ?? result.aired?.prop?.from?.year ?? '',
dataSource: this.apiName,
id: result.mal_id,
} as SeriesModel),
}),
);
}
}
@ -127,7 +127,7 @@ export class MALAPI extends APIModel {
lastWatched: '',
personalRating: 0,
},
} as MovieModel);
});
}
if (type === 'movie' || type === 'special') {
@ -159,7 +159,7 @@ export class MALAPI extends APIModel {
lastWatched: '',
personalRating: 0,
},
} as MovieModel);
});
} else if (type === 'series' || type === 'ova') {
return new SeriesModel({
subType: type,
@ -190,9 +190,9 @@ export class MALAPI extends APIModel {
lastWatched: '',
personalRating: 0,
},
} as SeriesModel);
});
}
return;
throw new Error(`MDB | Unknown media type for id ${id}`);
}
}

View file

@ -1,8 +1,8 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import type MediaDbPlugin from '../../main';
import { MangaModel } from '../../models/MangaModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class MALAPIManga extends APIModel {
plugin: MediaDbPlugin;
@ -73,7 +73,7 @@ export class MALAPIManga extends APIModel {
lastWatched: '',
personalRating: 0,
},
} as MangaModel),
}),
);
}
@ -123,6 +123,6 @@ export class MALAPIManga extends APIModel {
lastWatched: '',
personalRating: 0,
},
} as MangaModel);
});
}
}

View file

@ -1,9 +1,10 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import { Notice } from 'obsidian';
import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class MobyGamesAPI extends APIModel {
plugin: MediaDbPlugin;
@ -22,7 +23,7 @@ export class MobyGamesAPI extends APIModel {
console.log(`MDB | api "${this.apiName}" queried by Title`);
if (!this.plugin.settings.MobyGamesKey) {
throw Error(`MDB | API key for ${this.apiName} missing.`);
throw new Error(`MDB | API key for ${this.apiName} missing.`);
}
const searchUrl = `${this.apiUrl}/games?title=${encodeURIComponent(title)}&api_key=${this.plugin.settings.MobyGamesKey}`;
@ -104,6 +105,6 @@ export class MobyGamesAPI extends APIModel {
personalRating: 0,
},
} as GameModel);
});
}
}

View file

@ -1,10 +1,10 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MusicReleaseModel } from '../../models/MusicReleaseModel';
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
import { MediaType } from '../../utils/MediaType';
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
import { APIModel } from '../APIModel';
export class MusicBrainzAPI extends APIModel {
plugin: MediaDbPlugin;
@ -55,7 +55,7 @@ export class MusicBrainzAPI extends APIModel {
artists: result['artist-credit'].map((a: any) => a.name),
subType: result['primary-type'],
} as MusicReleaseModel),
}),
);
}
@ -97,6 +97,6 @@ export class MusicBrainzAPI extends APIModel {
userData: {
personalRating: 0,
},
} as MusicReleaseModel);
});
}
}

View file

@ -1,10 +1,11 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import { MovieModel } from '../../models/MovieModel';
import MediaDbPlugin from '../../main';
import { SeriesModel } from '../../models/SeriesModel';
import { Notice } from 'obsidian';
import type MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MovieModel } from '../../models/MovieModel';
import { SeriesModel } from '../../models/SeriesModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class OMDbAPI extends APIModel {
plugin: MediaDbPlugin;
@ -29,7 +30,7 @@ export class OMDbAPI extends APIModel {
console.log(`MDB | api "${this.apiName}" queried by Title`);
if (!this.plugin.settings.OMDbKey) {
throw Error(`MDB | API key for ${this.apiName} missing.`);
throw new Error(`MDB | API key for ${this.apiName} missing.`);
}
const searchUrl = `https://www.omdbapi.com/?s=${encodeURIComponent(title)}&apikey=${this.plugin.settings.OMDbKey}`;
@ -73,7 +74,7 @@ export class OMDbAPI extends APIModel {
year: result.Year,
dataSource: this.apiName,
id: result.imdbID,
} as MovieModel),
}),
);
} else if (type === 'series') {
ret.push(
@ -84,7 +85,7 @@ export class OMDbAPI extends APIModel {
year: result.Year,
dataSource: this.apiName,
id: result.imdbID,
} as SeriesModel),
}),
);
} else if (type === 'game') {
ret.push(
@ -95,7 +96,7 @@ export class OMDbAPI extends APIModel {
year: result.Year,
dataSource: this.apiName,
id: result.imdbID,
} as GameModel),
}),
);
}
}
@ -161,7 +162,7 @@ export class OMDbAPI extends APIModel {
lastWatched: '',
personalRating: 0,
},
} as MovieModel);
});
} else if (type === 'series') {
return new SeriesModel({
type: type,
@ -193,7 +194,7 @@ export class OMDbAPI extends APIModel {
lastWatched: '',
personalRating: 0,
},
} as SeriesModel);
});
} else if (type === 'game') {
return new GameModel({
type: type,
@ -217,9 +218,9 @@ export class OMDbAPI extends APIModel {
played: false,
personalRating: 0,
},
} as GameModel);
});
}
return;
throw new Error(`MDB | Unknown media type for id ${id}`);
}
}

View file

@ -1,8 +1,8 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { BookModel } from 'src/models/BookModel';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class OpenLibraryAPI extends APIModel {
plugin: MediaDbPlugin;
@ -42,7 +42,7 @@ export class OpenLibraryAPI extends APIModel {
dataSource: this.apiName,
id: result.key,
author: result.author_name ?? 'unknown',
} as BookModel),
}),
);
}
@ -87,6 +87,6 @@ export class OpenLibraryAPI extends APIModel {
lastRead: '',
personalRating: 0,
},
} as BookModel);
});
}
}

View file

@ -1,9 +1,9 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class SteamAPI extends APIModel {
plugin: MediaDbPlugin;
@ -49,7 +49,7 @@ export class SteamAPI extends APIModel {
year: '',
dataSource: this.apiName,
id: result.appid,
} as GameModel),
}),
);
}
@ -94,19 +94,19 @@ export class SteamAPI extends APIModel {
url: `https://store.steampowered.com/app/${result.steam_appid}`,
id: result.steam_appid,
developers: result['developers'],
publishers: result['publishers'],
developers: result.developers,
publishers: result.publishers,
genres: result.genres?.map((x: any) => x.description) ?? [],
onlineRating: Number.parseFloat(result.metacritic?.score ?? 0),
image: result.header_image ?? '',
released: !result.release_date?.comming_soon,
released: !result.release_date?.coming_soon,
releaseDate: this.plugin.dateFormatter.format(result.release_date?.date, this.apiDateFormat) ?? 'unknown',
userData: {
played: false,
personalRating: 0,
},
} as GameModel);
});
}
}

View file

@ -1,8 +1,8 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { WikiModel } from '../../models/WikiModel';
import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class WikipediaAPI extends APIModel {
plugin: MediaDbPlugin;
@ -42,7 +42,7 @@ export class WikipediaAPI extends APIModel {
year: '',
dataSource: this.apiName,
id: result.pageid,
} as WikiModel),
}),
);
}
@ -73,10 +73,10 @@ export class WikipediaAPI extends APIModel {
id: result.pageid,
wikiUrl: result.fullurl,
lastUpdated: this.plugin.dateFormatter.format(result.touched, this.apiDateFormat),
lastUpdated: this.plugin.dateFormatter.format(result.touched, this.apiDateFormat) ?? undefined,
length: result.length,
userData: {},
} as WikiModel);
});
}
}