Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/SUPPORT.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
## Support Policy

We will always support the latest stable Firebot release. Previous stable releases will be supported for **no more than 30 days** after a newer stable update has been released. After 30 days, you must be on a supported version in order to receive support via our official channels (Discord, GitHub, etc.).

Pre-release versions (e.g. betas) are no longer supported **immediately** upon a corresponding stable release or if superseded by a newer pre-release (e.g. `5.66.0-beta1` would immediately become unsupported upon release of either `5.66.0-beta2` or a `5.66.0` stable release). Nightly releases continue to receive limited support due to their potentially unstable nature.


### Need help or have a question?

Please feel free to stop by our [Discord](https://discord.gg/crowbartools-372817064034959370) or connect with us on [Bluesky](https://bsky.app/profile/firebot.app)!
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firebotv5",
"version": "5.65.1",
"version": "5.65.2",
"description": "Powerful all-in-one bot for Twitch streamers.",
"main": "build/main.js",
"scripts": {
Expand Down
18 changes: 18 additions & 0 deletions src/backend/app-management/electron/window-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const logger = require("../../logwrapper");

const EventEmitter = require("events");

const { copyDebugInfoToClipboard } = require("../../common/debug-info");

/**
* Firebot's main window
* Keeps a global reference of the window object, if you don't, the window will
Expand Down Expand Up @@ -196,6 +198,12 @@ async function createAppMenu() {

const overlayInstances = SettingsManager.getSetting("OverlayInstances");

/**
* Steps to get new icon images:
* - Select icon from https://pictogrammers.com/library/mdi/
* - Do an Advanced PNG Export with 48x48 size and a white foreground
*/

/**
* @type {Electron.MenuItemConstructorOptions[]}
*/
Expand Down Expand Up @@ -474,6 +482,16 @@ async function createAppMenu() {
{
type: 'separator'
},
{
label: 'Copy Debug Info...',
click: () => {
copyDebugInfoToClipboard();
},
icon: await createIconImage("../../../gui/images/icons/mdi/bug-outline.png")
},
{
type: 'separator'
},
{
label: 'About Firebot...',
click: () => {
Expand Down
3 changes: 3 additions & 0 deletions src/backend/common/common-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { app, dialog, shell, autoUpdater } = require("electron");
const os = require('os');
const logger = require("../logwrapper");
const { restartApp } = require("../app-management/electron/app-helpers");
const { copyDebugInfoToClipboard } = require("../common/debug-info");

function getLocalIpAddress() {
try {
Expand Down Expand Up @@ -136,4 +137,6 @@ exports.setupCommonListeners = () => {

autoUpdater.quitAndInstall();
});

frontendCommunicator.on("copy-debug-info-to-clipboard", copyDebugInfoToClipboard);
};
80 changes: 80 additions & 0 deletions src/backend/common/debug-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@


import { app } from "electron";
import os from "os";
import frontendCommunicator from "./frontend-communicator";
import ConnectionManager from "./connection-manager";
import { AccountAccess } from "./account-access";
import HttpServerManager from "../../server/http-server-manager";
import WebsocketServerManager from "../../server/websocket-server-manager";
import startupScriptsManager from "../common/handlers/custom-scripts/startup-scripts-manager";
import { isConnected } from "../integrations/builtin/obs/obs-remote";

function getOsName(platform: NodeJS.Platform): string {
if (platform === "darwin") {
return "macOS";
}

if (platform === "win32") {
return "Windows";
}

return "Linux";
}

async function getDebugInfoString(): Promise<string> {
const appVersion = app.getVersion();

const electronVersion = process.versions.electron ?? "unknown";
const nodeVersion = process.versions.node ?? process.version;

const osName = getOsName(process.platform);
const osVersion = typeof process.getSystemVersion === "function" ? process.getSystemVersion() : os.release();
const osArch = os.arch();

const { locale } = Intl.DateTimeFormat().resolvedOptions();

const accounts = AccountAccess.getAccounts();
const streamerLoggedIn = accounts.streamer.loggedIn ? "Yes" : "No";
const botLoggedIn = accounts.bot.loggedIn ? "Yes" : "No";

const connectedToTwitch = ConnectionManager.chatIsConnected() ? "Connected" : "Disconnected";
const connectedToOBS = isConnected() ? "Connected" : "Disconnected";

const httpServerStatus = HttpServerManager.isDefaultServerStarted ? "Running" : "Stopped";
const websocketClients = WebsocketServerManager.getNumberOfOverlayClients();

const startupScripts = Object.values(startupScriptsManager.getLoadedStartupScripts());

return [
"Firebot Debug Info",
"------------------",
`OS: ${osName} ${osVersion} (${osArch})`,
`Firebot: ${appVersion}`,
`Electron: ${electronVersion}`,
`Node: ${nodeVersion}`,
`Locale: ${locale}\n`,
'Accounts:',
` - Streamer: ${streamerLoggedIn}`,
` - Bot: ${botLoggedIn}\n`,
'Connections:',
` - Twitch: ${connectedToTwitch}`,
` - OBS: ${connectedToOBS}\n`,
'Server:',
` - HTTP Server: ${httpServerStatus}`,
` - Overlay Clients: ${websocketClients}\n`,
'Plugins:',
startupScripts.length === 0
? " - None"
: startupScripts.map(script => ` - ${script.name}`).join("\n")
].join("\n");
}

export async function copyDebugInfoToClipboard() {
const debugInfo = await getDebugInfoString();

frontendCommunicator.send("copy-to-clipboard", {
text: debugInfo,
toastMessage: "Debug information copied to clipboard"
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ function getStartupScriptData(startupScriptDataId) {
return startupScripts[startupScriptDataId];
}

function getLoadedStartupScripts() {
return { ...startupScripts };
}

/**
* Turns startup script data into valid Custom Script effects and runs them
*/
Expand All @@ -123,4 +127,5 @@ frontendCommunicator.on("deleteStartupScriptData", (startupScriptDataId) => {

exports.runStartupScripts = runStartupScripts;
exports.loadStartupConfig = loadStartupConfig;
exports.getStartupScriptData = getStartupScriptData;
exports.getStartupScriptData = getStartupScriptData;
exports.getLoadedStartupScripts = getLoadedStartupScripts;
16 changes: 12 additions & 4 deletions src/backend/common/profile-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { app } from "electron";
import { app, dialog } from "electron";
import { JsonDB } from "node-json-db";
import fs from "fs";
import path from "path";
Expand Down Expand Up @@ -104,9 +104,17 @@ class ProfileManager {
activeProfiles.push(profileId);

// Push our new profile to settings.
globalSettingsDb.push("/profiles/activeProfiles", activeProfiles);
globalSettingsDb.push("/profiles/loggedInProfile", profileId);
logger.info(`New profile created: ${profileId}.${restart ? " Restarting." : ""}`);
try {
globalSettingsDb.push("/profiles/activeProfiles", activeProfiles);
globalSettingsDb.push("/profiles/loggedInProfile", profileId);
logger.info(`New profile created: ${profileId}.${restart ? " Restarting." : ""}`);
} catch (error) {
const errorMessage = (error as Error).name === "DatabaseError" ? error?.inner?.message ?? error.stack : error;
logger.error(`Error saving ${profileId} profile to global settings. Is the file locked or corrupted?`, errorMessage);
dialog.showErrorBox("Error Loading Profile", `An error occurred while trying to load your ${profileId} profile. Please try starting Firebot again. If this issue continues, please reach out on our Discord for support.`);
app.quit();
return;
}

// Log the new profile in and (optionally) restart app.
this.logInProfile(profileId, restart);
Expand Down
4 changes: 3 additions & 1 deletion src/backend/common/settings-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ class SettingsManager extends EventEmitter {
if (defaultValue !== undefined) {
this.settingsCache[settingPath] = defaultValue;
}
if ((err as Error).name !== "DataError") {
if ((err as Error).name === "DatabaseError") {
logger.error(`Failed to read "${settingPath}" in global settings file. File may be corrupt.`, err?.inner?.message ?? err.stack);
} else if ((err as Error).name !== "DataError") {
logger.warn(err);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/backend/effects/builtin/shoutout.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ const effect = {
const boxArtId = `box-art-${uniqueId}`;

const shoutoutElement = `
<div>
<div id="${wrapperId}" class="firebot-shoutout-wrapper" style="background: linear-gradient(135deg, ${data.bgColor1} 0%, ${data.bgColor2} 100%); transform: scale(${scale});${data.zIndex ? ` position: relative; z-index: ${data.zIndex};` : ''}">
<div style="scale: ${scale};">
<div id="${wrapperId}" class="firebot-shoutout-wrapper" style="background: linear-gradient(135deg, ${data.bgColor1} 0%, ${data.bgColor2} 100%);${data.zIndex ? ` position: relative; z-index: ${data.zIndex};` : ''}">

<div style="position:relative;">
<div id="${avatarWrapperId}" class="firebot-shoutout-avatar-wrapper firebot-shoutout-padding">
Expand Down
4 changes: 3 additions & 1 deletion src/backend/restrictions/restriction-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class RestrictionsManager extends TypedEmitter<Events> {
}
return Promise.resolve();

} else if (restrictionData.mode === "all") {
} else if (restrictionData.mode === "all" || restrictionData.mode == null) {
const predicatePromises = [];
for (const restriction of restrictions) {
const restrictionDef = this.getRestrictionById(restriction.type);
Expand All @@ -165,6 +165,8 @@ class RestrictionsManager extends TypedEmitter<Events> {
}
});
}
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors, @typescript-eslint/restrict-template-expressions
return Promise.reject(`Invalid restriction mode '${restrictionData.mode}'`);
}
}

Expand Down
59 changes: 14 additions & 45 deletions src/gui/app/app-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,59 +279,28 @@
}
};

$rootScope.copyTextToClipboard = function(text) {
const textArea = document.createElement("textarea");
// Place in top-left corner of screen regardless of scroll position.
textArea.style.position = "fixed";
textArea.style.top = 0;
textArea.style.left = 0;

// Ensure it has a small width and height. Setting to 1px / 1em
// doesn't work as this gives a negative w/h on some browsers.
textArea.style.width = "2em";
textArea.style.height = "2em";

// We don't need padding, reducing the size if it does flash render.
textArea.style.padding = 0;

// Clean up any borders.
textArea.style.border = "none";
textArea.style.outline = "none";
textArea.style.boxShadow = "none";

// Avoid flash of white box if rendered for any reason.
textArea.style.background = "transparent";

textArea.value = text;

document.body.appendChild(textArea);

textArea.select();

try {
const successful = document.execCommand("copy");
const msg = successful ? "successful" : "unsuccessful";
logger.info(`Copying text command was ${msg}`);
} catch {
logger.error("Oops, unable to copy text to clipboard.");
}
$rootScope.copyTextToClipboard = function(text, toastConfig = { show: false }) {
navigator.clipboard.writeText(text).then(function() {
logger.info("Text copied to clipboard");

if (toastConfig?.show) {
ngToast.create({
className: 'info',
content: toastConfig.message || `Copied '${text}' to clipboard`
});
}

document.body.removeChild(textArea);
}, function(err) {
logger.error("Could not copy text: ", err);
});
};

backendCommunicator.on("copy-to-clipboard", (data) => {
if (!data?.text?.length) {
return;
}

$rootScope.copyTextToClipboard(data.text);

if (!data.silent) {
ngToast.create({
className: 'info',
content: data.toastMessage || `Copied '${data.text}' to clipboard`
});
}
$rootScope.copyTextToClipboard(data.text, { show: !data.silent, message: data.toastMessage });

return;
});
Expand Down
9 changes: 8 additions & 1 deletion src/gui/app/directives/modals/misc/about-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,28 @@
<a href ng-click="$root.openLinkExternally('https://firebot.app/testimonial-submission')">Submit a Testimonial</a>
</p>
</section>
<section>
<button class="btn btn-sm btn-default-outlined" style="width: 100%;" ng-click="$ctrl.copyDebugInfoToClipboard()">Copy Debug Info</button>
</button>
</div>
`,
bindings: {
resolve: "<",
close: "&",
dismiss: "&"
},
controller: function() {
controller: function(backendCommunicator) {
const $ctrl = this;

$ctrl.$onInit = function() {
$ctrl.version = firebotAppDetails.version;
$ctrl.osType = firebotAppDetails.os.type;
$ctrl.osVersion = firebotAppDetails.os.release;
};

$ctrl.copyDebugInfoToClipboard = function() {
backendCommunicator.send("copy-debug-info-to-clipboard");
};
}
});
}());
Loading