68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { Hono } from 'hono'
|
|
import { serveStatic } from 'hono/bun'
|
|
import { Cron } from 'croner'
|
|
import { parseHTML } from 'linkedom'
|
|
import { z } from 'zod'
|
|
|
|
const units: Record<string, number> = {}
|
|
|
|
const app = new Hono()
|
|
|
|
app.use('/*', serveStatic({ root: './dist' }))
|
|
app.get('/api/units', (c) => c.json(units))
|
|
app.get('*', serveStatic({ root: './dist', path: 'index.html' }))
|
|
|
|
const updateExchangeRates = async () => {
|
|
const page = await fetch('https://www.cbr.ru/currency_base/daily/').then((d) => d.text())
|
|
const { document } = parseHTML(page)
|
|
const table = document.querySelector('div.table table')
|
|
if (table) {
|
|
const [_header, ...rows] = document.querySelectorAll('tr')
|
|
|
|
let amountOfUnits = 0
|
|
|
|
const codeParser = z.string().regex(/^[A-Z]{3}$/)
|
|
const amountParser = z.coerce.number().int()
|
|
const priceParser = z
|
|
.string()
|
|
.transform((val) => Number(val.replace('.', '$SPECIAL').replace(',', '.').replace('$SPECIAL', ',')))
|
|
.pipe(z.number())
|
|
|
|
for (const row of rows) {
|
|
const rowData: string[] = [...row.children].map((td) => td.textContent!).filter(Boolean)
|
|
const [_numberCode, _code, _amount, _description, _price] = rowData
|
|
|
|
try {
|
|
const code = codeParser.parse(_code)
|
|
const amount = amountParser.parse(_amount)
|
|
const price = priceParser.parse(_price)
|
|
|
|
units[code] = price / amount
|
|
amountOfUnits++
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
}
|
|
|
|
console.log(`Загружен курс для ${amountOfUnits} валют`)
|
|
} else {
|
|
console.error('При запросе "https://www.cbr.ru/currency_base/daily/" на странице не найдена таблица')
|
|
}
|
|
}
|
|
|
|
updateExchangeRates()
|
|
|
|
new Cron(
|
|
'0 12 * * *',
|
|
{
|
|
name: 'updateExchangeRate',
|
|
timezone: 'Europe/Moscow',
|
|
catch: (e) => {
|
|
console.error(e)
|
|
},
|
|
},
|
|
updateExchangeRates,
|
|
)
|
|
|
|
export default app
|