Skip to content

Instantly share code, notes, and snippets.

@kyleawayan
Created October 19, 2024 23:25
Show Gist options
  • Save kyleawayan/ee1838e995bae3561ec7f2a749156608 to your computer and use it in GitHub Desktop.
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
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