Add legacy api key modal

Pop up modal when legacy keys are found in data.json
The keys are hidden by default but the user can show them or just copy them. The old plaintext keys should be deleted for safety reasons
This commit is contained in:
ltctceplrm 2026-06-21 19:50:23 +02:00
parent e38f450e24
commit 54f971f609
3 changed files with 156 additions and 20 deletions

View file

@ -16,6 +16,8 @@ import { TMDBSeasonAPI } from 'packages/obsidian/src/api/apis/TMDBSeasonAPI';
import { TMDBSeriesAPI } from 'packages/obsidian/src/api/apis/TMDBSeriesAPI'; import { TMDBSeriesAPI } from 'packages/obsidian/src/api/apis/TMDBSeriesAPI';
import { VNDBAPI } from 'packages/obsidian/src/api/apis/VNDBAPI'; import { VNDBAPI } from 'packages/obsidian/src/api/apis/VNDBAPI';
import { WikipediaAPI } from 'packages/obsidian/src/api/apis/WikipediaAPI'; 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 { PropertyMapper } from 'packages/obsidian/src/settings/PropertyMapper';
import { PropertyMappingModel } from 'packages/obsidian/src/settings/PropertyMapping'; import { PropertyMappingModel } from 'packages/obsidian/src/settings/PropertyMapping';
import type { MediaDbPluginSettings } from 'packages/obsidian/src/settings/Settings'; import type { MediaDbPluginSettings } from 'packages/obsidian/src/settings/Settings';
@ -31,6 +33,8 @@ import { MEDIA_TYPES } from 'packages/obsidian/src/utils/MediaTypeManager';
import { ModalHelper } from 'packages/obsidian/src/utils/ModalHelper'; import { ModalHelper } from 'packages/obsidian/src/utils/ModalHelper';
import { unCamelCase } from 'packages/obsidian/src/utils/Utils'; import { unCamelCase } from 'packages/obsidian/src/utils/Utils';
const LEGACY_API_KEYS = ['OMDbKey', 'TMDBKey', 'MobyGamesKey', 'GiantBombKey', 'ComicVineKey', 'BoardgameGeekKey'] as const;
export default class MediaDbPlugin extends Plugin { export default class MediaDbPlugin extends Plugin {
declare settings: MediaDbPluginSettings; declare settings: MediaDbPluginSettings;
apiManager!: APIManager; apiManager!: APIManager;
@ -168,35 +172,42 @@ export default class MediaDbPlugin extends Plugin {
}); });
} }
async loadSettings(): Promise<void> { private getLegacyApiKeyEntries(diskSettings: Record<string, unknown>): LegacyApiKeyEntry[] {
const diskSettings: MediaDbPluginSettings = (await this.loadData()) as MediaDbPluginSettings; return LEGACY_API_KEYS.filter(key => typeof diskSettings[key] === 'string' && (diskSettings[key]).length > 0).map(key => ({
const defaultSettings: MediaDbPluginSettings = getDefaultSettings(this); key,
const loadedSettings: MediaDbPluginSettings = Object.assign({}, defaultSettings, diskSettings); value: diskSettings[key] as string,
}));
}
// delete old api keys private removeLegacyApiKeys(settings: Record<string, unknown>): void {
// @ts-ignore for (const key of LEGACY_API_KEYS) {
delete loadedSettings.BoardgameGeekKey; delete settings[key];
// @ts-ignore }
delete loadedSettings.ComicVineKey; }
// @ts-ignore
delete loadedSettings.GiantBombKey; async loadSettings(): Promise<void> {
// @ts-ignore const diskSettings = (await this.loadData()) as Record<string, unknown>;
delete loadedSettings.MobyGamesKey; const defaultSettings: MediaDbPluginSettings = getDefaultSettings(this);
// @ts-ignore const loadedSettings = Object.assign({}, defaultSettings, diskSettings) as MediaDbPluginSettings;
delete loadedSettings.OMDbKey;
// @ts-ignore
delete loadedSettings.TMDBKey;
const migratedModels = PropertyMappingModel.migrateModels( const migratedModels = PropertyMappingModel.migrateModels(
loadedSettings.propertyMappingModels || [], loadedSettings.propertyMappingModels || [],
defaultSettings.propertyMappingModels.map(m => PropertyMappingModel.fromJSON(m)), defaultSettings.propertyMappingModels.map(m => PropertyMappingModel.fromJSON(m)),
); );
loadedSettings.propertyMappingModels = migratedModels.map(m => m.toJSON()); loadedSettings.propertyMappingModels = migratedModels.map(m => m.toJSON());
this.settings = loadedSettings; this.settings = loadedSettings;
const legacyEntries = this.getLegacyApiKeyEntries(diskSettings);
if (legacyEntries.length > 0) {
this.app.workspace.onLayoutReady(() => {
window.setTimeout(() => {
new LegacyApiKeysModal(this.app, legacyEntries, async () => {
this.removeLegacyApiKeys(this.settings as unknown as Record<string, unknown>);
await this.saveSettings(); await this.saveSettings();
}).open();
}, 0);
});
}
} }
async saveSettings(): Promise<void> { async saveSettings(): Promise<void> {

View file

@ -0,0 +1,91 @@
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. 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.style.display = 'none';
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.style.display === 'none';
if (isHidden) {
textarea.style.display = '';
btn.setButtonText('Hide keys');
} else {
textarea.style.display = 'none';
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();
}
}

View file

@ -295,3 +295,37 @@ small.media-db-plugin-list-text {
width: var(--checkbox-size); width: var(--checkbox-size);
height: 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: 0.75rem;
width: 100%;
max-width: 100%;
}
.media-db-plugin-legacy-keys-textarea {
width: 100%;
max-width: 100%;
min-height: 100px;
min-width: 500px;
box-sizing: border-box;
resize: vertical;
padding: 0.75rem;
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;
}