1This script takes a Steam ID, Game Name, and array of discord webhook url destinations. It scans the main Steam community discussion page,
2and looks for new threads and threads with new comments, and then constructs a webhook with a configurable amount threads per hook, sending it to
3all configured destinations.
15 embedColor: 9043685, // optional decimal color to change the embed highlight
16 steamId: "570",
17 discordWebhook: [
18 Deno.env.get("DISCHOOK_URL_1"), // set up your webhook urls as environment variables
19 Deno.env.get("DISCHOOK_URL_2"), // you can pass in a single url or an array
8const MAX_THREADS_PER_BATCH = 5; // Maximum threads to include in a single batch
9const MAX_DEBUG_HTML_RUNS = 3; // Maximum number of debug HTML runs to keep
10const DEFAULT_EMBED_COLOR = 3447003; // Default Discord embed color (blue)
11const MAX_STORED_THREADS = 100; // Maximum number of threads to store
12
24 gameName: string;
25 steamId: string; // Changed from steamUrl to steamId
26 discordWebhook?: string | string[]; // Now accepts a single string or an array of strings
27 maxThreads?: number;
28 embedColor?: number; // Decimal color value
48 gameName,
49 steamId,
50 discordWebhook,
51 maxThreads = MAX_THREADS_PER_BATCH,
52 embedColor,
80 );
81
82 if (discordWebhook && threadsToNotify.length > 0) {
83 await sendDiscordNotifications(gameName, threadsToNotify, discordWebhook, maxThreads, embedColor, steamId);
84 }
85
150}
151
152async function sendDiscordNotifications(
153 gameName: string,
154 threads: SteamThread[],
164 for (const webhookUrl of webhookList) {
165 try {
166 await sendDiscordWebhook(gameName, threadBatch, webhookUrl, embedColor, steamId);
167 await new Promise(resolve => setTimeout(resolve, RATE_LIMIT_DELAY_MS));
168 } catch (webhookError) {
169 console.error(`Failed to send webhook for ${gameName} to ${webhookUrl} for thread batch starting at index ${i}:`, webhookError);
170 await sendErrorEmail(gameName, webhookError, `sending Discord webhook to ${webhookUrl} for thread batch starting at index ${i}`);
171 }
172 }
295}
296
297async function sendDiscordWebhook(
298 gameName: string,
299 threads: SteamThread[],
331 };
332
333 console.log("Discord Webhook Payload:", JSON.stringify(payload, null, 2));
334
335 const response = await fetch(webhookUrl, {
5This template will help you:
61. Search for specific mentions on Twitter/X using customizable keywords.
72. Deliver notifications wherever you'd like (email, Discord, Slack, Telegram, etc).
8
9## Example
10This val tracks mentions of "Val Town" and related terms, excluding noise like retweets and irrelevant accounts. Notifications are sent to a Discord webhook but can be easily reconfigured for other platforms.
11<img src="https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/85912106-f625-443e-5321-6e2699453200/public" width="500"/>
12To see exactly how we use this template at Val Town: https://www.val.town/x/stevekrouse/twitterAlert
37### 5. Choose Notification Method
38
39This template uses a Discord webhook for notifications, but
40you can update this to your preferred platform by replacing the `discordWebhook` call
41with a call to [Slack](https://docs.val.town/integrations/slack/send-messages-to-slack/), [`@std/email`](https://docs.val.town/std/email/), etc.
42
43**Create a Discord webhook following [this guide](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks).**
44
45Save your Discord Webhook URL in your Environment Variables (you can find this on the left sidebar):
46- Key: `mentionsDiscord`
47- Value: Your Discord webhook URL.
48Notifications will be sent using this function:
49
50```ts
51await discordWebhook({
52 url: Deno.env.get("mentionsDiscord"),
53 content,
54});
1import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
2import { socialDataSearch, Tweet } from "https://esm.town/v/stevekrouse/socialDataSearch";
3
25
26 // Format notification content as a list of Tweet URLs
27 // Using fxtwitter, which fixes Twitter Embed Unfurls for Discord
28 const content = response.tweets?.map(({ user: { screen_name }, id_str }) =>
29 `https://fxtwitter.com/${screen_name}/status/${id_str}`
39 // Send notifications (only if isProd is set to true)
40 if (isProd && content?.length) {
41 await discordWebhook({
42 url: Deno.env.get("mentionsDiscord"), // Replace with your Discord webhook URL or other notification method
43 content,
44 });
10 let redirectUrl = DEFAULT_REDIRECT_TARGET; // Assume default redirect initially
11
12 // Check if User-Agent exists and contains 'twitter' or 'discord' (case-insensitive)
13 if (userAgent) {
14 const lowerCaseUserAgent = userAgent.toLowerCase();
15 if (
16 lowerCaseUserAgent.includes("twitter")
17 || lowerCaseUserAgent.includes("discord")
18 ) {
19 // If it matches, set the special redirect target
11
12 try {
13 const DISCORD_PUBLIC_KEY = process.env.discordPublicKey;
14
15 if (!DISCORD_PUBLIC_KEY) {
16 throw new Error("Discord webhook public key is not set");
17 }
18
30
31 // Convert public key and signature to Uint8Array
32 const publicKeyBytes = sodium.from_hex(DISCORD_PUBLIC_KEY);
33 const signatureBytes = sodium.from_hex(signature);
34
44 }
45
46 // Parse the incoming JSON payload from the Discord webhook
47 const payload = await JSON.parse(body);
48
53
54 // Log the entire payload to the console
55 console.log("Discord Webhook Received:", JSON.stringify(payload, null, 2));
56
57 // Respond with a 200 OK to acknowledge receipt
1import { searchWithSerpApi } from "https://esm.town/v/charmaine/searchWithSerpApi";
2import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
3
4// Customize your search parameters
5const KEYWORDS = "\"val town\" OR \"val.town\" OR \"val.run\"";
6const DISCORD_API_KEY = Deno.env.get("mentionsDiscord");
7const SERP_API_KEY = Deno.env.get("SERP_API_KEY");
8
13
14export async function redditAlert({ lastRunAt }: Interval) {
15 if (!SERP_API_KEY || !DISCORD_API_KEY) {
16 console.error("Missing SERP_API_KEY or Discord webhook URL. Exiting.");
17 return;
18 }
59 console.log("Relevant Reddit Posts:", content);
60
61 // Send Discord notification (only if isProd is true)
62 if (isProd) {
63 await discordWebhook({
64 url: DISCORD_API_KEY,
65 content,
66 });
67 console.log("Discord notification sent successfully.");
68 }
69 } catch (error) {
5This template will help you:
61. Search Reddit for specific keywords within a defined time range.
72. Send notifications to your preferred platform (Discord, Slack, email, etc.)
8
9Reddit does not have an API that allows users to scrape data, so we are doing this with the Google Search API, [Serp](https://serpapi.com/).
12
13## Example
14This val tracks mentions of "Val Town" and related terms on Reddit, filtering results from the last 7 days and sending alerts to a Discord webhook.
15
16
52
53### 5. Set Up Your Notification Method
54This template uses a Discord webhook for notifications. You can update this to your preferred platform:
55
56**Create a Discord webhook following [this guide](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks).**
57Save your webhook URL in your Val Town environment variables (you can find this on the left sidebar):
58- Key: `mentionsDiscord`
59- Value: Your Discord webhook URL.
60Notifications will be sent using this function:
61```
62 await discordWebhook({
63 url: Deno.env.get("mentionsDiscord"),
64 content,
65 });
66```
67
68To switch to another platform (e.g., Slack, email, or custom webhooks), replace the discordWebhook call with the appropriate integration ((e.g., [`@std/email`](https://docs.val.town/std/email/), [Slack](https://docs.val.town/integrations/slack/send-messages-to-slack/), or [anywhere else](https://docs.val.town/guides/creating-a-webhook/))
69
70---
9 let redirectUrl = DEFAULT_REDIRECT_TARGET; // Assume default redirect initially
10
11 // Check if User-Agent exists and contains 'twitter' or 'discord' (case-insensitive)
12 if (userAgent) {
13 const lowerCaseUserAgent = userAgent.toLowerCase();
14 if (
15 lowerCaseUserAgent.includes("twitter")
16 || lowerCaseUserAgent.includes("discord")
17 ) {
18 // If it matches, set the special redirect target
12This "migration" runs once on every app startup because it's imported in `index.ts`. You can comment this line out for a slight (30ms) performance improvement on cold starts. It's left in so that users who fork this project will have the migration run correctly.
13
14SQLite has much more limited support for altering existing tables as compared to other databases. Often it's easier to create new tables with the schema you want, and then copy the data over. Happily LLMs are quite good at those sort of database operations, but please reach out in the [Val Town Discord](https://discord.com/invite/dHv45uN5RY) if you need help.
15
16## Queries