A powershell module to automate the conversion of a Goodreads library export file into individual markdown files, for each book, to use within an Obsidian vault. Inspired by jag3773's gist.
Grab a copy of the repository, either by cloning or downloading the zip, and save it to a convenient location:
PS> git clone "https://github.com/Mootix1313/ConvertFrom-GoodReads.git"
PS> Get-ChildItem
Directory: /Users/mootix1313/Downloads
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 12/1/2023 12:13 AM ConvertFrom-GoodReads
or
PS> Invoke-WebRequest "https://github.com/Mootix1313/ConvertFrom-GoodReads/archive/refs/heads/main.zip" -OutFile main.zip
PS> 7z x ./main.zip
PS> gci
Directory: /Users/mootix1313/Downloads
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 12/1/2023 12:13 AM ConvertFrom-GoodReads-main
If you cloned the repository:
PS> Import-Module ./ConvertFrom-GoodReads
If you downloaded main.zip:
PS> Import-Module ./ConvertFrom-GoodReads-main/ConvertFrom-GoodReads.psd1
ConvertFrom-GoodReads `
-goodreads_filepath <String> `
-output_filepath <String> `
-starting_index <Int32> `
-logging <CommonParameters> `
goodreads_filepath: "path\to\goodreads_library_export.csv".
Required? true Position? 1 Default value n/a Accept pipeline input? false Accept wildcard characters? false
output_filepath: "path\to\output_dir".
Required? false Position? 2 Default value "." Accept pipeline input? false Accept wildcard characters? false
starting_index: Zero-based index of the Goodreads CSV to begin processing from.
Required? false Position? 3 Default value 0 Accept pipeline input? false Accept wildcard characters? false
logging: toggles console output during execution, otherwise there's no output while processing.
Required? false Position? named Default value False Accept pipeline input? false Accept wildcard characters? false
Note: running this way should not produce output to the Host.
PS> ConvertFrom-GoodReads "path\to\goodreads_library_export.csv"`
Note: running this way should not produce output to the Host.
PS> ConvertFrom-GoodReads `
-goodreads_filepath "path\to\goodreads_library_export.csv" `
-output_filepath "path\to\output\folder"
PS> ConvertFrom-GoodReads `
-goodreads_filepath "path\to\goodreads_library_export.csv" `
-output_filepath "path\to\output\folder" `
-logging
Sample output:
[22:54:21 INF] Retrieved 242 from './goodreads_library_export.csv'...will process 242 item(s).
[22:54:22 INF] [00.41% Imported] Created booklog object for 'The Wicked + The Divine Deluxe Edition: Year Three'
[22:54:23 INF] [00.83% Imported] Created booklog object for 'Classic Horror Stories'
...
[23:07:36 INF] [60.74% Imported] Created booklog object for 'HOUSE OF LEAVES.'
[23:07:37 INF] [61.16% Imported] Created booklog object for 'Lord of the Flies'
[23:07:38 INF] [61.57% Imported] Created booklog object for 'River Marked (Mercy Thompson, #6)'
[23:07:38 INF] [61.98% Imported] Created booklog object for 'Cloaked'
[23:07:39 INF] [62.40% Imported] Created booklog object for 'The Alienist (Dr. Laszlo Kreizler, #1)'
[23:07:40 INF] [62.81% Imported] Created booklog object for 'The Lottery and Other Stories'
[23:07:41 INF] [63.22% Imported] Created booklog object for 'A Canticle for Leibowitz (St. Leibowitz, #1)'
...
[23:09:22 INF] [98.76% Processed] Wrote log for 'Romeo and Juliet' to './Reading/1597-William_Shakespeare-Romeo_and_Juliet.md'.
[23:09:22 INF] [99.17% Processed] Wrote log for 'Where the Sidewalk Ends' to './Reading/1974-Shel_Silverstein-Where_the_Sidewalk_Ends.md'.
[23:09:22 INF] [99.59% Processed] Wrote log for 'The Great Gatsby' to './Reading/1925-F._Scott_Fitzgerald-The_Great_Gatsby.md'.
[23:09:22 INF] [100.00% Processed] Wrote log for 'To Kill a Mockingbird' to './Reading/1960-Harper_Lee-To_Kill_a_Mockingbird.md'.
[23:09:22 INF] Took 04m 42.97s to process 242 item(s).
PS> ConvertFrom-GoodReads `
-goodreads_filepath "path\to\goodreads_library_export.csv" `
-output_filepath "path\to\output\folder" `
-logging
-starting_index 240
Sample output:
[22:50:10 INF] Retrieved 242 from './goodreads_library_export.csv'...will process 2 item(s).
[22:50:14 INF] [50.00% Imported] Created booklog object for 'The Great Gatsby'
[22:50:16 INF] [100.00% Imported] Created booklog object for 'To Kill a Mockingbird'
[22:50:16 INF] [50.00% Processed] Wrote log for 'The Great Gatsby' to './Reading/1925-F._Scott_Fitzgerald-The_Great_Gatsby.md'.
[22:50:16 INF] [100.00% Processed] Wrote log for 'To Kill a Mockingbird' to './Reading/1960-Harper_Lee-To_Kill_a_Mockingbird.md'.
[22:50:16 INF] Took 00m 05.7s to process 2 item(s).
PS> ConvertFrom-GoodReads `
-goodreads_filepath "path\to\goodreads_library_export.csv" `
-output_filepath "path\to\output\folder" `
-logging
Sample output:
[23:04:39 INF] Retrieved 242 from './goodreads_library_export.csv'...will process 242 item(s).
[23:04:40 ERR] Empty Gbook object for 'The Wicked + The Divine Deluxe Edition: Year Three'
[Search Query] 'https://www.googleapis.com/books/v1/volumes?projection=full&q=isbn:9781534308572+intitle:The Wicked + The Divine Deluxe Edition: Year Three'
┌───────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ CategoryInfo │ InvalidOperation: (:) [], RuntimeException │
├───────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ FullyQualifiedErrorId │ NullArray │
├───────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ScriptStackTrace │ at Search-Gbook, /Users/mootix1313/Downloads/ConvertFrom-GoodReads/Libs/search-functions.psm1: line 32 │
│ │ at Merge-BookData, /Users/mootix1313/Downloads/ConvertFrom-GoodReads/Libs/manipulate-data.psm1: line 125 │
│ │ at initialize_log, /Users/mootix1313/Downloads/ConvertFrom-GoodReads/Libs/booklog-class.psm1: line 29 │
│ │ at BookLog, /Users/mootix1313/Downloads/ConvertFrom-GoodReads/Libs/booklog-class.psm1: line 8 │
│ │ at New-BookLogObject, /Users/mootix1313/Downloads/ConvertFrom-GoodReads/Libs/booklog-class.psm1: line 43 │
│ │ at Import-GoodreadsLibrary, /Users/mootix1313/Downloads/ConvertFrom-GoodReads/Libs/manipulate-data.psm1: line 103 │
│ │ at ConvertFrom-GoodReads<Begin>, /Users/mootix1313/Downloads/ConvertFrom-GoodReads/ConvertFrom-GoodReads.psm1: line 72 │
│ │ at <ScriptBlock>, <No file>: line 1 │
└───────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
System.Management.Automation.RuntimeException: Cannot index into a null array.
at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
[23:04:40 INF] [00.41% Imported] Created booklog object for 'The Wicked + The Divine Deluxe Edition: Year Three'
...
Each booklog's metadata can have up to three sources:
- The goodreads_library_export.csv file
- A Book Volume object returned from a Google Books query
- A Works object returned from an Openlibrary query
The merged sources are distilled into a [BookLog] instance, which contains:
- The distilled book metadata
- The name of the book's log file
- The raw log contents for output
Comprises front matter (dynamic) and the book content (static). The static content fits my setup, but can be changed as needed:
$raw_log =
@"
---
$(ConvertTo-Yaml booklog.metadata)
---
~~~dataview
TABLE WITHOUT ID
("![]("+book-cover-url+")") as "Cover",
book-title as "Title",
book-author as "Author",
book-categories as "Categories"
WHERE file = this.file
~~~
---
~~~dataview
TABLE WITHOUT ID
book-tags as "Status",
avg-goodreads-rating as "Avg Rating",
my-rating as "My Rating"
WHERE file = this.file
~~~
---
##Notes
"@