Public
HTTP (deprecated)
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { convertRelativeDateToString } from "https://esm.town/v/sarahxc/convertRelativeDateToString";
import { searchParams } from "https://esm.town/v/stevekrouse/searchParams";
import { twitterJSON } from "https://esm.town/v/stevekrouse/twitterJSON";
import { twitterUser } from "https://esm.town/v/stevekrouse/twitterUser";
interface Website {
source: string;
url: string;
title?: string;
date_published: string;
}
function formatTweetText(text: string): string {
// Remove any URLs from the text
text = text.replace(/https?:\/\/\S+/g, "");
// Remove any Twitter handles
text = text.replace(/@\w+/g, "");
// Remove hashtags
text = text.replace(/#\w+/g, "");
// Remove emojis
text = text.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}]/gu, "");
// Remove excess whitespace and trim
text = text.replace(/\s+/g, " ").trim();
// Escape special characters for Slack
text = text.replace(/([&<>])/g, "\\$1");
// Truncate text if it's too long
const maxLength = 200;
if (text.length > maxLength) {
text = text.substring(0, maxLength - 3) + "...";
}
return text;
}
export async function twitterSearch({
query,
maxResults = 4,
daysBack = 1,
apiKey,
}: {
query: string;
maxResults?: number;
daysBack?: number;
apiKey: string;
}): Promise<Website[]> {
const startDate = new Date();
startDate.setDate(startDate.getDate() - daysBack);
// Ensure the query is properly formatted for the Twitter API
const formattedQuery = encodeURIComponent(`${query} -is:retweet lang:en`);
let allTweets: Website[] = [];
try {
console.log(`Searching Twitter for query: "${query}", start date: ${startDate.toISOString()}`);
const res = await twitterJSON({
url: `https://api.twitter.com/2/tweets/search/recent?query=${formattedQuery}&${await searchParams(
{
start_time: startDate.toISOString(),
expansions: ["author_id"],
["tweet.fields"]: ["created_at", "public_metrics"],
"user.fields": ["username"],
max_results: 100,
sort_order: "relevancy",
},
)}`,
bearerToken: apiKey,
});
if (res.errors) {
console.error("twitterSearch error", JSON.stringify(res));
throw new Error(JSON.stringify(res.errors));
}
// Filter out tweets with hashtags and emojis
const filteredTweets = (res.data || []).filter(tweet => {
return !/#/.test(tweet.text)
&& !/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}]/u.test(tweet.text);
});
// Sort tweets by engagement score (combination of likes, retweets, and replies)
const sortedTweets = filteredTweets
.sort((a, b) => {
const scoreA = (a.public_metrics?.like_count || 0)
+ (a.public_metrics?.retweet_count || 0) * 2
+ (a.public_metrics?.reply_count || 0) * 3;
const scoreB = (b.public_metrics?.like_count || 0)
+ (b.public_metrics?.retweet_count || 0) * 2
+ (b.public_metrics?.reply_count || 0) * 3;
return scoreB - scoreA;
})
.slice(0, maxResults);
allTweets = await Promise.all(
sortedTweets.map(async (tweet) => {
let author;
try {
const authorResponse = await twitterUser({
id: tweet.author_id,
bearerToken: apiKey,
});
author = authorResponse.data;
} catch (error) {
alexdphan-twittersearch.web.val.run
September 4, 2024