3 Commits

Author SHA1 Message Date
b2b72b3b88 Merge branch 'main' of https://git.n8x.sx/chiko/77th_eventcalenderntfy 2025-10-21 23:49:40 +02:00
2bcd3a4d61 merging... 2025-10-21 23:47:11 +02:00
999bdf7222 fix-docker (#1)
Reviewed-on: #1
2025-10-21 00:52:32 +00:00
32 changed files with 264 additions and 870 deletions

View File

@@ -9,7 +9,7 @@ LICENSE
.vscode .vscode
Makefile Makefile
helm-charts helm-charts
.env* .env
.editorconfig .editorconfig
.idea .idea
coverage* coverage*

View File

@@ -1,15 +1,9 @@
TZ=Europe/Berlin
DB_FILEPATH=./data/db
DB_FILENAME=77th_eventntfy.db
apprise_https=false
apprise_hostname=apprise
apprise_port=8000
notification_mock=true
ntfy_on=true ntfy_on=true
ntfy_username=chiko ntfy_username=chiko
ntfy_password=Blub ntfy_password=Blub
ntfy_host=ntfy.some-service.com ntfy_host=ntfy.some-service.com
ntfy_topic=SomeTopic ntfy_topic=SomeTopic
dc_on=true dc_on=true
dc_webhook=123123123123123/ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF dc_webhook=123123123123123/ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF
dc_botname=Botname Here dc_botname=Botname Here

View File

@@ -1 +0,0 @@
.eslintrc.cjs

View File

@@ -1,17 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/no-floating-promises": [
"error",
{
"ignoreVoid": false,
"ignoreIIFE": false
}
]
}
}

View File

@@ -1,17 +0,0 @@
/* eslint-env node */
export default {
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
parserOptions: {
project: "./tsconfig.json",
sourceType: "module"
},
rules: {
"@typescript-eslint/no-floating-promises": "error"
}
};

1
.gitignore vendored
View File

@@ -23,7 +23,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
.env.test.local .env.test.local
.env.production.local .env.production.local
.env.local .env.local
.env.*
# caches # caches
.eslintcache .eslintcache

View File

@@ -1,7 +0,0 @@
{
"eslint.enable": true,
"eslint.validate": [
"javascript",
"typescript"
]
}

2
Crontab Normal file
View File

@@ -0,0 +1,2 @@
1 * * * * bun run ./src/app.ts --today > /dev/null 2>&1
0 * * * * bun run ./src/app.ts > /dev/null 2>&1

View File

