Last active
September 26, 2025 19:55
-
-
Save brunoamaral/4123e22a5ee12362afc0f70882c66540 to your computer and use it in GitHub Desktop.
Bash script to export bookmarks from Shiori as obsidian notes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Shiori is a good way to bookmark and archive webpage: https://github.com/go-shiori/shiori | |
# You can self-host it and use this script if you want your bookmarks as obsidian notes. | |
# This script assumes that shiori is running using docker. | |
# | |
# Usage: ./convert_shiori_to_obsidian.sh output_directory | |
# Example: ./convert_shiori_to_obsidian.sh ./obsidian_notes/ | |
output_dir="$1" | |
if [ $# -ne 1 ]; then | |
echo "Usage: $0 <output_directory>" | |
echo "Example: $0 ./obsidian_notes/" | |
exit 1 | |
fi | |
echo "Fetching bookmarks from Shiori..." | |
# Get bookmarks from Shiori Docker container | |
if ! docker exec shiori shiori list > /tmp/shiori_bookmarks.txt 2>/dev/null; then | |
echo "Error: Failed to fetch bookmarks from Shiori container" | |
echo "Make sure Docker is running and the 'shiori' container exists" | |
exit 1 | |
fi | |
input_file="/tmp/shiori_bookmarks.txt" | |
# Database path | |
DB_PATH="/home/brunoamaral/containers/Shiori/data/shiori.db" | |
# Create output directory if it doesn't exist | |
mkdir -p "$output_dir" | |
# Function to get existing bookmark IDs from output directory | |
get_existing_ids() { | |
if [ -d "$output_dir" ]; then | |
# Find all .md files and extract id from frontmatter | |
find "$output_dir" -name "*.md" -type f -exec awk '/^---$/{frontmatter=!frontmatter; next} frontmatter && /^id:/{gsub(/^id:[[:space:]]*/, ""); print; exit}' {} \; 2>/dev/null | grep -v '^$' | |
fi | |
} | |
# Function to check if bookmark ID already exists | |
id_exists() { | |
local id_to_check="$1" | |
if [ -f "$existing_ids_file" ]; then | |
grep -q "^${id_to_check}$" "$existing_ids_file" | |
return $? | |
fi | |
return 1 # ID doesn't exist if file doesn't exist | |
} | |
# Get existing bookmark IDs to avoid duplicates | |
echo "Scanning existing notes for bookmark IDs..." | |
existing_ids_file="/tmp/existing_bookmark_ids.txt" | |
get_existing_ids > "$existing_ids_file" | |
existing_count=$(wc -l < "$existing_ids_file" 2>/dev/null || echo "0") | |
echo "Found $existing_count existing bookmark(s)" | |
# Initialize variables | |
bookmark_id="" | |
title="" | |
url="" | |
description="" | |
tags="" | |
new_bookmarks_count=0 | |
skipped_bookmarks_count=0 | |
# Function to create obsidian note | |
create_note() { | |
if [ -n "$bookmark_id" ] && [ -n "$title" ]; then | |
# Check if this bookmark ID already exists | |
if id_exists "$bookmark_id"; then | |
# echo "Skipping bookmark ID $bookmark_id (already exists): $title" | |
((skipped_bookmarks_count++)) | |
return | |
fi | |
# Clean title for filename (remove special characters and truncate) | |
filename=$(echo "$title" | sed 's/[^a-zA-Z0-9 ]//g' | sed 's/ */ /g' | sed 's/^ *//;s/ *$//') | |
filename="${filename// /_}" | |
# Truncate filename to max 120 characters (leaving room for .md extension) | |
if [ ${#filename} -gt 116 ]; then | |
filename="${filename:0:116}" | |
fi | |
filename="${filename}.md" | |
# Get content from SQLite database | |
content=$(sqlite3 "$DB_PATH" "select bc.content from bookmark b left join bookmark_content bc on b.id = bc.docid where b.id=${bookmark_id};" 2>/dev/null) | |
# Trim leading and trailing whitespace from content | |
content=$(echo "$content" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
# Create note content | |
cat > "$output_dir/$filename" << EOF | |
--- | |
id: $bookmark_id | |
kind: bookmark | |
url: $url | |
tags: [$tags] | |
created: $(date -Iseconds) | |
--- | |
# $title | |
$description | |
## Link | |
[$title]($url) | |
## Tags | |
$tags | |
## Content | |
$content | |
EOF | |
echo "Created: $filename (ID: $bookmark_id)" | |
((new_bookmarks_count++)) | |
fi | |
} | |
# Process the input file | |
while IFS= read -r line; do | |
if [[ $line =~ ^[0-9]+\. ]]; then | |
# If we have a previous bookmark, create its note | |
create_note | |
# Extract bookmark ID and title | |
bookmark_id=$(echo "$line" | sed 's/^\([0-9]*\)\..*$/\1/') | |
title=$(echo "$line" | sed 's/^[0-9]*\. *\(.*\)$/\1/') | |
# Reset other variables | |
url="" | |
description="" | |
tags="" | |
elif [[ $line =~ ^[[:space:]]*\> ]]; then | |
# Extract URL | |
url=$(echo "$line" | sed 's/^[[:space:]]*> *\(.*\)$/\1/') | |
elif [[ $line =~ ^[[:space:]]*\+ ]]; then | |
# Extract description | |
description=$(echo "$line" | sed 's/^[[:space:]]*+ *\(.*\)$/\1/') | |
elif [[ $line =~ ^[[:space:]]*# ]]; then | |
# Extract tags | |
tags=$(echo "$line" | sed 's/^[[:space:]]*# *\(.*\)$/\1/') | |
fi | |
done < "$input_file" | |
# Don't forget the last bookmark | |
create_note | |
# Clean up temporary files | |
rm -f "/tmp/shiori_bookmarks.txt" | |
rm -f "$existing_ids_file" | |
echo "Conversion complete! Notes saved to: $output_dir" | |
echo "Statistics:" | |
echo " - Existing bookmarks: $existing_count" | |
echo " - New bookmarks created: $new_bookmarks_count" | |
echo " - Bookmarks skipped (already exist): $skipped_bookmarks_count" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment