init: inital commit
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -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
|
||||||
15
README.md
Normal file
15
README.md
Normal file
@@ -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.
|
||||||
30
bun.lock
Normal file
30
bun.lock
Normal file
@@ -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=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
181
index.ts
Normal file
181
index.ts
Normal file
@@ -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)
|
||||||
|
})
|
||||||
15
package.json
Normal file
15
package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
tsconfig.json
Normal file
29
tsconfig.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user