Skip to content

Commit eced8ef

Browse files
chore: add tweet posting script for autonomous social updates (#11)
Adds scripts/tweet.ts — posts to @swfactory_dev via Twitter API v2 with OAuth 1.0a. Called by the Tweet Drafter automation as: npx tsx scripts/tweet.ts "<tweet text>" Requires TWITTER_CONSUMER_KEY, TWITTER_SECRET_KEY, TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET env vars. Co-authored-by: Ona <no-reply@ona.com>
1 parent 8568e84 commit eced8ef

3 files changed

Lines changed: 71 additions & 0 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"jsdom": "^29.0.2",
3333
"playwright": "^1.59.1",
3434
"tailwindcss": "^4",
35+
"twitter-api-v2": "^1.29.0",
3536
"typescript": "^5",
3637
"vitest": "^4.1.4"
3738
}

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/tweet.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Post a tweet to @swfactory_dev via Twitter API v2.
3+
*
4+
* Usage: npx tsx scripts/tweet.ts "Your tweet text here"
5+
*
6+
* Required env vars (OAuth 1.0a User Context):
7+
* TWITTER_CONSUMER_KEY
8+
* TWITTER_SECRET_KEY
9+
* TWITTER_ACCESS_TOKEN
10+
* TWITTER_ACCESS_TOKEN_SECRET
11+
*/
12+
13+
import { TwitterApi } from "twitter-api-v2";
14+
15+
const text = process.argv[2];
16+
17+
if (!text) {
18+
console.error("Usage: npx tsx scripts/tweet.ts <text>");
19+
process.exit(1);
20+
}
21+
22+
if (text.length > 280) {
23+
console.error(`Tweet is ${text.length} chars (max 280). Trim it first.`);
24+
process.exit(1);
25+
}
26+
27+
const required = [
28+
"TWITTER_CONSUMER_KEY",
29+
"TWITTER_SECRET_KEY",
30+
"TWITTER_ACCESS_TOKEN",
31+
"TWITTER_ACCESS_TOKEN_SECRET",
32+
] as const;
33+
34+
const missing = required.filter((k) => !process.env[k]);
35+
if (missing.length) {
36+
console.error(`Missing env vars: ${missing.join(", ")}`);
37+
process.exit(1);
38+
}
39+
40+
const client = new TwitterApi({
41+
appKey: process.env.TWITTER_CONSUMER_KEY!,
42+
appSecret: process.env.TWITTER_SECRET_KEY!,
43+
accessToken: process.env.TWITTER_ACCESS_TOKEN!,
44+
accessSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET!,
45+
});
46+
47+
async function main() {
48+
try {
49+
const { data } = await client.v2.tweet(text);
50+
const url = `https://x.com/swfactory_dev/status/${data.id}`;
51+
console.log(`Posted: ${url}`);
52+
} catch (err: unknown) {
53+
if (err instanceof Error) {
54+
console.error(`Failed to post tweet: ${err.message}`);
55+
} else {
56+
console.error("Failed to post tweet:", err);
57+
}
58+
process.exit(1);
59+
}
60+
}
61+
62+
main();

0 commit comments

Comments
 (0)