296 lines
7.2 KiB
TypeScript
296 lines
7.2 KiB
TypeScript
import crypto from 'crypto';
|
|
import { closeTunnelIfUnused } from "./tunnel.manager";
|
|
|
|
interface RoomPermissions {
|
|
canControl: boolean;
|
|
canManageQueue: boolean;
|
|
}
|
|
|
|
interface RoomUser {
|
|
id: string;
|
|
username: string;
|
|
avatar?: string;
|
|
isHost: boolean;
|
|
isGuest: boolean;
|
|
userId?: number;
|
|
permissions?: RoomPermissions;
|
|
ipAddress?: string;
|
|
}
|
|
|
|
interface SourceContext {
|
|
animeId: string;
|
|
episode: string | number;
|
|
source: string;
|
|
extension: string;
|
|
server: string;
|
|
category: string;
|
|
}
|
|
|
|
export interface QueueItem {
|
|
uid: string;
|
|
metadata: RoomMetadata;
|
|
videoData: any;
|
|
addedBy: string;
|
|
}
|
|
|
|
interface RoomMetadata {
|
|
id: string;
|
|
title: string;
|
|
episode: number;
|
|
image?: string;
|
|
source?: string;
|
|
malId?: number;
|
|
}
|
|
|
|
interface RoomData {
|
|
id: string;
|
|
name: string;
|
|
host: RoomUser;
|
|
users: Map<string, RoomUser>;
|
|
createdAt: number;
|
|
currentVideo: {
|
|
animeId?: number;
|
|
episode?: number;
|
|
source?: string;
|
|
videoData?: any;
|
|
currentTime: number;
|
|
isPlaying: boolean;
|
|
context?: SourceContext;
|
|
} | null;
|
|
password?: string;
|
|
metadata?: RoomMetadata | null;
|
|
exposed: boolean;
|
|
publicUrl?: string;
|
|
queue: QueueItem[];
|
|
bannedIPs: Set<string>;
|
|
}
|
|
|
|
export const DEFAULT_GUEST_PERMISSIONS: RoomPermissions = {
|
|
canControl: false,
|
|
canManageQueue: false
|
|
};
|
|
|
|
const rooms = new Map<string, RoomData>();
|
|
|
|
export function generateRoomId(): string {
|
|
return crypto.randomBytes(8).toString('hex');
|
|
}
|
|
|
|
export function createRoom(name: string, host: RoomUser, password?: string, exposed = false, publicUrl?: string): RoomData {
|
|
const roomId = generateRoomId();
|
|
|
|
const room: RoomData = {
|
|
id: roomId,
|
|
name,
|
|
host,
|
|
users: new Map([[host.id, host]]),
|
|
createdAt: Date.now(),
|
|
currentVideo: null,
|
|
password: password || undefined,
|
|
metadata: null,
|
|
exposed,
|
|
publicUrl,
|
|
queue: [],
|
|
bannedIPs: new Set() // NUEVO
|
|
};
|
|
|
|
rooms.set(roomId, room);
|
|
return room;
|
|
}
|
|
|
|
export function updateUserPermissions(roomId: string, userId: string, permissions: Partial<RoomPermissions>): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
const user: any = room.users.get(userId);
|
|
if (!user) return false;
|
|
|
|
if (user.isHost) return false;
|
|
|
|
user.permissions = {
|
|
...user.permissions,
|
|
...permissions
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
export function banUserIP(roomId: string, ipAddress: string): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
room.bannedIPs.add(ipAddress);
|
|
|
|
// Remover a todos los usuarios con esa IP
|
|
Array.from(room.users.values()).forEach(user => {
|
|
if (user.ipAddress === ipAddress) {
|
|
removeUserFromRoom(roomId, user.id);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
export function unbanUserIP(roomId: string, ipAddress: string): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
return room.bannedIPs.delete(ipAddress);
|
|
}
|
|
|
|
export function isIPBanned(roomId: string, ipAddress: string): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
return room.bannedIPs.has(ipAddress);
|
|
}
|
|
|
|
export function getBannedIPs(roomId: string): string[] {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return [];
|
|
return Array.from(room.bannedIPs);
|
|
}
|
|
|
|
export function hasPermission(roomId: string, userId: string, permission: keyof RoomPermissions): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
const user = room.users.get(userId);
|
|
if (!user) return false;
|
|
|
|
// El host siempre tiene todos los permisos
|
|
if (user.isHost) return true;
|
|
|
|
// Si no tiene permisos definidos, usar defaults
|
|
const userPerms = user.permissions || DEFAULT_GUEST_PERMISSIONS;
|
|
return userPerms[permission] || false;
|
|
}
|
|
|
|
export function getRoom(roomId: string): RoomData | null {
|
|
return rooms.get(roomId) || null;
|
|
}
|
|
|
|
export function getAllRooms(): RoomData[] {
|
|
return Array.from(rooms.values()).map(room => ({
|
|
...room,
|
|
users: room.users
|
|
}));
|
|
}
|
|
|
|
export function addQueueItem(roomId: string, item: QueueItem): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
room.queue.push(item);
|
|
return true;
|
|
}
|
|
|
|
export function removeQueueItem(roomId: string, itemUid: string): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
room.queue = room.queue.filter(i => i.uid !== itemUid);
|
|
return true;
|
|
}
|
|
|
|
export function getNextQueueItem(roomId: string): QueueItem | undefined {
|
|
const room = rooms.get(roomId);
|
|
if (!room || room.queue.length === 0) return undefined;
|
|
return room.queue.shift(); // Saca el primero y lo retorna
|
|
}
|
|
|
|
export function getAndRemoveQueueItem(roomId: string, itemUid: string): QueueItem | undefined {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return undefined;
|
|
|
|
const index = room.queue.findIndex(i => i.uid === itemUid);
|
|
if (index === -1) return undefined;
|
|
|
|
const [item] = room.queue.splice(index, 1);
|
|
return item;
|
|
}
|
|
|
|
export function moveQueueItem(roomId: string, itemUid: string, direction: 'up' | 'down'): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
const index = room.queue.findIndex(i => i.uid === itemUid);
|
|
if (index === -1) return false;
|
|
|
|
const newIndex = direction === 'up' ? index - 1 : index + 1;
|
|
|
|
if (newIndex < 0 || newIndex >= room.queue.length) return false;
|
|
|
|
const temp = room.queue[newIndex];
|
|
room.queue[newIndex] = room.queue[index];
|
|
room.queue[index] = temp;
|
|
|
|
return true;
|
|
}
|
|
|
|
export function addUserToRoom(roomId: string, user: RoomUser): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
room.users.set(user.id, user);
|
|
return true;
|
|
}
|
|
|
|
export function removeUserFromRoom(roomId: string, userId: string): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
room.users.delete(userId);
|
|
|
|
if (room.users.size === 0) {
|
|
if (room.exposed) {
|
|
closeTunnelIfUnused();
|
|
}
|
|
rooms.delete(roomId);
|
|
return true;
|
|
}
|
|
|
|
if (room.host.id === userId && room.users.size > 0) {
|
|
const newHost = Array.from(room.users.values())[0];
|
|
newHost.isHost = true;
|
|
room.host = newHost;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
export function updateRoomVideo(roomId: string, videoData: any): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
room.currentVideo = {
|
|
...room.currentVideo,
|
|
...videoData
|
|
};
|
|
return true;
|
|
}
|
|
|
|
export function deleteRoom(roomId: string): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
if (room.exposed) {
|
|
closeTunnelIfUnused();
|
|
}
|
|
|
|
return rooms.delete(roomId);
|
|
}
|
|
|
|
export function verifyRoomPassword(roomId: string, password?: string): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
if (!room.password) return true;
|
|
if (!password) return false;
|
|
|
|
return room.password === password;
|
|
}
|
|
|
|
export function updateRoomMetadata(roomId: string, metadata: any): boolean {
|
|
const room = rooms.get(roomId);
|
|
if (!room) return false;
|
|
|
|
room.metadata = metadata;
|
|
return true;
|
|
} |