後で読みたい記事をブックマークレットを使ってワンクリックでSlackに積んでおく(CORS回避)

# はじめに
情報収集をネット記事で行う方が多いと思いますが、良い記事を見つけたけど時間がなくてあとで読みたい時はどうしていますか？

私は対象記事のURLを記事を積んでおくためのSlackチャンネルに投稿しておいて、あとでまとめて読んでいます。
Slackに飛ばしておくとPCでもスマホでもすぐに確認できるので便利です。

Slackに記事を積んでおく時、以前は下記のような手順で保存していました。
1. URLをコピー
2. Slackを開く
3. 記事チャンネルにURLを貼り付け
4. 投稿

エンジニアは面倒くさがりの方が多いと思いますので、4ステップもあると改善したくなりますよね。
ということで手順を簡略化する方法を検討しました。

# 方針
やりたいことは、PCとスマホで開いたネット記事をSlackへ送信です。
ちなみにスマホのブラウザには「共有」ボタンがあり、そこからSlackを選択して、、、と進んでいくとSlackへ通知することができます。
ただ、使ったことある方はわかると思いますが動作がもっさりしていたり多機能なので共有する先を選んだりでクリック数はあまり削減されません。

そこで今回はブックマークレットで実現することにしました。
ブックマークレットであればPCでもスマホでも動作するはずです。
またChromeであればブックマークをPCとスマホで共有することもできるので便利です。

# 作り方
## Slack通知用URL取得
まずは通知するためのURLを取得します。
通知したいチャンネルの⚙️(チャンネル設定)をクリックして、「アプリを追加する」をクリックします。
<img width="441" alt="スクリーンショット 2019-08-22 14.35.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83424/e66ed430-8847-6420-7505-4ce1b1b5bd3d.png">

incoming-webhookをインストールします。
（画像では「表示する」になっていますが、未インストールの場合「インストール」になっていると思います）
<img width="722" alt="スクリーンショット 2019-08-22 14.39.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83424/8b8cd88d-7c3f-b57e-625c-cf957fba9a97.png">

設定画面でチャンネルへの投稿を指定のチャンネルを指定します。
通知に必要なWebhookURLやその仕様もこのページに載っています。※このURLがバレると誰でも通知できるようになってしまうので管理は慎重に。
使い方や例なども載っているので親切ですね。
<img width="978" alt="スクリーンショット 2019-08-22 14.41.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83424/3a1204e3-9f34-304e-1de5-10bd43d78fb5.png">

## ブックマークレットを実装
ブックマークレットで行うことは下記。
1. 現在開いているページのURLを取得する。`location.href`
2. WebhookURLに必要な情報をつけてリクエスト

下記のように実装しました。
エラーの時はアラートを表示するようにしています。

```javascript
javascript:(function(){
  const url = ''; // TODO: set WebhookURL
  fetch(url, {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ text: location.href })
  }).then(response => console.log)
    .catch(error => alert('error!!'));
})()
```

それではChromeで動作確認。デベロッパーツールのコンソールに貼り付ければ簡単に動作確認できます。
Qiitaのトップページで動作確認してみます。https://qiita.com がSlackに通知されれば成功です。
<img width="1156" alt="スクリーンショット 2019-08-27 17.06.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83424/e386b773-d4b6-53a2-8e8e-7aa11aa001e3.png">

実行したところ下記エラーが発生しました。

```
Access to fetch at 'https://hooks.slack.com/services/xxx/yyy/zzz' from origin 'https://qiita.com' has been blocked by CORS policy:
Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
VM3507:3 POST https://hooks.slack.com/services/xxx/yyy/zzz net::ERR_FAILED
(anonymous) @ VM3507:3
(anonymous) @ VM3507:11
A bad HTTP response code (404) was received when fetching the script.
```

CORS(Cross-Origin Resource Sharing)に引っかかってしましました。。。
参考：https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

SlackのWebhookURLなんて色々なところから呼びたいんだから広く許可しておいてくれよと思ったが、なっていないものは仕方ない。
ajaxで呼べないならブックマークレットはきびしいか、、、と諦めかけていたんですがググってみたらstackoverflowに答えが載っていました。
`'Content-Type': 'application/json'`を削除せよとのこと。
https://stackoverflow.com/questions/45752537/slack-incoming-webhook-request-header-field-content-type-is-not-allowed-by-acce

ということでcontent-typeを削除した下記ソースで実行しました。

```javascript
javascript:(function(){
  const url = ''; // TODO: set WebhookURL
  fetch(url, {
    method: 'POST',
    headers: {
      'Accept': 'application/json'
    },
    body: JSON.stringify({ text: location.href })
  }).then(response => console.log)
    .catch(error => alert('error!!'));
})()
```
無事届きました！！
<img width="1375" alt="スクリーンショット 2019-08-27 18.05.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83424/9c7e1360-3bb2-988b-f0b6-95ee4b01692d.png">

## CORSの考察
結果的には１行消すだけで動くようになったので独力で解決したかったなーと思いつつ、動くようになった理由を考察。
stackoverflowになぜ動くようになるのか理由も書いてありますが、よくエラーメッセージを読んでみると

```
Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
```
と書かれています。
preflightで許可されていないcontent-typeだと怒られているようです。
CORSの仕様を改めて読み返してみると、POSTの場合はpreflightを送らずに送る方法もあります。
そこに気づき、content-typeを色々いじって動作確認してみればよかったのですが、jsonなんだから`'Content-Type': 'application/json'`は必須でしょと思って何も試さなかった。。。
改めて「エラーメッセージをきちんと読むこと」と「トライアンドエラー」の重要さを認識しました。

## ブックマークに登録
ブックマークレットが無事完成したのでブックマークを登録します。
私はChromeを使っているのでブックマークマネージャーを開きブックマークレットを追加します。
<img width="511" alt="スクリーンショット 2019-08-28 16.04.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83424/b1403ed6-b2e4-bece-ed1c-60f15a37b08e.png">

* PC(mac book / Chrome)  
あとで読みたいページを開いて、登録したブックマークをクリックすることでSlackへ通知することができました。
* スマホ(Android / Chrome)  
単純にページを開いてブックマークをクリックしても動作しなかったのですが、アドレスバーにブックマーク名"toSlack"を入れると無事動作しました。
アドレスバーにブックマーク名を入れて起動できることを初めて知りました。結構便利！

![Screenshot_20190827-181908_Chrome.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83424/32beefe7-ae5b-fe6d-d090-917c8f2e0a60.jpeg)

# 最後に
このブックマークレットで今まで４ステップかかっていた作業が１ステップになりました。
体感では1回１０秒くらいかかっていた作業が1秒くらいになった気がします。
1日２記事あとで読みたいと思った場合、年間では`(10 - 1) * 2 * 365 = 6,570秒 ≒ 1.8時間`の短縮ですね！やったね！！
（まあ、このブックマークレット作成に1.8時間以上はかかっているので実際に元がとれるのはずっと先ですが、色々知見が得られたのでよしとしましょう）