diff --git a/.github/workflows/compile-and-release.yml b/.github/workflows/compile-and-release.yml index 50e45204f..c7f894e11 100644 --- a/.github/workflows/compile-and-release.yml +++ b/.github/workflows/compile-and-release.yml @@ -87,6 +87,10 @@ jobs: npm config set msvs_version 2022 npm install --global --production --omit=dev windows-build-tools@4.0.0 + - name: Linux Build Prep + if: runner.os == 'linux' + run: sudo apt-get install libx11-dev libxtst-dev libpng-dev + - name: Restore Robotjs from cache id: robotjs-cache uses: actions/cache/restore@v3 @@ -94,10 +98,6 @@ jobs: key: ${{ runner.os }}-robotjs path: ./node_modules/robotjs/ - - name: Linux Build Prep - if: runner.os == 'linux' - run: sudo apt-get install libx11-dev libxtst-dev libpng-dev - - name: Install Global Dependencies run: npm install --global --production --omit=dev grunt-cli node-gyp @@ -140,9 +140,6 @@ jobs: with: path: ./bundles/ - - name: Rename Artifacts - run: mv ./bundles/Windows/firebot-${{ needs.checkversion.outputs.version }}-full.nupkg ./bundles/Windows/firebot-v${{ needs.checkversion.outputs.version }}-full.nupkg - - name: Create Release uses: softprops/action-gh-release@v1 env: diff --git a/package-lock.json b/package-lock.json index 496376bb8..aec5f0937 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,10 @@ "dependencies": { "@aws-sdk/client-polly": "^3.26.0", "@crowbartools/firebot-custom-scripts-types": "^5.53.2-6", - "@twurple/api": "^6.0.6", - "@twurple/auth": "^6.0.6", - "@twurple/chat": "^6.0.6", - "@twurple/pubsub": "^6.0.6", + "@twurple/api": "^6.0.9", + "@twurple/auth": "^6.0.9", + "@twurple/chat": "^6.0.9", + "@twurple/pubsub": "^6.0.9", "@zunderscore/elgato-light-control": "^1.1.2", "angular": "^1.8.0", "angular-animate": "^1.7.8", @@ -1620,17 +1620,17 @@ } }, "node_modules/@twurple/api": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/api/-/api-6.0.6.tgz", - "integrity": "sha512-Rf/KkWC/b7xY1nZ6386CXFAk2owl7IJRVGVR6aaog3nJSdr7LKhhHNn6Tgec9/TjpE4FzUct/styJl7fxr3HuA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/api/-/api-6.0.9.tgz", + "integrity": "sha512-ZWiMEG7hc6p0m0fxyR5iqYWy1wY2d30COQ4rvdh6FatYc9lb95nIy7BY9NCRdvH0l1GZaADzMhUu+NrVihBEIg==", "dependencies": { "@d-fischer/cache-decorators": "^3.0.0", "@d-fischer/detect-node": "^3.0.1", "@d-fischer/logger": "^4.2.1", "@d-fischer/rate-limiter": "^0.7.2", "@d-fischer/shared-utils": "^3.4.0", - "@twurple/api-call": "6.0.6", - "@twurple/common": "6.0.6", + "@twurple/api-call": "6.0.9", + "@twurple/common": "6.0.9", "retry": "^0.13.1", "tslib": "^2.0.3" }, @@ -1638,7 +1638,7 @@ "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "@twurple/auth": "6.0.6" + "@twurple/auth": "6.0.9" } }, "node_modules/@twurple/api-call": { @@ -1658,14 +1658,14 @@ } }, "node_modules/@twurple/api/node_modules/@twurple/api-call": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-6.0.6.tgz", - "integrity": "sha512-hoJU8GogOWnCx6DgH5/rcStkxLC/SO4y4MUO5XHNfqc5dmNIenF/g9AKBUVWYKYyNd5PM6qnEaf0VXQU0d9U8w==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-6.0.9.tgz", + "integrity": "sha512-9X6usnG7JudEkwCar/OqTn5GgqFYR9sSzuWfe87fKjYv8W6oac2Rze4TBgNQ8pQrHHNDjRTEW2Ra8xhGBjoYGQ==", "dependencies": { "@d-fischer/cross-fetch": "^4.0.2", "@d-fischer/qs": "^7.0.2", "@d-fischer/shared-utils": "^3.4.0", - "@twurple/common": "6.0.6", + "@twurple/common": "6.0.9", "@types/node-fetch": "^2.5.7", "tslib": "^2.0.3" }, @@ -1674,9 +1674,9 @@ } }, "node_modules/@twurple/api/node_modules/@twurple/common": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.6.tgz", - "integrity": "sha512-spgbeP98C++n0BZAx7MdzdQ+4xg09gh957vOsk24liWTWpwp9AImPRN+oo7Mr0I0B0VezAUjEOXQ1otiv59wsA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.9.tgz", + "integrity": "sha512-PKMXI5krHCaZT274vi4cBZn7t4Nq2zJc8illBr/inHwuuG6GYhFzRs5hBLdl5GL+F3hSyDpiD5MNLGLsfKPkWg==", "dependencies": { "@d-fischer/shared-utils": "^3.4.0", "klona": "^2.0.4", @@ -1695,14 +1695,14 @@ } }, "node_modules/@twurple/auth": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/auth/-/auth-6.0.6.tgz", - "integrity": "sha512-nbo9tm+Ee4mYHMn4Ek3XZB+q0pDL7ewXcLCVjVJoJ48Wi5sk9fGzMqyP/EMkmhfEQJHiRLB9dAVP8ggG4Iwq4w==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/auth/-/auth-6.0.9.tgz", + "integrity": "sha512-t9vFmiVD0QKsWjAYAQ0NnpeGFtxiTi3R+dIBASwYbadSbnNTIQtocW3csD2b+pUb4dzsRMNwhz8eWiUY/Lc03w==", "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.4.0", - "@twurple/api-call": "6.0.6", - "@twurple/common": "6.0.6", + "@twurple/api-call": "6.0.9", + "@twurple/common": "6.0.9", "tslib": "^2.0.3" }, "funding": { @@ -1710,14 +1710,14 @@ } }, "node_modules/@twurple/auth/node_modules/@twurple/api-call": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-6.0.6.tgz", - "integrity": "sha512-hoJU8GogOWnCx6DgH5/rcStkxLC/SO4y4MUO5XHNfqc5dmNIenF/g9AKBUVWYKYyNd5PM6qnEaf0VXQU0d9U8w==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-6.0.9.tgz", + "integrity": "sha512-9X6usnG7JudEkwCar/OqTn5GgqFYR9sSzuWfe87fKjYv8W6oac2Rze4TBgNQ8pQrHHNDjRTEW2Ra8xhGBjoYGQ==", "dependencies": { "@d-fischer/cross-fetch": "^4.0.2", "@d-fischer/qs": "^7.0.2", "@d-fischer/shared-utils": "^3.4.0", - "@twurple/common": "6.0.6", + "@twurple/common": "6.0.9", "@types/node-fetch": "^2.5.7", "tslib": "^2.0.3" }, @@ -1726,9 +1726,9 @@ } }, "node_modules/@twurple/auth/node_modules/@twurple/common": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.6.tgz", - "integrity": "sha512-spgbeP98C++n0BZAx7MdzdQ+4xg09gh957vOsk24liWTWpwp9AImPRN+oo7Mr0I0B0VezAUjEOXQ1otiv59wsA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.9.tgz", + "integrity": "sha512-PKMXI5krHCaZT274vi4cBZn7t4Nq2zJc8illBr/inHwuuG6GYhFzRs5hBLdl5GL+F3hSyDpiD5MNLGLsfKPkWg==", "dependencies": { "@d-fischer/shared-utils": "^3.4.0", "klona": "^2.0.4", @@ -1739,9 +1739,9 @@ } }, "node_modules/@twurple/chat": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/chat/-/chat-6.0.6.tgz", - "integrity": "sha512-ikLJqjnC1L19jB31KAIAsXAUCiP5pPL62VosFRjD9Q9P28jzcktnhBkyQ3+TF3Ps3lUafsfwfgM6gZQvjuiKdw==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/chat/-/chat-6.0.9.tgz", + "integrity": "sha512-sRaO4p3UYgzEorsOYPPHsM+FebzEEg2dIum7bFIbg8OsqyESqMcTcsfeLMgxJjnncDmT4uZ3cdpVGntsqfQPsw==", "dependencies": { "@d-fischer/cache-decorators": "^3.0.0", "@d-fischer/deprecate": "^2.0.2", @@ -1749,7 +1749,7 @@ "@d-fischer/rate-limiter": "^0.7.2", "@d-fischer/shared-utils": "^3.4.0", "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/common": "6.0.6", + "@twurple/common": "6.0.9", "ircv3": "^0.31.6", "tslib": "^2.0.3" }, @@ -1757,13 +1757,13 @@ "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "@twurple/auth": "6.0.6" + "@twurple/auth": "6.0.9" } }, "node_modules/@twurple/chat/node_modules/@twurple/common": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.6.tgz", - "integrity": "sha512-spgbeP98C++n0BZAx7MdzdQ+4xg09gh957vOsk24liWTWpwp9AImPRN+oo7Mr0I0B0VezAUjEOXQ1otiv59wsA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.9.tgz", + "integrity": "sha512-PKMXI5krHCaZT274vi4cBZn7t4Nq2zJc8illBr/inHwuuG6GYhFzRs5hBLdl5GL+F3hSyDpiD5MNLGLsfKPkWg==", "dependencies": { "@d-fischer/shared-utils": "^3.4.0", "klona": "^2.0.4", @@ -1787,28 +1787,28 @@ } }, "node_modules/@twurple/pubsub": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/pubsub/-/pubsub-6.0.6.tgz", - "integrity": "sha512-TCZBCIbDtwpx+3PI9qDpIt82jKmP7d8Pkq0W3kY+07meNkl1Bbv4hE0uj2TvyWihahXF7jqy2kKEPAIFk4/17A==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/pubsub/-/pubsub-6.0.9.tgz", + "integrity": "sha512-ZHGuLdpV47s3fiy7DAl4x10zbm8ml6aG+W/V3Q/50z/XndKbxw3Khz7bW7CRBEGOb7Rqsnrs9qmah3lHcsJmSQ==", "dependencies": { "@d-fischer/connection": "^8.0.2", "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.4.0", "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/common": "6.0.6", + "@twurple/common": "6.0.9", "tslib": "^2.0.3" }, "funding": { "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "@twurple/auth": "6.0.6" + "@twurple/auth": "6.0.9" } }, "node_modules/@twurple/pubsub/node_modules/@twurple/common": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.6.tgz", - "integrity": "sha512-spgbeP98C++n0BZAx7MdzdQ+4xg09gh957vOsk24liWTWpwp9AImPRN+oo7Mr0I0B0VezAUjEOXQ1otiv59wsA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.9.tgz", + "integrity": "sha512-PKMXI5krHCaZT274vi4cBZn7t4Nq2zJc8illBr/inHwuuG6GYhFzRs5hBLdl5GL+F3hSyDpiD5MNLGLsfKPkWg==", "dependencies": { "@d-fischer/shared-utils": "^3.4.0", "klona": "^2.0.4", @@ -14861,38 +14861,38 @@ "dev": true }, "@twurple/api": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/api/-/api-6.0.6.tgz", - "integrity": "sha512-Rf/KkWC/b7xY1nZ6386CXFAk2owl7IJRVGVR6aaog3nJSdr7LKhhHNn6Tgec9/TjpE4FzUct/styJl7fxr3HuA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/api/-/api-6.0.9.tgz", + "integrity": "sha512-ZWiMEG7hc6p0m0fxyR5iqYWy1wY2d30COQ4rvdh6FatYc9lb95nIy7BY9NCRdvH0l1GZaADzMhUu+NrVihBEIg==", "requires": { "@d-fischer/cache-decorators": "^3.0.0", "@d-fischer/detect-node": "^3.0.1", "@d-fischer/logger": "^4.2.1", "@d-fischer/rate-limiter": "^0.7.2", "@d-fischer/shared-utils": "^3.4.0", - "@twurple/api-call": "6.0.6", - "@twurple/common": "6.0.6", + "@twurple/api-call": "6.0.9", + "@twurple/common": "6.0.9", "retry": "^0.13.1", "tslib": "^2.0.3" }, "dependencies": { "@twurple/api-call": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-6.0.6.tgz", - "integrity": "sha512-hoJU8GogOWnCx6DgH5/rcStkxLC/SO4y4MUO5XHNfqc5dmNIenF/g9AKBUVWYKYyNd5PM6qnEaf0VXQU0d9U8w==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-6.0.9.tgz", + "integrity": "sha512-9X6usnG7JudEkwCar/OqTn5GgqFYR9sSzuWfe87fKjYv8W6oac2Rze4TBgNQ8pQrHHNDjRTEW2Ra8xhGBjoYGQ==", "requires": { "@d-fischer/cross-fetch": "^4.0.2", "@d-fischer/qs": "^7.0.2", "@d-fischer/shared-utils": "^3.4.0", - "@twurple/common": "6.0.6", + "@twurple/common": "6.0.9", "@types/node-fetch": "^2.5.7", "tslib": "^2.0.3" } }, "@twurple/common": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.6.tgz", - "integrity": "sha512-spgbeP98C++n0BZAx7MdzdQ+4xg09gh957vOsk24liWTWpwp9AImPRN+oo7Mr0I0B0VezAUjEOXQ1otiv59wsA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.9.tgz", + "integrity": "sha512-PKMXI5krHCaZT274vi4cBZn7t4Nq2zJc8illBr/inHwuuG6GYhFzRs5hBLdl5GL+F3hSyDpiD5MNLGLsfKPkWg==", "requires": { "@d-fischer/shared-utils": "^3.4.0", "klona": "^2.0.4", @@ -14920,34 +14920,34 @@ } }, "@twurple/auth": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/auth/-/auth-6.0.6.tgz", - "integrity": "sha512-nbo9tm+Ee4mYHMn4Ek3XZB+q0pDL7ewXcLCVjVJoJ48Wi5sk9fGzMqyP/EMkmhfEQJHiRLB9dAVP8ggG4Iwq4w==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/auth/-/auth-6.0.9.tgz", + "integrity": "sha512-t9vFmiVD0QKsWjAYAQ0NnpeGFtxiTi3R+dIBASwYbadSbnNTIQtocW3csD2b+pUb4dzsRMNwhz8eWiUY/Lc03w==", "requires": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.4.0", - "@twurple/api-call": "6.0.6", - "@twurple/common": "6.0.6", + "@twurple/api-call": "6.0.9", + "@twurple/common": "6.0.9", "tslib": "^2.0.3" }, "dependencies": { "@twurple/api-call": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-6.0.6.tgz", - "integrity": "sha512-hoJU8GogOWnCx6DgH5/rcStkxLC/SO4y4MUO5XHNfqc5dmNIenF/g9AKBUVWYKYyNd5PM6qnEaf0VXQU0d9U8w==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-6.0.9.tgz", + "integrity": "sha512-9X6usnG7JudEkwCar/OqTn5GgqFYR9sSzuWfe87fKjYv8W6oac2Rze4TBgNQ8pQrHHNDjRTEW2Ra8xhGBjoYGQ==", "requires": { "@d-fischer/cross-fetch": "^4.0.2", "@d-fischer/qs": "^7.0.2", "@d-fischer/shared-utils": "^3.4.0", - "@twurple/common": "6.0.6", + "@twurple/common": "6.0.9", "@types/node-fetch": "^2.5.7", "tslib": "^2.0.3" } }, "@twurple/common": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.6.tgz", - "integrity": "sha512-spgbeP98C++n0BZAx7MdzdQ+4xg09gh957vOsk24liWTWpwp9AImPRN+oo7Mr0I0B0VezAUjEOXQ1otiv59wsA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.9.tgz", + "integrity": "sha512-PKMXI5krHCaZT274vi4cBZn7t4Nq2zJc8illBr/inHwuuG6GYhFzRs5hBLdl5GL+F3hSyDpiD5MNLGLsfKPkWg==", "requires": { "@d-fischer/shared-utils": "^3.4.0", "klona": "^2.0.4", @@ -14957,9 +14957,9 @@ } }, "@twurple/chat": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/chat/-/chat-6.0.6.tgz", - "integrity": "sha512-ikLJqjnC1L19jB31KAIAsXAUCiP5pPL62VosFRjD9Q9P28jzcktnhBkyQ3+TF3Ps3lUafsfwfgM6gZQvjuiKdw==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/chat/-/chat-6.0.9.tgz", + "integrity": "sha512-sRaO4p3UYgzEorsOYPPHsM+FebzEEg2dIum7bFIbg8OsqyESqMcTcsfeLMgxJjnncDmT4uZ3cdpVGntsqfQPsw==", "requires": { "@d-fischer/cache-decorators": "^3.0.0", "@d-fischer/deprecate": "^2.0.2", @@ -14967,15 +14967,15 @@ "@d-fischer/rate-limiter": "^0.7.2", "@d-fischer/shared-utils": "^3.4.0", "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/common": "6.0.6", + "@twurple/common": "6.0.9", "ircv3": "^0.31.6", "tslib": "^2.0.3" }, "dependencies": { "@twurple/common": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.6.tgz", - "integrity": "sha512-spgbeP98C++n0BZAx7MdzdQ+4xg09gh957vOsk24liWTWpwp9AImPRN+oo7Mr0I0B0VezAUjEOXQ1otiv59wsA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.9.tgz", + "integrity": "sha512-PKMXI5krHCaZT274vi4cBZn7t4Nq2zJc8illBr/inHwuuG6GYhFzRs5hBLdl5GL+F3hSyDpiD5MNLGLsfKPkWg==", "requires": { "@d-fischer/shared-utils": "^3.4.0", "klona": "^2.0.4", @@ -14995,22 +14995,22 @@ } }, "@twurple/pubsub": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/pubsub/-/pubsub-6.0.6.tgz", - "integrity": "sha512-TCZBCIbDtwpx+3PI9qDpIt82jKmP7d8Pkq0W3kY+07meNkl1Bbv4hE0uj2TvyWihahXF7jqy2kKEPAIFk4/17A==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/pubsub/-/pubsub-6.0.9.tgz", + "integrity": "sha512-ZHGuLdpV47s3fiy7DAl4x10zbm8ml6aG+W/V3Q/50z/XndKbxw3Khz7bW7CRBEGOb7Rqsnrs9qmah3lHcsJmSQ==", "requires": { "@d-fischer/connection": "^8.0.2", "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.4.0", "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/common": "6.0.6", + "@twurple/common": "6.0.9", "tslib": "^2.0.3" }, "dependencies": { "@twurple/common": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.6.tgz", - "integrity": "sha512-spgbeP98C++n0BZAx7MdzdQ+4xg09gh957vOsk24liWTWpwp9AImPRN+oo7Mr0I0B0VezAUjEOXQ1otiv59wsA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-6.0.9.tgz", + "integrity": "sha512-PKMXI5krHCaZT274vi4cBZn7t4Nq2zJc8illBr/inHwuuG6GYhFzRs5hBLdl5GL+F3hSyDpiD5MNLGLsfKPkWg==", "requires": { "@d-fischer/shared-utils": "^3.4.0", "klona": "^2.0.4", diff --git a/package.json b/package.json index ad08923dd..df76042c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebotv5", - "version": "5.57.0", + "version": "5.58.0", "description": "Powerful all-in-one bot for Twitch streamers.", "main": "build/main.js", "scripts": { @@ -49,10 +49,10 @@ "dependencies": { "@aws-sdk/client-polly": "^3.26.0", "@crowbartools/firebot-custom-scripts-types": "^5.53.2-6", - "@twurple/api": "^6.0.6", - "@twurple/auth": "^6.0.6", - "@twurple/chat": "^6.0.6", - "@twurple/pubsub": "^6.0.6", + "@twurple/api": "^6.0.9", + "@twurple/auth": "^6.0.9", + "@twurple/chat": "^6.0.9", + "@twurple/pubsub": "^6.0.9", "@zunderscore/elgato-light-control": "^1.1.2", "angular": "^1.8.0", "angular-animate": "^1.7.8", diff --git a/src/backend/chat/commands/builtin/steam/steam-access.js b/src/backend/chat/commands/builtin/steam/steam-access.js index 1dbc00934..9c2d06a10 100644 --- a/src/backend/chat/commands/builtin/steam/steam-access.js +++ b/src/backend/chat/commands/builtin/steam/steam-access.js @@ -108,6 +108,10 @@ const getSteamGameDetails = async (requestedGame) => { gameDetails.releaseDate = foundGame.release_date.date; } + if (foundGame.short_description) { + gameDetails.shortDescription = foundGame.short_description; + } + return gameDetails; }; diff --git a/src/backend/chat/commands/builtin/steam/steam.js b/src/backend/chat/commands/builtin/steam/steam.js index f4fc324a0..c7eaa57c0 100644 --- a/src/backend/chat/commands/builtin/steam/steam.js +++ b/src/backend/chat/commands/builtin/steam/steam.js @@ -22,7 +22,7 @@ const steam = { outputTemplate: { type: "string", title: "Output Template", - tip: "Variables: {gameName}, {price}, {releaseDate}, {metaCriticScore}, {steamUrl}", + tip: "Variables: {gameName}, {price}, {releaseDate}, {metaCriticScore}, {steamUrl}, {steamShortDescription}", default: `{gameName} (Price: {price} - Released: {releaseDate} - Metacritic: {metaCriticScore}) {steamUrl}`, useTextArea: true } @@ -49,7 +49,8 @@ const steam = { .replace("{price}", gameDetails.price || "Unknown") .replace("{releaseDate}", gameDetails.releaseDate || "Unknown") .replace("{metaCriticScore}", gameDetails.score || "Unknown") - .replace("{steamUrl}", gameDetails.url); + .replace("{steamUrl}", gameDetails.url) + .replace("{steamShortDescription}", gameDetails.shortDescription || "Unknown"); } } diff --git a/src/backend/common/handlers/custom-scripts/custom-script-helpers.js b/src/backend/common/handlers/custom-scripts/custom-script-helpers.js index d140d0a56..2f73c8f6f 100644 --- a/src/backend/common/handlers/custom-scripts/custom-script-helpers.js +++ b/src/backend/common/handlers/custom-scripts/custom-script-helpers.js @@ -97,6 +97,7 @@ function buildModules(scriptManifest) { twitchApi: twitchApi, httpServer: require("../../../../server/http-server-manager"), effectManager: require("../../../effects/effectManager"), + effectRunner: require("../../effect-runner"), conditionManager: require("../../../effects/builtin/conditional-effects/conditions/condition-manager"), restrictionManager: require("../../../restrictions/restriction-manager"), commandManager: require("../../../chat/commands/CommandManager"), @@ -115,7 +116,8 @@ function buildModules(scriptManifest) { quotesManager: require("../../../quotes/quotes-manager"), frontendCommunicator: require("../../frontend-communicator"), counterManager: require("../../../counters/counter-manager"), - utils: require("../../../utility") + utils: require("../../../utility"), + resourceTokenManager: require("../../../resourceTokenManager") }; } diff --git a/src/backend/common/handlers/fileWriterProcessor.js b/src/backend/common/handlers/fileWriterProcessor.js index dad4d7ac4..bdb624a83 100644 --- a/src/backend/common/handlers/fileWriterProcessor.js +++ b/src/backend/common/handlers/fileWriterProcessor.js @@ -1,6 +1,7 @@ "use strict"; const fs = require("fs-extra"); +const path = require("path"); const logger = require("../../logwrapper"); function doesTextExistInFile(filepath, text) { @@ -75,6 +76,7 @@ exports.run = async effect => { fs.appendFileSync(effect.filepath, text + "\n", "utf8"); } } else { + fs.ensureDirSync(path.dirname(effect.filepath)); fs.appendFileSync(effect.filepath, text + "\n", "utf8"); } } else if (effect.writeMode === "delete") { diff --git a/src/backend/common/user-access.js b/src/backend/common/user-access.js index 2c7166e0d..c83251ee2 100644 --- a/src/backend/common/user-access.js +++ b/src/backend/common/user-access.js @@ -24,7 +24,7 @@ async function userFollowsChannels(username, channelNames) { if (cachedFollow !== undefined) { userFollowsChannel = cachedFollow; } else { - userFollowsChannel = await twitchApi.users.doesUserFollowChannelLegacy(username, channelName); + userFollowsChannel = await twitchApi.users.doesUserFollowChannel(username, channelName); // set cache value followCache.set(`${username}:${channelName}`, userFollowsChannel); diff --git a/src/backend/effects/builtin/control-emulation.js b/src/backend/effects/builtin/control-emulation.js index b68a499a7..d8a3e0ffe 100644 --- a/src/backend/effects/builtin/control-emulation.js +++ b/src/backend/effects/builtin/control-emulation.js @@ -125,6 +125,18 @@ const effect = { "f10", "f11", "f12", + "f13", + "f14", + "f15", + "f16", + "f17", + "f18", + "f19", + "f20", + "f21", + "f22", + "f23", + "f24", "alt", "control", "shift", diff --git a/src/backend/effects/builtin/cooldown-command.js b/src/backend/effects/builtin/cooldown-command.js index 4113edf4e..7db985cc0 100644 --- a/src/backend/effects/builtin/cooldown-command.js +++ b/src/backend/effects/builtin/cooldown-command.js @@ -209,7 +209,7 @@ const model = { commandId: id, subcommandId: effect.subcommandId, username: effect.username, - cooldowns: { + cooldown: { global: !isNaN(effect.globalCooldownSecs) ? parseInt(effect.globalCooldownSecs) : undefined, user: !isNaN(effect.userCooldownSecs) && effect.username != null && effect.username !== '' ? parseInt(effect.userCooldownSecs) : undefined } @@ -219,7 +219,7 @@ const model = { commandId: id, subcommandId: effect.subcommandId, username: effect.clearUsername, - cooldowns: { + cooldown: { global: effect.clearGlobalCooldown, user: effect.clearUserCooldown } diff --git a/src/backend/effects/builtin/loop-effects.js b/src/backend/effects/builtin/loop-effects.js index 18e6174f0..93aef593e 100644 --- a/src/backend/effects/builtin/loop-effects.js +++ b/src/backend/effects/builtin/loop-effects.js @@ -81,9 +81,9 @@ const model = { $scope.loopModeChanged = () => { if ($scope.effect.loopMode === "count") { - $scope.effect.loopCount = 5; + $scope.effect.loopCount = "5"; } else if ($scope.effect.loopMode === "conditional") { - $scope.effect.loopCount = 25; + $scope.effect.loopCount = "25"; } }; diff --git a/src/backend/effects/builtin/pause-resume-effect-queue.ts b/src/backend/effects/builtin/pause-resume-effect-queue.ts index 4f6254ecb..284cdc724 100644 --- a/src/backend/effects/builtin/pause-resume-effect-queue.ts +++ b/src/backend/effects/builtin/pause-resume-effect-queue.ts @@ -88,11 +88,11 @@ const model: EffectType<{ return false; } else { if (effect.action === "Pause") { - effectQueueRunner.pauseQueue(effect.effectQueue); + effectQueueManager.pauseQueue(effect.effectQueue); } else if (effect.action === "Resume") { - effectQueueRunner.resumeQueue(effect.effectQueue); + effectQueueManager.resumeQueue(effect.effectQueue); } else { - effectQueueRunner.toggleQueue(effect.effectQueue); + effectQueueManager.toggleQueue(effect.effectQueue); } } diff --git a/src/backend/effects/builtin/play-video.js b/src/backend/effects/builtin/play-video.js index e41ac2bed..24506db9a 100644 --- a/src/backend/effects/builtin/play-video.js +++ b/src/backend/effects/builtin/play-video.js @@ -10,6 +10,9 @@ const accountAccess = require("../../common/account-access"); const util = require("../../utility"); const fs = require('fs-extra'); const path = require("path"); +const frontendCommunicator = require('../../common/frontend-communicator'); +const { wait } = require("../../utility"); +const { parseYoutubeId } = require("../../../shared/youtube-url-parser"); /** * The Play Video effect @@ -366,7 +369,7 @@ const playVideo = { /**@type {import('@twurple/api').HelixClip} */ let clip; if (effect.videoType === "Twitch Clip") { - clipId = effect.twitchClipUrl.replace("https://clips.twitch.tv/", ""); + clipId = effect.twitchClipUrl.split("/").pop(); try { clip = await client.clips.getClipById(clipId); } catch (error) { @@ -439,54 +442,40 @@ const playVideo = { } let resourceToken; - if (effect.videoType === "YouTube Video") { - resourceToken = resourceTokenManager.storeResourcePath(data.filepath, effect.length); - } else { - const durationToken = resourceTokenManager.storeResourcePath(data.filepath, 5); - - const durationPromise = new Promise(async (resolve, reject) => { - const listener = (event) => { - try { - if (event.name === "video-duration" && event.data.resourceToken === durationToken) { - webServer.removeListener("overlay-event", listener); - resolve(event.data.duration); - } - } catch (err) { - logger.error("Error while trying to process overlay-event for getVideoDuration: ", err); - reject(err); - } - }; - webServer.on("overlay-event", listener); - }); - - webServer.sendToOverlay("getVideoDuration", { - resourceToken: durationToken, - overlayInstance: data.overlayInstance - }); - - const duration = await durationPromise; - resourceToken = resourceTokenManager.storeResourcePath(data.filepath, duration + 5); - logger.info(`Retrieved duration for video: ${duration}`); + let duration; + if (effect.videoType === "YouTube Video" && !effect.wait) { + logger.debug("Play Video Effect: Proceeding without Youtube Video Duration because wait is false"); + } else if (effect.videoType === "YouTube Video" && effect.wait) { + const youtubeData = parseYoutubeId(data.youtubeId); + data.youtubeId = youtubeData.id; + const result = await frontendCommunicator.fireEventAsync("getYoutubeVideoDuration", data.youtubeId); + if (!isNaN(result)) { + duration = result; + } else { + // Error + logger.error("Play Video Effect: Unable to retrieve Youtube Video Duration", result); + return; + } + if (data.videoStarttime == null || data.videoStarttime == "" || data.videoStarttime == 0) { + data.videoStarttime = youtubeData.startTime; + } + } else if (effect.videoType === "Local Video") { + const result = await frontendCommunicator.fireEventAsync("getVideoDuration", data.filepath); + if (!isNaN(result)) { + duration = result; + } + resourceToken = resourceTokenManager.storeResourcePath(data.filepath, duration); } data.resourceToken = resourceToken; webServer.sendToOverlay("video", data); if (effect.wait) { - await new Promise(async (resolve, reject) => { - const listener = (event) => { - try { - if (event.name === "video-end" && event.data.resourceToken === resourceToken) { - webServer.removeListener("overlay-event", listener); - resolve(); - } - } catch (err) { - logger.error("Error while trying to process overlay-event for play-video: ", err); - reject(err); - } - }; - webServer.on("overlay-event", listener); - }); + let internalDuration = data.videoDuration; + if (internalDuration == null || internalDuration === 0 || internalDuration === "") { + internalDuration = duration; + } + await wait(internalDuration * 1000); } return true; }, @@ -544,7 +533,7 @@ const playVideo = { const data = event; const videoType = data.videoType; - const filepath = data.filepath; + const filepath = data.filepath ?? ""; let fileExt = filepath.split(".").pop(); if (fileExt === "ogv") { fileExt = "ogg"; @@ -637,9 +626,6 @@ const playVideo = { const exitVideo = () => { delete startedVidCache[this.id]; // eslint-disable-line no-undef animateVideoExit(`#${wrapperId}`, exitAnimation, exitDuration, inbetweenAnimation); - setTimeout(function(){ - sendWebsocketEvent("video-end", {resourceToken: token}); // eslint-disable-line no-undef - }, millisecondsFromString(exitDuration)); }; // Remove div after X time. @@ -669,29 +655,6 @@ const playVideo = { $(".wrapper").append(wrappedHtml); - try { - const url = new URL(youtubeId); - if (url.hostname.includes("www.youtube.com")) { - for (const [key, value] of url.searchParams) { - if (key === "v") { - youtubeId = value; - } else if (key === "t") { - videoStarttime = value; - } - } - } - if (url.hostname.includes("youtu.be")) { - youtubeId = url.pathname.replace("/", ""); - for (const [key, value] of url.searchParams) { - if (key === "t") { - videoStarttime = value; - } - } - } - } catch (error) { - //failed to convert url - } - // Add iframe. const playerVars = { @@ -737,9 +700,6 @@ const playVideo = { onStateChange: (event) => { if (event.data === 0 && !videoDuration) { animateVideoExit(`#${wrapperId}`, exitAnimation, exitDuration, inbetweenAnimation); - setTimeout(function(){ - sendWebsocketEvent("video-end", {resourceToken: token}); // eslint-disable-line no-undef - }, millisecondsFromString(exitDuration)); } } } @@ -757,9 +717,6 @@ const playVideo = { if (videoDuration) { setTimeout(function () { animateVideoExit(`#${wrapperId}`, exitAnimation, exitDuration, inbetweenAnimation); - setTimeout(function(){ - sendWebsocketEvent("video-end", {resourceToken: token}); // eslint-disable-line no-undef - }, millisecondsFromString(exitDuration)); }, videoDuration); } } diff --git a/src/backend/effects/builtin/stream-game.js b/src/backend/effects/builtin/stream-game.js index c68a6e8f5..2c1bfe589 100644 --- a/src/backend/effects/builtin/stream-game.js +++ b/src/backend/effects/builtin/stream-game.js @@ -3,6 +3,7 @@ const { EffectCategory } = require('../../../shared/effect-constants'); const logger = require('../../logwrapper'); const twitchApi = require("../../twitch-api/api"); +const eventsManager = require("../../events/EventManager"); const model = { definition: { @@ -104,6 +105,9 @@ const model = { await twitchApi.channels.updateChannelInformation({ gameId: category.id }); } } + + const category = (await twitchApi.channels.getChannelInformation()).gameName; + eventsManager.triggerEvent("firebot", "category-changed", {category: category}); return true; } }; diff --git a/src/backend/effects/builtin/take-screenshot.js b/src/backend/effects/builtin/take-screenshot.js index d38b20a28..03c92a24d 100644 --- a/src/backend/effects/builtin/take-screenshot.js +++ b/src/backend/effects/builtin/take-screenshot.js @@ -91,7 +91,7 @@ const clip = { - +
Note: Screenshots will capture the entirety of the selected display.
@@ -204,7 +204,7 @@ const clip = { description: "Screenshot by Firebot" }]; const screenshotEmbed = await discordEmbedBuilder.buildScreenshotEmbed(`attachment://${filename}`); - discord.sendDiscordMessage(effect.discordChannelId, "A new screenshot was taken!", screenshotEmbed, files); + await discord.sendDiscordMessage(effect.discordChannelId, "A new screenshot was taken!", screenshotEmbed, files); } if (effect.showInOverlay) { diff --git a/src/backend/effects/builtin/twitch-shoutout.ts b/src/backend/effects/builtin/twitch-shoutout.ts index 171fdebe6..02ec8ff3d 100644 --- a/src/backend/effects/builtin/twitch-shoutout.ts +++ b/src/backend/effects/builtin/twitch-shoutout.ts @@ -4,19 +4,19 @@ import twitchApi from "../../twitch-api/api"; import logger from "../../logwrapper"; const model: EffectType<{ - username: string; -}> = { - definition: { - id: "firebot:twitch-shoutout", - name: "Twitch Shoutout", - description: "Send a Twitch shoutout to another channel", - icon: "fad fa-bullhorn", - categories: [ EffectCategory.COMMON, EffectCategory.TWITCH ], - dependencies: [] - }, - optionsTemplate: ` + username: string; +}> = { + definition: { + id: "firebot:twitch-shoutout", + name: "Twitch Shoutout", + description: "Send a Twitch shoutout to another channel", + icon: "fad fa-bullhorn", + categories: [EffectCategory.COMMON, EffectCategory.TWITCH], + dependencies: [], + }, + optionsTemplate: ` - + @@ -25,27 +25,30 @@ const model: EffectType<{ `, - optionsValidator: (effect) => { - const errors: string[] = []; - const username = effect.username?.trim(); + optionsValidator: (effect) => { + const errors: string[] = []; + const username = effect.username?.trim(); - if (!username?.length) { - errors.push("You must specify a channel to shoutout"); - } - - return errors; - }, - optionsController: () => { }, - onTriggerEvent: async ({ effect }) => { - const targetUserId = (await twitchApi.users.getUserByName(effect.username))?.id; + if (!username?.length) { + errors.push("You must specify a channel to shoutout"); + } - if (targetUserId == null) { - logger.error(`Unable to shoutout channel. Twitch user ${effect.username} does not exist.`); - return false; - } + return errors; + }, + optionsController: () => {}, + onTriggerEvent: async ({ effect }) => { + const targetUserId = (await twitchApi.users.getUserByName(effect.username)) + ?.id; - return await twitchApi.chat.sendShoutout(targetUserId); + if (targetUserId == null) { + logger.error( + `Unable to shoutout channel. Twitch user ${effect.username} does not exist.` + ); + return false; } -} + + return await twitchApi.chat.sendShoutout(targetUserId); + }, +}; module.exports = model; \ No newline at end of file diff --git a/src/backend/effects/queues/effect-queue-manager.js b/src/backend/effects/queues/effect-queue-manager.js index 34effee40..f55908909 100644 --- a/src/backend/effects/queues/effect-queue-manager.js +++ b/src/backend/effects/queues/effect-queue-manager.js @@ -11,6 +11,9 @@ const effectQueueRunner = require("./effect-queue-runner"); * @prop {string} mode - the mode of the effect queue * @prop {number} [interval] - the interval set for the interval mode * @prop {string[]} sortTags - the tags for the effect queue + * @prop {boolean} active - the effect queue activity status + * @prop {number} length - amount of items currently in queue. don't save + * @prop {any[]} queue - effects queue. don't save */ /** @@ -23,12 +26,35 @@ class EffectQueueManager extends JsonDbManager { super("Effect Queue", "/effects/effectqueues"); } + /** + * @override + * @returns {void} + */ + loadItems() { + super.loadItems(); + const queues = this.getAllItems(); + + let save = false; + + for (const queue of queues) { + if (queue.active == null) { + queue.active = true; + save = true; + } + } + if (save) { + this.saveAllItems(queues); + } + } + /** * @override * @param {EffectQueue} effectQueue * @returns {EffectQueue | null} * */ saveItem(effectQueue) { + delete effectQueue.length; + delete effectQueue.queue; const savedEffectQueue = super.saveItem(effectQueue); if (savedEffectQueue) { @@ -39,12 +65,80 @@ class EffectQueueManager extends JsonDbManager { return null; } + /** + * @override + * @param allItems - array of all effect queues + */ + saveAllItems(allItems) { + for (const item of allItems) { + delete item.length; + delete item.queue; + } + super.saveAllItems(allItems); + } + + /** + * @override + */ + getAllItems() { + const items = JSON.parse(JSON.stringify(super.getAllItems())); + for (const item of items) { + item.length = effectQueueRunner.getQueue(item.id).length; + } + return items; + } + + /** + * @override + * @param itemId + * @returns {T|null} + */ + getItem(itemId) { + if (itemId == null) { + return null; + } + + const item = JSON.parse(JSON.stringify(super.getItem(itemId))); + + item.queue = effectQueueRunner.getQueue(itemId); + + return item; + } + /** * @returns {void} */ triggerUiRefresh() { frontendCommunicator.send("all-queues", this.getAllItems()); } + + /** @private */ + setQueueActivity(queue, status) { + queue.active = status; + this.saveItem(queue); + frontendCommunicator.send("updateQueueStatus", {id: queue.id, active: status}); + } + + pauseQueue(queueId) { + const queue = this.getItem(queueId); + if (queue != null && queue.active) { + this.setQueueActivity(queue, false); + } + } + + resumeQueue(queueId) { + const queue = this.getItem(queueId); + if (queue != null && !queue.active) { + this.setQueueActivity(queue, true); + } + } + + toggleQueue(queueId) { + const queue = this.getItem(queueId); + if (queue != null) { + this.setQueueActivity(queue, !queue.active); + } + } } const effectQueueManager = new EffectQueueManager(); @@ -61,4 +155,10 @@ frontendCommunicator.onAsync("saveAllEffectQueues", frontendCommunicator.on("deleteEffectQueue", (/** @type {string} */ effectQueueId) => effectQueueManager.deleteItem(effectQueueId)); +frontendCommunicator.on("clearEffectQueue", + (/** @type {string} */ effectQueueId) => effectQueueRunner.removeQueue(effectQueueId)); + +frontendCommunicator.on("toggleEffectQueue", + (/** @type {string} */ effectQueueId) => effectQueueManager.toggleQueue(effectQueueId)); + module.exports = effectQueueManager; diff --git a/src/backend/effects/queues/effect-queue-runner.js b/src/backend/effects/queues/effect-queue-runner.js index 4f08d3881..a146e578a 100644 --- a/src/backend/effects/queues/effect-queue-runner.js +++ b/src/backend/effects/queues/effect-queue-runner.js @@ -1,6 +1,7 @@ "use strict"; const logger = require("../../logwrapper"); const effectRunner = require("../../common/effect-runner"); +const frontendCommunicator = require("../../common/frontend-communicator"); /** * Queue Entry @@ -14,7 +15,7 @@ const effectRunner = require("../../common/effect-runner"); * Effect queue class */ class EffectQueue { - constructor(id, mode, interval = 0) { + constructor(id, mode, interval = 0, active = true) { this.id = id; this.mode = mode; this.interval = interval; @@ -24,10 +25,17 @@ class EffectQueue { */ this._queue = []; this._running = false; - this._paused = false; + this._paused = !active; this.canceled = false; } + sendQueueLengthUpdate(lengthOverride = null) { + frontendCommunicator.send("updateQueueLength", { + id: this.id, + length: lengthOverride ?? this._queue.length + }); + } + runQueue() { return new Promise(resolve => { if (this._queue.length === 0 || this.canceled || this._paused === true) { @@ -42,6 +50,8 @@ class EffectQueue { logger.debug(`Running next effects for queue ${this.id}. Mode=${this.mode}, Interval?=${this.interval}, Remaining queue length=${this._queue.length}`); + this.sendQueueLengthUpdate(); + if (this.mode === "interval") { effectRunner.runEffects(runEffectsContext) .catch(err => { @@ -94,6 +104,8 @@ class EffectQueue { logger.debug(`Added more effects to queue ${this.id}. Current length=${this._queue.length}`); + this.sendQueueLengthUpdate(); + this.processEffectQueue(); } @@ -122,14 +134,6 @@ class EffectQueue { this._paused = false; this.processEffectQueue(); } - - toggleQueue() { - if (this._paused) { - this.resumeQueue(); - } else { - this.pauseQueue(); - } - } } /** @@ -144,7 +148,7 @@ function addEffectsToQueue(queueConfig, runEffectsContext, duration, priority) { let queue = queues[queueConfig.id]; if (queue == null) { logger.debug(`Creating queue ${queueConfig.id}...`); - queue = new EffectQueue(queueConfig.id, queueConfig.mode, queueConfig.interval); + queue = new EffectQueue(queueConfig.id, queueConfig.mode, queueConfig.interval, queueConfig.active); queues[queueConfig.id] = queue; } @@ -159,27 +163,11 @@ function updateQueueConfig(queueConfig) { if (queue != null) { queue.mode = queueConfig.mode; queue.interval = queueConfig.interval; - } -} - -function pauseQueue(queueId) { - const queue = queues[queueId]; - if (queue != null) { - queue.pauseQueue(); - } -} - -function resumeQueue(queueId) { - const queue = queues[queueId]; - if (queue != null) { - queue.resumeQueue(); - } -} - -function toggleQueue(queueId) { - const queue = queues[queueId]; - if (queue != null) { - queue.toggleQueue(); + if (queueConfig.active) { + queue.resumeQueue(); + } else { + queue.pauseQueue(); + } } } @@ -192,18 +180,25 @@ function removeQueue(queueId) { if (queue != null) { logger.debug(`Removing queue ${queue.id}`); queue.canceled = true; + queue.sendQueueLengthUpdate(0); delete queues[queueId]; } } +function getQueue(queueId) { + const queue = queues[queueId]; + if (queue == null) { + return []; + } + return queue._queue; +} + function clearAllQueues() { Object.keys(queues).forEach(queueId => removeQueue(queueId)); } exports.addEffectsToQueue = addEffectsToQueue; +exports.getQueue = getQueue; exports.updateQueueConfig = updateQueueConfig; -exports.pauseQueue = pauseQueue; -exports.resumeQueue = resumeQueue; -exports.toggleQueue = toggleQueue; exports.removeQueue = removeQueue; exports.clearAllQueues = clearAllQueues; diff --git a/src/backend/games/builtin/bid/bid-command.js b/src/backend/games/builtin/bid/bid-command.js index f09e893ab..ee669a372 100644 --- a/src/backend/games/builtin/bid/bid-command.js +++ b/src/backend/games/builtin/bid/bid-command.js @@ -99,7 +99,7 @@ const bidCommand = { ] }, onTriggerEvent: async event => { - const { chatEvent, userCommand } = event; + const { chatMessage, userCommand } = event; const bidSettings = gameManager.getGameSettings("firebot-bid"); const chatter = bidSettings.settings.chatSettings.chatter; @@ -113,17 +113,17 @@ const bidCommand = { const bidAmount = parseInt(triggeredArg); if (isNaN(bidAmount)) { - await twitchChat.sendChatMessage(`Invalid amount. Please enter a number to start bidding.`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`Invalid amount. Please enter a number to start bidding.`, null, chatter, chatMessage.id); return; } if (activeBiddingInfo.active !== false) { - await twitchChat.sendChatMessage(`There is already a bid running. Use !bid stop to stop it.`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`There is already a bid running. Use !bid stop to stop it.`, null, chatter, chatMessage.id); return; } if (bidAmount < bidSettings.settings.currencySettings.minBid) { - await twitchChat.sendChatMessage(`The opening bid must be more than ${bidSettings.settings.currencySettings.minBid}.`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`The opening bid must be more than ${bidSettings.settings.currencySettings.minBid}.`, null, chatter, chatMessage.id); return; } @@ -151,45 +151,45 @@ const bidCommand = { const username = userCommand.commandSender; if (activeBiddingInfo.active === false) { - await twitchChat.sendChatMessage(`There is no active bidding in progress.`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`There is no active bidding in progress.`, null, chatter, chatMessage.id); return; } const cooldownExpireTime = cooldownCache.get(username); if (cooldownExpireTime && moment().isBefore(cooldownExpireTime)) { const timeRemainingDisplay = util.secondsForHumans(Math.abs(moment().diff(cooldownExpireTime, 'seconds'))); - await twitchChat.sendChatMessage(`You placed a bid recently! Please wait ${timeRemainingDisplay} before placing another bid.`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`You placed a bid recently! Please wait ${timeRemainingDisplay} before placing another bid.`, null, chatter, chatMessage.id); return; } if (activeBiddingInfo.topBidder === username) { - await twitchChat.sendChatMessage("You are already the top bidder. You can't bid against yourself.", null, chatter, chatEvent.id); + await twitchChat.sendChatMessage("You are already the top bidder. You can't bid against yourself.", null, chatter, chatMessage.id); return; } if (bidAmount < 1) { - await twitchChat.sendChatMessage("Bid amount must be more than 0.", null, chatter, chatEvent.id); + await twitchChat.sendChatMessage("Bid amount must be more than 0.", null, chatter, chatMessage.id); return; } const minBid = bidSettings.settings.currencySettings.minBid; if (minBid != null & minBid > 0) { if (bidAmount < minBid) { - await twitchChat.sendChatMessage(`Bid amount must be at least ${minBid} ${currencyName}.`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`Bid amount must be at least ${minBid} ${currencyName}.`, null, chatter, chatMessage.id); return; } } const userBalance = await currencyDatabase.getUserCurrencyAmount(username, currencyId); if (userBalance < bidAmount) { - await twitchChat.sendChatMessage(`You don't have enough ${currencyName}!`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`You don't have enough ${currencyName}!`, null, chatter, chatMessage.id); return; } const raiseMinimum = bidSettings.settings.currencySettings.minIncrement; const minimumBidWithRaise = activeBiddingInfo.currentBid + raiseMinimum; if (bidAmount < minimumBidWithRaise) { - await twitchChat.sendChatMessage(`You must bid at least ${minimumBidWithRaise} ${currencyName}.`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`You must bid at least ${minimumBidWithRaise} ${currencyName}.`, null, chatter, chatMessage.id); return; } @@ -197,7 +197,7 @@ const bidCommand = { const previousHighBidAmount = activeBiddingInfo.currentBid; if (previousHighBidder != null && previousHighBidder !== "") { await currencyDatabase.adjustCurrencyForUser(previousHighBidder, currencyId, previousHighBidAmount); - await twitchChat.sendChatMessage(`You have been out bid! You've been refunded ${previousHighBidAmount} ${currencyName}.`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`You have been out bid! You've been refunded ${previousHighBidAmount} ${currencyName}.`, null, chatter, chatMessage.id); } await currencyDatabase.adjustCurrencyForUser(username, currencyId, -Math.abs(bidAmount)); @@ -213,7 +213,7 @@ const bidCommand = { cooldownCache.set(username, expireTime, cooldownSecs); } } else { - await twitchChat.sendChatMessage(`Incorrect bid usage: ${userCommand.trigger} [bidAmount]`, null, chatter, chatEvent.id); + await twitchChat.sendChatMessage(`Incorrect bid usage: ${userCommand.trigger} [bidAmount]`, null, chatter, chatMessage.id); } } }; diff --git a/src/backend/games/builtin/heist/heist-command.js b/src/backend/games/builtin/heist/heist-command.js index ed872c850..c11d44008 100644 --- a/src/backend/games/builtin/heist/heist-command.js +++ b/src/backend/games/builtin/heist/heist-command.js @@ -133,7 +133,7 @@ const heistCommand = { if (heistSettings.settings.entryMessages.wagerAmountTooHigh) { const wagerAmountTooHighMsg = heistSettings.settings.entryMessages.wagerAmountTooHigh .replace("{user}", username) - .replace("minWager}", minWager); + .replace("{maxWager}", maxWager); await twitchChat.sendChatMessage(wagerAmountTooHighMsg, null, chatter); } diff --git a/src/backend/integrations/builtin/aws/text-to-speech-polly-effect.js b/src/backend/integrations/builtin/aws/text-to-speech-polly-effect.js index 1207890b3..9af5442fa 100644 --- a/src/backend/integrations/builtin/aws/text-to-speech-polly-effect.js +++ b/src/backend/integrations/builtin/aws/text-to-speech-polly-effect.js @@ -12,7 +12,7 @@ const frontendCommunicator = require("../../../common/frontend-communicator"); const integrationManager = require("../../IntegrationManager"); const { EffectCategory } = require('../../../../shared/effect-constants'); const { wait } = require("../../../utility"); -const { PollyClient, DescribeVoicesCommand, SynthesizeSpeechCommand } = require('@aws-sdk/client-polly'); +const { PollyClient, DescribeVoicesCommand, SynthesizeSpeechCommand, ListLexiconsCommand } = require('@aws-sdk/client-polly'); frontendCommunicator.onAsync("getAwsPollyVoices", async () => { const response = { @@ -60,6 +60,51 @@ frontendCommunicator.onAsync("getAwsPollyVoices", async () => { return response; }); +frontendCommunicator.onAsync("getAwsPollyLexicons", async () => { + const response = { + error: false, + lexicons: [] + }; + const awsIntegration = integrationManager.getIntegrationDefinitionById("aws"); + + if (awsIntegration && awsIntegration.userSettings) { + if (awsIntegration.userSettings.iamCredentials.accessKeyId && + awsIntegration.userSettings.iamCredentials.secretAccessKey) { + + const polly = new PollyClient({ + credentials: { + accessKeyId: awsIntegration.userSettings.iamCredentials.accessKeyId, + secretAccessKey: awsIntegration.userSettings.iamCredentials.secretAccessKey + }, + region: awsIntegration.userSettings.iamCredentials.region || 'us-east-1' + }); + + let listLexiconsResponse = null; + + do { + try { + const listLexiconsCommand = new ListLexiconsCommand({ + NextToken: listLexiconsResponse ? listLexiconsResponse.NextToken : undefined + }); + listLexiconsResponse = await polly.send(listLexiconsCommand); + listLexiconsResponse.Lexicons.forEach(lexicon => response.lexicons.push(lexicon.Name)); + } catch (e) { + response.lexicons = []; + response.error = e; + listLexiconsResponse = null; + break; + } + } while (listLexiconsResponse && listLexiconsResponse.NextToken); + } else { + response.error = "NotConfigured"; + } + } else { + response.error = "NotConfigured"; + } + + return response; +}); + const POLLY_TMP_DIR = getPathInTmpDir('/awspollyfx'); /** @@ -146,6 +191,15 @@ const playSound = {
+ + + {{$item}} + +
+
+
+
+
Seconds @@ -185,6 +239,12 @@ const playSound = {
+
+ + An error has occurred while trying to read available lexicons from AWS. The error was: {{ lexiconFetchError }}. + +
+
Your AWS Credentials are not configured yet! You can configure them in Settings > Integrations > AWS @@ -203,11 +263,18 @@ const playSound = { $scope.effect.volume = 5; } + if ($scope.effect.lexicons == null) { + $scope.effect.lexicons = []; + } + $scope.isFetchingVoices = true; $scope.fetchError = false; + $scope.isFetchingLexicons = true; + $scope.lexiconFetchError = false; $scope.validLanguages = []; $scope.validVoices = {}; + $scope.lexicons = []; $scope.openLink = $rootScope.openLinkExternally; @@ -385,6 +452,20 @@ const playSound = { $scope.fetchError = voices.error; } }); + + $q.when(backendCommunicator.fireEventAsync("getAwsPollyLexicons")) + .then(lexicons => { + $scope.isFetchingLexicons = false; + + $scope.lexicons = lexicons.lexicons; + + if (lexicons.error) { + $scope.lexiconFetchError = lexicons.error; + } + + // Filter out missing lexicons. + $scope.effect.lexicons = $scope.effect.lexicons.filter(lexicon => $scope.lexicons.includes(lexicon)); + }); }, /** * When the effect is saved @@ -434,12 +515,39 @@ const playSound = { effect.text = effect.text.replace("&", "&"); } + let listLexiconsResponse = null; + let lexicons = []; + let lexiconError; + + do { + try { + const listLexiconsCommand = new ListLexiconsCommand({ + NextToken: listLexiconsResponse ? listLexiconsResponse.NextToken : undefined + }); + listLexiconsResponse = await polly.send(listLexiconsCommand); + listLexiconsResponse.Lexicons.forEach(lexicon => lexicons.push(lexicon.Name)); + } catch (e) { + lexicons = []; + lexiconError = e; + listLexiconsResponse = null; + break; + } + } while (listLexiconsResponse && listLexiconsResponse.NextToken); + + if (lexiconError) { + logger.error("Error while trying to fetch lexicons before speech synthesis, proceeding without lexicons."); + effect.lexicons = []; + } else { + effect.lexicons = effect.lexicons.filter(lexicon => lexicons.includes(lexicon)); + } + const synthSpeechCommand = new SynthesizeSpeechCommand({ Engine: effect.engine, OutputFormat: "mp3", Text: effect.text, TextType: effect.isSsml ? "ssml" : "text", - VoiceId: effect.voiceId + VoiceId: effect.voiceId, + LexiconNames: effect.lexicons.length !== 0 ? effect.lexicons : undefined }); let synthSpeedResponse = undefined; diff --git a/src/backend/integrations/builtin/discord/discord-message-sender.js b/src/backend/integrations/builtin/discord/discord-message-sender.js index 4313fc118..52b799c20 100644 --- a/src/backend/integrations/builtin/discord/discord-message-sender.js +++ b/src/backend/integrations/builtin/discord/discord-message-sender.js @@ -7,6 +7,8 @@ const axios = axiosDefault.create(); const integrationManager = require("../../IntegrationManager"); +const logger = require("../../../logwrapper"); + /** * * @param {*} discordChannelId @@ -66,18 +68,36 @@ async function sendDiscordMessage(discordChannelId, content, embed, files = null } - form.submit(channel.webhookUrl, (error) => { + /*form.submit(channel.webhookUrl + "?wait=true", (error, response) => { if (error) { console.log("Error sending screenshot discord message", error); } + });*/ + + return new Promise((resolve, reject) => { + form.submit(`${channel.webhookUrl}?wait=true`, (error, response) => { + if (error) { + logger.error("Error sending discord message with file(s)", error); + reject(error); + } + //resolve(response); + const chunks = []; + response.on('data', (chunk) => chunks.push(Buffer.from(chunk))); + response.on('error', (err) => reject(err)); + response.on('end', () => { + const result = Buffer.concat(chunks).toString('utf8'); + if (response.statusCode !== 200) { + reject(response.statusMessage); + } else { + resolve(result); + } + }); + }); }); - - return; } - axios.post(channel.webhookUrl, payload).catch(error => { - console.log("Failed to send webhook", error); - }); + const response = await axios.post(channel.webhookUrl + "?wait=true", payload); + return JSON.stringify(response.data); } exports.sendDiscordMessage = sendDiscordMessage; diff --git a/src/backend/integrations/builtin/discord/send-discord-message-effect.js b/src/backend/integrations/builtin/discord/send-discord-message-effect.js index 6886377ea..11afa7c3d 100644 --- a/src/backend/integrations/builtin/discord/send-discord-message-effect.js +++ b/src/backend/integrations/builtin/discord/send-discord-message-effect.js @@ -5,6 +5,8 @@ const integrationManager = require("../../IntegrationManager"); const discordEmbedBuilder = require('./discord-embed-builder'); const discord = require("./discord-message-sender"); const frontEndCommunicator = require("../../../common/frontend-communicator"); +const logger = require("../../../logwrapper"); +const fs = require("fs"); frontEndCommunicator.onAsync("getDiscordChannels", async () => { const channels = []; @@ -25,7 +27,19 @@ module.exports = { description: "Send a message and/or embed to a Discord channel", icon: "fab fa-discord", categories: [EffectCategory.INTEGRATIONS], - dependencies: [] + dependencies: [], + outputs: [ + { + label: "Success status", + description: "returns true if the message was sent successfully, false otherwise.", + defaultName: "discordSuccess" + }, + { + label: "Message Output", + description: "returns the discord message object if the message was sent successfully, returns an error otherwise.", + defaultName: "discordMessage" + } + ] }, globalSettings: {}, optionsTemplate: ` @@ -40,6 +54,10 @@ module.exports = { + + + +