- Remove invalid CreateGroup API calls (not supported in OBS WebSocket v5) - Implement nested scenes approach for stream grouping - Create a separate scene for each stream containing browser source and text overlay - Add nested scene to team scene to simulate group behavior - Fix lint errors and remove unused imports 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
213 lines
No EOL
6.5 KiB
TypeScript
213 lines
No EOL
6.5 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getDatabase } from '../../../lib/database';
|
|
import { connectToOBS, getOBSClient, disconnectFromOBS, addSourceToSwitcher, createStreamGroup } from '../../../lib/obsClient';
|
|
import { open } from 'sqlite';
|
|
import sqlite3 from 'sqlite3';
|
|
import path from 'path';
|
|
import { getTableName, BASE_TABLE_NAMES } from '../../../lib/constants';
|
|
|
|
interface OBSClient {
|
|
call: (method: string, params?: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
}
|
|
|
|
interface OBSInput {
|
|
inputName: string;
|
|
}
|
|
|
|
|
|
interface GetInputListResponse {
|
|
inputs: OBSInput[];
|
|
}
|
|
const screens = [
|
|
'ss_large',
|
|
'ss_left',
|
|
'ss_right',
|
|
'ss_top_left',
|
|
'ss_top_right',
|
|
'ss_bottom_left',
|
|
'ss_bottom_right',
|
|
];
|
|
|
|
async function fetchTeamInfo(teamId: number) {
|
|
const FILE_DIRECTORY = path.resolve(process.env.FILE_DIRECTORY || './files');
|
|
try {
|
|
const dbPath = path.join(FILE_DIRECTORY, 'sources.db');
|
|
const db = await open({
|
|
filename: dbPath,
|
|
driver: sqlite3.Database,
|
|
});
|
|
|
|
const teamsTableName = getTableName(BASE_TABLE_NAMES.TEAMS, {
|
|
year: 2025,
|
|
season: 'summer',
|
|
suffix: 'sat'
|
|
});
|
|
|
|
const teamInfo = await db.get(
|
|
`SELECT team_name, group_name, group_uuid FROM ${teamsTableName} WHERE team_id = ?`,
|
|
[teamId]
|
|
);
|
|
|
|
await db.close();
|
|
return teamInfo;
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
console.error('Error fetching team info:', error.message);
|
|
} else {
|
|
console.error('An unknown error occurred:', error);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
import { validateStreamInput } from '../../../lib/security';
|
|
|
|
// Generate OBS source name from stream name
|
|
function generateOBSSourceName(streamName: string): string {
|
|
return streamName.toLowerCase().replace(/\s+/g, '_') + '_twitch';
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
let name: string, url: string, team_id: number, obs_source_name: string;
|
|
|
|
// Parse and validate request body
|
|
try {
|
|
const body = await request.json();
|
|
const validation = validateStreamInput(body);
|
|
|
|
if (!validation.valid) {
|
|
return NextResponse.json({
|
|
error: 'Validation failed',
|
|
details: validation.errors
|
|
}, { status: 400 });
|
|
}
|
|
|
|
({ name, url, team_id } = validation.data!);
|
|
|
|
// Auto-generate OBS source name from stream name
|
|
obs_source_name = generateOBSSourceName(name);
|
|
|
|
} catch {
|
|
return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 });
|
|
}
|
|
|
|
try {
|
|
|
|
// Connect to OBS WebSocket
|
|
console.log("Pre-connect")
|
|
await connectToOBS();
|
|
console.log('Pre client')
|
|
const obs: OBSClient = await getOBSClient();
|
|
// obs.on('message', (msg) => {
|
|
// console.log('Message from OBS:', msg);
|
|
// });
|
|
let inputs;
|
|
try {
|
|
const response = await obs.call('GetInputList');
|
|
const inputListResponse = response as unknown as GetInputListResponse;
|
|
inputs = inputListResponse.inputs;
|
|
// console.log('Inputs:', inputs);
|
|
} catch (err) {
|
|
if (err instanceof Error) {
|
|
console.error('Failed to fetch inputs:', err.message);
|
|
} else {
|
|
console.error('Failed to fetch inputs:', err);
|
|
}
|
|
throw new Error('GetInputList failed.');
|
|
}
|
|
|
|
const teamInfo = await fetchTeamInfo(team_id);
|
|
if (!teamInfo) {
|
|
throw new Error('Team not found');
|
|
}
|
|
|
|
console.log('Team Info:', teamInfo);
|
|
|
|
// Use group_name if it exists, otherwise use team_name
|
|
const groupName = teamInfo.group_name || teamInfo.team_name;
|
|
|
|
const sourceExists = inputs.some((input: OBSInput) => input.inputName === obs_source_name);
|
|
|
|
if (!sourceExists) {
|
|
// Create stream group with text overlay
|
|
await createStreamGroup(groupName, name, teamInfo.team_name, url);
|
|
|
|
// Update team with group UUID if not set
|
|
if (!teamInfo.group_uuid) {
|
|
const FILE_DIRECTORY = path.resolve(process.env.FILE_DIRECTORY || './files');
|
|
const dbPath = path.join(FILE_DIRECTORY, 'sources.db');
|
|
const db = await open({
|
|
filename: dbPath,
|
|
driver: sqlite3.Database,
|
|
});
|
|
|
|
const teamsTableName = getTableName(BASE_TABLE_NAMES.TEAMS, {
|
|
year: 2025,
|
|
season: 'summer',
|
|
suffix: 'sat'
|
|
});
|
|
|
|
try {
|
|
// Get the scene UUID for the group
|
|
const obsClient = await getOBSClient();
|
|
const { scenes } = await obsClient.call('GetSceneList');
|
|
const scene = scenes.find((s: { sceneName: string; sceneUuid: string }) => s.sceneName === groupName);
|
|
|
|
if (scene) {
|
|
await db.run(
|
|
`UPDATE ${teamsTableName} SET group_name = ?, group_uuid = ? WHERE team_id = ?`,
|
|
[groupName, scene.sceneUuid, team_id]
|
|
);
|
|
console.log(`Updated team ${team_id} with group UUID: ${scene.sceneUuid}`);
|
|
} else {
|
|
console.log(`Scene "${groupName}" not found in OBS`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating team group UUID:', error);
|
|
} finally {
|
|
await db.close();
|
|
}
|
|
}
|
|
|
|
console.log(`OBS source "${obs_source_name}" created.`);
|
|
|
|
for (const screen of screens) {
|
|
try {
|
|
const streamGroupName = `${name.toLowerCase().replace(/\s+/g, '_')}_stream`;
|
|
await addSourceToSwitcher(screen, [
|
|
{ hidden: false, selected: false, value: streamGroupName },
|
|
]);
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
console.error(`Failed to add source to ${screen}:`, error.message);
|
|
} else {
|
|
console.error(`Failed to add source to ${screen}:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
console.log(`OBS source "${obs_source_name}" already exists.`);
|
|
}
|
|
|
|
const db = await getDatabase();
|
|
const streamsTableName = getTableName(BASE_TABLE_NAMES.STREAMS, {
|
|
year: 2025,
|
|
season: 'summer',
|
|
suffix: 'sat'
|
|
});
|
|
const query = `INSERT INTO ${streamsTableName} (name, obs_source_name, url, team_id) VALUES (?, ?, ?, ?)`;
|
|
db.run(query, [name, obs_source_name, url, team_id])
|
|
await disconnectFromOBS();
|
|
return NextResponse.json({ message: 'Stream added successfully' }, {status: 201})
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
console.error('Error adding stream:', error.message);
|
|
} else {
|
|
console.error('An unknown error occurred while adding stream:', error);
|
|
}
|
|
await disconnectFromOBS();
|
|
return NextResponse.json({ error: 'Failed to add stream' }, { status: 500 });
|
|
}
|
|
} |