Unityの.alfファイルから自動で.ulfをダウンロードしたい！

# CIのActivateとかでライセンスを自動でActivateさせたい！

CIでUnityを扱う時はJenkinsとかであれば問題ないのですが、CircleCIやGitHub Actionsを使用するときにDockerでのライセンス認証では.ulfファイルというのが必要になってきます。

現在.ulfファイルをコマンドラインから生成することはできません。生成するにはブラウザ経由の一択です。
それを今回Puppeteerというnode.jsのツールを使って自動化してみました。

※今回の認証フローはPersonalEdition固定になります。

Puppetterとは、Webブラウザでの操作をソースコードから行えるものになります。
詳しくはこちら
[Puppeteer](https://developers.google.com/web/tools/puppeteer)

今回のリポジトリはこちら
[MizoTake/unity-license-activate](https://github.com/MizoTake/unity-license-activate)

## 実装

npm経由でPuppeteerを入れて以下のjsで実装しました。

今回はライセンスの認証が必要になるので `https://license.unity3d.com/manual` のページで操作を行います。
手元にあるalfファイルから最終的にulfファイルをダウンロードする操作になります。

叩くコマンドは以下になります
`node activate.js $email $password $alf_file_path`

以下が今回のScriptの全容ですが細かく分けてどうなっているのか下で記述します。

```js:activate.js
const puppeteer = require('puppeteer')
const fs = require('fs')

;(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  const downloadPath = process.cwd()
  const client = await page.target().createCDPSession()
  await client.send('Page.setDownloadBehavior', {
    behavior: 'allow',
    downloadPath: downloadPath
  })

  await page.goto('https://license.unity3d.com/manual')

  await page.waitForNavigation({
    timeout: 60000,
    waitUntil: 'domcontentloaded'
  })

  const email = `${process.argv[2]}`
  await page.type('input[type=email]', email)

  const password = `${process.argv[3]}`
  await page.type('input[type=password]', password)
  await page.click('input[name="commit"]')

  await page.waitForNavigation({
    timeout: 60000,
    waitUntil: 'domcontentloaded'
  })

  const input = await page.$('input[name="licenseFile"]')

  const alfPath = `${process.argv[4]}`
  await input.uploadFile(alfPath)

  await page.click('input[name="commit"]')

  await page.waitForNavigation({
    timeout: 60000,
    waitUntil: 'domcontentloaded'
  })

  const selectedTypePersonal = 'input[id="type_personal"][value="personal"]'
  await page.evaluate(
    s => document.querySelector(s).click(),
    selectedTypePersonal
  )

  const selectedPersonalCapacity =
    'input[id="option3"][name="personal_capacity"]'
  await page.evaluate(
    s => document.querySelector(s).click(),
    selectedPersonalCapacity
  )

  await page.click('input[class="btn mb10"]')

  await page.waitForNavigation()

  await page.click('input[name="commit"]')

  let _ = await (async () => {
    let ulf
    do {
      for (const file of fs.readdirSync(downloadPath)) {
        ulf |= file.endsWith('.ulf')
      }
      await sleep(1000)
    } while (!ulf)
  })()

  function sleep(milliSeconds) {
    return new Promise((resolve, reject) => {
      setTimeout(resolve, milliSeconds)
    })
  }

  await browser.close()
})()

```

## 画面ごとの処理


![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/690c9725-0905-33bb-ffc5-068057a8e7cb.png)
 
```js
  // ライセンス認証を行うページに行く
  await page.goto('https://license.unity3d.com/manual')

  await page.waitForNavigation({
    timeout: 60000,
    waitUntil: 'domcontentloaded'
  })

  // ライセンス認証を行うページに飛ばしたがリダイレクトでUnityのログインページに飛んでいる

  //コマンド引数からメールアドレスとパスワードをとってくる
  const email = `${process.argv[2]}`
  await page.type('input[type=email]', email)

  const password = `${process.argv[3]}`
  await page.type('input[type=password]', password)

  // Sign inのボタンを押す
  await page.click('input[name="commit"]')
```

↓

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/a149b604-4d9d-2305-fd7f-85ed47bcc5c8.png)

```js

  const input = await page.$('input[name="licenseFile"]')

  // コマンドライン引数で指定したpathからfileを添付
  const alfPath = `${process.argv[4]}`
  await input.uploadFile(alfPath)

  // Nextボタンを押す
  await page.click('input[name="commit"]')
```

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/39f2f45f-1270-89e1-6b34-3abae4cccb85.png)

これでファイル添付ができてることがわかります。ファイル添付までできるのすげぇ…Puppeteer…

↓

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/ad9f6d8e-f0e9-1f2e-9f65-01caa9bfd855.png)

```js
  // Personal Editionの選択
  const selectedTypePersonal = 'input[id="type_personal"][value="personal"]'
  await page.evaluate(
    s => document.querySelector(s).click(),
    selectedTypePersonal
  )
  
  // Personal Edition選択後に出てくるのOptionを選択
  const selectedPersonalCapacity =
    'input[id="option3"][name="personal_capacity"]'
  await page.evaluate(
    s => document.querySelector(s).click(),
    selectedPersonalCapacity
  )
　　
　// Nextボタンを押す
  await page.click('input[class="btn mb10"]')
```

この画面の実行後はこうなっています。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/a0df234c-d0e4-4541-b9b1-93062900f9f8.png)

↓

![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127761/0ea67b7a-ff5c-019c-cb2e-93486985d1f5.png)

```js
  // Download license fileボタンを押す
  await page.click('input[name="commit"]')

  // ダウンロードが始まるので手元に.ulfファイルができるまで待つ
  let _ = await (async () => {
    let ulf
    do {
      for (const file of fs.readdirSync(downloadPath)) {
        ulf |= file.endsWith('.ulf')
      }
      await sleep(1000)
    } while (!ulf)
  })()

  function sleep(milliSeconds) {
    return new Promise((resolve, reject) => {
      setTimeout(resolve, milliSeconds)
    })
  }

```

以上のような流れになっています。

## さいごに

Puppeteer便利！！！！

```js
  await page.screenshot( {
    path: "./example.png"
  });
```
でScreenShotを撮りつつ実装してました。

ブラウザでしか行えない操作もこれがあればできるので色々と捗るんじゃないかなと思っています。

## 追記 (2019/12/11)
今回のScriptでは二段階認証を設定していると一度認証されているPCからでのみうまくいきます。
その辺をクリアしようと検証はしてますがまだうまくいかないので注意しつつ使用してみてください。