diff --git a/packages/obsidian/src/main.ts b/packages/obsidian/src/main.ts index 48401d76..13c11273 100644 --- a/packages/obsidian/src/main.ts +++ b/packages/obsidian/src/main.ts @@ -16,6 +16,8 @@ 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'; @@ -168,35 +170,42 @@ export default class MediaDbPlugin extends Plugin { }); } + private getLegacyApiKeyEntries(diskSettings: Record): LegacyApiKeyEntry[] { + return this.settings.LegacyApiKeys.filter(key => typeof diskSettings[key] === 'string' && diskSettings[key].length > 0).map(key => ({ + key, + value: diskSettings[key] as string, + })); + } + + private removeLegacyApiKeys(settings: Record): void { + for (const key of this.settings.LegacyApiKeys) { + delete settings[key]; + } + } + async loadSettings(): Promise { - const diskSettings: MediaDbPluginSettings = (await this.loadData()) as MediaDbPluginSettings; + const diskSettings = (await this.loadData()) as Record; const defaultSettings: MediaDbPluginSettings = getDefaultSettings(this); - const loadedSettings: MediaDbPluginSettings = Object.assign({}, defaultSettings, diskSettings); - - // delete old api keys - // @ts-ignore - delete loadedSettings.BoardgameGeekKey; - // @ts-ignore - delete loadedSettings.ComicVineKey; - // @ts-ignore - delete loadedSettings.GiantBombKey; - // @ts-ignore - delete loadedSettings.MobyGamesKey; - // @ts-ignore - delete loadedSettings.OMDbKey; - // @ts-ignore - delete loadedSettings.TMDBKey; + 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; - await this.saveSettings(); + 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); + void this.saveSettings(); + }).open(); + }, 0); + }); + } } async saveSettings(): Promise { diff --git a/packages/obsidian/src/modals/LegacyApiKeysModal.ts b/packages/obsidian/src/modals/LegacyApiKeysModal.ts new file mode 100644 index 00000000..144e48e7 --- /dev/null +++ b/packages/obsidian/src/modals/LegacyApiKeysModal.ts @@ -0,0 +1,93 @@ +import type { App } from 'obsidian'; +import { Modal, Setting, Notice } from 'obsidian'; + +export interface LegacyApiKeyEntry { + key: string; + value: string; +} + +export class LegacyApiKeysModal extends Modal { + private entries: LegacyApiKeyEntry[]; + private onConfirm: () => void; + + constructor(app: App, entries: LegacyApiKeyEntry[], onConfirm: () => void) { + super(app); + this.entries = entries; + this.onConfirm = onConfirm; + } + + onOpen(): void { + const { contentEl } = this; + contentEl.addClass('media-db-plugin-legacy-keys-modal'); + + contentEl.createEl('h2', { text: 'Media DB plugin: Legacy API keys found' }); + + const wrapper = contentEl.createDiv({ cls: 'media-db-plugin-legacy-keys-wrapper' }); + + const intro = wrapper.createDiv({ cls: 'media-db-plugin-legacy-keys-intro' }); + intro.createEl('p', { + text: 'Media DB plugin has found old plaintext API keys in your settings. Copy them now if needed, they should be removed for safety.', + }); + + intro.createEl('p', { + text: 'The new keychain-backed system will be used instead. The plugin will not be usable until the old plaintext keys have been deleted.', + }); + + const textarea = wrapper.createEl('textarea', { + cls: 'media-db-plugin-legacy-keys-textarea', + attr: { + readonly: 'true', + spellcheck: 'false', + }, + }); + + textarea.value = this.entries.map(e => `${e.key}: ${e.value}`).join('\n'); + textarea.addClass('media-db-plugin-hidden'); + + contentEl.createDiv({ cls: 'media-db-plugin-spacer' }); + + const bottomSettingRow = new Setting(contentEl); + + bottomSettingRow.addButton(btn => { + btn.setButtonText('Copy'); + btn.onClick(async () => { + await navigator.clipboard.writeText(textarea.value); + new Notice('Legacy API keys copied to clipboard.'); + }); + btn.buttonEl.addClass('media-db-plugin-button'); + }); + + bottomSettingRow.addButton(btn => { + btn.setButtonText('Show keys'); + + btn.onClick(() => { + const isHidden = textarea.hasClass('media-db-plugin-hidden'); + if (isHidden) { + textarea.removeClass('media-db-plugin-hidden'); + btn.setButtonText('Hide keys'); + } else { + textarea.addClass('media-db-plugin-hidden'); + btn.setButtonText('Show keys'); + } + }); + + btn.buttonEl.addClass('media-db-plugin-button'); + }); + + bottomSettingRow.addButton(btn => { + btn.setButtonText('Delete plaintext keys'); + btn.setCta(); + btn.onClick(() => { + this.close(); + this.onConfirm(); + }); + btn.buttonEl.addClass('media-db-plugin-button'); + btn.buttonEl.addClass('mod-warning'); + btn.buttonEl.addClass('mod-danger'); + }); + } + + onClose(): void { + this.contentEl.empty(); + } +} diff --git a/packages/obsidian/src/settings/Settings.ts b/packages/obsidian/src/settings/Settings.ts index b0885f55..02bcb0a3 100644 --- a/packages/obsidian/src/settings/Settings.ts +++ b/packages/obsidian/src/settings/Settings.ts @@ -50,14 +50,16 @@ function createPropertyMappingsDescription(): DocumentFragment { export interface MediaDbPluginSettings { OMDbKeyId: string; TMDBKeyId: string; - MobyGamesKeyId: string; - GiantBombKeyId: string; + //MobyGamesKeyId: string; + //GiantBombKeyId: string; IGDBClientId: string; IGDBClientSecret: string; RAWGAPIKeyId: string; ComicVineKeyId: string; BoardgameGeekKeyId: string; + LegacyApiKeys: readonly ['OMDbKey', 'TMDBKey', 'MobyGamesKey', 'GiantBombKey', 'ComicVineKey', 'BoardgameGeekKey']; + sfwFilter: boolean; templates: boolean; customDateFormat: string; @@ -69,12 +71,12 @@ export interface MediaDbPluginSettings { BoardgameGeekAPI_disabledMediaTypes: MediaType[]; ComicVineAPI_disabledMediaTypes: MediaType[]; - GiantBombAPI_disabledMediaTypes: MediaType[]; + //GiantBombAPI_disabledMediaTypes: MediaType[]; IGDBAPI_disabledMediaTypes: MediaType[]; RAWGAPI_disabledMediaTypes: MediaType[]; MALAPI_disabledMediaTypes: MediaType[]; MALAPIManga_disabledMediaTypes: MediaType[]; - MobyGamesAPI_disabledMediaTypes: MediaType[]; + //MobyGamesAPI_disabledMediaTypes: MediaType[]; MusicBrainzAPI_disabledMediaTypes: MediaType[]; OMDbAPI_disabledMediaTypes: MediaType[]; OpenLibraryAPI_disabledMediaTypes: MediaType[]; @@ -309,14 +311,16 @@ class MediaTypeMappedSettings { const DEFAULT_SETTINGS: MediaDbPluginSettings = { OMDbKeyId: '', TMDBKeyId: '', - MobyGamesKeyId: '', - GiantBombKeyId: '', + //MobyGamesKeyId: '', + //GiantBombKeyId: '', IGDBClientId: '', IGDBClientSecret: '', RAWGAPIKeyId: '', ComicVineKeyId: '', BoardgameGeekKeyId: '', + LegacyApiKeys: ['OMDbKey', 'TMDBKey', 'MobyGamesKey', 'GiantBombKey', 'ComicVineKey', 'BoardgameGeekKey'], + sfwFilter: true, templates: true, customDateFormat: 'L', @@ -328,12 +332,12 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = { BoardgameGeekAPI_disabledMediaTypes: [], ComicVineAPI_disabledMediaTypes: [], - GiantBombAPI_disabledMediaTypes: [], + //GiantBombAPI_disabledMediaTypes: [], IGDBAPI_disabledMediaTypes: [], RAWGAPI_disabledMediaTypes: [], MALAPI_disabledMediaTypes: [], MALAPIManga_disabledMediaTypes: [], - MobyGamesAPI_disabledMediaTypes: [], + //MobyGamesAPI_disabledMediaTypes: [], MusicBrainzAPI_disabledMediaTypes: [], OMDbAPI_disabledMediaTypes: [], OpenLibraryAPI_disabledMediaTypes: [], @@ -610,38 +614,38 @@ export class MediaDbSettingTab extends PluginSettingTab { return component; }), ); - apiKeyGroup.addSetting( - setting => - void setting - .setName('Moby Games key') - .setDesc('API key for "www.mobygames.com".') - .addComponent(el => { - const component = new SecretComponent(this.app, el); - - component.setValue(this.plugin.settings.MobyGamesKeyId).onChange(data => { - this.plugin.settings.MobyGamesKeyId = data; - void this.plugin.saveSettings(); - }); - - return component; - }), - ); - apiKeyGroup.addSetting( - setting => - void setting - .setName('Giant Bomb Key') - .setDesc('API key for "www.giantbomb.com".') - .addComponent(el => { - const component = new SecretComponent(this.app, el); - - component.setValue(this.plugin.settings.GiantBombKeyId).onChange(data => { - this.plugin.settings.GiantBombKeyId = data; - void this.plugin.saveSettings(); - }); - - return component; - }), - ); + // apiKeyGroup.addSetting( + // setting => + // void setting + // .setName('Moby Games key') + // .setDesc('API key for "www.mobygames.com".') + // .addComponent(el => { + // const component = new SecretComponent(this.app, el); + + // component.setValue(this.plugin.settings.MobyGamesKeyId).onChange(data => { + // this.plugin.settings.MobyGamesKeyId = data; + // void this.plugin.saveSettings(); + // }); + + // return component; + // }), + // ); + // apiKeyGroup.addSetting( + // setting => + // void setting + // .setName('Giant Bomb Key') + // .setDesc('API key for "www.giantbomb.com".') + // .addComponent(el => { + // const component = new SecretComponent(this.app, el); + + // component.setValue(this.plugin.settings.GiantBombKeyId).onChange(data => { + // this.plugin.settings.GiantBombKeyId = data; + // void this.plugin.saveSettings(); + // }); + + // return component; + // }), + // ); apiKeyGroup.addSetting( setting => void setting diff --git a/packages/obsidian/src/styles.css b/packages/obsidian/src/styles.css index ed3a79e0..0219ad8f 100644 --- a/packages/obsidian/src/styles.css +++ b/packages/obsidian/src/styles.css @@ -295,3 +295,40 @@ small.media-db-plugin-list-text { width: var(--checkbox-size); height: var(--checkbox-size); } + +.media-db-plugin-legacy-keys-modal { + display: contents; +} + +.media-db-plugin-legacy-keys-intro { + color: var(--text-normal); +} + +.media-db-plugin-legacy-keys-wrapper { + display: flex; + flex-direction: column; + gap: var(--size-4-3); + width: 100%; + max-width: 100%; +} + +.media-db-plugin-legacy-keys-textarea { + width: 100%; + max-width: 100%; + min-height: var(--size-4-5); + min-width: var(--size-4-7); + box-sizing: border-box; + resize: vertical; + padding: var(--size-4-3); + border-radius: var(--radius-s); + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + color: var(--text-normal); + font-family: var(--font-monospace); + font-size: var(--font-ui-smaller); + line-height: 1.5; + white-space: pre; +} +.media-db-plugin-hidden { + display: none; +}