Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

bytes.dev newsletter notifier

hld architecture diagram for notifier

Tech Stack

How it works

At the lowest level it is powered by 3 main scripts, which are invoked by a scheduled cron job that runs daily

  • scraper Goes to bytes.dev and scrapes latest published newsletter
  • inserter Insert it to SQLite if this newsletter already not exists
  • notifier Uses Pushover API to send ios mobile notifications

Pushover notifications

image

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
import pushover from "https://esm.town/v/pranjaldotdev/pushover";
import { webScrapeBytesNewsletter } from "https://esm.town/v/pranjaldotdev/scraper";
import { sqlite } from "https://esm.town/v/std/sqlite";
// formats date in SQLITE format YYYY-MM-DD
function formatDate(date: string) {
let [month, day, year] = date.split("/");
if (month.length == 1) month = "0" + month;
if (day.length == 1) day = "0" + day;
return `${year}-${month}-${day}`;
}
// insert newsletter metadata in sqlite
async function insertRow(articleNumber: number, title: string, date: string) {
try {
await sqlite.execute({
sql: `insert into newsletter(article_number, title, date) values (:articleNumber, :title, :date)`,
args: { articleNumber, title, date: formatDate(date) },
});
} catch (err) {
console.error(err);
}
}
// check if newsletter id exists
async function checkNewsletterPresent(articleNumber: number) {
const data = await sqlite.execute({
sql: `SELECT EXISTS(SELECT 1 FROM newsletter WHERE article_number=:articleNumber)`,
args: { articleNumber },
});
return data.rows.length === 1;
}
// cron scheduled
export const scheduledNotifier = async (interval: Interval) => {
try {
const data = await webScrapeBytesNewsletter();
const isPresent = await checkNewsletterPresent(data.id);
let title = "";
if (isPresent) {
console.log(`Article ${data.id} already exists!!!`);
title = "Have you still read this amazing bytes.dev newsletter";
} else {
// insert and notify
await insertRow(data.id, data.title, data.date);
title = `Latest bytes.dev newsletter dropped ${data.date}`;
}
const response = await pushover({
token: Deno.env.get("PO_API_TOKEN")!,
user: Deno.env.get("PO_USER_KEY")!,
title,
message: data.title,
url: `https://bytes.dev/archives/${data.id}`,
});
console.log("Notified: ", response);
} catch (err) {
console.error("Error scraping newsletter ", err);
}
};
April 11, 2024