Created using Claude.ai by @RamenJunkie
A beautiful dark-mode microblog web application that automatically posts to Bluesky and Mastodon. Features a modern UI inspired by popular social media platforms with automatic scheduling and rich media support.
✨ Modern Dark Mode UI - Beautiful, centered interface inspired by Bluesky/Mastodon 👁️ Public Timeline - Anyone can view posts without logging in 🔐 Secure Admin Access - Single admin account with password protection ⚙️ Settings Page - Configure API credentials through web interface 📡 RSS Feed Integration - Add RSS feeds and easily share articles with commentary 📝 Multi-Platform Posting - Post to both Bluesky and Mastodon simultaneously 🖼️ Rich Media Support - Images displayed inline, links with automatic metadata fetching ⏰ Automatic Scheduling - Hourly auto-posting from queue 🔍 Searchable Archive - Full-text search across all posts with pagination 📱 Responsive Design - Works great on mobile and desktop 💾 SQLite Database - Local database for settings and user management
git clone https://github.com/ramenjunkie/flask-microblog.git
cd flask-microblog# Create a virtual environment (recommended)
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install required packages
pip install -r requirements.txtRun the application:
python app.pyVisit http://localhost:5000 in your browser. You'll be redirected to the setup page.
- Enter a username
- Enter a password (minimum 8 characters)
- Confirm your password
- Click "Create Admin Account"
- Log in with your admin account
- Click "Settings" in the navigation
- Enter your Bluesky credentials:
- Bluesky Handle: your-handle.bsky.social
- Bluesky App Password: Get from Bluesky Settings → App Passwords
- Enter your Mastodon credentials:
- Mastodon Instance URL: https://your-instance.social
- Mastodon Access Token: Get from Settings → Development → New Application
- Click "Save Settings"
Now you're ready to start posting! = 'your-access-token'
app.config['SECRET_KEY'] = 'your-random-secret-key-here'
#### Getting Bluesky Credentials:
1. Go to Bluesky Settings → App Passwords
2. Create a new app password
3. Use your handle and the generated password
#### Getting Mastodon Credentials:
1. Go to your Mastodon instance Settings → Development
2. Create a new application with `write:statuses` and `write:media` permissions
3. Copy the access token
### 4. Run the Application
```bash
python app.py
Visit http://localhost:5000 in your browser.
flask-microblog/
├── app.py # Main Flask application
├── requirements.txt # Python dependencies
├── README.md # This file
├── microblog.db # SQLite database (auto-created)
├── topost.txt # Queue (auto-created)
├── posted.txt # Archive (auto-created)
├── templates/
│ ├── base.html # Base template with header/nav
│ ├── index.html # Main page with post form (logged in)
│ ├── index_public.html # Public timeline view (not logged in)
│ ├── queue.html # Queue management page
│ ├── setup.html # Initial setup page
│ ├── login.html # Login page
│ ├── settings.html # Settings/credentials page
│ ├── rss.html # RSS feed management
│ └── rss_browse.html # Browse RSS entries
├── static/
│ └── css/
│ └── style.css # Dark mode stylesheet
├── images/ # Uploaded images (auto-created)
└── uploads/ # Additional uploads (auto-created)
- Create Admin Account - On first run, you'll be prompted to create an admin account
- Note: Only ONE admin account can be created during initial setup
- After setup is complete, the setup page is no longer accessible
- Configure Credentials - Go to Settings and enter your Bluesky and Mastodon API credentials
- Start Posting - You're ready to go!
- Anyone can view the timeline without logging in
- Visitors see all posts with images and links
- Search functionality available to all
- Only logged-in admin can create posts, manage queue, or change settings
All API credentials are stored securely in the local SQLite database:
- Navigate to Settings in the top menu
- Update your Bluesky or Mastodon credentials anytime
- Passwords are stored securely using industry-standard hashing
Text Post:
- Just enter your text in the main field
- Check "Post immediately" to post right away
- Uncheck to add to the hourly queue
- Check "Local only" to add to timeline without posting to social networks
Link Post:
- Enter text in the main field
- Add a URL in the "Link" field
- The app automatically fetches title, description, and featured image
- Creates rich link previews on both platforms (unless local only)
Image Post:
- Enter caption text
- Upload an image file (JPG, PNG, GIF, WebP)
- Image is stored locally and posted to both platforms (unless local only)
- Images are displayed inline in the timeline for all visitors
Local-Only Posts:
- Check the "Local only" checkbox
- Post appears in your timeline immediately
- Does NOT get posted to Bluesky or Mastodon
- Perfect for personal notes or drafts you want to keep locally
- View queued posts at
/queue - Posts are automatically posted every hour (if nothing else has been posted)
- Delete items from queue as needed
- Manual posts reset the 1-hour timer
- All posts are archived with timestamps
- Full-text search across content
- Paginated view (20 posts per page)
- Click links to open original URLs
Add RSS Feeds:
- Navigate to the RSS page
- Enter an RSS feed URL (e.g.,
https://example.com/feed.xml) - Optionally give it a friendly name
- Click "Add Feed"
Browse and Share Articles:
- Click "Browse" on any saved feed
- View the 15 most recent entries
- See title, summary, author, and publication date
- Add optional commentary
- Click "Add to Queue" to schedule for posting
- Articles will be posted with your commentary and a link
Manage Feeds:
- View all saved RSS feeds
- Delete feeds you no longer need
- Feeds persist between sessions in the database
Edit the auto_poster_thread() function in app.py:
if time.time() - last_auto_post_time >= 3600: # 3600 = 1 hourChange 3600 to your desired interval in seconds (e.g., 7200 for 2 hours).
Edit the index() route in app.py:
result = get_posted_entries(page=page, per_page=20) # Change 20Edit static/css/style.css to modify:
- Colors (change
#5b7ec4for primary accent) - Layout width (change
.container max-width) - Dark mode shades
- Typography
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:appCreate /etc/systemd/system/microblog.service:
[Unit]
Description=Flask Microblog
After=network.target
[Service]
User=yourusername
WorkingDirectory=/path/to/flask-microblog
Environment="PATH=/path/to/flask-microblog/venv/bin"
ExecStart=/path/to/flask-microblog/venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 app:app
Restart=always
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl enable microblog
sudo systemctl start microblog
sudo systemctl status microblogAdd to your Nginx configuration:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static {
alias /path/to/flask-microblog/static;
expires 30d;
}
location /images {
alias /path/to/flask-microblog/images;
expires 30d;
}
}sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.comInstead of hardcoding credentials in app.py, use environment variables:
import os
BLUESKY_HANDLE = os.getenv('BLUESKY_HANDLE')
BLUESKY_PASSWORD = os.getenv('BLUESKY_PASSWORD')
MASTODON_INSTANCE_URL = os.getenv('MASTODON_INSTANCE_URL')
MASTODON_ACCESS_TOKEN = os.getenv('MASTODON_ACCESS_TOKEN')
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'fallback-dev-key')Set them in your environment:
export BLUESKY_HANDLE='your-handle.bsky.social'
export BLUESKY_PASSWORD='your-app-password'
export MASTODON_INSTANCE_URL='https://your-instance.social'
export MASTODON_ACCESS_TOKEN='your-access-token'
export SECRET_KEY='your-random-secret-key'chmod 600 topost.txt posted.txt microblog.db
chmod 700 images/sudo ufw allow 5000/tcp # If exposing directly
# Or only allow nginx
### 4. Firewall Configuration
```bash
sudo ufw allow 5000/tcp # If exposing directly
# Or only allow nginx
sudo ufw allow 'Nginx Full'pip install --upgrade -r requirements.txtIssue: Redirected to setup page when user already exists
- Solution: Navigate directly to
/loginor deletemicroblog.dbto start fresh
Issue: Cannot log in to the application
- Solution: Delete
microblog.dbto reset (Note: This will also delete your API credentials) - Better Solution: Add a password reset feature or create a new admin via Python:
import sqlite3
from werkzeug.security import generate_password_hash
conn = sqlite3.connect('microblog.db')
c = conn.cursor()
c.execute('UPDATE users SET password_hash = ? WHERE username = ?',
(generate_password_hash('new-password'), 'admin'))
conn.commit()
conn.close()Issue: Cannot fetch RSS feed entries
- Solution: Verify the RSS feed URL is correct and accessible
- Check if the feed is valid RSS/Atom format
- Test the feed URL in an RSS reader first
- Check console for feedparser errors
Issue: "Invalid RSS feed" error
- Solution: Ensure the URL points to an actual RSS/Atom feed (usually ends in .xml, .rss, or /feed)
- Some sites have RSS feeds at
/feed,/rss, or/atom - Check if the site blocks automated requests
Issue: Published dates appear incorrect
- Solution: This is the date from the feed itself - contact the feed publisher if incorrect
- Verify credentials are correct in Settings page
- Check console output for error messages
- Test network connectivity to both platforms
- Ensure API permissions are correct (Mastodon needs
write:statusesandwrite:media)
- Check
images/folder exists and is writable - Verify file size is under 16MB
- Ensure file format is supported (JPG, PNG, GIF, WebP)
- Check console for PIL/Pillow errors
- Ensure Flask app is running continuously (not just for testing)
- Check
topost.txthas content - Verify background thread started (check console on startup)
- Ensure at least 1 hour has passed since last post
# Find process using port 5000
lsof -i :5000
# Kill the process
kill -9 <PID>
# Or use a different port
python app.py --port 5001- Flask 3.0.0 - Web framework
- atproto 0.0.46 - Bluesky API client
- Mastodon.py 1.8.1 - Mastodon API client
- requests 2.31.0 - HTTP library
- beautifulsoup4 4.12.2 - HTML parsing for metadata
- Pillow 10.1.0 - Image processing
- feedparser 6.0.10 - RSS/Atom feed parsing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is open source and available under the MIT License.
- UI design inspired by Bluesky and Mastodon
- Built with Flask and modern web technologies
- Thanks to the atproto and Mastodon.py library maintainers
For issues, questions, or suggestions:
- Open an issue on GitHub
- Check existing issues for solutions
- Review the troubleshooting section above
Happy Posting! 🚀