daily.js
1 /** 2 * V2EX Daily Check-in adapter. 3 */ 4 import { CommandExecutionError } from '@jackwener/opencli/errors'; 5 import { cli, Strategy } from '@jackwener/opencli/registry'; 6 cli({ 7 site: 'v2ex', 8 name: 'daily', 9 description: 'V2EX 每日签到并领取铜币', 10 domain: 'www.v2ex.com', 11 strategy: Strategy.COOKIE, 12 browser: true, 13 args: [], 14 columns: ['status', 'message'], 15 func: async (page) => { 16 if (!page) 17 throw new CommandExecutionError('Browser page required'); 18 if (process.env.OPENCLI_VERBOSE) { 19 console.error('[opencli:v2ex] Navigating to /mission/daily'); 20 } 21 await page.goto('https://www.v2ex.com/mission/daily'); 22 // Cloudflare challenge bypass wait 23 for (let i = 0; i < 5; i++) { 24 await new Promise(r => setTimeout(r, 1500)); 25 const title = await page.evaluate(`() => document.title`); 26 if (!title?.includes('Just a moment')) 27 break; 28 if (process.env.OPENCLI_VERBOSE) 29 console.error('[opencli:v2ex] Waiting for Cloudflare...'); 30 } 31 // Evaluate DOM to find if we need to check in 32 const checkResult = await page.evaluate(` 33 async () => { 34 const btn = document.querySelector('input.super.normal.button'); 35 if (!btn || !btn.value.includes('领取')) { 36 return { claimed: true, message: '今日奖励已发/无需领取' }; 37 } 38 39 const onclick = btn.getAttribute('onclick'); 40 if (onclick) { 41 const match = onclick.match(/once=(\\d+)/); 42 if (match) { 43 return { claimed: false, once: match[1], message: btn.value }; 44 } 45 } 46 47 return { 48 claimed: false, 49 error: '找到了按钮,但未能提取 once token', 50 debug_title: document.title, 51 debug_body: document.body.innerText.substring(0, 200).replace(/\\n/g, ' ') 52 }; 53 } 54 `); 55 if (checkResult.error) { 56 if (process.env.OPENCLI_VERBOSE) { 57 console.error(`[opencli:v2ex:debug] Page Title: ${checkResult.debug_title}`); 58 console.error(`[opencli:v2ex:debug] Page Body: ${checkResult.debug_body}`); 59 } 60 throw new CommandExecutionError(checkResult.error); 61 } 62 if (checkResult.claimed) { 63 return [{ status: '✅ 已签到', message: checkResult.message }]; 64 } 65 // Perform check in 66 if (process.env.OPENCLI_VERBOSE) { 67 console.error(`[opencli:v2ex] Found check-in token: once=${checkResult.once}. Checking in...`); 68 } 69 await page.goto(`https://www.v2ex.com/mission/daily/redeem?once=${checkResult.once}`); 70 await new Promise(resolve => setTimeout(resolve, 3000)); // wait longer for redirect 71 // Verify result 72 const verifyResult = await page.evaluate(` 73 async () => { 74 const btn = document.querySelector('input.super.normal.button'); 75 if (!btn || !btn.value.includes('领取')) { 76 // fetch balance to show user 77 let balance = ''; 78 const balanceLink = document.querySelector('a.balance_area'); 79 if (balanceLink) { 80 balance = Array.from(balanceLink.childNodes) 81 .filter(n => n.nodeType === 3) 82 .map(n => n.textContent?.trim()) 83 .join(' ') 84 .trim(); 85 } 86 return { success: true, balance }; 87 } 88 return { success: false }; 89 } 90 `); 91 if (verifyResult.success) { 92 return [{ status: '🎉 签到成功', message: `当前余额: ${verifyResult.balance || '未知'}` }]; 93 } 94 else { 95 return [{ status: '❌ 签到失败', message: '未能确认签到结果,请手动检查' }]; 96 } 97 }, 98 });