twitter-mastodon-relay/main.js

110 lines
3.6 KiB
JavaScript

const masto = require("masto");
const rtj = require("rss-to-json");
const config = require("./config.json");
const cheerio = require("cheerio");
const axios = require("axios");
const fs = require("fs");
let prev;
(async function() {
if (config.prev) prev = config.prev;
config.spoiler = config.spoiler.split("{{username}}").join(config.account);
await runSync();
})();
async function runSync() {
const client = await masto.login(config.credentials)
let rss = await (await rtj.parse(`${config.instance}/${config.account}/rss?random=${Date.now() * 1}`)).items
let id = lastPost(rss).link.split("/status/")[1].split("#")[0];
if (prev !== id) {
let post = lastPost(rss);
console.log(`[mirror] Starting to mirror https://twitter.com/i/status/${id}`);
console.log(`[mirror] Extracting media...`);
let assets = await extractAssets(post.description);
console.log(`[mirror] Extracted media, ${assets.length} assets found`);
console.log(`[mirror] Attaching media...`);
let m = [];
for (let a in assets) {
let at = await client.mediaAttachments.create({
file: fs.createReadStream(assets[a].location)
});
m.push(at.id);
console.log(`[mirror] Attached asset ${(a + 1)}`);
}
console.log(`[mirror] Attached media.`)
if (m.length == 0) m = null;
if (post.title == "Image") post.title = "";
else if (config.linkOriginal) post.title = `${post.title}\n\n`;
let status;
status = post.title;
if (config.linkOriginal) status = `${status}[Original tweet: https://twitter.com/i/status/${id}]`;
let p = await client.statuses.create({
status: status,
visibility: config.visibility,
mediaIds: m,
spoilerText: config.spoiler
});
console.log(`[mirror] Successfully mirrored ${id} to ${p.url}`);
setTimeout(async function() {
config.prev = id;
prev = id;
fs.writeFileSync(`${__dirname}/config.json`, JSON.stringify(config, null, 2));
runSync();
}, config.updateInterval);
} else {
console.log(`[mirror] Already mirrored ${id}, trying again in ${(config.updateInterval / 1000).toFixed(0)} seconds.`);
setTimeout(async function() {
runSync();
}, config.updateInterval);
}
}
function detectType(text) {
if (typeof text == "object") text = text.title;
if (text.startsWith("R to ")) return "reply";
if (text.startsWith("RT by ")) return "repost";
if (text == "Image") return "image-only-post";
return "post";
}
function lastPost(array) {
for (let a in array) {
let t = detectType(array[a].title);
if (t == "image-only-post" || t == "post") return array[a];
}
return null;
}
async function extractAssets(description) {
let $ = cheerio.load(description);
if ($("img").length == 0) return [];
let media = [];
for (let c in $("img")) {
if (typeof $("img")[c] !== "object") continue;
if ($("img")[c].type !== "tag") continue;
let ax = await axios($("img")[c].attribs?.src, {
headers: {
accept: "image/*, */*"
},
responseType: "stream"
});
let id = $("img")[c].attribs?.src?.split("media%2F")[1].split(".")[0];
let ex = $("img")[c].attribs?.src?.split(".")[$("img")[c].attribs?.src?.split(".").length - 1];
if (!fs.existsSync(`${__dirname}/temp`)) fs.mkdirSync(`${__dirname}/temp`);
if (fs.existsSync(`${__dirname}/temp/${id}.${ex}`)) fs.rmSync(`${__dirname}/temp/${id}.${ex}`);
await ax.data.pipe(fs.createWriteStream(`${__dirname}/temp/${id}.${ex}`));
media.push({
type: "image",
original: $("img")[c].attribs?.src,
location: `${__dirname}/temp/${id}.${ex}`
});
}
return media;
}