feat: Add comprehensive documentation suite and reorganize project structure
- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
This commit is contained in:
257
api/auth.php
Normal file
257
api/auth.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
/*******************************************************************************************************************
|
||||
| Software Name : EasyStream
|
||||
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
||||
| Software Author : (c) Sami Ahmed
|
||||
|*******************************************************************************************************************
|
||||
|
|
||||
|*******************************************************************************************************************
|
||||
| This source file is subject to the EasyStream Proprietary License Agreement.
|
||||
|
|
||||
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|
||||
|*******************************************************************************************************************
|
||||
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
||||
|*******************************************************************************************************************/
|
||||
|
||||
define('_ISVALID', true);
|
||||
|
||||
// Set JSON content type
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
|
||||
// Handle preflight requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once '../f_core/config.core.php';
|
||||
|
||||
// Initialize classes
|
||||
$auth = VAuth::getInstance();
|
||||
$security = VSecurity::getInstance();
|
||||
$logger = VLogger::getInstance();
|
||||
|
||||
/**
|
||||
* Send JSON response
|
||||
*/
|
||||
function sendResponse($data, $statusCode = 200) {
|
||||
http_response_code($statusCode);
|
||||
echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSON input
|
||||
*/
|
||||
function getJsonInput() {
|
||||
$input = file_get_contents('php://input');
|
||||
return json_decode($input, true) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate required fields
|
||||
*/
|
||||
function validateRequired($data, $fields) {
|
||||
$missing = [];
|
||||
foreach ($fields as $field) {
|
||||
if (!isset($data[$field]) || empty($data[$field])) {
|
||||
$missing[] = $field;
|
||||
}
|
||||
}
|
||||
return $missing;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get request method and action
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = VSecurity::getParam('action', 'string') ?: VSecurity::postParam('action', 'string');
|
||||
|
||||
// Route requests based on action
|
||||
switch ($action) {
|
||||
case 'register':
|
||||
if ($method !== 'POST') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!VSecurity::validateCSRFFromPost('register')) {
|
||||
sendResponse(['success' => false, 'message' => 'Invalid CSRF token'], 403);
|
||||
}
|
||||
|
||||
$data = array_merge($_POST, getJsonInput());
|
||||
$missing = validateRequired($data, ['username', 'email', 'password']);
|
||||
|
||||
if (!empty($missing)) {
|
||||
sendResponse([
|
||||
'success' => false,
|
||||
'message' => 'Missing required fields: ' . implode(', ', $missing)
|
||||
], 400);
|
||||
}
|
||||
|
||||
$result = $auth->register($data);
|
||||
sendResponse($result, $result['success'] ? 201 : 400);
|
||||
break;
|
||||
|
||||
case 'verify_email':
|
||||
if ($method !== 'POST') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
$data = array_merge($_POST, getJsonInput());
|
||||
$token = $data['token'] ?? '';
|
||||
|
||||
if (empty($token)) {
|
||||
sendResponse(['success' => false, 'message' => 'Verification token is required'], 400);
|
||||
}
|
||||
|
||||
$result = $auth->verifyEmail($token);
|
||||
sendResponse($result, $result['success'] ? 200 : 400);
|
||||
break;
|
||||
|
||||
case 'login':
|
||||
if ($method !== 'POST') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!VSecurity::validateCSRFFromPost('login')) {
|
||||
sendResponse(['success' => false, 'message' => 'Invalid CSRF token'], 403);
|
||||
}
|
||||
|
||||
$data = array_merge($_POST, getJsonInput());
|
||||
$missing = validateRequired($data, ['identifier', 'password']);
|
||||
|
||||
if (!empty($missing)) {
|
||||
sendResponse([
|
||||
'success' => false,
|
||||
'message' => 'Username/email and password are required'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$rememberMe = !empty($data['remember_me']);
|
||||
$result = $auth->login($data['identifier'], $data['password'], $rememberMe);
|
||||
sendResponse($result, $result['success'] ? 200 : 401);
|
||||
break;
|
||||
|
||||
case 'logout':
|
||||
if ($method !== 'POST') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!VSecurity::validateCSRFFromPost('logout')) {
|
||||
sendResponse(['success' => false, 'message' => 'Invalid CSRF token'], 403);
|
||||
}
|
||||
|
||||
$result = $auth->logout();
|
||||
sendResponse($result);
|
||||
break;
|
||||
|
||||
case 'me':
|
||||
if ($method !== 'GET') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
if (!$auth->isAuthenticated()) {
|
||||
sendResponse(['success' => false, 'message' => 'Not authenticated'], 401);
|
||||
}
|
||||
|
||||
$user = $auth->getCurrentUser();
|
||||
sendResponse(['success' => true, 'user' => $user]);
|
||||
break;
|
||||
|
||||
case 'request_password_reset':
|
||||
if ($method !== 'POST') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!VSecurity::validateCSRFFromPost('password_reset')) {
|
||||
sendResponse(['success' => false, 'message' => 'Invalid CSRF token'], 403);
|
||||
}
|
||||
|
||||
$data = array_merge($_POST, getJsonInput());
|
||||
$email = $data['email'] ?? '';
|
||||
|
||||
if (empty($email)) {
|
||||
sendResponse(['success' => false, 'message' => 'Email is required'], 400);
|
||||
}
|
||||
|
||||
$result = $auth->requestPasswordReset($email);
|
||||
sendResponse($result);
|
||||
break;
|
||||
|
||||
case 'reset_password':
|
||||
if ($method !== 'POST') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!VSecurity::validateCSRFFromPost('password_reset')) {
|
||||
sendResponse(['success' => false, 'message' => 'Invalid CSRF token'], 403);
|
||||
}
|
||||
|
||||
$data = array_merge($_POST, getJsonInput());
|
||||
$missing = validateRequired($data, ['token', 'password']);
|
||||
|
||||
if (!empty($missing)) {
|
||||
sendResponse([
|
||||
'success' => false,
|
||||
'message' => 'Reset token and new password are required'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$result = $auth->resetPassword($data['token'], $data['password']);
|
||||
sendResponse($result, $result['success'] ? 200 : 400);
|
||||
break;
|
||||
|
||||
case 'csrf_token':
|
||||
if ($method !== 'GET') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
$action = VSecurity::getParam('for', 'string', 'default');
|
||||
$token = VSecurity::generateCSRFToken($action);
|
||||
|
||||
sendResponse([
|
||||
'success' => true,
|
||||
'token' => $token,
|
||||
'action' => $action
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
if ($method !== 'GET') {
|
||||
sendResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
$isAuthenticated = $auth->isAuthenticated();
|
||||
$user = $isAuthenticated ? $auth->getCurrentUser() : null;
|
||||
|
||||
sendResponse([
|
||||
'success' => true,
|
||||
'authenticated' => $isAuthenticated,
|
||||
'user' => $user
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
sendResponse(['success' => false, 'message' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$logger->error('Auth API error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'action' => $action ?? 'unknown',
|
||||
'method' => $method ?? 'unknown'
|
||||
]);
|
||||
|
||||
sendResponse([
|
||||
'success' => false,
|
||||
'message' => 'An internal error occurred'
|
||||
], 500);
|
||||
}
|
||||
161
api/auto_post.php
Normal file
161
api/auto_post.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
define('_ISVALID', true);
|
||||
include_once '../f_core/config.core.php';
|
||||
|
||||
// Set up logging
|
||||
$log_file = __DIR__ . '/auto_post.log';
|
||||
function writeLog($message) {
|
||||
global $log_file;
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$log_message = "[$timestamp] $message\n";
|
||||
file_put_contents($log_file, $log_message, FILE_APPEND);
|
||||
}
|
||||
|
||||
// Load API configuration
|
||||
$api_config = require_once __DIR__ . '/config.php';
|
||||
$telegram_bot_token = $api_config['telegram']['bot_token'];
|
||||
$telegram_channel_id = $api_config['telegram']['channel_id'];
|
||||
|
||||
// Verify configuration
|
||||
if (empty($telegram_bot_token) || $telegram_bot_token === '123456789:ABCdefGHIjklmNOPQRstuvwxyz') {
|
||||
writeLog("ERROR: Invalid bot token. Please update config.php with your actual bot token.");
|
||||
die("Invalid bot token. Please check the logs.");
|
||||
}
|
||||
|
||||
if (empty($telegram_channel_id) || $telegram_channel_id === 'YOUR_CHANNEL_ID') {
|
||||
writeLog("ERROR: Invalid channel ID. Please update config.php with your actual channel ID.");
|
||||
die("Invalid channel ID. Please check the logs.");
|
||||
}
|
||||
|
||||
// Function to send data to Telegram channel
|
||||
function sendToChannel($message, $parse_mode = 'HTML') {
|
||||
global $telegram_bot_token, $telegram_channel_id;
|
||||
try {
|
||||
$url = "https://api.telegram.org/bot{$telegram_bot_token}/sendMessage";
|
||||
$data = [
|
||||
'chat_id' => $telegram_channel_id,
|
||||
'text' => $message,
|
||||
'parse_mode' => $parse_mode
|
||||
];
|
||||
|
||||
$options = [
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
|
||||
'content' => http_build_query($data)
|
||||
]
|
||||
];
|
||||
|
||||
$context = stream_context_create($options);
|
||||
$result = file_get_contents($url, false, $context);
|
||||
|
||||
if ($result === false) {
|
||||
writeLog("ERROR: Failed to send message to channel");
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = json_decode($result, true);
|
||||
if (!$response['ok']) {
|
||||
writeLog("ERROR: Telegram API error: " . ($response['description'] ?? 'Unknown error'));
|
||||
return false;
|
||||
}
|
||||
|
||||
writeLog("SUCCESS: Message sent to channel");
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
writeLog("ERROR: Telegram API Exception: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to format video/stream message
|
||||
function formatContentMessage($content) {
|
||||
$message = "🎥 <b>{$content['title']}</b>\n\n";
|
||||
$message .= "📝 {$content['description']}\n\n";
|
||||
$message .= "👤 Posted by: {$content['username']}\n";
|
||||
$message .= "👁 Views: {$content['views']}\n";
|
||||
$message .= "🔗 <a href='{$content['url']}'>Watch on EasyStream</a>\n";
|
||||
|
||||
// Add hashtags if available
|
||||
if (!empty($content['tags'])) {
|
||||
$message .= "\n🏷 Tags: " . implode(' ', array_map(function($tag) {
|
||||
return "#" . str_replace(' ', '', $tag);
|
||||
}, $content['tags']));
|
||||
}
|
||||
|
||||
// Branding normalization - already using EasyStream above
|
||||
return $message;
|
||||
}
|
||||
|
||||
// Function to check and post new content
|
||||
function checkAndPostNewContent() {
|
||||
global $class_database, $cfg, $api_config;
|
||||
|
||||
writeLog("Starting content check...");
|
||||
|
||||
// Get latest videos (last 5 minutes)
|
||||
try {
|
||||
$videos = $class_database->getLatestVideos(
|
||||
$api_config['content']['max_items'],
|
||||
$api_config['content']['time_window']
|
||||
);
|
||||
|
||||
writeLog("Found " . count($videos) . " new videos");
|
||||
|
||||
foreach ($videos as $video) {
|
||||
$content = [
|
||||
'title' => htmlspecialchars($video['title']),
|
||||
'description' => htmlspecialchars($video['description']),
|
||||
'username' => htmlspecialchars($video['username']),
|
||||
'views' => $video['views'],
|
||||
'url' => $cfg['main_url'] . '/video/' . $video['file_key'],
|
||||
'tags' => explode(',', $video['tags'])
|
||||
];
|
||||
|
||||
$message = formatContentMessage($content);
|
||||
sendToChannel($message);
|
||||
|
||||
// Add a small delay between posts to avoid rate limiting
|
||||
sleep(1);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
writeLog("ERROR: Failed to process videos: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Get latest streams (last 5 minutes)
|
||||
try {
|
||||
$streams = $class_database->getLatestStreams(
|
||||
$api_config['content']['max_items'],
|
||||
$api_config['content']['time_window']
|
||||
);
|
||||
|
||||
writeLog("Found " . count($streams) . " new streams");
|
||||
|
||||
foreach ($streams as $stream) {
|
||||
$content = [
|
||||
'title' => htmlspecialchars($stream['title']),
|
||||
'description' => htmlspecialchars($stream['description']),
|
||||
'username' => htmlspecialchars($stream['username']),
|
||||
'views' => $stream['views'],
|
||||
'url' => $cfg['main_url'] . '/stream/' . $stream['stream_key'],
|
||||
'tags' => explode(',', $stream['tags'])
|
||||
];
|
||||
|
||||
$message = formatContentMessage($content);
|
||||
$message = "🔴 LIVE NOW: " . $message; // Add LIVE indicator for streams
|
||||
sendToChannel($message);
|
||||
|
||||
// Add a small delay between posts to avoid rate limiting
|
||||
sleep(1);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
writeLog("ERROR: Failed to process streams: " . $e->getMessage());
|
||||
}
|
||||
|
||||
writeLog("Content check completed");
|
||||
}
|
||||
|
||||
// Run the check
|
||||
writeLog("Script started");
|
||||
checkAndPostNewContent();
|
||||
writeLog("Script finished");
|
||||
22
api/config.php
Normal file
22
api/config.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
// API Configuration
|
||||
return [
|
||||
'telegram' => [
|
||||
'bot_token' => '123456789:ABCdefGHIjklmNOPQRstuvwxyz', // Replace with your actual bot token from BotFather
|
||||
'channel_id' => 'YOUR_CHANNEL_ID', // Replace with your channel ID (e.g., -100xxxxxxxxxx)
|
||||
'allowed_ips' => [
|
||||
// Add Telegram's IP ranges here for security
|
||||
'149.154.160.0/20',
|
||||
'91.108.4.0/22'
|
||||
]
|
||||
],
|
||||
'security' => [
|
||||
'rate_limit' => 30, // requests per minute
|
||||
'max_requests' => 1000 // per hour
|
||||
],
|
||||
'content' => [
|
||||
'check_interval' => 5, // minutes between checks
|
||||
'max_items' => 10, // maximum items to post per check
|
||||
'time_window' => 5 // minutes to look back for new content
|
||||
]
|
||||
];
|
||||
55
api/privacy.php
Normal file
55
api/privacy.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
define('_ISVALID', true);
|
||||
include_once __DIR__ . '/../f_core/config.core.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Require login
|
||||
if (!VSession::isLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Authentication required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = VSecurity::getParam('action', 'alpha', 'export');
|
||||
$uid = (int) $_SESSION['USER_ID'];
|
||||
|
||||
// Basic rate limit
|
||||
if (!VSecurity::checkRateLimit('privacy_' . $uid, 5, 60)) {
|
||||
http_response_code(429);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Too many requests']);
|
||||
exit;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'export':
|
||||
// TODO: Collect actual data
|
||||
$bundle = [
|
||||
'user' => [
|
||||
'id' => $uid,
|
||||
'username' => $_SESSION['USER_NAME'] ?? null,
|
||||
'display_name' => $_SESSION['USER_DNAME'] ?? null,
|
||||
],
|
||||
'files' => [],
|
||||
'subscriptions' => [],
|
||||
];
|
||||
echo json_encode(['status' => 'ok', 'data' => $bundle]);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !VSecurity::validateCSRFFromPost('privacy_delete')) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid CSRF or method']);
|
||||
exit;
|
||||
}
|
||||
// TODO: Implement soft-delete/anonymization workflow
|
||||
VLogger::getInstance()->warning('User requested account deletion', ['user_id' => $uid]);
|
||||
http_response_code(202);
|
||||
echo json_encode(['status' => 'accepted', 'message' => 'Deletion request received']);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Unknown action']);
|
||||
}
|
||||
|
||||
380
api/social.php
Normal file
380
api/social.php
Normal file
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
/*******************************************************************************************************************
|
||||
| Software Name : EasyStream
|
||||
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
||||
| Software Author : (c) Sami Ahmed
|
||||
|*******************************************************************************************************************
|
||||
|
|
||||
|*******************************************************************************************************************
|
||||
| This source file is subject to the EasyStream Proprietary License Agreement.
|
||||
|
|
||||
| By using this software, you acknowledge have read this Agreement and agree to be bound thereby.
|
||||
|*******************************************************************************************************************
|
||||
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
||||
|*******************************************************************************************************************/
|
||||
|
||||
define('_ISVALID', true);
|
||||
|
||||
// Include core configuration
|
||||
include_once '../f_core/config.core.php';
|
||||
|
||||
// Set JSON response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
|
||||
|
||||
// Handle preflight requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize social system
|
||||
$social = VSocial::getInstance();
|
||||
|
||||
// Get request method and path
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$path = $_SERVER['PATH_INFO'] ?? $_SERVER['REQUEST_URI'] ?? '';
|
||||
$path = parse_url($path, PHP_URL_PATH);
|
||||
$pathParts = array_filter(explode('/', $path));
|
||||
|
||||
// Get current user
|
||||
$userId = VSession::isLoggedIn() ? $_SESSION['user_id'] : null;
|
||||
|
||||
// Route the request
|
||||
switch ($method) {
|
||||
case 'POST':
|
||||
handlePostRequest($social, $pathParts, $userId);
|
||||
break;
|
||||
|
||||
case 'GET':
|
||||
handleGetRequest($social, $pathParts, $userId);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
handleDeleteRequest($social, $pathParts, $userId);
|
||||
break;
|
||||
|
||||
default:
|
||||
sendError('Method not allowed', 405);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
VLogger::getInstance()->error('Social API error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
sendError('Internal server error', 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle POST requests
|
||||
*/
|
||||
function handlePostRequest($social, $pathParts, $userId)
|
||||
{
|
||||
$action = end($pathParts);
|
||||
$data = getJsonInput();
|
||||
|
||||
switch ($action) {
|
||||
case 'vote':
|
||||
handleVote($social, $data, $userId);
|
||||
break;
|
||||
|
||||
case 'comment':
|
||||
handleAddComment($social, $data, $userId);
|
||||
break;
|
||||
|
||||
case 'share':
|
||||
handleShare($social, $data, $userId);
|
||||
break;
|
||||
|
||||
default:
|
||||
sendError('Invalid action', 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle GET requests
|
||||
*/
|
||||
function handleGetRequest($social, $pathParts, $userId)
|
||||
{
|
||||
$action = end($pathParts);
|
||||
|
||||
switch ($action) {
|
||||
case 'comments':
|
||||
handleGetComments($social, $_GET, $userId);
|
||||
break;
|
||||
|
||||
case 'replies':
|
||||
handleGetReplies($social, $_GET, $userId);
|
||||
break;
|
||||
|
||||
case 'sharing-urls':
|
||||
handleGetSharingUrls($social, $_GET);
|
||||
break;
|
||||
|
||||
case 'stats':
|
||||
handleGetStats($social, $_GET);
|
||||
break;
|
||||
|
||||
default:
|
||||
sendError('Invalid action', 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle DELETE requests
|
||||
*/
|
||||
function handleDeleteRequest($social, $pathParts, $userId)
|
||||
{
|
||||
$action = $pathParts[count($pathParts) - 2] ?? '';
|
||||
$id = end($pathParts);
|
||||
|
||||
switch ($action) {
|
||||
case 'comment':
|
||||
handleDeleteComment($social, $id, $userId);
|
||||
break;
|
||||
|
||||
default:
|
||||
sendError('Invalid action', 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle vote action (like/dislike)
|
||||
*/
|
||||
function handleVote($social, $data, $userId)
|
||||
{
|
||||
// Validate required fields
|
||||
$requiredFields = ['content_type', 'content_id', 'action'];
|
||||
foreach ($requiredFields as $field) {
|
||||
if (!isset($data[$field]) || empty($data[$field])) {
|
||||
sendError("Missing required field: {$field}", 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!VSecurity::validateCSRFToken('social_vote', $data['csrf_token'] ?? '')) {
|
||||
sendError('Invalid CSRF token', 403);
|
||||
}
|
||||
|
||||
$contentType = $data['content_type'];
|
||||
$contentId = $data['content_id'];
|
||||
$action = $data['action']; // 'like', 'dislike', or 'remove'
|
||||
|
||||
// Validate action
|
||||
if (!in_array($action, ['like', 'dislike', 'remove'])) {
|
||||
sendError('Invalid action. Must be like, dislike, or remove', 400);
|
||||
}
|
||||
|
||||
$result = $social->handleVote($contentType, $contentId, $userId, $action);
|
||||
|
||||
if ($result['success']) {
|
||||
sendSuccess($result);
|
||||
} else {
|
||||
sendError($result['error'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle add comment
|
||||
*/
|
||||
function handleAddComment($social, $data, $userId)
|
||||
{
|
||||
// Validate required fields
|
||||
$requiredFields = ['content_type', 'content_id', 'comment'];
|
||||
foreach ($requiredFields as $field) {
|
||||
if (!isset($data[$field])) {
|
||||
sendError("Missing required field: {$field}", 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!VSecurity::validateCSRFToken('social_comment', $data['csrf_token'] ?? '')) {
|
||||
sendError('Invalid CSRF token', 403);
|
||||
}
|
||||
|
||||
$contentType = $data['content_type'];
|
||||
$contentId = $data['content_id'];
|
||||
$comment = $data['comment'];
|
||||
$parentId = $data['parent_id'] ?? null;
|
||||
|
||||
$result = $social->addComment($contentType, $contentId, $userId, $comment, $parentId);
|
||||
|
||||
if ($result['success']) {
|
||||
sendSuccess($result);
|
||||
} else {
|
||||
sendError($result['error'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle share tracking
|
||||
*/
|
||||
function handleShare($social, $data, $userId)
|
||||
{
|
||||
// Validate required fields
|
||||
$requiredFields = ['content_type', 'content_id', 'platform'];
|
||||
foreach ($requiredFields as $field) {
|
||||
if (!isset($data[$field]) || empty($data[$field])) {
|
||||
sendError("Missing required field: {$field}", 400);
|
||||
}
|
||||
}
|
||||
|
||||
$contentType = $data['content_type'];
|
||||
$contentId = $data['content_id'];
|
||||
$platform = $data['platform'];
|
||||
|
||||
// Track the share
|
||||
$social->trackShare($contentType, $contentId, $platform, $userId);
|
||||
|
||||
sendSuccess(['message' => 'Share tracked successfully']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get comments
|
||||
*/
|
||||
function handleGetComments($social, $params, $userId)
|
||||
{
|
||||
// Validate required parameters
|
||||
if (!isset($params['content_type']) || !isset($params['content_id'])) {
|
||||
sendError('Missing content_type or content_id parameter', 400);
|
||||
}
|
||||
|
||||
$contentType = $params['content_type'];
|
||||
$contentId = $params['content_id'];
|
||||
$page = (int)($params['page'] ?? 1);
|
||||
$limit = min(50, (int)($params['limit'] ?? 20)); // Max 50 comments per page
|
||||
$sort = $params['sort'] ?? 'newest';
|
||||
|
||||
$result = $social->getComments($contentType, $contentId, $page, $limit, $sort);
|
||||
|
||||
if ($result['success']) {
|
||||
sendSuccess($result);
|
||||
} else {
|
||||
sendError($result['error'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get comment replies
|
||||
*/
|
||||
function handleGetReplies($social, $params, $userId)
|
||||
{
|
||||
if (!isset($params['parent_id'])) {
|
||||
sendError('Missing parent_id parameter', 400);
|
||||
}
|
||||
|
||||
$parentId = $params['parent_id'];
|
||||
$limit = min(50, (int)($params['limit'] ?? 10));
|
||||
|
||||
$replies = $social->getCommentReplies($parentId, $limit);
|
||||
|
||||
sendSuccess([
|
||||
'replies' => $replies,
|
||||
'count' => count($replies)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get sharing URLs
|
||||
*/
|
||||
function handleGetSharingUrls($social, $params)
|
||||
{
|
||||
// Validate required parameters
|
||||
if (!isset($params['content_type']) || !isset($params['content_id'])) {
|
||||
sendError('Missing content_type or content_id parameter', 400);
|
||||
}
|
||||
|
||||
$contentType = $params['content_type'];
|
||||
$contentId = $params['content_id'];
|
||||
|
||||
$result = $social->generateSharingUrls($contentType, $contentId);
|
||||
|
||||
if ($result['success']) {
|
||||
sendSuccess($result);
|
||||
} else {
|
||||
sendError($result['error'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get social stats
|
||||
*/
|
||||
function handleGetStats($social, $params)
|
||||
{
|
||||
// Validate required parameters
|
||||
if (!isset($params['content_type']) || !isset($params['content_id'])) {
|
||||
sendError('Missing content_type or content_id parameter', 400);
|
||||
}
|
||||
|
||||
$contentType = $params['content_type'];
|
||||
$contentId = $params['content_id'];
|
||||
|
||||
$result = $social->getSocialStats($contentType, $contentId);
|
||||
|
||||
if ($result['success']) {
|
||||
sendSuccess($result);
|
||||
} else {
|
||||
sendError($result['error'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete comment
|
||||
*/
|
||||
function handleDeleteComment($social, $commentId, $userId)
|
||||
{
|
||||
if (!$commentId) {
|
||||
sendError('Missing comment ID', 400);
|
||||
}
|
||||
|
||||
$result = $social->deleteComment($commentId, $userId);
|
||||
|
||||
if ($result['success']) {
|
||||
sendSuccess($result);
|
||||
} else {
|
||||
sendError($result['error'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functions
|
||||
*/
|
||||
function getJsonInput()
|
||||
{
|
||||
$input = file_get_contents('php://input');
|
||||
$data = json_decode($input, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
sendError('Invalid JSON input', 400);
|
||||
}
|
||||
|
||||
return $data ?? [];
|
||||
}
|
||||
|
||||
function sendSuccess($data, $code = 200)
|
||||
{
|
||||
http_response_code($code);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
function sendError($message, $code = 400)
|
||||
{
|
||||
http_response_code($code);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $message,
|
||||
'timestamp' => time()
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
101
api/telegram.php
Normal file
101
api/telegram.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
define('_ISVALID', true);
|
||||
include_once '../f_core/config.core.php';
|
||||
|
||||
// Load API configuration
|
||||
$api_config = require_once __DIR__ . '/config.php';
|
||||
$telegram_bot_token = $api_config['telegram']['bot_token'];
|
||||
|
||||
// Function to send data to Telegram
|
||||
function sendToTelegram($chat_id, $message) {
|
||||
global $telegram_bot_token;
|
||||
try {
|
||||
$url = "https://api.telegram.org/bot{$telegram_bot_token}/sendMessage";
|
||||
$data = [
|
||||
'chat_id' => $chat_id,
|
||||
'text' => $message,
|
||||
'parse_mode' => 'HTML'
|
||||
];
|
||||
|
||||
$options = [
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
|
||||
'content' => http_build_query($data)
|
||||
]
|
||||
];
|
||||
|
||||
$context = stream_context_create($options);
|
||||
$result = file_get_contents($url, false, $context);
|
||||
|
||||
if ($result === false) {
|
||||
error_log("Failed to send Telegram message to chat_id: {$chat_id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
error_log("Telegram API Error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle incoming webhook
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$update = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
// Process the update
|
||||
if (isset($update['message'])) {
|
||||
$message = $update['message'];
|
||||
$chat_id = $message['chat']['id'];
|
||||
$text = $message['text'] ?? '';
|
||||
|
||||
// Handle commands
|
||||
if (strpos($text, '/') === 0) {
|
||||
switch ($text) {
|
||||
case '/start':
|
||||
sendToTelegram($chat_id, "Welcome to EasyStream Bot! Use /videos to get the latest videos.");
|
||||
break;
|
||||
|
||||
case '/videos':
|
||||
// Get latest videos from EasyStream
|
||||
$videos = $class_database->getLatestVideos(5); // Adjust limit as needed
|
||||
$response = "Latest Videos:\n\n";
|
||||
foreach ($videos as $video) {
|
||||
$response .= "📹 {$video['title']}\n";
|
||||
$response .= "👤 {$video['username']}\n";
|
||||
$response .= "👁 {$video['views']} views\n";
|
||||
$response .= "🔗 {$cfg['main_url']}/video/{$video['file_key']}\n\n";
|
||||
}
|
||||
sendToTelegram($chat_id, $response);
|
||||
break;
|
||||
|
||||
case '/search':
|
||||
$query = trim(substr($text, 7));
|
||||
if (empty($query)) {
|
||||
sendToTelegram($chat_id, "Please provide a search query: /search <query>");
|
||||
break;
|
||||
}
|
||||
|
||||
$results = $class_database->searchVideos($query, 5);
|
||||
if (empty($results)) {
|
||||
sendToTelegram($chat_id, "No videos found for: {$query}");
|
||||
break;
|
||||
}
|
||||
|
||||
$response = "Search Results for: {$query}\n\n";
|
||||
foreach ($results as $video) {
|
||||
$response .= "📹 {$video['title']}\n";
|
||||
$response .= "👤 {$video['username']}\n";
|
||||
$response .= "👁 {$video['views']} views\n";
|
||||
$response .= "🔗 {$cfg['main_url']}/video/{$video['file_key']}\n\n";
|
||||
}
|
||||
sendToTelegram($chat_id, $response);
|
||||
break;
|
||||
|
||||
default:
|
||||
sendToTelegram($chat_id, "Unknown command. Available commands:\n/start - Start the bot\n/videos - Get latest videos\n/search <query> - Search for videos");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
api/test.php
Normal file
102
api/test.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
define('_ISVALID', true);
|
||||
include_once '../f_core/config.core.php';
|
||||
|
||||
// Load API configuration
|
||||
$api_config = require_once __DIR__ . '/config.php';
|
||||
|
||||
// Test results array
|
||||
$tests = [];
|
||||
|
||||
// Test 1: Check if config file exists and is readable
|
||||
$tests['config_file'] = [
|
||||
'name' => 'Configuration File',
|
||||
'status' => file_exists(__DIR__ . '/config.php') ? 'OK' : 'FAIL',
|
||||
'message' => file_exists(__DIR__ . '/config.php') ? 'Config file exists' : 'Config file not found'
|
||||
];
|
||||
|
||||
// Test 2: Verify bot token
|
||||
$tests['bot_token'] = [
|
||||
'name' => 'Bot Token',
|
||||
'status' => (!empty($api_config['telegram']['bot_token']) && $api_config['telegram']['bot_token'] !== '123456789:ABCdefGHIjklmNOPQRstuvwxyz') ? 'OK' : 'FAIL',
|
||||
'message' => (!empty($api_config['telegram']['bot_token']) && $api_config['telegram']['bot_token'] !== '123456789:ABCdefGHIjklmNOPQRstuvwxyz') ? 'Bot token is set' : 'Please set your bot token in config.php'
|
||||
];
|
||||
|
||||
// Test 3: Verify channel ID
|
||||
$tests['channel_id'] = [
|
||||
'name' => 'Channel ID',
|
||||
'status' => (!empty($api_config['telegram']['channel_id']) && $api_config['telegram']['channel_id'] !== 'YOUR_CHANNEL_ID') ? 'OK' : 'FAIL',
|
||||
'message' => (!empty($api_config['telegram']['channel_id']) && $api_config['telegram']['channel_id'] !== 'YOUR_CHANNEL_ID') ? 'Channel ID is set' : 'Please set your channel ID in config.php'
|
||||
];
|
||||
|
||||
// Test 4: Test Telegram API connection and channel access
|
||||
function testTelegramAPI($bot_token, $channel_id) {
|
||||
// First test bot token
|
||||
$url = "https://api.telegram.org/bot{$bot_token}/getMe";
|
||||
$result = file_get_contents($url);
|
||||
if ($result === false) {
|
||||
return ['status' => 'FAIL', 'message' => 'Could not connect to Telegram API'];
|
||||
}
|
||||
|
||||
$response = json_decode($result, true);
|
||||
if (!$response['ok']) {
|
||||
return ['status' => 'FAIL', 'message' => 'Invalid bot token'];
|
||||
}
|
||||
|
||||
// Then test channel access
|
||||
$url = "https://api.telegram.org/bot{$bot_token}/getChat?chat_id={$channel_id}";
|
||||
$result = file_get_contents($url);
|
||||
if ($result === false) {
|
||||
return ['status' => 'FAIL', 'message' => 'Could not access channel'];
|
||||
}
|
||||
|
||||
$response = json_decode($result, true);
|
||||
return [
|
||||
'status' => $response['ok'] ? 'OK' : 'FAIL',
|
||||
'message' => $response['ok'] ? 'Successfully connected to Telegram API and channel' : 'Failed to access channel. Make sure bot is an admin.'
|
||||
];
|
||||
}
|
||||
|
||||
$telegram_test = testTelegramAPI($api_config['telegram']['bot_token'], $api_config['telegram']['channel_id']);
|
||||
$tests['telegram_api'] = [
|
||||
'name' => 'Telegram API & Channel Access',
|
||||
'status' => $telegram_test['status'],
|
||||
'message' => $telegram_test['message']
|
||||
];
|
||||
|
||||
// Test 5: Check database connection
|
||||
$tests['database'] = [
|
||||
'name' => 'Database Connection',
|
||||
'status' => isset($class_database) ? 'OK' : 'FAIL',
|
||||
'message' => isset($class_database) ? 'Database connection is available' : 'Database connection failed'
|
||||
];
|
||||
|
||||
// Test 6: Check file permissions
|
||||
$tests['permissions'] = [
|
||||
'name' => 'File Permissions',
|
||||
'status' => is_writable(__DIR__) ? 'OK' : 'FAIL',
|
||||
'message' => is_writable(__DIR__) ? 'Directory is writable' : 'Directory is not writable'
|
||||
];
|
||||
|
||||
// Output test results
|
||||
echo "<h2>EasyStream Telegram Channel Setup Test</h2>";
|
||||
echo "<table border='1' cellpadding='5'>";
|
||||
echo "<tr><th>Test</th><th>Status</th><th>Message</th></tr>";
|
||||
foreach ($tests as $test) {
|
||||
$color = $test['status'] === 'OK' ? 'green' : 'red';
|
||||
echo "<tr>";
|
||||
echo "<td>{$test['name']}</td>";
|
||||
echo "<td style='color: {$color}'>{$test['status']}</td>";
|
||||
echo "<td>{$test['message']}</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
// Additional instructions
|
||||
echo "<h3>Next Steps:</h3>";
|
||||
echo "<ol>";
|
||||
echo "<li>Make sure all tests pass (show as OK)</li>";
|
||||
echo "<li>If any test fails, follow the message instructions to fix it</li>";
|
||||
echo "<li>Once all tests pass, set up the cron job to run auto_post.php every 5 minutes</li>";
|
||||
echo "<li>Monitor the auto_post.log file for any errors</li>";
|
||||
echo "</ol>";
|
||||
145
api/upload/progress.php
Normal file
145
api/upload/progress.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/*******************************************************************************************************************
|
||||
| Software Name : EasyStream
|
||||
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
||||
| Software Author : (c) Sami Ahmed
|
||||
|*******************************************************************************************************************
|
||||
|
|
||||
|*******************************************************************************************************************
|
||||
| This source file is subject to the EasyStream Proprietary License Agreement.
|
||||
|
|
||||
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|
||||
|*******************************************************************************************************************
|
||||
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
||||
|*******************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Upload Progress API
|
||||
*
|
||||
* Provides real-time upload progress information
|
||||
* Tracks: Upload percentage, processing status, encoding status, completion
|
||||
*/
|
||||
|
||||
define('_ISVALID', true);
|
||||
|
||||
$main_dir = realpath(dirname(__FILE__) . '/../../');
|
||||
set_include_path($main_dir);
|
||||
|
||||
include_once 'f_core/config.core.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST');
|
||||
|
||||
$usr_id = isset($_SESSION['USER_ID']) ? (int) $_SESSION['USER_ID'] : 0;
|
||||
|
||||
if ($usr_id == 0) {
|
||||
echo json_encode(['error' => 'Not authenticated']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = isset($_GET['action']) ? $_GET['action'] : 'get_status';
|
||||
$upload_id = isset($_GET['upload_id']) ? $_GET['upload_id'] : '';
|
||||
|
||||
switch ($action) {
|
||||
case 'get_status':
|
||||
echo json_encode(getUploadStatus($upload_id, $usr_id));
|
||||
break;
|
||||
|
||||
case 'get_all':
|
||||
echo json_encode(getAllUploads($usr_id));
|
||||
break;
|
||||
|
||||
case 'cancel':
|
||||
echo json_encode(cancelUpload($upload_id, $usr_id));
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['error' => 'Invalid action']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload status for a specific upload
|
||||
*/
|
||||
function getUploadStatus($upload_id, $usr_id)
|
||||
{
|
||||
global $class_database;
|
||||
|
||||
if (empty($upload_id)) {
|
||||
return ['error' => 'Upload ID required'];
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM `db_upload_progress`
|
||||
WHERE `upload_id` = '%s' AND `usr_id` = %d
|
||||
LIMIT 1";
|
||||
|
||||
$result = $class_database->doQuery($sql, $upload_id, $usr_id);
|
||||
$row = $result->fetch_assoc();
|
||||
|
||||
if (!$row) {
|
||||
return ['error' => 'Upload not found'];
|
||||
}
|
||||
|
||||
return [
|
||||
'upload_id' => $row['upload_id'],
|
||||
'filename' => $row['filename'],
|
||||
'file_type' => $row['file_type'],
|
||||
'file_size' => (int) $row['file_size'],
|
||||
'uploaded_bytes' => (int) $row['uploaded_bytes'],
|
||||
'upload_percent' => (float) $row['upload_percent'],
|
||||
'status' => $row['status'], // uploading, processing, encoding, completed, failed
|
||||
'processing_step' => $row['processing_step'],
|
||||
'error_message' => $row['error_message'],
|
||||
'file_key' => $row['file_key'],
|
||||
'started_at' => $row['started_at'],
|
||||
'completed_at' => $row['completed_at']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active uploads for user
|
||||
*/
|
||||
function getAllUploads($usr_id)
|
||||
{
|
||||
global $class_database;
|
||||
|
||||
$sql = "SELECT * FROM `db_upload_progress`
|
||||
WHERE `usr_id` = %d
|
||||
AND `status` IN ('uploading', 'processing', 'encoding')
|
||||
ORDER BY `started_at` DESC";
|
||||
|
||||
$result = $class_database->doQuery($sql, $usr_id);
|
||||
|
||||
$uploads = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$uploads[] = [
|
||||
'upload_id' => $row['upload_id'],
|
||||
'filename' => $row['filename'],
|
||||
'file_type' => $row['file_type'],
|
||||
'file_size' => (int) $row['file_size'],
|
||||
'uploaded_bytes' => (int) $row['uploaded_bytes'],
|
||||
'upload_percent' => (float) $row['upload_percent'],
|
||||
'status' => $row['status'],
|
||||
'processing_step' => $row['processing_step'],
|
||||
'started_at' => $row['started_at']
|
||||
];
|
||||
}
|
||||
|
||||
return ['uploads' => $uploads];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel an upload
|
||||
*/
|
||||
function cancelUpload($upload_id, $usr_id)
|
||||
{
|
||||
global $class_database;
|
||||
|
||||
$sql = "UPDATE `db_upload_progress`
|
||||
SET `status` = 'cancelled'
|
||||
WHERE `upload_id` = '%s' AND `usr_id` = %d";
|
||||
|
||||
$class_database->doQuery($sql, $upload_id, $usr_id);
|
||||
|
||||
return ['success' => true, 'message' => 'Upload cancelled'];
|
||||
}
|
||||
117
api/video/progress.php
Normal file
117
api/video/progress.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/*******************************************************************************************************************
|
||||
| Video Progress Tracking API
|
||||
| Handles watch progress updates for resume functionality
|
||||
|*******************************************************************************************************************/
|
||||
|
||||
define('_ISVALID', true);
|
||||
|
||||
// Include core configuration
|
||||
include_once '../../f_core/config.core.php';
|
||||
|
||||
// Set JSON response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
|
||||
|
||||
// Handle preflight requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
// Only allow POST requests
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
sendError('Method not allowed', 405);
|
||||
}
|
||||
|
||||
// Get current user
|
||||
$userId = VSession::isLoggedIn() ? $_SESSION['user_id'] : null;
|
||||
|
||||
if (!$userId) {
|
||||
sendError('User must be logged in', 401);
|
||||
}
|
||||
|
||||
// Get JSON input
|
||||
$input = file_get_contents('php://input');
|
||||
$data = json_decode($input, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
sendError('Invalid JSON input', 400);
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
$requiredFields = ['video_key', 'current_time', 'duration'];
|
||||
foreach ($requiredFields as $field) {
|
||||
if (!isset($data[$field])) {
|
||||
sendError("Missing required field: {$field}", 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!VSecurity::validateCSRFToken('video_progress', $data['csrf_token'] ?? '')) {
|
||||
sendError('Invalid CSRF token', 403);
|
||||
}
|
||||
|
||||
$videoKey = $data['video_key'];
|
||||
$currentTime = (float)$data['current_time'];
|
||||
$duration = (float)$data['duration'];
|
||||
|
||||
// Validate data
|
||||
if ($currentTime < 0 || $duration <= 0 || $currentTime > $duration) {
|
||||
sendError('Invalid time values', 400);
|
||||
}
|
||||
|
||||
// Initialize streaming system
|
||||
$streaming = new VStreaming();
|
||||
|
||||
// Update watch progress
|
||||
$streaming->updateWatchProgress($videoKey, $userId, $currentTime, $duration);
|
||||
|
||||
sendSuccess([
|
||||
'message' => 'Progress updated successfully',
|
||||
'video_key' => $videoKey,
|
||||
'current_time' => $currentTime,
|
||||
'duration' => $duration,
|
||||
'watch_percentage' => ($duration > 0) ? ($currentTime / $duration) * 100 : 0
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
VLogger::getInstance()->error('Video progress API error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
sendError('Internal server error', 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send success response
|
||||
*/
|
||||
function sendSuccess($data, $code = 200)
|
||||
{
|
||||
http_response_code($code);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send error response
|
||||
*/
|
||||
function sendError($message, $code = 400)
|
||||
{
|
||||
http_response_code($code);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $message,
|
||||
'timestamp' => time()
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user