Created
October 19, 2024 23:25
-
-
Save kyleawayan/ee1838e995bae3561ec7f2a749156608 to your computer and use it in GitHub Desktop.
Control OBS scenes with an HTTP API. Basic script to proxy the WebSocket OBS remote protocol and expose a simple API. Generated with ChatGPT o1-preview
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const WebSocket = require("ws"); | |
const express = require("express"); | |
const crypto = require("crypto"); | |
const app = express(); | |
const port = 3000; // You can change this to any port you prefer | |
// Replace 'your_password' with your actual OBS WebSocket password if set | |
const OBS_WEBSOCKET_PASSWORD = ""; | |
let obsSocket; | |
let requestIdCounter = 1; | |
const pendingRequests = {}; | |
// Function to connect to OBS via WebSocket | |
function connectToOBS() { | |
return new Promise((resolve, reject) => { | |
obsSocket = new WebSocket("ws://localhost:4455"); | |
obsSocket.on("open", () => { | |
console.log("Connected to OBS via WebSocket"); | |
}); | |
obsSocket.on("message", async (data) => { | |
let msg = JSON.parse(data); | |
switch (msg.op) { | |
case 0: // Hello | |
await handleHello(msg); | |
break; | |
case 2: // Identified | |
console.log("Authentication successful"); | |
resolve(); | |
break; | |
case 5: // Event | |
// Handle events if necessary | |
break; | |
case 7: // RequestResponse | |
handleRequestResponse(msg); | |
break; | |
default: | |
console.log("Received message:", msg); | |
} | |
}); | |
obsSocket.on("error", (error) => { | |
console.error("WebSocket error:", error); | |
reject(error); | |
}); | |
obsSocket.on("close", () => { | |
console.log("OBS WebSocket connection closed"); | |
}); | |
}); | |
} | |
// Handle the 'Hello' message from OBS | |
async function handleHello(msg) { | |
const { authentication } = msg.d; | |
let identifyData = { | |
rpcVersion: msg.d.rpcVersion, | |
}; | |
if (authentication && authentication.challenge && authentication.salt) { | |
// Authentication is required | |
const auth = computeAuthHash( | |
OBS_WEBSOCKET_PASSWORD, | |
authentication.salt, | |
authentication.challenge | |
); | |
identifyData.authentication = auth; | |
} | |
const identifyMsg = { | |
op: 1, // Identify | |
d: identifyData, | |
}; | |
obsSocket.send(JSON.stringify(identifyMsg)); | |
} | |
// Compute authentication hash according to OBS WebSocket protocol | |
function computeAuthHash(password, salt, challenge) { | |
const sha256 = (data) => crypto.createHash("sha256").update(data).digest(); | |
const base64encode = (data) => data.toString("base64"); | |
const base64decode = (data) => Buffer.from(data, "base64"); | |
const saltDecoded = base64decode(salt); | |
const challengeDecoded = base64decode(challenge); | |
// Compute authSecret = base64encode(SHA256(password + saltDecoded)) | |
const authSecret = base64encode( | |
sha256(Buffer.concat([Buffer.from(password, "utf8"), saltDecoded])) | |
); | |
// Compute authResponse = base64encode(SHA256(authSecret + challengeDecoded)) | |
const authResponse = base64encode( | |
sha256(Buffer.concat([Buffer.from(authSecret, "utf8"), challengeDecoded])) | |
); | |
return authResponse; | |
} | |
// Function to send requests to OBS | |
function sendRequest(requestType, requestData = {}) { | |
return new Promise((resolve, reject) => { | |
const requestId = String(requestIdCounter++); | |
const message = { | |
op: 6, // Request | |
d: { | |
requestType: requestType, | |
requestId: requestId, | |
requestData: requestData, | |
}, | |
}; | |
pendingRequests[requestId] = { resolve, reject }; | |
obsSocket.send(JSON.stringify(message)); | |
}); | |
} | |
// Handle responses from OBS | |
function handleRequestResponse(msg) { | |
const { requestId, requestStatus, responseData } = msg.d; | |
if (pendingRequests[requestId]) { | |
if (requestStatus.result) { | |
pendingRequests[requestId].resolve(responseData); | |
} else { | |
pendingRequests[requestId].reject( | |
requestStatus.comment || "Unknown error" | |
); | |
} | |
delete pendingRequests[requestId]; | |
} | |
} | |
// Start the connection to OBS and then start the Express server | |
connectToOBS() | |
.then(() => { | |
// Start the Express server | |
app.listen(port, () => { | |
console.log(`Express server running at http://localhost:${port}/`); | |
}); | |
// API endpoint to change scenes by scene number | |
app.get("/change-scene/:sceneNumber", async (req, res) => { | |
const sceneNumber = parseInt(req.params.sceneNumber); | |
try { | |
// Retrieve the list of available scenes | |
const data = await sendRequest("GetSceneList"); | |
const scenes = data.scenes; | |
// Validate the scene number | |
if (sceneNumber < 1 || sceneNumber > scenes.length) { | |
res.status(400).send("Invalid scene number"); | |
return; | |
} | |
const sceneName = scenes[sceneNumber - 1].sceneName; | |
// Change to the specified scene | |
await sendRequest("SetCurrentProgramScene", { sceneName: sceneName }); | |
res.send(`Changed to scene #${sceneNumber}: ${sceneName}`); | |
} catch (error) { | |
console.error("Error changing scene:", error); | |
res.status(500).send("An error occurred while changing the scene"); | |
} | |
}); | |
}) | |
.catch((err) => { | |
console.error("Failed to connect to OBS:", err); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment