TLに流れてくるネガティブなツイートを近しい松岡修造語録に上書きするChrome拡張を作ってみた

こんにちは！

突然ですがみなさんのTLはポジティブですか？たまにはネガティブなツイートも流れてきますよね。

文章の力は偉大です。
ネガティブな文章は人をネガティブに、ポジティブな文章は人をポジティブに変える力を持っています。
できればポジティブなツイートに囲まれて幸せなツイッターライフを満喫したいところです。

ポジティブといえば誰を思い浮かべますか？
そうですね。<b>松岡修造さん</b>ですよね。

ネガティブなTweetは松岡修造さんのお言葉から同等の意味のものを拝借することで伝えたいことを崩さずにTLをポジティブな空間にすることができるかもしれません。

ということで[COTOHA API](https://api.ce-cotoha.com/)を使って<b>ポジティブでないTweetを似た意味の松岡修造語録に書き換えちゃうChrome拡張機能</b>を作ってみたいと思います。

# COTOHA API
[COTOHA API](https://api.ce-cotoha.com/contents/index.html)はNTTコミュニケーションズさんが提供する自然言語処理・音声処理APIプラットフォームです。

今回はその中でも『[<b>感情分析API</b>](https://api.ce-cotoha.com/contents/reference/apireference.html#sentiment)』を使ってポジティブではないツイートを検出し、『[<b>類似度算出API</b>](https://api.ce-cotoha.com/contents/reference/apireference.html)』を使って最も近しい松岡修造語録を見つけたいと思います。

# つくってみる。
Chrome拡張機能の作り方のベースは「[Chrome 拡張機能でタイムライン上のツイートを「ばぶ」らせてみた - Qiita](https://qiita.com/38tter_miya/items/7265ec0843ce20c25146)」を多大に参考にさせていただきました。

とりあえず、適当にワークスペースを作って開発にとりかかります。

## マニフェストファイルを作成する
Chrome拡張機能の基本的な設定や情報を記載する`manifest.json`を作成します。
今回はChrome拡張のアイコンをクリックしたら処理が実行されるようなアプリケーションを検討しているので、以下のように`manifest.json`を記述します。

```json:manifest.json
{
  "name": "positter",
  "description": "positter deletes negative tweets from your time line.",
  "manifest_version": 2,
  "version": "1.0",
  "browser_action": {},
  "content_scripts": [
    {
      "matches": [ "https://twitter.com/*" ],
      "js": [ "js/jquery.min.js", "js/content.js" ]
    }
  ],
  "background": {
    "scripts": [ "js/background.js" ],
    "persistent": false
  },
  "permissions": [
    "https://xxxxxxxxxx.com/*" // COTOHA APIのアカウントホームページでBase URLを確認
  ]
}
```
特に重要な奴らを紹介します。

### content_scripts
`content_scripts`は読み込むjsファイルとそれを読み込むページを定義しています。
今回はTwitterのページに限り、`js/jquery.min.js`と`js/content.js`を読み込みます。これらのファイルは後々作成します。

### background
`background`はバックグラウンドで動くjsを定義しています。今回は`js/background.js`を読み込みます。
`persistent`を`false`に指定することで特定のイベント時のみ起動するようになります。（これをしないとずっとバックグランドで処理が動いちゃうので非推奨です：[Manage Events with Background Scripts - Google Chrome](https://developer.chrome.com/extensions/background_pages)）

### permissions
`permissions`は利用できる外部APIの穴あけを定義する箇所です。今回はCOTOHA APIの`base url`を設定しておきます。`base url`は

## jqueryを使えるようにする
まずはjQueryを使ってささっと開発をしたいので、[公式HP](https://jquery.com/download/)からファイルをダウンロードして`js/jquery.min.js`として保存します。

## backgroundでアイコンクリックを検知する
`background`でアイコンクリックを検知して、`content_scripts`にそれを通知してあげます。

```js:js/background.js
chrome.browserAction.onClicked.addListener(function(tab) {
  chrome.tabs.sendMessage(tab.id, "clicked")
})
```
`chrome.browserAction.onClicked.addListener`でChrome拡張のアイコンがクリックされたイベントを検知して、`chrome.tabs.sendMessage`で`content_scripts`に`tab.id`と`clicked`というメッセージを送信しています。

`content_scripts`でこの`clicked`というメッセージを受け取った場合の処理をコーディングしていきます。

## content_scriptsでメッセージを受信する
`content_scripts`がメッセージを受診した後にやるべきことは以下です。

1. 現在開いているページのTLからTweetsの内容を取得（`get_tweets`）
2. COTOHA APIのアクセストークンを取得する（`get_access_token`）
3. 取得したTweetsのネガポジ判定をする（`get_tweet_sentiment`）
2. ポジティブでないTweetsと近しい内容の松岡修造語録を取得する（`get_similar_shuzo_quote`）
3. ポジティブでないTweetsの内容を松岡修造語録に変換する（`change_tweet`）


ひとまずそれぞれの処理の詳細はおいておいて、`background`のメッセージを受け取って、それぞれの処理を実行する外形を作ります。

```js:js/content.js
// backgroundからメッセージを受信して処理を実行する
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  if ( request == "clicked" ) {
    
    // 現在開いているページのTLからTweetsの内容を取得（`get_tweets`）
    tweets = get_tweets()
    
    // COTOHA APIのアクセストークンを取得する（`get_access_token`）
    get_access_token().then((access_token) => {

      // tweetsから１件ずつtweetに格納して処理する
      for (let tweet of tweets) {

        // 取得したtweetのネガポジ判定をする（`get_tweet_sentiment`）
        get_tweet_sentiment(access_token, tweet).then(sentiment => {

          // tweetがポジティブでないと判断した場合、処理を継続。
          if (sentiment != "Positive") {

            // ポジティブでないtweetと近しい内容の松岡修造語録を取得する（`get_similar_shuzo_quote`）
            get_similar_shuzo_quote(access_token, tweet).then(new_tweet => {

              // ポジティブでないtweetの内容を松岡修造語録に変換する（`change_tweet`）
              change_tweet(tweet, new_tweet)
            })
          }
        })
      }
    })
  }
})
```
かなり段階的になってしまっていますが、内容自体は単純です。あとは５つの関数を定義していくだけです。
メッセージのやりとりについては「[Chrome 拡張機能で background scripts から content scripts にメッセージを送信する - to-me-mo-rrow - 未来の自分に残すメモ -](https://r17n.page/2019/10/27/chrome-extension-message-from-background-to-content/)」の記事も参考にさせていただきました。

それでは、ここからは各ファンクションをコーディングしていきます！

## get_tweets
`get_tweets`はTLからTweetを抜き出してくる機能にします。
jQueryで文字列は`要素.text()`で抽出することができます。要素の特徴（`id`とか`class`とか）さえわかってしまえば造作もありません。

ということでTwitterのTLの構造を理解するために`body`タグを抽出してみます！

```html
<body cz-shortcut-listen="true" style="background-color: rgb(255, 255, 255);">
  <!-- 省略 -->
  <div id="react-root" style="height:100%;display:flex;">
    <div class="css-1dbjc4n r-13awgt0 r-12vffkv" data-reactroot="">
      <div class="css-1dbjc4n r-13awgt0 r-12vffkv">
        <!-- 省略 -->
        <div class="css-1dbjc4n r-18u37iz r-1pi2tsx r-13qz1uu r-417010" data-at-shortcutkeys="{&quot;n&quot;:&quot;新しいツイート&quot;,&quot;Cmd Enter&quot;:&quot;ツイートを送信&quot;,&quot;m&quot;:&quot;ダイレクトメッセージを作成&quot;,&quot;/&quot;:&quot;検索&quot;,&quot;l&quot;:&quot;いいね&quot;,&quot;r&quot;:&quot;返信&quot;,&quot;t&quot;:&quot;リツイート&quot;,&quot;s&quot;:&quot;ツイートを共有&quot;,&quot;b&quot;:&quot;ブックマーク&quot;,&quot;u&quot;:&quot;アカウントをミュート&quot;,&quot;x&quot;:&quot;アカウントをブロック&quot;,&quot;Enter&quot;:&quot;ツイートの詳細を開く&quot;,&quot;o&quot;:&quot;画像を開く&quot;,&quot;?&quot;:&quot;ショートカットのヘルプ&quot;,&quot;j&quot;:&quot;次のツイート&quot;,&quot;k&quot;:&quot;前のツイート&quot;,&quot;Space&quot;:&quot;ページ下へ移動&quot;,&quot;.&quot;:&quot;最新ツイートを読み込む&quot;,&quot;g h&quot;:&quot;ホーム&quot;,&quot;g e&quot;:&quot;話題を検索&quot;,&quot;g n&quot;:&quot;通知&quot;,&quot;g r&quot;:&quot;@ツイート&quot;,&quot;g p&quot;:&quot;プロフィール&quot;,&quot;g l&quot;:&quot;いいね&quot;,&quot;g i&quot;:&quot;リスト&quot;,&quot;g m&quot;:&quot;ダイレクトメッセージ&quot;,&quot;g s&quot;:&quot;設定&quot;,&quot;g b&quot;:&quot;ブックマーク&quot;,&quot;g u&quot;:&quot;プロフィールページを見る...&quot;,&quot;g d&quot;:&quot;表示設定&quot;}" aria-hidden="false" style="min-height: 789px;">
          <!-- 省略 -->
          <main role="main" class="css-1dbjc4n r-1habvwh r-16y2uox r-1wbh5a2">
            <div class="css-1dbjc4n r-150rngu r-16y2uox r-1wbh5a2 r-1obr2lp">
              <div class="css-1dbjc4n r-aqfbo4 r-16y2uox">
                <div class="css-1dbjc4n r-1oszu61 r-1niwhzg r-18u37iz r-16y2uox r-1wtj0ep r-2llsf r-13qz1uu">
                  <div class="css-1dbjc4n r-14lw9ot r-1tlfku8 r-1ljd8xs r-13l2t4g r-1phboty r-1jgb5lz r-11wrixw r-61z16t r-1ye8kvj r-13qz1uu r-184en5c" data-testid="primaryColumn">
                    <div class="css-1dbjc4n">
                      <!-- 省略 -->
                      <div class="css-1dbjc4n r-1jgb5lz r-1ye8kvj r-13qz1uu">
                        <div class="css-1dbjc4n">
                          <div class="css-1dbjc4n">
                            <section aria-labelledby="accessible-list-1" role="region" class="css-1dbjc4n">
                              <h1 aria-level="1" dir="auto" role="heading" class="css-4rbku5 css-901oao r-4iw3lz r-1xk2f4g r-109y4c4 r-1udh08x r-wwvuq4 r-u8s1d r-92ng3h" id="accessible-list-1">ホームタイムライン</h1>
                              <div aria-label="タイムライン: ホームタイムライン" class="css-1dbjc4n">
                                <div style="padding-bottom: 0px;">
                                  <div style="padding-top: 0px; padding-bottom: 9800px;">
                                    <!-- ここから1つのTweetはじまり -->
                                    <div>
                                      <div class="css-1dbjc4n r-my5ep6 r-qklmqi r-1adg3ll">
                                        <article aria-haspopup="false" role="article" data-focusable="true" tabindex="0" class="css-1dbjc4n r-1loqt21 r-1udh08x r-o7ynqc r-1j63xyz">
                                          <div class="css-1dbjc4n">
                                            <div class="css-1dbjc4n r-1j3t67a">
                                              <div class="css-1dbjc4n r-18u37iz r-thb0q2">
                                                <div class="css-1dbjc4n r-1iusvr4 r-16y2uox r-5f2r5o r-m611by">
                                                  <!-- 省略（●●がリツイート、など） -->
                                                </div>
                                              </div>
                                              <div class="css-1dbjc4n r-18u37iz r-thb0q2" data-testid="tweet">
                                                <div class="css-1dbjc4n r-1awozwy r-18kxxzh r-5f2r5o" style="flex-basis: 49px;">
                                                  <!-- 省略（ユーザーアイコン） -->
                                                </div>
                                                <div class="css-1dbjc4n r-1iusvr4 r-16y2uox r-1777fci r-5f2r5o r-1mi0q7o">
                                                  <div class="css-1dbjc4n">
                                                    <!-- 省略（ユーザー名など） -->
                                                  </div>
                                                  <div class="css-1dbjc4n">
                                                    <div class="css-1dbjc4n">
                                                      <div lang="ja" dir="auto" class="css-901oao r-hkyrab r-gwet1z r-a023e6 r-16dba41 r-ad9z0x r-bcqeeo r-bnwqim r-qvutc0">
                                                        <span class="css-901oao css-16my406 r-gwet1z r-ad9z0x r-bcqeeo r-qvutc0">ここにTweetの文字列が表示されます！！</span>
                                                      </div>
                                                    </div>
                                                    <div class="css-1dbjc4n">
                                                      <!-- 省略（リンク・画像など） -->
                                                    </div>
                                                    <div role="group" class="css-1dbjc4n r-18u37iz r-1wtj0ep r-156q2ks r-1mdbhws">
                                                      <!-- 省略（リツイートアイコンなど） -->
                                                    </div>
                                                  </div>
                                                </div>
                                              </div>
                                            </div>
                                          </div>
                                        </article>
                                      </div>
                                    </div>
                                    <!-- ここまで1つのTweet -->
                                    <!-- あとはこのTweetブロックの繰り返し -->
                                  </div>
                                </div>
                              </div>
                            </section>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="css-1dbjc4n r-aqfbo4 r-zso239 r-1jocfgc" data-testid="sidebarColumn">
                    <!-- 省略（サイドメニューなど） -->
                  </div>
                </div>
              </div>
            </div>
          </main>
        </div>
      </div>
    </div>
  </div>
  <!-- 省略（scripts） -->
</body>
```
<b>なにこれ長っ！深っ！複雑！</b>

「ここにTweetの文字列が表示されます！！」っていうところが実際のTweetの文字列なんですが、深すぎる。複雑すぎる！すごくやめたいと思いました笑

ここは泥臭く構造を辿っていくしかないですね...特出すべきは各Tweetの文字列の`span`の直前の`div`タグにだけ`lang="ja"`属性がついていることでしょうか。これをキーワードにして属性を取り出し、その中のテキストを`text()`メソッドを使って取り出してみます。

```javascript:js/content.js
...
function get_tweets() {
  tweets = []
  $("div[lang='ja']").each(function(i, target) {
    tweets.push($(target).text())
  })
  return tweets
}
...
```
これで表示されているTweetの文字列を配列で関数の呼び出し元に返してあげるプログラムを組むことができました。

このtweetsをCOTOHA APIで解析するために次はアクセストークンの払い出しを実行します。

## get_access_token
ここからはCOTOHA APIと通信を行っていくのですが、CROBが効いてしまうので`content.js`から`background.js`にメッセージを送り、`background.js`経由でCOTOHA APIを利用するようにします。

```js:content.js
...
function get_access_token() {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(
      {
        contentScriptQuery: "get_access_token"
      },
      (response) => { resolve(response) }
    )  
  })
}
...
```
`content.js`からは`contentScriptQuery: "get_access_token"`をキーワードにしてメッセージを送ります。

```js:background.js
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    cotoha_api_base_url = "https://xxxxxxxxxx.com/" // COTOHA APIのアカウントホームページで確認

    // アクセストークンの払い出し
    if (request.contentScriptQuery == "get_access_token") {
    cotoha_access_token_publish_url = "https://xxxxxxxxxx.com/access_token" // COTOHA APIのアカウントホームページで確認
    cotoha_client_id = "xxxxxxxxxx" // COTOHA APIのアカウントホームページで確認
    cotoha_client_secret = "xxxxxxxxxx" // COTOHA APIのアカウントホームページで確認

    fetch(cotoha_access_token_publish_url, {
      method: 'POST',
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        grantType: "client_credentials",
        clientId: cotoha_client_id,
        clientSecret: cotoha_client_secret
      })
    })
    .then(response => response.json())
    .then(body => sendResponse(body.access_token))
    }

    return true
  }
)
```
`background.js`では、`chrome.runtime.onMessage.addListener`でメッセージを受け取ります。`request`の中に先ほどの`contentScriptQuery`といった引数を受け取ることができ、`sendResponse`にデータを入れることでメッセージをお返しします。

APIは`fetch`を使って実行しています。
使い方は「[Fetch を使う - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)」などを参考に。
APIはかなりシンプルなので、特に迷うことはないかと思います。 => 「[アクセストークン取得 | リファレンス | COTOHA API](https://api.ce-cotoha.com/contents/reference/accesstoken.html)」

最後に`sendResponse(body.access_token)`でAPIのレスポンスから`access_token`を取得して`content.js`に投げ返してあげています。

これで他のAPIを使う準備ができたので、次はTweetsのネガポジ判定をしてみましょう！

## get_tweet_sentiment
`content.js`では`tweets`と`access_token`を取得した状態になっており、`get_tweet_sentiment`が実行されるタイミングでは`tweets`からひとつずつ`tweet`を取り出して`get_tweet_sentiment`に`access_token`と`tweet`を引き渡しています。

今回もAPIはかなりシンプルですので、とくに難しいところはないです。 => [APIリファレンス | COTOHA API](https://api.ce-cotoha.com/contents/reference/apireference.html)（感情分析）
`sentence`に対象の文章を与えるだけで、その文章の感情分析や感情を強く感じる単語を抽出してくれます。レスポンスとして、`result.sentiment`でその文章が『Positive』なのか『Negative』なのか『Neutral』なのかを教えてくれます。

```js:content.js
...
function get_tweet_sentiment(access_token, tweet) {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(
      {
        contentScriptQuery: "get_tweet_sentiment",
        access_token: access_token,
        tweet: tweet
      },
      (response) => { resolve(response) }
    )
  })
}
...
```

```js:background.js
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    ...
    // ツイートのネガポジ判定
    if (request.contentScriptQuery == "get_tweet_sentiment") {
      cotoha_sentiment_api_endpoint = "nlp/v1/sentiment"

      fetch(cotoha_api_base_url + cotoha_sentiment_api_endpoint, {
        method: 'POST',
        headers: {
          "Content-Type": "application/json; charset=UTF-8",
          Authorization: `Bearer ${request.access_token}`
        },
        body: JSON.stringify({
          sentence: request.tweet
        })
      })
      .then(response => response.json())
      .then(body => {
        sendResponse(body.result.sentiment)
      })
    }
    ...
    return true
  }
)
```
`content.js`から送っている`tweet`を検査対象として、`sendResponse`で感情分析結果を`content.js`に返しています。
感情分析結果は`Positive`, `Neutral`, `Negative`の3種類から判定されますが、今回は`Positive`なTweetであふれかえすのが目的なので`Neutral`と`Negative`を松岡修造語録へ変換する対象とします。

```content.js
...
        get_tweet_sentiment(access_token, tweet).then(sentiment => {

          // tweetがポジティブでないと判断した場合、処理を継続。
          if (sentiment != "Positive") {

            // ポジティブでないtweetと近しい内容の松岡修造語録を取得する（`get_similar_shuzo_quote`）
            get_similar_shuzo_quote(access_token, tweet).then(new_tweet => {

              // ポジティブでないtweetの内容を松岡修造語録に変換する（`change_tweet`）
              change_tweet(tweet, new_tweet)
            })
          }
        })
...
```
そのため、`content.js`でも`!= "Positive"`として、Positiveでなければその次の`get_similar_shuzo_quote`に進むようにコーディングしています。

## get_similar_shuzo_quote
次に、Positiveではないと判定されたtweetに対して、一番内容の近い松岡修造語録を取得する関数を作っていきます。
COTOHA APIの<b>類似度算出API</b>を利用しますが、これも大変シンプルで２つの文章（`s1`, `s2`）を与えるだけでその2つの文章の内容の類似度を教えてくれます。 => [APIリファレンス | COTOHA API](https://api.ce-cotoha.com/contents/reference/apireference.html)（類似度算出）

松岡修造さんのポジティブな名言は色々とあるのですが、COTOHA APIの無料枠では1日1000リクエスト/APIまでと決められていることもあり、5つに厳選してみました。

> <b>100回叩くと壊れる壁があったとする。でもみんな何回叩けば壊れるかわからないから、90回まで来ていても途中であきらめてしまう。</b>

> <b>みんな！！竹になろうよ。竹ってさあ台風が来てもしなやかじゃない。台風負けないんだよ。雪が来てもね。おもいっきりそれを跳ね除ける！！力強さがあるんだよ。そう、みんな！！！竹になろう！！！バンブー！！！</b>

> <b>ベストを尽くすだけでは勝てない。僕は勝ちにいく。</b>

> <b>もっと熱くなれよ！熱い血燃やしてけよ！！人間熱くなったときがホントの自分に出会えるんだ！！</b>

> <b>一番になるっていったよな？日本一なるっつったよな！ぬるま湯なんかつかってんじゃねぇよお前！！</b>

どれも元気が出ますね。
Positiveと判断されなかったtweetとこの5つの名言をひとつずつ類似度算出APIで比較し、最も類似度の高かった名言を返却する関数を作ります。

```js:content.js
...
function get_similar_shuzo_quote(access_token, tweet) {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(
      {
        contentScriptQuery: "get_similar_shuzo_quote",
        access_token: access_token,
        tweet: tweet
      },
      (response) => { resolve(response) }
    )
  })
}
...
```
`content.js`からはPositive判定されなかったtweetをメッセージングします。

```js:background.js
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
...
    // ポジティブでないツイートを松岡修造の熱い名言から最も近しいものに変える
    if (request.contentScriptQuery == "get_similar_shuzo_quote") {
      cotoha_similarity_api_endpoint = "nlp/v1/similarity"

      function get_similarity(s1, s2) {
        return new Promise((resolve, reject) => {
          fetch(cotoha_api_base_url + cotoha_similarity_api_endpoint, {
            method: 'POST',
            headers: {
              "Content-Type": "application/json; charset=UTF-8",
              Authorization: `Bearer ${request.access_token}`
            },
            body: JSON.stringify({
              s1: s1,
              s2: s2
            })
          })
          .then(response => response.json())
          .then(body => resolve([s2, body.result.score]))
        })
      }

      Promise.all([
        get_similarity(request.tweet, "100回叩くと壊れる壁があったとする。でもみんな何回叩けば壊れるかわからないから、90回まで来ていても途中であきらめてしまう。"),
        get_similarity(request.tweet, "みんな！！竹になろうよ。竹ってさあ台風が来てもしなやかじゃない。台風負けないんだよ。雪が来てもね。おもいっきりそれを跳ね除ける！！力強さがあるんだよ。そう、みんな！！！竹になろう！！！バンブー！！！"),
        get_similarity(request.tweet, "ベストを尽くすだけでは勝てない。僕は勝ちにいく。"),
        get_similarity(request.tweet, "もっと熱くなれよ！熱い血燃やしてけよ！！人間熱くなったときがホントの自分に出会えるんだ！！"),
        get_similarity(request.tweet, "一番になるっていったよな？日本一なるっつったよな！ぬるま湯なんかつかってんじゃねぇよお前！！")
      ]).then((results) => {
        // 類似度を比較して一番類似している名言をsendResponseで返却する。
        most_similar_quote = ["", 0] // [名言, 類似度(score)]
        for (result of results) {
          if (most_similar_quote[1] < result[1]) {
            most_similar_quote = result
          }
        }
        sendResponse(result[0])
      })
    }
    return true
  }
)
```
これはかなり試行錯誤しました...javascript不慣れなもので非同期処理がかなりつまづいた...

類似度算出APIについては先ほどものべた通りとてもシンプルです。APIを叩く部分を`get_similarity`に関数化しました。
今回は5つの文章との比較が全部終わったあとで類似度を比較して一番近い名言を決める、ということがしたかったのですが、javascriptは非同期に処理を勧めてくれるおかげで、他の言語を書いているイメージのまま書くとAPIのレスポンスが返ってくる前に次の処理が進んでしまうなんてことがありました。

これを回避するために、今回は`Promise`と`Promise.all`を使っています。
細かい使い方は「[JavaScriptのPromise - Qiita](https://qiita.com/ysk_1031/items/888a84cb259cec4e0625)」を参考にさせていただきました！

`Promise.all`のおかげで、5つの名言とtweetの類似度比較が全て終わってから類似度の比較を行うことができるようになっています。
比較の結果、最も類似度の高い名言が`content.js`にレスポンスとしてメッセージングされています。

さて、これでPositiveではないTweetに変わる松岡修造語録をゲットすることができました！
あとはこれをViewに反映するだけです！！

## change_tweet
最後にDOM操作です。Positiveではないと判定された`tweet`の文字列を、それに類似した松岡修造語録`new_tweet`に書き換えてあげます。
ついでに文字も真っ赤に燃やしてみましょう。

```content.js
...
function change_tweet(tweet, new_tweet) {
  target = $(`div[lang='ja']:contains(${tweet})`)
  $(target).text(new_tweet)
  $(target).css("color", "red")
}
...
```
最初の方で解析した通り、Tweetの文字列は`lang='ja'`属性がついている`div`タグの配下にあります。`contains`を使ってPositiveではないと判断された`tweet`をもつ要素を探して`target`に格納します。
そしてその`target`に対して`text`で文字列を松岡修造語録に変え、`css`でテキストカラーを真っ赤に変えてみました。

# あんなにネガティブだった僕のプロフィールページがこんなにもポジティブに！！

さて、ここまでできたので、Chrome extensionを`chrome://extensions/`からインストールして動作確認をしてみます！
他の方のtweetを出すのもあれなので自分のアカウントのプロフィールページでやっていますが、トップページでもどうように動きます。

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254216/c808fec5-a61b-e168-5407-7adbf8650c1d.gif)

各tweetに対して非同期で処理を実行しているのでAPIの処理などが終わったtweetから反映されていくんですね。

# まとめ
あんなにネガティブだった僕のTLを<b>COTOHA API</b>と<b>松岡修造さん</b>のおかげでこんなにもポジティブで熱いTLに変えることができました。
類似度のところは毎回 tweets x quotes 回API叩くのも無駄ですね...Doc2Vecとかならうまくいくのかな...（初心者）

とりあえず自信を持って生きていこうと思います！！

# ソースコード
今回のソースコードをGitHubにあげました。

- https://github.com/at946/positter