Implement VNDB API integration
This commit is contained in:
parent
265bf10426
commit
5e5407f8de
2 changed files with 197 additions and 0 deletions
195
src/api/apis/VNDBAPI.ts
Normal file
195
src/api/apis/VNDBAPI.ts
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||||
|
import MediaDbPlugin from '../../main';
|
||||||
|
import { GameModel } from '../../models/GameModel';
|
||||||
|
import { requestUrl } from 'obsidian';
|
||||||
|
import { MediaType } from '../../utils/MediaType';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A partial `POST /vn` response payload; desired fields should be listed in the request body.
|
||||||
|
*/
|
||||||
|
interface VNJSONResponse {
|
||||||
|
more: boolean;
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
titles: [
|
||||||
|
{
|
||||||
|
title: string;
|
||||||
|
lang: string;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
devstatus: 0 | 1 | 2; // Released | In-development | Cancelled
|
||||||
|
released: string | 'TBA' | null;
|
||||||
|
image: {
|
||||||
|
url: string;
|
||||||
|
} | null;
|
||||||
|
rating: number | null;
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
category: 'cont' | 'ero' | 'tech';
|
||||||
|
rating: number;
|
||||||
|
spoiler: 0 | 1 | 2; // None | Minor | Major
|
||||||
|
},
|
||||||
|
];
|
||||||
|
developers: [
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A partial `POST /release` response payload; desired fields should be listed in the request body.
|
||||||
|
*/
|
||||||
|
interface ReleaseJSONResponse {
|
||||||
|
more: boolean;
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
producers: [
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
developer: boolean;
|
||||||
|
publisher: boolean;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VNDBAPI extends APIModel {
|
||||||
|
plugin: MediaDbPlugin;
|
||||||
|
apiDateFormat: string = 'YYYY-MM-DD'; // Can also return YYYY-MM or YYYY
|
||||||
|
|
||||||
|
constructor(plugin: MediaDbPlugin) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.apiName = 'VNDB API';
|
||||||
|
this.apiDescription = 'A free API for visual novels.';
|
||||||
|
this.apiUrl = 'https://api.vndb.org/kana';
|
||||||
|
this.types = [MediaType.Game];
|
||||||
|
}
|
||||||
|
|
||||||
|
postVNQuery = (body: string): Promise<VNJSONResponse> => this.postQuery('/vn', body);
|
||||||
|
postReleaseQuery = (body: string): Promise<ReleaseJSONResponse> => this.postQuery('/release', body);
|
||||||
|
async postQuery(endpoint: string, body: string): Promise<any> {
|
||||||
|
const fetchData = await requestUrl({
|
||||||
|
url: `${this.apiUrl}${endpoint}`,
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: body,
|
||||||
|
throw: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fetchData.status !== 200) {
|
||||||
|
switch (fetchData.status) {
|
||||||
|
case 400:
|
||||||
|
throw Error(`MDB | Invalid request body or query [${fetchData.text}].`);
|
||||||
|
case 404:
|
||||||
|
throw Error(`MDB | Invalid API path or HTTP method.`);
|
||||||
|
case 429:
|
||||||
|
throw Error(`MDB | Throttled.`);
|
||||||
|
case 500:
|
||||||
|
throw Error(`MDB | VNDB server error.`);
|
||||||
|
case 502:
|
||||||
|
throw Error(`MDB | VNDB server is down.`);
|
||||||
|
default:
|
||||||
|
throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchData.json;
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
|
||||||
|
console.log(`MDB | api "${this.apiName}" queried by Title`);
|
||||||
|
|
||||||
|
const vnData = await this.postVNQuery(`{
|
||||||
|
"filters": ["search", "=", "${title}"],
|
||||||
|
"fields": "title, titles{title, lang}, released",
|
||||||
|
"sort": "searchrank",
|
||||||
|
"results": 20
|
||||||
|
}`);
|
||||||
|
|
||||||
|
const ret: MediaTypeModel[] = [];
|
||||||
|
for (const vn of vnData.results) {
|
||||||
|
ret.push(
|
||||||
|
new GameModel({
|
||||||
|
type: MediaType.Game,
|
||||||
|
title: vn.title,
|
||||||
|
englishTitle: vn.titles.find(t => t.lang === 'en')?.title ?? vn.title,
|
||||||
|
year: vn.released && vn.released !== 'TBA' ? new Date(vn.released).getFullYear().toString() : 'TBA',
|
||||||
|
dataSource: this.apiName,
|
||||||
|
id: vn.id,
|
||||||
|
} as GameModel),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string): Promise<MediaTypeModel> {
|
||||||
|
console.log(`MDB | api "${this.apiName}" queried by ID`);
|
||||||
|
|
||||||
|
const vnData = await this.postVNQuery(`{
|
||||||
|
"filters": ["id", "=", "${id}"],
|
||||||
|
"fields": "title, titles{title, lang}, devstatus, released, image{url}, rating, tags{name, category, rating, spoiler}, developers{name}"
|
||||||
|
}`);
|
||||||
|
|
||||||
|
if (vnData.results.length !== 1) throw Error(`MDB | Expected 1 result from query, got ${vnData.results.length}.`);
|
||||||
|
const vn = vnData.results[0];
|
||||||
|
|
||||||
|
const releaseData = await this.postReleaseQuery(`{
|
||||||
|
"filters": ["and",
|
||||||
|
["vn", "=",
|
||||||
|
["id", "=", "${id}"]
|
||||||
|
],
|
||||||
|
["official", "=", 1],
|
||||||
|
["patch", "!=", 1]
|
||||||
|
],
|
||||||
|
"fields": "producers.name, producers.publisher, producers.developer",
|
||||||
|
"results": 100
|
||||||
|
}`);
|
||||||
|
|
||||||
|
return new GameModel({
|
||||||
|
type: MediaType.Game,
|
||||||
|
title: vn.title,
|
||||||
|
englishTitle: vn.titles.find(t => t.lang === 'en')?.title ?? vn.title,
|
||||||
|
year: vn.released && vn.released !== 'TBA' ? new Date(vn.released).getFullYear().toString() : 'TBA',
|
||||||
|
dataSource: this.apiName,
|
||||||
|
url: `https://vndb.org/${vn.id}`,
|
||||||
|
id: vn.id,
|
||||||
|
|
||||||
|
developers: vn.developers.map(d => d.name),
|
||||||
|
publishers: releaseData.results
|
||||||
|
.flatMap(r => r.producers)
|
||||||
|
.filter(p => p.publisher)
|
||||||
|
.sort((p1, p2) => Number(p2.developer) - Number(p1.developer)) // Place developer-publishers first in publisher list
|
||||||
|
.map(p => p.name)
|
||||||
|
.unique(),
|
||||||
|
genres: vn.tags
|
||||||
|
.filter(t => t.category === 'cont' && t.spoiler === 0 && t.rating >= 2)
|
||||||
|
.sort((t1, t2) => t2.rating - t1.rating)
|
||||||
|
.map(t => t.name),
|
||||||
|
onlineRating: vn.rating ?? NaN,
|
||||||
|
image: vn.image?.url,
|
||||||
|
|
||||||
|
released: vn.devstatus === 0,
|
||||||
|
releaseDate: this.plugin.dateFormatter.format(vn.released, this.apiDateFormat),
|
||||||
|
|
||||||
|
userData: {
|
||||||
|
played: false,
|
||||||
|
personalRating: 0,
|
||||||
|
},
|
||||||
|
} as GameModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import { SteamAPI } from './api/apis/SteamAPI';
|
||||||
import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI';
|
import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI';
|
||||||
import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI';
|
import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI';
|
||||||
import { MobyGamesAPI } from './api/apis/MobyGamesAPI';
|
import { MobyGamesAPI } from './api/apis/MobyGamesAPI';
|
||||||
|
import { VNDBAPI } from './api/apis/VNDBAPI';
|
||||||
import { PropertyMapper } from './settings/PropertyMapper';
|
import { PropertyMapper } from './settings/PropertyMapper';
|
||||||
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal';
|
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal';
|
||||||
import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping';
|
import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping';
|
||||||
|
|
@ -58,6 +59,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
this.apiManager.registerAPI(new BoardGameGeekAPI(this));
|
this.apiManager.registerAPI(new BoardGameGeekAPI(this));
|
||||||
this.apiManager.registerAPI(new OpenLibraryAPI(this));
|
this.apiManager.registerAPI(new OpenLibraryAPI(this));
|
||||||
this.apiManager.registerAPI(new MobyGamesAPI(this));
|
this.apiManager.registerAPI(new MobyGamesAPI(this));
|
||||||
|
this.apiManager.registerAPI(new VNDBAPI(this));
|
||||||
// this.apiManager.registerAPI(new LocGovAPI(this)); // TODO: parse data
|
// this.apiManager.registerAPI(new LocGovAPI(this)); // TODO: parse data
|
||||||
|
|
||||||
this.mediaTypeManager = new MediaTypeManager();
|
this.mediaTypeManager = new MediaTypeManager();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue