This commit is contained in:
cupcakearmy 2021-12-20 15:38:11 +01:00
commit afd830c3bc
No known key found for this signature in database
GPG Key ID: 3235314B4D31232F
23 changed files with 1257 additions and 0 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
*
!src
!tsconfig.json
!package.json
!pnpm-lock.yaml

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
data
dist
.env

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM node:16-alpine
WORKDIR /app
RUN npm -g i pnpm
COPY package.json pnpm-lock.yaml ./
RUN pnpm i --frozen-lockfile
COPY src tsconfig.json ./
RUN pnpm run build
CMD [ "node", "." ]

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# Hagen Control Center
## Docs
[Telegram Bot API](https://core.telegram.org/bots/api)
[Telegraf](https://telegraf.js.org/)
## Deployment

6
docker-compose.yaml Normal file
View File

@ -0,0 +1,6 @@
version: '3.8'
services:
app:
build: .
env_file: .env

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"private": true,
"version": "1.0.0-rc.0",
"type": "module",
"main": "./dist/index.js",
"engines": {
"node": "16.x",
"npm": "please-use-pnpm",
"yarn": "please-use-pnpm",
"pnpm": "6.x"
},
"scripts": {
"start": "node .",
"build": "tsc"
},
"dependencies": {
"dayjs": "^1.10.7",
"dotenv": "^10.0.0",
"lowdb": "^3.0.0",
"ms": "^2.1.3",
"node-cron": "^3.0.0",
"pino": "^7.6.0",
"pino-pretty": "^7.3.0",
"telegraf": "^4.5.2"
},
"devDependencies": {
"@types/ms": "^0.7.31",
"@types/node": "16",
"@types/node-cron": "^3.0.0",
"ts-node-dev": "^1.1.8",
"typescript": "^4.5.4"
}
}

772
pnpm-lock.yaml generated Normal file
View File

@ -0,0 +1,772 @@
lockfileVersion: 5.3
specifiers:
'@types/ms': ^0.7.31
'@types/node': '16'
'@types/node-cron': ^3.0.0
dayjs: ^1.10.7
dotenv: ^10.0.0
lowdb: ^3.0.0
ms: ^2.1.3
node-cron: ^3.0.0
pino: ^7.6.0
pino-pretty: ^7.3.0
telegraf: ^4.5.2
ts-node-dev: ^1.1.8
typescript: ^4.5.4
dependencies:
dayjs: 1.10.7
dotenv: 10.0.0
lowdb: 3.0.0
ms: 2.1.3
node-cron: 3.0.0
pino: 7.6.0
pino-pretty: 7.3.0
telegraf: 4.5.2
devDependencies:
'@types/ms': 0.7.31
'@types/node': 16.11.14
'@types/node-cron': 3.0.0
ts-node-dev: 1.1.8_typescript@4.5.4
typescript: 4.5.4
packages:
/@types/ms/0.7.31:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: true
/@types/node-cron/3.0.0:
resolution: {integrity: sha512-RNBIyVwa/1v2r8/SqK8tadH2sJlFRAo5Ghac/cOcCv4Kp94m0I03UmAh9WVhCqS9ZdB84dF3x47p9aTw8E4c4A==}
dev: true
/@types/node/16.11.14:
resolution: {integrity: sha512-mK6BKLpL0bG6v2CxHbm0ed6RcZrAtTHBTd/ZpnlVPVa3HkumsqLE4BC4u6TQ8D7pnrRbOU0am6epuALs+Ncnzw==}
dev: true
/@types/strip-bom/3.0.0:
resolution: {integrity: sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=}
dev: true
/@types/strip-json-comments/0.0.30:
resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==}
dev: true
/abort-controller/3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
dependencies:
event-target-shim: 5.0.1
dev: false
/ansi-styles/3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
dependencies:
color-convert: 1.9.3
dev: false
/anymatch/3.1.2:
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.0
dev: true
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/args/5.0.1:
resolution: {integrity: sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==}
engines: {node: '>= 6.0.0'}
dependencies:
camelcase: 5.0.0
chalk: 2.4.2
leven: 2.1.0
mri: 1.1.4
dev: false
/atomic-sleep/1.0.0:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
dev: false
/balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/braces/3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/buffer-alloc-unsafe/1.1.0:
resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==}
dev: false
/buffer-alloc/1.2.0:
resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==}
dependencies:
buffer-alloc-unsafe: 1.1.0
buffer-fill: 1.0.0
dev: false
/buffer-fill/1.0.0:
resolution: {integrity: sha1-+PeLdniYiO858gXNY39o5wISKyw=}
dev: false
/buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true
/camelcase/5.0.0:
resolution: {integrity: sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==}
engines: {node: '>=6'}
dev: false
/chalk/2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
dependencies:
ansi-styles: 3.2.1
escape-string-regexp: 1.0.5
supports-color: 5.5.0
dev: false
/chokidar/3.5.2:
resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.2
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
color-name: 1.1.3
dev: false
/color-name/1.1.3:
resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=}
dev: false
/colorette/2.0.16:
resolution: {integrity: sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==}
dev: false
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
dev: true
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/dateformat/4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
dev: false
/dayjs/1.10.7:
resolution: {integrity: sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==}
dev: false
/debug/4.3.3:
resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: false
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/dotenv/10.0.0:
resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==}
engines: {node: '>=10'}
dev: false
/duplexify/4.1.2:
resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==}
dependencies:
end-of-stream: 1.4.4
inherits: 2.0.4
readable-stream: 3.6.0
stream-shift: 1.0.1
dev: false
/dynamic-dedupe/0.3.0:
resolution: {integrity: sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=}
dependencies:
xtend: 4.0.2
dev: true
/end-of-stream/1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
dependencies:
once: 1.4.0
dev: false
/escape-string-regexp/1.0.5:
resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}
engines: {node: '>=0.8.0'}
dev: false
/event-target-shim/5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
dev: false
/fast-redact/3.0.2:
resolution: {integrity: sha512-YN+CYfCVRVMUZOUPeinHNKgytM1wPI/C/UCLEi56EsY2dwwvI00kIJHJoI7pMVqGoMew8SMZ2SSfHKHULHXDsg==}
engines: {node: '>=6'}
dev: false
/fast-safe-stringify/2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
dev: false
/fastify-warning/0.2.0:
resolution: {integrity: sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==}
dev: false
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
dev: true
/glob/7.2.0:
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/has-flag/3.0.0:
resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=}
engines: {node: '>=4'}
dev: false
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
dev: true
/inflight/1.0.6:
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: true
/is-core-module/2.8.0:
resolution: {integrity: sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==}
dependencies:
has: 1.0.3
dev: true
/is-extglob/2.1.1:
resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}
engines: {node: '>=0.10.0'}
dev: true
/is-glob/4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-number/7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/joycon/3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
dev: false
/leven/2.1.0:
resolution: {integrity: sha1-wuep93IJTe6dNCAq6KzORoeHVYA=}
engines: {node: '>=0.10.0'}
dev: false
/lowdb/3.0.0:
resolution: {integrity: sha512-9KZRulmIcU8fZuWiaM0d5e2/nPnrFyXkeXVpqT+MJS+vgbgOf1EbtvgQmba8HwUFgDl1oeZR6XqEJnkJmQdKmg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
steno: 2.1.0
dev: false
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/minimatch/3.0.4:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minimist/1.2.5:
resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==}
/mkdirp/1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
dev: true
/module-alias/2.2.2:
resolution: {integrity: sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==}
dev: false
/moment-timezone/0.5.34:
resolution: {integrity: sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==}
dependencies:
moment: 2.29.1
dev: false
/moment/2.29.1:
resolution: {integrity: sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==}
dev: false
/mri/1.1.4:
resolution: {integrity: sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==}
engines: {node: '>=4'}
dev: false
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: false
/ms/2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: false
/node-cron/3.0.0:
resolution: {integrity: sha512-DDwIvvuCwrNiaU7HEivFDULcaQualDv7KoNlB/UU1wPW0n1tDEmBJKhEIE6DlF2FuoOHcNbLJ8ITL2Iv/3AWmA==}
engines: {node: '>=6.0.0'}
dependencies:
moment-timezone: 0.5.34
dev: false
/node-fetch/2.6.6:
resolution: {integrity: sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==}
engines: {node: 4.x || >=6.0.0}
dependencies:
whatwg-url: 5.0.0
dev: false
/normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/on-exit-leak-free/0.2.0:
resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==}
dev: false
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
/p-timeout/4.1.0:
resolution: {integrity: sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==}
engines: {node: '>=10'}
dev: false
/path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
engines: {node: '>=0.10.0'}
dev: true
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/picomatch/2.3.0:
resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}
engines: {node: '>=8.6'}
dev: true
/pino-abstract-transport/0.5.0:
resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==}
dependencies:
duplexify: 4.1.2
split2: 4.1.0
dev: false
/pino-pretty/7.3.0:
resolution: {integrity: sha512-HAhShJ2z2QzxXhYAn6XfwYpF13o1PQbjzSNA9q+30FAvhjOmeACit9lprhV/mCOw/8YFWSyyNk0YCq2EDYGYpw==}
hasBin: true
dependencies:
args: 5.0.1
colorette: 2.0.16
dateformat: 4.6.3
fast-safe-stringify: 2.1.1
joycon: 3.1.1
pino-abstract-transport: 0.5.0
pump: 3.0.0
readable-stream: 3.6.0
rfdc: 1.3.0
secure-json-parse: 2.4.0
sonic-boom: 2.4.1
strip-json-comments: 3.1.1
dev: false
/pino-std-serializers/4.0.0:
resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==}
dev: false
/pino/7.6.0:
resolution: {integrity: sha512-CCCdryvM/chT0CDt9jQ1//z62RpSXPrzUFUpY4b8eKCVq3T2T3UF6DomoczkPze9d6VFiTyVF6Y8A6F9iAyAxg==}
hasBin: true
dependencies:
fast-redact: 3.0.2
fastify-warning: 0.2.0
on-exit-leak-free: 0.2.0
pino-abstract-transport: 0.5.0
pino-std-serializers: 4.0.0
quick-format-unescaped: 4.0.4
real-require: 0.1.0
safe-stable-stringify: 2.3.1
sonic-boom: 2.4.1
thread-stream: 0.13.0
dev: false
/pump/3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
dev: false
/quick-format-unescaped/4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
dev: false
/readable-stream/3.6.0:
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.0
dev: true
/real-require/0.1.0:
resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==}
engines: {node: '>= 12.13.0'}
dev: false
/resolve/1.20.0:
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
dependencies:
is-core-module: 2.8.0
path-parse: 1.0.7
dev: true
/rfdc/1.3.0:
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
dev: false
/rimraf/2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
hasBin: true
dependencies:
glob: 7.2.0
dev: true
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/safe-compare/1.1.4:
resolution: {integrity: sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==}
dependencies:
buffer-alloc: 1.2.0
dev: false
/safe-stable-stringify/2.3.1:
resolution: {integrity: sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==}
engines: {node: '>=10'}
dev: false
/sandwich-stream/2.0.2:
resolution: {integrity: sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==}
engines: {node: '>= 0.10'}
dev: false
/secure-json-parse/2.4.0:
resolution: {integrity: sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg==}
dev: false
/sonic-boom/2.4.1:
resolution: {integrity: sha512-WgtVLfGl347/zS1oTuLaOAvVD5zijgjphAJHgbbnBJGgexnr+C1ULSj0j7ftoGxpuxR4PaV635NkwFemG8m/5w==}
dependencies:
atomic-sleep: 1.0.0
dev: false
/source-map-support/0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
dependencies:
buffer-from: 1.1.2
source-map: 0.6.1
dev: true
/source-map/0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
dev: true
/split2/4.1.0:
resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==}
engines: {node: '>= 10.x'}
dev: false
/steno/2.1.0:
resolution: {integrity: sha512-mauOsiaqTNGFkWqIfwcm3y/fq+qKKaIWf1vf3ocOuTdco9XoHCO2AGF1gFYXuZFSWuP38Q8LBHBGJv2KnJSXyA==}
engines: {node: ^14.13.1 || >=16.0.0}
dev: false
/stream-shift/1.0.1:
resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==}
dev: false
/string_decoder/1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: false
/strip-bom/3.0.0:
resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=}
engines: {node: '>=4'}
dev: true
/strip-json-comments/2.0.1:
resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=}
engines: {node: '>=0.10.0'}
dev: true
/strip-json-comments/3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
dev: false
/supports-color/5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
dependencies:
has-flag: 3.0.0
dev: false
/telegraf/4.5.2:
resolution: {integrity: sha512-OG68wQqYQQq2ldzAMv6JJUkh9XU+4mWRgHinMeJ8FoRA5ZZuA4WauqRFi8aY/OQiwJM2gTT2XWCfopN2dZWDNw==}
engines: {node: ^12.20.0 || >=14.13.1}
hasBin: true
dependencies:
abort-controller: 3.0.0
debug: 4.3.3
minimist: 1.2.5
module-alias: 2.2.2
node-fetch: 2.6.6
p-timeout: 4.1.0
safe-compare: 1.1.4
sandwich-stream: 2.0.2
typegram: 3.6.2
transitivePeerDependencies:
- supports-color
dev: false
/thread-stream/0.13.0:
resolution: {integrity: sha512-kTMZeX4Dzlb1zZ00/01aerGaTw2i8NE4sWF0TvF1uXewRhCiUjCvatQkvxIvFqauWG2ADFS2Wpd3qBeYL9i3dg==}
dependencies:
real-require: 0.1.0
dev: false
/to-regex-range/5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/tr46/0.0.3:
resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=}
dev: false
/tree-kill/1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
dev: true
/ts-node-dev/1.1.8_typescript@4.5.4:
resolution: {integrity: sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==}
engines: {node: '>=0.8.0'}
hasBin: true
peerDependencies:
node-notifier: '*'
typescript: '*'
peerDependenciesMeta:
node-notifier:
optional: true
dependencies:
chokidar: 3.5.2
dynamic-dedupe: 0.3.0
minimist: 1.2.5
mkdirp: 1.0.4
resolve: 1.20.0
rimraf: 2.7.1
source-map-support: 0.5.21
tree-kill: 1.2.2
ts-node: 9.1.1_typescript@4.5.4
tsconfig: 7.0.0
typescript: 4.5.4
dev: true
/ts-node/9.1.1_typescript@4.5.4:
resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==}
engines: {node: '>=10.0.0'}
hasBin: true
peerDependencies:
typescript: '>=2.7'
dependencies:
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
source-map-support: 0.5.21
typescript: 4.5.4
yn: 3.1.1
dev: true
/tsconfig/7.0.0:
resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
dependencies:
'@types/strip-bom': 3.0.0
'@types/strip-json-comments': 0.0.30
strip-bom: 3.0.0
strip-json-comments: 2.0.1
dev: true
/typegram/3.6.2:
resolution: {integrity: sha512-q222XE5vFtzhY8q+x+yl4oNhVqjDSAixwvnpmzze61i+u/eaKIrimT3xz/oJrCUmSvaROH1GNZS3jqDDDNp94A==}
dev: false
/typescript/4.5.4:
resolution: {integrity: sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/util-deprecate/1.0.2:
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
dev: false
/webidl-conversions/3.0.1:
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
dev: false
/whatwg-url/5.0.0:
resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
/xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
dev: true
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true

21
src/config.ts Normal file
View File

@ -0,0 +1,21 @@
import dotenv from 'dotenv'
import { Logger } from './logger.js'
dotenv.config()
const token = process.env['BOT_TOKEN']
if (!token) {
Logger.error('No token found')
process.exit(1)
}
const password = process.env['HCS_PASSWORD']
if (!password) {
Logger.error('No password found')
process.exit(1)
}
export const Config = {
token,
password,
}

24
src/cron.ts Normal file
View File

@ -0,0 +1,24 @@
import dayjs from 'dayjs'
import cron from 'node-cron'
import { DB } from './db.js'
import { bot } from './index.js'
import { getLatests } from './routes/status.js'
async function fn() {
const now = dayjs()
for (const user of DB.data?.users ?? []) {
const { fed, clean } = getLatests()
if (!fed || dayjs(fed.timestamp).isBefore(now.subtract(12, 'hours'))) {
await bot.telegram.sendMessage(user.id, '🥜 You have not fed me in the last 12 hours!')
}
if (!clean || dayjs(clean.timestamp).isBefore(now.subtract(7, 'days'))) {
await bot.telegram.sendMessage(user.id, '🛁 You have not cleaned me in the last 7 days!')
}
}
}
export function init() {
cron.schedule('0 22 * * *', fn)
// cron.schedule('*/10 * * * * *', fn)
}

38
src/db.ts Normal file
View File

@ -0,0 +1,38 @@
import { mkdir } from 'fs/promises'
import { Low, JSONFile } from 'lowdb'
import { join, resolve } from 'path'
const dataDir = resolve('./data')
export type Feeding = {
timestamp: number
by: string
}
export type Cleaning = {
timestamp: number
by: string
}
export type User = {
username: string
id: number
}
export type Data = {
feeding: Feeding[]
cleaning: Cleaning[]
users: User[]
}
export const DB = new Low<Data>(new JSONFile(join(dataDir, 'db.json')))
export async function init() {
await mkdir(dataDir, { recursive: true })
await DB.read()
DB.data ||= {
cleaning: [],
feeding: [],
users: [],
}
}

1
src/feeding.ts Normal file
View File

@ -0,0 +1 @@
import { Telegraf, Markup } from 'telegraf'

32
src/index.ts Normal file
View File

@ -0,0 +1,32 @@
import { Context, Telegraf } from 'telegraf'
import { Config } from './config.js'
import { init as initCron } from './cron.js'
import { init as initDB, User } from './db.js'
import { Logger } from './logger.js'
import { init as initMiddleware } from './middleware.js'
import { init as initRoutes } from './routes/index.js'
import { Version } from './routes/version.js'
export interface HCSContext extends Context {
user: User
authenticated: boolean
}
export const bot = new Telegraf<HCSContext>(Config.token)
export type Bot = typeof bot
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))
async function init() {
await initDB()
initMiddleware(bot)
initRoutes(bot)
initCron()
await bot.launch()
Logger.info('Bot started')
Logger.info(`Version: ${Version}`)
}
init()

