Avatar

chet

22 public vals
Joined July 18, 2022
1
2
3
4
5
import { watchWebsite } from "https://esm.town/v/chet/watchWebsite";
export default async function(interval: Interval) {
await watchWebsite("https://cdec.water.ca.gov/dynamicapp/QueryRES?s=NAT");
}
1
2
3
4
5
import { watchWebsite } from "https://esm.town/v/chet/watchWebsite";
export default async function(interval: Interval) {
await watchWebsite("https://www.livenation.com/venue/KovZpZAJ6lvA/ace-of-spades-events");
}
1
2
3
4
5
6
import { cleanHtml } from "https://esm.town/v/chet/cleanHtml";
import { diffLines } from "https://esm.town/v/chet/diffLines";
export function diffHtml(before: string, after: string) {
return diffLines(cleanHtml(before), cleanHtml(after));
}
1
2
3
4
5
6
7
8
9
10
export function diffLines(before: string, after: string) {
const addedLines = new Set(after.split("\n"));
before.split("\n").forEach((line) => addedLines.delete(line));
const removedLines = new Set(before.split("\n"));
after.split("\n").forEach((line) => removedLines.delete(line));
return [
...Array.from(addedLines).map((line) => "+ " + line),
...Array.from(removedLines).map((line) => "- " + line),
].join("\n");
}
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
export function cleanHtml(html: string) {
// Remove script, link tags
html = html.replace(
/<\s*(script|link|time|form)\b[^<]*(?:(?!<\/\s*\1\s*>)<[^<]*)*<\/\s*\1\s*>/gi,
"",
);
// Remove inputs because date initial values can change
html = html.replace(/<\s*(input)\b[^<]*\s*>/gi, "");
// Remove style attributes
html = html.replace(/ style="[^"]*"/gi, "");
// Separate all tags by newlines
html = html.replace(/><+/g, ">\n<");
// Remove leading/trailing whitespace on each line
html = html.replace(/^\s+|\s+$/gm, "");
// Remove blank lines
html = html.replace(/\n\s*\n/g, "\n");
// Parse out only the contents of the body
var bodyStart = html.indexOf("<body");
if (bodyStart !== -1) {
bodyStart = html.indexOf(">", bodyStart) + 1;
var bodyEnd = html.indexOf("</body", bodyStart);
if (bodyEnd !== -1) {
html = html.substring(bodyStart, bodyEnd);
}
}
return html;
}
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
import { email } from "https://esm.town/v/std/email?v=11";
import { fetchRSS } from "https://esm.town/v/stevekrouse/fetchRSS";
import { newRSSItems } from "https://esm.town/v/stevekrouse/newRSSItems?v=6";
import { parseXML } from "https://esm.town/v/stevekrouse/parseXML";
export default async function(interval: Interval) {
// const lastRunAt = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30 * 3);
const lastRunAt = interval.lastRunAt;
const response = await fetch("https://sive.rs/en.atom");
const xml = await response.text();
const data = await parseXML(xml);
const entries: { id: string; title: string; content: string; updated: string }[] = data.feed.entry;
const newItems = entries.filter(entry => {
return new Date(entry.updated) > new Date(lastRunAt);
});
console.log("New Items", lastRunAt, newItems);
const html = newItems.map(({ id, title, content, updated }) => {
const date = new Date(updated).toLocaleDateString();
return `<h2><a href="${id}">${title}</a> - ${date}</h2>${content}`;
}).join("\n");
if (newItems.length === 0) return;
email({ subject: `Sive.rs: ${newItems[0].title}`, html: html });
}
1
2
3
4
5
import siversRssEmailNotification from "https://esm.town/v/chet/siversRssEmailNotification";
export default async function(interval: Interval) {
await siversRssEmailNotification(interval);
}
1
2
3
4
5
6
7
import { sailingNotify } from "https://esm.town/v/chet/sailingNotify";
import { email } from "https://esm.town/v/std/email";
import process from "node:process";
export async function sailingNotifyCron({ lastRunAt }) {
const result = await sailingNotify("Fair Oaks, CA", 10);
}
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
import { getWeather } from "https://esm.town/v/chet/getWeather";
import { email } from "https://esm.town/v/std/email";
export async function sailingNotify(
where: string,
minWindMph = 10,
minTemp = 80,
) {
const forecast = await getWeather(where, 10);
// Could filter only for hours during the day.
// forecast.forecast.forecastday.map((day) => {
// day.hour.map((hour) => {
// const hourInt = parseInt(hour.time.split(" ")[1].split(":")[0]);
// if (hourInt >= 10 && hourInt <= 19) {
// // Filter only for hours during the day...
// }
// });
// });
const windy = forecast.forecast.forecastday.filter((day) =>
day.day.maxwind_mph >= minWindMph
&& day.day.maxtemp_f >= minTemp
);
if (windy.length === 0)
return console.log("Bad Upcoming Sailing Weather");
const body = windy
.map((day) => `- ${day.date}: ${day.day.maxwind_mph}mph`)
.join("\n");
console.log("Good Upcoming Sailing Weather");
const subject = `Good Sailing Weather for ${where}: ${windy[0].date}`;
const verb = windy.length === 1 ? "is" : "are";
const plural = windy.length === 1 ? "day" : "days";
const message = `There ${verb} ${windy.length} upcoming windy and warm ${plural} in ${where}\n${body}`;
await email({ subject, text: message });
}
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
import { diffHtml } from "https://esm.town/v/chet/diffHtml";
import { blob } from "https://esm.town/v/std/blob?v=11";
import { email } from "https://esm.town/v/std/email?v=11";
import { fetch } from "https://esm.town/v/std/fetch";
export async function watchWebsite(url: string) {
const newHtml = await fetch(url).then(r => r.text());
const key = "watch:" + url;
let oldHtml = "";
try {
oldHtml = await blob.get(key).then(r => r.text());
} catch (error) {}
await blob.set(key, newHtml);
if (!oldHtml) return console.log("NO OLD", { oldHtml, newHtml });
const diff = diffHtml(oldHtml, newHtml);
if (!diff) return console.log("NO DIFF", { oldHtml, newHtml });
console.log("DIFF", diff);
email({ subject: `Diff for ${url}`, text: diff });
}