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; } }