Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/selfire1/goodsidian

A Goodreads to Obsidian script
https://github.com/selfire1/goodsidian

goodreads obsidian-md

Last synced: 3 months ago
JSON representation

A Goodreads to Obsidian script

Awesome Lists containing this project

README

        

# Goodsidian
Goodsidian takes updates to your shelves on [Goodreads](https://www.goodreads.com/) and formats them to a note in [Obsidian](https://obsidian.md/).

> You can help me keep creating tools like this by [buying me a coffee](https://www.buymeacoffee.com/joschua).

Buy Me A Coffee

## Help wanted! 🤝
I'd love to turn this into a proper plugin for Obsidian but my TypeScript knowledge is pretty *lacking*.

If you'd like to collaborate/contribute on that, head to the [Goodsidian plugin](https://github.com/selfire1/goodsidian-plugin) repo and reach out!

## Overview
Goodsidian extracts data from your "currently-reading" and "read" Goodreads rss feeds. That data then gets formatted and creates (if a new book) or updates (if book is read) a note in your Obsidian vault.

![Goodsidian overview picture](https://github.com/selfire1/goodsidian/blob/master/images/g-to-obs.png?raw=true)

**Disclaimer**: Never run a script without knowing what it does. Make sure you understand the script and back up your vault. I use this script on my own vault but cannot guarantee no data losses or unintended changes.

If you find value in this resource, consider sponsoring my next read on Patreon.

## Setting up the script
This master branch of this script is written for bash on MacOS. Thanks to [bboerzel](https://github.com/selfire1/goodsidian/issues/1)'s contribution you can find the script for linux bash on [another branch](https://github.com/selfire1/goodsidian/tree/linux-bash).

## Variables
You can find the url to your Goodreads RSS feed by navigating to one of your shelfes and clicking the "RSS" button at the bottom of the page.
* for `url` enter the Goodreads rss url for the "currently-reading" shelf
* for `readurl` enter the Goodreads rss url for the "read" shelf
* for `vaultpath` enter the path to your vault

## Name of your bookshelf
Open your Goodreads rss feed. It will say something like "Joschua's bookshelf" in the beginning. Replace `Joschua.s bookshelf` in line 24 in the script with the name of your bookshelf.

## Adapt your format
### Currently reading
For a book that's being currently read the default output will look like this:

![Format of a currently reading book note](https://github.com/selfire1/goodsidian/blob/master/images/reading%20example.png?raw=true)

For your use case you will surely want to change this. You can do so with the following variables:
* `${title}`: Self-explanatory. The title.
* `${author}`: The author.
* `${bookid}`: The Goodreads bookid. Keep this in your frontmatter, so that the script can check for books marked as "read"
* `${imglink}`: The link to the book cover.
* `${pub}`: Year published

### Read
When a book is marked 'read' in the Goodreads rss feed, the script checks if a note has a corresponding Goodreads bookid. If so, it removes the `#currently-reading` tag, adds a `#read` tag. It also appends two lines for "Year read" and "Month read" after the "Year published" line.

So when tinkering with the formatting, be aware that the script targets the bookid and the "Year read" lines. If you delete or change these around, the script might have unintended consquences.

## Running the script
If you're unfamiliar with file navigation and executing a script, [here](https://joschuasgarden.com/Shell#File+navigation) is a short overview.

Otherwise, set the script as executable and run it. It might take a bit but then should notify you on new books read or currently-reading book notes created.

Forks and improvements are welcome. If you have a problem, don't hesitate to raise an issue or shoot me a message at [email protected]. I'll do my best to help out.

## Detailed script commentary

First, the script sets the urls and path to vault as variables. It also assigns the current time to variables.
```bash
# url for "Currently reading":
url="https://www.goodreads.com/url-to-your-rss-feed-shelf=currently-reading"
# url for "Read":
readurl="https://www.goodreads.com/url-to-your-rss-feed-shelf=read"

# Enter the path to your Vault
vaultpath="Path/to/your/vault"

# Assign times to variables
year=$(date +%Y)
nummonth=$(date +%m)
month=$(date +%B)
```

Next up the script grabs the data from the rss feed and takes out the items that we need. First from the "currently-reading" sheld and then from the "read" shelf:
```bash
# This grabs the data from the currently reading rss feed and formats it
IFS=$'\n' feed=$(curl --silent "$url" | grep -E '(title>|book_large_image_url>|author_name>|book_published>|book_id>)' | \
sed -e 's///' \
-e 's/Joschua.s bookshelf: currently-reading//' \
-e 's///' -e 's/<\/book_large_image_url>/ | /' \
-e 's///' -e 's/<\/title>/ | /' \
-e 's///' -e 's/<\/author_name>/ | /' \
-e 's///' -e 's/<\/book_published>/ | /' \
-e 's///' -e 's/<\/book_id>/ | /' \
-e 's/^[ \t]*//' -e 's/[ \t]*$//' | \
tail +3 | \
fmt
)

# Grab the bookid from READ data from the url and format it
IFS=$'\n' readfeed=$(curl --silent "$readurl" | grep -E '(book_id>)' | \
sed -e 's///' -e 's/<\/book_id>/ | /' \
-e 's/^[ \t]*//' -e 's/[ \t]*$//' | \
fmt
)
```

Next up we turn the data into an array, a list essentially. Each item that we pulled from the rss gets split up. We also remove leading and trailing whitespace.
```bash
# Turn the data into an array
arr=($(echo $feed | tr "|" "\n")) # CURRENTLY-READING
readarr=($(echo $readfeed | tr "|" "\n")) # READ

# Remove whitespace on each element: CURRENTLY-READING
for (( i = 0 ; i < ${#arr[@]} ; i++ ))
do
arr[$i]=$(echo "${arr[$i]}" | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//')
done

# Remove whitespace on each element: READ
for (( i = 0 ; i < ${#readarr[@]} ; i++ ))
do
readarr[$i]=$(echo "${readarr[$i]}" | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//')
done
```

Since there are five elements we extracted (Title, bookid, author, image link and year published) we can divide the length of our list by five and get the amount of new books.

Then we start a loop for the amount of new books. In each iteration we check if a note for this book exists already (by checking looking for a note with corresponding bookid). If there is one, we delete the book from our array since we don't need a second note on a book.

```bash
# Get the amount of books by dividing array by 5
bookamount=$( expr "${#arr[@]}" / 5)

for (( i = 0 ; i < ${bookamount} ; i++ ))
do
# Create a temporary counter to loop through books
counter=$( expr "$i" \* 5)

# Set variables
bookid=${arr[$( expr "$counter" + 1)]}

# Check if book already exists in note by bookid

if grep -q "${bookid}" -r "${vaultpath}"
then
# code if found
unset arr["$counter"]
unset arr[$( expr "$counter" + 1)]
unset arr[$( expr "$counter" + 2)]
unset arr[$( expr "$counter" + 3)]
unset arr[$( expr "$counter" + 4)]

# code if not found

fi
done
```

We then have to reindex our array to find the length. By dividing through five again, we can see how many books we have left. If there are none left, a notification is shown.

```bash
# Reindex array to take away gaps
for i in "${!arr[@]}"; do
new_array+=( "${arr[i]}" )
done
arr=("${new_array[@]}")
unset new_array

# Get the amount of books by dividing array by 5
bookamount=$( expr "${#arr[@]}" / 5)

if (( "$bookamount" == 0 )); then
osascript -e "display notification \"No new books found.\" with title \"Currently-reading: No update\""
fi
```

For all remaining books we set our variables. The script also deletes illegal characters (':' and '/') and unwanted ('#'). Hash characters are used by Obsidian to denote headers.

```bash
# Start the loop for each book
for (( i = 0 ; i < ${bookamount} ; i++ ))
do

counter=$( expr "$i" \* 5)

# Set variables
title=${arr["$counter"]}
bookid=${arr[$( expr "$counter" + 1)]}
imglink=${arr[$( expr "$counter" + 2)]}
author=${arr[$( expr "$counter" + 3)]}
pub=${arr[$( expr "$counter" + 4)]}

# Delete illegal (':' and '/') and unwanted ('#') characters
cleantitle=$(echo "${title}" | sed -e 's/\///' -e 's/:/ –/' -e 's/#//')
```

Then a booknote gets created for each new book. We show a notification for each created book.

```bash
# Write the contents for the book file

if [[ "$cleantitle" == "" ]];
then
osascript -e "display notification \"Failed to create note due to empty array.\" with title \"Error!\""
else
echo "---
bookid: ${bookid}
---

links: [[Books MOC]]
#currently-reading
# ${title}
![b|150](${imglink})
* Type: #book/
* Universe/Series: ADD SERIES
* Author: [[${author}]]
* Year published: [[${pub}]]" >> "${vaultpath}/${cleantitle}.md"

# Display a notification when creating the file
osascript -e "display notification \"Booknote created!\" with title \"${cleantitle//\"/\\\"}\""
fi

done
```

Next up we get into the meat of things. We now check if any book notes that were "currently-reading" now have been read.

We take all bookids from the 'read' shelf and check if there is any note that contains them and is marked with the tag `#currently-reading`.

For any that are found the `#currently-read` tag gets replaced by a `#read` tag and we also add a line for "Year read" and a line for "Month read".

A notification gets displayed if anything has been found.

```bash
ifbookid=$(find "${vaultpath}" -type f -print0 | xargs -0 grep -li "${cbookid}")
ifcurrread=$(find "${vaultpath}" -type f -print0 | xargs -0 grep -li "#currently-reading")

if find "${vaultpath}" -type f -print0 | xargs -0 grep -li "${cbookid}"
then
# Code if found: update read books
fname=$(find "${vaultpath}" -type f -print0 | xargs -0 grep -li "${cbookid}")
sed -i '' "/Year published: \[\[[0-9][0-9][0-9][0-9]\]\]/ a\\
\* Year read: #read${year}" "$fname"
sed -i '' "/Year read: #read${year}/ a\\
\* Month read: [[${year}-${nummonth}-${month}|${month} ${year}]]" "$fname"
sed -i '' -e 's/#currently-reading/#read/' "$fname"

# Grab the name of the changed book
fname=$(echo ${fname} | sed 's/^.*\///' | sed 's/\.[^.]*$//')
osascript -e "display notification \"${fname}\" with title \"Updated read books\""
else
# code if not found: No new books
osascript -e "display notification \"No new read books.\" with title \"Read: No update\""
fi

#circle through bookid array
cbookid=${readarr["$i"]}

# If in the path to the vault, there is a file with the current id, then …
if find "${vaultpath}" -not -path "*/\.*" -type f \( -iname "*.md" \) -print0 | xargs -0 grep -li "${cbookid}"
then
# … set variable fname to that file
fname=$(find "${vaultpath}" -not -path "*/\.*" -type f \( -iname "*.md" \) -print0 | xargs -0 grep -li "${cbookid}")
# Check if it has tag "#currently-reading"
if grep "#currently-reading" "${fname}"
then
# If yes, change the formatting, delete the "#currently-reading" tag
sed -i '' "/Year published: \[\[[0-9][0-9][0-9][0-9]\]\]/ a\\
\* Year read: #read${year}" "$fname"
sed -i '' "/Year read: #read${year}/ a\\
\* Month read: [[${year}-${nummonth}-${month}|${month} ${year}]]" "$fname"
sed -i '' -e 's/#currently-reading/#outline \/ #welcome/' "$fname"

# Grab the name of the changed book
declare -i updatedbooks; updatedbooks+=1
fname=$(echo ${fname} | sed 's/^.*\///' | sed 's/\.[^.]*$//')
# Show notification
osascript -e "display notification \"${fname}\" with title \"Updated read books\""
fi
fi
done

# code if not found: No new books
if [[ ${updatedbooks} = "" ]]
then
osascript -e "display notification \"No new read books.\" with title \"Read: No update\""
fi
```