false, 'error' => 'Must be logged in to chat']; } // Check if chat is enabled $settings = self::getChatSettings($stream_key); if (!$settings['chat_enabled']) { return ['success' => false, 'error' => 'Chat is disabled for this stream']; } // Check if user is banned or timed out if (self::isUserBanned($stream_key, $usr_id)) { return ['success' => false, 'error' => 'You are banned from this chat']; } if (self::isUserTimedOut($stream_key, $usr_id)) { return ['success' => false, 'error' => 'You are timed out from this chat']; } // Validate message length $max_length = (int) ($cfg['live_chat_max_length'] ?? 500); if (strlen($message) > $max_length) { return ['success' => false, 'error' => "Message too long (max {$max_length} characters)"]; } // Rate limiting $rate_limit = self::checkRateLimit($stream_key, $usr_id, $settings['slow_mode']); if (!$rate_limit['allowed']) { return ['success' => false, 'error' => $rate_limit['message']]; } // Sanitize message $message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); // Insert message $sql = "INSERT INTO `db_live_chat_messages` (`stream_key`, `usr_id`, `message`, `message_type`, `super_chat_amount`) VALUES ('%s', %d, '%s', '%s', %s)"; $class_database->doQuery($sql, $stream_key, $usr_id, $message, $type, $super_chat_amount ? (float) $super_chat_amount : 'NULL' ); $msg_id = $class_database->insert_id(); return [ 'success' => true, 'msg_id' => $msg_id, 'message' => $message, 'timestamp' => date('Y-m-d H:i:s') ]; } /** * Get chat messages for a stream * * @param string $stream_key Stream file key * @param int $limit Number of messages * @param int $since_id Get messages after this ID * @return array Messages */ public static function getMessages($stream_key, $limit = 50, $since_id = 0) { global $class_database; $since_clause = $since_id > 0 ? "AND m.msg_id > {$since_id}" : ""; $sql = "SELECT m.msg_id, m.message, m.message_type, m.super_chat_amount, m.timestamp, u.usr_id, u.usr_user, u.usr_dname, u.usr_key, u.usr_partner, u.usr_affiliate FROM `db_live_chat_messages` m JOIN `db_accountuser` u ON m.usr_id = u.usr_id WHERE m.stream_key = '%s' AND m.deleted = 0 {$since_clause} ORDER BY m.msg_id DESC LIMIT %d"; $result = $class_database->doQuery($sql, $stream_key, $limit); $messages = []; while ($row = $result->fetch_assoc()) { $messages[] = [ 'msg_id' => $row['msg_id'], 'user_id' => $row['usr_id'], 'username' => $row['usr_user'], 'display_name' => $row['usr_dname'], 'user_key' => $row['usr_key'], 'is_partner' => $row['usr_partner'] == 1, 'is_affiliate' => $row['usr_affiliate'] == 1, 'message' => $row['message'], 'type' => $row['message_type'], 'super_chat_amount' => $row['super_chat_amount'], 'timestamp' => $row['timestamp'], 'is_moderator' => self::isModerator($stream_key, $row['usr_id']) ]; } return array_reverse($messages); // Return in chronological order } /** * Delete a message (moderation) * * @param int $msg_id Message ID * @param int $moderator_id Moderator user ID * @return bool Success */ public static function deleteMessage($msg_id, $moderator_id) { global $class_database; $sql = "UPDATE `db_live_chat_messages` SET `deleted` = 1, `deleted_by` = %d WHERE `msg_id` = %d"; $class_database->doQuery($sql, $moderator_id, $msg_id); return true; } /** * Timeout a user * * @param string $stream_key Stream file key * @param int $usr_id User to timeout * @param int $duration Duration in seconds * @param string $reason Reason * @param int $moderator_id Moderator user ID * @return bool Success */ public static function timeoutUser($stream_key, $usr_id, $duration, $reason, $moderator_id) { global $class_database; $expires_at = date('Y-m-d H:i:s', time() + $duration); $sql = "INSERT INTO `db_live_chat_moderation` (`stream_key`, `usr_id`, `action`, `duration`, `reason`, `moderator_id`, `expires_at`) VALUES ('%s', %d, 'timeout', %d, '%s', %d, '%s')"; $class_database->doQuery($sql, $stream_key, $usr_id, $duration, $reason, $moderator_id, $expires_at ); return true; } /** * Ban a user from chat * * @param string $stream_key Stream file key * @param int $usr_id User to ban * @param string $reason Reason * @param int $moderator_id Moderator user ID * @return bool Success */ public static function banUser($stream_key, $usr_id, $reason, $moderator_id) { global $class_database; $sql = "INSERT INTO `db_live_chat_moderation` (`stream_key`, `usr_id`, `action`, `reason`, `moderator_id`) VALUES ('%s', %d, 'ban', '%s', %d)"; $class_database->doQuery($sql, $stream_key, $usr_id, $reason, $moderator_id); return true; } /** * Check if user is banned * * @param string $stream_key Stream file key * @param int $usr_id User ID * @return bool Is banned */ public static function isUserBanned($stream_key, $usr_id) { global $class_database; $sql = "SELECT COUNT(*) as count FROM `db_live_chat_moderation` WHERE `stream_key` = '%s' AND `usr_id` = %d AND `action` = 'ban' ORDER BY `mod_id` DESC LIMIT 1"; $result = $class_database->doQuery($sql, $stream_key, $usr_id); $row = $result->fetch_assoc(); return $row['count'] > 0; } /** * Check if user is timed out * * @param string $stream_key Stream file key * @param int $usr_id User ID * @return bool Is timed out */ public static function isUserTimedOut($stream_key, $usr_id) { global $class_database; $sql = "SELECT COUNT(*) as count FROM `db_live_chat_moderation` WHERE `stream_key` = '%s' AND `usr_id` = %d AND `action` = 'timeout' AND `expires_at` > NOW()"; $result = $class_database->doQuery($sql, $stream_key, $usr_id); $row = $result->fetch_assoc(); return $row['count'] > 0; } /** * Check if user is a moderator * * @param string $stream_key Stream file key * @param int $usr_id User ID * @return bool Is moderator */ public static function isModerator($stream_key, $usr_id) { global $class_database; $sql = "SELECT COUNT(*) as count FROM `db_live_chat_moderators` WHERE `stream_key` = '%s' AND `usr_id` = %d"; $result = $class_database->doQuery($sql, $stream_key, $usr_id); $row = $result->fetch_assoc(); return $row['count'] > 0; } /** * Get chat settings for stream * * @param string $stream_key Stream file key * @return array Settings */ public static function getChatSettings($stream_key) { global $class_database; $sql = "SELECT * FROM `db_live_chat_settings` WHERE `stream_key` = '%s' LIMIT 1"; $result = $class_database->doQuery($sql, $stream_key); $row = $result->fetch_assoc(); if (!$row) { // Return defaults return [ 'chat_enabled' => 1, 'slow_mode' => null, 'subscriber_only' => 0, 'follower_only' => 0, 'emotes_enabled' => 1, 'links_allowed' => 0 ]; } return $row; } /** * Check rate limit * * @param string $stream_key Stream file key * @param int $usr_id User ID * @param int $slow_mode Slow mode seconds * @return array Result */ private static function checkRateLimit($stream_key, $usr_id, $slow_mode) { global $class_database, $cfg; $rate_limit = max((int) ($cfg['live_chat_rate_limit'] ?? 2), (int) $slow_mode); $sql = "SELECT MAX(timestamp) as last_message FROM `db_live_chat_messages` WHERE `stream_key` = '%s' AND `usr_id` = %d"; $result = $class_database->doQuery($sql, $stream_key, $usr_id); $row = $result->fetch_assoc(); if ($row['last_message']) { $last_time = strtotime($row['last_message']); $now = time(); $diff = $now - $last_time; if ($diff < $rate_limit) { $wait = $rate_limit - $diff; return [ 'allowed' => false, 'message' => "Please wait {$wait} seconds before sending another message" ]; } } return ['allowed' => true]; } }