nostrX
Self-hosted Python tool to automatically sync Nostr posts to Twitter/X with media support and state tracking.
File Browser:
- 📄 env.example
- 📄 LICENSE
- 📄 nostrx.py
- 📄 README.md
- 📄 requirements.txt
README
NostrX
A local, self-hosted tool to automatically sync your Nostr posts to Twitter/X.
Features
- Runs locally - No need to trust third-party services
- One-Way Sync - Reads from Nostr, posts to Twitter
- Stateful - Remembers where it left off (runs efficiently via cron)
- Media Support - Downloads images/videos from Nostr and uploads them to Twitter natively
- Filters replies - Only posts top-level notes
Setup
1. Install Dependencies
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install requirements
pip install -r requirements.txt
2. Configure Twitter/X Developer App (Crucial Step!)
You need a free Twitter Developer account to get API keys.
Dont worry its FREE.
- Go to developer.twitter.com and sign up.
- Create a Free project/app.
- Configure Permissions (IMPORTANT):
- Go to Projects & Apps > [Your project name] > User authentication settings.
- Click Edit.
- App permissions: Change to "Read and Write".
- Type of App: Select "Web App, Automated App or Bot".
- Callback URI / Redirect URL: Enter
http://localhost(required but unused). - Website URL: Enter your Twitter profile URL (e.g.,
https://twitter.com/yourhandle). - Click Save.
- Get Your Keys:
- Go to the Keys and Tokens tab.
- API Key and Secret: Copy these.
- Access Token and Secret: IMPORTANT: If you just changed permissions, you MUST click Regenerate here to get new tokens with "Write" access. Old tokens will fail.
- Note: You can ignore "OAuth 2.0 Client ID" and "Client Secret". You only need the 4 keys above.
3. Configure Environment Variables
Copy the example file to create your own configuration:
cp env.example .env
Open .env and paste your keys:
# .env file
TWITTER_API_KEY=your_api_key
TWITTER_API_SECRET=your_api_secret
TWITTER_ACCESS_TOKEN=your_access_token
TWITTER_ACCESS_SECRET=your_access_secret
4. Configure Your Nostr Account
Add your npub to the .env file:
NOSTR_NPUBS=npub1...
You can add multiple npubs by separating them with commas:
NOSTR_NPUBS=npub1...,npub1...
Optional: Custom Relays
By default, NostrX connects to 4 popular relays. You can add your own by uncommenting and editing the NOSTR_RELAYS line in .env:
NOSTR_RELAYS=wss://relay.damus.io,wss://nos.lol,wss://your-relay.com
Add as many relays as you want (comma-separated). More relays = better chance of finding your posts.
Usage
Run the script manually or via cron:
./venv/bin/python nostrx.py
First Run: It will check the last 24 hours of posts. Subsequent Runs: It will only check for new posts since the last sync.
Sync Behavior
First Run
When you run the tool for the very first time (and sync_state.json doesn't exist):
- It defaults to syncing posts from the last 24 hours only.
- This is a safety feature to prevent spamming your Twitter account with years of history.
Want to sync more history?
Open nostr_crossposter.py and find this line (around line 60):
"last_synced_timestamp": int(time.time()) - 86400,
Change 86400 (24 hours in seconds) to a larger number.
- 604800 = 7 days
- 2592000 = 30 days
Subsequent Runs
- The tool creates a file called
sync_state.json. - It records the exact timestamp of the last post it successfully synced.
- Next time you run it, it continues exactly where it left off, ensuring no posts are missed and no duplicates are created.
Twitter Free Tier Limits
If you are using a Free Twitter Developer account, be aware of these limits:
- 500 posts per month (approx. 17 posts per day).
- If you exceed this, the script will get a 403 Forbidden error.
- Recommendation: Do not set the initial history lookback too far back (e.g., don't try to sync last year's posts), or you will burn through your monthly limit immediately.
How it Works (Technical)
- State Tracking: Uses
sync_state.jsonto store the last synced Unix timestamp and a list of recent event IDs for deduplication. - Media: Automatically detects image/video URLs in your Nostr notes, downloads them to a temporary file, and uploads them to Twitter as native media attachments.
- One-Way: Strictly reads from Nostr and writes to Twitter. It does not read your Tweets.
- Character Limit: Posts longer than 280 characters are automatically truncated to 277 characters with "..." appended.
Credits & Inspiration
This tool was inspired by nos-crossposting-service by Planetary Social.
While that project is a centralized web service written in Go, this tool is a lightweight, self-hosted Python alternative designed for personal use.