Add OBS scene switching controls with dynamic button states #12

Merged
deco merged 3 commits from scene-switching-controls into main 2025-07-26 02:29:27 +03:00
7 changed files with 4393 additions and 1607 deletions
Showing only changes of commit a89fa7c8d6 - Show all commits

View file

@ -1,6 +1,7 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { createSuccessResponse, createErrorResponse, withErrorHandling } from '../../../lib/apiHelpers'; import { createSuccessResponse, createErrorResponse, withErrorHandling } from '../../../lib/apiHelpers';
import { SCREEN_POSITIONS } from '../../../lib/constants';
const FILE_DIRECTORY = path.resolve(process.env.FILE_DIRECTORY || './files') const FILE_DIRECTORY = path.resolve(process.env.FILE_DIRECTORY || './files')
// Ensure directory exists // Ensure directory exists
@ -11,31 +12,17 @@ console.log('using', FILE_DIRECTORY)
async function getActiveHandler() { async function getActiveHandler() {
try { try {
const largePath = path.join(FILE_DIRECTORY, 'large.txt'); const activeSources: Record<string, string | null> = {};
const leftPath = path.join(FILE_DIRECTORY, 'left.txt');
const rightPath = path.join(FILE_DIRECTORY, 'right.txt');
const topLeftPath = path.join(FILE_DIRECTORY, 'topLeft.txt');
const topRightPath = path.join(FILE_DIRECTORY, 'topRight.txt');
const bottomLeftPath = path.join(FILE_DIRECTORY, 'bottomLeft.txt');
const bottomRightPath = path.join(FILE_DIRECTORY, 'bottomRight.txt');
const large = fs.existsSync(largePath) ? fs.readFileSync(largePath, 'utf-8').trim() : null; // Read each screen position file using the constant
const left = fs.existsSync(leftPath) ? fs.readFileSync(leftPath, 'utf-8').trim() : null; for (const screen of SCREEN_POSITIONS) {
const right = fs.existsSync(rightPath) ? fs.readFileSync(rightPath, 'utf-8').trim() : null; const filePath = path.join(FILE_DIRECTORY, `${screen}.txt`);
const topLeft = fs.existsSync(topLeftPath) ? fs.readFileSync(topLeftPath, 'utf-8').trim() : null; activeSources[screen] = fs.existsSync(filePath)
const topRight = fs.existsSync(topRightPath) ? fs.readFileSync(topRightPath, 'utf-8').trim() : null; ? fs.readFileSync(filePath, 'utf-8').trim()
const bottomLeft = fs.existsSync(bottomLeftPath) ? fs.readFileSync(bottomLeftPath, 'utf-8').trim() : null; : null;
const bottomRight = fs.existsSync(bottomRightPath) ? fs.readFileSync(bottomRightPath, 'utf-8').trim() : null; }
return createSuccessResponse({ return createSuccessResponse(activeSources);
large,
left,
right,
topLeft,
topRight,
bottomLeft,
bottomRight
});
} catch (error) { } catch (error) {
console.error('Error reading active sources:', error); console.error('Error reading active sources:', error);
return createErrorResponse('Failed to read active sources', 500, 'Could not read source files', error); return createErrorResponse('Failed to read active sources', 500, 'Could not read source files', error);

View file

@ -17,10 +17,10 @@ You must create **exactly 7 Source Switcher sources** in OBS with these specific
| `ss_large` | Main/Large screen | `large.txt` | | `ss_large` | Main/Large screen | `large.txt` |
| `ss_left` | Left screen | `left.txt` | | `ss_left` | Left screen | `left.txt` |
| `ss_right` | Right screen | `right.txt` | | `ss_right` | Right screen | `right.txt` |
| `ss_top_left` | Top left corner | `topLeft.txt` | | `ss_top_left` | Top left corner | `top_left.txt` |
| `ss_top_right` | Top right corner | `topRight.txt` | | `ss_top_right` | Top right corner | `top_right.txt` |
| `ss_bottom_left` | Bottom left corner | `bottomLeft.txt` | | `ss_bottom_left` | Bottom left corner | `bottom_left.txt` |
| `ss_bottom_right` | Bottom right corner | `bottomRight.txt` | | `ss_bottom_right` | Bottom right corner | `bottom_right.txt` |
## Setup Instructions ## Setup Instructions

File diff suppressed because it is too large Load diff

View file

@ -45,10 +45,10 @@ export const SCREEN_POSITIONS = [
'large', 'large',
'left', 'left',
'right', 'right',
'topLeft', 'top_left',
'topRight', 'top_right',
'bottomLeft', 'bottom_left',
'bottomRight' 'bottom_right'
] as const; ] as const;
export const SOURCE_SWITCHER_NAMES = [ export const SOURCE_SWITCHER_NAMES = [

View file

@ -1,6 +1,6 @@
// Security utilities for input validation and sanitization // Security utilities for input validation and sanitization
export const VALID_SCREENS = ['large', 'left', 'right', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'] as const; export const VALID_SCREENS = ['large', 'left', 'right', 'top_left', 'top_right', 'bottom_left', 'bottom_right'] as const;
export type ValidScreen = typeof VALID_SCREENS[number]; export type ValidScreen = typeof VALID_SCREENS[number];
// Input validation functions // Input validation functions

View file

@ -21,10 +21,6 @@ export function middleware(request: NextRequest) {
// Skip authentication for localhost/internal requests (optional security) // Skip authentication for localhost/internal requests (optional security)
const host = request.headers.get('host'); const host = request.headers.get('host');
if (host && (host.startsWith('localhost') || host.startsWith('127.0.0.1') || host.startsWith('192.168.'))) { if (host && (host.startsWith('localhost') || host.startsWith('127.0.0.1') || host.startsWith('192.168.'))) {
// Don't log for frequently polled endpoints to reduce noise
if (!request.nextUrl.pathname.includes('/api/obsStatus')) {
console.log('Allowing internal network access without API key');
}
return NextResponse.next(); return NextResponse.next();
} }

2878
package-lock.json generated

File diff suppressed because it is too large Load diff