2024-01-12 12:30:45 +00:00
|
|
|
require("dotenv").config();
|
|
|
|
const fs = require("fs");
|
|
|
|
const path = require("path");
|
|
|
|
const { RichText, BskyAgent } = require("@atproto/api");
|
|
|
|
const axios = require("axios");
|
|
|
|
|
|
|
|
// Mastodon credentials
|
|
|
|
const mastodonInstance = process.env.MASTODON_INSTANCE;
|
|
|
|
const mastodonUser = process.env.MASTODON_USER;
|
|
|
|
|
|
|
|
// Bluesky agent
|
|
|
|
const agent = new BskyAgent({ service: process.env.BLUESKY_ENDPOINT });
|
2024-04-14 18:48:00 +00:00
|
|
|
await agent.login({
|
|
|
|
identifier: process.env.BLUESKY_HANDLE,
|
|
|
|
password: process.env.BLUESKY_PASSWORD,
|
|
|
|
});
|
2024-01-12 12:30:45 +00:00
|
|
|
|
|
|
|
// File to store the last processed Mastodon post ID
|
2024-03-27 18:43:00 +00:00
|
|
|
const lastProcessedPostIdFile = path.join(
|
|
|
|
__dirname,
|
|
|
|
"data",
|
|
|
|
"lastProcessedPostId.txt"
|
|
|
|
);
|
2024-01-12 12:30:45 +00:00
|
|
|
|
|
|
|
// Variable to store the last processed Mastodon post ID
|
|
|
|
let lastProcessedPostId = loadLastProcessedPostId();
|
|
|
|
|
|
|
|
// Function to load the last processed post ID from the file
|
|
|
|
function loadLastProcessedPostId() {
|
|
|
|
try {
|
|
|
|
return fs.readFileSync(lastProcessedPostIdFile, "utf8").trim();
|
|
|
|
} catch (error) {
|
|
|
|
console.error("Error loading last processed post ID:", error);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Function to save the last processed post ID to the file
|
|
|
|
function saveLastProcessedPostId() {
|
|
|
|
try {
|
|
|
|
fs.writeFileSync(lastProcessedPostIdFile, `${lastProcessedPostId}`);
|
|
|
|
} catch (error) {
|
|
|
|
console.error("Error saving last processed post ID:", error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-14 18:48:00 +00:00
|
|
|
async function postToBluesky(text, images) {
|
2024-01-12 12:30:45 +00:00
|
|
|
const richText = new RichText({ text });
|
|
|
|
await richText.detectFacets(agent);
|
2024-04-14 18:48:00 +00:00
|
|
|
|
|
|
|
const blueSkyImages = await images.reduce(async (list, {url, alt}) => {
|
|
|
|
const imageResponse = await axios.get({
|
|
|
|
url,
|
|
|
|
method: 'GET',
|
|
|
|
responseType: 'blob',
|
|
|
|
});
|
|
|
|
|
|
|
|
const uploadResponse = await agent.uploadBlob(imageResponse.data);
|
|
|
|
|
|
|
|
return [
|
|
|
|
...list,
|
|
|
|
{
|
|
|
|
alt,
|
|
|
|
image: uploadResponse.blob,
|
|
|
|
}
|
|
|
|
];
|
|
|
|
}, []);
|
|
|
|
|
2024-01-12 12:30:45 +00:00
|
|
|
await agent.post({
|
|
|
|
text: richText.text,
|
|
|
|
facets: richText.facets,
|
2024-04-14 18:48:00 +00:00
|
|
|
...(blueSkyImages.length > 0
|
|
|
|
? {
|
|
|
|
embed: {
|
|
|
|
type: 'app.bsky.embed.images',
|
|
|
|
images: blueSkyImages,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
: {}
|
|
|
|
),
|
2024-01-12 12:30:45 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeHtmlTags(input) {
|
|
|
|
return input.replace(/<[^>]*>/g, "");
|
|
|
|
}
|
|
|
|
|
2024-04-03 08:01:54 +00:00
|
|
|
function truncate(text, timestampId) {
|
|
|
|
if (text.length > 300) {
|
|
|
|
console.warn(`✂ post '${timestampId}' was truncated`)
|
|
|
|
return text.substring(0, 299) + '…'
|
|
|
|
}
|
|
|
|
|
|
|
|
return text
|
|
|
|
}
|
|
|
|
|
2024-01-12 12:30:45 +00:00
|
|
|
// Function to periodically fetch new Mastodon posts
|
|
|
|
async function fetchNewPosts() {
|
2024-03-27 18:43:00 +00:00
|
|
|
const response = await axios.get(
|
|
|
|
`${mastodonInstance}/users/${mastodonUser}/outbox?page=true`
|
|
|
|
);
|
2024-01-12 12:30:45 +00:00
|
|
|
|
2024-03-27 18:43:00 +00:00
|
|
|
const reversed = response.data.orderedItems
|
|
|
|
.filter((item) => item.object.type === "Note")
|
|
|
|
.filter((item) => item.object.inReplyTo === null)
|
|
|
|
.reverse();
|
2024-01-12 12:30:45 +00:00
|
|
|
|
|
|
|
let newTimestampId = 0;
|
2024-03-27 18:43:00 +00:00
|
|
|
|
|
|
|
reversed.forEach((item) => {
|
2024-01-12 12:30:45 +00:00
|
|
|
const currentTimestampId = Date.parse(item.published);
|
|
|
|
|
2024-03-27 18:43:00 +00:00
|
|
|
if (currentTimestampId > lastProcessedPostId && lastProcessedPostId != 0) {
|
2024-04-03 08:01:54 +00:00
|
|
|
try {
|
|
|
|
console.log('📧 posting to BlueSky', currentTimestampId)
|
|
|
|
const text = truncate(removeHtmlTags(item.object.content), currentTimestampId);
|
2024-04-14 18:48:00 +00:00
|
|
|
|
|
|
|
const images = item.attachment
|
|
|
|
.filter(attachment => attachment.type === 'Document' && attachment.mediaType.startsWith('image/'))
|
|
|
|
.map(({name: alt, url}) => ({ alt, url }));
|
|
|
|
postToBluesky(text, images);
|
2024-04-03 08:01:54 +00:00
|
|
|
|
|
|
|
if (currentTimestampId > newTimestampId) {
|
|
|
|
newTimestampId = currentTimestampId;
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('🔥 can\'t post to Bluesky', currentTimestampId, error)
|
|
|
|
}
|
2024-01-12 12:30:45 +00:00
|
|
|
}
|
2024-03-27 18:43:00 +00:00
|
|
|
});
|
2024-01-12 12:30:45 +00:00
|
|
|
|
2024-03-27 18:43:00 +00:00
|
|
|
if (newTimestampId > 0) {
|
2024-01-12 12:30:45 +00:00
|
|
|
lastProcessedPostId = newTimestampId;
|
|
|
|
saveLastProcessedPostId();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fetchNewPosts();
|
|
|
|
// Fetch new posts every 5 minutes (adjust as needed)
|
2024-03-27 18:58:36 +00:00
|
|
|
setInterval(fetchNewPosts, (process.env.INTERVAL_MINUTES ?? 5) * 60 * 1000);
|