GigaProjects

← Back to Projects

nostrX

Self-hosted Python tool to automatically sync Nostr posts to Twitter/X with media support and state tracking.

File Browser:

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.

  1. Go to developer.twitter.com and sign up.
  2. Create a Free project/app.
  3. 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.
  4. 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.json to 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.