250 lines
10 KiB
TypeScript
250 lines
10 KiB
TypeScript
import 'packages/obsidian/src/styles.css';
|
|
import { Notice, Plugin, TFolder } from 'obsidian';
|
|
import { APIManager } from 'packages/obsidian/src/api/APIManager';
|
|
import { BoardGameGeekAPI } from 'packages/obsidian/src/api/apis/BoardGameGeekAPI';
|
|
import { ComicVineAPI } from 'packages/obsidian/src/api/apis/ComicVineAPI';
|
|
import { IGDBAPI } from 'packages/obsidian/src/api/apis/IGDBAPI';
|
|
import { MALAPI } from 'packages/obsidian/src/api/apis/MALAPI';
|
|
import { MALAPIManga } from 'packages/obsidian/src/api/apis/MALAPIManga';
|
|
import { MusicBrainzAPI } from 'packages/obsidian/src/api/apis/MusicBrainzAPI';
|
|
import { OMDbAPI } from 'packages/obsidian/src/api/apis/OMDbAPI';
|
|
import { OpenLibraryAPI } from 'packages/obsidian/src/api/apis/OpenLibraryAPI';
|
|
import { RAWGAPI } from 'packages/obsidian/src/api/apis/RAWGAPI';
|
|
import { SteamAPI } from 'packages/obsidian/src/api/apis/SteamAPI';
|
|
import { TMDBMovieAPI } from 'packages/obsidian/src/api/apis/TMDBMovieAPI';
|
|
import { TMDBSeasonAPI } from 'packages/obsidian/src/api/apis/TMDBSeasonAPI';
|
|
import { TMDBSeriesAPI } from 'packages/obsidian/src/api/apis/TMDBSeriesAPI';
|
|
import { VNDBAPI } from 'packages/obsidian/src/api/apis/VNDBAPI';
|
|
import { WikipediaAPI } from 'packages/obsidian/src/api/apis/WikipediaAPI';
|
|
import type { LegacyApiKeyEntry } from 'packages/obsidian/src/modals/LegacyApiKeysModal';
|
|
import { LegacyApiKeysModal } from 'packages/obsidian/src/modals/LegacyApiKeysModal';
|
|
import { PropertyMapper } from 'packages/obsidian/src/settings/PropertyMapper';
|
|
import { PropertyMappingModel } from 'packages/obsidian/src/settings/PropertyMapping';
|
|
import type { MediaDbPluginSettings } from 'packages/obsidian/src/settings/Settings';
|
|
import { LEGACY_API_KEY_SETTINGS, MediaDbSettingTab } from 'packages/obsidian/src/settings/Settings';
|
|
import { getDefaultSettings } from 'packages/obsidian/src/settings/Settings';
|
|
import { BulkImportHelper } from 'packages/obsidian/src/utils/BulkImportHelper';
|
|
import { DateFormatter } from 'packages/obsidian/src/utils/DateFormatter';
|
|
import { ErrorReporter } from 'packages/obsidian/src/utils/ErrorReporter';
|
|
import { MediaDbEntryHelper } from 'packages/obsidian/src/utils/MediaDbEntryHelper';
|
|
import { MediaDbFileHelper } from 'packages/obsidian/src/utils/MediaDbFileHelper';
|
|
import { MediaTypeManager } from 'packages/obsidian/src/utils/MediaTypeManager';
|
|
import { MEDIA_TYPES } from 'packages/obsidian/src/utils/MediaTypeManager';
|
|
import { ModalHelper } from 'packages/obsidian/src/utils/ModalHelper';
|
|
import { unCamelCase } from 'packages/obsidian/src/utils/Utils';
|
|
import { WatchlistController } from 'packages/obsidian/src/watchlist/WatchlistController';
|
|
|
|
export default class MediaDbPlugin extends Plugin {
|
|
declare settings: MediaDbPluginSettings;
|
|
apiManager!: APIManager;
|
|
mediaTypeManager!: MediaTypeManager;
|
|
modelPropertyMapper!: PropertyMapper;
|
|
modalHelper!: ModalHelper;
|
|
fileHelper!: MediaDbFileHelper;
|
|
entryHelper!: MediaDbEntryHelper;
|
|
bulkImportHelper!: BulkImportHelper;
|
|
dateFormatter!: DateFormatter;
|
|
errorReporter!: ErrorReporter;
|
|
watchlist!: WatchlistController;
|
|
|
|
async onload(): Promise<void> {
|
|
this.mediaTypeManager = new MediaTypeManager();
|
|
this.modelPropertyMapper = new PropertyMapper(this);
|
|
this.errorReporter = new ErrorReporter();
|
|
this.modalHelper = new ModalHelper(this);
|
|
this.fileHelper = new MediaDbFileHelper(this);
|
|
this.entryHelper = new MediaDbEntryHelper(this);
|
|
this.bulkImportHelper = new BulkImportHelper(this);
|
|
this.dateFormatter = new DateFormatter();
|
|
|
|
await this.loadSettings();
|
|
this.registerDefaultApis();
|
|
this.addSettingTab(new MediaDbSettingTab(this.app, this));
|
|
this.registerRibbonAndFileMenu();
|
|
this.registerCommands();
|
|
|
|
this.watchlist = new WatchlistController(this);
|
|
// catch-up shortly after startup (let vault index settle), then hourly due-check
|
|
const catchUpTimeout = window.setTimeout(() => void this.watchlist.maybeCatchUp(), 30_000);
|
|
this.register(() => window.clearTimeout(catchUpTimeout));
|
|
this.registerInterval(window.setInterval(() => void this.watchlist.maybeCatchUp(), 3600_000));
|
|
}
|
|
|
|
onunload(): void {}
|
|
|
|
private registerDefaultApis(): void {
|
|
this.apiManager = new APIManager();
|
|
this.apiManager.registerAPI(new OMDbAPI(this));
|
|
this.apiManager.registerAPI(new MALAPI(this));
|
|
this.apiManager.registerAPI(new MALAPIManga(this));
|
|
this.apiManager.registerAPI(new WikipediaAPI(this));
|
|
this.apiManager.registerAPI(new MusicBrainzAPI(this));
|
|
this.apiManager.registerAPI(new SteamAPI(this));
|
|
this.apiManager.registerAPI(new TMDBSeriesAPI(this));
|
|
this.apiManager.registerAPI(new TMDBSeasonAPI(this));
|
|
this.apiManager.registerAPI(new TMDBMovieAPI(this));
|
|
this.apiManager.registerAPI(new BoardGameGeekAPI(this));
|
|
this.apiManager.registerAPI(new OpenLibraryAPI(this));
|
|
this.apiManager.registerAPI(new ComicVineAPI(this));
|
|
this.apiManager.registerAPI(new IGDBAPI(this));
|
|
this.apiManager.registerAPI(new RAWGAPI(this));
|
|
this.apiManager.registerAPI(new VNDBAPI(this));
|
|
}
|
|
|
|
private registerRibbonAndFileMenu(): void {
|
|
const ribbonIconEl = this.addRibbonIcon('database', 'Add new Media DB entry', () => this.entryHelper.createEntryWithAdvancedSearchModal());
|
|
ribbonIconEl.addClass('media-db-sync-ribbon-class');
|
|
|
|
this.registerEvent(
|
|
this.app.workspace.on('file-menu', (menu, file) => {
|
|
if (file instanceof TFolder) {
|
|
menu.addItem(item => {
|
|
item.setTitle('Import folder as Media DB entries')
|
|
.setIcon('database')
|
|
.onClick(() => this.bulkImportHelper.import(file));
|
|
});
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
|
|
private registerCommands(): void {
|
|
this.addCommand({
|
|
id: 'open-media-db-search-modal',
|
|
name: 'Create entry',
|
|
callback: () => this.entryHelper.createEntryWithSearchModal(),
|
|
});
|
|
|
|
for (const mediaType of MEDIA_TYPES) {
|
|
this.addCommand({
|
|
id: `open-media-db-search-modal-with-${mediaType}`,
|
|
name: `Create entry (${unCamelCase(mediaType)})`,
|
|
callback: () => this.entryHelper.createEntryWithSearchModal({ preselectedTypes: [mediaType] }),
|
|
});
|
|
}
|
|
|
|
this.addCommand({
|
|
id: 'open-media-db-advanced-search-modal',
|
|
name: 'Create entry (advanced search)',
|
|
callback: () => this.entryHelper.createEntryWithAdvancedSearchModal(),
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'open-media-db-id-search-modal',
|
|
name: 'Create entry by id',
|
|
callback: () => this.entryHelper.createEntryWithIdSearchModal(),
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'update-media-db-note',
|
|
name: 'Update open note (this will recreate the note)',
|
|
checkCallback: (checking: boolean) => {
|
|
if (!this.app.workspace.getActiveFile()) {
|
|
return false;
|
|
}
|
|
if (!checking) {
|
|
void this.fileHelper.updateActiveNote(false);
|
|
}
|
|
return true;
|
|
},
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'update-media-db-note-metadata',
|
|
name: 'Update metadata',
|
|
checkCallback: (checking: boolean) => {
|
|
if (!this.app.workspace.getActiveFile()) {
|
|
return false;
|
|
}
|
|
if (!checking) {
|
|
void this.fileHelper.updateActiveNote(true);
|
|
}
|
|
return true;
|
|
},
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'add-media-db-link',
|
|
name: 'Insert link',
|
|
checkCallback: (checking: boolean) => {
|
|
if (!this.app.workspace.getActiveFile()) {
|
|
return false;
|
|
}
|
|
if (!checking) {
|
|
void this.entryHelper.createLinkWithSearchModal();
|
|
}
|
|
return true;
|
|
},
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'watchlist-sync-now',
|
|
name: 'Watchlist: sync now (airing/active only)',
|
|
callback: () => void this.watchlist.syncNow(false).catch((e: unknown) => new Notice(e instanceof Error ? e.message : String(e))),
|
|
});
|
|
this.addCommand({
|
|
id: 'watchlist-sync-full',
|
|
name: 'Watchlist: full sync (all entries)',
|
|
callback: () => void this.watchlist.syncNow(true).catch((e: unknown) => new Notice(e instanceof Error ? e.message : String(e))),
|
|
});
|
|
this.addCommand({
|
|
id: 'watchlist-sync-dry-run',
|
|
name: 'Watchlist: dry-run full sync (log only, no writes)',
|
|
callback: () => void this.watchlist.syncNow(true, true).catch((e: unknown) => new Notice(e instanceof Error ? e.message : String(e))),
|
|
});
|
|
this.addCommand({
|
|
id: 'watchlist-resolve-ids',
|
|
name: 'Watchlist: resolve missing TMDB ids',
|
|
callback: () => void this.watchlist.resolveMissingIds().catch((e: unknown) => new Notice(e instanceof Error ? e.message : String(e))),
|
|
});
|
|
}
|
|
|
|
private getLegacyApiKeyEntries(diskSettings: Record<string, unknown>): LegacyApiKeyEntry[] {
|
|
return LEGACY_API_KEY_SETTINGS.filter(key => typeof diskSettings[key] === 'string' && diskSettings[key].length > 0).map(key => ({
|
|
key,
|
|
value: diskSettings[key] as string,
|
|
}));
|
|
}
|
|
|
|
private removeLegacyApiKeys(settings: Record<string, unknown>): void {
|
|
for (const key of LEGACY_API_KEY_SETTINGS) {
|
|
delete settings[key];
|
|
}
|
|
}
|
|
|
|
async loadSettings(): Promise<void> {
|
|
const diskSettings = ((await this.loadData()) ?? {}) as Record<string, unknown>;
|
|
const defaultSettings: MediaDbPluginSettings = getDefaultSettings(this);
|
|
const loadedSettings = Object.assign({}, defaultSettings, diskSettings) as MediaDbPluginSettings;
|
|
|
|
const migratedModels = PropertyMappingModel.migrateModels(
|
|
loadedSettings.propertyMappingModels || [],
|
|
defaultSettings.propertyMappingModels.map(m => PropertyMappingModel.fromJSON(m)),
|
|
);
|
|
loadedSettings.propertyMappingModels = migratedModels.map(m => m.toJSON());
|
|
this.settings = loadedSettings;
|
|
this.mediaTypeManager.updateTemplates(this.settings);
|
|
this.mediaTypeManager.updateFolders(this.settings);
|
|
this.dateFormatter.setFormat(this.settings.customDateFormat);
|
|
|
|
const legacyEntries = this.getLegacyApiKeyEntries(diskSettings);
|
|
if (legacyEntries.length > 0) {
|
|
this.app.workspace.onLayoutReady((): void => {
|
|
window.setTimeout((): void => {
|
|
new LegacyApiKeysModal(this.app, legacyEntries, (): void => {
|
|
this.removeLegacyApiKeys(this.settings as unknown as Record<string, unknown>);
|
|
void this.saveSettings();
|
|
}).open();
|
|
}, 0);
|
|
});
|
|
}
|
|
}
|
|
|
|
async saveSettings(): Promise<void> {
|
|
this.mediaTypeManager.updateTemplates(this.settings);
|
|
this.mediaTypeManager.updateFolders(this.settings);
|
|
this.dateFormatter.setFormat(this.settings.customDateFormat);
|
|
|
|
await this.saveData(this.settings);
|
|
}
|
|
}
|