/ clis / jike / comment.js
comment.js
  1  import { cli, Strategy } from '@jackwener/opencli/registry';
  2  /**
  3   * 评论即刻帖子
  4   *
  5   * 帖子详情页有评论输入框(contenteditable 或 textarea),
  6   * 填入文本后点击"回复"或"发布"按钮提交。
  7   */
  8  cli({
  9      site: 'jike',
 10      name: 'comment',
 11      description: '评论即刻帖子',
 12      domain: 'web.okjike.com',
 13      strategy: Strategy.UI,
 14      browser: true,
 15      args: [
 16          { name: 'id', type: 'string', required: true, positional: true, help: '帖子 ID' },
 17          { name: 'text', type: 'string', required: true, positional: true, help: '评论内容' },
 18      ],
 19      columns: ['status', 'message'],
 20      func: async (page, kwargs) => {
 21          await page.goto(`https://web.okjike.com/originalPost/${kwargs.id}`);
 22          // 1. 找到评论输入框并填入文本
 23          const inputResult = await page.evaluate(`(async () => {
 24        try {
 25          const textToInsert = ${JSON.stringify(kwargs.text)};
 26  
 27          // 优先在评论区容器内找 contenteditable,避免误选页面其他编辑器;
 28          // 若评论区 class 名变更则回退到全页查找
 29          const editor =
 30            document.querySelector('[class*="_comment_"] [contenteditable="true"]') ||
 31            document.querySelector('[contenteditable="true"]');
 32          if (editor) {
 33            editor.focus();
 34            const dt = new DataTransfer();
 35            dt.setData('text/plain', textToInsert);
 36            editor.dispatchEvent(new ClipboardEvent('paste', {
 37              clipboardData: dt, bubbles: true, cancelable: true,
 38            }));
 39            await new Promise(r => setTimeout(r, 800));
 40            if (editor.textContent?.length > 0) {
 41              return { ok: true, message: 'contenteditable' };
 42            }
 43          }
 44  
 45          // 回退:textarea(带评论相关 placeholder)
 46          const textareas = document.querySelectorAll('textarea');
 47          for (const ta of textareas) {
 48            const ph = ta.getAttribute('placeholder') || '';
 49            if (ph.includes('评论') || ph.includes('回复') || ph.includes('说点什么')) {
 50              ta.focus();
 51              const setter = Object.getOwnPropertyDescriptor(
 52                HTMLTextAreaElement.prototype, 'value'
 53              )?.set;
 54              setter?.call(ta, textToInsert);
 55              ta.dispatchEvent(new Event('input', { bubbles: true }));
 56              await new Promise(r => setTimeout(r, 500));
 57              return { ok: true, message: 'textarea' };
 58            }
 59          }
 60  
 61          // 兜底:任意 textarea
 62          if (textareas.length > 0) {
 63            const ta = textareas[0];
 64            ta.focus();
 65            const setter = Object.getOwnPropertyDescriptor(
 66              HTMLTextAreaElement.prototype, 'value'
 67            )?.set;
 68            setter?.call(ta, textToInsert);
 69            ta.dispatchEvent(new Event('input', { bubbles: true }));
 70            await new Promise(r => setTimeout(r, 500));
 71            return { ok: true, message: 'textarea-fallback' };
 72          }
 73  
 74          return { ok: false, message: '未找到评论输入框' };
 75        } catch (e) {
 76          return { ok: false, message: e.toString() };
 77        }
 78      })()`);
 79          if (!inputResult.ok) {
 80              return [{ status: 'failed', message: inputResult.message }];
 81          }
 82          // 2. 点击"回复"或"发布"按钮
 83          const submitResult = await page.evaluate(`(async () => {
 84        try {
 85          await new Promise(r => setTimeout(r, 500));
 86          const btns = Array.from(document.querySelectorAll('button')).filter(btn => {
 87            const text = btn.textContent?.trim() || '';
 88            return (text === '回复' || text === '发布' || text === '发送' || text === '评论') && !btn.disabled;
 89          });
 90          if (btns.length === 0) {
 91            return { ok: false, message: '未找到可用的回复按钮(可能因内容为空而禁用)' };
 92          }
 93          btns[0].click();
 94          return { ok: true, message: '评论发布成功' };
 95        } catch (e) {
 96          return { ok: false, message: e.toString() };
 97        }
 98      })()`);
 99          if (submitResult.ok)
100              await page.wait(3);
101          return [{
102                  status: submitResult.ok ? 'success' : 'failed',
103                  message: submitResult.message,
104              }];
105      },
106  });