/ clis / jike / create.js
create.js
  1  import { cli, Strategy } from '@jackwener/opencli/registry';
  2  /**
  3   * 发布即刻动态
  4   *
  5   * 即刻首页 /following 顶部有内联发帖框("分享你的想法..."),
  6   * 直接在其中输入文本,点击"发送"按钮即可发布。
  7   */
  8  cli({
  9      site: 'jike',
 10      name: 'create',
 11      description: '发布即刻动态',
 12      domain: 'web.okjike.com',
 13      strategy: Strategy.UI,
 14      browser: true,
 15      args: [
 16          { name: 'text', type: 'string', required: true, positional: true, help: '动态正文内容' },
 17      ],
 18      columns: ['status', 'message'],
 19      func: async (page, kwargs) => {
 20          // 1. 导航到首页(有内联发帖框)
 21          await page.goto('https://web.okjike.com');
 22          // 2. 在发帖框中输入文本
 23          const textResult = await page.evaluate(`(async () => {
 24        try {
 25          const textToInsert = ${JSON.stringify(kwargs.text)};
 26  
 27          // 首页发帖框在 _postForm_ 容器内,查找其中的 contenteditable
 28          const form = document.querySelector('[class*="_postForm_"]');
 29          const editor = form
 30            ? form.querySelector('[contenteditable="true"]')
 31            : document.querySelector('[contenteditable="true"]');
 32  
 33          if (editor) {
 34            editor.focus();
 35            // 用 ClipboardEvent paste 触发 React 状态更新
 36            const dt = new DataTransfer();
 37            dt.setData('text/plain', textToInsert);
 38            editor.dispatchEvent(new ClipboardEvent('paste', {
 39              clipboardData: dt, bubbles: true, cancelable: true,
 40            }));
 41            await new Promise(r => setTimeout(r, 800));
 42  
 43            // 检查是否成功插入
 44            const inserted = editor.textContent || '';
 45            if (inserted.length > 0) {
 46              return { ok: true, message: 'contenteditable' };
 47            }
 48          }
 49  
 50          // 回退:textarea
 51          const textarea = form
 52            ? form.querySelector('textarea')
 53            : document.querySelector('textarea');
 54  
 55          if (textarea) {
 56            textarea.focus();
 57            const setter = Object.getOwnPropertyDescriptor(
 58              HTMLTextAreaElement.prototype, 'value'
 59            )?.set;
 60            setter?.call(textarea, textToInsert);
 61            textarea.dispatchEvent(new Event('input', { bubbles: true }));
 62            await new Promise(r => setTimeout(r, 500));
 63            return { ok: true, message: 'textarea' };
 64          }
 65  
 66          return { ok: false, message: '未找到发帖输入框' };
 67        } catch (e) {
 68          return { ok: false, message: e.toString() };
 69        }
 70      })()`);
 71          if (!textResult.ok) {
 72              return [{ status: 'failed', message: textResult.message }];
 73          }
 74          // 3. 点击"发送"按钮
 75          const submitResult = await page.evaluate(`(async () => {
 76        try {
 77          await new Promise(r => setTimeout(r, 500));
 78  
 79          // 即刻首页发帖框的按钮文字为"发送"
 80          const candidates = [
 81            ...Array.from(document.querySelectorAll('button')).filter(btn => {
 82              const text = btn.textContent?.trim() || '';
 83              return text === '发送' || text === '发布';
 84            }),
 85          ].filter(el => el && !el.disabled);
 86  
 87          if (candidates.length === 0) {
 88            return { ok: false, message: '未找到可用的发送按钮(按钮可能因内容为空而禁用)' };
 89          }
 90  
 91          candidates[0].click();
 92          return { ok: true, message: '动态发布成功' };
 93        } catch (e) {
 94          return { ok: false, message: e.toString() };
 95        }
 96      })()`);
 97          if (submitResult.ok) {
 98              await page.wait(3);
 99          }
100          return [{
101                  status: submitResult.ok ? 'success' : 'failed',
102                  message: submitResult.message,
103              }];
104      },
105  });