xxleyi / learning_list

聚集自己的学习笔记
11 stars 3 forks source link

automate chrome by script kit #329

Open xxleyi opened 2 years ago

xxleyi commented 2 years ago

script kit 是一个对前端开发很友好的工具,可以使用 JS 操控 Mac,将很多事情自动化。

需求:使用 script kit 控制浏览器,具体的技术点如下

使用浏览器打开 url 使用 AppleScript 将一段 JS 注入浏览器,并执行 其中第一点很轻松,第二点折腾了很久,才踩遍其中的坑。

坑:

最后搞出来一个可以工作的版本:

// Name: new-learning-issue

import "@johnlindquist/kit"

var result: any

async function waitTabLoaded() {
  await applescript(`if running of application "Google Chrome" then
    tell application "Google Chrome"
        if front window exists then
            tell front window
                repeat while (loading of active tab)
                    delay 0.1
                end repeat
            end tell
        end if
    end tell
end if`)
}

async function waitTabUrlToBe(url: string) {
  await applescript(`if running of application "Google Chrome" then
  tell application "Google Chrome"
      if front window exists then
          tell front window
              repeat while (URL of active tab is not equal to "${url}")
                  delay 0.1
              end repeat
          end tell
      end if
  end tell
  end if`)
}

/** 
 * @param {function} f
 * - f's body will be executed in active tab by applescript
 * - f's last expression value will the result of applescript which is 'success' or 'fail'
 * - you should know what you are doing if you pass f into this function
 */
function extractCodeFrom(f) {
  const s = f.toString()
  return s.slice(s.indexOf("{") + 1, s.lastIndexOf("}"))
    // escape the code
    .replace(/"/g, '\\"')
}

function valueFromApplescriptIsValid(v: string) {
  v = (v || '').trim()
  return v && v !== 'fail' && v !== 'missing value'
}

async function makeBrowserExecute(f: () => void, isValid = valueFromApplescriptIsValid) {
  const operation = f.name
  const jsCode = extractCodeFrom(f)
  let res = 'fail'
  // 重试 10 次
  for (let i = 0; i < 10; i++) {
    const asInput = `
    tell application "Google Chrome"
    execute front window's active tab javascript "${jsCode}"
    end tell`.replace(/\\u/g, '\\\\u')
    res = await applescript(asInput)
    if (isValid(res)) {
      notify(`${operation} success: ${res}`)
      console.log(`${operation} success: ${res}`)
      return res
    } else {
      await wait(1000)
    }
  }
  console.log(`${operation} fail: `, res)
  throw new Error(`${operation} fail: ${res}`)
}

await hide()
await exec(`open 'https://github.com/xxleyi/learning_list/issues'`)
await waitTabLoaded()

function makeBrowserClickNewIssue() {
  // query all buttons
  let buttons = Array.from(document.querySelectorAll<HTMLLinkElement>("a[role='button']"))
  // transform to array
  // find the button with text "New issue"
  let newIssueButton = buttons.find(button => button.textContent?.match(/new issue/i))
  if (newIssueButton) {
    // click the button
    newIssueButton.click()
    // return to applescript
    result = 'success'
  }
}

await makeBrowserExecute(makeBrowserClickNewIssue)

await waitTabUrlToBe('https://github.com/xxleyi/learning_list/issues/new')
await waitTabLoaded()

function makeBrowserFillTitle() {
  // query title input
  let titleInput = document.querySelector<HTMLInputElement>('input[name="issue[title]"]')
  if (titleInput && titleInput.disabled === false) {
    titleInput.focus()
    // set the title
    titleInput.value = '学习笔记'
    // return to applescript
    result = 'success'
  }
}

await makeBrowserExecute(makeBrowserFillTitle)