YouTubeの英語字幕をディクテーションできるChrome拡張機能を作る（海外ドラマで英語学習がしたい！）

[ZOZOテクノロジーズ #4 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech4) の10日目です。
昨日は[@pakio](https://qiita.com/pakio)さんの「[Laravel + Vue + Vuetifyでbundle sizeの削減を試みてみる](https://qiita.com/pakio/items/dda7fea8cef964891c45)」でした。

なんと先日、**英単語の日本語訳とレベルを同時に取得でき、しかもローカル環境だけで動く素晴らしいJavaScriptライブラリ**に出会いました！ この記事ではそのライブラリを使ってChrome拡張機能を作っていきます。

## 作っているもの
**YouTubeの英語字幕をディクテーション（リスニング）できるChrome拡張機能「YouTube-Dictation」**
[github.com/tippy3/youtube-dictation](https://github.com/tippy3/youtube-dictation)
![qiita用スクリーンショット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/d12ee5ca-09cd-f4cb-a838-9f316dc4e3ed.png)
わかりにくいスクショで恐縮です。
つまりは動画の下に字幕を出して、ユーザーが英単語をディクテーションするということです。

## 実際に作った流れ

Chrome拡張機能はJavaScript, HTML, CSSで開発します。**めちゃくちゃ簡単です。**

### 1 manifest.json

まずは各ファイルを用意し、エントリーポイントとなる`manifest.json`を作ります。

```json
{
  "name": "YouTube-Dictation",
  "short_name": "YouTube-Dictation",
  "description": "YouTubeで楽しく英語学習。好きな動画の英語字幕でディクテーション（リスニング）ができる拡張機能です。",
  "version": "1.0",
  "icons": {
    "128": "images/128.png"
  },
  "content_scripts": [{
    "matches": ["https://*.youtube.com/*", "http://*.youtube.com/*"],
    "run_at": "document_end",
    "css": ["css/main.css"],
    "js": ["js/superagent-master/superagent.js", "js/underscore-master/underscore.js", "js/javascript-lemmatizer-master/js/lemmatizer.js", "js/kantan-ej-dictionary-master/kantan-ej-dictionary.js", "js/main.js"]
  }],
  "web_accessible_resources": [
    "js/javascript-lemmatizer-master/dict/*.json"
  ],
  "permissions": [
    "storage"
  ],
  "manifest_version": 2
}
```

概要をざっくり説明すると、

**content_scripts**は表示しているWebページに挿入されるJavaScriptです。表示中のDOMを操作したり、キーイベントを追加したりすることができます。他にも拡張機能のアイコンを押されたときに実行される**page\_action**などがあります。


今回は**content_scripts**でYouTubeのURLにマッチしたときにCSSとJavaScriptを読み込みます。JavaScriptは依存関係のあるライブラリを順に読み込み、最後に自分の処理を書く`main.js`を読み込みます。"run_at"でWebページの読み込み後に実行するようにしています。

また、ライブラリにJSONを使うものがあったので**web\_accessible\_resources**にファイル名を書きました。これを書かないとJavaScriptからJSONにアクセスできません。また、Web Storageを使うライブラリがあったので**permissions**に"storage"を入れました。

**ここから**`main.js`**をガリガリ書いていきます。**

### 2 UIを出力する

このやり方は完全に自己流なのですが、JavaScriptでDOMをappendChildしてUIを出力します。なんか頭の悪い感じもしますが動くのでヨシ！

```JavaScript
let html = document.createElement("div");
html.id = "yt-dictation-container";
html.innerHTML = '<p id="yt-dictation-text">YouTube-Dictation</p>...長いので省略';
html.addEventListener('click', clickEvent);
document.body.appendChild(html);
```

### 3 AjaxでAPIを叩き字幕を取得する

Ajaxするためのライブラリ[superagent](https://github.com/visionmedia/superagent)でJSONを取得します。使い方はjQueryの.ajax()とほぼ同じです。

```JavaScript
function requestCCList(){
  SUPERAGENT
  .get(YOUTUBE_CC_API)
  .accept('json')
  .query({
    type: 'list',
    v: video_id
  })
  .end(responseCCList);
}

function responseCCList(err, res){
// 略。下記参照
}
```

実際には、まずは字幕一覧を取得し、もし英語の字幕があったらその字幕を取得する、という２段構えになっています。
また、字幕を取得後、行ごとの配列を作成し、さらに単語ごとの配列を作成しています。

### 4 ディクテーションする英単語を選ぶ

**この章がこの記事の本編です。**はじめは英単語をランダムに出題していたのですが、**ユーザーのレベルに合わせた英単語を出題したい**と思いました。そこで使ったのが以下のライブラリです。

- **[kantan-ej-dictionary](https://github.com/gunyarakun/kantan-ej-dictionary)**
  - 英単語の和訳とSVLレベルを取得できるとても素晴らしいライブラリ
- **[javascript-lemmatizer](https://github.com/takafumir/javascript-lemmatizer)**
  - 英単語の原型を取得できるとても素晴らしいライブラリ

英単語のレベル分けには、[アルク](https://www.alc.co.jp/vocgram/article/svl/)によるSVL12000（重要英単語12000語を1から12までの12段階にレベルわけしたもの）を採用し、それを和訳とともに取得できるライブラリ[kantan-ej-dictionary](https://github.com/gunyarakun/kantan-ej-dictionary)を使ってみました。

**しかし、複数形や過去形だと[kantan-ej-dictionary](https://github.com/gunyarakun/kantan-ej-dictionary)にヒットしないため、原型でヒットしなかった場合は[javascript-lemmatizer](https://github.com/takafumir/javascript-lemmatizer)を使って英単語を原型に戻し、再度検索しました。**

なんとこれらライブラリはいずれもローカルだけで動きます。しゅばらしい。。。

あとは取得した英単語のレベルを使い、最もユーザーのレベルにあった英単語を出題します。
実際のコードが下記です。英単語ごとにループを回し、以下の処理を行います。

```javascript
let candidates = []; // 問題にする単語の候補(単語のインデックス番号が入る)
let min_diff = 99;

cc.words.forEach((word,index)=>{

  const original_word = removeSymbol(word);
  let result = kanten_dictionary[original_word]; // まずは英単語を辞書で検索

  if(!result || !result.svl_level){
    // 辞書にヒットしなかった場合、英単語を原型に戻す
    const lemma = LEMMATIZER.lemmas( original_word );
    if(lemma.length==0){
      return false; // 原型に戻せなかった場合
    }else{
      result = lemma[0][0];
    }
    result = kanten_dictionary[result]; // 原型で辞書を検索
    if(!result || !result.svl_level){
      return false; // 原型でも辞書にヒットしなかった場合
    }
  }

  // 単語のレベルとユーザーのレベルの差を求める
  const diff = Math.abs( result.svl_level - USER_SVL_LEVEL );
  if( diff<min_diff || min_diff==99 ){
    // よりレベルの近い単語が見つかった場合、その単語を候補にする
    min_diff = diff;
    candidates = [];
    candidates.push(index);
  }else if( diff==min_diff ){
    // レベルの差が同じ単語が見つかった場合、その単語を候補に入れる
    candidates.push(index);
  }

});
```
**あとは出題する英単語をinputタグに変え、動画の再生時間に合わせて字幕をUIに出力します。入力の判定処理やキーイベントなども書いて、、、完成！！！せず、本日を迎えてしまいました。**

実は一旦ストアへリリースしたのですが、

- このテキトーなUIを改善しなきゃなぁ
- ユーザーのレベルは決め打ちじゃなくて動的に決めたいなぁ
- 英単語の和訳も表示したいなぁ
- 間違えた単語をセーブして復習できるようにしたいなぁ

などの欲が出た結果、このアドベントカレンダーに間に合わなくなりました。ごぺんなさい🐧

**ここからはストアへの公開についてです。**

## Chromeウェブストアへの公開方法

**Q: ストアへの登録は難しいですか？**
**A: 面倒くさいですが難しくはありません。**

### 1 まずはデベロッパー登録($5)

無料で公開する場合もデベロッパー登録が必要です。氏名・住所・電話番号等を入力し、登録料5ドルをクレジットカードで支払います。登録料は１回払えばOKです。

### 2 作った拡張機能を登録

拡張機能の名前や説明、販売する国や金額を入力していきます。途中で縦横比別にサムネイル画像を登録していくのですが、これがとても面倒くさかったです。アイコンやスクリーンショット含めて５種類も画像を作らされました。

### 3 Googleによる審査を待つ

無料公開でもGoogleによる審査があります。私の場合は公開申請後1日以内に通ることが多かったです。

**これでようやく公開！（してないけど）**
記事本文はここまでです。

## 謝辞

共同開発者の[@motonari728](https://github.com/motonari728)に深く感謝申し上げます。また一緒に食事でも行きましょう。

## もっとChrome拡張機能について知りたくなったら（参考文献）

QiitaにはChrome拡張機能に関する記事がたくさんあります。はじめに読むべき２本をオススメとして紹介します。

- [Chrome拡張開発 超概要](https://qiita.com/M_Kagawa/items/539d7597536ec90b1bc6)（特に[主な拡張の種類](https://qiita.com/M_Kagawa/items/539d7597536ec90b1bc6#%E4%B8%BB%E3%81%AA%E6%8B%A1%E5%BC%B5%E3%81%AE%E7%A8%AE%E9%A1%9E)の章が分かりやすいです）
-  [Chrome 拡張機能のマニフェストファイルの書き方](https://qiita.com/mdstoy/items/9866544e37987337dc79)（他にもググるとたくさん出てきます）

## 次回予告

明日は[@252525](https://qiita.com/252525)さんです。お楽しみに！
最後までお読みいただきありがとうございました。