tambahannya
This commit is contained in:
63
bun.lock
63
bun.lock
@@ -21,6 +21,7 @@
|
||||
"@types/jwt-decode": "^3.1.0",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/sharp": "^0.32.0",
|
||||
"add": "^2.0.6",
|
||||
"colors": "^1.4.0",
|
||||
"dayjs": "^1.11.18",
|
||||
@@ -30,6 +31,7 @@
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"meta-cloud-api": "^1.3.0",
|
||||
"mime": "^4.1.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"pino": "^10.1.0",
|
||||
"pino-pretty": "^13.1.2",
|
||||
@@ -38,6 +40,7 @@
|
||||
"react-dom": "^19.2.0",
|
||||
"react-qr-code": "^2.0.18",
|
||||
"react-router-dom": "^7.9.4",
|
||||
"sharp": "^0.34.4",
|
||||
"swr": "^2.3.6",
|
||||
"uuid": "^13.0.0",
|
||||
"whatsapp-api-js": "^6.1.1",
|
||||
@@ -69,6 +72,8 @@
|
||||
|
||||
"@elysiajs/swagger": ["@elysiajs/swagger@1.3.1", "", { "dependencies": { "@scalar/themes": "^0.9.52", "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-LcbLHa0zE6FJKWPWKsIC/f+62wbDv3aXydqcNPVPyqNcaUgwvCajIi+5kHEU6GO3oXUCpzKaMsb3gsjt8sLzFQ=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
|
||||
@@ -79,6 +84,52 @@
|
||||
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
||||
|
||||
"@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.3" }, "os": "darwin", "cpu": "x64" }, "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg=="],
|
||||
|
||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g=="],
|
||||
|
||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.3" }, "os": "linux", "cpu": "arm" }, "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA=="],
|
||||
|
||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ=="],
|
||||
|
||||
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ=="],
|
||||
|
||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.3" }, "os": "linux", "cpu": "s390x" }, "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw=="],
|
||||
|
||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A=="],
|
||||
|
||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA=="],
|
||||
|
||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg=="],
|
||||
|
||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.4", "", { "dependencies": { "@emnapi/runtime": "^1.5.0" }, "cpu": "none" }, "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA=="],
|
||||
|
||||
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA=="],
|
||||
|
||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw=="],
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.4", "", { "os": "win32", "cpu": "x64" }, "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig=="],
|
||||
|
||||
"@lglab/react-qr-code": ["@lglab/react-qr-code@1.4.5", "", { "peerDependencies": { "react": "^18 || ^19" } }, "sha512-kfaWOsbqqN+iskfRJLSHaPCHX0FAnRtZefj86rPq8WAZUVe9fnRzpr/xlOlXSsC+g1KWdm6LgXeoKgkvOa2F7g=="],
|
||||
|
||||
"@mantine/core": ["@mantine/core@8.3.4", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "clsx": "^2.1.1", "react-number-format": "^5.4.4", "react-remove-scroll": "^2.7.1", "react-textarea-autosize": "8.5.9", "type-fest": "^4.41.0" }, "peerDependencies": { "@mantine/hooks": "8.3.4", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-RJ5QUe2FLLJ1uF8xWUpNhDqRFbaOn4S5yTjqLuaurqtZvzee85O/T90dRcR8UNDuE8e/Qqie/jsF/G9RiSxC6g=="],
|
||||
@@ -167,6 +218,8 @@
|
||||
|
||||
"@types/serve-static": ["@types/serve-static@1.15.9", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA=="],
|
||||
|
||||
"@types/sharp": ["@types/sharp@0.32.0", "", { "dependencies": { "sharp": "*" } }, "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw=="],
|
||||
|
||||
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
|
||||
|
||||
"@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="],
|
||||
@@ -275,6 +328,8 @@
|
||||
|
||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
||||
|
||||
"devtools-protocol": ["devtools-protocol@0.0.1045489", "", {}, "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ=="],
|
||||
@@ -423,7 +478,7 @@
|
||||
|
||||
"meta-cloud-api": ["meta-cloud-api@1.3.0", "", {}, "sha512-NVBhIx41Ond5zI26lWu/1IDddYV2exLkfq89ELkkEaTp+HLROJMNspE6btLES2MUJrR7rVqT5mW+tUvjE+YunQ=="],
|
||||
|
||||
"mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||
"mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
@@ -571,10 +626,14 @@
|
||||
|
||||
"secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
|
||||
|
||||
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||
|
||||
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
|
||||
|
||||
"sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="],
|
||||
|
||||
"sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
@@ -703,6 +762,8 @@
|
||||
|
||||
"whatsapp-client-sdk/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"whatsapp-web.js/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||
|
||||
"whatsapp-web.js/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="],
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"@types/jwt-decode": "^3.1.0",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/sharp": "^0.32.0",
|
||||
"add": "^2.0.6",
|
||||
"colors": "^1.4.0",
|
||||
"dayjs": "^1.11.18",
|
||||
@@ -36,6 +37,7 @@
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"meta-cloud-api": "^1.3.0",
|
||||
"mime": "^4.1.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"pino": "^10.1.0",
|
||||
"pino-pretty": "^13.1.2",
|
||||
@@ -44,6 +46,7 @@
|
||||
"react-dom": "^19.2.0",
|
||||
"react-qr-code": "^2.0.18",
|
||||
"react-router-dom": "^7.9.4",
|
||||
"sharp": "^0.34.4",
|
||||
"swr": "^2.3.6",
|
||||
"uuid": "^13.0.0",
|
||||
"whatsapp-api-js": "^6.1.1",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Navigate, Outlet } from "react-router-dom";
|
||||
import useSWR from "swr";
|
||||
import apiFetch from "@/lib/apiFetch";
|
||||
import { Badge, Button, Chip, Group, Pill, Stack } from "@mantine/core";
|
||||
import { Badge, Button, Chip, Group, Pill, Stack, Text } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import clientRoutes from "@/clientRoutes";
|
||||
import { modals } from "@mantine/modals";
|
||||
|
||||
export default function WajsLayout() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -41,6 +42,28 @@ export default function WajsLayout() {
|
||||
>
|
||||
Reconnect
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
modals.openConfirmModal({
|
||||
title: "Rescan QR",
|
||||
children: <Text>Are you sure you want to rescan QR?</Text>,
|
||||
confirmProps: { color: "red" },
|
||||
labels: {
|
||||
cancel: "Cancel",
|
||||
confirm: "Rescan QR",
|
||||
},
|
||||
onCancel: () => setLoading(false),
|
||||
onConfirm: () => {
|
||||
apiFetch.api.wa.restart.post();
|
||||
setLoading(false);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Rescan QR
|
||||
</Button>
|
||||
</Group>
|
||||
<Outlet />
|
||||
</Stack>
|
||||
|
||||
116
src/server/lib/mim_utils.ts
Normal file
116
src/server/lib/mim_utils.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
// ✅ Tipe kategori utama MIME
|
||||
export type MimeCategory = "image" | "video" | "audio" | "document" | "archive" | "other";
|
||||
|
||||
// ✅ Struktur detail MIME
|
||||
export interface MimeDetail {
|
||||
type: string;
|
||||
category: MimeCategory;
|
||||
exampleMime: string[];
|
||||
extensions: string[];
|
||||
}
|
||||
|
||||
// ✅ Full list mimetype yang bisa dikembangkan
|
||||
export const MimeMap: Record<string, MimeDetail> = {
|
||||
image: {
|
||||
type: "image",
|
||||
category: "image",
|
||||
exampleMime: ["image/jpeg", "image/png", "image/gif"],
|
||||
extensions: ["jpg", "jpeg", "png", "gif", "webp"]
|
||||
},
|
||||
video: {
|
||||
type: "video",
|
||||
category: "video",
|
||||
exampleMime: ["video/mp4", "video/mkv", "video/webm"],
|
||||
extensions: ["mp4", "mkv", "avi", "mov", "webm"]
|
||||
},
|
||||
audio: {
|
||||
type: "audio",
|
||||
category: "audio",
|
||||
exampleMime: ["audio/mpeg", "audio/wav", "audio/aac"],
|
||||
extensions: ["mp3", "wav", "aac", "ogg", "flac"]
|
||||
},
|
||||
document: {
|
||||
type: "application",
|
||||
category: "document",
|
||||
exampleMime: ["application/pdf", "application/msword"],
|
||||
extensions: ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"]
|
||||
},
|
||||
archive: {
|
||||
type: "application",
|
||||
category: "archive",
|
||||
exampleMime: ["application/zip", "application/x-rar-compressed"],
|
||||
extensions: ["zip", "rar", "7z", "tar", "gz"]
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Ambil semua ekstensi valid
|
||||
const allExtensions = Object.values(MimeMap).flatMap(m => m.extensions);
|
||||
|
||||
// ✅ Type Guard untuk menghindari "never"
|
||||
export function isFileExtension(ext: string): ext is (typeof allExtensions)[number] {
|
||||
return allExtensions.includes(ext as any);
|
||||
}
|
||||
|
||||
// ✅ Class utama
|
||||
export class MimeType {
|
||||
private input: string;
|
||||
private ext?: string;
|
||||
private type?: string;
|
||||
|
||||
constructor(input: string) {
|
||||
this.input = input.toLowerCase().trim();
|
||||
this.parseInput();
|
||||
}
|
||||
|
||||
private parseInput() {
|
||||
if (this.input.includes("/")) {
|
||||
const [type, ext] = this.input.split("/");
|
||||
this.type = type;
|
||||
this.ext = ext;
|
||||
} else if (isFileExtension(this.input.replace(/^\./, ""))) {
|
||||
this.ext = this.input.replace(/^\./, "");
|
||||
this.type = Object.values(MimeMap).find(m => m.extensions.includes(this.ext!))?.type;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Dapatkan MIME Type lengkap: "image/png"
|
||||
getType(): string | undefined {
|
||||
if (this.type && this.ext) return `${this.type}/${this.ext}`;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ✅ Dapatkan ekstensi yang digunakan
|
||||
getExtension(): string | undefined {
|
||||
return this.ext;
|
||||
}
|
||||
|
||||
// ✅ Semua ekstensi dalam grup/type
|
||||
getExtensions(): string[] | undefined {
|
||||
const cat = this.getCategory();
|
||||
if (!cat) return;
|
||||
return MimeMap[cat]?.extensions;
|
||||
}
|
||||
|
||||
// ✅ Ambil kategori: "image", "video", dll.
|
||||
getCategory(): MimeCategory | undefined {
|
||||
if (this.type) {
|
||||
const found = Object.values(MimeMap).find(m => m.type === this.type);
|
||||
return found?.category;
|
||||
}
|
||||
if (this.ext) {
|
||||
const found = Object.values(MimeMap).find(m => m.extensions.includes(this.ext!));
|
||||
return found?.category;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ✅ Check cepat berdasarkan kategori
|
||||
isImage() { return this.getCategory() === "image"; }
|
||||
isVideo() { return this.getCategory() === "video"; }
|
||||
isAudio() { return this.getCategory() === "audio"; }
|
||||
isDocument() { return this.getCategory() === "document"; }
|
||||
isArchive() { return this.getCategory() === "archive"; }
|
||||
}
|
||||
|
||||
// ✅ Export default agar bisa: import MimeType from "./..."
|
||||
export default MimeType;
|
||||
@@ -6,6 +6,44 @@ import { v4 as uuid } from 'uuid';
|
||||
import { prisma } from '../prisma';
|
||||
import { getValueByPath } from '../get_value_by_path';
|
||||
import "colors"
|
||||
import { logger } from '../logger';
|
||||
import _ from 'lodash';
|
||||
import MimeType from '../mim_utils';
|
||||
import sharp from "sharp";
|
||||
|
||||
interface Base64ImageResult {
|
||||
fileName: string;
|
||||
base64: string;
|
||||
sizeBeforeKB: number;
|
||||
sizeAfterKB: number;
|
||||
}
|
||||
|
||||
export async function convertImageToPngBase64(
|
||||
inputPath: string,
|
||||
): Promise<Base64ImageResult> {
|
||||
// Baca buffer asli
|
||||
const originalBuffer = await fs.readFile(inputPath);
|
||||
const sizeBeforeKB = originalBuffer.length / 1024;
|
||||
|
||||
// Konversi & kompres ke PNG
|
||||
const optimizedBuffer = await sharp(originalBuffer)
|
||||
.png({ compressionLevel: 9 })
|
||||
.toBuffer();
|
||||
|
||||
const sizeAfterKB = optimizedBuffer.length / 1024;
|
||||
|
||||
// Convert ke base64
|
||||
const base64 = `data:image/png;base64,${optimizedBuffer.toString("base64")}`;
|
||||
|
||||
return {
|
||||
fileName: inputPath.split("/").pop() || "image.png",
|
||||
base64,
|
||||
sizeBeforeKB,
|
||||
sizeAfterKB,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
const MEDIA_DIR = path.join(process.cwd(), 'downloads');
|
||||
await ensureDir(MEDIA_DIR);
|
||||
@@ -27,7 +65,7 @@ type DataMessage = {
|
||||
type: WAWebJS.MessageTypes;
|
||||
to: string;
|
||||
deviceType: string;
|
||||
media: any[] | null;
|
||||
media: Record<string, any>;
|
||||
notifyName: string;
|
||||
}
|
||||
|
||||
@@ -180,17 +218,6 @@ async function startClient() {
|
||||
}
|
||||
}
|
||||
|
||||
function detectFileCategory(mime: string) {
|
||||
if (mime.startsWith("image/")) return "image";
|
||||
if (mime.startsWith("audio/")) return "audio";
|
||||
if (mime.startsWith("video/")) return "video";
|
||||
if (mime === "application/pdf") return "pdf";
|
||||
if (mime.includes("spreadsheet") || mime.includes("excel")) return "excel";
|
||||
if (mime.includes("word")) return "document";
|
||||
if (mime.includes("presentation") || mime.includes("powerpoint")) return "presentation";
|
||||
return "file";
|
||||
}
|
||||
|
||||
// === HANDLER PESAN MASUK ===
|
||||
async function handleIncomingMessage(msg: WAWebJS.Message) {
|
||||
const chat = await msg.getChat();
|
||||
@@ -205,6 +232,18 @@ async function handleIncomingMessage(msg: WAWebJS.Message) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("kirim ke webhook")
|
||||
const res = await fetch("https://n8n.wibudev.com/webhook/dc164759-b7ba-47d5-b5d8-ffd9d5840090", {
|
||||
body: JSON.stringify(msg),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
|
||||
const json = await res.text();
|
||||
console.log(json);
|
||||
|
||||
try {
|
||||
const notifyName = (msg as any)._data.notifyName;
|
||||
|
||||
@@ -217,107 +256,118 @@ async function handleIncomingMessage(msg: WAWebJS.Message) {
|
||||
type: msg.type,
|
||||
to: msg.to,
|
||||
deviceType: msg.deviceType,
|
||||
media: null,
|
||||
media: {},
|
||||
notifyName,
|
||||
};
|
||||
|
||||
// === HANDLE MEDIA ===
|
||||
if (msg.hasMedia) {
|
||||
const media = await msg.downloadMedia();
|
||||
|
||||
// Pastikan formatnya data:<mimetype>;base64,<data>
|
||||
const mime = media.mimetype || 'application/octet-stream';
|
||||
const prefixedBase64 = `data:${mime};base64,${media.data}`;
|
||||
|
||||
dataMessage.media = [{
|
||||
type: "file:full",
|
||||
data: prefixedBase64,
|
||||
mime: mime,
|
||||
name: media.filename || `${uuid()}.${mime.split('/')[1] || 'bin'}`
|
||||
}];
|
||||
|
||||
// await fs.writeFile(path.join(MEDIA_DIR, dataMessage.media[0].name), Buffer.from(media.data, 'base64'));
|
||||
|
||||
}
|
||||
|
||||
// === KIRIM KE WEBHOOK ===
|
||||
try {
|
||||
const webhooks = await prisma.webHook.findMany({ where: { enabled: true } });
|
||||
if (!webhooks.length) {
|
||||
log('🚫 Tidak ada webhook yang aktif');
|
||||
return;
|
||||
}
|
||||
// try {
|
||||
// const webhooks = await prisma.webHook.findMany({ where: { enabled: true } });
|
||||
// if (!webhooks.length) {
|
||||
// log('🚫 Tidak ada webhook yang aktif');
|
||||
// return;
|
||||
// }
|
||||
|
||||
await Promise.allSettled(
|
||||
webhooks.map(async (hook) => {
|
||||
try {
|
||||
log(`🌐 Mengirim webhook ke ${hook.url}`);
|
||||
|
||||
let body = payloadConverter({
|
||||
payload: hook.payload ?? JSON.stringify(dataMessage),
|
||||
data: dataMessage,
|
||||
});
|
||||
|
||||
if (dataMessage.hasMedia) {
|
||||
const bodyMedia = JSON.parse(body);
|
||||
bodyMedia.question = msg.body ?? dataMessage.media?.[0].mime;
|
||||
bodyMedia.uploads = dataMessage.media;
|
||||
body = JSON.stringify(bodyMedia);
|
||||
}
|
||||
// // await Promise.allSettled(
|
||||
// // webhooks.map(async (hook) => {
|
||||
// // try {
|
||||
// // log(`🌐 Mengirim webhook ke ${hook.url}`);
|
||||
|
||||
// await fs.writeFile(path.join(process.cwd(), 'webhook.json'), body);
|
||||
// // let res: Response = {} as Response;
|
||||
// // if (!dataMessage.hasMedia) {
|
||||
// // logger.info(`[SEND NO MEDIA] ${hook.url}`);
|
||||
// // res = await fetch(hook.url, {
|
||||
// // method: hook.method,
|
||||
// // headers: {
|
||||
// // "Content-Type": "application/json",
|
||||
// // Authorization: `Bearer ${hook.apiToken}`,
|
||||
// // },
|
||||
// // body: JSON.stringify({
|
||||
// // question: msg.body,
|
||||
// // overrideConfig: {
|
||||
// // sessionId: `${_.kebabCase(dataMessage.fromNumber)}_x_${dataMessage.fromNumber}`,
|
||||
// // vars: { userName: _.kebabCase(dataMessage.fromNumber), userPhone: dataMessage.fromNumber },
|
||||
// // }
|
||||
// // }),
|
||||
// // });
|
||||
// // }
|
||||
|
||||
const res = await fetch(hook.url, {
|
||||
method: hook.method,
|
||||
headers: {
|
||||
...(JSON.parse(hook.headers ?? '{}') as Record<string, string>),
|
||||
...(hook.apiToken ? { Authorization: `Bearer ${hook.apiToken}` } : {}),
|
||||
},
|
||||
body,
|
||||
});
|
||||
// // if (dataMessage.hasMedia) {
|
||||
// // logger.info(`[SEND MEDIA] ${hook.url}`);
|
||||
// // const media = await msg.downloadMedia();
|
||||
|
||||
const responseText = await res.text();
|
||||
// // const mimeMessage = media.mimetype || 'application/octet-stream';
|
||||
// // const typeMime = new MimeType(mimeMessage);
|
||||
|
||||
if (!res.ok) {
|
||||
log(`⚠️ Webhook ${hook.url} gagal: ${res.status}`);
|
||||
log(responseText);
|
||||
await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR01]");
|
||||
return;
|
||||
}
|
||||
// // const prefixedBase64 = `data:${mimeMessage};base64,${media.data}`;
|
||||
|
||||
const responseJson = JSON.parse(responseText);
|
||||
// // dataMessage.media = {
|
||||
// // type: typeMime.getCategory() === "image" ? "file" : "file:full",
|
||||
// // data: prefixedBase64,
|
||||
// // mime: mimeMessage,
|
||||
// // name: media.filename || `${uuid()}.${typeMime.getExtension()}`
|
||||
// // };
|
||||
|
||||
if (hook.replay) {
|
||||
try {
|
||||
const textResponseRaw = hook.replayKey
|
||||
? getValueByPath(responseJson, hook.replayKey, JSON.stringify(responseJson))
|
||||
: JSON.stringify(responseJson, null, 2);
|
||||
// // res = await fetch(hook.url, {
|
||||
// // method: hook.method,
|
||||
// // headers: {
|
||||
// // "Content-Type": "application/json",
|
||||
// // Authorization: `Bearer ${hook.apiToken}`,
|
||||
// // },
|
||||
// // body: JSON.stringify({
|
||||
// // question: msg.body || dataMessage.media.mime,
|
||||
// // overrideConfig: {
|
||||
// // sessionId: `${_.kebabCase(dataMessage.fromNumber)}_x_${dataMessage.fromNumber}`,
|
||||
// // vars: { userName: _.kebabCase(dataMessage.fromNumber), userPhone: dataMessage.fromNumber },
|
||||
// // },
|
||||
// // uploads: [dataMessage.media],
|
||||
// // }),
|
||||
// // });
|
||||
// // }
|
||||
|
||||
const typingDelay = Math.min(5000, Math.max(1500, textResponseRaw.length * 20));
|
||||
await new Promise((r) => setTimeout(r, typingDelay));
|
||||
// // const responseText = await res.text();
|
||||
|
||||
await chat.clearState();
|
||||
// send message
|
||||
await chat.sendMessage(textResponseRaw);
|
||||
|
||||
log(`💬 Balasan dikirim ke ${msg.from} setelah mengetik selama ${typingDelay}ms`);
|
||||
} catch (err) {
|
||||
log('⚠️ Gagal menampilkan status mengetik:', err);
|
||||
await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR03]");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log(`❌ Gagal kirim ke ${hook.url}:`, err);
|
||||
await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR04]");
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
log('❌ Error mengirim webhook:', error);
|
||||
await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR05]");
|
||||
}
|
||||
// // if (!res.ok) {
|
||||
// // log(`⚠️ Webhook ${hook.url} gagal: ${res.status}`);
|
||||
// // logger.error(`[REPLY] Response: ${responseText}`);
|
||||
// // await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR01]");
|
||||
// // return;
|
||||
// // }
|
||||
|
||||
// // const responseJson = JSON.parse(responseText);
|
||||
// // logger.info(`[REPLY] Response: ${responseJson.text}`);
|
||||
|
||||
// // if (hook.replay) {
|
||||
// // try {
|
||||
// // const textResponseRaw = hook.replayKey
|
||||
// // ? getValueByPath(responseJson, hook.replayKey, JSON.stringify(responseJson))
|
||||
// // : JSON.stringify(responseJson, null, 2);
|
||||
|
||||
// // await chat.clearState();
|
||||
// // // send message
|
||||
// // await chat.sendMessage(textResponseRaw);
|
||||
|
||||
// // logger.info(`💬 Balasan dikirim ke ${msg.from}`);
|
||||
// // } catch (err) {
|
||||
// // logger.error(`⚠️ Gagal menampilkan status mengetik: ${err}`);
|
||||
// // await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR03]");
|
||||
// // }
|
||||
// // }
|
||||
// // } catch (err) {
|
||||
// // logger.error(`❌ Gagal kirim ke ${hook.url}: ${err}`);
|
||||
// // await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR04]");
|
||||
// // }
|
||||
// // })
|
||||
// // );
|
||||
// } catch (error) {
|
||||
// logger.error(`❌ Error mengirim webhook [ERR05]: ${error}`);
|
||||
// await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR05]");
|
||||
// }
|
||||
} catch (err) {
|
||||
log('❌ Error handling pesan:', err);
|
||||
logger.error(`❌ Error handling pesan [ERR06]: ${err}`);
|
||||
await msg.reply("Maaf, terjadi kesalahan saat memproses pesan Anda [ERR06]");
|
||||
} finally {
|
||||
await chat.clearState();
|
||||
@@ -325,46 +375,6 @@ async function handleIncomingMessage(msg: WAWebJS.Message) {
|
||||
}
|
||||
|
||||
|
||||
function payloadConverter({ payload, data }: { payload: string; data: DataMessage }) {
|
||||
try {
|
||||
const map: Record<string, any> = {
|
||||
'data.from': data.from,
|
||||
'data.fromNumber': data.fromNumber,
|
||||
'data.fromMe': data.fromMe,
|
||||
'data.body': data.body,
|
||||
'data.hasMedia': data.hasMedia,
|
||||
'data.type': data.type,
|
||||
'data.to': data.to,
|
||||
'data.deviceType': data.deviceType,
|
||||
'data.notifyName': data.notifyName,
|
||||
'data.media': data.media
|
||||
};
|
||||
|
||||
let result = payload;
|
||||
|
||||
for (const [key, value] of Object.entries(map)) {
|
||||
let safeValue: string;
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
safeValue = '';
|
||||
} else if (typeof value === 'object') {
|
||||
// Perbaikan di sini — objek seperti media dikonversi ke JSON string
|
||||
safeValue = JSON.stringify(value);
|
||||
} else {
|
||||
safeValue = String(value);
|
||||
}
|
||||
|
||||
result = result.replace(new RegExp(`{{\\s*${key}\\s*}}`, 'g'), safeValue);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error("⚠️ payloadConverter error:", err);
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === CLEANUP SAAT EXIT ===
|
||||
process.on('SIGINT', async () => {
|
||||
log('🛑 SIGINT diterima, menutup client...');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Elysia from "elysia";
|
||||
import Elysia, { t } from "elysia";
|
||||
import { startClient, getState } from "../lib/wa/wa_service";
|
||||
import _ from "lodash";
|
||||
|
||||
@@ -48,5 +48,36 @@ const WaRoute = new Elysia({
|
||||
state: _.omit(state, "client"),
|
||||
};
|
||||
})
|
||||
.post("send-text", async ({body}) => {
|
||||
const state = getState();
|
||||
if (!state.ready) {
|
||||
return {
|
||||
message: "WhatsApp route not ready",
|
||||
};
|
||||
}
|
||||
|
||||
const client = state.client;
|
||||
if (!client) {
|
||||
return {
|
||||
message: "WhatsApp client not ready",
|
||||
};
|
||||
}
|
||||
|
||||
const chat = await client.getChatById(`${body.number}@c.us`);
|
||||
await chat.sendMessage(body.text);
|
||||
|
||||
return {
|
||||
message: "WhatsApp route ready",
|
||||
};
|
||||
},{
|
||||
body: t.Object({
|
||||
number: t.String(),
|
||||
text: t.String(),
|
||||
}),
|
||||
detail: {
|
||||
description: "Send text to WhatsApp",
|
||||
tags: ["WhatsApp"],
|
||||
}
|
||||
})
|
||||
|
||||
export default WaRoute;
|
||||
|
||||
10
x.sh
10
x.sh
@@ -1,7 +1,3 @@
|
||||
TOKEN="EAALP22EWyC4BPnZCfcPQNmD5pGLKV6Ao3GIeWZCc81aPivDFc2FXGA1ZBgrRGcB60LaZCdAr1sbnfP1ufrH3dGthxQzpf18BTjDZBkgG3vBiYZAMpHa7MEZBiRIUZCBe4BDXe8KV0r7DsDmQHJqhA3yZBDKPOL1PKJPEqIq40tLxPwMqWYg4o7xf0sBmZCzx2wI1KtJL8I20MV1ggldngHZCIcnOKDL0uPzDAhc2LAQuI7ZBsgZDZD"
|
||||
MEDIA_ID="24893686766920074"
|
||||
BUSINESS_PHONE_NUMBER_ID="783866307805501"
|
||||
|
||||
curl 'https://graph.facebook.com/v19.0/$MEDIA_ID?phone_number_id=$BUSINESS_PHONE_NUMBER_ID' \
|
||||
-H 'Authorization: Bearer $TOKEN' \
|
||||
-H 'Content-Type: application/json'
|
||||
curl -X POST https://n8n.wibudev.com/webhook/dc164759-b7ba-47d5-b5d8-ffd9d5840090 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "kirimkan pesan ke 089697338821 pesannya hari ini bagaimana ya?", "sessionId": "dc164759-b7ba-47d5-b5d8-ffd9d5840090"}'
|
||||
53
x.yml
Normal file
53
x.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
services:
|
||||
n8n:
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
container_name: n8n
|
||||
environment:
|
||||
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
|
||||
- N8N_RUNNERS_ENABLED=true
|
||||
- N8N_HOST=n8n.wibudev.com
|
||||
- N8N_PORT=5678
|
||||
- WEBHOOK_URL=https://n8n.wibudev.com/
|
||||
- N8N_BASIC_AUTH_ACTIVE=true
|
||||
- N8N_BASIC_AUTH_USER=wibu@bip.com
|
||||
- N8N_BASIC_AUTH_PASSWORD=Production_123
|
||||
- N8N_PROTOCOL=https
|
||||
- NODE_ENV=production
|
||||
volumes:
|
||||
- ./data/n8n:/home/node/.n8n
|
||||
- ./data/n8n/local-files:/files
|
||||
restart: always
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5678"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
networks:
|
||||
- n8n-network
|
||||
|
||||
n8n-frpc:
|
||||
image: snowdreamtech/frpc:latest
|
||||
container_name: n8n-frpc
|
||||
restart: always
|
||||
volumes:
|
||||
- ./data/frpc/frpc.toml:/etc/frp/frpc.toml:ro
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
depends_on:
|
||||
n8n:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- n8n-network
|
||||
|
||||
networks:
|
||||
n8n-network:
|
||||
driver: bridge
|
||||
67
xx.ts
67
xx.ts
@@ -1,66 +1,3 @@
|
||||
import fs from "fs";
|
||||
import { parse, stringify } from "yaml";
|
||||
import mime from "mime";
|
||||
|
||||
export interface LogRotateOptions {
|
||||
maxSize?: string;
|
||||
maxFile?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tambahkan log rotate (logging.driver json-file) ke semua service
|
||||
* yang belum memiliki konfigurasi logging di docker-compose.yml.
|
||||
*/
|
||||
export async function applyLogRotateCompose(
|
||||
filePath: string,
|
||||
options: LogRotateOptions = {}
|
||||
) {
|
||||
const { maxSize = "10m", maxFile = "3" } = options;
|
||||
|
||||
// Pastikan file ada
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`❌ File not found: ${filePath}`);
|
||||
}
|
||||
|
||||
const raw = fs.readFileSync(filePath, "utf8");
|
||||
const compose = parse(raw); // ✅ Pakai yaml.parse()
|
||||
|
||||
if (!compose.services) {
|
||||
throw new Error("❌ Tidak ditemukan 'services:' di docker-compose.yml");
|
||||
}
|
||||
|
||||
let modified = false;
|
||||
|
||||
for (const [name, service] of Object.entries<any>(compose.services)) {
|
||||
if (!service.logging) {
|
||||
service.logging = {
|
||||
driver: "json-file",
|
||||
options: {
|
||||
"max-size": maxSize,
|
||||
"max-file": maxFile,
|
||||
},
|
||||
};
|
||||
console.log(`✅ Log rotate ditambahkan ke: ${name}`);
|
||||
modified = true;
|
||||
} else {
|
||||
console.log(`⚠️ Lewati (sudah ada logging): ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!modified) {
|
||||
console.log("👌 Semua service sudah punya log-rotate, tidak ada perubahan.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Backup file lama
|
||||
const backupPath = `${filePath}.backup-${Date.now()}`;
|
||||
fs.writeFileSync(backupPath, raw, "utf8");
|
||||
|
||||
// Simpan file baru
|
||||
const updated = stringify(compose); // ✅ Pakai yaml.stringify()
|
||||
fs.writeFileSync(filePath, updated, "utf8");
|
||||
|
||||
console.log(`✅ Selesai update file: ${filePath}`);
|
||||
console.log(`📦 Backup dibuat: ${backupPath}`);
|
||||
}
|
||||
|
||||
applyLogRotateCompose("compose.yml");
|
||||
console.log(mime.getAllExtensions("image/png"));
|
||||
|
||||
Reference in New Issue
Block a user