Avatar

buttondown

4 public vals
Joined May 10, 2023
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
import { email } from "https://esm.town/v/std/email?v=9";
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
import { searchParams } from "https://esm.town/v/stevekrouse/searchParams";
import { twitterJSON } from "https://esm.town/v/stevekrouse/twitterJSON";
const query = "buttondown";
export async function twitterSearch({
query,
start_time,
bearerToken,
...rest
}: {
query?: string;
start_time?: Date;
bearerToken: string;
}): Promise<TweetResult[]> {
const { data } = await twitterJSON({
url: `https://api.twitter.com/2/tweets/search/recent?query=${await searchParams(
{
query,
start_time: start_time?.toISOString(),
expansions: "author_id",
...rest,
},
)}`,
bearerToken: bearerToken,
});
console.log(data);
return Promise.all(
(data || []).map(async (tweet) => {
const { data: author } = await twitterUser({
id: tweet.author_id,
bearerToken,
});
return {
...tweet,
author_name: author?.name,
author_username: author?.username,
};
}),
);
}
interface TweetResult {
author_id: string;
text: string;
id: string;
edit_history_tweet_ids: string[];
author_name?: string;
author_username?: string;
}
export default async function twitterAlert({ lastRunAt }: Interval) {
const results = await twitterSearch({
query,
bearerToken: Deno.env.get("TWITTER_API_KEY"),
});
console.log("Here!", results);
if (!results.length) return;
// format results
let content = results
.map(({ author_name, author_username, text, id }) => `https://twitter.com/${author_username}/status/${id}`)
.join("\n");
// notify
await email({
html: content,
subject: `New post in Twitter`,
});
}
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
import { encounteredIDs } from "https://esm.town/v/buttondown/encounteredIDs";
import { email } from "https://esm.town/v/std/email?v=9";
import { set } from "https://esm.town/v/std/set?v=11";
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
export const runner = async () => {
const ALERT_STRINGS = [
"buttondown",
"buttondown.email",
];
// Let's not email ourselves when we post..
const USERNAME_DENYLIST = [
"buttondown.bsky.social",
];
const promises = ALERT_STRINGS.map(async function(keyword) {
let posts = await fetchJSON(
`https://search.bsky.social/search/posts?q=${keyword}`,
);
let newPosts = posts.filter((post) =>
!encounteredIDs.includes(post.tid)
&& !USERNAME_DENYLIST.includes(post.user.handle)
);
newPosts.map((post) => {
encounteredIDs.push(post.tid);
});
await Promise.all(newPosts.map(async (post) => {
const id = post.tid.split("/")[1];
// We intentionally dedupe subjects so that multiple responses all
// have their own threads; this makes it easier to figure out what
// I've seen and/or responded to over time.
await email({
html: `https://bsky.app/profile/${post.user.did}/post/${id}`,
subject: `New post in Bluesky for ${keyword} (${id})`,
});
}));
});
await Promise.all(promises);
await set("encounteredIDs", encounteredIDs);
};
1
2
// set at Mon Feb 05 2024 20:41:55 GMT+0000 (Coordinated Universal Time)
export let encounteredIDs = ["app.bsky.feed.post/3k4feciwfew27","app.bsky.feed.post/3k4fdxe2te725","app.bsky.feed.post/3k4fdta6auc2o","app.bsky.feed.post/3k4fbfxa32m2j","app.bsky.feed.post/3k4ezg2scsx25","app.bsky.feed.post/3k4eywrclju2i","app.bsky.feed.po
1
2
3
import { hnTopStory } from "https://esm.town/v/stevekrouse/hnTopStory?v=3";
export const hnTopStoryExample = hnTopStory();
Next