+8
.dockerignore
+8
.dockerignore
+2
.gitignore
+2
.gitignore
+50
.tangled/workflows/build-images.yml
+50
.tangled/workflows/build-images.yml
···
+41
Containerfile
+41
Containerfile
···
+21
LICENSE
+21
LICENSE
···
+137
-20
README.md
+137
-20
README.md
···+XPost is a social media cross-posting tool that differs from others by using streaming APIs to allow instant, zero-input cross-posting. this means you can continue posting on your preferred platform without using special apps.+XPost tries to support as many features as possible. for example, when cross-posting from mastodon to bluesky, unsupported file types will be attached as links. posts with mixed media or too many files will be split and spread across text.+the tool may undergo breaking changes as new features are added, so proceed with caution when deploying.-first install `ffmpeg` with `ffprobe`, make sure they are available on PATH! `ffmpeg` is required to crosspost media.+first install `ffmpeg`, `ffprobe` and `libmagic`, make sure that `ffmpeg` is available on PATH! `ffmpeg` and `libmagic` are required to crosspost media.···+the official immage is available on [docker hub](https://hub.docker.com/r/melontini/xpost). example `compose.yaml`. this assumes that data dir is `./data`, and env file is `./.config/docker.env`. add `:Z` to volume mounts for podman.+listens to repo operation events emmited by Jetstream. handle becomes optional if you specify a DID.+"jetstream": "wss://jetstream2.us-east.bsky.network/subscribe" //optional, change jetstream endpoint+listens to the user's home timeline for new posts, crossposts only the public/unlisted ones by the user.+"token": "env:MASTODON_TOKEN", // Must be a mastodon token. get from something like phanpy + webtools. or https://getauth.thms.uk/?client_name=xpost&scopes=read:statuses%20write:statuses%20profile but doesn't work with all softwareany instance implementing `/api/v1/instance`, `/api/v1/accounts/verify_credentials` and `/api/v1/streaming?stream` will work fine.+listens to the homeTimeline channel for new posts, crossposts only the public/home ones by the user.+**IMPORTANT**: Misskey WSS does Not support deletes, you must delete posts manually. if you know how i can listen to all note events, i would appreciate your help.+"token": "env:MASTODON_TOKEN", // Must be a mastodon token. get from something like phanpy + webtools. or https://getauth.thms.uk/?client_name=xpost&scopes=read%20write%20profile but doesn't work with all software+in the bluesky block, you can configure who is allowed to reply to and quote the new posts. handle becomes optional if you specify a DID.+"bsky_appview": "env:BLUESKY_APPVIEW", // bypass suspensions by specifying a different appview (e.g. did:web:bsky.zeppelin.social)+"encode_videos": true, // bluesky only accepts mp4 videos, try to convert if the video is not mp4
+196
bluesky/atproto2.py
+196
bluesky/atproto2.py
···
+199
bluesky/common.py
+199
bluesky/common.py
···
+203
bluesky/input.py
+203
bluesky/input.py
···
+481
bluesky/output.py
+481
bluesky/output.py
···
-167
bluesky.py
-167
bluesky.py
···-embed=models.AppBskyEmbedVideo.Main(video=upload.blob, alt=video_alt, aspect_ratio=video_aspect_ratio),
+237
cross.py
+237
cross.py
···
-118
database.py
-118
database.py
···
+133
-383
main.py
+133
-383
main.py
···-def create_post_records(self, status: dict) -> list[models.AppBskyFeedPost.CreateRecordResponse] | None:-labels = models.ComAtprotoLabelDefs.SelfLabels(values=[models.ComAtprotoLabelDefs.SelfLabel(val=label) for label in label_text])-root_post = models.AppBskyFeedPost.CreateRecordResponse(uri=str(root_data['uri']), cid=str(root_data['cid']))-reply_post = models.AppBskyFeedPost.CreateRecordResponse(uri=str(reply_data['uri']), cid=str(reply_data['cid']))-aspect_ratio = models.AppBskyEmbedDefs.AspectRatio(width=meta['width'], height=meta['height'])-click.echo(f"Skipping post_id '{status['id']}'. Attachment type mismatch. got: '{attachment['type']}' expected: 'image'")-click.echo(f"Skipping post_id '{status['id']}', media attachment still too large after compression!")-image_aspect_ratios.append(models.AppBskyEmbedDefs.AspectRatio(width=meta['width'], height=meta['height']))-# also, since the db only stores post relations, we have to pull all the replies from masto and the pds.-@click.option('--data_dir', default='./data', type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True, writable=True))
+52
mastodon/common.py
+52
mastodon/common.py
···
+225
mastodon/input.py
+225
mastodon/input.py
···+f"'allowed_visibility' only accepts {', '.join(ALLOWED_VISIBILITY)}, got: {allowed_visibility}"
+448
mastodon/output.py
+448
mastodon/output.py
···
-37
mastodon.py
-37
mastodon.py
···
-88
media_util.py
-88
media_util.py
···-proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+54
misskey/common.py
+54
misskey/common.py
···
+202
misskey/input.py
+202
misskey/input.py
···+f"'allowed_visibility' only accepts {', '.join(ALLOWED_VISIBILITY)}, got: {allowed_visibility}"
+38
misskey/mfm_util.py
+38
misskey/mfm_util.py
···
+3
-1
pyproject.toml
+3
-1
pyproject.toml
···
+290
util/database.py
+290
util/database.py
···
+172
util/html_util.py
+172
util/html_util.py
···
+123
util/md_util.py
+123
util/md_util.py
···
+160
util/media.py
+160
util/media.py
···
+43
util/util.py
+43
util/util.py
···
-92
util.py
-92
util.py
···
+14
-1
uv.lock
+14
-1
uv.lock
···+sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" }+{ url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" },···