@@ -3,30 +3,32 @@ ARG BUILD_DATE
ARG VERSION ARG VERSION
LABEL build_version="77th_eventcalendarntfy ${VERSION}, Build-date:- ${BUILD_DATE}" LABEL build_version="77th_eventcalendarntfy ${VERSION}, Build-date:- ${BUILD_DATE}"
LABEL maintainer="chiko <chiko@xcsone.de>" LABEL maintainer="chiko <chiko@xcsone.de>"
ENV TZ=Europe/Berlin
WORKDIR /opt/app WORKDIR /opt/app
<<<<<<< HEAD
RUN set -eux && \ RUN set -eux && \
echo "Updating APT" && \ echo "Updating APT" && \
apt-get update -y -qq && \ apt-get update -y -qq && \
apt-get upgrade -y -qq && \ apt-get upgrade -y -qq && \
echo "Installing tools" && \ echo "Installing tools" && \
apt-get install -y -qq \ apt-get install -y -qq \
curl unzip cron ca-certificates logrotate dos2unix tzdata && \ curl unzip cron ca-certificates logrotate && \
echo "Remove exim" && \
apt-get remove -y -qq exim4 exim4-base exim4-daemon-light && \
echo "Cleaning up" && \ echo "Cleaning up" && \
apt-get --yes autoremove --purge -qq && \ apt-get --yes autoremove --purge && \
apt-get clean --yes -qq && \ apt-get clean --yes && \
rm --recursive --force --verbose /var/lib/apt/lists/* && \ rm --recursive --force --verbose /var/lib/apt/lists/* && \
rm --recursive --force --verbose /tmp/* && \ rm --recursive --force --verbose /tmp/* && \
rm --recursive --force --verbose /var/tmp/* && \ rm --recursive --force --verbose /var/tmp/* && \
rm --recursive --force --verbose /var/cache/apt/archives/* && \ rm --recursive --force --verbose /var/cache/apt/archives/* && \
truncate --size 0 /var/log/*log truncate --size 0 /var/log/*log
=======
RUN apt-get update && \
# apt-get install -y curl unzip cron ca-certificates python3 python3-pip && \
apt-get install -y curl unzip cron ca-certificates python3 python3-pip && \
rm -rf /var/lib/apt/lists/*
>>>>>>> 999bdf7222a68995dd40ec7da967b360f67d354a
# install BunJs # install BunJs
RUN curl -fsSL https://bun.com/install | bash RUN curl -fsSL https://bun.com/install | bash
ENV PATH="/root/.bun/bin:$PATH" ENV PATH="/root/.bun/bin:$PATH"
RUN ln -s /root/.bun/bin/bun /usr/local/bin/bun
# install dependencies into temp directory # install dependencies into temp directory
# this will cache them and speed up future builds # this will cache them and speed up future builds
FROM base AS install FROM base AS install
@@ -39,26 +41,49 @@ RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/ COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production RUN cd /temp/prod && bun install --frozen-lockfile --production
<<<<<<< HEAD
COPY ./docker/Crontab /etc/cron.d/
RUN chmod 0644 /etc/cron.d/Crontab
COPY ./docker/cron-bun-log /etc/logrotate.d/
RUN mkdir /var/log/cron && touch /var/log/cron.log
=======
# and install python dependencies
# COPY ./requirements.txt .
# RUN python3 -m pip install --break-system-packages -r requirements.txt
# RUN python3 -m pip install -U python-dotenv
>>>>>>> 999bdf7222a68995dd40ec7da967b360f67d354a
# copy node_modules from temp directory # copy node_modules from temp directory
# then copy all (non-ignored) project files into the image # then copy all (non-ignored) project files into the image
FROM base AS prerelease FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules COPY --from=install /temp/dev/node_modules node_modules
COPY . . COPY . ./
<<<<<<< HEAD
=======
>>>>>>> 999bdf7222a68995dd40ec7da967b360f67d354a
# [optional] tests & build # [optional] tests & build
ENV NODE_ENV=production
# copy production dependencies and source code into final image # copy production dependencies and source code into final image
FROM base AS release FROM base AS release
ENV NODE_ENV=production WORKDIR /opt/app
COPY --from=install /temp/prod/node_modules node_modules COPY --from=install /temp/prod/node_modules node_modules
# COPY ./docker/cron-bun-log /etc/logrotate.d/ COPY --from=prerelease /opt/app/package.json .
COPY ./docker/Crontab /etc/cron.d/ #COPY --from=prerelease .entrypoint.sh .
RUN chmod 0644 /etc/cron.d/Crontab && \ <<<<<<< HEAD
chown root:root /etc/cron.d/Crontab COPY . ./
COPY . . RUN mkdir /var/log/cron && touch /var/log/cron.log
RUN dos2unix \ VOLUME /opt/app/data/db
/opt/app/docker/docker-entrypoint.sh \ # VOLUME /var/log/cron
/opt/app/run-task.sh \ =======
/etc/cron.d/Crontab COPY Crontab /etc/cron.d/
RUN chmod +x /opt/app/docker/docker-entrypoint.sh && \ RUN chmod 0644 /etc/cron.d/Crontab
chmod +x /opt/app/run-task.sh COPY . ./
ENTRYPOINT [ "/opt/app/docker/docker-entrypoint.sh" ] # USER bun
RUN touch /var/log/cron.log
# RUN chmod +x entrypoint.sh
# ENTRYPOINT ["./entrypoint.sh"]
VOLUME /opt/app/data/db
>>>>>>> 999bdf7222a68995dd40ec7da967b360f67d354a
CMD bun run ./src/app.ts --today && cron && tail -f /var/log/cron.log

View File

@@ -1,27 +1,15 @@
# 77th Event Calendar Notifcations # 77th Event Calender Notifcations
To install dependencies: To install dependencies:
```bash ```bashe
bun install bun install
``` ```
To run: To run:
```bash ```bash
bun run ./src/app.ts bun run index.ts
bun run start
bun run dev
``` ```
## Docker This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
```bash
docker compose build
docker compose up -d
```
## Parameter
### --today
fetch all Events, track all Changes (new, changed and deleted Events) and additionally Send a Notification for todays mission

254
bun.lock
View File

@@ -8,282 +8,30 @@
"minimist": "^1.2.8", "minimist": "^1.2.8",
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.38.0",
"@types/bun": "^1.3.0", "@types/bun": "^1.3.0",
"@types/minimist": "^1.2.5",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^9.38.0",
"globals": "^16.4.0",
"jiti": "^2.6.1",
"typescript-eslint": "^8.46.2",
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.9.3", "typescript": "^5",
}, },
}, },
}, },
"packages": { "packages": {
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.1", "", { "dependencies": { "@eslint/core": "^0.16.0" } }, "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw=="],
"@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
"@eslint/js": ["@eslint/js@9.38.0", "", {}, "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="], "@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="],
"@types/node": ["@types/node@24.8.1", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q=="], "@types/node": ["@types/node@24.8.1", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/type-utils": "8.46.2", "@typescript-eslint/utils": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.2", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.2", "@typescript-eslint/types": "^8.46.2", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.2", "", { "dependencies": { "@typescript-eslint/types": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2" } }, "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.46.2", "", { "dependencies": { "@typescript-eslint/types": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2", "@typescript-eslint/utils": "8.46.2", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.46.2", "", {}, "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.2", "@typescript-eslint/tsconfig-utils": "8.46.2", "@typescript-eslint/types": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.46.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.2", "", { "dependencies": { "@typescript-eslint/types": "8.46.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], "bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.38.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.1", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.38.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw=="],
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"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=="],
"typescript-eslint": ["typescript-eslint@8.46.2", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.2", "@typescript-eslint/parser": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2", "@typescript-eslint/utils": "8.46.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg=="],
"undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="], "undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
} }
} }

View File

@@ -1,9 +1,12 @@
services: services:
app: app:
image: chiko/77th_eventcalendarntfy:v0.1.3
build: . build: .
volumes: volumes:
- ./data/db:/opt/app/data/db - ./data/db:/opt/app/data/db
<<<<<<< HEAD
- ./data/app/log:/var/log
=======
>>>>>>> 999bdf7222a68995dd40ec7da967b360f67d354a
env_file: env_file:
- path: ./.env - path: ./.env
required: true required: true
@@ -13,7 +16,7 @@ services:
links: links:
- apprise - apprise
apprise: apprise:
image: caronc/apprise:1.2.2 image: caronc/apprise:latest
hostname: apprise hostname: apprise
environment: environment:
- APPRISE_WORKER_COUNT=1 - APPRISE_WORKER_COUNT=1
@@ -24,10 +27,23 @@ services:
- ./data/apprise/config:/config - ./data/apprise/config:/config
- ./data/apprise/plugin:/plugin - ./data/apprise/plugin:/plugin
- ./data/apprise/attach:/attach - ./data/apprise/attach:/attach
<<<<<<< HEAD
# ports: # ports:
#- 8880:8000 #- 8880:8000
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/status"] test: ["CMD", "curl", "-f", "http://localhost:8000/status"]
interval: 5s interval: 10s
timeout: 3s timeout: 5s
=======
ports:
- 8000:8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/status"]
interval: 30s
timeout: 10s
>>>>>>> 999bdf7222a68995dd40ec7da967b360f67d354a
retries: 5 retries: 5
# networks:
# default:
# external: true
# name: npm

View File

@@ -1,5 +1,2 @@
SHELL=/bin/bash 8 * * * * bun run ./src/app.ts --today > /var/log/cron.log 2>&1
MAILTO="" */15 * * * * bun run ./src/app.ts > /var/log/cron.log 2>&1
0 8 * * * root . /etc/cron-env.sh && /opt/app/run-task.sh --today >> /proc/1/fd/1 2>&1
*/15 * * * * root . /etc/cron-env.sh && /opt/app/run-task.sh >> /proc/1/fd/1 2>&1
* * * * * root echo "cron test ran at $(date)" >> /proc/1/fd/1 2>&1

