diff --git a/.github/workflows/compile-and-release.yml b/.github/workflows/compile-and-release.yml index 4212da622..846898306 100644 --- a/.github/workflows/compile-and-release.yml +++ b/.github/workflows/compile-and-release.yml @@ -83,7 +83,9 @@ jobs: - name: Linux Build Prep if: runner.os == 'linux' - run: sudo apt-get install libx11-dev libxtst-dev libpng-dev + run: | + sudo apt-get update + sudo apt-get install libx11-dev libxtst-dev libpng-dev - name: MacOS Build Prep if: runner.os == 'macOS' diff --git a/.vscode/settings.json b/.vscode/settings.json index 15e1aa5ac..121944746 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,5 +36,7 @@ "prettier.trailingComma": "none", "[scss]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } -} \ No newline at end of file + }, + "js/ts.tsdk.path": "node_modules/typescript/lib", + "js/ts.tsdk.promptToUseWorkspaceVersion": true +} diff --git a/package-lock.json b/package-lock.json index 35ba5fd6a..c8e945247 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,18 @@ { "name": "firebotv5", - "version": "5.65.4", + "version": "5.66.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firebotv5", - "version": "5.65.4", + "version": "5.66.0", "license": "GPL-3.0", "dependencies": { "@aws-sdk/client-polly": "^3.26.0", "@seald-io/nedb": "^4.0.4", - "@twurple/api": "^8.0.3", - "@twurple/auth": "^8.0.3", - "@twurple/chat": "^8.0.3", - "@twurple/eventsub-ws": "^8.0.3", + "@twurple/chat": "^8.1.4", + "@twurple/eventsub-ws": "^8.1.4", "@types/tinycolor2": "^1.4.6", "@zunderscore/elgato-light-control": "^1.2.0", "angular": "^1.8.0", @@ -50,11 +48,10 @@ "escape-html": "^1.0.3", "eventsource": "^1.0.7", "express": "^5.1.0", - "expressionish": "github:SReject/expressionish#ab800d753263d46990c8ad5ba1459c366c1acca9", + "expressionish": "github:SReject/expressionish#27f38753346b60c5a09d69ebe407bf70583eec2d", "extra-life-ts": "^0.4.0", "firebot-nutjs": "github:crowbartools/firebot-nutjs#f52581d4c4426cf7f4dcc2b0d26cf56777e115f8", "form-data": "^4.0.5", - "fs-extra": "^11.2.0", "fuse.js": "^7.1.0", "glob": "^10.3.10", "he": "^1.2.0", @@ -97,7 +94,7 @@ "@types/unzipper": "^0.10.11", "@types/yargs-parser": "^21.0.3", "@typescript-eslint/parser": "^8.46.0", - "electron": "^39.2.4", + "electron": "^42.0.1", "electron-installer-dmg": "^5.0.1", "electron-winstaller": "^5.4.0", "eslint": "^9.37.0", @@ -773,6 +770,16 @@ "node": ">=0.1.90" } }, + "node_modules/@d-fischer/cache-decorators": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@d-fischer/cache-decorators/-/cache-decorators-4.0.1.tgz", + "integrity": "sha512-HNYLBLWs/t28GFZZeqdIBqq8f37mqDIFO6xNPof94VjpKvuP6ROqCZGafx88dk5zZUlBfViV9jD8iNNlXfc4CA==", + "license": "MIT", + "dependencies": { + "@d-fischer/shared-utils": "^3.6.3", + "tslib": "^2.6.2" + } + }, "node_modules/@d-fischer/connection": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@d-fischer/connection/-/connection-9.0.0.tgz", @@ -855,9 +862,10 @@ } }, "node_modules/@d-fischer/typed-event-emitter": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@d-fischer/typed-event-emitter/-/typed-event-emitter-3.3.2.tgz", - "integrity": "sha512-M+fZQxiAA6UTwaTNeDhNoQcQG+eg9B85cNTw4FwwmN0ZEi6IfvXS0cnLFW1Ec86TdnFkoQ1VHxGx4pTtKPVe1Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@d-fischer/typed-event-emitter/-/typed-event-emitter-3.3.3.tgz", + "integrity": "sha512-OvSEOa8icfdWDqcRtjSEZtgJTFOFNgTjje7zaL0+nAtu2/kZtRCSK5wUMrI/aXtCH8o0Qz2vA8UqkhWUTARFQQ==", + "license": "MIT", "dependencies": { "tslib": "^2.4.0" }, @@ -1078,31 +1086,32 @@ } }, "node_modules/@electron/get": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz", - "integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz", + "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==", + "license": "MIT", "dependencies": { "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", "progress": "^2.0.3", - "semver": "^6.2.0", + "semver": "^7.6.3", "sumchecker": "^3.0.1" }, "engines": { - "node": ">=12" + "node": ">=22.12.0" }, "optionalDependencies": { - "global-agent": "^3.0.0" + "undici": "^7.24.4" } }, "node_modules/@electron/get/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "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.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1113,87 +1122,34 @@ } } }, - "node_modules/@electron/get/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@electron/get/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/@electron/get/node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/@electron/get/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@electron/get/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "node_modules/@electron/get/node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "license": "MIT", "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@electron/get/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==" + "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/@electron/get/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@electron/osx-sign": { @@ -1499,22 +1455,6 @@ } } }, - "node_modules/@electron/packager/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@electron/packager/node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", @@ -1526,55 +1466,6 @@ "p-limit": "^3.1.0 " } }, - "node_modules/@electron/packager/node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/@electron/packager/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@electron/packager/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@electron/packager/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2279,6 +2170,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, "engines": { "node": ">=10" }, @@ -2873,6 +2765,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, "dependencies": { "defer-to-connect": "^2.0.0" }, @@ -2881,9 +2774,9 @@ } }, "node_modules/@twurple/api": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@twurple/api/-/api-8.0.3.tgz", - "integrity": "sha512-vnqVi9YlNDbCqgpUUvTIq4sDitKCY0dkTw9zPluZvRNqUB1eCsuoaRNW96HQDhKtA9P4pRzwZ8xU7v/1KU2ytg==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@twurple/api/-/api-8.1.4.tgz", + "integrity": "sha512-UA2eg5lyZRB0w55NjGvdhSmWPyIBaOthFKGPjg3L/jCQVYnY/FcmUuPipe2Op9U4Ej99c29cdjsmTSQI7P7Vqg==", "license": "MIT", "dependencies": { "@d-fischer/cache-decorators": "^4.0.0", @@ -2891,9 +2784,9 @@ "@d-fischer/logger": "^4.2.1", "@d-fischer/rate-limiter": "^1.1.0", "@d-fischer/shared-utils": "^3.6.1", - "@d-fischer/typed-event-emitter": "^3.3.1", - "@twurple/api-call": "8.0.3", - "@twurple/common": "8.0.3", + "@d-fischer/typed-event-emitter": "^3.3.3", + "@twurple/api-call": "8.1.4", + "@twurple/common": "8.1.4", "retry": "^0.13.1", "tslib": "^2.0.3" }, @@ -2901,51 +2794,43 @@ "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "@twurple/auth": "8.0.3" + "@twurple/auth": "8.1.4" } }, "node_modules/@twurple/api-call": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-8.0.3.tgz", - "integrity": "sha512-/5DBTqFjpYB+qqOkkFzoTWE79a7+I8uLXmBIIIYjGoq/CIPxKcHnlemXlU8cQhTr87PVa3th8zJXGYiNkpRx8w==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-8.1.4.tgz", + "integrity": "sha512-qh2TpdxxyiSkwadcCSes6uBHQB6l4Fz8sVfmzk+Brb12asemHMXTEyQAdrMJT7LlgtZq01nr+RASzWM3jmGtkw==", "license": "MIT", "dependencies": { "@d-fischer/shared-utils": "^3.6.1", - "@twurple/common": "8.0.3", + "@twurple/common": "8.1.4", "tslib": "^2.0.3" }, "funding": { "url": "https://github.com/sponsors/d-fischer" } }, - "node_modules/@twurple/api/node_modules/@d-fischer/cache-decorators": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@d-fischer/cache-decorators/-/cache-decorators-4.0.1.tgz", - "integrity": "sha512-HNYLBLWs/t28GFZZeqdIBqq8f37mqDIFO6xNPof94VjpKvuP6ROqCZGafx88dk5zZUlBfViV9jD8iNNlXfc4CA==", - "dependencies": { - "@d-fischer/shared-utils": "^3.6.3", - "tslib": "^2.6.2" - } - }, "node_modules/@twurple/api/node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/@twurple/auth": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@twurple/auth/-/auth-8.0.3.tgz", - "integrity": "sha512-Xlv+WNXmGQir4aBXYeRCqdno5XurA6jzYTIovSEHa7FZf3AMHMFqtzW7yqTCUn4iOahfUSA2TIIxmxFM0wis0g==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@twurple/auth/-/auth-8.1.4.tgz", + "integrity": "sha512-ylsJoPInCw9BwOqxKcx+1k2ce9QG3vJpKFzPdIyHh49HvM/ulQZ0CAGysydugDYXF0iO/TGryh7PluSwx5fIwA==", "license": "MIT", "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.6.1", - "@d-fischer/typed-event-emitter": "^3.3.1", - "@twurple/api-call": "8.0.3", - "@twurple/common": "8.0.3", + "@d-fischer/typed-event-emitter": "^3.3.3", + "@twurple/api-call": "8.1.4", + "@twurple/common": "8.1.4", "tslib": "^2.0.3" }, "funding": { @@ -2953,9 +2838,9 @@ } }, "node_modules/@twurple/chat": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@twurple/chat/-/chat-8.0.3.tgz", - "integrity": "sha512-rhm6xhWKp+4zYFimaEj5fPm6lw/yjrAOsGXXSvPDsEqFR+fc0cVXzmHmglTavkmEELRajFiqNBKZjg73JZWhTQ==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@twurple/chat/-/chat-8.1.4.tgz", + "integrity": "sha512-654LU7BwEpR7lLnWaVMgHtd7wMsaQo71GPTrFZ5ChS283d7B7GTe+9RJ5SxNliNxB5Xn0+R3R3M5HJlUOGYNeA==", "license": "MIT", "dependencies": { "@d-fischer/cache-decorators": "^4.0.0", @@ -2963,8 +2848,8 @@ "@d-fischer/logger": "^4.2.1", "@d-fischer/rate-limiter": "^1.1.0", "@d-fischer/shared-utils": "^3.6.1", - "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/common": "8.0.3", + "@d-fischer/typed-event-emitter": "^3.3.3", + "@twurple/common": "8.1.4", "ircv3": "^0.33.0", "tslib": "^2.0.3" }, @@ -2972,22 +2857,13 @@ "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "@twurple/auth": "8.0.3" - } - }, - "node_modules/@twurple/chat/node_modules/@d-fischer/cache-decorators": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@d-fischer/cache-decorators/-/cache-decorators-4.0.1.tgz", - "integrity": "sha512-HNYLBLWs/t28GFZZeqdIBqq8f37mqDIFO6xNPof94VjpKvuP6ROqCZGafx88dk5zZUlBfViV9jD8iNNlXfc4CA==", - "dependencies": { - "@d-fischer/shared-utils": "^3.6.3", - "tslib": "^2.6.2" + "@twurple/auth": "8.1.4" } }, "node_modules/@twurple/common": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@twurple/common/-/common-8.0.3.tgz", - "integrity": "sha512-JQ2lb5qSFT21Y9qMfIouAILb94ppedLHASq49Fe/AP8oq0k3IC9Q7tX2n6tiMzGWqn+n8MnONUpMSZ6FhulMXA==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@twurple/common/-/common-8.1.4.tgz", + "integrity": "sha512-1iN5DvOnW+g+Nl3OTI5zUJHgAfjmPCb50HpKsAFik6OYQEAHLsscQKgTOJ+KRuFBYepo/JkHsOWOmWhXxnK6lQ==", "license": "MIT", "dependencies": { "@d-fischer/shared-utils": "^3.6.1", @@ -2999,17 +2875,17 @@ } }, "node_modules/@twurple/eventsub-base": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@twurple/eventsub-base/-/eventsub-base-8.0.3.tgz", - "integrity": "sha512-59G5xJbHWLTSO6NAgwtkHPfIlmdjrABgiEumFnHhNusMbLM9qdA+kLcW5NB2NImNliytl6zZtqY92FInzUE6NA==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@twurple/eventsub-base/-/eventsub-base-8.1.4.tgz", + "integrity": "sha512-tMt44+BoepEBRDn90d+axP9F+0JH0/AT6to0Fz6O/JyIXBXMa7GiZxAkHG6pMZsFYfhKZkb4aehP1iQfc2PkFQ==", "license": "MIT", "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.6.1", - "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/api": "8.0.3", - "@twurple/auth": "8.0.3", - "@twurple/common": "8.0.3", + "@d-fischer/typed-event-emitter": "^3.3.3", + "@twurple/api": "8.1.4", + "@twurple/auth": "8.1.4", + "@twurple/common": "8.1.4", "tslib": "^2.0.3" }, "funding": { @@ -3017,25 +2893,53 @@ } }, "node_modules/@twurple/eventsub-ws": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@twurple/eventsub-ws/-/eventsub-ws-8.0.3.tgz", - "integrity": "sha512-ndZ5GlekLD34updOHajNdz0p49IysBL8yNm7RvhHr0Pg/5B+TEjjg4uuxZixZ+e3i/BLSBfff1BI2GHzvEn+4Q==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@twurple/eventsub-ws/-/eventsub-ws-8.1.4.tgz", + "integrity": "sha512-fFqwkVq2/T48VVkPYlgIQCYsKN82MvOS51xMh8LPLW0PLVfau5b4v8X88JWx7wzGNZ9d9DImDhCHs+JV/efpfg==", "license": "MIT", "dependencies": { - "@d-fischer/connection": "^9.0.0", + "@d-fischer/connection": "^10.0.1", "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.6.1", - "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/auth": "8.0.3", - "@twurple/common": "8.0.3", - "@twurple/eventsub-base": "8.0.3", + "@d-fischer/typed-event-emitter": "^3.3.3", + "@twurple/auth": "8.1.4", + "@twurple/common": "8.1.4", + "@twurple/eventsub-base": "8.1.4", "tslib": "^2.0.3" }, "funding": { "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "@twurple/api": "8.0.3" + "@twurple/api": "8.1.4" + } + }, + "node_modules/@twurple/eventsub-ws/node_modules/@d-fischer/connection": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@d-fischer/connection/-/connection-10.0.1.tgz", + "integrity": "sha512-CRP/azUPxwWpR4yT8wOQoM9XFliTVWVAJ8h1SlFnVRAgMlPNyg88/vbDEqZ+udtSB5m8uS10XafZxMUcegMBlQ==", + "license": "MIT", + "dependencies": { + "@d-fischer/isomorphic-ws": "^7.0.0", + "@d-fischer/logger": "^4.2.1", + "@d-fischer/shared-utils": "^3.5.0", + "@d-fischer/typed-event-emitter": "^3.3.0", + "@types/node": "^20.19.37", + "@types/ws": "^8.5.4", + "tslib": "^2.4.1", + "ws": "^8.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/@twurple/eventsub-ws/node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" } }, "node_modules/@types/angular": { @@ -3080,6 +2984,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", @@ -3148,7 +3053,8 @@ "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -3161,6 +3067,7 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -3214,6 +3121,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -4650,6 +4558,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "dev": true, "optional": true }, "node_modules/bottleneck": { @@ -4744,6 +4653,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, "engines": { "node": ">=10.6.0" } @@ -4752,6 +4662,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dev": true, "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -4769,6 +4680,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, "dependencies": { "pump": "^3.0.0" }, @@ -4779,14 +4691,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -4926,6 +4830,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, "dependencies": { "mimic-response": "^1.0.0" }, @@ -4937,6 +4842,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, "engines": { "node": ">=4" } @@ -5459,6 +5365,22 @@ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -5477,6 +5399,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, "engines": { "node": ">=10" } @@ -5502,6 +5425,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, "optional": true, "dependencies": { "has-property-descriptors": "^1.0.0", @@ -5543,6 +5467,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, "optional": true }, "node_modules/dns-packet": { @@ -5633,21 +5558,21 @@ } }, "node_modules/electron": { - "version": "39.2.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.4.tgz", - "integrity": "sha512-KxPtwpFceQKSxRtUY39piHLYhJMMyHfOhc70e6zRnKGrbRdK6hzEqssth8IGjlKOdkeT4KCvIEngnNraYk39+g==", - "hasInstallScript": true, + "version": "42.0.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-42.0.1.tgz", + "integrity": "sha512-d8HnycE970DGESe91Nj30eonFBUcAI9EZ1TwUGJVzSAnJZdh0BkFEinAXjdklvDYst+bVDc8HsksCuqVLrnqdg==", "license": "MIT", "dependencies": { - "@electron/get": "^2.0.0", - "@types/node": "^22.7.7", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { - "electron": "cli.js" + "electron": "cli.js", + "install-electron": "install.js" }, "engines": { - "node": ">= 12.20.55" + "node": ">= 22.12.0" } }, "node_modules/electron-installer-common": { @@ -6059,6 +5984,21 @@ "dev": true, "license": "MIT" }, + "node_modules/electron/node_modules/@types/node": { + "version": "24.12.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.3.tgz", + "integrity": "sha512-8oljBDGun9cIsZRJR6fkihn0TSXJI0UDOOhncYaERq6M0JMDoPLxyscwruJcb4GKS6dvK/d8xebYBg27h/duaQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6158,6 +6098,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, "engines": { "node": ">=6" } @@ -6227,6 +6168,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, "optional": true }, "node_modules/escalade": { @@ -6254,7 +6196,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "devOptional": true, + "dev": true, "engines": { "node": ">=10" }, @@ -6814,9 +6756,9 @@ "license": "MIT" }, "node_modules/expressionish": { - "version": "0.0.4", - "resolved": "git+ssh://git@github.com/SReject/expressionish.git#ab800d753263d46990c8ad5ba1459c366c1acca9", - "integrity": "sha512-iV9aqSrXtyI8QPruX4WzpGcdChOt3mOB13DzsWKnlqijgZM4H4j64qFvJ3M5WaeiyPzHS9JtLPPsK4UA+dvqzA==", + "version": "0.0.5", + "resolved": "git+ssh://git@github.com/SReject/expressionish.git#27f38753346b60c5a09d69ebe407bf70583eec2d", + "integrity": "sha512-CfodJDY96u3bjKg9kSGt9h8XRwmbDiI//6RtVNXZunDkzY3xUdytnrTn31m1zp254VeTievsIGNEFC2rn6lYsA==", "license": "ISC" }, "node_modules/extend": { @@ -7783,6 +7725,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, "optional": true, "dependencies": { "boolean": "^3.0.1", @@ -7800,6 +7743,7 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, "optional": true, "bin": { "semver": "bin/semver.js" @@ -7855,6 +7799,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, "optional": true, "dependencies": { "define-properties": "^1.1.3" @@ -7878,10 +7823,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", @@ -8654,7 +8626,8 @@ "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true }, "node_modules/http-errors": { "version": "2.0.1", @@ -8680,6 +8653,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" @@ -9184,7 +9158,8 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -9202,6 +9177,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, "optional": true }, "node_modules/jsonfile": { @@ -9237,6 +9213,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -9453,6 +9430,16 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -9533,6 +9520,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, "optional": true, "dependencies": { "escape-string-regexp": "^4.0.0" @@ -9637,6 +9625,19 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -9893,6 +9894,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, "engines": { "node": ">=10" }, @@ -9954,6 +9956,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "optional": true, "engines": { "node": ">= 0.4" @@ -10126,6 +10129,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, "engines": { "node": ">=8" } @@ -10677,6 +10681,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, "engines": { "node": ">=10" }, @@ -10906,7 +10911,8 @@ "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true }, "node_modules/resolve-dir": { "version": "1.0.1", @@ -10934,6 +10940,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, "dependencies": { "lowercase-keys": "^2.0.0" }, @@ -10941,14 +10948,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/responselike/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -10974,6 +10973,7 @@ "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, "optional": true, "dependencies": { "boolean": "^3.0.1", @@ -11128,6 +11128,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, "optional": true }, "node_modules/send": { @@ -11200,6 +11201,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, "optional": true, "dependencies": { "type-fest": "^0.13.1" @@ -11979,6 +11981,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, "optional": true, "engines": { "node": ">=10" @@ -12155,6 +12158,16 @@ "node": "*" } }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -12172,6 +12185,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, "engines": { "node": ">= 4.0.0" } diff --git a/package.json b/package.json index d943346f6..746cfbb18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebotv5", - "version": "5.65.4", + "version": "5.66.0", "description": "Powerful all-in-one bot for Twitch streamers.", "main": "build/main.js", "scripts": { @@ -35,7 +35,7 @@ "@types/unzipper": "^0.10.11", "@types/yargs-parser": "^21.0.3", "@typescript-eslint/parser": "^8.46.0", - "electron": "^39.2.4", + "electron": "^42.0.1", "electron-installer-dmg": "^5.0.1", "electron-winstaller": "^5.4.0", "eslint": "^9.37.0", @@ -54,10 +54,8 @@ "dependencies": { "@aws-sdk/client-polly": "^3.26.0", "@seald-io/nedb": "^4.0.4", - "@twurple/api": "^8.0.3", - "@twurple/auth": "^8.0.3", - "@twurple/chat": "^8.0.3", - "@twurple/eventsub-ws": "^8.0.3", + "@twurple/chat": "^8.1.4", + "@twurple/eventsub-ws": "^8.1.4", "@types/tinycolor2": "^1.4.6", "@zunderscore/elgato-light-control": "^1.2.0", "angular": "^1.8.0", @@ -93,11 +91,10 @@ "escape-html": "^1.0.3", "eventsource": "^1.0.7", "express": "^5.1.0", - "expressionish": "github:SReject/expressionish#ab800d753263d46990c8ad5ba1459c366c1acca9", + "expressionish": "github:SReject/expressionish#27f38753346b60c5a09d69ebe407bf70583eec2d", "extra-life-ts": "^0.4.0", "firebot-nutjs": "github:crowbartools/firebot-nutjs#f52581d4c4426cf7f4dcc2b0d26cf56777e115f8", "form-data": "^4.0.5", - "fs-extra": "^11.2.0", "fuse.js": "^7.1.0", "glob": "^10.3.10", "he": "^1.2.0", diff --git a/src/backend/app-management/electron/events/when-ready.ts b/src/backend/app-management/electron/events/when-ready.ts index edb4d07c0..46c620ead 100644 --- a/src/backend/app-management/electron/events/when-ready.ts +++ b/src/backend/app-management/electron/events/when-ready.ts @@ -3,7 +3,6 @@ import frontendCommunicator from "../../../common/frontend-communicator"; import logger from "../../../logwrapper"; export async function whenReady() { - logger.debug("...Applying IPC events"); const { setupIpcEvents } = await import("./ipc-events"); setupIpcEvents(); @@ -23,6 +22,10 @@ export async function whenReady() { const { ensureRequiredFoldersExist } = await import("../../data-tasks"); await ensureRequiredFoldersExist(); + windowManagement.updateSplashScreenStatus("Loading pronoun cache..."); + const { FirebotPronounManager } = await import("../../../pronouns/pronoun-manager"); + await FirebotPronounManager.cachePronouns(); + // load twitch auth windowManagement.updateSplashScreenStatus("Loading Twitch login system..."); await import("../../../auth/auth-manager"); @@ -52,8 +55,7 @@ export async function whenReady() { windowManagement.updateSplashScreenStatus("Refreshing Twitch account data..."); - // Loading these first so that the refresh caches the account avatar URLs - const _chatHelpers = await import("../../../chat/chat-helpers"); + // Loading this first so that the refresh caches the account avatar URLs const _eventSubChatHelpers = await import("../../../streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers"); const { TwitchApi } = await import("../../../streaming-platforms/twitch/api"); @@ -263,6 +265,10 @@ export async function whenReady() { const channelRewardManager = (await import("../../../channel-rewards/channel-reward-manager")).default; await channelRewardManager.loadChannelRewards(); + windowManagement.updateSplashScreenStatus("Loading power-ups..."); + const powerUpsManager = (await import("../../../power-ups/power-ups-manager")).default; + await powerUpsManager.loadPowerUps(); + // load activity feed manager await import("../../../events/activity-feed-manager"); @@ -283,7 +289,8 @@ export async function whenReady() { // start crowbar relay websocket await import("../../../crowbar-relay/crowbar-relay-websocket"); - const countdownManager = (await import("../../../overlay-widgets/builtin-types/countdown/countdown-manager")).default; + const countdownManager = (await import("../../../overlay-widgets/builtin-types/countdown/countdown-manager")) + .default; countdownManager.startTimer(); logger.debug("...loading main window"); @@ -291,11 +298,7 @@ export async function whenReady() { await windowManagement.createMainWindow(); // Receive log messages from frontend - frontendCommunicator.on("logging", (data: { - level: string; - message: string; - meta?: unknown[]; - }) => { + frontendCommunicator.on("logging", (data: { level: string, message: string, meta?: unknown[] }) => { logger.log(data.level, data.message, ...(data.meta ?? [])); }); -}; \ No newline at end of file +} diff --git a/src/backend/channel-rewards/channel-reward-manager.ts b/src/backend/channel-rewards/channel-reward-manager.ts index 262c5a3b3..ffc005310 100644 --- a/src/backend/channel-rewards/channel-reward-manager.ts +++ b/src/backend/channel-rewards/channel-reward-manager.ts @@ -24,6 +24,7 @@ class ChannelRewardManager { TwitchApi.channelRewards.getTotalChannelRewardCount); frontendCommunicator.on("get-channel-rewards", () => Object.values(this.channelRewards)); + frontendCommunicator.onAsync("get-channel-rewards", async () => Object.values(this.channelRewards)); frontendCommunicator.on("get-channel-rewards-eligibility", () => this._eligible); @@ -432,10 +433,6 @@ class ChannelRewardManager { } async refreshChannelRewardRedemptions(): Promise { - if (AccountAccess.getAccounts().streamer.broadcasterType === "") { - return; - } - this._channelRewardRedemptions = await TwitchApi.channelRewards.getOpenChannelRewardRedemptions(); frontendCommunicator.send("channel-reward-redemptions-updated", this.getChannelRewardRedemptions()); diff --git a/src/backend/chat/active-user-handler.ts b/src/backend/chat/active-user-handler.ts index 8b6ddac3c..4dbc94ef3 100644 --- a/src/backend/chat/active-user-handler.ts +++ b/src/backend/chat/active-user-handler.ts @@ -4,7 +4,7 @@ import type { HelixChatChatter } from "@twurple/api"; import { SettingsManager } from "../common/settings-manager"; import { TwitchApi } from "../streaming-platforms/twitch/api"; -import chatHelpers from "./chat-helpers"; +import { TwitchEventSubChatHelpers } from "../streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers"; import chatRolesManager from "../roles/chat-roles-manager"; import viewerDatabase from "../viewers/viewer-database"; import frontendCommunicator from "../common/frontend-communicator"; @@ -202,7 +202,7 @@ class ActiveUserHandler extends TypedEmitter { disableViewerList: false }; - chatHelpers.setUserProfilePicUrl(twitchUser.id, twitchUser.profilePictureUrl); + TwitchEventSubChatHelpers.setUserProfilePicUrl(twitchUser.id, twitchUser.profilePictureUrl); await viewerDatabase.addNewViewerFromChat(userDetails, true); @@ -242,7 +242,7 @@ class ActiveUserHandler extends TypedEmitter { ...(chatUser.isMod || chatUser.badges?.has("lead_moderator") ? ['mod'] : []), ...(chatUser.isVip ? ['vip'] : []) ], - profilePicUrl: (await chatHelpers.getUserProfilePicUrl(chatUser.userId)), + profilePicUrl: (await TwitchEventSubChatHelpers.getUserProfilePicUrl(chatUser.userId)), disableViewerList: !!user?.disableViewerList }; diff --git a/src/backend/chat/chat-helpers.ts b/src/backend/chat/chat-helpers.ts index 66d964249..2e7788d66 100644 --- a/src/backend/chat/chat-helpers.ts +++ b/src/backend/chat/chat-helpers.ts @@ -1,194 +1,40 @@ -import { HelixChatBadgeSet, HelixCheermoteList, HelixEmoteScale } from "@twurple/api"; -import { ChatMessage, ParsedMessageCheerPart, ParsedMessagePart, findCheermotePositions, parseChatMessage } from "@twurple/chat"; -import { EventSubAutoModMessageHoldV2Event } from "@twurple/eventsub-base"; - import { + type ChatMessage, + type ParsedMessageCheerPart, + type ParsedMessagePart, + parseChatMessage +} from "@twurple/chat"; +import type { EventSubAutoModMessageHoldV2Event } from "@twurple/eventsub-base"; + +import type { FirebotChatMessage, FirebotCheermoteInstance, FirebotParsedMessagePart -} from "../../types/chat"; -import { FirebotAccount } from "../../types/accounts"; +} from "../../types"; import { AccountAccess } from "../common/account-access"; -import { SettingsManager } from "../common/settings-manager"; +import { FirebotPronounManager } from "../pronouns/pronoun-manager"; import { SharedChatCache } from "../streaming-platforms/twitch/chat/shared-chat-cache"; -import { TwitchApi } from "../streaming-platforms/twitch/api"; -import rankManager from "../ranks/rank-manager"; -import roleManager from "../roles/custom-roles-manager"; -import viewerDatabase from "../viewers/viewer-database"; -import frontendCommunicator from "../common/frontend-communicator"; +import { type ChatBadge, TwitchEventSubChatHelpers } from "../streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers"; import logger from "../logwrapper"; import { getUrlRegex } from "../utils"; -import { ThirdPartyEmote, ThirdPartyEmoteProvider } from "./third-party/third-party-emote-provider"; -import { BTTVEmoteProvider } from "./third-party/bttv"; -import { FFZEmoteProvider } from "./third-party/ffz"; -import { SevenTVEmoteProvider } from "./third-party/7tv"; - interface ExtensionBadge { id: string; version: string; } -// Yoinked from Twurple -interface HelixEmoteBase { - id: string; - name: string; - getStaticImageUrl(scale?: HelixEmoteScale): string; - getAnimatedImageUrl(scale?: HelixEmoteScale): string; -} - class FirebotChatHelpers { - private _badgeCache: HelixChatBadgeSet[] = []; - - private _getAllTwitchEmotes = false; - private _twitchEmotes: { - streamer: HelixEmoteBase[]; - bot: HelixEmoteBase[]; - } = { streamer: [], bot: [] }; - - private _thirdPartyEmotes: ThirdPartyEmote[] = []; - private _thirdPartyEmoteProviders: ThirdPartyEmoteProvider[] = [ - new BTTVEmoteProvider(), - new FFZEmoteProvider(), - new SevenTVEmoteProvider() - ]; - - private _twitchCheermotes: HelixCheermoteList; - - private _profilePicUrlCache: Record = {}; - private readonly URL_REGEX = getUrlRegex(false); - constructor() { - AccountAccess.on("account-update", - (cache) => { - if (cache.streamer?.loggedIn) { - this.setUserProfilePicUrl( - cache.streamer.userId, - cache.streamer.avatar, - false - ); - } - - if (cache.bot?.loggedIn) { - this.setUserProfilePicUrl( - cache.bot.userId, - cache.bot.avatar, - false - ); - } - } - ); - - viewerDatabase.on("updated-viewer-avatar", - ({ userId, url }) => { - this.setUserProfilePicUrl(userId, url); - } - ); - } - - async cacheBadges(): Promise { - logger.debug("Caching Twitch badges"); - const streamer = AccountAccess.getAccounts().streamer; - const client = TwitchApi.streamerClient; - if (streamer.loggedIn && client) { - try { - const channelBadges = await client.chat.getChannelBadges(streamer.userId); - const globalBadges = await client.chat.getGlobalBadges(); - - this._badgeCache = [ - ...channelBadges, - ...globalBadges - ]; - } catch (error) { - logger.error("Failed to get channel chat badges", error); - } - } - } - - async cacheTwitchEmotes(): Promise { - logger.debug("Caching Twitch emotes"); - - // Cache this setting so it's consistent during the chat connection - this._getAllTwitchEmotes = SettingsManager.getSetting("ChatGetAllEmotes") === true; - const client = TwitchApi.streamerClient; - - const { streamer, bot } = AccountAccess.getAccounts(); - - if (client == null || !streamer.loggedIn) { - return; - } - - try { - let streamerEmotes: HelixEmoteBase[] = []; - - if (this._getAllTwitchEmotes) { - logger.debug(`Caching all available Twitch emotes for streamer ${streamer.username}`); - - // This includes: global, streamer channel, all channels streamer is subscribed to, etc. - // This may take SEVERAL calls so it can take several seconds to complete - streamerEmotes = await TwitchApi.chat.getAllUserEmotes(); - - if (!streamerEmotes) { - return; - } - } else { - logger.debug(`Caching Twitch channel emotes for ${streamer.username}`); - const channelEmotes = await client.chat.getChannelEmotes(streamer.userId); - - logger.debug("Caching Twitch global emotes"); - const globalEmotes = await client.chat.getGlobalEmotes(); - - if (!channelEmotes && !globalEmotes) { - return; - } - - streamerEmotes = [ - ...channelEmotes, - ...globalEmotes - ]; - } - - this._twitchEmotes.streamer = streamerEmotes; - - if (this._getAllTwitchEmotes) { - if (bot.loggedIn) { - logger.debug(`Caching all available Twitch emotes for bot ${bot.username}`); - - this._twitchEmotes.bot = await TwitchApi.chat.getAllUserEmotes("bot") ?? []; - } else { - logger.debug("Bot account not logged in; Skipping Twitch bot emotes"); - } - } - } catch (err) { - logger.error("Failed to get Twitch chat emotes", err); - return null; - } - } - - async cacheThirdPartyEmotes() { - logger.debug("Caching third-party emotes"); - this._thirdPartyEmotes = []; - for (const provider of this._thirdPartyEmoteProviders) { - logger.debug(`Caching ${provider.providerName} emotes`); - this._thirdPartyEmotes.push(...await provider.getAllEmotes()); - } - } - - async cacheCheermotes() { - logger.debug("Caching Twitch cheermotes"); - this._twitchCheermotes = await TwitchApi.bits.getChannelCheermotes(); - } - parseCheermote(part: ParsedMessageCheerPart | FirebotParsedMessagePart): FirebotCheermoteInstance { - const displayInfo = this._twitchCheermotes.getCheermoteDisplayInfo(part.name, part.amount, { + const displayInfo = TwitchEventSubChatHelpers.twitchCheermotes.getCheermoteDisplayInfo(part.name, part.amount, { background: "light", state: "animated", scale: "4" }); - const staticDisplayInfo = this._twitchCheermotes.getCheermoteDisplayInfo(part.name, part.amount, { + const staticDisplayInfo = TwitchEventSubChatHelpers.twitchCheermotes.getCheermoteDisplayInfo(part.name, part.amount, { background: "light", state: "static", scale: "4" @@ -203,92 +49,12 @@ class FirebotChatHelpers { }; } - async getCheermoteData(message: string): Promise { - const cheermotes: FirebotCheermoteInstance[] = []; - if (!(message?.length > 0)) { - return cheermotes; - } - - const parsedCheermotes = findCheermotePositions(message, this._twitchCheermotes.getPossibleNames()); - - return parsedCheermotes.map(this.parseCheermote); - } - - async cacheChatAssets(): Promise { - await this.cacheBadges(); - await this.cacheCheermotes(); - await this.cacheThirdPartyEmotes(); - await this.cacheTwitchEmotes(); - - // If the all emotes setting is disabled, just send the standard global/channel list for both accounts - frontendCommunicator.send("all-emotes", { - streamer: this._mapCachedEmotesForFrontend(this._twitchEmotes.streamer), - bot: this._mapCachedEmotesForFrontend(this._getAllTwitchEmotes ? this._twitchEmotes.bot : this._twitchEmotes.streamer), - thirdParty: this._thirdPartyEmotes - }); - } - - private _mapCachedEmotesForFrontend(emoteList: HelixEmoteBase[]) { - return emoteList.map(e => ({ - url: e.getStaticImageUrl("3.0"), - animatedUrl: e.getAnimatedImageUrl("3.0"), - origin: "Twitch", - code: e.name - })); - } - - private _updateAccountAvatar( - accountType: "streamer" | "bot", - account: FirebotAccount, - url: string - ): void { - account.avatar = url; - AccountAccess.updateAccount(accountType, account, false); - } - - async getUserProfilePicUrl(userId: string): Promise { - if (userId == null) { - return null; - } - - if (this._profilePicUrlCache[userId]) { - return this._profilePicUrlCache[userId]; - } - - const streamer = AccountAccess.getAccounts().streamer; - const client = TwitchApi.streamerClient; - if (streamer.loggedIn && client) { - const user = await TwitchApi.users.getUserById(userId); - if (user) { - this._profilePicUrlCache[userId] = user.profilePictureUrl; - return user.profilePictureUrl; - } - } - return null; - } - - setUserProfilePicUrl(userId: string, url: string, updateAccountAvatars = true) { - if (userId == null || url == null) { - return; - } - - this._profilePicUrlCache[userId] = url; - - if (updateAccountAvatars) { - if (userId === AccountAccess.getAccounts().streamer.userId) { - this._updateAccountAvatar("streamer", AccountAccess.getAccounts().streamer, url); - } else if (userId === AccountAccess.getAccounts().bot.userId) { - this._updateAccountAvatar("bot", AccountAccess.getAccounts().bot, url); - } - } - } - private _parseMessageParts(firebotChatMessage: FirebotChatMessage, parts: ParsedMessagePart[] | FirebotParsedMessagePart[]) { if (firebotChatMessage == null || parts == null) { return []; } const { streamer, bot } = AccountAccess.getAccounts(); - return parts.flatMap((p) => { + return parts.flatMap((p: ParsedMessagePart | FirebotParsedMessagePart) => { if (p.type === "text" && p.text != null) { if (firebotChatMessage.username !== streamer.username && @@ -314,7 +80,7 @@ class FirebotChatHelpers { } // check for third party emotes - const thirdPartyEmote = this._thirdPartyEmotes.find(e => e.code === word); + const thirdPartyEmote = TwitchEventSubChatHelpers.thirdPartyEmotes.find(e => e.code === word); if (thirdPartyEmote) { subParts.push({ type: "third-party-emote", @@ -332,7 +98,7 @@ class FirebotChatHelpers { subParts.push({ type: "text", text: `${word} `, - flagged: p.flagged + flagged: (p as FirebotParsedMessagePart).flagged }); } } @@ -360,9 +126,9 @@ class FirebotChatHelpers { if (part.type === "emote") { part.origin = "Twitch"; - let emote = this._twitchEmotes.streamer.find(e => e.name === part.name); + let emote = TwitchEventSubChatHelpers.twitchEmotes.streamer.find(e => e.name === part.name); if (emote == null) { - emote = this._twitchEmotes.bot.find(e => e.name === part.name); + emote = TwitchEventSubChatHelpers.twitchEmotes.bot.find(e => e.name === part.name); } part.url = emote ? emote.getStaticImageUrl("3.0") : `https://static-cdn.jtvnw.net/emoticons/v2/${part.id}/default/dark/3.0`; @@ -381,16 +147,16 @@ class FirebotChatHelpers { }); } - private _getChatBadges(badges: Map) { - if (this._badgeCache == null) { + private _getChatBadges(badges: Map): ChatBadge[] { + if (TwitchEventSubChatHelpers.badges == null) { return []; } - const chatBadges = []; + const chatBadges: ChatBadge[] = []; for (const [setName, version] of badges.entries()) { - const set = this._badgeCache.find(b => b.id === setName); + const set = TwitchEventSubChatHelpers.badges.find(b => b.id === setName); if (set == null) { continue; } @@ -414,7 +180,7 @@ class FirebotChatHelpers { } private _getMessageParts(text: string): FirebotParsedMessagePart[] { - if (!this._twitchEmotes) { + if (!TwitchEventSubChatHelpers.twitchEmotes) { const part: FirebotParsedMessagePart = { type: "text", text: text @@ -424,11 +190,11 @@ class FirebotChatHelpers { const words = text.split(" "); return words.map((word) => { - let emoteId = null; + let emoteId: string = null; let url = ""; let animatedUrl = ""; try { - const foundEmote = Object.values(this._twitchEmotes || {}) + const foundEmote = Object.values(TwitchEventSubChatHelpers.twitchEmotes || {}) .flat() .find(e => e.name === word); if (foundEmote) { @@ -436,9 +202,7 @@ class FirebotChatHelpers { url = foundEmote.getStaticImageUrl("3.0"); animatedUrl = foundEmote.getAnimatedImageUrl("3.0"); } - } catch (err) { - //logger.silly(`Failed to find emote id for ${word}`, err); - } + } catch { } let part: FirebotParsedMessagePart; if (emoteId != null) { @@ -485,12 +249,11 @@ class FirebotChatHelpers { username: msg.userInfo.userName, userId: msg.userInfo.userId, userDisplayName: msg.userInfo.displayName, + pronouns: await FirebotPronounManager.getUserFriendlyPronounString(msg.userInfo.userName), customRewardId: msg.tags.get("custom-reward-id") || undefined, isHighlighted: msg.tags.get("msg-id") === "highlighted-message", isAnnouncement: false, isHiddenFromChatFeed: false, - isFirstChat: msg.isFirst ?? false, - isReturningChatter: msg.isReturningChatter ?? false, isReply: msg.tags.has("reply-parent-msg-id"), isGigantified: isGigantified, replyParentMessageId: msg.tags.get("reply-parent-msg-id"), @@ -501,7 +264,9 @@ class FirebotChatHelpers { threadParentMessageSenderUserId: msg.tags.get("reply-thread-parent-user-id"), threadParentMessageSenderDisplayName: msg.tags.get("reply-thread-parent-display-name"), - //TODO: Waiting for EventSub to supply these 3 fields + //TODO: Waiting for EventSub to supply these fields + isFirstChat: msg.isFirst ?? false, + isReturningChatter: msg.isReturningChatter ?? false, isRaider: false, raidingFrom: "", isSuspiciousUser: false, @@ -524,10 +289,10 @@ class FirebotChatHelpers { sharedChatRoomProfilePicUrl: sharedChatRoom?.profilePictureUrl }; - const profilePicUrl = await this.getUserProfilePicUrl(firebotChatMessage.userId); + const profilePicUrl = await TwitchEventSubChatHelpers.getUserProfilePicUrl(firebotChatMessage.userId); firebotChatMessage.profilePicUrl = profilePicUrl; - await this.enrichMessageWithRanksAndRoles(firebotChatMessage); + await TwitchEventSubChatHelpers.enrichMessageWithRanksAndRoles(firebotChatMessage); const { streamer, bot } = AccountAccess.getAccounts(); @@ -544,7 +309,7 @@ class FirebotChatHelpers { } const messageParts = this._parseMessageParts(firebotChatMessage, - parseChatMessage(msgText, msg.emoteOffsets, this._twitchCheermotes?.getPossibleNames())); + parseChatMessage(msgText, msg.emoteOffsets, TwitchEventSubChatHelpers.twitchCheermotes?.getPossibleNames())); firebotChatMessage.parts = messageParts; @@ -564,7 +329,7 @@ class FirebotChatHelpers { } } - if (this._badgeCache != null) { + if (TwitchEventSubChatHelpers.badges != null) { firebotChatMessage.badges = this._getChatBadges(msg.userInfo.badges); } @@ -600,7 +365,7 @@ class FirebotChatHelpers { firebotChatMessage.isCheer = msg.isCheer === true; - firebotChatMessage.color = msg.userInfo.color; + firebotChatMessage.color = TwitchEventSubChatHelpers.cacheUserColor(msg.userInfo.userId, msg.userInfo.color); return firebotChatMessage; } @@ -626,7 +391,7 @@ class FirebotChatHelpers { )) : [], parts: this._getMessageParts(text), roles: [], - isSharedChatMessage: false // todo: check if extension messages pass through Shared Chat, and if we can listen for it + isSharedChatMessage: false }; firebotChatMessage.parts = this._parseMessageParts(firebotChatMessage, firebotChatMessage.parts); @@ -635,7 +400,7 @@ class FirebotChatHelpers { } async buildViewerFirebotChatMessageFromAutoModMessage(msg: EventSubAutoModMessageHoldV2Event) { - const profilePicUrl = await this.getUserProfilePicUrl(msg.userId); + const profilePicUrl = await TwitchEventSubChatHelpers.getUserProfilePicUrl(msg.userId); const viewerFirebotChatMessage: FirebotChatMessage = { id: msg.messageId, @@ -644,6 +409,8 @@ class FirebotChatHelpers { userDisplayName: msg.userDisplayName, rawText: msg.messageText, profilePicUrl: profilePicUrl, + pronouns: await FirebotPronounManager.getUserFriendlyPronounString(msg.userName), + color: TwitchEventSubChatHelpers.cacheUserColor(msg.userId), whisper: false, action: false, tagged: false, @@ -659,7 +426,7 @@ class FirebotChatHelpers { isSharedChatMessage: false // todo: check if automod messages have a way to associate them with shared chat }; - await this.enrichMessageWithRanksAndRoles(viewerFirebotChatMessage); + await TwitchEventSubChatHelpers.enrichMessageWithRanksAndRoles(viewerFirebotChatMessage); const { streamer, bot } = AccountAccess.getAccounts(); if ((streamer.loggedIn && (msg.messageText.includes(streamer.username) || msg.messageText.includes(streamer.displayName))) @@ -709,22 +476,6 @@ class FirebotChatHelpers { return viewerFirebotChatMessage; } - - private async enrichMessageWithRanksAndRoles(firebotChatMessage: FirebotChatMessage) { - const viewer = await viewerDatabase.getViewerById(firebotChatMessage.userId); - firebotChatMessage.viewerRanks = Object.entries(viewer?.ranks || {}).reduce((obj, [ladderId, rankId]) => { - const ladder = rankManager.getItem(ladderId); - if (ladder && ladder.settings.showBadgeInChat && rankId) { - obj[ladder.name] = ladder.ranks.find(r => r.id === rankId)?.name || "Unknown"; - } - return obj; - }, {} as Record); - - const customRoles = roleManager.getAllCustomRolesForViewer(firebotChatMessage.userId); - firebotChatMessage.viewerCustomRoles = customRoles - .filter(r => r.showBadgeInChat) - .map(r => r.name); - } } const chatHelpers = new FirebotChatHelpers(); diff --git a/src/backend/chat/chat-listeners/twitch-chat-listeners.js b/src/backend/chat/chat-listeners/twitch-chat-listeners.js index e38aaf7e5..32a4f7f47 100644 --- a/src/backend/chat/chat-listeners/twitch-chat-listeners.js +++ b/src/backend/chat/chat-listeners/twitch-chat-listeners.js @@ -1,11 +1,11 @@ "use strict"; -const frontendCommunicator = require("../../common/frontend-communicator"); const chatCommandHandler = require("../commands/chat-command-handler"); const chatHelpers = require("../chat-helpers"); const { AccountAccess } = require("../../common/account-access"); const { ActiveUserHandler } = require("../active-user-handler"); const { ChatModerationManager } = require("../moderation/chat-moderation-manager"); +const { FirebotFrontendChatHelpers } = require("../frontend-chat-helpers"); const { TwitchEventHandlers } = require("../../streaming-platforms/twitch/events"); const twitchRolesManager = require("../../roles/twitch-roles-manager"); const raidMessageChecker = require(".././moderation/raid-message-checker"); @@ -38,7 +38,7 @@ exports.setupChatListeners = (streamerChatClient, botChatClient) => { firebotChatMessage.isAnnouncement = true; firebotChatMessage.announcementColor = announcementInfo.color ?? "PRIMARY"; - frontendCommunicator.send("twitch:chat:message", firebotChatMessage); + FirebotFrontendChatHelpers.sendChatMessageToFrontend(firebotChatMessage); TwitchEventHandlers.announcement.triggerAnnouncement( firebotChatMessage.username, @@ -75,7 +75,7 @@ exports.setupChatListeners = (streamerChatClient, botChatClient) => { imageUrl: "https://static-cdn.jtvnw.net/automatic-reward-images/highlight-4.png" }; } - frontendCommunicator.send("twitch:chat:message", firebotChatMessage); + FirebotFrontendChatHelpers.sendChatMessageToFrontend(firebotChatMessage); exports.events.emit("chat-message", firebotChatMessage); const { ranCommand, command, userCommand } = await chatCommandHandler.handleChatMessage(firebotChatMessage); @@ -116,7 +116,7 @@ exports.setupChatListeners = (streamerChatClient, botChatClient) => { chatCommandHandler.handleChatMessage(firebotChatMessage); - frontendCommunicator.send("twitch:chat:message", firebotChatMessage); + FirebotFrontendChatHelpers.sendChatMessageToFrontend(firebotChatMessage); TwitchEventHandlers.whisper.triggerWhisper( msg.userInfo.userName, @@ -143,7 +143,7 @@ exports.setupChatListeners = (streamerChatClient, botChatClient) => { twitchRolesManager.removeVipFromVipList(msg.userInfo.userId); } - frontendCommunicator.send("twitch:chat:message", firebotChatMessage); + FirebotFrontendChatHelpers.sendChatMessageToFrontend(firebotChatMessage); const { ranCommand, command, userCommand } = await chatCommandHandler.handleChatMessage(firebotChatMessage); @@ -173,7 +173,7 @@ exports.setupChatListeners = (streamerChatClient, botChatClient) => { if (subInfo.message != null && subInfo.message.length > 0) { const firebotChatMessage = await chatHelpers.buildFirebotChatMessage(msg, subInfo.message); - frontendCommunicator.send("twitch:chat:message", firebotChatMessage); + FirebotFrontendChatHelpers.sendChatMessageToFrontend(firebotChatMessage); exports.events.emit("chat-message", firebotChatMessage); } diff --git a/src/backend/chat/frontend-chat-helpers.ts b/src/backend/chat/frontend-chat-helpers.ts new file mode 100644 index 000000000..35cc94669 --- /dev/null +++ b/src/backend/chat/frontend-chat-helpers.ts @@ -0,0 +1,200 @@ +import { encode } from "he"; +import type { + FirebotChatMessage, + FirebotChatMessagePart, + FirebotParsedMessagePart, + OverlayWidgetConfig +} from "../../types"; +import type { ChatWidgetSettings, ChatWidgetState } from "../overlay-widgets/builtin-types/chat/chat"; +import type { AdvancedChatWidgetSettings } from "../overlay-widgets/builtin-types/chat/chat-advanced"; +import overlayWidgetsManager from "../overlay-widgets/overlay-widgets-manager"; +import overlayWidgetConfigManager from "../overlay-widgets/overlay-widget-config-manager"; +import frontendCommunicator from "../common/frontend-communicator"; +import logger from "../logwrapper"; + +class FirebotFrontendChatHelpers { + private _pendingMessageCache: Record = { }; + + private sendChatMessageToChatWidget( + chatWidget: OverlayWidgetConfig, + chatMessage: FirebotChatMessage + ): void { + if (chatWidget.settings.delayMessages === true) { + if (this._pendingMessageCache[chatWidget.id].some(m => m === chatMessage.id)) { + // Remove it from the pending list so we know we've taken care of it + this._pendingMessageCache[chatWidget.id] = this._pendingMessageCache[chatWidget.id] + .filter(m => m !== chatMessage.id); + } else { + logger.info(`Chat message ${chatMessage.id} not in pending cache for widget ${chatWidget.id}; ignoring`); + return; + } + } + + const frontendChatMessage = { + ...chatMessage + }; + + frontendChatMessage.parts = chatMessage.parts.map((p: FirebotParsedMessagePart | FirebotChatMessagePart) => { + const part = { ...p }; + + if (part.type === "text" || part.type === "link") { + part.text = encode(part.text); + } + + return part; + }); + + const existingChatMessages = chatWidget.state?.chatMessages ?? []; + overlayWidgetConfigManager.setWidgetStateById(chatWidget.id, { + chatMessages: [...existingChatMessages.slice(-99), frontendChatMessage] + }); + + void overlayWidgetsManager.sendWidgetEventToOverlay( + "message", + chatWidget, + { + messageName: "chat-message", + messageData: { + chatMessage: frontendChatMessage + } + } + ); + + const messageTimeout = chatWidget.settings.messageTimeout; + if (chatWidget.settings.autoRemoveMessages === true && messageTimeout != null && messageTimeout > 0) { + setTimeout(() => { + this.deleteMessageFromChatWidget(chatWidget, chatMessage.id, true); + }, messageTimeout * 1000); + } + } + + sendChatMessageToFrontend(chatMessage: FirebotChatMessage): void { + frontendCommunicator.send("twitch:chat:message", chatMessage); + + if (chatMessage.whisper === true + || chatMessage.isAutoModHeld === true + || chatMessage.autoModStatus === "denied" + || chatMessage.autoModStatus === "expired" + ) { + return; + } + + chatMessage.timestamp = new Date().getTime(); + + const chatWidgets = overlayWidgetConfigManager.getConfigsOfType>("firebot:chat"); + const advancedChatWidgets = overlayWidgetConfigManager.getConfigsOfType>("firebot:chat-advanced"); + + for (const chatWidget of [...chatWidgets, ...advancedChatWidgets]) { + if (chatWidget.settings.delayMessages === true && chatWidget.settings.messageDelay) { + this._pendingMessageCache[chatWidget.id] ??= []; + + this._pendingMessageCache[chatWidget.id].push(chatMessage.id); + + setTimeout(() => { + this.sendChatMessageToChatWidget(chatWidget, chatMessage); + }, chatWidget.settings.messageDelay * 1000); + } else { + this.sendChatMessageToChatWidget(chatWidget, chatMessage); + } + } + } + + private deleteMessageFromChatWidget( + chatWidget: OverlayWidgetConfig, + messageId: string, + animate = false + ): void { + this._pendingMessageCache[chatWidget.id] = (this._pendingMessageCache[chatWidget.id] ?? []) + .filter(m => m !== messageId) ?? []; + + const chatMessages = (chatWidget.state?.chatMessages ?? []) + .filter(m => m.id !== messageId); + + overlayWidgetConfigManager.setWidgetStateById(chatWidget.id, { + chatMessages: chatMessages + }); + + void overlayWidgetsManager.sendWidgetEventToOverlay( + "message", + chatWidget, + { + messageName: "delete-message", + messageData: { + messageId, + animate + } + } + ); + } + + deleteMessageFromFrontend(messageId: string, animate = false): void { + frontendCommunicator.send("twitch:chat:message:deleted", messageId); + + const chatWidgets = overlayWidgetConfigManager.getConfigsOfType>("firebot:chat"); + const advancedChatWidgets = overlayWidgetConfigManager.getConfigsOfType>("firebot:chat-advanced"); + + for (const chatWidget of [...chatWidgets, ...advancedChatWidgets]) { + this.deleteMessageFromChatWidget(chatWidget, messageId, animate); + } + } + + deleteUserMessagesFromFrontend(username: string) { + frontendCommunicator.send("twitch:chat:user:delete-messages", username); + + const chatWidgets = overlayWidgetConfigManager.getConfigsOfType>("firebot:chat"); + const advancedChatWidgets = overlayWidgetConfigManager.getConfigsOfType>("firebot:chat-advanced"); + + for (const chatWidget of [...chatWidgets, ...advancedChatWidgets]) { + const chatMessages = (chatWidget.state?.chatMessages ?? []) + .filter(m => m.username !== username); + + overlayWidgetConfigManager.setWidgetStateById(chatWidget.id, { + chatMessages: chatMessages + }); + + void overlayWidgetsManager.sendWidgetEventToOverlay( + "message", + chatWidget, + { + messageName: "delete-user-messages", + messageData: { + username + } + } + ); + } + } + + clearChatFeed(moderatorName: string): void { + frontendCommunicator.send("twitch:chat:clear-feed", moderatorName); + + const chatWidgets = overlayWidgetConfigManager.getConfigsOfType>("firebot:chat"); + const advancedChatWidgets = overlayWidgetConfigManager.getConfigsOfType>("firebot:chat-advanced"); + + for (const chatWidget of [...chatWidgets, ...advancedChatWidgets]) { + overlayWidgetConfigManager.setWidgetStateById(chatWidget.id, { + chatMessages: null + }); + } + } + + updateMessageAutomodStatus( + messageId: string, + newStatus: FirebotChatMessage["autoModStatus"], + resolverName: string, + resolverId: string, + flaggedPhrases: string[] + ): void { + frontendCommunicator.send("twitch:chat:automod-update", { + messageId, + newStatus, + resolverName, + resolverId, + flaggedPhrases + }); + } +} + +const frontendChatHelpers = new FirebotFrontendChatHelpers(); + +export { frontendChatHelpers as FirebotFrontendChatHelpers }; \ No newline at end of file diff --git a/src/backend/chat/third-party/bttv.ts b/src/backend/chat/third-party/bttv.ts index cb74da677..0b68c95dc 100644 --- a/src/backend/chat/third-party/bttv.ts +++ b/src/backend/chat/third-party/bttv.ts @@ -21,7 +21,7 @@ export class BTTVEmoteProvider extends ThirdPartyEmoteProvider ({ - url: `https://cdn.betterttv.net/emote/${e.id}/1x`, + url: `https://cdn.betterttv.net/emote/${e.id}/3x`, code: e.code, animated: e.imageType && e.imageType.toLowerCase() === "gif", origin: this.providerName diff --git a/src/backend/chat/third-party/ffz.ts b/src/backend/chat/third-party/ffz.ts index d8c89fc82..927aeb854 100644 --- a/src/backend/chat/third-party/ffz.ts +++ b/src/backend/chat/third-party/ffz.ts @@ -3,6 +3,8 @@ import { ThirdPartyEmote, ThirdPartyEmoteProvider } from "./third-party-emote-pr type FFZEmotesResponse = Array<{ images: { "1x": string; + "2x": string; + "4x": string; }; code: string; imageType: string; @@ -18,7 +20,7 @@ export class FFZEmoteProvider extends ThirdPartyEmoteProvider private emoteMapper = (response: FFZEmotesResponse): ThirdPartyEmote[] => response?.map(e => ({ - url: e.images && e.images["1x"], + url: e.images && e.images["4x"] || e.images["2x"] || e.images["1x"] || "", code: e.code, animated: e.imageType?.toLowerCase() === "gif", origin: this.providerName diff --git a/src/backend/chat/twitch-chat.ts b/src/backend/chat/twitch-chat.ts index e5c06fb04..d220c09c2 100644 --- a/src/backend/chat/twitch-chat.ts +++ b/src/backend/chat/twitch-chat.ts @@ -6,7 +6,7 @@ import { ActiveUserHandler } from "./active-user-handler"; import { FirebotDeviceAuthProvider } from "../auth/firebot-device-auth-provider"; import { SharedChatCache } from "../streaming-platforms/twitch/chat/shared-chat-cache"; import { TwitchApi } from "../streaming-platforms/twitch/api"; -import chatHelpers from "./chat-helpers"; +import { TwitchEventSubChatHelpers } from "../streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers"; import chatRolesManager from "../roles/chat-roles-manager"; import twitchRolesManager from "../roles/twitch-roles-manager"; import chatterPoll from "../streaming-platforms/twitch/chatter-poll"; @@ -112,7 +112,7 @@ class TwitchChat extends EventEmitter { * This is just to cache badges/emotes/cheermotes * Fire and forget this so we can get everything else setup */ - void chatHelpers.cacheChatAssets(); + void TwitchEventSubChatHelpers.cacheChatAssets(); // Attempt to reload the known bot list in case it failed on start await chatRolesManager.cacheViewerListBots(); diff --git a/src/backend/common/EffectType.js b/src/backend/common/EffectType.js index 323a6ce16..dab900e57 100644 --- a/src/backend/common/EffectType.js +++ b/src/backend/common/EffectType.js @@ -10,6 +10,7 @@ const Trigger = { SCHEDULED_TASK: "scheduled_task", COUNTER: "counter", CHANNEL_REWARD: "channel_reward", + POWER_UP: "power_up", MANUAL: "manual" }; 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 411b59d83..bffcda73f 100644 --- a/src/backend/common/handlers/custom-scripts/custom-script-helpers.js +++ b/src/backend/common/handlers/custom-scripts/custom-script-helpers.js @@ -102,7 +102,6 @@ function buildModules(scriptManifest) { return { spawn: require("child_process").spawn, childProcess: require("child_process"), - fs: require("fs-extra"), path: require("path"), JsonDb: require("node-json-db").JsonDB, moment: require("moment"), diff --git a/src/backend/common/settings-manager.ts b/src/backend/common/settings-manager.ts index 624b2b873..3ebda974a 100644 --- a/src/backend/common/settings-manager.ts +++ b/src/backend/common/settings-manager.ts @@ -60,7 +60,7 @@ class SettingsManager extends EventEmitter { private handleCorruptSettingsFile() { logger.warn("settings.json file appears to be corrupt. Resetting file..."); - const settingsPath = this.getLoggedInProfilePath("settings.json"); + const settingsPath = path.join(dataAccess.getUserDataPath(), this.getLoggedInProfilePath("settings.json")); fs.writeFileSync(settingsPath, JSON.stringify({ settings: { firstTimeUse: false diff --git a/src/backend/crowbar-relay/crowbar-relay-websocket.ts b/src/backend/crowbar-relay/crowbar-relay-websocket.ts index ec9f4d0ab..c7b3b68bf 100644 --- a/src/backend/crowbar-relay/crowbar-relay-websocket.ts +++ b/src/backend/crowbar-relay/crowbar-relay-websocket.ts @@ -52,16 +52,33 @@ class CrowbarRelayWebSocket extends TypedEmitter<{ } }); + let pingTimeout: NodeJS.Timeout; + + function setPingTimeout() { + clearTimeout(pingTimeout); + pingTimeout = setTimeout(() => { + logger.warn("Crowbar Relay WebSocket ping timeout, reconnecting..."); + this.ws.reconnect(); + }, 75_000); + } + + setPingTimeout(); + this.ws.addEventListener("open", () => { logger.info("Crowbar Relay WebSocket connected!"); this.emit("ready"); }); + this.ws.addEventListener("ping", () => { + setPingTimeout(); + }); + this.ws.addEventListener("error", (err) => { logger.error("Crowbar Relay WebSocket errored", err); }); this.ws.addEventListener("close", (closedEvent) => { + clearTimeout(pingTimeout); const unauthorized = closedEvent.target?._ws?._req?.res?.statusCode === 401; if (unauthorized) { logger.error("Crowbar Relay WebSocket unauthorized!"); diff --git a/src/backend/effects/builtin-effect-loader.js b/src/backend/effects/builtin-effect-loader.js index eb3086a15..2dc9b6bee 100644 --- a/src/backend/effects/builtin-effect-loader.js +++ b/src/backend/effects/builtin-effect-loader.js @@ -44,7 +44,6 @@ exports.loadEffects = () => { 'play-sound', 'play-video', // No migration needed. 'overlay-alert', - 'random-effect', 'random-reddit-image', 'remove-user-metadata', 'reset-timer', @@ -52,7 +51,6 @@ exports.loadEffects = () => { 'run-command', 'run-program', 'send-custom-websocket-event', - 'sequential-effect', 'set-output', 'set-user-metadata', 'shoutout', @@ -80,7 +78,14 @@ exports.loadEffects = () => { 'overlay-widgets/update-progress-bar', 'overlay-widgets/update-dynamic-countdown', 'overlay-widgets/set-custom-widget-state', - 'overlay-widgets/send-message-to-custom-widget' + 'overlay-widgets/send-message-to-custom-widget', + + // Deprecated - remove after min 90 days post-release + 'deprecated/show-text', + + // Deprecated (no remove date) + 'deprecated/random-effect', + 'deprecated/sequential-effect', ].forEach((filename) => { const definition = require(`./builtin/${filename}`); EffectManager.registerEffect(definition); diff --git a/src/backend/effects/builtin/clips.js b/src/backend/effects/builtin/clips.js index 185d19d83..8d4faeea2 100644 --- a/src/backend/effects/builtin/clips.js +++ b/src/backend/effects/builtin/clips.js @@ -35,7 +35,29 @@ const clip = { }, globalSettings: {}, optionsTemplate: ` - + + + + + + +