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
// This val checks Hacker News for news about Patreon, Buy Me A Coffee, and Ko-fi,
// sends an email alert if any are found, and keeps track of notified stories in a database.
// It runs automatically every hour.
import { email } from "https://esm.town/v/std/email";
import { sqlite } from "https://esm.town/v/stevekrouse/sqlite";
const KEY = new URL(import.meta.url).pathname.split("/").at(-1);
const TABLE_NAME = `${KEY}_notified_stories`;
async function initializeDatabase() {
await sqlite.execute(`
CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY,
story_id INTEGER UNIQUE,
notified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
}
async function checkHackerNewsForPatreon() {
await initializeDatabase();
const response = await fetch("https://hacker-news.firebaseio.com/v0/newstories.json");
const storyIds = await response.json();
const keywordRegex = /(patreon|buymeacoffee|ko-fi)/i;
const patreonStories = [];
for (let i = 0; i < Math.min(100, storyIds.length); i++) {
const storyResponse = await fetch(`https://hacker-news.firebaseio.com/v0/item/${storyIds[i]}.json`);
const story = await storyResponse.json();
if (
story && story.id && story.title
&& (keywordRegex.test(story.title) || (story.text && keywordRegex.test(story.text)))
) {
// Check if we've already notified about this story
const result = await sqlite.execute(`SELECT * FROM ${TABLE_NAME} WHERE story_id = ?`, [story.id]);
if (result.rows.length === 0) {
patreonStories.push(story);
// Add the story to the notified list
await sqlite.execute(`INSERT INTO ${TABLE_NAME} (story_id) VALUES (?)`, [story.id]);
}
}
}
if (patreonStories.length > 0) {
const emailContent = patreonStories.map(story =>
`<p><strong>${story.title}</strong><br>
${story.url ? `URL: <a href="${story.url}">${story.url}</a><br>` : ""}
Comments: <a href="https://news.ycombinator.com/item?id=${story.id}">https://news.ycombinator.com/item?id=${story.id}</a></p>`
).join("");
await email({
subject: `Hacker News Alert: ${patreonStories.length} new stories about Patreon, Buy Me A Coffee, or Ko-fi`,
html: `<h1>New Stories about Patreon, Buy Me A Coffee, or Ko-fi on Hacker News</h1>${emailContent}`,
});
} else {
console.log("No new stories found.");
}
// Optionally, clean up old entries (e.g., keep only the last 7 days)
await sqlite.execute(`DELETE FROM ${TABLE_NAME} WHERE notified_at < datetime('now', '-7 days')`);
}
export const main = async () => {
await checkHackerNewsForPatreon();
};
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Comments
Nobody has commented on this val yet: be the first!
September 6, 2024