View File

@@ -1,40 +0,0 @@
#!/bin/bash
# Create or overwrite the cron env file
cat /dev/null > /etc/cron-env.sh
chmod 600 /etc/cron-env.sh
chmod +x /etc/cron-env.sh
# Write the Env Vars into a file for cron. happens during runtime of the container and not build.
# List your environment variables here
env_vars=(
TZ
DB_FILEPATH
DB_FILENAME
apprise_https
apprise_hostname
apprise_port
notification_mock
ntfy_on
ntfy_username
ntfy_password
ntfy_host
ntfy_topic
dc_on
dc_webhook
dc_botname
dc_avatar_url
)
for var in "${env_vars[@]}"; do
val="${!var}"
if [ -n "$val" ]; then
# Safely export the variable with proper quoting
printf 'export %s=%q\n' "$var" "$val" >> /etc/cron-env.sh
fi
done
export PATH="/root/.bun/bin:$PATH"
bun run /opt/app/src/app.ts --today
# Start cron in foreground
exec cron -f

View File

@@ -1,9 +0,0 @@
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import { defineConfig } from "eslint/config";
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
tseslint.configs.recommended,
]);

View File

@@ -1,32 +1,23 @@
{ {
"version": "0.1.3", "version": "0.1.0",
"name": "77th_eventcalendarnotification", "name": "eventcalender",
"module": "./src/app.ts", "module": "./src/app.ts",
"type": "module", "type": "module",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.38.0", "@types/bun": "^1.3.0"
"@types/bun": "^1.3.0",
"@types/minimist": "^1.2.5",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^9.38.0",
"globals": "^16.4.0",
"jiti": "^2.6.1",
"typescript-eslint": "^8.46.2"
}, },
"scripts": { "scripts": {
"prod": "NODE_ENV=production bun run ./src/app.ts", "dev": "bun run ./src/app.ts",
"dev": "NODE_ENV=development bun ./src/app.ts", "dev:init": "bun run ./src/app.ts --init",
"db:init": "bun run ./run/db_init.ts", "db:init": "bun run ./run/db_init.ts",
"db:deleteall": "bun run ./run/db_event_deleteall.ts", "db:deleteall": "bun run ./run/db_deleteall.ts",
"db:event:dedup": "bun run ./run/db_event_delete_duplicates.ts", "build": "bun build --compile --minify --sourcemap ./src/app.ts --outfile ./build/77th_event_calendar_notification",
"build": "bun build --compile --minify --sourcemap ./src/app.ts --outfile ./build/77th_eventcalendarnotification", "build:linux": "bun build --compile --minify --sourcemap --target=bun-linux-arm64 ./src/app.ts --outfile ./build/77th_event_calendar_notification",
"build:linux": "bun build --compile --minify --sourcemap --target=bun-linux-arm64 ./src/app.ts --outfile ./build/77th_eventcalendarnotification",
"docker:build": "docker build -t chiko/77th_eventcalendarntfy:0.1.0 ." "docker:build": "docker build -t chiko/77th_eventcalendarntfy:0.1.0 ."
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.9.3" "typescript": "^5"
}, },
"dependencies": { "dependencies": {
"dotenv": "^17.2.3", "dotenv": "^17.2.3",

View File

@@ -1,26 +0,0 @@
#!/bin/bash
# set -e
export PATH="/root/.bun/bin:$PATH"
set -o allexport
. /etc/cron-env.sh || echo "[WARN] Failed to load env" >> /proc/1/fd/2
set +o allexport
log_info() {
echo "[INFO] $(date) $1" >> /proc/1/fd/1
}
log_error() {
echo "[ERROR] $(date) $1" >> /proc/1/fd/2
}
log_info "Starting task with args: $*"
cd /opt/app
if bun run start "$@" >> /proc/1/fd/1 2>> /proc/1/fd/2; then
log_info "Task completed successfully."
else
log_error "Task failed!"
exit 1
fi

View File

@@ -1,10 +0,0 @@
import * as db from "../src/sql";
const query = db.db.query(`DELETE FROM events
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM events
GROUP BY uid
);`);
query.run();

View File

@@ -1,39 +0,0 @@
import db from "../src/sql";
const run_migration = db.transaction(() => {
// SQL 1: Insert a new user
db.run(`DELETE FROM events
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM events
GROUP BY uid
);`);
// SQL 2: Update product stock
db.run(`CREATE TABLE events_new (
"event_uid" INTEGER PRIMARY KEY,
"uid" TEXT NOT NULL UNIQUE,
"title" TEXT NOT NULL,
"date_at" DATETIME NOT NULL,
"time_start" TEXT NOT NULL,
"time_end" TEXT NOT NULL,
"posted_by" TEXT NOT NULL,
"location" TEXT NOT NULL,
"event_type" TEXT NOT NULL,
"link" TEXT NOT NULL,
"description" TEXT NOT NULL,
"timezone" TEXT NOT NULL,
"notification" TEXT NOT NULL,
"deleteDate" INTEGER NULL
);`);
// SQL 3: Log the transaction
db.run(`INSERT INTO events_new (event_uid, uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification, deleteDate)
SELECT event_uid, uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification, deleteDate FROM events;
`);
db.run(`DROP TABLE events;
ALTER TABLE events_new RENAME TO events;`);
});
// Run the transaction
run_migration();

View File

@@ -1 +0,0 @@
CREATE UNIQUE INDEX idx_events_uid ON events(uid);

View File

@@ -1,6 +0,0 @@
DELETE FROM events
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM events
GROUP BY uid
);

View File

@@ -1,4 +0,0 @@
SELECT uid, COUNT(*) AS count
FROM events
GROUP BY uid
HAVING COUNT(*) > 1;

View File

@@ -1,29 +0,0 @@
DELETE FROM events
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM events
GROUP BY uid
);
CREATE TABLE events_new (
"event_uid" INTEGER PRIMARY KEY,
"uid" TEXT NOT NULL UNIQUE,
"title" TEXT NOT NULL,
"date_at" DATETIME NOT NULL,
"time_start" TEXT NOT NULL,
"time_end" TEXT NOT NULL,
"posted_by" TEXT NOT NULL,
"location" TEXT NOT NULL,
"event_type" TEXT NOT NULL,
"link" TEXT NOT NULL,
"description" TEXT NOT NULL,
"timezone" TEXT NOT NULL,
"notification" TEXT NOT NULL,
"deleteDate" INTEGER NULL
);
INSERT INTO events_new (event_uid, uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification, deleteDate)
SELECT event_uid, uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification, deleteDate FROM events;
DROP TABLE events;
ALTER TABLE events_new RENAME TO events;

