feat: add cron and manual trigger
This commit is contained in:
9
bun.lock
9
bun.lock
@@ -1,10 +1,13 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 0,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "daily-digest",
|
"name": "daily-digest",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "^1.11.20",
|
||||||
|
"eventsource": "^4.1.0",
|
||||||
|
"node-cron": "^4.6.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
@@ -23,6 +26,12 @@
|
|||||||
|
|
||||||
"dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="],
|
"dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="],
|
||||||
|
|
||||||
|
"eventsource": ["eventsource@4.1.0", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ=="],
|
||||||
|
|
||||||
|
"eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="],
|
||||||
|
|
||||||
|
"node-cron": ["node-cron@4.6.0", "", {}, "sha512-Si/bzYiKRHOB8/a99T2+SDGN582ONDMSTlJr5oCkT6GtnqPjZ2s10eoQRYkW9ZHwjVxONL+W8Fb+qR0AHMQsdg=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|||||||
124
fx.ts
Normal file
124
fx.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
|
const API =
|
||||||
|
"https://api.frankfurter.app/latest?from=USD&to=INR"
|
||||||
|
|
||||||
|
export async function getUSD() {
|
||||||
|
const res = await fetch(API)
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Exchange API failed: ${res.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
const rate = (data as { rates: { INR: number } }).rates.INR
|
||||||
|
|
||||||
|
if (typeof rate !== "number") {
|
||||||
|
throw new Error("Invalid exchange rate")
|
||||||
|
}
|
||||||
|
|
||||||
|
const helions = (rate / 100).toFixed(2)
|
||||||
|
|
||||||
|
return `**📈 Exchange Rate**
|
||||||
|
$1 = ₹${rate.toFixed(2)} = ⚙${helions}
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getUsage() {
|
||||||
|
//
|
||||||
|
// CPU LOAD
|
||||||
|
//
|
||||||
|
|
||||||
|
const loadavg = await $`cat /proc/loadavg`.text()
|
||||||
|
const [load1] = loadavg.trim().split(" ")
|
||||||
|
|
||||||
|
//
|
||||||
|
// MEMORY
|
||||||
|
//
|
||||||
|
|
||||||
|
const memRaw = await $`free -b`.text()
|
||||||
|
|
||||||
|
const memLine = memRaw
|
||||||
|
.split("\n")
|
||||||
|
.find((line) => line.startsWith("Mem:"))
|
||||||
|
|
||||||
|
if (!memLine) {
|
||||||
|
throw new Error("Failed to parse memory info")
|
||||||
|
}
|
||||||
|
|
||||||
|
const [
|
||||||
|
,
|
||||||
|
totalMem,
|
||||||
|
usedMem,
|
||||||
|
] = memLine.trim().split(/\s+/)
|
||||||
|
|
||||||
|
//
|
||||||
|
// DISK
|
||||||
|
//
|
||||||
|
|
||||||
|
const diskRaw = await $`df -B1 /`.text()
|
||||||
|
|
||||||
|
const diskLine = diskRaw
|
||||||
|
.split("\n")[1]
|
||||||
|
|
||||||
|
if (!diskLine) {
|
||||||
|
throw new Error("Failed to parse disk info")
|
||||||
|
}
|
||||||
|
|
||||||
|
const [
|
||||||
|
,
|
||||||
|
totalDisk,
|
||||||
|
usedDisk,
|
||||||
|
] = diskLine.trim().split(/\s+/)
|
||||||
|
|
||||||
|
const temp = await $`vcgencmd measure_temp`.text()
|
||||||
|
const tempData = temp.split("=")?.[1]?.replace("'", "°")
|
||||||
|
|
||||||
|
//
|
||||||
|
// HELPERS
|
||||||
|
//
|
||||||
|
|
||||||
|
function formatBytesIEC(bytes: number) {
|
||||||
|
const units = ["B", "KiB", "MiB", "GiB", "TiB"]
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
let value = bytes
|
||||||
|
|
||||||
|
while (value >= 1024 && i < units.length - 1) {
|
||||||
|
value /= 1024
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${value.toFixed(1)} ${units[i]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// OUTPUT
|
||||||
|
//
|
||||||
|
|
||||||
|
return `**💾 Usage**
|
||||||
|
• **CPU:** ${load1}
|
||||||
|
• **Memory:** ${formatBytesIEC(Number(usedMem))} / ${formatBytesIEC(Number(totalMem))}
|
||||||
|
• **Disk:** ${formatBytesIEC(Number(usedDisk))} / ${formatBytesIEC(Number(totalDisk))}
|
||||||
|
• **Temperature:** ${tempData}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getWeather() {
|
||||||
|
const res = await fetch(`https://wttr.in/Bengaluru?format="%c%C| %f | %h | %m | %w | %p"`)
|
||||||
|
const data = await res.text()
|
||||||
|
const [weather, temp, humidity, moon, winds, preci] = data.replace(/\"/g, '').split("|")
|
||||||
|
|
||||||
|
return `**🌥️ Weather Report**
|
||||||
|
• **Weather:** ${weather}
|
||||||
|
• **Temperature:** ${temp}
|
||||||
|
• **Humidity:** ${humidity}
|
||||||
|
• **Moon Phase:** ${moon}
|
||||||
|
• **Winds:** ${winds}
|
||||||
|
• **Precipitation:** ${preci}
|
||||||
|
`
|
||||||
|
}
|
||||||
160
index.ts
160
index.ts
@@ -1,6 +1,9 @@
|
|||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
import cron from "node-cron"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import "dayjs/locale/es"
|
import "dayjs/locale/es"
|
||||||
|
import { getUSD, getUsage, getWeather } from "./fx"
|
||||||
|
import { EventSource } from "eventsource"
|
||||||
|
|
||||||
const TOPIC = "daily-digest"
|
const TOPIC = "daily-digest"
|
||||||
const TOKEN = process.env.NTFY_TOKEN
|
const TOKEN = process.env.NTFY_TOKEN
|
||||||
@@ -10,130 +13,6 @@ if (!TOKEN) {
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const API =
|
|
||||||
"https://api.frankfurter.app/latest?from=USD&to=INR"
|
|
||||||
|
|
||||||
async function getUSD() {
|
|
||||||
const res = await fetch(API)
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`Exchange API failed: ${res.status}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json()
|
|
||||||
|
|
||||||
const rate = data.rates.INR
|
|
||||||
|
|
||||||
if (typeof rate !== "number") {
|
|
||||||
throw new Error("Invalid exchange rate")
|
|
||||||
}
|
|
||||||
|
|
||||||
const helions = (rate / 100).toFixed(2)
|
|
||||||
|
|
||||||
return `**📈 Exchange Rate**
|
|
||||||
$1 = ₹${rate.toFixed(2)} = ⚙${helions}
|
|
||||||
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function getUsage() {
|
|
||||||
//
|
|
||||||
// CPU LOAD
|
|
||||||
//
|
|
||||||
|
|
||||||
const loadavg = await $`cat /proc/loadavg`.text()
|
|
||||||
const [load1] = loadavg.trim().split(" ")
|
|
||||||
|
|
||||||
//
|
|
||||||
// MEMORY
|
|
||||||
//
|
|
||||||
|
|
||||||
const memRaw = await $`free -b`.text()
|
|
||||||
|
|
||||||
const memLine = memRaw
|
|
||||||
.split("\n")
|
|
||||||
.find((line) => line.startsWith("Mem:"))
|
|
||||||
|
|
||||||
if (!memLine) {
|
|
||||||
throw new Error("Failed to parse memory info")
|
|
||||||
}
|
|
||||||
|
|
||||||
const [
|
|
||||||
,
|
|
||||||
totalMem,
|
|
||||||
usedMem,
|
|
||||||
] = memLine.trim().split(/\s+/)
|
|
||||||
|
|
||||||
//
|
|
||||||
// DISK
|
|
||||||
//
|
|
||||||
|
|
||||||
const diskRaw = await $`df -B1 /`.text()
|
|
||||||
|
|
||||||
const diskLine = diskRaw
|
|
||||||
.split("\n")[1]
|
|
||||||
|
|
||||||
if (!diskLine) {
|
|
||||||
throw new Error("Failed to parse disk info")
|
|
||||||
}
|
|
||||||
|
|
||||||
const [
|
|
||||||
,
|
|
||||||
totalDisk,
|
|
||||||
usedDisk,
|
|
||||||
] = diskLine.trim().split(/\s+/)
|
|
||||||
|
|
||||||
const temp = await $`vcgencmd measure_temp`.text()
|
|
||||||
const tempData = temp.split("=")[1].replace("'", "°")
|
|
||||||
|
|
||||||
//
|
|
||||||
// HELPERS
|
|
||||||
//
|
|
||||||
|
|
||||||
function formatBytesIEC(bytes: number) {
|
|
||||||
const units = ["B", "KiB", "MiB", "GiB", "TiB"]
|
|
||||||
|
|
||||||
let i = 0
|
|
||||||
let value = bytes
|
|
||||||
|
|
||||||
while (value >= 1024 && i < units.length - 1) {
|
|
||||||
value /= 1024
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${value.toFixed(1)} ${units[i]}`
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// OUTPUT
|
|
||||||
//
|
|
||||||
|
|
||||||
return `**💾 Usage**
|
|
||||||
• **CPU:** ${load1}
|
|
||||||
• **Memory:** ${formatBytesIEC(Number(usedMem))} / ${formatBytesIEC(Number(totalMem))}
|
|
||||||
• **Disk:** ${formatBytesIEC(Number(usedDisk))} / ${formatBytesIEC(Number(totalDisk))}
|
|
||||||
• **Temperature:** ${tempData}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function getWeather() {
|
|
||||||
const res = await fetch(`https://wttr.in/Bengaluru?format="%c%C| %f | %h | %m | %w | %p"`)
|
|
||||||
const data = await res.text()
|
|
||||||
const [weather, temp, humidity, moon, winds, preci] = data.replace(/\"/g, '').split("|")
|
|
||||||
|
|
||||||
return `**🌥️ Weather Report**
|
|
||||||
• **Weather:** ${weather}
|
|
||||||
• **Temperature:** ${temp}
|
|
||||||
• **Humidity:** ${humidity}
|
|
||||||
• **Moon Phase:** ${moon}
|
|
||||||
• **Winds:** ${winds}
|
|
||||||
• **Precipitation:** ${preci}
|
|
||||||
`
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -175,7 +54,34 @@ ${usage}
|
|||||||
console.log("Notification sent at " + dayjs().format("YYYY-MM-dd HH:mm:ss"))
|
console.log("Notification sent at " + dayjs().format("YYYY-MM-dd HH:mm:ss"))
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => {
|
cron.schedule("0 7 * * *", () => {
|
||||||
console.error(err)
|
main().catch((err) => {
|
||||||
process.exit(1)
|
console.error(err)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const es = new EventSource("https://ntfy.hy-pr.link/daily-digest/sse", {
|
||||||
|
fetch: (input, init) =>
|
||||||
|
fetch(input, {
|
||||||
|
...init,
|
||||||
|
headers: {
|
||||||
|
...init?.headers,
|
||||||
|
Authorization: `Bearer ${TOKEN}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
es.onmessage = (e) => {
|
||||||
|
const msg = JSON.parse(e.data).message as string
|
||||||
|
|
||||||
|
if (msg === "/digest") {
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
es.onerror = (e) => {
|
||||||
|
console.error(e);
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "^1.11.20"
|
"dayjs": "^1.11.20",
|
||||||
|
"eventsource": "^4.1.0",
|
||||||
|
"node-cron": "^4.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user