const express = require('express'); const http = require('http'); const path = require('path'); const os = require('os'); // Added for system stats require('dotenv').config({ path: path.join(__dirname, '.env') }); require('dotenv').config({ path: path.join(__dirname, '..', '.env') }); const cors = require('cors'); const bodyParser = require('body-parser'); const { Server } = require('socket.io'); const { FarmBot } = require('./src/core/FarmBot'); const { loadProto, types } = require('./src/proto'); const { log, toLong, toNum } = require('./src/utils'); const { getLevelExpProgress, getPlantNameBySeedId, getFruitName, getItemNameById } = require('./src/gameConfig'); const userManager = require('./src/userManager'); const { MiniProgramLoginSession } = require('./src/qrlib/session'); // OAuth Dependencies const session = require('express-session'); const passport = require('passport'); const OAuth2Strategy = require('passport-oauth2'); // 初始化 Express App const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { origin: "*", // 开发阶段允许跨域,生产环境应限制 methods: ["GET", "POST"] } }); const oauthEnabled = Boolean(process.env.OAUTH_AUTHENTIK_ISSUER) && Boolean(process.env.OAUTH_AUTHENTIK_CLIENT_ID) && Boolean(process.env.OAUTH_AUTHENTIK_CLIENT_SECRET) && Boolean(process.env.OAUTH_AUTHENTIK_REDIRECT_URI) && Boolean(process.env.OAUTH_AUTHENTIK_SCOPE); // OAuth Setup if (oauthEnabled) { app.use(session({ secret: 'farm-session-secret', resave: false, saveUninitialized: false })); app.use(passport.initialize()); app.use(passport.session()); passport.serializeUser((user, done) => done(null, user.email)); passport.deserializeUser((email, done) => { const user = userManager.getUser(email); done(null, user); }); passport.use('authentik', new OAuth2Strategy({ authorizationURL: `${process.env.OAUTH_AUTHENTIK_ISSUER}authorize/`, tokenURL: `${process.env.OAUTH_AUTHENTIK_ISSUER}token/`, clientID: process.env.OAUTH_AUTHENTIK_CLIENT_ID, clientSecret: process.env.OAUTH_AUTHENTIK_CLIENT_SECRET, callbackURL: process.env.OAUTH_AUTHENTIK_REDIRECT_URI, scope: process.env.OAUTH_AUTHENTIK_SCOPE.split(' ') }, async (accessToken, refreshToken, profile, cb) => { try { // Fetch user info using accessToken const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); const userInfoRes = await fetch(`${process.env.OAUTH_AUTHENTIK_ISSUER}userinfo/`, { headers: { 'Authorization': `Bearer ${accessToken}` } }); const userInfo = await userInfoRes.json(); const email = userInfo.email || userInfo.preferred_username; // Adjust based on Authentik response // Register or Login const user = await userManager.register(email, null, 'authentik', userInfo.sub); return cb(null, user); } catch (err) { return cb(err); } })); // OAuth Routes app.get('/api/auth/oauth/authentik', passport.authenticate('authentik', { state: true })); app.get('/api/auth/oauth/authentik/callback', (req, res, next) => { passport.authenticate('authentik', { state: true }, (err, user, info) => { const frontendUrl = process.env.NODE_ENV === 'development' ? 'http://localhost:5173' : ''; if (err) { console.error('OAuth Error:', err); return res.redirect(`${frontendUrl}/login?error=oauth_failed`); } if (!user) { console.error('OAuth Failed: No user returned'); return res.redirect(`${frontendUrl}/login?error=oauth_failed`); } req.logIn(user, (loginErr) => { if (loginErr) { console.error('Login Error:', loginErr); return res.redirect(`${frontendUrl}/login?error=login_err`); } res.redirect(`${frontendUrl}/login?email=${encodeURIComponent(user.email)}&oauth=success&_t=${Date.now()}`); }); })(req, res, next); }); } // 中间件 app.use(cors()); app.use(bodyParser.json()); if (process.env.NODE_ENV === 'production') { app.use(express.static(path.join(__dirname, '../web/dist'))); } app.get('/api/auth/oauth/status', (req, res) => { res.json({ enabled: oauthEnabled }); }); // 存储活跃的 Bot 实例: Map const activeBots = new Map(); const unluckyBoards = new Map(); userManager.loadUnluckyBoards().then((stored) => { if (!stored) return; for (const [email, board] of stored.entries()) { unluckyBoards.set(email, board); } }).catch((err) => { log('Leaderboard', `加载失败: ${err.message}`); }); function normalizeSettings(settings) { if (!settings || typeof settings !== 'object') return {}; const result = {}; const farmIntervalSec = Number(settings.farmIntervalSec); if (Number.isFinite(farmIntervalSec) && farmIntervalSec >= 1) { result.farmCheckInterval = Math.floor(farmIntervalSec * 1000); } const friendIntervalSec = Number(settings.friendIntervalSec); if (Number.isFinite(friendIntervalSec) && friendIntervalSec >= 1) { result.friendCheckInterval = Math.floor(friendIntervalSec * 1000); } if (settings.seedStrategy === 'forceLowest') { result.forceLowestLevelCrop = true; } else if (settings.seedStrategy === 'default') { result.forceLowestLevelCrop = false; } if (typeof settings.enableFriendOps === 'boolean') { result.enableFriendOps = settings.enableFriendOps; } if (typeof settings.enableSteal === 'boolean') { result.enableSteal = settings.enableSteal; } if (typeof settings.enableNormalFertilize === 'boolean') { result.enableNormalFertilize = settings.enableNormalFertilize; } if (typeof settings.enableOrganicFertilize === 'boolean') { result.enableOrganicFertilize = settings.enableOrganicFertilize; } if (typeof settings.allowTicketFertilizerPurchase === 'boolean') { result.allowTicketFertilizerPurchase = settings.allowTicketFertilizerPurchase; } if (typeof settings.enableAutoSell === 'boolean') { result.enableAutoSell = settings.enableAutoSell; } if (typeof settings.allowBuySeeds === 'boolean') { result.allowBuySeeds = settings.allowBuySeeds; } if (typeof settings.allowRemove === 'boolean') { result.allowRemove = settings.allowRemove; } if (settings.idleStrategy === 'task') { result.idleStrategy = 'task'; } else if (settings.idleStrategy === 'exp') { result.idleStrategy = 'exp'; } return result; } function getTodayKey() { const now = new Date(); const y = now.getFullYear(); const m = `${now.getMonth() + 1}`.padStart(2, '0'); const d = `${now.getDate()}`.padStart(2, '0'); return `${y}-${m}-${d}`; } async function fetchTaskInfo(bot) { const body = types.TaskInfoRequest.encode(types.TaskInfoRequest.create({})).finish(); const { body: replyBody } = await bot.network.sendMsgAsync('gamepb.taskpb.TaskService', 'TaskInfo', body); const reply = types.TaskInfoReply.decode(replyBody); return reply.task_info || null; } function buildTaskList(taskInfo) { const tasks = []; if (!taskInfo) return tasks; const seen = new Set(); const seenContent = new Set(); const addTasks = (list, category) => { if (!list) return; for (const task of list) { const id = toNum(task.id); if (seen.has(id)) continue; const progress = toNum(task.progress); const totalProgress = toNum(task.total_progress); const isClaimed = Boolean(task.is_claimed); const isUnlocked = Boolean(task.is_unlocked); const shareMultiple = toNum(task.share_multiple); const rewards = []; const rewardList = task.rewards || []; for (const reward of rewardList) { rewards.push({ id: toNum(reward.id), count: toNum(reward.count) }); } const desc = task.desc || `任务#${id}`; const contentKey = `${desc.trim()}|${progress}|${totalProgress}`; if (seenContent.has(contentKey)) continue; tasks.push({ id, desc, progress, totalProgress, isClaimed, isUnlocked, shareMultiple, rewards, category }); seen.add(id); seenContent.add(contentKey); } }; addTasks(taskInfo.growth_tasks, 'growth'); addTasks(taskInfo.daily_tasks, 'daily'); addTasks(taskInfo.tasks, 'normal'); return tasks; } function isTaskClaimable(task) { if (!task.isUnlocked || task.isClaimed) return false; if (task.totalProgress <= 0) return false; return task.progress >= task.totalProgress; } async function claimTaskReward(bot, taskId, doShared) { const body = types.ClaimTaskRewardRequest.encode(types.ClaimTaskRewardRequest.create({ id: toLong(taskId), do_shared: Boolean(doShared) })).finish(); const { body: replyBody } = await bot.network.sendMsgAsync('gamepb.taskpb.TaskService', 'ClaimTaskReward', body); return types.ClaimTaskRewardReply.decode(replyBody); } function updateUnluckyBoard(email, name, count) { if (!email || !name || !Number.isFinite(count) || count <= 0) return; let board = unluckyBoards.get(email); if (!board) { board = new Map(); unluckyBoards.set(email, board); } const prev = board.get(name) || 0; board.set(name, prev + count); userManager.saveUnlucky(email, name, count).catch((err) => { log('Leaderboard', `保存失败: ${err.message}`); }); } // 初始化 Proto loadProto().then(() => { log('系统', '协议定义加载完成。'); }).catch(err => { console.error('Failed to load Proto:', err); process.exit(1); }); // ============ API 路由 ============ // QR Code Login API (Mini Program) app.post('/api/qr/create', async (req, res) => { try { log('扫码', '正在获取登录二维码...'); const result = await MiniProgramLoginSession.requestLoginCode(); log('扫码', '已获取登录二维码'); res.json({ success: true, qrsig: result.code, qrcode: `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(result.url)}`, url: result.url }); } catch (error) { console.error('QR Create Error:', error); res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/qr/check', async (req, res) => { const { qrsig } = req.body; if (!qrsig) { return res.status(400).json({ success: false, message: 'Missing qrsig/code' }); } try { const result = await MiniProgramLoginSession.queryStatus(qrsig); if (result.status === 'Wait') { res.json({ success: true, status: 'Wait', msg: '等待扫码...' }); } else if (result.status === 'Used') { res.json({ success: true, status: 'Used', msg: '二维码已失效' }); } else if (result.status === 'OK') { const ticket = result.ticket; // Determine AppID (Farm AppID) const appid = MiniProgramLoginSession.Presets.farm.appid; // QQ Classic Farm const code = await MiniProgramLoginSession.getAuthCode(ticket, appid); res.json({ success: true, status: 'OK', msg: '登录成功', code: code // This is the Game Code we need }); } else { res.json({ success: true, status: 'Error', msg: '状态查询错误' }); } } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // 注册 API app.post('/api/auth/register', async (req, res) => { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ error: '请输入邮箱和密码' }); } try { if (userManager.getUser(email)) { return res.status(400).json({ error: '该邮箱已被注册' }); } const user = userManager.register(email, password); log('Auth', `New user registered: ${email}`); res.json({ success: true, user: { email: user.email, hasCode: !!user.code } }); } catch (e) { res.status(500).json({ error: e.message }); } }); // 登录 API (仅登录) app.post('/api/auth/login', async (req, res) => { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ error: '请输入邮箱和密码' }); } try { const user = userManager.login(email, password); if (!user) { return res.status(401).json({ error: '邮箱或密码错误' }); } // 返回用户信息和 Bot 状态 const botRunning = activeBots.has(email); res.json({ success: true, user: { email: user.email, code: user.code, hasCode: !!user.code }, botRunning }); } catch (e) { res.status(500).json({ error: e.message }); } }); app.get('/api/auth/profile', async (req, res) => { const { email } = req.query; if (!email) { return res.status(400).json({ error: 'Email is required' }); } const user = userManager.getUser(email); if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json({ user: { email: user.email, hasCode: !!user.code, auth_provider: user.auth_provider || 'local' } }); }); // 好友 API app.get('/api/friends', async (req, res) => { const { email } = req.query; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); if (bot.config && bot.config.enableFriendOps === false) { return res.status(403).json({ error: 'Friend operations disabled' }); } try { const reply = await bot.friendManager.getAllFriends(); // 处理好友列表 const friends = (reply.friends || []).map(f => ({ uin: f.uin ? f.uin.toString() : '', userName: f.userName, headPic: f.headPic, yellowLevel: f.yellowLevel, exp: f.exp, money: f.money })); res.json({ friends }); } catch (e) { res.status(500).json({ error: e.message }); } }); // 仓库出售 API app.post('/api/warehouse/sell', async (req, res) => { const { email, items } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } if (!items || !Array.isArray(items) || items.length === 0) { return res.status(400).json({ error: 'No items to sell' }); } const bot = activeBots.get(email); try { const reply = await bot.warehouseManager.sellItems(items); const gold = bot.warehouseManager.extractGold(reply); // 更新本地金币缓存 if (bot.user) { bot.user.gold = (bot.user.gold || 0) + gold; } res.json({ success: true, gold }); } catch (e) { res.status(500).json({ error: e.message }); } }); // 访问好友农场 API app.post('/api/friends/visit', async (req, res) => { const { email, friendUid } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); if (bot.config && bot.config.enableFriendOps === false) { return res.status(403).json({ error: 'Friend operations disabled' }); } try { const reply = await bot.friendManager.enterFriendFarm(friendUid); if (!reply.farm || !reply.farm.lands) { return res.json({ lands: [] }); } // 使用 FriendManager 的逻辑解析好友土地数据 const analysis = bot.friendManager.analyzeFriendLands(lands, bot.user.gid); const formattedLands = lands.map(land => { const id = Number(land.id); if (!land.unlocked) return { id, type: 'locked' }; const plant = land.plant; if (!plant || !plant.phases || plant.phases.length === 0) { return { id, type: 'empty' }; } const currentPhase = bot.farmManager.getCurrentPhase(plant.phases, false); const phaseVal = currentPhase ? currentPhase.phase : 0; // 6=Mature, 7=Dead let status = 'growing'; if (phaseVal === 7) status = 'dead'; else if (phaseVal === 6) status = 'mature'; return { id, type: 'planted', status, plantId: Number(plant.id), plantName: getPlantNameBySeedId(Number(plant.id)) || `作物${plant.id}`, phase: phaseVal, needs: { water: analysis.needWater.includes(id), weed: analysis.needWeed.includes(id), bug: analysis.needBug.includes(id) }, canSteal: analysis.stealable.includes(id) // 使用 analyzeFriendLands 的 stealable 结果 }; }); res.json({ lands: formattedLands, farmUser: reply.farm.user }); } catch (e) { res.status(500).json({ error: e.message }); } }); // 好友互动 API (偷菜/浇水等) app.post('/api/friends/action', async (req, res) => { const { email, friendUid, actionType, landIds } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); if (bot.config && bot.config.enableFriendOps === false) { return res.status(403).json({ error: 'Friend operations disabled' }); } if (actionType === 'steal' && bot.config && bot.config.enableSteal === false) { return res.status(403).json({ error: 'Steal disabled' }); } try { let result; let message = ''; switch (actionType) { case 'water': result = await bot.friendManager.helpWater(friendUid, landIds); message = '浇水成功'; break; case 'weed': result = await bot.friendManager.helpWeed(friendUid, landIds); message = '除草成功'; break; case 'insecticide': result = await bot.friendManager.helpInsecticide(friendUid, landIds); message = '除虫成功'; break; case 'steal': result = await bot.friendManager.stealHarvest(friendUid, landIds); message = '偷菜成功'; break; default: throw new Error('Unknown action type'); } res.json({ success: true, message, result }); } catch (e) { res.status(500).json({ error: e.message }); } }); // 启动 Bot API app.post('/api/bot/start', async (req, res) => { const { email, code, settings } = req.body; // 如果提供了 code,更新并启动 if (!email) { return res.status(400).json({ error: 'Email is required' }); } const user = userManager.getUser(email); if (!user) { return res.status(404).json({ error: 'User not found' }); } // 如果提供了新 code,更新用户数据 let gameCode = code; if (gameCode) { userManager.updateCode(email, gameCode); } else { gameCode = user.code; } if (!gameCode) { return res.status(400).json({ error: 'Game code is required. Please provide it.' }); } try { // 如果已存在 Bot,先停止 if (activeBots.has(email)) { const oldBot = activeBots.get(email); oldBot.stop(); activeBots.delete(email); } const botConfig = { code: gameCode, ...normalizeSettings(settings) }; const bot = new FarmBot(botConfig); // 监听 Bot 事件并通过 Socket.IO 推送给前端 (使用 email 作为 room/key) bot.on('started', () => { io.emit(`bot-status-${email}`, { status: 'running' }); }); bot.on('stopped', () => { io.emit(`bot-status-${email}`, { status: 'stopped' }); activeBots.delete(email); }); bot.on('error', (err) => { io.emit(`bot-error-${email}`, { message: err.message }); userManager.saveBotLog(email, { tag: 'Error', msg: err.message, type: 'error', time: Date.now() }); }); bot.on('log', (logData) => { io.emit(`bot-log-${email}`, logData); userManager.saveBotLog(email, logData); }); bot.on('stealRecord', (data) => { const name = data && data.name; const count = Number(data && data.count); updateUnluckyBoard(email, name, count); }); // 启动 Bot await bot.start(); activeBots.set(email, bot); res.json({ success: true, message: 'Bot started successfully.' }); } catch (error) { console.error('Bot start failed:', error); res.status(500).json({ error: error.message }); } }); // 停止 Bot API app.post('/api/bot/stop', (req, res) => { const { email } = req.body; if (activeBots.has(email)) { const bot = activeBots.get(email); bot.stop(); activeBots.delete(email); res.json({ success: true }); } else { res.status(404).json({ error: 'Bot is not running' }); } }); // 获取状态 API app.get('/api/status', async (req, res) => { const { email } = req.query; if (!email || !activeBots.has(email)) { // 返回 stopped 状态而不是 404,以便前端正确显示 UI return res.json({ status: 'stopped', user: null }); } const bot = activeBots.get(email); const user = bot.user || {}; const todayKey = getTodayKey(); const freeMallClaimed = user.freeMallClaimDate === todayKey; // Calculate next level exp const levelProgress = getLevelExpProgress(user.level || 0, user.exp || 0); const tickets = user.tickets || 0; const fertilizerContainer = Number(user.fertilizerContainer) || 0; const fertilizerItems = user.fertilizerItems || {}; const fallbackHours = fertilizerContainer >= 3600 ? fertilizerContainer / 3600 : fertilizerContainer; const organicFertilizerContainer = Number(user.organicFertilizerContainer) || 0; const organicFertilizerItems = user.organicFertilizerItems || {}; const fallbackOrganicHours = organicFertilizerContainer >= 3600 ? organicFertilizerContainer / 3600 : organicFertilizerContainer; let fertilizerHours = Number.isFinite(user.fertilizerHours) ? user.fertilizerHours : fallbackHours; let organicFertilizerHours = Number.isFinite(user.organicFertilizerHours) ? user.organicFertilizerHours : fallbackOrganicHours; if (bot.warehouseManager) { try { const bagReply = await bot.warehouseManager.getBag(); const items = bot.warehouseManager.getBagItems(bagReply); let normalContainer = 0; let organicContainer = 0; for (const item of items) { const id = toNum(item.id); const count = toNum(item.count); if (id === 1011) normalContainer = count; if (id === 1012) organicContainer = count; } const normalContainerHours = normalContainer >= 3600 ? normalContainer / 3600 : normalContainer; const organicContainerHours = organicContainer >= 3600 ? organicContainer / 3600 : organicContainer; const normalHours = normalContainerHours; const organicHours = organicContainerHours; fertilizerHours = normalHours; user.normalFertilizerHours = normalHours; user.organicFertilizerHours = organicHours; organicFertilizerHours = organicHours; } catch (error) { log('状态', `获取背包失败: ${error.message}`); } } res.json({ status: 'running', user: { nickname: user.name || 'Farmer', level: user.level || 0, exp: levelProgress.current || 0, nextLevelExp: levelProgress.needed || 100, gold: user.gold || 0, tickets, fertilizerHours, normalFertilizerHours: Number.isFinite(user.normalFertilizerHours) ? user.normalFertilizerHours : fertilizerHours, organicFertilizerHours, avatarUrl: user.avatarUrl || '', freeMallClaimed } }); }); app.get('/api/announcement', (req, res) => { const message = typeof process.env.LOGIN_ANNOUNCEMENT === 'string' ? process.env.LOGIN_ANNOUNCEMENT : ''; res.json({ message }); }); app.get('/api/logs', async (req, res) => { const { email, limit } = req.query; if (!email) { return res.status(400).json({ error: 'Email is required' }); } const count = Number(limit); try { const logs = await userManager.getBotLogs(email, count); res.json({ logs }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/debug/fertilizer', async (req, res) => { const { email } = req.query; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); try { const bagReply = await bot.warehouseManager.getBag(); const items = bot.warehouseManager.getBagItems(bagReply); let normalContainer = 0; let normal1 = 0; let normal4 = 0; let normal8 = 0; let normal12 = 0; let organicContainer = 0; let organic1 = 0; let organic4 = 0; let organic8 = 0; let organic12 = 0; for (const item of items) { const id = toNum(item.id); const count = toNum(item.count); if (id === 1011) normalContainer = count; if (id === 80001) normal1 = count; if (id === 80002) normal4 = count; if (id === 80003) normal8 = count; if (id === 80004) normal12 = count; if (id === 1012) organicContainer = count; if (id === 80011) organic1 = count; if (id === 80012) organic4 = count; if (id === 80013) organic8 = count; if (id === 80014) organic12 = count; } const normalContainerHours = normalContainer >= 3600 ? normalContainer / 3600 : normalContainer; const organicContainerHours = organicContainer >= 3600 ? organicContainer / 3600 : organicContainer; const normalHours = normalContainerHours + normal1 + normal4 * 4 + normal8 * 8 + normal12 * 12; const organicHours = organicContainerHours + organic1 + organic4 * 4 + organic8 * 8 + organic12 * 12; res.json({ normal: { container: normalContainer, hour1: normal1, hour4: normal4, hour8: normal8, hour12: normal12, hours: normalHours }, organic: { container: organicContainer, hour1: organic1, hour4: organic4, hour8: organic8, hour12: organic12, hours: organicHours } }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/leaderboard/unlucky', (req, res) => { const { email } = req.query; if (!email) { return res.status(400).json({ error: 'Email is required' }); } if (activeBots.has(email)) { const bot = activeBots.get(email); if (bot.config && bot.config.enableFriendOps === false) { return res.json({ items: [] }); } if (bot.config && bot.config.enableSteal === false) { return res.json({ items: [] }); } } const board = unluckyBoards.get(email); if (!board) { return res.json({ items: [] }); } const items = Array.from(board.entries()) .map(([name, count]) => ({ name, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); res.json({ items }); }); app.get('/api/tasks', async (req, res) => { const { email } = req.query; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); try { const taskInfo = await fetchTaskInfo(bot); const tasks = buildTaskList(taskInfo); res.json({ tasks }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/tasks/claim', async (req, res) => { const { email } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); try { const taskInfo = await fetchTaskInfo(bot); const tasks = buildTaskList(taskInfo); const claimable = []; for (const task of tasks) { if (isTaskClaimable(task)) { claimable.push(task); } } let claimedCount = 0; const failed = []; for (const task of claimable) { try { await claimTaskReward(bot, task.id, task.shareMultiple > 1); claimedCount += 1; } catch (err) { failed.push({ id: task.id, message: err.message }); } } const latestTaskInfo = await fetchTaskInfo(bot); const latestTasks = buildTaskList(latestTaskInfo); res.json({ claimedCount, failed, tasks: latestTasks }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/tasks/claim-one', async (req, res) => { const { email, taskId, doShared } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const id = Number(taskId); if (!Number.isFinite(id) || id <= 0) { return res.status(400).json({ error: 'Invalid taskId' }); } const bot = activeBots.get(email); try { await claimTaskReward(bot, id, Boolean(doShared)); const latestTaskInfo = await fetchTaskInfo(bot); const latestTasks = buildTaskList(latestTaskInfo); res.json({ claimedCount: 1, failedCount: 0, tasks: latestTasks }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Get lands API app.get('/api/lands', async (req, res) => { const { email } = req.query; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); try { const lands = await bot.farmManager.getFormattedLands(); res.json({ lands }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/lands/remove', async (req, res) => { const { email, landIds } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } if (!Array.isArray(landIds)) { return res.status(400).json({ error: 'Invalid landIds' }); } const ids = landIds.map(Number).filter(id => Number.isFinite(id) && id > 0); if (ids.length === 0) { return res.status(400).json({ error: 'Invalid landIds' }); } const bot = activeBots.get(email); try { await bot.farmManager.removePlant(ids); res.json({ success: true, removed: ids.length }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/lands/unlock', async (req, res) => { const { email, landId, doShared } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const id = Number(landId); if (!Number.isFinite(id) || id <= 0) { return res.status(400).json({ error: 'Invalid landId' }); } const bot = activeBots.get(email); try { await bot.farmManager.unlockLand(id, Boolean(doShared)); res.json({ success: true, landId: id }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Shop API app.get('/api/shop', async (req, res) => { const { email } = req.query; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); try { const goods = await bot.shopManager.getSeedShopList(); res.json({ goods }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/shop/buy', async (req, res) => { const { email, goodsId, count, price } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); try { await bot.shopManager.buyGoods(goodsId, count, price); res.json({ success: true }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/paymall/buy', async (req, res) => { const { email, itemId, count } = req.body; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const id = Number(itemId); if (!Number.isFinite(id) || id <= 0) { return res.status(400).json({ error: 'Invalid itemId' }); } const buyCount = Number(count ?? 1); if (!Number.isFinite(buyCount) || buyCount <= 0) { return res.status(400).json({ error: 'Invalid count' }); } const bot = activeBots.get(email); try { await bot.shopManager.purchaseMallItem(id, buyCount); if (id === 1001) { bot.user.freeMallClaimDate = getTodayKey(); bot.emit('userUpdate', bot.user); } res.json({ success: true }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Warehouse API app.get('/api/warehouse', async (req, res) => { const { email } = req.query; if (!email || !activeBots.has(email)) { return res.status(404).json({ error: 'Bot not running' }); } const bot = activeBots.get(email); try { const data = await bot.warehouseManager.getFormattedBag(); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/warehouse/sell', async (req, res) => { const { email, items } = req.body; const bot = activeBots.get(email); if (!bot) return res.status(404).json({ error: 'Bot not found' }); try { const reply = await bot.warehouseManager.sellItems(items); const gold = bot.warehouseManager.extractGold(reply); res.json({ success: true, gold }); } catch (e) { res.status(500).json({ error: e.message }); } }); app.post('/api/warehouse/use', async (req, res) => { const { email, items } = req.body; const bot = activeBots.get(email); if (!bot) return res.status(404).json({ error: 'Bot not found' }); try { const result = await bot.warehouseManager.useItems(items); res.json(result); } catch (e) { res.status(500).json({ error: e.message }); } }); // System stats API app.get('/api/system/stats', (req, res) => { const memory = process.memoryUsage(); // Convert to MB const memoryUsage = { rss: Math.round(memory.rss / 1024 / 1024), heapTotal: Math.round(memory.heapTotal / 1024 / 1024), heapUsed: Math.round(memory.heapUsed / 1024 / 1024), }; res.json({ memory: memoryUsage, uptime: Math.floor(process.uptime()), activeBots: activeBots.size }); }); // 所有其他请求返回前端 index.html (SPA 支持) app.get('*', (req, res) => { if (process.env.NODE_ENV === 'production') { res.sendFile(path.join(__dirname, '../web/dist/index.html')); } else { res.status(404).send('Not Found (Development Mode: Use Frontend Dev Server at port 5173)'); } }); // ============ Socket.IO ============ io.on('connection', (socket) => { log('Server', `Client connected: ${socket.id}`); socket.on('disconnect', () => { log('Server', `Client disconnected: ${socket.id}`); }); }); // 启动服务器 const PORT = process.env.PORT || 3000; server.listen(PORT, () => { log('Server', `Server running on http://localhost:${PORT}`); });