Sam Patterson

FULLSTACK DEVELOPER

Automated Screenshot Hosting with jsDelivr

Published: February 11, 2025
Last updated: March 11, 2025 at 11:58 PM

In Which I Make a Cool Script

As I was writing about the Zonos TTS, I took a screenshot of the interface, then realized I didn’t have a method for displaying it in my post.

I did a lot of blogging a long time ago, but it’s been a while - what’s the best way to display an image file, anyway?

Claude gave me several recommendations. One was Cloudinary, which I have used before, but it’s proprietary and I’d rather go open source when I can. It also recommended a unique hack:

3. GitHub Issues "hack" (Super simple):

- Open a new issue in any repo
- Drag & drop or paste screenshot
- GitHub generates a permanent CDN URL
- Copy URL, close issue without saving
- Use URL in your markdown

Who says AI can’t be creative?

It also recommended jsDelivr, which I’ve never heard about. It’s a free CDN for open source projects. Sweet.

In my local directory for code repos, I creatively created a new repo, changed into it, then made it a git repo:

mkdir media
cd media
git init

This repo is public, and now I can easily link anything in the repo using a link like this:

https://cdn.jsdelivr.net/gh/[username]/[repo@branch]/path to file

I like the simplicity of the solution, but of course I don’t want to be manually adding screenshots into the Github repo, committing then pushing then copying the url. No no no I need to automate this.

So I asked Claude for help. After a few iterations, here’s the bash script we settled on:

#!/bin/bash

REPO_PATH="MY LOCAL PATH"
POST_NAME="$1"
MEDIA_PATH="$2"
MEDIA_TYPE="$3"  # 'image' or 'audio'

if [ -z "$POST_NAME" ] || [ -z "$MEDIA_PATH" ] || [ -z "$MEDIA_TYPE" ]; then
    echo "Usage: ./publish_media.sh <post-name> <file-path> <image|audio>"
    exit 1
fi

# Check file size (50MB limit for jsDelivr)
FILE_SIZE=$(stat -c %s "$MEDIA_PATH")
MAX_SIZE=$((50 * 1024 * 1024))  # 50MB in bytes

if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
    echo "Error: File size exceeds 50MB limit for jsDelivr"
    exit 1
fi

# Create directories if they don't exist
mkdir -p "$REPO_PATH/posts/$POST_NAME/$MEDIA_TYPE"

# Copy file to repo
cp "$MEDIA_PATH" "$REPO_PATH/posts/$POST_NAME/$MEDIA_TYPE/"

# Get filename
FILENAME=$(basename "$MEDIA_PATH")

# Generate markdown
JSDELIVR_URL="https://cdn.jsdelivr.net/gh/sampatt/media@main/posts/$POST_NAME/$MEDIA_TYPE/$FILENAME"

if [ "$MEDIA_TYPE" = "image" ]; then
    MARKDOWN="![Screenshot]($JSDELIVR_URL)"
elif [ "$MEDIA_TYPE" = "audio" ]; then
    MARKDOWN="<audio src=\"$JSDELIVR_URL\" controls></audio>"
fi

# Copy to clipboard
echo "$MARKDOWN" | xclip -selection clipboard

# Git commands
cd "$REPO_PATH"
git add "posts/$POST_NAME/$MEDIA_TYPE/$FILENAME"
git commit -m "Add $MEDIA_TYPE: $POST_NAME/$FILENAME"
git push

echo "Markdown copied to clipboard!"
echo "URL: $JSDELIVR_URL"

Nifty. This puts the file in the repo, adds it, commits and pushes it, then copies the jsDelivr url - in markdown format - into my clipboard so that I can just Ctrl+V into the editor where I’m writing my articles (Obsidian).

For example, here’s exactly what is automatically loaded into my clipboard after using the tool for a screenshot below:

![Screenshot](https://cdn.jsdelivr.net/gh/sampatt/media@main/posts/2025-02-11-jsDelivr/image/2025-02-10_2.png)

But how will this script trigger? I need it to only do this after a screenshot, and only when I say yes. I don’t want all my screenshots posted to a public Github!

Solution? Another bash script:

#!/bin/bash

WATCH_DIR="screenshots/temp"
MEDIA_SCRIPT="/publish_media.sh"
LAST_POST_FILE="Scripts/.last_post_name"

# Create the file if it doesn't exist with a default value
if [ ! -f "$LAST_POST_FILE" ]; then
    echo "2025-02-10-zonos" > "$LAST_POST_FILE"
fi

echo "Watching $WATCH_DIR for new screenshots..."

inotifywait -m "$WATCH_DIR" -e create -e moved_to |
    while read -r directory events filename; do
        if [[ "$filename" =~ .*png$ ]]; then
            FULL_PATH="$WATCH_DIR/$filename"
            LAST_POST=$(cat "$LAST_POST_FILE")
            
            notify-send "New Screenshot" "Screenshot saved: $filename"
            
            if zenity --question --text="Publish $filename to blog?"; then
                NEW_POST_NAME=$(zenity --entry --title="Post Name" \
                    --text="Enter post name" \
                    --entry-text="$LAST_POST")
                
                if [ -n "$NEW_POST_NAME" ]; then
                    # Save the new post name for next time
                    echo "$NEW_POST_NAME" > "$LAST_POST_FILE"
                    
                    "$MEDIA_SCRIPT" "$NEW_POST_NAME" "$FULL_PATH" "image"
                    
                    if zenity --question --text="Delete original file?"; then
                        rm "$FULL_PATH"
                        notify-send "Screenshot" "Original file deleted"
                    else
                        notify-send "Screenshot" "Original file kept"
                    fi
                fi
            fi
        fi
    done

This watches the temp screenshot directory I created specifically for this flow, and when it sees a new file, it triggers a notification. This tells me about the new screenshot, then it asks me (using zenity) if I want to publish it or not. If yes, it uses the publishing script above.

Now I don’t actually want this for my main screenshot flow, it would be a bit annoying to be asked if I want to publish each time. So instead, I kept my default screenshot tool bound to the Print Screen button, but I added a new custom keyboard shortcut.

Screenshot

It calls flameshot, a screenshot tool which allows me to set a custom path for the screenshots. That way when I use shift + Print Screen I’ll get this upload specific screenshot flow.

Screenshot

I tested it, and it works great. Now I need to automate this. Claude suggests an autostart entry.

Screenshot

I implemented it, and now I don’t need to start these scripts.

So far, it’s working like a charm, it cost me $0, and it has a keyboard binding - what more could a man want?