diff --git a/libs/castmate-core/package.json b/libs/castmate-core/package.json index 360e542a..6b580c2b 100644 --- a/libs/castmate-core/package.json +++ b/libs/castmate-core/package.json @@ -27,7 +27,7 @@ "@types/fluent-ffmpeg": "^2.1.21", "castmate-schema": "workspace:^", "chokidar": "^3.5.3", - "electron": "^29.1.1", + "electron": "29.4.5", "electron-updater": "6.1.8", "express": "^4.18.2", "fluent-ffmpeg": "^2.1.2", diff --git a/libs/castmate-overlay-widget-loader/package.json b/libs/castmate-overlay-widget-loader/package.json index 32878ae9..548c0fbd 100644 --- a/libs/castmate-overlay-widget-loader/package.json +++ b/libs/castmate-overlay-widget-loader/package.json @@ -19,7 +19,7 @@ "castmate-plugin-twitch-overlays": "workspace:^", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "primevue": "*", diff --git a/libs/castmate-ui-core/package.json b/libs/castmate-ui-core/package.json index 3b1395e0..539044a0 100644 --- a/libs/castmate-ui-core/package.json +++ b/libs/castmate-ui-core/package.json @@ -15,7 +15,7 @@ "@vueuse/core": "*", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "primevue": "*", diff --git a/libs/castmate-ui-core/src/components/data/base-components/AutocompleteDropList.vue b/libs/castmate-ui-core/src/components/data/base-components/AutocompleteDropList.vue index 9ba4094f..73be09b3 100644 --- a/libs/castmate-ui-core/src/components/data/base-components/AutocompleteDropList.vue +++ b/libs/castmate-ui-core/src/components/data/base-components/AutocompleteDropList.vue @@ -34,6 +34,7 @@ + @@ -49,6 +50,7 @@ import { getNextItem, getPrevItem, findItem, + groupItems, } from "../../../util/autocomplete-helpers" import { usePropagationStop } from "../../../main" diff --git a/libs/castmate-ui-core/src/plugins/plugin-store.ts b/libs/castmate-ui-core/src/plugins/plugin-store.ts index ba3b6baa..7c7c2b47 100644 --- a/libs/castmate-ui-core/src/plugins/plugin-store.ts +++ b/libs/castmate-ui-core/src/plugins/plugin-store.ts @@ -19,7 +19,7 @@ import { IPCStateDefinition, } from "castmate-schema" -import { computed, ref, unref, type MaybeRefOrGetter, toValue, Component, markRaw } from "vue" +import { computed, ref, unref, type MaybeRefOrGetter, toValue, Component, markRaw, onMounted, onUnmounted } from "vue" import * as chromatism from "chromatism2" import { handleIpcMessage, useIpcCaller } from "../util/electron" @@ -208,6 +208,8 @@ export interface SettingsChange { value: any } +export type SettingUpdateWatcher = (plugin: string, setting: string, value: any) => any + export const usePluginStore = defineStore("plugins", () => { const pluginMap = ref>(new Map()) @@ -215,6 +217,8 @@ export const usePluginStore = defineStore("plugins", () => { const getPlugin = useIpcCaller<(id: string) => IPCPluginDefinition>("plugins", "getPlugin") const doSettingsUpdate = useIpcCaller<(changes: SettingsChange[]) => boolean>("plugins", "updateSettings") + const settingsUpdateWatchers = new Array() + async function initialize() { handleIpcMessage("plugins", "registerPlugin", (event, plugin: IPCPluginDefinition) => { //console.log("Registering Late Plugin", plugin.id) @@ -229,8 +233,11 @@ export const usePluginStore = defineStore("plugins", () => { const plugin = pluginMap.value.get(id) if (plugin) { const setting = plugin.settings[settingId] - if (setting?.type == "value") { + if (setting?.type == "value" || setting?.type == "secret") { setting.value = value + for (const watcher of settingsUpdateWatchers) { + watcher(id, settingId, value) + } } } }) @@ -367,6 +374,16 @@ export const usePluginStore = defineStore("plugins", () => { await doSettingsUpdate(changes) } + function registerSettingWatcher(watcher: SettingUpdateWatcher) { + settingsUpdateWatchers.push(watcher) + } + + function unregisterSettingWatcher(watcher: SettingUpdateWatcher) { + const idx = settingsUpdateWatchers.findIndex((w) => w == watcher) + if (idx < 0) return + settingsUpdateWatchers.splice(idx, 1) + } + return { pluginMap: computed(() => pluginMap.value), initialize, @@ -375,6 +392,8 @@ export const usePluginStore = defineStore("plugins", () => { setActionComponent, setSettingComponent, updateSettings, + registerSettingWatcher, + unregisterSettingWatcher, } }) @@ -565,3 +584,33 @@ export function useFullState() { return fullState }) } + +export function useFullSettings() { + const pluginStore = usePluginStore() + + return computed(() => { + const fullSettings: Record> = {} + + for (const plugin of pluginStore.pluginMap.values()) { + fullSettings[plugin.id] = {} + + for (const [key, state] of Object.entries(plugin.settings)) { + if (state.type == "value" || state.type == "secret") fullSettings[plugin.id][key] = state.value + } + } + + return fullSettings + }) +} + +export function useSettingWatcher(watcher: SettingUpdateWatcher) { + const pluginStore = usePluginStore() + + onMounted(() => { + pluginStore.registerSettingWatcher(watcher) + }) + + onUnmounted(() => { + pluginStore.unregisterSettingWatcher(watcher) + }) +} diff --git a/package.json b/package.json index 1e398469..d089aa98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "castmate-monorepo", - "version": "0.5.2-beta", + "version": "0.5.2", "description": "", "private": true, "type": "module", @@ -29,7 +29,7 @@ "@vue/tsconfig": "^0.4.0", "@vueuse/core": "10.7.2", "chromatism2": "^3.0.2", - "electron": "^29.1.1", + "electron": "29.4.5", "eslint": "^8.34.0", "eslint-plugin-vue": "^9.9.0", "node-gyp": "^10.0.1", diff --git a/packages/castmate/package.json b/packages/castmate/package.json index fd433164..3f6105bc 100644 --- a/packages/castmate/package.json +++ b/packages/castmate/package.json @@ -1,12 +1,13 @@ { "name": "castmate", - "version": "0.5.2-beta", + "version": "0.5.2", "description": "CastMate", "author": "LordTocs", "scripts": { "compile": "vue-tsc", "trace": "vue-tsc --traceResolution", "build": "vue-tsc && vite build", + "inspect": "electron ./dist/dist-electron/background.js --inspect", "electron:build": "electron-builder --config electron-builder-config.cjs", "electron:buildpublish": "electron-builder --config electron-builder-config.cjs --publish always", "fix-app-deps": "electron-builder install-app-deps" @@ -76,7 +77,7 @@ "castmate-ui-core": "workspace:^", "castmate-vite": "workspace:^", "chromatism2": "*", - "electron": "29.1.1", + "electron": "29.4.5", "electron-builder": "^25.0.0-alpha.8", "electron-devtools-installer": "^3.2.0", "eslint": "^8.17.0", diff --git a/packages/castmate/src/main/background.ts b/packages/castmate/src/main/background.ts index 0fb5cb0e..afbd8f12 100644 --- a/packages/castmate/src/main/background.ts +++ b/packages/castmate/src/main/background.ts @@ -2,7 +2,7 @@ //import { createRequire } from "node:module" import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer" //import electronUpdater from "electron-updater" -import { app, BrowserWindow, ipcMain } from "electron" +import { app, BrowserWindow, ipcMain, contentTracing } from "electron" import { createWindow } from "./electron/electron-helpers" import { initializeCastMate, finializeCastMateSetup, loadAutomations, setupCastMateDirectories } from "castmate-core" import { loadPlugins } from "./plugins" @@ -37,6 +37,22 @@ function createMainWindow() { } app.whenReady().then(async () => { + /* + //Uncomment this block if you want to take a trace to view in chrome://tracing + //Keep the time short else it will fail to open + setTimeout(async () => { + console.log("Starting Trace") + await contentTracing.startRecording({ + included_categories: ["*"], + }) + + setTimeout(async () => { + console.log("Stopping Trace!") + const path = await contentTracing.stopRecording("./trace.json") + console.log("Stopped Trace", path) + }, 2 * 1000) + }, 60 * 1000)*/ + //Setup our user folder location / session data await setupCastMateDirectories() diff --git a/packages/castmate/src/main/migration/old-migration.ts b/packages/castmate/src/main/migration/old-migration.ts index ab964d63..09046181 100644 --- a/packages/castmate/src/main/migration/old-migration.ts +++ b/packages/castmate/src/main/migration/old-migration.ts @@ -2426,6 +2426,11 @@ defineIPCFunc("oldMigration", "finishMigrate", () => { async function createBackup() { const backupPath = resolveProjectPath("../backup_04.zip") + if (fsSync.existsSync(backupPath)) { + //Backup already exists, don't overwrite + return + } + logger.log("Creating Backup") const outStream = fsSync.createWriteStream(backupPath) @@ -2438,6 +2443,7 @@ async function createBackup() { const archive = archiver("zip", { zlib: { level: 9 } }) + //@ts-ignore archive.pipe(outStream) function archiveDir(dir: string) { diff --git a/packages/castmate/src/main/plugins/elgato.js b/packages/castmate/src/main/plugins/elgato.js deleted file mode 100644 index 328cd611..00000000 --- a/packages/castmate/src/main/plugins/elgato.js +++ /dev/null @@ -1,260 +0,0 @@ -import { clamp } from "lodash" -import bonjour from "bonjour" -import { IoTProvider, Light, Plug } from "../iot/iot-manager" -import { reactify } from "../state/reactive" -import * as chromatism from "chromatism2" -import axios from "axios" -import util from "util" - -function elgatoToKelvin(value) { - return Math.round((-4100 * value) / 201 + 1993300 / 201) -} - -function kelvinToElgato(kelvin) { - return Math.round(((kelvin - 1993300 / 201) * 201) / -4100) -} - -class ElgatoKeyLight extends Light { - /** - * - * @param {bonjour.RemoteService} service - */ - constructor(service) { - super() - - const ip = service.referer.address - const port = service.port - this.axios = axios.create({ - baseURL: `http://${ip}:${port}/elgato`, - }) - this.id = "elgato." + service.txt.id - - this.state = reactify({ - on: false, - color: null, - }) - } - - startPolling() { - this.poll() - setInterval(() => this.poll(), 30 * 1000) - } - - async poll() { - const lightState = await this.axios.get("lights") - - const state = lightState.data.lights[0] - this.state.on = state.on - - const bri = state.brightness - const kelvin = elgatoToKelvin(state.temperature) - - this.state.color = { bri, kelvin } - } - - async init() { - const info = await this.axios.get("accessory-info") - - this.config = { - name: info.data.displayName ?? info.data.productName, - plugin: "elgato", - type: "bulb", - rgb: { - available: false, - }, - kelvin: { - available: true, - min: elgatoToKelvin(143), - max: elgatoToKelvin(344), - }, - dimming: { - available: true, - }, - } - - this.startPolling() - } - - async setLightState(on, color, duration) { - if (on == "toggle") { - on = !this.state.on - } - on = on ?? this.state.on - - const brightness = color?.bri ?? this.state.color?.bri ?? 100 - const kelvin = color?.kelvin ?? this.state.color?.kelvin ?? 3000 - const temperature = kelvinToElgato(kelvin) - - this.state.on = on - this.state.color = { bri: brightness, kelvin } - - await this.axios.put("lights", { - numberOfLights: 1, - lights: [ - { - on, - brightness, - temperature, - }, - ], - }) - } -} - -class ElgatoLightStrip extends Light { - /** - * - * @param {bonjour.RemoteService} service - */ - constructor(service) { - super() - - const ip = service.referer.address - const port = service.port - this.axios = axios.create({ - baseURL: `http://${ip}:${port}/elgato`, - }) - this.id = "elgato." + service.txt.id - - this.state = reactify({ - on: false, - color: null, - }) - } - - startPolling() { - this.poll() - setInterval(() => this.poll(), 30 * 1000) - } - - async poll() { - const lightState = await this.axios.get("lights") - - const state = lightState.data.lights[0] - this.state.on = state.on - - const bri = state.brightness - if (state.temperature) { - const kelvin = elgatoToKelvin(state.temperature) - this.state.color = { bri, kelvin } - } else { - this.state.color = { bri, hue: state.hue, sat: state.saturation } - } - } - - async init() { - const info = await this.axios.get("accessory-info") - const lights = await this.axios.get("lights") - - this.config = { - name: info.data.displayName ?? info.data.productName, - plugin: "elgato", - type: "bulb", - rgb: { - available: true, - }, - kelvin: { - available: true, - min: elgatoToKelvin(143), - max: elgatoToKelvin(344), - }, - dimming: { - available: true, - }, - numberOfLights: lights.data.numberOfLights, - } - - this.startPolling() - } - - async setLightState(on, color, duration) { - if (on == "toggle") { - on = !this.state.on - } - on = on ?? this.state.on - - const brightness = color?.bri ?? this.state.color?.bri ?? 100 - - const lightState = { - on, - brightness, - } - - this.state.on = on - - if (color) { - if ("kelvin" in color) { - const kelvin = color?.kelvin ?? this.state.color?.kelvin ?? 3000 - lightState.temperature = kelvinToElgato(kelvin) - this.state.color = { bri: brightness, kelvin } - } else if ("sat" in color || "hue" in color) { - const sat = color?.sat ?? this.state.color?.sat ?? 100 - const hue = color?.hue ?? this.state.color?.hue ?? 0 - lightState.saturation = sat - lightState.hue = hue - this.state.color = { bri: brightness, sat, hue } - } - } - - await this.axios.put("lights", { - numberOfLights: this.config.numberOfLights, - lights: new Array(this.config.numberOfLights).fill(lightState), - }) - } -} - -class ElgatoIoTProvider extends IoTProvider { - constructor(pluginObj) { - super("elgato") - this.pluginObj = pluginObj - this.bonjour = bonjour() - } - - async initServices() { - this.keylightBrowser = this.bonjour.find( - { type: "elg" }, - async (service) => { - if (service.txt.md.includes("Light Strip")) { - //Light strip? - const light = new ElgatoLightStrip(service) - try { - //console.log("Elgato Light!") - await light.init() - - await this._addNewLight(light) - } catch (err) { - console.error(err) - } - } else { - const light = new ElgatoKeyLight(service) - try { - //console.log("Elgato Light!") - await light.init() - - await this._addNewLight(light) - } catch (err) { - console.error(err) - } - } - } - ) - } - - async loadPlugs() { - return [] - } - - async loadLights() { - return [] - } -} - -export default { - name: "elgato", - uiName: "Elgato", - icon: "mdi-lightbulb-on-outline", - color: "#7F743F", - async init() { - this.iotProvider = new ElgatoIoTProvider(this) - }, -} diff --git a/packages/castmate/src/main/plugins/govee.js b/packages/castmate/src/main/plugins/govee.js deleted file mode 100644 index ac68235c..00000000 --- a/packages/castmate/src/main/plugins/govee.js +++ /dev/null @@ -1,468 +0,0 @@ -import { clamp } from "lodash" -import { Govee, Device } from "@j3lte/govee-lan-controller" -import GoveeCloud from "node-govee-led" -import { IoTProvider, Light, Plug } from "../iot/iot-manager" -import { reactify } from "../state/reactive" -import * as chromatism from "chromatism2" -import logger from "../utils/logger" -import util from "util" - -class GoveeBulb extends Light { - constructor(cloudDesc) { - super() - //this.lanDevice = lanDevice - - this.cloudDevice = new GoveeCloud({ - mac: cloudDesc.device, - model: cloudDesc.model, - apiKey: API_KEY, - }) - - const id = cloudDesc.device - - this.id = "govee." + id - this.config = { - name: cloudDesc.deviceName, - goveeId: id, - plugin: "govee", - cloud: true, - type: "bulb", - rgb: { - available: cloudDesc.supportCmds?.includes("color") ?? false, - }, //????? - kelvin: { - available: - cloudDesc.supportCmds?.includes("colorTemp") ?? false, - }, //???? - dimming: { - available: - cloudDesc.supportCmds?.includes("brightness") ?? false, - }, //???? - } - - if (this.config.kelvin.available) { - this.config.kelvin.min = - cloudDesc.properties?.colorTem?.range?.min ?? 2000 - this.config.kelvin.max = - cloudDesc.properties?.colorTem?.range?.max ?? 6000 - } - - this.state = reactify({ - on: false, - color: null, - }) - - this.startPolling() - } - - startPolling() { - this.poll() - setInterval(() => this.poll(), 30 * 1000) - } - - async poll() { - const state = await this.cloudDevice.getState() - - if (state.properties) { - this.state.on = state.properties.powerState == "on" - - const bri = this.state.properties.brightness - const kelvin = this.state.properties.colorTem - const rgb = this.state.properties.color - - const newColor = {} - - if (rgb != null) { - const hsv = chromatism.convert(rgb).hsv - newColor.hue = hsv.h - newColor.sat = hsv.s - newColor.bri = hsv.v - } - - if (kelvin != null) { - newColor.kelvin = kelvin - } - - if (bri != null) { - newColor.bri = bri - } - - this.state.color = newColor - } - } - - async setLightState(on, color, duration) { - //Sadly no cloud duration support. - - if (on == false) { - await this.cloudDevice.turnOff() - this.state.on = false - return - } - - if (on == "toggle") { - if (this.state.on) { - await this.cloudDevice.turnOff() - this.state.on = false - return - } else if (!this.state.on) { - await this.cloudDevice.turnOn() - this.state.on = true - } - } - - if (color) { - if ("hue" in color || "sat" in color) { - const newColor = { ...this.state.color, ...color } - delete newColor.kelvin - - const hex = chromatism.convert({ - h: newColor.hue, - s: newColor.sat, - v: newColor.bri, - }).hex - await this.cloudDevice.setColor(hex) - this.state.color = newColor - } else { - const newColor = { ...this.state.color, ...color } - if ("kelvin" in color) { - delete newColor.hue - delete newColor.sat - const kelvin = color.kelvin - if (this.state.kelvin != kelvin) { - await this.cloudDevice.setColorTemperature(kelvin) - } - } - if ("bri" in color) { - const bri = color.bri - if (this.state.bri != bri) { - await this.cloudDevice.setBrightness(bri) - } - } - this.state.color = newColor - } - } - } -} - -class GoveeLanLight extends Light { - /** - * - * @param {import("@j3lte/govee-lan-controller").Device} goveeDevice - */ - constructor(goveeDevice) { - super() - this.device = goveeDevice - - //console.log(goveeDevice) - - this.id = "govee." + this.device.id - this.config = { - name: this.device.name, - goveeId: this.device.id, - plugin: "govee", - type: "bulb", - rgb: { - available: true, - }, - kelvin: { - available: false, - }, - dimming: { - available: true, - }, - } - - this.state = reactify({ - on: false, - color: null, - }) - - this.device.triggerUpdate() - - this.device.on( - "state_change", - /** - * - * @param {import("@j3lte/govee-lan-controller/build/types/types").GoveeDeviceStatusData} state - */ - (state) => { - this.state.on = state.onOff != 0 - - const hsv = chromatism.convert(state.color).hsv - - this.state.color = { - hue: hsv.h, - sat: hsv.s, - bri: state.brightness, - } - } - ) - } - - async setLightState(on, color, duration) { - if (on == "toggle") { - on = !this.state.on - } - - if (on == false) { - //console.log("Turning Off") - await this.device.turnOff() - this.state.on = on - return - } - - if (color) { - const newColor = { ...this.state.color } - - if ("hue" in color || "sat" in color) { - if (color.hue != null) newColor.hue = color.hue - if (color.sat != null) newColor.sat = color.sat - - delete newColor.kelvin - } - - if ("bri" in color) { - newColor.bri = color.bri - } - - /* - if ("kelvin" in color) { - delete newColor.hue - delete newColor.sat - - newColor.kelvin = color.kelvin - }*/ - - this.state.color = newColor - - const rgb = chromatism.convert({ - h: newColor.hue, - s: newColor.sat, - v: 100, - }).rgb - - await this.device.setColorRGB(rgb) - await this.device.setBrightness(newColor.bri) - - //console.log("Setting Color/Bri", rgb, newColor.bri) - } - - if (on == true) { - //console.log("Turning On") - await this.device.turnOn() - this.state.on = on - } - } -} - -class GoveePlug extends Plug { - constructor(cloudDesc) { - super() - - this.cloudDevice = new GoveeCloud({ - mac: cloudDesc.device, - model: cloudDesc.model, - apiKey: API_KEY, - }) - - const id = cloudDesc.device - this.id = "govee." + id - - this.config = { - name: cloudDesc.deviceName, - goveeId: id, - plugin: "govee", - cloud: true, - } - - this.state = reactify({ - on: false, - }) - - this.startPolling() - } - - startPolling() { - this.poll() - setInterval(() => this.poll(), 30 * 1000) - } - - async poll() { - const state = await this.cloudDevice.getState() - - if (state.properties) { - ;(this.state.on == state.properties.powerState) == "on" - } - } - - async setPlugState(on) { - if (on == "toggle") { - if (this.state.on) { - on = false - } else if (!this.state.on) { - on = true - } - } - - if (on) { - await this.cloudDevice.turnOn() - } else { - await this.cloudDevice.turnOff() - } - this.state.on = on - } -} - -class GoveeIoTProvider extends IoTProvider { - constructor(pluginObj) { - super("govee") - this.pluginObj = pluginObj - } - - startCloudPolling() { - if (!this.pluginObj.secrets.goveeCloudKey) { - return - } - - this.cloudClient = new GoveeCloud({ - apiKey: this.pluginObj.secrets.goveeCloudKey, - mac: "", - model: "", - }) - - this.poll() - this.pollingInterval = setInterval(() => { - this.poll() - }, 30 * 1000) - } - - async poll() { - const { devices } = await this.cloudClient.getDevices() - - const existingLights = this.lights - const existingPlugs = this.plugs - - const newDevices = devices.filter((d) => { - const l = existingLights.find((l) => l.config.goveeId == d.device) - const p = existingPlugs.find((l) => l.config.goveeId == d.device) - return p == null && l == null - }) - - const bulbDevices = newDevices.filter( - (d) => - d.supportCmds?.includes("brightness") || - d.supportCmds?.includes("color") || - d.supportCmds?.includes("colorTem") - ) - - const plugDevices = newDevices.filter( - (d) => - !( - d.supportCmds?.includes("brightness") || - d.supportCmds?.includes("color") || - d.supportCmds?.includes("colorTem") - ) - ) - - const bulbs = bulbDevices.map((d) => new GoveeBulb(d)) - const plugs = plugDevices.map((d) => new GoveePlug(d)) - - bulbs.map((b) => { - this._addNewLight(b) - }) - plugs.map((p) => { - this._addNewPlug(p) - }) - } - - async reset() { - if (this.pollingInterval) { - clearInterval(this.pollingInterval) - } - await this.clearResources() - } - - async secretsChanged() { - await this.reset() - this.startCloudPolling() - } - - startLanPolling() { - if (this.lanInterval) { - clearInterval(this.lanInterval) - } - - this.lanInterval = setInterval(() => { - //console.log("Govee Lan Poll") - this.goveeLan?.discover() - }, 60000) - - //console.log("Govee Lan Poll") - this.goveeLan?.discover() - } - - async initServices() { - this.startCloudPolling() - - this.goveeLan = new Govee({ - discover: false, - }) - - this.goveeLan.on( - "new_device", - /** - * - * @param {import("@j3lte/govee-lan-controller").Device} device - */ - async (device) => { - const lanlight = new GoveeLanLight(device) - - const existingLight = this.lights.find((l) => { - return l.config.goveeId == device.id - }) - if (existingLight) { - await this._removeLight(existingLight) - } - - await this._addNewLight(lanlight) - } - ) - - // TODO: Why does the LAN search sometimes fail? - this.goveeLan - .waitForReady() - .then(() => { - logger.info("Successfully Started Govee LAN") - this.startLanPolling() - }) - .catch((err) => { - logger.error(`Failed to start Govee LAN Controller`) - logger.error(util.inspect(err)) - }) - } - - async loadPlugs() { - return [] - } - - async loadLights() { - return [] - } -} - -export default { - name: "govee", - uiName: "Govee", - icon: "mdi-lightbulb-on-outline", - color: "#7F743F", - async init() { - this.iotProvider = new GoveeIoTProvider(this) - }, - async onSecretsReload() { - this.iotProvider.secretsChanged() - }, - secrets: { - goveeCloudKey: { type: String, name: "Govee Cloud API Key" }, - }, -} diff --git a/packages/castmate/src/main/plugins/lifx.js b/packages/castmate/src/main/plugins/lifx.js deleted file mode 100644 index f383127f..00000000 --- a/packages/castmate/src/main/plugins/lifx.js +++ /dev/null @@ -1,211 +0,0 @@ -import { clamp } from "lodash" -import { Client } from "lifx-lan-client" -import { IoTProvider, Light, Plug } from "../iot/iot-manager" -import { reactify } from "../state/reactive" - -class LIFXBulb extends Light { - /** - * - * @param {import("lifx-lan-client").Light} light - */ - constructor(light, initialState, hardwareInfo) { - super() - this.light = light - this.id = "lifx." + light.id - - this.config = { - name: initialState.label, - plugin: "lifx", - type: "bulb", - rgb: { available: hardwareInfo.productFeatures?.color ?? true }, - kelvin: { available: true, min: 2500, max: 9000 }, - dimming: { available: true }, - } - - this.state = reactify({ - on: false, - color: null, - }) - - this.startPolling() - } - - getCurrentState() { - return new Promise((resolve, reject) => { - this.light.getState((err, data) => { - if (err) { - return reject(err) - } - - const state = { - on: !!data.power, - color: { - bri: data.color.brightness, - }, - } - - if (data.color.saturation == 0) { - state.color.kelvin = data.color.kelvin - } else { - state.color.hue = data.color.hue - state.color.sat = data.color.saturation - } - - resolve(state) - }) - }) - } - - async updateState() { - const state = await this.getCurrentState() - this.state.on = state.on - this.state.color = state.color - } - - startPolling() { - this.updateState() - this.poller = setInterval(async () => { - try { - this.updateState() - } catch {} - }, 30000) - } - - async setLightState(on, color, duration) { - const update = {} - update.on = on - - if (on == "toggle") { - update.on = !this.state.on - } - - if (color != null) { - const newColor = { ...this.state.color } - if ("bri" in color) { - newColor.bri = Math.ceil(clamp(color.bri, 0, 100)) - } - if ("hue" in color || "sat" in color) { - const hue = Math.ceil(clamp(color.hue ?? 0, 0, 360)) - const sat = Math.ceil(clamp(color.sat ?? 100, 0, 100)) - - newColor.hue = hue - newColor.sat = sat - - delete newColor.kelvin - } - - if ("kelvin" in color) { - newColor.kelvin = Math.ceil(color.kelvin) - - delete newColor.hue - delete newColor.sat - } - - update.color = newColor - } - - if (duration != null) { - update.duration = Math.ceil(duration * 1000) - } - - if (!update.on) { - this.light.off(update.duration) - } else { - if (!this.state.on) { - this.light.color( - update.color.hue ?? 0, - update.color.sat ?? 0, - update.color.bri ?? 0, - update.color.kelvin, - 0 - ) - this.light.on(update.duration) - } else { - this.light.color( - update.color.hue ?? 0, - update.color.sat ?? 0, - update.color.bri ?? 0, - update.color.kelvin, - update.duration - ) - } - } - - this.state.on = update.on - } -} - -class LIFXIoTProvider extends IoTProvider { - constructor(pluginObj) { - super("lifx") - this.pluginObj = pluginObj - this.client = new Client() - - this.client.on("light-new", async (light) => { - const [data, hardware] = await Promise.all([ - this._getInitialState(light), - this._getHardwareData(light), - ]) - - const bulb = new LIFXBulb(light, data, hardware) - this._addNewLight(bulb) - }) - } - - /** - * - * @param {import("lifx-lan-client").Light} light - */ - _getInitialState(light) { - return new Promise((resolve, reject) => { - light.getState((err, data) => { - if (err) { - console.error("Error getting hardware data") - return reject(err) - } - - resolve(data) - }) - }) - } - - /** - * - * @param {import("lifx-lan-client").Light} light - */ - _getHardwareData(light) { - return new Promise((resolve, reject) => { - light.getHardwareVersion((err, data) => { - if (err) { - console.error("Error getting hardware data") - return reject(err) - } - - resolve(data) - }) - }) - } - - async initServices() { - console.log("Starting LIFX Discovery...") - this.client.init() - } - - async loadPlugs() { - return [] - } - - async loadLights() { - return [] - } -} - -export default { - name: "lifx", - uiName: "LIFX Lights", - icon: "mdi-lightbulb-on-outline", - color: "#7F743F", - async init() { - this.iotProvider = new LIFXIoTProvider(this) - }, -} diff --git a/packages/castmate/src/main/plugins/wyze.js b/packages/castmate/src/main/plugins/wyze.js deleted file mode 100644 index 59dba99c..00000000 --- a/packages/castmate/src/main/plugins/wyze.js +++ /dev/null @@ -1,529 +0,0 @@ -import { IoTProvider, Light, Plug } from "../iot/iot-manager" -import { reactify } from "../state/reactive" -import moment from "moment" -import axios from "axios" -import md5 from "md5" -import logger from "../utils/logger" -import { clamp, isBoolean } from "lodash" -import * as chromatism from "chromatism2" - -const LIGHT_PRODUCT_TYPES = ["MeshLight"] -const PLUG_PRODUCT_TYPES = ["Plug"] - -// Borrowed from wyze-node - -const WYZE_API_KEY = "WMXHYf79Nr5gIlt3r0r7p9Tcw5bvs6BB4U8O8nGJ" -const WYZE_AUTH_URL = "https://auth-prod.api.wyze.com" -const WYZE_API_URL = "https://api.wyzecam.com" -const WYZE_USER_AGENT = "wyze_ios_2.21.35" -const WYZE_PHONE_ID = "wyze_developer_api" -const WYZE_APP_VERSION = "wyze_developer_api" -const WYZE_SC = "wyze_developer_api" -const WYZE_SV = "wyze_developer_api" - -const WyzeProps = { - power: "P3", - color: "P1507", - brightness: "P1501", - colorTemp: "P1502", //Kelvin [1800, 6500] -} - -const WyzePropsReverse = {} - -for (let k in WyzeProps) { - WyzePropsReverse[WyzeProps[k]] = k -} - -function propsToWyzeList(props = {}) { - return Object.keys(props).map((k) => { - const wyzePid = WyzeProps[k] - let value = props[k] - - if (isBoolean(value)) { - value = value ? 1 : 0 - } - - return { - pid: wyzePid, - pvalue: value.toString(), - } - }) -} - -class WyzeApi { - constructor(pluginObj) { - this.pluginObj = pluginObj - } - - get username() { - return this.pluginObj.settings.username - } - - get password() { - return this.pluginObj.secrets.password - } - - get keyId() { - return this.pluginObj.secrets.keyId - } - - get apiKey() { - return this.pluginObj.secrets.apiKey - } - - async clearTokens() { - await this.pluginObj.wyzeAuthCache.set({}) - } - - async getTokens() { - return (await this.pluginObj.wyzeAuthCache.get()) ?? {} - } - - async setTokens(newTokens) { - await this.pluginObj.wyzeAuthCache.set(newTokens) - } - - _formatRequestBody(moreData) { - return { - phone_id: WYZE_PHONE_ID, - app_ver: WYZE_APP_VERSION, - sc: WYZE_SC, - sv: WYZE_SV, - ts: moment().unix(), - ...moreData, - } - } - - async login() { - try { - if ( - !this.username || - !this.password || - !this.apiKey || - !this.keyId - ) { - return false - } - - logger.info("Doing Wyze Login") - const result = await axios.post( - `https://auth-prod.api.wyze.com/api/user/login`, - { - email: this.username, - password: md5(md5(md5(this.password))), - }, - { - headers: { - Keyid: this.keyId, - Apikey: this.apiKey, - "user-agent": WYZE_USER_AGENT, - "phone-id": WYZE_PHONE_ID, - }, - } - ) - - const { access_token: accessToken, refresh_token: refreshToken } = - result.data - - if (!accessToken || !refreshToken) { - logger.info("Wyze Login Failed") - return false - } - - await this.setTokens({ - accessToken, - refreshToken, - }) - - return true - } catch (err) { - console.error("LOGIN ERR") - logger.info("Wyze Login Failed") - return false - } - } - - async refreshAccessToken() { - let { refreshToken: oldRefreshToken } = await this.getTokens() - - const result = await axios.post( - `${WYZE_API_URL}/app/user/refresh_token`, - this._formatRequestBody({ - refresh_token: oldRefreshToken, - }) - ) - - const { access_token: accessToken, refresh_token: refreshToken } = - result.data - - await this.setTokens({ - accessToken, - refreshToken, - }) - } - - async tryAuth() { - const { accessToken } = await this.getTokens() - - if (!accessToken) { - if (!(await this.login())) { - return false - } - } - - return true - } - - async _apiRequest(path, data) { - const { accessToken } = await this.getTokens() - - if (!accessToken) { - if (!(await this.login())) { - throw new Error("Unable to login") - } - } - - let result = await axios.post( - `${WYZE_API_URL}${path}`, - this._formatRequestBody({ - access_token: accessToken, - ...data, - }) - ) - - if (result.data.msg === "AccessTokenError") { - await this.refreshAccessToken() - - const { accessToken: newAccessToken } = await this.getTokens() - - result = await axios.post( - `${WYZE_API_URL}${path}`, - this._formatRequestBody({ - access_token: newAccessToken, - ...data, - }) - ) - - if (result.data.msg === "AccessTokenError") { - throw new Error("Unable to refresh access token!") - } - } - - return result - } - - async _runAction(mac, model, actionKey, props) { - const data = { - provider_key: model, - instance_id: mac, - action_key: actionKey, - action_params: props - ? { - list: [ - { - mac, - plist: propsToWyzeList(props), - }, - ], - } - : {}, - custom_string: "", - } - - const result = await this._apiRequest("/app/v2/auto/run_action", data) - - // console.log( - // "Action", - // actionKey, - // mac, - // model, - // data.action_params.list?.[0]?.plist - // ) - - //console.log(result.data) - - return result - } - - async getDevices() { - const result = await this._apiRequest( - "/app/v2/home_page/get_object_list" - ) - return result.data.data.device_list ?? [] - } - - async turnOff(deviceMac, deviceModel) { - return await this._runAction(deviceMac, deviceModel, "power_off") - } - - async turnOn(deviceMac, deviceModel) { - return await this._runAction(deviceMac, deviceModel, "power_on") - } - - async getDeviceInfo(deviceMac, deviceModel) { - const result = await this._apiRequest( - "/app/v2/device/get_device_info", - { - device_mac: deviceMac, - device_model: deviceModel, - } - ) - - return result.data.data - } - - async getDeviceState(deviceMac, deviceModel) { - const info = await this.getDeviceInfo(deviceMac, deviceModel) - - const result = {} - - for (const prop of info.property_list) { - const propName = WyzePropsReverse[prop.pid] - if (propName) { - const value = prop.value - if (propName != "color") { - const num = Number.parseInt(value) - if (!isNaN(num)) { - result[propName] = num - } - } else { - result[propName] = value - } - } - } - - // console.log("DeviceState", deviceMac, deviceModel, result) - - return result - } -} - -class WyzePlug extends Plug { - constructor(wyzeDevice, wyze) { - super() - - this.id = "wyze." + wyzeDevice.mac - this.mac = wyzeDevice.mac - this.model = wyzeDevice.product_model - - this.wyze = wyze - - this.config = { - name: wyzeDevice.nickname, - plugin: "wyze", - type: "plug", - } - - this.state = reactify({ - on: wyzeDevice.device_params.switch_state == 1, - }) - } - - async setPlugState(on) { - if (on === "toggle") { - const { power } = await this.wyze.getDeviceState( - this.mac, - this.model - ) - on = !power - } - - if (on) { - await this.wyze.turnOn(this.mac, this.model) - } else { - await this.wyze.turnOff(this.mac, this.model) - } - } -} - -class WyzeBulb extends Light { - constructor(wyzeDevice, wyze) { - super() - - this.id = "wyze." + wyzeDevice.mac - this.mac = wyzeDevice.mac - this.model = wyzeDevice.product_model - - this.wyze = wyze - - this.config = { - name: wyzeDevice.nickname, - plugin: "wyze", - type: "bulb", - rgb: { available: true }, - dimming: { available: true }, - kelvin: { available: true, min: 1800, max: 6500 }, - } - } - - async setLightState(on, color, duration) { - if (on === "toggle") { - const { power } = await this.wyze.getDeviceState( - this.mac, - this.model - ) - on = !power - } - - const props = {} - - if (on != null) { - props.power = on ? "1" : "0" - } - - if ("bri" in color) { - props.brightness = clamp(Number(color.bri), 0, 100) - } - - if ("hue" in color || "sat" in color) { - const hue = clamp(color.hue ?? 0, 0, 360) - const sat = clamp(color.sat ?? 100, 0, 100) - const bri = clamp(color.bri ?? 100, 0, 100) - props.color = chromatism - .convert({ h: hue, s: sat, v: bri }) - .hex.substring(1) - } - - if ("kelvin" in color) { - props.colorTemp = clamp(Math.round(color.kelvin), 2000, 6500) - } - - await this.wyze._runAction( - this.mac, - this.model, - "set_mesh_property", - props - ) - } -} - -class WyzeIotProvider extends IoTProvider { - constructor(pluginObj) { - super("wyze") - - this.pluginObj = pluginObj - this.wyze = new WyzeApi(this.pluginObj) - } - - setupPolling() { - if (this.pollingInterval) { - clearInterval(this.pollingInterval) - this.pollingInterval = null - } - this.pollingInterval = setInterval( - () => this.refreshDevices(), - 30 * 1000 - ) - } - - async relog() { - if (this.relogging) { - return - } - this.relogging = true - - await this.clearResources() - - await this.wyze.clearTokens() - - if (await this.wyze.login()) { - await this.refreshDevices() - this.setupPolling() - } - this.relogging = false - } - - async refreshDevices() { - try { - if (!(await this.wyze.tryAuth())) { - return false - } - - const existingLights = this.lights - const existingPlugs = this.plugs - - const devices = await this.wyze.getDevices() - - const newDevices = devices.filter((wd) => { - const l = existingLights.find((l) => l.mac == wd.mac) - const p = existingPlugs.find((p) => p.mac == wd.mac) - return (p == null) & (l == null) - }) - - for (let device of newDevices) { - if (PLUG_PRODUCT_TYPES.includes(device.product_type)) { - //PLUG - // console.log("Plug!", device) - const plug = new WyzePlug(device, this.wyze) - - await this._addNewPlug(plug) - } else if (LIGHT_PRODUCT_TYPES.includes(device.product_type)) { - // console.log("LIGHT", device) - const bulb = new WyzeBulb(device, this.wyze) - await this._addNewLight(bulb) - } else { - console.log("UNKNOWN", device.nickname, device.product_type) - } - } - } catch (err) { - //No auth - console.log("ERROR WITH WYZE", err) - return false - } - return true - } - - async initServices() { - if (await this.refreshDevices()) { - this.setupPolling() - } - } - - async loadPlugs() { - return [] - } - - async loadLights() { - return [] - } -} - -export default { - name: "wyze", - uiName: "Wyze", - icon: "mdi-pencil", - color: "#3F918D", - async init() { - this.wyzeAuthCache = this.getCache("wyzeAuth", true) - this.iotProvider = new WyzeIotProvider(this) - }, - async onSecretsReload() { - await this.wyzeAuthCache.set({}) - await this.iotProvider.relog() - }, - async onSettingsReload() { - await this.wyzeAuthCache.set({}) - await this.iotProvider.relog() - }, - secrets: { - password: { - type: String, - name: "Wyze Account Password", - }, - keyId: { - type: String, - name: "Wyze API Key ID", - }, - apiKey: { - type: String, - name: "Wyze API Key", - }, - }, - settings: { - username: { - type: String, - name: "Wyze Account Username", - }, - }, - settingsView: "wyze", -} diff --git a/packages/castmate/src/renderer/components/settings/SettingsPage.vue b/packages/castmate/src/renderer/components/settings/SettingsPage.vue index 72fa0e03..ca4402e2 100644 --- a/packages/castmate/src/renderer/components/settings/SettingsPage.vue +++ b/packages/castmate/src/renderer/components/settings/SettingsPage.vue @@ -53,8 +53,11 @@ import { DataInput, useResourceStore, SettingDefinition, + useSettingWatcher, + useDocumentId, + useDocument, } from "castmate-ui-core" -import { computed, ref, useModel } from "vue" +import { computed, ref, useModel, watch } from "vue" import { SettingsDocumentData, SettingsViewData } from "./SettingsTypes" import PInputText from "primevue/inputtext" @@ -69,11 +72,16 @@ const view = useModel(props, "view") const pluginStore = usePluginStore() const resourceStore = useResourceStore() -function getPlugin(id: string) { - const plugin = pluginStore.pluginMap.get(id) - if (!plugin) throw new Error() - return plugin -} +const documentId = useDocumentId() + +const document = useDocument(() => documentId.value) + +useSettingWatcher((plugin, setting, value) => { + if (!document.value) return + if (!document.value.data.settings) return + if (!document.value.data.settings[plugin]) return + document.value.data.settings[plugin][setting] = value +}) const filteredSettings = computed(() => { const filterValue = view.value.filter.toLocaleLowerCase() diff --git a/packages/castmate/src/renderer/util/queues.ts b/packages/castmate/src/renderer/util/queues.ts index 47acb23e..f0a2b9c7 100644 --- a/packages/castmate/src/renderer/util/queues.ts +++ b/packages/castmate/src/renderer/util/queues.ts @@ -27,7 +27,7 @@ export function initializeQueues() { properties: { name: { type: String, name: "Name", required: true }, paused: { type: Boolean, name: "Paused", required: true, default: false }, - gap: { type: Duration, name: "Duration", required: true, default: 0 }, + gap: { type: Duration, name: "Gap", required: true, default: 0 }, }, }) resourceStore.registerEditComponent("ActionQueue", ResourceSchemaEdit) diff --git a/packages/castmate/vite.config.mts b/packages/castmate/vite.config.mts index 805d6584..09731a9a 100644 --- a/packages/castmate/vite.config.mts +++ b/packages/castmate/vite.config.mts @@ -48,6 +48,10 @@ export default defineConfig({ }, }, }, + onstart(args) { + console.log("Vite Electron Start") + args.startup([".", "--no-sandbox"]) + }, }), //subpackage("castmate-overlay-components"), library("castmate-ui-core"), diff --git a/plugins/discord/renderer/package.json b/plugins/discord/renderer/package.json index d724731d..a7de241d 100644 --- a/plugins/discord/renderer/package.json +++ b/plugins/discord/renderer/package.json @@ -15,7 +15,7 @@ "@vueuse/core": "*", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/elgato/renderer/package.json b/plugins/elgato/renderer/package.json index 9de79e9b..d4410d87 100644 --- a/plugins/elgato/renderer/package.json +++ b/plugins/elgato/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/govee/renderer/package.json b/plugins/govee/renderer/package.json index a4fa4d49..f45fe776 100644 --- a/plugins/govee/renderer/package.json +++ b/plugins/govee/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/http/renderer/package.json b/plugins/http/renderer/package.json index 017e8c7e..e4883581 100644 --- a/plugins/http/renderer/package.json +++ b/plugins/http/renderer/package.json @@ -15,7 +15,7 @@ "@vueuse/core": "*", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/input/main/src/mouse.ts b/plugins/input/main/src/mouse.ts index a0830057..efba5c4d 100644 --- a/plugins/input/main/src/mouse.ts +++ b/plugins/input/main/src/mouse.ts @@ -15,10 +15,17 @@ export function setupMouse(inputInterface: InputInterface) { name: "Button", default: "left", enum: ["left", "right", "middle", "mouse4", "mouse5"], + required: true, }, duration: { type: Duration, name: "Duration", required: true, default: 0.1 }, }, }, + duration: { + dragType: "length", + rightSlider: { + sliderProp: "duration", + }, + }, async invoke(config, contextData, abortSignal) { inputInterface.simulateMouseDown(config.button as MouseButton) diff --git a/plugins/input/native/src/input-interface.cc b/plugins/input/native/src/input-interface.cc index 42cc4b8e..1235884d 100644 --- a/plugins/input/native/src/input-interface.cc +++ b/plugins/input/native/src/input-interface.cc @@ -13,7 +13,7 @@ Napi::Object input_interface::init(Napi::Env env, Napi::Object exports) InstanceMethod("simulateKeyDown", &input_interface::simulate_key_down), InstanceMethod("simulateKeyUp", &input_interface::simulate_key_up), InstanceMethod("simulateMouseDown", &input_interface::simulate_mouse_down), - InstanceMethod("simulateMouseUp", &input_interface::simulate_mouse_down), + InstanceMethod("simulateMouseUp", &input_interface::simulate_mouse_up), InstanceMethod("startEvents", &input_interface::start_events), InstanceMethod("stopEvents", &input_interface::stop_events), InstanceMethod("isKeyDown", &input_interface::is_key_down), diff --git a/plugins/input/native/src/native-index.cc b/plugins/input/native/src/native-index.cc index a48f47c6..fae4b86d 100644 --- a/plugins/input/native/src/native-index.cc +++ b/plugins/input/native/src/native-index.cc @@ -7,40 +7,11 @@ #include "input-interface.hh" -class uv_message_pump -{ - uv_idle_t idle; -public: - uv_message_pump(Napi::Env env) - { - uv_loop_s* loop; - napi_get_uv_event_loop(env, &loop); - uv_idle_init(loop, &idle); - uv_idle_start(&idle, &uv_message_pump::message_pump); - } - - ~uv_message_pump() - { - uv_idle_stop(&idle); - } - - static void message_pump(uv_idle_t* handle) - { - MSG msg; - if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } -}; class instance_data { - uv_message_pump pump; public: instance_data(Napi::Env env) - : pump(env) { } diff --git a/plugins/input/renderer/package.json b/plugins/input/renderer/package.json index e15748c1..7464b91b 100644 --- a/plugins/input/renderer/package.json +++ b/plugins/input/renderer/package.json @@ -17,7 +17,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/iot/renderer/package.json b/plugins/iot/renderer/package.json index 79354f18..fc337ea4 100644 --- a/plugins/iot/renderer/package.json +++ b/plugins/iot/renderer/package.json @@ -17,7 +17,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/lifx/renderer/package.json b/plugins/lifx/renderer/package.json index edc5db54..27f0f847 100644 --- a/plugins/lifx/renderer/package.json +++ b/plugins/lifx/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/minecraft/renderer/package.json b/plugins/minecraft/renderer/package.json index b54ee94f..4937e07b 100644 --- a/plugins/minecraft/renderer/package.json +++ b/plugins/minecraft/renderer/package.json @@ -15,7 +15,7 @@ "@vueuse/core": "*", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/obs/main/src/auto-connect.ts b/plugins/obs/main/src/auto-connect.ts index 20b46db5..3fb0f9d3 100644 --- a/plugins/obs/main/src/auto-connect.ts +++ b/plugins/obs/main/src/auto-connect.ts @@ -32,7 +32,7 @@ export async function attemptQRReading() { const port = Number(portStr) return { - host: ip, + host: "localhost", port, password, } diff --git a/plugins/obs/main/src/connection.ts b/plugins/obs/main/src/connection.ts index 2b2123e8..4426f142 100644 --- a/plugins/obs/main/src/connection.ts +++ b/plugins/obs/main/src/connection.ts @@ -151,8 +151,11 @@ export class OBSConnection extends FileResource { + if (this.state.connected) { + logger.log("Connection Closed", err.code, err.name, err.message) + } + this.state.connected = false - //logger.log("Connection Closed", err.code, err.name, err.message) if (this.forceStop) { logger.log("Force Stopping OBS Connection Loop") @@ -203,13 +206,11 @@ export class OBSConnection extends FileResource { - //logger.log("Identified!") + logger.log("OBS Identified Ver:", ev.negotiatedRpcVersion) this.state.connected = true this.queryInitialStateLoop() }) - - //this.connection.on("") } private async queryInitialStateLoop() { @@ -244,6 +245,32 @@ export class OBSConnection extends FileResource((resolve, reject) => { + nextTick(() => { + //logger.log("Trying Connection", `ws://${hostname}:${port}`, password) + this.connection + .connect(`ws://${hostname}:${port}`, password) + .then((result) => { + logger.log( + "Connected to OBS", + `ws://${hostname}:${port}`, + "ver", + result.rpcVersion, + "negotiatedVer", + result.negotiatedRpcVersion, + "socketVer", + result.obsWebSocketVersion + ) + resolve(true) + }) + .catch((err) => { + reject(err) + }) + }) + }) + } + private async tryConnect(hostname: string, port: number, password?: string) { if (this.retryTimeout) { clearTimeout(this.retryTimeout) @@ -254,13 +281,13 @@ export class OBSConnection extends FileResource { - //logger.log("Trying Connection", `ws://${hostname}:${port}`, password) - this.connection.connect(`ws://${hostname}:${port}`, password).catch((err) => { - //logger.log("Connect Failed", err) - }) - }) + await this.nextTickConnect(hostname, port, password) return true } catch (err) { diff --git a/plugins/obs/renderer/package.json b/plugins/obs/renderer/package.json index 8fc6204b..bb326dec 100644 --- a/plugins/obs/renderer/package.json +++ b/plugins/obs/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/os/renderer/package.json b/plugins/os/renderer/package.json index 1c10e8e3..4c367e7c 100644 --- a/plugins/os/renderer/package.json +++ b/plugins/os/renderer/package.json @@ -17,7 +17,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/overlays/overlay/package.json b/plugins/overlays/overlay/package.json index cbedd676..db08ae94 100644 --- a/plugins/overlays/overlay/package.json +++ b/plugins/overlays/overlay/package.json @@ -16,7 +16,7 @@ "castmate-overlay-core": "workspace:^", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "primevue": "*", diff --git a/plugins/overlays/renderer/package.json b/plugins/overlays/renderer/package.json index 8f5b1fd9..ea4107c6 100644 --- a/plugins/overlays/renderer/package.json +++ b/plugins/overlays/renderer/package.json @@ -20,7 +20,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "font-list": "^1.5.1", "nanoid": "^5.0.7", "pinia": "^2.1.7", diff --git a/plugins/philips-hue/renderer/package.json b/plugins/philips-hue/renderer/package.json index 41e606f5..b51f8a26 100644 --- a/plugins/philips-hue/renderer/package.json +++ b/plugins/philips-hue/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/random/overlay/package.json b/plugins/random/overlay/package.json index 3ee5ad02..54d14f6a 100644 --- a/plugins/random/overlay/package.json +++ b/plugins/random/overlay/package.json @@ -16,7 +16,7 @@ "castmate-overlay-core": "workspace:^", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "primevue": "*", diff --git a/plugins/random/renderer/package.json b/plugins/random/renderer/package.json index 89f53dbf..ae23992b 100644 --- a/plugins/random/renderer/package.json +++ b/plugins/random/renderer/package.json @@ -17,7 +17,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "*", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/remote/renderer/package.json b/plugins/remote/renderer/package.json index 1bf6350b..57d1ff09 100644 --- a/plugins/remote/renderer/package.json +++ b/plugins/remote/renderer/package.json @@ -17,7 +17,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "*", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/sound/renderer/package.json b/plugins/sound/renderer/package.json index 9fcb0844..efe8fba4 100644 --- a/plugins/sound/renderer/package.json +++ b/plugins/sound/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/spellcast/renderer/package.json b/plugins/spellcast/renderer/package.json index dceaf092..0b5d0c9e 100644 --- a/plugins/spellcast/renderer/package.json +++ b/plugins/spellcast/renderer/package.json @@ -17,7 +17,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/stream-plans/renderer/package.json b/plugins/stream-plans/renderer/package.json index 35af5b5b..ec6c3457 100644 --- a/plugins/stream-plans/renderer/package.json +++ b/plugins/stream-plans/renderer/package.json @@ -17,7 +17,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "*", + "electron": "29.4.5", "nanoid": "*", "pinia": "*", "vue": "*" diff --git a/plugins/time/renderer/package.json b/plugins/time/renderer/package.json index 9d6dc7c6..30b7d47e 100644 --- a/plugins/time/renderer/package.json +++ b/plugins/time/renderer/package.json @@ -16,7 +16,7 @@ "castmate-plugin-time-shared": "workspace:^", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/tplink-kasa/renderer/package.json b/plugins/tplink-kasa/renderer/package.json index 820a99b1..83f92c1d 100644 --- a/plugins/tplink-kasa/renderer/package.json +++ b/plugins/tplink-kasa/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/twinkly/renderer/package.json b/plugins/twinkly/renderer/package.json index 57d96a69..1547bfab 100644 --- a/plugins/twinkly/renderer/package.json +++ b/plugins/twinkly/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/twitch/main/package.json b/plugins/twitch/main/package.json index c54ea1e6..803f9fc4 100644 --- a/plugins/twitch/main/package.json +++ b/plugins/twitch/main/package.json @@ -24,7 +24,7 @@ "castmate-plugin-overlays-main": "workspace:^", "castmate-plugin-twitch-shared": "workspace:^", "castmate-schema": "workspace:^", - "electron": "^29.1.1", + "electron": "29.4.5", "fuzzysort": "^2.0.4" } } diff --git a/plugins/twitch/main/src/channelpoints.ts b/plugins/twitch/main/src/channelpoints.ts index c183f965..bbda4357 100644 --- a/plugins/twitch/main/src/channelpoints.ts +++ b/plugins/twitch/main/src/channelpoints.ts @@ -159,7 +159,7 @@ export class ChannelPointReward extends Resource): Promise { await super.applyConfig(config) - await this.updateTwitchServers() + await this.updateServersSafe() await this.initializeReactivity() await this.save() return true @@ -167,7 +167,7 @@ export class ChannelPointReward extends Resource { await super.setConfig(config) - await this.updateTwitchServers() + await this.updateServersSafe() await this.initializeReactivity() await this.save() return true @@ -384,7 +384,7 @@ export class ChannelPointReward extends Resource { - await this.updateTwitchServers() + await this.updateServersSafe() }, 300) async initializeReactivity() { @@ -392,30 +392,34 @@ export class ChannelPointReward extends Resource await this.getHelixRewardData(), this.updateServerDebounced) } - private async updateTwitchServers() { + private async updateServersSafe() { try { - if (!this.config.controllable) return - if (this.config.transient) return - - if (!TwitchAccount.channel.config.isAffiliate) return - - const helixData = await this.getHelixRewardData() - if (this.config.twitchId) { - const update = await TwitchAccount.channel.apiClient.channelPoints.updateCustomReward( - TwitchAccount.channel.twitchId, - this.config.twitchId, - helixData - ) - await this.updateFromTwurple(update) - } else { - const created = await TwitchAccount.channel.apiClient.channelPoints.createCustomReward( - TwitchAccount.channel.twitchId, - helixData - ) - await this.updateFromTwurple(created) - } + await this.updateTwitchServers() } catch (err) { - logger.error("Error Updating Channel Point Reward", this.config.name, err) + logger.error("Error Updating ChannelPointReward", this.id, err) + } + } + + private async updateTwitchServers() { + if (!this.config.controllable) return + if (this.config.transient) return + + if (!TwitchAccount.channel.config.isAffiliate) return + + const helixData = await this.getHelixRewardData() + if (this.config.twitchId) { + const update = await TwitchAccount.channel.apiClient.channelPoints.updateCustomReward( + TwitchAccount.channel.twitchId, + this.config.twitchId, + helixData + ) + await this.updateFromTwurple(update) + } else { + const created = await TwitchAccount.channel.apiClient.channelPoints.createCustomReward( + TwitchAccount.channel.twitchId, + helixData + ) + await this.updateFromTwurple(created) } } } diff --git a/plugins/twitch/main/src/chat.ts b/plugins/twitch/main/src/chat.ts index 58481c20..5643e416 100644 --- a/plugins/twitch/main/src/chat.ts +++ b/plugins/twitch/main/src/chat.ts @@ -252,7 +252,7 @@ export function setupChat() { type: Object, properties: { bits: { type: Range, name: "Bits Cheered", required: true, default: {} }, - group: { type: TwitchViewerGroup, name: "Viewer Group", required: true, default: {} }, + group: { type: TwitchViewerGroup, name: "Viewer Group", required: true, default: {}, anonymous: true }, }, }, context: { @@ -307,7 +307,7 @@ export function setupChat() { bits({ bits: event.bits, - viewer: event.userId ?? "anonymouse", + viewer: event.userId ?? "anonymous", message: event.message, }) }) diff --git a/plugins/twitch/main/src/group.ts b/plugins/twitch/main/src/group.ts index bdf85f80..d7eb45ee 100644 --- a/plugins/twitch/main/src/group.ts +++ b/plugins/twitch/main/src/group.ts @@ -163,6 +163,10 @@ async function satisfiesRule(userId: string, rule: TwitchViewerGroupRule): Promi return false } else if ("properties" in rule) { //Todo: Make this not silly hardcoded + if (rule.properties.anonymous && userId == "anonymous") return true + + if (userId == "anonymous") return false + if (rule.properties.following) { if (await ViewerCache.getInstance().getIsFollowing(userId)) return true } @@ -198,6 +202,10 @@ async function satisfiesRule(userId: string, rule: TwitchViewerGroupRule): Promi return false } +export async function isEmptyTwitchViewerGroup(group: TwitchViewerGroup) { + return !group?.rule +} + export async function inTwitchViewerGroup(userId: string, group: TwitchViewerGroup) { if (!group?.rule) return true //Empty is everyone diff --git a/plugins/twitch/main/src/subscriptions.ts b/plugins/twitch/main/src/subscriptions.ts index 0725a8ba..65c39b22 100644 --- a/plugins/twitch/main/src/subscriptions.ts +++ b/plugins/twitch/main/src/subscriptions.ts @@ -6,7 +6,7 @@ import { Range } from "castmate-schema" import { TwitchAPIService, onBotAuth, onChannelAuth } from "./api-harness" import { ViewerCache } from "./viewer-cache" import { TwitchViewer, TwitchViewerGroup } from "castmate-plugin-twitch-shared" -import { inTwitchViewerGroup } from "./group" +import { inTwitchViewerGroup, isEmptyTwitchViewerGroup } from "./group" export function setupSubscriptions() { const logger = usePluginLogger() @@ -63,13 +63,17 @@ export function setupSubscriptions() { config: { type: Object, properties: { + tier1: { type: Boolean, name: "Tier 1", required: true, default: true }, + tier2: { type: Boolean, name: "Tier 2", required: true, default: true }, + tier3: { type: Boolean, name: "Tier 3", required: true, default: true }, subs: { type: Range, name: "Subs Gifted", required: true, default: { min: 1 } }, - group: { type: TwitchViewerGroup, name: "Viewer Group", required: true }, + group: { type: TwitchViewerGroup, name: "Viewer Group", required: true, anonymous: true }, }, }, context: { type: Object, properties: { + tier: { type: Number, required: true, default: 1 }, gifter: { type: TwitchViewer, required: true, default: "27082158" }, subs: { type: Number, required: true, default: 2 }, }, @@ -165,7 +169,8 @@ export function setupSubscriptions() { } giftSub({ - gifter: event.gifterId, + tier, + gifter: event.isAnonymous ? "anonymous" : event.gifterId, subs: event.amount, }) }) diff --git a/plugins/twitch/main/src/viewer-cache.ts b/plugins/twitch/main/src/viewer-cache.ts index d4acfa2d..a423ee53 100644 --- a/plugins/twitch/main/src/viewer-cache.ts +++ b/plugins/twitch/main/src/viewer-cache.ts @@ -47,6 +47,8 @@ interface CachedTwitchViewer extends Partial { } function getNValues(set: Set, requiredValues: T[], n: number): T[] { + requiredValues = requiredValues.filter((id) => id != "anonymous") + const result = [...requiredValues] if (result.length >= n) { @@ -245,6 +247,7 @@ export const ViewerCache = Service( private getOrCreate(userId: string) { if (userId == "") throw new Error("No empty IDs!") + if (userId == "anonymous") throw new Error("No anonymous!") let cached = this._viewerLookup.get(userId) if (!cached) { @@ -280,6 +283,8 @@ export const ViewerCache = Service( } async getChatColor(userId: string): Promise { + if (userId == "anonymous") return "default" + const cached = this.getOrCreate(userId) if (cached.color != null) { return cached.color @@ -345,6 +350,8 @@ export const ViewerCache = Service( } cacheGiftSubEvent(event: EventSubChannelSubscriptionGiftEvent) { + if (event.isAnonymous) return + const cached = this.getOrCreate(event.gifterId) this.markSeen(cached) this.updateNameCache(cached, event.gifterDisplayName) @@ -376,6 +383,8 @@ export const ViewerCache = Service( private async queryFollowing(...userIds: string[]) { try { + userIds = userIds.filter((id) => id != "anonymous") + //Annoyingly check each follow independently const followingPromises = userIds.map((id) => TwitchAccount.channel.apiClient.channels.getChannelFollowers(TwitchAccount.channel.twitchId, id) @@ -402,6 +411,8 @@ export const ViewerCache = Service( } async getIsFollowing(userId: string): Promise { + if (userId == "anonymous") return false + const cached = this.getOrCreate(userId) if (cached.following != null) { return cached.following @@ -517,7 +528,10 @@ export const ViewerCache = Service( const neededUserInfoIds: string[] = [] const neededFollowerIds: string[] = [] - const cachedUsers = userIds.map((id) => this.getOrCreate(id)) + const cachedUsers = userIds.map((id) => { + if (id == "anonymous") return TwitchViewer.anonymous + return this.getOrCreate(id) + }) for (const cached of cachedUsers) { if (cached.subbed == null || (cached.subbed === true && cached.sub == null)) { @@ -573,7 +587,10 @@ export const ViewerCache = Service( } async getDisplayDatasByIds(userIds: string[]): Promise { - const users = userIds.map((id) => this.getOrCreate(id)) + const users = userIds.map((id) => { + if (id == "anonymous") return TwitchViewer.anonymous + return this.getOrCreate(id) + }) const needsColors: string[] = [] const needsUserInfo: string[] = [] @@ -611,7 +628,7 @@ export const ViewerCache = Service( async getDisplayDataById(userId: string): Promise { if (!userId) return undefined - const cached = this.getOrCreate(userId) + const cached = userId == "anonymous" ? TwitchViewer.anonymous : this.getOrCreate(userId) const queries: Promise[] = [] diff --git a/plugins/twitch/overlay/package.json b/plugins/twitch/overlay/package.json index 3685f557..f0223868 100644 --- a/plugins/twitch/overlay/package.json +++ b/plugins/twitch/overlay/package.json @@ -18,7 +18,7 @@ "castmate-plugin-twitch-shared": "workspace:^", "castmate-schema": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "matter-js": "^0.19.0", "nanoid": "^5.0.7", "pinia": "^2.1.7", diff --git a/plugins/twitch/renderer/package.json b/plugins/twitch/renderer/package.json index 0a095d2e..8f67c46c 100644 --- a/plugins/twitch/renderer/package.json +++ b/plugins/twitch/renderer/package.json @@ -17,7 +17,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/twitch/renderer/src/components/TwitchViewerGroupEdit.vue b/plugins/twitch/renderer/src/components/TwitchViewerGroupEdit.vue index b8a79905..73b34742 100644 --- a/plugins/twitch/renderer/src/components/TwitchViewerGroupEdit.vue +++ b/plugins/twitch/renderer/src/components/TwitchViewerGroupEdit.vue @@ -5,6 +5,7 @@ Customize diff --git a/plugins/twitch/renderer/src/components/groups/TwitchViewerGroupResourceRef.vue b/plugins/twitch/renderer/src/components/groups/TwitchViewerGroupResourceRef.vue index 6c0fff1d..2dce22a8 100644 --- a/plugins/twitch/renderer/src/components/groups/TwitchViewerGroupResourceRef.vue +++ b/plugins/twitch/renderer/src/components/groups/TwitchViewerGroupResourceRef.vue @@ -2,7 +2,7 @@ - + - - - - - - diff --git a/plugins/twitch/renderer/src/components/groups/TwitchViewerGroupRuleNegator.vue b/plugins/twitch/renderer/src/components/groups/TwitchViewerGroupRuleNegator.vue index 0ec321cb..01b4e582 100644 --- a/plugins/twitch/renderer/src/components/groups/TwitchViewerGroupRuleNegator.vue +++ b/plugins/twitch/renderer/src/components/groups/TwitchViewerGroupRuleNegator.vue @@ -7,24 +7,28 @@ v-model="internalModel" v-model:excluded="excluded" @delete="emit('delete')" + :schema="schema" /> @@ -39,6 +43,7 @@ import { isGroupResourceRef, isInlineViewerGroup, isViewerGroupPropertyRule, + SchemaTwitchViewerGroup, } from "castmate-plugin-twitch-shared" import TwitchViewerGroupLogicOp from "./TwitchViewerGroupLogicOp.vue" import TwitchViewerGroupResourceRef from "./TwitchViewerGroupResourceRef.vue" @@ -49,6 +54,7 @@ import PButton from "primevue/button" const props = defineProps<{ modelValue: TwitchViewerGroupRule + schema: SchemaTwitchViewerGroup }>() const model = useModel(props, "modelValue") diff --git a/plugins/twitch/renderer/src/components/viewer/TwitchViewerInput.vue b/plugins/twitch/renderer/src/components/viewer/TwitchViewerInput.vue index fcb73e0d..18b10b5e 100644 --- a/plugins/twitch/renderer/src/components/viewer/TwitchViewerInput.vue +++ b/plugins/twitch/renderer/src/components/viewer/TwitchViewerInput.vue @@ -49,6 +49,9 @@ {{ item.displayName }} + + Type Twitch Name, Hit Enter to Search + @@ -127,7 +130,9 @@ const groupedSuggestions = computed(() => { const result: TwitchViewerDisplayData[][] = [] const suggestions = fuzzySuggestions.value - result.push(suggestions) + if (fuzzySuggestions.value.length > 0) { + result.push(suggestions) + } return result }) diff --git a/plugins/twitch/renderer/src/util/group.ts b/plugins/twitch/renderer/src/util/group.ts index df2bffb0..9e4d9f74 100644 --- a/plugins/twitch/renderer/src/util/group.ts +++ b/plugins/twitch/renderer/src/util/group.ts @@ -29,6 +29,10 @@ export function getGroupPhrase(group: TwitchViewerGroup, resourceStore: ReturnTy words.push("Followers") } + if (rule.properties.anonymous) { + words.push("Anonymous") + } + const subTier1 = rule.properties.subTier1 const subTier2 = rule.properties.subTier1 const subTier3 = rule.properties.subTier1 @@ -145,6 +149,10 @@ export function getGroupSpanItems(group: TwitchViewerGroup | undefined): GroupSp words.push("Followers") } + if (rule.properties.anonymous) { + words.push("Anonymous") + } + const subTier1 = rule.properties.subTier1 const subTier2 = rule.properties.subTier2 const subTier3 = rule.properties.subTier3 diff --git a/plugins/twitch/shared/src/resources/group.ts b/plugins/twitch/shared/src/resources/group.ts index f07869a2..ec2f69bc 100644 --- a/plugins/twitch/shared/src/resources/group.ts +++ b/plugins/twitch/shared/src/resources/group.ts @@ -7,6 +7,7 @@ export interface TwitchViewerGroupConfig { export interface TwitchViewerGroupProperties { properties: { + anonymous?: boolean following?: boolean vip?: boolean subTier1?: boolean @@ -60,6 +61,7 @@ export const TwitchViewerGroup: TwitchViewerGroupFactory = { export interface SchemaTwitchViewerGroup extends SchemaBase { type: TwitchViewerGroupFactory + anonymous?: boolean } declare module "castmate-schema" { diff --git a/plugins/twitch/shared/src/viewers/viewer.ts b/plugins/twitch/shared/src/viewers/viewer.ts index f5cd160b..48f0fa14 100644 --- a/plugins/twitch/shared/src/viewers/viewer.ts +++ b/plugins/twitch/shared/src/viewers/viewer.ts @@ -43,6 +43,20 @@ export const TwitchViewer = { }, } }, + get anonymous(): TwitchViewer { + return { + id: "anonymous", + displayName: "Anonymous", + description: "", + profilePicture: "", + color: "#000000", + following: false, + subbed: false, + [Symbol.toPrimitive](hint: "default" | "string" | "number") { + return this.displayName + }, + } + }, } type TwitchViewerFactory = typeof TwitchViewer diff --git a/plugins/variables/renderer/package.json b/plugins/variables/renderer/package.json index 9c8fba01..05e7d0f5 100644 --- a/plugins/variables/renderer/package.json +++ b/plugins/variables/renderer/package.json @@ -16,7 +16,7 @@ "castmate-schema": "workspace:^", "castmate-ui-core": "workspace:^", "chromatism2": "*", - "electron": "^29.1.1", + "electron": "29.4.5", "nanoid": "^5.0.7", "pinia": "^2.1.7", "vue": "^3.4.23" diff --git a/plugins/variables/renderer/src/components/VariablesPage.vue b/plugins/variables/renderer/src/components/VariablesPage.vue index 842dbc70..f8023039 100644 --- a/plugins/variables/renderer/src/components/VariablesPage.vue +++ b/plugins/variables/renderer/src/components/VariablesPage.vue @@ -1,57 +1,64 @@ - - - - Create Variable - - - - - - - - - - - - - {{ getTypeByConstructor(data.schema.type)?.name ?? "UNKNOWN TYPE" }} - - - - - - - - - - - - - - - - - - - - - + + + + + Create Variable + + + + + - - + + + + + + {{ getTypeByConstructor(data.schema.type)?.name ?? "UNKNOWN TYPE" }} + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -144,6 +151,15 @@ async function reset(def: RendererVariableDefinition) {