Merge pull request #277 from ltctceplrm/api-key-popup

Add legacy api key popup so users can copy old keys
This commit is contained in:
Moritz Jung 2026-06-21 22:20:48 +02:00 committed by GitHub
commit 63e8359895
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 199 additions and 56 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';
@ -168,35 +170,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 this.settings.LegacyApiKeys.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 this.settings.LegacyApiKeys) {
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;
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<string, unknown>);
void this.saveSettings();
}).open();
}, 0);
});
}
} }
async saveSettings(): Promise<void> { async saveSettings(): Promise<void> {

View file

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

View file

@ -50,14 +50,16 @@ function createPropertyMappingsDescription(): DocumentFragment {
export interface MediaDbPluginSettings { export interface MediaDbPluginSettings {
OMDbKeyId: string; OMDbKeyId: string;
TMDBKeyId: string; TMDBKeyId: string;
MobyGamesKeyId: string; //MobyGamesKeyId: string;
GiantBombKeyId: string; //GiantBombKeyId: string;
IGDBClientId: string; IGDBClientId: string;
IGDBClientSecret: string; IGDBClientSecret: string;
RAWGAPIKeyId: string; RAWGAPIKeyId: string;
ComicVineKeyId: string; ComicVineKeyId: string;
BoardgameGeekKeyId: string; BoardgameGeekKeyId: string;
LegacyApiKeys: readonly ['OMDbKey', 'TMDBKey', 'MobyGamesKey', 'GiantBombKey', 'ComicVineKey', 'BoardgameGeekKey'];
sfwFilter: boolean; sfwFilter: boolean;
templates: boolean; templates: boolean;
customDateFormat: string; customDateFormat: string;
@ -69,12 +71,12 @@ export interface MediaDbPluginSettings {
BoardgameGeekAPI_disabledMediaTypes: MediaType[]; BoardgameGeekAPI_disabledMediaTypes: MediaType[];
ComicVineAPI_disabledMediaTypes: MediaType[]; ComicVineAPI_disabledMediaTypes: MediaType[];
GiantBombAPI_disabledMediaTypes: MediaType[]; //GiantBombAPI_disabledMediaTypes: MediaType[];
IGDBAPI_disabledMediaTypes: MediaType[]; IGDBAPI_disabledMediaTypes: MediaType[];
RAWGAPI_disabledMediaTypes: MediaType[]; RAWGAPI_disabledMediaTypes: MediaType[];
MALAPI_disabledMediaTypes: MediaType[]; MALAPI_disabledMediaTypes: MediaType[];
MALAPIManga_disabledMediaTypes: MediaType[]; MALAPIManga_disabledMediaTypes: MediaType[];
MobyGamesAPI_disabledMediaTypes: MediaType[]; //MobyGamesAPI_disabledMediaTypes: MediaType[];
MusicBrainzAPI_disabledMediaTypes: MediaType[]; MusicBrainzAPI_disabledMediaTypes: MediaType[];
OMDbAPI_disabledMediaTypes: MediaType[]; OMDbAPI_disabledMediaTypes: MediaType[];
OpenLibraryAPI_disabledMediaTypes: MediaType[]; OpenLibraryAPI_disabledMediaTypes: MediaType[];
@ -309,14 +311,16 @@ class MediaTypeMappedSettings {
const DEFAULT_SETTINGS: MediaDbPluginSettings = { const DEFAULT_SETTINGS: MediaDbPluginSettings = {
OMDbKeyId: '', OMDbKeyId: '',
TMDBKeyId: '', TMDBKeyId: '',
MobyGamesKeyId: '', //MobyGamesKeyId: '',
GiantBombKeyId: '', //GiantBombKeyId: '',
IGDBClientId: '', IGDBClientId: '',
IGDBClientSecret: '', IGDBClientSecret: '',
RAWGAPIKeyId: '', RAWGAPIKeyId: '',
ComicVineKeyId: '', ComicVineKeyId: '',
BoardgameGeekKeyId: '', BoardgameGeekKeyId: '',
LegacyApiKeys: ['OMDbKey', 'TMDBKey', 'MobyGamesKey', 'GiantBombKey', 'ComicVineKey', 'BoardgameGeekKey'],
sfwFilter: true, sfwFilter: true,
templates: true, templates: true,
customDateFormat: 'L', customDateFormat: 'L',
@ -328,12 +332,12 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
BoardgameGeekAPI_disabledMediaTypes: [], BoardgameGeekAPI_disabledMediaTypes: [],
ComicVineAPI_disabledMediaTypes: [], ComicVineAPI_disabledMediaTypes: [],
GiantBombAPI_disabledMediaTypes: [], //GiantBombAPI_disabledMediaTypes: [],
IGDBAPI_disabledMediaTypes: [], IGDBAPI_disabledMediaTypes: [],
RAWGAPI_disabledMediaTypes: [], RAWGAPI_disabledMediaTypes: [],
MALAPI_disabledMediaTypes: [], MALAPI_disabledMediaTypes: [],
MALAPIManga_disabledMediaTypes: [], MALAPIManga_disabledMediaTypes: [],
MobyGamesAPI_disabledMediaTypes: [], //MobyGamesAPI_disabledMediaTypes: [],
MusicBrainzAPI_disabledMediaTypes: [], MusicBrainzAPI_disabledMediaTypes: [],
OMDbAPI_disabledMediaTypes: [], OMDbAPI_disabledMediaTypes: [],
OpenLibraryAPI_disabledMediaTypes: [], OpenLibraryAPI_disabledMediaTypes: [],
@ -610,38 +614,38 @@ export class MediaDbSettingTab extends PluginSettingTab {
return component; return component;
}), }),
); );
apiKeyGroup.addSetting( // apiKeyGroup.addSetting(
setting => // setting =>
void setting // void setting
.setName('Moby Games key') // .setName('Moby Games key')
.setDesc('API key for "www.mobygames.com".') // .setDesc('API key for "www.mobygames.com".')
.addComponent(el => { // .addComponent(el => {
const component = new SecretComponent(this.app, el); // const component = new SecretComponent(this.app, el);
component.setValue(this.plugin.settings.MobyGamesKeyId).onChange(data => { // component.setValue(this.plugin.settings.MobyGamesKeyId).onChange(data => {
this.plugin.settings.MobyGamesKeyId = data; // this.plugin.settings.MobyGamesKeyId = data;
void this.plugin.saveSettings(); // void this.plugin.saveSettings();
}); // });
return component; // return component;
}), // }),
); // );
apiKeyGroup.addSetting( // apiKeyGroup.addSetting(
setting => // setting =>
void setting // void setting
.setName('Giant Bomb Key') // .setName('Giant Bomb Key')
.setDesc('API key for "www.giantbomb.com".') // .setDesc('API key for "www.giantbomb.com".')
.addComponent(el => { // .addComponent(el => {
const component = new SecretComponent(this.app, el); // const component = new SecretComponent(this.app, el);
component.setValue(this.plugin.settings.GiantBombKeyId).onChange(data => { // component.setValue(this.plugin.settings.GiantBombKeyId).onChange(data => {
this.plugin.settings.GiantBombKeyId = data; // this.plugin.settings.GiantBombKeyId = data;
void this.plugin.saveSettings(); // void this.plugin.saveSettings();
}); // });
return component; // return component;
}), // }),
); // );
apiKeyGroup.addSetting( apiKeyGroup.addSetting(
setting => setting =>
void setting void setting

View file

@ -295,3 +295,40 @@ 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: 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;
}