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
// This val fetches recent tweets about @SnapAR or Lens Studio, removes duplicates,
// and displays them as embedded tweets with preview images on a dark background.
// It uses the Twitter API for fetching tweets and the Twitter widget for embedding.
export default async function server(request: Request): Promise<Response> {
const apiUrl = "https://api.twitter.com/2/tweets/search/recent";
const query = encodeURIComponent("@SnapAR OR \"Lens Studio\"");
const fullUrl =
`${apiUrl}?query=${query}&max_results=100&tweet.fields=created_at,author_id,in_reply_to_user_id&expansions=author_id,attachments.media_keys&media.fields=type,url,preview_image_url&user.fields=username`;
const bearerToken = Deno.env.get("twitter");
if (!bearerToken) {
return new Response(JSON.stringify({ error: "Twitter API bearer token is not set" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
try {
const response = await fetch(fullUrl, {
headers: { "Authorization": `Bearer ${bearerToken}` },
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`);
}
const data = await response.json();
if (!data.data || !Array.isArray(data.data)) {
throw new Error(`Unexpected API response structure: ${JSON.stringify(data)}`);
}
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const processedTweets = data.data
.filter((tweet: any) => new Date(tweet.created_at) >= sevenDaysAgo && !tweet.in_reply_to_user_id)
.map((tweet: any) => {
const user = data.includes.users.find((user: any) => user.id === tweet.author_id);
return {
id: tweet.id,
text: tweet.text,
created_at: tweet.created_at,
username: user ? user.username : "Unknown",
url: `https://twitter.com/${user ? user.username : "twitter"}/status/${tweet.id}`,
};
});
// Remove duplicates based on tweet text
const uniqueTweets = Array.from(new Map(processedTweets.map(tweet => [tweet.text, tweet])).values())
.sort((a, b) => {
const dateA = new Date(a.created_at);
const dateB = new Date(b.created_at);
return dateB.getTime() - dateA.getTime();
});
const tweetHtml = uniqueTweets.map(tweet => `
<div class="tweet-wrapper" data-tweet-id="${tweet.id}">
<div class="tweet-container"></div>
</div>
`).join("\n");
const tweetIds = uniqueTweets.map(tweet => tweet.id);
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Recent Tweets about @SnapAR or Lens Studio</title>
<style>
html { background-color: black; }
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: black; }
h1 { color: #1da1f2; text-align: center; position: sticky; top: 0; background-color: rgba(0, 0, 0, 0.8); padding: 10px 0; z-index: 1000; }
h1 { color: #1da1f2; text-align: center; }
.tweet-wrapper { margin-bottom: 20px; }
a { color: #1da1f2; text-decoration: none; }
a:hover { text-decoration: underline; }
.twitter-tweet { margin: 0 auto; }
.source-link { text-align: center; margin-top: 20px; }
</style>
</head>
<body>
<h1>Recent Tweets about @SnapAR or Lens Studio</h1>
${tweetHtml}
<p class="source-link">View source: <a href="${import.meta.url.replace("esm.town", "val.town")}">${
import.meta.url.replace("esm.town", "val.town")
}</a></p>
<script>
// Load Twitter widget asynchronously
window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
charmaine-twitterrecentmentions.web.val.run
September 5, 2024