Skip to content

Commit 9e86111

Browse files
committed
Merge branch 'v5' into firebot-importer
2 parents aa0edcf + 5d83433 commit 9e86111

File tree

208 files changed

+6598
-3618
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

208 files changed

+6598
-3618
lines changed

.vscode/launch.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
"request": "launch",
77
"name": "Electron: Main",
88
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
9-
"runtimeArgs": [
10-
".",
11-
"--remote-debugging-port=9223"
12-
],
9+
"runtimeArgs": [".", "--remote-debugging-port=9223"],
1310
"windows": {
1411
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
1512
},
1613
"outputCapture": "std",
17-
"preLaunchTask": "grunt-prep"
14+
"preLaunchTask": "grunt-prep",
15+
"outFiles": ["${workspaceFolder}/build/**/*.js"],
16+
"resolveSourceMapLocations": [
17+
"${workspaceFolder}/**",
18+
"!**/node_modules/**"
19+
]
1820
},
1921
{
2022
"name": "Electron: Renderer",

package-lock.json

Lines changed: 50 additions & 799 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firebotv5",
3-
"version": "5.65.0",
3+
"version": "5.65.0-beta1",
44
"description": "Powerful all-in-one bot for Twitch streamers.",
55
"main": "build/main.js",
66
"scripts": {
@@ -24,6 +24,7 @@
2424
"@eslint/js": "^9.37.0",
2525
"@octokit/rest": "^22.0.0",
2626
"@stylistic/eslint-plugin": "^5.4.0",
27+
"@types/angular": "^1.8.4",
2728
"@types/archiver": "^6.0.3",
2829
"@types/escape-html": "^1.0.4",
2930
"@types/express": "^5.0.3",
@@ -52,7 +53,6 @@
5253
},
5354
"dependencies": {
5455
"@aws-sdk/client-polly": "^3.26.0",
55-
"@crowbartools/firebot-custom-scripts-types": "^5.53.2-6",
5656
"@seald-io/nedb": "^4.0.4",
5757
"@twurple/api": "^7.4.0",
5858
"@twurple/auth": "^7.4.0",
@@ -88,7 +88,7 @@
8888
"cronstrue": "^3.3.0",
8989
"custom-electron-titlebar": "^4.1.0",
9090
"deepmerge": "^4.2.2",
91-
"dompurify": "^2.2.2",
91+
"dompurify": "^3.3.0",
9292
"ejs": "^3.1.10",
9393
"electron-oauth2": "^3.0.0",
9494
"electron-regedit": "^2.0.0",

src/backend/app-management/electron/window-management.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,6 @@ async function createMainWindow() {
592592

593593
mainWindow.show();
594594

595-
// mainWindow.show();
596595
if (splashscreenWindow) {
597596
splashscreenWindow.destroy();
598597
}
@@ -667,7 +666,7 @@ async function createMainWindow() {
667666
const createSplashScreen = async () => {
668667
splashscreenWindow = new BrowserWindow({
669668
width: 375,
670-
height: 420,
669+
height: 450,
671670
icon: path.join(__dirname, "../../../gui/images/logo_transparent_2.png"),
672671
transparent: true,
673672
backgroundColor: undefined,

src/backend/backup-manager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,11 @@ class BackupManager {
245245

246246
if (!isSameDay) {
247247
logger.info("Doing once a day backup");
248-
await this.startBackup();
248+
try {
249+
await this.startBackup();
250+
} catch (error) {
251+
logger.error("Error during once a day backup", error);
252+
}
249253
}
250254
}
251255
}

src/backend/chat/active-user-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class ActiveUserHandler extends TypedEmitter<Events> {
105105
}
106106

107107
getAllActiveUsers(): User[] {
108-
return this._activeUsers.keys().map((id) => {
108+
return this._activeUsers.keys().filter(v => !isNaN(Number(v))).map((id) => {
109109
return {
110110
id: id,
111111
username: this._activeUsers.get(id)

src/backend/chat/chat-helpers.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { FirebotAccount } from "../../types/accounts";
1111

1212
import { AccountAccess } from "../common/account-access";
1313
import { SettingsManager } from "../common/settings-manager";
14+
import { SharedChatCache } from "../streaming-platforms/twitch/chat/shared-chat-cache";
1415
import { TwitchApi } from "../streaming-platforms/twitch/api";
1516
import rankManager from "../ranks/rank-manager";
1617
import roleManager from "../roles/custom-roles-manager";
@@ -476,6 +477,7 @@ class FirebotChatHelpers {
476477

477478
async buildFirebotChatMessage(msg: ChatMessage, msgText: string, whisper = false, action = false) {
478479
const sharedChatRoomId = msg.tags.get("source-room-id");
480+
const sharedChatRoom = SharedChatCache.participants[sharedChatRoomId];
479481
const isSharedChatMessage = sharedChatRoomId != null && sharedChatRoomId !== AccountAccess.getAccounts().streamer.userId;
480482
const isGigantified = msg.tags.get("msg-id") === "gigantified-emote-message";
481483
const firebotChatMessage: FirebotChatMessage = {
@@ -516,7 +518,10 @@ class FirebotChatHelpers {
516518
viewerRanks: {},
517519
viewerCustomRoles: [],
518520
isSharedChatMessage,
519-
sharedChatRoomId: isSharedChatMessage ? sharedChatRoomId : null
521+
sharedChatRoomId,
522+
sharedChatRoomUsername: sharedChatRoom?.broadcasterName,
523+
sharedChatRoomDisplayName: sharedChatRoom?.broadcasterDisplayName,
524+
sharedChatRoomProfilePicUrl: sharedChatRoom?.profilePictureUrl
520525
};
521526

522527
const profilePicUrl = await this.getUserProfilePicUrl(firebotChatMessage.userId);

src/backend/chat/twitch-chat.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ChatClient } from "@twurple/chat";
44
import { AccountAccess } from "../common/account-access";
55
import { ActiveUserHandler } from "./active-user-handler";
66
import { FirebotDeviceAuthProvider } from "../auth/firebot-device-auth-provider";
7+
import { SharedChatCache } from "../streaming-platforms/twitch/chat/shared-chat-cache";
78
import { TwitchApi } from "../streaming-platforms/twitch/api";
89
import chatHelpers from "./chat-helpers";
910
import chatRolesManager from "../roles/chat-roles-manager";
@@ -126,6 +127,9 @@ class TwitchChat extends EventEmitter {
126127
if (!twitchRolesManager.getSubscribers().length) {
127128
await twitchRolesManager.loadSubscribers();
128129
}
130+
131+
// Load the current Shared Chat session
132+
await SharedChatCache.loadSessionFromTwitch();
129133
} catch (error) {
130134
logger.error("Chat connect error", error);
131135
this.disconnect();

src/backend/common/common-listeners.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,40 @@
11
"use strict";
22

33
const { app, dialog, shell, autoUpdater } = require("electron");
4+
const os = require('os');
45
const logger = require("../logwrapper");
56
const { restartApp } = require("../app-management/electron/app-helpers");
67

8+
function getLocalIpAddress() {
9+
try {
10+
const networkInterfaces = os.networkInterfaces();
11+
for (const interfaceName of Object.keys(networkInterfaces)) {
12+
const addresses = networkInterfaces[interfaceName];
13+
for (const address of addresses) {
14+
// Look for IPv4 addresses that are not internal (loopback)
15+
if (address.family === 'IPv4' && !address.internal) {
16+
return address.address;
17+
}
18+
}
19+
}
20+
} catch {}
21+
return null;
22+
}
23+
724
exports.setupCommonListeners = () => {
825
const frontendCommunicator = require("./frontend-communicator");
926
const { SettingsManager } = require("./settings-manager");
1027
const { BackupManager } = require("../backup-manager");
1128
const webServer = require("../../server/http-server-manager");
1229

30+
frontendCommunicator.onAsync("get-ip-address", async () => {
31+
return getLocalIpAddress();
32+
});
33+
34+
frontendCommunicator.onAsync("getPlatform", async () => {
35+
return process.platform;
36+
});
37+
1338
frontendCommunicator.on("show-twitch-preview", () => {
1439
const windowManagement = require("../app-management/electron/window-management");
1540
windowManagement.createStreamPreviewWindow();
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { EffectInstance, EffectList } from "../../types";
2+
import { EffectManager } from "../effects/effect-manager";
3+
import { containsAll, getRandomInt, shuffleArray } from "../utils";
4+
import logger from "../logwrapper";
5+
6+
interface CacheEntry {
7+
/**
8+
* The queue of effect IDs to be executed in order
9+
*/
10+
queue: string[];
11+
/**
12+
* The current set of effect IDs in the effect list, used to determine if the effect list has changed since last execution
13+
*/
14+
currentEffectIds: string[];
15+
/**
16+
* The last effect ID that was executed from this list
17+
*/
18+
lastEffectId?: string;
19+
}
20+
21+
type EffectListCache = Record<string, CacheEntry>;
22+
23+
const sequentialCache: EffectListCache = {};
24+
const randomCache: EffectListCache = {};
25+
26+
function getEntryFromCache(effectListId: string, effectIds: string[], cache: EffectListCache, shuffle: boolean): CacheEntry {
27+
28+
// try to find queue in cache
29+
let cacheEntry = cache[effectListId];
30+
if (!cacheEntry) {
31+
// we don't have a preexisting queue in the cache, create a new one
32+
cacheEntry = {
33+
queue: shuffleArray(effectIds),
34+
currentEffectIds: effectIds
35+
};
36+
37+
// add to the cache
38+
cache[effectListId] = cacheEntry;
39+
} else {
40+
// theres an existing queue in the cache, check if the effect list has changed at all since last time
41+
// and if so, rebuild the queue
42+
const effectsHaventChanged = containsAll(effectIds, cacheEntry.currentEffectIds);
43+
if (!effectsHaventChanged) {
44+
cacheEntry.queue = shuffle ? shuffleArray(effectIds) : effectIds;
45+
cacheEntry.currentEffectIds = effectIds;
46+
}
47+
}
48+
49+
// if empty, we need to rebuild the queue
50+
if (cacheEntry.queue.length === 0) {
51+
if (shuffle) {
52+
if (effectIds.length > 1) {
53+
let shuffledEffectIds: string[] = [];
54+
55+
// keep shuffling until we get a different first effect than the last played effect
56+
do {
57+
shuffledEffectIds = shuffleArray(effectIds);
58+
} while (cacheEntry.lastEffectId && shuffledEffectIds[0] === cacheEntry.lastEffectId);
59+
60+
cacheEntry.queue = shuffledEffectIds;
61+
} else {
62+
// if we don't have more than one effect, there is nothing to shuffle
63+
cacheEntry.queue = effectIds;
64+
}
65+
} else {
66+
cacheEntry.queue = effectIds;
67+
}
68+
}
69+
70+
return cacheEntry;
71+
}
72+
73+
function getValidEffects(effectList: EffectList): EffectInstance[] {
74+
return effectList.list.filter((e) => {
75+
if (e.active != null && !e.active) {
76+
return false;
77+
}
78+
const effectType = EffectManager.getEffectById(e.type);
79+
return effectType?.definition?.isNoOp !== true;
80+
});
81+
}
82+
83+
function getSequentialEffect(effectList: EffectList): EffectInstance | null {
84+
const validEffects = getValidEffects(effectList);
85+
86+
if (!validEffects.length) {
87+
return null;
88+
}
89+
90+
const effectIds = validEffects.map(e => e.id);
91+
92+
const cacheEntry = getEntryFromCache(effectList.id, effectIds, sequentialCache, false);
93+
94+
// gets the next effect from beginning of queue and removes it
95+
const nextEffectId = cacheEntry.queue.shift();
96+
cacheEntry.lastEffectId = nextEffectId;
97+
98+
return effectList.list.find(e => e.id === nextEffectId);
99+
}
100+
101+
function getRandomEffect(effectList: EffectList): EffectInstance | null {
102+
const validEffects = getValidEffects(effectList);
103+
104+
if (!validEffects.length) {
105+
return null;
106+
}
107+
108+
let chosenEffect: EffectInstance | null = null;
109+
110+
const dontRepeat = effectList.dontRepeatUntilAllUsed === true;
111+
const weighted = effectList.weighted === true;
112+
113+
if (weighted) {
114+
const sumOfAllWeights = validEffects.reduce((acc, e) => acc + (e.percentWeight ?? 0.5), 0);
115+
const effectsWithPercentages = validEffects.map(e => ({
116+
effect: e,
117+
chance: ((e.percentWeight ?? 0.5) / sumOfAllWeights) * 100
118+
}));
119+
120+
const min = 0.0001, max = 100.0;
121+
const random = Math.random() * (max - min) + min;
122+
123+
logger.debug("Random effect chance roll: ", random);
124+
125+
let currentChance = 0;
126+
for (let i = 0; i < effectsWithPercentages.length; i++) {
127+
const effectWithPercentage = effectsWithPercentages[i];
128+
currentChance += effectWithPercentage.chance;
129+
if (random <= currentChance) {
130+
chosenEffect = effectWithPercentage.effect;
131+
break;
132+
}
133+
}
134+
} else if (dontRepeat) {
135+
// if we shouldnt repeat, we need to use the cache to queue effects
136+
137+
// get array of effect ids in this random effect
138+
const effectIds = validEffects.map(e => e.id);
139+
140+
// try to find queue in cache
141+
const cacheEntry = getEntryFromCache(effectList.id, effectIds, randomCache, true);
142+
143+
// gets the next effect from beginning of queue and removes it
144+
const chosenEffectId = cacheEntry.queue.shift();
145+
cacheEntry.lastEffectId = chosenEffectId;
146+
chosenEffect = effectList.list.find(e => e.id === chosenEffectId);
147+
148+
} else {
149+
// we don't care about repeats, just get an effect via random index
150+
const randomIndex = getRandomInt(0, validEffects.length - 1);
151+
chosenEffect = validEffects[randomIndex];
152+
}
153+
154+
//removed any cached entries if we are no longer using them
155+
if ((weighted || !dontRepeat) && randomCache[effectList.id]) {
156+
delete randomCache[effectList.id];
157+
}
158+
159+
return chosenEffect;
160+
}
161+
162+
export function resolveEffectsForExecution(effectList: EffectList): EffectInstance[] {
163+
const mode = effectList.runMode ?? "all";
164+
if (mode === "all") {
165+
return effectList.list;
166+
}
167+
if (mode === "sequential") {
168+
const nextEffect = getSequentialEffect(effectList);
169+
return nextEffect ? [nextEffect] : [];
170+
}
171+
if (mode === "random") {
172+
const nextEffect = getRandomEffect(effectList);
173+
return nextEffect ? [nextEffect] : [];
174+
}
175+
return [];
176+
}
177+

0 commit comments

Comments
 (0)