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 { OpenLibraryAPI } from './api/apis/OpenLibraryAPI';
|
||||
import { MobyGamesAPI } from './api/apis/MobyGamesAPI';
|
||||
import { VNDBAPI } from './api/apis/VNDBAPI';
|
||||
import { PropertyMapper } from './settings/PropertyMapper';
|
||||
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal';
|
||||
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 OpenLibraryAPI(this));
|
||||
this.apiManager.registerAPI(new MobyGamesAPI(this));
|
||||
this.apiManager.registerAPI(new VNDBAPI(this));
|
||||
// this.apiManager.registerAPI(new LocGovAPI(this)); // TODO: parse data
|
||||
|
||||
this.mediaTypeManager = new MediaTypeManager();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue