Type something to search...

From Blog Post to BlueSky and Back

From Blog Post to BlueSky and Back

Thanks to the power of GitHub Copilot’s Agent mode, I’ve recently created a new blog using Astro and ported all 521 of my old blog posts from a HTML archive into the new site.

Thanks to the amazing work by whitep4nth3r and with inspiration from Ashley Willis I’ve added integration with BlueSky to show likes and comments. But that requires me to remember to post the blog to BlueSky, manually copy the post ID and then republish the site.

This evening I’ve been tinkering with an automated workflow that bridges the gap between my blog and BlueSky social media platform. The result is a system that automatically posts new blog entries to BlueSky using AI-generated social media text powered by GitHub Models, then updates the blog post to display likes and comments from the social platform.

It’s still a work in progress that I’m ironing out, but here’s how it all works currently.

Automated Social Integration for GitHub Pages

I built an automated pipeline using GitHub Actions and GitHub Models that handles the entire workflow:

  1. Detect new blog posts when they’re published
  2. Generate engaging social media text using AI
  3. Post automatically to BlueSky with a link back to the blog
  4. Update the blog post with the BlueSky post identifier
  5. Display social engagement directly on the blog through embedded likes and comments

How It Works: The Technical Details

Step 1: Detecting New Posts

The workflow starts in my GitHub Actions CI pipeline (/.github/workflows/ci.yml). When I push a new blog post, the action detects changes in the src/content/post/ directory:

# Get changed files and find new posts
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD)
NEW_POSTS=$(echo "$CHANGED_FILES" | grep "^src/content/post/.*\.md$")

# Check if any new posts don't have blueskyPostURI in frontmatter
for post in $NEW_POSTS; do
  # Extract only the frontmatter (between --- markers) to avoid false positives
  FRONTMATTER=$(awk '/^---$/{if(seen){exit} seen=1; next} seen{print}' "$post")
  if ! echo "$FRONTMATTER" | grep -q "blueskyPostURI:"; then
    # This post needs BlueSky integration
  fi
done

Step 2: AI-Generated Social Media Text

This is where the magic happens. With the appropriate permission granted, GitHub Actions can talk to GitHub Models using the GITHUB_TOKEN. There is a pretty healthy free allowance and a huge set of models. At the moment I’m using GPT-4.1 to analyses the blog post content and generates contextual social media text. Probably overkill on the model front and could go for a smaller one - but working great for now:

# Extract post metadata and content
POST_TITLE=$(grep "^title:" "$post_file" | sed 's/title: *"//' | sed 's/"$//')
POST_DESCRIPTION=$(grep "^description:" "$post_file")
POST_CATEGORIES=$(grep "^categories:" "$post_file")

# Create enhanced prompt with post context
PROMPT="Write a short, engaging social media post (under 250 characters) for a new blog post. 
Use UK english, never use emdash and only use hashtags sparingly. 
Title: $POST_TITLE. Description: $POST_DESCRIPTION. Categories: $POST_CATEGORIES. 
Make it conversational and include relevant hashtags. Don't include the URL."

# Call GitHub Models API
AI_RESPONSE=$(curl -s "https://models.github.ai/inference/chat/completions" \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -d "{
    \"messages\": [{\"role\": \"user\", \"content\": \"$PROMPT\"}],
    \"model\": \"openai/gpt-4.1\"
  }")

The AI considers the post’s title, description, categories, and even a preview of the content to craft engaging, contextual social media text that feels natural and personal.

Step 3: Posting to BlueSky

Once the AI generates the social media text, the workflow posts it to BlueSky using the excellent bluesky-post-action:

- name: Post to Bluesky
  uses: zentered/bluesky-post-action@v0.1.0
  id: bluesky-post
  with:
    post: ${{ steps.process-posts.outputs.BLUESKY_TEXT }}
  env:
    BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }}
    BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }}

Step 4: Updating the Blog Post

The BlueSky action returns a URI for the created post, which the workflow automatically adds to the blog post’s frontmatter:

# Update the post file with the Bluesky URI
sed -i "/^draft: false$/a blueskyPostURI: \"${{ steps.bluesky-post.outputs.uri }}\"" "$POST_FILE"

# Commit the changes
git add src/content/post/
git commit -m "Add Bluesky post URI to new blog post"
git push

Step 5: Rebuilding with Social Integration

The workflow then rebuilds and redeploys the site with the updated frontmatter, which enables the BlueSky integration component.

The BlueSky Likes Component

The real magic happens in the browser with the BlueSky Likes component (massive credit to whitep4nth3r and ashleymcnamara for inspiration). This Astro component:

  1. Fetches social data from BlueSky’s public API
  2. Displays user avatars of people who liked the post
  3. Shows threaded comments from the BlueSky discussion
  4. Provides interactive elements for engaging with the social post

The component uses the AT Protocol URI stored in the post’s frontmatter to query BlueSky’s public API endpoints:

// Fetch likes and comments
const getLikesURL = `${BSKY_PUBLIC_API}app.bsky.feed.getLikes?uri=${postURI}`;
const getPostURL = `${BSKY_PUBLIC_API}app.bsky.feed.getPosts?uris=${postURI}`;
const threadURL = `${BSKY_PUBLIC_API}app.bsky.feed.getPostThread?uri=${postURI}`;

The UK English Touch

Just my cup of tea

One delightful detail: the AI is specifically prompted to use UK English spelling and avoid em-dashes (which I find jarring in social media contexts). This ensures the generated content maintains consistency with my writing style and preferences. I can tweak the prompt as we go to make it more like me.

The entire system is open source and built on GitHub’s infrastructure, making it accessible to any developer with a GitHub login and a BlueSky account.

This post hopefully demonstrates the system in action - it was automatically posted to BlueSky with AI-generated text, and you should see the social engagement displayed below. You can find the source of my CI workflow here.

Loading likes...

Comments

Loading comments...