Twitterからマシュマロに飛べる Chrome拡張機能作ってみた

Twitterのプロフィールに、マシュマロのリンクを追加するクローム拡張機能を作りました。
リンクが生成されるので、簡単にマシュマロが投げられます。

※マシュマロは、匿名でメッセージや質問を送ることができるサービスです。


![wi-mini_dsub15-cbl-l.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257943/dea28adf-dc5a-764f-ab17-c76803f23b20.jpeg)

拡張機能としてリリースしているので、よろしければこちらのリンクからDLしてください！
→[TwitterToMarshmallow - chromeウェブストア](https://chrome.google.com/webstore/detail/twittertomarshmallow/pibnbgfmabhhjkjmajoonedfmomlggmi?authuser=0&hl=ja)

ソースコードはこちら→[Nemurino-kai/TwitterToMarshmallow](https://github.com/Nemurino-kai/TwitterToMarshmallow)

マシュマロかわいいね
<img width="64" alt="128.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257943/f5cc1be3-2259-b545-3321-a5f9d514a643.jpeg">

以下、拡張機能の作り方など、技術的な話をしていきます。

## 大まかな仕組み
1.ドメインがtwitter.comならjsを起動、ページを監視
2.ページに変化があったら、ページ上にTwitterのプロフィールがあるか確認する
3.プロフィールがあるなら、ユーザーネームを抽出し、マシュマロへのURLを生成する
4.マシュマロのURLに接続し、正しく接続出来たら、Twitterのプロフィールにマシュマロのリンクを追加する

## 拡張機能の作りかた
拡張機能の作成に最低限必要なのは、manifest.jsonとjsファイルの2つです。

### manifest.json
manifest.jsonは、拡張機能で使用するスクリプトなどの情報を記載するファイルです。

作成したファイルは以下の通りです。
ここでは、matchesに当てはまるサイト(今回だとtwitter.com)を開いたときに、指定したスクリプトが動作するようになっています。

```json:manifest.json
{

  "description": "Twitter内に、マシュマロを投げるリンクを追加",
  "manifest_version": 2,
  "name": "TwitterToMarshmallow",
  "version": "1.0.0",

  "permissions": [
    "https://*.twitter.com/",
    "https://marshmallow-qa.com/"
  ],

  "browser_action": {
    "default_icon": "images/128.png",
    "default_title": "TwitterToMarshmallow"
  },

  "content_scripts": [
    {
      "matches": ["https://*.twitter.com/*"],
      "js": ["index.js"]
    }
  ],
  "icons": {
    "128": "images/128.png",
    "32": "images/32.png",
    "16": "images/16.png"
  },
  "web_accessible_resources": [

    "images/*"
  
  ],

  "content_security_policy": "script-src 'self' https://marshmallow-qa.com/; object-src 'self' "
}
```

### jsファイル
使用するjsファイルは、manifestで指定した名前で作成します。
作成したファイルは次の通りです、要所要所説明していきます。

```javascript:index.js
'use strict';

function replaceTwitterBio() {

    // UserProfileHeader_Itemsの要素があるか
    const profile = document.querySelector("[data-testid='UserProfileHeader_Items']");
    // 無いならば、処理を終わる
    if (!profile) {
        return;
    }

    // URLからscreen_nameの抽出
    const user_name = location.pathname.split("/")[1];
    // screen_nameが取り出せないorすでにリンク生成済み
    if (!user_name || document.body.getAttribute("data-user_name") === user_name) {
        return;
    }
    document.body.setAttribute("data-user_name", user_name);

    // 前に追加した要素があるなら消す
    const items = profile.getElementsByClassName("question")
    for(let i = 0; i < items.length; ++i) {
        profile.removeChild(items[i]);
    }

    const xhr = new XMLHttpRequest();
    xhr.onload = function() {

        // 正しく読み込めるなら、リンクの生成を行う
        if (this.status == 200) {
        
        // 説明文の生成
        const description = user_name + "にマシュマロを投げる";

        // リンクの生成
        const icon = chrome.runtime.getURL("images/20marsh.png")
        const question = '<div class="question"><img src=' + icon + '><a href="https://marshmallow-qa.com/' + user_name + '" target="_blank" role="link" data-focusable="true" style="color:#00acee;"> <strong>' + description + '</strong></a></div>';

        // profileにリンクを挿入する
        profile.insertAdjacentHTML("afterbegin", question);
        }

    };
    xhr.open("GET", "https://marshmallow-qa.com/" + user_name.toLocaleLowerCase());
    xhr.send();


}

const observer = new MutationObserver(replaceTwitterBio)
observer.observe(document.body, {childList: true, subtree: true});
```

#### 1.ページを監視する
```javascript
const observer = new MutationObserver(replaceTwitterBio)
observer.observe(document.body, {childList: true, subtree: true});
```
MutationObserverは、「Mutation（変化）」を「Observe（監視）」するオブジェクトです。
変化があったとき、ここではreplaceTwitterBio関数が実行されます。

#### 2.ページ上にTwitterのプロフィールがあるか確認する
```javascript
function replaceTwitterBio() {

    // UserProfileHeader_Itemsの要素があるか
    const profile = document.querySelector("[data-testid='UserProfileHeader_Items']");
    // 無いならば、処理を終わる
    if (!profile) {
        return;
    }
```
document上に、data-testidがUserProfileHeader_Itemsの要素があるかを確認します。
UserProfileHeader_Itemsは、プロフィール内の誕生日とかを表示している部分です。
↓コレ
![non-threshposidist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257943/9dca0a8c-a316-efbb-9ffd-db40d56f4a24.png)

これが無かったらリンクを表示する部分がないので、その時点で処理を終えます。

#### 3.プロフィールがあるなら、ユーザーネームを抽出する
```javascript
    // URLからscreen_nameの抽出
    const user_name = location.pathname.split("/")[1];
```
location.pathnameは、現在ページURLのパス名を参照します。
[twitter.com/(ユーザー名)](https://twitter.com/DoubleZ_prog) のような文字列が返ってくるので、「/」で文字列を分割し、ユーザー名を抽出しています。

#### 4.マシュマロのURLに接続し、正しく接続出来たら、Twitterのプロフィールにマシュマロのリンクを追加する
```javascript

    const xhr = new XMLHttpRequest();
    xhr.onload = function() {

        // 正しく読み込めるなら、リンクの生成を行う
        if (this.status == 200) {
        
        // 説明文の生成
        const description = user_name + "にマシュマロを投げる";

        // リンクの生成
        const icon = chrome.runtime.getURL("images/20marsh.png")
        const question = '<div class="question"><img src=' + icon + '><a href="https://marshmallow-qa.com/' + user_name + '" target="_blank" role="link" data-focusable="true" style="color:#00acee;"> <strong>' + description + '</strong></a></div>';

        // profileにリンクを挿入する
        profile.insertAdjacentHTML("afterbegin", question);
        }

    };
    xhr.open("GET", "https://marshmallow-qa.com/" + user_name.toLocaleLowerCase());
    xhr.send();
```
XMLHttpRequestは、外部との通信を行うオブジェクトです。
取得したユーザー名を用いてURLを生成、接続を行います。
その結果、ステータス番号が200(OK)ならば、リンクを生成し、```profile.insertAdjacentHTML("afterbegin", question);```によって、プロフィールにリンクを挿入します。
(URLが存在しない場合、404(Not Found)が返ってくるので、リンクは生成しません)

## おわりに
Web開発にも手を出してみたいなぁというお気持ちでこの拡張機能を作りました。
他の方の進捗も見るのが楽しみです、それでは:wave: