在之前的文章自动化:小书签已经不够用了,上油猴脚本!中我有预告过,虽然油猴脚本已经极大的减少了我发布远程工作的重复劳动,但是依然不是我理想的自动化工作流。

Image

理想的工作流是,利用扩展助手发布到远程工作者网站,然后再自动发布到豆瓣和知识星球,从其他渠道引流到网站。

这几日,我终于走通了豆瓣小组发布文章的自动化流程,同步远程工作信息的效率大大提升了。

先来看看我之前的操作流程,需要来来回回复制粘贴,尤其标签页多的时候,很容易信息错配,耗时费力,虽然比之以往纯手工格式化信息已经进步很多,但是还是不够。

于是,狗哥我几经雕琢,改进版的发布同步工具终于出炉了,目前已经能自动发布到TG频道,豆瓣小组以及知识星球了,先看视频。

Image

除了 TG 使用了官方提供的 API 以外,其它的两个渠道均是采用了模拟网页操作的方式(国内的产品相对还是比较闭塞),借助的是 Chrome Extension 的脚本执行能力。

Chrome 扩展的这个能力是一把双刃剑,既强大,又危险,我在 GDG 西安 2022 年的 DevFest 活动中做过技术分享。

https://www.bilibili.com/video/BV1jK411D7hJ/

技术分享演示用到的相关代码有开源在 github,感兴趣的可以交流学习一下。这里多说一句,对于非 Webstore 渠道,来源不明的扩展,除非你对它的执行过程有清楚的认识,还是谨慎使用,毕竟安全大于天。

https://github.com/greatghoul/chrome-extension-risks-demo

其实知识星球的分享这里有一点瑕疵,就是必须是标签页激活的状态,不然它的编辑器识别不到输入区域文本的变化,提交按钮无法点击,不能像豆瓣小组那样静默执行。但这一定是可以想办法绕过的,只是狗哥我急着发文,暂时搁置了这个问题,凑活用就得了。

目前的自动化工具中,并未包含发布内容到微信公众号的部分,不过狗哥我已经有了规划,准备做成类似 [自动化] 为了方便发今日头条,我做了一个浏览器扩展 的样子,留到下次再做分享。

Demo 看完了,这里以豆瓣小组为例,再简单说明一下这个扩展的“核心技术”。

Chrome 支持在指定标签页的上下文中动态调用一个方法,需要注意的是,该方法内不能引用任何该方法定义之外的其他方法或者变量,否则会访问错误,但是它支持动态设置方法参数,这提供了一定的灵活性。


function scriptPostingTopic () {
  return async (title, message) => {
    // 等待指定时间
    const wait = (timeout) => new Promise(resolve => setTimeout(resolve, timeout));
    // 等待指定元素出现
    const waitElement = (selector, timeout = 5000, interval = 300) => {
      return new Promise((resolve, reject) => {
        let elapsed = 0;
        const check = () => {
          const el = document.querySelector(selector);
          if (el) {
            resolve(el);
          } else if (elapsed >= timeout) {
            reject(new Error(`Element ${selector} not found within ${timeout}ms`));
          } else {
            elapsed += interval;
            setTimeout(check, interval);
          }
        };
        check();
      });
    }
    // 模拟点击
    const clickElement = (element) => {
      const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
      element.dispatchEvent(clickEvent);
    }
    // 粘贴内容
    const pasteElement = (element, text) => {
      const selection = window.getSelection();
      const range = document.createRange();
      range.selectNodeContents(element);
      range.collapse(true);
      selection.removeAllRanges();
      selection.addRange(range);
      const clipboardData = new DataTransfer();
      clipboardData.setData('text/plain', text);
      
      const pasteEvent = new ClipboardEvent('paste', { clipboardData: clipboardData, bubbles: true, cancelable: true });
      element.dispatchEvent(pasteEvent);
    }
    // 模拟触发输入事件,不然表单识别不到输入的内容
    const inputElement = (element, text) => {
      element.focus();
      element.value = text;
      const inputEvent = new Event('input', { bubbles: true });
      element.dispatchEvent(inputEvent);
    }
    // 填写标题
    const titleElement = await waitElement('.DRE-topic-editor-title-inputor');
    clickElement(titleElement);
    await wait(500);
    inputElement(titleElement, `分享远程工作:${title}`);
    await wait(1000);
    // 填写正文
    const contentElement = await waitElement('.DRE-topic-editor-main [contenteditable="true"]');
    clickElement(contentElement);
    await wait(500);
    pasteElement(contentElement, message);
    await wait(1000);
    const submitButton = await waitElement('.DRE-topic-editor-header .DRE-primary-button');
    submitButton.click();
  }
}

async function postMessage () {
  const tab = await chrome.tabs.create({ url: GROUP_POST_URL, active: false });
  await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: scriptPostingTopic(),
    args: [title, message], // 调用 scriptPostingTopic 传递的参数
  });
}

这是一个简单的调用场景,打开标签页,动态执行脚本,填写标题和正文,并提交表单。如果你的脚本的逻辑更加复杂,可以使用独立的脚本文件的方式去调用执行,不过执行脚本文件的方式就不支持例子中便捷的传参方式了,需要使用消息通信的 API 来实现数据传递,本文不多做扩展。感兴趣的朋友可以参考 Scripting 和 Message Passing 文档。


文章同步发表于微信公众号老狗拾光,欢迎关注。

微信公众号老狗拾光