View File

@@ -1,49 +1,45 @@
import { TEventType } from "./component/event/event.types";
import { db } from "./sql"; import { db } from "./sql";
import { Event, type TEventEntityNew, type TGetEventsOptions } from "./component/event/events"; import { Event, type TEventEntityNew, type TGetEventsOptions } from "./component/event/events";
import { createPlaceholders, getTsNow } from "./util"; import { createPlaceholders, getTsNow, pad_l2 } from "./util";
import { sendNotification } from "./sendNotification"; import { sendNotification } from "./sendNotification";
import minimist from "minimist";
const argv = minimist(process.argv.slice(2)) const argv = require('minimist')(process.argv.slice(2));
console.log("App started"); console.log("App started");
console.dir({argv}) console.dir({argv})
const TODAY = getTsNow(); async function main ( ) {
console.dir({TODAY}); console.log("Excecuting main()");
const TODAY = getTsNow();
console.dir(TODAY);
const events_currentMonth = await Event.fetch_events( TODAY.year, TODAY.month , -120 );
console.log("events_currentMonth.length:" + events_currentMonth.length );
const events_nextMonth = await Event.fetch_events( TODAY.year, TODAY.month + 1 , -120 );
console.log("events_nextMonth.length:" + events_nextMonth.length );
const events = [...events_currentMonth, ...events_nextMonth];
console.log("events.length:" + events.length );
async function events_update_db() { // const TS_TODAY = new Date();
const events_fetched_currentMonth = await Event.fetch_events( TODAY.year, TODAY.month , -120 ); // Write to JSON File Section START
console.log("events_fetched_currentMonth.length: " + events_fetched_currentMonth.length ); // const data = JSON.stringify(events, null, 2);
const events_fetched_nextMonth = await Event.fetch_events( TODAY.year, TODAY.month + 1 , -120 ); // const TS = `${TS_TODAY.getFullYear()}-${TS_TODAY.getMonth() + 1}-${TS_TODAY.getDate()}_${TS_TODAY.getHours()}-${TS_TODAY.getMinutes()}-${TS_TODAY.getSeconds()}`;
console.log("events_fetched_nextMonth.length: " + events_fetched_nextMonth.length ); // await Bun.write(path.join(import.meta.dir, "output", `output_${TS}.json`), data );
const events_fetched = [...events_fetched_currentMonth, ...events_fetched_nextMonth]; // Write to JSON File Section END
console.log("events_fetched.length: " + events_fetched.length );
const events_fetched_list_of_uids = events_fetched.map( event => { return event.uid; }); const allEventUids = events.map( event => { return event.uid; });
console.dir({events_fetched_list_of_uids} ); console.dir(allEventUids );
const placeholders = createPlaceholders( allEventUids );
const events_db_currentMonth = Event.get_events({month: {year: TODAY.year, month: TODAY.month}}, db);
const events_removed: Event[] = events_db_currentMonth.filter( (ev) => {
return ! events_fetched_list_of_uids.includes(ev.uid);
});
console.dir({events_removed});
events_removed.forEach( ev => {
ev.set_notification("removed", db);
});
const placeholders = createPlaceholders( events_fetched_list_of_uids );
const getAllRelevantEventsQuery = db.query( const getAllRelevantEventsQuery = db.query(
`SELECT * FROM events WHERE uid IN (${placeholders}) AND deleteDate IS NULL;` `SELECT * FROM events WHERE uid IN (${placeholders}); `
).as(Event ); ).as(Event );
const AllRelevantEvents = getAllRelevantEventsQuery.all(...events_fetched_list_of_uids); const AllRelevantEvents = getAllRelevantEventsQuery.all(...allEventUids);
console.log("AllRelevantEvents.length: " + AllRelevantEvents.length ); console.log("AllRelevantEvents.length:" + AllRelevantEvents.length );
const eventsToInsert: TEventEntityNew[] = []; const eventsToInsert: TEventEntityNew[] = [];
for ( const ev of events_fetched ) { for ( const ev of events ) {
console.log("loop ev " + ev.uid + " : " + [ ev.title, ev.date_at ].join( ", " ) ); console.log("loop ev: " + [ ev.uid, ev.title, ev.date_at ].join( ", " ) );
const found = AllRelevantEvents.find(event => event.uid === ev.uid); const found = AllRelevantEvents.find(event => event.uid === ev.uid);
if ( found ) { if ( found ) {
console.log("loop ev " + ev.uid + " found: " + [ found.title, found.date_at ].join( ", " ) ); console.log("loop ev found: " + [ found.uid, found.title, found.date_at ].join( ", " ) );
if ( if (
found.title != ev.title || found.title != ev.title ||
found.description != ev.description || found.description != ev.description ||
@@ -56,25 +52,20 @@ async function events_update_db() {
found.timezone != ev.timezone || found.timezone != ev.timezone ||
found.link != ev.link found.link != ev.link
) { ) {
console.log("loop ev " + ev.uid + " different (changed): " + [ ev.title, ev.date_at ].join( ", " ) ); console.log("loop ev different (changed): " + [ ev.uid, ev.title, ev.date_at ].join( ", " ) );
const newEventToInsert: TEventEntityNew = {... ev, notification: "changed"}; const newEventToInsert: TEventEntityNew = {... ev, notification: "changed"};
eventsToInsert.push( newEventToInsert ); eventsToInsert.push( newEventToInsert );
} }
} else { } else {
console.log("loop ev " + ev.uid + " added (new): " + [ ev.title, ev.date_at ].join( ", " ) ); console.log("loop ev added (new): " + [ ev.uid, ev.title, ev.date_at ].join( ", " ) );
const newEventToInsert: TEventEntityNew = {... ev, notification: "new"}; const newEventToInsert: TEventEntityNew = {... ev, notification: "new"};
eventsToInsert.push( newEventToInsert ); eventsToInsert.push( newEventToInsert );
} }
} }
console.dir({eventsToInsert}) console.dir(eventsToInsert)
Event.insert( eventsToInsert, db); Event.insert( eventsToInsert, db);
} const where: TGetEventsOptions = {}
where.notification = ["new", "changed"]
async function events_check_for_notification() {
const where: TGetEventsOptions = {
notification: ["new", "changed", "removed"],
deleted: false
}
if ( argv.today ) { if ( argv.today ) {
where.date = { where.date = {
year: TODAY.year, year: TODAY.year,
@@ -88,27 +79,71 @@ async function events_check_for_notification() {
where where
}); });
for ( const ev of list_of_events ) { for ( const ev of list_of_events ) {
console.log("loop list_of_events - ev: " + [ ev.uid, ev.title, ev.date_at, "notification: " + ev.notification ].join( ", " ) ); console.log("loop list_of_events - ev: " + [ev.uid, ev.title, ev.date_at, "notification:" + ev.notification].join( ", " ) );
console.log("loop list_of_events - ev 'title': " + ev.get_title() ); const body = [
const notificationOptions = { `Title: ${ev.title}`,
ntfy: null, `Location: ${ev.location}`,
discord: { `Type: ${ TEventType[ ev.event_type ] }`,
avatar_url: ( process.env.dc_avatar_url as string), `Date: ${ev.date_at}`,
botname: ( process.env.dc_botname as string) `Time: ${ev.time_start}`,
`By: ${ev.posted_by}`,
`Link: ${ev.link}`,
].join("\n");
console.log("loop list_of_events - ev 'body': " + body );
const notification_prefix = ( (event: Event) => {
switch( event.notification) {
case "new":
return "New";
case "changed":
return "Changed";
case "deleted":
return "Deleted";
default:
return null;
} }
}; } ) ( ev );
await sendNotification( ev.get_title(), ev.get_body(), notificationOptions );
if ( ev.notification == "removed" ) { const today_prefix = ( (ev: Event) => {
ev.set_deleted( db ); const now = getTsNow();
} const [year, month, day] = ev.date_at.split("-")
if (
year == String(now.year) &&
month == pad_l2( String(now.month) ) &&
day == pad_l2( String( now.day ) )
) {
return true;
}
return false;
})( ev );
const title = `${today_prefix ? "TODAY " : ""}${notification_prefix ? notification_prefix + ": " : ""} ${ev.title} (${ TEventType[ ev.event_type ] })`;
<<<<<<< HEAD
console.log("loop list_of_events - ev 'title': " + title );
await sendNotification( title, body, ev.link ? ev.link : null);
=======
await fetch("http://apprise:8000/notify", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
urls: [
`ntfys://${process.env.ntfy_username}:${process.env.ntfy_password}@${process.env.ntfy_host}/${process.env.ntfy_topic}${ ev.link ? `?click=${ev.link}`: "?click=https://77th-jsoc.com/#/events" }`,
`discord://${process.env.dc_webhook}?avatar_url=${process.env.dc_avatar_url}&botname=${process.env.dc_botname}`
].join(","),
title: title,
body: body,
format: "text"
})
});
// await sendNotification(
// title,
// body
// // `${ev.link || "https://77th-jsoc.com/#/events"}`
// );
>>>>>>> 999bdf7222a68995dd40ec7da967b360f67d354a
ev.set_notification("done", db); ev.set_notification("done", db);
} }
}
async function main ( ) {
console.log("Excecuting main()");
await events_update_db();
await events_check_for_notification();
}; };
main(); main();

