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..e62595503 --- /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 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": { 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 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/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/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); } } 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 = []; 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) { 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"); 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/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() { 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