This project uses a CI workflow to automatically publish bioinformatics blog posts from selected RSS feeds to a Bluesky account every day at 09:00 UTC. Each run scans for posts published within the previous 24 hours and shares them on Bluesky with rich link previews, ensuring no duplicates are posted and avoiding the need to track previously published entries.
The use case here is bioinfoblogs.
pip install atproto pyyamlOr add to your pyproject.toml:
dependencies = [
"feedparser>=6.0.12",
"jinja2>=3.1.6",
"atproto>=0.0.55",
"pyyaml>=6.0",
]- Log in to Bluesky
- Go to Settings → App Passwords
- Create a new app password
- Save it securely
- Go to your repository on GitHub
- Navigate to Settings → Secrets and variables → Actions
- Click New repository secret
- Add two secrets:
- Name:
BLUESKY_USERNAME
Value:your-handle.bsky.social(without the @) - Name:
BLUESKY_PASSWORD
Value: Your Bluesky app password created in step 2
- Name:
These secrets will be securely used by the GitHub Actions workflow.
To see what would be published without actually posting:
python bluesky_publisher.py \
--username your-handle.bsky.social \
--password your-app-password \
--dry-runpython bluesky_publisher.py \
--username your-handle.bsky.social \
--password your-app-password--hours N: Fetch posts from the last N hours (default: 24)--dry-run: Test mode without actual publication
Create .github/workflows/bluesky.yml:
name: Publish to Bluesky
on:
schedule:
# Run every day at 9 a.m. UTC
- cron: '0 9 * * *'
workflow_dispatch: # Allows manual execution
inputs:
dry_run:
description: 'Dry-run - does not actually publish'
required: false
type: boolean
default: false
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
run: |
pip install feedparser atproto pyyaml
- name: Publish to Bluesky
env:
BLUESKY_USERNAME: ${{ secrets.BLUESKY_USERNAME }}
BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }}
run: |
python bluesky_publisher.py \
--username "$BLUESKY_USERNAME" \
--password "$BLUESKY_PASSWORD" \
--hours 24 \
${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}- The script reads RSS feeds from feeds.yaml
- It fetches only posts published in the last 24 hours (configurable with
--hours) - It publishes each post on Bluesky with:
- The article title
- The source (blog name)
- A rich link to the article
- It saves published URLs to avoid duplicates
Example of a generated post:
📝 A new method for genome assembly
✍️ Dave Tang's blog
🔗 https://davetang.org/muse/...
RSS feeds are defined in feeds.yaml:
feeds:
- name: "Blog name"
url: "https://example.com/feed.xml"
- name: "Another blog"
url: "https://example2.com/rss"To add a new feed, simply edit this file.
- Always use
--dry-runfirst to check what will be published - Adjust
--hoursaccording to your CI execution frequency - If CI runs every 24h, use
--hours 24to avoid duplicates