Compare commits
6 Commits
5487f633a4
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
8c470f1362 | ||
|
d9201697cf | ||
|
17f06d4a60 | ||
338f7fe79c | |||
c3a481b592 | |||
|
adb08cbdf3 |
21
README.md
21
README.md
@@ -1 +1,20 @@
|
|||||||
# M2B - Mastodon ➡️ Bluesky crossposter
|
# M2B - Mastodon ➡️ Bluesky crossposter
|
||||||
|
|
||||||
|
FYI: This is a work-in-progress and I intend to clean up the code and make this *MUCH* more user-friendly.
|
||||||
|
|
||||||
|
## Why did I make this?
|
||||||
|
|
||||||
|
I was having a hard time finding a mastodon --> bluesky crossposting tool that supported replies and boosts on top of normal posts. This ended up wasting enough time to where I figured I'd just make my own.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Don't forget to install dependencies with `npm i` and adjust the config by copying `example.env` into `.env`.
|
||||||
|
Get the mastodon credentials from going to user preferences > development > and creating a new application.
|
||||||
|
For bluesky credentials you have to make a new app password under settings > privacy and security > app passwords.
|
||||||
|
This should ideally be run from a cron job (specifically `run.sh`), but can be run with just `node index.js`.
|
||||||
|
|
||||||
|
## ToDo List
|
||||||
|
|
||||||
|
* Cleanup code
|
||||||
|
* Maybe fix the "reply to" add-in
|
||||||
|
* Maybe generate image preview of mastodon post
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
MASTODON_TOKEN: ""
|
MASTODON_TOKEN: ""
|
||||||
MASTODON_INSTANCE: "https://mastodon.social"
|
MASTODON_INSTANCE: "https://mastodon.social"
|
||||||
MASTODON_USER: ""
|
MASTODON_USER: "alice"
|
||||||
BSKY_INSTANCE: "https://bsky.social"
|
BSKY_INSTANCE: "https://bsky.social"
|
||||||
BSKY_USER: "username.bsky.social"
|
BSKY_USER: "alice.bsky.social"
|
||||||
BSKY_APP_PASS: ""
|
BSKY_APP_PASS: ""
|
23
index.mjs
23
index.mjs
@@ -88,16 +88,12 @@ if (existsSync(historyFile)){
|
|||||||
history += readFileSync(historyFile, 'UTF-8').split('\n');
|
history += readFileSync(historyFile, 'UTF-8').split('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
const bskyPost = async (text, media = []) => {
|
const bskyPost = async (text, postUrl, media = []) => {
|
||||||
let uploadedMedia = [];
|
let uploadedMedia = [];
|
||||||
for (let m of media){
|
for (let m of media){
|
||||||
// let mime = `${m.url}`.split('.').pop();
|
|
||||||
// mime = mime.toLowerCase();
|
|
||||||
// mime = mime === 'jpg'?'jpeg':mime;
|
|
||||||
const fileBuffer = Buffer.from(readFileSync(m.data));
|
const fileBuffer = Buffer.from(readFileSync(m.data));
|
||||||
const { ext, mimeT } = await fileTypeFromBuffer(fileBuffer);
|
const { ext, mimeT } = await fileTypeFromBuffer(fileBuffer);
|
||||||
let uploadResult = await bsky.uploadBlob(fileBuffer, {
|
let uploadResult = await bsky.uploadBlob(fileBuffer, {
|
||||||
// encoding: `image/${mime}`
|
|
||||||
encoding: mimeT
|
encoding: mimeT
|
||||||
});
|
});
|
||||||
if (uploadResult.success){
|
if (uploadResult.success){
|
||||||
@@ -124,6 +120,16 @@ const bskyPost = async (text, media = []) => {
|
|||||||
"$type": "app.bsky.embed.images",
|
"$type": "app.bsky.embed.images",
|
||||||
images: uploadedMedia
|
images: uploadedMedia
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// set embed to mastodon post
|
||||||
|
post.embed = {
|
||||||
|
"$type": "app.bsky.embed.external",
|
||||||
|
external: {
|
||||||
|
uri: postUrl,
|
||||||
|
title: `Post from @${process.env.MASTODON_USER}@${process.env.MASTODON_INSTANCE.substring(8)}`,
|
||||||
|
description: text
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.log(JSON.stringify(post));
|
console.log(JSON.stringify(post));
|
||||||
let bskyPostData = await bsky.post(post);
|
let bskyPostData = await bsky.post(post);
|
||||||
@@ -165,6 +171,7 @@ client.get(`/accounts/${process.env.MASTODON_ID}/statuses`, {
|
|||||||
});
|
});
|
||||||
if (status.mentions > 0 && status.in_reply_to_id !== null){
|
if (status.mentions > 0 && status.in_reply_to_id !== null){
|
||||||
// Post is a reply
|
// Post is a reply
|
||||||
|
// TODO: This isn't working yet
|
||||||
const replyTo = [...status.mentions].filter((mention) => mention.id === status.in_reply_to_account_id);
|
const replyTo = [...status.mentions].filter((mention) => mention.id === status.in_reply_to_account_id);
|
||||||
if (replyTo.length > 0){
|
if (replyTo.length > 0){
|
||||||
text = `Reply to: ${replyTo[0].url}/@${replyTo[0].acct}/${status.in_reply_to_id}\n${text}`;
|
text = `Reply to: ${replyTo[0].url}/@${replyTo[0].acct}/${status.in_reply_to_id}\n${text}`;
|
||||||
@@ -186,7 +193,7 @@ client.get(`/accounts/${process.env.MASTODON_ID}/statuses`, {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bskyPost(text, medias);
|
bskyPost(text, status.url, medias);
|
||||||
appendFileSync(historyFile, `\n${status.id}`);
|
appendFileSync(historyFile, `\n${status.id}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`ERROR: ${status.url} is too long and unable to be reposted`);
|
console.log(`ERROR: ${status.url} is too long and unable to be reposted`);
|
||||||
@@ -194,9 +201,9 @@ client.get(`/accounts/${process.env.MASTODON_ID}/statuses`, {
|
|||||||
}else{
|
}else{
|
||||||
// is boosted post
|
// is boosted post
|
||||||
let text = status.reblog.url;
|
let text = status.reblog.url;
|
||||||
bskyPost(text, []);
|
bskyPost(text, status.url, []);
|
||||||
appendFileSync(historyFile, `\n${status.id}`);
|
appendFileSync(historyFile, `\n${status.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user