// ==UserScript== // @name ygmc_tool // @namespace http://your-namespace.com // @version 2026-06-05.1 // @description 阳光牧场自动刷新、操作与播种脚本 // @author 生猛的程序 // @match http://mfhya.yiyutx.top/* // @grant none // ==/UserScript== (function () { 'use strict'; const CYCLE_INTERVAL = 600000; // 每轮间隔 10 分钟 const CLICK_DELAY = 5000; // 按钮点击间隔 5 秒 const REFRESH_WAIT = 3000; // 刷新后等待数据加载 3 秒 const POPUP_WAIT = 2000; // 弹窗操作等待 2 秒 const SOW_WAIT = 3500; // 播种后等待 3.5 秒(需等API调用完成,避免showLoading未配对警告) const TAB_SWITCH_WAIT = 1000; // 弹窗标签切换等待 1 秒 const HARVEST_CHECK_INTERVAL = 30000; // 收获检查间隔 30 秒 // ── 自动登录配置 ── const LOGIN_ACCOUNT = '1115519420'; // 在此填入账号(被挤下线后10分钟自动登录) const LOGIN_PASSWORD = '123456a'; // 在此填入密码 const LOGIN_DELAY = 600000; // 登录延迟 10 分钟(毫秒) // ── 播种优先级配置 ── const SOW_PRIORITY = [ { mainTab: '作物', subTab: '未制标' }, { mainTab: '鲜花', subTab: '未制标' }, { mainTab: '珍稀', subTab: '未制标' }, { mainTab: '变异', subTab: '未制标' }, { mainTab: '作物', subTab: '已制标' }, { mainTab: '鲜花', subTab: '已制标' }, { mainTab: '珍稀', subTab: '已制标' }, { mainTab: '变异', subTab: '已制标' }, ]; // ── 安全获取元素文本 ── function safeText(el) { try { return (el && typeof el.textContent === 'string') ? el.textContent.trim() : ''; } catch (_) { return ''; } } // ── 在指定范围内按精确文本查找 .onOnclikCo 元素并点击 ── function clickButton(scope, text) { if (!scope) return false; try { const candidates = scope.querySelectorAll('.onOnclikCo'); for (const el of candidates) { if (safeText(el) === text) { el.click(); console.log(`[ygmc_tool] 已点击: ${text}`); return true; } } } catch (err) { console.warn(`[ygmc_tool] clickButton 异常(${text}):`, err.message); } console.warn(`[ygmc_tool] 未找到按钮: ${text}`); return false; } // ── 定位"一键操作"面板 ── // 优先匹配农场页面面板(data-v-84b169e8),后备通用匹配(畜牧场等页面) function getActionPanel() { try { // 方式1:通过data-v-84b169e8属性定位(农场页面) const views = document.querySelectorAll('uni-view[data-v-84b169e8]'); for (const view of views) { const firstSpan = view.querySelector(':scope > span'); if (firstSpan && safeText(firstSpan).includes('一键操作')) { return view; } } // 方式2:通用匹配(畜牧场等页面,data-v属性可能不同) const allViews = document.querySelectorAll('uni-view'); for (const view of allViews) { const firstSpan = view.querySelector(':scope > span'); if (firstSpan && safeText(firstSpan).includes('一键操作')) { return view; } } } catch (err) { console.warn('[ygmc_tool] getActionPanel 异常:', err.message); } return null; } // ── 定位导航栏中的"刷新"按钮 ── // 逐级查找:uni-flex.uni-row → 内含"农场"文本的行 → .onOnclikCo 文本为"刷新" function getNavBar() { try { const rows = document.querySelectorAll('uni-view.uni-flex.uni-row'); for (const row of rows) { if (row.textContent && row.textContent.includes('农场')) { return row; } } } catch (err) { console.warn('[ygmc_tool] getNavBar 异常:', err.message); } return null; } // ── 判断当前是否在农场主页 ── // 必须同时存在导航栏("农场"为激活态)和"一键操作"面板 function isFarmPage() { try { const navBar = getNavBar(); if (!navBar) return false; // "农场"标签无onOnclikCo类表示当前处于农场页面 const items = navBar.querySelectorAll('.flex-item'); for (const item of items) { if (safeText(item) === '农场') { if (item.classList.contains('onOnclikCo')) return false; break; } } return getActionPanel() !== null; } catch (_) { return false; } } // ── 判断当前是否在畜牧场页面 ── function isRanchPage() { try { const navBar = getNavBar(); if (!navBar) return false; const items = navBar.querySelectorAll('.flex-item'); for (const item of items) { if (safeText(item) === '畜牧场') { return !item.classList.contains('onOnclikCo'); } } return false; } catch (_) { return false; } } // ── 切换到畜牧场 ── async function switchToRanch() { try { const navBar = getNavBar(); if (!navBar) return false; if (clickButton(navBar, '畜牧场')) { await delay(REFRESH_WAIT); return isRanchPage(); } } catch (err) { console.warn('[ygmc_tool] switchToRanch 异常:', err.message); } return false; } // ── 切换回农场 ── async function switchToFarm() { try { const navBar = getNavBar(); if (!navBar) return false; if (clickButton(navBar, '农场')) { await delay(REFRESH_WAIT); return isFarmPage(); } } catch (err) { console.warn('[ygmc_tool] switchToFarm 异常:', err.message); } return false; } // ── 扫描坑位状态 ── // 返回 { needHarvest, needClear, needSow } 标识各操作是否需要 function scanPlotStatus() { let needHarvest = false; let needClear = false; let needSow = false; try { const allViews = document.querySelectorAll('uni-view[data-v-84b169e8]'); for (const view of allViews) { if (view.closest('.uni-popup-dialog') || view.closest('.uni-popup__wrapper')) continue; const firstSpan = view.querySelector(':scope > span'); if (!firstSpan || !safeText(firstSpan).match(/\[坑|\[温室/)) continue; if (safeText(view).includes('开垦')) continue; const spans = view.querySelectorAll('span.onOnclikCo'); for (const span of spans) { const text = safeText(span); if (text === '收获') needHarvest = true; else if (text === '铲地') needClear = true; else if (text === '播种') needSow = true; } } } catch (err) { console.warn('[ygmc_tool] scanPlotStatus 异常:', err.message); } return { needHarvest, needClear, needSow }; } // ── 快速响应(轻量扫描,发现需操作坑位立即执行对应链路) ── // 检测顺序:收获 → 铲地 → 播种,从前置步骤开始向下执行 let isRunningCycle = false; async function quickAction() { if (isRunningCycle) return; if (!isFarmPage()) return; if (getSowPopup()) return; const status = scanPlotStatus(); if (!status.needHarvest && !status.needClear && !status.needSow) return; isRunningCycle = true; try { const panel = getActionPanel(); if (!panel) return; // 1. 收获(如需要) if (status.needHarvest) { console.log('[ygmc_tool] 快速响应:发现可收获作物'); clickButton(panel, '收获'); await delay(CLICK_DELAY); if (!isFarmPage()) return; } // 收获后可能产生新的铲地需求,重新扫描 const statusAfterHarvest = status.needHarvest ? scanPlotStatus() : status; if (statusAfterHarvest.needClear) { console.log('[ygmc_tool] 快速响应:发现需铲地坑位'); clickButton(panel, '铲地'); await delay(CLICK_DELAY); if (!isFarmPage()) return; } // 铲地后可能产生新的播种需求,重新扫描 if (scanPlotStatus().needSow) { console.log('[ygmc_tool] 快速响应:发现空坑位,开始播种'); await autoSow(); } } finally { isRunningCycle = false; } } // ── 判断当前是否在登录页面 ── function isLoginPage() { try { const container = document.querySelector('uni-view.container[data-v-5fe319ae]'); if (!container) return false; const accountInput = container.querySelector('input[type="text"].uni-input-input'); const passwordInput = container.querySelector('input[type="password"].uni-input-input'); const loginBtn = container.querySelector('uni-button'); return !!(accountInput && passwordInput && loginBtn); } catch (_) { return false; } } // ── 自动登录 ── // 被挤下线后,检测到登录页面时延迟10分钟再自动登录 let loginScheduled = false; async function autoLogin() { if (loginScheduled) return; if (!isLoginPage()) return; if (!LOGIN_ACCOUNT || !LOGIN_PASSWORD) { console.warn('[ygmc_tool] 检测到登录页面,但未配置账号/密码,跳过自动登录'); return; } loginScheduled = true; console.log(`[ygmc_tool] 检测到登录页面,${LOGIN_DELAY / 1000}秒后自动登录...`); await delay(LOGIN_DELAY); // 等待期间可能已手动恢复 if (!isLoginPage()) { console.log('[ygmc_tool] 等待期间页面已恢复,取消自动登录'); loginScheduled = false; return; } try { const container = document.querySelector('uni-view.container[data-v-5fe319ae]'); const accountInput = container.querySelector('input[type="text"].uni-input-input'); const passwordInput = container.querySelector('input[type="password"].uni-input-input'); // 使用原生 setter 触发 Vue/uni-app 响应式绑定 const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; nativeSetter.call(accountInput, LOGIN_ACCOUNT); accountInput.dispatchEvent(new Event('input', { bubbles: true })); accountInput.dispatchEvent(new Event('change', { bubbles: true })); console.log('[ygmc_tool] 已填入账号'); await delay(500); nativeSetter.call(passwordInput, LOGIN_PASSWORD); passwordInput.dispatchEvent(new Event('input', { bubbles: true })); passwordInput.dispatchEvent(new Event('change', { bubbles: true })); console.log('[ygmc_tool] 已填入密码'); await delay(500); // 点击登录按钮 const loginBtn = container.querySelector('uni-button'); if (loginBtn) { loginBtn.click(); console.log('[ygmc_tool] 已点击登录按钮'); } } catch (err) { console.warn('[ygmc_tool] 自动登录异常:', err.message); } loginScheduled = false; } // ── 查找空坑位的"播种"按钮 ── // 逐级定位:uni-view[data-v-84b169e8] → 直接子span含[坑/温室]标识 → 含"未种植" → 排除"开垦"坑位 → 内部 span.onOnclikCo 文本为"播种" function getEmptyPlotSowButtons() { const buttons = []; try { const allViews = document.querySelectorAll('uni-view[data-v-84b169e8]'); for (const view of allViews) { // 排除弹窗内的元素 if (view.closest('.uni-popup-dialog') || view.closest('.uni-popup__wrapper')) continue; // 只匹配单个坑位行:直接子span含[坑或[温室标识,排除父级容器误匹配 const firstSpan = view.querySelector(':scope > span'); if (!firstSpan || !safeText(firstSpan).match(/\[坑|\[温室/)) continue; const viewText = safeText(view); // 排除"开垦"坑位(下一等级才能使用,不可种植) if (viewText.includes('开垦')) continue; // 必须含"未种植" if (!viewText.includes('未种植')) continue; const spans = view.querySelectorAll('span.onOnclikCo'); for (const span of spans) { if (safeText(span) === '播种') { buttons.push(span); break; } } } } catch (err) { console.warn('[ygmc_tool] getEmptyPlotSowButtons 异常:', err.message); } return buttons; } // ── 获取播种弹窗 ── function getSowPopup() { try { return document.querySelector('.uni-popup-dialog'); } catch (_) { return null; } } // ── 切换弹窗标签 ── async function switchPopupTab(popup, mainTab, subTab) { if (!popup) return; try { // 切换主标签(作物/鲜花/珍稀/变异) const tab1 = popup.querySelectorAll('.onOnclikCo'); for (const el of tab1) { if (safeText(el) === mainTab) { el.click(); await delay(TAB_SWITCH_WAIT); break; } } // 重新查询DOM(切换主标签后内容会更新),再切换子标签 const tab2 = popup.querySelectorAll('.onOnclikCo'); for (const el of tab2) { if (safeText(el) === subTab) { el.click(); await delay(TAB_SWITCH_WAIT); break; } } } catch (err) { console.warn('[ygmc_tool] switchPopupTab 异常:', err.message); } } // ── 从"播种"按钮获取种子名称 ── // 同行 .flowerName 元素中排除序号/数量/按钮文本,剩余即为种子名 function getSeedNameFromSowBtn(sowBtn) { try { const row = sowBtn.closest('.uni-flex.uni-row'); if (!row) return ''; const nameEls = row.querySelectorAll('.flowerName'); for (const el of nameEls) { const text = safeText(el); if (!text) continue; if (/^\d+\.$/.test(text)) continue; // 序号(如"1.") if (text.startsWith('*')) continue; // 数量(如"*1") if (text === '播种' || text === '一键播种') continue; return text; } } catch (err) { console.warn('[ygmc_tool] getSeedNameFromSowBtn 异常:', err.message); } return ''; } // ── 在弹窗当前页中查找未播种过的种子"播种"按钮 ── function findUnsownSeedBtn(popup, sownCrops) { try { const btns = popup.querySelectorAll('.flowerName.onOnclikCo'); for (const btn of btns) { if (safeText(btn) !== '播种') continue; // 跳过"一键播种"及其他 const name = getSeedNameFromSowBtn(btn); if (name && !sownCrops.has(name)) { return btn; } } } catch (err) { console.warn('[ygmc_tool] findUnsownSeedBtn 异常:', err.message); } return null; } // ── 关闭播种弹窗 ── // uni-app弹窗需点击对话框外部的遮罩区域才能关闭,直接click()包装器无效 function closeSowPopup() { try { const dialog = document.querySelector('.uni-popup-dialog'); if (dialog) { const rect = dialog.getBoundingClientRect(); // 在对话框左上角外侧定位遮罩元素并点击 const x = Math.max(1, rect.left - 30); const y = Math.max(1, rect.top - 30); const target = document.elementFromPoint(x, y); if (target && target !== dialog && !dialog.contains(target)) { target.click(); return; } } // 后备:直接点击包装器 const wrapper = document.querySelector('.uni-popup__wrapper'); if (wrapper) { wrapper.click(); } } catch (err) { console.warn('[ygmc_tool] closeSowPopup 异常:', err.message); } } // ── 点击弹窗"下一页" ── function clickNextPage(popup) { try { const spans = popup.querySelectorAll('span'); for (const span of spans) { if (safeText(span) === '下一页') { span.click(); return true; } } } catch (err) { console.warn('[ygmc_tool] clickNextPage 异常:', err.message); } return false; } // ── 自动播种 ── // 弹窗特性:点击种子"播种"后弹窗不会关闭,可连续播种下一个空坑位 async function autoSow() { if (!isFarmPage()) return; const emptyPlots = getEmptyPlotSowButtons(); if (emptyPlots.length === 0) return; const totalPlots = emptyPlots.length; console.log(`[ygmc_tool] 发现 ${totalPlots} 个空坑位,开始播种`); // 1. 点击第一个空坑位的"播种",打开弹窗(弹窗此后保持打开) try { emptyPlots[0].click(); } catch (err) { console.warn('[ygmc_tool] 点击坑位播种异常:', err.message); return; } await delay(POPUP_WAIT); let popup = getSowPopup(); if (!popup) { console.warn('[ygmc_tool] 播种弹窗未出现,跳过播种'); return; } // 2. 按优先级遍历标签页,在弹窗内连续播种 const sownCrops = new Set(); // 记录本轮已播种的作物名,确保每种只播一次 let sownCount = 0; for (const p of SOW_PRIORITY) { if (sownCount >= totalPlots) break; // 坑位已种完 // 重新获取弹窗引用(DOM可能因播种而更新) popup = getSowPopup(); if (!popup) break; await switchPopupTab(popup, p.mainTab, p.subTab); // 在当前标签页内持续播种,直到坑位种完或无可用种子 while (sownCount < totalPlots) { popup = getSowPopup(); if (!popup) break; let seedBtn = findUnsownSeedBtn(popup, sownCrops); // 当前页无可用种子,尝试翻页查找 if (!seedBtn) { let pageNum = 1; while (pageNum < 10) { if (!clickNextPage(popup)) break; await delay(TAB_SWITCH_WAIT); pageNum++; popup = getSowPopup(); if (!popup) break; seedBtn = findUnsownSeedBtn(popup, sownCrops); if (seedBtn) break; } if (!seedBtn) break; // 当前标签页确实无可用种子,切下一个 } if (!popup || !seedBtn) break; const name = getSeedNameFromSowBtn(seedBtn); try { seedBtn.click(); sownCrops.add(name); sownCount++; console.log(`[ygmc_tool] 已播种: ${name} (${sownCount}/${totalPlots})`); } catch (err) { console.warn(`[ygmc_tool] 播种点击异常(${name}):`, err.message); break; } await delay(SOW_WAIT); // 播种后等待API完成,避免showLoading未配对 } } // 3. 播种完成,关闭弹窗 closeSowPopup(); await delay(POPUP_WAIT); console.log(`[ygmc_tool] 播种流程结束,共播种 ${sownCount} 种作物`); } // ── 畜牧场操作 ── // 切换到畜牧场,执行收获和操作,然后切回农场 async function doRanchOps() { console.log('[ygmc_tool] ── 切换到畜牧场 ──'); const switched = await switchToRanch(); if (!switched) { console.warn('[ygmc_tool] 切换畜牧场失败,跳过畜牧场操作'); // 尝试切回农场,防止意外停留 await switchToFarm(); return; } const panel = getActionPanel(); if (panel) { clickButton(panel, '收获'); await delay(CLICK_DELAY); if (!isRanchPage()) { console.warn('[ygmc_tool] 畜牧场操作中途页面变化,尝试切回农场'); await switchToFarm(); return; } clickButton(panel, '操作'); await delay(CLICK_DELAY); } else { console.warn('[ygmc_tool] 畜牧场未找到一键操作面板'); } console.log('[ygmc_tool] ── 切换回农场 ──'); const backSwitched = await switchToFarm(); if (!backSwitched) { console.warn('[ygmc_tool] 切换回农场失败'); } } function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // ── 执行一轮操作 ── async function runCycle() { isRunningCycle = true; try { // 前置判断:不在农场主页时处理 if (!isFarmPage()) { // 检查是否需要自动登录 if (isLoginPage()) { autoLogin(); // 非阻塞,后台执行10分钟倒计时 return; } // 可能停留在畜牧场等其他页面,尝试切换回农场 console.log('[ygmc_tool] 当前不在农场页面,尝试切换'); const switched = await switchToFarm(); if (!switched) { console.warn('[ygmc_tool] 切换农场失败,跳过本次'); return; } } console.log('[ygmc_tool] ── 开始新一轮操作 ──'); // 1. 点击导航栏"刷新" const navBar = getNavBar(); if (navBar) { clickButton(navBar, '刷新'); } else { console.warn('[ygmc_tool] 未找到导航栏,跳过刷新'); } await delay(REFRESH_WAIT); // 2. 再次确认仍在农场主页(刷新可能导致页面切换) if (!isFarmPage()) { console.warn('[ygmc_tool] 刷新后已离开农场主页,中止本轮'); return; } // 3. 在"一键操作"面板内依次点击 const panel = getActionPanel(); if (!panel) { console.warn('[ygmc_tool] 未找到一键操作面板,跳过本轮'); return; } clickButton(panel, '收获'); await delay(CLICK_DELAY); // 每次点击前确认仍在农场主页 if (!isFarmPage()) { console.warn('[ygmc_tool] 操作中途已离开农场主页,中止本轮'); return; } clickButton(panel, '铲地'); await delay(CLICK_DELAY); if (!isFarmPage()) { console.warn('[ygmc_tool] 操作中途已离开农场主页,中止本轮'); return; } clickButton(panel, '操作'); await delay(CLICK_DELAY); // 4. 自动播种 await autoSow(); // 5. 畜牧场操作 await doRanchOps(); console.log('[ygmc_tool] ── 本轮操作完成 ──'); } finally { isRunningCycle = false; } } // ── 主循环 ── async function main() { console.log('[ygmc_tool] 脚本已启动,每10分钟执行一轮操作(农场+畜牧场),每30秒快速响应'); await delay(5000); // 等待页面稳定 // 快速响应:每30秒轻量扫描,发现需操作的坑位立即执行对应链路 setInterval(() => { quickAction().catch(err => { console.error('[ygmc_tool] 快速响应异常:', err); }); }, HARVEST_CHECK_INTERVAL); // 完整操作循环:每10分钟执行刷新、收获、铲地、操作、播种 while (true) { try { await runCycle(); } catch (err) { console.error('[ygmc_tool] 循环异常,已捕获:', err); } await delay(CYCLE_INTERVAL); } } if (document.readyState === 'complete') { main(); } else { window.addEventListener('load', main); } })();