commit db3c32a871a9fec2df25c5d910135981719d3df5 Author: wuxu Date: Wed Jun 24 22:24:43 2026 +0800 feat: 初始化短信网关核心 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37bf180 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +node_modules/ +logs/ +*.log +config.json +.DS_Store +.env +.vscode/ +.idea/ +*.swp +*.swo +*~ +.npm +.npm-cache/ +*.tgz +.eslintcache diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..c25f82d --- /dev/null +++ b/config.example.json @@ -0,0 +1,74 @@ +{ + "serial": { + "path": "COM3", + "baudRate": 115200, + "_comment": "Windows使用COM1/COM2/COM3等,也可以直接写数字如3; Linux使用/dev/ttyUSB0" + }, + "smtp": { + "server": "smtp.qq.com", + "port": 465, + "user": "your@qq.com", + "pass": "your_auth_code", + "sendTo": "recipient@example.com" + }, + "pushChannels": [ + { + "enabled": true, + "type": "dingtalk", + "name": "钉钉通知", + "url": "https://oapi.dingtalk.com/robot/send?access_token=xxx", + "secret": "SECxxx", + "key1": "", + "key2": "", + "customBody": "" + }, + { + "enabled": false, + "type": "feishu", + "name": "飞书通知", + "url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxx", + "secret": "", + "key1": "", + "key2": "", + "customBody": "" + }, + { + "enabled": false, + "type": "telegram", + "name": "Telegram通知", + "url": "1234567890:ABCdefGHIjklMNOpqrsTUVwxyz", + "secret": "", + "key1": "987654321", + "key2": "", + "customBody": "" + }, + { + "enabled": false, + "type": "pushplus", + "name": "PushPlus通知", + "url": "", + "secret": "", + "key1": "your_pushplus_token", + "key2": "", + "customBody": "" + }, + { + "enabled": false, + "type": "custom", + "name": "自定义推送", + "url": "https://your-webhook.com/api/notify", + "secret": "", + "key1": "", + "key2": "", + "customBody": "{\"from\":\"{sender}\",\"msg\":\"{message}\",\"ts\":\"{timestamp}\"}" + } + ], + "api": { + "port": 3000, + "webToken": "change-this-token", + "auth": { + "username": "admin", + "password": "admin123" + } + } +} diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..90306d9 --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,21 @@ +module.exports = { + apps: [{ + name: 'sms-gateway', + script: './src/index.js', + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: '200M', + env: { + NODE_ENV: 'production', + LOG_LEVEL: 'info' + }, + error_file: './logs/pm2-error.log', + out_file: './logs/pm2-out.log', + log_date_format: 'YYYY-MM-DD HH:mm:ss', + merge_logs: true, + min_uptime: '10s', + max_restarts: 10, + restart_delay: 5000 + }] +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1a64f4a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2048 @@ +{ + "name": "nodejs-sms-gateway", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nodejs-sms-gateway", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@serialport/parser-readline": "^12.0.0", + "axios": "^1.6.2", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "express-basic-auth": "^1.2.1", + "node-pdu": "^2.1.2", + "nodemailer": "^6.9.7", + "serialport": "^12.0.0", + "winston": "^3.11.0" + }, + "devDependencies": { + "nodemon": "^3.0.2" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@serialport/binding-mock": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz", + "integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==", + "license": "MIT", + "dependencies": { + "@serialport/bindings-interface": "^1.2.1", + "debug": "^4.3.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@serialport/binding-mock/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@serialport/binding-mock/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@serialport/bindings-cpp": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", + "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", + "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", + "license": "MIT", + "dependencies": { + "@serialport/parser-delimiter": "11.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz", + "integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==", + "license": "MIT", + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/@serialport/parser-byte-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "license": "MIT", + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "license": "MIT", + "dependencies": { + "@serialport/parser-delimiter": "12.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "license": "MIT", + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@serialport/stream/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-basic-auth": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz", + "integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==", + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-pdu": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-pdu/-/node-pdu-2.1.2.tgz", + "integrity": "sha512-7lba3ECMYTP1JBt7NJI0r5fV1ARNuvGWFHQgzs0MveDcmT7JefkoAh2HlQpNKWQvGSezhDNR9aM500X8aW629Q==", + "license": "MIT" + }, + "node_modules/nodemailer": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "license": "MIT", + "dependencies": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/serialport/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/serialport/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ec76498 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "nodejs-sms-gateway", + "version": "1.0.0", + "description": "4G SMS Gateway with AT command communication", + "main": "src/index.js", + "type": "module", + "scripts": { + "start": "node src/index.js", + "dev": "nodemon src/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "sms", + "4g", + "at-commands", + "gateway", + "modem" + ], + "author": "", + "license": "MIT", + "dependencies": { + "serialport": "^12.0.0", + "@serialport/parser-readline": "^12.0.0", + "node-pdu": "^2.1.2", + "express": "^4.18.2", + "express-basic-auth": "^1.2.1", + "nodemailer": "^6.9.7", + "axios": "^1.6.2", + "winston": "^3.11.0", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "nodemon": "^3.0.2" + } +} diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..70546e9 --- /dev/null +++ b/src/api.js @@ -0,0 +1,394 @@ +import express from 'express'; +import crypto from 'crypto'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import logger from './logger.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +class APIServer { + constructor(config, modem, smsProcessor) { + this.config = config; + this.modem = modem; + this.smsProcessor = smsProcessor; + this.app = express(); + this.publicDir = path.join(__dirname, '../public'); + + this.setupMiddleware(); + this.setupRoutes(); + } + + setupMiddleware() { + // JSON解析 + this.app.use(express.json()); + + // Web Token认证,API保留Basic Auth兼容 + this.app.use((req, res, next) => { + this.authenticateRequest(req, res, next); + }); + + // 请求日志 + this.app.use((req, res, next) => { + logger.info(`${req.method} ${req.path}`); + next(); + }); + + this.app.use('/assets', express.static(path.join(this.publicDir, 'assets'))); + } + + authenticateRequest(req, res, next) { + if (this.hasValidWebToken(req)) { + this.persistWebToken(req, res); + + if (this.shouldCleanTokenFromUrl(req)) { + return res.redirect(302, this.getCleanUrl(req)); + } + + return next(); + } + + if (this.isWebRoute(req) && this.getConfiguredWebToken()) { + return this.sendTokenGate(res); + } + + if (this.hasValidBasicAuth(req)) { + return next(); + } + + if (req.path.startsWith('/api/')) { + return res.status(401).json({ + success: false, + error: this.getConfiguredWebToken() ? '需要有效token或Basic Auth' : '需要Basic Auth' + }); + } + + return this.sendBasicAuthChallenge(res); + } + + getConfiguredWebToken() { + return String(this.config.api.webToken || '').trim(); + } + + hasValidWebToken(req) { + const expected = this.getConfiguredWebToken(); + if (!expected) { + return false; + } + + const token = this.getRequestToken(req); + return this.safeEqual(token, expected); + } + + getRequestToken(req) { + const bearer = req.get('authorization')?.match(/^Bearer\s+(.+)$/i)?.[1]; + return req.query.token || req.get('x-web-token') || bearer || this.getCookie(req, 'sms_gateway_token') || ''; + } + + getCookie(req, name) { + const cookieHeader = req.get('cookie') || ''; + const cookies = cookieHeader.split(';').map(item => item.trim()); + const prefix = `${name}=`; + const cookie = cookies.find(item => item.startsWith(prefix)); + return cookie ? decodeURIComponent(cookie.slice(prefix.length)) : ''; + } + + persistWebToken(req, res) { + if (!req.query.token) { + return; + } + + res.cookie('sms_gateway_token', req.query.token, { + httpOnly: true, + sameSite: 'strict', + maxAge: 7 * 24 * 60 * 60 * 1000, + path: '/' + }); + } + + shouldCleanTokenFromUrl(req) { + return this.isWebRoute(req) && Boolean(req.query.token); + } + + getCleanUrl(req) { + const url = new URL(req.originalUrl, 'http://localhost'); + url.searchParams.delete('token'); + return `${url.pathname}${url.search}`; + } + + hasValidBasicAuth(req) { + const auth = req.get('authorization') || ''; + if (!auth.startsWith('Basic ')) { + return false; + } + + const decoded = Buffer.from(auth.slice(6), 'base64').toString('utf8'); + const separatorIndex = decoded.indexOf(':'); + if (separatorIndex === -1) { + return false; + } + + const username = decoded.slice(0, separatorIndex); + const password = decoded.slice(separatorIndex + 1); + return this.safeEqual(username, this.config.api.auth.username) && + this.safeEqual(password, this.config.api.auth.password); + } + + safeEqual(actual, expected) { + const actualBuffer = Buffer.from(String(actual)); + const expectedBuffer = Buffer.from(String(expected)); + + if (actualBuffer.length !== expectedBuffer.length) { + return false; + } + + return crypto.timingSafeEqual(actualBuffer, expectedBuffer); + } + + isWebRoute(req) { + return req.path === '/' || req.path === '/admin' || req.path.startsWith('/assets/'); + } + + sendBasicAuthChallenge(res) { + res.set('WWW-Authenticate', 'Basic realm="SMS Gateway"'); + return res.status(401).send('Authentication required'); + } + + sendTokenGate(res) { + res.set('Cache-Control', 'no-store'); + return res.status(401).type('html').send(` + + + + + SMS Gateway Token + + + +
+ +

输入访问 token

+

管理台需要有效 token 才能继续访问。

+
+ + +
+
+ +`); + } + + setupRoutes() { + this.app.get('/', (req, res) => { + res.redirect('/admin'); + }); + + this.app.get('/admin', (req, res) => { + res.sendFile(path.join(this.publicDir, 'admin.html')); + }); + + // 状态查询 + this.app.get('/api/status', async (req, res) => { + try { + const signal = await this.modem.getSignalQuality(); + const operator = await this.modem.getOperator(); + + res.json({ + success: true, + data: { + ready: this.modem.ready, + model: this.modem.modelInfo, + signal, + operator, + uptime: process.uptime() + } + }); + } catch (err) { + logger.error('查询状态失败:', err); + res.status(500).json({ + success: false, + error: err.message + }); + } + }); + + // 发送短信 + this.app.post('/api/sms/send', async (req, res) => { + try { + const { phone, message } = req.body; + + if (!phone || !message) { + return res.status(400).json({ + success: false, + error: '缺少必要参数: phone, message' + }); + } + + const success = await this.modem.sendSMS(phone, message); + + res.json({ + success, + message: success ? '短信发送成功' : '短信发送失败' + }); + } catch (err) { + logger.error('发送短信失败:', err); + res.status(500).json({ + success: false, + error: err.message + }); + } + }); + + // 查询收到的短信 + this.app.get('/api/sms/received', (req, res) => { + try { + const messages = this.smsProcessor.getReceivedMessages(req.query.limit); + res.json({ + success: true, + data: messages + }); + } catch (err) { + logger.error('查询收到短信失败:', err); + res.status(500).json({ + success: false, + error: err.message + }); + } + }); + + // 查询信号强度 + this.app.get('/api/modem/signal', async (req, res) => { + try { + const signal = await this.modem.getSignalQuality(); + res.json({ + success: true, + data: signal + }); + } catch (err) { + logger.error('查询信号失败:', err); + res.status(500).json({ + success: false, + error: err.message + }); + } + }); + + // 查询模组信息 + this.app.get('/api/modem/info', async (req, res) => { + try { + const iccid = await this.modem.getICCID(); + res.json({ + success: true, + data: { + model: this.modem.modelInfo, + iccid, + ready: this.modem.ready + } + }); + } catch (err) { + logger.error('查询模组信息失败:', err); + res.status(500).json({ + success: false, + error: err.message + }); + } + }); + + // 发送AT命令 + this.app.post('/api/modem/at', async (req, res) => { + try { + const { command } = req.body; + + if (!command) { + return res.status(400).json({ + success: false, + error: '缺少必要参数: command' + }); + } + + const response = await this.modem.sendATCommand(command, 5000); + + res.json({ + success: true, + data: { + command, + response + } + }); + } catch (err) { + logger.error('发送AT命令失败:', err); + res.status(500).json({ + success: false, + error: err.message + }); + } + }); + + // 获取日志 + this.app.get('/api/logs', (req, res) => { + try { + const logs = logger.getBuffer().getAll(); + res.json({ + success: true, + data: logs + }); + } catch (err) { + logger.error('获取日志失败:', err); + res.status(500).json({ + success: false, + error: err.message + }); + } + }); + + // 健康检查 + this.app.get('/health', (req, res) => { + res.json({ + status: 'ok', + uptime: process.uptime() + }); + }); + + // 404处理 + this.app.use((req, res) => { + res.status(404).json({ + success: false, + error: 'Not Found' + }); + }); + + // 错误处理 + this.app.use((err, req, res, next) => { + logger.error('API错误:', err); + res.status(500).json({ + success: false, + error: err.message + }); + }); + } + + start() { + const port = this.config.api.port; + this.app.listen(port, () => { + logger.info(`API服务器已启动,监听端口: ${port}`); + }); + } +} + +export default APIServer; diff --git a/src/concat.js b/src/concat.js new file mode 100644 index 0000000..64e3f5d --- /dev/null +++ b/src/concat.js @@ -0,0 +1,165 @@ +import EventEmitter from 'events'; +import logger from './logger.js'; + +class ConcatManager extends EventEmitter { + constructor() { + super(); + this.buffer = []; + this.maxSlots = 5; + this.maxParts = 10; + this.timeoutMs = 30000; // 30秒超时 + } + + /** + * 查找或创建长短信槽位 + */ + findOrCreateSlot(refNumber, sender, totalParts) { + // 先查找是否已存在 + let slot = this.buffer.find(s => + s.inUse && s.refNumber === refNumber && s.sender === sender + ); + + if (slot) { + return slot; + } + + // 查找空闲槽位 + slot = this.buffer.find(s => !s.inUse); + if (slot) { + this.initSlot(slot, refNumber, sender, totalParts); + return slot; + } + + // 没有空闲槽位,查找最老的槽位覆盖 + if (this.buffer.length < this.maxSlots) { + slot = { + inUse: false, + parts: [] + }; + this.buffer.push(slot); + } else { + slot = this.buffer.reduce((oldest, current) => + current.firstPartTime < oldest.firstPartTime ? current : oldest + ); + logger.warn('长短信缓存已满,覆盖最老的槽位'); + } + + this.initSlot(slot, refNumber, sender, totalParts); + return slot; + } + + /** + * 初始化槽位 + */ + initSlot(slot, refNumber, sender, totalParts) { + slot.inUse = true; + slot.refNumber = refNumber; + slot.sender = sender; + slot.totalParts = totalParts; + slot.receivedParts = 0; + slot.firstPartTime = Date.now(); + slot.timestamp = null; + slot.parts = new Array(totalParts).fill(null); + } + + /** + * 添加短信分段 + */ + addPart(refNumber, sender, partNumber, totalParts, text, timestamp) { + logger.info(`收到长短信分段 ${partNumber}/${totalParts}, 参考号: ${refNumber}`); + + const slot = this.findOrCreateSlot(refNumber, sender, totalParts); + const partIndex = partNumber - 1; // partNumber从1开始,数组从0开始 + + if (partIndex >= 0 && partIndex < this.maxParts) { + if (!slot.parts[partIndex]) { + slot.parts[partIndex] = text; + slot.receivedParts++; + + // 保存第一个收到的分段的时间戳 + if (slot.receivedParts === 1) { + slot.timestamp = timestamp; + } + + logger.info(`已缓存分段 ${partNumber},当前已收到 ${slot.receivedParts}/${totalParts}`); + + // 检查是否已收齐 + if (slot.receivedParts >= totalParts) { + logger.info('✅ 长短信已收齐,开始合并转发'); + this.assembleAndEmit(slot); + } + } else { + logger.warn(`⚠️ 分段 ${partNumber} 已存在,跳过`); + } + } + } + + /** + * 合并并发射完整短信 + */ + assembleAndEmit(slot) { + let fullText = ''; + for (let i = 0; i < slot.totalParts; i++) { + if (slot.parts[i]) { + fullText += slot.parts[i]; + } else { + fullText += `[缺失分段${i + 1}]`; + } + } + + this.emit('complete', { + sender: slot.sender, + text: fullText, + timestamp: slot.timestamp + }); + + // 清空槽位 + this.clearSlot(slot); + } + + /** + * 清空槽位 + */ + clearSlot(slot) { + slot.inUse = false; + slot.parts = []; + slot.receivedParts = 0; + } + + /** + * 检查超时 + */ + checkTimeout() { + const now = Date.now(); + this.buffer.forEach(slot => { + if (slot.inUse && (now - slot.firstPartTime) >= this.timeoutMs) { + logger.warn(`⏰ 长短信超时,强制转发不完整消息`); + logger.warn(` 参考号: ${slot.refNumber}, 已收到: ${slot.receivedParts}/${slot.totalParts}`); + this.assembleAndEmit(slot); + } + }); + } + + /** + * 启动超时检查定时器 + */ + startTimeoutChecker() { + this.timeoutChecker = setInterval(() => { + this.checkTimeout(); + }, 5000); // 每5秒检查一次 + logger.info('长短信超时检查器已启动'); + } + + /** + * 停止超时检查定时器 + */ + stopTimeoutChecker() { + if (this.timeoutChecker) { + clearInterval(this.timeoutChecker); + this.timeoutChecker = null; + logger.info('长短信超时检查器已停止'); + } + } +} + +export default ConcatManager; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..de635ac --- /dev/null +++ b/src/index.js @@ -0,0 +1,145 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import logger from './logger.js'; +import ModemManager from './modem.js'; +import ConcatManager from './concat.js'; +import PushManager from './push.js'; +import SMSProcessor from './sms.js'; +import APIServer from './api.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// 加载配置 +function loadConfig() { + const configPath = path.join(__dirname, '../config.json'); + if (!fs.existsSync(configPath)) { + logger.error('配置文件不存在: config.json'); + logger.error('请复制 config.example.json 为 config.json 并修改配置'); + process.exit(1); + } + + const configContent = fs.readFileSync(configPath, 'utf-8'); + return JSON.parse(configContent); +} + +// 创建日志目录 +function ensureLogDirectory() { + const logDir = path.join(__dirname, '../logs'); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } +} + +// 主函数 +async function main() { + // 确保日志目录存在 + ensureLogDirectory(); + + logger.info('========================================'); + logger.info(' 4G SMS Gateway 启动中... '); + logger.info('========================================'); + + // 加载配置 + const config = loadConfig(); + logger.info('配置加载完成'); + + // 创建模组管理器 + const modem = new ModemManager(config.serial); + + // 创建长短信管理器 + const concatManager = new ConcatManager(); + + // 创建推送管理器 + const pushManager = new PushManager(config); + + // 创建短信处理器 + const smsProcessor = new SMSProcessor(config, modem, concatManager, pushManager); + + // 监听长短信合并完成事件 + concatManager.on('complete', async (sms) => { + logger.info('收到长短信合并完成事件'); + await smsProcessor.processSmsContent(sms.sender, sms.text, sms.timestamp); + }); + + // 监听模组短信事件 + modem.on('sms', async (pduHex) => { + await smsProcessor.processPDU(pduHex); + }); + + // 监听模组错误事件 + modem.on('error', (err) => { + logger.error('模组错误:', err); + }); + + // 监听模组关闭事件 + modem.on('close', () => { + logger.warn('模组连接已关闭'); + process.exit(1); // PM2会自动重启 + }); + + // 监听模组就绪事件 + modem.on('ready', async () => { + logger.info('✓ 模组已就绪'); + + // 发送启动通知邮件 + if (config.smtp && config.smtp.server) { + const subject = '短信网关已启动'; + const body = `4G SMS Gateway 已成功启动\n\n模组信息:\n- 厂商: ${modem.modelInfo.manufacturer}\n- 型号: ${modem.modelInfo.model}\n- 版本: ${modem.modelInfo.version}\n\nAPI地址: http://your-server:${config.api.port}`; + await pushManager.sendEmail(subject, body); + } + }); + + try { + // 打开串口并初始化模组 + await modem.open(); + + // 启动长短信超时检查器 + concatManager.startTimeoutChecker(); + + // 启动API服务器 + const apiServer = new APIServer(config, modem, smsProcessor); + apiServer.start(); + + logger.info('========================================'); + logger.info(' 4G SMS Gateway 运行中 '); + logger.info('========================================'); + + } catch (err) { + logger.error('启动失败:', err); + process.exit(1); + } + + // 优雅退出 + process.on('SIGINT', async () => { + logger.info('\n收到 SIGINT 信号,正在关闭...'); + concatManager.stopTimeoutChecker(); + await modem.close(); + process.exit(0); + }); + + process.on('SIGTERM', async () => { + logger.info('\n收到 SIGTERM 信号,正在关闭...'); + concatManager.stopTimeoutChecker(); + await modem.close(); + process.exit(0); + }); + + // 捕获未处理的异常 + process.on('uncaughtException', (err) => { + logger.error('未捕获的异常:', err); + process.exit(1); + }); + + process.on('unhandledRejection', (reason, promise) => { + logger.error('未处理的Promise拒绝:', reason); + process.exit(1); + }); +} + +// 启动应用 +main().catch(err => { + logger.error('应用启动失败:', err); + process.exit(1); +}); diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..b72bc3a --- /dev/null +++ b/src/logger.js @@ -0,0 +1,126 @@ +import winston from 'winston'; + +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.errors({ stack: true }), + winston.format.printf(({ level, message, timestamp, stack }) => { + if (stack) { + return `${timestamp} [${level.toUpperCase()}] ${message}\n${stack}`; + } + return `${timestamp} [${level.toUpperCase()}] ${message}`; + }) + ), + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.printf(({ level, message, timestamp }) => { + return `${timestamp} ${level}: ${message}`; + }) + ) + }), + new winston.transports.File({ + filename: 'logs/error.log', + level: 'error', + maxsize: 5242880, // 5MB + maxFiles: 5 + }), + new winston.transports.File({ + filename: 'logs/combined.log', + maxsize: 5242880, // 5MB + maxFiles: 5 + }) + ] +}); + +// 创建循环日志缓冲区(用于API查询) +class LogBuffer { + constructor(maxLines = 120) { + this.buffer = []; + this.maxLines = maxLines; + this.index = 0; + } + + add(line) { + if (this.buffer.length < this.maxLines) { + this.buffer.push(line); + } else { + this.buffer[this.index] = line; + this.index = (this.index + 1) % this.maxLines; + } + } + + getAll() { + if (this.buffer.length < this.maxLines) { + return this.buffer.slice(); + } + // 从index位置开始重新排序,保持时间顺序 + return [ + ...this.buffer.slice(this.index), + ...this.buffer.slice(0, this.index) + ]; + } + + clear() { + this.buffer = []; + this.index = 0; + } +} + +// 创建全局日志缓冲区 +const logBuffer = new LogBuffer(120); + +function formatLogPart(part) { + if (part instanceof Error) { + return part.stack || part.message; + } + if (typeof part === 'object' && part !== null) { + try { + return JSON.stringify(part); + } catch (err) { + return String(part); + } + } + return String(part); +} + +function formatLocalTimestamp(date = new Date()) { + const pad = (value) => String(value).padStart(2, '0'); + return [ + date.getFullYear(), + pad(date.getMonth() + 1), + pad(date.getDate()) + ].join('-') + ' ' + [ + pad(date.getHours()), + pad(date.getMinutes()), + pad(date.getSeconds()) + ].join(':'); +} + +function addToBuffer(level, message, args = []) { + const timestamp = formatLocalTimestamp(); + const details = [message, ...args].map(formatLogPart).join(' '); + logBuffer.add(`${timestamp} [${level.toUpperCase()}] ${details}`); +} + +// 拦截日志输出,同时写入缓冲区。winston的info/warn/error便捷方法不会走自定义log方法。 +const originalLog = logger.log.bind(logger); +logger.log = function(level, message, ...args) { + addToBuffer(level, message, args); + return originalLog(level, message, ...args); +}; + +['error', 'warn', 'info', 'debug'].forEach((level) => { + const original = logger[level].bind(logger); + logger[level] = function(message, ...args) { + addToBuffer(level, message, args); + return original(message, ...args); + }; +}); + +// 导出日志缓冲区 +logger.getBuffer = () => logBuffer; + +export default logger; diff --git a/src/modem.js b/src/modem.js new file mode 100644 index 0000000..1c0d705 --- /dev/null +++ b/src/modem.js @@ -0,0 +1,617 @@ +import { SerialPort } from 'serialport'; +import { ReadlineParser } from '@serialport/parser-readline'; +import { Submit } from 'node-pdu'; +import EventEmitter from 'events'; +import logger from './logger.js'; + +class ModemManager extends EventEmitter { + constructor(config) { + super(); + this.config = config; + this.port = null; + this.parser = null; + this.ready = false; + this.modelInfo = { + manufacturer: '未知', + model: '未知', + version: '未知' + }; + } + + /** + * 打开串口并初始化模组 + */ + async open() { + try { + // 处理串口路径:Windows支持COMx格式 + let portPath = this.config.path; + + // 如果是Windows且指定了COM端口号(数字),自动添加COM前缀 + if (process.platform === 'win32' && /^\d+$/.test(portPath)) { + portPath = `COM${portPath}`; + } + + logger.info(`准备打开串口: ${portPath} (${this.config.baudRate})`); + + // 打开串口 + this.port = new SerialPort({ + path: portPath, + baudRate: this.config.baudRate, + dataBits: 8, + stopBits: 1, + parity: 'none' + }); + + // 设置行解析器 + this.parser = this.port.pipe(new ReadlineParser({ delimiter: '\r\n' })); + + // 监听串口事件 + this.port.on('error', (err) => { + logger.error('串口错误:', err); + this.emit('error', err); + }); + + this.port.on('close', () => { + logger.warn('串口已关闭'); + this.ready = false; + this.emit('close'); + }); + + // 监听URC(主动上报)消息 + this.setupURCListener(); + + // 等待串口打开 + await new Promise((resolve) => this.port.once('open', resolve)); + logger.info(`串口已打开: ${this.config.path}`); + + // 初始化模组 + await this.init(); + + } catch (err) { + logger.error('打开串口失败:', err); + throw err; + } + } + + /** + * 设置URC监听器 + */ + setupURCListener() { + let waitingPDU = false; + + this.parser.on('data', (line) => { + line = line.trim(); + + // 调试输出 + if (line.length > 0 && !line.startsWith('AT')) { + logger.debug(`<< ${line}`); + } + + // 检测短信URC + if (line.startsWith('+CMT:')) { + logger.info('检测到短信URC,等待PDU数据...'); + waitingPDU = true; + } else if (waitingPDU && this.isHexString(line)) { + logger.info('收到PDU数据'); + waitingPDU = false; + this.emit('sms', line); // 发射短信事件 + } else if (waitingPDU && line.length === 0) { + // 跳过空行 + } else if (waitingPDU) { + // 收到非PDU数据,返回等待状态 + waitingPDU = false; + } + + // 检测网络注册状态变化 + if (line.startsWith('+CEREG:')) { + this.emit('cereg', line); + } + }); + } + + /** + * 检查是否为十六进制字符串 + */ + isHexString(str) { + return /^[0-9A-Fa-f]+$/.test(str); + } + + /** + * 发送AT命令并等待响应 + */ + async sendATCommand(cmd, timeout = 2000) { + return new Promise((resolve, reject) => { + let buffer = ''; + const timer = setTimeout(() => { + this.parser.removeListener('data', handler); + reject(new Error(`AT命令超时: ${cmd}`)); + }, timeout); + + const handler = (line) => { + buffer += line + '\n'; + if (line.includes('OK') || line.includes('ERROR')) { + clearTimeout(timer); + this.parser.removeListener('data', handler); + resolve(buffer); + } + }; + + this.parser.on('data', handler); + + logger.debug(`>> ${cmd}`); + this.port.write(cmd + '\r\n'); + }); + } + + /** + * 发送AT命令并等待OK + */ + async sendATandWaitOK(cmd, timeout = 2000) { + try { + const resp = await this.sendATCommand(cmd, timeout); + return resp.includes('OK'); + } catch (err) { + return false; + } + } + + /** + * 发送AT命令,失败时打印模组原始响应,便于排查型号差异 + */ + async sendATWithRetry(cmd, options = {}) { + const { + timeout = 2000, + retries = 3, + retryDelay = 1000, + label = cmd, + required = true + } = options; + + for (let attempt = 1; attempt <= retries; attempt++) { + try { + const resp = await this.sendATCommand(cmd, timeout); + if (resp.includes('OK')) { + return resp; + } + + logger.warn(`${label}失败(${attempt}/${retries}): ${this.formatATResponse(resp)}`); + } catch (err) { + logger.warn(`${label}失败(${attempt}/${retries}): ${err.message}`); + } + + if (attempt < retries && retryDelay > 0) { + await this.sleep(retryDelay); + } + } + + if (required) { + throw new Error(`${label}失败`); + } + + return null; + } + + /** + * 压缩AT响应,避免日志跨太多行 + */ + formatATResponse(resp) { + return resp + .split('\n') + .map(line => line.trim()) + .filter(Boolean) + .join(' | '); + } + + /** + * 初始化模组 + */ + async init() { + logger.info('开始初始化4G模组...'); + + // 1. AT握手 + let retries = 0; + while (!(await this.sendATandWaitOK('AT', 1000)) && retries < 10) { + logger.warn('AT未响应,重试...'); + retries++; + await this.sleep(1000); + } + if (retries >= 10) { + throw new Error('模组AT握手失败'); + } + logger.info('✓ 模组AT响应正常'); + + // 2. 查询模组信息 + try { + const resp = await this.sendATCommand('ATI', 2000); + const lines = resp.split('\n').map(l => l.trim()).filter(l => l && l !== 'ATI' && l !== 'OK'); + if (lines.length >= 3) { + this.modelInfo.manufacturer = lines[0]; + this.modelInfo.model = lines[1]; + this.modelInfo.version = lines[2]; + logger.info(`模组信息: ${this.modelInfo.manufacturer} ${this.modelInfo.model} ${this.modelInfo.version}`); + } + } catch (err) { + logger.warn('查询模组信息失败'); + } + + // 3. 按ML307A文档先确认SIM卡和协议栈状态 + await this.waitSIMReady(); + await this.ensureFullFunctionality(); + + // 4. 等待网络注册 + retries = 0; + while (!(await this.waitCEREG()) && retries < 30) { + logger.info('等待网络注册...'); + retries++; + await this.sleep(2000); + } + if (retries < 30) { + logger.info('✓ 网络已注册'); + this.ready = true; + } else { + logger.error('网络注册超时(无SIM卡或信号差)'); + this.ready = false; + } + + // 5. 数据连接处理。ML307文档不建议用CGACT做PDP激活/去激活。 + await this.disableDataConnection(); + + // 6. 按文档配置短信功能,并启用本项目需要的PDU模式 + await this.configureSMS(); + + logger.info('模组初始化完成'); + this.emit('ready'); + } + + /** + * 等待SIM卡完成初始化 + */ + async waitSIMReady() { + for (let attempt = 1; attempt <= 10; attempt++) { + try { + const resp = await this.sendATCommand('AT+CPIN?', 2000); + if (resp.includes('+CPIN: READY')) { + logger.info('✓ SIM卡已就绪'); + return; + } + + logger.warn(`SIM卡未就绪(${attempt}/10): ${this.formatATResponse(resp)}`); + } catch (err) { + logger.warn(`查询SIM卡状态失败(${attempt}/10): ${err.message}`); + } + + await this.sleep(1000); + } + + throw new Error('SIM卡未就绪'); + } + + /** + * 确保驻网前协议栈功能模式为CFUN=1 + */ + async ensureFullFunctionality() { + const resp = await this.sendATCommand('AT+CFUN?', 2000); + const match = resp.match(/\+CFUN:\s*(\d+)/); + + if (match && match[1] === '1') { + logger.info('✓ 协议栈功能模式正常(CFUN=1)'); + return; + } + + logger.warn(`当前CFUN状态不是1: ${this.formatATResponse(resp)}`); + await this.sendATWithRetry('AT+CFUN=1', { + timeout: 5000, + retries: 3, + label: '设置CFUN=1' + }); + + // 文档要求CFUN切换后等待模组完成协议栈恢复。 + await this.sleep(2000); + logger.info('✓ 已设置协议栈功能模式(CFUN=1)'); + } + + /** + * 按模组型号处理数据连接,避免ML307系列使用文档不推荐的CGACT。 + */ + async disableDataConnection() { + if (this.isML307Family()) { + logger.info('ML307系列跳过AT+CGACT,按文档尝试断开应用层拨号(AT+MIPCALL=0,1)'); + const resp = await this.sendATWithRetry('AT+MIPCALL=0,1', { + timeout: 5000, + retries: 1, + label: '断开应用层拨号', + required: false + }); + + if (resp) { + logger.info('✓ 已断开应用层拨号连接'); + } else { + logger.warn('应用层拨号未断开或当前未激活,继续启动'); + } + return; + } + + const resp = await this.sendATWithRetry('AT+CGACT=0,1', { + timeout: 5000, + retries: 3, + label: '禁用数据连接', + required: false + }); + + if (resp) { + logger.info('✓ 已禁用数据连接(AT+CGACT=0,1),防止流量消耗'); + } else { + logger.warn('设置CGACT失败,可能会消耗流量'); + } + } + + /** + * 按ML307A文档配置短信功能;项目接收逻辑依赖PDU模式下的+CMT上报。 + */ + async configureSMS() { + if (this.modelInfo.version.includes('ML307A-DL')) { + throw new Error('当前ML307A-DL型号不支持短信功能'); + } + + await this.sendATWithRetry('AT+CMGF=0', { + timeout: 2000, + retries: 3, + label: '设置PDU模式' + }); + logger.info('✓ PDU模式设置完成'); + + await this.sendATWithRetry('AT+CNMI=2,2,0,2,0', { + timeout: 2000, + retries: 3, + label: '设置CNMI短信上报' + }); + logger.info('✓ CNMI参数设置完成'); + + await this.sendATWithRetry('AT+CSCS="IRA"', { + timeout: 2000, + retries: 3, + label: '设置短信字符集' + }); + logger.info('✓ 短信字符集设置完成'); + + await this.sendATWithRetry('AT+CSMP=33,167,0,0', { + timeout: 2000, + retries: 3, + label: '设置短信发送参数' + }); + logger.info('✓ 短信发送参数设置完成'); + } + + /** + * 判断是否为ML307系列模组 + */ + isML307Family() { + return this.modelInfo.model.startsWith('ML307') || this.modelInfo.version.startsWith('ML307'); + } + + /** + * 检测网络注册状态 + */ + async waitCEREG() { + try { + const resp = await this.sendATCommand('AT+CEREG?', 2000); + // +CEREG: 0,1 或 +CEREG: 0,5 表示已注册 + if (resp.includes('+CEREG:')) { + if (resp.includes(',1') || resp.includes(',5')) { + return true; + } + } + return false; + } catch (err) { + return false; + } + } + + /** + * 发送短信(PDU模式) + */ + async sendSMS(phoneNumber, message) { + logger.info(`准备发送短信到: ${phoneNumber}`); + logger.info(`短信内容: ${message}`); + + try { + // 编码PDU + const submit = new Submit(phoneNumber, message); + const parts = submit.getPartStrings(); + + if (parts.length === 0) { + throw new Error('PDU编码失败'); + } + + for (let i = 0; i < parts.length; i++) { + const pduData = parts[i]; + const pduLength = this.getSubmitPduLength(pduData); + + logger.debug(`PDU分段: ${i + 1}/${parts.length}`); + logger.debug(`PDU数据: ${pduData}`); + logger.debug(`PDU长度: ${pduLength}`); + + const success = await this.sendPduPart(pduData, pduLength); + if (!success) { + logger.error(`✗ 短信分段发送失败: ${i + 1}/${parts.length}`); + return false; + } + } + + logger.info('✓ 短信发送成功'); + return true; + } catch (err) { + logger.error('发送短信异常:', err); + return false; + } + } + + /** + * 计算AT+CMGS需要的TPDU长度,不包含SMSC长度字段和SMSC内容。 + */ + getSubmitPduLength(pduData) { + const smscLength = parseInt(pduData.slice(0, 2), 16); + const totalLength = pduData.length / 2; + const submitLength = totalLength - smscLength - 1; + + if (!Number.isFinite(submitLength) || submitLength <= 0) { + throw new Error(`PDU长度无效: ${pduData}`); + } + + return submitLength; + } + + /** + * 发送单个PDU分段。 + */ + async sendPduPart(pduData, pduLength) { + const cmgsCmd = `AT+CMGS=${pduLength}`; + this.port.write(cmgsCmd + '\r\n'); + + const gotPrompt = await this.waitForPrompt(); + if (!gotPrompt) { + throw new Error('未收到>提示符'); + } + + logger.debug('收到>提示符,发送PDU数据...'); + this.port.write(pduData + String.fromCharCode(0x1A)); + + return await this.waitForCMGSResult(); + } + + /** + * 等待CMGS输入提示符。提示符通常没有行结束符,所以直接监听原始串口数据。 + */ + waitForPrompt(timeout = 5000) { + return new Promise((resolve) => { + let buffer = ''; + const timer = setTimeout(() => { + this.port.removeListener('data', handler); + resolve(false); + }, timeout); + + const handler = (chunk) => { + buffer += chunk.toString('utf8'); + if (buffer.includes('>')) { + clearTimeout(timer); + this.port.removeListener('data', handler); + resolve(true); + } + }; + + this.port.on('data', handler); + }); + } + + /** + * 等待短信发送结果。 + */ + waitForCMGSResult(timeout = 30000) { + return new Promise((resolve) => { + const timer = setTimeout(() => { + this.parser.removeListener('data', handler); + resolve(false); + }, timeout); + + const handler = (line) => { + if (line.includes('OK')) { + clearTimeout(timer); + this.parser.removeListener('data', handler); + resolve(true); + } else if (line.includes('ERROR')) { + clearTimeout(timer); + this.parser.removeListener('data', handler); + resolve(false); + } + }; + + this.parser.on('data', handler); + }); + } + + /** + * 查询信号强度 + */ + async getSignalQuality() { + try { + const resp = await this.sendATCommand('AT+CSQ', 2000); + const match = resp.match(/\+CSQ:\s*(\d+),(\d+)/); + if (match) { + const rssi = parseInt(match[1]); + const ber = parseInt(match[2]); + let quality = '未知'; + if (rssi === 99) { + quality = '未知或不可检测'; + } else if (rssi >= 20) { + quality = '很好'; + } else if (rssi >= 15) { + quality = '好'; + } else if (rssi >= 10) { + quality = '一般'; + } else { + quality = '弱'; + } + return { rssi, ber, quality }; + } + return null; + } catch (err) { + logger.error('查询信号强度失败:', err); + return null; + } + } + + /** + * 查询运营商 + */ + async getOperator() { + try { + const resp = await this.sendATCommand('AT+COPS?', 2000); + const match = resp.match(/\+COPS:\s*\d+,\d+,"([^"]+)"/); + if (match) { + return match[1]; + } + return '未知'; + } catch (err) { + logger.error('查询运营商失败:', err); + return '未知'; + } + } + + /** + * 查询ICCID + */ + async getICCID() { + try { + const resp = await this.sendATCommand('AT+CCID', 2000); + const match = resp.match(/\+CCID:\s*(\d+)/); + if (match) { + return match[1]; + } + return null; + } catch (err) { + logger.error('查询ICCID失败:', err); + return null; + } + } + + /** + * 关闭串口 + */ + async close() { + if (this.port && this.port.isOpen) { + await new Promise((resolve) => { + this.port.close(resolve); + }); + logger.info('串口已关闭'); + } + } + + /** + * 辅助函数:延时 + */ + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +export default ModemManager; diff --git a/src/push.js b/src/push.js new file mode 100644 index 0000000..1d5458f --- /dev/null +++ b/src/push.js @@ -0,0 +1,283 @@ +import nodemailer from 'nodemailer'; +import axios from 'axios'; +import crypto from 'crypto'; +import logger from './logger.js'; + +class PushManager { + constructor(config) { + this.config = config; + this.smtpTransporter = null; + + // 初始化SMTP + if (config.smtp && config.smtp.server) { + this.smtpTransporter = nodemailer.createTransport({ + host: config.smtp.server, + port: config.smtp.port, + secure: config.smtp.port === 465, + auth: { + user: config.smtp.user, + pass: config.smtp.pass + } + }); + } + } + + /** + * 发送邮件通知 + */ + async sendEmail(subject, body) { + if (!this.smtpTransporter) { + logger.warn('邮件配置不完整,跳过发送'); + return false; + } + + try { + await this.smtpTransporter.sendMail({ + from: `"SMS Notify" <${this.config.smtp.user}>`, + to: this.config.smtp.sendTo, + subject: subject, + text: body + }); + logger.info('✓ 邮件发送成功'); + return true; + } catch (err) { + logger.error('邮件发送失败:', err); + return false; + } + } + + /** + * 发送到所有启用的推送通道 + */ + async pushToAll(sender, message, timestamp) { + const channels = this.config.pushChannels || []; + const promises = []; + + for (const channel of channels) { + if (channel.enabled) { + promises.push(this.pushToChannel(channel, sender, message, timestamp)); + } + } + + await Promise.allSettled(promises); + } + + /** + * 发送到单个推送通道 + */ + async pushToChannel(channel, sender, message, timestamp) { + logger.info(`推送到通道: ${channel.name} (${channel.type})`); + + try { + switch (channel.type) { + case 'post_json': + return await this.pushPostJSON(channel, sender, message, timestamp); + case 'bark': + return await this.pushBark(channel, sender, message); + case 'get': + return await this.pushGET(channel, sender, message, timestamp); + case 'dingtalk': + return await this.pushDingTalk(channel, sender, message); + case 'pushplus': + return await this.pushPushPlus(channel, sender, message); + case 'serverchan': + return await this.pushServerChan(channel, sender, message); + case 'custom': + return await this.pushCustom(channel, sender, message, timestamp); + case 'feishu': + return await this.pushFeishu(channel, sender, message); + case 'telegram': + return await this.pushTelegram(channel, sender, message); + default: + logger.warn(`未知的推送类型: ${channel.type}`); + return false; + } + } catch (err) { + logger.error(`推送到 ${channel.name} 失败:`, err); + return false; + } + } + + /** + * POST JSON 推送 + */ + async pushPostJSON(channel, sender, message, timestamp) { + const response = await axios.post(channel.url, { + sender, + message, + timestamp + }, { + timeout: 10000 + }); + logger.info(`✓ POST JSON推送成功: ${response.status}`); + return true; + } + + /** + * Bark 推送 + */ + async pushBark(channel, sender, message) { + const response = await axios.post(channel.url, { + title: sender, + body: message + }, { + timeout: 10000 + }); + logger.info(`✓ Bark推送成功: ${response.status}`); + return true; + } + + /** + * GET 推送 + */ + async pushGET(channel, sender, message, timestamp) { + const url = new URL(channel.url); + url.searchParams.set('sender', sender); + url.searchParams.set('message', message); + url.searchParams.set('timestamp', timestamp); + + const response = await axios.get(url.toString(), { + timeout: 10000 + }); + logger.info(`✓ GET推送成功: ${response.status}`); + return true; + } + + /** + * 钉钉机器人推送 + */ + async pushDingTalk(channel, sender, message) { + let url = channel.url; + + // 如果配置了secret,进行签名 + if (channel.secret) { + const timestamp = Date.now(); + const sign = this.dingtalkSign(channel.secret, timestamp); + url += `×tamp=${timestamp}&sign=${sign}`; + } + + const response = await axios.post(url, { + msgtype: 'text', + text: { + content: `来自: ${sender}\n\n${message}` + } + }, { + timeout: 10000 + }); + logger.info(`✓ 钉钉推送成功: ${response.status}`); + return true; + } + + /** + * 钉钉签名 + */ + dingtalkSign(secret, timestamp) { + const stringToSign = `${timestamp}\n${secret}`; + const hmac = crypto.createHmac('sha256', secret); + hmac.update(stringToSign); + const sign = hmac.digest('base64'); + return encodeURIComponent(sign); + } + + /** + * PushPlus 推送 + */ + async pushPushPlus(channel, sender, message) { + const response = await axios.post('http://www.pushplus.plus/send', { + token: channel.key1, + title: `来自 ${sender}`, + content: message, + template: 'html' + }, { + timeout: 10000 + }); + logger.info(`✓ PushPlus推送成功: ${response.status}`); + return true; + } + + /** + * Server酱 推送 + */ + async pushServerChan(channel, sender, message) { + const url = `https://sctapi.ftqq.com/${channel.key1}.send`; + const response = await axios.post(url, { + title: `来自 ${sender}`, + desp: message + }, { + timeout: 10000 + }); + logger.info(`✓ Server酱推送成功: ${response.status}`); + return true; + } + + /** + * 自定义模板推送 + */ + async pushCustom(channel, sender, message, timestamp) { + let body = channel.customBody || '{}'; + body = body.replace('{sender}', sender); + body = body.replace('{message}', message); + body = body.replace('{timestamp}', timestamp); + + const response = await axios.post(channel.url, JSON.parse(body), { + timeout: 10000 + }); + logger.info(`✓ 自定义推送成功: ${response.status}`); + return true; + } + + /** + * 飞书机器人推送 + */ + async pushFeishu(channel, sender, message) { + let url = channel.url; + + // 如果配置了secret,进行签名 + if (channel.secret) { + const timestamp = Math.floor(Date.now() / 1000); + const sign = this.feishuSign(channel.secret, timestamp); + url += `×tamp=${timestamp}&sign=${sign}`; + } + + const response = await axios.post(url, { + msg_type: 'text', + content: { + text: `来自: ${sender}\n\n${message}` + } + }, { + timeout: 10000 + }); + logger.info(`✓ 飞书推送成功: ${response.status}`); + return true; + } + + /** + * 飞书签名 + */ + feishuSign(secret, timestamp) { + const stringToSign = `${timestamp}\n${secret}`; + const hmac = crypto.createHmac('sha256', secret); + hmac.update(stringToSign); + return hmac.digest('base64'); + } + + /** + * Telegram Bot 推送 + */ + async pushTelegram(channel, sender, message) { + const botToken = channel.url; // url字段存储bot token + const chatId = channel.key1; + const url = `https://api.telegram.org/bot${botToken}/sendMessage`; + + const response = await axios.post(url, { + chat_id: chatId, + text: `来自: ${sender}\n\n${message}` + }, { + timeout: 10000 + }); + logger.info(`✓ Telegram推送成功: ${response.status}`); + return true; + } +} + +export default PushManager; diff --git a/src/sms.js b/src/sms.js new file mode 100644 index 0000000..d0868e5 --- /dev/null +++ b/src/sms.js @@ -0,0 +1,137 @@ +import { parse } from 'node-pdu'; +import logger from './logger.js'; + +class SMSProcessor { + constructor(config, modem, concatManager, pushManager) { + this.config = config; + this.modem = modem; + this.concatManager = concatManager; + this.pushManager = pushManager; + this.receivedMessages = []; + this.receivedMessageSeq = 0; + this.maxReceivedMessages = 200; + } + + /** + * 处理接收到的PDU短信 + */ + async processPDU(pduHex) { + try { + logger.info('开始解析PDU数据...'); + + // 解析PDU + const parsed = parse(pduHex); + + if (!parsed) { + logger.error('PDU解析失败'); + return; + } + + const sender = parsed.address?.phone || '未知号码'; + const timestamp = parsed.serviceCenterTimeStamp?.getIsoString?.() || new Date().toISOString(); + const text = parsed.data?.getText?.() || ''; + const part = parsed.data?.parts?.[0]; + const header = part?.header; + + logger.info('✓ PDU解析成功'); + logger.info(`发送者: ${sender}`); + logger.info(`时间戳: ${timestamp}`); + logger.info(`内容: ${text}`); + + // 检查是否为长短信 + if (header && header.getType() !== undefined) { + // 长短信头部 + const refNumber = header.getPointer(); + const totalParts = header.getSegments(); + const partNumber = header.getCurrent(); + + logger.info(`长短信信息: 参考号=${refNumber}, 当前=${partNumber}, 总计=${totalParts}`); + + // 添加到长短信缓存 + this.concatManager.addPart( + refNumber, + sender, + partNumber, + totalParts, + text, + timestamp + ); + } else { + // 普通短信,直接处理 + await this.processSmsContent(sender, text, timestamp); + } + } catch (err) { + logger.error('处理PDU失败:', err); + } + } + + /** + * 处理短信内容并转发 + */ + async processSmsContent(sender, text, timestamp) { + logger.info('=== 处理短信内容 ==='); + logger.info(`发送者: ${sender}`); + logger.info(`时间戳: ${timestamp}`); + logger.info(`内容: ${text}`); + logger.info('===================='); + + const messageRecord = this.addReceivedMessage(sender, text, timestamp); + + // 推送到所有通道 + await this.pushManager.pushToAll(sender, text, timestamp); + + // 发送邮件通知 + const subject = `短信${sender},${text.substring(0, 20)}`; + const body = `来自:${sender},时间:${timestamp},内容:${text}`; + await this.pushManager.sendEmail(subject, body); + + this.updateReceivedMessage(messageRecord.id, { + status: 'forwarded', + statusText: '已转发' + }); + } + + /** + * 记录收到的短信,供Web管理端展示。 + */ + addReceivedMessage(sender, text, timestamp) { + const message = { + id: `${Date.now()}-${++this.receivedMessageSeq}`, + sender, + text, + timestamp, + receivedAt: new Date().toISOString(), + status: 'received', + statusText: '已接收' + }; + + this.receivedMessages.unshift(message); + if (this.receivedMessages.length > this.maxReceivedMessages) { + this.receivedMessages.length = this.maxReceivedMessages; + } + + logger.info(`短信已进入Web收件箱: ${sender}`); + return message; + } + + /** + * 更新收件箱短信状态。 + */ + updateReceivedMessage(id, patch) { + const message = this.receivedMessages.find(item => item.id === id); + if (message) { + Object.assign(message, patch); + } + } + + /** + * 获取最近收到的短信。 + */ + getReceivedMessages(limit = 50) { + const safeLimit = Math.min(Math.max(Number(limit) || 50, 1), this.maxReceivedMessages); + return this.receivedMessages.slice(0, safeLimit); + } + +} + +export default SMSProcessor;