From b093c7d5fc6383da19c6434f57037e014da822de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schimmelpfennig?= Date: Wed, 29 May 2024 09:57:57 +0200 Subject: [PATCH] feat: split long posts into replies (#10) * post split text as reply --------- Co-authored-by: Maurice Renck --- main.js | 216 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 128 insertions(+), 88 deletions(-) diff --git a/main.js b/main.js index 329979a..728dcb1 100644 --- a/main.js +++ b/main.js @@ -8,102 +8,142 @@ const axios = require("axios"); 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, - "data", - "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); - } -} - -async function postToBluesky(text) { - await agent.login({ +async function main() { + // Bluesky agent + const agent = new BskyAgent({ service: process.env.BLUESKY_ENDPOINT }); + const loginResponse = await agent.login({ identifier: process.env.BLUESKY_HANDLE, password: process.env.BLUESKY_PASSWORD, }); + if (!loginResponse.success) console.error("🔒 login failed"); - const richText = new RichText({ text }); - await richText.detectFacets(agent); - await agent.post({ - text: richText.text, - facets: richText.facets, - }); -} - -function removeHtmlTags(input) { - return input.replace(/<[^>]*>/g, ""); -} - -function truncate(text, timestampId) { - if (text.length > 300) { - console.warn(`✂ post '${timestampId}' was truncated`) - return text.substring(0, 299) + '…' - } - - return text -} - -// Function to periodically fetch new Mastodon posts -async function fetchNewPosts() { - const response = await axios.get( - `${mastodonInstance}/users/${mastodonUser}/outbox?page=true` + // File to store the last processed Mastodon post ID + const lastProcessedPostIdFile = path.join( + __dirname, + "data", + "lastProcessedPostId.txt" ); - const reversed = response.data.orderedItems - .filter((item) => item.object.type === "Note") - .filter((item) => item.object.inReplyTo === null) - .reverse(); + // Variable to store the last processed Mastodon post ID + let lastProcessedPostId = loadLastProcessedPostId(); - let newTimestampId = 0; - - reversed.forEach((item) => { - const currentTimestampId = Date.parse(item.published); - - if (currentTimestampId > newTimestampId) { - newTimestampId = currentTimestampId; + // 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; } - - if (currentTimestampId > lastProcessedPostId && lastProcessedPostId != 0) { - try { - console.log('📧 posting to BlueSky', currentTimestampId) - const text = truncate(removeHtmlTags(item.object.content), currentTimestampId); - postToBluesky(text); - } catch (error) { - console.error('🔥 can\'t post to Bluesky', currentTimestampId, error) - } - } - }); - - if (newTimestampId > 0) { - lastProcessedPostId = newTimestampId; - saveLastProcessedPostId(); } + + // 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); + } + } + + async function createBlueskyMessage(text) { + const richText = new RichText({ text }); + await richText.detectFacets(agent); + + return { + text: richText.text, + facets: richText.facets + }; + } + + async function postToBluesky(textParts) { + const rootMessageResponse = await agent.post(await createBlueskyMessage(textParts[0])); + + if (textParts.length === 1) return; + + let replyMessageResponse = null + for (let index = 1; index < textParts.length; index++) { + replyMessageResponse = await agent.post({ + ...(await createBlueskyMessage(textParts[index])), + reply: { + root: rootMessageResponse, + parent: replyMessageResponse ?? rootMessageResponse, + } + }); + } + } + + function removeHtmlTags(input) { + return input.replace(/<[^>]*>/g, ""); + } + + function splitText(text, maxLength) { + // Split the text by spaces + const words = text.split(" "); + + let result = []; + let currentChunk = ""; + + for (const word of words) { + // Add the current word to the current chunk + const potentialChunk = `${currentChunk} ${word}`.trim(); + + if (potentialChunk.length <= maxLength) { + // If the current chunk is still under max length, add the word + currentChunk = potentialChunk; + } else { + // Otherwise, add the current chunk to the result and start a new chunk + result.push(currentChunk); + currentChunk = word; + } + } + + // Add the last chunk to the result + result.push(currentChunk); + + return result; + } + + // 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") + .filter((item) => item.object.inReplyTo === null) + .reverse(); + + let newTimestampId = 0; + + reversed.forEach((item) => { + const currentTimestampId = Date.parse(item.published); + + if (currentTimestampId > newTimestampId) { + newTimestampId = currentTimestampId; + } + + if (currentTimestampId > lastProcessedPostId && lastProcessedPostId != 0) { + try { + console.log('📧 posting to BlueSky', currentTimestampId) + const textParts = splitText(removeHtmlTags(item.object.content), 300); + postToBluesky(textParts); + } catch (error) { + console.error('🔥 can\'t post to Bluesky', currentTimestampId, error) + } + } + }); + + if (newTimestampId > 0) { + lastProcessedPostId = newTimestampId; + saveLastProcessedPostId(); + } + } + + fetchNewPosts(); + // Fetch new posts every 5 minutes (adjust as needed) + setInterval(fetchNewPosts, (process.env.INTERVAL_MINUTES ?? 5) * 60 * 1000); } -fetchNewPosts(); -// Fetch new posts every 5 minutes (adjust as needed) -setInterval(fetchNewPosts, (process.env.INTERVAL_MINUTES ?? 5) * 60 * 1000); +main() \ No newline at end of file