Runs every 1 days
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { getTweets } from "https://esm.town/v/geoffreylitt/getTweets";
import { email } from "https://esm.town/v/std/email?v=12";
import { OpenAI } from "https://esm.town/v/std/openai?v=4";
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
import { twitterSearch } from "https://esm.town/v/stevekrouse/twitterSearch";
const usernames = [
"stevekrouse",
"worrydream",
"wattenberger",
"mappletons",
"maryrosecook",
// Rate limits seem to be an issue...
// "thesephist",
// "tylerangert",
// "szymon_k",
// "schickling",
// "pvh",
// "todepond",
// "rsnous",
// "rsms",
// "andy_matuschak",
// "sliminality",
];
const openai = new OpenAI();
export async function twitterAlert({ lastRunAt }: Interval) {
let results = [];
for (const username of usernames) {
const tweets = await getTweets(username);
results = results.concat(tweets.data.map(tweet => ({ ...tweet, username })));
// wait 1 seconds between requests to try to avoid rate limits
await new Promise((resolve) => setTimeout(resolve, 1000));
}
async function filterTweets(tweets) {
const completion = await openai.chat.completions.create({
messages: [
{
role: "system",
content:
`You'll be given a list of tweets as JSON. I want you to filter the list and return a JSON array of IDs, plus a headline for the filtered list: { headline: string; ids: string[] } The filter criteria are:
I want to hear about important tweets sharing major updates, new projects, new papers or essays, website updates, etc. I don't want to see shitposts or jokes.
The headline should be short for an email subject, like 10 words, tease who said what with specifics of what they said.`,
},
{ role: "user", content: JSON.stringify(results) },
],
model: "gpt-4o",
response_format: { type: "json_object" },
});
const parsed = JSON.parse(completion.choices[0].message.content);
return { tweets: tweets.filter(tweet => parsed.ids.includes(tweet.id)), headline: parsed.headline };
}
const { tweets, headline } = await filterTweets(results);
// Format email HTML
const emailHtml = tweets.map((tweet) => {
const url = `https://fxtwitter.com/${tweet.username}/status/${tweet.id}`;
return `
<div style="border: 1px solid #e1e8ed; padding: 10px; margin-bottom: 10px; border-radius: 10px;">
<p style="margin: 0; font-size: 14px;"><strong>${tweet.username}:</strong> ${tweet.text}</p>
<a href="${url}" style="font-size: 12px; color: #1da1f2;">Read more</a>
</div>
`;
}).join("");
const htmlContent = `
<div style="font-family: Arial, sans-serif; color: #333;">
${emailHtml}
</div>
`;
email({ subject: headline, html: htmlContent });
console.log("Sent!", headline);
console.log(htmlContent);
}