mirror of
https://github.com/cjdenio/trax-accidents.git
synced 2026-08-05 02:46:34 +00:00
Initial commit
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
||||
# 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
|
||||
uta-emails/
|
||||
database.db
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "trax-accidents",
|
||||
"dependencies": {
|
||||
"date-fns": "^4.1.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"mailparser": "^3.9.4",
|
||||
"rss-parser": "^3.13.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
||||
|
||||
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
||||
|
||||
"@zone-eu/mailsplit": ["@zone-eu/mailsplit@5.4.8", "", { "dependencies": { "libbase64": "1.3.0", "libmime": "5.3.7", "libqp": "2.1.1" } }, "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
||||
|
||||
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"encoding-japanese": ["encoding-japanese@2.2.0", "", {}, "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A=="],
|
||||
|
||||
"entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
|
||||
|
||||
"handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
|
||||
|
||||
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
|
||||
|
||||
"html-to-text": ["html-to-text@9.0.5", "", { "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", "dom-serializer": "^2.0.0", "htmlparser2": "^8.0.2", "selderee": "^0.11.0" } }, "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg=="],
|
||||
|
||||
"htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="],
|
||||
|
||||
"libbase64": ["libbase64@1.3.0", "", {}, "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg=="],
|
||||
|
||||
"libmime": ["libmime@5.3.7", "", { "dependencies": { "encoding-japanese": "2.2.0", "iconv-lite": "0.6.3", "libbase64": "1.3.0", "libqp": "2.1.1" } }, "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw=="],
|
||||
|
||||
"libqp": ["libqp@2.1.1", "", {}, "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow=="],
|
||||
|
||||
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
|
||||
|
||||
"mailparser": ["mailparser@3.9.4", "", { "dependencies": { "@zone-eu/mailsplit": "5.4.8", "encoding-japanese": "2.2.0", "he": "1.2.0", "html-to-text": "9.0.5", "iconv-lite": "0.7.2", "libmime": "5.3.7", "linkify-it": "5.0.0", "nodemailer": "8.0.2", "punycode.js": "2.3.1", "tlds": "1.261.0" } }, "sha512-ZmCnrMnRod+Cq6h7afn9DMFlT1B5gf484Ji+55NhUR4+w4q9z9d7lHHvL8pXP6kp/ehr8pGLQZBmjHpjvItuTQ=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
||||
|
||||
"nodemailer": ["nodemailer@8.0.2", "", {}, "sha512-zbj002pZAIkWQFxyAaqoxvn+zoIwRnS40hgjqTXudKOOJkiFFgBeNqjgD3/YCR12sZnrghWYBY+yP1ZucdDRpw=="],
|
||||
|
||||
"parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="],
|
||||
|
||||
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
|
||||
|
||||
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
|
||||
|
||||
"rss-parser": ["rss-parser@3.13.0", "", { "dependencies": { "entities": "^2.0.3", "xml2js": "^0.5.0" } }, "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
||||
|
||||
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
|
||||
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
|
||||
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
||||
|
||||
"xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
|
||||
|
||||
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"libmime/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/* so the tailwind language server knows we're using tailwind */
|
||||
@import "tailwindcss"
|
||||
@@ -0,0 +1,44 @@
|
||||
import Handlebars from "handlebars";
|
||||
import { Database } from "bun:sqlite";
|
||||
import { differenceInDays, startOfToday, startOfDay } from "date-fns";
|
||||
|
||||
const db = new Database("database.db");
|
||||
|
||||
await Bun.cron("./scripts/sync-rss.js", "*/5 * * * *", "rss-sync");
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/": async () => {
|
||||
const homeTemplate = Handlebars.compile(
|
||||
await Bun.file(
|
||||
import.meta.dir + "/templates/home.handlebars",
|
||||
).text(),
|
||||
);
|
||||
|
||||
const latestCollision = await db
|
||||
.query(
|
||||
"SELECT * FROM service_alerts WHERE is_collision ORDER BY date DESC LIMIT 1;",
|
||||
)
|
||||
.get();
|
||||
|
||||
return new Response(
|
||||
homeTemplate({
|
||||
daysSince: differenceInDays(
|
||||
startOfToday(new Date()),
|
||||
startOfDay(new Date(latestCollision.date)),
|
||||
),
|
||||
latestCollision: {
|
||||
...latestCollision,
|
||||
formattedDate: new Date(
|
||||
latestCollision.date,
|
||||
).toLocaleDateString("en-US", { dateStyle: "medium" }),
|
||||
},
|
||||
}),
|
||||
{
|
||||
headers: { "Content-Type": "text/html" },
|
||||
},
|
||||
);
|
||||
},
|
||||
"/uta-logo.svg": Bun.file(import.meta.dir + "/public/uta-logo.svg"),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "trax-accidents",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": "^4.1.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"mailparser": "^3.9.4",
|
||||
"rss-parser": "^3.13.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 149.8 39.8" enable-background="new 0 0 149.8 39.8" xml:space="preserve">
|
||||
<g>
|
||||
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:2.71925px;line-height:1.25;font-family:sans-serif;fill:#0055A4;fill-opacity:1;stroke:none;stroke-width:0.509861" x="143.08537" y="32.567734">
|
||||
<tspan x="144.8" y="32.567734" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.71925px;font-family:Arial;-inkscape-font-specification:Arial;fill:#0055a4;fill-opacity:1;stroke-width:0.509861">SM</tspan>
|
||||
</text>
|
||||
<path fill="#0055A4" d="m 9.7,26.1 c 2.5,0 4,-1.6 4,-4.8 V 9.8 H 18 v 11.5 c 0,6 -3.3,9.2 -8.4,9.2 -5.2,0 -8.5,-3.2 -8.5,-9.2 V 9.8 h 4.7 v 11 c 0.080136,2.902026 1.7300152,5.246593 3.9,5.3 z" />
|
||||
<polygon fill="#0055A4" points="37.5,14 31.1,14 31.1,9.8 48.7,9.8 48.7,14 42.3,14 42.3,29.9 37.5,29.9" />
|
||||
<path fill="#0055A4" d="M66.3,23.5l2.6-6.2V9.3L58.8,29.9h4.8l1.3-2.9h4v-3.5H66.3z M68.9,17.3L68.9,17.3l2.6,6.2h-2.6v3.5H73 l1.3,2.9h5l-10-20.7H69l-0.1,0.2V17.3z" />
|
||||
<rect x="93" y="9.6" fill="#EC1C24" width="56" height="10" />
|
||||
<rect x="93" y="23.6" fill="#0055A4" width="56" height="6" />
|
||||
<path fill="#0055A4" d="m 120.9,0.9 c -6.7,0 -12.51406,3.40625 -15.91406,8.70625 h 4.9 C 112.68594,6.60625 116.5,4.8 120.9,4.8 c 8.3,0 15.1,6.8 15.1,15.1 0,8.3 -6.99687,14.795313 -15.39687,14.795313 -4.5,0 -8.5,-2 -11.3,-5.1 h -4.8 c 3.3,5.4 9.3,9 16.1,9 10.5,0 19.29687,-8.195313 19.29687,-18.695313 0,-10.5 -8.4,-19 -19,-19" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
import { simpleParser } from "mailparser";
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database("database.db");
|
||||
|
||||
const emails = await readdir("uta-emails/");
|
||||
|
||||
for (const filename of emails) {
|
||||
const data = await simpleParser(
|
||||
await Bun.file(`uta-emails/${filename}`).text(),
|
||||
);
|
||||
|
||||
const result = data.text.match(
|
||||
/train[- ]v(?:ersus|s?\.?)?[- ](?:auto|vehicle)/i,
|
||||
);
|
||||
|
||||
let content = data.text;
|
||||
|
||||
const footerIndex = content.indexOf(
|
||||
"You have received this message because you are subscribed to UTA Service Alerts",
|
||||
);
|
||||
if (footerIndex != -1) {
|
||||
content = content.slice(0, footerIndex).trim();
|
||||
}
|
||||
|
||||
db.query(
|
||||
`INSERT INTO service_alerts (
|
||||
id,
|
||||
date,
|
||||
content,
|
||||
is_collision
|
||||
) VALUES ($id, $date, $content, $isCollision) ON CONFLICT DO NOTHING`,
|
||||
).run({
|
||||
$id: Bun.hash(data.date.toISOString() + data.text).toString(),
|
||||
$date: data.date.toISOString(),
|
||||
$content: content,
|
||||
$isCollision: result !== null,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import Parser from "rss-parser";
|
||||
|
||||
const URLS = [
|
||||
"https://content.govdelivery.com/accounts/UTTA/widgets/UTTA_WIDGET_3.rss", // blue line
|
||||
"https://content.govdelivery.com/accounts/UTTA/widgets/UTTA_WIDGET_4.rss", // red line
|
||||
"https://content.govdelivery.com/accounts/UTTA/widgets/UTTA_WIDGET_5.rss", // green line
|
||||
"https://content.govdelivery.com/accounts/UTTA/widgets/UTTA_WIDGET_11.rss", // s-line
|
||||
"https://content.govdelivery.com/accounts/UTTA/widgets/UTTA_WIDGET_7.rss", // frontrunner
|
||||
];
|
||||
|
||||
export default {
|
||||
async scheduled(controller) {
|
||||
const db = new Database(import.meta.dir + "/../database.db");
|
||||
|
||||
const parser = new Parser();
|
||||
|
||||
for (const url of URLS) {
|
||||
const feed = await parser.parseURL(url);
|
||||
|
||||
for (const item of feed.items) {
|
||||
const result = item.contentSnippet.match(
|
||||
/train[- ]v(?:ersus|s?\.?)?[- ](?:auto|vehicle)/i,
|
||||
);
|
||||
|
||||
let content = item.contentSnippet;
|
||||
|
||||
const footerIndex = content.indexOf(
|
||||
"You have received this message because you are subscribed to UTA Service Alerts",
|
||||
);
|
||||
if (footerIndex != -1) {
|
||||
content = content.slice(0, footerIndex).trim();
|
||||
}
|
||||
|
||||
db.query(
|
||||
`INSERT INTO service_alerts (
|
||||
id,
|
||||
date,
|
||||
content,
|
||||
is_collision
|
||||
) VALUES ($id, $date, $content, $isCollision) ON CONFLICT DO NOTHING`,
|
||||
).run({
|
||||
$id: Bun.hash(
|
||||
item.isoDate + item.contentSnippet,
|
||||
).toString(),
|
||||
$date: item.isoDate,
|
||||
$content: content,
|
||||
$isCollision: result !== null,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>IT HAS BEEN {{ daysSince }} DAYS SINCE A CAR HIT A UTA TRAIN</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<meta charset="UTF-8" />
|
||||
</head>
|
||||
<body class="py-10">
|
||||
<div
|
||||
class="max-w-96 bg-blue-950 text-white mx-auto rounded-lg p-8 space-y-4 mb-10"
|
||||
>
|
||||
<p class="text-center text-4xl">IT HAS BEEN</p>
|
||||
<div
|
||||
class="bg-white text-black text-9xl w-fit mx-auto p-4 rounded-lg"
|
||||
>
|
||||
{{ daysSince }}
|
||||
</div>
|
||||
<p class="text-center text-5xl">DAYS</p>
|
||||
<p class="text-center text-2xl">
|
||||
SINCE A CAR HIT A
|
||||
<img src="/uta-logo.svg" alt="UTA logo" class="w-32 mx-auto" />
|
||||
TRAIN
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="max-w-96 mx-auto font-sans overflow-hidden">
|
||||
<div class="bg-gray-200 border border-gray-500 rounded-t-lg px-2">{{ latestCollision.formattedDate }}</div>
|
||||
<pre class="font-sans text-wrap border border-gray-500 border-t-0 rounded-b-lg p-2">{{ latestCollision.content }}</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user