/ clis / v2ex / daily.js
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  });