> For the complete documentation index, see [llms.txt](https://bot-docs.yopro.studio/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bot-docs.yopro.studio/getting-started/twitter-to-bluesky-mirror.md).

# Twitter to Bluesky Mirror

Made by Adam / Yoproo20 / yopro.studio

## What It Does

This project is a self-hosted bot that continuously monitors a target Twitter/X account and mirrors its posts to a Bluesky account. When a new tweet is detected, the bot:

1. Downloads any attached media (images/videos)
2. Optionally tralstaed the tweet text via RapidAPI
3. Posts the content to Bluesky using the AT Protocol (`atproto`)
4. Saves state so it never reposts a tweet it has already mirrored

The bot is operated entirely from the command line and is configured through a single `.env` file.

***

## Two-Phase Operation

The script operates in two distinct phases that must be run in order.

{% code lineNumbers="true" expandable="true" %}

```mermaid
flowchart LR

    subgraph Phase_1_One_Time
        A[python setup.py] --> B[env file written]
    end

    subgraph Phase_2_Continuous
        C[python main.py] --> D[load_config]
        D --> E[init_twitter_app]
        D --> F[init_bluesky_client]
        E --> G[monitor_tweets]
        F --> G
        G --> G
    end

    B --> C
```

{% endcode %}

<table><thead><tr><th>Phase</th><th>Script</th><th width="250">Frequency</th><th>Output</th></tr></thead><tbody><tr><td>Setup</td><td><code>setup.py</code></td><td>Once</td><td><code>.env</code> file with all credentials</td></tr><tr><td>Runtime</td><td><code>main.py</code></td><td>Continuously</td><td>Posts to Bluesky, <code>state.json</code>,<code>events.log</code></td></tr></tbody></table>

***

## Repository Layout

| File               | Role                                                                    |
| ------------------ | ----------------------------------------------------------------------- |
| `setup.py`         | Interactive configuration wizard; writes `.env`                         |
| `main.py`          | Main bot runtime; polling loop and all core logic                       |
| `updater.py`       | Auto-update mechanism; fetches new `.py` files from GitHub              |
| `requirements.txt` | Python dependency list                                                  |
| `.env`             | Credentials and runtime configuration (generated by `setup.py`)         |
| `state.json`       | Persists `last_tweet_id` and `last_update_check` (generated at runtime) |
| `events.log`       | Runtime log output                                                      |
| `version.txt`      | Current commit SHA for update tracking (if update checking is enabled)  |
| `session.txt`      | Cached Bluesky session token                                            |

***

## External Service Dependencies

The bot integrates with 3 services at runtime

{% code overflow="wrap" fullWidth="false" expandable="true" %}

```mermaid
flowchart TB

    MAIN[main.py]

    %% GitHub
    subgraph GitHub
        GH1[updater.perform_update\ncommits / compare / raw]
    end

    %% RapidAPI — translation/optional
    subgraph RapidAPI_optional
        RA1[free_google_translator translate_text]
        RA2[TRANSLATOR_RAPIDAPI_KEY]
        RA1 --> RA2
    end

    %% Bluesky — AT Protocol
    subgraph Bluesky_AT_Protocol
        B1[atproto Client send_post]
        B2[atproto com atproto repo upload_blob]
        B3[session.txt]
        B1 --> B3
    end

    %% Twitter / X
    subgraph Twitter_X
        T1[tweety ns TwitterAsync]
        T2[cookie auth_token ct0 guest_id twid]
        T3[password auth TWITTER_USERNAME TWITTER_PASSWORD]
        T1 --> T2
        T1 --> T3
    end

    %% Connections from main.py
    MAIN -->|auto update| GH1
    MAIN -->|optional translation| RA1
    MAIN -->|publishes posts and media| B1
    MAIN -->|uploads blobs| B2
    MAIN -->|scrapes posts| T1
```

{% endcode %}

<table><thead><tr><th width="353">Service</th><th width="310.333251953125">Used for</th><th width="108.66650390625">Required</th></tr></thead><tbody><tr><td>Twitter/X</td><td>Scraping target account tweets via <code>tweety-ns</code></td><td>Yes</td></tr><tr><td>Bluesky (AT Protocol)</td><td>Publishing mirrored posts via <code>atproto</code></td><td>Yes</td></tr><tr><td>RapidAPI (<code>free-google-translator</code>)</td><td>Translating tweet text before posting</td><td>No</td></tr><tr><td>Github (<code>Yoproo20/twitter-to-bluesky-template</code>)</td><td>Auto-updating bot scripts</td><td>No</td></tr></tbody></table>

***

## Core Runtime Concepts <a href="#id-1-core-runtime-concepts" id="id-1-core-runtime-concepts"></a>

### Polling Interval

The bot polls Twitter on a schedule via `CHECK_INTERVAL` variable (in seconds). Also checks for a `shutdown_flag` every second for a clean exit via CTRL+C.

### State Persistence

Two files keep the bot idempotent across runs.

* `state.json` - stores `last_tweet_id` and `last_update_check`. Managed by `load_state()` , `save_state()`, `update_last_tweet_id()`, and `update_last_check_time()`
* `session.txt` - stores serialized Bluesky session token. Reused at startup via `get_session()`&#x20;

### Tweet Processing Pipeline

{% code overflow="wrap" expandable="true" %}

```mermaid
flowchart TB

    A[monitor_tweets app_get_tweets]

    A --> B{tweet_id greater than last_tweet_id}

    %% YES branch
    B -->|yes| C[update_last_tweet_id state.json]
    C --> D[process_tweet]

    D --> E[clean_tweet_text remove_tco_links]
    D --> F[download_tweet_media returns images and videos]
    D --> G[remove temp image and video files]

    E --> H[post_to_bluesky]
    F --> H

    H --> I[upload_media upload_blob]
    I --> J[bluesky_client send_post]

    J --> K{ENABLE_TRANSLATION}

    K -->|yes| L[translate_text RapidAPI POST]
    L --> M[send_translation_reply bluesky_client send_post reply]

    %% NO branch from decision
    B -->|no skip| N[interruptible_sleep CHECK_INTERVAL]

    %% Cleanup also flows to sleep
    G --> N
```

{% endcode %}

***

## (Key) Configuration Variables

<table><thead><tr><th width="228.3333740234375">Variable</th><th width="104.9998779296875">Default</th><th>Effect</th></tr></thead><tbody><tr><td><code>TARGET_USER</code></td><td><em>(required)</em></td><td>Twitter handle to monitor (no <code>@</code>)</td></tr><tr><td><code>CHECK_INTERVAL</code></td><td><code>300</code></td><td>Seconds between tweet polls</td></tr><tr><td><code>ENABLE_TRANSLATION</code></td><td><code>false</code></td><td>Enables RapidAPI translation</td></tr><tr><td><code>TRANSLATION_FROM</code></td><td><code>es</code></td><td>Source language code</td></tr><tr><td><code>TRANSLATION_TO</code></td><td><code>en</code></td><td>Target language code</td></tr><tr><td><code>AUTO_UPDATE</code></td><td><code>true</code></td><td>Enables automatic script updates from GitHub</td></tr><tr><td><code>UPDATE_CHECK_INTERVAL</code></td><td><code>86400</code></td><td>Seconds between GitHub update checks</td></tr></tbody></table>

For the full variable reference, including authentication, see <a href="/misc./environment-variable-reference.md" class="button primary">Enviroment Variable Reference</a>.
