feat: post to twitter
Node.js CI / Lint and Test (pull_request) Successful in 1m26s

This commit is contained in:
2025-07-19 18:08:51 -07:00
parent b89741f660
commit b982f78e22
6 changed files with 64 additions and 6 deletions
+2 -1
View File
@@ -22,7 +22,8 @@
"@nhcarrigan/logger": "1.0.0",
"@prisma/client": "6.11.1",
"fastify": "5.4.0",
"gray-matter": "4.0.3"
"gray-matter": "4.0.3",
"twitter-api-v2": "1.24.0"
},
"devDependencies": {
"@types/node": "24.0.10",
+6 -1
View File
@@ -8,4 +8,9 @@ REDDIT_CLIENT_SECRET="op://Environment Variables - Naomi/Hikari/reddit_client_se
REDDIT_PASSWORD="op://Environment Variables - Naomi/Hikari/reddit_password"
REDDIT_USERNAME="op://Environment Variables - Naomi/Hikari/reddit_username"
BSKY_APP_PASSWORD="op://Environment Variables - Naomi/Hikari/bsky_password"
ANTHROPIC_KEY="op://Environment Variables - Naomi/Hikari/anthropic_key"
ANTHROPIC_KEY="op://Environment Variables - Naomi/Hikari/anthropic_key"
TWITTER_TOKEN="op://Environment Variables - Naomi/Hikari/twitter_access_token"
TWITTER_SECRET="op://Environment Variables - Naomi/Hikari/twitter_access_secret"
TWITTER_CONSUMER_KEY="op://Environment Variables - Naomi/Hikari/twitter_consumer_key"
TWITTER_CONSUMER_SECRET="op://Environment Variables - Naomi/Hikari/twitter_consumer_secret"
TWITTER_BEARER_TOKEN="op://Environment Variables - Naomi/Hikari/twitter_bearer_token"
+41
View File
@@ -0,0 +1,41 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { TwitterApi } from "twitter-api-v2";
/**
* Forwards an announcement to our Twitter account.
* @param content - The main body of the announcement.
* @returns A message indicating the success or failure of the operation.
*/
export const announceOnTwitter = async(content: string): Promise<string> => {
if (
process.env.TWITTER_CONSUMER_KEY === undefined
|| process.env.TWITTER_CONSUMER_SECRET === undefined
|| process.env.TWITTER_TOKEN === undefined
|| process.env.TWITTER_SECRET === undefined
) {
return "Twitter credentials are not set.";
}
const twitterClient = new TwitterApi({
accessSecret: process.env.TWITTER_SECRET,
accessToken: process.env.TWITTER_TOKEN,
appKey: process.env.TWITTER_CONSUMER_KEY,
appSecret: process.env.TWITTER_CONSUMER_SECRET,
});
const result = await twitterClient.v2.
tweet(content).
catch((error: unknown) => {
return error instanceof Error
? error.message
: String(error);
});
if (typeof result === "string") {
return `Failed to send message to Twitter. ${result}`;
}
return "Successfully sent message to Twitter.";
};
+2 -1
View File
@@ -4,11 +4,12 @@
* @author Naomi Carrigan
*/
// eslint-disable-next-line @typescript-eslint/naming-convention -- 'Tis a class.
import Anthropic from "@anthropic-ai/sdk";
/**
* Summarises an announcement using AI, to condense the content for platforms like Bluesky and Twitter.
* @param title
* @param title - The title of the announcement.
* @param content - The main body of the announcement.
* @returns A message indicating the success or failure of the operation.
*/
+5 -3
View File
@@ -10,6 +10,7 @@ import { announceOnBluesky } from "../modules/announceOnBluesky.js";
import { announceOnDiscord } from "../modules/announceOnDiscord.js";
import { announceOnForum } from "../modules/announceOnForum.js";
import { announceOnReddit } from "../modules/announceOnReddit.js";
import { announceOnTwitter } from "../modules/announceOnTwitter.js";
import { getIpFromRequest } from "../modules/getIpFromRequest.js";
import { summarisePost } from "../modules/summarisePost.js";
import type { FastifyPluginAsync } from "fastify";
@@ -109,18 +110,19 @@ export const announcementRoutes: FastifyPluginAsync = async(server) => {
const summary = await summarisePost(title, content);
if (summary === null) {
return await reply.status(201).send({
message: `Announcement processed. Discord: ${discord}, Forum: ${forum}, Reddit: ${reddit}, Bluesky: Skipped (AI summarisation failed).`,
message: `Announcement processed. Discord: ${discord}, Forum: ${forum}, Reddit: ${reddit}, Bluesky: Skipped (AI summarisation failed), Twitter: Skipped (AI summarisation failed).`,
});
}
if (summary.length > 280) {
return await reply.status(201).send({
message: `Announcement processed. Discord: ${discord}, Forum: ${forum}, Reddit: ${reddit}, Bluesky: Skipped (AI summary too long).`,
message: `Announcement processed. Discord: ${discord}, Forum: ${forum}, Reddit: ${reddit}, Bluesky: Skipped (AI summary too long), Twitter: Skipped (AI summary too long).`,
});
}
const bluesky = await announceOnBluesky(summary);
const twitter = await announceOnTwitter(summary);
return await reply.status(201).send({
message: `Announcement processed. Discord: ${discord}, Forum: ${forum}, Reddit: ${reddit}, Bluesky: ${bluesky}`,
message: `Announcement processed. Discord: ${discord}, Forum: ${forum}, Reddit: ${reddit}, Bluesky: ${bluesky}, Twitter: ${twitter}`,
});
},
);