diff --git a/App/Controllers/Admin/AgentController.php b/App/Controllers/Admin/AgentController.php index c992369..3dbe69f 100644 --- a/App/Controllers/Admin/AgentController.php +++ b/App/Controllers/Admin/AgentController.php @@ -34,7 +34,7 @@ class AgentController extends AdminBaseController { // 创建代理:先创建用户,再创建代理记录 $userId = (int)($data['user_id'] ?? 0); if (!$userId) { $this->json(['status'=>'error','message'=>'User ID required']); return; } - $code = strtoupper(substr(md5(uniqid()), 0, 8)); + $code = strtoupper(bin2hex(random_bytes(4))); $this->db->insert('agents', [ 'user_id' => $userId, 'parent_id' => !empty($data['parent_id']) ? (int)$data['parent_id'] : null, diff --git a/App/Controllers/Admin/BotController.php b/App/Controllers/Admin/BotController.php new file mode 100644 index 0000000..d0111b9 --- /dev/null +++ b/App/Controllers/Admin/BotController.php @@ -0,0 +1,965 @@ +checkLogin(); + $this->checkAdmin(); + $this->db = new Database(); + } + + public function index(): void + { + $stats = [ + 'bot_count' => 0, + 'group_count' => 0, + 'wallet_count' => 0, + 'enabled_group_count' => 0, + 'member_count' => 0, + 'shill_count' => 0, + 'push_count' => 0, + 'push_success_count' => 0, + 'countdown_group_count' => 0, + 'animation_group_count' => 0, + 'api_request_count' => 0, + 'api_signature_ok_count' => 0, + ]; + + $bots = []; + $groups = []; + $wallets = []; + $rules = []; + $games = []; + $users = []; + $members = []; + $shills = []; + $recentOrders = []; + $pushLogs = []; + $apiRequestLogs = []; + $pushSummary = [ + 'countdown' => ['total' => 0, 'success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0], + 'bet_success' => ['total' => 0, 'success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0], + 'draw' => ['total' => 0, 'success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0], + 'credit' => ['total' => 0, 'success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0], + 'debit' => ['total' => 0, 'success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0], + 'system' => ['total' => 0, 'success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0], + ]; + + try { + $stats['bot_count'] = (int)($this->db->count('bot_instances') ?: 0); + $stats['group_count'] = (int)($this->db->count('bot_groups') ?: 0); + $stats['wallet_count'] = (int)($this->db->count('bot_group_wallets', ['status' => 1]) ?: 0); + $stats['enabled_group_count'] = (int)($this->db->count('bot_groups', ['status' => 1, 'bet_enabled' => 1]) ?: 0); + $stats['member_count'] = (int)($this->db->count('bot_group_members') ?: 0); + $stats['shill_count'] = (int)($this->db->count('bot_shills', ['enabled' => 1]) ?: 0); + $stats['push_count'] = (int)($this->db->count('bot_push_logs') ?: 0); + $stats['push_success_count'] = (int)($this->db->count('bot_push_logs', ['status' => 'success']) ?: 0); + $stats['countdown_group_count'] = (int)($this->db->count('bot_groups', ['remind_close_countdown' => 1]) ?: 0); + $stats['animation_group_count'] = (int)($this->db->count('bot_groups', ['animation_enabled' => 1]) ?: 0); + $stats['api_request_count'] = (int)($this->db->count('bot_api_request_logs') ?: 0); + $stats['api_signature_ok_count'] = (int)($this->db->count('bot_api_request_logs', ['signature_ok' => 1]) ?: 0); + + $bots = $this->db->select('bot_instances', '*', ['ORDER' => ['id' => 'DESC']]) ?: []; + foreach ($bots as &$bot) { + $botId = (int)$bot['id']; + $bot['group_count'] = (int)($this->db->count('bot_groups', ['bot_id' => $botId]) ?: 0); + $bot['active_group_count'] = (int)($this->db->count('bot_groups', [ + 'bot_id' => $botId, + 'status' => 1, + 'bet_enabled' => 1, + ]) ?: 0); + } + unset($bot); + + $groups = $this->db->select('bot_groups', [ + '[>]bot_instances' => ['bot_id' => 'id'], + '[>]games' => ['game_id' => 'id'], + ], [ + 'bot_groups.id', + 'bot_groups.bot_id', + 'bot_groups.game_id', + 'bot_groups.bet_format_rule_id', + 'bot_groups.tg_group_id', + 'bot_groups.group_name', + 'bot_groups.group_type', + 'bot_groups.status', + 'bot_groups.bet_enabled', + 'bot_groups.remind_bet_success', + 'bot_groups.remind_draw_result', + 'bot_groups.remind_close_countdown', + 'bot_groups.animation_enabled', + 'bot_groups.countdown_config', + 'bot_groups.updated_at', + 'bot_instances.name(bot_name)', + 'games.name(game_name)', + ], [ + 'ORDER' => ['bot_groups.id' => 'DESC'], + ]) ?: []; + + $wallets = $this->db->select('bot_group_wallets', [ + '[>]bot_groups' => ['group_id' => 'id'], + '[>]users' => ['platform_user_id' => 'id'], + ], [ + 'bot_group_wallets.id', + 'bot_group_wallets.group_id', + 'bot_group_wallets.platform_user_id', + 'bot_group_wallets.wallet_mode', + 'bot_group_wallets.status', + 'bot_group_wallets.updated_at', + 'bot_groups.group_name', + 'bot_groups.tg_group_id', + 'users.id(platform_user_id)', + 'users.username(platform_username)', + 'users.balance(platform_balance)', + ], [ + 'ORDER' => ['bot_group_wallets.id' => 'DESC'], + ]) ?: []; + + $rules = $this->db->select('bot_bet_format_rules', ['id', 'rule_code', 'name', 'status'], [ + 'status' => 1, + 'ORDER' => ['id' => 'DESC'], + ]) ?: []; + + $games = $this->db->select('games', ['id', 'name', 'type', 'status'], [ + 'status' => 1, + 'ORDER' => ['sort_order' => 'ASC', 'id' => 'DESC'], + ]) ?: []; + + $users = $this->db->select('users', ['id', 'username', 'balance', 'status', 'role'], [ + 'role[!]' => 'admin', + 'ORDER' => ['id' => 'DESC'], + 'LIMIT' => 200, + ]) ?: []; + + $members = $this->db->select('bot_group_members', [ + '[>]bot_groups' => ['group_id' => 'id'], + '[>]users' => ['platform_user_id' => 'id'], + ], [ + 'bot_group_members.id', + 'bot_group_members.group_id', + 'bot_group_members.tg_user_id', + 'bot_group_members.tg_username', + 'bot_group_members.tg_nickname', + 'bot_group_members.platform_user_id', + 'bot_group_members.role', + 'bot_group_members.bet_enabled', + 'bot_group_members.last_seen_at', + 'bot_group_members.updated_at', + 'bot_groups.group_name', + 'bot_groups.tg_group_id', + 'users.username(platform_username)', + ], [ + 'ORDER' => ['bot_group_members.id' => 'DESC'], + 'LIMIT' => 200, + ]) ?: []; + + $shills = $this->db->select('bot_shills', [ + '[>]bot_groups' => ['group_id' => 'id'], + '[>]bot_group_members' => [ + 'bot_shills.group_id' => 'group_id', + 'bot_shills.tg_user_id' => 'tg_user_id', + ], + ], [ + 'bot_shills.id', + 'bot_shills.group_id', + 'bot_shills.tg_user_id', + 'bot_shills.note', + 'bot_shills.enabled', + 'bot_shills.updated_at', + 'bot_groups.group_name', + 'bot_groups.tg_group_id', + 'bot_group_members.tg_username', + 'bot_group_members.tg_nickname', + ], [ + 'ORDER' => ['bot_shills.id' => 'DESC'], + 'LIMIT' => 200, + ]) ?: []; + + $recentOrders = $this->db->select('bot_bet_orders', [ + '[>]bot_groups' => ['group_id' => 'id'], + ], [ + 'bot_bet_orders.id(order_id)', + 'bot_bet_orders.period_number', + 'bot_bet_orders.bet_amount_total', + 'bot_bet_orders.accepted_bet_count', + 'bot_bet_orders.sync_status', + 'bot_bet_orders.sync_error', + 'bot_bet_orders.is_shill', + 'bot_bet_orders.created_at', + 'bot_bet_orders.tg_username', + 'bot_groups.group_name', + 'bot_groups.tg_group_id', + ], [ + 'ORDER' => ['bot_bet_orders.id' => 'DESC'], + 'LIMIT' => 10, + ]) ?: []; + + $pushLogs = $this->db->select('bot_push_logs', [ + '[>]bot_groups' => ['group_id' => 'id'], + ], [ + 'bot_push_logs.id', + 'bot_push_logs.group_id', + 'bot_push_logs.period_number', + 'bot_push_logs.push_type', + 'bot_push_logs.payload_json', + 'bot_push_logs.tg_message_id', + 'bot_push_logs.status', + 'bot_push_logs.error_message', + 'bot_push_logs.created_at', + 'bot_groups.group_name', + 'bot_groups.tg_group_id', + ], [ + 'ORDER' => ['bot_push_logs.id' => 'DESC'], + 'LIMIT' => 20, + ]) ?: []; + + foreach ($pushLogs as &$pushLog) { + $payload = []; + if (!empty($pushLog['payload_json'])) { + $decodedPayload = json_decode((string)$pushLog['payload_json'], true); + if (is_array($decodedPayload)) { + $payload = $decodedPayload; + } + } + + $pushLog['payload'] = $payload; + $pushType = (string)($pushLog['push_type'] ?? 'system'); + $status = (string)($pushLog['status'] ?? 'pending'); + if (!isset($pushSummary[$pushType])) { + $pushSummary[$pushType] = ['total' => 0, 'success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0]; + } + $pushSummary[$pushType]['total']++; + if (isset($pushSummary[$pushType][$status])) { + $pushSummary[$pushType][$status]++; + } + } + unset($pushLog); + + $apiRequestLogs = $this->db->select('bot_api_request_logs', [ + '[>]bot_instances' => ['bot_id' => 'id'], + '[>]bot_groups' => ['group_id' => 'id'], + ], [ + 'bot_api_request_logs.id', + 'bot_api_request_logs.request_uri', + 'bot_api_request_logs.http_method', + 'bot_api_request_logs.idempotency_key', + 'bot_api_request_logs.response_code', + 'bot_api_request_logs.client_ip', + 'bot_api_request_logs.signature_ok', + 'bot_api_request_logs.created_at', + 'bot_instances.name(bot_name)', + 'bot_groups.group_name', + 'bot_groups.tg_group_id', + ], [ + 'ORDER' => ['bot_api_request_logs.id' => 'DESC'], + 'LIMIT' => 20, + ]) ?: []; + } catch (\Throwable $e) { + } + + $this->render('Admin/bots.php', compact( + 'stats', 'bots', 'groups', 'wallets', 'rules', 'games', 'users', 'members', 'shills', 'recentOrders', 'pushLogs', 'pushSummary', 'apiRequestLogs' + ) + ['title' => 'Bot 管理']); + } + + public function saveBot(): void + { + $payload = $this->getRequestData(); + $name = trim((string)($payload['name'] ?? '')); + $botToken = trim((string)($payload['bot_token'] ?? '')); + $botUsername = $this->normalizeNullableString($payload['bot_username'] ?? null); + $runMode = $this->normalizeRunMode((string)($payload['run_mode'] ?? 'polling')); + $webhookUrl = $this->normalizeNullableString($payload['webhook_url'] ?? null); + $remark = $this->normalizeNullableString($payload['remark'] ?? null); + + if ($name === '' || $botToken === '') { + $this->json(['success' => false, 'message' => 'Bot 名称和 Token 必填']); + return; + } + + try { + $id = $this->db->insert('bot_instances', [ + 'name' => $name, + 'bot_token' => $botToken, + 'bot_username' => $botUsername, + 'bot_key' => bin2hex(random_bytes(8)), + 'bot_secret' => bin2hex(random_bytes(16)), + 'webhook_url' => $webhookUrl, + 'run_mode' => $runMode, + 'status' => 1, + 'remark' => $remark, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + $this->json(['success' => true, 'message' => 'Bot 实例创建成功', 'data' => ['id' => (int)$id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => 'Bot 实例创建失败:' . $e->getMessage()]); + } + } + + public function updateBot(): void + { + $payload = $this->getRequestData(); + $id = (int)($payload['id'] ?? 0); + $name = trim((string)($payload['name'] ?? '')); + $botToken = trim((string)($payload['bot_token'] ?? '')); + $botUsername = $this->normalizeNullableString($payload['bot_username'] ?? null); + $runMode = $this->normalizeRunMode((string)($payload['run_mode'] ?? 'polling')); + $webhookUrl = $this->normalizeNullableString($payload['webhook_url'] ?? null); + $remark = $this->normalizeNullableString($payload['remark'] ?? null); + + if ($id <= 0 || $name === '' || $botToken === '') { + $this->json(['success' => false, 'message' => 'Bot ID、名称和 Token 必填']); + return; + } + + try { + $bot = $this->db->get('bot_instances', ['id'], ['id' => $id]); + if (!$bot) { + $this->json(['success' => false, 'message' => 'Bot 实例不存在']); + return; + } + + $this->db->update('bot_instances', [ + 'name' => $name, + 'bot_token' => $botToken, + 'bot_username' => $botUsername, + 'webhook_url' => $webhookUrl, + 'run_mode' => $runMode, + 'remark' => $remark, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => $id]); + + $this->json(['success' => true, 'message' => 'Bot 实例更新成功', 'data' => ['id' => $id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => 'Bot 实例更新失败:' . $e->getMessage()]); + } + } + + public function toggleBot(): void + { + $this->toggleRecord('bot_instances', 'Bot 参数无效', 'Bot 实例不存在', 'Bot %s已%s'); + } + + public function saveGroup(): void + { + $payload = $this->getRequestData(); + $botId = (int)($payload['bot_id'] ?? 0); + $gameId = (int)($payload['game_id'] ?? 0); + $tgGroupId = trim((string)($payload['tg_group_id'] ?? '')); + $groupName = trim((string)($payload['group_name'] ?? '')); + $groupType = $this->normalizeGroupType((string)($payload['group_type'] ?? 'group')); + $ruleId = (int)($payload['bet_format_rule_id'] ?? 0); + + if ($botId <= 0 || $gameId <= 0 || $tgGroupId === '' || $groupName === '') { + $this->json(['success' => false, 'message' => 'Bot、游戏、群ID、群名称均必填']); + return; + } + + try { + if (!$this->exists('bot_instances', $botId) || !$this->exists('games', $gameId)) { + $this->json(['success' => false, 'message' => 'Bot 或游戏不存在']); + return; + } + + $id = $this->db->insert('bot_groups', [ + 'bot_id' => $botId, + 'tg_group_id' => $tgGroupId, + 'group_name' => $groupName, + 'group_type' => $groupType, + 'game_id' => $gameId, + 'bet_format_rule_id' => $ruleId > 0 ? $ruleId : null, + 'remind_bet_success' => !empty($payload['remind_bet_success']) ? 1 : 0, + 'remind_draw_result' => !empty($payload['remind_draw_result']) ? 1 : 0, + 'remind_close_countdown' => !empty($payload['remind_close_countdown']) ? 1 : 0, + 'countdown_config' => !empty($payload['countdown_config']) ? $this->normalizeCountdownConfig((string)$payload['countdown_config']) : null, + 'animation_enabled' => !empty($payload['animation_enabled']) ? 1 : 0, + 'bet_enabled' => !empty($payload['bet_enabled']) ? 1 : 0, + 'status' => 1, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + $this->json(['success' => true, 'message' => '群配置创建成功', 'data' => ['id' => (int)$id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '群配置创建失败:' . $e->getMessage()]); + } + } + + public function updateGroup(): void + { + $payload = $this->getRequestData(); + $id = (int)($payload['id'] ?? 0); + $botId = (int)($payload['bot_id'] ?? 0); + $gameId = (int)($payload['game_id'] ?? 0); + $tgGroupId = trim((string)($payload['tg_group_id'] ?? '')); + $groupName = trim((string)($payload['group_name'] ?? '')); + $groupType = $this->normalizeGroupType((string)($payload['group_type'] ?? 'group')); + $ruleId = (int)($payload['bet_format_rule_id'] ?? 0); + + if ($id <= 0 || $botId <= 0 || $gameId <= 0 || $tgGroupId === '' || $groupName === '') { + $this->json(['success' => false, 'message' => '群配置参数不完整']); + return; + } + + try { + if (!$this->exists('bot_groups', $id)) { + $this->json(['success' => false, 'message' => '群配置不存在']); + return; + } + if (!$this->exists('bot_instances', $botId) || !$this->exists('games', $gameId)) { + $this->json(['success' => false, 'message' => 'Bot 或游戏不存在']); + return; + } + + $this->db->update('bot_groups', [ + 'bot_id' => $botId, + 'tg_group_id' => $tgGroupId, + 'group_name' => $groupName, + 'group_type' => $groupType, + 'game_id' => $gameId, + 'bet_format_rule_id' => $ruleId > 0 ? $ruleId : null, + 'remind_bet_success' => !empty($payload['remind_bet_success']) ? 1 : 0, + 'remind_draw_result' => !empty($payload['remind_draw_result']) ? 1 : 0, + 'remind_close_countdown' => !empty($payload['remind_close_countdown']) ? 1 : 0, + 'countdown_config' => !empty($payload['countdown_config']) ? $this->normalizeCountdownConfig((string)$payload['countdown_config']) : null, + 'animation_enabled' => !empty($payload['animation_enabled']) ? 1 : 0, + 'bet_enabled' => !empty($payload['bet_enabled']) ? 1 : 0, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => $id]); + + $this->json(['success' => true, 'message' => '群配置更新成功', 'data' => ['id' => $id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '群配置更新失败:' . $e->getMessage()]); + } + } + + public function toggleGroup(): void + { + $this->toggleRecord('bot_groups', '群配置参数无效', '群配置不存在', '群 %s已%s', 'group_name'); + } + + public function saveWallet(): void + { + $payload = $this->getRequestData(); + $groupId = (int)($payload['group_id'] ?? 0); + $platformUserId = (int)($payload['platform_user_id'] ?? 0); + $walletMode = $this->normalizeWalletMode((string)($payload['wallet_mode'] ?? 'master_pool')); + + if ($groupId <= 0 || $platformUserId <= 0) { + $this->json(['success' => false, 'message' => '群配置和总账号必填']); + return; + } + + try { + $user = $this->db->get('users', ['id', 'username'], ['id' => $platformUserId]); + if (!$this->exists('bot_groups', $groupId) || !$user) { + $this->json(['success' => false, 'message' => '群配置或网站账号不存在']); + return; + } + + $exists = $this->db->get('bot_group_wallets', ['id'], ['group_id' => $groupId]); + $data = [ + 'platform_user_id' => $platformUserId, + 'platform_username_snapshot' => $user['username'], + 'wallet_mode' => $walletMode, + 'status' => 1, + 'updated_at' => date('Y-m-d H:i:s'), + ]; + + if ($exists) { + $this->db->update('bot_group_wallets', $data, ['group_id' => $groupId]); + $id = (int)$exists['id']; + $message = '群总账号绑定已更新'; + } else { + $data['group_id'] = $groupId; + $data['created_at'] = date('Y-m-d H:i:s'); + $id = (int)$this->db->insert('bot_group_wallets', $data); + $message = '群总账号绑定成功'; + } + + $this->json(['success' => true, 'message' => $message, 'data' => ['id' => $id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '群总账号绑定失败:' . $e->getMessage()]); + } + } + + public function updateWallet(): void + { + $payload = $this->getRequestData(); + $id = (int)($payload['id'] ?? 0); + $groupId = (int)($payload['group_id'] ?? 0); + $platformUserId = (int)($payload['platform_user_id'] ?? 0); + $walletMode = $this->normalizeWalletMode((string)($payload['wallet_mode'] ?? 'master_pool')); + + if ($id <= 0 || $groupId <= 0 || $platformUserId <= 0) { + $this->json(['success' => false, 'message' => '钱包绑定参数不完整']); + return; + } + + try { + $wallet = $this->db->get('bot_group_wallets', ['id'], ['id' => $id]); + $user = $this->db->get('users', ['id', 'username'], ['id' => $platformUserId]); + if (!$wallet) { + $this->json(['success' => false, 'message' => '钱包绑定不存在']); + return; + } + if (!$this->exists('bot_groups', $groupId) || !$user) { + $this->json(['success' => false, 'message' => '群配置或网站账号不存在']); + return; + } + + $duplicate = $this->db->get('bot_group_wallets', ['id'], [ + 'group_id' => $groupId, + 'id[!]' => $id, + ]); + if ($duplicate) { + $this->json(['success' => false, 'message' => '该群已绑定其他总账号']); + return; + } + + $this->db->update('bot_group_wallets', [ + 'group_id' => $groupId, + 'platform_user_id' => $platformUserId, + 'platform_username_snapshot' => $user['username'], + 'wallet_mode' => $walletMode, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => $id]); + + $this->json(['success' => true, 'message' => '群总账号绑定更新成功', 'data' => ['id' => $id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '群总账号绑定更新失败:' . $e->getMessage()]); + } + } + + public function toggleWallet(): void + { + $this->toggleRecord('bot_group_wallets', '钱包绑定参数无效', '钱包绑定不存在', '群总账号绑定已%s', 'id', false); + } + + public function saveMember(): void + { + $payload = $this->getRequestData(); + $groupId = (int)($payload['group_id'] ?? 0); + $tgUserId = trim((string)($payload['tg_user_id'] ?? '')); + $tgUsername = $this->normalizeNullableString($payload['tg_username'] ?? null); + $tgNickname = $this->normalizeNullableString($payload['tg_nickname'] ?? null); + $platformUserId = (int)($payload['platform_user_id'] ?? 0); + $role = $this->normalizeMemberRole((string)($payload['role'] ?? 'member')); + $betEnabled = !empty($payload['bet_enabled']) ? 1 : 0; + + if ($groupId <= 0 || $tgUserId === '') { + $this->json(['success' => false, 'message' => '群配置与 TG 用户 ID 必填']); + return; + } + + try { + if (!$this->exists('bot_groups', $groupId)) { + $this->json(['success' => false, 'message' => '群配置不存在']); + return; + } + if ($platformUserId > 0 && !$this->exists('users', $platformUserId)) { + $this->json(['success' => false, 'message' => '绑定网站账号不存在']); + return; + } + + $id = (int)$this->db->insert('bot_group_members', [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + 'tg_username' => $tgUsername, + 'tg_nickname' => $tgNickname, + 'platform_user_id' => $platformUserId > 0 ? $platformUserId : null, + 'role' => $role, + 'bet_enabled' => $betEnabled, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + + $this->syncShillRecord($groupId, $tgUserId, $role === 'shill', $this->normalizeNullableString($payload['shill_note'] ?? null)); + $this->json(['success' => true, 'message' => '群成员映射创建成功', 'data' => ['id' => $id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '群成员映射创建失败:' . $e->getMessage()]); + } + } + + public function updateMember(): void + { + $payload = $this->getRequestData(); + $id = (int)($payload['id'] ?? 0); + $groupId = (int)($payload['group_id'] ?? 0); + $tgUserId = trim((string)($payload['tg_user_id'] ?? '')); + $tgUsername = $this->normalizeNullableString($payload['tg_username'] ?? null); + $tgNickname = $this->normalizeNullableString($payload['tg_nickname'] ?? null); + $platformUserId = (int)($payload['platform_user_id'] ?? 0); + $role = $this->normalizeMemberRole((string)($payload['role'] ?? 'member')); + $betEnabled = !empty($payload['bet_enabled']) ? 1 : 0; + + if ($id <= 0 || $groupId <= 0 || $tgUserId === '') { + $this->json(['success' => false, 'message' => '成员参数不完整']); + return; + } + + try { + if (!$this->exists('bot_group_members', $id)) { + $this->json(['success' => false, 'message' => '群成员映射不存在']); + return; + } + if (!$this->exists('bot_groups', $groupId)) { + $this->json(['success' => false, 'message' => '群配置不存在']); + return; + } + if ($platformUserId > 0 && !$this->exists('users', $platformUserId)) { + $this->json(['success' => false, 'message' => '绑定网站账号不存在']); + return; + } + + $duplicate = $this->db->get('bot_group_members', ['id'], [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + 'id[!]' => $id, + ]); + if ($duplicate) { + $this->json(['success' => false, 'message' => '该 TG 用户已存在于当前群']); + return; + } + + $this->db->update('bot_group_members', [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + 'tg_username' => $tgUsername, + 'tg_nickname' => $tgNickname, + 'platform_user_id' => $platformUserId > 0 ? $platformUserId : null, + 'role' => $role, + 'bet_enabled' => $betEnabled, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => $id]); + + $this->syncShillRecord($groupId, $tgUserId, $role === 'shill', $this->normalizeNullableString($payload['shill_note'] ?? null)); + $this->json(['success' => true, 'message' => '群成员映射更新成功', 'data' => ['id' => $id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '群成员映射更新失败:' . $e->getMessage()]); + } + } + + public function toggleMemberBet(): void + { + $payload = $this->getRequestData(); + $id = (int)($payload['id'] ?? 0); + $betEnabled = isset($payload['bet_enabled']) ? (int)$payload['bet_enabled'] : null; + + if ($id <= 0 || !in_array($betEnabled, [0, 1], true)) { + $this->json(['success' => false, 'message' => '成员参数无效']); + return; + } + + try { + $member = $this->db->get('bot_group_members', ['id', 'tg_user_id'], ['id' => $id]); + if (!$member) { + $this->json(['success' => false, 'message' => '群成员映射不存在']); + return; + } + + $this->db->update('bot_group_members', [ + 'bet_enabled' => $betEnabled, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => $id]); + + $this->json([ + 'success' => true, + 'message' => sprintf('成员 %s 下注权限已%s', (string)$member['tg_user_id'], $betEnabled === 1 ? '开启' : '关闭'), + 'data' => ['id' => $id, 'bet_enabled' => $betEnabled], + ]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '成员下注权限更新失败:' . $e->getMessage()]); + } + } + + public function saveShill(): void + { + $payload = $this->getRequestData(); + $groupId = (int)($payload['group_id'] ?? 0); + $tgUserId = trim((string)($payload['tg_user_id'] ?? '')); + $note = $this->normalizeNullableString($payload['note'] ?? null); + + if ($groupId <= 0 || $tgUserId === '') { + $this->json(['success' => false, 'message' => '群配置与 TG 用户 ID 必填']); + return; + } + + try { + if (!$this->exists('bot_groups', $groupId)) { + $this->json(['success' => false, 'message' => '群配置不存在']); + return; + } + + $member = $this->db->get('bot_group_members', ['id'], [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + ]); + if ($member) { + $this->db->update('bot_group_members', [ + 'role' => 'shill', + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => (int)$member['id']]); + } + + $existing = $this->db->get('bot_shills', ['id'], [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + ]); + if ($existing) { + $this->db->update('bot_shills', [ + 'note' => $note, + 'enabled' => 1, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => (int)$existing['id']]); + $id = (int)$existing['id']; + $message = '托号已更新'; + } else { + $id = (int)$this->db->insert('bot_shills', [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + 'note' => $note, + 'enabled' => 1, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + $message = '托号创建成功'; + } + + $this->json(['success' => true, 'message' => $message, 'data' => ['id' => $id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '托号创建失败:' . $e->getMessage()]); + } + } + + public function updateShill(): void + { + $payload = $this->getRequestData(); + $id = (int)($payload['id'] ?? 0); + $groupId = (int)($payload['group_id'] ?? 0); + $tgUserId = trim((string)($payload['tg_user_id'] ?? '')); + $note = $this->normalizeNullableString($payload['note'] ?? null); + + if ($id <= 0 || $groupId <= 0 || $tgUserId === '') { + $this->json(['success' => false, 'message' => '托号参数不完整']); + return; + } + + try { + $shill = $this->db->get('bot_shills', ['id'], ['id' => $id]); + if (!$shill) { + $this->json(['success' => false, 'message' => '托号记录不存在']); + return; + } + if (!$this->exists('bot_groups', $groupId)) { + $this->json(['success' => false, 'message' => '群配置不存在']); + return; + } + + $duplicate = $this->db->get('bot_shills', ['id'], [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + 'id[!]' => $id, + ]); + if ($duplicate) { + $this->json(['success' => false, 'message' => '该托号已存在']); + return; + } + + $this->db->update('bot_shills', [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + 'note' => $note, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => $id]); + + $member = $this->db->get('bot_group_members', ['id'], [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + ]); + if ($member) { + $this->db->update('bot_group_members', [ + 'role' => 'shill', + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => (int)$member['id']]); + } + + $this->json(['success' => true, 'message' => '托号更新成功', 'data' => ['id' => $id]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '托号更新失败:' . $e->getMessage()]); + } + } + + public function toggleShill(): void + { + $payload = $this->getRequestData(); + $id = (int)($payload['id'] ?? 0); + $enabled = isset($payload['enabled']) ? (int)$payload['enabled'] : null; + + if ($id <= 0 || !in_array($enabled, [0, 1], true)) { + $this->json(['success' => false, 'message' => '托号参数无效']); + return; + } + + try { + $shill = $this->db->get('bot_shills', ['id', 'group_id', 'tg_user_id'], ['id' => $id]); + if (!$shill) { + $this->json(['success' => false, 'message' => '托号记录不存在']); + return; + } + + $this->db->update('bot_shills', [ + 'enabled' => $enabled, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => $id]); + + $member = $this->db->get('bot_group_members', ['id'], [ + 'group_id' => (int)$shill['group_id'], + 'tg_user_id' => (string)$shill['tg_user_id'], + ]); + if ($member) { + $this->db->update('bot_group_members', [ + 'role' => $enabled === 1 ? 'shill' : 'member', + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => (int)$member['id']]); + } + + $this->json([ + 'success' => true, + 'message' => sprintf('托号 %s 已%s统计排除', (string)$shill['tg_user_id'], $enabled === 1 ? '加入' : '移出'), + 'data' => ['id' => $id, 'enabled' => $enabled], + ]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '托号状态更新失败:' . $e->getMessage()]); + } + } + + private function getRequestData(): array + { + $data = json_decode(file_get_contents('php://input'), true); + if (is_array($data) && !empty($data)) { + return $data; + } + return $_POST; + } + + private function normalizeCountdownConfig(string $raw): string + { + $items = array_filter(array_map('trim', explode(',', $raw)), static function ($item) { + return $item !== '' && ctype_digit($item); + }); + $numbers = array_map('intval', $items); + rsort($numbers); + return json_encode(array_values(array_unique($numbers)), JSON_UNESCAPED_UNICODE); + } + + private function normalizeNullableString($value): ?string + { + $text = trim((string)$value); + return $text === '' ? null : $text; + } + + private function normalizeRunMode(string $runMode): string + { + return in_array($runMode, ['polling', 'webhook'], true) ? $runMode : 'polling'; + } + + private function normalizeGroupType(string $groupType): string + { + return in_array($groupType, ['group', 'supergroup', 'channel'], true) ? $groupType : 'group'; + } + + private function normalizeWalletMode(string $walletMode): string + { + return in_array($walletMode, ['master_pool', 'per_member'], true) ? $walletMode : 'master_pool'; + } + + private function normalizeMemberRole(string $role): string + { + return in_array($role, ['member', 'admin', 'shill'], true) ? $role : 'member'; + } + + private function exists(string $table, int $id): bool + { + return $id > 0 && $this->db->has($table, ['id' => $id]); + } + + private function syncShillRecord(int $groupId, string $tgUserId, bool $enabled, ?string $note = null): void + { + $existing = $this->db->get('bot_shills', ['id'], [ + 'group_id' => $groupId, + 'tg_user_id' => $tgUserId, + ]); + + if ($enabled) { + $data = [ + 'note' => $note, + 'enabled' => 1, + 'updated_at' => date('Y-m-d H:i:s'), + ]; + if ($existing) { + $this->db->update('bot_shills', $data, ['id' => (int)$existing['id']]); + } else { + $data['group_id'] = $groupId; + $data['tg_user_id'] = $tgUserId; + $data['created_at'] = date('Y-m-d H:i:s'); + $this->db->insert('bot_shills', $data); + } + return; + } + + if ($existing) { + $this->db->update('bot_shills', [ + 'enabled' => 0, + 'note' => $note, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => (int)$existing['id']]); + } + } + + private function toggleRecord(string $table, string $invalidMessage, string $notFoundMessage, string $messageTemplate, string $nameField = 'name', bool $withName = true): void + { + $payload = $this->getRequestData(); + $id = (int)($payload['id'] ?? 0); + $status = isset($payload['status']) ? (int)$payload['status'] : null; + + if ($id <= 0 || !in_array($status, [0, 1], true)) { + $this->json(['success' => false, 'message' => $invalidMessage]); + return; + } + + try { + $row = $this->db->get($table, ['id', $nameField], ['id' => $id]); + if (!$row) { + $this->json(['success' => false, 'message' => $notFoundMessage]); + return; + } + + $this->db->update($table, [ + 'status' => $status, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => $id]); + + $message = $withName + ? sprintf($messageTemplate, (string)($row[$nameField] ?? $id), $status === 1 ? '启用' : '停用') + : sprintf($messageTemplate, $status === 1 ? '启用' : '停用'); + $this->json(['success' => true, 'message' => $message, 'data' => ['id' => $id, 'status' => $status]]); + } catch (\Throwable $e) { + $this->json(['success' => false, 'message' => '状态更新失败:' . $e->getMessage()]); + } + } + + private function json(array $data): void + { + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($data, JSON_UNESCAPED_UNICODE); + exit; + } +} diff --git a/App/Controllers/Admin/FollowPlanController.php b/App/Controllers/Admin/FollowPlanController.php new file mode 100644 index 0000000..c945b00 --- /dev/null +++ b/App/Controllers/Admin/FollowPlanController.php @@ -0,0 +1,53 @@ +db = $db; + } + + public function index() { + $this->checkLogin(); $this->checkAdmin(); + $service = new FollowPlanService($this->db); + $plans = $service->getAllPlans(); + $games = $this->db->select('games', ['id', 'name', 'code'], ['status' => 1]); + $this->render('Admin/follow_plans.php', compact('plans', 'games')); + } + + public function save() { + $this->checkLogin(); $this->checkAdmin(); + header('Content-Type: application/json'); + $data = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $data['created_by'] = $_SESSION['admin_id'] ?? 0; + $service = new FollowPlanService($this->db); + echo json_encode($service->savePlan($data)); + } + + public function delete() { + $this->checkLogin(); $this->checkAdmin(); + header('Content-Type: application/json'); + $data = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $id = intval($data['id'] ?? 0); + if ($id <= 0) { echo json_encode(['success' => false, 'message' => 'ID invalid']); return; } + $service = new FollowPlanService($this->db); + echo json_encode($service->deletePlan($id)); + } + + public function toggle() { + $this->checkLogin(); $this->checkAdmin(); + header('Content-Type: application/json'); + $data = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $id = intval($data['id'] ?? 0); + $plan = $this->db->get('follow_plans', '*', ['id' => $id]); + if (!$plan) { echo json_encode(['success' => false, 'message' => 'Not found']); return; } + $newStatus = $plan['status'] ? 0 : 1; + $this->db->update('follow_plans', ['status' => $newStatus], ['id' => $id]); + echo json_encode(['success' => true, 'status' => $newStatus]); + } +} diff --git a/App/Controllers/Admin/FundRequestController.php b/App/Controllers/Admin/FundRequestController.php new file mode 100644 index 0000000..81cedc0 --- /dev/null +++ b/App/Controllers/Admin/FundRequestController.php @@ -0,0 +1,284 @@ +checkLogin(); + } + + /** + * 充提审核列表页 + */ + public function index() { + $db = new Database(); + try { + // 查询充提申请列表,JOIN users 获取用户名 + $requests = $db->select('fund_requests', [ + '[>]users' => ['user_id' => 'id'] + ], [ + 'fund_requests.id', + 'fund_requests.user_id', + 'users.username', + 'users.usdt_address', + 'fund_requests.type', + 'fund_requests.amount', + 'fund_requests.status', + 'fund_requests.remark', + 'fund_requests.admin_remark', + 'fund_requests.operator_id', + 'fund_requests.created_at', + 'fund_requests.processed_at' + ], [ + 'ORDER' => ['fund_requests.id' => 'DESC'], + 'LIMIT' => 200 + ]); + + if (!is_array($requests)) { + $requests = []; + } + + // 统计各状态数量 + $pendingCount = $db->count('fund_requests', ['status' => 'pending']); + $todayDeposit = $db->count('fund_requests', [ + 'type' => 'deposit', + 'created_at[>=]' => date('Y-m-d 00:00:00') + ]); + $todayWithdraw = $db->count('fund_requests', [ + 'type' => 'withdraw', + 'created_at[>=]' => date('Y-m-d 00:00:00') + ]); + + } catch (\Throwable $e) { + $requests = []; + $pendingCount = 0; + $todayDeposit = 0; + $todayWithdraw = 0; + } + + $this->render('Admin/fund_requests.php', [ + 'requests' => $requests, + 'pendingCount' => $pendingCount, + 'todayDeposit' => $todayDeposit, + 'todayWithdraw' => $todayWithdraw, + 'title' => '充提审核' + ]); + } + + /** + * 审批通过 + */ + public function approve() { + header('Content-Type: application/json'); + + $rawData = file_get_contents('php://input'); + $data = json_decode($rawData, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + echo json_encode(['success' => false, 'message' => '数据格式错误']); + return; + } + + $id = isset($data['id']) ? (int)$data['id'] : 0; + $adminRemark = trim($data['admin_remark'] ?? ''); + + if ($id <= 0) { + echo json_encode(['success' => false, 'message' => '无效的申请ID']); + return; + } + + $db = new Database(); + + try { + // 查询申请记录 + $request = $db->get('fund_requests', '*', ['id' => $id]); + if (!$request) { + echo json_encode(['success' => false, 'message' => '申请记录不存在']); + return; + } + + if ($request['status'] !== 'pending') { + echo json_encode(['success' => false, 'message' => '该申请已处理,状态:' . $request['status']]); + return; + } + + $userId = (int)$request['user_id']; + $amount = floatval($request['amount']); + $type = $request['type']; + + // 获取用户信息 + $user = $db->get('users', ['id', 'balance'], ['id' => $userId]); + if (!$user) { + echo json_encode(['success' => false, 'message' => '用户不存在']); + return; + } + + $balanceBefore = floatval($user['balance']); + $now = date('Y-m-d H:i:s'); + $operatorId = $_SESSION['user_id'] ?? 0; + + // 开启事务 + $db->medoo->pdo->beginTransaction(); + + if ($type === 'deposit') { + // 充值:给用户加款 + $balanceAfter = $balanceBefore + $amount; + + $db->update('users', [ + 'balance' => $balanceAfter, + 'updated_at' => $now + ], ['id' => $userId]); + + $db->insert('transactions', [ + 'user_id' => $userId, + 'type' => 'deposit', + 'amount' => $amount, + 'balance_before' => $balanceBefore, + 'balance_after' => $balanceAfter, + 'description' => '充值申请审核通过 #' . $id, + 'created_at' => $now + ]); + } else { + // 提现:余额已在申请时预扣,确认放款 + $balanceAfter = $balanceBefore; // 余额不变,已预扣 + + $db->insert('transactions', [ + 'user_id' => $userId, + 'type' => 'withdraw', + 'amount' => -$amount, + 'balance_before' => $balanceBefore, + 'balance_after' => $balanceAfter, + 'description' => '提现申请审核通过 #' . $id, + 'created_at' => $now + ]); + } + + // 更新申请状态 + $db->update('fund_requests', [ + 'status' => 'approved', + 'admin_remark' => $adminRemark, + 'operator_id' => $operatorId, + 'processed_at' => $now + ], ['id' => $id]); + + $db->medoo->pdo->commit(); + + echo json_encode(['success' => true, 'message' => '审批通过']); + + } catch (\Exception $e) { + if ($db->medoo->pdo->inTransaction()) { + $db->medoo->pdo->rollBack(); + } + echo json_encode(['success' => false, 'message' => '操作失败:' . $e->getMessage()]); + } + } + + /** + * 拒绝申请 + */ + public function reject() { + header('Content-Type: application/json'); + + $rawData = file_get_contents('php://input'); + $data = json_decode($rawData, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + echo json_encode(['success' => false, 'message' => '数据格式错误']); + return; + } + + $id = isset($data['id']) ? (int)$data['id'] : 0; + $adminRemark = trim($data['admin_remark'] ?? ''); + + if ($id <= 0) { + echo json_encode(['success' => false, 'message' => '无效的申请ID']); + return; + } + + $db = new Database(); + + try { + // 查询申请记录 + $request = $db->get('fund_requests', '*', ['id' => $id]); + if (!$request) { + echo json_encode(['success' => false, 'message' => '申请记录不存在']); + return; + } + + if ($request['status'] !== 'pending') { + echo json_encode(['success' => false, 'message' => '该申请已处理,状态:' . $request['status']]); + return; + } + + $userId = (int)$request['user_id']; + $amount = floatval($request['amount']); + $type = $request['type']; + $now = date('Y-m-d H:i:s'); + $operatorId = $_SESSION['user_id'] ?? 0; + + // 开启事务 + $db->medoo->pdo->beginTransaction(); + + if ($type === 'withdraw') { + // 提现拒绝:退还预扣余额 + $user = $db->get('users', ['id', 'balance'], ['id' => $userId]); + if ($user) { + $balanceBefore = floatval($user['balance']); + $balanceAfter = $balanceBefore + $amount; + + $db->update('users', [ + 'balance' => $balanceAfter, + 'updated_at' => $now + ], ['id' => $userId]); + + $db->insert('transactions', [ + 'user_id' => $userId, + 'type' => 'refund', + 'amount' => $amount, + 'balance_before' => $balanceBefore, + 'balance_after' => $balanceAfter, + 'description' => '提现申请被拒绝,退还预扣金额 #' . $id, + 'created_at' => $now + ]); + } + } + // 充值拒绝:无需退款 + + // 更新申请状态 + $db->update('fund_requests', [ + 'status' => 'rejected', + 'admin_remark' => $adminRemark, + 'operator_id' => $operatorId, + 'processed_at' => $now + ], ['id' => $id]); + + $db->medoo->pdo->commit(); + + echo json_encode(['success' => true, 'message' => '已拒绝']); + + } catch (\Exception $e) { + if ($db->medoo->pdo->inTransaction()) { + $db->medoo->pdo->rollBack(); + } + echo json_encode(['success' => false, 'message' => '操作失败:' . $e->getMessage()]); + } + } + + /** + * 获取待处理数量(轮询接口) + */ + public function pendingCount() { + header('Content-Type: application/json'); + + $db = new Database(); + try { + $count = $db->count('fund_requests', ['status' => 'pending']); + echo json_encode(['count' => (int)$count]); + } catch (\Throwable $e) { + echo json_encode(['count' => 0]); + } + } +} diff --git a/App/Controllers/Admin/PK10PeriodController.php b/App/Controllers/Admin/PK10PeriodController.php index a920965..c7745bb 100644 --- a/App/Controllers/Admin/PK10PeriodController.php +++ b/App/Controllers/Admin/PK10PeriodController.php @@ -61,12 +61,23 @@ class PK10PeriodController extends AdminBaseController { } $periodNumber = PK10Algorithm::generatePeriodNumber($gameId); + $createdAt = date('Y-m-d H:i:s'); $this->db->insert('periods', [ 'game_id' => $gameId, 'period_number' => $periodNumber, 'status' => 'pending', - 'created_at' => date('Y-m-d H:i:s'), + 'start_time' => $createdAt, + 'created_at' => $createdAt, ]); + $periodId = (int)$this->db->id(); + + if ($periodId > 0) { + $this->queueCountdownPushes($gameId, [ + 'id' => $periodId, + 'period_number' => $periodNumber, + 'created_at' => $createdAt, + ]); + } $this->json(['status' => 'success', 'message' => 'New period started', 'period_number' => $periodNumber]); } @@ -83,6 +94,14 @@ class PK10PeriodController extends AdminBaseController { } $this->db->update('periods', ['status' => 'locked'], ['id' => $periodId]); + $this->queueBotPushByGame($period['game_id'], 'countdown', [ + 'event' => 'period_locked', + 'period_id' => (int)$period['id'], + 'period_number' => (string)($period['period_number'] ?? ''), + 'message' => 'Period locked', + ], [ + 'remind_close_countdown' => 1, + ]); $this->json(['status' => 'success', 'message' => 'Period locked']); } @@ -122,7 +141,8 @@ class PK10PeriodController extends AdminBaseController { ]); if (!empty($waterConfig) && !empty($bets)) { - $result = PK10Algorithm::generateControlledResult($bets, $waterConfig); + $targetProfitRate = floatval(\App\Core\SettingsHelper::get('target_profit_rate')); + $result = PK10Algorithm::generateControlledResult($bets, $waterConfig, 100, $targetProfitRate); } else { $result = PK10Algorithm::generateResult(); } @@ -150,6 +170,17 @@ class PK10PeriodController extends AdminBaseController { 'rank_10' => $result[9], 'champion_sum' => $sum, ]); + $this->queueBotPushByGame($period['game_id'], 'draw', [ + 'event' => 'period_drawn', + 'period_id' => (int)$period['id'], + 'period_number' => (string)($period['period_number'] ?? ''), + 'result' => $result, + 'champion_sum' => $sum, + 'manual' => !empty($manual), + ], [ + 'remind_draw_result' => 1, + ], (string)($period['period_number'] ?? '')); + $this->db->medoo->pdo->commit(); } catch (\Throwable $e) { $this->db->medoo->pdo->rollBack(); @@ -180,6 +211,15 @@ class PK10PeriodController extends AdminBaseController { $bets = $this->db->select('bets', '*', ['period_id' => $periodId, 'status' => 'pending']); if (empty($bets)) { $this->db->update('periods', ['status' => 'settled'], ['id' => $periodId]); + $this->queueBotPushByGame($period['game_id'], 'system', [ + 'event' => 'period_settled', + 'period_id' => (int)$period['id'], + 'period_number' => (string)($period['period_number'] ?? ''), + 'wins' => 0, + 'losses' => 0, + 'total_payout' => 0, + 'message' => 'No bets to settle', + ], [], (string)($period['period_number'] ?? '')); $this->json(['status' => 'success', 'message' => 'No bets to settle', 'wins' => 0, 'losses' => 0]); return; } @@ -230,6 +270,14 @@ class PK10PeriodController extends AdminBaseController { $this->settleAgentCommissions($bets, $periodId); $this->db->update('periods', ['status' => 'settled'], ['id' => $periodId]); + $this->queueBotPushByGame($period['game_id'], 'system', [ + 'event' => 'period_settled', + 'period_id' => (int)$period['id'], + 'period_number' => (string)($period['period_number'] ?? ''), + 'wins' => $wins, + 'losses' => $losses, + 'total_payout' => (float)$totalPayout, + ], [], (string)($period['period_number'] ?? '')); $this->db->medoo->pdo->commit(); } catch (\Throwable $e) { $this->db->medoo->pdo->rollBack(); @@ -298,6 +346,90 @@ class PK10PeriodController extends AdminBaseController { return $game ? (int)$game : 0; } + private function queueCountdownPushes(int $gameId, array $period): void { + if ($gameId <= 0 || empty($period['id']) || empty($period['period_number'])) { + return; + } + + $groups = $this->db->select('bot_groups', ['id', 'countdown_config'], [ + 'game_id' => $gameId, + 'status' => 1, + 'remind_close_countdown' => 1, + ]) ?: []; + if (empty($groups)) { + return; + } + + $game = $this->db->get('games', ['period_duration', 'lock_before_end'], ['id' => $gameId]); + $periodDuration = (int)($game['period_duration'] ?? 300); + $lockBeforeEnd = max(0, (int)($game['lock_before_end'] ?? 30)); + $periodCreatedAt = strtotime((string)($period['created_at'] ?? '')); + if ($periodCreatedAt <= 0) { + $periodCreatedAt = time(); + } + $closeAtTs = $periodCreatedAt + max(0, $periodDuration - $lockBeforeEnd); + + $now = date('Y-m-d H:i:s'); + foreach ($groups as $group) { + $decoded = json_decode((string)($group['countdown_config'] ?? ''), true); + if (!is_array($decoded) || empty($decoded)) { + continue; + } + + $secondsList = array_values(array_unique(array_filter(array_map('intval', $decoded), static function (int $seconds) use ($periodDuration, $lockBeforeEnd) { + return $seconds > 0 && $seconds <= max(0, $periodDuration - $lockBeforeEnd); + }))); + rsort($secondsList); + + foreach ($secondsList as $seconds) { + $dispatchAtTs = $closeAtTs - $seconds; + if ($dispatchAtTs <= $periodCreatedAt) { + continue; + } + $payload = [ + 'event' => 'countdown_tick', + 'period_id' => (int)$period['id'], + 'period_number' => (string)$period['period_number'], + 'countdown_seconds' => $seconds, + 'dispatch_at' => date('Y-m-d H:i:s', $dispatchAtTs), + 'message' => sprintf('Bet closes in %d seconds', $seconds), + ]; + + $this->db->insert('bot_push_logs', [ + 'group_id' => (int)$group['id'], + 'period_number' => (string)$period['period_number'], + 'push_type' => 'countdown', + 'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE), + 'status' => 'pending', + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + } + } + + private function queueBotPushByGame(int $gameId, string $pushType, array $payload, array $groupFilters = [], ?string $periodNumber = null): void { + if ($gameId <= 0) { + return; + } + + $where = array_merge(['game_id' => $gameId, 'status' => 1], $groupFilters); + $groups = $this->db->select('bot_groups', ['id'], $where) ?: []; + $now = date('Y-m-d H:i:s'); + + foreach ($groups as $group) { + $this->db->insert('bot_push_logs', [ + 'group_id' => (int)$group['id'], + 'period_number' => $periodNumber !== null ? $periodNumber : ((string)($payload['period_number'] ?? '') ?: null), + 'push_type' => $pushType, + 'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE), + 'status' => 'pending', + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + } + private function json(array $data) { header('Content-Type: application/json'); echo json_encode($data); diff --git a/App/Controllers/Admin/ReportController.php b/App/Controllers/Admin/ReportController.php index 075329a..64d9579 100644 --- a/App/Controllers/Admin/ReportController.php +++ b/App/Controllers/Admin/ReportController.php @@ -12,94 +12,132 @@ class ReportController extends AdminBaseController { $this->checkLogin(); $this->checkAdmin(); $dateFrom = $_GET['from'] ?? date('Y-m-d', strtotime('-7 days')); $dateTo = $_GET['to'] ?? date('Y-m-d'); - $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; - // 总投注(排除虚拟) - $totalBet = $this->db->sum('bets', 'amount', [ - 'is_virtual' => 0, 'created_at[<>]' => $range - ]) ?: 0; + $reportService = new \App\Services\ReportService($this->db); - // 总派奖 - $totalWin = $this->db->sum('bets', 'win_amount', [ - 'is_virtual' => 0, 'status' => 'win', 'created_at[<>]' => $range - ]) ?: 0; + // 总体统计 + $overview = $reportService->getOverviewStats($dateFrom, $dateTo); + extract($overview); // totalBet, totalWin, profit, profitRate, totalDeposit, totalWithdraw, totalCommission, totalRebate, totalUsers, newUsers, activeUsers - // 平台利润 - $profit = $totalBet - $totalWin; + // 日明细统计 + $dailyStats = $reportService->getDailyStats($dateFrom, $dateTo); - // 总充值 - $totalDeposit = $this->db->sum('transactions', 'amount', [ - 'is_virtual' => 0, 'type[~]' => '%deposit%', 'amount[>]' => 0, - 'created_at[<>]' => $range - ]) ?: 0; - - // 总提现 - $totalWithdraw = abs($this->db->sum('transactions', 'amount', [ - 'is_virtual' => 0, 'type[~]' => '%withdraw%', 'amount[<]' => 0, - 'created_at[<>]' => $range - ]) ?: 0); - - // 代理佣金 - $totalCommission = $this->db->sum('agent_commissions', 'commission', [ - 'created_at[<>]' => $range - ]) ?: 0; - - // 反水 - $totalRebate = $this->db->sum('agent_commissions', 'commission', [ - 'type' => 'rebate', 'created_at[<>]' => $range - ]) ?: 0; - - // 用户统计 - $totalUsers = $this->db->count('users', ['is_virtual' => 0]); - $newUsers = $this->db->count('users', [ - 'is_virtual' => 0, 'created_at[<>]' => $range - ]); - - // 每日明细 - $dailyStats = []; - $start = new \DateTime($dateFrom); - $end = new \DateTime($dateTo); - $end->modify('+1 day'); - $interval = new \DateInterval('P1D'); - $period = new \DatePeriod($start, $interval, $end); - foreach ($period as $day) { - $d = $day->format('Y-m-d'); - $dr = [$d . ' 00:00:00', $d . ' 23:59:59']; - $dailyStats[] = [ - 'date' => $d, - 'bets' => $this->db->sum('bets', 'amount', ['is_virtual'=>0,'created_at[<>]'=>$dr]) ?: 0, - 'wins' => $this->db->sum('bets', 'win_amount', ['is_virtual'=>0,'status'=>'win','created_at[<>]'=>$dr]) ?: 0, - 'deposits' => $this->db->sum('transactions', 'amount', ['is_virtual'=>0,'type[~]'=>'%deposit%','amount[>]'=>0,'created_at[<>]'=>$dr]) ?: 0, - 'withdraws' => abs($this->db->sum('transactions', 'amount', ['is_virtual'=>0,'type[~]'=>'%withdraw%','amount[<]'=>0,'created_at[<>]'=>$dr]) ?: 0), - ]; - } + // 用户排行(投注前20) + $topUsers = $reportService->getTopUsers($dateFrom, $dateTo, 20); // 代理报表 - $agentStats = []; - $agents = $this->db->select('agents', '*', ['status' => 1]); - foreach ($agents as $ag) { - $playerIds = $this->db->select('users', 'id', ['agent_id' => $ag['id'], 'is_virtual' => 0]); - $agBet = 0; $agWin = 0; - if (!empty($playerIds)) { - $agBet = $this->db->sum('bets', 'amount', ['user_id' => $playerIds, 'is_virtual'=>0, 'created_at[<>]'=>$range]) ?: 0; - $agWin = $this->db->sum('bets', 'win_amount', ['user_id' => $playerIds, 'is_virtual'=>0, 'status'=>'win', 'created_at[<>]'=>$range]) ?: 0; - } - $agComm = $this->db->sum('agent_commissions', 'commission', ['agent_id'=>$ag['id'],'created_at[<>]'=>$range]) ?: 0; - $agentStats[] = [ - 'agent' => $ag, - 'user' => $this->db->get('users', ['username'], ['id' => $ag['user_id']]), - 'players' => count($playerIds), - 'bets' => $agBet, 'wins' => $agWin, 'commission' => $agComm, - 'profit' => $agBet - $agWin - $agComm, - ]; - } + $agentStats = $reportService->getAgentStats($dateFrom, $dateTo); + + // 用户输赢明细 + $winLossData = $reportService->getUserWinLoss($dateFrom, $dateTo); + $userSummary = $winLossData['summary']; $this->render('Admin/reports.php', compact( - 'dateFrom','dateTo','totalBet','totalWin','profit', + 'dateFrom','dateTo','totalBet','totalWin','profit','profitRate', 'totalDeposit','totalWithdraw','totalCommission','totalRebate', - 'totalUsers','newUsers','dailyStats','agentStats' + 'totalUsers','newUsers','activeUsers','dailyStats','topUsers','agentStats', + 'userSummary' )); } + /** + * CSV 导出 + */ + public function export() { + $this->checkLogin(); $this->checkAdmin(); + $dateFrom = $_GET['from'] ?? date('Y-m-d', strtotime('-7 days')); + $dateTo = $_GET['to'] ?? date('Y-m-d'); + + $reportService = new \App\Services\ReportService($this->db); + + // 通过 Service 获取数据 + $dailyStats = $reportService->getDailyStats($dateFrom, $dateTo); + $topUsers = $reportService->getTopUsers($dateFrom, $dateTo, 0); // 不限条数 + $winLossData = $reportService->getUserWinLoss($dateFrom, $dateTo); + $userSummary = $winLossData['summary']; + $userWinLossRaw = $winLossData['raw']; + + // 输出 CSV + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename="report_' . $dateFrom . '_' . $dateTo . '.csv"'); + // BOM 头(Excel 兼容中文) + echo "\xEF\xBB\xBF"; + $out = fopen('php://output', 'w'); + + // 日明细 + fputcsv($out, ['=== 日明细报表 ===']); + fputcsv($out, ['日期', '投注额', '派彩额', '利润', '投注笔数', '中奖笔数', '胜率']); + foreach ($dailyStats as $day) { + $totalBet = floatval($day['total_bet'] ?? 0); + $totalWin = floatval($day['total_win'] ?? 0); + $betCount = intval($day['bet_count'] ?? 0); + $winCount = intval($day['win_count'] ?? 0); + $winRate = $betCount > 0 ? round($winCount / $betCount * 100, 1) . '%' : '0%'; + fputcsv($out, [ + $day['date'], + number_format($totalBet, 2, '.', ''), + number_format($totalWin, 2, '.', ''), + number_format($totalBet - $totalWin, 2, '.', ''), + $betCount, + $winCount, + $winRate + ]); + } + + fputcsv($out, []); + fputcsv($out, ['=== 用户投注排行 ===']); + fputcsv($out, ['排名', '用户名', '投注额', '派彩额', '平台盈亏', '投注笔数']); + $rank = 1; + foreach ($topUsers as $user) { + fputcsv($out, [ + $rank++, + $user['username'], + number_format(floatval($user['total_bet']), 2, '.', ''), + number_format(floatval($user['total_win']), 2, '.', ''), + number_format(floatval($user['profit']), 2, '.', ''), + intval($user['bet_count']) + ]); + } + + // 用户输赢明细 + fputcsv($out, []); + fputcsv($out, ['=== 用户输赢明细 ===']); + fputcsv($out, ['用户名', '当前余额', '总投注', '总派彩', '平台盈亏(投注-派彩)', '投注笔数', '中奖笔数', '胜率']); + foreach ($userSummary as $ud) { + $tb = $ud['total_bet']; + $tw = $ud['total_win']; + $bc = $ud['bet_count']; + $wc = $ud['win_count']; + $wr = $bc > 0 ? round($wc / $bc * 100, 1) . '%' : '0%'; + fputcsv($out, [ + $ud['username'], + number_format($ud['balance'], 2, '.', ''), + number_format($tb, 2, '.', ''), + number_format($tw, 2, '.', ''), + number_format($tb - $tw, 2, '.', ''), + $bc, $wc, $wr + ]); + } + + // 用户日输赢明细 + fputcsv($out, []); + fputcsv($out, ['=== 用户日输赢明细 ===']); + fputcsv($out, ['用户名', '日期', '投注额', '派彩额', '平台盈亏', '笔数']); + foreach ($userWinLossRaw as $dd) { + $tb = floatval($dd['total_bet']); + $tw = floatval($dd['total_win']); + fputcsv($out, [ + $dd['username'], $dd['date'], + number_format($tb, 2, '.', ''), + number_format($tw, 2, '.', ''), + number_format($tb - $tw, 2, '.', ''), + intval($dd['bet_count']) + ]); + } + + fclose($out); + exit; + } + private function json($data) { header('Content-Type: application/json'); echo json_encode($data); } } diff --git a/App/Controllers/Admin/SettingsController.php b/App/Controllers/Admin/SettingsController.php index 75ef65c..f33fc4f 100755 --- a/App/Controllers/Admin/SettingsController.php +++ b/App/Controllers/Admin/SettingsController.php @@ -107,6 +107,7 @@ class SettingsController extends AdminBaseController { 'site_logo', 'site_favicon', 'site_copyright', 'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_from', 'smtp_from_name', 'smtp_encryption', + 'customer_service_url', ]; foreach ($allowedKeys as $key) { diff --git a/App/Controllers/Admin/WaterController.php b/App/Controllers/Admin/WaterController.php index a1ac6b4..b042c4a 100644 --- a/App/Controllers/Admin/WaterController.php +++ b/App/Controllers/Admin/WaterController.php @@ -13,7 +13,8 @@ class WaterController extends AdminBaseController { $gameId = $this->getGameId(); $configs = $this->db->select('water_control', '*', ['game_id' => $gameId]); $limits = $this->db->select('bet_limits', '*', ['game_id' => $gameId]); - $this->render('Admin/water_control.php', compact('configs', 'limits', 'gameId')); + $targetProfitRate = \App\Core\SettingsHelper::get('target_profit_rate'); + $this->render('Admin/water_control.php', compact('configs', 'limits', 'gameId', 'targetProfitRate')); } public function updateWater() { @@ -61,6 +62,26 @@ class WaterController extends AdminBaseController { $this->json(['status' => 'success']); } + public function updateProfitRate() { + $this->checkLogin(); $this->checkAdmin(); + $data = json_decode(file_get_contents('php://input'), true); + $rate = isset($data['target_profit_rate']) ? floatval($data['target_profit_rate']) : 15; + if ($rate < 0 || $rate > 100) { + $this->json(['status' => 'error', 'message' => '盈利率必须在0-100之间']); + return; + } + + $db = $this->db; + $existing = $db->get('system_settings', 'id', ['setting_key' => 'target_profit_rate']); + if ($existing) { + $db->update('system_settings', ['setting_value' => (string)$rate], ['id' => $existing]); + } else { + $db->insert('system_settings', ['setting_key' => 'target_profit_rate', 'setting_value' => (string)$rate]); + } + \App\Core\SettingsHelper::clearCache(); + $this->json(['status' => 'success']); + } + private function getGameId(): int { if (!empty($_GET['game_id'])) return (int)$_GET['game_id']; return (int)($this->db->get('games', 'id', ['code' => 'pk10']) ?: 0); diff --git a/App/Controllers/Api/BotController.php b/App/Controllers/Api/BotController.php new file mode 100644 index 0000000..28e2b95 --- /dev/null +++ b/App/Controllers/Api/BotController.php @@ -0,0 +1,1351 @@ +getRuntimeSecret(); + + if ($expectedSecret === '' || !hash_equals($expectedSecret, $runtimeSecret)) { + $this->jsonResponse(false, 'Runtime auth failed', [], 401); + } + + try { + $bots = $this->db->select('bot_instances', '*', [ + 'status' => 1, + 'ORDER' => ['id' => 'ASC'], + ]) ?: []; + + $instances = []; + foreach ($bots as $bot) { + $botId = (int)($bot['id'] ?? 0); + if ($botId <= 0 || empty($bot['bot_token']) || empty($bot['bot_key']) || empty($bot['bot_secret'])) { + continue; + } + + $groups = $this->db->select('bot_groups', [ + 'id', + 'tg_group_id', + 'group_name', + 'status', + 'bet_enabled', + ], [ + 'bot_id' => $botId, + 'status' => 1, + 'ORDER' => ['id' => 'ASC'], + ]) ?: []; + + if (empty($groups)) { + continue; + } + + $instances[] = [ + 'id' => $botId, + 'name' => (string)($bot['name'] ?? ''), + 'bot_token' => (string)$bot['bot_token'], + 'bot_username' => (string)($bot['bot_username'] ?? ''), + 'bot_key' => (string)$bot['bot_key'], + 'bot_secret' => (string)$bot['bot_secret'], + 'run_mode' => (string)($bot['run_mode'] ?? 'polling'), + 'webhook_url' => (string)($bot['webhook_url'] ?? ''), + 'groups' => array_map(static function (array $group): array { + return [ + 'id' => (int)($group['id'] ?? 0), + 'tg_group_id' => (string)($group['tg_group_id'] ?? ''), + 'group_name' => (string)($group['group_name'] ?? ''), + 'status' => (int)($group['status'] ?? 0), + 'bet_enabled' => (int)($group['bet_enabled'] ?? 0) === 1, + ]; + }, $groups), + ]; + } + + $this->jsonResponse(true, 'ok', [ + 'instances' => $instances, + 'count' => count($instances), + 'server_time' => date('Y-m-d H:i:s'), + ], 200); + } catch (\Throwable $e) { + $this->jsonResponse(false, 'Failed to load runtime instances', [], 500); + } + } + + private function buildPeriodSnapshot(array $group): array + { + $gameId = (int)$group['game_id']; + $game = $this->db->get('games', ['id', 'type', 'period_duration', 'lock_before_end'], ['id' => $gameId]); + if (!$game) { + throw new \RuntimeException('Game not found'); + } + + $period = $this->db->get('periods', '*', [ + 'game_id' => $gameId, + 'ORDER' => ['id' => 'DESC'] + ]); + + $now = time(); + $countdown = 0; + $lockCountdown = 0; + if ($period && in_array($period['status'], ['pending', 'locked'], true)) { + $start = strtotime($period['start_time'] ?? $period['created_at']); + $countdown = max(0, !empty($period['end_time']) ? strtotime($period['end_time']) - $now : ((int)($game['period_duration'] ?? 300) - ($now - $start))); + $periodDuration = (int)($game['period_duration'] ?? 300); + $lockBefore = (int)($game['lock_before_end'] ?? 30); + $lockCountdown = max(0, ($periodDuration - $lockBefore) - ($now - $start)); + } + + return [ + 'game_id' => $gameId, + 'game' => $game, + 'period' => $period, + 'countdown' => $countdown, + 'lock_countdown' => $lockCountdown, + 'server_time' => date('Y-m-d H:i:s'), + ]; + } + + private function getResultPayload(array $game, ?array $periodRow): ?array + { + if (!$periodRow) { + return null; + } + + $algoClass = GameFactory::getAlgorithm($game['type'] ?? 'pk10'); + $resultTable = $algoClass::getResultTable(); + if ($resultTable) { + $result = $this->db->get($resultTable, '*', ['period_id' => (int)$periodRow['id']]); + if (!$result) { + return null; + } + $result['period_number'] = $periodRow['period_number']; + $result['status'] = $periodRow['status']; + return $result; + } + + $result = $periodRow; + $result['period_number'] = $periodRow['period_number']; + return $result; + } + + private function applyFundChange(array $group, array $wallet, string $requestType): void + { + $idempotencyKey = $this->buildIdempotencyKey($requestType); + $amount = (float)($this->requestJson['amount'] ?? 0); + $reason = trim((string)($this->requestJson['reason'] ?? '')); + $tgUserId = (string)($this->requestJson['tg_user_id'] ?? ''); + $operator = trim((string)($this->requestJson['operator'] ?? 'bot')); + $platformUserId = (int)($this->requestJson['platform_user_id'] ?? $wallet['platform_user_id']); + + if ($amount <= 0) { + $this->auditAndRespond(false, 'Amount must be greater than 0', [], 422, (int)$group['id'], $idempotencyKey); + } + + $existing = $this->db->get('bot_fund_requests', '*', ['idempotency_key' => $idempotencyKey]); + if ($existing) { + $this->auditAndRespond(true, 'Duplicate request ignored', [ + 'fund_request_id' => (int)$existing['id'], + 'transaction_id' => (int)($existing['transaction_id'] ?? 0), + 'status' => $existing['status'], + 'duplicate' => true, + ], 200, (int)$group['id'], $idempotencyKey); + } + + try { + $this->db->medoo->pdo->beginTransaction(); + + $stmt = $this->db->medoo->pdo->prepare('SELECT balance, is_virtual FROM users WHERE id = :id FOR UPDATE'); + $stmt->execute([':id' => $platformUserId]); + $user = $stmt->fetch(\PDO::FETCH_ASSOC); + if (!$user) { + throw new \RuntimeException('Platform wallet user not found'); + } + + $balanceBefore = (float)$user['balance']; + $delta = $requestType === 'credit' ? $amount : -$amount; + if ($requestType === 'debit' && $balanceBefore < $amount) { + $this->db->medoo->pdo->rollBack(); + $this->auditAndRespond(false, 'Insufficient balance', [], 422, (int)$group['id'], $idempotencyKey); + } + + $balanceAfter = $balanceBefore + $delta; + $this->db->update('users', [ + 'balance' => $balanceAfter, + 'updated_at' => date('Y-m-d H:i:s') + ], ['id' => $platformUserId]); + + $transactionId = $this->db->insert('transactions', [ + 'user_id' => $platformUserId, + 'type' => $requestType, + 'amount' => $delta, + 'balance_before' => $balanceBefore, + 'balance_after' => $balanceAfter, + 'description' => $reason !== '' ? $reason : ('TG Bot ' . $requestType), + 'is_virtual' => (int)($user['is_virtual'] ?? 0), + 'source' => 'bot', + 'source_ref' => $idempotencyKey, + 'operator_type' => 'system', + 'created_at' => date('Y-m-d H:i:s') + ]); + + $fundRequestId = $this->db->insert('bot_fund_requests', [ + 'group_id' => (int)$group['id'], + 'tg_user_id' => $tgUserId !== '' ? $tgUserId : null, + 'platform_user_id' => $platformUserId, + 'request_type' => $requestType, + 'amount' => $amount, + 'reason' => $reason !== '' ? $reason : ('TG Bot ' . $requestType), + 'idempotency_key' => $idempotencyKey, + 'transaction_id' => $transactionId, + 'status' => 'approved', + 'operator_type' => 'system', + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s') + ]); + + $this->db->insert('bot_push_logs', [ + 'group_id' => (int)$group['id'], + 'push_type' => $requestType, + 'payload_json' => json_encode([ + 'operator' => $operator, + 'platform_user_id' => $platformUserId, + 'amount' => $amount, + 'balance_after' => $balanceAfter, + ], JSON_UNESCAPED_UNICODE), + 'status' => 'success', + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s') + ]); + + $this->db->medoo->pdo->commit(); + + $this->auditAndRespond(true, ucfirst($requestType) . ' success', [ + 'fund_request_id' => (int)$fundRequestId, + 'transaction_id' => (int)$transactionId, + 'platform_user_id' => $platformUserId, + 'balance_before' => $balanceBefore, + 'balance_after' => $balanceAfter, + 'amount' => $amount, + ], 200, (int)$group['id'], $idempotencyKey); + } catch (\Throwable $e) { + if ($this->db->medoo && $this->db->medoo->pdo->inTransaction()) { + $this->db->medoo->pdo->rollBack(); + } + + try { + $this->db->insert('bot_fund_requests', [ + 'group_id' => (int)$group['id'], + 'tg_user_id' => $tgUserId !== '' ? $tgUserId : null, + 'platform_user_id' => $platformUserId, + 'request_type' => $requestType, + 'amount' => $amount, + 'reason' => $reason !== '' ? $reason : ('TG Bot ' . $requestType), + 'idempotency_key' => $idempotencyKey, + 'status' => 'failed', + 'operator_type' => 'system', + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s') + ]); + } catch (\Throwable $ignore) { + } + + $this->auditAndRespond(false, 'System error', [], 500, (int)$group['id'], $idempotencyKey); + } + } + + public function ping(): void + { + $this->authenticateBotRequest(); + + $this->auditAndRespond(true, 'pong', [ + 'bot_id' => (int)$this->botInstance['id'], + 'bot_name' => $this->botInstance['name'], + 'server_time' => date('Y-m-d H:i:s'), + ], 200, null, null); + } + + public function getGroupConfig(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + $wallet = $this->getGroupWallet((int)$group['id']); + + $rule = null; + if (!empty($group['bet_format_rule_id'])) { + $rule = $this->db->get('bot_bet_format_rules', '*', ['id' => (int)$group['bet_format_rule_id']]); + } + + $members = $this->db->count('bot_group_members', ['group_id' => (int)$group['id']]); + + $this->auditAndRespond(true, 'ok', [ + 'group_id' => (int)$group['id'], + 'tg_group_id' => $group['tg_group_id'], + 'group_name' => $group['group_name'], + 'game_id' => (int)$group['game_id'], + 'bet_enabled' => (int)$group['bet_enabled'] === 1, + 'wallet_mode' => $wallet['wallet_mode'], + 'master_platform_user_id' => (int)$wallet['platform_user_id'], + 'reminders' => [ + 'bet_success' => (int)$group['remind_bet_success'] === 1, + 'draw_result' => (int)$group['remind_draw_result'] === 1, + 'close_countdown' => (int)$group['remind_close_countdown'] === 1, + ], + 'countdown_config' => $group['countdown_config'] ? json_decode($group['countdown_config'], true) : [], + 'animation_enabled' => (int)$group['animation_enabled'] === 1, + 'member_count' => (int)$members, + 'bet_format_rule' => $rule ? [ + 'id' => (int)$rule['id'], + 'rule_code' => $rule['rule_code'], + 'name' => $rule['name'], + 'parser_type' => $rule['parser_type'], + 'rule_config' => $rule['rule_config'] ? json_decode($rule['rule_config'], true) : null, + 'example_text' => $rule['example_text'], + ] : null, + ], 200, (int)$group['id']); + } + + private function updateGroupSwitches(array $group, array $allowedFields): void + { + $this->authenticateBotRequest(); + $payload = $this->requestJson; + $actorTgUserId = trim((string)($payload['actor_tg_user_id'] ?? '')); + $field = trim((string)($payload['field'] ?? '')); + $enabledRaw = $payload['enabled'] ?? null; + $idempotencyKey = $this->buildIdempotencyKey('group-action'); + + if ($actorTgUserId === '' || $field === '' || !array_key_exists($field, $allowedFields) || !in_array($enabledRaw, [0, 1, '0', '1', true, false], true)) { + $this->auditAndRespond(false, 'Invalid group action payload', [], 422, (int)$group['id'], $idempotencyKey); + } + + $member = $this->db->get('bot_group_members', ['id', 'role'], [ + 'group_id' => (int)$group['id'], + 'tg_user_id' => $actorTgUserId, + ]); + $isConfiguredAdmin = $member && in_array((string)($member['role'] ?? ''), ['admin', 'owner'], true); + $isRuntimeAdmin = !empty($payload['is_runtime_admin']); + if (!$isConfiguredAdmin && !$isRuntimeAdmin) { + $this->auditAndRespond(false, 'Permission denied', [], 403, (int)$group['id'], $idempotencyKey); + } + + $enabled = in_array($enabledRaw, [1, '1', true], true) ? 1 : 0; + $dbField = $allowedFields[$field]; + + try { + $this->db->update('bot_groups', [ + $dbField => $enabled, + 'updated_at' => date('Y-m-d H:i:s'), + ], ['id' => (int)$group['id']]); + + $group[$dbField] = $enabled; + $this->auditAndRespond(true, 'Group action updated', [ + 'field' => $field, + 'db_field' => $dbField, + 'enabled' => $enabled === 1, + 'group_id' => (int)$group['id'], + 'tg_group_id' => (string)$group['tg_group_id'], + ], 200, (int)$group['id'], $idempotencyKey); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to update group action', [], 500, (int)$group['id'], $idempotencyKey); + } + } + + public function getDrawLatest(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + try { + $snapshot = $this->buildPeriodSnapshot($group); + $game = $snapshot['game']; + $lastDrawn = $this->db->get('periods', '*', [ + 'game_id' => (int)$snapshot['game_id'], + 'status' => ['drawn', 'settled'], + 'ORDER' => ['id' => 'DESC'] + ]); + $currentLocked = null; + if (!empty($snapshot['period']) && ($snapshot['period']['status'] ?? '') === 'locked' && !empty($snapshot['period']['result'])) { + $currentLocked = $this->getResultPayload($game, $snapshot['period']); + } + + $latest = $this->getResultPayload($game, $lastDrawn); + $this->auditAndRespond(true, 'ok', [ + 'latest_result' => $latest, + 'current_locked_result' => $currentLocked, + 'server_time' => $snapshot['server_time'], + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load latest draw', [], 500, (int)$group['id']); + } + } + + public function toggleGroupSetting(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + $this->updateGroupSwitches($group, [ + 'bet_enabled' => 'bet_enabled', + 'bet_success' => 'remind_bet_success', + 'draw_result' => 'remind_draw_result', + 'close_countdown' => 'remind_close_countdown', + 'animation' => 'animation_enabled', + ]); + } + + public function getOdds(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + try { + $gameId = (int)$group['game_id']; + $game = $this->db->get('games', ['id', 'name', 'type'], ['id' => $gameId]); + if (!$game) { + $this->auditAndRespond(false, 'Game not found', [], 404, (int)$group['id']); + } + + $oddsRows = $this->db->select('game_odds', '*', ['game_id' => $gameId]); + $grouped = []; + foreach ($oddsRows as $row) { + $type = (string)($row['type'] ?? ''); + if (!isset($grouped[$type])) { + $grouped[$type] = []; + } + $grouped[$type][] = [ + 'target' => (string)($row['target'] ?? ''), + 'odds' => (float)($row['odds'] ?? 0), + ]; + } + + $this->auditAndRespond(true, 'ok', [ + 'game_id' => $gameId, + 'game_name' => $game['name'], + 'game_type' => $game['type'], + 'odds' => $grouped, + 'total' => count($oddsRows), + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load odds', [], 500, (int)$group['id']); + } + } + + public function getBetFormatRules(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + try { + $rule = null; + if (!empty($group['bet_format_rule_id'])) { + $rule = $this->db->get('bot_bet_format_rules', '*', [ + 'id' => (int)$group['bet_format_rule_id'], + 'status' => 1, + ]); + } + + if (!$rule) { + $rules = $this->db->select('bot_bet_format_rules', '*', [ + 'game_id' => (int)$group['game_id'], + 'status' => 1, + 'ORDER' => ['id' => 'ASC'], + 'LIMIT' => 10, + ]); + + $this->auditAndRespond(true, 'ok', [ + 'active_rule' => null, + 'available_rules' => array_map(function ($r) { + return [ + 'id' => (int)$r['id'], + 'rule_code' => $r['rule_code'], + 'name' => $r['name'], + 'parser_type' => $r['parser_type'], + 'rule_config' => $r['rule_config'] ? json_decode($r['rule_config'], true) : null, + 'example_text' => $r['example_text'], + ]; + }, $rules ?: []), + ], 200, (int)$group['id']); + return; + } + + $this->auditAndRespond(true, 'ok', [ + 'active_rule' => [ + 'id' => (int)$rule['id'], + 'rule_code' => $rule['rule_code'], + 'name' => $rule['name'], + 'parser_type' => $rule['parser_type'], + 'rule_config' => $rule['rule_config'] ? json_decode($rule['rule_config'], true) : null, + 'example_text' => $rule['example_text'], + ], + 'available_rules' => [], + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load bet format rules', [], 500, (int)$group['id']); + } + } + + public function getHistory(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + try { + $snapshot = $this->buildPeriodSnapshot($group); + $game = $snapshot['game']; + $limit = (int)($_GET['limit'] ?? 10); + if ($limit <= 0) { + $limit = 10; + } + $limit = min($limit, 50); + + $periods = $this->db->select('periods', '*', [ + 'game_id' => (int)$snapshot['game_id'], + 'status' => ['drawn', 'settled'], + 'ORDER' => ['id' => 'DESC'], + 'LIMIT' => $limit, + ]); + + $history = []; + foreach ($periods as $period) { + $payload = $this->getResultPayload($game, $period); + if ($payload) { + $history[] = $payload; + } + } + + $this->auditAndRespond(true, 'ok', [ + 'items' => $history, + 'count' => count($history), + 'server_time' => $snapshot['server_time'], + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load history', [], 500, (int)$group['id']); + } + } + + public function touchMember(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + $tgUserId = trim((string)($this->requestJson['tg_user_id'] ?? '')); + $tgUsername = trim((string)($this->requestJson['tg_username'] ?? '')); + $tgNickname = trim((string)($this->requestJson['tg_nickname'] ?? '')); + + if ($tgUserId === '') { + $this->auditAndRespond(false, 'Missing tg_user_id', [], 422, (int)$group['id']); + } + + try { + $existing = $this->db->get('bot_group_members', ['id', 'tg_username', 'tg_nickname'], [ + 'group_id' => (int)$group['id'], + 'tg_user_id' => $tgUserId, + ]); + + $now = date('Y-m-d H:i:s'); + if ($existing) { + $update = ['last_seen_at' => $now, 'updated_at' => $now]; + if ($tgUsername !== '' && $tgUsername !== ($existing['tg_username'] ?? '')) { + $update['tg_username'] = $tgUsername; + } + if ($tgNickname !== '' && $tgNickname !== ($existing['tg_nickname'] ?? '')) { + $update['tg_nickname'] = $tgNickname; + } + $this->db->update('bot_group_members', $update, ['id' => (int)$existing['id']]); + $this->auditAndRespond(true, 'ok', ['action' => 'updated', 'member_id' => (int)$existing['id']], 200, (int)$group['id']); + } + + $this->db->insert('bot_group_members', [ + 'group_id' => (int)$group['id'], + 'tg_user_id' => $tgUserId, + 'tg_username' => $tgUsername ?: null, + 'tg_nickname' => $tgNickname ?: null, + 'role' => 'member', + 'bet_enabled' => 1, + 'last_seen_at' => $now, + 'created_at' => $now, + 'updated_at' => $now, + ]); + $memberId = (int)$this->db->id(); + $this->auditAndRespond(true, 'ok', ['action' => 'created', 'member_id' => $memberId], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(true, 'ok', ['action' => 'skipped'], 200, (int)$group['id']); + } + } + + public function bindMember(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + $tgUserId = trim((string)($this->requestJson['tg_user_id'] ?? '')); + $platformUsername = trim((string)($this->requestJson['platform_username'] ?? '')); + + if ($tgUserId === '' || $platformUsername === '') { + $this->auditAndRespond(false, 'Missing tg_user_id or platform_username', [], 422, (int)$group['id']); + } + + try { + $user = $this->db->get('users', ['id', 'username', 'balance', 'status'], [ + 'username' => $platformUsername, + ]); + if (!$user) { + $this->auditAndRespond(false, 'Platform user not found', [], 404, (int)$group['id']); + } + + $existing = $this->db->get('bot_group_members', ['id'], [ + 'group_id' => (int)$group['id'], + 'tg_user_id' => $tgUserId, + ]); + + $now = date('Y-m-d H:i:s'); + if ($existing) { + $this->db->update('bot_group_members', [ + 'platform_user_id' => (int)$user['id'], + 'updated_at' => $now, + ], ['id' => (int)$existing['id']]); + } else { + $this->db->insert('bot_group_members', [ + 'group_id' => (int)$group['id'], + 'tg_user_id' => $tgUserId, + 'tg_username' => trim((string)($this->requestJson['tg_username'] ?? '')) ?: null, + 'tg_nickname' => trim((string)($this->requestJson['tg_nickname'] ?? '')) ?: null, + 'platform_user_id' => (int)$user['id'], + 'role' => 'member', + 'bet_enabled' => 1, + 'last_seen_at' => $now, + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + + $this->auditAndRespond(true, 'Bind success', [ + 'platform_user_id' => (int)$user['id'], + 'platform_username' => $user['username'], + 'platform_balance' => (float)$user['balance'], + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Bind failed', [], 500, (int)$group['id']); + } + } + + public function getGroupMembers(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + try { + $members = $this->db->select('bot_group_members', [ + '[>]users' => ['platform_user_id' => 'id'], + ], [ + 'bot_group_members.id', + 'bot_group_members.tg_user_id', + 'bot_group_members.tg_username', + 'bot_group_members.tg_nickname', + 'bot_group_members.platform_user_id', + 'bot_group_members.role', + 'bot_group_members.bet_enabled', + 'bot_group_members.last_seen_at', + 'bot_group_members.created_at', + 'bot_group_members.updated_at', + 'users.username(platform_username)', + 'users.balance(platform_balance)', + 'users.status(platform_status)', + ], [ + 'bot_group_members.group_id' => (int)$group['id'], + 'ORDER' => ['bot_group_members.id' => 'ASC'], + ]); + + $shillUserIds = $this->db->select('bot_shills', ['tg_user_id'], [ + 'group_id' => (int)$group['id'], + 'enabled' => 1, + ]); + $shillMap = array_fill_keys($shillUserIds ?: [], true); + + $items = []; + foreach ($members as $member) { + $tgUserId = (string)$member['tg_user_id']; + $items[] = [ + 'id' => (int)$member['id'], + 'tg_user_id' => $tgUserId, + 'tg_username' => $member['tg_username'], + 'tg_nickname' => $member['tg_nickname'], + 'platform_user_id' => $member['platform_user_id'] !== null ? (int)$member['platform_user_id'] : null, + 'platform_username' => $member['platform_username'] ?? null, + 'platform_balance' => $member['platform_balance'] !== null ? (float)$member['platform_balance'] : null, + 'platform_status' => $member['platform_status'] !== null ? (int)$member['platform_status'] : null, + 'role' => $member['role'], + 'bet_enabled' => (int)$member['bet_enabled'] === 1, + 'is_shill' => isset($shillMap[$tgUserId]) || $member['role'] === 'shill', + 'last_seen_at' => $member['last_seen_at'], + 'created_at' => $member['created_at'], + 'updated_at' => $member['updated_at'], + ]; + } + + $this->auditAndRespond(true, 'ok', [ + 'group_id' => (int)$group['id'], + 'items' => $items, + 'count' => count($items), + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load group members', [], 500, (int)$group['id']); + } + } + + public function getMemberStats(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + try { + $from = trim((string)($_GET['date'] ?? date('Y-m-d'))); + $startAt = $from . ' 00:00:00'; + $endAt = $from . ' 23:59:59'; + + $orders = $this->db->select('bot_bet_orders', [ + 'tg_user_id', + 'tg_username', + 'bet_amount_total', + 'accepted_bet_count', + 'is_shill', + ], [ + 'group_id' => (int)$group['id'], + 'sync_status' => 'success', + 'created_at[<>]' => [$startAt, $endAt], + 'ORDER' => ['id' => 'ASC'], + ]); + + $memberMap = []; + foreach ($orders as $order) { + $tgUserId = (string)($order['tg_user_id'] ?? ''); + if ($tgUserId === '') { + continue; + } + + if (!isset($memberMap[$tgUserId])) { + $memberMap[$tgUserId] = [ + 'tg_user_id' => $tgUserId, + 'tg_username' => (string)($order['tg_username'] ?? ''), + 'bet_order_count' => 0, + 'bet_count' => 0, + 'total_bet' => 0.0, + 'is_shill' => (int)($order['is_shill'] ?? 0) === 1, + ]; + } + + $memberMap[$tgUserId]['bet_order_count']++; + $memberMap[$tgUserId]['bet_count'] += (int)($order['accepted_bet_count'] ?? 0); + $memberMap[$tgUserId]['total_bet'] += (float)($order['bet_amount_total'] ?? 0); + if ($memberMap[$tgUserId]['tg_username'] === '' && !empty($order['tg_username'])) { + $memberMap[$tgUserId]['tg_username'] = (string)$order['tg_username']; + } + if ((int)($order['is_shill'] ?? 0) === 1) { + $memberMap[$tgUserId]['is_shill'] = true; + } + } + + $members = $this->db->select('bot_group_members', [ + 'tg_user_id', + 'tg_username', + 'tg_nickname', + 'role', + 'bet_enabled', + 'last_seen_at', + ], [ + 'group_id' => (int)$group['id'], + ]); + + foreach ($members as $member) { + $tgUserId = (string)$member['tg_user_id']; + if (!isset($memberMap[$tgUserId])) { + $memberMap[$tgUserId] = [ + 'tg_user_id' => $tgUserId, + 'tg_username' => (string)($member['tg_username'] ?? ''), + 'bet_order_count' => 0, + 'bet_count' => 0, + 'total_bet' => 0.0, + 'is_shill' => $member['role'] === 'shill', + ]; + } + + $memberMap[$tgUserId]['tg_nickname'] = $member['tg_nickname']; + $memberMap[$tgUserId]['role'] = $member['role']; + $memberMap[$tgUserId]['bet_enabled'] = (int)$member['bet_enabled'] === 1; + $memberMap[$tgUserId]['last_seen_at'] = $member['last_seen_at']; + if ($memberMap[$tgUserId]['tg_username'] === '' && !empty($member['tg_username'])) { + $memberMap[$tgUserId]['tg_username'] = (string)$member['tg_username']; + } + if ($member['role'] === 'shill') { + $memberMap[$tgUserId]['is_shill'] = true; + } + } + + $items = array_values($memberMap); + usort($items, function (array $left, array $right): int { + $betCompare = $right['total_bet'] <=> $left['total_bet']; + if ($betCompare !== 0) { + return $betCompare; + } + return $right['bet_order_count'] <=> $left['bet_order_count']; + }); + + $this->auditAndRespond(true, 'ok', [ + 'date' => $from, + 'group_id' => (int)$group['id'], + 'items' => $items, + 'count' => count($items), + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load member stats', [], 500, (int)$group['id']); + } + } + + public function getDailyStats(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + try { + $wallet = $this->getGroupWallet((int)$group['id']); + $platformUserId = (int)$wallet['platform_user_id']; + $from = trim((string)($_GET['date'] ?? date('Y-m-d'))); + $startAt = $from . ' 00:00:00'; + $endAt = $from . ' 23:59:59'; + + $betWhere = [ + 'user_id' => $platformUserId, + 'source' => 'bot', + 'created_at[<>]' => [$startAt, $endAt], + ]; + $txWhere = [ + 'user_id' => $platformUserId, + 'source' => 'bot', + 'created_at[<>]' => [$startAt, $endAt], + ]; + + $totalBet = (float)($this->db->sum('bets', 'amount', $betWhere) ?: 0); + $totalWin = (float)($this->db->sum('bets', 'win_amount', array_merge($betWhere, ['status' => 'win'])) ?: 0); + $betCount = (int)($this->db->count('bets', $betWhere) ?: 0); + $deposit = (float)($this->db->sum('transactions', 'amount', array_merge($txWhere, ['type' => 'credit', 'amount[>]' => 0])) ?: 0); + $withdraw = abs((float)($this->db->sum('transactions', 'amount', array_merge($txWhere, ['type' => 'debit', 'amount[<]' => 0])) ?: 0)); + $orderCount = (int)($this->db->count('bot_bet_orders', [ + 'group_id' => (int)$group['id'], + 'created_at[<>]' => [$startAt, $endAt], + 'sync_status' => 'success', + ]) ?: 0); + $shillOrderCount = (int)($this->db->count('bot_bet_orders', [ + 'group_id' => (int)$group['id'], + 'created_at[<>]' => [$startAt, $endAt], + 'sync_status' => 'success', + 'is_shill' => 1, + ]) ?: 0); + + $this->auditAndRespond(true, 'ok', [ + 'date' => $from, + 'group_id' => (int)$group['id'], + 'platform_user_id' => $platformUserId, + 'bet_order_count' => $orderCount, + 'shill_order_count' => $shillOrderCount, + 'effective_order_count' => max(0, $orderCount - $shillOrderCount), + 'bet_count' => $betCount, + 'total_bet' => $totalBet, + 'total_win' => $totalWin, + 'profit_loss' => $totalWin - $totalBet, + 'credit_total' => $deposit, + 'debit_total' => $withdraw, + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load daily stats', [], 500, (int)$group['id']); + } + } + + public function getCurrentPeriod(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + try { + $snapshot = $this->buildPeriodSnapshot($group); + $period = $snapshot['period']; + $this->auditAndRespond(true, 'ok', [ + 'id' => (int)($period['id'] ?? 0), + 'game_id' => (int)$snapshot['game_id'], + 'period_number' => $period['period_number'] ?? '', + 'status' => $period['status'] ?? 'none', + 'remaining_seconds' => (int)$snapshot['countdown'], + 'lock_countdown' => (int)$snapshot['lock_countdown'], + 'auto_generated' => (int)($period['auto_generated'] ?? 0), + 'server_time' => $snapshot['server_time'], + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load current period', [], 500, (int)$group['id']); + } + } + + public function credit(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + $wallet = $this->getGroupWallet((int)$group['id']); + $this->applyFundChange($group, $wallet, 'credit'); + } + + public function debit(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + $wallet = $this->getGroupWallet((int)$group['id']); + $this->applyFundChange($group, $wallet, 'debit'); + } + + public function placeGroupBet(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + $wallet = $this->getGroupWallet((int)$group['id']); + + if ((int)$group['bet_enabled'] !== 1) { + $this->auditAndRespond(false, 'Bet sync disabled for this group', [], 403, (int)$group['id']); + } + + $gameId = (int)($this->requestJson['game_id'] ?? $group['game_id'] ?? 0); + $periodNumber = trim((string)($this->requestJson['period_number'] ?? '')); + $bets = $this->requestJson['bets'] ?? []; + $tgUser = $this->requestJson['tg_user'] ?? []; + $rawText = trim((string)($this->requestJson['bet_context']['raw_text'] ?? $this->requestJson['raw_text'] ?? '')); + $idempotencyKey = $this->buildIdempotencyKey('tg'); + + if ($gameId <= 0 || $periodNumber === '' || !is_array($bets) || empty($bets)) { + $this->auditAndRespond(false, 'Missing required bet fields', [], 422, (int)$group['id'], $idempotencyKey); + } + + $exists = $this->db->get('bot_bet_orders', '*', ['idempotency_key' => $idempotencyKey]); + if ($exists) { + $data = [ + 'platform_order_ref' => $exists['platform_order_ref'], + 'sync_status' => $exists['sync_status'], + 'bet_amount_total' => (float)$exists['bet_amount_total'], + 'accepted_bet_count' => (int)$exists['accepted_bet_count'], + 'duplicate' => true, + ]; + $this->auditAndRespond(true, 'Duplicate request ignored', $data, 200, (int)$group['id'], $idempotencyKey); + } + + $period = $this->db->get('periods', '*', [ + 'period_number' => $periodNumber, + 'game_id' => $gameId, + ]); + if (!$period || $period['status'] !== 'pending') { + $this->auditAndRespond(false, 'Period is not open for betting', [], 422, (int)$group['id'], $idempotencyKey); + } + + $oddsRows = $this->db->select('game_odds', '*', ['game_id' => $gameId]); + $oddsMap = []; + foreach ($oddsRows as $row) { + $oddsMap[$row['type'] . '_' . $row['target']] = (float)$row['odds']; + } + + $limitsRaw = $this->db->select('bet_limits', '*', ['game_id' => $gameId]); + $limits = []; + foreach ($limitsRaw as $row) { + $limits[$row['bet_type']] = $row; + } + + $totalAmount = 0.0; + $validBets = []; + foreach ($bets as $bet) { + $type = trim((string)($bet['type'] ?? '')); + $target = trim((string)($bet['target'] ?? $bet['value'] ?? '')); + $amount = (float)($bet['amount'] ?? 0); + if ($type === '' || $target === '' || $amount <= 0) { + continue; + } + + $key = $type . '_' . $target; + $odds = $oddsMap[$key] ?? null; + if ($odds === null) { + $this->auditAndRespond(false, 'Invalid bet: ' . $key, [], 422, (int)$group['id'], $idempotencyKey); + } + + if (isset($limits[$type])) { + if ($amount < (float)$limits[$type]['min_amount']) { + $this->auditAndRespond(false, 'Min bet for ' . $type . ': ' . $limits[$type]['min_amount'], [], 422, (int)$group['id'], $idempotencyKey); + } + if ($amount > (float)$limits[$type]['max_amount']) { + $this->auditAndRespond(false, 'Max bet for ' . $type . ': ' . $limits[$type]['max_amount'], [], 422, (int)$group['id'], $idempotencyKey); + } + } + + $totalAmount += $amount; + $validBets[] = [ + 'type' => $type, + 'target' => $target, + 'amount' => $amount, + 'odds' => $odds, + ]; + } + + if (empty($validBets)) { + $this->auditAndRespond(false, 'No valid bets', [], 422, (int)$group['id'], $idempotencyKey); + } + + $platformUserId = (int)$wallet['platform_user_id']; + $platformOrderRef = 'BOT' . date('YmdHis') . substr(hash('sha256', $idempotencyKey), 0, 10); + $isShill = $this->db->has('bot_shills', [ + 'group_id' => (int)$group['id'], + 'tg_user_id' => (string)($tgUser['id'] ?? ''), + 'enabled' => 1, + ]) ? 1 : 0; + + try { + $this->db->medoo->pdo->beginTransaction(); + + $stmt = $this->db->medoo->pdo->prepare('SELECT balance, is_virtual, agent_id FROM users WHERE id = :id FOR UPDATE'); + $stmt->execute([':id' => $platformUserId]); + $user = $stmt->fetch(\PDO::FETCH_ASSOC); + if (!$user) { + throw new \RuntimeException('Platform wallet user not found'); + } + + $currentBalance = (float)$user['balance']; + if ($currentBalance < $totalAmount) { + $this->db->medoo->pdo->rollBack(); + $this->auditAndRespond(false, 'Insufficient balance', [], 422, (int)$group['id'], $idempotencyKey); + } + + $newBalance = $currentBalance - $totalAmount; + $this->db->update('users', ['balance' => $newBalance], ['id' => $platformUserId]); + + $this->db->insert('transactions', [ + 'user_id' => $platformUserId, + 'type' => 'bet', + 'amount' => -$totalAmount, + 'balance_before' => $currentBalance, + 'balance_after' => $newBalance, + 'related_id' => (int)$period['id'], + 'description' => 'TG Bot Bet - ' . $periodNumber, + 'is_virtual' => (int)($user['is_virtual'] ?? 0), + 'source' => 'bot', + 'source_ref' => $idempotencyKey, + 'created_at' => date('Y-m-d H:i:s'), + ]); + + foreach ($validBets as $vb) { + $this->db->insert('bets', [ + 'user_id' => $platformUserId, + 'game_id' => $gameId, + 'period_id' => (int)$period['id'], + 'period_number' => $periodNumber, + 'bet_type' => $vb['type'], + 'bet_value' => $vb['target'], + 'amount' => $vb['amount'], + 'odds' => $vb['odds'], + 'status' => 'pending', + 'is_virtual' => (int)($user['is_virtual'] ?? 0), + 'agent_id' => $user['agent_id'] ?? null, + 'source' => 'bot', + 'source_ref' => $idempotencyKey, + 'created_at' => date('Y-m-d H:i:s'), + ]); + } + + $this->db->insert('bot_bet_orders', [ + 'group_id' => (int)$group['id'], + 'tg_chat_id' => $tgGroupId, + 'tg_message_id' => (int)($this->requestJson['bet_context']['message_id'] ?? $this->requestJson['message_id'] ?? 0), + 'tg_user_id' => (string)($tgUser['id'] ?? ''), + 'tg_username' => (string)($tgUser['username'] ?? ''), + 'platform_user_id' => $platformUserId, + 'game_id' => $gameId, + 'period_number' => $periodNumber, + 'raw_text' => $rawText, + 'parsed_payload_json' => json_encode($validBets, JSON_UNESCAPED_UNICODE), + 'bet_amount_total' => $totalAmount, + 'accepted_bet_count' => count($validBets), + 'platform_order_ref' => $platformOrderRef, + 'idempotency_key' => $idempotencyKey, + 'sync_status' => 'success', + 'is_shill' => $isShill, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + + if ((int)$group['remind_bet_success'] === 1) { + $this->db->insert('bot_push_logs', [ + 'group_id' => (int)$group['id'], + 'period_number' => $periodNumber, + 'push_type' => 'bet_success', + 'payload_json' => json_encode([ + 'event' => 'bet_success', + 'period_number' => $periodNumber, + 'platform_order_ref' => $platformOrderRef, + 'tg_user_id' => (string)($tgUser['id'] ?? ''), + 'tg_username' => (string)($tgUser['username'] ?? ''), + 'accepted_bets' => count($validBets), + 'total_amount' => $totalAmount, + 'balance_after' => $newBalance, + 'raw_text' => $rawText, + ], JSON_UNESCAPED_UNICODE), + 'status' => 'pending', + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + } + + $this->db->medoo->pdo->commit(); + + $data = [ + 'platform_order_ref' => $platformOrderRef, + 'new_balance' => $newBalance, + 'accepted_bets' => count($validBets), + 'total_amount' => $totalAmount, + 'is_shill' => $isShill === 1, + ]; + + $this->auditAndRespond(true, 'Bet placed successfully', $data, 200, (int)$group['id'], $idempotencyKey); + } catch (\Throwable $e) { + if ($this->db->medoo && $this->db->medoo->pdo->inTransaction()) { + $this->db->medoo->pdo->rollBack(); + } + + try { + $this->db->insert('bot_bet_orders', [ + 'group_id' => (int)$group['id'], + 'tg_chat_id' => $tgGroupId, + 'tg_message_id' => (int)($this->requestJson['bet_context']['message_id'] ?? $this->requestJson['message_id'] ?? 0), + 'tg_user_id' => (string)($tgUser['id'] ?? ''), + 'tg_username' => (string)($tgUser['username'] ?? ''), + 'platform_user_id' => $platformUserId, + 'game_id' => $gameId, + 'period_number' => $periodNumber, + 'raw_text' => $rawText, + 'parsed_payload_json' => json_encode($bets, JSON_UNESCAPED_UNICODE), + 'bet_amount_total' => $totalAmount, + 'accepted_bet_count' => 0, + 'platform_order_ref' => $platformOrderRef, + 'idempotency_key' => $idempotencyKey, + 'sync_status' => 'failed', + 'sync_error' => mb_substr($e->getMessage(), 0, 255), + 'is_shill' => $isShill, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + } catch (\Throwable $ignore) { + } + + $this->auditAndRespond(false, 'System error', [], 500, (int)$group['id'], $idempotencyKey); + } + } + + public function getPendingPushes(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + + $limit = (int)($this->requestJson['limit'] ?? $_GET['limit'] ?? 20); + if ($limit <= 0) { + $limit = 20; + } + $limit = min($limit, 100); + + $workerToken = trim((string)($this->requestJson['worker_token'] ?? $_GET['worker_token'] ?? '')); + if ($workerToken === '') { + $workerToken = 'worker:' . substr(hash('sha256', uniqid('', true) . '|' . microtime(true)), 0, 24); + } + + try { + $reclaimBefore = date('Y-m-d H:i:s', time() - 120); + $this->db->update('bot_push_logs', [ + 'claim_token' => null, + 'claimed_at' => null, + 'updated_at' => date('Y-m-d H:i:s'), + ], [ + 'group_id' => (int)$group['id'], + 'status' => 'pending', + 'claim_token[!]' => null, + 'claimed_at[<]' => $reclaimBefore, + ]); + + $rows = $this->db->select('bot_push_logs', '*', [ + 'group_id' => (int)$group['id'], + 'status' => 'pending', + 'claim_token' => null, + 'ORDER' => ['id' => 'ASC'], + 'LIMIT' => 200, + ]) ?: []; + + $pushes = []; + $nowTs = time(); + $claimedAt = date('Y-m-d H:i:s'); + foreach ($rows as $row) { + $payload = []; + if (!empty($row['payload_json'])) { + $decoded = json_decode((string)$row['payload_json'], true); + if (is_array($decoded)) { + $payload = $decoded; + } + } + + $dispatchAtTs = !empty($payload['dispatch_at']) ? strtotime((string)$payload['dispatch_at']) : 0; + if ($dispatchAtTs > 0 && $dispatchAtTs > $nowTs) { + continue; + } + + $updated = $this->db->update('bot_push_logs', [ + 'claim_token' => $workerToken, + 'claimed_at' => $claimedAt, + 'updated_at' => $claimedAt, + ], [ + 'id' => (int)$row['id'], + 'group_id' => (int)$group['id'], + 'status' => 'pending', + 'claim_token' => null, + ]); + + if (($updated->rowCount() ?? 0) <= 0) { + continue; + } + + $pushes[] = [ + 'id' => (int)$row['id'], + 'group_id' => (int)$row['group_id'], + 'period_number' => $row['period_number'], + 'push_type' => $row['push_type'], + 'payload' => $payload, + 'claim_token' => $workerToken, + 'claimed_at' => $claimedAt, + 'created_at' => $row['created_at'], + 'updated_at' => $claimedAt, + ]; + + if (count($pushes) >= $limit) { + break; + } + } + + $this->auditAndRespond(true, 'ok', [ + 'items' => $pushes, + 'count' => count($pushes), + 'limit' => $limit, + 'worker_token' => $workerToken, + ], 200, (int)$group['id']); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to load pending pushes', [], 500, (int)$group['id']); + } + } + + public function ackPushes(string $tgGroupId): void + { + $this->authenticateBotRequest(); + $group = $this->requireGroupByTelegramId($tgGroupId); + $idempotencyKey = $this->buildIdempotencyKey('push-ack'); + + $pushLogIds = $this->requestJson['push_log_ids'] ?? []; + $status = trim((string)($this->requestJson['status'] ?? '')); + $errorMessage = trim((string)($this->requestJson['error_message'] ?? '')); + $tgMessageId = (int)($this->requestJson['tg_message_id'] ?? 0); + $claimToken = trim((string)($this->requestJson['claim_token'] ?? '')); + + if (!is_array($pushLogIds) || empty($pushLogIds)) { + $this->auditAndRespond(false, 'push_log_ids is required', [], 422, (int)$group['id'], $idempotencyKey); + } + + $allowedStatus = ['success', 'failed', 'skipped']; + if (!in_array($status, $allowedStatus, true)) { + $this->auditAndRespond(false, 'Invalid status', [], 422, (int)$group['id'], $idempotencyKey); + } + + if ($claimToken === '') { + $this->auditAndRespond(false, 'claim_token is required', [], 422, (int)$group['id'], $idempotencyKey); + } + + $pushLogIds = array_values(array_unique(array_filter(array_map('intval', $pushLogIds), static function ($id) { + return $id > 0; + }))); + if (empty($pushLogIds)) { + $this->auditAndRespond(false, 'No valid push_log_ids', [], 422, (int)$group['id'], $idempotencyKey); + } + + try { + $rows = $this->db->select('bot_push_logs', ['id', 'status', 'claim_token'], [ + 'id' => $pushLogIds, + 'group_id' => (int)$group['id'], + 'claim_token' => $claimToken, + ]) ?: []; + + if (empty($rows)) { + $this->auditAndRespond(false, 'Push logs not found or not claimed by this worker', [], 404, (int)$group['id'], $idempotencyKey); + } + + $foundIds = array_map(static function (array $row): int { + return (int)$row['id']; + }, $rows); + sort($foundIds); + $expectedIds = $pushLogIds; + sort($expectedIds); + if ($foundIds !== $expectedIds) { + $this->auditAndRespond(false, 'Some push logs are missing or owned by another worker', [ + 'expected_ids' => $expectedIds, + 'found_ids' => $foundIds, + ], 409, (int)$group['id'], $idempotencyKey); + } + + $updateData = [ + 'status' => $status, + 'updated_at' => date('Y-m-d H:i:s'), + 'error_message' => $errorMessage !== '' ? mb_substr($errorMessage, 0, 255) : null, + 'claim_token' => null, + 'claimed_at' => null, + ]; + if ($tgMessageId > 0) { + $updateData['tg_message_id'] = $tgMessageId; + } + + $this->db->update('bot_push_logs', $updateData, [ + 'id' => $foundIds, + 'group_id' => (int)$group['id'], + 'claim_token' => $claimToken, + ]); + + $this->auditAndRespond(true, 'Push ack updated', [ + 'updated_count' => count($foundIds), + 'updated_ids' => $foundIds, + 'status' => $status, + 'tg_message_id' => $tgMessageId > 0 ? $tgMessageId : null, + 'claim_token' => $claimToken, + ], 200, (int)$group['id'], $idempotencyKey); + } catch (\Throwable $e) { + $this->auditAndRespond(false, 'Failed to ack pushes', [], 500, (int)$group['id'], $idempotencyKey); + } + } +} diff --git a/App/Controllers/Web/FollowPlanController.php b/App/Controllers/Web/FollowPlanController.php new file mode 100644 index 0000000..8c685cd --- /dev/null +++ b/App/Controllers/Web/FollowPlanController.php @@ -0,0 +1,44 @@ +checkWebLogin(); + I18n::init(); + $db = new Database(); + $userId = $this->getCurrentUserId(); + $user = $db->get('users', ['id','username','balance'], ['id' => $userId]); + + $service = new FollowPlanService($db); + $game = $db->get('games', '*', ['code' => 'pk10', 'status' => 1]); + $gameId = $game['id'] ?? 0; + + $plans = $service->getActivePlans($gameId); + $userFollows = $service->getUserFollowStatus($userId); + $summary = $service->getUserSummary($userId); + + extract(compact('user', 'plans', 'userFollows', 'summary', 'game')); + include __DIR__ . '/../../Views/Web/follow_plan.php'; + } + + /** + * 切换跟单状态 API + */ + public function toggleFollow() { + $this->checkWebLogin(); + header('Content-Type: application/json'); + $db = new Database(); + $data = json_decode(file_get_contents('php://input'), true); + $planId = intval($data['plan_id'] ?? 0); + + $service = new FollowPlanService($db); + $result = $service->toggleFollow($this->getCurrentUserId(), $planId); + echo json_encode($result); + } +} diff --git a/App/Controllers/Web/HomeController.php b/App/Controllers/Web/HomeController.php index 620dc63..ad1a1ec 100755 --- a/App/Controllers/Web/HomeController.php +++ b/App/Controllers/Web/HomeController.php @@ -29,10 +29,26 @@ class HomeController extends WebBaseController { $user = $db->get('users', ['id','username','balance','lang'], ['id' => $userId]); $game = $db->get('games', '*', ['code' => 'pk10', 'status' => 1]); $isAgent = !!$db->get('agents', 'id', ['user_id' => $userId, 'status' => 1]); + // 加载赔率(优先代理专属赔率,否则用游戏默认赔率) + $gameId = $game['id'] ?? 0; + $oddsRaw = $db->select('game_odds', '*', ['game_id' => $gameId]); + $oddsMap = []; + foreach ($oddsRaw as $o) { + $oddsMap[$o['type'] . '_' . $o['target']] = (float)$o['odds']; + } + // 代理专属赔率覆盖 + $agentId = $db->get('agents', 'id', ['user_id' => $userId, 'status' => 1]); + if ($agentId) { + $agentOdds = $db->select('agent_odds', '*', ['agent_id' => $agentId, 'game_id' => $gameId]); + foreach ($agentOdds as $ao) { + $oddsMap[$ao['bet_type'] . '_' . $ao['bet_target']] = (float)$ao['odds']; + } + } extract([ 'user' => $user ?: ['id' => $userId, 'username' => $this->getCurrentUsername(), 'balance' => 0], 'game' => $game, - 'isAgent' => $isAgent, + 'isAgent' => !!$agentId, + 'oddsMap' => $oddsMap, ]); include __DIR__ . '/../../Views/Web/pk10.php'; } @@ -56,7 +72,13 @@ class HomeController extends WebBaseController { 'ORDER' => ['id' => 'DESC'], 'LIMIT' => 20 ]); - extract(compact('user', 'transactions', 'bets')); + // 充提记录 + $fundRequests = $db->select('fund_requests', '*', [ + 'user_id' => $userId, + 'ORDER' => ['id' => 'DESC'], 'LIMIT' => 20 + ]); + + extract(compact('user', 'transactions', 'bets', 'fundRequests')); include __DIR__ . '/../../Views/Web/profile.php'; } diff --git a/App/Controllers/Web/PeriodController.php b/App/Controllers/Web/PeriodController.php index ffff7e1..b395a9e 100755 --- a/App/Controllers/Web/PeriodController.php +++ b/App/Controllers/Web/PeriodController.php @@ -84,7 +84,7 @@ class PeriodController extends WebBaseController { 'game_id' => $gameId, 'status' => ['drawn', 'settled'], 'ORDER' => ['id' => 'DESC'], - 'LIMIT' => 10 + 'LIMIT' => 30 ]); foreach ($recentPeriods as $s) { $pk = $getResult($s); diff --git a/App/Controllers/Web/ReportWebController.php b/App/Controllers/Web/ReportWebController.php new file mode 100644 index 0000000..acc1020 --- /dev/null +++ b/App/Controllers/Web/ReportWebController.php @@ -0,0 +1,36 @@ +checkWebLogin(); + I18n::init(); + $db = new Database(); + $userId = $this->getCurrentUserId(); + $user = $db->get('users', ['id','username','balance'], ['id' => $userId]); + extract(compact('user')); + include __DIR__ . '/../../Views/Web/report.php'; + } + + /** + * 前台报表查询 API + */ + public function userReportApi() { + $this->checkWebLogin(); + header('Content-Type: application/json'); + $service = new ReportService(new Database()); + $dateFrom = $_GET['from'] ?? date('Y-m-d'); + $dateTo = $_GET['to'] ?? date('Y-m-d'); + $data = $service->getUserReport($this->getCurrentUserId(), $dateFrom, $dateTo); + echo json_encode(['success' => true, 'data' => $data]); + } +} diff --git a/App/Controllers/Web/TransactionController.php b/App/Controllers/Web/TransactionController.php new file mode 100644 index 0000000..fb213dc --- /dev/null +++ b/App/Controllers/Web/TransactionController.php @@ -0,0 +1,36 @@ +checkWebLogin(); + header('Content-Type: application/json'); + $data = json_decode(file_get_contents('php://input'), true); + $service = new TransactionService(new Database()); + $result = $service->fundRequest( + $this->getCurrentUserId(), + $data['type'] ?? '', + floatval($data['amount'] ?? 0), + $data['remark'] ?? '' + ); + echo json_encode($result); + } + + public function transfer() { + $this->checkWebLogin(); + header('Content-Type: application/json'); + $data = json_decode(file_get_contents('php://input'), true); + $service = new TransactionService(new Database()); + $result = $service->transfer( + $this->getCurrentUserId(), + trim($data['to_username'] ?? ''), + floatval($data['amount'] ?? 0) + ); + echo json_encode($result); + } +} diff --git a/App/Core/BotApiBaseController.php b/App/Core/BotApiBaseController.php new file mode 100644 index 0000000..62aeb49 --- /dev/null +++ b/App/Core/BotApiBaseController.php @@ -0,0 +1,181 @@ +db = new Database(); + $this->rawRequestBody = file_get_contents('php://input') ?: ''; + $this->requestJson = $this->getJsonInput(); + } + + protected function getJsonInput(): array + { + $raw = $this->rawRequestBody; + if (!$raw) { + return []; + } + + $data = json_decode($raw, true); + return is_array($data) ? $data : []; + } + + protected function jsonResponse(bool $success, string $message = '', array $data = [], int $statusCode = 200): void + { + http_response_code($statusCode); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode([ + 'success' => $success, + 'message' => $message, + 'data' => $data, + ], JSON_UNESCAPED_UNICODE); + exit; + } + + protected function auditAndRespond(bool $success, string $message = '', array $data = [], int $statusCode = 200, ?int $groupId = null, ?string $idempotencyKey = null): void + { + $payload = [ + 'success' => $success, + 'message' => $message, + 'data' => $data, + ]; + $responseBody = json_encode($payload, JSON_UNESCAPED_UNICODE); + $this->logApiRequest( + (int)($this->botInstance['id'] ?? 0) ?: null, + $groupId, + $idempotencyKey, + $statusCode, + $this->botInstance !== null, + $this->rawRequestBody, + $responseBody + ); + + http_response_code($statusCode); + header('Content-Type: application/json; charset=utf-8'); + echo $responseBody; + exit; + } + + protected function getHeader(string $name): string + { + $key = 'HTTP_' . strtoupper(str_replace('-', '_', $name)); + return trim($_SERVER[$key] ?? ''); + } + + protected function authenticateBotRequest(): void + { + $botKey = $this->getHeader('X-Bot-Key'); + $timestamp = $this->getHeader('X-Timestamp'); + $signature = $this->getHeader('X-Signature'); + + if ($botKey === '' || $timestamp === '' || $signature === '') { + $this->jsonResponse(false, 'Missing bot auth headers', [], 401); + } + + if (!ctype_digit($timestamp)) { + $this->jsonResponse(false, 'Invalid timestamp', [], 401); + } + + $now = time(); + if (abs($now - (int)$timestamp) > 300) { + $this->jsonResponse(false, 'Timestamp expired', [], 401); + } + + $bot = $this->db->get('bot_instances', '*', [ + 'bot_key' => $botKey, + 'status' => 1, + ]); + + if (!$bot) { + $this->jsonResponse(false, 'Bot auth failed', [], 401); + } + + $rawBody = $this->rawRequestBody; + $method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'); + $path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/'; + $signPayload = $timestamp . "\n" . $method . "\n" . $path . "\n" . $rawBody; + $expected = hash_hmac('sha256', $signPayload, $bot['bot_secret']); + + if (!hash_equals($expected, $signature)) { + $this->logApiRequest((int)$bot['id'], null, null, 0, false, $rawBody, ''); + $this->jsonResponse(false, 'Signature verify failed', [], 401); + } + + $this->botInstance = $bot; + } + + protected function requireGroupByTelegramId(string $tgGroupId): array + { + $group = $this->db->get('bot_groups', '*', [ + 'tg_group_id' => $tgGroupId, + 'status' => 1, + ]); + + if (!$group) { + $this->jsonResponse(false, 'Group config not found', [], 404); + } + + if ((int)$group['bot_id'] !== (int)($this->botInstance['id'] ?? 0)) { + $this->jsonResponse(false, 'Group does not belong to this bot', [], 403); + } + + $this->groupConfig = $group; + return $group; + } + + protected function getGroupWallet(int $groupId): array + { + $wallet = $this->db->get('bot_group_wallets', '*', [ + 'group_id' => $groupId, + 'status' => 1, + ]); + + if (!$wallet) { + $this->jsonResponse(false, 'Group wallet not configured', [], 422); + } + + return $wallet; + } + + protected function buildIdempotencyKey(string $fallbackPrefix = 'bot'): string + { + $key = trim((string)($this->requestJson['idempotency_key'] ?? '')); + if ($key !== '') { + return $key; + } + + $groupId = (string)($this->groupConfig['tg_group_id'] ?? 'unknown'); + $messageId = (string)($this->requestJson['bet_context']['message_id'] ?? $this->requestJson['message_id'] ?? uniqid()); + return $fallbackPrefix . ':' . $groupId . ':' . $messageId; + } + + protected function logApiRequest(?int $botId, ?int $groupId, ?string $idempotencyKey, int $responseCode, bool $signatureOk, string $requestBody, string $responseBody): void + { + try { + $this->db->insert('bot_api_request_logs', [ + 'bot_id' => $botId, + 'group_id' => $groupId, + 'request_uri' => parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/', + 'http_method' => strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'), + 'idempotency_key' => $idempotencyKey, + 'request_body' => $requestBody, + 'response_body' => $responseBody, + 'response_code' => $responseCode, + 'client_ip' => $_SERVER['REMOTE_ADDR'] ?? '', + 'signature_ok' => $signatureOk ? 1 : 0, + 'created_at' => date('Y-m-d H:i:s'), + ]); + } catch (\Throwable $e) { + // 审计失败不阻断主流程 + } + } +} diff --git a/App/Core/PK10Algorithm.php b/App/Core/PK10Algorithm.php index 635f0d7..25529ac 100644 --- a/App/Core/PK10Algorithm.php +++ b/App/Core/PK10Algorithm.php @@ -14,26 +14,56 @@ class PK10Algorithm implements GameAlgorithmInterface { /** * 带放水机制的开奖结果生成 + * + * 算法策略: + * - targetProfitRate > 0 时:选择利润最接近「总投注额 × 目标盈利率」的结果 + * - targetProfitRate == 0 时:选择平台利润最高的结果(原逻辑) + * * @param array $bets 当期所有投注 [{bet_type, bet_target, amount, odds}, ...] * @param array $waterConfig [{bet_type => win_rate_pct}, ...] + * 当前版本中 waterConfig 的 win_rate_pct 未直接参与结果选择, + * 因为开奖结果是全局排列,无法单独控制某个投注类型的胜率。 + * 保留此参数供未来扩展(如按类型加权评分、分类型概率偏移等)。 * @param int $attempts 最大尝试次数 + * @param float $targetProfitRate 目标盈利率(百分比,如 5.0 表示 5%) */ - public static function generateControlledResult(array $bets, array $waterConfig, int $attempts = 100): array { - if (empty($bets) || empty($waterConfig)) { + public static function generateControlledResult(array $bets, array $waterConfig, int $attempts = 100, float $targetProfitRate = 0): array { + if (empty($bets)) { return self::generateResult(); } + // 计算目标利润(仅当 targetProfitRate > 0 时生效) + $targetProfit = 0; + if ($targetProfitRate > 0) { + $totalBet = 0; + foreach ($bets as $bet) { + $totalBet += (float)$bet['amount']; + } + $targetProfit = $totalBet * ($targetProfitRate / 100); + } + $bestResult = null; $bestProfit = PHP_INT_MIN; + $bestDistance = PHP_FLOAT_MAX; for ($i = 0; $i < $attempts; $i++) { $result = self::generateResult(); $profit = self::calculatePlatformProfit($result, $bets); - // 选择平台利润最高的结果 - if ($profit > $bestProfit) { - $bestProfit = $profit; - $bestResult = $result; + if ($targetProfitRate > 0) { + // 目标盈利率模式:选择利润最接近目标值的结果 + $distance = abs($profit - $targetProfit); + if ($distance < $bestDistance) { + $bestDistance = $distance; + $bestProfit = $profit; + $bestResult = $result; + } + } else { + // 原逻辑:选择平台利润最高的结果 + if ($profit > $bestProfit) { + $bestProfit = $profit; + $bestResult = $result; + } } } diff --git a/App/Core/PluginManager.php b/App/Core/PluginManager.php index 2771413..ac3e41d 100755 --- a/App/Core/PluginManager.php +++ b/App/Core/PluginManager.php @@ -38,21 +38,21 @@ class PluginManager { $rootDir = realpath(__DIR__ . '/../../'); $logDir = $rootDir . '/Storage/log'; - - // 确保日志目录(如不存在则创建) - if (!is_dir($logDir)) { - if (!mkdir($logDir, 0755, true) && !is_dir($logDir)) { - throw new \RuntimeException("无法创建日志目录: $logDir ,请检查权限"); - } - } - - // 检查目录可写性 - if (!is_writable($logDir)) { - throw new \RuntimeException("日志目录不可写: $logDir ,请检查权限"); - } - $this->logFile = $logDir . '/plugin_manager.log'; - + + // 尝试准备日志目录;失败时降级到 PHP error_log,不能阻断主流程 + if (!is_dir($logDir) && !@mkdir($logDir, 0755, true) && !is_dir($logDir)) { + $this->logFile = null; + error_log("PluginManager log directory create failed: $logDir"); + return; + } + + if (!$this->canWriteLogFile()) { + $this->logFile = null; + error_log("PluginManager log path not writable: $logDir"); + return; + } + // 检查日志文件大小并自动清理(大于2MB时) $this->cleanupLogFile(2); // 传入最大允许的MB数 } @@ -62,32 +62,47 @@ class PluginManager { * @param int $maxSizeMB 最大允许的文件大小(MB) */ private function cleanupLogFile(int $maxSizeMB) { - // 检查文件是否存在 - if (!file_exists($this->logFile)) { + if (!$this->canWriteLogFile() || !file_exists($this->logFile)) { return; } - + // 转换MB为字节 $maxSizeBytes = $maxSizeMB * 1024 * 1024; - + // 获取当前文件大小 - $currentSize = filesize($this->logFile); - + $currentSize = @filesize($this->logFile); + if ($currentSize === false) { + return; + } + // 如果文件大小超过限制,清空文件 if ($currentSize > $maxSizeBytes) { // 先备份当前日志内容(可选) $backupFile = $this->logFile . '.bak_' . date('YmdHis'); - copy($this->logFile, $backupFile); - + @copy($this->logFile, $backupFile); + // 清空日志文件 - file_put_contents($this->logFile, ''); - + @file_put_contents($this->logFile, ''); + // 记录清理日志 $message = "[" . date('Y-m-d H:i:s') . "] 日志文件超过{$maxSizeMB}MB,已自动清理\n"; - file_put_contents($this->logFile, $message, FILE_APPEND); + @file_put_contents($this->logFile, $message, FILE_APPEND); } } + private function canWriteLogFile(): bool { + if (empty($this->logFile)) { + return false; + } + + $logDir = dirname($this->logFile); + if (!is_dir($logDir) || !is_writable($logDir)) { + return false; + } + + return !file_exists($this->logFile) || is_writable($this->logFile); + } + /** * 设置系统核心路由 @@ -121,13 +136,12 @@ class PluginManager { protected function log(string $msg, string $level = 'INFO'): void { $date = date('Y-m-d H:i:s'); $logMsg = "[$date] [$level] $msg\n"; - $logDir = dirname($this->logFile); try { - if (is_dir($logDir) && is_writable($logDir)) { - file_put_contents($this->logFile, $logMsg, FILE_APPEND); + if ($this->canWriteLogFile()) { + @file_put_contents($this->logFile, $logMsg, FILE_APPEND); } else { - error_log("PluginManager log directory not writable: $logDir"); + error_log("PluginManager: " . trim($logMsg)); } } catch (\Throwable $e) { error_log("Failed to write plugin log: " . $e->getMessage()); diff --git a/App/Core/SettingsHelper.php b/App/Core/SettingsHelper.php index d2eb37e..6c37a75 100755 --- a/App/Core/SettingsHelper.php +++ b/App/Core/SettingsHelper.php @@ -60,12 +60,14 @@ class SettingsHelper { */ private static function getDefaults() { return [ - 'site_title' => 'PK10 Speed Racing', - 'site_description' => 'PK10 Speed Racing - Online Betting Platform', - 'site_keywords' => 'pk10, speed racing, betting, online game', + 'site_title' => 'F1 Racing', + 'site_description' => 'F1 Racing - Online Betting Platform', + 'site_keywords' => 'f1, racing, betting, online game', 'site_logo' => '/Static/images/logo.png', 'site_favicon' => '/Static/css/favicon.ico', - 'site_copyright' => '© 2025 PK10 Racing. All rights reserved.' + 'site_copyright' => '© 2025 F1 Racing. All rights reserved.', + 'target_profit_rate' => '15', // 目标盈利率,百分比,如15表示15% + 'customer_service_url' => '', // 客服链接(充值时跳转) ]; } diff --git a/App/Services/FollowPlanService.php b/App/Services/FollowPlanService.php new file mode 100644 index 0000000..7f3e00d --- /dev/null +++ b/App/Services/FollowPlanService.php @@ -0,0 +1,245 @@ +db = $db; + } + + /** + * 获取所有启用的跟单计划(前台展示) + */ + public function getActivePlans(int $gameId = 0): array + { + $where = ['status' => 1]; + if ($gameId > 0) $where['game_id'] = $gameId; + $where['ORDER'] = ['id' => 'ASC']; + + $plans = $this->db->select('follow_plans', '*', $where); + foreach ($plans as &$plan) { + $stats = $this->getPlanStats($plan['id']); + $plan['total_records'] = $stats['total']; + $plan['win_count'] = $stats['wins']; + $plan['win_rate'] = $stats['total'] > 0 ? round($stats['wins'] / $stats['total'] * 100, 1) : 0; + // 最近 10 期记录 + $plan['recent'] = $this->db->select('follow_plan_records', '*', [ + 'plan_id' => $plan['id'], + 'is_win[!]' => null, + 'ORDER' => ['id' => 'DESC'], + 'LIMIT' => 10 + ]); + } + unset($plan); + return $plans; + } + + /** + * 获取计划统计 + */ + public function getPlanStats(int $planId): array + { + $total = $this->db->count('follow_plan_records', [ + 'plan_id' => $planId, + 'is_win[!]' => null + ]); + $wins = $this->db->count('follow_plan_records', [ + 'plan_id' => $planId, + 'is_win' => 1 + ]); + return ['total' => $total, 'wins' => $wins]; + } + + /** + * 获取用户的跟单状态 + */ + public function getUserFollowStatus(int $userId): array + { + $rows = $this->db->select('user_follow_plans', '*', [ + 'user_id' => $userId, + 'is_active' => 1 + ]); + $result = []; + foreach ($rows as $r) { + $result[$r['plan_id']] = $r; + } + return $result; + } + + /** + * 用户开始/停止跟单 + */ + public function toggleFollow(int $userId, int $planId): array + { + $existing = $this->db->get('user_follow_plans', '*', [ + 'user_id' => $userId, + 'plan_id' => $planId + ]); + + if ($existing) { + $newStatus = $existing['is_active'] ? 0 : 1; + $this->db->update('user_follow_plans', ['is_active' => $newStatus], [ + 'id' => $existing['id'] + ]); + return ['success' => true, 'active' => $newStatus]; + } + + // 新建 + $plan = $this->db->get('follow_plans', '*', ['id' => $planId, 'status' => 1]); + if (!$plan) { + return ['success' => false, 'message' => '计划不存在或已禁用']; + } + + $this->db->insert('user_follow_plans', [ + 'user_id' => $userId, + 'plan_id' => $planId, + 'is_active' => 1, + ]); + return ['success' => true, 'active' => 1]; + } + + /** + * 获取用户跟单汇总(总胜率、总盈亏) + */ + public function getUserSummary(int $userId): array + { + $pdo = $this->db->medoo->pdo; + $totalWinRate = 0; + $totalProfit = 0; + + try { + $stmt = $pdo->prepare(" + SELECT SUM(total_bets) as bets, SUM(total_wins) as wins, SUM(total_profit) as profit + FROM user_follow_plans WHERE user_id = ? + "); + $stmt->execute([$userId]); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + $bets = intval($row['bets'] ?? 0); + $wins = intval($row['wins'] ?? 0); + $totalWinRate = $bets > 0 ? round($wins / $bets * 100, 1) : 0; + $totalProfit = floatval($row['profit'] ?? 0); + } catch (\Exception $e) {} + + return ['win_rate' => $totalWinRate, 'profit' => $totalProfit]; + } + + // ===== 后台管理 ===== + + /** + * 获取所有计划(后台) + */ + public function getAllPlans(): array + { + $plans = $this->db->select('follow_plans', '*', ['ORDER' => ['id' => 'DESC']]); + foreach ($plans as &$plan) { + $stats = $this->getPlanStats($plan['id']); + $plan['total_records'] = $stats['total']; + $plan['win_count'] = $stats['wins']; + $plan['win_rate'] = $stats['total'] > 0 ? round($stats['wins'] / $stats['total'] * 100, 1) : 0; + $plan['follower_count'] = $this->db->count('user_follow_plans', [ + 'plan_id' => $plan['id'], 'is_active' => 1 + ]); + } + unset($plan); + return $plans; + } + + /** + * 创建/更新计划 + */ + public function savePlan(array $data): array + { + $fields = [ + 'game_id' => intval($data['game_id'] ?? 1), + 'name' => trim($data['name'] ?? ''), + 'plan_type' => trim($data['plan_type'] ?? 'bs'), + 'target_rank' => intval($data['target_rank'] ?? 1), + 'strategy' => trim($data['strategy'] ?? 'follow'), + 'bet_amount' => floatval($data['bet_amount'] ?? 100), + 'status' => intval($data['status'] ?? 1), + ]; + + if (empty($fields['name'])) { + return ['success' => false, 'message' => '计划名称不能为空']; + } + + $id = intval($data['id'] ?? 0); + if ($id > 0) { + $this->db->update('follow_plans', $fields, ['id' => $id]); + } else { + $fields['created_by'] = intval($data['created_by'] ?? 0); + $this->db->insert('follow_plans', $fields); + $id = intval($this->db->id()); + } + + return ['success' => true, 'id' => $id]; + } + + /** + * 删除计划 + */ + public function deletePlan(int $planId): array + { + $this->db->delete('follow_plan_records', ['plan_id' => $planId]); + $this->db->delete('user_follow_plans', ['plan_id' => $planId]); + $this->db->delete('follow_plans', ['id' => $planId]); + return ['success' => true]; + } + + /** + * 添加计划推荐记录(由 cron 或后台手动触发) + */ + public function addRecord(int $planId, int $periodId, string $periodNumber, string $recommendValue): array + { + $exists = $this->db->get('follow_plan_records', 'id', [ + 'plan_id' => $planId, 'period_id' => $periodId + ]); + if ($exists) { + return ['success' => false, 'message' => '该期已有推荐记录']; + } + + $this->db->insert('follow_plan_records', [ + 'plan_id' => $planId, + 'period_id' => $periodId, + 'period_number' => $periodNumber, + 'recommend_value' => $recommendValue, + ]); + return ['success' => true, 'id' => intval($this->db->id())]; + } + + /** + * 结算推荐记录(开奖后调用) + */ + public function settleRecord(int $recordId, string $resultValue, bool $isWin): void + { + $this->db->update('follow_plan_records', [ + 'result_value' => $resultValue, + 'is_win' => $isWin ? 1 : 0, + ], ['id' => $recordId]); + + // 更新所有跟了这个计划的用户统计 + $record = $this->db->get('follow_plan_records', '*', ['id' => $recordId]); + if (!$record) return; + + $plan = $this->db->get('follow_plans', '*', ['id' => $record['plan_id']]); + if (!$plan) return; + + $betAmount = floatval($plan['bet_amount']); + $profit = $isWin ? $betAmount * 0.95 : -$betAmount; // 简化盈亏计算 + + $followers = $this->db->select('user_follow_plans', '*', [ + 'plan_id' => $record['plan_id'], 'is_active' => 1 + ]); + + foreach ($followers as $f) { + $this->db->update('user_follow_plans', [ + 'total_bets[+]' => 1, + 'total_wins[+]' => $isWin ? 1 : 0, + 'total_profit[+]' => $profit, + ], ['id' => $f['id']]); + } + } +} diff --git a/App/Services/ReportService.php b/App/Services/ReportService.php new file mode 100644 index 0000000..c5462e6 --- /dev/null +++ b/App/Services/ReportService.php @@ -0,0 +1,344 @@ +db = $db; + } + + /** + * 前台用户报表:按投注类型分组统计 + * + * @param int $userId 用户ID + * @param string $dateFrom 开始日期 (Y-m-d) + * @param string $dateTo 结束日期 (Y-m-d) + * @return array + */ + public function getUserReport(int $userId, string $dateFrom, string $dateTo): array + { + $rangeFrom = $dateFrom . ' 00:00:00'; + $rangeTo = $dateTo . ' 23:59:59'; + + $pdo = $this->db->medoo->pdo; + try { + // 投注统计(有效流水 = 已结算投注额,排除 pending) + $stmt = $pdo->prepare(" + SELECT + bet_type as type, + COUNT(*) as bet_count, + SUM(amount) as amount, + SUM(CASE WHEN status IN ('win','lose') THEN amount ELSE 0 END) as effective_flow, + SUM(CASE + WHEN status='win' THEN win_amount + WHEN status='lose' THEN -amount + ELSE 0 + END) as win_loss + FROM bets + WHERE user_id = ? AND created_at BETWEEN ? AND ? + GROUP BY bet_type + ORDER BY bet_type + "); + $stmt->execute([$userId, $rangeFrom, $rangeTo]); + $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + // 退水(代理返佣给该用户的金额) + $rebateTotal = 0; + try { + $stmt2 = $pdo->prepare(" + SELECT COALESCE(SUM(commission), 0) as total + FROM agent_commissions + WHERE from_user_id = ? AND type = 'rebate' AND created_at BETWEEN ? AND ? + "); + $stmt2->execute([$userId, $rangeFrom, $rangeTo]); + $rebateTotal = floatval($stmt2->fetch(\PDO::FETCH_ASSOC)['total'] ?? 0); + } catch (\Exception $e) { + $rebateTotal = 0; + } + + // 将退水按比例分配到各类型(或全部放在第一行) + $totalBet = array_sum(array_column($rows, 'amount')); + foreach ($rows as &$row) { + $row['amount'] = floatval($row['amount']); + $row['effective_flow'] = floatval($row['effective_flow']); + $row['win_loss'] = floatval($row['win_loss']); + // 按投注额占比分配退水 + $ratio = $totalBet > 0 ? $row['amount'] / $totalBet : 0; + $row['rebate'] = round($rebateTotal * $ratio, 2); + } + unset($row); + + return $rows; + } catch (\Exception $e) { + return []; + } + } + + /** + * 后台总体统计 + * + * @param string $dateFrom 开始日期 (Y-m-d) + * @param string $dateTo 结束日期 (Y-m-d) + * @return array + */ + public function getOverviewStats(string $dateFrom, string $dateTo): array + { + $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; + $pdo = $this->db->medoo->pdo; + + // 总投注(排除虚拟) + $totalBet = $this->db->sum('bets', 'amount', [ + 'is_virtual' => 0, 'created_at[<>]' => $range + ]) ?: 0; + + // 总派奖 + $totalWin = $this->db->sum('bets', 'win_amount', [ + 'is_virtual' => 0, 'status' => 'win', 'created_at[<>]' => $range + ]) ?: 0; + + // 平台利润 + $profit = $totalBet - $totalWin; + $profitRate = $totalBet > 0 ? round($profit / $totalBet * 100, 2) : 0; + + // 总充值 + $totalDeposit = $this->db->sum('transactions', 'amount', [ + 'is_virtual' => 0, 'type[~]' => '%deposit%', 'amount[>]' => 0, + 'created_at[<>]' => $range + ]) ?: 0; + + // 总提现 + $totalWithdraw = abs($this->db->sum('transactions', 'amount', [ + 'is_virtual' => 0, 'type[~]' => '%withdraw%', 'amount[<]' => 0, + 'created_at[<>]' => $range + ]) ?: 0); + + // 代理佣金 + $totalCommission = $this->db->sum('agent_commissions', 'commission', [ + 'created_at[<>]' => $range + ]) ?: 0; + + // 反水 + $totalRebate = $this->db->sum('agent_commissions', 'commission', [ + 'type' => 'rebate', 'created_at[<>]' => $range + ]) ?: 0; + + // 用户统计 + $totalUsers = $this->db->count('users', ['is_virtual' => 0]); + $newUsers = $this->db->count('users', [ + 'is_virtual' => 0, 'created_at[<>]' => $range + ]); + + // 活跃用户数(有投注记录的非虚拟用户) + $activeUsers = 0; + try { + $stmt = $pdo->prepare("SELECT COUNT(DISTINCT user_id) as cnt FROM bets WHERE is_virtual = 0 AND created_at BETWEEN ? AND ?"); + $stmt->execute([$range[0], $range[1]]); + $activeUsers = (int)($stmt->fetch(\PDO::FETCH_ASSOC)['cnt'] ?? 0); + } catch (\Exception $e) { + $activeUsers = 0; + } + + return compact( + 'totalBet', 'totalWin', 'profit', 'profitRate', + 'totalDeposit', 'totalWithdraw', 'totalCommission', 'totalRebate', + 'totalUsers', 'newUsers', 'activeUsers' + ); + } + + /** + * 日明细统计 + * + * @param string $dateFrom 开始日期 (Y-m-d) + * @param string $dateTo 结束日期 (Y-m-d) + * @return array + */ + public function getDailyStats(string $dateFrom, string $dateTo): array + { + $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; + $pdo = $this->db->medoo->pdo; + + $dailyStats = []; + try { + $stmt = $pdo->prepare(" + SELECT + DATE(created_at) as date, + SUM(amount) as total_bet, + SUM(CASE WHEN status='win' THEN win_amount ELSE 0 END) as total_win, + COUNT(*) as bet_count, + SUM(CASE WHEN status='win' THEN 1 ELSE 0 END) as win_count + FROM bets + WHERE is_virtual = 0 AND created_at BETWEEN ? AND ? + GROUP BY DATE(created_at) + ORDER BY date DESC + "); + $stmt->execute([$range[0], $range[1]]); + $dailyStats = $stmt->fetchAll(\PDO::FETCH_ASSOC); + } catch (\Exception $e) { + return []; + } + + // 补充每日充提数据 + foreach ($dailyStats as &$day) { + $dr = [$day['date'] . ' 00:00:00', $day['date'] . ' 23:59:59']; + $day['deposits'] = $this->db->sum('transactions', 'amount', [ + 'is_virtual' => 0, 'type[~]' => '%deposit%', 'amount[>]' => 0, + 'created_at[<>]' => $dr + ]) ?: 0; + $day['withdraws'] = abs($this->db->sum('transactions', 'amount', [ + 'is_virtual' => 0, 'type[~]' => '%withdraw%', 'amount[<]' => 0, + 'created_at[<>]' => $dr + ]) ?: 0); + $day['total_bet'] = floatval($day['total_bet'] ?? 0); + $day['total_win'] = floatval($day['total_win'] ?? 0); + $day['bet_count'] = intval($day['bet_count'] ?? 0); + $day['win_count'] = intval($day['win_count'] ?? 0); + $day['profit'] = $day['total_bet'] - $day['total_win']; + $day['win_rate'] = $day['bet_count'] > 0 ? round($day['win_count'] / $day['bet_count'] * 100, 1) : 0; + } + unset($day); + + return $dailyStats; + } + + /** + * 用户输赢明细(含日维度分组 + 用户汇总) + * + * @param string $dateFrom 开始日期 (Y-m-d) + * @param string $dateTo 结束日期 (Y-m-d) + * @return array ['raw' => 原始行, 'summary' => 按用户汇总] + */ + public function getUserWinLoss(string $dateFrom, string $dateTo): array + { + $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; + $pdo = $this->db->medoo->pdo; + + $userWinLoss = []; + try { + $stmt = $pdo->prepare(" + SELECT u.id as user_id, u.username, u.balance, + DATE(b.created_at) as date, + SUM(b.amount) as total_bet, + SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as total_win, + COUNT(*) as bet_count, + SUM(CASE WHEN b.status='win' THEN 1 ELSE 0 END) as win_count + FROM bets b + JOIN users u ON b.user_id = u.id + WHERE b.is_virtual = 0 AND b.created_at BETWEEN ? AND ? + GROUP BY b.user_id, DATE(b.created_at) + ORDER BY u.username, date DESC + "); + $stmt->execute([$range[0], $range[1]]); + $userWinLoss = $stmt->fetchAll(\PDO::FETCH_ASSOC); + } catch (\Exception $e) { + return ['raw' => [], 'summary' => []]; + } + + // 按用户汇总 + $userSummary = []; + foreach ($userWinLoss as $row) { + $uid = $row['user_id']; + if (!isset($userSummary[$uid])) { + $userSummary[$uid] = [ + 'username' => $row['username'], + 'balance' => floatval($row['balance']), + 'total_bet' => 0, 'total_win' => 0, + 'bet_count' => 0, 'win_count' => 0, + 'days' => [] + ]; + } + $bet = floatval($row['total_bet']); + $win = floatval($row['total_win']); + $userSummary[$uid]['total_bet'] += $bet; + $userSummary[$uid]['total_win'] += $win; + $userSummary[$uid]['bet_count'] += intval($row['bet_count']); + $userSummary[$uid]['win_count'] += intval($row['win_count']); + $userSummary[$uid]['days'][] = [ + 'date' => $row['date'], + 'bet' => $bet, 'win' => $win, + 'profit' => $bet - $win, + 'count' => intval($row['bet_count']), + ]; + } + // 排序:按总投注额降序 + uasort($userSummary, function($a, $b) { return $b['total_bet'] <=> $a['total_bet']; }); + + return ['raw' => $userWinLoss, 'summary' => $userSummary]; + } + + /** + * 用户排行(投注额 TOP N) + * + * @param string $dateFrom 开始日期 (Y-m-d) + * @param string $dateTo 结束日期 (Y-m-d) + * @param int $limit 条数限制,0=不限 + * @return array + */ + public function getTopUsers(string $dateFrom, string $dateTo, int $limit = 20): array + { + $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; + $pdo = $this->db->medoo->pdo; + + $sql = " + SELECT u.username, + SUM(b.amount) as total_bet, + SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as total_win, + SUM(b.amount) - SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as profit, + COUNT(*) as bet_count + FROM bets b + JOIN users u ON b.user_id = u.id + WHERE b.is_virtual = 0 AND b.created_at BETWEEN ? AND ? + GROUP BY b.user_id + ORDER BY total_bet DESC + "; + if ($limit > 0) { + $sql .= " LIMIT " . intval($limit); + } + + try { + $stmt = $pdo->prepare($sql); + $stmt->execute([$range[0], $range[1]]); + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } catch (\Exception $e) { + return []; + } + } + + /** + * 代理报表 + * + * @param string $dateFrom 开始日期 (Y-m-d) + * @param string $dateTo 结束日期 (Y-m-d) + * @return array + */ + public function getAgentStats(string $dateFrom, string $dateTo): array + { + $range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']; + + $agentStats = []; + $agents = $this->db->select('agents', '*', ['status' => 1]); + foreach ($agents as $ag) { + $playerIds = $this->db->select('users', 'id', ['agent_id' => $ag['id'], 'is_virtual' => 0]); + $agBet = 0; + $agWin = 0; + if (!empty($playerIds)) { + $agBet = $this->db->sum('bets', 'amount', ['user_id' => $playerIds, 'is_virtual' => 0, 'created_at[<>]' => $range]) ?: 0; + $agWin = $this->db->sum('bets', 'win_amount', ['user_id' => $playerIds, 'is_virtual' => 0, 'status' => 'win', 'created_at[<>]' => $range]) ?: 0; + } + $agComm = $this->db->sum('agent_commissions', 'commission', ['agent_id' => $ag['id'], 'created_at[<>]' => $range]) ?: 0; + $agentStats[] = [ + 'agent' => $ag, + 'user' => $this->db->get('users', ['username'], ['id' => $ag['user_id']]), + 'players' => count($playerIds), + 'bets' => $agBet, + 'wins' => $agWin, + 'commission' => $agComm, + 'profit' => $agBet - $agWin - $agComm, + ]; + } + + return $agentStats; + } +} diff --git a/App/Services/TransactionService.php b/App/Services/TransactionService.php new file mode 100644 index 0000000..9fdf4a2 --- /dev/null +++ b/App/Services/TransactionService.php @@ -0,0 +1,146 @@ +db = $db; + } + + /** + * 用户间转账 + * + * @param int $fromUserId 转出用户ID + * @param string $toUsername 收款用户名 + * @param float $amount 转账金额 + * @return array ['success' => bool, 'message' => string] + */ + public function transfer(int $fromUserId, string $toUsername, float $amount): array + { + $toUsername = trim($toUsername); + + if ($toUsername === '') { + return ['success' => false, 'message' => '请输入对方用户名']; + } + if ($amount <= 0) { + return ['success' => false, 'message' => '金额无效']; + } + + $fromUser = $this->db->get('users', '*', ['id' => $fromUserId]); + if (!$fromUser) { + return ['success' => false, 'message' => '用户不存在']; + } + if ($fromUser['balance'] < $amount) { + return ['success' => false, 'message' => '余额不足']; + } + + // 查找收款用户 + $toUser = $this->db->get('users', '*', ['username' => $toUsername]); + if (!$toUser) { + return ['success' => false, 'message' => '收款用户不存在']; + } + if ($toUser['id'] === $fromUser['id']) { + return ['success' => false, 'message' => '不能转给自己']; + } + + $this->db->medoo->pdo->beginTransaction(); + try { + $now = date('Y-m-d H:i:s'); + $fromBalanceBefore = floatval($fromUser['balance']); + $toBalanceBefore = floatval($toUser['balance']); + $fromBalanceAfter = $fromBalanceBefore - $amount; + $toBalanceAfter = $toBalanceBefore + $amount; + + // 扣款 + $this->db->update('users', ['balance' => $fromBalanceAfter, 'updated_at' => $now], ['id' => $fromUser['id']]); + // 加款 + $this->db->update('users', ['balance' => $toBalanceAfter, 'updated_at' => $now], ['id' => $toUser['id']]); + + // 转出流水 + $this->db->insert('transactions', [ + 'user_id' => $fromUser['id'], + 'type' => 'transfer_out', + 'amount' => -$amount, + 'balance_before' => $fromBalanceBefore, + 'balance_after' => $fromBalanceAfter, + 'description' => '转账给 ' . $toUser['username'], + 'created_at' => $now, + ]); + // 转入流水 + $this->db->insert('transactions', [ + 'user_id' => $toUser['id'], + 'type' => 'transfer_in', + 'amount' => $amount, + 'balance_before' => $toBalanceBefore, + 'balance_after' => $toBalanceAfter, + 'description' => '收到 ' . $fromUser['username'] . ' 的转账', + 'created_at' => $now, + ]); + + $this->db->medoo->pdo->commit(); + return ['success' => true, 'message' => '转账成功']; + } catch (\Exception $e) { + $this->db->medoo->pdo->rollBack(); + return ['success' => false, 'message' => '系统错误']; + } + } + + /** + * 充提申请 + * + * @param int $userId 用户ID + * @param string $type 类型: deposit|withdraw + * @param float $amount 金额 + * @param string $remark 备注 + * @return array ['success' => bool, 'message' => string] + */ + public function fundRequest(int $userId, string $type, float $amount, string $remark = ''): array + { + if (!in_array($type, ['deposit', 'withdraw'])) { + return ['success' => false, 'message' => '无效类型']; + } + if ($amount <= 0) { + return ['success' => false, 'message' => '金额无效']; + } + + $user = $this->db->get('users', '*', ['id' => $userId]); + if (!$user) { + return ['success' => false, 'message' => '用户不存在']; + } + + if ($type === 'withdraw') { + if ($user['balance'] < $amount) { + return ['success' => false, 'message' => '余额不足']; + } + $this->db->medoo->pdo->beginTransaction(); + try { + $this->db->update('users', ['balance[-]' => $amount], ['id' => $user['id']]); + $this->db->insert('fund_requests', [ + 'user_id' => $user['id'], + 'type' => 'withdraw', + 'amount' => $amount, + 'status' => 'pending', + 'remark' => $remark, + ]); + $this->db->medoo->pdo->commit(); + return ['success' => true, 'message' => '提现申请已提交']; + } catch (\Exception $e) { + $this->db->medoo->pdo->rollBack(); + return ['success' => false, 'message' => '系统错误']; + } + } + + // deposit - 记录申请 + $this->db->insert('fund_requests', [ + 'user_id' => $user['id'], + 'type' => 'deposit', + 'amount' => $amount, + 'status' => 'pending', + 'remark' => $remark, + ]); + return ['success' => true, 'message' => '充值申请已提交']; + } +} diff --git a/App/Views/.DS_Store b/App/Views/.DS_Store index 0cfcd92..3966231 100755 Binary files a/App/Views/.DS_Store and b/App/Views/.DS_Store differ diff --git a/App/Views/Admin/bots.php b/App/Views/Admin/bots.php new file mode 100644 index 0000000..41d3cc9 --- /dev/null +++ b/App/Views/Admin/bots.php @@ -0,0 +1,444 @@ +

+ + Bot 管理 +

+ + (int)($botItem['id'] ?? 0), + 'name' => (string)($botItem['name'] ?? ''), + ]; +} + +$gameOptions = []; +foreach (($games ?? []) as $gameItem) { + $gameOptions[] = [ + 'id' => (int)($gameItem['id'] ?? 0), + 'name' => (string)($gameItem['name'] ?? ''), + 'type' => (string)($gameItem['type'] ?? ''), + ]; +} + +$ruleOptions = []; +foreach (($rules ?? []) as $ruleItem) { + $ruleOptions[] = [ + 'id' => (int)($ruleItem['id'] ?? 0), + 'name' => (string)($ruleItem['name'] ?? ''), + 'rule_code' => (string)($ruleItem['rule_code'] ?? ''), + ]; +} + +$groupOptions = []; +foreach (($groups ?? []) as $groupItem) { + $countdown = ''; + if (!empty($groupItem['countdown_config'])) { + $decodedCountdown = json_decode((string)$groupItem['countdown_config'], true); + if (is_array($decodedCountdown)) { + $countdown = implode(',', array_map('intval', $decodedCountdown)); + } + } + + $groupOptions[] = [ + 'id' => (int)($groupItem['id'] ?? 0), + 'bot_id' => (int)($groupItem['bot_id'] ?? 0), + 'game_id' => (int)($groupItem['game_id'] ?? 0), + 'bet_format_rule_id' => (int)($groupItem['bet_format_rule_id'] ?? 0), + 'tg_group_id' => (string)($groupItem['tg_group_id'] ?? ''), + 'group_name' => (string)($groupItem['group_name'] ?? ''), + 'group_type' => (string)($groupItem['group_type'] ?? 'group'), + 'countdown_config' => $countdown, + ]; +} + +$userOptions = []; +foreach (($users ?? []) as $userItem) { + $userOptions[] = [ + 'id' => (int)($userItem['id'] ?? 0), + 'username' => (string)($userItem['username'] ?? ''), + 'balance' => number_format((float)($userItem['balance'] ?? 0), 2, '.', ''), + ]; +} +?> + + + +
+

Bot 实例数

+

群配置数

+

已绑总账号

+

启用群数

+

成员映射数

+

托号数量

+

推送日志数

+

推送成功数

+

倒计时群数

+

动画群数

+

API 请求数

+

签名通过数

+
+ +
+
+
+
+

Bot 实例总览

+

现在已支持新增、编辑、启停 Bot 实例。

+
+ Phase 6 进阶 +
+
+ + + + + + + + + + + + + + + +
实例运行模式群数状态更新时间操作
Key:
@
总群数:
启用下注:
尚未配置任何 Bot 实例。
+
+
+ +
+
+

新增 Bot 实例

+
+ + + + + + + + +
+
+
+

绑定群配置

+
+ + + + + + + + +
+ +
+
+
+

绑定群总账号

+
+ + + + + +
+
+
+
+ +
+
+

群配置总览

+
Bot / 游戏开关状态操作
/
倒计时:
下注:
提醒:
动画:
暂无群配置。
+
+
+

群总账号绑定

+
网站账号模式状态操作
UID / 余额
暂无群总账号绑定。
+
+
+ +
+
+
+

群成员映射

+
+ + + + + + + + + + +
+
+
+

成员映射列表

+
成员群 / 网站账号角色下注操作
UID / @
网站:
暂无成员映射。
+
+
+
+
+

托号管理

+
+ + + + + +
+
+
+

托号列表

+
托号备注状态操作
UID
暂无托号配置。
+
+
+
+ +
+
+
+

消息提醒 / 推送运营面

+

先把自动消息、开奖提醒、封盘倒计时、上下分提醒的运营可见性拉出来,便于对照 TG 成品补齐行为。

+
+ Push Ops +
+
+
+

推送类型汇总

+
+ + + + + + + + + + + + $summary): ?> + + + + + + + + + + 0; }))): ?> + + + +
类型总数成功失败待发/跳过
暂无推送汇总数据,待 TG runtime 写入。
+
+
+
+

群提醒能力快照

+
+ +
+
+
+
+
+
+ +
+
+ 下注成功 + 开奖提醒 + 封盘倒计时 + 开奖动画 +
+
+ +
暂无群配置,无法展示提醒快照。
+ +
+
+
+
+ +
+

最近推送日志

覆盖 countdown / bet_success / draw / credit / debit / system,先做观测面,再回填自动触发链。

+
类型期号 / 消息载荷摘要状态时间
期号:
TG Msg ID:
错误:
暂无推送日志。
+
+ +
+

Bot API 请求审计

对照成品 TG 机器人的发送队列与多账号路由,这里补入网站侧入站请求审计链,便于排查签名、限流、重复请求。

+
Bot / 群请求幂等 / IP签名 / 响应时间
/
IP:
HTTP
暂无 API 请求日志。
+
+ +
+

最近 Bot 下注同步

用于快速核对群消息→平台订单的同步状态。

+
用户期号金额 / 注数状态时间
/
暂无 Bot 同步记录。
+
+ + diff --git a/App/Views/Admin/follow_plans.php b/App/Views/Admin/follow_plans.php new file mode 100644 index 0000000..ece9a8e --- /dev/null +++ b/App/Views/Admin/follow_plans.php @@ -0,0 +1,152 @@ +

+ + 跟单计划管理 +

+ + +
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID计划名称类型名次建议金额总期数胜率跟单人数状态操作
+ + '大小','oe'=>'单双','dt'=>'龙虎','sum_bs'=>'冠亚大小'][$p['plan_type']] ?? $p['plan_type'] ?> + + + + % + + + + + + + + +
+
+ +

暂无跟单计划

+

点击上方按钮新建跟单计划

+
+
+
+
+ + + diff --git a/App/Views/Admin/fund_requests.php b/App/Views/Admin/fund_requests.php new file mode 100644 index 0000000..e14e91d --- /dev/null +++ b/App/Views/Admin/fund_requests.php @@ -0,0 +1,306 @@ +

+ + 充提审核 +

+ + +
+
+
+
+

待处理

+

+ +

+
+
+ +
+
+
+
+
+
+

今日充值申请

+

+ +

+
+
+ +
+
+
+
+
+
+

今日提现申请

+

+ +

+
+
+ +
+
+
+
+ + +
+
+
+ + +
+
+ + +
+ +
+
+ + +
+
+
+

充提申请列表

+

审核用户的充值和提现申请

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID用户钱包地址类型金额状态操作
+ # + +
+
ID:
+
+ + +
+ +
+ + 未绑定 + +
+ + 充值 + + 提现 + + + + + + + ['text' => '待处理', 'class' => 'bg-warning/10 text-warning'], + 'approved' => ['text' => '已通过', 'class' => 'bg-success/10 text-success'], + 'rejected' => ['text' => '已拒绝', 'class' => 'bg-danger/10 text-danger'], + ]; + $statusInfo = $statusMap[$status] ?? ['text' => $status, 'class' => 'bg-gray-100 text-gray-700']; + ?> + + + + + +
+ + +
+ + 已处理 + +
+
+ +

暂无充提申请

+

还没有任何充提申请记录。

+
+
+
+
+ + + diff --git a/App/Views/Admin/index.php b/App/Views/Admin/index.php index 066407e..fe93e8a 100755 --- a/App/Views/Admin/index.php +++ b/App/Views/Admin/index.php @@ -36,6 +36,13 @@ count('fund_requests', ['status' => 'pending']); + } catch (\Throwable $e) { + $pendingFundCount = 0; + } ?> 管理后台 @@ -116,6 +123,11 @@ 投注记录 + + + 充提审核 + + 财务管理 @@ -136,6 +148,14 @@ 数据报表 + + + 跟单计划 + + + + Bot 管理 + 系统设置 @@ -265,7 +285,7 @@ @@ -274,6 +294,22 @@ + diff --git a/App/Views/Admin/reports.php b/App/Views/Admin/reports.php index a820f37..f62dbdc 100644 --- a/App/Views/Admin/reports.php +++ b/App/Views/Admin/reports.php @@ -1,60 +1,403 @@ -
-

📊 数据报表

+

+ + 数据报表 +

- -
- - - + +
+
+
+ + +
+
+ + +
+ + +
- -
-
总投注
-
总中奖
-
平台盈利
-
佣金
-
总充值
-
总提现
-
总用户
-
新增用户
+ +
+
+
+
+

总投注

+

+
+
+ +
+
+
+
+
+
+

总派彩

+

+
+
+ +
+
+
+
+
+
+

总利润

+

+
+
+ +
+
+
+
+
+
+

利润率

+ 10) $prColor = 'text-success'; + elseif ($profitRate < 0) $prColor = 'text-danger'; + ?> +

%

+
+
+ +
+
+
+
+
+
+

总充值

+

+
+
+ +
+
+
+
+
+
+

总提现

+

+
+
+ +
+
+
+
+
+
+

活跃用户

+

+
+
+ +
+
+
+
+
+
+

新增用户

+

+
+
+ +
+
+
- -
-

每日明细

- - - - - - - - - - - - - -
日期投注中奖盈利充值提现
+ +
+

+ 日明细统计 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
日期投注额派彩额利润充值提现笔数胜率
+ = 0 ? '+' : '' ?> + + + % + +
+
+ +

暂无数据

+

选定日期范围内没有投注记录

+
+
+
+
+ + +
+

+ 用户投注排行(前20) +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
排名用户名投注额派彩额平台盈亏笔数
+ + + + + + + + + = 0 ? '+' : '' ?> +
+
+ +

暂无数据

+

选定日期范围内没有用户投注

+
+
+
+
- -
-

代理报表

- - - - - - - - - - - - - -
代理玩家数投注中奖佣金盈利
- + +
+

+ 代理报表 +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
代理玩家数投注额中奖额佣金盈利
+ = 0 ? '+' : '' ?> +
+
+ + + +
+

+ 用户输赢明细 +

+
+ + + + + + + + + + + + + + + + $us): + $uProfit = $us['total_bet'] - $us['total_win']; + $uWinRate = $us['bet_count'] > 0 ? round($us['win_count'] / $us['bet_count'] * 100, 1) : 0; + ?> + + + + + + + + + + + + + + + + + + + + +
用户名当前余额总投注总派彩平台盈亏笔数胜率日明细
+ = 0 ? '+' : '' ?> + + + % + + + +
+
+ +

暂无数据

+

选定日期范围内没有用户投注记录

+
+
+
+
+ + + diff --git a/App/Views/Admin/settings.php b/App/Views/Admin/settings.php index 7a9d9dc..a6c4546 100755 --- a/App/Views/Admin/settings.php +++ b/App/Views/Admin/settings.php @@ -99,6 +99,24 @@
+ +
+

+ + 客服设置 +

+
+
+ + +

用户充值时将跳转至此链接联系客服,支持 Telegram、WhatsApp 等链接

+
+
+
+
+ 说明:0=取最大利润(原逻辑),15=平台每期目标盈利15% +
+
+ + diff --git a/App/Views/Web/home.php b/App/Views/Web/home.php index 3563c3f..9665ed7 100644 --- a/App/Views/Web/home.php +++ b/App/Views/Web/home.php @@ -44,15 +44,15 @@
- - - @@ -93,5 +93,45 @@
+ diff --git a/App/Views/Web/index.php b/App/Views/Web/index.php index b5f0e48..d2cb42c 100755 --- a/App/Views/Web/index.php +++ b/App/Views/Web/index.php @@ -3,7 +3,7 @@ -<?= htmlspecialchars($settings['site_title'] ?? 'PK10 Speed Racing') ?> +<?= htmlspecialchars($settings['site_title'] ?? 'F1 Racing') ?> diff --git a/App/Views/Web/pk10.php b/App/Views/Web/pk10.php index 9434be0..f403927 100644 --- a/App/Views/Web/pk10.php +++ b/App/Views/Web/pk10.php @@ -1,36 +1,250 @@ - +select('game_odds','*',['game_id'=>$gameId]); +$oddsMap = []; +foreach(($_oddsRaw??[]) as $_o){ $oddsMap[$_o['type'].'_'.$_o['target']]=(float)$_o['odds']; } +unset($_oddsDb,$_oddsRaw,$_o); +?> <?=$t('pk10_title')?> + - +
@@ -117,33 +353,191 @@ - -
-
-
-
- -
-
- # -
-
- - -
-
- + + + + + + +
+
+ +
+ +
- + +
+
+ +
+ F1赛车开奖视频 + +
+ +
+ +
+
+ F1赛车 + 期数:--- + 🔊 + +
+
---
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+ + + + + + + + +
+ +
+
00:00
+
+ + + +
+
+ +
+
+ +
+
+
+ 2nd + 2nd +
+
+ 1st + 1st +
+
+ 3rd + 3rd +
+
+
+ ['#f1c40f','#d4ac0d'],2 =>['#3498db','#2980b9'],3 =>['#555555','#333333'], + 4 =>['#e67e22','#ca6f1e'],5 =>['#2ecc71','#27ae60'],6 =>['#2c3e99','#1a2a6c'], + 7 =>['#999999','#777777'],8 =>['#e74c3c','#c0392b'],9 =>['#cc2222','#991111'], + 10=>['#27ae60','#1e8449'] + ]; + for($i=1;$i<=10;$i++): $c=$carColors[$i]; ?> +
+
+ # +
+
+
+
+
+ Car <?=$i?> + + +
+
+ +
+ +
+ 期号:--- + 冠亚和 - - - + 龙虎 --- +
+
+
+
+ +
-
+
+
+
+
@@ -151,30 +545,31 @@
-
+ +
-
2.2
-
1.79
+
+
-
1.79
-
2.2
+
+
$r]); ?>
-
1.995
-
1.995
+
+
-
1.995
-
1.995
+
+
-
1.995
-
1.995
+
+
@@ -186,9 +581,10 @@
-
+ +
-
9.80
+
@@ -200,40 +596,103 @@ + + + + + + + + +
- -
-
10
-
50
-
100
-
500
-
-
- -
-
: 0 x0
- - + +
+ +
+ 已选 0 注 + +
+ +
+
50
+
100
+
200
+
500
+
1000
+
+ +
+
+ +
+ +
预设
+
+ +
+ + +
@@ -266,85 +725,83 @@ foreach($sumOdds as $v=>$o): ?> + + + + + + + + + + + + + + diff --git a/App/Views/Web/pk10_stats.php b/App/Views/Web/pk10_stats.php index 35e4029..e530133 100644 --- a/App/Views/Web/pk10_stats.php +++ b/App/Views/Web/pk10_stats.php @@ -88,7 +88,7 @@ for($i=1;$i<=10;$i++) $rankLabels[$i]=$i<=2?($i==1?$t('champion'):$t('runner_up' diff --git a/App/Views/Web/report.php b/App/Views/Web/report.php new file mode 100644 index 0000000..1f87fe3 --- /dev/null +++ b/App/Views/Web/report.php @@ -0,0 +1,138 @@ + + + +<?=$t('report_query')?> + + + + + +
+ +
+ + + + + +
+ + + +
+
+
+
0.00
+
+
+
+
0.00
+
+
+
+
0.00
+
+
+ + + + + + + + + + + + + + + +
+ + + diff --git a/Db/migration_bot_push_claim.sql b/Db/migration_bot_push_claim.sql new file mode 100644 index 0000000..296bde5 --- /dev/null +++ b/Db/migration_bot_push_claim.sql @@ -0,0 +1,7 @@ +ALTER TABLE `bot_push_logs` + ADD COLUMN `claim_token` varchar(64) DEFAULT NULL COMMENT '推送领取令牌' AFTER `error_message`, + ADD COLUMN `claimed_at` datetime DEFAULT NULL COMMENT '推送领取时间' AFTER `claim_token`; + +ALTER TABLE `bot_push_logs` + ADD KEY `idx_claim_token` (`claim_token`), + ADD KEY `idx_claimed_at` (`claimed_at`); diff --git a/Db/migration_follow_plans.sql b/Db/migration_follow_plans.sql new file mode 100644 index 0000000..834a1e7 --- /dev/null +++ b/Db/migration_follow_plans.sql @@ -0,0 +1,47 @@ +-- 跟单计划表 +CREATE TABLE IF NOT EXISTS `follow_plans` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `game_id` int(11) NOT NULL DEFAULT 1, + `name` varchar(100) NOT NULL COMMENT '计划名称,如:大小计划、单双计划', + `plan_type` varchar(30) NOT NULL COMMENT '计划类型:bs(大小), oe(单双), dt(龙虎), sum_bs(冠亚大小)', + `target_rank` int(11) NOT NULL DEFAULT 1 COMMENT '目标名次 1-10', + `strategy` varchar(30) NOT NULL DEFAULT 'follow' COMMENT '策略:follow(跟投), reverse(反投)', + `bet_amount` decimal(15,2) NOT NULL DEFAULT 100.00 COMMENT '每期建议投注额', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1=启用 0=禁用', + `created_by` int(11) DEFAULT NULL COMMENT '创建管理员ID', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_game_status` (`game_id`, `status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 跟单计划每期推荐记录 +CREATE TABLE IF NOT EXISTS `follow_plan_records` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `plan_id` int(11) NOT NULL, + `period_id` int(11) NOT NULL, + `period_number` varchar(50) NOT NULL, + `recommend_value` varchar(20) NOT NULL COMMENT '推荐值:big/small/odd/even/dragon/tiger', + `result_value` varchar(20) DEFAULT NULL COMMENT '实际结果', + `is_win` tinyint(1) DEFAULT NULL COMMENT '1=赢 0=输 NULL=未开奖', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_plan_period` (`plan_id`, `period_id`), + KEY `idx_plan_id` (`plan_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 用户跟单记录 +CREATE TABLE IF NOT EXISTS `user_follow_plans` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `plan_id` int(11) NOT NULL, + `is_active` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否正在跟单', + `total_bets` int(11) NOT NULL DEFAULT 0 COMMENT '总跟注期数', + `total_wins` int(11) NOT NULL DEFAULT 0 COMMENT '总赢期数', + `total_profit` decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT '累计盈亏', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_plan` (`user_id`, `plan_id`), + KEY `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/Db/migration_fund_requests.sql b/Db/migration_fund_requests.sql new file mode 100644 index 0000000..e2b38a5 --- /dev/null +++ b/Db/migration_fund_requests.sql @@ -0,0 +1,18 @@ +-- 充提申请表 +CREATE TABLE IF NOT EXISTS `fund_requests` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `type` enum('deposit','withdraw') NOT NULL COMMENT '充值/提现', + `amount` decimal(15,2) NOT NULL, + `status` enum('pending','approved','rejected') DEFAULT 'pending', + `remark` text COMMENT '用户备注', + `admin_remark` text COMMENT '管理员备注', + `operator_id` int(11) DEFAULT NULL COMMENT '审核人ID', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP, + `processed_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_status` (`status`), + KEY `idx_type_status` (`type`, `status`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='充提申请记录'; diff --git a/Db/migration_tg_bot_integration.sql b/Db/migration_tg_bot_integration.sql new file mode 100644 index 0000000..c2fee1c --- /dev/null +++ b/Db/migration_tg_bot_integration.sql @@ -0,0 +1,226 @@ +-- ========================================= +-- TG Bot Integration - Database Migration v1 +-- 目标:为 Telegram 机器人接入提供独立业务层,不直接侵入核心下注/账务表 +-- 兼容:MySQL 5.7+ +-- ========================================= + +START TRANSACTION; + +-- 1) 机器人实例表 +CREATE TABLE IF NOT EXISTS `bot_instances` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL COMMENT '机器人名称', + `bot_token` varchar(255) NOT NULL COMMENT 'Telegram Bot Token', + `bot_username` varchar(64) DEFAULT NULL COMMENT '机器人用户名', + `bot_key` varchar(64) NOT NULL COMMENT '公开访问标识,用于X-Bot-Key', + `bot_secret` varchar(128) NOT NULL COMMENT 'Bot API 共享密钥/HMAC secret', + `webhook_url` varchar(255) DEFAULT NULL COMMENT 'Webhook 地址(可为空)', + `run_mode` enum('polling','webhook') NOT NULL DEFAULT 'polling', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1启用 0停用', + `remark` varchar(255) DEFAULT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_bot_name` (`name`), + UNIQUE KEY `uk_bot_key` (`bot_key`), + UNIQUE KEY `uk_bot_username` (`bot_username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TG机器人实例'; + +-- 2) 下注格式规则表(每群可配置不同格式) +CREATE TABLE IF NOT EXISTS `bot_bet_format_rules` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `rule_code` varchar(64) NOT NULL COMMENT '规则编码,如 pk10_v1', + `name` varchar(64) NOT NULL COMMENT '规则名称', + `game_id` int(11) NOT NULL COMMENT '对应游戏ID', + `parser_type` varchar(32) NOT NULL DEFAULT 'regex' COMMENT 'regex/json/custom', + `rule_config` json DEFAULT NULL COMMENT '解析规则JSON', + `example_text` text COMMENT '示例文本', + `status` tinyint(1) NOT NULL DEFAULT 1, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_rule_code` (`rule_code`), + KEY `idx_game_id` (`game_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TG下注格式规则'; + +-- 3) 群配置表 +CREATE TABLE IF NOT EXISTS `bot_groups` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `bot_id` int(11) NOT NULL COMMENT '关联 bot_instances.id', + `tg_group_id` varchar(32) NOT NULL COMMENT 'Telegram群ID,保留-100前缀', + `group_name` varchar(128) NOT NULL COMMENT '群名称快照', + `group_type` varchar(16) NOT NULL DEFAULT 'group' COMMENT 'group/supergroup/channel', + `game_id` int(11) NOT NULL COMMENT '群默认游戏', + `bet_format_rule_id` int(11) DEFAULT NULL COMMENT '关联 bot_bet_format_rules.id', + `remind_bet_success` tinyint(1) NOT NULL DEFAULT 1 COMMENT '下注成功提醒', + `remind_draw_result` tinyint(1) NOT NULL DEFAULT 1 COMMENT '开奖提醒', + `remind_close_countdown` tinyint(1) NOT NULL DEFAULT 1 COMMENT '封盘倒计时提醒', + `countdown_config` json DEFAULT NULL COMMENT '如 [60,30,10]', + `animation_enabled` tinyint(1) NOT NULL DEFAULT 0 COMMENT '开奖动画/截图开关', + `bet_enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否允许群内下注同步', + `status` tinyint(1) NOT NULL DEFAULT 1, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tg_group_id` (`tg_group_id`), + KEY `idx_bot_id` (`bot_id`), + KEY `idx_game_id` (`game_id`), + KEY `idx_rule_id` (`bet_format_rule_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TG群配置'; + +-- 4) 群总账号绑定表 +CREATE TABLE IF NOT EXISTS `bot_group_wallets` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL COMMENT '关联 bot_groups.id', + `platform_user_id` int(11) NOT NULL COMMENT '网站总账号 users.id', + `platform_username_snapshot` varchar(64) DEFAULT NULL COMMENT '用户名快照', + `wallet_mode` enum('master_pool','per_member') NOT NULL DEFAULT 'master_pool' COMMENT '首发建议 master_pool', + `status` tinyint(1) NOT NULL DEFAULT 1, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_group_wallet` (`group_id`), + KEY `idx_platform_user_id` (`platform_user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='群绑定的网站总账号'; + +-- 5) 群成员映射表 +CREATE TABLE IF NOT EXISTS `bot_group_members` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `tg_user_id` varchar(32) NOT NULL, + `tg_username` varchar(64) DEFAULT NULL, + `tg_nickname` varchar(128) DEFAULT NULL, + `platform_user_id` int(11) DEFAULT NULL COMMENT '如后续启用 per_member 模式使用', + `role` enum('member','admin','shill') NOT NULL DEFAULT 'member', + `bet_enabled` tinyint(1) NOT NULL DEFAULT 1, + `last_seen_at` datetime DEFAULT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_group_tg_user` (`group_id`,`tg_user_id`), + KEY `idx_platform_user_id` (`platform_user_id`), + KEY `idx_role` (`role`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='群成员映射'; + +-- 6) 托号表(冗余显式表,便于运营查询与统计排除) +CREATE TABLE IF NOT EXISTS `bot_shills` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `tg_user_id` varchar(32) NOT NULL, + `note` varchar(255) DEFAULT NULL, + `enabled` tinyint(1) NOT NULL DEFAULT 1, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_group_shill` (`group_id`,`tg_user_id`), + KEY `idx_enabled` (`enabled`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='托号配置'; + +-- 7) 消息下注订单表 +CREATE TABLE IF NOT EXISTS `bot_bet_orders` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `tg_chat_id` varchar(32) NOT NULL, + `tg_message_id` bigint(20) NOT NULL, + `tg_user_id` varchar(32) NOT NULL, + `tg_username` varchar(64) DEFAULT NULL, + `platform_user_id` int(11) NOT NULL COMMENT '首发取群总账号', + `game_id` int(11) NOT NULL, + `period_number` varchar(50) NOT NULL, + `raw_text` text NOT NULL COMMENT '原始Telegram消息', + `parsed_payload_json` json DEFAULT NULL COMMENT '解析后的标准bets结构', + `bet_amount_total` decimal(15,2) NOT NULL DEFAULT '0.00', + `accepted_bet_count` int(11) NOT NULL DEFAULT 0, + `platform_order_ref` varchar(64) DEFAULT NULL COMMENT '平台侧订单引用,可为空后续回填', + `idempotency_key` varchar(128) NOT NULL COMMENT '如 tg:-100xxx:12345', + `sync_status` enum('pending','success','failed','duplicate') NOT NULL DEFAULT 'pending', + `sync_error` varchar(255) DEFAULT NULL, + `is_shill` tinyint(1) NOT NULL DEFAULT 0 COMMENT '下注者是否托号快照', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_idempotency_key` (`idempotency_key`), + UNIQUE KEY `uk_chat_message` (`tg_chat_id`,`tg_message_id`), + KEY `idx_group_id` (`group_id`), + KEY `idx_platform_user_id` (`platform_user_id`), + KEY `idx_period_number` (`period_number`), + KEY `idx_sync_status` (`sync_status`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TG消息下注订单'; + +-- 8) Bot API 请求日志(审计+重放排查) +CREATE TABLE IF NOT EXISTS `bot_api_request_logs` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `bot_id` int(11) DEFAULT NULL, + `group_id` int(11) DEFAULT NULL, + `request_uri` varchar(255) NOT NULL, + `http_method` varchar(10) NOT NULL, + `idempotency_key` varchar(128) DEFAULT NULL, + `request_body` mediumtext, + `response_body` mediumtext, + `response_code` int(11) DEFAULT NULL, + `client_ip` varchar(64) DEFAULT NULL, + `signature_ok` tinyint(1) NOT NULL DEFAULT 0, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_bot_id` (`bot_id`), + KEY `idx_group_id` (`group_id`), + KEY `idx_idempotency_key` (`idempotency_key`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Bot API请求日志'; + +-- 9) 推送日志(开奖/封盘/下注成功/上下分) +CREATE TABLE IF NOT EXISTS `bot_push_logs` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `period_number` varchar(50) DEFAULT NULL, + `push_type` enum('countdown','bet_success','draw','credit','debit','system') NOT NULL, + `payload_json` json DEFAULT NULL, + `tg_message_id` bigint(20) DEFAULT NULL, + `status` enum('pending','success','failed','skipped') NOT NULL DEFAULT 'pending', + `error_message` varchar(255) DEFAULT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_group_id` (`group_id`), + KEY `idx_period_number` (`period_number`), + KEY `idx_push_type` (`push_type`), + KEY `idx_status` (`status`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Bot推送日志'; + +-- 10) 群上下分申请/记录表 +CREATE TABLE IF NOT EXISTS `bot_fund_requests` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `tg_user_id` varchar(32) DEFAULT NULL, + `platform_user_id` int(11) NOT NULL, + `request_type` enum('credit','debit') NOT NULL, + `amount` decimal(15,2) NOT NULL, + `reason` varchar(255) DEFAULT NULL, + `idempotency_key` varchar(128) NOT NULL, + `transaction_id` int(11) DEFAULT NULL COMMENT '关联 transactions.id', + `status` enum('pending','approved','rejected','failed') NOT NULL DEFAULT 'pending', + `operator_id` int(11) DEFAULT NULL COMMENT '后台审核人/系统操作者', + `operator_type` varchar(16) DEFAULT NULL COMMENT 'admin/employee/system', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_fund_idempotency_key` (`idempotency_key`), + KEY `idx_group_id` (`group_id`), + KEY `idx_platform_user_id` (`platform_user_id`), + KEY `idx_transaction_id` (`transaction_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Bot上下分记录'; + +-- 11) 为 transactions 增加 Bot 来源标识(不破坏现有逻辑) +ALTER TABLE `transactions` + ADD COLUMN `source` varchar(20) DEFAULT NULL COMMENT '来源:web/admin/employee/bot' AFTER `operator_type`, + ADD COLUMN `source_ref` varchar(64) DEFAULT NULL COMMENT '来源引用:bot订单/请求号' AFTER `source`; + +-- 12) 为 bets 增加 Bot 来源引用(便于订单追踪) +ALTER TABLE `bets` + ADD COLUMN `source` varchar(20) DEFAULT NULL COMMENT '来源:web/bot' AFTER `agent_id`, + ADD COLUMN `source_ref` varchar(64) DEFAULT NULL COMMENT '来源引用:idempotency_key/order_ref' AFTER `source`; + +COMMIT; diff --git a/Lang/bn.php b/Lang/bn.php index d3ab2dc..c5c181f 100644 --- a/Lang/bn.php +++ b/Lang/bn.php @@ -1,6 +1,6 @@ 'PK10 স্পিড রেসিং', + 'app_name' => 'F1 রেসিং', 'login' => 'লগইন', 'register' => 'নিবন্ধন', 'logout' => 'লগআউট', 'username' => 'ব্যবহারকারীর নাম', 'password' => 'পাসওয়ার্ড', 'email' => 'ইমেইল', 'confirm_password' => 'পাসওয়ার্ড নিশ্চিত করুন', 'submit' => 'জমা দিন', 'cancel' => 'বাতিল', @@ -21,7 +21,7 @@ return [ 'login_failed' => 'ভুল ব্যবহারকারীর নাম বা পাসওয়ার্ড', 'account_disabled' => 'অ্যাকাউন্ট নিষ্ক্রিয় করা হয়েছে', 'email_not_verified' => 'অনুগ্রহ করে প্রথমে ইমেইল যাচাই করুন', - 'pk10_title' => 'PK10 স্পিড রেসিং', 'period' => 'পিরিয়ড', + 'pk10_title' => 'F1 রেসিং', 'period' => 'পিরিয়ড', 'current_period' => 'বর্তমান পিরিয়ড', 'last_result' => 'আগের ফলাফল', 'history' => 'ইতিহাস', 'place_bet' => 'বাজি ধরুন', 'my_bets' => 'আমার বাজি', 'betting' => 'বাজি চলছে', 'closed' => 'বন্ধ', 'drawing' => 'ড্র হচ্ছে', @@ -68,4 +68,6 @@ return [ 'sum_bs_big' => 'যোগফল বড়', 'sum_bs_small' => 'যোগফল ছোট', 'sum_bs_odd' => 'যোগফল বিজোড়', 'sum_bs_even' => 'যোগফল জোড়', 'notify_bell' => 'বিজ্ঞপ্তি', + 'quick_hint' => 'ব্যাচ বেট যোগ করতে ট্যাপ করুন', + 'bets_added' => ' টি বাজি যোগ হয়েছে', ]; diff --git a/Lang/en.php b/Lang/en.php index 7c09aed..8f46a50 100644 --- a/Lang/en.php +++ b/Lang/en.php @@ -1,7 +1,7 @@ 'PK10 Speed Racing', + 'app_name' => 'F1 Racing', 'login' => 'Login', 'register' => 'Register', 'logout' => 'Logout', @@ -54,7 +54,7 @@ return [ 'email_not_verified' => 'Please verify your email first', // Game - 'pk10_title' => 'PK10 Speed Racing', + 'pk10_title' => 'F1 Racing', 'period' => 'Period', 'current_period' => 'Current Period', 'last_result' => 'Last Result', @@ -157,4 +157,40 @@ return [ 'sum_bs_big' => 'Sum Big', 'sum_bs_small' => 'Sum Small', 'sum_bs_odd' => 'Sum Odd', 'sum_bs_even' => 'Sum Even', 'notify_bell' => 'Notify', + 'coming_soon' => 'Coming Soon', + 'deposit_confirm_msg' => 'You will be redirected to customer service. Please contact them for deposit address.', + 'deposit_amount' => 'Enter deposit amount', + 'deposit_submitted' => 'Deposit request submitted. Please contact customer service to complete payment.', + 'withdraw_amount' => 'Enter withdrawal amount', + 'withdraw_submitted' => 'Withdrawal request submitted, pending review', + 'contact_admin' => 'Please contact admin to configure service link', + 'transfer_to' => 'Enter recipient username', + 'transfer_amount' => 'Enter transfer amount', + 'transfer_confirm' => 'Confirm transfer to', + 'transfer_success' => 'Transfer successful', + 'amount' => 'Amount', + 'fund_request_history' => 'Deposit/Withdraw History', + 'fund_status_pending' => 'Pending', + 'fund_status_approved' => 'Approved', + 'fund_status_rejected' => 'Rejected', + 'quick_hint' => 'Tap to add batch bets', + 'bets_added' => ' bets added', + + // Report & Follow Plan + 'report_query' => 'Reports', + 'follow_plan' => 'Follow Plan', + 'today' => 'Today', + 'yesterday' => 'Yesterday', + 'last_7_days' => 'Last 7 Days', + 'last_30_days' => 'Last 30 Days', + 'custom_date' => 'Custom', + 'bet_type' => 'Type', + 'bet_amount' => 'Bet', + 'effective_flow' => 'Valid Flow', + 'rebate_amount' => 'Rebate', + 'win_loss' => 'Win/Loss', + 'total_win_rate' => 'Win Rate', + 'total_profit' => 'Total P&L', + 'no_follow_plan' => 'Sorry, you have no follow plans at the moment', + 'bs_plan' => 'Big/Small Plan', ]; diff --git a/Lang/fil.php b/Lang/fil.php index a56f883..a863595 100644 --- a/Lang/fil.php +++ b/Lang/fil.php @@ -1,6 +1,6 @@ 'PK10 Speed Racing', + 'app_name' => 'F1 Racing', 'login' => 'Mag-login', 'register' => 'Magrehistro', 'logout' => 'Mag-logout', 'username' => 'Username', 'password' => 'Password', 'email' => 'Email', 'confirm_password' => 'Kumpirmahin ang Password', 'submit' => 'Isumite', 'cancel' => 'Kanselahin', @@ -21,7 +21,7 @@ return [ 'login_failed' => 'Mali ang username o password', 'account_disabled' => 'Na-disable ang account', 'email_not_verified' => 'Paki-verify muna ang iyong email', - 'pk10_title' => 'PK10 Speed Racing', 'period' => 'Period', + 'pk10_title' => 'F1 Racing', 'period' => 'Period', 'current_period' => 'Kasalukuyang Period', 'last_result' => 'Nakaraang Resulta', 'history' => 'Kasaysayan', 'place_bet' => 'Tumaya', 'my_bets' => 'Mga Taya Ko', 'betting' => 'Tumataya', 'closed' => 'Sarado', 'drawing' => 'Hinuhugot', @@ -68,4 +68,6 @@ return [ 'sum_bs_big' => 'Kabuuang Malaki', 'sum_bs_small' => 'Kabuuang Maliit', 'sum_bs_odd' => 'Kabuuang Odd', 'sum_bs_even' => 'Kabuuang Even', 'notify_bell' => 'Abiso', + 'quick_hint' => 'I-tap para magdagdag ng batch na taya', + 'bets_added' => ' taya naidagdag', ]; diff --git a/Lang/ms.php b/Lang/ms.php index 7630d17..17faa43 100644 --- a/Lang/ms.php +++ b/Lang/ms.php @@ -1,6 +1,6 @@ 'PK10 Lumba Laju', + 'app_name' => 'F1 Racing', 'login' => 'Log Masuk', 'register' => 'Daftar', 'logout' => 'Log Keluar', 'username' => 'Nama Pengguna', 'password' => 'Kata Laluan', 'email' => 'Emel', 'confirm_password' => 'Sahkan Kata Laluan', 'submit' => 'Hantar', 'cancel' => 'Batal', @@ -21,7 +21,7 @@ return [ 'login_failed' => 'Nama pengguna atau kata laluan salah', 'account_disabled' => 'Akaun telah dilumpuhkan', 'email_not_verified' => 'Sila sahkan emel anda dahulu', - 'pk10_title' => 'PK10 Lumba Laju', 'period' => 'Tempoh', + 'pk10_title' => 'F1 Racing', 'period' => 'Tempoh', 'current_period' => 'Tempoh Semasa', 'last_result' => 'Keputusan Lepas', 'history' => 'Sejarah', 'place_bet' => 'Letak Taruhan', 'my_bets' => 'Taruhan Saya', 'betting' => 'Bertaruh', 'closed' => 'Ditutup', 'drawing' => 'Cabutan', @@ -68,4 +68,6 @@ return [ 'sum_bs_big' => 'Jumlah Besar', 'sum_bs_small' => 'Jumlah Kecil', 'sum_bs_odd' => 'Jumlah Ganjil', 'sum_bs_even' => 'Jumlah Genap', 'notify_bell' => 'Pemberitahuan', + 'quick_hint' => 'Ketik untuk tambah pertaruhan pukal', + 'bets_added' => ' pertaruhan ditambah', ]; diff --git a/Lang/th.php b/Lang/th.php index b90fe25..21e8311 100644 --- a/Lang/th.php +++ b/Lang/th.php @@ -1,6 +1,6 @@ 'PK10 แข่งรถความเร็ว', + 'app_name' => 'F1 แข่งรถ', 'login' => 'เข้าสู่ระบบ', 'register' => 'สมัครสมาชิก', 'logout' => 'ออกจากระบบ', 'username' => 'ชื่อผู้ใช้', 'password' => 'รหัสผ่าน', 'email' => 'อีเมล', 'confirm_password' => 'ยืนยันรหัสผ่าน', 'submit' => 'ส่ง', 'cancel' => 'ยกเลิก', @@ -22,7 +22,7 @@ return [ 'login_failed' => 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', 'account_disabled' => 'บัญชีถูกปิดใช้งาน', 'email_not_verified' => 'กรุณายืนยันอีเมลก่อน', - 'pk10_title' => 'PK10 แข่งรถความเร็ว', 'period' => 'งวด', + 'pk10_title' => 'F1 แข่งรถ', 'period' => 'งวด', 'current_period' => 'งวดปัจจุบัน', 'last_result' => 'ผลก่อนหน้า', 'history' => 'ประวัติ', 'place_bet' => 'วางเดิมพัน', 'my_bets' => 'เดิมพันของฉัน', 'betting' => 'กำลังเดิมพัน', 'closed' => 'ปิดรับ', 'drawing' => 'กำลังออกผล', @@ -68,4 +68,6 @@ return [ 'sum_bs_big' => 'รวมใหญ่', 'sum_bs_small' => 'รวมเล็ก', 'sum_bs_odd' => 'รวมคี่', 'sum_bs_even' => 'รวมคู่', 'notify_bell' => 'แจ้งเตือน', + 'quick_hint' => 'แตะเพื่อเพิ่มเดิมพันเป็นชุด', + 'bets_added' => ' รายการเพิ่มแล้ว', ]; diff --git a/Lang/vi.php b/Lang/vi.php index bdc6ed7..391cc41 100644 --- a/Lang/vi.php +++ b/Lang/vi.php @@ -1,6 +1,6 @@ 'PK10 Đua Xe Tốc Độ', + 'app_name' => 'F1 Đua Xe', 'login' => 'Đăng nhập', 'register' => 'Đăng ký', 'logout' => 'Đăng xuất', 'username' => 'Tên đăng nhập', 'password' => 'Mật khẩu', 'email' => 'Email', 'confirm_password' => 'Xác nhận mật khẩu', 'submit' => 'Gửi', 'cancel' => 'Hủy', @@ -22,7 +22,7 @@ return [ 'login_failed' => 'Sai tên đăng nhập hoặc mật khẩu', 'account_disabled' => 'Tài khoản đã bị vô hiệu hóa', 'email_not_verified' => 'Vui lòng xác minh email trước', - 'pk10_title' => 'PK10 Đua Xe Tốc Độ', 'period' => 'Kỳ', + 'pk10_title' => 'F1 Đua Xe', 'period' => 'Kỳ', 'current_period' => 'Kỳ hiện tại', 'last_result' => 'Kết quả trước', 'history' => 'Lịch sử', 'place_bet' => 'Đặt cược', 'my_bets' => 'Cược của tôi', 'betting' => 'Đang cược', 'closed' => 'Đã đóng', 'drawing' => 'Đang quay', @@ -69,4 +69,6 @@ return [ 'sum_bs_big' => 'Tổng tài', 'sum_bs_small' => 'Tổng xỉu', 'sum_bs_odd' => 'Tổng lẻ', 'sum_bs_even' => 'Tổng chẵn', 'notify_bell' => 'Thông báo', + 'quick_hint' => 'Nhấn để thêm cược hàng loạt', + 'bets_added' => ' cược đã thêm', ]; diff --git a/Lang/zh.php b/Lang/zh.php index 1eed7db..f97ab5e 100644 --- a/Lang/zh.php +++ b/Lang/zh.php @@ -1,6 +1,6 @@ 'PK10 极速赛车', + 'app_name' => 'F1 赛车', 'login' => '登录', 'register' => '注册', 'logout' => '退出', 'username' => '用户名', 'password' => '密码', 'email' => '邮箱', 'confirm_password' => '确认密码', 'submit' => '提交', 'cancel' => '取消', @@ -21,7 +21,7 @@ return [ 'register_success' => '注册成功,请验证邮箱', 'login_failed' => '用户名或密码错误', 'account_disabled' => '账户已被禁用', 'email_not_verified' => '请先验证邮箱', - 'pk10_title' => 'PK10 极速赛车', 'period' => '期号', + 'pk10_title' => 'F1 赛车', 'period' => '期号', 'current_period' => '当前期', 'last_result' => '上期结果', 'history' => '历史记录', 'place_bet' => '下注', 'my_bets' => '我的投注', 'betting' => '投注中', 'closed' => '已封盘', 'drawing' => '开奖中', @@ -71,4 +71,40 @@ return [ 'sum_bs_big' => '冠亚大', 'sum_bs_small' => '冠亚小', 'sum_bs_odd' => '冠亚单', 'sum_bs_even' => '冠亚双', 'notify_bell' => '提醒', + 'coming_soon' => '即将开放', + 'deposit_confirm_msg' => '即将跳转到客服页面,请联系客服获取充值地址', + 'deposit_amount' => '请输入充值金额', + 'deposit_submitted' => '充值申请已提交,请联系客服完成付款', + 'withdraw_amount' => '请输入提现金额', + 'withdraw_submitted' => '提现申请已提交,请等待审核', + 'contact_admin' => '请联系管理员配置客服链接', + 'transfer_to' => '请输入对方用户名', + 'transfer_amount' => '请输入转账金额', + 'transfer_confirm' => '确认转账给', + 'transfer_success' => '转账成功', + 'amount' => '金额', + 'fund_request_history' => '充提记录', + 'fund_status_pending' => '待审核', + 'fund_status_approved' => '已通过', + 'fund_status_rejected' => '已拒绝', + 'quick_hint' => '点击按钮一键添加对应注单', + 'bets_added' => '注已添加', + + // 报表查询 & 跟单计划 + 'report_query' => '报表查询', + 'follow_plan' => '跟单计划', + 'today' => '今日', + 'yesterday' => '昨日', + 'last_7_days' => '近7天', + 'last_30_days' => '近30天', + 'custom_date' => '自定义', + 'bet_type' => '类型', + 'bet_amount' => '下注', + 'effective_flow' => '有效流水', + 'rebate_amount' => '退水', + 'win_loss' => '输赢', + 'total_win_rate' => '总胜率', + 'total_profit' => '总盈亏', + 'no_follow_plan' => '对不起,你现暂无任何跟单计划', + 'bs_plan' => '大小计划', ]; diff --git a/Static/ckplayer/js/ckplayer.js b/Static/ckplayer/js/ckplayer.js index 26a1e8d..4b15331 100755 --- a/Static/ckplayer/js/ckplayer.js +++ b/Static/ckplayer/js/ckplayer.js @@ -5647,10 +5647,29 @@ */ res.htm=function(val){ if(!isUndefined(val)){ - res.innerHTML=val; + if (typeof val === 'string' && /' + val + '
', 'text/html'); + var root = doc.body.firstChild; + res.replaceChildren(); + while (root && root.firstChild) { + res.appendChild(root.firstChild); + } + } + else if (val && typeof val === 'object' && val.nodeType) { + res.replaceChildren(val); + } + else{ + res.textContent=String(val); + } } else{ - return res.innerHTML; + return Array.prototype.map.call(res.childNodes,function(node){ + return new XMLSerializer().serializeToString(node); + }).join(''); } }; /* diff --git a/Static/ckplayer/js/ckplayer.min.js b/Static/ckplayer/js/ckplayer.min.js index f9a53be..c616c5e 100755 --- a/Static/ckplayer/js/ckplayer.min.js +++ b/Static/ckplayer/js/ckplayer.min.js @@ -4,4 +4,4 @@ * 版权:www.ckplayer.com * 开源协议:MIT */ -(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?module.exports=factory():typeof define==='function'&&define.amd?define(factory):(global=global||self,global.ckplayer=factory())}(this,function(){'use strict';var language={play:'%E6%92%AD%E6%94%BE',pause:'%E6%9A%82%E5%81%9C',refresh:'%E9%87%8D%E6%92%AD',full:'%E5%85%A8%E5%B1%8F',exitFull:'%E9%80%80%E5%87%BA%E5%85%A8%E5%B1%8F',webFull:'%E9%A1%B5%E9%9D%A2%E5%85%A8%E5%B1%8F',exitWebFull:'%E9%80%80%E5%87%BA%E9%A1%B5%E9%9D%A2%E5%85%A8%E5%B1%8F',theatre:'%E5%89%A7%E5%9C%BA%E6%A8%A1%E5%BC%8F',exitTheatre:'%E9%80%80%E5%87%BA%E5%89%A7%E5%9C%BA%E6%A8%A1%E5%BC%8F',volume:'%E9%9F%B3%E9%87%8F%EF%BC%9A',muted:'%E9%9D%99%E9%9F%B3',exitmuted:'%E6%81%A2%E5%A4%8D%E9%9F%B3%E9%87%8F',seek:'seek%EF%BC%9A',waiting:'%E7%BC%93%E5%86%B2',live:'%E7%9B%B4%E6%92%AD%E4%B8%AD',backLive:'%E8%BF%94%E5%9B%9E%E7%9B%B4%E6%92%AD',lookBack:'%E5%9B%9E%E7%9C%8B%EF%BC%9A',next:'%E4%B8%8B%E4%B8%80%E9%9B%86',screenshot:'%E8%A7%86%E9%A2%91%E6%88%AA%E5%9B%BE',smallwindows:'%E5%B0%8F%E7%AA%97%E5%8F%A3%E6%92%AD%E6%94%BE%E5%8A%9F%E8%83%BD',playbackrate:'%E5%80%8D%E9%80%9F',playbackrateSuffix:'%E5%80%8D',track:'%E5%AD%97%E5%B9%95',noTrack:'%E6%97%A0%E5%AD%97%E5%B9%95',definition:'%E6%B8%85%E6%99%B0%E5%BA%A6',switchTo:'%E5%88%87%E6%8D%A2%E6%88%90%EF%BC%9A',closeTime:'%7Bseconds%7D%E7%A7%92%E5%90%8E%E5%8F%AF%E5%85%B3%E9%97%AD%E5%B9%BF%E5%91%8A',closeAd:'%E5%85%B3%E9%97%AD%E5%B9%BF%E5%91%8A',second:'%E7%A7%92',details:'%E6%9F%A5%E7%9C%8B%E8%AF%A6%E6%83%85',copy:'%E5%A4%8D%E5%88%B6',copySucceeded:'%E5%A4%8D%E5%88%B6%E6%88%90%E5%8A%9F%EF%BC%8C%E5%8F%AF%E8%B4%B4%E7%B2%98%EF%BC%81',smallwindowsOpen:'%E5%B0%8F%E7%AA%97%E5%8F%A3%E5%8A%9F%E8%83%BD%E5%B7%B2%E5%BC%80%E5%90%AF',smallwindowsClose:'%E5%B0%8F%E7%AA%97%E5%8F%A3%E5%8A%9F%E8%83%BD%E5%B7%B2%E5%85%B3%E9%97%AD',screenshotStart:'%E6%88%AA%E5%9B%BE%E4%B8%AD%EF%BC%8C%E8%AF%B7%E7%A8%8D%E5%80%99...',screenshotClose:'%E6%88%AA%E5%9B%BE%E5%8A%9F%E8%83%BD%E5%B7%B2%E5%85%B3%E9%97%AD',loopOpen:'%E5%BE%AA%E7%8E%AF%E6%92%AD%E6%94%BE',loopClose:'%E5%B7%B2%E5%85%B3%E9%97%AD%E5%BE%AA%E7%8E%AF%E6%92%AD%E6%94%BE',close:'%E5%85%B3%E9%97%AD',down:'%E4%B8%8B%E8%BD%BD',p50:'50%25',p75:'75%25',p100:'100%25',timeScheduleAdjust:{prohibit:'%E8%A7%86%E9%A2%91%E7%A6%81%E6%AD%A2%E6%8B%96%E5%8A%A8',prohibitBackOff:'%E8%A7%86%E9%A2%91%E7%A6%81%E6%AD%A2%E9%87%8D%E5%A4%8D%E8%A7%82%E7%9C%8B',prohibitForward:'%E8%A7%86%E9%A2%91%E7%A6%81%E6%AD%A2%E5%BF%AB%E8%BF%9B',prohibitLookBack:'%E8%A7%86%E9%A2%91%E7%A6%81%E6%AD%A2%E6%92%AD%E6%94%BE%E9%83%A8%E5%88%86%E5%86%85%E5%AE%B9',prohibitForwardNotViewed:'%E8%A7%86%E9%A2%91%E7%A6%81%E6%AD%A2%E6%92%AD%E6%94%BE%E6%9C%AA%E8%A7%82%E7%9C%8B%E7%9A%84%E9%83%A8%E5%88%86'},error:{noMessage:'%E6%9C%AA%E7%9F%A5%E9%94%99%E8%AF%AF',supportVideoError:'%E8%AF%A5%E6%B5%8F%E8%A7%88%E5%99%A8%E7%89%88%E6%9C%AC%E5%A4%AA%E4%BD%8E%EF%BC%8C%E5%BB%BA%E8%AE%AE%E6%9B%B4%E6%8D%A2%E6%88%90%E5%85%B6%E5%AE%83%E6%B5%8F%E8%A7%88%E5%99%A8',videoTypeError:'%E8%AF%A5%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%8D%E6%94%AF%E6%8C%81%E6%92%AD%E6%94%BE%E8%AF%A5%E8%A7%86%E9%A2%91%EF%BC%8C%E5%BB%BA%E8%AE%AE%E6%9B%B4%E6%8D%A2%E6%88%90%E5%85%B6%E5%AE%83%E6%B5%8F%E8%A7%88%E5%99%A8',loadingFailed:'%E5%8A%A0%E8%BD%BD%E5%A4%B1%E8%B4%A5',emptied:'%E8%A7%86%E9%A2%91%E6%96%87%E4%BB%B6%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B%E4%B8%AD%E5%87%BA%E7%8E%B0%E9%94%99%E8%AF%AF',screenshot:'%E8%A7%86%E9%A2%91%E6%88%AA%E5%9B%BE%E5%A4%B1%E8%B4%A5',ajax:'Ajax%E6%95%B0%E6%8D%AE%E8%AF%B7%E6%B1%82%E9%94%99%E8%AF%AF',noVideoContainer:'%E6%9C%AA%E6%89%BE%E5%88%B0%E6%94%BE%E7%BD%AE%E8%A7%86%E9%A2%91%E7%9A%84%E5%AE%B9%E5%99%A8'}};var videoObjectDefault={container:'',volume:0.8,poster:'',autoplay:false,loop:false,live:false,rotate:0,zoom:0,ad:null,backLive:false,seek:0,next:null,loaded:'',plug:'',duration:0,preview:null,prompt:null,crossOrigin:'',video:null,type:'',playbackrate:1,ended:null,webFull:false,theatre:null,controls:false,rightBar:null,smallWindows:null,smallWindowsDrag:true,screenshot:false,timeScheduleAdjust:1,logo:'',menu:null,information:{'Load:':'{loadTime} second','Duration:':'{duration} second','Size:':'{videoWidth}x{videoHeight}','Volume:':'{volume}%','Fps:':'{fps}fps','Sudio decoded:':'{audioDecodedByteCount} Byte','Video decoded:':'{videoDecodedByteCount} Byte'},track:null,title:'',language:'',barHideTime:1500,playbackrateOpen:true,playbackrateList:[0.75,1,1.25,1.5,2,4],cookie:null,domain:null,cookiePath:'/',documentFocusPause:false,mouseWheelVolume:2,keyVolume:2,errorShow:true};function ckplayerEmbed(videoObj){var rightMenu=[{title:'ckplayer',link:'http://www.ckplayer.com'},{title:'version:X3',underline:true},{title:'about',click:'aboutShow'}];var vars={},varsTemp={};var video=null;var duration=0;var mutedState='';var recoveryVolume=false;var waited=true;var paused=true;var loadTime=0;var seekTime=0;var oldTime=0,playTime=0,firstSeekTime=-1,maxSeeTime=0;var isChangeDef=true;var playType='';var msgSetTime=null,tipSetTime=null,mouseSetTime=null;var adFrontSetTime=null,adPauseSetTime=null;var closeTipFun=null;var closeTipMouseOut=null;var waitingMessage=true;var hidePreviewSetTime=null;var pSliderMouseDown=false;var playbackTime=0;var loadedmetadataNum=0;var screenshotImg=null;var ad=null;var frontAdPlay=false,pauseAdPlay=false;var barShow=true,rightBarShow=true;var EventTarget=null,newEvent=null,eventTargetList=null;var loadedTrack=false;var C={};var CT=null,CK=null,CM=null,CV=null;var pugPlayer=null;var hls=null;var loadMeta=true;var app='';var nowRotate=0;var nowZoom=100;var smallWindowsState=false;var isDrag=false;var ckplayerCookie='ckplayer-player-cookie';var cookieName='';var cookieTime=0;var cookieArray=[];var focusPause=true;var existenceObj=false;var into=function(obj){if(isUndefined(obj)){player.add=player.into=into;return player}language=decodeURIString(language);if(!isUndefined(window.ckplayerLanguage)){language=mergeObj(language,window.ckplayerLanguage)}eventTarget();documentHidden(function(state){eventTarget('visibilityState',state)});app=getApp();varsTemp={};for(var key in videoObjectDefault){varsTemp[key]=videoObjectDefault[key]}if(valType(obj)=='string'&&(obj.substr(0,8)=='website:'||obj.substr(0,4)=='url:')){var ajaxUrl='';if(obj.substr(0,8)=='website:'){ajaxUrl=obj.substring(8)}if(obj.substr(0,4)=='url:'){ajaxUrl=obj.substring(4)}ajax({url:ajaxUrl,success:function(data){if(data){return into(data)}else{showWindowsError(language['error']['ajax']+',url:'+ajaxUrl);return null}}})}else if(valType(obj)=='object'){varsTemp=standardization(varsTemp,obj);if(!isUndefined(obj['container'])&&obj['container']){CT=$(obj['container']);if(CT){return loadLanguage(obj)}else{return bodyReady(obj)}}else{return bodyReady(obj)}}else{showWindowsError(language['error']['noVideoContainer']);return null}},bodyReady=function(obj){documentReady(function(){if(!isUndefined(obj['container'])){CT=$(obj['container']);if(CT){return loadLanguage(obj)}else{showWindowsError(language['error']['noVideoContainer']);return null}}else{return ajaxWebsite(obj)}});return null},ajaxWebsite=function(obj){if(valType(obj['video'])=='string'&&(obj['video'].substr(0,8)=='website:'||obj['video'].substr(0,4)=='url:')){varsTemp=standardization(varsTemp,obj);var ajaxUrl='';if(obj['video'].substr(0,8)=='website:'){ajaxUrl=obj['video'].substring(8)}if(obj['video'].substr(0,4)=='url:'){ajaxUrl=obj['video'].substring(4)}ajax({url:ajaxUrl,success:function(data){if(data){return into(data)}else{showWindowsError(language['error']['ajax']+',url:'+ajaxUrl);return null}}})}else{showWindowsError(language['error']['noVideoContainer']);return null}},loadLanguage=function(obj){vars=standardization(varsTemp,obj);if(vars['language']){var path=getPath('language')+vars['language']+'.js';loadJs(path,function(){if(!isUndefined(window['ckplayerLanguage'])){language=mergeObj(language,window['ckplayerLanguage'])}return embed(obj)})}else{return embed(obj)}},embed=function(obj){mutedState=vars['volume']>0?false:true;if(!isUndefined(CT)&&CT!=null){if(valType(CT)=='htmlarray'){CT=CT.eq(0)}}else{return null}calculationFps();existenceObj=valType(vars['video'])=='string'&&((vars['video'].substr(0,1)=='.'&&vars['video'].indexOf('/')==-1)||vars['video'].substr(0,1)=='#');if(existenceObj){CK=CT.find('.ckplayer-ckplayer')?CT.find('.ckplayer-ckplayer').eq(0):null;if(CK){CM=CK.find('.ck-main')?CK.find('.ck-main').eq(0):null}if(CM){CV=CM.find('.ck-video')?CM.find('.ck-video').eq(0):null}}if(!CK||!CM||!CV){CT.htm('');CK=createlDiv('ckplayer-ckplayer');CT.append(CK);CM=createlDiv('ck-main');CK.append(CM);CV=createlDiv('ck-video');CM.append(CV)}if(video){video.remove();video=null}if(existenceObj){video=$(vars['video'])?$(vars['video']).eq(0):null}if(!video){video=createlVideo();video.attr('width','100%').attr('height','100%');CV.append(video)}video.volume=vars['volume'];if(vars['autoplay']){player.volume(0);player.muted();recoveryVolume=true;video.attr('autoplay','autoplay');paused=false}else{video.attr('preload','metadata')}CT.loop=vars['loop'];if(CT.loop){video.attr('loop','loop')}if(vars['controls']){video.controls=true}else{video.controls=false}try{if(!existenceObj){video.attr('controlslist','nodownload');video.attr('x-webkit-airplay','true');video.attr('x5-video-orientation','portraint');video.attr('playsinline','true');video.attr('webkit-playsinline','true');video.attr('x5-playsinline','true')}if(vars['crossOrigin']){video.useCORS=true;video.crossOrigin=vars['crossOrigin']}}catch(event){}CT.smallWindows=true;CT.webFull=false;CT.theatre=false;loadFace();if(vars['rotate']>0){player.rotate(vars['rotate'])}if(vars['zoom']>0){player.zoom(vars['zoom'])}if(isUndefined(video.canPlayType)){CT.error={code:5,message:language['error']['supportVideoError']};eventTarget('error',CT.error);showError();return player}addAllListener();if(!isUndefined(vars['loaded'])){if(valType(vars['loaded'])=='function'){try{vars['loaded'](player)}catch(event){}}else if(valType(vars['loaded'])=='string'){try{eval(vars['loaded']+'(player)')}catch(event){}}}if(!existenceObj){changeVideo(vars['video'])}else{changeVideo(video.attr('src'))}return player},changeVideo=function(vstr){var i=0;if(video.attr('src')||video.htm()){player.pause()}if(video.attr('src')&&!existenceObj){video.attr('src','');video.removeAttr('src')}if(!isUndefined(video.textTracks)&&video.textTracks.length>0){for(i=video.find('track').length-1;i>-1;i--){video.find('track').eq(i).remove()}}if(!existenceObj){video.htm('')}if(!isUndefined(vars['ad'])){ad=vars['ad']}var source='';if(valType(vstr)=='string'){if(vstr.substr(0,8)=='website:'||vstr.substr(0,4)=='url:'){var ajaxUrl='';if(vstr.substr(0,8)=='website:'){ajaxUrl=vstr.substring(8)}if(vstr.substr(0,4)=='url:'){ajaxUrl=vstr.substring(4)}ajax({url:ajaxUrl,success:function(data){if(data!=null){vars=standardization(varsTemp,data);changeVideo(vars['video'])}else{CT.error={code:8,message:language['error']['ajax']};eventTarget('error',CT.error);showError()}}});return}else{if(vars['plug']&&!canPlay(vstr)){plugPlayer(vstr)}else{if(!existenceObj){video.attr('src',vstr)}}loadTrack()}}else if(valType(vstr)=='array'){for(i=0;i1){source+=''}}}}if(source){video.htm(source)}loadTrack()}else if(valType(vstr)=='object'){if(vars['plug']){plugPlayer(vstr)}else{CT.error={code:10,message:language['error']['emptied']};eventTarget('error',CT.error);showError()}loadTrack()}if(!isUndefined(video.find('source'))&&video.find('source').length>0){video.find('source').eq(video.find('source').length-1).addListener('error',videoHandler.error)}checkBar();loadLogo();if(isChangeDef){checkDefinition()}if(vars['cookie']){if(valType(vars['cookie'])=='array'&&vars['cookie'].length>=2){if(vars['cookie'][0]&&valType(vars['cookie'][0])=='string'){cookieName=vars['cookie'][0]}if(vars['cookie'][1]&&valType(vars['cookie'][1])=='number'){cookieTime=vars['cookie'][1]}}if(valType(vars['cookie'])=='object'&&!isUndefined(vars['cookie']['name'])){cookieName=vars['cookie']['name'];if(!isUndefined(vars['cookie']['hour'])&&valType(vars['cookie']['hour'])=='number'){cookieTime=vars['cookie']['hour']}}if(valType(vars['cookie'])=='string'){cookieName=vars['cookie']}if(cookieName){cookieName=cookieName.replace(/[ ]*,[ ]*|[ ]+/g,'').replace(/[ ]*;[ ]*|[ ]+/g,'')}}if(!isUndefined(vars['seek'])&&!seekTime){if(valType(vars['seek'])=='number'&&vars['seek']>0){seekTime=vars['seek']}if(valType(vars['seek'])=='string'&&vars['seek']=='cookie'&&cookieName){var cke=player.cookie(cookieName);if(cke){seekTime=cke['time']}}}},plugPlayer=function(url){switch(vars['plug']){case'hls.js':hlsPlayer(url);break;case'flv.js':flvPlayer(url);break;case'mpegts.js':mpegtsPlayer(url);break;case'dash.js':dashPlayer(url);break;default:if(valType(vars['plug'])=='function'){vars['plug'](video,url)}else{CT.error={code:10,message:language['error']['emptied']};eventTarget('error',CT.error);showError()}break}},hlsPlayer=function(url){var path=getPath('hls.js')+'hls.min.js';loadJs(path,function(){if(!isUndefined(Hls)&&Hls.isSupported()){hls=new Hls();hls.loadSource(url);hls.attachMedia(video);hls.on(Hls.Events.ERROR,function(event,data){var code=9;var message=language['error']['loadingFailed'];var fatal=data.fatal;if(!isUndefined(data.response)){if(!isUndefined(data.response.code)){code=data.response.code}if(!isUndefined(data.response.text)&&data.response.text){message=data.response.text}else{if(!isUndefined(event)){message=event}}}CT.error={code:code,message:message};eventTarget('error',CT.error);if(fatal){showError()}})}else if(canPlay(url)){video.attr('src',url)}})},flvPlayer=function(url){var path=getPath('flv.js')+'flv.min.js';loadJs(path,function(){if(typeof(flvjs)!='undefined'&&!isUndefined(flvjs)&&flvjs.isSupported()){var config={type:'flv',url:url};if(valType(url)=='object'){config=url}if(vars['live']){config['isLive']=true}if(pugPlayer){pugPlayer.pause();pugPlayer.unload();pugPlayer.detachMediaElement();pugPlayer.destroy();pugPlayer=null}pugPlayer=flvjs.createPlayer(config);pugPlayer.attachMediaElement(video);pugPlayer.load();pugPlayer.on(flvjs.Events.METADATA_ARRIVED,function(res){videoHandler.loadedMetaData()});pugPlayer.on(flvjs.Events.ERROR,function(errorType,errorDetail,errorInfo){CT.error={code:errorInfo['code'],message:errorInfo['msg']};eventTarget('error',CT.error);showError()})}})},mpegtsPlayer=function(url){var path=getPath('mpegts.js')+'mpegts.js';loadJs(path,function(){if(typeof(mpegts)!='undefined'&&!isUndefined(mpegts)&&mpegts.getFeatureList().mseLivePlayback){var config={type:'mse',url:url};if(valType(url)=='object'){config=url}if(vars['live']){config['isLive']=true}if(pugPlayer){pugPlayer.pause();pugPlayer.unload();pugPlayer.detachMediaElement();pugPlayer.destroy();pugPlayer=null}pugPlayer=mpegts.createPlayer(config);pugPlayer.attachMediaElement(video);pugPlayer.load();pugPlayer.on(mpegts.Events.METADATA_ARRIVED,function(res){videoHandler.loadedMetaData()});pugPlayer.on(mpegts.Events.ERROR,function(errorType,errorDetail,errorInfo){CT.error={code:errorInfo['code'],message:errorInfo['msg']};eventTarget('error',CT.error);showError()})}})},canPlay=function(vStr,num){var vTypeArr=[{vidType:'video/ogg',codType:'theora, vorbis'},{vidType:'video/mp4',codType:'avc1.4D401E, mp4a.40.2'},{vidType:'video/mp4',codType:'avc1'},{vidType:'video/mp4',codType:'hevc'},{vidType:'video/webm',codType:'vp8.0, vorbis'},{vidType:'video/webm',codType:'vp9'},{vidType:'',codType:'application/x-mpegURL'},{vidType:'',codType:'application/vnd.apple.mpegurl'}];var vType={ogg:vTypeArr[0],mp4:[vTypeArr[1],vTypeArr[2],vTypeArr[3]],webm:[vTypeArr[4],vTypeArr[5]],m3u8:[vTypeArr[6],vTypeArr[7]]};var arr=[];var k='';var supportType=function(obj){var vType=obj['vidType'];var str='';if(vType){str=vType+'; codecs="'+obj['codType']+'"'}else{str=obj['codType']}var sup=video.canPlayType(str);if(sup==''){sup='no'}return sup};var getExtension=function(filepath){return filepath.replace(/.+\./,'')};if(isUndefined(num)){if(valType(vStr)=='string'){if(vars['type']){for(k in vTypeArr){if(vTypeArr[k]['vidType']==vars['type']){arr.push(vTypeArr[k])}}}else{if(getExtension(vStr)&&getExtension(vStr)in vType){var temp=vType[getExtension(vStr)];if(valType(temp)=='array'){for(var i=0;i0){for(var i=0;icTime&&arr[i][0]!=cookieName&&y<19){tempArr.push(arr[i]);y++}}arr=tempArr}if(arr.length>0){cookie(ckplayerCookie,'delete',vars['domain'],vars['cookiePath']);cookie(ckplayerCookie,arrayToString(arr),vars['domain'],vars['cookiePath'])}cookieArray=arr},loadTrack=function(){if(isUndefined(video.textTracks)){return}var i=0;if(!isUndefined(vars['track'])&&valType(vars['track'])=='array'&&!video.find('track')){var track='';var arr=vars['track'];C['bar']['track'].show();for(i=0;i'}if(video){var html=video.htm()+track;video.htm(html)}if(video.find('track')&&!loadedTrack){loadedTrack=true;checkTrack()}}},loadedMetaData=function(){eventTarget('loadedMetaData',{width:CK.getWidth(),height:CK.getHeight(),videoWidth:CT.videoWidth,videoHeight:CT.videoHeight,duration:duration,volume:CT.volume});eventTarget('duration',duration);if('error'in C){C['error'].hide()}replaceInformation('videoWidth',CT.videoWidth);replaceInformation('videoHeight',CT.videoHeight);replaceInformation('volume',parseInt(CT.volume*100));replaceInformation('duration',parseInt(duration));documentHidden(function(state){if(vars['documentFocusPause']){if(state=='show'){if(!focusPause&&paused){player.play()}}else{focusPause=paused;player.pause()}}})};var videoHandler={loadStart:function(){if(!vars['controls']&&app!='iphone'){C['loading'].show()}C['centerPlay'].hide();eventTarget('loadstart')},canPlay:function(){C['loading'].hide();eventTarget('canplay')},loadedData:function(){eventTarget('loadeddata')},loadedMetaData:function(){duration=this.duration;if(!isUndefined(this.duration)&&vars['duration']){duration=vars['duration']}if(!vars['live']){C['time'].htm(formatSeconds(this.currentTime)+'/'+formatSeconds(duration));C['tempTime'].htm(formatSeconds(this.currentTime)+'/'+formatSeconds(duration))}C['loading'].hide();if(vars['poster']){video.attr('poster',vars['poster'])}if(!vars['autoplay']&&!vars['controls']){C['centerPlay'].show();C['buffer'].hide()}CT.duration=duration;CT.videoWidth=this.videoWidth;CT.videoHeight=this.videoHeight;CT.volume=this.volume;if(CT.videoWidth||CT.videoHeight||duration){loadedMetaData()}var len=0;if(!isUndefined(this.buffered)){len=this.buffered.length}if(len>0){changeLoad()}if(!vars['live']){changePreview(vars['preview'])}else{changePreview()}if(!isUndefined(vars['prompt'])&&valType(vars['prompt'])=='array'&&!vars['live']){changePrompt()}else{deletePrompt()}if(isChangeDef){checkDefinition();isChangeDef=false}pSliderMouseDown=false;loadedmetadataNum++;if(!isUndefined(ad)&&!isUndefined(ad['frontPlay'])&&ad['frontPlay']&&loadedmetadataNum>1){player.play();calculationAdFrontTime(duration)}if(!isUndefined(ad)&&!isUndefined(ad['frontPlay'])&&!ad['frontPlay']){player.volume(vars['volume']);player.play()}if(!vars['autoplay']&&loadMeta){loadMeta=false;player.pause();setTimeout(player.pause,300)}},timeUpDate:function(){if(!waited){waited=true;C['buffer'].hide();eventTarget('buffer','end')}var len=this.buffered.length;if(len>0){changeLoad()}if(!duration&&this.duration){duration=this.duration;CT.duration=duration;CT.videoWidth=this.videoWidth;CT.videoHeight=this.videoHeight;if(CT.videoWidth||CT.videoHeight||duration){loadedMetaData()}}oldTime=playTime;playTime=this.currentTime;if(maxSeeTime1){calculationAdFrontTime(duration-playTime)}if(!isUndefined(C['error'])&&C['error'].css('display')=='block'){C['error'].hide()}if(cookieName){changeCookie(playTime)}replaceInformation('audioDecodedByteCount',this.webkitAudioDecodedByteCount||this.audioDecodedByteCount||0);replaceInformation('videoDecodedByteCount',this.webkitVideoDecodedByteCount||this.videoDecodedByteCount||0)},ended:function(){if(!isUndefined(ad)&&!isUndefined(ad['frontPlay'])&&ad['frontPlay']){adFrontEnded()}else{C['bar']['playAndPause']['pause'].hide();C['bar']['playAndPause']['play'].hide();C['bar']['playAndPause']['refresh'].show();eventTarget('ended');showEnded();message()}},error:function(event){if(!isUndefined(event.type)){var errorInfo=this.error;var errorBak=function(){var code=12;var msg=language['error']['loadingFailed'];if(!isUndefined(errorInfo)){if(!isUndefined(errorInfo.code)){code=errorInfo.code}if(!isUndefined(errorInfo.message)){msg=errorInfo.message}CT.error={code:code,message:msg};showError()}else{CT.error={code:code,message:msg};if(event.target.attr('src')){showError()}}eventTarget('error',CT.error)};if(event.type=='error'){try{if(video.currentSrc){ajax({url:video.currentSrc,error:function(info){if(info&&valType(info)=='object'&&'code'in info&&info['code']){CT.error=info;eventTarget('error',CT.error);showError()}else{errorBak()}},success:function(data){if(!data){errorBak()}}})}else{errorBak()}}catch(event){errorBak()}}}},stalled:function(){eventTarget('stalled');CT.error={code:13,message:'load:stalled'};eventTarget('error',CT.error)},suspend:function(){eventTarget('suspend')},emptied:function(){eventTarget('emptied');CT.error={code:7,message:language['error']['emptied']};eventTarget('error',CT.error)},seeked:function(){if(paused){player.play()}pSliderMouseDown=false;eventTarget('seek',{time:this.currentTime,state:'seeked'});eventTarget('seeked')},seeking:function(){if(paused){player.play()}var seekingTime=this.currentTime;if(firstSeekTime==-1){firstSeekTime=seekingTime}switch(vars['timeScheduleAdjust']){case 0:if(oldTime!=seekingTime){waitingMessage=false;player.seek(oldTime);message(language['timeScheduleAdjust']['prohibit']);return}break;case 2:if(seekingTimeoldTime){waitingMessage=false;player.seek(oldTime);message(language['timeScheduleAdjust']['prohibitForward']);return}break;case 4:if(seekingTimemaxSeeTime){waitingMessage=false;player.seek(maxSeeTime);message(language['timeScheduleAdjust']['prohibitForwardNotViewed']);return}break}eventTarget('seek',{time:seekingTime,state:'seeking'});eventTarget('seeking');if(!vars['live']){C['time'].htm(formatSeconds(seekingTime)+'/'+formatSeconds(duration));C['tempTime'].htm(formatSeconds(seekingTime)+'/'+formatSeconds(duration))}},waiting:function(){waited=false;eventTarget('buffer','start');C['centerPlay'].hide();if(!vars['controls']){C['buffer'].show()}if(!waitingMessage){waitingMessage=true}else{message(language['waiting'])}},play:function(){paused=false;C['bar']['playAndPause']['play'].hide();C['bar']['playAndPause']['pause'].show();C['bar']['playAndPause']['refresh'].hide();C['centerPlay'].hide();if(playType=='button'){C['bar']['pbox'].removeClass('ck-bar-progress-out')}else{C['bar']['pbox'].addClass('ck-bar-progress-out')}if(recoveryVolume){recoveryVolume=false;player.exitMuted()}hideBar();closePauseAd();playType='';if(!isUndefined(C['ended'])){player.closeLayer(C['ended'])}if('error'in C){C['error'].hide()}eventTarget('play');if(!isUndefined(ad)){if(!isUndefined(ad['front'])){if(isUndefined(ad['frontPlay'])){ad['frontPlayI']=0;ad['frontPlay']=true;player.pause();adFrontPlay();return}}}if(seekTime&&(isUndefined(vars['live'])||(!isUndefined(vars['live'])&&valType(vars['live'])=='boolean'&&!vars['live']))&&seekTime0&&mutedState){this.muted=false}if(mutedState!=this.muted){mutedState=this.muted;changeMuted(this.muted);CT.muted=this.muted;eventTarget('muted',CT.muted)}CT.volume=this.volume;eventTarget('volume',this.volume);if(!isUndefined(C['bar']['vbox'])){var vol=parseInt(this.volume*100);C['bar']['vbox']['volume']['txt'].htm(vol);if(!C['bar']['vbox']['mouseDown']){changeVolumeSlider(this.muted?0:this.volume)}if(!this.muted){message(language['volume']+vol+'%')}}replaceInformation('volume',parseInt(this.volume*100))},resize:function(){var fullState=document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen||document.msFullscreenElement;if(fullState){C['bar']['fullAndExit']['exitFull'].show();C['bar']['fullAndExit']['full'].hide();C['ad']['bottom']['fullAndExit']['exitFull'].show();C['ad']['bottom']['fullAndExit']['full'].hide();if(!CM.hasClass('ck-main-full')){CM.addClass('ck-main-full')}message(language['full']);C['bar']['webFullAndExit'].hide();C['bar']['theatreAndExit'].hide();if(!isUndefined(C['menu'])){CM.append(C['menu'])}}else{fullState=false;C['bar']['fullAndExit']['full'].show();C['bar']['fullAndExit']['exitFull'].hide();C['ad']['bottom']['fullAndExit']['full'].show();C['ad']['bottom']['fullAndExit']['exitFull'].hide();if(CM.hasClass('ck-main-full')){CM.removeClass('ck-main-full')}if(!isUndefined(vars['webFull'])&&vars['webFull']){C['bar']['webFullAndExit'].show()}if(!isUndefined(vars['theatre'])&&!CT.webFull){C['bar']['theatreAndExit'].show()}if(!isUndefined(C['menu'])){$('body').append(C['menu'])}if(CV.css('width')!=CM.css('width')){player.zoom(100)}}if(CT.full!=fullState){CT.full=fullState;eventTarget('full',fullState)}checkVideoRotate();changeProgress(player.time())},keydown:function(event){event=window.event||event;var keycode=event.keyCode||event.which;var v=player.volume();var pre=function(){if(event.preventDefault){event.preventDefault()}else{event.returnValue=false}};switch(keycode){case 32:pre();player.playOrPause();break;case 37:player.fastBack();break;case 39:player.fastNext();break;case 38:if(valType(vars['keyVolume'])=='number'&&(vars['keyVolume']==1||(vars['keyVolume']==2&&CT.full))){pre();player.volume(v+.1<1?v+=.1:1)}break;case 40:if(valType(vars['keyVolume'])=='number'&&(vars['keyVolume']==1||(vars['keyVolume']==2&&CT.full))){pre();player.volume(v-.1>0?v-=.1:0)}break;default:break}},mouseWheel:function(event){if(valType(vars['mouseWheelVolume'])=='number'&&(vars['mouseWheelVolume']==1||(vars['mouseWheelVolume']==2&&CT.full))){event=window.event||event;var v=player.volume();if(event.preventDefault){event.preventDefault()}else{event.returnValue=false}if(event.wheelDelta){if(event.wheelDelta>0){player.volume(v+.1<1?v+=.1:1)}if(event.wheelDelta<0){player.volume(v-.1>0?v-=.1:0)}}else if(event.detail){if(event.detail>0){player.volume(v-.1>0?v-=.1:0)}if(event.detail<0){player.volume(v+.1<1?v+=.1:1)}}}}};var adFrontPlay=function(){if(ad['frontPlayI']==0){allBarHide(false);C['ad'].show();ad['front']['video']=false;if(!isUndefined(ad['front']['closeTime'])){if(ad['front']['closeTime']>0){C['ad']['top']['closeTime'].show();adFrontCloseTime()}else{C['ad']['top']['closeAd'].show()}}}if(!isUndefined(ad['front']['node'])&&ad['front']['node']!=null){CM.append(ad['front']['node']);player.closeLayer(ad['front']['node']);ad['front']['node']=null}C['ad']['picture'].htm('');C['ad']['picture'].hide();C['ad']['link'].hide();if(!isUndefined(ad['front']['list'][ad['frontPlayI']])){var adv=ad['front']['list'][ad['frontPlayI']];frontAdPlay=true;eventTarget('frontAd',adv);if(adv['type']=='picture'){adPicturePlay(adv)}else if(adv['type']=='node'){adNode(adv)}else{C['ad']['link'].attr('data-link',adv['link']);adVideoPlay(adv['file'])}if(!isUndefined(adv['link'])){C['ad']['bottom']['details'].show();C['ad']['bottom']['details'].htm(''+language['details']+'')}else{C['ad']['bottom']['details'].hide()}}},adFrontCloseTime=function(){if(!isUndefined(ad['front']['closeSetTime'])){clearTimeout(ad['front']['closeSetTime']);ad['front']['closeSetTime']=null}C['ad']['top']['closeTime'].htmReplace('{seconds}',ad['front']['closeTime'],language['closeTime']);ad['front']['closeTime']--;if(ad['front']['closeTime']>0){ad['front']['closeSetTime']=setTimeout(adFrontCloseTime,1000)}else{C['ad']['top']['closeAd'].show();C['ad']['top']['closeTime'].hide()}},calculationAdFrontTime=function(t){if(!isUndefined(t)){ad['front']['list'][ad['frontPlayI']]['time']=t}ad['front']['countDown']=0;for(var i=ad['frontPlayI'];i0){adv['time']--;ad['front']['countDown']--;C['ad']['top']['countDown'].htm(ad['front']['countDown']+language['second']);adFrontSetTime=setTimeout(adFrontCountDown,1000)}else{adFrontEnded()}},adFrontEnded=function(){if(ad['frontPlayI']0||h>0){if(adv['link']){var a=createlA('',adv['link']);a.append(this);C['ad']['picture'].append(a)}else{C['ad']['picture'].append(this)}img.unbind();img.click(function(){eventTarget('frontAdClick',adv)})}else{if(imgloadNum<10){imgloadNum++;imgLoadHandler()}}})};imgLoadHandler();calculationAdFrontTime();adFrontCountDown();C['ad']['bottom']['mutedAndExit'].hide()},adNode=function(adv){if(isUndefined(adv['content']))return;ad['front']['node']=player.layer(adv);ad['front']['node'].css({'z-index':1});C['ad'].append(ad['front']['node']);ad['front']['node'].unbind();ad['front']['node'].click(function(){eventTarget('frontAdClick',adv)});calculationAdFrontTime();adFrontCountDown();C['ad']['bottom']['mutedAndExit'].hide()},frontVideoClickHandler=function(){var url=C['ad']['link'].attr('data-link');window.open(url,'_blank')},closeFrontAd=function(){if(!isUndefined(ad)&&!isUndefined(ad['front'])&&!isUndefined(ad['front']['closeButtonClick'])){if(valType(ad['front']['closeButtonClick'])=='function'){try{ad['front']['closeButtonClick']()}catch(event){console.error(event)}}else if(valType(ad['front']['closeButtonClick'])=='string'){try{eval(ad['front']['closeButtonClick']+'()')}catch(event){console.error(event)}}}else{player.closeFrontAd()}},adPausePlay=function(){closePauseAd();if(!isUndefined(ad)&&!isUndefined(ad['pause'])&&!isUndefined(ad['pause']['list'])&&ad['pause']['list'].length-1>=ad['pausePlayI']){var adv=ad['pause']['list'][ad['pausePlayI']];if(!isUndefined(ad['pause']['close'])&&ad['pause']['close']){ad['pauseClose']=createlButton('ck-pause-close');ad['pauseClose'].click(function(){C['centerPlay'].show();C['buffer'].hide();closePauseAd()})}if(!isUndefined(adv['file'])){var img=null;var imgloadNum=0;var imgLoadHandler=function(){img=createlImg(adv['file']);C['adPause'].show();C['adPause'].htm('');C['adPause'].append(img);img.addListener('load',function(){var w=this.getWidth(),h=this.getHeight();if(w>0||h>0){C['adPause'].htm('');if(adv['link']){var a=createlA('',adv['link']);a.append(this);C['adPause'].append(a)}else{C['adPause'].append(this)}C['adPause'].css({'width':w+'px','height':h+'px'});if(ad['pauseClose']){C['adPause'].append(ad['pauseClose'])}img.click(function(){eventTarget('pauseAdClick',adv)})}else{if(imgloadNum<10){imgloadNum++;imgLoadHandler()}}})};imgLoadHandler()}else if(!isUndefined(adv['content'])){ad['pause']['node']=player.layer(adv);if(ad['pauseClose']){ad['pause']['node'].append(ad['pauseClose'])}ad['pause']['node'].click(function(){eventTarget('pauseAdClick',adv)})}eventTarget('pauseAd',adv);if(!isUndefined(adv['time'])&&adv['time']>0){adPauseSetTime=setTimeout(function(){ad['pausePlayI']++;if(ad['pausePlayI']>ad['pause']['list'].length-1){ad['pausePlayI']=0}adPausePlay()},adv['time']*1000)}}},closePauseAd=function(){if(adPauseSetTime){clearTimeout(adPauseSetTime);adPauseSetTime=null}C['adPause'].hide();if(!isUndefined(ad)&&!isUndefined(ad['pauseClose'])&&ad['pauseClose']!=null){ad['pauseClose'].remove();ad['pauseClose']=null}if(!isUndefined(ad)&&!isUndefined(ad['pause'])&&!isUndefined(ad['pause']['node'])&&ad['pause']['node']){ad['pause']['node'].unbind();player.closeLayer(ad['pause']['node']);eventTarget('pauseAdClose')}};var player={width:function(value){if(!isUndefined(value)){if(valType(value)=='number'){value+='px'}CT.css({'width':value});checkVideoRotate()}return CK.getWidth()},height:function(value){if(!isUndefined(value)){if(valType(value)=='number'){value+='px'}CT.css({'height':value});checkVideoRotate()}return CK.getHeight()},videoWidth:function(){if(video){return video.videoWidth}return null},videoHeight:function(){if(video){return video.videoHeight}return null},zoom:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('zoom',fn)}if(valType(fn)=='number'){var arr=[C['topBar']['zoomEle']['zoom50'],C['topBar']['zoomEle']['zoom75'],C['topBar']['zoomEle']['zoom100']];for(var i=0;ifn&&fn>0){rot=arr[fn]}else{if(arr.indexOf(fn)>-1){rot=fn}}if(rot!=nowRotate){eventTarget('rotate',rot);nowRotate=rot;checkVideoRotate()}}}},vars:function(obj,val){if(isUndefined(obj)){return}if(valType(obj)=='string'&&!isUndefined(val)){var temp={};temp[obj]=val;obj=temp}var isChange=false;for(var k in obj){if(k in vars&&obj[k]!=vars[k]){isChange=true;break}}if(isChange){vars=standardization(vars,obj)}if(!isUndefined(obj['video'])){isChangeDef=true;changeVideo(vars['video'])}if(!isUndefined(obj['title'])){C['topBar']['titleEle'].htm(obj['title'])}},loadstart:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('loadstart',fn)}},loadeddata:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('loadeddata',fn)}},loadedMetaData:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('loadedMetaData',fn)}},canplay:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('canplay',fn)}},play:function(fn){if(valType(fn)=='object'){if(fn['target']==C['bar']['playAndPause']['play']||fn['target']==C['bar']['playAndPause']['refresh']){playType='button'}}if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('play',fn)}else{if(loadedmetadataNum>0&&video){try{video.play()}catch(event){console.error(event)}}}return player},pause:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('pause',fn)}else{if(loadedmetadataNum>0&&video){try{video.pause()}catch(event){console.error(event)}}}return player},playOrPause:function(){if(paused){player.play()}else{player.pause()}return player},volume:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('volume',fn)}else if(valType(fn)=='number'&&video){if(fn<0)fn=0;if(fn>1)fn=1;if(video.muted){video.volume=fn*.5}video.volume=fn}}else if(video){return video.volume}return player},muted:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('muted',fn)}else if(video){video.muted=true}return player},exitMuted:function(){if(video){video.muted=false;if(video.volume==0){player.volume(vars['volume']>0?vars['volume']:.8)}}return player},time:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('time',fn)}else if(video){return video.currentTime||0}},seek:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('seek',fn)}if(valType(fn)=='number'&&video){switch(vars['timeScheduleAdjust']){case 0:message(language['timeScheduleAdjust']['prohibit']);return;break;case 1:break;case 2:if(fnoldTime){message(language['timeScheduleAdjust']['prohibitForward']);return}break;case 4:if(fnmaxSeeTime){waitingMessage=false;video.currentTime=maxSeeTime;message(language['timeScheduleAdjust']['prohibitForwardNotViewed']);return}break}pSliderMouseDown=false;video.currentTime=fn}}return player},buffer:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('buffer',fn)}return player},ended:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('ended',fn)}return player},next:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('next',fn)}return player},error:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('error',fn)}return player},emptied:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('emptied',fn)}return player},duration:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('duration',fn)}else{return duration||0}},playbackRate:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('playbackRate',fn)}else if(valType(fn)=='number'&&video){video.playbackRate=fn;CT.playbackRate=fn;eventTarget('playbackRate',fn);changePlaybackrateVal(fn)}}else{return video.playbackRate}},track:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('track',fn)}else if(valType(fn)=='number'&&video){var track=null;if(!isUndefined(video.textTracks)){track=video.textTracks}if(track&&valType(track)=='texttracklist'){var arr=vars['track'];for(var i=track.length-1;i>-1;i--){video.find('track').eq(i).remove()}for(var i=0;i'}if(video){var html=video.htm()+track;video.htm(html)}changeTrackVal(fn)}}}else{return CT.track}},fastBack:function(num){if(duration==0)return;if(isUndefined(num)||valType(num)!='number'){num=20}var time=player.time();if(time-num>0){time-=num}else{time=0}player.seek(time);return player},fastNext:function(num){if(duration==0)return;if(isUndefined(num)||valType(num)!='number'){num=20}var time=player.time();if(time+num200)zIndex=200;if(isUndefined(ele.cssDisplay)){ele.cssPosition=ele.css('position');ele.cssDisplay=ele.css('display')}ele.css({'position':'absolute','z-index':zIndex});if(!cBar){CM.append(ele)}else{C['bar'].append(ele)}ele.CK=CK;ele.show()}return ele},closeLayer:function(ele){if(valType(ele)=='string'){var temp=$(ele);if(!isUndefined(temp)){if(temp.length>0){ele=temp.eq(0)}}}if(!isUndefined(ele)&&valType(ele)=='htmlobject'){if(!isUndefined(ele.captureParentNode)){var eleP=ele.captureParentNode;if(ele.nextElement){try{eleP.insertBefore(ele,ele.nextElement)}catch(event){eleP.append(ele)}}else if(ele.prevElement){try{eleP.insertAfter(ele,ele.prevElement)}catch(event){eleP.append(ele)}}else{eleP.append(ele)}ele.css({'display':ele.cssDisplay})}else{ele.remove()}if(!isUndefined(ele.cssDisplay)){ele.css({'display':ele.cssDisplay,'position':ele.cssPosition})}}return player},message:function(str){message(str);return player},closeFrontAd:function(){if(frontAdPlay&&!isUndefined(ad)&&!isUndefined(ad['front'])&&!isUndefined(ad['front']['list'])){ad['frontPlayI']=ad['front']['list'].length-1;adFrontEnded();clearAdFrontSetTime()}return player},addListener:function(name,fn){newEvent.addEventListener(name,fn)},removeListener:function(name,fn){newEvent.removeEventListener(name,fn)},bar:function(bool){barShow=bool;if(bool){C['bar'].show()}else{C['bar'].hide()}},rightBar:function(bool){rightBarShow=bool;if(bool){C['rightBar'].show()}else{C['rightBar'].hide()}},mouseActive:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('mouseActive',fn)}return this},cookie:function(name){if(name=='delete'){cookie(ckplayerCookie,'delete',vars['domain']);return null}if(!isUndefined(name)&&name){name=name.replace(/[ ]*,[ ]*|[ ]+/g,'').replace(/[ ]*;[ ]*|[ ]+/g,'')}var cStr=cookie(ckplayerCookie);if(cStr){var arr=stringToArray(cStr);var tempArr=[];var cTime=parseInt(Date.now()*0.001);for(i=0;icTime){tempArr.push(arr[i])}}arr=tempArr;if(arr.length>0){cookie(ckplayerCookie,'delete',vars['domain'],vars['cookiePath']);cookie(ckplayerCookie,arrayToString(arr),vars['domain'],vars['cookiePath']);for(var i=0;i-1;i--){var pTemp=createlButton('ck-list-p',playbackrate[i]+language['playbackrateSuffix']);pTemp.attr('data-i',i).attr('data-val',playbackrate[i]);C['bar']['playbackrate']['bg']['p'].push(pTemp);C['bar']['playbackrate']['bg'].append(pTemp);pTemp.click(playbackrateClick)}C['bar']['playbackrate'].hide();if(!isUndefined(vars['playbackrateOpen'])&&vars['playbackrateOpen']){C['bar']['playbackrate'].show();C['bar']['playbackrate']['bgbox'].show();var w=C['bar']['playbackrate']['bg'].getWidth(),h=C['bar']['playbackrate']['bg'].getHeight();var bw=C['bar']['playbackrate']['button'].getWidth();C['bar']['playbackrate']['bg'].css({'width':w+10+'px'});w=C['bar']['playbackrate']['bg'].getWidth();if(getBarHeight()>C['bar'].getHeight()){h+=(getBarHeight()-C['bar'].getHeight())}C['bar']['playbackrate']['bgbox'].attr('style','');C['bar']['playbackrate']['bgbox'].css({'width':w+'px','height':(h+2)+'px','left':-(w-bw)*0.5+'px'});C['bar']['playbackrate']['button'].mouseover(function(){w=C['bar']['playbackrate']['bg'].getWidth();bw=C['bar']['playbackrate']['button'].getWidth();C['bar']['playbackrate']['bgbox'].css({'left':-(w-bw)*0.5+'px'})});changePlaybackrateVal(pbr)}},playbackrateClick=function(e){C['bar']['playbackrate']['bgbox'].hide();setTimeout(function(){C['bar']['playbackrate']['bgbox'].removeCss('display')},300);message(language['switchTo']+$(this).htm());player.playbackRate(parseFloat($(this).attr('data-val')))},changePlaybackrateVal=function(num){C['bar']['playbackrate']['bg'].find('button').each(function(index,ele){var val=parseFloat(ele.attr('data-val'));ele.removeClass('ck-list-p-focus');if(num==val){ele.addClass('ck-list-p-focus');if(num!=1){C['bar']['playbackrate']['button'].htm(ele.htm())}else{C['bar']['playbackrate']['button'].htm(language['playbackrate'])}}else{ele.removeClass('ck-list-p-focus')}})},checkTrack=function(){var track=vars['track'];var pbr=-1;C['bar']['track']['bgbox'].htm('');C['bar']['track']['bg']=createlDiv('ck-list-bg');C['bar']['track']['bgbox'].append(C['bar']['track']['bg']);C['bar']['track']['bg']['p']=[];var pTemp=null;for(var i=track.length-1;i>-1;i--){pTemp=createlButton('ck-list-p',track[i]['label']);pTemp.attr('data-i',i).attr('data-title',track[i]['label']);C['bar']['track']['bg']['p'].push(pTemp);C['bar']['track']['bg'].append(pTemp);if(!isUndefined(track[i]['default'])&&track[i]['default']){pbr=i}pTemp.click(trackClick)}pTemp=createlButton('ck-list-p',language['noTrack']);pTemp.attr('data-i',-1).attr('data-title',language['noTrack']);C['bar']['track']['bg']['p'].push(pTemp);C['bar']['track']['bg'].append(pTemp);pTemp.click(trackClick);C['bar']['track'].hide();C['bar']['track'].show();C['bar']['track']['bgbox'].show();var w=C['bar']['track']['bg'].getWidth(),h=C['bar']['track']['bg'].getHeight();var bw=C['bar']['track']['button'].getWidth();C['bar']['track']['bg'].css({'width':w+10+'px'});w=C['bar']['track']['bg'].getWidth();if(getBarHeight()>C['bar'].getHeight()){h+=(getBarHeight()-C['bar'].getHeight())}C['bar']['track']['bgbox'].attr('style','');C['bar']['track']['bgbox'].css({'width':w+'px','height':(h+2)+'px','left':-(w-bw)*0.5+'px'});C['bar']['track']['button'].mouseover(function(){w=C['bar']['track']['bg'].getWidth();bw=C['bar']['track']['button'].getWidth();C['bar']['track']['bgbox'].css({'left':-(w-bw)*0.5+'px'})});changeTrackVal(pbr)},trackClick=function(e){C['bar']['track']['bgbox'].hide();setTimeout(function(){C['bar']['track']['bgbox'].removeCss('display')},300);message(language['switchTo']+$(this).attr('data-title'));player.track(parseFloat($(this).attr('data-i')))},changeTrackVal=function(num){C['bar']['track']['bg'].find('button').each(function(index,ele){var i=parseInt(ele.attr('data-i'));if(num==i){C['bar']['track']['button'].htm(ele.attr('data-title'));if(i>-1){CT.track=vars['track'][i];eventTarget('track',vars['track'][i])}else{CT.track=null;eventTarget('track',null)}ele.addClass('ck-list-p-focus')}else{ele.removeClass('ck-list-p-focus')}})},checkDefinition=function(){var pi=-1;var videoUrl=video['currentSrc'];if(!isUndefined(C['bar']['definition']['bg'])){C['bar']['definition']['bgbox'].htm('');C['bar']['definition']['bgbox'].attr('style','');C['bar']['definition']['button'].htm(language['definition'])}if(valType(vars['video'])=='array'){var arr=vars['video'];C['bar']['definition']['bg']=createlDiv('ck-list-bg');C['bar']['definition']['bgbox'].append(C['bar']['definition']['bg']);C['bar']['definition']['bg']['p']=[];for(var i=arr.length-1;i>-1;i--){var pTemp=createlButton('ck-list-p',arr[i][2]);pTemp.attr('data-i',i).attr('data-video',arr[i][0]).attr('data-type',arr[i][1]).attr('data-title',arr[i][2]);C['bar']['definition']['bg']['p'].push(pTemp);C['bar']['definition']['bg'].append(pTemp);if(videoUrl&&videoUrl.indexOf(arr[i][0])>-1){pi=i}pTemp.click(definitionClick)}C['bar']['definition'].show();C['bar']['definition']['bgbox'].show();var w=C['bar']['definition']['bg'].getWidth(),h=C['bar']['definition']['bg'].getHeight();var bw=C['bar']['definition']['button'].getWidth();C['bar']['definition']['bg'].css({'width':w+10+'px'});w=C['bar']['definition']['bg'].getWidth();if(getBarHeight()>C['bar'].getHeight()){h+=(getBarHeight()-C['bar'].getHeight())}C['bar']['definition']['bgbox'].attr('style','');C['bar']['definition']['bgbox'].css({'width':w+'px','height':(h+2)+'px','left':-(w-bw)*0.5+'px'});C['bar']['definition']['button'].mouseover(function(){w=C['bar']['definition']['bg'].getWidth();bw=C['bar']['definition']['button'].getWidth();C['bar']['definition']['bgbox'].css({'left':-(w-bw)*0.5+'px'})})}if(pi>-1){C['bar']['definition']['bg'].find('button').each(function(index,ele){var i=parseInt(ele.attr('data-i'));if(pi==i){ele.addClass('ck-list-p-focus')}else{ele.removeClass('ck-list-p-focus')}})}},definitionClick=function(e){C['bar']['definition']['bgbox'].hide();setTimeout(function(){C['bar']['definition']['bgbox'].removeCss('display')},300);changeDefinition(parseInt($(this).attr('data-i')))},changeDefinition=function(num){C['bar']['definition']['bg'].find('button').each(function(index,ele){var i=parseInt(ele.attr('data-i'));if(num==i){if(!isUndefined(video.textTracks)&&video.textTracks.length>0){for(var y=video.find('track').length-1;y>-1;y--){video.find('track').eq(y).remove()}}seekTime=player.time();video.attr('autoplay','autoplay');changeVideo(ele.attr('data-video'));message(language['switchTo']+ele.htm());C['bar']['definition']['button'].htm(ele.htm());ele.addClass('ck-list-p-focus');eventTarget('definition',{id:i,video:ele.attr('data-video'),title:ele.attr('data-title')})}else{ele.removeClass('ck-list-p-focus')}})},deletePrompt=function(){var i=0;if(!isUndefined(C['prompt'])&&valType(C['prompt'])=='array'){for(i=C['prompt'].length;i>-1;i--){if(!isUndefined(C['prompt'][i])){C['prompt'][i].remove()}}C['prompt']=null}C['prompt']=[]},changePrompt=function(){deletePrompt();for(var i=0;i100)left=100;ele.css({'left':left+'%'});ele.mouseover(function(){if(this.attr('data-words')){showPromptWords(this)}});ele.mouseout(function(){if(this.attr('data-words')){showPromptWords()}});C['prompt'].push(ele)}}},showPromptWords=function(ele){if(isUndefined(C['promptWords'])){C['promptWords']=createlDiv('ck-prompt-words');CM.append(C['promptWords']);C['promptWords']['content']=createlDiv('ck-prompt-content');C['promptWords'].append(C['promptWords']['content'])}if(isUndefined(ele)){C['promptWords']['content'].htm('');C['promptWords'].hide();return}C['promptWords']['content'].htm(ele.attr('data-words'));C['promptWords'].show();var pW=C['promptWords'].getWidth(),pH=C['promptWords'].getHeight();var bg=C['bar']['pbox']['bg'];var time=parseFloat(ele.attr('data-time'));var top=CK.getHeight()-getBarHeight();var left=ele.offset()['left']-CK.offset()['left']+ele.getWidth()*.5-pW*.5;if(!isUndefined(C['preview'])){top=CK.getHeight()-parseFloat((getBarHeight()+C['preview']['smallHight']+parseInt(C['preview'].css('margin-bottom'))))}else{top=CK.getHeight()-parseFloat((getBarHeight()+C['tip']['minHeight']+parseInt(C['tip'].css('margin-bottom'))));top-=(getBarHeight()-C['bar'].getHeight())}top-=parseFloat(C['promptWords'].css('margin-bottom'));top-=pH;left-=parseFloat(C['promptWords'].css('margin-right'));if(left<0)left=0;if(left>CK.getWidth()-pW)left=CK.getWidth()-pW;C['promptWords'].css({'top':top+'px','left':left+'px'})},changePreview=function(preview){if(!isUndefined(C['preview'])){C['preview'].remove();C['preview']=null}if(!isUndefined(preview)){C['preview']=createlDiv('ck-preview');CM.append(C['preview']);if(isUndefined(vars['preview']['type'])){vars['preview']['type']=0}if(vars['preview']['type']==1){C['preview']['bg']=createlDiv('ck-preview-bg');C['preview'].append(C['preview']['bg'])}var files=[];var loadImg=function(bigW,bigH,smallW,smallH){if(vars['preview']['type']==1){C['preview']['bg']['img']=[];for(var i=0;i1){if(valType(vars['preview']['thumbnail'][0])=='number'){thumbnail[0]=vars['preview']['thumbnail'][0]}if(valType(vars['preview']['thumbnail'][1])=='number'){thumbnail[1]=vars['preview']['thumbnail'][1]}vars['preview']['thumbnail']=thumbnail}if(files[0]){var img=createlImg(files[0],'ck-preview-load-img').addListener('load',function(){loadImg(this.getWidth(),this.getHeight(),this.getWidth()/thumbnail[0],this.getHeight()/thumbnail[1]);this.remove()});CM.append(img)}}else{C['preview'].removeChild();C['preview']=null}}else{C['preview'].removeChild();C['preview']=null}}},showPreview=function(time){if(hidePreviewSetTime){clearTimeout(hidePreviewSetTime);hidePreviewSetTime=null}if(time<0){if(!isUndefined(C['preview'])){hidePreviewSetTime=setTimeout(function(){C['preview'].hide();hidePreviewSetTime=null},200);return}}if(!isUndefined(C['preview'])&&!isUndefined(vars['preview']['thumbnail'])&&duration>0){if(isUndefined(vars['preview']['type'])){vars['preview']['type']=0}var scale=2,thumbnail=vars['preview']['thumbnail'];if(!isUndefined(vars['preview']['scale'])){scale=vars['preview']['scale']}var divI=0,imgI=0;var left=0,frameLeft=0;var pW=0;var smallW=C['preview']['smallWidth'],smallH=C['preview']['smallHight'];var bgXw=time*C['bar']['pbox']['bg'].getWidth()/duration+(C['bar']['pbox']['bg'].offset()['left']-CK.offset()['left']);if(C['preview'].css('display')=='none'){C['preview'].show()}C['preview'].css({'height':smallH+'px',});if(vars['preview']['type']==1){divI=parseInt(time/(scale*thumbnail[0]));imgI=parseInt(time/scale);var minI=divI-5,maxI=divI+5;if(minI<0)minI=0;if(maxI>C['preview']['bg']['img'].length-1){maxI=C['preview']['bg']['img'].length-1}for(var i=minI;iC['preview'].getWidth()-C['preview']['frame'].getWidth()){var xz=frameLeft-(C['preview'].getWidth()-C['preview']['frame'].getWidth());frameLeft=C['preview'].getWidth()-C['preview']['frame'].getWidth();left-=xz}C['preview']['bg'].css({'left':left+'px'});C['preview']['frame'].css({'left':frameLeft+'px'})}else{var temp=(time/scale)/(thumbnail[0]*thumbnail[1]);imgI=parseInt(temp);var smallI=parseInt((temp-imgI)*100);var rows=parseInt(smallI/thumbnail[0]);var column=smallI%thumbnail[0];var files=vars['preview']['file'];left=bgXw-smallW*.5;if(left<0){left=0}if(left>CK.getWidth()-smallW){left=CK.getWidth()-smallW}C['preview'].css({'width':C['preview']['smallWidth']+'px','background':'url('+files[imgI]+') no-repeat','background-position':'-'+column*smallW+'px -'+rows*smallH+'px','left':left+'px'})}}},showError=function(){C['loading'].hide();if(vars['controls']||!vars['errorShow']){return}if(isUndefined(C['error'])){C['error']=createlDiv('ck-error');CM.append(C['error'])}var msg=CT.error['message'];if(!msg)msg=language['error']['noMessage'];var html=msg+'(code:'+CT.error['code']+')';C['error'].htm(html);C['error'].show()},showWindowsError=function(error){var ele=createlDiv('ckplayer-error',error);$('body').append(ele)},showNextEpisode=function(){if(isUndefined(vars['next']))return;if(valType(vars['next'])=='object'){if('content'in vars['next']){C['next']=player.layer(vars['next']);return}if('title'in vars['next']){tip(C['bar']['nextEpisode'],vars['next']['title']);return}}tip(C['bar']['nextEpisode'],language['next'])},nextClick=function(){if(valType(vars['next'])=='object'){if('click'in vars['next']){if(valType(vars['next']['click'])=='function'){vars['next']['click']()}else if(valType(vars['next']['click'])=='string'){eval(vars['next']['click']+'()')}}if('link'in vars['next']){window.location.href=vars['next']['link']}eventTarget('next')}},showEnded=function(){if(!CT.loop&&!isUndefined(vars['ended'])){closePauseAd();C['ended']=player.layer(vars['ended']);if(C['ended']){C['centerPlay'].hide()}return}},windowScroll=function(){if(!CT.smallWindows)return;if(CT.webFull||CT.theatre||CT.full){exitSmallWindows();return}var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;if(scrollTop>CT.offset()['top']+CT.getHeight()){if(!CK.hasClass('ckplayer-ckplayer-smallwindow')){CK.addClass('ckplayer-ckplayer-smallwindow');eventTarget('smallWindows',true);smallWindowsState=true;if(vars['smallWindowsDrag']){drag(true)}allBarHide();checkVideoRotate()}}else{exitSmallWindows()}},exitSmallWindows=function(){if(CK.hasClass('ckplayer-ckplayer-smallwindow')){CK.removeClass('ckplayer-ckplayer-smallwindow');if(vars['smallWindowsDrag']){drag()}eventTarget('smallWindows',false);smallWindowsState=false;allBarShow();checkVideoRotate()}},drag=function(state){var doc=$(document);var posX=0,posY=0,posXRecord=0,posYRecord=0;var open=false;if(!isUndefined(state)){open=state}var mouseDown=function(e){e.preventDefault&&e.preventDefault();e=e||window.event;var client=getClient(e);posXRecord=client['x'];posYRecord=client['y'];doc.mousemove(docMouseMove);doc.mouseup(docMouseUp);CK.css({'cursor':'move'})};var docMouseMove=function(e){e=e||window.event;var client=getClient(e);var eleOffset=CK.offset();posY=client['y']-posYRecord;posX=client['x']-posXRecord;posXRecord=client['x'];posYRecord=client['y'];var left=eleOffset['left']+posX;var top=eleOffset['top']+posY;CK.css({'left':left+'px','top':top+'px'});if(CK.css('position')!='fixed'){CK.css({'position':'fixed'})}isDrag=true};var docMouseUp=function(){doc.removeListener('mousemove',docMouseMove);doc.removeListener('mouseup',docMouseUp);CK.css({'cursor':'default'})};if(open){CK.mousedown(mouseDown)}else{CK.unbind('mousedown')}},checkVideoRotate=function(){var ckW=CK.getWidth()*nowZoom*0.01,ckH=CK.getHeight()*nowZoom*0.01;var vW=CV.getWidth(),vH=CV.getHeight();CV.css({'transform':'rotate('+nowRotate+'deg)','-ms-transform':'rotate('+nowRotate+'deg)','-moz-transform':'rotate('+nowRotate+'deg)','-webkit-transform':'rotate('+nowRotate+'deg)','-o-transform':'rotate('+nowRotate+'deg)','width':nowZoom+'%','height':nowZoom+'%'});if(nowRotate!=0&&nowRotate!=180){if(vH>ckW&&vW>ckH){if(vH/ckW>vW/ckH){CV.css({'height':nowZoom+'%','width':ckH+'px'})}else{CV.css({'width':nowZoom+'%','height':ckW+'px'})}return}if(vHckH){CV.css({'height':nowZoom+'%','width':ckH+'px'})}else{if(vH/vW>=ckW/ckH){CV.css({'width':nowZoom+'%','height':ckW+'px'})}else{CV.css({'height':nowZoom+'%','width':ckH+'px'})}}return}if(vH>=ckW&&vW<=ckH){CV.css({'width':nowZoom+'%','height':ckW+'px'});return}if(vW>=ckH&&vHC['bar'].getHeight()){top-=(getBarHeight()-C['bar'].getHeight())}if(w>ew){left=offset['left']-ckOffset['left']-(w-ew)*.5}else{left=offset['left']-ckOffset['left']+(ew-w)*.5}if(align=='left'){left=offset['left']-ckOffset['left']-w;top=offset['top']-ckOffset['top']}if(!isUndefined(cl)){left=cl['x']-ckOffset['left']-w*.5}if(left<0){left=0}if(top<0){top=0}if(left>CM.getWidth()-w){left=CM.getWidth()-w}if(top>CM.getHeight()-h){top=CM.getHeight()-h}C['tip'].css({'left':left+'px','top':top-parseInt(C['tip'].css('margin-bottom'))+'px'})},progressDragX=function(){var bg=C['bar']['pbox']['bg'],play=C['bar']['pbox']['play'],slider=C['bar']['pbox']['slider'],mouseLine=C['bar']['pbox']['mouseLine'];slider.unbind();bg.unbind();var pos=0,posRecord=0;var doc=$(document);var sliderDownTime=0;var sliderMouseDown=function(e){e.preventDefault&&e.preventDefault();if(!vars['timeScheduleAdjust']){return}e=e||window.event;var eleOffset=slider.offset(),bgOffset=bg.offset(),client=getClient(e);pSliderMouseDown=true;posRecord=client['x'];var left=eleOffset['left']-bgOffset['left'];sliderDownTime=left*duration/(bg.getWidth()-slider.getWidth());slider.removeListener('mousedown',sliderMouseDown);slider.removeListener('touchstart',sliderMouseDown);doc.mousemove(docMouseMove);doc.mouseup(docMouseUp);slider.touchmove(docMouseMove);slider.touchend(docMouseUp);noScrolling(true)};var docMouseMove=function(e){e=e||window.event;var eleOffset=slider.offset(),bgOffset=bg.offset(),client=getClient(e);pSliderMouseDown=true;pos=posRecord-client['x'];posRecord=client['x'];var left=eleOffset['left']-pos-bgOffset['left'];if(left<0){left=0}if(left>bg.getWidth()-slider.getWidth()){left=bg.getWidth()-slider.getWidth()}C['bar']['pbox'].addClass('ck-bar-progress-slider-move');var time='';if(!vars['live']){time=left*duration/(bg.getWidth()-slider.getWidth());switch(vars['timeScheduleAdjust']){case 2:if(timesliderDownTime){left=-1000;message(language['timeScheduleAdjust']['prohibitForward']);return}break;case 4:if(timemaxSeeTime){left=-1000;player.seek(maxSeeTime);message(language['timeScheduleAdjust']['prohibitForwardNotViewed']);return}break}time=formatSeconds(time)}else{time=formatLiveTime(left*getLiveTime()/(bg.getWidth()-slider.getWidth()))}tip(slider,time);if(left>=0){slider.css({'left':left+'px'});play.css({'width':left+slider.getWidth()*.5+'px'})}else{pSliderMouseDown=false}};var docMouseUp=function(){noScrolling();doc.removeListener('mousemove',docMouseMove);doc.removeListener('mouseup',docMouseUp);slider.removeListener('touchmove',docMouseMove);slider.removeListener('touchend',docMouseUp);slider.mousedown(sliderMouseDown);slider.touchstart(sliderMouseDown);if(!vars['live']){var time=play.getWidth()*duration/(bg.getWidth()-slider.getWidth()*.5);if(time>duration){time=duration}seekTime=0;player.seek(time)}else{var playbackTime=parseInt(vars['live']+play.getWidth()*getLiveTime()/(bg.getWidth()-slider.getWidth()*.5));eventTarget('playback',{time:playbackTime,date:date('Y-m-d H:i:s',playbackTime)});eventTarget('seek',{time:playbackTime,state:'seeking',date:date('Y-m-d H:i:s',playbackTime)})}C['bar']['pbox'].removeClass('ck-bar-progress-slider-move');if(isMouseLeave){tip()}};var isMouseLeave=true;var sliderMouseOver=function(){var time='';if(!vars['live']){time=formatSeconds(parseInt(slider.css('left'))*duration/(bg.getWidth()-slider.getWidth()))}else{time=language['live']}isMouseLeave=false;tip(slider,time)};var sliderMouseLeave=function(){isMouseLeave=true};slider.mousedown(sliderMouseDown);slider.mouseover(sliderMouseOver);slider.mouseleave(sliderMouseLeave);slider.touchstart(sliderMouseDown);var bgMouseDown=function(e){e=e||window.event;var client=getClient(e);var bgOffset=bg.offset();var w=client['x']-bgOffset['left'];if(CT.full){w+=CK.offset()['left']}if(!vars['live']){var time=duration*w/bg.getWidth();switch(vars['timeScheduleAdjust']){case 0:message(language['timeScheduleAdjust']['prohibit']);return;break;case 2:if(timeoldTime){message(language['timeScheduleAdjust']['prohibitForward']);return}break;case 4:if(timemaxSeeTime){message(language['timeScheduleAdjust']['prohibitForwardNotViewed']);return}break}seekTime=0;player.seek(time)}else{var playbackTime=parseInt(vars['live']+getLiveTime()*w/bg.getWidth());eventTarget('playback',{time:playbackTime,date:date('Y-m-d H:i:s',playbackTime)});eventTarget('seek',{time:playbackTime,state:'seeking',date:date('Y-m-d H:i:s',playbackTime)})}if(w>bg.getWidth()-slider.getWidth()*.5){w=bg.getWidth()-slider.getWidth()*.5}if(wmaxTop){sliderTop=maxTop}slider.css({'top':sliderTop+'px'});var ppMarginTop=sliderTop-minTop+slider.getHeight()*0.5;pp.css({'margin-top':ppMarginTop+'px'});var vol=(bg.getHeight()-ppMarginTop-slider.getHeight()*0.5)/(bg.getHeight()-slider.getHeight());player.volume(vol)};var docMouseUp=function(){noScrolling();doc.removeListener('mousemove',docMouseMove);doc.removeListener('mouseup',docMouseUp);slider.removeListener('touchmove',docMouseMove);slider.removeListener('touchend',docMouseUp);slider.mousedown(sliderMouseDown);slider.touchstart(sliderMouseDown);C['bar']['vbox']['mouseDown']=false};slider.mousedown(sliderMouseDown);slider.touchstart(sliderMouseDown);var bgMouseDown=function(e){e=e||window.event;var client=getClient(e);var bgOffset=bg.offset();var h=client['y']-bgOffset['top'];var max=bg.getHeight();var vol=1-h/max;player.volume(vol)};bg.mousedown(bgMouseDown)},changeProgress=function(time){if((valType(vars['live'])=='boolean'&&vars['live'])){return}if(!pSliderMouseDown){var bg=C['bar']['pbox']['bg'],slider=C['bar']['pbox']['slider'];var playW=(time/duration)*100;if(valType(vars['live'])=='number'){playW='100'}C['bar']['pbox']['play'].css({'width':playW+'%'});var sliderW=(slider.getWidth()/bg.getWidth())*100;var sliderLeft=playW-sliderW*.5;if(sliderLeft<0){sliderLeft=0}if(sliderLeft>100-sliderW){sliderLeft=100-sliderW}C['bar']['pbox']['slider'].css({'left':sliderLeft+'%'});checkProgressSlider()}},checkProgressSlider=function(){var bg=C['bar']['pbox']['bg'],slider=C['bar']['pbox']['slider'];var st=0;if(C['bar']['pbox'].css('transition-duration')){st=parseFloat(C['bar']['pbox'].css('transition-duration'))*1000+20}var checkThis=function(){var sliderLeft=parseInt(C['bar']['pbox']['slider'].css('left'));if(sliderLeft<0){sliderLeft=0;C['bar']['pbox']['slider'].css({'left':sliderLeft+'px'})}if(sliderLeft>bg.getWidth()-slider.getWidth()){sliderLeft=bg.getWidth()-slider.getWidth();C['bar']['pbox']['slider'].css({'left':sliderLeft+'px'})}};setTimeout(checkThis,st)},changeTopTime=function(){C['topBar']['timeEle'].htm(date('H:i:s'));setTimeout(changeTopTime,1000)},changeVolumeSlider=function(vol){if(!isUndefined(C['bar']['vbox'])){var box=C['bar']['vbox']['volume']['box'],bg=C['bar']['vbox']['volume']['bg'],pp=C['bar']['vbox']['volume']['pp'],slider=C['bar']['vbox']['volume']['slider'];C['bar']['vbox']['volume']['txt'].htm(parseInt(vol*100));var bgH=bg.getHeight();if(bgH){var ppMarginTop=bgH-vol*bgH;pp.css({'margin-top':ppMarginTop+'px'});var sliderTop=bg.offset()['top']-box.offset()['top']+ppMarginTop-slider.getHeight()*0.5;slider.css({'top':sliderTop+'px'})}}},changeLoad=function(){if(video){var len=video.buffered.length;if(len>0){var bufferEnd=video.buffered.end(len-1);if(loadTime=1000){last+=offset;appendFps(fps);fps=0}requestAnimationFrame(step)};var appendFps=function(fps){if(fps!=CT.fps){CT.fps=fps;replaceInformation('fps',fps);eventTarget('fps',fps)}};if(isUndefined(CT.fps)){CT.fps=0}step()},getElement=function(content){var ele=$(content);if(isUndefined(ele))return;if(valType(ele)=='htmlarray'){ele=ele[0]}if(isUndefined(ele.captureParentNode)){ele.captureParentNode=ele.parentNode;ele.prevElement=ele.prev();ele.nextElement=ele.next()}return ele},$=function(obj,eL){var parent=null;if(document){parent=document}if(!isUndefined(eL)){parent=eL}var res=[];if(obj){if(valType(obj)=='htmlobject'||valType(obj)=='object'){res=obj;if(!isUndefined(res.ckplayer)){return res}}else if(valType(obj)=='string'&&obj!=''){switch(obj.substr(0,1)){case'.':obj=obj.substr(1,obj.length);if(parent.getElementsByClassName){res=parent.getElementsByClassName(obj);if(!res.length){return null}}else if(!parent&&document.getElementsByClassName){res=document.getElementsByClassName(obj);if(!res.length){return null}}else{var reg=new RegExp(' '+obj+' ','i');var ele=null;if(parent.getElementsByTagName){ele=parent.getElementsByTagName('*')}else{ele=document.getElementsByTagName('*')}for(var i=0;i1){b=arr[1].substr(0,1).toLocaleUpperCase()+arr[1].substr(1,arr[1].length-1)}if(arr.length>2){b=arr[2].substr(0,1).toLocaleUpperCase()+arr[2].substr(1,arr[2].length-1)}return a+b+c}return str};if(isUndefined(value)){if(!isUndefined(key)&&valType(key)=='string'){if(this.currentStyle){return this.currentStyle[key]}else{return document.defaultView.getComputedStyle(this,null)[key]}}if(isUndefined(key)){if(this.currentStyle){return this.currentStyle}else{return document.defaultView.getComputedStyle(this,null)}}}if(valType(key)!='object'){var newKey=keyNew(key);if(this.length>1){for(var i=0;i=m+1){return this[m]}return null}return null};for(var i=0;imaxZ){maxZ=parseInt(temp)}}return maxZ}function getWindowSize(){return{width:window.innerWidth,height:window.innerHeight}}function standardization(o,n){var h={};var k;for(k in o){h[k]=o[k]}for(k in n){if(k in h){h[k]=n[k]}}return h}function mergeObj(o,n){var h={};var k;for(k in o){h[k]=o[k]}for(k in n){if(k in h){switch(valType(h[k])){case'object':if(valType(n[k])=='object'){h[k]=mergeObj(h[k],n[k])}break;default:if(valType(h[k])==valType(n[k])){h[k]=n[k]}break}}}return h}function valType(val){if(typeof val==='undefined')return'undefined';var str=Object.prototype.toString.call(val).split(' ')[1].replace(']','').toLowerCase();if(str.substr(0,4)=='html'&&(str.substr(-7)=='element'||str.substr(-8)=='document'||str=='window')){str='htmlobject'}if(str=='htmlcollection'||str=='nodelist'){str='htmlarray'}return str}function isUndefined(val){try{return valType(val)==='undefined'||val===undefined||val===null||(valType(val)==='number'&&isNaN(val))}catch(event){return true}return false}function prev(ele){var e=ele.previousSibling;if(e==null){return null}if(e.nodeType==3){var t=prev(e);if(t&&t.nodeType==1){return t}}else{if(e.nodeType==1){return e}else{return false}}}function next(ele){var e=ele.nextSibling;if(e==null){return null}if(e.nodeType==3){var t=next(e);if(t&&t.nodeType==1){return t}}else{if(e.nodeType==1){return e}else{return false}}}function addListener(ele,e,f,t){if(isUndefined(t)){t=false}if(ele.addEventListener){try{ele.addEventListener(e,f,t)}catch(event){}}else if(ele.attachEvent){try{ele.attachEvent('on'+e,f)}catch(event){}}else{ele['on'+e]=f}if(isUndefined(ele.listenerList)){ele.listenerList=[]}ele.listenerList.push([e,f]);return ele};function removeListener(ele,e,f,t){if(isUndefined(t)){t=false}if(ele.removeEventListener){try{ele.removeEventListener(e,f,t)}catch(e){}}else if(ele.detachEvent){try{ele.detachEvent('on'+e,f)}catch(e){}}else{ele['on'+e]=null}if(!isUndefined(ele.listenerList)){var temp=[];for(var i=0;i>0},W:function(){var a=f.z(),b=364+f.L()-a;var nd2,nd=(new Date(jsdate.getFullYear()+'/1/1').getDay()||7)-1;if(b<=2&&((jsdate.getDay()||7)-1)<=2-b){return 1}else{if(a<=2&&nd>=4&&a>=(6-nd)){nd2=new Date(jsdate.getFullYear()-1+'/12/31');return date('W',Math.round(nd2.getTime()/1000))}else{return(1+(nd<=3?((a+nd)/7):(a-(7-nd))/7)>>0)}}},F:function(){return txt_months[f.n()]},m:function(){return pad(f.n(),2)},M:function(){return f.F().substr(0,3)},n:function(){return jsdate.getMonth()+1},t:function(){var n;if((n=jsdate.getMonth()+1)==2){return 28+f.L()}else{if(n&1&&n<8||!(n&1)&&n>7){return 31}else{return 30}}},L:function(){var y=f.Y();return(!(y&3)&&(y%1e2||!(y%4e2)))?1:0},Y:function(){return jsdate.getFullYear()},y:function(){return(jsdate.getFullYear()+'').slice(2)},a:function(){return jsdate.getHours()>11?'pm':'am'},A:function(){return f.a().toUpperCase()},B:function(){var off=(jsdate.getTimezoneOffset()+60)*60;var theSeconds=(jsdate.getHours()*3600)+(jsdate.getMinutes()*60)+jsdate.getSeconds()+off;var beat=Math.floor(theSeconds/86.4);if(beat>1000)beat-=1000;if(beat<0)beat+=1000;if((String(beat)).length==1)beat='00'+beat;if((String(beat)).length==2)beat='0'+beat;return beat},g:function(){return jsdate.getHours()%12||12},G:function(){return jsdate.getHours()},h:function(){return pad(f.g(),2)},H:function(){return pad(jsdate.getHours(),2)},i:function(){return pad(jsdate.getMinutes(),2)},s:function(){return pad(jsdate.getSeconds(),2)},O:function(){var t=pad(Math.abs(jsdate.getTimezoneOffset()/60*100),4);if(jsdate.getTimezoneOffset()>0)t='-'+t;else t='+'+t;return t},P:function(){var O=f.O();return(O.substr(0,3)+':'+O.substr(3,2))},c:function(){return f.Y()+'-'+f.m()+'-'+f.d()+'T'+f.h()+':'+f.i()+':'+f.s()+f.P()},U:function(){return Math.round(jsdate.getTime()/1000)}};return format.replace(/([a-zA-Z])/g,function(t,s){var ret='';if(t!=s){ret=s}else if(f[s]){ret=f[s]()}else{ret=s}return ret})}function dataURLtoBlob(dataurl){var arr=dataurl.split(','),mime=arr[0].match(/:(.*?);/)[1],bstr=atob(arr[1]),n=bstr.length,u8arr=new Uint8Array(n);while(n--){u8arr[n]=bstr.charCodeAt(n)}return new Blob([u8arr],{type:mime})}function decodeURIString(obj){for(var k in obj){if(valType(obj[k])=='string'&&obj[k]){try{obj[k]=decodeURI(obj[k])}catch(event){}}if(valType(obj[k])=='object'){obj[k]=decodeURIString(obj[k])}}return obj}function getApp(){var u=navigator.userAgent.toLowerCase();if(u.indexOf('iphone')>-1){return'iphone'}}function cookie(name,value,domain,path){if(isUndefined(domain)){domain=''}if(isUndefined(path)){path='/'}var ckStr=';domain='+domain+';path='+path;if(location.protocol=='https'){ckStr+=';SameSite=None;Secure=true'}var set=function(name,value){var time=360*24*60*60*1000;var exp=new Date();exp.setTime(exp.getTime()+time);try{document.cookie=name+'='+escape(value)+';expires='+exp.toGMTString()+ckStr}catch(event){console.error(event)}},get=function(name){var arr,reg=new RegExp('(^| )'+name+'=([^;]*)(;|$)');if(arr=document.cookie.match(reg)){return unescape(arr[2])}else{return null}},del=function(name){var exp=new Date();exp.setTime(exp.getTime()-1);var cval=get(name);if(cval!=null){document.cookie=name+'='+cval+';expires='+exp.toGMTString()+ckStr}};if(!isUndefined(name)&&!isUndefined(value)){if(value=='delete'){del(name);return null}else{set(name,value);return get(name)}}else if(!isUndefined(name)&&isUndefined(value)){return get(name)}else if(!isUndefined(name)){}}function arrayToString(arr){var str='';if(!isUndefined(arr)){for(var i=0;i0){str+=','}for(var y=0;y0){str+=';'}if(valType(temp[y])=='number'){str+=parseInt(temp[y]*100)}else{str+=temp[y]}}}}return str}function stringToArray(str){var newArr=[];if(!isUndefined(str)){var arr=str.split(',');for(var i=0;i0?false:true;if(!isUndefined(CT)&&CT!=null){if(valType(CT)=='htmlarray'){CT=CT.eq(0)}}else{return null}calculationFps();existenceObj=valType(vars['video'])=='string'&&((vars['video'].substr(0,1)=='.'&&vars['video'].indexOf('/')==-1)||vars['video'].substr(0,1)=='#');if(existenceObj){CK=CT.find('.ckplayer-ckplayer')?CT.find('.ckplayer-ckplayer').eq(0):null;if(CK){CM=CK.find('.ck-main')?CK.find('.ck-main').eq(0):null}if(CM){CV=CM.find('.ck-video')?CM.find('.ck-video').eq(0):null}}if(!CK||!CM||!CV){CT.htm('');CK=createlDiv('ckplayer-ckplayer');CT.append(CK);CM=createlDiv('ck-main');CK.append(CM);CV=createlDiv('ck-video');CM.append(CV)}if(video){video.remove();video=null}if(existenceObj){video=$(vars['video'])?$(vars['video']).eq(0):null}if(!video){video=createlVideo();video.attr('width','100%').attr('height','100%');CV.append(video)}video.volume=vars['volume'];if(vars['autoplay']){player.volume(0);player.muted();recoveryVolume=true;video.attr('autoplay','autoplay');paused=false}else{video.attr('preload','metadata')}CT.loop=vars['loop'];if(CT.loop){video.attr('loop','loop')}if(vars['controls']){video.controls=true}else{video.controls=false}try{if(!existenceObj){video.attr('controlslist','nodownload');video.attr('x-webkit-airplay','true');video.attr('x5-video-orientation','portraint');video.attr('playsinline','true');video.attr('webkit-playsinline','true');video.attr('x5-playsinline','true')}if(vars['crossOrigin']){video.useCORS=true;video.crossOrigin=vars['crossOrigin']}}catch(event){}CT.smallWindows=true;CT.webFull=false;CT.theatre=false;loadFace();if(vars['rotate']>0){player.rotate(vars['rotate'])}if(vars['zoom']>0){player.zoom(vars['zoom'])}if(isUndefined(video.canPlayType)){CT.error={code:5,message:language['error']['supportVideoError']};eventTarget('error',CT.error);showError();return player}addAllListener();if(!isUndefined(vars['loaded'])){if(valType(vars['loaded'])=='function'){try{vars['loaded'](player)}catch(event){}}else if(valType(vars['loaded'])=='string'){try{eval(vars['loaded']+'(player)')}catch(event){}}}if(!existenceObj){changeVideo(vars['video'])}else{changeVideo(video.attr('src'))}return player},changeVideo=function(vstr){var i=0;if(video.attr('src')||video.htm()){player.pause()}if(video.attr('src')&&!existenceObj){video.attr('src','');video.removeAttr('src')}if(!isUndefined(video.textTracks)&&video.textTracks.length>0){for(i=video.find('track').length-1;i>-1;i--){video.find('track').eq(i).remove()}}if(!existenceObj){video.htm('')}if(!isUndefined(vars['ad'])){ad=vars['ad']}var source='';if(valType(vstr)=='string'){if(vstr.substr(0,8)=='website:'||vstr.substr(0,4)=='url:'){var ajaxUrl='';if(vstr.substr(0,8)=='website:'){ajaxUrl=vstr.substring(8)}if(vstr.substr(0,4)=='url:'){ajaxUrl=vstr.substring(4)}ajax({url:ajaxUrl,success:function(data){if(data!=null){vars=standardization(varsTemp,data);changeVideo(vars['video'])}else{CT.error={code:8,message:language['error']['ajax']};eventTarget('error',CT.error);showError()}}});return}else{if(vars['plug']&&!canPlay(vstr)){plugPlayer(vstr)}else{if(!existenceObj){video.attr('src',vstr)}}loadTrack()}}else if(valType(vstr)=='array'){for(i=0;i1){source+=''}}}}if(source){video.htm(source)}loadTrack()}else if(valType(vstr)=='object'){if(vars['plug']){plugPlayer(vstr)}else{CT.error={code:10,message:language['error']['emptied']};eventTarget('error',CT.error);showError()}loadTrack()}if(!isUndefined(video.find('source'))&&video.find('source').length>0){video.find('source').eq(video.find('source').length-1).addListener('error',videoHandler.error)}checkBar();loadLogo();if(isChangeDef){checkDefinition()}if(vars['cookie']){if(valType(vars['cookie'])=='array'&&vars['cookie'].length>=2){if(vars['cookie'][0]&&valType(vars['cookie'][0])=='string'){cookieName=vars['cookie'][0]}if(vars['cookie'][1]&&valType(vars['cookie'][1])=='number'){cookieTime=vars['cookie'][1]}}if(valType(vars['cookie'])=='object'&&!isUndefined(vars['cookie']['name'])){cookieName=vars['cookie']['name'];if(!isUndefined(vars['cookie']['hour'])&&valType(vars['cookie']['hour'])=='number'){cookieTime=vars['cookie']['hour']}}if(valType(vars['cookie'])=='string'){cookieName=vars['cookie']}if(cookieName){cookieName=cookieName.replace(/[ ]*,[ ]*|[ ]+/g,'').replace(/[ ]*;[ ]*|[ ]+/g,'')}}if(!isUndefined(vars['seek'])&&!seekTime){if(valType(vars['seek'])=='number'&&vars['seek']>0){seekTime=vars['seek']}if(valType(vars['seek'])=='string'&&vars['seek']=='cookie'&&cookieName){var cke=player.cookie(cookieName);if(cke){seekTime=cke['time']}}}},plugPlayer=function(url){switch(vars['plug']){case'hls.js':hlsPlayer(url);break;case'flv.js':flvPlayer(url);break;case'mpegts.js':mpegtsPlayer(url);break;case'dash.js':dashPlayer(url);break;default:if(valType(vars['plug'])=='function'){vars['plug'](video,url)}else{CT.error={code:10,message:language['error']['emptied']};eventTarget('error',CT.error);showError()}break}},hlsPlayer=function(url){var path=getPath('hls.js')+'hls.min.js';loadJs(path,function(){if(!isUndefined(Hls)&&Hls.isSupported()){hls=new Hls();hls.loadSource(url);hls.attachMedia(video);hls.on(Hls.Events.ERROR,function(event,data){var code=9;var message=language['error']['loadingFailed'];var fatal=data.fatal;if(!isUndefined(data.response)){if(!isUndefined(data.response.code)){code=data.response.code}if(!isUndefined(data.response.text)&&data.response.text){message=data.response.text}else{if(!isUndefined(event)){message=event}}}CT.error={code:code,message:message};eventTarget('error',CT.error);if(fatal){showError()}})}else if(canPlay(url)){video.attr('src',url)}})},flvPlayer=function(url){var path=getPath('flv.js')+'flv.min.js';loadJs(path,function(){if(typeof(flvjs)!='undefined'&&!isUndefined(flvjs)&&flvjs.isSupported()){var config={type:'flv',url:url};if(valType(url)=='object'){config=url}if(vars['live']){config['isLive']=true}if(pugPlayer){pugPlayer.pause();pugPlayer.unload();pugPlayer.detachMediaElement();pugPlayer.destroy();pugPlayer=null}pugPlayer=flvjs.createPlayer(config);pugPlayer.attachMediaElement(video);pugPlayer.load();pugPlayer.on(flvjs.Events.METADATA_ARRIVED,function(res){videoHandler.loadedMetaData()});pugPlayer.on(flvjs.Events.ERROR,function(errorType,errorDetail,errorInfo){CT.error={code:errorInfo['code'],message:errorInfo['msg']};eventTarget('error',CT.error);showError()})}})},mpegtsPlayer=function(url){var path=getPath('mpegts.js')+'mpegts.js';loadJs(path,function(){if(typeof(mpegts)!='undefined'&&!isUndefined(mpegts)&&mpegts.getFeatureList().mseLivePlayback){var config={type:'mse',url:url};if(valType(url)=='object'){config=url}if(vars['live']){config['isLive']=true}if(pugPlayer){pugPlayer.pause();pugPlayer.unload();pugPlayer.detachMediaElement();pugPlayer.destroy();pugPlayer=null}pugPlayer=mpegts.createPlayer(config);pugPlayer.attachMediaElement(video);pugPlayer.load();pugPlayer.on(mpegts.Events.METADATA_ARRIVED,function(res){videoHandler.loadedMetaData()});pugPlayer.on(mpegts.Events.ERROR,function(errorType,errorDetail,errorInfo){CT.error={code:errorInfo['code'],message:errorInfo['msg']};eventTarget('error',CT.error);showError()})}})},canPlay=function(vStr,num){var vTypeArr=[{vidType:'video/ogg',codType:'theora, vorbis'},{vidType:'video/mp4',codType:'avc1.4D401E, mp4a.40.2'},{vidType:'video/mp4',codType:'avc1'},{vidType:'video/mp4',codType:'hevc'},{vidType:'video/webm',codType:'vp8.0, vorbis'},{vidType:'video/webm',codType:'vp9'},{vidType:'',codType:'application/x-mpegURL'},{vidType:'',codType:'application/vnd.apple.mpegurl'}];var vType={ogg:vTypeArr[0],mp4:[vTypeArr[1],vTypeArr[2],vTypeArr[3]],webm:[vTypeArr[4],vTypeArr[5]],m3u8:[vTypeArr[6],vTypeArr[7]]};var arr=[];var k='';var supportType=function(obj){var vType=obj['vidType'];var str='';if(vType){str=vType+'; codecs="'+obj['codType']+'"'}else{str=obj['codType']}var sup=video.canPlayType(str);if(sup==''){sup='no'}return sup};var getExtension=function(filepath){return filepath.replace(/.+\./,'')};if(isUndefined(num)){if(valType(vStr)=='string'){if(vars['type']){for(k in vTypeArr){if(vTypeArr[k]['vidType']==vars['type']){arr.push(vTypeArr[k])}}}else{if(getExtension(vStr)&&getExtension(vStr)in vType){var temp=vType[getExtension(vStr)];if(valType(temp)=='array'){for(var i=0;i0){for(var i=0;icTime&&arr[i][0]!=cookieName&&y<19){tempArr.push(arr[i]);y++}}arr=tempArr}if(arr.length>0){cookie(ckplayerCookie,'delete',vars['domain'],vars['cookiePath']);cookie(ckplayerCookie,arrayToString(arr),vars['domain'],vars['cookiePath'])}cookieArray=arr},loadTrack=function(){if(isUndefined(video.textTracks)){return}var i=0;if(!isUndefined(vars['track'])&&valType(vars['track'])=='array'&&!video.find('track')){var track='';var arr=vars['track'];C['bar']['track'].show();for(i=0;i'}if(video){var html=video.htm()+track;video.htm(html)}if(video.find('track')&&!loadedTrack){loadedTrack=true;checkTrack()}}},loadedMetaData=function(){eventTarget('loadedMetaData',{width:CK.getWidth(),height:CK.getHeight(),videoWidth:CT.videoWidth,videoHeight:CT.videoHeight,duration:duration,volume:CT.volume});eventTarget('duration',duration);if('error'in C){C['error'].hide()}replaceInformation('videoWidth',CT.videoWidth);replaceInformation('videoHeight',CT.videoHeight);replaceInformation('volume',parseInt(CT.volume*100));replaceInformation('duration',parseInt(duration));documentHidden(function(state){if(vars['documentFocusPause']){if(state=='show'){if(!focusPause&&paused){player.play()}}else{focusPause=paused;player.pause()}}})};var videoHandler={loadStart:function(){if(!vars['controls']&&app!='iphone'){C['loading'].show()}C['centerPlay'].hide();eventTarget('loadstart')},canPlay:function(){C['loading'].hide();eventTarget('canplay')},loadedData:function(){eventTarget('loadeddata')},loadedMetaData:function(){duration=this.duration;if(!isUndefined(this.duration)&&vars['duration']){duration=vars['duration']}if(!vars['live']){C['time'].htm(formatSeconds(this.currentTime)+'/'+formatSeconds(duration));C['tempTime'].htm(formatSeconds(this.currentTime)+'/'+formatSeconds(duration))}C['loading'].hide();if(vars['poster']){video.attr('poster',vars['poster'])}if(!vars['autoplay']&&!vars['controls']){C['centerPlay'].show();C['buffer'].hide()}CT.duration=duration;CT.videoWidth=this.videoWidth;CT.videoHeight=this.videoHeight;CT.volume=this.volume;if(CT.videoWidth||CT.videoHeight||duration){loadedMetaData()}var len=0;if(!isUndefined(this.buffered)){len=this.buffered.length}if(len>0){changeLoad()}if(!vars['live']){changePreview(vars['preview'])}else{changePreview()}if(!isUndefined(vars['prompt'])&&valType(vars['prompt'])=='array'&&!vars['live']){changePrompt()}else{deletePrompt()}if(isChangeDef){checkDefinition();isChangeDef=false}pSliderMouseDown=false;loadedmetadataNum++;if(!isUndefined(ad)&&!isUndefined(ad['frontPlay'])&&ad['frontPlay']&&loadedmetadataNum>1){player.play();calculationAdFrontTime(duration)}if(!isUndefined(ad)&&!isUndefined(ad['frontPlay'])&&!ad['frontPlay']){player.volume(vars['volume']);player.play()}if(!vars['autoplay']&&loadMeta){loadMeta=false;player.pause();setTimeout(player.pause,300)}},timeUpDate:function(){if(!waited){waited=true;C['buffer'].hide();eventTarget('buffer','end')}var len=this.buffered.length;if(len>0){changeLoad()}if(!duration&&this.duration){duration=this.duration;CT.duration=duration;CT.videoWidth=this.videoWidth;CT.videoHeight=this.videoHeight;if(CT.videoWidth||CT.videoHeight||duration){loadedMetaData()}}oldTime=playTime;playTime=this.currentTime;if(maxSeeTime1){calculationAdFrontTime(duration-playTime)}if(!isUndefined(C['error'])&&C['error'].css('display')=='block'){C['error'].hide()}if(cookieName){changeCookie(playTime)}replaceInformation('audioDecodedByteCount',this.webkitAudioDecodedByteCount||this.audioDecodedByteCount||0);replaceInformation('videoDecodedByteCount',this.webkitVideoDecodedByteCount||this.videoDecodedByteCount||0)},ended:function(){if(!isUndefined(ad)&&!isUndefined(ad['frontPlay'])&&ad['frontPlay']){adFrontEnded()}else{C['bar']['playAndPause']['pause'].hide();C['bar']['playAndPause']['play'].hide();C['bar']['playAndPause']['refresh'].show();eventTarget('ended');showEnded();message()}},error:function(event){if(!isUndefined(event.type)){var errorInfo=this.error;var errorBak=function(){var code=12;var msg=language['error']['loadingFailed'];if(!isUndefined(errorInfo)){if(!isUndefined(errorInfo.code)){code=errorInfo.code}if(!isUndefined(errorInfo.message)){msg=errorInfo.message}CT.error={code:code,message:msg};showError()}else{CT.error={code:code,message:msg};if(event.target.attr('src')){showError()}}eventTarget('error',CT.error)};if(event.type=='error'){try{if(video.currentSrc){ajax({url:video.currentSrc,error:function(info){if(info&&valType(info)=='object'&&'code'in info&&info['code']){CT.error=info;eventTarget('error',CT.error);showError()}else{errorBak()}},success:function(data){if(!data){errorBak()}}})}else{errorBak()}}catch(event){errorBak()}}}},stalled:function(){eventTarget('stalled');CT.error={code:13,message:'load:stalled'};eventTarget('error',CT.error)},suspend:function(){eventTarget('suspend')},emptied:function(){eventTarget('emptied');CT.error={code:7,message:language['error']['emptied']};eventTarget('error',CT.error)},seeked:function(){if(paused){player.play()}pSliderMouseDown=false;eventTarget('seek',{time:this.currentTime,state:'seeked'});eventTarget('seeked')},seeking:function(){if(paused){player.play()}var seekingTime=this.currentTime;if(firstSeekTime==-1){firstSeekTime=seekingTime}switch(vars['timeScheduleAdjust']){case 0:if(oldTime!=seekingTime){waitingMessage=false;player.seek(oldTime);message(language['timeScheduleAdjust']['prohibit']);return}break;case 2:if(seekingTimeoldTime){waitingMessage=false;player.seek(oldTime);message(language['timeScheduleAdjust']['prohibitForward']);return}break;case 4:if(seekingTimemaxSeeTime){waitingMessage=false;player.seek(maxSeeTime);message(language['timeScheduleAdjust']['prohibitForwardNotViewed']);return}break}eventTarget('seek',{time:seekingTime,state:'seeking'});eventTarget('seeking');if(!vars['live']){C['time'].htm(formatSeconds(seekingTime)+'/'+formatSeconds(duration));C['tempTime'].htm(formatSeconds(seekingTime)+'/'+formatSeconds(duration))}},waiting:function(){waited=false;eventTarget('buffer','start');C['centerPlay'].hide();if(!vars['controls']){C['buffer'].show()}if(!waitingMessage){waitingMessage=true}else{message(language['waiting'])}},play:function(){paused=false;C['bar']['playAndPause']['play'].hide();C['bar']['playAndPause']['pause'].show();C['bar']['playAndPause']['refresh'].hide();C['centerPlay'].hide();if(playType=='button'){C['bar']['pbox'].removeClass('ck-bar-progress-out')}else{C['bar']['pbox'].addClass('ck-bar-progress-out')}if(recoveryVolume){recoveryVolume=false;player.exitMuted()}hideBar();closePauseAd();playType='';if(!isUndefined(C['ended'])){player.closeLayer(C['ended'])}if('error'in C){C['error'].hide()}eventTarget('play');if(!isUndefined(ad)){if(!isUndefined(ad['front'])){if(isUndefined(ad['frontPlay'])){ad['frontPlayI']=0;ad['frontPlay']=true;player.pause();adFrontPlay();return}}}if(seekTime&&(isUndefined(vars['live'])||(!isUndefined(vars['live'])&&valType(vars['live'])=='boolean'&&!vars['live']))&&seekTime0&&mutedState){this.muted=false}if(mutedState!=this.muted){mutedState=this.muted;changeMuted(this.muted);CT.muted=this.muted;eventTarget('muted',CT.muted)}CT.volume=this.volume;eventTarget('volume',this.volume);if(!isUndefined(C['bar']['vbox'])){var vol=parseInt(this.volume*100);C['bar']['vbox']['volume']['txt'].htm(vol);if(!C['bar']['vbox']['mouseDown']){changeVolumeSlider(this.muted?0:this.volume)}if(!this.muted){message(language['volume']+vol+'%')}}replaceInformation('volume',parseInt(this.volume*100))},resize:function(){var fullState=document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen||document.msFullscreenElement;if(fullState){C['bar']['fullAndExit']['exitFull'].show();C['bar']['fullAndExit']['full'].hide();C['ad']['bottom']['fullAndExit']['exitFull'].show();C['ad']['bottom']['fullAndExit']['full'].hide();if(!CM.hasClass('ck-main-full')){CM.addClass('ck-main-full')}message(language['full']);C['bar']['webFullAndExit'].hide();C['bar']['theatreAndExit'].hide();if(!isUndefined(C['menu'])){CM.append(C['menu'])}}else{fullState=false;C['bar']['fullAndExit']['full'].show();C['bar']['fullAndExit']['exitFull'].hide();C['ad']['bottom']['fullAndExit']['full'].show();C['ad']['bottom']['fullAndExit']['exitFull'].hide();if(CM.hasClass('ck-main-full')){CM.removeClass('ck-main-full')}if(!isUndefined(vars['webFull'])&&vars['webFull']){C['bar']['webFullAndExit'].show()}if(!isUndefined(vars['theatre'])&&!CT.webFull){C['bar']['theatreAndExit'].show()}if(!isUndefined(C['menu'])){$('body').append(C['menu'])}if(CV.css('width')!=CM.css('width')){player.zoom(100)}}if(CT.full!=fullState){CT.full=fullState;eventTarget('full',fullState)}checkVideoRotate();changeProgress(player.time())},keydown:function(event){event=window.event||event;var keycode=event.keyCode||event.which;var v=player.volume();var pre=function(){if(event.preventDefault){event.preventDefault()}else{event.returnValue=false}};switch(keycode){case 32:pre();player.playOrPause();break;case 37:player.fastBack();break;case 39:player.fastNext();break;case 38:if(valType(vars['keyVolume'])=='number'&&(vars['keyVolume']==1||(vars['keyVolume']==2&&CT.full))){pre();player.volume(v+.1<1?v+=.1:1)}break;case 40:if(valType(vars['keyVolume'])=='number'&&(vars['keyVolume']==1||(vars['keyVolume']==2&&CT.full))){pre();player.volume(v-.1>0?v-=.1:0)}break;default:break}},mouseWheel:function(event){if(valType(vars['mouseWheelVolume'])=='number'&&(vars['mouseWheelVolume']==1||(vars['mouseWheelVolume']==2&&CT.full))){event=window.event||event;var v=player.volume();if(event.preventDefault){event.preventDefault()}else{event.returnValue=false}if(event.wheelDelta){if(event.wheelDelta>0){player.volume(v+.1<1?v+=.1:1)}if(event.wheelDelta<0){player.volume(v-.1>0?v-=.1:0)}}else if(event.detail){if(event.detail>0){player.volume(v-.1>0?v-=.1:0)}if(event.detail<0){player.volume(v+.1<1?v+=.1:1)}}}}};var adFrontPlay=function(){if(ad['frontPlayI']==0){allBarHide(false);C['ad'].show();ad['front']['video']=false;if(!isUndefined(ad['front']['closeTime'])){if(ad['front']['closeTime']>0){C['ad']['top']['closeTime'].show();adFrontCloseTime()}else{C['ad']['top']['closeAd'].show()}}}if(!isUndefined(ad['front']['node'])&&ad['front']['node']!=null){CM.append(ad['front']['node']);player.closeLayer(ad['front']['node']);ad['front']['node']=null}C['ad']['picture'].htm('');C['ad']['picture'].hide();C['ad']['link'].hide();if(!isUndefined(ad['front']['list'][ad['frontPlayI']])){var adv=ad['front']['list'][ad['frontPlayI']];frontAdPlay=true;eventTarget('frontAd',adv);if(adv['type']=='picture'){adPicturePlay(adv)}else if(adv['type']=='node'){adNode(adv)}else{C['ad']['link'].attr('data-link',adv['link']);adVideoPlay(adv['file'])}if(!isUndefined(adv['link'])){C['ad']['bottom']['details'].show();C['ad']['bottom']['details'].htm(''+language['details']+'')}else{C['ad']['bottom']['details'].hide()}}},adFrontCloseTime=function(){if(!isUndefined(ad['front']['closeSetTime'])){clearTimeout(ad['front']['closeSetTime']);ad['front']['closeSetTime']=null}C['ad']['top']['closeTime'].htmReplace('{seconds}',ad['front']['closeTime'],language['closeTime']);ad['front']['closeTime']--;if(ad['front']['closeTime']>0){ad['front']['closeSetTime']=setTimeout(adFrontCloseTime,1000)}else{C['ad']['top']['closeAd'].show();C['ad']['top']['closeTime'].hide()}},calculationAdFrontTime=function(t){if(!isUndefined(t)){ad['front']['list'][ad['frontPlayI']]['time']=t}ad['front']['countDown']=0;for(var i=ad['frontPlayI'];i0){adv['time']--;ad['front']['countDown']--;C['ad']['top']['countDown'].htm(ad['front']['countDown']+language['second']);adFrontSetTime=setTimeout(adFrontCountDown,1000)}else{adFrontEnded()}},adFrontEnded=function(){if(ad['frontPlayI']0||h>0){if(adv['link']){var a=createlA('',adv['link']);a.append(this);C['ad']['picture'].append(a)}else{C['ad']['picture'].append(this)}img.unbind();img.click(function(){eventTarget('frontAdClick',adv)})}else{if(imgloadNum<10){imgloadNum++;imgLoadHandler()}}})};imgLoadHandler();calculationAdFrontTime();adFrontCountDown();C['ad']['bottom']['mutedAndExit'].hide()},adNode=function(adv){if(isUndefined(adv['content']))return;ad['front']['node']=player.layer(adv);ad['front']['node'].css({'z-index':1});C['ad'].append(ad['front']['node']);ad['front']['node'].unbind();ad['front']['node'].click(function(){eventTarget('frontAdClick',adv)});calculationAdFrontTime();adFrontCountDown();C['ad']['bottom']['mutedAndExit'].hide()},frontVideoClickHandler=function(){var url=C['ad']['link'].attr('data-link');window.open(url,'_blank')},closeFrontAd=function(){if(!isUndefined(ad)&&!isUndefined(ad['front'])&&!isUndefined(ad['front']['closeButtonClick'])){if(valType(ad['front']['closeButtonClick'])=='function'){try{ad['front']['closeButtonClick']()}catch(event){console.error(event)}}else if(valType(ad['front']['closeButtonClick'])=='string'){try{eval(ad['front']['closeButtonClick']+'()')}catch(event){console.error(event)}}}else{player.closeFrontAd()}},adPausePlay=function(){closePauseAd();if(!isUndefined(ad)&&!isUndefined(ad['pause'])&&!isUndefined(ad['pause']['list'])&&ad['pause']['list'].length-1>=ad['pausePlayI']){var adv=ad['pause']['list'][ad['pausePlayI']];if(!isUndefined(ad['pause']['close'])&&ad['pause']['close']){ad['pauseClose']=createlButton('ck-pause-close');ad['pauseClose'].click(function(){C['centerPlay'].show();C['buffer'].hide();closePauseAd()})}if(!isUndefined(adv['file'])){var img=null;var imgloadNum=0;var imgLoadHandler=function(){img=createlImg(adv['file']);C['adPause'].show();C['adPause'].htm('');C['adPause'].append(img);img.addListener('load',function(){var w=this.getWidth(),h=this.getHeight();if(w>0||h>0){C['adPause'].htm('');if(adv['link']){var a=createlA('',adv['link']);a.append(this);C['adPause'].append(a)}else{C['adPause'].append(this)}C['adPause'].css({'width':w+'px','height':h+'px'});if(ad['pauseClose']){C['adPause'].append(ad['pauseClose'])}img.click(function(){eventTarget('pauseAdClick',adv)})}else{if(imgloadNum<10){imgloadNum++;imgLoadHandler()}}})};imgLoadHandler()}else if(!isUndefined(adv['content'])){ad['pause']['node']=player.layer(adv);if(ad['pauseClose']){ad['pause']['node'].append(ad['pauseClose'])}ad['pause']['node'].click(function(){eventTarget('pauseAdClick',adv)})}eventTarget('pauseAd',adv);if(!isUndefined(adv['time'])&&adv['time']>0){adPauseSetTime=setTimeout(function(){ad['pausePlayI']++;if(ad['pausePlayI']>ad['pause']['list'].length-1){ad['pausePlayI']=0}adPausePlay()},adv['time']*1000)}}},closePauseAd=function(){if(adPauseSetTime){clearTimeout(adPauseSetTime);adPauseSetTime=null}C['adPause'].hide();if(!isUndefined(ad)&&!isUndefined(ad['pauseClose'])&&ad['pauseClose']!=null){ad['pauseClose'].remove();ad['pauseClose']=null}if(!isUndefined(ad)&&!isUndefined(ad['pause'])&&!isUndefined(ad['pause']['node'])&&ad['pause']['node']){ad['pause']['node'].unbind();player.closeLayer(ad['pause']['node']);eventTarget('pauseAdClose')}};var player={width:function(value){if(!isUndefined(value)){if(valType(value)=='number'){value+='px'}CT.css({'width':value});checkVideoRotate()}return CK.getWidth()},height:function(value){if(!isUndefined(value)){if(valType(value)=='number'){value+='px'}CT.css({'height':value});checkVideoRotate()}return CK.getHeight()},videoWidth:function(){if(video){return video.videoWidth}return null},videoHeight:function(){if(video){return video.videoHeight}return null},zoom:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('zoom',fn)}if(valType(fn)=='number'){var arr=[C['topBar']['zoomEle']['zoom50'],C['topBar']['zoomEle']['zoom75'],C['topBar']['zoomEle']['zoom100']];for(var i=0;ifn&&fn>0){rot=arr[fn]}else{if(arr.indexOf(fn)>-1){rot=fn}}if(rot!=nowRotate){eventTarget('rotate',rot);nowRotate=rot;checkVideoRotate()}}}},vars:function(obj,val){if(isUndefined(obj)){return}if(valType(obj)=='string'&&!isUndefined(val)){var temp={};temp[obj]=val;obj=temp}var isChange=false;for(var k in obj){if(k in vars&&obj[k]!=vars[k]){isChange=true;break}}if(isChange){vars=standardization(vars,obj)}if(!isUndefined(obj['video'])){isChangeDef=true;changeVideo(vars['video'])}if(!isUndefined(obj['title'])){C['topBar']['titleEle'].htm(obj['title'])}},loadstart:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('loadstart',fn)}},loadeddata:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('loadeddata',fn)}},loadedMetaData:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('loadedMetaData',fn)}},canplay:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('canplay',fn)}},play:function(fn){if(valType(fn)=='object'){if(fn['target']==C['bar']['playAndPause']['play']||fn['target']==C['bar']['playAndPause']['refresh']){playType='button'}}if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('play',fn)}else{if(loadedmetadataNum>0&&video){try{video.play()}catch(event){console.error(event)}}}return player},pause:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('pause',fn)}else{if(loadedmetadataNum>0&&video){try{video.pause()}catch(event){console.error(event)}}}return player},playOrPause:function(){if(paused){player.play()}else{player.pause()}return player},volume:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('volume',fn)}else if(valType(fn)=='number'&&video){if(fn<0)fn=0;if(fn>1)fn=1;if(video.muted){video.volume=fn*.5}video.volume=fn}}else if(video){return video.volume}return player},muted:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('muted',fn)}else if(video){video.muted=true}return player},exitMuted:function(){if(video){video.muted=false;if(video.volume==0){player.volume(vars['volume']>0?vars['volume']:.8)}}return player},time:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('time',fn)}else if(video){return video.currentTime||0}},seek:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('seek',fn)}if(valType(fn)=='number'&&video){switch(vars['timeScheduleAdjust']){case 0:message(language['timeScheduleAdjust']['prohibit']);return;break;case 1:break;case 2:if(fnoldTime){message(language['timeScheduleAdjust']['prohibitForward']);return}break;case 4:if(fnmaxSeeTime){waitingMessage=false;video.currentTime=maxSeeTime;message(language['timeScheduleAdjust']['prohibitForwardNotViewed']);return}break}pSliderMouseDown=false;video.currentTime=fn}}return player},buffer:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('buffer',fn)}return player},ended:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('ended',fn)}return player},next:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('next',fn)}return player},error:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('error',fn)}return player},emptied:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('emptied',fn)}return player},duration:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('duration',fn)}else{return duration||0}},playbackRate:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('playbackRate',fn)}else if(valType(fn)=='number'&&video){video.playbackRate=fn;CT.playbackRate=fn;eventTarget('playbackRate',fn);changePlaybackrateVal(fn)}}else{return video.playbackRate}},track:function(fn){if(!isUndefined(fn)){if(valType(fn)=='function'){newEvent.addEventListener('track',fn)}else if(valType(fn)=='number'&&video){var track=null;if(!isUndefined(video.textTracks)){track=video.textTracks}if(track&&valType(track)=='texttracklist'){var arr=vars['track'];for(var i=track.length-1;i>-1;i--){video.find('track').eq(i).remove()}for(var i=0;i'}if(video){var html=video.htm()+track;video.htm(html)}changeTrackVal(fn)}}}else{return CT.track}},fastBack:function(num){if(duration==0)return;if(isUndefined(num)||valType(num)!='number'){num=20}var time=player.time();if(time-num>0){time-=num}else{time=0}player.seek(time);return player},fastNext:function(num){if(duration==0)return;if(isUndefined(num)||valType(num)!='number'){num=20}var time=player.time();if(time+num200)zIndex=200;if(isUndefined(ele.cssDisplay)){ele.cssPosition=ele.css('position');ele.cssDisplay=ele.css('display')}ele.css({'position':'absolute','z-index':zIndex});if(!cBar){CM.append(ele)}else{C['bar'].append(ele)}ele.CK=CK;ele.show()}return ele},closeLayer:function(ele){if(valType(ele)=='string'){var temp=$(ele);if(!isUndefined(temp)){if(temp.length>0){ele=temp.eq(0)}}}if(!isUndefined(ele)&&valType(ele)=='htmlobject'){if(!isUndefined(ele.captureParentNode)){var eleP=ele.captureParentNode;if(ele.nextElement){try{eleP.insertBefore(ele,ele.nextElement)}catch(event){eleP.append(ele)}}else if(ele.prevElement){try{eleP.insertAfter(ele,ele.prevElement)}catch(event){eleP.append(ele)}}else{eleP.append(ele)}ele.css({'display':ele.cssDisplay})}else{ele.remove()}if(!isUndefined(ele.cssDisplay)){ele.css({'display':ele.cssDisplay,'position':ele.cssPosition})}}return player},message:function(str){message(str);return player},closeFrontAd:function(){if(frontAdPlay&&!isUndefined(ad)&&!isUndefined(ad['front'])&&!isUndefined(ad['front']['list'])){ad['frontPlayI']=ad['front']['list'].length-1;adFrontEnded();clearAdFrontSetTime()}return player},addListener:function(name,fn){newEvent.addEventListener(name,fn)},removeListener:function(name,fn){newEvent.removeEventListener(name,fn)},bar:function(bool){barShow=bool;if(bool){C['bar'].show()}else{C['bar'].hide()}},rightBar:function(bool){rightBarShow=bool;if(bool){C['rightBar'].show()}else{C['rightBar'].hide()}},mouseActive:function(fn){if(!isUndefined(fn)&&valType(fn)=='function'){newEvent.addEventListener('mouseActive',fn)}return this},cookie:function(name){if(name=='delete'){cookie(ckplayerCookie,'delete',vars['domain']);return null}if(!isUndefined(name)&&name){name=name.replace(/[ ]*,[ ]*|[ ]+/g,'').replace(/[ ]*;[ ]*|[ ]+/g,'')}var cStr=cookie(ckplayerCookie);if(cStr){var arr=stringToArray(cStr);var tempArr=[];var cTime=parseInt(Date.now()*0.001);for(i=0;icTime){tempArr.push(arr[i])}}arr=tempArr;if(arr.length>0){cookie(ckplayerCookie,'delete',vars['domain'],vars['cookiePath']);cookie(ckplayerCookie,arrayToString(arr),vars['domain'],vars['cookiePath']);for(var i=0;i-1;i--){var pTemp=createlButton('ck-list-p',playbackrate[i]+language['playbackrateSuffix']);pTemp.attr('data-i',i).attr('data-val',playbackrate[i]);C['bar']['playbackrate']['bg']['p'].push(pTemp);C['bar']['playbackrate']['bg'].append(pTemp);pTemp.click(playbackrateClick)}C['bar']['playbackrate'].hide();if(!isUndefined(vars['playbackrateOpen'])&&vars['playbackrateOpen']){C['bar']['playbackrate'].show();C['bar']['playbackrate']['bgbox'].show();var w=C['bar']['playbackrate']['bg'].getWidth(),h=C['bar']['playbackrate']['bg'].getHeight();var bw=C['bar']['playbackrate']['button'].getWidth();C['bar']['playbackrate']['bg'].css({'width':w+10+'px'});w=C['bar']['playbackrate']['bg'].getWidth();if(getBarHeight()>C['bar'].getHeight()){h+=(getBarHeight()-C['bar'].getHeight())}C['bar']['playbackrate']['bgbox'].attr('style','');C['bar']['playbackrate']['bgbox'].css({'width':w+'px','height':(h+2)+'px','left':-(w-bw)*0.5+'px'});C['bar']['playbackrate']['button'].mouseover(function(){w=C['bar']['playbackrate']['bg'].getWidth();bw=C['bar']['playbackrate']['button'].getWidth();C['bar']['playbackrate']['bgbox'].css({'left':-(w-bw)*0.5+'px'})});changePlaybackrateVal(pbr)}},playbackrateClick=function(e){C['bar']['playbackrate']['bgbox'].hide();setTimeout(function(){C['bar']['playbackrate']['bgbox'].removeCss('display')},300);message(language['switchTo']+$(this).htm());player.playbackRate(parseFloat($(this).attr('data-val')))},changePlaybackrateVal=function(num){C['bar']['playbackrate']['bg'].find('button').each(function(index,ele){var val=parseFloat(ele.attr('data-val'));ele.removeClass('ck-list-p-focus');if(num==val){ele.addClass('ck-list-p-focus');if(num!=1){C['bar']['playbackrate']['button'].htm(ele.htm())}else{C['bar']['playbackrate']['button'].htm(language['playbackrate'])}}else{ele.removeClass('ck-list-p-focus')}})},checkTrack=function(){var track=vars['track'];var pbr=-1;C['bar']['track']['bgbox'].htm('');C['bar']['track']['bg']=createlDiv('ck-list-bg');C['bar']['track']['bgbox'].append(C['bar']['track']['bg']);C['bar']['track']['bg']['p']=[];var pTemp=null;for(var i=track.length-1;i>-1;i--){pTemp=createlButton('ck-list-p',track[i]['label']);pTemp.attr('data-i',i).attr('data-title',track[i]['label']);C['bar']['track']['bg']['p'].push(pTemp);C['bar']['track']['bg'].append(pTemp);if(!isUndefined(track[i]['default'])&&track[i]['default']){pbr=i}pTemp.click(trackClick)}pTemp=createlButton('ck-list-p',language['noTrack']);pTemp.attr('data-i',-1).attr('data-title',language['noTrack']);C['bar']['track']['bg']['p'].push(pTemp);C['bar']['track']['bg'].append(pTemp);pTemp.click(trackClick);C['bar']['track'].hide();C['bar']['track'].show();C['bar']['track']['bgbox'].show();var w=C['bar']['track']['bg'].getWidth(),h=C['bar']['track']['bg'].getHeight();var bw=C['bar']['track']['button'].getWidth();C['bar']['track']['bg'].css({'width':w+10+'px'});w=C['bar']['track']['bg'].getWidth();if(getBarHeight()>C['bar'].getHeight()){h+=(getBarHeight()-C['bar'].getHeight())}C['bar']['track']['bgbox'].attr('style','');C['bar']['track']['bgbox'].css({'width':w+'px','height':(h+2)+'px','left':-(w-bw)*0.5+'px'});C['bar']['track']['button'].mouseover(function(){w=C['bar']['track']['bg'].getWidth();bw=C['bar']['track']['button'].getWidth();C['bar']['track']['bgbox'].css({'left':-(w-bw)*0.5+'px'})});changeTrackVal(pbr)},trackClick=function(e){C['bar']['track']['bgbox'].hide();setTimeout(function(){C['bar']['track']['bgbox'].removeCss('display')},300);message(language['switchTo']+$(this).attr('data-title'));player.track(parseFloat($(this).attr('data-i')))},changeTrackVal=function(num){C['bar']['track']['bg'].find('button').each(function(index,ele){var i=parseInt(ele.attr('data-i'));if(num==i){C['bar']['track']['button'].htm(ele.attr('data-title'));if(i>-1){CT.track=vars['track'][i];eventTarget('track',vars['track'][i])}else{CT.track=null;eventTarget('track',null)}ele.addClass('ck-list-p-focus')}else{ele.removeClass('ck-list-p-focus')}})},checkDefinition=function(){var pi=-1;var videoUrl=video['currentSrc'];if(!isUndefined(C['bar']['definition']['bg'])){C['bar']['definition']['bgbox'].htm('');C['bar']['definition']['bgbox'].attr('style','');C['bar']['definition']['button'].htm(language['definition'])}if(valType(vars['video'])=='array'){var arr=vars['video'];C['bar']['definition']['bg']=createlDiv('ck-list-bg');C['bar']['definition']['bgbox'].append(C['bar']['definition']['bg']);C['bar']['definition']['bg']['p']=[];for(var i=arr.length-1;i>-1;i--){var pTemp=createlButton('ck-list-p',arr[i][2]);pTemp.attr('data-i',i).attr('data-video',arr[i][0]).attr('data-type',arr[i][1]).attr('data-title',arr[i][2]);C['bar']['definition']['bg']['p'].push(pTemp);C['bar']['definition']['bg'].append(pTemp);if(videoUrl&&videoUrl.indexOf(arr[i][0])>-1){pi=i}pTemp.click(definitionClick)}C['bar']['definition'].show();C['bar']['definition']['bgbox'].show();var w=C['bar']['definition']['bg'].getWidth(),h=C['bar']['definition']['bg'].getHeight();var bw=C['bar']['definition']['button'].getWidth();C['bar']['definition']['bg'].css({'width':w+10+'px'});w=C['bar']['definition']['bg'].getWidth();if(getBarHeight()>C['bar'].getHeight()){h+=(getBarHeight()-C['bar'].getHeight())}C['bar']['definition']['bgbox'].attr('style','');C['bar']['definition']['bgbox'].css({'width':w+'px','height':(h+2)+'px','left':-(w-bw)*0.5+'px'});C['bar']['definition']['button'].mouseover(function(){w=C['bar']['definition']['bg'].getWidth();bw=C['bar']['definition']['button'].getWidth();C['bar']['definition']['bgbox'].css({'left':-(w-bw)*0.5+'px'})})}if(pi>-1){C['bar']['definition']['bg'].find('button').each(function(index,ele){var i=parseInt(ele.attr('data-i'));if(pi==i){ele.addClass('ck-list-p-focus')}else{ele.removeClass('ck-list-p-focus')}})}},definitionClick=function(e){C['bar']['definition']['bgbox'].hide();setTimeout(function(){C['bar']['definition']['bgbox'].removeCss('display')},300);changeDefinition(parseInt($(this).attr('data-i')))},changeDefinition=function(num){C['bar']['definition']['bg'].find('button').each(function(index,ele){var i=parseInt(ele.attr('data-i'));if(num==i){if(!isUndefined(video.textTracks)&&video.textTracks.length>0){for(var y=video.find('track').length-1;y>-1;y--){video.find('track').eq(y).remove()}}seekTime=player.time();video.attr('autoplay','autoplay');changeVideo(ele.attr('data-video'));message(language['switchTo']+ele.htm());C['bar']['definition']['button'].htm(ele.htm());ele.addClass('ck-list-p-focus');eventTarget('definition',{id:i,video:ele.attr('data-video'),title:ele.attr('data-title')})}else{ele.removeClass('ck-list-p-focus')}})},deletePrompt=function(){var i=0;if(!isUndefined(C['prompt'])&&valType(C['prompt'])=='array'){for(i=C['prompt'].length;i>-1;i--){if(!isUndefined(C['prompt'][i])){C['prompt'][i].remove()}}C['prompt']=null}C['prompt']=[]},changePrompt=function(){deletePrompt();for(var i=0;i100)left=100;ele.css({'left':left+'%'});ele.mouseover(function(){if(this.attr('data-words')){showPromptWords(this)}});ele.mouseout(function(){if(this.attr('data-words')){showPromptWords()}});C['prompt'].push(ele)}}},showPromptWords=function(ele){if(isUndefined(C['promptWords'])){C['promptWords']=createlDiv('ck-prompt-words');CM.append(C['promptWords']);C['promptWords']['content']=createlDiv('ck-prompt-content');C['promptWords'].append(C['promptWords']['content'])}if(isUndefined(ele)){C['promptWords']['content'].htm('');C['promptWords'].hide();return}C['promptWords']['content'].htm(ele.attr('data-words'));C['promptWords'].show();var pW=C['promptWords'].getWidth(),pH=C['promptWords'].getHeight();var bg=C['bar']['pbox']['bg'];var time=parseFloat(ele.attr('data-time'));var top=CK.getHeight()-getBarHeight();var left=ele.offset()['left']-CK.offset()['left']+ele.getWidth()*.5-pW*.5;if(!isUndefined(C['preview'])){top=CK.getHeight()-parseFloat((getBarHeight()+C['preview']['smallHight']+parseInt(C['preview'].css('margin-bottom'))))}else{top=CK.getHeight()-parseFloat((getBarHeight()+C['tip']['minHeight']+parseInt(C['tip'].css('margin-bottom'))));top-=(getBarHeight()-C['bar'].getHeight())}top-=parseFloat(C['promptWords'].css('margin-bottom'));top-=pH;left-=parseFloat(C['promptWords'].css('margin-right'));if(left<0)left=0;if(left>CK.getWidth()-pW)left=CK.getWidth()-pW;C['promptWords'].css({'top':top+'px','left':left+'px'})},changePreview=function(preview){if(!isUndefined(C['preview'])){C['preview'].remove();C['preview']=null}if(!isUndefined(preview)){C['preview']=createlDiv('ck-preview');CM.append(C['preview']);if(isUndefined(vars['preview']['type'])){vars['preview']['type']=0}if(vars['preview']['type']==1){C['preview']['bg']=createlDiv('ck-preview-bg');C['preview'].append(C['preview']['bg'])}var files=[];var loadImg=function(bigW,bigH,smallW,smallH){if(vars['preview']['type']==1){C['preview']['bg']['img']=[];for(var i=0;i1){if(valType(vars['preview']['thumbnail'][0])=='number'){thumbnail[0]=vars['preview']['thumbnail'][0]}if(valType(vars['preview']['thumbnail'][1])=='number'){thumbnail[1]=vars['preview']['thumbnail'][1]}vars['preview']['thumbnail']=thumbnail}if(files[0]){var img=createlImg(files[0],'ck-preview-load-img').addListener('load',function(){loadImg(this.getWidth(),this.getHeight(),this.getWidth()/thumbnail[0],this.getHeight()/thumbnail[1]);this.remove()});CM.append(img)}}else{C['preview'].removeChild();C['preview']=null}}else{C['preview'].removeChild();C['preview']=null}}},showPreview=function(time){if(hidePreviewSetTime){clearTimeout(hidePreviewSetTime);hidePreviewSetTime=null}if(time<0){if(!isUndefined(C['preview'])){hidePreviewSetTime=setTimeout(function(){C['preview'].hide();hidePreviewSetTime=null},200);return}}if(!isUndefined(C['preview'])&&!isUndefined(vars['preview']['thumbnail'])&&duration>0){if(isUndefined(vars['preview']['type'])){vars['preview']['type']=0}var scale=2,thumbnail=vars['preview']['thumbnail'];if(!isUndefined(vars['preview']['scale'])){scale=vars['preview']['scale']}var divI=0,imgI=0;var left=0,frameLeft=0;var pW=0;var smallW=C['preview']['smallWidth'],smallH=C['preview']['smallHight'];var bgXw=time*C['bar']['pbox']['bg'].getWidth()/duration+(C['bar']['pbox']['bg'].offset()['left']-CK.offset()['left']);if(C['preview'].css('display')=='none'){C['preview'].show()}C['preview'].css({'height':smallH+'px',});if(vars['preview']['type']==1){divI=parseInt(time/(scale*thumbnail[0]));imgI=parseInt(time/scale);var minI=divI-5,maxI=divI+5;if(minI<0)minI=0;if(maxI>C['preview']['bg']['img'].length-1){maxI=C['preview']['bg']['img'].length-1}for(var i=minI;iC['preview'].getWidth()-C['preview']['frame'].getWidth()){var xz=frameLeft-(C['preview'].getWidth()-C['preview']['frame'].getWidth());frameLeft=C['preview'].getWidth()-C['preview']['frame'].getWidth();left-=xz}C['preview']['bg'].css({'left':left+'px'});C['preview']['frame'].css({'left':frameLeft+'px'})}else{var temp=(time/scale)/(thumbnail[0]*thumbnail[1]);imgI=parseInt(temp);var smallI=parseInt((temp-imgI)*100);var rows=parseInt(smallI/thumbnail[0]);var column=smallI%thumbnail[0];var files=vars['preview']['file'];left=bgXw-smallW*.5;if(left<0){left=0}if(left>CK.getWidth()-smallW){left=CK.getWidth()-smallW}C['preview'].css({'width':C['preview']['smallWidth']+'px','background':'url('+files[imgI]+') no-repeat','background-position':'-'+column*smallW+'px -'+rows*smallH+'px','left':left+'px'})}}},showError=function(){C['loading'].hide();if(vars['controls']||!vars['errorShow']){return}if(isUndefined(C['error'])){C['error']=createlDiv('ck-error');CM.append(C['error'])}var msg=CT.error['message'];if(!msg)msg=language['error']['noMessage'];var html=msg+'(code:'+CT.error['code']+')';C['error'].htm(html);C['error'].show()},showWindowsError=function(error){var ele=createlDiv('ckplayer-error',error);$('body').append(ele)},showNextEpisode=function(){if(isUndefined(vars['next']))return;if(valType(vars['next'])=='object'){if('content'in vars['next']){C['next']=player.layer(vars['next']);return}if('title'in vars['next']){tip(C['bar']['nextEpisode'],vars['next']['title']);return}}tip(C['bar']['nextEpisode'],language['next'])},nextClick=function(){if(valType(vars['next'])=='object'){if('click'in vars['next']){if(valType(vars['next']['click'])=='function'){vars['next']['click']()}else if(valType(vars['next']['click'])=='string'){eval(vars['next']['click']+'()')}}if('link'in vars['next']){window.location.href=vars['next']['link']}eventTarget('next')}},showEnded=function(){if(!CT.loop&&!isUndefined(vars['ended'])){closePauseAd();C['ended']=player.layer(vars['ended']);if(C['ended']){C['centerPlay'].hide()}return}},windowScroll=function(){if(!CT.smallWindows)return;if(CT.webFull||CT.theatre||CT.full){exitSmallWindows();return}var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;if(scrollTop>CT.offset()['top']+CT.getHeight()){if(!CK.hasClass('ckplayer-ckplayer-smallwindow')){CK.addClass('ckplayer-ckplayer-smallwindow');eventTarget('smallWindows',true);smallWindowsState=true;if(vars['smallWindowsDrag']){drag(true)}allBarHide();checkVideoRotate()}}else{exitSmallWindows()}},exitSmallWindows=function(){if(CK.hasClass('ckplayer-ckplayer-smallwindow')){CK.removeClass('ckplayer-ckplayer-smallwindow');if(vars['smallWindowsDrag']){drag()}eventTarget('smallWindows',false);smallWindowsState=false;allBarShow();checkVideoRotate()}},drag=function(state){var doc=$(document);var posX=0,posY=0,posXRecord=0,posYRecord=0;var open=false;if(!isUndefined(state)){open=state}var mouseDown=function(e){e.preventDefault&&e.preventDefault();e=e||window.event;var client=getClient(e);posXRecord=client['x'];posYRecord=client['y'];doc.mousemove(docMouseMove);doc.mouseup(docMouseUp);CK.css({'cursor':'move'})};var docMouseMove=function(e){e=e||window.event;var client=getClient(e);var eleOffset=CK.offset();posY=client['y']-posYRecord;posX=client['x']-posXRecord;posXRecord=client['x'];posYRecord=client['y'];var left=eleOffset['left']+posX;var top=eleOffset['top']+posY;CK.css({'left':left+'px','top':top+'px'});if(CK.css('position')!='fixed'){CK.css({'position':'fixed'})}isDrag=true};var docMouseUp=function(){doc.removeListener('mousemove',docMouseMove);doc.removeListener('mouseup',docMouseUp);CK.css({'cursor':'default'})};if(open){CK.mousedown(mouseDown)}else{CK.unbind('mousedown')}},checkVideoRotate=function(){var ckW=CK.getWidth()*nowZoom*0.01,ckH=CK.getHeight()*nowZoom*0.01;var vW=CV.getWidth(),vH=CV.getHeight();CV.css({'transform':'rotate('+nowRotate+'deg)','-ms-transform':'rotate('+nowRotate+'deg)','-moz-transform':'rotate('+nowRotate+'deg)','-webkit-transform':'rotate('+nowRotate+'deg)','-o-transform':'rotate('+nowRotate+'deg)','width':nowZoom+'%','height':nowZoom+'%'});if(nowRotate!=0&&nowRotate!=180){if(vH>ckW&&vW>ckH){if(vH/ckW>vW/ckH){CV.css({'height':nowZoom+'%','width':ckH+'px'})}else{CV.css({'width':nowZoom+'%','height':ckW+'px'})}return}if(vHckH){CV.css({'height':nowZoom+'%','width':ckH+'px'})}else{if(vH/vW>=ckW/ckH){CV.css({'width':nowZoom+'%','height':ckW+'px'})}else{CV.css({'height':nowZoom+'%','width':ckH+'px'})}}return}if(vH>=ckW&&vW<=ckH){CV.css({'width':nowZoom+'%','height':ckW+'px'});return}if(vW>=ckH&&vHC['bar'].getHeight()){top-=(getBarHeight()-C['bar'].getHeight())}if(w>ew){left=offset['left']-ckOffset['left']-(w-ew)*.5}else{left=offset['left']-ckOffset['left']+(ew-w)*.5}if(align=='left'){left=offset['left']-ckOffset['left']-w;top=offset['top']-ckOffset['top']}if(!isUndefined(cl)){left=cl['x']-ckOffset['left']-w*.5}if(left<0){left=0}if(top<0){top=0}if(left>CM.getWidth()-w){left=CM.getWidth()-w}if(top>CM.getHeight()-h){top=CM.getHeight()-h}C['tip'].css({'left':left+'px','top':top-parseInt(C['tip'].css('margin-bottom'))+'px'})},progressDragX=function(){var bg=C['bar']['pbox']['bg'],play=C['bar']['pbox']['play'],slider=C['bar']['pbox']['slider'],mouseLine=C['bar']['pbox']['mouseLine'];slider.unbind();bg.unbind();var pos=0,posRecord=0;var doc=$(document);var sliderDownTime=0;var sliderMouseDown=function(e){e.preventDefault&&e.preventDefault();if(!vars['timeScheduleAdjust']){return}e=e||window.event;var eleOffset=slider.offset(),bgOffset=bg.offset(),client=getClient(e);pSliderMouseDown=true;posRecord=client['x'];var left=eleOffset['left']-bgOffset['left'];sliderDownTime=left*duration/(bg.getWidth()-slider.getWidth());slider.removeListener('mousedown',sliderMouseDown);slider.removeListener('touchstart',sliderMouseDown);doc.mousemove(docMouseMove);doc.mouseup(docMouseUp);slider.touchmove(docMouseMove);slider.touchend(docMouseUp);noScrolling(true)};var docMouseMove=function(e){e=e||window.event;var eleOffset=slider.offset(),bgOffset=bg.offset(),client=getClient(e);pSliderMouseDown=true;pos=posRecord-client['x'];posRecord=client['x'];var left=eleOffset['left']-pos-bgOffset['left'];if(left<0){left=0}if(left>bg.getWidth()-slider.getWidth()){left=bg.getWidth()-slider.getWidth()}C['bar']['pbox'].addClass('ck-bar-progress-slider-move');var time='';if(!vars['live']){time=left*duration/(bg.getWidth()-slider.getWidth());switch(vars['timeScheduleAdjust']){case 2:if(timesliderDownTime){left=-1000;message(language['timeScheduleAdjust']['prohibitForward']);return}break;case 4:if(timemaxSeeTime){left=-1000;player.seek(maxSeeTime);message(language['timeScheduleAdjust']['prohibitForwardNotViewed']);return}break}time=formatSeconds(time)}else{time=formatLiveTime(left*getLiveTime()/(bg.getWidth()-slider.getWidth()))}tip(slider,time);if(left>=0){slider.css({'left':left+'px'});play.css({'width':left+slider.getWidth()*.5+'px'})}else{pSliderMouseDown=false}};var docMouseUp=function(){noScrolling();doc.removeListener('mousemove',docMouseMove);doc.removeListener('mouseup',docMouseUp);slider.removeListener('touchmove',docMouseMove);slider.removeListener('touchend',docMouseUp);slider.mousedown(sliderMouseDown);slider.touchstart(sliderMouseDown);if(!vars['live']){var time=play.getWidth()*duration/(bg.getWidth()-slider.getWidth()*.5);if(time>duration){time=duration}seekTime=0;player.seek(time)}else{var playbackTime=parseInt(vars['live']+play.getWidth()*getLiveTime()/(bg.getWidth()-slider.getWidth()*.5));eventTarget('playback',{time:playbackTime,date:date('Y-m-d H:i:s',playbackTime)});eventTarget('seek',{time:playbackTime,state:'seeking',date:date('Y-m-d H:i:s',playbackTime)})}C['bar']['pbox'].removeClass('ck-bar-progress-slider-move');if(isMouseLeave){tip()}};var isMouseLeave=true;var sliderMouseOver=function(){var time='';if(!vars['live']){time=formatSeconds(parseInt(slider.css('left'))*duration/(bg.getWidth()-slider.getWidth()))}else{time=language['live']}isMouseLeave=false;tip(slider,time)};var sliderMouseLeave=function(){isMouseLeave=true};slider.mousedown(sliderMouseDown);slider.mouseover(sliderMouseOver);slider.mouseleave(sliderMouseLeave);slider.touchstart(sliderMouseDown);var bgMouseDown=function(e){e=e||window.event;var client=getClient(e);var bgOffset=bg.offset();var w=client['x']-bgOffset['left'];if(CT.full){w+=CK.offset()['left']}if(!vars['live']){var time=duration*w/bg.getWidth();switch(vars['timeScheduleAdjust']){case 0:message(language['timeScheduleAdjust']['prohibit']);return;break;case 2:if(timeoldTime){message(language['timeScheduleAdjust']['prohibitForward']);return}break;case 4:if(timemaxSeeTime){message(language['timeScheduleAdjust']['prohibitForwardNotViewed']);return}break}seekTime=0;player.seek(time)}else{var playbackTime=parseInt(vars['live']+getLiveTime()*w/bg.getWidth());eventTarget('playback',{time:playbackTime,date:date('Y-m-d H:i:s',playbackTime)});eventTarget('seek',{time:playbackTime,state:'seeking',date:date('Y-m-d H:i:s',playbackTime)})}if(w>bg.getWidth()-slider.getWidth()*.5){w=bg.getWidth()-slider.getWidth()*.5}if(wmaxTop){sliderTop=maxTop}slider.css({'top':sliderTop+'px'});var ppMarginTop=sliderTop-minTop+slider.getHeight()*0.5;pp.css({'margin-top':ppMarginTop+'px'});var vol=(bg.getHeight()-ppMarginTop-slider.getHeight()*0.5)/(bg.getHeight()-slider.getHeight());player.volume(vol)};var docMouseUp=function(){noScrolling();doc.removeListener('mousemove',docMouseMove);doc.removeListener('mouseup',docMouseUp);slider.removeListener('touchmove',docMouseMove);slider.removeListener('touchend',docMouseUp);slider.mousedown(sliderMouseDown);slider.touchstart(sliderMouseDown);C['bar']['vbox']['mouseDown']=false};slider.mousedown(sliderMouseDown);slider.touchstart(sliderMouseDown);var bgMouseDown=function(e){e=e||window.event;var client=getClient(e);var bgOffset=bg.offset();var h=client['y']-bgOffset['top'];var max=bg.getHeight();var vol=1-h/max;player.volume(vol)};bg.mousedown(bgMouseDown)},changeProgress=function(time){if((valType(vars['live'])=='boolean'&&vars['live'])){return}if(!pSliderMouseDown){var bg=C['bar']['pbox']['bg'],slider=C['bar']['pbox']['slider'];var playW=(time/duration)*100;if(valType(vars['live'])=='number'){playW='100'}C['bar']['pbox']['play'].css({'width':playW+'%'});var sliderW=(slider.getWidth()/bg.getWidth())*100;var sliderLeft=playW-sliderW*.5;if(sliderLeft<0){sliderLeft=0}if(sliderLeft>100-sliderW){sliderLeft=100-sliderW}C['bar']['pbox']['slider'].css({'left':sliderLeft+'%'});checkProgressSlider()}},checkProgressSlider=function(){var bg=C['bar']['pbox']['bg'],slider=C['bar']['pbox']['slider'];var st=0;if(C['bar']['pbox'].css('transition-duration')){st=parseFloat(C['bar']['pbox'].css('transition-duration'))*1000+20}var checkThis=function(){var sliderLeft=parseInt(C['bar']['pbox']['slider'].css('left'));if(sliderLeft<0){sliderLeft=0;C['bar']['pbox']['slider'].css({'left':sliderLeft+'px'})}if(sliderLeft>bg.getWidth()-slider.getWidth()){sliderLeft=bg.getWidth()-slider.getWidth();C['bar']['pbox']['slider'].css({'left':sliderLeft+'px'})}};setTimeout(checkThis,st)},changeTopTime=function(){C['topBar']['timeEle'].htm(date('H:i:s'));setTimeout(changeTopTime,1000)},changeVolumeSlider=function(vol){if(!isUndefined(C['bar']['vbox'])){var box=C['bar']['vbox']['volume']['box'],bg=C['bar']['vbox']['volume']['bg'],pp=C['bar']['vbox']['volume']['pp'],slider=C['bar']['vbox']['volume']['slider'];C['bar']['vbox']['volume']['txt'].htm(parseInt(vol*100));var bgH=bg.getHeight();if(bgH){var ppMarginTop=bgH-vol*bgH;pp.css({'margin-top':ppMarginTop+'px'});var sliderTop=bg.offset()['top']-box.offset()['top']+ppMarginTop-slider.getHeight()*0.5;slider.css({'top':sliderTop+'px'})}}},changeLoad=function(){if(video){var len=video.buffered.length;if(len>0){var bufferEnd=video.buffered.end(len-1);if(loadTime=1000){last+=offset;appendFps(fps);fps=0}requestAnimationFrame(step)};var appendFps=function(fps){if(fps!=CT.fps){CT.fps=fps;replaceInformation('fps',fps);eventTarget('fps',fps)}};if(isUndefined(CT.fps)){CT.fps=0}step()},getElement=function(content){var ele=$(content);if(isUndefined(ele))return;if(valType(ele)=='htmlarray'){ele=ele[0]}if(isUndefined(ele.captureParentNode)){ele.captureParentNode=ele.parentNode;ele.prevElement=ele.prev();ele.nextElement=ele.next()}return ele},$=function(obj,eL){var parent=null;if(document){parent=document}if(!isUndefined(eL)){parent=eL}var res=[];if(obj){if(valType(obj)=='htmlobject'||valType(obj)=='object'){res=obj;if(!isUndefined(res.ckplayer)){return res}}else if(valType(obj)=='string'&&obj!=''){switch(obj.substr(0,1)){case'.':obj=obj.substr(1,obj.length);if(parent.getElementsByClassName){res=parent.getElementsByClassName(obj);if(!res.length){return null}}else if(!parent&&document.getElementsByClassName){res=document.getElementsByClassName(obj);if(!res.length){return null}}else{var reg=new RegExp(' '+obj+' ','i');var ele=null;if(parent.getElementsByTagName){ele=parent.getElementsByTagName('*')}else{ele=document.getElementsByTagName('*')}for(var i=0;i1){b=arr[1].substr(0,1).toLocaleUpperCase()+arr[1].substr(1,arr[1].length-1)}if(arr.length>2){b=arr[2].substr(0,1).toLocaleUpperCase()+arr[2].substr(1,arr[2].length-1)}return a+b+c}return str};if(isUndefined(value)){if(!isUndefined(key)&&valType(key)=='string'){if(this.currentStyle){return this.currentStyle[key]}else{return document.defaultView.getComputedStyle(this,null)[key]}}if(isUndefined(key)){if(this.currentStyle){return this.currentStyle}else{return document.defaultView.getComputedStyle(this,null)}}}if(valType(key)!='object'){var newKey=keyNew(key);if(this.length>1){for(var i=0;i'+val+'
','text/html');var root=doc.body.firstChild;res.replaceChildren();while(root&&root.firstChild){res.appendChild(root.firstChild)}}else if(val&&typeof val==='object'&&val.nodeType){res.replaceChildren(val)}else{res.textContent=String(val)}}else{return Array.prototype.map.call(res.childNodes,function(node){return new XMLSerializer().serializeToString(node)}).join('')}};res.htmReplace=function(ar,val,html){if(!isUndefined(val)&&!isUndefined(ar)){if(isUndefined(this.attr('data-htm'))){this.attr('data-htm',html)}var htm=this.attr('data-htm');var reg=new RegExp(ar,'g');htm=htm.replace(reg,val);this.htm(htm)}};res.offset=function(){var par=this.offsetParent,left=this.offsetLeft,top=this.offsetTop;while(par&&par.tagName!=='BODY'){if(!/MSIE 8\.0/.test(navigator.userAgent)){left+=par.clientLeft;top+=par.clientTop}left+=par.offsetLeft;top+=par.offsetTop;par=par.offsetParent}return{top:top,left:left}};res.fixed=function(){if(this.css('position')=='fixed'){return true}if(!isUndefined(this.offsetParent)){return $(this.offsetParent).fixed()}return false};res.getWidth=function(){return this.offsetWidth};res.getHeight=function(){return this.offsetHeight};if(isUndefined(res.width)){res.width=function(){return this.offsetWidth}}if(isUndefined(res.height)){res.height=function(){return this.offsetWidth}}res.resize=function(fn){addListener(window,'resize',fn)};res.click=function(fn){addListener(this,'click',fn);return this};res.singleClick=function(fn){if(isUndefined(this.dbClick)){doubleClickEvent(this);this.dbClick=true}this.addListener('sigClick',fn);return this};res.doubleClick=function(fn){if(isUndefined(this.dbClick)){doubleClickEvent(this);this.dbClick=true}this.addListener('dobClick',fn);return this};res.mouseover=function(fn){addListener(this,'mouseover',fn);return this};res.mouseout=function(fn){addListener(this,'mouseout',fn);return this};res.mousedown=function(fn){addListener(this,'mousedown',fn);return this};res.mouseup=function(fn){addListener(this,'mouseup',fn);return this};res.mousemove=function(fn){addListener(this,'mousemove',fn);return this};res.mouseWheel=function(fn){addListener(this,'mousewheel',fn);addListener(this,'DOMMouseScroll',fn,false);return this};res.mouseleave=function(fn){addListener(this,'mouseleave',fn);return this};res.touchstart=function(fn){addListener(this,'touchstart',fn);return this};res.touchmove=function(fn){addListener(this,'touchmove',fn);return this};res.touchend=function(fn){addListener(this,'touchend',fn);return this};res.show=function(){this.css('display','block');return this};res.hide=function(){this.css('display','none');return this};res.animate=function(parameter,totalTime,easing,callBack){if(isUndefined(this.CK)){return res}var thisTemp=this;var parNode=this.CK;var w=parNode.getWidth(),h=parNode.getHeight();var speed=10;this.timerTween=null;this.tweenPlay=true;if(isUndefined(parameter)){return this}if(isUndefined(totalTime)||totalTime==0){totalTime=1000}if(isUndefined(easing)||easing==''){easing='None.easeIn'}var effArr=easing.split('.');var tween={None:{easeIn:function(t,b,c,d){return c*t/d+b},easeOut:function(t,b,c,d){return c*t/d+b},easeInOut:function(t,b,c,d){return c*t/d+b}},Quadratic:{easeIn:function(t,b,c,d){return c*(t/=d)*t+b},easeOut:function(t,b,c,d){return-c*(t/=d)*(t-2)+b},easeInOut:function(t,b,c,d){if((t/=d/2)<1)return c/2*t*t+b;return-c/2*((--t)*(t-2)-1)+b}},Cubic:{easeIn:function(t,b,c,d){return c*(t/=d)*t*t+b},easeOut:function(t,b,c,d){return c*((t=t/d-1)*t*t+1)+b},easeInOut:function(t,b,c,d){if((t/=d/2)<1)return c/2*t*t*t+b;return c/2*((t-=2)*t*t+2)+b}},Quartic:{easeIn:function(t,b,c,d){return c*(t/=d)*t*t*t+b},easeOut:function(t,b,c,d){return-c*((t=t/d-1)*t*t*t-1)+b},easeInOut:function(t,b,c,d){if((t/=d/2)<1)return c/2*t*t*t*t+b;return-c/2*((t-=2)*t*t*t-2)+b}},Quintic:{easeIn:function(t,b,c,d){return c*(t/=d)*t*t*t*t+b},easeOut:function(t,b,c,d){return c*((t=t/d-1)*t*t*t*t+1)+b},easeInOut:function(t,b,c,d){if((t/=d/2)<1)return c/2*t*t*t*t*t+b;return c/2*((t-=2)*t*t*t*t+2)+b}},Sine:{easeIn:function(t,b,c,d){return-c*Math.cos(t/d*(Math.PI/2))+c+b},easeOut:function(t,b,c,d){return c*Math.sin(t/d*(Math.PI/2))+b},easeInOut:function(t,b,c,d){return-c/2*(Math.cos(Math.PI*t/d)-1)+b}},Exponential:{easeIn:function(t,b,c,d){return(t==0)?b:c*Math.pow(2,10*(t/d-1))+b},easeOut:function(t,b,c,d){return(t==d)?b+c:c*(-Math.pow(2,-10*t/d)+1)+b},easeInOut:function(t,b,c,d){if(t==0)return b;if(t==d)return b+c;if((t/=d/2)<1)return c/2*Math.pow(2,10*(t-1))+b;return c/2*(-Math.pow(2,-10*--t)+2)+b}},Circular:{easeIn:function(t,b,c,d){return-c*(Math.sqrt(1-(t/=d)*t)-1)+b},easeOut:function(t,b,c,d){return c*Math.sqrt(1-(t=t/d-1)*t)+b},easeInOut:function(t,b,c,d){if((t/=d/2)<1)return-c/2*(Math.sqrt(1-t*t)-1)+b;return c/2*(Math.sqrt(1-(t-=2)*t)+1)+b}},Elastic:{easeIn:function(t,b,c,d,a,p){if(t==0)return b;if((t/=d)==1)return b+c;if(!p)p=d*.3;if(!a||a=m+1){return this[m]}return null}return null};for(var i=0;imaxZ){maxZ=parseInt(temp)}}return maxZ}function getWindowSize(){return{width:window.innerWidth,height:window.innerHeight}}function standardization(o,n){var h={};var k;for(k in o){h[k]=o[k]}for(k in n){if(k in h){h[k]=n[k]}}return h}function mergeObj(o,n){var h={};var k;for(k in o){h[k]=o[k]}for(k in n){if(k in h){switch(valType(h[k])){case'object':if(valType(n[k])=='object'){h[k]=mergeObj(h[k],n[k])}break;default:if(valType(h[k])==valType(n[k])){h[k]=n[k]}break}}}return h}function valType(val){if(typeof val==='undefined')return'undefined';var str=Object.prototype.toString.call(val).split(' ')[1].replace(']','').toLowerCase();if(str.substr(0,4)=='html'&&(str.substr(-7)=='element'||str.substr(-8)=='document'||str=='window')){str='htmlobject'}if(str=='htmlcollection'||str=='nodelist'){str='htmlarray'}return str}function isUndefined(val){try{return valType(val)==='undefined'||val===undefined||val===null||(valType(val)==='number'&&isNaN(val))}catch(event){return true}return false}function prev(ele){var e=ele.previousSibling;if(e==null){return null}if(e.nodeType==3){var t=prev(e);if(t&&t.nodeType==1){return t}}else{if(e.nodeType==1){return e}else{return false}}}function next(ele){var e=ele.nextSibling;if(e==null){return null}if(e.nodeType==3){var t=next(e);if(t&&t.nodeType==1){return t}}else{if(e.nodeType==1){return e}else{return false}}}function addListener(ele,e,f,t){if(isUndefined(t)){t=false}if(ele.addEventListener){try{ele.addEventListener(e,f,t)}catch(event){}}else if(ele.attachEvent){try{ele.attachEvent('on'+e,f)}catch(event){}}else{ele['on'+e]=f}if(isUndefined(ele.listenerList)){ele.listenerList=[]}ele.listenerList.push([e,f]);return ele};function removeListener(ele,e,f,t){if(isUndefined(t)){t=false}if(ele.removeEventListener){try{ele.removeEventListener(e,f,t)}catch(e){}}else if(ele.detachEvent){try{ele.detachEvent('on'+e,f)}catch(e){}}else{ele['on'+e]=null}if(!isUndefined(ele.listenerList)){var temp=[];for(var i=0;i>0},W:function(){var a=f.z(),b=364+f.L()-a;var nd2,nd=(new Date(jsdate.getFullYear()+'/1/1').getDay()||7)-1;if(b<=2&&((jsdate.getDay()||7)-1)<=2-b){return 1}else{if(a<=2&&nd>=4&&a>=(6-nd)){nd2=new Date(jsdate.getFullYear()-1+'/12/31');return date('W',Math.round(nd2.getTime()/1000))}else{return(1+(nd<=3?((a+nd)/7):(a-(7-nd))/7)>>0)}}},F:function(){return txt_months[f.n()]},m:function(){return pad(f.n(),2)},M:function(){return f.F().substr(0,3)},n:function(){return jsdate.getMonth()+1},t:function(){var n;if((n=jsdate.getMonth()+1)==2){return 28+f.L()}else{if(n&1&&n<8||!(n&1)&&n>7){return 31}else{return 30}}},L:function(){var y=f.Y();return(!(y&3)&&(y%1e2||!(y%4e2)))?1:0},Y:function(){return jsdate.getFullYear()},y:function(){return(jsdate.getFullYear()+'').slice(2)},a:function(){return jsdate.getHours()>11?'pm':'am'},A:function(){return f.a().toUpperCase()},B:function(){var off=(jsdate.getTimezoneOffset()+60)*60;var theSeconds=(jsdate.getHours()*3600)+(jsdate.getMinutes()*60)+jsdate.getSeconds()+off;var beat=Math.floor(theSeconds/86.4);if(beat>1000)beat-=1000;if(beat<0)beat+=1000;if((String(beat)).length==1)beat='00'+beat;if((String(beat)).length==2)beat='0'+beat;return beat},g:function(){return jsdate.getHours()%12||12},G:function(){return jsdate.getHours()},h:function(){return pad(f.g(),2)},H:function(){return pad(jsdate.getHours(),2)},i:function(){return pad(jsdate.getMinutes(),2)},s:function(){return pad(jsdate.getSeconds(),2)},O:function(){var t=pad(Math.abs(jsdate.getTimezoneOffset()/60*100),4);if(jsdate.getTimezoneOffset()>0)t='-'+t;else t='+'+t;return t},P:function(){var O=f.O();return(O.substr(0,3)+':'+O.substr(3,2))},c:function(){return f.Y()+'-'+f.m()+'-'+f.d()+'T'+f.h()+':'+f.i()+':'+f.s()+f.P()},U:function(){return Math.round(jsdate.getTime()/1000)}};return format.replace(/([a-zA-Z])/g,function(t,s){var ret='';if(t!=s){ret=s}else if(f[s]){ret=f[s]()}else{ret=s}return ret})}function dataURLtoBlob(dataurl){var arr=dataurl.split(','),mime=arr[0].match(/:(.*?);/)[1],bstr=atob(arr[1]),n=bstr.length,u8arr=new Uint8Array(n);while(n--){u8arr[n]=bstr.charCodeAt(n)}return new Blob([u8arr],{type:mime})}function decodeURIString(obj){for(var k in obj){if(valType(obj[k])=='string'&&obj[k]){try{obj[k]=decodeURI(obj[k])}catch(event){}}if(valType(obj[k])=='object'){obj[k]=decodeURIString(obj[k])}}return obj}function getApp(){var u=navigator.userAgent.toLowerCase();if(u.indexOf('iphone')>-1){return'iphone'}}function cookie(name,value,domain,path){if(isUndefined(domain)){domain=''}if(isUndefined(path)){path='/'}var ckStr=';domain='+domain+';path='+path;if(location.protocol=='https'){ckStr+=';SameSite=None;Secure=true'}var set=function(name,value){var time=360*24*60*60*1000;var exp=new Date();exp.setTime(exp.getTime()+time);try{document.cookie=name+'='+escape(value)+';expires='+exp.toGMTString()+ckStr}catch(event){console.error(event)}},get=function(name){var arr,reg=new RegExp('(^| )'+name+'=([^;]*)(;|$)');if(arr=document.cookie.match(reg)){return unescape(arr[2])}else{return null}},del=function(name){var exp=new Date();exp.setTime(exp.getTime()-1);var cval=get(name);if(cval!=null){document.cookie=name+'='+cval+';expires='+exp.toGMTString()+ckStr}};if(!isUndefined(name)&&!isUndefined(value)){if(value=='delete'){del(name);return null}else{set(name,value);return get(name)}}else if(!isUndefined(name)&&isUndefined(value)){return get(name)}else if(!isUndefined(name)){}}function arrayToString(arr){var str='';if(!isUndefined(arr)){for(var i=0;i0){str+=','}for(var y=0;y0){str+=';'}if(valType(temp[y])=='number'){str+=parseInt(temp[y]*100)}else{str+=temp[y]}}}}return str}function stringToArray(str){var newArr=[];if(!isUndefined(str)){var arr=str.split(',');for(var i=0;i node.remove()); + target.replaceChildren(...Array.from(doc.body.childNodes)); + } + + function renderStatusBadge(target, text, className) { + target.replaceChildren(); + const badge = document.createElement('span'); + badge.className = `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${className}`; + badge.textContent = text; + target.appendChild(badge); + } + + function captureButtonContent(button) { + return Array.from(button.childNodes).map(node => node.cloneNode(true)); + } + + function restoreButtonContent(button, nodes) { + button.replaceChildren(...nodes.map(node => node.cloneNode(true))); + } + + function setSubmitButtonLoading(button, isLoading, originalNodes = []) { + if (isLoading) { + button.replaceChildren(); + const icon = document.createElement('i'); + icon.className = 'fa fa-spinner fa-spin mr-2'; + const text = document.createTextNode(' 保存中...'); + button.append(icon, text); + return; + } + + restoreButtonContent(button, originalNodes); + } + function showMessage(message, type = 'success') { const styles = {success: 'bg-green-500', error: 'bg-red-500', warning: 'bg-yellow-500', info: 'bg-blue-500' }; const icons = {success: 'fa-check-circle', error: 'fa-exclamation-circle', warning: 'fa-exclamation-triangle', info: 'fa-info-circle' }; @@ -31,7 +119,7 @@ note.style.transform = 'translateX(calc(100% + 20px))'; note.className = ''; note.classList.add(...baseClasses, styles[type]); - note.innerHTML = `${message}`; + setNotificationContent(note, icons[type], message); setTimeout(() => { note.style.transform = 'translateX(0)'; }, 10); @@ -49,22 +137,9 @@ return new Promise(resolve => { const mask = document.createElement("div"); mask.className = "fixed inset-0 bg-black/50 flex items-center justify-center z-50"; - mask.innerHTML = ` -
-
-

${title}

-
-
-

${msg}

-
-
- - -
-
- `; + const { panel, cancelBtn, okBtn } = buildConfirmDialog(title, msg); + mask.appendChild(panel); document.body.appendChild(mask); - const [cancelBtn, okBtn] = mask.querySelectorAll("button"); cancelBtn.onclick = () => { mask.remove(); resolve(false); }; okBtn.onclick = () => { mask.remove(); resolve(true); }; }); @@ -221,7 +296,7 @@ .then(html => { const mainContent = document.getElementById('main-content'); if (mainContent) { - mainContent.innerHTML = html; + safelyReplaceHtml(mainContent, html); highlightActiveMenu(); // 触发子页面加载完成事件 @@ -318,11 +393,7 @@ : 'bg-gray-100 text-gray-800'; // 渲染状态标签 - statusDisplay.innerHTML = ` - - ${statusText} - - `; + renderStatusBadge(statusDisplay, statusText, statusClass); } } @@ -362,10 +433,10 @@ const submitBtn = profileForm.querySelector('button[type="submit"]'); if (!submitBtn) return; - const originalText = submitBtn.innerHTML; + const originalNodes = captureButtonContent(submitBtn); // 禁用按钮并显示加载状态 submitBtn.disabled = true; - submitBtn.innerHTML = ' 保存中...'; + setSubmitButtonLoading(submitBtn, true, originalNodes); try { const formData = new FormData(profileForm); @@ -403,7 +474,7 @@ } finally { // 恢复按钮状态 submitBtn.disabled = false; - submitBtn.innerHTML = originalText; + setSubmitButtonLoading(submitBtn, false, originalNodes); } }; diff --git a/Static/js/controller.js b/Static/js/controller.js index e69ba9d..be61c06 100755 --- a/Static/js/controller.js +++ b/Static/js/controller.js @@ -1,13 +1,5 @@ (function() { - // 常量定义 - 类名移除avif并添加ja-前缀 - const CLOSE_ICON = ` `; - const RESET_ICON = ` `; - const PREVIEW_ICON = ` `; - const COPY_ICON = ` `; - const UP_ICON = ` `; - const ERRO_ICON = ` `; - // 基础配置 const scriptBaseUrl = getScriptBaseUrl(); const API_CONFIG = { @@ -76,11 +68,265 @@ function getScriptBaseUrl() { window.addEventListener('resize', setVh); window.addEventListener('orientationchange', setVh); + function createIconWrapper(className, iconType) { + const wrapper = document.createElement('div'); + wrapper.className = className; + wrapper.replaceChildren(createIconNode(iconType)); + return wrapper; + } + + function createImageElement(src, alt = '', className = '', width = '') { + const image = document.createElement('img'); + image.src = src; + image.alt = alt; + if (width) { + image.width = Number(width); + } + return image; + } + + function createIconNode(type) { + switch (type) { + case 'close': + return createImageElement('https://yanxuan.nosdn.127.net/0c64ce12c71cf276504cb2e15164d4ff.png', '', '', '25'); + case 'reset': + return createImageElement('https://yanxuan.nosdn.127.net/8296ea39d8c548ed320f79a483756cd9.jpg', '', '', '25'); + case 'preview': + return createImageElement('https://yanxuan.nosdn.127.net/520b793c329df6349a25404efefbd0f4.png', '', 'ja-action-icon'); + case 'copy': + return createImageElement('https://yanxuan.nosdn.127.net/0732b6e6d1f249dad11ac7d004af5964.png', '', 'ja-action-icon'); + case 'upload': + return createImageElement('https://yanxuan.nosdn.127.net/94494a4dc561ae21157f50764ddf035e.png', '', 'ja-button-icon'); + case 'error': + return createImageElement('https://yanxuan.nosdn.127.net/74777259ff515f056e8fc340df28fcd7.png', '', 'ja-erro-icon'); + default: + return document.createTextNode(''); + } + } + + function createUploadingIcon() { + return createFontIcon('uploading-icon'); + } + + + function createLoadingContainer(message = '加载中...') { + const container = document.createElement('div'); + container.className = 'ja-loading-container'; + + const icon = document.createElement('i'); + icon.className = 'loading-icon'; + + const text = document.createElement('p'); + text.className = 'ja-loading-text'; + text.textContent = message; + + container.append(icon, text); + return container; + } + + function createStatusContainer(type, message) { + const container = document.createElement('div'); + container.className = `ja-${type}-container`; + container.appendChild(createIconNode('error')); + + const text = document.createElement('p'); + text.className = `ja-${type}-text`; + text.textContent = String(message || ''); + container.appendChild(text); + + return container; + } + + function createMediaCard(item) { + const itemElement = document.createElement('div'); + itemElement.className = 'ja-media-item'; + + const thumbnail = document.createElement('div'); + thumbnail.className = 'ja-item-thumbnail'; + + const image = document.createElement('img'); + image.src = item.url; + image.alt = item.name; + image.className = 'ja-thumbnail-image'; + image.loading = 'lazy'; + + const overlay = document.createElement('div'); + overlay.className = 'ja-item-overlay'; + + const copyButton = document.createElement('button'); + copyButton.className = 'ja-copy-button'; + copyButton.dataset.url = item.url; + copyButton.appendChild(createIconNode('copy')); + + const previewButton = document.createElement('button'); + previewButton.className = 'ja-preview-button'; + previewButton.dataset.url = item.url; + previewButton.appendChild(createIconNode('preview')); + + overlay.append(copyButton, previewButton); + thumbnail.append(image, overlay); + + const info = document.createElement('div'); + info.className = 'ja-item-info'; + + const name = document.createElement('div'); + name.className = 'ja-item-name'; + name.textContent = item.name; + + const details = document.createElement('div'); + details.className = 'ja-item-details'; + + const size = document.createElement('span'); + size.className = 'ja-item-size'; + size.textContent = formatFileSize(item.size); + + const dimensions = document.createElement('span'); + dimensions.className = 'ja-item-dimensions'; + dimensions.textContent = `${item.width}*${item.height}`; + + details.append(size, dimensions); + info.append(name, details); + itemElement.append(thumbnail, info); + + return itemElement; + } + + function createUploadErrorItem(fileName, reason) { + const errorItem = document.createElement('div'); + errorItem.className = 'ja-upload-error'; + + const header = document.createElement('div'); + header.className = 'ja-error-header'; + + const name = document.createElement('span'); + name.className = 'ja-error-filename'; + name.textContent = fileName; + + const status = document.createElement('span'); + status.className = 'ja-error-status'; + status.textContent = '错误'; + + header.append(name, status); + + const message = document.createElement('div'); + message.className = 'ja-error-message'; + message.textContent = reason; + + errorItem.append(header, message); + return errorItem; + } + + function createUploadSuccessItem(data) { + const wrapper = document.createElement('div'); + wrapper.className = 'ja-media-item'; + + const thumbnail = document.createElement('div'); + thumbnail.className = 'ja-item-thumbnail relative'; + + const image = document.createElement('img'); + image.src = data.url; + image.alt = data.name; + image.className = 'ja-thumbnail-image'; + + const overlay = document.createElement('div'); + overlay.className = 'ja-item-overlay'; + + const copyButton = document.createElement('button'); + copyButton.className = 'ja-copy-button'; + copyButton.dataset.url = data.url; + copyButton.appendChild(createIconNode('copy')); + + const previewButton = document.createElement('button'); + previewButton.className = 'ja-preview-button'; + previewButton.dataset.url = data.url; + previewButton.appendChild(createIconNode('preview')); + + overlay.append(copyButton, previewButton); + thumbnail.append(image, overlay); + + const info = document.createElement('div'); + info.className = 'ja-item-info'; + + const detailTop = document.createElement('div'); + detailTop.className = 'ja-item-details'; + + const name = document.createElement('span'); + name.className = 'ja-item-name'; + name.textContent = data.name; + + const success = document.createElement('span'); + success.className = 'ja-upload-success'; + success.textContent = '已完成'; + + detailTop.append(name, success); + + const detailBottom = document.createElement('div'); + detailBottom.className = 'ja-item-details'; + + const size = document.createElement('span'); + size.className = 'ja-item-size'; + size.textContent = formatFileSize(data.size || 0); + + const dimensions = document.createElement('span'); + dimensions.className = 'ja-item-dimensions'; + dimensions.textContent = `${data.width}*${data.height}`; + + detailBottom.append(size, dimensions); + info.append(detailTop, detailBottom); + wrapper.append(thumbnail, info); + + return wrapper; + } + + function createUploadFailureContent(message) { + const fragment = document.createDocumentFragment(); + + const header = document.createElement('div'); + header.className = 'ja-upload-header'; + + const fileName = document.createElement('span'); + fileName.className = 'ja-upload-filename'; + + const failure = document.createElement('span'); + failure.className = 'ja-upload-failure'; + + header.append(fileName, failure); + + const detail = document.createElement('div'); + detail.className = 'ja-upload-error-message'; + detail.textContent = message; + + fragment.append(header, detail); + return fragment; + } + + function createPreviewModal(url) { + const modal = document.createElement('div'); + modal.className = 'ja-image-preview-modal'; + + const content = document.createElement('div'); + content.className = 'ja-modal-content'; + + const image = document.createElement('img'); + image.src = url; + image.alt = '预览图片'; + image.className = 'ja-modal-image'; + + const button = document.createElement('button'); + button.className = 'ja-close-button'; + button.appendChild(createIconNode('close')); + + content.append(image, button); + modal.appendChild(content); + + return { modal, content, image, button }; + } + // UI 创建函数 - 所有ID和类名添加ja-前缀并移除avif function createUI() { // 动态插入 CSS,禁止顶部下拉刷新 const style = document.createElement('style'); - style.innerHTML = ` + style.textContent = ` html, body { overscroll-behavior-y: contain; } @@ -100,14 +346,14 @@ function getScriptBaseUrl() { // 创建关闭按钮 closeBtn = document.createElement('button'); closeBtn.className = 'ja-modal-close'; - closeBtn.innerHTML = `${CLOSE_ICON}`; + closeBtn.appendChild(createIconNode('close')); // 创建重置按钮 resetBtn = document.createElement('button'); resetBtn.className = 'ja-img-close'; resetBtn.setAttribute('data-action', 'reset'); - resetBtn.innerHTML = `${RESET_ICON}`; + resetBtn.appendChild(createIconNode('reset')); // 标签页导航 const tabs = document.createElement('div'); @@ -143,27 +389,56 @@ function getScriptBaseUrl() { const qualityLabel = document.createElement('label'); qualityLabel.className = 'ja-param-label'; - qualityLabel.innerHTML = ` - 质量设置 (1-100) -
- - 60 -
- `; + const qualityTitle = document.createElement('span'); + qualityTitle.className = 'ja-param-title'; + qualityTitle.textContent = '质量设置 (1-100)'; + + const qualityWrapper = document.createElement('div'); + qualityWrapper.className = 'ja-range-wrapper'; + + const qualityRange = document.createElement('input'); + qualityRange.type = 'range'; + qualityRange.id = 'ja-quality'; + qualityRange.min = '1'; + qualityRange.max = '100'; + qualityRange.value = '60'; + qualityRange.className = 'ja-range-input'; + + const qualityValue = document.createElement('span'); + qualityValue.id = 'ja-quality-value'; + qualityValue.className = 'ja-range-span'; + qualityValue.textContent = '60'; + + qualityWrapper.append(qualityRange, qualityValue); + qualityLabel.append(qualityTitle, qualityWrapper); const widthLabel = document.createElement('label'); widthLabel.className = 'ja-param-label'; - widthLabel.innerHTML = ` - 转换宽度 - - `; + const widthTitle = document.createElement('span'); + widthTitle.className = 'ja-param-title'; + widthTitle.textContent = '转换宽度'; + + const widthInput = document.createElement('input'); + widthInput.type = 'number'; + widthInput.id = 'ja-width'; + widthInput.placeholder = '留空为自动'; + widthInput.className = 'ja-text-input'; + + widthLabel.append(widthTitle, widthInput); const heightLabel = document.createElement('label'); heightLabel.className = 'ja-param-label'; - heightLabel.innerHTML = ` - 转换高度 - - `; + const heightTitle = document.createElement('span'); + heightTitle.className = 'ja-param-title'; + heightTitle.textContent = '转换高度'; + + const heightInput = document.createElement('input'); + heightInput.type = 'number'; + heightInput.id = 'ja-height'; + heightInput.placeholder = '留空为自动'; + heightInput.className = 'ja-text-input'; + + heightLabel.append(heightTitle, heightInput); // 上传控件 const uploadInput = document.createElement('input'); @@ -176,10 +451,12 @@ function getScriptBaseUrl() { const uploadBtn = document.createElement('button'); uploadBtn.id = 'ja-upload-btn'; uploadBtn.className = 'ja-button ja-button-primary'; - uploadBtn.innerHTML = ` -
${UP_ICON}
-
开始上传
- `; + const uploadSvg = createIconWrapper('ja-upload-svg', 'upload'); + uploadSvg.id = 'ja-upload-svg'; + const uploadStatus = document.createElement('div'); + uploadStatus.id = 'ja-upload-status'; + uploadStatus.textContent = '开始上传'; + uploadBtn.append(uploadSvg, uploadStatus); const uploadResults = document.createElement('div'); uploadResults.id = 'ja-upload-results'; @@ -189,9 +466,9 @@ function getScriptBaseUrl() { const loadMoreBtn = document.createElement('button'); loadMoreBtn.id = 'ja-load-more'; loadMoreBtn.className = 'ja-load-more ja-button ja-button-secondary mt-4'; - loadMoreBtn.innerHTML = ` - 加载更多 - `; + const loadMoreIcon = document.createElement('i'); + loadMoreIcon.className = 'fa fa-refresh mr-2'; + loadMoreBtn.append(loadMoreIcon, document.createTextNode(' 加载更多')); document.head.appendChild(style); @@ -343,7 +620,7 @@ function getScriptBaseUrl() { // 清空上传结果 const uploadResults = document.getElementById('ja-upload-results'); - if (uploadResults) uploadResults.innerHTML = ''; + if (uploadResults) uploadResults.replaceChildren(); // 清空文件选择 const uploadInput = document.getElementById('ja-upload-input'); @@ -384,22 +661,12 @@ function getScriptBaseUrl() { // 首次加载显示加载状态 if (isInitialLoad) { - grid.innerHTML = ` -
- -

加载中...

-
- `; + grid.replaceChildren(createLoadingContainer()); // 初始加载时隐藏加载更多按钮 loadMoreBtn.style.display = 'none'; } else { // 非首次加载时显示加载状态 - const loadingIndicator = document.createElement('div'); - loadingIndicator.className = 'ja-loading-container'; - loadingIndicator.innerHTML = ` - -

加载中...

- `; + const loadingIndicator = createLoadingContainer(); grid.appendChild(loadingIndicator); } @@ -430,15 +697,17 @@ function getScriptBaseUrl() { // 控制加载更多按钮显示 if (state.currentPage >= state.totalPages) { - loadMoreBtn.innerHTML = ` - 没有更多图片了 - `; + loadMoreBtn.replaceChildren(); + const doneIcon = document.createElement('i'); + doneIcon.className = 'fa fa-check mr-2'; + loadMoreBtn.append(doneIcon, document.createTextNode(' 没有更多图片了')); loadMoreBtn.disabled = true; loadMoreBtn.classList.add('opacity-50', 'cursor-not-allowed'); } else { - loadMoreBtn.innerHTML = ` - 加载更多 - `; + loadMoreBtn.replaceChildren(); + const refreshIcon = document.createElement('i'); + refreshIcon.className = 'fa fa-refresh mr-2'; + loadMoreBtn.append(refreshIcon, document.createTextNode(' 加载更多')); loadMoreBtn.disabled = false; loadMoreBtn.classList.remove('opacity-50', 'cursor-not-allowed'); } @@ -452,23 +721,13 @@ function getScriptBaseUrl() { } } else { // 错误处理 - grid.innerHTML = ` -
- ${ERRO_ICON} -

${data.message}

-
- `; + grid.replaceChildren(createStatusContainer('error', data.message)); loadMoreBtn.style.display = 'none'; } }) .catch(error => { console.error('获取图片列表失败:', error); - grid.innerHTML = ` -
- ${ERRO_ICON} -

网络错误,请重试

-
- `; + grid.replaceChildren(createStatusContainer('error', '网络错误,请重试')); loadMoreBtn.style.display = 'none'; }) .finally(() => { @@ -498,39 +757,17 @@ function getScriptBaseUrl() { function renderMediaGrid() { const grid = document.getElementById('ja-media-grid'); const itemsToRender = state.mediaItems; - grid.innerHTML = ''; + grid.replaceChildren(); if (itemsToRender.length === 0) { // 空状态显示 - grid.innerHTML = ` -
- ${ERRO_ICON} -

没有找到图片

-
- `; + grid.replaceChildren(createStatusContainer('empty', '没有找到图片')); return; } // 正常渲染图片列表 itemsToRender.forEach(item => { - const itemElement = document.createElement('div'); - itemElement.className = 'ja-media-item'; - itemElement.innerHTML = ` -
- ${item.name} -
- - -
-
-
-
${item.name}
-
- ${formatFileSize(item.size)} - ${item.width}*${item.height} -
-
- `; + const itemElement = createMediaCard(item); grid.appendChild(itemElement); }); } @@ -561,15 +798,7 @@ function getScriptBaseUrl() { if (invalidFiles.length > 0) { const uploadResults = document.getElementById('ja-upload-results'); invalidFiles.forEach(({ file, reason }) => { - const errorItem = document.createElement('div'); - errorItem.className = 'ja-upload-error'; - errorItem.innerHTML = ` -
- - 错误 -
-
${reason}
- `; + const errorItem = createUploadErrorItem(file.name, reason); uploadResults.appendChild(errorItem); }); } @@ -588,7 +817,7 @@ function uploadFiles(files) { const height = document.getElementById('ja-height').value; const uploadResults = document.getElementById('ja-upload-results'); saveFormState(width, height); - uploadResults.innerHTML = ''; + uploadResults.replaceChildren(); files.forEach(file => { const formData = new FormData(); @@ -622,7 +851,7 @@ function uploadFiles(files) { progressItem.style.width = `${percentComplete}%`; if (ja.statusText) { ja.statusText.textContent = `上传中 ${Math.round(percentComplete)}%`; - ja.upsvg.innerHTML = ''; + ja.upsvg.replaceChildren(createUploadingIcon()); } } }); @@ -684,37 +913,14 @@ function resetUploadButton() { const ja = window.ja; if (ja.statusText) { ja.statusText.textContent = `开始上传`; - ja.upsvg.innerHTML = ` - ${UP_ICON} - `; + ja.upsvg.replaceChildren(createIconNode('upload')); } } // 处理上传成功 function handleUploadSuccess(response, progressItem) { updateButtonVisibility(true); - progressItem.innerHTML = ` - -
-
- ${response.data.name} -
- - -
-
-
-
- ${response.data.name} - 已完成 -
-
- ${formatFileSize(response.data.size || 0)} - ${response.data.width}*${response.data.height} -
-
-
- `; + progressItem.replaceChildren(createUploadSuccessItem(response.data)); // 确保响应中包含正确的URL if (!response.data || !response.data.url) { @@ -748,28 +954,12 @@ function resetUploadButton() { // 处理上传失败 function handleUploadError(file, message, progressItem) { progressItem.className = 'ja-upload-item ja-upload-error'; - progressItem.innerHTML = ` -
- - -
-
${message}
- `; + progressItem.replaceChildren(createUploadFailureContent(message)); } // 预览图片 function previewFile(url) { - const modal = document.createElement('div'); - modal.className = 'ja-image-preview-modal'; - modal.innerHTML = ` -
- 预览图片 - -
- `; - const modalContent = modal.querySelector('.ja-modal-content'); - const image = modal.querySelector('.ja-modal-image'); - const closeButton = modal.querySelector('.ja-close-button'); + const { modal, content: modalContent, image, button: closeButton } = createPreviewModal(url); // 关闭模态窗口 closeButton.addEventListener('click', () => { diff --git a/Static/js/pk10-bet.js b/Static/js/pk10-bet.js new file mode 100644 index 0000000..9c79b95 --- /dev/null +++ b/Static/js/pk10-bet.js @@ -0,0 +1,184 @@ +/** + * PK10 Betting Logic + * Depends on: pk10-sound.js, pk10-race.js + * Globals expected: GAME_ID, selectedChip, bets, periodData, historyCache, + * rankNames, dtPairs, I18N, curMissRank + */ + +function formatBetLabel(type,value){ + var m; + if(type==='rank'&&(m=value.match(/^rank(\d+)_(\d+)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' #'+m[2];} + if(type==='bs'&&(m=value.match(/^rank(\d+)_(big|small)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' '+(m[2]==='big'?I18N.big:I18N.small);} + if(type==='oe'&&(m=value.match(/^rank(\d+)_(odd|even)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' '+(m[2]==='odd'?I18N.odd:I18N.even);} + if(type==='dt'&&(m=value.match(/^dt(\d+)_(dragon|tiger)$/))){var p=dtPairs[+m[1]]||[0,0];return(rankNames[p[0]]||p[0])+'vs'+(rankNames[p[1]]||p[1])+' '+(m[2]==='dragon'?I18N.dragon:I18N.tiger);} + if(type==='sum'&&(m=value.match(/^sum_(\d+)$/))){return I18N.sum+' '+m[1];} + if(type==='sum_bs'){var sm={sum_big:I18N.sum_big,sum_small:I18N.sum_small,sum_odd:I18N.sum_odd,sum_even:I18N.sum_even};return sm[value]||value;} + return type+':'+value; +} +function selectChip(a,el){selectedChip=a;document.querySelectorAll('.amount-btn').forEach(function(c){c.classList.remove('active');});if(el)el.classList.add('active'); +var ci=document.getElementById('customAmount');if(ci)ci.value=a;} +function toggleCustomInput(){var row=document.getElementById('customInputRow');if(row)row.style.display=row.style.display==='none'?'flex':'none';} +function applyCustomChip(){var v=parseInt(document.getElementById('customAmount').value);if(!v||v<=0){showToast(I18N.error+': > 0','error');return;}selectedChip=v;document.querySelectorAll('.amount-btn').forEach(function(c){c.classList.remove('active');});} +function addBet(type,target,el){if(periodData&&periodData.status!=='pending'){showToast(I18N.period_closed,'error');return;}var key=type+'_'+target;if(!bets[key])bets[key]={type:type,value:target,amount:0,el:el};bets[key].amount+=selectedChip;el.classList.add('selected');var badge=el.querySelector('.bet-amount-badge');if(!badge){badge=document.createElement('span');badge.className='bet-amount-badge';el.appendChild(badge);}badge.textContent=bets[key].amount>=1000?(bets[key].amount/1000).toFixed(1)+'k':bets[key].amount;updateBetSummary();} +function clearBets(){bets={};document.querySelectorAll('.bet-btn.selected,.bet-cell.selected').forEach(function(e){e.classList.remove('selected');var b=e.querySelector('.bet-amount-badge');if(b)b.remove();});updateBetSummary();} +function updateBetSummary(){var total=0,count=0;Object.values(bets).forEach(function(b){total+=b.amount;count++;});document.getElementById('betCount').textContent=count; +var badge=document.getElementById('betFloatBadge');if(badge)badge.classList.toggle('hide',count===0);} +function toggleBetPanel(){/* placeholder */} +function submitBets(){var arr=Object.values(bets);if(!arr.length){showToast(I18N.place_bet,'error');return;}if(!periodData||!periodData.period_number){showToast(I18N.waiting,'error');return;}fetch('/api/bet',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({game_id:GAME_ID,period_number:periodData.period_number,bets:arr})}).then(function(r){return r.json();}).then(function(d){if(d.success){showToast(d.message,'ok');document.getElementById('balanceDisplay').textContent=parseFloat(d.new_balance).toFixed(2);clearBets();pollPeriod();}else{showToast(d.message,'error');}}).catch(function(e){showToast(I18N.net_err,'error');});} +function showBetTab(tab,btn){['Rank','Bs','Sum','Quick','Dragon','Miss'].forEach(function(t){var p=document.getElementById('panel'+t);if(p)p.classList.toggle('hidden',t.toLowerCase()!==tab);});document.querySelectorAll('.side-menu .mi').forEach(function(m){m.classList.remove('active');});if(btn)btn.classList.add('active');if(tab==='dragon')renderDragonStreak();if(tab==='miss')renderMissPanel();} + +// ===== Quick bet ===== +function quickBet(type,side){ + if(periodData&&periodData.status!=='pending'){showToast(I18N.period_closed,'error');return;} + var count=0; + if(type==='bs'||type==='oe'){ + for(var r=1;r<=10;r++){ + var target='rank'+r+'_'+side; + var key=type+'_'+target; + if(!bets[key])bets[key]={type:type,value:target,amount:0}; + bets[key].amount+=selectedChip;count++; + syncBetCellUI(key,bets[key].amount); + } + } else if(type==='dt'){ + for(var r=1;r<=5;r++){ + var target='dt'+r+'_'+side; + var key='dt_'+target; + if(!bets[key])bets[key]={type:'dt',value:target,amount:0}; + bets[key].amount+=selectedChip;count++; + syncBetCellUI(key,bets[key].amount); + } + } else if(type==='sum_bs'){ + var key='sum_bs_'+side; + if(!bets[key])bets[key]={type:'sum_bs',value:side,amount:0}; + bets[key].amount+=selectedChip;count=1; + syncBetCellUI(key,bets[key].amount); + } + updateBetSummary(); + showToast(count+I18N.bets_added,'ok'); +} +function syncBetCellUI(key,amount){ + var parts=key.split('_');var type=parts[0]; + var value=key.substring(type.length+1); + if(type==='sum'){type='sum_bs';value=key.substring(7);} + var cells=document.querySelectorAll('.bet-cell,.bet-btn'); + for(var i=0;i-1){ + cells[i].classList.add('selected'); + var badge=cells[i].querySelector('.bet-amount-badge'); + if(!badge){badge=document.createElement('span');badge.className='bet-amount-badge';cells[i].appendChild(badge);} + badge.textContent=amount>=1000?(amount/1000).toFixed(1)+'k':amount; + break; + } + } +} + +// ===== Miss stats ===== +function switchMissRank(idx,el){ + curMissRank=idx; + document.querySelectorAll('#missTabs .miss-tab').forEach(function(t){t.classList.remove('active');}); + if(el)el.classList.add('active'); + renderMissPanel(); +} +function renderMissPanel(){ + var D=historyCache; + var grid=document.getElementById('missGrid'); + var tsBox=document.getElementById('missTwoSide'); + if(!grid||!tsBox)return; + if(!D||!D.length){grid.innerHTML='
'+I18N.no_bets+'
';tsBox.innerHTML='';return;} + var r=curMissRank+1; + var html=''; + for(var num=1;num<=10;num++){ + var miss=-1; + for(var i=0;i=5)cls='cold'; + html+='
'+num+'
'+val+'
'; + } + grid.innerHTML=html; + var v0=+D[0]['rank_'+r]; + var sides=[ + {name:I18N.big,check:function(v){return v>5;}}, + {name:I18N.small,check:function(v){return v<=5;}}, + {name:I18N.odd,check:function(v){return v%2===1;}}, + {name:I18N.even,check:function(v){return v%2===0;}} + ]; + if(r<=5){ + sides.push({name:I18N.dragon,check:function(v,row){return +row['rank_'+r]>+row['rank_'+(11-r)];}}); + sides.push({name:I18N.tiger,check:function(v,row){return +row['rank_'+r]<+row['rank_'+(11-r)];}}); + } + var tsHtml=''; + sides.forEach(function(sd){ + var miss=-1; + for(var i=0;i'+val+'
'; + }); + tsBox.innerHTML=tsHtml; +} + +// ===== Dragon streak stats ===== +function renderDragonStreak(){ + var box=document.getElementById('dragonStreakList'); + if(!box)return; + var D=historyCache; + if(!D||D.length<2){box.innerHTML='
'+I18N.no_bets+'
';return;} + var streaks=[]; + var COLORS={big:'#e74c3c',small:'#3498db',odd:'#f39c12',even:'#9b59b6',dragon:'#e74c3c',tiger:'#3498db'}; + var LABELS={big:I18N.big,small:I18N.small,odd:I18N.odd,even:I18N.even,dragon:I18N.dragon,tiger:I18N.tiger}; + for(var r=1;r<=10;r++){ + var v0=+D[0]['rank_'+r];if(!v0)continue; + var bs0=v0>5?'big':'small',bsCnt=1; + for(var i=1;i5?'big':'small')===bs0)bsCnt++;else break;} + if(bsCnt>=2)streaks.push({rank:rankNames[r],key:bs0,color:COLORS[bs0],label:LABELS[bs0],count:bsCnt}); + var oe0=v0%2?'odd':'even',oeCnt=1; + for(var i=1;i=2)streaks.push({rank:rankNames[r],key:oe0,color:COLORS[oe0],label:LABELS[oe0],count:oeCnt}); + if(r<=5){ + var a=+D[0]['rank_'+r],b=+D[0]['rank_'+(11-r)]; + var dt0=a>b?'dragon':'tiger',dtCnt=1; + for(var i=1;ib2?'dragon':'tiger')===dt0)dtCnt++;else break;} + if(dtCnt>=2)streaks.push({rank:rankNames[r],key:dt0,color:COLORS[dt0],label:LABELS[dt0],count:dtCnt}); + } + } + var s0=(+D[0].rank_1)+(+D[0].rank_2); + if(s0){ + var sbs0=s0>=12?'big':'small',sbsCnt=1; + for(var i=1;i=12?'big':'small')===sbs0)sbsCnt++;else break;} + if(sbsCnt>=2)streaks.push({rank:I18N.sum,key:sbs0,color:COLORS[sbs0],label:LABELS[sbs0],count:sbsCnt}); + var soe0=s0%2?'odd':'even',soeCnt=1; + for(var i=1;i=2)streaks.push({rank:I18N.sum,key:soe0,color:COLORS[soe0],label:LABELS[soe0],count:soeCnt}); + } + streaks.sort(function(a,b){return b.count-a.count;}); + if(!streaks.length){box.innerHTML='
'+I18N.no_bets+'
';return;} + var maxCnt=streaks[0].count; + var html=''; + streaks.forEach(function(s){ + var pct=Math.round(s.count/Math.max(maxCnt,1)*100); + var fire=s.count>=5?'🔥':s.count>=3?'💨':''; + html+='
' + +''+s.rank+'' + +''+s.label+'' + +'
' + +''+s.count+'' + +(fire?''+fire+'':'') + +'
'; + }); + box.innerHTML=html; +} + +// ===== Render result ===== +function renderResult(result,container){if(!result)return;var el=document.getElementById(container);el.innerHTML='';['rank_1','rank_2','rank_3','rank_4','rank_5','rank_6','rank_7','rank_8','rank_9','rank_10'].forEach(function(k){var v=result[k];if(!v)return;var d=document.createElement('div');d.className='car c'+v;d.style.cssText='width:28px;height:28px;font-size:11px';d.textContent=v;el.appendChild(d);}); +var tags=document.getElementById('lastResultTags');if(!tags)return;tags.innerHTML='';var r1=+result.rank_1,r2=+result.rank_2;if(r1&&r2){var sum=r1+r2;var items=[sum,(sum>=12?I18N.big:I18N.small),(sum%2?I18N.odd:I18N.even)];for(var i=1;i<=5;i++){var a=+result['rank_'+i],b=+result['rank_'+(11-i)];if(a&&b)items.push(a>b?I18N.dragon:I18N.tiger);}items.forEach(function(t){var s=document.createElement('span');s.textContent=t;tags.appendChild(s);}); +var badge=document.getElementById('sumHotBadge');if(badge){badge.textContent=sum;badge.style.display='inline-flex';}}} + +// ===== Render my bets ===== +function renderMyBets(myBets){var list=document.getElementById('myBetsList');var countEl=document.getElementById('myBetsCount');if(!myBets||!myBets.length){list.innerHTML='
'+I18N.no_bets+'
';countEl.textContent='0';return;}countEl.textContent=myBets.length;var total=0,html='';myBets.forEach(function(b){var label=formatBetLabel(b.bet_type,b.bet_value);var amt=parseFloat(b.amount);total+=amt;var sl=b.status==='pending'?''+I18N.draw+'':b.status==='win'?'+'+parseFloat(b.win_amount).toFixed(0)+'':'-'+amt.toFixed(0)+'';html+='
'+label+''+amt.toFixed(0)+' x'+b.odds+''+sl+'
';});html+='
'+I18N.total+''+total.toFixed(0)+'
';list.innerHTML=html;} +function renderLastMyBets(lastMyBets){var panel=document.getElementById('lastResultPanel');var list=document.getElementById('lastMyBetsList');var profitEl=document.getElementById('lastProfitDisplay');if(!lastMyBets||!lastMyBets.length){panel.classList.add('hidden');return;}panel.classList.remove('hidden');var totalWin=0,totalBet=0,html='';lastMyBets.forEach(function(b){var label=formatBetLabel(b.bet_type,b.bet_value);var amt=parseFloat(b.amount);totalBet+=amt;if(b.status==='win'){var win=parseFloat(b.win_amount);totalWin+=win+amt;html+='
'+label+'+'+win.toFixed(0)+'
';}else{html+='
'+label+'-'+amt.toFixed(0)+'
';}});list.innerHTML=html;var profit=totalWin-totalBet;if(profit>0){profitEl.style.color='var(--success)';profitEl.textContent='+'+profit.toFixed(0);}else if(profit<0){profitEl.style.color='var(--danger)';profitEl.textContent=profit.toFixed(0);}else{profitEl.style.color='var(--text3)';profitEl.textContent='0';}} + +// ===== Toast & Lang ===== +function showToast(msg,type){var t=document.createElement('div');t.style.cssText='position:fixed;top:80px;left:50%;transform:translateX(-50%);padding:10px 24px;border-radius:24px;font-size:13px;z-index:999;color:#fff;box-shadow:0 4px 16px rgba(0,0,0,.15)';t.style.background=type==='ok'?'var(--success)':'var(--danger)';t.textContent=msg;document.body.appendChild(t);setTimeout(function(){t.remove();},2500);} +function setLang(l){fetch('/api/set-lang',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({lang:l})}).then(function(){location.reload();});} diff --git a/Static/js/pk10-poll.js b/Static/js/pk10-poll.js new file mode 100644 index 0000000..319c09d --- /dev/null +++ b/Static/js/pk10-poll.js @@ -0,0 +1,127 @@ +/** + * PK10 Polling & Countdown + * Depends on: pk10-sound.js, pk10-race.js, pk10-bet.js + * Globals expected: GAME_ID, periodData, historyCache, I18N, localCountdown, + * localLockCountdown, lastPollStatus, lastPeriodId, drawnResultShown, + * cachedRaceResult, raceTriggered, pendingRaceHeader, raceAnimState, + * PK10_CONFIG (for status labels) + */ + +// ===== Countdown interval ===== +setInterval(function(){if(localCountdown>0)localCountdown--;if(localLockCountdown>0)localLockCountdown--;var m=Math.floor(localCountdown/60),s=localCountdown%60;document.getElementById('countdown').textContent=localCountdown>0?m+':'+(s<10?'0':'')+s:'--'; +var badge=document.getElementById('statusBadge'); +if(lastPollStatus==='pending'&&localLockCountdown>0){var lm=Math.floor(localLockCountdown/60),ls=localLockCountdown%60;badge.textContent=lm+':'+(ls<10?'0':'')+ls;badge.style.cssText='color:var(--success)';} +else if(lastPollStatus==='pending'&&localLockCountdown<=0){badge.textContent=PK10_CONFIG.lang.sealed;badge.style.cssText='color:var(--danger)';} +if(raceAnimState==='waiting')updateRaceOverlayTimer(localCountdown); +if(localCountdown>0&&localCountdown<=10&&raceAnimState==='waiting')SoundFX.tick(); +},1000); + +// ===== Trigger race sequence ===== +function triggerRaceSequence(){ + if(raceTriggered||raceAnimState!=='waiting')return; + raceTriggered=true; + var remaining=localCountdown*1000; + var readyDur=3000; + var raceDur=Math.max(5500,remaining-readyDur-500); + raceDur=Math.min(raceDur,25000); + var waitBeforeRace=Math.max(0,remaining-readyDur-raceDur); + startReadyAnimation(); + setTimeout(function(){ + if(cachedRaceResult&&raceAnimState==='ready'){ + var raceRes=cachedRaceResult;cachedRaceResult=null; + pendingRaceHeader=raceRes._raw||null; + animateRace(raceRes.arr,raceDur); + } + },readyDur+waitBeforeRace); +} + +// ===== Poll period ===== +function pollPeriod(){return fetch('/api/period/current?game_id='+GAME_ID).then(function(r){return r.json();}).then(function(d){if(!d.success)return;periodData=d.data; +var serverRemaining=periodData.remaining_seconds||0;var periodChanged=(periodData.id&&periodData.id!==lastPeriodId);var statusChanged=(periodData.status!==lastPollStatus);var driftTooMuch=Math.abs(localCountdown-serverRemaining)>3; +if(periodChanged||statusChanged||driftTooMuch||localCountdown<=0)localCountdown=serverRemaining; +var serverLockCountdown=periodData.lock_countdown||0;if(periodChanged||statusChanged||Math.abs(localLockCountdown-serverLockCountdown)>3)localLockCountdown=serverLockCountdown; +if(d.balance!==null&&d.balance!==undefined)document.getElementById('balanceDisplay').textContent=parseFloat(d.balance).toFixed(2); +// Status badge +var badge=document.getElementById('statusBadge');var statusMap={pending:[PK10_CONFIG.lang.betting,'seal-status','color:var(--success)'],locked:[PK10_CONFIG.lang.sealed,'seal-status','color:var(--danger)'],drawn:[I18N.drawing,'seal-status','color:var(--warn)'],settled:[I18N.settled_label,'seal-status','color:var(--warn)']};var s=statusMap[periodData.status]||statusMap.pending;badge.textContent=s[0];badge.className=s[1];badge.style.cssText=s[2]; +switchVideoOrAnim(periodData.status,statusChanged); +// Period number +var fullPn=periodData.period_number||'---';var shortPn=fullPn.length>8?fullPn.slice(0,-4)+'-'+fullPn.slice(-4):fullPn;var pnEl=document.getElementById('periodNum');pnEl.textContent=shortPn;pnEl.title=fullPn; +// Extract result array +function extractResultArr(res){if(!res)return null;var arr=[res.rank_1,res.rank_2,res.rank_3,res.rank_4,res.rank_5,res.rank_6,res.rank_7,res.rank_8,res.rank_9,res.rank_10];return arr.every(function(v){return v;})?arr:null;} + +// Scene header sync +if(raceAnimState!=='racing'&&raceAnimState!=='finished'&&raceAnimState!=='ready'){ + if(d.last_result) updateSceneHeader(d.last_result, d.last_result.period_number||fullPn); +} + +// === Pending → idle === +if(periodData.status==='pending'){ + cachedRaceResult=null;raceTriggered=false; + if(d.last_result){ + var lp=d.last_result.period_number||''; + document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp; + renderResult(d.last_result,'lastResult'); + } + if(raceAnimState==='finished'||raceAnimState==='racing'||raceAnimState==='ready'){ + if(!window._podiumTimer){ + var protectMs=8000; + if(raceAnimState==='ready')protectMs=16000; + else if(raceAnimState==='racing')protectMs=12000; + window._podiumTimer=setTimeout(function(){ + resetRace(); + startIdleAnimation(); + window._podiumTimer=null; + },protectMs); + } + } else if(raceAnimState!=='waiting'){ + resetRace(); + startIdleAnimation(); + } +} + +// === Locked → cache result + trigger sprint === +if(periodData.status==='locked'){ + var raceRes=extractResultArr(d.race_result); + if(raceRes&&!cachedRaceResult){ + cachedRaceResult={arr:raceRes,_raw:d.race_result}; + } + if(cachedRaceResult)triggerRaceSequence(); +} + +// === Drawn/Settled → show result + podium === +if(d.last_result&&(periodData.status==='drawn'||periodData.status==='settled')){ + if(statusChanged||periodChanged){ + var lp=d.last_result.period_number||''; + document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp; + var ra=extractResultArr(d.last_result); + if(ra&&raceAnimState!=='finished'&&raceAnimState!=='racing'&&raceAnimState!=='ready'){ + cachedRaceResult=null; + pendingRaceHeader=d.last_result; + animateRace(ra); + } else if(raceAnimState!=='racing'&&raceAnimState!=='ready'){ + updateSceneHeader(d.last_result, lp); + } + setTimeout(function(){renderResult(d.last_result,'lastResult');},5500); + } +} + +// === First load === +if(d.last_result&&!lastAnimatedResult&&raceAnimState==='idle'){ + var ra=extractResultArr(d.last_result); + if(ra){ + var lp=d.last_result.period_number||''; + document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp; + renderResult(d.last_result,'lastResult'); + startIdleAnimation(); + } +} +renderMyBets(d.my_bets||[]);renderLastMyBets(d.last_my_bets||[]); +// History +if(d.history&&periodChanged){historyCache=d.history;var hl=document.getElementById('historyList');hl.innerHTML='';d.history.forEach(function(h){var row=document.createElement('div');row.className='card';row.style.cssText='padding:6px 8px;margin-bottom:4px';var pn=h.period_number||'';var sp=pn.length>4?'#'+pn.slice(-4):pn;var balls='';['rank_1','rank_2','rank_3','rank_4','rank_5','rank_6','rank_7','rank_8','rank_9','rank_10'].forEach(function(k){if(h[k])balls+='
'+h[k]+'
';});row.innerHTML='
'+sp+'
'+balls+'
';hl.appendChild(row);});if(!document.getElementById('panelDragon').classList.contains('hidden'))renderDragonStreak();} +lastPollStatus=periodData.status;lastPeriodId=periodData.id||0; +}).catch(function(e){console.error('pollPeriod error:',e);});} + +// ===== Start polling ===== +pollPeriod(); +(function dynamicPoll(){var delay=(lastPollStatus==='locked'||lastPollStatus==='drawn'||lastPollStatus==='settled')?1500:3000;setTimeout(function(){pollPeriod().then(dynamicPoll).catch(dynamicPoll);},delay);})(); + diff --git a/Static/js/pk10-race.js b/Static/js/pk10-race.js new file mode 100644 index 0000000..0f9e815 --- /dev/null +++ b/Static/js/pk10-race.js @@ -0,0 +1,179 @@ +/** + * PK10 Race Animation System + * Depends on: pk10-sound.js (SoundFX) + * Globals expected: raceAnimState, lastAnimatedResult, idleAnimTimer, carPositions, + * raceFrameTimer, ballRollTimer, FINISH_LINE, START_LINE, trafficLightTimers, + * CAR_COLORS, I18N, raceModalOpen, currentView, cachedRaceResult, pendingRaceHeader + */ + +function setCarPosition(n,pct,dur){var c=document.getElementById('carContainer'+n);var b=document.getElementById('bar'+n);if(!c)return;c.style.transition='right '+dur+'s ease';c.style.right=pct+'%';if(b){b.style.transition='width '+dur+'s ease';b.style.width=Math.min(pct+3,92)+'%';}carPositions[n]=pct;} +function setCarMoving(n,moving,sprinting){var c=document.getElementById('carContainer'+n);if(!c)return;c.classList.toggle('moving',moving);c.classList.toggle('sprinting',!!sprinting);c.classList.remove('ready','braking');} +function setCarBraking(n){var c=document.getElementById('carContainer'+n);if(!c)return;c.classList.remove('sprinting','moving');c.classList.add('braking');setTimeout(function(){if(c)c.classList.remove('braking');},800);} +function setRankBadge(n,rank){var badge=document.getElementById('rankBadge'+n);if(!badge)return;var c=document.getElementById('carContainer'+n);if(rank>0){var labels=['1st','2nd','3rd','4th','5th','6th','7th','8th','9th','10th'];badge.textContent=labels[rank-1]||rank;if(c)c.classList.add('show-rank');}else{badge.textContent='';if(c)c.classList.remove('show-rank');}} + +// ANIM: idle +function startIdleAnimation(){ +if(idleAnimTimer)return;raceAnimState='waiting';hidePodium(); +showRaceOverlay();clearTrafficLights(); +for(var i=1;i<=10;i++){ + setCarPosition(i,START_LINE,0.6); + setCarMoving(i,false,false); + setRankBadge(i,0); +} +idleAnimTimer=true;} +function stopIdleAnimation(){if(idleAnimTimer&&idleAnimTimer!==true){clearInterval(idleAnimTimer);}idleAnimTimer=null;if(raceFrameTimer){cancelAnimationFrame(raceFrameTimer);raceFrameTimer=null;}for(var i=1;i<=10;i++){setCarMoving(i,false,false);var c=document.getElementById('carContainer'+i);if(c)c.classList.remove('ready');}} +function startReadyAnimation(){stopIdleAnimation();raceAnimState='ready';hidePodium();showRaceOverlay(); +for(var i=1;i<=10;i++){setCarPosition(i,START_LINE,0.8);setCarMoving(i,true,false);setRankBadge(i,0);var c=document.getElementById('carContainer'+i);if(c)c.classList.add('ready');} +SoundFX.engineIdle(); +startTrafficLightSequence();} + +// ANIM: race +function triggerFinishBurst(){var fb=document.getElementById('finishBurst');if(!fb)return;fb.classList.remove('active');void fb.offsetWidth;fb.classList.add('active');setTimeout(function(){fb.classList.remove('active');},1000);} +function generateConfetti(){var box=document.getElementById('confettiBox');if(!box)return;box.innerHTML='';var colors=['#ff3333','#3399ff','#ffcc00','#33ff66','#ff66cc','#ff8800','#66ffff','#ffff33','#9b59b6','#1abc9c'];for(var i=0;i<40;i++){var c=document.createElement('div');c.className='confetti';c.style.left=Math.random()*100+'%';c.style.background=colors[i%colors.length];c.style.setProperty('--cf-dur',(2.5+Math.random()*2)+'s');c.style.setProperty('--cf-delay',(Math.random()*1.5)+'s');c.style.width=(4+Math.random()*4)+'px';c.style.height=(6+Math.random()*8)+'px';c.style.borderRadius=Math.random()>.5?'50%':'2px';box.appendChild(c);}} +function startTrackShake(){var t=document.getElementById('raceArea');if(t)t.classList.add('shaking');} +function stopTrackShake(){var t=document.getElementById('raceArea');if(t)t.classList.remove('shaking');} + +/* Direct positioning (rAF driven, no CSS transition) */ +function setCarPosDirect(n,pct){var c=document.getElementById('carContainer'+n),b=document.getElementById('bar'+n);if(!c)return;c.style.transition='none';c.style.right=pct+'%';if(b){b.style.transition='none';b.style.width=Math.min(pct+3,92)+'%';}carPositions[n]=pct;} + +/* Race script generation (high-frequency overtaking) */ +function _raceScript(rankMap,carTargets){ +var sc={},i,w,c,tmp,f; +var N=25,T=[],PF=[],PG=[],B=[]; +for(i=0;i<=N;i++){f=i/N;T.push(f); + PF.push(START_LINE+f*(FINISH_LINE-START_LINE)); + PG.push(f<0.82?(1.0+Math.abs(Math.sin(f*Math.PI*6))*0.8):(1.0+(f-0.82)/0.18*6.5)); + B.push(f<0.78?f*0.06:0.05+(f-0.78)/0.22*0.95); +}B[0]=0;B[N]=1;PG[0]=0; +var FO=[];for(w=0;w<=N;w++){var a=[];for(c=1;c<=10;c++)a.push(c);for(i=9;i>0;i--){var j=Math.floor(Math.random()*(i+1));tmp=a[i];a[i]=a[j];a[j]=tmp;}FO.push(a);} +var fin=[];for(c=1;c<=10;c++)fin[rankMap[c]]=c;FO[N]=fin.slice(); +var W=fin[0],S2=fin[1]; +for(w=1;w1){si=Math.floor(Math.random()*2);tmp=FO[w][wi];FO[w][wi]=FO[w][si];FO[w][si]=tmp;}} +if(S2)for(w=Math.floor(N*.4);w2&&Math.random()>.3){tmp=FO[w][s2i];FO[w][s2i]=FO[w][0];FO[w][0]=tmp;}} +for(c=1;c<=10;c++){sc[c]=[];for(w=0;w<=N;w++){ + if(!w){sc[c].push({t:0,p:START_LINE});continue;} + var fk=FO[w].indexOf(c),rk=rankMap[c]!==undefined?rankMap[c]:9; + var br=fk*(1-B[w])+rk*B[w]; + var p=PF[w]-br*PG[w]+(Math.random()-.5)*1.5; + sc[c].push({t:T[w],p:Math.max(START_LINE,Math.min(FINISH_LINE+2,p))}); +}}return sc;} + +/* Smoothstep interpolation */ +function _interp(wp,t){if(t<=wp[0].t)return wp[0].p;var L=wp.length-1;if(t>=wp[L].t)return wp[L].p;for(var i=0;i=wp[i].t&&t<=wp[i+1].t){var s=(t-wp[i].t)/(wp[i+1].t-wp[i].t);s=s*s*(3-2*s);return wp[i].p+(wp[i+1].p-wp[i].p)*s;}}return wp[L].p;} + +/* Core race animation (dramatic chase version) */ +function animateRace(result,dur){if(!result||result.length<10)return;var rk=result.join(',');if(lastAnimatedResult===rk&&raceAnimState==='finished')return; +lastAnimatedResult=rk;stopIdleAnimation();hideRaceOverlay();hidePodium();raceAnimState='racing'; +dur=dur||5500; +var rankMap={};for(var i=0;i<10;i++)rankMap[result[i]]=i; +var carTargets={};for(var i=1;i<=10;i++){var r=rankMap[i]!==undefined?rankMap[i]:9;carTargets[i]=FINISH_LINE-r*7.5;setCarMoving(i,true,false);setRankBadge(i,0);} +var script=_raceScript(rankMap,carTargets); +startBallRoll(dur); +var t0=performance.now(),shook=false,sprinted=false; +function tick(now){if(raceAnimState!=='racing')return; +var el=now-t0,t=Math.min(el/dur,1); +if(!shook&&t>.10){shook=true;startTrackShake();SoundFX.engineStart();} +if(!sprinted&&t>.16){sprinted=true;for(var i=1;i<=10;i++)setCarMoving(i,true,true);} +for(var c=1;c<=10;c++)setCarPosDirect(c,_interp(script[c],t)); +if(t>=1){for(var i=1;i<=10;i++){setCarPosDirect(i,carTargets[i]);setCarBraking(i);} +SoundFX.brakeSqueal();triggerFinishBurst();SoundFX.finish(); +setTimeout(function(){if(raceAnimState!=='racing')return;raceAnimState='finished';stopTrackShake();stopBallRoll();for(var i=1;i<=10;i++){setCarMoving(i,false,false);var c=document.getElementById('carContainer'+i);if(c)c.classList.remove('braking');var rank=rankMap[i]!==undefined?rankMap[i]+1:0;setRankBadge(i,rank);} +var badge=document.getElementById('statusBadge');if(badge){badge.textContent=I18N.drawing;badge.style.cssText='color:var(--warn)';} +if(pendingRaceHeader){updateSceneHeader(pendingRaceHeader,pendingRaceHeader.period_number||'');pendingRaceHeader=null;} +setTimeout(function(){generateConfetti();showPodium(result);SoundFX.podium();},800);},500);return;} +raceFrameTimer=requestAnimationFrame(tick);} +raceFrameTimer=requestAnimationFrame(tick);} + +function resetRace(){stopIdleAnimation();hideRaceOverlay();hidePodium();stopTrackShake();stopBallRoll();raceAnimState='idle';lastAnimatedResult=null;cachedRaceResult=null;pendingRaceHeader=null;if(window._podiumTimer){clearTimeout(window._podiumTimer);window._podiumTimer=null;}var fb=document.getElementById('finishBurst');if(fb)fb.classList.remove('active');var cb=document.getElementById('confettiBox');if(cb)cb.innerHTML='';for(var i=1;i<=10;i++){var c=document.getElementById('carContainer'+i);var b=document.getElementById('bar'+i);if(c){c.style.transition='right 0.5s ease';c.style.right=START_LINE+'%';c.classList.remove('moving','sprinting','ready','show-rank','braking');}if(b){b.style.transition='width 0.5s ease';b.style.width='5%';}carPositions[i]=START_LINE;setRankBadge(i,0);}} + +// ===== Scene controls (traffic lights + podium + scene header) ===== +function showRaceOverlay(){var ov=document.getElementById('raceOverlay');if(ov)ov.classList.add('show');} +function hideRaceOverlay(){var ov=document.getElementById('raceOverlay');if(ov)ov.classList.remove('show');clearTrafficLights();} +function clearTrafficLights(){trafficLightTimers.forEach(function(t){clearTimeout(t);});trafficLightTimers=[];for(var i=1;i<=5;i++){var l=document.getElementById('tl'+i);if(l)l.className='tl';}} +function startTrafficLightSequence(){clearTrafficLights(); +trafficLightTimers.push(setTimeout(function(){document.getElementById('tl1').className='tl red';SoundFX.trafficBeep(4);},300)); +trafficLightTimers.push(setTimeout(function(){document.getElementById('tl2').className='tl red';SoundFX.trafficBeep(3);},600)); +trafficLightTimers.push(setTimeout(function(){document.getElementById('tl3').className='tl yellow';SoundFX.trafficBeep(2);},900)); +trafficLightTimers.push(setTimeout(function(){document.getElementById('tl4').className='tl red';SoundFX.trafficBeep(1);},1200)); +trafficLightTimers.push(setTimeout(function(){document.getElementById('tl5').className='tl red';SoundFX.trafficBeep(0);},1500)); +trafficLightTimers.push(setTimeout(function(){for(var i=1;i<=5;i++)document.getElementById('tl'+i).className='tl green';SoundFX.trafficBeep(0);setTimeout(clearTrafficLights,500);},2500));} +function updateRaceOverlayTimer(secs){var td=document.getElementById('raceTimerDisplay');if(!td)return;var m=Math.floor(secs/60),s=secs%60;td.innerHTML=(m<10?'0':'')+m+':'+(s<10?'0':'')+s+'00';} + +// ===== Ball roll effect ===== +function startBallRoll(dur){ + stopBallRoll(); + var balls=document.getElementById('raceSceneBalls'); + if(!balls)return; + dur=dur||5500; + var speed=100; + function roll(){ + var arr=[];for(var i=1;i<=10;i++)arr.push(i); + for(var i=arr.length-1;i>0;i--){var j=Math.floor(Math.random()*(i+1));var t=arr[i];arr[i]=arr[j];arr[j]=t;} + var html=''; + arr.forEach(function(v){var cc=CAR_COLORS[v];html+=''+v+'';}); + balls.innerHTML=html; + } + roll(); + ballRollTimer=setInterval(roll,speed); + var slow1=Math.round(dur*0.4); + var slow2=Math.round(dur*0.75); + setTimeout(function(){if(ballRollTimer){clearInterval(ballRollTimer);ballRollTimer=setInterval(roll,200);}},slow1); + setTimeout(function(){if(ballRollTimer){clearInterval(ballRollTimer);ballRollTimer=setInterval(roll,400);}},slow2); +} +function stopBallRoll(){ + if(ballRollTimer){clearInterval(ballRollTimer);ballRollTimer=null;} +} + +function updateSceneHeader(result,periodNum){ +var balls=document.getElementById('raceSceneBalls');var pn=document.getElementById('raceScenePeriod'); +if(pn&&periodNum){var short=periodNum.length>6?periodNum.slice(-6):periodNum;pn.textContent='\u671F\u6570\uFF1A'+short;} +if(balls&&result){var html='';['rank_1','rank_2','rank_3','rank_4','rank_5','rank_6','rank_7','rank_8','rank_9','rank_10'].forEach(function(k){var v=result[k];if(v){var cc=CAR_COLORS[v];html+=''+v+'';}});balls.innerHTML=html||'---';} +updateSceneFooter(result,periodNum); +} +function updateSceneFooter(result,periodNum){ +var ftPn=document.getElementById('ftPeriodNum'); +if(ftPn&&periodNum)ftPn.textContent=periodNum; +if(!result)return; +var r1=+result.rank_1,r2=+result.rank_2; +if(r1&&r2){ + var sum=r1+r2; + var ftSv=document.getElementById('ftSumVal');if(ftSv)ftSv.textContent=sum; + var ftSb=document.getElementById('ftSumBs');if(ftSb)ftSb.textContent=sum>=12?I18N.big:I18N.small; + var ftSo=document.getElementById('ftSumOe');if(ftSo)ftSo.textContent=sum%2?I18N.odd:I18N.even; +} +var ftDt=document.getElementById('ftDtTags'); +if(ftDt){var html='';for(var i=1;i<=5;i++){var a=+result['rank_'+i],b=+result['rank_'+(11-i)];if(a&&b){var isDragon=a>b;html+=''+(isDragon?I18N.dragon:I18N.tiger)+'';}}ftDt.innerHTML=html||'---';} +} + +// ===== Toggle scene collapse ===== +function toggleRaceScene(){var scene=document.querySelector('.race-scene');if(!scene)return;scene.classList.toggle('collapsed');localStorage.setItem('raceSceneCollapsed',scene.classList.contains('collapsed')?'1':'0');} +// Init collapsed state + sound icon +(function(){var btn=document.querySelector('.rs-sound');if(btn)btn.textContent=SoundFX.enabled?'🔊':'🔇';if(localStorage.getItem('raceSceneCollapsed')==='1'){var s=document.querySelector('.race-scene');if(s)s.classList.add('collapsed');}})(); + +// ===== Modal control ===== +function openRaceModal(){ + document.getElementById('raceModalMask').classList.add('show'); + raceModalOpen=true; + document.body.style.overflow='hidden'; +} +function closeRaceModal(){ + document.getElementById('raceModalMask').classList.remove('show'); + raceModalOpen=false; + document.body.style.overflow=''; + SoundFX.stopAll(); +} +function getPodiumCarSrc(num){return '/Static/img/cars/car'+num+'.png';} +function showPodium(result){if(!result||result.length<3)return;document.getElementById('podiumImg1').src=getPodiumCarSrc(result[0]);document.getElementById('podiumImg2').src=getPodiumCarSrc(result[1]);document.getElementById('podiumImg3').src=getPodiumCarSrc(result[2]);var po=document.getElementById('podiumOverlay');if(po)po.classList.add('show');} +function hidePodium(){var po=document.getElementById('podiumOverlay');if(po)po.classList.remove('show');} + +// ===== Video/Anim switch ===== +function switchVideoOrAnim(status,changed){ +currentView='anim'; +var desc=document.getElementById('raceEntryDesc'); +if(desc){ + var map={pending:'\u7B49\u5F85\u5F00\u5956\u4E2D...',locked:'\u6B63\u5728\u5F00\u5956\uFF01',drawn:'\u5F00\u5956\u7ED3\u675F',settled:'\u5DF2\u7ED3\u7B97'}; + desc.textContent=map[status]||'\u70B9\u51FB\u67E5\u770B\u5F00\u5956\u52A8\u753B'; +} +} +function tryResumeVideo(){var v=document.querySelector('#pk10VideoPlayer video');if(v){v.muted=true;v.play().catch(function(){});}} diff --git a/Static/js/pk10-sound.js b/Static/js/pk10-sound.js new file mode 100644 index 0000000..73ee354 --- /dev/null +++ b/Static/js/pk10-sound.js @@ -0,0 +1,33 @@ +/** + * PK10 Sound Engine (SoundFX) + * Depends on: (none - first to load) + * Provides: SoundFX, toggleRaceSound + * + * Sound only plays when the race modal is open (raceModalOpen === true). + * betSuccess/error are exempt — they are UI feedback, not race sounds. + */ +var SoundFX={ +enabled:localStorage.getItem('raceSoundOn')!=='0', +_cache:{}, +// Core play — checks both enabled AND raceModalOpen +_play:function(name,vol,startAt){if(!this.enabled||!window.raceModalOpen)return null;var a=new Audio('/Static/sounds/'+name+'.mp3');a.volume=Math.min(vol||0.6,1);if(startAt)a.currentTime=startAt;a.play().catch(function(){});return a;}, +_stop:function(key){if(this._cache[key]){try{this._cache[key].pause();this._cache[key].currentTime=0;}catch(e){}delete this._cache[key];}}, +engineIdle:function(){this._cache.idle=this._play('engine',0.3);}, +trafficBeep:function(n){this._play('beep',n>0?0.4:0.6);}, +engineStart:function(){this._stop('idle');this._cache.eng=this._play('engine',0.7);}, +accelerate:function(){this._stop('eng');this._cache.race=this._play('racing',0.5);}, +gearShift:function(){this._play('engine',0.25,1.5);}, +whoosh:function(){this._play('whoosh',0.5);}, +brakeSqueal:function(){this._stop('race');this._play('brake',0.6);}, +finish:function(){this._play('finish',0.7);}, +podium:function(){this._play('finish',0.5);}, +// tick — race countdown sound, only when modal open +tick:function(){if(!this.enabled||!window.raceModalOpen)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();var o=ctx.createOscillator(),g=ctx.createGain();o.type='sine';o.frequency.value=800;g.gain.setValueAtTime(0.1,ctx.currentTime);g.gain.linearRampToValueAtTime(0,ctx.currentTime+0.05);o.connect(g).connect(ctx.destination);o.start();o.stop(ctx.currentTime+0.05);}catch(e){}}, +// betSuccess/error — UI feedback, always plays when enabled (no modal check) +betSuccess:function(){if(!this.enabled)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();[880,1175].forEach(function(f,i){var o=ctx.createOscillator(),g=ctx.createGain();o.type='sine';o.frequency.value=f;g.gain.setValueAtTime(0.08,ctx.currentTime+i*0.08);g.gain.linearRampToValueAtTime(0,ctx.currentTime+i*0.08+0.12);o.connect(g).connect(ctx.destination);o.start(ctx.currentTime+i*0.08);o.stop(ctx.currentTime+i*0.08+0.12);});}catch(e){}}, +error:function(){if(!this.enabled)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();var o=ctx.createOscillator(),g=ctx.createGain();o.type='square';o.frequency.setValueAtTime(200,ctx.currentTime);o.frequency.setValueAtTime(150,ctx.currentTime+0.15);g.gain.setValueAtTime(0.06,ctx.currentTime);g.gain.linearRampToValueAtTime(0,ctx.currentTime+0.3);o.connect(g).connect(ctx.destination);o.start();o.stop(ctx.currentTime+0.3);}catch(e){}}, +// stopAll — kill all cached sounds (called when modal closes) +stopAll:function(){for(var k in this._cache){this._stop(k);}}, +toggle:function(){this.enabled=!this.enabled;localStorage.setItem('raceSoundOn',this.enabled?'1':'0');return this.enabled;} +}; +function toggleRaceSound(){var on=SoundFX.toggle();var btn=document.querySelector('.rs-sound');if(btn)btn.textContent=on?'🔊':'🔇';if(on)SoundFX.tick();} diff --git a/Static/sounds/beep.mp3 b/Static/sounds/beep.mp3 new file mode 100644 index 0000000..e76fcfa Binary files /dev/null and b/Static/sounds/beep.mp3 differ diff --git a/Static/sounds/brake.mp3 b/Static/sounds/brake.mp3 new file mode 100644 index 0000000..675e84c Binary files /dev/null and b/Static/sounds/brake.mp3 differ diff --git a/Static/sounds/engine.mp3 b/Static/sounds/engine.mp3 new file mode 100644 index 0000000..e7ac6fa Binary files /dev/null and b/Static/sounds/engine.mp3 differ diff --git a/Static/sounds/finish.mp3 b/Static/sounds/finish.mp3 new file mode 100644 index 0000000..3692eb8 Binary files /dev/null and b/Static/sounds/finish.mp3 differ diff --git a/cron/auto_period_task.php b/cron/auto_period_task.php index f50912c..d1c2ca1 100644 --- a/cron/auto_period_task.php +++ b/cron/auto_period_task.php @@ -49,6 +49,7 @@ require_once $rootDir . '/App/Core/GameFactory.php'; require_once $rootDir . '/App/Core/PK10Algorithm.php'; require_once $rootDir . '/App/Core/DiceAlgorithm.php'; require_once $rootDir . '/App/Core/XocDiaAlgorithm.php'; +require_once $rootDir . '/App/Core/SettingsHelper.php'; use Db\Database; use App\Core\GameFactory; @@ -208,9 +209,24 @@ function processGame($db, $game) { } else { doDraw($db, $periodId, $gameId, $algoClass); } + // 推送开奖结果到 bot 群组 + $drawResult = json_decode($db->get('periods', 'result', ['id' => $periodId]) ?: '[]', true); + if (!empty($drawResult)) { + $pk10Row = $db->get('pk10_results', '*', ['period_id' => $periodId]); + $championSum = $pk10Row ? (int)$pk10Row['champion_sum'] : ($drawResult[0] + $drawResult[1]); + queueBotPush($db, $gameId, 'draw', [ + 'event' => 'period_drawn', + 'period_id' => $periodId, + 'period_number' => $periodNum, + 'result' => $drawResult, + 'champion_sum' => $championSum, + 'manual' => false, + ], ['remind_draw_result' => 1], $periodNum); + } + logToDb($db, $gameId, $periodId, 'draw', '自动开奖'); $status = 'drawn'; - return; + // fall through to settle immediately } // === 阶段3:结算 + 开新期 === @@ -301,7 +317,8 @@ function doPreDraw($db, $periodId, $gameId, $algoClass) { ]); if (!empty($waterConfig) && !empty($bets)) { - return $algoClass::generateControlledResult($bets, $waterConfig); + $targetProfitRate = floatval(\App\Core\SettingsHelper::get('target_profit_rate')); + return $algoClass::generateControlledResult($bets, $waterConfig, 100, $targetProfitRate); } return $algoClass::generateResult(); } @@ -356,6 +373,14 @@ function doSettle($db, $period, $algoClass, $gameType) { $bets = $db->select('bets', '*', ['period_id' => $periodId, 'status' => 'pending']); if (empty($bets)) { $db->update('periods', ['status' => 'settled', 'updated_at' => date('Y-m-d H:i:s')], ['id' => $periodId]); + $gameId = (int)$period['game_id']; + queueBotPush($db, $gameId, 'system', [ + 'event' => 'period_settled', + 'period_id' => $periodId, + 'period_number' => $period['period_number'], + 'wins' => 0, 'losses' => 0, 'total_payout' => 0, + 'message' => 'No bets to settle', + ], [], $period['period_number']); logInfo(" ✓ 结算: 无投注,直接结算"); return; } @@ -402,6 +427,18 @@ function doSettle($db, $period, $algoClass, $gameType) { doSettleAgentCommissions($db, $bets, $periodId); $db->update('periods', ['status' => 'settled', 'updated_at' => date('Y-m-d H:i:s')], ['id' => $periodId]); + + // 推送结算结果到 bot 群组 + $gameId = (int)$period['game_id']; + queueBotPush($db, $gameId, 'system', [ + 'event' => 'period_settled', + 'period_id' => $periodId, + 'period_number' => $period['period_number'], + 'wins' => $wins, + 'losses' => $losses, + 'total_payout' => (float)$totalPayout, + ], [], $period['period_number']); + $db->medoo->pdo->commit(); logInfo(" ✓ {$gameName}结算: 中{$wins}笔 负{$losses}笔 派奖{$totalPayout}"); } catch (\Throwable $e) { @@ -451,3 +488,31 @@ function doSettleAgentCommissions($db, $bets, $periodId) { ]); } } + +// ============================ +// Bot 推送队列 +// ============================ +function queueBotPush($db, int $gameId, string $pushType, array $payload, array $groupFilters = [], ?string $periodNumber = null): void { + if ($gameId <= 0) return; + try { + $where = array_merge(['game_id' => $gameId, 'status' => 1], $groupFilters); + $groups = $db->select('bot_groups', ['id'], $where) ?: []; + $now = date('Y-m-d H:i:s'); + foreach ($groups as $group) { + $db->insert('bot_push_logs', [ + 'group_id' => (int)$group['id'], + 'period_number' => $periodNumber, + 'push_type' => $pushType, + 'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE), + 'status' => 'pending', + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + if (!empty($groups)) { + logInfo(" ✓ Bot推送: {$pushType} → " . count($groups) . "个群组"); + } + } catch (\Throwable $e) { + logInfo(" ⚠ Bot推送失败: " . $e->getMessage()); + } +} diff --git a/index.php b/index.php index 3254956..353f39b 100755 --- a/index.php +++ b/index.php @@ -29,6 +29,14 @@ spl_autoload_register(function ($class) { } } + if (str_starts_with($class, 'App\\Services\\')) { + $file = __DIR__ . DIRECTORY_SEPARATOR . $classPath; + if (file_exists($file)) { + require_once $file; + return; + } + } + if (str_starts_with($class, 'App\\Core\\')) { $relativePath = substr($classPath, strlen('App' . DIRECTORY_SEPARATOR . 'Core' . DIRECTORY_SEPARATOR)); $file = APP_CORE_PATH . $relativePath; diff --git a/routes.php b/routes.php index d37e7f7..c9a044e 100755 --- a/routes.php +++ b/routes.php @@ -1,139 +1,267 @@ isFile() && $file->getExtension() === 'php') { + $phpFiles[] = $file->getPathname(); + } + } +} +// Also check root-level PHP files +foreach (glob($ROOT . '/*.php') as $f) { + $phpFiles[] = $f; +} + +$syntaxErrors = []; +foreach ($phpFiles as $file) { + $output = []; + $ret = 0; + exec('php -l ' . escapeshellarg($file) . ' 2>&1', $output, $ret); + if ($ret !== 0) { + $relPath = str_replace($ROOT . '/', '', $file); + $syntaxErrors[] = $relPath . ': ' . implode(' ', $output); + } +} + +if (empty($syntaxErrors)) { + test_pass("PHP syntax check: all " . count($phpFiles) . " files pass"); +} else { + foreach ($syntaxErrors as $err) { + test_fail("PHP syntax error", $err); + } +} + +// ============================================================ +// 2. 路由完整性验证 +// ============================================================ +echo "=== 2. Route Integrity ===\n"; +$routes = include $ROOT . '/routes.php'; +$seenPaths = []; +$routeIssues = 0; + +foreach ($routes as $idx => $route) { + $method = $route[0]; + $path = $route[1]; + $action = $route[2]; + + // 检查重复路由 path (method+path) + $key = $method . ':' . $path; + if (isset($seenPaths[$key])) { + test_fail("Route duplicate", "'{$key}' duplicated at line " . ($idx + 1)); + $routeIssues++; + } + $seenPaths[$key] = true; + + // 解析 Controller@method + if (strpos($action, '@') === false) { + test_fail("Route format", "'{$path}' action '{$action}' missing @method"); + $routeIssues++; + continue; + } + list($controllerClass, $methodName) = explode('@', $action); + + // 转换类名到文件路径 + $controllerFile = $ROOT . '/' . str_replace('\\', '/', ltrim($controllerClass, '\\')) . '.php'; + if (!file_exists($controllerFile)) { + test_fail("Route controller missing", "'{$path}' -> {$controllerClass} file not found: {$controllerFile}"); + $routeIssues++; + continue; + } + + // 检查方法是否存在(通过正则搜索) + $content = file_get_contents($controllerFile); + if (!preg_match('/function\s+' . preg_quote($methodName) . '\s*\(/', $content)) { + test_fail("Route method missing", "'{$path}' -> {$controllerClass}@{$methodName} method not found"); + $routeIssues++; + } +} + +if ($routeIssues === 0) { + test_pass("Route integrity: all " . count($routes) . " routes valid"); +} + +// ============================================================ +// 3. Service 层验证 +// ============================================================ +echo "=== 3. Service Layer ===\n"; + +// TransactionService 方法检查 +$tsFile = $ROOT . '/App/Services/TransactionService.php'; +if (!file_exists($tsFile)) { + test_fail("TransactionService", "File not found"); +} else { + $tsContent = file_get_contents($tsFile); + $tsMethods = ['transfer', 'fundRequest']; + foreach ($tsMethods as $m) { + if (preg_match('/function\s+' . $m . '\s*\(/', $tsContent)) { + test_pass("TransactionService has {$m}() method"); + } else { + test_fail("TransactionService", "Missing method {$m}()"); + } + } +} + +// ReportService 方法检查 +$rsFile = $ROOT . '/App/Services/ReportService.php'; +if (!file_exists($rsFile)) { + test_fail("ReportService", "File not found"); +} else { + $rsContent = file_get_contents($rsFile); + $rsMethods = ['getUserReport', 'getOverviewStats', 'getDailyStats', 'getUserWinLoss', 'getTopUsers', 'getAgentStats']; + foreach ($rsMethods as $m) { + if (preg_match('/function\s+' . $m . '\s*\(/', $rsContent)) { + test_pass("ReportService has {$m}() method"); + } else { + test_fail("ReportService", "Missing method {$m}()"); + } + } +} + +// Service 纯业务逻辑检查(不含 HTTP 操作) +$serviceFiles = glob($ROOT . '/App/Services/*.php'); +$forbiddenPatterns = [ + 'header\s*\(' => 'header()', + '\becho\b' => 'echo', + '\$_GET' => '$_GET', + '\$_POST' => '$_POST', + '\$_SESSION' => '$_SESSION', +]; +foreach ($serviceFiles as $sf) { + $sfContent = file_get_contents($sf); + $sfName = basename($sf); + $sfClean = true; + foreach ($forbiddenPatterns as $pattern => $label) { + if (preg_match('/' . $pattern . '/', $sfContent)) { + test_fail("Service purity: {$sfName}", "Contains forbidden pattern: {$label}"); + $sfClean = false; + } + } + if ($sfClean) { + test_pass("Service purity: {$sfName} is clean (no HTTP operations)"); + } +} + +// ============================================================ +// 4. Controller → Service 调用链验证 +// ============================================================ +echo "=== 4. Controller-Service Call Chain ===\n"; + +// TransactionController must call TransactionService +$tcFile = $ROOT . '/App/Controllers/Web/TransactionController.php'; +if (file_exists($tcFile)) { + $tcContent = file_get_contents($tcFile); + // fundRequest calls TransactionService + if (preg_match('/TransactionService/', $tcContent) && preg_match('/function\s+fundRequest/', $tcContent)) { + test_pass("TransactionController::fundRequest calls TransactionService"); + } else { + test_fail("TransactionController", "fundRequest does not call TransactionService"); + } + // transfer calls TransactionService + if (preg_match('/TransactionService/', $tcContent) && preg_match('/function\s+transfer/', $tcContent)) { + test_pass("TransactionController::transfer calls TransactionService"); + } else { + test_fail("TransactionController", "transfer does not call TransactionService"); + } +} else { + test_fail("TransactionController", "File not found"); +} + +// ReportWebController must call ReportService +$rwcFile = $ROOT . '/App/Controllers/Web/ReportWebController.php'; +if (file_exists($rwcFile)) { + $rwcContent = file_get_contents($rwcFile); + if (preg_match('/ReportService/', $rwcContent) && preg_match('/function\s+userReportApi/', $rwcContent)) { + test_pass("ReportWebController::userReportApi calls ReportService"); + } else { + test_fail("ReportWebController", "userReportApi does not call ReportService"); + } +} else { + test_fail("ReportWebController", "File not found"); +} + +// Admin\ReportController must call ReportService +$arcFile = $ROOT . '/App/Controllers/Admin/ReportController.php'; +if (file_exists($arcFile)) { + $arcContent = file_get_contents($arcFile); + $arcOk = true; + if (!preg_match('/ReportService/', $arcContent)) { + test_fail("Admin\\ReportController", "Does not reference ReportService"); + $arcOk = false; + } + if (preg_match('/function\s+index/', $arcContent) && preg_match('/ReportService/', $arcContent)) { + test_pass("Admin\\ReportController::index calls ReportService"); + } else if ($arcOk) { + test_fail("Admin\\ReportController", "index does not call ReportService"); + } + if (preg_match('/function\s+export/', $arcContent) && preg_match('/ReportService/', $arcContent)) { + test_pass("Admin\\ReportController::export calls ReportService"); + } else if ($arcOk) { + test_fail("Admin\\ReportController", "export does not call ReportService"); + } +} else { + test_fail("Admin\\ReportController", "File not found"); +} + +// HomeController must NOT contain old methods +$hcFile = $ROOT . '/App/Controllers/Web/HomeController.php'; +if (file_exists($hcFile)) { + $hcContent = file_get_contents($hcFile); + $removedMethods = ['fundRequest', 'transfer', 'report', 'followPlan', 'userReportApi']; + $hcClean = true; + foreach ($removedMethods as $rm) { + if (preg_match('/function\s+' . $rm . '\s*\(/', $hcContent)) { + test_fail("HomeController cleanup", "Still contains method {$rm}()"); + $hcClean = false; + } + } + if ($hcClean) { + test_pass("HomeController no longer contains removed methods (fundRequest/transfer/report/followPlan/userReportApi)"); + } +} else { + test_fail("HomeController", "File not found"); +} + +// ============================================================ +// 5. JS 文件验证 +// ============================================================ +echo "=== 5. JS File Validation ===\n"; + +$jsFiles = ['pk10-sound.js', 'pk10-race.js', 'pk10-bet.js', 'pk10-poll.js']; +foreach ($jsFiles as $jsf) { + $jsPath = $ROOT . '/Static/js/' . $jsf; + if (!file_exists($jsPath)) { + test_fail("JS file exists: {$jsf}", "File not found"); + continue; + } + $jsSize = filesize($jsPath); + if ($jsSize === 0) { + test_fail("JS file non-empty: {$jsf}", "File is empty"); + continue; + } + test_pass("JS file exists and non-empty: {$jsf} ({$jsSize} bytes)"); + + // No PHP tags + $jsContent = file_get_contents($jsPath); + if (preg_match('/<\?php|<\?=/', $jsContent)) { + test_fail("JS no PHP tags: {$jsf}", "Contains PHP tags"); + } else { + test_pass("JS no PHP tags: {$jsf}"); + } +} + +// pk10.php checks +$pk10File = $ROOT . '/App/Views/Web/pk10.php'; +if (file_exists($pk10File)) { + $pk10Content = file_get_contents($pk10File); + + // window.PK10_CONFIG + if (strpos($pk10Content, 'PK10_CONFIG') !== false) { + test_pass("pk10.php contains PK10_CONFIG configuration block"); + } else { + test_fail("pk10.php PK10_CONFIG", "Missing window.PK10_CONFIG block"); + } + + // 4 script src references + $scriptCount = 0; + foreach ($jsFiles as $jsf) { + if (strpos($pk10Content, $jsf) !== false) { + $scriptCount++; + } + } + if ($scriptCount === 4) { + test_pass("pk10.php includes all 4 pk10-*.js script references"); + } else { + test_fail("pk10.php script references", "Found {$scriptCount}/4 JS file references"); + } + + // JS 引用顺序: sound → race → bet → poll + $positions = []; + foreach ($jsFiles as $jsf) { + $pos = strpos($pk10Content, $jsf); + if ($pos !== false) { + $positions[$jsf] = $pos; + } + } + if (count($positions) === 4) { + $ordered = array_keys($positions); + usort($ordered, function($a, $b) use ($positions) { + return $positions[$a] - $positions[$b]; + }); + $expected = ['pk10-sound.js', 'pk10-race.js', 'pk10-bet.js', 'pk10-poll.js']; + if ($ordered === $expected) { + test_pass("pk10.php JS include order correct: sound -> race -> bet -> poll"); + } else { + test_fail("pk10.php JS order", "Expected: " . implode(' -> ', $expected) . " Got: " . implode(' -> ', $ordered)); + } + } +} else { + test_fail("pk10.php", "File not found"); +} + +// ============================================================ +// 6. JS 全局变量/函数完整性 +// ============================================================ +echo "=== 6. JS Global Function Integrity ===\n"; + +$jsSoundContent = file_exists($ROOT . '/Static/js/pk10-sound.js') ? file_get_contents($ROOT . '/Static/js/pk10-sound.js') : ''; +$jsRaceContent = file_exists($ROOT . '/Static/js/pk10-race.js') ? file_get_contents($ROOT . '/Static/js/pk10-race.js') : ''; +$jsBetContent = file_exists($ROOT . '/Static/js/pk10-bet.js') ? file_get_contents($ROOT . '/Static/js/pk10-bet.js') : ''; +$jsPollContent = file_exists($ROOT . '/Static/js/pk10-poll.js') ? file_get_contents($ROOT . '/Static/js/pk10-poll.js') : ''; + +// SoundFX in pk10-sound.js +if (preg_match('/\bSoundFX\b/', $jsSoundContent)) { + test_pass("SoundFX defined in pk10-sound.js"); +} else { + test_fail("JS global: SoundFX", "Not found in pk10-sound.js"); +} + +// Race functions in pk10-race.js +$raceFuncs = ['animateRace', 'startIdleAnimation', 'resetRace', 'updateSceneHeader']; +foreach ($raceFuncs as $rf) { + if (preg_match('/\b' . $rf . '\b/', $jsRaceContent)) { + test_pass("{$rf} defined in pk10-race.js"); + } else { + test_fail("JS global: {$rf}", "Not found in pk10-race.js"); + } +} + +// Bet functions in pk10-bet.js +$betFuncs = ['renderMyBets', 'renderLastMyBets', 'formatBetLabel']; +foreach ($betFuncs as $bf) { + if (preg_match('/\b' . $bf . '\b/', $jsBetContent)) { + test_pass("{$bf} defined in pk10-bet.js"); + } else { + test_fail("JS global: {$bf}", "Not found in pk10-bet.js"); + } +} + +// pollPeriod in pk10-poll.js +if (preg_match('/\bpollPeriod\b/', $jsPollContent)) { + test_pass("pollPeriod defined in pk10-poll.js"); +} else { + test_fail("JS global: pollPeriod", "Not found in pk10-poll.js"); +} + +// ============================================================ +// 7. View 文件验证 +// ============================================================ +echo "=== 7. View File Validation ===\n"; + +// report.php +$reportView = $ROOT . '/App/Views/Web/report.php'; +if (file_exists($reportView)) { + $rvContent = file_get_contents($reportView); + test_pass("report.php exists"); + if (strpos($rvContent, '/api/user-report') !== false) { + test_pass("report.php contains /api/user-report fetch call"); + } else { + test_fail("report.php content", "Missing /api/user-report fetch call"); + } +} else { + test_fail("report.php", "File not found"); +} + +// follow_plan.php +$fpView = $ROOT . '/App/Views/Web/follow_plan.php'; +if (file_exists($fpView)) { + $fpContent = file_get_contents($fpView); + test_pass("follow_plan.php exists"); + if (strpos($fpContent, 'PK10_CONFIG') !== false || strpos($fpContent, 'pk10') !== false || strpos($fpContent, 'follow') !== false) { + test_pass("follow_plan.php contains expected content"); + } else { + test_fail("follow_plan.php content", "Missing expected PK10_CONFIG or related content"); + } +} else { + test_fail("follow_plan.php", "File not found"); +} + +// profile.php +$profileView = $ROOT . '/App/Views/Web/profile.php'; +if (file_exists($profileView)) { + $pvContent = file_get_contents($profileView); + $profileOk = true; + if (strpos($pvContent, '/report') !== false) { + test_pass("profile.php contains /report link"); + } else { + test_fail("profile.php link", "Missing /report link"); + $profileOk = false; + } + if (strpos($pvContent, '/follow-plan') !== false || strpos($pvContent, 'follow_plan') !== false || strpos($pvContent, 'follow-plan') !== false) { + test_pass("profile.php contains /follow-plan link"); + } else { + test_fail("profile.php link", "Missing /follow-plan link"); + } +} else { + test_fail("profile.php", "File not found"); +} + +// pk10.php localLockCountdown +if (file_exists($pk10File)) { + $pk10Content = $pk10Content ?? file_get_contents($pk10File); + if (strpos($pk10Content, 'localLockCountdown') !== false) { + test_pass("pk10.php contains localLockCountdown in statusBadge code"); + } else { + test_fail("pk10.php localLockCountdown", "Missing localLockCountdown reference"); + } +} else { + test_fail("pk10.php", "File not found"); +} + +// ============================================================ +// 8. i18n 验证 +// ============================================================ +echo "=== 8. i18n Validation ===\n"; + +$zhFile = $ROOT . '/Lang/zh.php'; +$enFile = $ROOT . '/Lang/en.php'; + +if (!file_exists($zhFile) || !file_exists($enFile)) { + test_fail("i18n files", "zh.php or en.php not found"); +} else { + $zhLang = include $zhFile; + $enLang = include $enFile; + + $requiredKeys = ['report_query', 'follow_plan', 'today', 'yesterday']; + foreach ($requiredKeys as $rk) { + if (isset($zhLang[$rk])) { + test_pass("zh.php has key '{$rk}'"); + } else { + test_fail("zh.php i18n", "Missing key '{$rk}'"); + } + if (isset($enLang[$rk])) { + test_pass("en.php has key '{$rk}'"); + } else { + test_fail("en.php i18n", "Missing key '{$rk}'"); + } + } + + // Key count match + $zhCount = count($zhLang); + $enCount = count($enLang); + if ($zhCount === $enCount) { + test_pass("i18n key count match: zh={$zhCount}, en={$enCount}"); + } else { + $diff = abs($zhCount - $enCount); + // Find missing keys + $missingInEn = array_diff(array_keys($zhLang), array_keys($enLang)); + $missingInZh = array_diff(array_keys($enLang), array_keys($zhLang)); + $detail = "zh={$zhCount}, en={$enCount} (diff={$diff})"; + if (!empty($missingInEn)) { + $detail .= " | Missing in en: " . implode(', ', array_slice($missingInEn, 0, 10)); + } + if (!empty($missingInZh)) { + $detail .= " | Missing in zh: " . implode(', ', array_slice($missingInZh, 0, 10)); + } + test_fail("i18n key count mismatch", $detail); + } +} + +// ============================================================ +// 9. 路由无死链验证 +// ============================================================ +echo "=== 9. Route Dead Link Check ===\n"; + +$deadLinks = 0; +foreach ($routes as $route) { + $action = $route[2]; + if (strpos($action, '@') === false) continue; + list($controllerClass, $methodName) = explode('@', $action); + $controllerFile = $ROOT . '/' . str_replace('\\', '/', ltrim($controllerClass, '\\')) . '.php'; + + if (!file_exists($controllerFile)) { + // Already reported in section 2 + $deadLinks++; + continue; + } + + $content = file_get_contents($controllerFile); + if (!preg_match('/function\s+' . preg_quote($methodName) . '\s*\(/', $content)) { + // Already reported in section 2 + $deadLinks++; + } +} + +if ($deadLinks === 0) { + test_pass("No dead route links: all " . count($routes) . " routes point to existing methods"); +} else { + test_fail("Dead route links", "{$deadLinks} routes point to non-existent controllers/methods (see section 2 for details)"); +} + +// ============================================================ +// 汇总输出 +// ============================================================ +echo "\n" . str_repeat('=', 60) . "\n"; +echo "TEST RESULTS\n"; +echo str_repeat('=', 60) . "\n\n"; + +foreach ($results as $r) { + echo $r . "\n"; +} + +$total = $pass + $fail; +echo "\n" . str_repeat('-', 60) . "\n"; +echo "Total: {$total} tests | Pass: {$pass} | Fail: {$fail}\n"; +echo str_repeat('-', 60) . "\n"; + +if ($fail > 0) { + echo "\n*** {$fail} FAILURE(S) DETECTED ***\n"; + exit(1); +} else { + echo "\n*** ALL TESTS PASSED ***\n"; + exit(0); +}