Tiny app to post simultaneously to Mastodon and Bluesky.
Obviously, based on cuducos/from-my-ex
.
It supports:
- Post status updates to both networks with a simple CLI command
- Posting with images
- Including alt text for images
- Setting post language
It does not support:
- Tagging other users (they would have different IDs and servers in each platform)
It used to have:
- A simple GUI (version 0.1.1), but dropped support to focus on CLI โ there are other great GUI out there by now
- Python 3.9 or newer
Name | Description | Example | Default value |
---|---|---|---|
NOT_MY_EX_DEFAULT_LANG |
2-letter ISO 639-1 code | "pt" |
None |
To post to Bluesky
Name | Description | Example | Default value |
---|---|---|---|
NOT_MY_EX_BSKY_AGENT |
Bluesky instance | "https://bsky.social" |
"https://bsky.social" |
NOT_MY_EX_BSKY_EMAIL |
Email used in Bluesky | "cuducos@mailinator.com" |
None |
NOT_MY_EX_BSKY_PASSWORD |
Password used in Bluesky | As created in App Passwords. | None |
Not setting NOT_MY_EX_BSKY_EMAIL
or NOT_MY_EX_BSKY_PASSWORD
disables posting to Bluesky.
To post to Mastodon
Name | Description | Example | Default value |
---|---|---|---|
NOT_MY_EX_MASTODON_INSTANCE |
Mastodon instance | "https://tech.lgbt" |
"https://mastodon.social" |
NOT_MY_EX_MASTODON_TOKEN |
Mastodon access token | Go to your Settings, Development and then create an app to get the access token. Select the write:statuses and write:media scopes. |
None |
Not setting NOT_MY_EX_MASTODON_TOKEN
disables posting to Mastodon.
$ pip install not-my-ex
$ not-my-ex post "Magic, madness, heaven, sin" --images /tmp/1989.gif
You can skip --images
or pass multiple images (e.g. --images taylor.jpg --images swift.gif
). Check --help
for more details on commands and subcommands.
from asyncio import gather
from httpx import AsyncClient
from not_my_ex.auth import EnvAuth
from not_my_ex.bluesky import Bluesky
from not_my_ex.mastodon import Mastodon
from not_my_ex.media import Media
from not_my_ex.post import Post
async def main():
auth = EnvAuth()
media_tasks = tuple(
Media.from_img(path, alt, auth.image_size_limit)
for path, alt in (("taylor.jpg", "Taylor"), ("swift.jpg", "Swift"))
)
media = await gather(*media_tasks)
post = Post("Magic, madness, heaven, sin", auth.limitm, media, "en")
async with AsyncClient() as http:
post_tasks = tuple(cls(http).post(post) for cls in (Bluesky, Mastodon))
await gather(*post_tasks)
In Post
, limit
, media
and lang
are optional. In Media
, both alt
and image_size_limit
are optional.
The usage of auth.limit
and auth.image_size_limit
makes sure the limits are set according to the authenticated clients.
Requires uv
Python package manager. The tests include Ruff and Mypy:
$ uv run pytest