4
src/logger.ts Normal file
View File

@ -0,0 +1,4 @@
import pino from 'pino'
import pp from 'pino-pretty'
export const Logger = pino(pp({ ignore: 'pid,hostname' }))

6
src/messages.ts Normal file
View File

@ -0,0 +1,6 @@
export const messages = {
welcome: 'Welcome to the HCS\nThe Hagen Control Station!',
requestAccess: '🗝 Insert the password to access the HCS',
invalidPassword: '🤔 Invalid password',
gainedAccess: '🥳 You gained access to the HCS',
}

56
src/middleware.ts Normal file
View File

@ -0,0 +1,56 @@
import ms from 'ms'
import { Bot } from '.'
import { Config } from './config.js'
import { DB } from './db.js'
import { Logger } from './logger.js'
import { messages } from './messages.js'
export function init(bot: Bot) {
// Logger
bot.use(async (ctx, next) => {
const now = Date.now()
await next()
const elapsed = Date.now() - now
Logger.info(`Processed: ${ctx.updateType} (${ms(elapsed)})`)
})
// Auth
bot.use(async (ctx, next) => {
ctx.authenticated = false
const username = ctx.chat && ctx.chat.type === 'private' && ctx.chat.username
if (!username) {
ctx.reply('No username')
return
}
const user = DB.data?.users.find((u) => u.username === username)
if (user) {
ctx.authenticated = true
ctx.user = user
} else {
const message = (ctx.updateType === 'message' && 'text' in ctx.message! && ctx.message.text) || ''
switch (message.toLowerCase().trim()) {
case '/start':
case '/help':
break
case Config.password:
const user = {
username,
id: ctx.chat.id,
}
DB.data?.users.push(user)
DB.write()
ctx.authenticated = true
ctx.user = user
ctx.deleteMessage()
ctx.reply(messages.gainedAccess)
break
default:
if (ctx.updateType === 'message') ctx.reply(messages.invalidPassword)
return
}
}
await next()
})
}

