From 606c3d08bbf5a83048f88a551c8aa812977de6c6 Mon Sep 17 00:00:00 2001 From: Zack Williamson Date: Sun, 14 Dec 2025 00:53:34 -0500 Subject: [PATCH 01/10] 5.65.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4412d135f..e780a0b57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebotv5", - "version": "5.65.2", + "version": "5.65.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firebotv5", - "version": "5.65.2", + "version": "5.65.3", "license": "GPL-3.0", "dependencies": { "@aws-sdk/client-polly": "^3.26.0", diff --git a/package.json b/package.json index 421470371..37ca60f4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebotv5", - "version": "5.65.2", + "version": "5.65.3", "description": "Powerful all-in-one bot for Twitch streamers.", "main": "build/main.js", "scripts": { From 23615951db431cc1b881a6259e4695b13335d02b Mon Sep 17 00:00:00 2001 From: Zack Williamson Date: Sun, 14 Dec 2025 01:53:26 -0500 Subject: [PATCH 02/10] fix: move all shutdown logic to willQuit --- .../electron/events/will-quit.ts | 38 ++++++++++++++++--- .../electron/events/windows-all-closed.ts | 32 +--------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/backend/app-management/electron/events/will-quit.ts b/src/backend/app-management/electron/events/will-quit.ts index e68fb2889..7115a6efd 100644 --- a/src/backend/app-management/electron/events/will-quit.ts +++ b/src/backend/app-management/electron/events/will-quit.ts @@ -5,19 +5,47 @@ async function cleanup() { const { handleProfileDeletion, handleProfileRename } = await import("../../../app-management/profile-tasks"); handleProfileRename(); handleProfileDeletion(); +} + +export async function willQuit(event: Event) { + logger.debug("Will quit event triggered"); + + event.preventDefault(); const { EventManager } = await import("../../../events/event-manager"); await EventManager.triggerEvent("firebot", "before-firebot-closed", { username: "Firebot" }); -} -export async function willQuit(event: Event) { - const { AppCloseListenerManager } = await import("../../app-close-listener-manager"); + const { BackupManager } = await import("../../../backup-manager"); + const { CustomVariableManager } = await import("../../../common/custom-variable-manager"); + const { HotkeyManager } = await import("../../../hotkeys/hotkey-manager"); + const { ScheduledTaskManager } = await import("../../../timers/scheduled-task-manager"); + const { SettingsManager } = await import("../../../common/settings-manager"); + const customScriptRunner = await import("../../../common/handlers/custom-scripts/custom-script-runner"); + const viewerOnlineStatusManager = (await import("../../../viewers/viewer-online-status-manager")).default; - logger.debug("Will quit event triggered"); + // Stop all scheduled tasks + ScheduledTaskManager.stop(); - event.preventDefault(); + // Stop all custom scripts so they can clean up + await customScriptRunner.stopAllScripts(); + + // Unregister all shortcuts. + HotkeyManager.unregisterAllHotkeys(); + + // Persist custom variables + CustomVariableManager.persistVariablesToFile(); + + // Set all users to offline + await viewerOnlineStatusManager.setAllViewersOffline(); + + if (SettingsManager.getSetting("BackupOnExit")) { + // Make a backup + await BackupManager.startBackup(false); + } + + const { AppCloseListenerManager } = await import("../../app-close-listener-manager"); logger.debug("Running app close listeners..."); diff --git a/src/backend/app-management/electron/events/windows-all-closed.ts b/src/backend/app-management/electron/events/windows-all-closed.ts index 318ddcb6d..95793974c 100644 --- a/src/backend/app-management/electron/events/windows-all-closed.ts +++ b/src/backend/app-management/electron/events/windows-all-closed.ts @@ -1,36 +1,8 @@ +import { app } from "electron"; import logger from "../../../logwrapper"; -export async function windowsAllClosed() { - const { app } = await import("electron"); - const { BackupManager } = await import("../../../backup-manager"); - const { CustomVariableManager } = await import("../../../common/custom-variable-manager"); - const { HotkeyManager } = await import("../../../hotkeys/hotkey-manager"); - const { ScheduledTaskManager } = await import("../../../timers/scheduled-task-manager"); - const { SettingsManager } = await import("../../../common/settings-manager"); - const customScriptRunner = await import("../../../common/handlers/custom-scripts/custom-script-runner"); - const viewerOnlineStatusManager = (await import("../../../viewers/viewer-online-status-manager")).default; - +export function windowsAllClosed() { logger.debug("All windows closed triggered"); - // Stop all scheduled tasks - ScheduledTaskManager.stop(); - - // Stop all custom scripts so they can clean up - await customScriptRunner.stopAllScripts(); - - // Unregister all shortcuts. - HotkeyManager.unregisterAllHotkeys(); - - // Persist custom variables - CustomVariableManager.persistVariablesToFile(); - - // Set all users to offline - await viewerOnlineStatusManager.setAllViewersOffline(); - - if (SettingsManager.getSetting("BackupOnExit")) { - // Make a backup - await BackupManager.startBackup(false); - } - app.quit(); }; \ No newline at end of file From 1dba483b7fcee2263930a5afb9185d4b3cb54119 Mon Sep 17 00:00:00 2001 From: Zack Williamson Date: Sun, 14 Dec 2025 11:57:40 -0500 Subject: [PATCH 03/10] chore: persist custom vars to disk on updates --- src/backend/common/custom-variable-manager.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/backend/common/custom-variable-manager.ts b/src/backend/common/custom-variable-manager.ts index 90a6bf756..1d59b308c 100644 --- a/src/backend/common/custom-variable-manager.ts +++ b/src/backend/common/custom-variable-manager.ts @@ -67,6 +67,8 @@ class CustomVariableManager extends TypedEmitter<{ value, ttl: this._cache.getTtl(key) }); + + this.persistVariablesToFile(); } private onCustomVariableExpire(key: string, value: unknown): void { @@ -80,6 +82,8 @@ class CustomVariableManager extends TypedEmitter<{ key, value }); + + this.persistVariablesToFile(); } private onCustomVariableDelete(key: string, value: unknown): void { @@ -89,6 +93,8 @@ class CustomVariableManager extends TypedEmitter<{ }); frontendCommunicator.sendToVariableInspector("custom-variables:deleted", key); + + this.persistVariablesToFile(); }; private getVariableCacheDb(): JsonDB { @@ -112,6 +118,7 @@ class CustomVariableManager extends TypedEmitter<{ const db = this.getVariableCacheDb(); const persistAllVars = SettingsManager.getSetting("PersistCustomVariables"); if (persistAllVars) { + logger.debug("Persisting all custom variables to file"); db.push("/", this._cache.data); } else { const dataToPersist = Object.entries(this._cache.data as FirebotCacheData).reduce((acc, [key, { t, v, meta }]) => { @@ -120,6 +127,7 @@ class CustomVariableManager extends TypedEmitter<{ } return acc; }, {} as FirebotCacheData); + logger.debug("Persisting specified custom variables to file"); db.push("/", dataToPersist); } } From 68b8428cba3ae4bacf88558a359e3144c398b22c Mon Sep 17 00:00:00 2001 From: ebiggz Date: Sun, 14 Dec 2025 18:32:36 -0700 Subject: [PATCH 04/10] fix: update overlay widget settings effect modifying selected config object in memory #3383 --- .../overlay-widgets/update-overlay-widget-settings.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/effects/builtin/overlay-widgets/update-overlay-widget-settings.ts b/src/backend/effects/builtin/overlay-widgets/update-overlay-widget-settings.ts index ac33132cc..32e029198 100644 --- a/src/backend/effects/builtin/overlay-widgets/update-overlay-widget-settings.ts +++ b/src/backend/effects/builtin/overlay-widgets/update-overlay-widget-settings.ts @@ -178,7 +178,10 @@ const model: EffectType<{ $scope.typeHasSettings = false; function loadSelectedConfig(id: string) { - $scope.selectedConfig = overlayWidgetsService.getOverlayWidgetConfig(id); + const foundConfig = overlayWidgetsService.getOverlayWidgetConfig(id); + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + $scope.selectedConfig = foundConfig ? angular.copy(foundConfig) : null; if ($scope.selectedConfig == null) { $scope.selectedType = null; $scope.settingsSchema = []; From fd95b7b09211d8e01ae424c2795fa1cdcb6ca638 Mon Sep 17 00:00:00 2001 From: CaveMobster Date: Tue, 16 Dec 2025 20:06:48 +0100 Subject: [PATCH 05/10] feat(roles): add a confirmation modal to the role delete button --- .../modals/roles/addOrEditCustomRoleModal.js | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/gui/app/directives/modals/roles/addOrEditCustomRoleModal.js b/src/gui/app/directives/modals/roles/addOrEditCustomRoleModal.js index cfc027fd8..c3c1c32f1 100644 --- a/src/gui/app/directives/modals/roles/addOrEditCustomRoleModal.js +++ b/src/gui/app/directives/modals/roles/addOrEditCustomRoleModal.js @@ -64,7 +64,7 @@ @@ -138,17 +138,27 @@ } }; - $ctrl.delete = function() { + $ctrl.showRoleDeleteModal = function(role) { if ($ctrl.isNewRole) { return; } - $ctrl.close({ - $value: { - role: $ctrl.role, - action: "delete" - } - }); + utilityService + .showConfirmationModal({ + title: "Delete Role", + question: "Are you sure you want to delete this role?", + confirmLabel: "Delete" + }) + .then((confirmed) => { + if (confirmed) { + $ctrl.close({ + $value: { + role: $ctrl.role, + action: "delete" + } + }); + } + }); }; $ctrl.save = function() { From 88e35cc35f5d93b4d021797a3c122aa9abda734b Mon Sep 17 00:00:00 2001 From: CaveMobster Date: Tue, 16 Dec 2025 22:33:36 +0100 Subject: [PATCH 06/10] feat(effects): add clarification about volume to the TTS effect --- src/backend/effects/builtin/text-to-speech.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/effects/builtin/text-to-speech.ts b/src/backend/effects/builtin/text-to-speech.ts index da65ae8a2..77e1839be 100644 --- a/src/backend/effects/builtin/text-to-speech.ts +++ b/src/backend/effects/builtin/text-to-speech.ts @@ -41,6 +41,13 @@ const effect: EffectType<{ style="margin: 0px 15px 0px 0px" /> + + +
+

Text-To-Speech volume can only be adjusted globally.

+

Go to Settings -> TTS to change the volume.

+
+
`, optionsController: ($scope, ttsService) => { if ($scope.effect.voiceId == null) { From 5a574071b7915209ee2157401dcce6c7230eee5e Mon Sep 17 00:00:00 2001 From: Zack Williamson Date: Wed, 17 Dec 2025 02:28:04 -0500 Subject: [PATCH 07/10] fix(backups): reset backup path if not found --- src/backend/backup-manager.ts | 7 +++++++ src/gui/app/app-main.js | 7 ++++++- src/types/settings.ts | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/backend/backup-manager.ts b/src/backend/backup-manager.ts index 7ae7a3bb9..1d9036986 100644 --- a/src/backend/backup-manager.ts +++ b/src/backend/backup-manager.ts @@ -182,6 +182,13 @@ class BackupManager { manualActivation ? "_manual" : "" }.${fileExtension}`; + if (!fs.existsSync(this._backupFolderPath)) { + logger.warn(`Backup path ${this._backupFolderPath} does not exist. Resetting to default.`); + SettingsManager.deleteSetting("BackupLocation"); + this.updateBackupFolderPath(); + SettingsManager.saveSetting("BackupLocationReset", true); + } + const output = fs.createWriteStream(path.join(this._backupFolderPath, filename)); const archive = archiver(fileExtension, { zlib: { level: 9 } diff --git a/src/gui/app/app-main.js b/src/gui/app/app-main.js index 213a6cdcf..4717656d5 100644 --- a/src/gui/app/app-main.js +++ b/src/gui/app/app-main.js @@ -261,7 +261,7 @@ }); app.controller("MainController", function($scope, $rootScope, $timeout, connectionService, utilityService, - settingsService, backupService, sidebarManager, logger, backendCommunicator, fontManager, ngToast, watcherCountService) { + settingsService, backupService, sidebarManager, logger, backendCommunicator, fontManager, ngToast, modalFactory) { $rootScope.showSpinner = true; $scope.fontAwesome5KitUrl = `https://kit.fontawesome.com/${secrets.fontAwesome5KitId}.js`; @@ -516,6 +516,11 @@ keyboard: false, backdrop: "static" });*/ + + if (settingsService.getSetting("BackupLocationReset") === true) { + modalFactory.showInfoModal("Your previous backup location could not be found. Backup location has been reset to default. You can change it in Settings > Backups."); + settingsService.deleteSetting("BackupLocationReset"); + } }); // This adds a filter that we can use for ng-repeat, useful when we want to paginate something diff --git a/src/types/settings.ts b/src/types/settings.ts index 0bda3b5e2..f58cf573a 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -31,6 +31,7 @@ export type FirebotSettingsTypes = { BackupIgnoreResources: boolean; BackupKeepAll: boolean; BackupLocation: string; + BackupLocationReset: boolean; BackupOnceADay: boolean; BackupOnExit: boolean; ChatAlternateBackgrounds: boolean; @@ -129,6 +130,7 @@ export const FirebotGlobalSettings: Partial Date: Wed, 17 Dec 2025 22:22:08 -0500 Subject: [PATCH 08/10] fix(chat): detect lead mods --- src/backend/chat/active-user-handler.ts | 3 ++- src/backend/chat/chat-helpers.ts | 2 +- .../twitch/api/eventsub/eventsub-chat-helpers.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/backend/chat/active-user-handler.ts b/src/backend/chat/active-user-handler.ts index c78b45906..8b6ddac3c 100644 --- a/src/backend/chat/active-user-handler.ts +++ b/src/backend/chat/active-user-handler.ts @@ -33,6 +33,7 @@ type ChatUser = { isMod?: boolean; isVip?: boolean; online?: boolean; + badges?: Map; }; type Events = { @@ -238,7 +239,7 @@ class ActiveUserHandler extends TypedEmitter { twitchRoles: [ ...(chatUser.isBroadcaster ? ['broadcaster'] : []), ...(chatUser.isFounder || chatUser.isSubscriber ? ['sub'] : []), - ...(chatUser.isMod ? ['mod'] : []), + ...(chatUser.isMod || chatUser.badges?.has("lead_moderator") ? ['mod'] : []), ...(chatUser.isVip ? ['vip'] : []) ], profilePicUrl: (await chatHelpers.getUserProfilePicUrl(chatUser.userId)), diff --git a/src/backend/chat/chat-helpers.ts b/src/backend/chat/chat-helpers.ts index 7a44ae9cb..66d964249 100644 --- a/src/backend/chat/chat-helpers.ts +++ b/src/backend/chat/chat-helpers.ts @@ -569,7 +569,7 @@ class FirebotChatHelpers { } firebotChatMessage.isFounder = msg.userInfo.isFounder; - firebotChatMessage.isMod = msg.userInfo.isMod; + firebotChatMessage.isMod = msg.userInfo.isMod || msg.userInfo.badges.has("lead_moderator"); firebotChatMessage.isSubscriber = msg.userInfo.isSubscriber; firebotChatMessage.isVip = msg.userInfo.isVip; diff --git a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts index d4148adc0..021f21d34 100644 --- a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts +++ b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts @@ -597,7 +597,7 @@ class TwitchEventSubChatHelpers { chatMessage.parts = messageParts; chatMessage.isFounder = chatMessage.badges.some(b => b.title === "founder"); - chatMessage.isMod = chatMessage.badges.some(b => b.title === "moderator"); + chatMessage.isMod = chatMessage.badges.some(b => b.title === "moderator" || b.title === "lead_moderator"); chatMessage.isVip = chatMessage.badges.some(b => b.title === "vip"); chatMessage.isSubscriber = chatMessage.isFounder || chatMessage.badges.some(b => b.title === "subscriber"); From ca325e6d8cc0fbb5df4d6d2ba289db85aa9342c4 Mon Sep 17 00:00:00 2001 From: ebiggz Date: Wed, 17 Dec 2025 21:54:51 -0700 Subject: [PATCH 09/10] chore: add macos x64 install instructions + include .txt file extension when adding instructions to dmg --- grunt/compile.js | 5 +++-- macos-arm64-install-instructions.txt | 2 ++ macos-x64-install-instructions.txt | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 macos-x64-install-instructions.txt diff --git a/grunt/compile.js b/grunt/compile.js index 36e8262ac..c5cb59f93 100644 --- a/grunt/compile.js +++ b/grunt/compile.js @@ -78,7 +78,7 @@ module.exports = function (grunt) { x: 320, y: 240, type: 'file', path: config.installInstructionsPath, - name: 'Install Instructions' + name: 'Install Instructions.txt' }] : []) ]; @@ -144,7 +144,8 @@ module.exports = function (grunt) { name: `firebot-v${version}-macos-x64`, title: "Firebot Installer", icon: macDmgIcon, - background: macDmgBg + background: macDmgBg, + installInstructionsPath: path.resolve(__dirname, '../macos-x64-install-instructions.txt') }, arm64: { appPath: macArmPathIn, diff --git a/macos-arm64-install-instructions.txt b/macos-arm64-install-instructions.txt index 4f5a0f3cc..2a4e31d82 100644 --- a/macos-arm64-install-instructions.txt +++ b/macos-arm64-install-instructions.txt @@ -1,3 +1,5 @@ +Since Firebot is not notarized by Apple, macOS Gatekeeper will block it on first launch. Follow these steps to allow it to run: + 1. Drag Firebot into the Applications folder 2. Open the Terminal app and run the following command (copy/paste and hit enter): xattr -c /Applications/Firebot.app \ No newline at end of file diff --git a/macos-x64-install-instructions.txt b/macos-x64-install-instructions.txt new file mode 100644 index 000000000..2034f0a7f --- /dev/null +++ b/macos-x64-install-instructions.txt @@ -0,0 +1,9 @@ +Since Firebot is not notarized by Apple, macOS Gatekeeper will block it on first launch. Follow these steps to allow it to run: + +1. Drag Firebot into the Applications folder +2. Run Firebot +3. When the "Firebot Not Opened" alert appears, click **Done** (do NOT click "Move to Trash") +4. Open System Settings +5. Navigate to Privacy & Security +6. Scroll down and click "Open Anyway" next to "Firebot was blocked" +7. Click "Open Anyway"" in the confirmation dialog that appears \ No newline at end of file From 8f6f65c1784c076917b7d4900181d7e6647cd945 Mon Sep 17 00:00:00 2001 From: ebiggz Date: Wed, 17 Dec 2025 22:14:29 -0700 Subject: [PATCH 10/10] chore: further macos install instruction clean up --- macos-x64-install-instructions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macos-x64-install-instructions.txt b/macos-x64-install-instructions.txt index 2034f0a7f..e62595503 100644 --- a/macos-x64-install-instructions.txt +++ b/macos-x64-install-instructions.txt @@ -2,7 +2,7 @@ Since Firebot is not notarized by Apple, macOS Gatekeeper will block it on first 1. Drag Firebot into the Applications folder 2. Run Firebot -3. When the "Firebot Not Opened" alert appears, click **Done** (do NOT click "Move to Trash") +3. When the "Firebot Not Opened" alert appears, click "Done" (do NOT click "Move to Trash") 4. Open System Settings 5. Navigate to Privacy & Security 6. Scroll down and click "Open Anyway" next to "Firebot was blocked"