GithubのアクティビティみたいなのをQiitaにも搭載する拡張機能

# はじめに
Githubのこういうやつ↓がQiitaの記事にもあるといいなーと思いこんなものを作ってみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272803/aa1a257b-ba95-aecc-4abe-dd3578f58990.png)

# 作ったもの
【Chrome拡張機能】QiitaBeat
https://github.com/tonio0720/QiitaBeat
https://chrome.google.com/webstore/detail/qiitabeat/lopmlafnhcoggbikfjclehpmedhmhpgl/

拡張機能をいれるとこんな感じで記事のLGTMの推移を見ることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272803/d315d62f-2b02-b78e-1441-9cdfa69f12c8.png)

# 設定方法
## 拡張機能の追加
https://chrome.google.com/webstore/detail/qiitabeat/lopmlafnhcoggbikfjclehpmedhmhpgl/

### QiitaAPITokenの発行
[Qiita](https://qiita.com/) → ログイン → 右上のメニュー（自分のアイコン） → 設定
![image.png](https://qiita-image-store.s3.amazonaws.com/0/272803/7c0c221d-0540-6ede-0bc8-ff0382e49be4.png)

→アプリケーション → 個人用アクセストークン → 新しくトークンを発行する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/272803/12fd192b-a719-d987-cd80-5c65aff8c93a.png)

→説明：QiitaBeat、read_qiitaのみにチェック
![image.png](https://qiita-image-store.s3.amazonaws.com/0/272803/93e77714-7b73-baae-4848-7cb8936d828a.png)

→個人用アクセストークンをどこかにメモ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/272803/9bb815f4-b147-16a9-544e-dde2840e4e07.png)

## Tokenの設定
Chromeの右上から拡張機能のアイコンをクリック→ オプション
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272803/a81ae764-4dd7-fa91-4a07-83c0471b4456.png)

→先ほどメモしたアクセストークンを貼り付け → 保存
![image.png](https://qiita-image-store.s3.amazonaws.com/0/272803/59568bf0-be94-f94b-3653-93affc0c3cd1.png)

## 設定完了
Qiitaの記事にアクセスするとLGTMの推移が表示されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272803/caab1325-6f6a-c11a-0b93-4e5f301aff2a.png)

# ソース
```{js:app.js}
import axios from 'axios';
import $ from 'jquery';
import moment from 'moment';

import './style.scss';

// LGTMボタンを探す
let intervalid = setInterval(() => {
    if ($('.it-Actions_item-like').length > 0) {
        clearInterval(intervalid);
        setBeat();
    }
}, 100);

// storageからトークンを取得
async function getToken() {
    return new Promise((resolve) => {
        chrome.storage.sync.get({ token: '' }, (items) => {
            resolve(items.token);
        });
    });
}

// QiitaAPIで記事情報取得
async function getItem(itemId, token) {
    const response = await axios.get(`https://qiita.com/api/v2/items/${itemId}`, {
        headers: {
            Authorization: `Bearer ${token}`
        }
    });

    return response.data;
}

// QiitaAPIでLikeをすべて取得
async function getAllLikes(itemId, token) {
    const limit = 100;
    let page = 1;
    let likes = [];

    // APIで100件ずつ取得する
    let totalCnt;
    do {
        const response = await axios.get(`https://qiita.com/api/v2/items/${itemId}/likes?page=${page}&per_page=${limit}`, {
            headers: {
                Authorization: `Bearer ${token}`
            }
        });

        totalCnt = Number(response.headers['total-count']);
        likes = likes.concat(response.data);
    } while (page++ * limit < totalCnt);

    return likes;
}

async function setBeat() {
    // URLからitemidを取得
    const { pathname } = new URL(location.href);
    const { 2: itemid } = pathname.match(/\/(.*?)\/items\/(.*?)($|\/)/) || [];

    // トークン取得
    const token = await getToken();

    if (!token) {
        // トークンがないということは設定ができていない
        let $el = $(`
            <div class="qiitabeat-setting">
                <a>設定が必要です！</a>
            </div>
        `).insertAfter('.it-Actions_item-like');
        $el.find('a').click(() => {
            chrome.runtime.sendMessage({ options: true });
        });

        return;
    }

    // データ取得
    const item = await getItem(itemid, token);
    const likes = await getAllLikes(itemid, token);

    // 週ごとに集計
    const data = {};
    likes.forEach(({ created_at: createdAt }) => {
        const ymd = moment(createdAt).format('YYYY-WW');
        if (!data[ymd]) {
            data[ymd] = 0;
        }
        data[ymd]++;
    });

    const yws = [];
    const now = moment().add(1, 'days');
    const m = moment(item.created_at).add(-1, 'days');
    while (m < now) {
        yws.push(m.format('YYYY-WW'));
        m.add(1, 'days');
    }

    // チャート用のデータ作成
    const chartData = yws.map((ymd) => data[ymd] || 0);
    const max = Math.max(10, ...chartData);

    const points = chartData.map((value, i) => {
        return `${i * (50 / (chartData.length - 1))}, ${50 - (value / max) * 50}`
    });

    // グラフを挿入
    $(`
        <div class="qiitabeat-action">
            <svg width="50" height="50">
                <defs>
                    <linearGradient id="gradient" x1="0" x2="0" y1="1" y2="0">
                        <stop offset="10%" stop-color="#a89be9"></stop>
                        <stop offset="33%" stop-color="#6340c4"></stop>
                        <stop offset="66%" stop-color="#4e30a1"></stop>
                        <stop offset="90%" stop-color="#39216e"></stop>
                    </linearGradient>
                    <mask id="sparkline" x="0" y="0" width="50" height="50">
                        <polyline
                            points="${points.join(' ')}"
                            fill="transparent" stroke="#8cc665" stroke-width="2">
                        </polyline>
                    </mask>
                </defs>
                <g>
                    <rect width="50" height="50"
                        style="stroke: none; fill: url(#gradient); mask: url(#sparkline)"></rect>
                </g>
            </svg>
        </div>
    `).insertAfter('.it-Actions_item-like');
}
```

```{css:style.css}
.qiitabeat-action {
    width: 50px;
    font-size: 14px;
    margin-bottom: 16px;
    margin-right: 24px;
    transition: color 0.1s ease-out, background-color 0.1s ease-out;
}

.qiitabeat-chart {
    width: 100%;
    height: 50px;
}

.qiitabeat-setting {
    text-align: center;
    width: 50px;
    font-size: 14px;
    margin-bottom: 16px;
    margin-right: 24px;
    cursor: pointer;
}
```

# おわりに
この機能があると、直近でどのくらいLGTMされたのかが分かるので良いと思いました。
記事のなかでも投稿直後にLGTMが急増するものや、じわじわとLGTMを集めるものなどと見分けることができるのも面白い点です。