22
src/routes/auth.ts Normal file
View File

@ -0,0 +1,22 @@
import type { Bot } from '..'
import { DB } from '../db.js'
import { Logger } from '../logger.js'
import { messages } from '../messages.js'
export function init(bot: Bot) {
bot.on('my_chat_member', (ctx) => {
if (ctx.myChatMember.new_chat_member.status === 'kicked' || ctx.myChatMember.new_chat_member.status === 'left') {
Logger.info(`User ${ctx.user.username} left chat`)
if (DB.data?.users) {
DB.data.users = DB.data.users.filter((u) => u.username !== ctx.user.username)
DB.write()
}
}
})
bot.start((ctx) => {
if (!ctx.from.username) throw new Error('No username')
ctx.reply(messages.welcome)
ctx.reply(messages.requestAccess)
})
}

12
src/routes/index.ts Normal file
View File

@ -0,0 +1,12 @@
import { Bot } from '..'
import { init as initAuth } from './auth.js'
import { init as initLog } from './log.js'
import { init as initStatus } from './status.js'
import { init as initVersion } from './version.js'
export function init(bot: Bot) {
initAuth(bot)
initLog(bot)
initStatus(bot)
initVersion(bot)
}

47
src/routes/log.ts Normal file
View File

@ -0,0 +1,47 @@
import { Markup } from 'telegraf'
import type { Bot } from '..'
import { DB } from '../db.js'
import { disappearingMessage } from '../utils.js'
enum LogCommands {
Fed = 'log:fed',
Clean = 'log:clean',
Cancel = 'log:cancel',
}
export async function init(bot: Bot) {
bot.command('log', (ctx) => {
ctx.deleteMessage()
const buttons = Markup.inlineKeyboard([
[Markup.button.callback('🥜 Fed', LogCommands.Fed)],
[Markup.button.callback('🛁 Cleaned', LogCommands.Clean)],
[Markup.button.callback('❌ Cancel', LogCommands.Cancel)],
])
ctx.replyWithMarkdownV2('What do you want to log?', { reply_markup: buttons.reply_markup })
})
bot.action(LogCommands.Clean, (ctx) => {
ctx.deleteMessage()
DB.data?.cleaning.push({
timestamp: Date.now(),
by: ctx.user.username,
})
DB.write()
disappearingMessage(ctx, 'Saved')
})
bot.action(LogCommands.Fed, (ctx) => {
ctx.deleteMessage()
DB.data?.feeding.push({
timestamp: Date.now(),
by: ctx.user.username,
})
DB.write()
disappearingMessage(ctx, 'Saved')
})
bot.action(LogCommands.Cancel, (ctx) => {
ctx.deleteMessage()
disappearingMessage(ctx, 'Cancelled')
})
}

