diff --git a/libs/castmate-core/src/info/info-manager.ts b/libs/castmate-core/src/info/info-manager.ts index 2be70a16..e6265527 100644 --- a/libs/castmate-core/src/info/info-manager.ts +++ b/libs/castmate-core/src/info/info-manager.ts @@ -6,7 +6,7 @@ import { defineIPCFunc } from "../util/electron" import electronUpdater, { autoUpdater, UpdateInfo, CancellationToken } from "electron-updater" import { UpdateData } from "castmate-schema" -import { globalLogger } from "../logging/logging" +import { globalLogger, usePluginLogger } from "../logging/logging" import path from "path" import semver from "semver" @@ -15,6 +15,8 @@ interface StartInfo { lastVer: string } +const logger = usePluginLogger("info-manager") + export const InfoService = Service( class { startInfo: StartInfo | undefined @@ -81,18 +83,23 @@ export const InfoService = Service( } async checkUpdate() { - const result = await autoUpdater.checkForUpdates() - if (result != null) { - if (semver.gt(result.updateInfo.version, app.getVersion())) { - globalLogger.log("Update!", result.updateInfo.releaseName, result.updateInfo.version) - this.updateInfo = result.updateInfo - return true + try { + const result = await autoUpdater.checkForUpdates() + if (result != null) { + if (semver.gt(result.updateInfo.version, app.getVersion())) { + globalLogger.log("Update!", result.updateInfo.releaseName, result.updateInfo.version) + this.updateInfo = result.updateInfo + return true + } + return false + } else { + globalLogger.log("No Update :(") } return false - } else { - globalLogger.log("No Update :(") + } catch (err) { + logger.error("Error Checking Update", err) + return false } - return false } async checkInfo() { diff --git a/libs/castmate-core/src/media/media-manager.ts b/libs/castmate-core/src/media/media-manager.ts index 5bfcf8e1..9f131155 100644 --- a/libs/castmate-core/src/media/media-manager.ts +++ b/libs/castmate-core/src/media/media-manager.ts @@ -1,6 +1,7 @@ import { Service } from "../util/service" import { ImageFormats, MediaMetadata, stillImageFormats } from "castmate-schema" import * as fs from "fs/promises" +import * as fsSync from "fs" import path, * as pathTools from "path" import * as rra from "recursive-readdir-async" import * as ffmpeg from "fluent-ffmpeg" @@ -10,11 +11,15 @@ import { ensureDirectory, resolveProjectPath } from "../io/file-system" import { shell, app } from "electron" import { globalLogger, usePluginLogger } from "../logging/logging" import { WebService } from "../webserver/internal-webserver" -import express, { Application, Router } from "express" +import express, { Application, response, Router } from "express" +import { coreAxios } from "../util/request-utils" //require("@ffmpeg-installer/win32-x64") //require("@ffprobe-installer/win32-x64") //Thumbnails? //Durations? + +import http from "http" + const logger = usePluginLogger("media") function probeMedia(file: string) { @@ -37,6 +42,69 @@ export interface MediaFolder { path: string watcher: chokidar.FSWatcher } +/* +function downloadFile(url: string, dest: string) { + return new Promise((resolve, reject) => { + const writeStream = fsSync.createWriteStream(dest) + + const request = http.get(url, (resp) => { + if (resp.statusCode !== 200) { + reject(`Failed to download ${url} with ${resp.statusCode}`) + } + + resp.pipe(writeStream) + }) + + writeStream.on("finish", () => { + writeStream.close((err) => { + if (err) return reject(err) + resolve() + }) + }) + + request.on("error", (err) => { + fsSync.unlink(dest, (unlinkErr) => { + if (unlinkErr) return reject(unlinkErr) + reject(err) + }) + }) + + writeStream.on("error", (err) => { + fsSync.unlink(dest, (unlinkErr) => { + if (unlinkErr) return reject(unlinkErr) + reject(err) + }) + }) + }) +}*/ + +async function downloadFile(url: string, dest: string) { + const writeStream = fsSync.createWriteStream(dest) + //https://stackoverflow.com/questions/55374755/node-js-axios-download-file-stream-and-writefile + await coreAxios + .get(url, { + responseType: "stream", + }) + .then((response) => { + return new Promise((resolve, reject) => { + response.data.pipe(writeStream) + + let error: any = undefined + writeStream.on("error", (err) => { + error = err + writeStream.close() + reject(err) + }) + writeStream.on("close", () => { + if (!error) { + resolve(true) + } + //no need to call the reject here, as it will have been called in the + //'error' stream; + }) + }) + }) +} export const MediaManager = Service( class { @@ -62,11 +130,55 @@ export const MediaManager = Service( shell.showItemInFolder(mediaItem.file) }) + defineIPCFunc("media", "downloadMedia", async (url: string, mediaPath: string) => { + await this.downloadMedia(url, mediaPath) + }) + + defineIPCFunc("media", "copyMedia", async (localPath: string, mediaPath: string) => { + await this.copyMedia(localPath, mediaPath) + }) + const router = express.Router() router.use(express.static(mediaPath)) WebService.getInstance().addRootRouter("/media/default", router) } + getLocalPath(mediaPath: string) { + const baseMediaPath = resolveProjectPath("./media") + + if (!mediaPath.startsWith("/default")) throw new Error("not a media path") + + const defaultPath = path.relative("/default", mediaPath) + + const localPath = path.join(baseMediaPath, defaultPath) + + return localPath + } + + async downloadMedia(url: string, mediaPath: string) { + try { + const localPath = this.getLocalPath(mediaPath) + + logger.log("Downloading Media", url, "to", localPath) + + await downloadFile(url, localPath) + } catch (err) { + logger.error("ERROR DOWNLOADING MEDIA", err) + } + } + + async copyMedia(localPath: string, mediaPath: string) { + try { + const destPath = this.getLocalPath(mediaPath) + + logger.log("Copying Media", localPath, "to", destPath) + + await fs.copyFile(localPath, destPath) + } catch (err) { + logger.error("ERROR COPYING MEDIA", err) + } + } + getMedia(path: string) { return this.mediaFiles.get(path) } diff --git a/libs/castmate-ui-core/src/components/automation/DefaultActionComponent.vue b/libs/castmate-ui-core/src/components/automation/DefaultActionComponent.vue index 7b6e5524..fbe0a6d3 100644 --- a/libs/castmate-ui-core/src/components/automation/DefaultActionComponent.vue +++ b/libs/castmate-ui-core/src/components/automation/DefaultActionComponent.vue @@ -1,7 +1,7 @@ diff --git a/libs/castmate-ui-core/src/components/data/views/EnumableDataView.vue b/libs/castmate-ui-core/src/components/data/views/EnumableDataView.vue new file mode 100644 index 00000000..b86199c8 --- /dev/null +++ b/libs/castmate-ui-core/src/components/data/views/EnumableDataView.vue @@ -0,0 +1,68 @@ + + + diff --git a/libs/castmate-ui-core/src/components/document/DocumentEditor.vue b/libs/castmate-ui-core/src/components/document/DocumentEditor.vue index 9b590979..5fa03edb 100644 --- a/libs/castmate-ui-core/src/components/document/DocumentEditor.vue +++ b/libs/castmate-ui-core/src/components/document/DocumentEditor.vue @@ -4,7 +4,6 @@ v-if="documentComponent && document" v-model="documentData" v-model:view="documentView" - @keydown="onKeyDown" tabindex="-1" /> @@ -57,12 +56,6 @@ const documentView = computed({ document.value.viewData = data }, }) - -function onKeyDown(ev: KeyboardEvent) { - if (ev.ctrlKey && ev.code == "KeyS") { - documentStore.saveDocument(props.documentId) - } -} diff --git a/libs/castmate-ui-core/src/components/resources/ResourceEditDialog.vue b/libs/castmate-ui-core/src/components/resources/ResourceEditDialog.vue index dd42759b..d1d58c36 100644 --- a/libs/castmate-ui-core/src/components/resources/ResourceEditDialog.vue +++ b/libs/castmate-ui-core/src/components/resources/ResourceEditDialog.vue @@ -1,6 +1,18 @@ diff --git a/plugins/overlays/renderer/src/main.ts b/plugins/overlays/renderer/src/main.ts index e2ebfb4e..1cadf9da 100644 --- a/plugins/overlays/renderer/src/main.ts +++ b/plugins/overlays/renderer/src/main.ts @@ -1,6 +1,7 @@ import { computed, App } from "vue" import { ProjectGroup, + ResourceSchemaEdit, getResourceAsProjectGroup, handleIpcMessage, handleIpcRpc, @@ -67,11 +68,31 @@ export function initPlugin(app: App) { }, }, obsId: undefined, - showPreview: false, } }, }) + resourceStore.registerConfigSchema("Overlay", { + type: Object, + properties: { + name: { type: String, name: "Overlay Name", required: true, default: "Overlay" }, + size: { + name: "Size", + type: Object, + properties: { + width: { type: Number, required: true, name: "Width", default: 1920 }, + height: { type: Number, required: true, name: "Height", default: 1080 }, + }, + }, + }, + }) + + resourceStore.registerEditComponent("Overlay", ResourceSchemaEdit, async (id, data: any) => { + await resourceStore.applyResourceConfig("Overlay", id, data) + }) + + resourceStore.registerCreateComponent("Overlay", ResourceSchemaEdit) + projectStore.registerProjectGroupItem(overlayGroup) configStore.initialize() diff --git a/plugins/overlays/shared/src/overlay-resource-types.ts b/plugins/overlays/shared/src/overlay-resource-types.ts index 9fb2cdeb..26fecc3e 100644 --- a/plugins/overlays/shared/src/overlay-resource-types.ts +++ b/plugins/overlays/shared/src/overlay-resource-types.ts @@ -28,6 +28,11 @@ export interface OverlayPreviewConfig { source?: string | "obs" } +export interface InitialOverlayConfig { + name: string + size: { width: number; height: number } +} + export interface OverlayConfig { name: string size: { width: number; height: number } diff --git a/plugins/philips-hue/main/src/discovery.ts b/plugins/philips-hue/main/src/discovery.ts index 1ddf2d02..f5dd0af6 100644 --- a/plugins/philips-hue/main/src/discovery.ts +++ b/plugins/philips-hue/main/src/discovery.ts @@ -14,32 +14,59 @@ import os from "os" const logger = usePluginLogger("philips-hue") +async function validateHubIP(hubIp: string | undefined) { + try { + if (!hubIp) return false + + const resp = await coreAxios.get(`http://${hubIp}/api/0/config`) + + return "name" in resp.data && "bridgeid" in resp.data + } catch (err) { + logger.error(`Error Querying Possible Hub ${hubIp}`, err) + return false + } +} + export function setupDiscovery(hubIp: ReactiveRef, hubKey: ReactiveRef) { async function discoverBridge() { - const resp = await coreAxios.get("https://discovery.meethue.com/") + try { + const resp = await coreAxios.get("https://discovery.meethue.com/") - if (!Array.isArray(resp.data) || resp.data.length == 0) { - return - } + if (!Array.isArray(resp.data) || resp.data.length == 0) { + return + } - hubIp.value = resp.data[0]?.internalipaddress + for (const possibleHub of resp.data) { + if (await validateHubIP(possibleHub?.internalipaddress)) { + hubIp.value = possibleHub?.internalipaddress + return + } + } + } catch (err) { + logger.error("Error trying to discover bridge ip", err) + } } async function tryCreateKey() { if (!hubIp.value) return - const resp = await coreAxios.post(`http://${hubIp.value}/api`, { - devicetype: `CastMate#${os.userInfo().username}`, - }) + try { + const resp = await coreAxios.post(`http://${hubIp.value}/api`, { + devicetype: `CastMate#${os.userInfo().username}`, + }) - const key = resp.data[0]?.success?.username as string | undefined + const key = resp.data[0]?.success?.username as string | undefined - if (key) { - logger.log("Key Found", key) - hubKey.value = key - } + if (key) { + logger.log("Key Found", key) + hubKey.value = key + } - return key + return key + } catch (err) { + logger.error("Error Creating HUE Key", err) + return undefined + } } defineRendererCallable("findHueBridge", async () => { diff --git a/plugins/twitch/main/src/channelpoints.ts b/plugins/twitch/main/src/channelpoints.ts index bbda4357..1e4a93c0 100644 --- a/plugins/twitch/main/src/channelpoints.ts +++ b/plugins/twitch/main/src/channelpoints.ts @@ -7,6 +7,7 @@ import { onLoad, onProfilesChanged, runOnChange, + startPerfTime, template, templateSchema, usePluginLogger, @@ -444,50 +445,55 @@ export function setupChannelPointRewards() { } async function loadRewards() { - const channelAccount = TwitchAccount.channel + const perf = startPerfTime(`Load Rewards`) + try { + const channelAccount = TwitchAccount.channel - if (!channelAccount.config.isAffiliate) { - logger.log("Not Affiliate Skipping Rewards") - return - } + if (!channelAccount.config.isAffiliate) { + logger.log("Not Affiliate Skipping Rewards") + return + } - const channelId = channelAccount.config.twitchId + const channelId = channelAccount.config.twitchId - await clearNonCastMateRewards() + await clearNonCastMateRewards() - //Unforunately there's no way to query for rewards not controlled by this client id - //We can only query for all rewards and rewards we control - //Query both. - const rewards = await channelAccount.apiClient.channelPoints.getCustomRewards(channelId) - const castMateRewards = await channelAccount.apiClient.channelPoints.getCustomRewards(channelId, true) + //Unforunately there's no way to query for rewards not controlled by this client id + //We can only query for all rewards and rewards we control + //Query both. + const rewards = await channelAccount.apiClient.channelPoints.getCustomRewards(channelId) + const castMateRewards = await channelAccount.apiClient.channelPoints.getCustomRewards(channelId, true) - //Filter for non-castmate controllable rewards - const nonCastMateRewards = rewards.filter((r) => castMateRewards.find((o) => o.id == r.id) == null) + //Filter for non-castmate controllable rewards + const nonCastMateRewards = rewards.filter((r) => castMateRewards.find((o) => o.id == r.id) == null) - //Load all the non-castmate rewards into resources - await Promise.all(nonCastMateRewards.map((r) => ChannelPointReward.createNonCastmateReward(r))) + //Load all the non-castmate rewards into resources + await Promise.all(nonCastMateRewards.map((r) => ChannelPointReward.createNonCastmateReward(r))) - for (const reward of castMateRewards) { - const cpr = ChannelPointReward.getByTwitchId(reward.id) + for (const reward of castMateRewards) { + const cpr = ChannelPointReward.getByTwitchId(reward.id) - //This reward is controllable but doesn't have a locally stored resource, create it now - if (!cpr) { - await ChannelPointReward.recoverLocalReward(reward) - //Add this to the reward list so we handle it properly in the next step + //This reward is controllable but doesn't have a locally stored resource, create it now + if (!cpr) { + await ChannelPointReward.recoverLocalReward(reward) + //Add this to the reward list so we handle it properly in the next step + } } - } - for (const reward of ChannelPointReward.storage) { - if (!reward.config.controllable) continue + for (const reward of ChannelPointReward.storage) { + if (!reward.config.controllable) continue - const twitchReward = castMateRewards.find((r) => r.id == reward.config.twitchId) + const twitchReward = castMateRewards.find((r) => r.id == reward.config.twitchId) - try { - await reward.initializeFromTwurple(twitchReward) - await reward.initializeReactivity() - } catch (err) { - logger.error("Error Initializing Reward", reward.config.name, err) + try { + await reward.initializeFromTwurple(twitchReward) + await reward.initializeReactivity() + } catch (err) { + logger.error("Error Initializing Reward", reward.config.name, err) + } } + } finally { + perf.stop(logger) } } diff --git a/plugins/twitch/main/src/chat.ts b/plugins/twitch/main/src/chat.ts index 5643e416..a233b90b 100644 --- a/plugins/twitch/main/src/chat.ts +++ b/plugins/twitch/main/src/chat.ts @@ -125,6 +125,10 @@ export function setupChat() { if (matchResult == null) return undefined + if (!(await inTwitchViewerGroup(context.viewer, config.group))) { + return undefined + } + if (config.cooldown) { const now = Date.now() const slug = `${mapping.profileId}.${mapping.triggerId}` @@ -137,10 +141,6 @@ export function setupChat() { chatCommandCooldownMap.set(slug, now) } - if (!(await inTwitchViewerGroup(context.viewer, config.group))) { - return undefined - } - return { ...context, ...matchResult, diff --git a/plugins/twitch/main/src/follows.ts b/plugins/twitch/main/src/follows.ts index 8f94e5bf..f382dc77 100644 --- a/plugins/twitch/main/src/follows.ts +++ b/plugins/twitch/main/src/follows.ts @@ -1,4 +1,4 @@ -import { defineState, defineTrigger } from "castmate-core" +import { defineState, defineTrigger, startPerfTime, usePluginLogger } from "castmate-core" import { onChannelAuth } from "./api-harness" import { ViewerCache } from "./viewer-cache" import { TwitchViewer, TwitchViewerGroup } from "castmate-plugin-twitch-shared" @@ -6,6 +6,8 @@ import { inTwitchViewerGroup } from "./group" import { TwitchAccount } from "./twitch-auth" export function setupFollows() { + const logger = usePluginLogger() + const follow = defineTrigger({ id: "follow", name: "Followed", @@ -44,6 +46,8 @@ export function setupFollows() { }) async function updateFollowCount() { + const perf = startPerfTime(`Follow Count Update`) + const followersResp = await TwitchAccount.channel.apiClient.channels.getChannelFollowers( TwitchAccount.channel.twitchId ) @@ -52,6 +56,8 @@ export function setupFollows() { followers.value = followersResp.total lastFollower.value = await ViewerCache.getInstance().getResolvedViewer(followersResp.data[0].userId) + + perf.stop(logger) } onChannelAuth(async (account, service) => { diff --git a/plugins/twitch/main/src/hype-train.ts b/plugins/twitch/main/src/hype-train.ts index 3e3a13de..841b5e5c 100644 --- a/plugins/twitch/main/src/hype-train.ts +++ b/plugins/twitch/main/src/hype-train.ts @@ -1,8 +1,10 @@ -import { defineState, defineTrigger } from "castmate-core" +import { defineState, defineTrigger, startPerfTime, usePluginLogger } from "castmate-core" import { Range } from "castmate-schema" import { onChannelAuth } from "./api-harness" export function setupHypeTrains() { + const logger = usePluginLogger() + const hypeTrainStarted = defineTrigger({ id: "hypeTrainStarted", name: "Hype Train Started", @@ -118,6 +120,8 @@ export function setupHypeTrains() { }) onChannelAuth(async (channel, service) => { + const perf = startPerfTime(`HypeTrains`) + service.eventsub.onChannelHypeTrainBegin(channel.twitchId, (event) => { hypeTrainLevel.value = event.level hypeTrainProgress.value = event.progress @@ -183,5 +187,7 @@ export function setupHypeTrains() { hypeTrainExists.value = true } } + + perf.stop(logger) }) } diff --git a/plugins/twitch/main/src/viewer-cache.ts b/plugins/twitch/main/src/viewer-cache.ts index a423ee53..018b9904 100644 --- a/plugins/twitch/main/src/viewer-cache.ts +++ b/plugins/twitch/main/src/viewer-cache.ts @@ -56,14 +56,12 @@ function getNValues(set: Set, requiredValues: T[], n: number): T[] { return result } - let count = 0 for (const v of set) { if (requiredValues.includes(v)) { continue } result.push(v) - count++ - if (count >= n) { + if (result.length >= n) { break } } @@ -269,17 +267,27 @@ export const ViewerCache = Service( } private async queryColor(...userIds: string[]) { + const perf1 = startPerfTime("Query Color User Gather") const ids = getNValues(this.unknownColors, userIds, 100) + perf1.stop(logger) + try { + const perf2 = startPerfTime(`Run Query Color ${ids.length}`) const colors = await TwitchAccount.channel.apiClient.chat.getColorsForUsers(ids) + perf2.stop(logger) + const perf3 = startPerfTime("Update Colors") for (const [id, color] of colors) { //TODO: Default color for the unchosen this.get(id).color = (color as Color) ?? "default" } removeValues(this.unknownColors, ids) - } catch (err) {} + perf3.stop(logger) + } catch (err) { + logger.error("Error Querying Colors!", err) + logger.error("IDS", ids) + } } async getChatColor(userId: string): Promise { @@ -385,12 +393,17 @@ export const ViewerCache = Service( try { userIds = userIds.filter((id) => id != "anonymous") + const perf1 = startPerfTime("Running Follow Queries") + //Annoyingly check each follow independently const followingPromises = userIds.map((id) => TwitchAccount.channel.apiClient.channels.getChannelFollowers(TwitchAccount.channel.twitchId, id) ) const followingResults = await Promise.all(followingPromises) + perf1.stop(logger) + + const perf2 = startPerfTime("Updating Follows") for (let i = 0; i < userIds.length; ++i) { const cached = this.get(userIds[i]) @@ -407,7 +420,11 @@ export const ViewerCache = Service( cached.following = true //cached.followDate = following.data[0].followDate } - } catch (err) {} + perf2.stop(logger) + } catch (err) { + logger.error("Error Querying Follows", err) + logger.error("IDS", userIds) + } } async getIsFollowing(userId: string): Promise { @@ -443,14 +460,19 @@ export const ViewerCache = Service( } private async querySubInfo(...userIds: string[]) { + const perf1 = startPerfTime("Query Subs Gather Users") const ids = getNValues(this.unknownSubInfo, userIds, 100) + perf1.stop(logger) try { + const perf2 = startPerfTime(`Run Query Subs ${ids.length}`) const subs = await TwitchAccount.channel.apiClient.subscriptions.getSubscriptionsForUsers( TwitchAccount.channel.twitchId, ids ) + perf2.stop(logger) + const perf3 = startPerfTime("Update Subs") const leftOvers = new Set(ids) for (const sub of subs) { @@ -473,7 +495,11 @@ export const ViewerCache = Service( } removeValues(this.unknownSubInfo, ids) - } catch (err) {} + perf3.stop(logger) + } catch (err) { + logger.error("Error Querying Subs!", err) + logger.error("IDS", ids) + } } async getIsSubbed(userId: string) { @@ -499,10 +525,15 @@ export const ViewerCache = Service( } private async queryUserInfo(...userIds: string[]) { + const perf1 = startPerfTime("Query User Gather") const ids = getNValues(this.unknownUserInfo, userIds, 100) + perf1.stop(logger) try { + const perf2 = startPerfTime(`Run Get Users Query ${ids.length}`) const users = await TwitchAccount.channel.apiClient.users.getUsersByIds(ids) + perf2.stop(logger) + const perf3 = startPerfTime(`Update User Info`) for (const user of users) { const cached = this.getOrCreate(user.id) @@ -513,7 +544,11 @@ export const ViewerCache = Service( this.unknownUserInfo.delete(user.id) } - } catch (err) {} + perf3.stop(logger) + } catch (err) { + logger.error("Error Updating Users!", err) + logger.error("IDS", ids) + } } async getResolvedViewer(userId: string) { @@ -521,7 +556,7 @@ export const ViewerCache = Service( } async getResolvedViewers(userIds: string[]): Promise { - const perf = startPerfTime("Resolve Viewer") + const perf = startPerfTime(`Resolve Viewers ${userIds.length}`) try { const neededSubIds: string[] = [] const neededColorIds: string[] = [] @@ -554,18 +589,22 @@ export const ViewerCache = Service( const queryPromises: Promise[] = [] if (neededColorIds.length > 0) { + logger.log("---Querying Colors:", neededColorIds.length) queryPromises.push(this.queryColor(...neededColorIds)) } if (neededFollowerIds.length > 0) { + logger.log("---Querying Following:", neededFollowerIds.length) queryPromises.push(this.queryFollowing(...neededFollowerIds)) } if (neededSubIds.length > 0) { + logger.log("---Querying Subs:", neededSubIds.length) queryPromises.push(this.querySubInfo(...neededSubIds)) } if (neededUserInfoIds.length > 0) { + logger.log("---Query User Infos:", neededUserInfoIds.length) queryPromises.push(this.queryUserInfo(...neededUserInfoIds)) } diff --git a/plugins/twitch/renderer/src/components/channel-points/ChannelPointsEditPage.vue b/plugins/twitch/renderer/src/components/channel-points/ChannelPointsEditPage.vue index e17b4ec8..6e2ce89f 100644 --- a/plugins/twitch/renderer/src/components/channel-points/ChannelPointsEditPage.vue +++ b/plugins/twitch/renderer/src/components/channel-points/ChannelPointsEditPage.vue @@ -13,25 +13,26 @@ sort-field="config.controllable" :sort-order="-1" > - + Create CastMate Reward - --> + + diff --git a/yarn.lock b/yarn.lock index 98e4c189..8284a692 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3305,7 +3305,7 @@ __metadata: castmate-schema: "workspace:^" jsqr: "npm:^1.4.0" node-screenshots: "npm:^0.2.0" - obs-websocket-js: "npm:^5.0.5" + obs-websocket-js: "npm:^5.0.6" regedit: "npm:^5.1.3" typescript: "npm:*" languageName: unknown @@ -7828,9 +7828,9 @@ __metadata: languageName: node linkType: hard -"obs-websocket-js@npm:^5.0.5": - version: 5.0.5 - resolution: "obs-websocket-js@npm:5.0.5" +"obs-websocket-js@npm:^5.0.6": + version: 5.0.6 + resolution: "obs-websocket-js@npm:5.0.6" dependencies: "@msgpack/msgpack": "npm:^2.7.1" crypto-js: "npm:^4.1.1" @@ -7839,7 +7839,7 @@ __metadata: isomorphic-ws: "npm:^5.0.0" type-fest: "npm:^3.11.0" ws: "npm:^8.13.0" - checksum: 10/10075f057e05cddf0805b3edad9263f6c6da31c817a4cba27a2e6fd6fef20df188db0bcc929bbdd48bd6542b19f5c6e08e0acbdc062065ca7e571addd91a4601 + checksum: 10/a48a6df46c6673b374444443cb0eb3ec14b7a1928c3c33459181a9697f93b470c72349a8619518785dcde4c5f2f2287404e086c522519a61ff3e00727c0a5d3f languageName: node linkType: hard