diff --git a/.github/ISSUE_TEMPLATE/api-request.md b/.github/ISSUE_TEMPLATE/api-request.md new file mode 100644 index 0000000..b4f1d58 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/api-request.md @@ -0,0 +1,21 @@ +--- +name: API request +about: Suggest a new API to be added to this plugin. +title: '' +labels: API request +assignees: '' + +--- + +**Name** +Name of the API you would like to be added to the plugin + +**Link** +A link to their API documentation + +**What does the API do/offer** +A short description of what data the API offers + + +- [ ] Is the API free to use +- [ ] Does the API require authentication diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..6fa1d5a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +- [ ] The Plugin is up to date +- [ ] Obsidian is up to date + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Occurs on** +- [ ] Windows +- [ ] macOS +- [ ] Linux +- [ ] Android +- [ ] iOS + +**Plugin version** +x.x.x + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..e93eab6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: feature request +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/README.md b/README.md index e1b7b03..1bf3b3c 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,17 @@ Available variables that can be used in template tags are the same variables fro I also published my own templates [here](https://github.com/mProjectsCode/obsidian-media-db-templates). +#### Metadata field customization +Allows you to rename the metadata fields this plugin generates through mappings. + +A mapping has to follow this syntax `[origional property name] -> [new property name]`. +Multiple mappings are separated by a new line. +So e.g.: +``` +title -> name +year -> releaseYear +``` + ### How to install **The plugin is now released, so it can be installed directly through obsidian's plugin installer.** @@ -46,8 +57,7 @@ The folder structure should look like this: |_ main.js |_ manifest.json |_ styles.css -``` - +``` ### How to use @@ -102,6 +112,11 @@ Now you select the result you want and the plugin will cast it's magic and creat ### Problems, unexpected behavior or improvement suggestions? You are more than welcome to open an issue on [GitHub](https://github.com/mProjectsCode/obsidian-media-db-plugin/issues). +### Changelog +#### 0.2.0 +- Added the option to rename metadata fields through property mappings +- fixed note creation falling, when the folder set in the settings did not exist + ### Contributions Thank you for wanting to contribute to this project. diff --git a/manifest.json b/manifest.json index 05b08e5..6a82c3e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-media-db-plugin", "name": "Media DB Plugin", - "version": "0.1.11", + "version": "0.2.0", "minAppVersion": "0.14.0", "description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault. ", "author": "Moritz Jung", diff --git a/src/api/apis/SteamAPI.ts b/src/api/apis/SteamAPI.ts index c523609..ef8f21d 100644 --- a/src/api/apis/SteamAPI.ts +++ b/src/api/apis/SteamAPI.ts @@ -81,7 +81,10 @@ export class SteamAPI extends APIModel { let result; for (const [key, value] of Object.entries(await fetchData.json)) { - if (key == id) { + // console.log(typeof key, key) + // console.log(typeof id, id) + // after some testing I found out that id is somehow a number despite that it's defined as string... + if (key === String(id)) { result = value.data; } } diff --git a/src/main.ts b/src/main.ts index b72add6..eae5fef 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,11 +12,14 @@ import {WikipediaAPI} from './api/apis/WikipediaAPI'; import {MusicBrainzAPI} from './api/apis/MusicBrainzAPI'; import {MediaTypeManager} from './utils/MediaTypeManager'; import {SteamAPI} from './api/apis/SteamAPI'; +import {ModelPropertyMapper} from './settings/ModelPropertyMapper'; +import {YAMLConverter} from './utils/YAMLConverter'; export default class MediaDbPlugin extends Plugin { settings: MediaDbPluginSettings; apiManager: APIManager; mediaTypeManager: MediaTypeManager; + modelPropertyMapper: ModelPropertyMapper; async onload() { await this.loadSettings(); @@ -68,6 +71,7 @@ export default class MediaDbPlugin extends Plugin { // this.apiManager.registerAPI(new LocGovAPI(this)); // TODO: parse data this.mediaTypeManager = new MediaTypeManager(this.settings); + this.modelPropertyMapper = new ModelPropertyMapper(this.settings); } async createMediaDbNote(modal: () => Promise): Promise { @@ -87,7 +91,7 @@ export default class MediaDbPlugin extends Plugin { console.log('MDB | Creating new note...'); // console.log(mediaTypeModel); - let fileContent = `---\n${mediaTypeModel.toMetaData()}---\n`; + let fileContent = `---\n${YAMLConverter.toYaml(this.modelPropertyMapper.convertObject(mediaTypeModel.toMetaDataObject()))}---\n`; if (this.settings.templates) { fileContent += await this.mediaTypeManager.getContent(mediaTypeModel, this.app); @@ -96,6 +100,11 @@ export default class MediaDbPlugin extends Plugin { const fileName = replaceIllegalFileNameCharactersInString(this.mediaTypeManager.getFileName(mediaTypeModel)); const filePath = `${this.settings.folder.replace(/\/$/, '')}/${fileName}.md`; + const folder = this.app.vault.getAbstractFileByPath(this.settings.folder); + if (!folder) { + await this.app.vault.createFolder(this.settings.folder.replace(/\/$/, '')); + } + const file = this.app.vault.getAbstractFileByPath(filePath); if (file) { await this.app.vault.delete(file); @@ -144,13 +153,17 @@ export default class MediaDbPlugin extends Plugin { throw new Error('MDB | there is no active note'); } - let metadata: FrontMatterCache = this.app.metadataCache.getFileCache(activeFile).frontmatter; + let metadata: any = this.app.metadataCache.getFileCache(activeFile).frontmatter; + delete metadata.position; // remove unnecessary data from the FrontMatterCache + metadata = this.modelPropertyMapper.convertObjectBack(metadata); + + console.log(metadata) if (!metadata?.type || !metadata?.dataSource || !metadata?.id) { throw new Error('MDB | active note is not a Media DB entry or is missing metadata'); } - delete metadata.position; // remove unnecessary data from the FrontMatterCache + let oldMediaTypeModel = this.mediaTypeManager.createMediaTypeModelFromMediaType(metadata, metadata.type); let newMediaTypeModel = await this.apiManager.queryDetailedInfoById(metadata.id, metadata.dataSource); @@ -171,6 +184,8 @@ export default class MediaDbPlugin extends Plugin { async saveSettings() { this.mediaTypeManager.updateTemplates(this.settings); + this.modelPropertyMapper.updateConversionRules(this.settings); + await this.saveData(this.settings); } } diff --git a/src/models/MediaTypeModel.ts b/src/models/MediaTypeModel.ts index 9c52813..d8fbd4a 100644 --- a/src/models/MediaTypeModel.ts +++ b/src/models/MediaTypeModel.ts @@ -17,8 +17,8 @@ export abstract class MediaTypeModel { abstract getTags(): string[]; - toMetaData(): string { - return YAMLConverter.toYaml({...this.getWithOutUserData(), ...this.userData, tags: '#' + this.getTags().join('/')}); + toMetaDataObject(): object { + return {...this.getWithOutUserData(), ...this.userData, tags: '#' + this.getTags().join('/')}; } getWithOutUserData(): object { diff --git a/src/models/WikiModel.ts b/src/models/WikiModel.ts index 6c067e9..64b2ab5 100644 --- a/src/models/WikiModel.ts +++ b/src/models/WikiModel.ts @@ -16,6 +16,7 @@ export class WikiModel extends MediaTypeModel { wikiUrl: string; lastUpdated: string; length: number; + article: string; userData: {}; @@ -35,4 +36,11 @@ export class WikiModel extends MediaTypeModel { return MediaType.Wiki; } + override getWithOutUserData(): object { + const copy = JSON.parse(JSON.stringify(this)); + delete copy.userData; + delete copy.article; + return copy; + } + } diff --git a/src/settings/ModelPropertyConversionRule.ts b/src/settings/ModelPropertyConversionRule.ts new file mode 100644 index 0000000..d749918 --- /dev/null +++ b/src/settings/ModelPropertyConversionRule.ts @@ -0,0 +1,27 @@ +import {containsOnlyLettersAndUnderscores} from '../utils/Utils'; + +export class ModelPropertyConversionRule { + property: string; + newProperty: string; + + constructor(conversionRule: string) { + const conversionRuleParts = conversionRule.split('->'); + if (conversionRuleParts.length !== 2) { + throw Error(`Conversion rule "${conversionRule}" may only have exactly one "->"`); + } + + let property = conversionRuleParts[0].trim(); + let newProperty = conversionRuleParts[1].trim(); + + if (!property || !containsOnlyLettersAndUnderscores(property)) { + throw Error(`Error in conversion rule "${conversionRule}": property may not be empty and only contain letters and underscores.`); + } + + if (!newProperty || !containsOnlyLettersAndUnderscores(newProperty)) { + throw Error(`Error in conversion rule "${conversionRule}": new property may not be empty and only contain letters and underscores.`); + } + + this.property = property; + this.newProperty = newProperty; + } +} diff --git a/src/settings/ModelPropertyMapper.ts b/src/settings/ModelPropertyMapper.ts new file mode 100644 index 0000000..a97ed44 --- /dev/null +++ b/src/settings/ModelPropertyMapper.ts @@ -0,0 +1,109 @@ +import {MediaType} from '../utils/MediaType'; +import {MediaDbPluginSettings} from './Settings'; +import {ModelPropertyConversionRule} from './ModelPropertyConversionRule'; + +export class ModelPropertyMapper { + conversionRulesMap: Map; + + constructor(settings: MediaDbPluginSettings) { + this.updateConversionRules(settings); + } + + updateConversionRules(settings: MediaDbPluginSettings) { + this.conversionRulesMap = new Map(); + this.conversionRulesMap.set(MediaType.Movie, settings.moviePropertyConversionRules); + this.conversionRulesMap.set(MediaType.Series, settings.seriesPropertyConversionRules); + this.conversionRulesMap.set(MediaType.Game, settings.gamePropertyConversionRules); + this.conversionRulesMap.set(MediaType.Wiki, settings.wikiPropertyConversionRules); + this.conversionRulesMap.set(MediaType.MusicRelease, settings.musicReleasePropertyConversionRules); + } + + convertObject(obj: object): object { + if (!obj.hasOwnProperty('type')) { + return obj; + } + + // @ts-ignore + const conversionRulesString: string = this.conversionRulesMap.get(obj['type']); + if (!conversionRulesString) { + return obj; + } + + const conversionRules: ModelPropertyConversionRule[] = [] + for (const conversionRuleString of conversionRulesString.split('\n')) { + if (conversionRuleString) { + conversionRules.push(new ModelPropertyConversionRule(conversionRuleString)); + } + } + + const newObj: object = {}; + + + for (const [key, value] of Object.entries(obj)) { + if (key === 'type') { + // @ts-ignore + newObj[key] = value; + continue; + } + + let hasConversionRule = false; + for (const conversionRule of conversionRules) { + if (conversionRule.property === key) { + hasConversionRule = true; + // @ts-ignore + newObj[conversionRule.newProperty] = value; + } + } + if (!hasConversionRule) { + // @ts-ignore + newObj[key] = value; + } + } + + return newObj; + } + + convertObjectBack(obj: object): object { + if (!obj.hasOwnProperty('type')) { + return obj; + } + + // @ts-ignore + const conversionRulesString: string = this.conversionRulesMap.get(obj['type']); + if (!conversionRulesString) { + return obj; + } + + const conversionRules: ModelPropertyConversionRule[] = [] + for (const conversionRuleString of conversionRulesString.split('\n')) { + if (conversionRuleString) { + conversionRules.push(new ModelPropertyConversionRule(conversionRuleString)); + } + } + + const originalObj: object = {}; + + for (const [key, value] of Object.entries(obj)) { + if (key === 'type') { + // @ts-ignore + originalObj[key] = value; + continue; + } + + let hasConversionRule = false; + for (const conversionRule of conversionRules) { + if (conversionRule.newProperty === key) { + hasConversionRule = true; + // @ts-ignore + originalObj[conversionRule.property] = value; + } + } + if (!hasConversionRule) { + // @ts-ignore + originalObj[key] = value; + } + } + + return originalObj; + } +} diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 4fb9e95..f526dbe 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -22,6 +22,12 @@ export interface MediaDbPluginSettings { wikiFileNameTemplate: string, musicReleaseFileNameTemplate: string, + moviePropertyConversionRules: string, + seriesPropertyConversionRules: string, + gamePropertyConversionRules: string, + wikiPropertyConversionRules: string, + musicReleasePropertyConversionRules: string, + templates: boolean, } @@ -42,6 +48,12 @@ export const DEFAULT_SETTINGS: MediaDbPluginSettings = { wikiFileNameTemplate: '{{ title }}', musicReleaseFileNameTemplate: '{{ title }} (by {{ ENUM:artists }} - {{ year }})', + moviePropertyConversionRules: '', + seriesPropertyConversionRules: '', + gamePropertyConversionRules: '', + wikiPropertyConversionRules: '', + musicReleasePropertyConversionRules: '', + templates: true, }; @@ -181,7 +193,7 @@ export class MediaDbSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('Movie file name template') .setDesc('Template for the file name used when creating a new note for a movie.') - .addSearch(cb => { + .addText(cb => { cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.movieFileNameTemplate}`) .setValue(this.plugin.settings.movieFileNameTemplate) .onChange(data => { @@ -193,7 +205,7 @@ export class MediaDbSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('Series file name template') .setDesc('Template for the file name used when creating a new note for a series.') - .addSearch(cb => { + .addText(cb => { cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.seriesFileNameTemplate}`) .setValue(this.plugin.settings.seriesFileNameTemplate) .onChange(data => { @@ -205,7 +217,7 @@ export class MediaDbSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('Game file name template') .setDesc('Template for the file name used when creating a new note for a game.') - .addSearch(cb => { + .addText(cb => { cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.gameFileNameTemplate}`) .setValue(this.plugin.settings.gameFileNameTemplate) .onChange(data => { @@ -217,7 +229,7 @@ export class MediaDbSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('Wiki file name template') .setDesc('Template for the file name used when creating a new note for a wiki entry.') - .addSearch(cb => { + .addText(cb => { cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.wikiFileNameTemplate}`) .setValue(this.plugin.settings.wikiFileNameTemplate) .onChange(data => { @@ -229,7 +241,7 @@ export class MediaDbSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('Music Release file name template') .setDesc('Template for the file name used when creating a new note for a music release.') - .addSearch(cb => { + .addText(cb => { cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.musicReleaseFileNameTemplate}`) .setValue(this.plugin.settings.musicReleaseFileNameTemplate) .onChange(data => { @@ -239,6 +251,69 @@ export class MediaDbSettingTab extends PluginSettingTab { }); // endregion + containerEl.createEl('h3', {text: 'Property Mappings'}); + // region Property Mappings + new Setting(containerEl) + .setName('Movie model property mappings') + .setDesc('Mappings for the property names of a movie.') + .addTextArea(cb => { + cb.setPlaceholder(`Example: \ntitle -> name\nyear -> releaseYear`) + .setValue(this.plugin.settings.moviePropertyConversionRules) + .onChange(data => { + this.plugin.settings.moviePropertyConversionRules = data; + this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('Series model property mappings') + .setDesc('Mappings for the property names of a series.') + .addTextArea(cb => { + cb.setPlaceholder(`Example: \ntitle -> name\nyear -> releaseYear`) + .setValue(this.plugin.settings.seriesPropertyConversionRules) + .onChange(data => { + this.plugin.settings.seriesPropertyConversionRules = data; + this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('Game model property mappings') + .setDesc('Mappings for the property names of a game.') + .addTextArea(cb => { + cb.setPlaceholder(`Example: \ntitle -> name\nyear -> releaseYear`) + .setValue(this.plugin.settings.gamePropertyConversionRules) + .onChange(data => { + this.plugin.settings.gamePropertyConversionRules = data; + this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('Wiki model property mappings') + .setDesc('Mappings for the property names of a wiki entry.') + .addTextArea(cb => { + cb.setPlaceholder(`Example: \ntitle -> name\nyear -> releaseYear`) + .setValue(this.plugin.settings.wikiPropertyConversionRules) + .onChange(data => { + this.plugin.settings.wikiPropertyConversionRules = data; + this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('Music Release model property mappings') + .setDesc('Mappings for the property names of a music release.') + .addTextArea(cb => { + cb.setPlaceholder(`Example: \ntitle -> name\nyear -> releaseYear`) + .setValue(this.plugin.settings.musicReleasePropertyConversionRules) + .onChange(data => { + this.plugin.settings.musicReleasePropertyConversionRules = data; + this.plugin.saveSettings(); + }); + }); + // endregion + } } diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 25fb06e..3a7917d 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -4,7 +4,7 @@ import {MediaTypeModel} from '../models/MediaTypeModel'; export const pluginName: string = 'obsidian-media-db-plugin'; export const contactEmail: string = 'm.projects.code@gmail.com'; export const mediaDbTag: string = 'mediaDB'; -export const mediaDbVersion: string = '0.1.11'; +export const mediaDbVersion: string = '0.2.0'; export const debug: boolean = false; export function wrapAround(value: number, size: number): number { @@ -17,6 +17,10 @@ export function debugLog(o: any): void { } } +export function containsOnlyLettersAndUnderscores(str: string): boolean { + return /^[a-zA-Z_]+$/.test(str); +} + export function replaceIllegalFileNameCharactersInString(string: string): string { return string.replace(/[\\,#%&{}/*<>$"@.?]*/g, '').replace(/:+/g, ' -'); }