- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController - 新增 Service 层: TransactionService, ReportService, FollowPlanService - pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll) - 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan) - 后台新增跟单计划管理 + 用户输赢明细统计 - 封盘状态显示倒计时 (x:xx) - 音效仅在开奖弹窗打开时播放 - 路由按模块分组整理 - autoload 支持 App\Services 命名空间
966 lines
40 KiB
PHP
966 lines
40 KiB
PHP
<?php
|
|
namespace App\Controllers\Admin;
|
|
|
|
use App\Core\AdminBaseController;
|
|
use Db\Database;
|
|
|
|
class BotController extends AdminBaseController
|
|
{
|
|
private Database $db;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->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;
|
|
}
|
|
}
|