22
src/routes/status.ts Normal file
View File

@ -0,0 +1,22 @@
import { DB } from '../db.js'
import { Bot } from '../index.js'
import { format, getLatest } from '../utils.js'
export function getLatests() {
const fed = getLatest(DB.data?.feeding || [])
const clean = getLatest(DB.data?.cleaning || [])
return { fed, clean }
}
export function init(bot: Bot) {
bot.command('status', (ctx) => {
ctx.deleteMessage()
const { fed, clean } = getLatests()
let msg = ''
msg += '**🥜 Fed**\n'
msg += fed ? `${format(fed.timestamp)} by ${fed.by}` : '🥲 Never'
msg += '\n\n**🛁 Cleaned**\n'
msg += clean ? `${format(clean.timestamp)} by ${clean.by}` : '🥲 Never'
ctx.replyWithMarkdownV2(msg)
})
}

10
src/routes/version.ts Normal file
View File

@ -0,0 +1,10 @@
import { Bot } from '..'
export const Version = process.env['npm_package_version']
export function init(bot: Bot) {
bot.command('/version', async (ctx) => {
// ctx.reply('Version: ' + version)
ctx.reply(`Version: ${Version}`)
})
}

19
src/utils.ts Normal file
View File

@ -0,0 +1,19 @@
import dayjs from 'dayjs'
import type { Cleaning, Feeding } from './db.js'
import { HCSContext } from './index.js'
export function format(timestamp: number): string {
const d = dayjs(timestamp)
return d.format('DD MMM') + ' at ' + d.format('HH:mm')
}
export function getLatest<T extends Cleaning | Feeding>(data: T[]): T | null {
return data.sort((a, b) => b.timestamp - a.timestamp)[0] || null
}
export async function disappearingMessage(ctx: HCSContext, msg: string, timeout = 2000) {
const message = await ctx.reply(msg)
setTimeout(() => {
ctx.deleteMessage(message.message_id)
}, timeout)
}

101
tsconfig.json Normal file
View File

@ -0,0 +1,101 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
"incremental": true /* Enable incremental compilation */,
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "ES2020" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
"resolveJsonModule": true /* Enable importing .json files */,
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
"declarationMap": true /* Create sourcemaps for d.ts files. */,
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
"removeComments": true /* Disable emitting comments. */,
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied `any` type.. */,
"strictNullChecks": true /* When type checking, take into account `null` and `undefined`. */,
"strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */,
"strictBindCallApply": true /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */,
"strictPropertyInitialization": true /* Check for class properties that are declared but not set in the constructor. */,
"noImplicitThis": true /* Enable error reporting when `this` is given the type `any`. */,
"useUnknownInCatchVariables": true /* Type catch clause variables as 'unknown' instead of 'any'. */,
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
"exactOptionalPropertyTypes": true /* Interpret optional property types as written, rather than adding 'undefined'. */,
"noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */,
"noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
"noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */,
"noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */,
"noPropertyAccessFromIndexSignature": true /* Enforces using indexed accessors for keys declared using an indexed type */,
"allowUnusedLabels": true /* Disable error reporting for unused labels. */,
"allowUnreachableCode": true /* Disable error reporting for unreachable code. */,
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}