diff --git a/README.md b/README.md index 2e5b077..f8f28f5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Obsidian Moviegrabber A plugin to generate notes for movies and series with properties that can be used with [dataview](https://github.com/blacksmithgu/obsidian-dataview). Uses the [Open Movie Database (OMDb) API](http://www.omdbapi.com/) to retrieve movie/series data and the [Youtube Data API](https://developers.google.com/youtube/v3/docs?hl=de) to get the embed links for the trailers (optional). @@ -92,3 +93,114 @@ FROM "Movies" WHERE type = "movie" AND seen = Null ```` Note that the cards need at least ``country, year, length, trailer_embed`` in the querry to show a card. +======= +# Obsidian Moviegrabber + +A plugin to generate notes for movies and series with properties that can be used with [dataview](https://github.com/blacksmithgu/obsidian-dataview). Uses the [Open Movie Database (OMDb) API](http://www.omdbapi.com/) to retrieve movie/series data and the [Youtube Data API](https://developers.google.com/youtube/v3/docs?hl=de) to get the embed links for the trailers (optional). + +

+ + +

+ +--- + +# Usage + +https://github.com/Superschnizel/Obsidian-Moviegrabber/assets/47162464/28e2ca9d-e504-4923-9609-dc1e5953d219 + +*(Disclaimer: the choice selection uses outside assets for the movie posters in the preview, retrieved in the search request to OMDb)* + +To use this plugin you need to create an API key for the OMDb [here](http://www.omdbapi.com/apikey.aspx) and optionally also a Youtube Data API Key as described [here](https://developers.google.com/youtube/v3/docs#calling-the-api). and set these in the plugin settings. + +To search for a movie or series, simply call the command `Search movie` or `Search series` and search a movie by entering a title or a valid IMDB-id. + +## Templates + +To define how the data will be saved in your notes you can define a template. This template uses tags of the form `{{tag}}` to fill in the data. The available tags are: +``` +{{Title}} +{{Year}} +{{Rated}} +{{Runtime}} +{{Genre}} +{{Director}} +{{Writer}} +{{Actors}} +{{Plot}} +{{Language}} +{{Country}} +{{Awards}} +{{Poster}} +{{Ratings}} +{{Metascore}} +{{imdbRating}} +{{imdbVotes}} +{{imdbID}} +{{Type}} +{{DVD}} +{{BoxOffice}} +{{Production}} +{{Website}} +{{totalSeasons}} +{{YoutubeEmbed}} +``` +### Pre- and Suffix + +You can define a pre- and suffix to be applied to the data. this is done by using `{{tag|prefix|suffix}}`. *(if you want to use the "|" character, it can be escaped using "\\|")*. + +Example: ``{{Director|"[[|]]"}}`` will create an internal link of the form `"[[Director]]"` + +### Regex Transformation + +Additionally you can also give a *regex transform* to transform the data to your liking by using `{{tag|prefix|suffix|regexTransform}}`. + +Inside a regex transformation every regular expression given inside `<$ $>` delimiters will be replaced by the matching string from the input. This allows you to bring the data into the form you need it. + +Examples: +- `{{Actors|"[[|]]"|<$\w+$$>, <$^\w+$>\|@<$^\w+$> <$\w+$$>}}`. This regex transformation will transform the "Actors" data in the form of `Firstname Lastname` into a link in the form of `"[[Lastname, Firstname|@Firstname Lastname]]"` (see issue #21). +- `{{Ratings|"|"|<$Rotten Tomatoes\: .*$>}}`. This regex transformation will result in only the Rotten Tomatoes rating to be shown. + +When a regex transformation results in an empty string pre- and suffix will not be applied. + + +You can generate an example template in the plugin settings. If no template is given, this default template is used. + +## Regenerating notes + +When trying to create a note for a movie that already exists you will be asked if you want to overwrite the existing note. If you want to keep something from the old note in the newly generated one you can make use of the delimiter string: + +``` +%%==MOVIEGRABBER_KEEP==%% +``` +Everything below this will be transfered to the new note when overwriting. + +# Using the generated notes with Dataview and custom CSS + +Using a [dataview](https://github.com/blacksmithgu/) table in combination with a custom [css snippet](https://help.obsidian.md/Extending+Obsidian/CSS+snippets), you can use these notes to create an interactive display for your movies. + +![grafik](https://github.com/Superschnizel/Obsidian-Moviegrabber/assets/47162464/be2345a6-eeab-4e8b-b2e1-2a5263a9dc41) + + +https://github.com/Superschnizel/Obsidian-Moviegrabber/assets/47162464/fc555eea-0ae4-46b4-87d2-44cc2626d387 + +To use this, copy `aditional_css/CardViewMovies.css` to your vault's snippets folder (`.obsidian/snippets/`) and put + +```yaml +--- +cssclass: CardViewMovies +--- +``` +at the top of your note. + +A dataview query for movies not yet seen could look something like this: + +````dataview +```dataview +TABLE country, year, length, trailer_embed, availability, rating, seen +FROM "Movies" WHERE type = "movie" AND seen = Null +``` +```` + +Note that the cards need at least ``country, year, length, trailer_embed`` in the querry to show a card. +>>>>>>> eb84e7e8b72a19542ac9510d4053a36bbfbc310b diff --git a/main.ts b/main.ts index 2058126..17b35a5 100644 --- a/main.ts +++ b/main.ts @@ -235,13 +235,14 @@ export default class Moviegrabber extends Plugin { return; } - let titleTemplate = type == 'movie' + let filenameTemplate = type == 'movie' ? this.settings.FilenameTemplateMovie : this.settings.FilenameTemplateSeries; - let title = await this.FillTemplate(titleTemplate, itemData); - title = title == '' ? item.Title : title; + let filename = await this.FillTemplate(filenameTemplate, itemData); + filename = filename == '' ? item.Title : filename; - let path = `${dir}${title.replace(/[/\\?%*:|"<>]/g, '')}.md` + const cleanedTitle = filename.replace(/[/\\?%*:|"<>]/g, '') + let path = `${dir}${cleanedTitle}.md` let file = this.app.vault.getAbstractFileByPath(path); // console.log(`${file}, path: ${path}`); @@ -252,11 +253,27 @@ export default class Moviegrabber extends Plugin { return; } + if (this.settings.enablePosterImageSave && itemData.Poster !== null && itemData.Poster !== "N/A") { + const imageName = `${cleanedTitle}.jpg`; + const posterDirectory = this.settings.posterImagePath; + this.downloadAndSavePoster(item.Poster, posterDirectory, imageName) + .then(posterLocalPath => { + itemData!.PosterLocal = posterLocalPath; + new Notice(`Saved poster for: ${itemData!.Title} (${itemData!.Year})`); + }) + .catch(error => { + console.error("Failed to download and save the poster:", error); + new Notice(`Failed to download and save the movie poster for: ${itemData!.Title} (${itemData!.Year})`); + n.noticeEl.addClass("notice_error"); + itemData!.PosterLocal = 'null'; + }); + + } + this.createNote(itemData, type, path); } async createNote(item: MovieData, type: 'movie' | 'series', path: string, tFile: TFile | null = null) { - new Notice(`Creating Note for: ${item.Title} (${item.Year})`); // add and clean up data @@ -313,7 +330,26 @@ export default class Moviegrabber extends Plugin { } } - async GetTemplate(type: 'movie' | 'series'): Promise { + + async downloadAndSavePoster(imageUrl: string, directory: string, imageName: string): Promise { + if (!directory) { + console.error("Poster image directory is not specified."); + throw new Error("Poster image directory is not specified."); + } + + const filePath = normalizePath(`${directory}/${imageName}`); + try { + const response = await requestUrl({ url: imageUrl, method: "GET" }); + const imageData = response.arrayBuffer; + await this.app.vault.adapter.writeBinary(filePath, imageData); + return filePath; + } catch (error) { + console.error("Error downloading or saving poster image:", error); + throw error; + } + } + + async GetTemplate(type : 'movie' | 'series') : Promise { if (this.settings.MovieTemplatePath == '') { // no template given, return default return DEFAULT_TEMPLATE; diff --git a/src/MoviegrabberSearchObject.ts b/src/MoviegrabberSearchObject.ts index 266d1d0..ac09b81 100644 --- a/src/MoviegrabberSearchObject.ts +++ b/src/MoviegrabberSearchObject.ts @@ -1,153 +1,155 @@ -export interface MovieSearch{ - totalResults : number; - Response: boolean; - Search : Array; -} - -export interface MovieSearchItem{ - Title : string; - Year : number; - imdbID : string; - Type : string; - Poster : string; -} - -export interface MovieData{ - [key:string]: any, - Title: string, - Year: number, - Rated: string - Runtime: string, - Genre: string, - Director: string, - Writer: string, - Actors: string, - Plot: string, - Language: string, - Country: string, - Awards: string, - Poster: string, - Ratings: Array, - Metascore: number, - imdbRating: number, - imdbVotes: number, - imdbID: string, - Type: string, - DVD: string, - BoxOffice: string, - Production: string, - Website: string, - totalSeasons: number, - Response: boolean, - YoutubeEmbed: string - } - -export interface MovieRating{ - Source : string; - Value: string; -} - -export class Rating implements MovieRating { - Source: string; - Value: string; - public toString = () : string => { - return `${this.Source}: ${this.Value}`; - } -} - -interface MovieDataLowercase { - [key:string]: string -} - -// this is used to make tags case insensitive -export const MOVIE_DATA_LOWER : MovieDataLowercase = { - "title" : "Title", - "year" : "Year", - "rated" : "Rated", - "runtime" : "Runtime", - "genre" : "Genre", - "director" : "Director", - "writer" : "Writer", - "actors" : "Actors", - "plot" : "Plot", - "language" : "Language", - "country" : "Country", - "awards" : "Awards", - "poster" : "Poster", - "ratings" : "Ratings", - "metascore" : "Metascore", - "imdbrating" : "imdbRating", - "imdbvotes" : "imdbVotes", - "imdbid" : "imdbID", - "type" : "Type", - "dvd" : "DVD", - "boxoffice" : "BoxOffice", - "production" : "Production", - "website" : "Website", - "totalseasons" : "totalSeasons", - "response" : "Response", - "youtubeembed" : "YoutubeEmbed", -} - -export const TEST_SEARCH : MovieSearch = { - "Search": [ - { - "Title": "Mission: Impossible - Ghost Protocol", - "Year": 2011, - "imdbID": "tt1229238", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BMTY4MTUxMjQ5OV5BMl5BanBnXkFtZTcwNTUyMzg5Ng@@._V1_SX300.jpg" - }, - { - "Title": "Mission: Impossible", - "Year": 1996, - "imdbID": "tt0117060", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BMTc3NjI2MjU0Nl5BMl5BanBnXkFtZTgwNDk3ODYxMTE@._V1_SX300.jpg" - }, - { - "Title": "Mission: Impossible - Rogue Nation", - "Year": 2015, - "imdbID": "tt2381249", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BOTFmNDA3ZjMtN2Y0MC00NDYyLWFlY2UtNTQ4OTQxMmY1NmVjXkEyXkFqcGdeQXVyNTg4NDQ4NDY@._V1_SX300.jpg" - }, - { - "Title": "Mission: Impossible III", - "Year": 2006, - "imdbID": "tt0317919", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BOThhNTA1YjItYzk2Ny00M2Y1LWJlYWUtZDQyZDU0YmY5Y2M5XkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg" - }, - { - "Title": "Mission: Impossible II", - "Year": 2000, - "imdbID": "tt0120755", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BN2RkYWVkZDQtNTMxMi00NWQ4LWE2ODctNmQzOWM2NjQzYzdlXkEyXkFqcGdeQXVyMjUzOTY1NTc@._V1_SX300.jpg" - }, - { - "Title": "Mission: Impossible - Fallout", - "Year": 2018, - "imdbID": "tt4912910", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BNjRlZmM0ODktY2RjNS00ZDdjLWJhZGYtNDljNWZkMGM5MTg0XkEyXkFqcGdeQXVyNjAwMjI5MDk@._V1_SX300.jpg" - }, - { - "Title": "Mission: Impossible - Dead Reckoning Part One", - "Year": 2023, - "imdbID": "tt9603212", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BYzFiZjc1YzctMDY3Zi00NGE5LTlmNWEtN2Q3OWFjYjY1NGM2XkEyXkFqcGdeQXVyMTUyMTUzNjQ0._V1_SX300.jpg" - }, - { - "Title": "Mission Impossible Versus the Mob", - "Year": 1969, - "imdbID": "tt0063310", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BM2RiM2UzNmQtM2UxNS00NWI1LTkxZmEtMzAxM2M4OGY4OTBhXkEyXkFqcGdeQXVyMjUwMDUwNA@@._V1_SX300.jpg" - } - ], - "totalResults": 69, - "Response": true -} \ No newline at end of file +export interface MovieSearch{ + totalResults : number; + Response: boolean; + Search : Array; +} + +export interface MovieSearchItem{ + Title : string; + Year : number; + imdbID : string; + Type : string; + Poster : string; +} + +export interface MovieData{ + [key:string]: any, + Title: string, + Year: number, + Rated: string + Runtime: string, + Genre: string, + Director: string, + Writer: string, + Actors: string, + Plot: string, + Language: string, + Country: string, + Awards: string, + Poster: string, + PosterLocal: string, + Ratings: Array, + Metascore: number, + imdbRating: number, + imdbVotes: number, + imdbID: string, + Type: string, + DVD: string, + BoxOffice: string, + Production: string, + Website: string, + totalSeasons: number, + Response: boolean, + YoutubeEmbed: string + } + +export interface MovieRating{ + Source : string; + Value: string; +} + +export class Rating implements MovieRating { + Source: string; + Value: string; + public toString = () : string => { + return `${this.Source}: ${this.Value}`; + } +} + +interface MovieDataLowercase { + [key:string]: string +} + +// this is used to make tags case insensitive +export const MOVIE_DATA_LOWER : MovieDataLowercase = { + "title" : "Title", + "year" : "Year", + "rated" : "Rated", + "runtime" : "Runtime", + "genre" : "Genre", + "director" : "Director", + "writer" : "Writer", + "actors" : "Actors", + "plot" : "Plot", + "language" : "Language", + "country" : "Country", + "awards" : "Awards", + "poster" : "Poster", + "posterlocal": "PosterLocal", + "ratings" : "Ratings", + "metascore" : "Metascore", + "imdbrating" : "imdbRating", + "imdbvotes" : "imdbVotes", + "imdbid" : "imdbID", + "type" : "Type", + "dvd" : "DVD", + "boxoffice" : "BoxOffice", + "production" : "Production", + "website" : "Website", + "totalseasons" : "totalSeasons", + "response" : "Response", + "youtubeembed" : "YoutubeEmbed", +} + +export const TEST_SEARCH : MovieSearch = { + "Search": [ + { + "Title": "Mission: Impossible - Ghost Protocol", + "Year": 2011, + "imdbID": "tt1229238", + "Type": "movie", + "Poster": "https://m.media-amazon.com/images/M/MV5BMTY4MTUxMjQ5OV5BMl5BanBnXkFtZTcwNTUyMzg5Ng@@._V1_SX300.jpg" + }, + { + "Title": "Mission: Impossible", + "Year": 1996, + "imdbID": "tt0117060", + "Type": "movie", + "Poster": "https://m.media-amazon.com/images/M/MV5BMTc3NjI2MjU0Nl5BMl5BanBnXkFtZTgwNDk3ODYxMTE@._V1_SX300.jpg" + }, + { + "Title": "Mission: Impossible - Rogue Nation", + "Year": 2015, + "imdbID": "tt2381249", + "Type": "movie", + "Poster": "https://m.media-amazon.com/images/M/MV5BOTFmNDA3ZjMtN2Y0MC00NDYyLWFlY2UtNTQ4OTQxMmY1NmVjXkEyXkFqcGdeQXVyNTg4NDQ4NDY@._V1_SX300.jpg" + }, + { + "Title": "Mission: Impossible III", + "Year": 2006, + "imdbID": "tt0317919", + "Type": "movie", + "Poster": "https://m.media-amazon.com/images/M/MV5BOThhNTA1YjItYzk2Ny00M2Y1LWJlYWUtZDQyZDU0YmY5Y2M5XkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg" + }, + { + "Title": "Mission: Impossible II", + "Year": 2000, + "imdbID": "tt0120755", + "Type": "movie", + "Poster": "https://m.media-amazon.com/images/M/MV5BN2RkYWVkZDQtNTMxMi00NWQ4LWE2ODctNmQzOWM2NjQzYzdlXkEyXkFqcGdeQXVyMjUzOTY1NTc@._V1_SX300.jpg" + }, + { + "Title": "Mission: Impossible - Fallout", + "Year": 2018, + "imdbID": "tt4912910", + "Type": "movie", + "Poster": "https://m.media-amazon.com/images/M/MV5BNjRlZmM0ODktY2RjNS00ZDdjLWJhZGYtNDljNWZkMGM5MTg0XkEyXkFqcGdeQXVyNjAwMjI5MDk@._V1_SX300.jpg" + }, + { + "Title": "Mission: Impossible - Dead Reckoning Part One", + "Year": 2023, + "imdbID": "tt9603212", + "Type": "movie", + "Poster": "https://m.media-amazon.com/images/M/MV5BYzFiZjc1YzctMDY3Zi00NGE5LTlmNWEtN2Q3OWFjYjY1NGM2XkEyXkFqcGdeQXVyMTUyMTUzNjQ0._V1_SX300.jpg" + }, + { + "Title": "Mission Impossible Versus the Mob", + "Year": 1969, + "imdbID": "tt0063310", + "Type": "movie", + "Poster": "https://m.media-amazon.com/images/M/MV5BM2RiM2UzNmQtM2UxNS00NWI1LTkxZmEtMzAxM2M4OGY4OTBhXkEyXkFqcGdeQXVyMjUwMDUwNA@@._V1_SX300.jpg" + } + ], + "totalResults": 69, + "Response": true +} diff --git a/src/MoviegrabberSettings.ts b/src/MoviegrabberSettings.ts index f124c98..e7f6d7a 100644 --- a/src/MoviegrabberSettings.ts +++ b/src/MoviegrabberSettings.ts @@ -1,194 +1,223 @@ -import Moviegrabber from "main"; -import { PluginSettingTab, App, Setting } from "obsidian"; -import { FileSuggest } from "./interface/FileSuggester"; -import { FolderSuggest } from "./interface/FolderSuggester"; - -export interface MoviegrabberSettings { - MovieDirectory: string; - SeriesDirectory: string; - OMDb_API_Key: string; - YouTube_API_Key: string; - SwitchToCreatedNote: boolean; - - MovieTemplatePath: string; - SeriesTemplatePath: string; - - PlotLength: string; - FilenameTemplateMovie: string; - FilenameTemplateSeries: string; -} - -export const DEFAULT_SETTINGS: MoviegrabberSettings = { - MovieDirectory: 'Movies', - SeriesDirectory: 'Series', - OMDb_API_Key: '', - YouTube_API_Key: '', - SwitchToCreatedNote: true, - MovieTemplatePath: '', - SeriesTemplatePath: '', - PlotLength: 'short', - FilenameTemplateMovie: '{{Title}}', - FilenameTemplateSeries: '{{Title}}' -} - -export const DEFAULT_TEMPLATE: string = "---\n"+ - "type: {{Type}}\n"+ - `country: {{Country}}\n`+ - `title: {{Title}}\n`+ - `year: {{Year}}\n`+ - `director: {{Director}}\n`+ - `actors: [{{Actors}}]\n`+ - `genre: [{{Genre}}]\n`+ - `length: {{Runtime}}\n`+ - `seen:\n`+ - `rating: \n`+ - `found_at: \n`+ - `trailer_embed: {{YoutubeEmbed}}\n` + - `poster: "{{Poster}}"\n`+ - `availability:\n`+ - `---\n`+ - `{{Plot}}`; - -export class MoviegrabberSettingTab extends PluginSettingTab { - plugin: Moviegrabber; - - constructor(app: App, plugin: Moviegrabber) { - super(app, plugin); - this.plugin = plugin; - } - - display(): void { - const {containerEl} = this; - - containerEl.empty(); - - new Setting(containerEl) - .setName('Movie folder') - .setDesc('Folder in which to save the generated notes for series') - .addSearch((cb) => { - new FolderSuggest(cb.inputEl, this.plugin.app); - cb.setPlaceholder("Example: folder1/folder2") - .setValue(this.plugin.settings.MovieDirectory) - .onChange(async (newFolder) => { - this.plugin.settings.MovieDirectory = newFolder; - await this.plugin.saveSettings(); - }); - }); - - - new Setting(containerEl) - .setName('Series folder') - .setDesc('Folder in which to save the generated notes for series') - .addSearch((cb) => { - new FolderSuggest(cb.inputEl, this.plugin.app); - cb.setPlaceholder("Example: folder1/folder2") - .setValue(this.plugin.settings.SeriesDirectory) - .onChange(async (newFolder) => { - this.plugin.settings.SeriesDirectory = newFolder; - await this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('OMDb API key') - .setDesc('Your API key for OMDb') - .addText(text => text - .setPlaceholder('') - .setValue(this.plugin.settings.OMDb_API_Key) - .onChange(async (value) => { - this.plugin.settings.OMDb_API_Key = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Youtube API key') - .setDesc('Your API key for Youtube (optional)') - .addText(text => text - .setPlaceholder('') - .setValue(this.plugin.settings.YouTube_API_Key) - .onChange(async (value) => { - this.plugin.settings.YouTube_API_Key = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Plot length') - .setDesc('choose the plot length option for Omdb.') - .addDropdown(dropDown => dropDown - .addOption('short', 'short') - .addOption('full', 'full') - .setValue(this.plugin.settings.PlotLength) - .onChange(async (value) => { - this.plugin.settings.PlotLength = value; - await this.plugin.saveSettings(); - })) - - new Setting(containerEl) - .setName('Switch to generated notes') - .setDesc('Automatically switch to the current workspace to the newly created note') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.SwitchToCreatedNote) - .onChange(async (value) => { - this.plugin.settings.SwitchToCreatedNote = value; - await this.plugin.saveSettings(); - })); - - containerEl.createEl('h1', { text : "Templates"}) - new Setting(containerEl) - .setName('Movie template file path') - .setDesc('Path to the template file that is used to create notes for movies') - .addSearch((cb) => { - new FileSuggest(cb.inputEl, this.plugin.app); - cb.setPlaceholder("Example: folder1/folder2") - .setValue(this.plugin.settings.MovieTemplatePath) - .onChange(async (newFile) => { - this.plugin.settings.MovieTemplatePath = newFile; - await this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Series template file path') - .setDesc('Path to the template file that is used to create notes for series') - .addSearch((cb) => { - new FileSuggest(cb.inputEl, this.plugin.app); - cb.setPlaceholder("Example: folder1/folder2") - .setValue(this.plugin.settings.SeriesTemplatePath) - .onChange(async (newFile) => { - this.plugin.settings.SeriesTemplatePath = newFile; - await this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Create example template file') - .setDesc('Creates an example template file to expand and use.\nThe file is called `/Moviegrabber-example-template`') - .addButton(btn => btn - .setButtonText("Create") - .onClick((event) => { - this.plugin.CreateDefaultTemplateFile(); - })); - - new Setting(containerEl) - .setName('Movie filename template') - .setDesc('Template used for the filename of Movienotes. Used same template tags as other files.') - .addText(text => text - .setPlaceholder('') - .setValue(this.plugin.settings.FilenameTemplateMovie) - .onChange(async (value) => { - this.plugin.settings.FilenameTemplateMovie = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Series filename template') - .setDesc('Template used for the filename of Movienotes. Used same template tags as other files.') - .addText(text => text - .setPlaceholder('') - .setValue(this.plugin.settings.FilenameTemplateSeries) - .onChange(async (value) => { - this.plugin.settings.FilenameTemplateSeries = value; - await this.plugin.saveSettings(); - })); - } -} \ No newline at end of file +import Moviegrabber from "main"; +import { PluginSettingTab, App, Setting } from "obsidian"; +import { FileSuggest } from "./interface/FileSuggester"; +import { FolderSuggest } from "./interface/FolderSuggester"; + +export interface MoviegrabberSettings { + MovieDirectory: string; + SeriesDirectory: string; + OMDb_API_Key: string; + YouTube_API_Key: string; + SwitchToCreatedNote: boolean; + + MovieTemplatePath: string; + SeriesTemplatePath: string; + + PlotLength: string; + FilenameTemplateMovie: string; + FilenameTemplateSeries: string; + + enablePosterImageSave: boolean; + posterImagePath: string; +} + +export const DEFAULT_SETTINGS: MoviegrabberSettings = { + MovieDirectory: 'Movies', + SeriesDirectory: 'Series', + OMDb_API_Key: '', + YouTube_API_Key: '', + SwitchToCreatedNote: true, + MovieTemplatePath: '', + SeriesTemplatePath: '', + PlotLength: 'short', + FilenameTemplateMovie: '{{Title}}', + FilenameTemplateSeries: '{{Title}}', + enablePosterImageSave: false, + posterImagePath: '', +} + +export const DEFAULT_TEMPLATE: string = "---\n"+ + "type: {{Type}}\n"+ + `country: {{Country}}\n`+ + `title: {{Title}}\n`+ + `year: {{Year}}\n`+ + `director: {{Director}}\n`+ + `actors: [{{Actors}}]\n`+ + `genre: [{{Genre}}]\n`+ + `length: {{Runtime}}\n`+ + `seen:\n`+ + `rating: \n`+ + `found_at: \n`+ + `trailer_embed: {{YoutubeEmbed}}\n` + + `poster: "{{Poster}}"\n`+ + `availability:\n`+ + `---\n`+ + `{{Plot}}`; + +export class MoviegrabberSettingTab extends PluginSettingTab { + plugin: Moviegrabber; + + constructor(app: App, plugin: Moviegrabber) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const {containerEl} = this; + + containerEl.empty(); + + new Setting(containerEl) + .setName('Movie folder') + .setDesc('Folder in which to save the generated notes for series') + .addSearch((cb) => { + new FolderSuggest(cb.inputEl, this.plugin.app); + cb.setPlaceholder("Example: folder1/folder2") + .setValue(this.plugin.settings.MovieDirectory) + .onChange(async (newFolder) => { + this.plugin.settings.MovieDirectory = newFolder; + await this.plugin.saveSettings(); + }); + }); + + + new Setting(containerEl) + .setName('Series folder') + .setDesc('Folder in which to save the generated notes for series') + .addSearch((cb) => { + new FolderSuggest(cb.inputEl, this.plugin.app); + cb.setPlaceholder("Example: folder1/folder2") + .setValue(this.plugin.settings.SeriesDirectory) + .onChange(async (newFolder) => { + this.plugin.settings.SeriesDirectory = newFolder; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('OMDb API key') + .setDesc('Your API key for OMDb') + .addText(text => text + .setPlaceholder('') + .setValue(this.plugin.settings.OMDb_API_Key) + .onChange(async (value) => { + this.plugin.settings.OMDb_API_Key = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Youtube API key') + .setDesc('Your API key for Youtube (optional)') + .addText(text => text + .setPlaceholder('') + .setValue(this.plugin.settings.YouTube_API_Key) + .onChange(async (value) => { + this.plugin.settings.YouTube_API_Key = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Plot length') + .setDesc('choose the plot length option for Omdb.') + .addDropdown(dropDown => dropDown + .addOption('short', 'short') + .addOption('full', 'full') + .setValue(this.plugin.settings.PlotLength) + .onChange(async (value) => { + this.plugin.settings.PlotLength = value; + await this.plugin.saveSettings(); + })) + + new Setting(containerEl) + .setName('Switch to generated notes') + .setDesc('Automatically switch to the current workspace to the newly created note') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.SwitchToCreatedNote) + .onChange(async (value) => { + this.plugin.settings.SwitchToCreatedNote = value; + await this.plugin.saveSettings(); + })); + + containerEl.createEl('h1', { text : "Templates"}) + new Setting(containerEl) + .setName('Movie template file path') + .setDesc('Path to the template file that is used to create notes for movies') + .addSearch((cb) => { + new FileSuggest(cb.inputEl, this.plugin.app); + cb.setPlaceholder("Example: folder1/folder2") + .setValue(this.plugin.settings.MovieTemplatePath) + .onChange(async (newFile) => { + this.plugin.settings.MovieTemplatePath = newFile; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('Series template file path') + .setDesc('Path to the template file that is used to create notes for series') + .addSearch((cb) => { + new FileSuggest(cb.inputEl, this.plugin.app); + cb.setPlaceholder("Example: folder1/folder2") + .setValue(this.plugin.settings.SeriesTemplatePath) + .onChange(async (newFile) => { + this.plugin.settings.SeriesTemplatePath = newFile; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('Create example template file') + .setDesc('Creates an example template file to expand and use.\nThe file is called `/Moviegrabber-example-template`') + .addButton(btn => btn + .setButtonText("Create") + .onClick((event) => { + this.plugin.CreateDefaultTemplateFile(); + })); + + new Setting(containerEl) + .setName('Movie filename template') + .setDesc('Template used for the filename of Movienotes. Used same template tags as other files.') + .addText(text => text + .setPlaceholder('') + .setValue(this.plugin.settings.FilenameTemplateMovie) + .onChange(async (value) => { + this.plugin.settings.FilenameTemplateMovie = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Series filename template') + .setDesc('Template used for the filename of Movienotes. Used same template tags as other files.') + .addText(text => text + .setPlaceholder('') + .setValue(this.plugin.settings.FilenameTemplateSeries) + .onChange(async (value) => { + this.plugin.settings.FilenameTemplateSeries = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Enable Poster Image Save') + .setDesc('Toggle to enable or disable saving movie poster images in notes.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.enablePosterImageSave) + .onChange(async value => { + this.plugin.settings.enablePosterImageSave = value; + await this.plugin.saveSettings(); + }), + ); + + new Setting(containerEl) + .setName('Poster Image Path') + .setDesc('Specify the path where poster images should be saved.') + .addSearch(cb => { + new FolderSuggest(cb.inputEl, this.plugin.app); + cb.setPlaceholder("Enter the path (e.g., Movies/Posters)") + .setValue(this.plugin.settings.posterImagePath) + .onChange(async value => { + this.plugin.settings.posterImagePath = value.trim(); + await this.plugin.saveSettings(); + }); + }); + } +}