From cd412b0eef02683f7f56488216c81bc7ccfd39c4 Mon Sep 17 00:00:00 2001 From: Raspberry Pi Date: Thu, 23 Jul 2026 13:06:41 +0530 Subject: [PATCH] init: inital commit --- .gitignore | 34 ++++++++++ README.md | 15 +++++ bun.lock | 30 +++++++++ index.ts | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 15 +++++ tsconfig.json | 29 ++++++++ 6 files changed, 304 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bun.lock create mode 100644 index.ts create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..aee88b8 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# daily-digest + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.2.19. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..21b6f11 --- /dev/null +++ b/bun.lock @@ -0,0 +1,30 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "daily-digest", + "dependencies": { + "dayjs": "^1.11.20", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], + + "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=="], + } +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..af4613b --- /dev/null +++ b/index.ts @@ -0,0 +1,181 @@ +import { $ } from "bun" +import dayjs from "dayjs" +import "dayjs/locale/es" + +const TOPIC = "daily-digest" +const TOKEN = process.env.NTFY_TOKEN + +if (!TOKEN) { + console.error("ntfy token not set") + 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() { + const usd = await getUSD() + const usage = await getUsage() + const weather = await getWeather() + + const message = `Good Morning, SilicoFlare! + +Today is **${dayjs().format("dddd, DD MMMM YYYY")}**. +Hoy es **${dayjs().locale("es").format("dddd, D [de] MMMM [de] YYYY")}**. + +${weather} + +${usd} + +${usage} + + Have a great day!` + + const ntfy = await fetch( + `https://ntfy.hy-pr.link/${TOPIC}`, + { + method: "POST", + headers: { + Authorization: `Bearer ${TOKEN}`, + Title: `Daily Digest for ${dayjs().format("YYYY-MM-DD")}`, + Markdown: "yes", + Tags: "calendar" + }, + body: message, + }, + ) + + if (!ntfy.ok) { + throw new Error(`ntfy failed: ${ntfy.status}`) + } + + console.log("Notification sent at " + dayjs().format("YYYY-MM-dd HH:mm:ss")) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/package.json b/package.json new file mode 100644 index 0000000..55efa46 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "daily-digest", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "dayjs": "^1.11.20" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}