mastodon-to-bluesky/main.js

158 lines
4.4 KiB
JavaScript
Raw Normal View History

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 });
// File to store the last processed Mastodon post ID
const lastProcessedPostIdFile = path.join(__dirname, "lastProcessedPostId.txt");
// 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-03-04 03:27:13 +00:00
async function getImages(item) {
const responses = item.object.attachment
.filter(attachment => attachment.mediaType.includes("image"));
const infos = responses.map(attachment => {
return {
url: attachment.url,
width: attachment.width,
height: attachment.height
}
});
const respromise = await Promise.all(responses.map(attachment =>
axios.get(
attachment.url,
{
responseType: "arraybuffer"
}
)
));
return respromise.map((buf, index) => {
return {
blob: new Blob(
[buf.data],
{
type: buf.headers["content-type"]
}
),
url: infos[index].url,
width: infos[index].width,
height: infos[index].height
}
})
}
2024-01-12 12:30:45 +00:00
2024-03-04 03:27:13 +00:00
async function postToBluesky(text, images) {
2024-01-12 12:30:45 +00:00
await agent.login({
identifier: process.env.BLUESKY_HANDLE,
password: process.env.BLUESKY_PASSWORD,
});
2024-03-04 03:27:13 +00:00
// let imgdatas = await Promise.all(images.map(async img => {
// const buf = await img.blob.arrayBuffer();
// console.log(`detected img: ${buf}, ${buf}`);
// const dataArray = new Uint8Array(buf);
// const data = await agent.uploadBlob(
// dataArray,
// {
// // 画像の形式を指定 ('image/jpeg' 等の MIME タイプ)
// encoding: img.type,
// }
// );
// return {
// alt: "",
// image: data.data.blob,
// aspectRatio: {
// width: img.width,
// height: img.height
// }
// };
// }));
let completetext = text;
2024-03-04 04:14:49 +00:00
if (images.length > 0) {
2024-03-04 03:27:13 +00:00
completetext = text + " " + images.map(img => img.url).join(" ")
}
2024-03-04 04:14:49 +00:00
const richText = new RichText({ text: completetext });
2024-01-12 12:30:45 +00:00
await richText.detectFacets(agent);
2024-03-04 03:27:13 +00:00
2024-03-04 04:14:49 +00:00
return agent.post({
2024-01-12 12:30:45 +00:00
text: richText.text,
facets: richText.facets,
});
}
function removeHtmlTags(input) {
return input.replace(/<[^>]*>/g, "");
}
2024-03-04 03:27:13 +00:00
function convertQuote(input) {
return input.replace(/&quot;/g, '"');
}
function isReplyToMyself(item) {
const uri = item.object.inReplyTo;
if (uri === null) {
return false;
}
return uri.includes("${mastodonInstance}/users/${mastodonUser}");
}
2024-01-12 12:30:45 +00:00
// Function to periodically fetch new Mastodon posts
async function fetchNewPosts() {
const response = await axios.get(`${mastodonInstance}/users/${mastodonUser}/outbox?page=true`);
const reversed = response.data.orderedItems.filter(item => item.object.type === 'Note')
2024-03-04 03:27:13 +00:00
.filter(item => item.object.inReplyTo === null || isReplyToMyself(item))
.reverse();
2024-01-12 12:30:45 +00:00
let newTimestampId = 0;
2024-03-04 04:14:49 +00:00
for (item in reversed) {
2024-01-12 12:30:45 +00:00
const currentTimestampId = Date.parse(item.published);
2024-03-04 03:27:13 +00:00
if (currentTimestampId > newTimestampId) {
2024-01-12 12:30:45 +00:00
newTimestampId = currentTimestampId;
}
2024-03-04 03:27:13 +00:00
if (currentTimestampId > lastProcessedPostId && lastProcessedPostId != 0) {
const text = removeHtmlTags(convertQuote(item.object.content));
const images = await getImages(item);
2024-03-04 04:14:49 +00:00
let {uri} = await postToBluesky(text, images);
console.log(`posted ${item.object.id} to ${uri} . ${newTimestampId}`);
2024-01-12 12:30:45 +00:00
}
2024-03-04 04:14:49 +00:00
}
2024-01-12 12:30:45 +00:00
2024-03-04 03:27:13 +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-04 03:27:13 +00:00
setInterval(fetchNewPosts, 1 * 60 * 1000);