obsidian-media-db-sync/packages/obsidian/src/main.ts

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);
}
}