View File

@@ -15,6 +15,5 @@ export type TEvent = {
location: string, location: string,
event_type: keyof typeof TEventType, event_type: keyof typeof TEventType,
timezone: string, timezone: string,
link: string, link: string
deleteDate?: number | null
}; };

View File

@@ -1,6 +1,6 @@
import { Database } from "bun:sqlite"; import { Database } from "bun:sqlite";
import { TEventType, type TEvent } from "./event.types"; import type { TEvent } from "./event.types";
import { getTsNow, pad_l2, transformArray, formatTimeDiff, isEuropeanDST, subtractHours } from "../../util"; import { transformArray } from "../../util";
const BASE_URL = "https://77th-jsoc.com/service.php?action=get_events"; const BASE_URL = "https://77th-jsoc.com/service.php?action=get_events";
@@ -10,64 +10,38 @@ export type TGetEventsOptions = {
year: number, year: number,
month: number, month: number,
day: number day: number
}, }
month?: {
year: number,
month: number,
},
deleted?: boolean
} }
export type TEventEntity = TEvent & { export type TEventEntity = TEvent & {
event_uid: number event_uid: number
notification: "new" | "changed" | "removed" | "done" notification: "new" | "changed" | "deleted" | "done"
} }
export type TEventEntityNew = Omit<TEventEntity, "event_uid"> export type TEventEntityNew = Omit<TEventEntity, "event_uid">
export class Event implements TEventEntity { export class Event implements TEventEntity {
static table_name: "events" static table_name: "events"
event_uid: number;
uid: string;
title: string;
description: string;
date_at: string;
time_start: string;
time_end: string;
posted_by: string;
location: string;
event_type: TEventEntity["event_type"];
timezone: string;
link: string;
notification: TEventEntity["notification"];
deleteDate: TEventEntity["deleteDate"];
static createTable (db: Database): void { static createTable (db: Database): void {
const query = db.query(`CREATE TABLE IF NOT EXISTS "events" ( const query = db.query(`CREATE TABLE IF NOT EXISTS events (
"event_uid" INTEGER PRIMARY KEY, event_uid INTEGER PRIMARY KEY,
"uid" TEXT NOT NULL UNIQUE, uid TEXT NOT NULL UNIQUE,
"title" TEXT NOT NULL, title TEXT NOT NULL,
"date_at" DATETIME NOT NULL, date_at DATETIME NOT NULL,
"time_start" TEXT NOT NULL, time_start TEXT NOT NULL,
"time_end" TEXT NOT NULL, time_end TEXT NOT NULL,
"posted_by" TEXT NOT NULL, posted_by TEXT NOT NULL,
"location" TEXT NOT NULL, location TEXT NOT NULL,
"event_type" TEXT NOT NULL, event_type TEXT NOT NULL,
"link" TEXT NOT NULL, link TEXT NOT NULL,
"description" TEXT NOT NULL, description TEXT NOT NULL,
"timezone" TEXT NOT NULL, timezone TEXT NOT NULL,
"notification" TEXT NOT NULL, notification TEXT NOT NULL DEFAULT "new"
"deleteDate" INTEGER NULL
);`); );`);
query.run(); query.run();
} }
static insert ( events: TEventEntityNew[], db: Database ) { static insert ( events: TEventEntityNew[], db: Database ) {
const insert = db.prepare( [ const insert = db.prepare("INSERT OR REPLACE INTO events (uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification) VALUES ($uid, $title, $date_at, $time_start, $time_end, $posted_by, $location, $event_type, $link, $description, $timezone, $notification)");
"INSERT OR REPLACE INTO events",
"(uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification)",
"VALUES",
"($uid, $title, $date_at, $time_start, $time_end, $posted_by, $location, $event_type, $link, $description, $timezone, $notification)"
].join(" "));
const insertEvents = db.transaction(events => { const insertEvents = db.transaction(events => {
for (const event of events) insert.run(event); for (const event of events) insert.run(event);
return events.length; return events.length;
@@ -97,27 +71,32 @@ export class Event implements TEventEntity {
if (options.date) { if (options.date) {
whereConditions.push(`date_at = "${options.date.year}-${options.date.month}-${options.date.day}"`); whereConditions.push(`date_at = "${options.date.year}-${options.date.month}-${options.date.day}"`);
} }
if ( options.month ) {
whereConditions.push( `strftime('%Y-%m', date_at) = '${options.month.year}-${options.month.month}'`)
}
const where = ( () => { const where = ( () => {
let str = "WHERE "; let str = "WHERE ";
if ( options.deleted === true ) {
str += "deleteDate IS NOT NULL AND ";
} else if ( options.deleted === false ) {
str += "deleteDate IS NULL AND ";
}
if ( whereConditions.length >= 1 ) { if ( whereConditions.length >= 1 ) {
return str += `( ${ whereConditions.join(" OR ") } )`; str += whereConditions.join(" OR ");
} }
return null; return str;
})() })()
const query = db.query(`SELECT * FROM events${ where ? ( " " + where ) : ""};`).as(Event); const query = db.query(`SELECT * FROM events${ where ? ( " " + where ) : ""};`).as(Event);
return query.all(); return query.all();
} }
constructor(event_uid: number, uid: string, title: string, description: string, date_at: string, time_start: string, time_end: string, posted_by: string, location: string, event_type: TEventEntity["event_type"], timezone: string, link: string, notification: TEventEntity["notification"], deleteDate: TEventEntity["deleteDate"]) { event_uid: number;
uid: string;
title: string;
description: string;
date_at: string;
time_start: string;
time_end: string;
posted_by: string;
location: string;
event_type: TEventEntity["event_type"];
timezone: string;
link: string;
notification: TEventEntity["notification"]
constructor(event_uid: number, uid: string, title: string, description: string, date_at: string, time_start: string, time_end: string, posted_by: string, location: string, event_type: TEventEntity["event_type"], timezone: string, link: string, notification: TEventEntity["notification"]) {
this.event_uid = event_uid; this.event_uid = event_uid;
this.uid = uid; this.uid = uid;
this.title = title; this.title = title;
@@ -131,29 +110,9 @@ export class Event implements TEventEntity {
this.timezone = timezone; this.timezone = timezone;
this.link = link; this.link = link;
this.notification = notification; this.notification = notification;
this.deleteDate = deleteDate;
} }
toString() {
return {
event_uid: this.event_uid,
uid: this.uid,
title: this.title,
description: this.description,
date_at: this.date_at,
time_start: this.time_start,
time_end: this.time_end,
posted_by: this.posted_by,
location: this.location,
event_type: this.event_type,
timezone: this.timezone,
link: this.link,
notification: this.notification,
deleteDate: this.deleteDate
}
}
syncWithDb ( db: Database ) { syncWithDb ( db: Database ) {
const query = db.prepare( `SELECT * FROM events WHERE event_uid = $event_uid;`).as(Event); const query = db.prepare( `SELECT * FROM ${Event.table_name} WHERE event_uid = $event_uid;`).as(Event);
const entity = query.get({$event_uid: this.event_uid }); const entity = query.get({$event_uid: this.event_uid });
if ( ! entity ) { throw new Error(`Could not find Event with event_uid ${this.event_uid} in DB!`); } if ( ! entity ) { throw new Error(`Could not find Event with event_uid ${this.event_uid} in DB!`); }
this.uid = entity.uid; this.uid = entity.uid;
@@ -168,7 +127,6 @@ export class Event implements TEventEntity {
this.timezone = entity.timezone; this.timezone = entity.timezone;
this.link = entity.link; this.link = entity.link;
this.notification = entity.notification; this.notification = entity.notification;
this.deleteDate = entity.deleteDate;
return this; return this;
} }
@@ -179,75 +137,5 @@ export class Event implements TEventEntity {
WHERE event_uid = $event_uid;` WHERE event_uid = $event_uid;`
); );
query.get({$notification: newValue, $event_uid: this.event_uid }); query.get({$notification: newValue, $event_uid: this.event_uid });
return this.syncWithDb( db );
}
set_deleted ( db: Database ) {
const query = db.prepare(
`UPDATE events
SET deleteDate = $deleteDate
WHERE event_uid = $event_uid;`
);
query.get({
$deleteDate: Math.floor((new Date()).getTime() / 1000),
$event_uid: this.event_uid
});
return this.syncWithDb( db );
}
get_title() {
const type_of_notification = ( (event: Event) => {
switch ( event.notification ) {
case "new":
return "New";
case "changed":
return "Changed";
case "removed":
return "Removed";
default:
return null;
}
} ) ( this );
const title_prefix_arr = [];
if ( type_of_notification ) title_prefix_arr.push( "<" + type_of_notification + ">" );
if ( this.isEventToday() ) title_prefix_arr.push( "<TODAY>" )
return `${title_prefix_arr.length >= 1 ? ( title_prefix_arr.join(" " ) + " - ") : "" }${this.title} (${ TEventType[ this.event_type ] })`;
}
get_body() {
const BaseTime = new Date(`${this.date_at} 21:00`);
const RelativeEventTime = new Date(`${this.date_at} ${this.get_time_start()}`);
const TimeDiff = formatTimeDiff( BaseTime, RelativeEventTime);
const body = [
`Title: ${this.title}`,
`Date: ${this.date_at}`,
`Time: ${this.get_time_start()} (OP Time${ TimeDiff != "00:00" ? ` ${TimeDiff}` : "" })`,
`Type: ${ TEventType[ this.event_type ] }`,
`Location: ${this.location}`,
`By: ${this.posted_by}`,
`Link: ${this.link}`,
].join("\n");
return body;
}
isEventToday ( ) {
const now = getTsNow();
const [year, month, day] = this.date_at.split("-")
if (
year == String(now.year) &&
month == pad_l2( String(now.month) ) &&
day == pad_l2( String( now.day ) )
) {
return true;
}
return false;
}
get_time_start () {
const date = new Date( `${this.date_at} ${this.time_start}` );
if ( ! isEuropeanDST( date ) ) {
const newDate = subtractHours( date, 1);
const hours = newDate.getHours();
const minutes = newDate.getMinutes();
return `${pad_l2(hours)}:${pad_l2(minutes)}`;
}
return this.time_start;
} }
} }

View File

@@ -1,16 +0,0 @@
export const config = {
apprise: {
services: {
ntfy: {
url: `ntfys://${process.env.ntfy_username}:${process.env.ntfy_password}@${process.env.ntfy_host}/${process.env.ntfy_topic}`,
defaults: {
}
}
},
urls: [
`ntfys://${process.env.ntfy_username}:${process.env.ntfy_password}@${process.env.ntfy_host}/${process.env.ntfy_topic}`,
`discord://${process.env.dc_webhook}?avatar_url=${process.env.dc_avatar_url}&botname=${process.env.dc_botname}`
]
}
} as const

View File

@@ -1,49 +1,45 @@
import { createQS } from "./util"; <<<<<<< HEAD
export async function sendNotification(title: string, body: string, link?: string | null) {
type TSendNotificationOptions = {
ntfy: {
link?: string;
} | null,
discord: {
href?: string
avatar_url: string,
botname: string
}
}
export async function sendNotification( title: string, body: string, options: TSendNotificationOptions ) {
console.dir({ console.dir({
sendNotification: { sendNotification: {
title, title,
body body,
link
} }
}); });
const QS = { const response = await fetch("http://apprise:8000/notify", {
ntfy: options.ntfy ? createQS(options.ntfy) : null, method: "POST",
discord: createQS(options.discord) headers: {
} "Content-Type": "application/json"
if ( ! ( process.env.notification_mock == "true" ) ) { },
const response = await fetch(`${ process.env.apprise_https == "true" ? "https" : "http"}://${process.env.apprise_host ? process.env.apprise_host : "apprise"}:${process.env.apprise_port ? String(process.env.apprise_port) : "80" }/notify`, { body: JSON.stringify({
method: "POST", urls: [
headers: { `ntfys://${process.env.ntfy_username}:${process.env.ntfy_password}@${process.env.ntfy_host}/${process.env.ntfy_topic}${ link ? `?click=${link}`: "?click=https://77th-jsoc.com/#/events" }`,
"Content-Type": "application/json" `discord://${process.env.dc_webhook}?avatar_url=${process.env.dc_avatar_url}&botname=${process.env.dc_botname}`
}, ].join(","),
body: JSON.stringify({ title: title,
urls: [ body: body,
`ntfys://${process.env.ntfy_username}:${process.env.ntfy_password}@${process.env.ntfy_host}/${process.env.ntfy_topic}${ QS.ntfy ? "?" + QS.ntfy : ""}`, format: "text"
`discord://${process.env.dc_webhook}?${QS.discord}`
].join(","),
title: title,
body: body,
format: "markdown"
})
});
const responseBody = await response.json();
return responseBody;
} else {
console.dir({
sendNotification: "mocking"
}) })
} });
const responseBody = await response.json();
return responseBody;
} }
=======
import * as Bun from "bun";
export async function sendNotification(title: string, body: string, click?: string | null) {
const command = [
"python3",
"./src/notification.py",
`--title=${title}`,
`--body=${body}`,
];
if ( click ) {
command.push(`--click=${click}`);
}
const proc = Bun.spawn(command);
const text = await proc.stdout.text();
console.log("sendNotification: " + text);
}
>>>>>>> 999bdf7222a68995dd40ec7da967b360f67d354a

View File

@@ -8,8 +8,6 @@ console.log(db_filepath);
export const db = new Database(db_filepath); export const db = new Database(db_filepath);
export default db;
export function init () { export function init () {
Event.createTable(db); Event.createTable(db);
} }

View File

@@ -39,60 +39,3 @@ export function getTsNow() {
} }
return rtn; return rtn;
} }
export function unixToDate( unix_timestamp: number ) { return new Date(unix_timestamp * 1000) }
export function dateToUnix( date: Date ) { return Math.round( date.getTime()/1000 ) }
export function formatTimeDiff(dateA: Date, dateB: Date) {
// Difference in milliseconds
const diffMs = dateB.getTime() - dateA.getTime();
// Get sign (+ or -)
const sign = diffMs < 0 ? "-" : "";
// Convert to absolute minutes
const diffMinutes = Math.floor(Math.abs(diffMs) / 60000);
// Split into hours and minutes
const hours = Math.floor(diffMinutes / 60);
const minutes = diffMinutes % 60;
// Return formatted string
return `${sign}${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
}
export function subtractHours(date: Date, hours: number) {
// Create a new Date so we don't mutate the original
return new Date(date.getTime() - hours * 60 * 60 * 1000);
}
// Helper: get last Sunday of a given month
function lastSundayOfMonth(year: number, month: number ) {
const lastDay = new Date(Date.UTC(year, month + 1, 0)); // last day of month
const day = lastDay.getUTCDay(); // 0 = Sunday
const diff = day === 0 ? 0 : day; // how far back to go to reach Sunday
lastDay.setUTCDate(lastDay.getUTCDate() - diff);
return lastDay;
}
export function isEuropeanDST( date: Date ) {
const year = date.getFullYear();
// DST starts: last Sunday in March, 01:00 UTC
const start = lastSundayOfMonth(year, 2); // March (month = 2)
start.setUTCHours(1, 0, 0, 0);
// DST ends: last Sunday in October, 01:00 UTC
const end = lastSundayOfMonth(year, 9); // October (month = 9)
end.setUTCHours(1, 0, 0, 0);
// Return true if within DST period
return date >= start && date < end;
}
export function createQS (params: Record<string, string | number | boolean>): string {
const queryString = Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join("&");
return queryString;
}

View File

@@ -1,10 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
// Environment setup & latest features // Environment setup & latest features
"lib": [ "lib": ["ESNext"],
"ESNext",
"es2015.promise"
],
"target": "ESNext", "target": "ESNext",
"module": "Preserve", "module": "Preserve",
"moduleDetection": "force", "moduleDetection": "force",