「Slackの投稿、たくさんあって見逃してしまう…」を防げるかもしれない "ピックアップチャンネル" "ストックスレッド" （と読んでるハック）

本記事は [Slack Advent Calendar 2021](https://qiita.com/advent-calendar/2021/slack) の12日目です。
前日は @torifukukaiou さんによる「[私が最近チョコチョコっとつくったSlackアプリのソースコードを全文掲載 （2021/12/11）](https://qiita.com/torifukukaiou/items/74567d2c3302a5d574ab)」でした。
（記事終わりでのご紹介、ありがとうございました 🙇 Advent Calendar 内でのバトン的な表記、完全に忘れていました…。Qiita上でのメンション通知をいただくのも珍しく、新鮮な気持ちになりました）

---

こんにちは。[**Togetter**](http://togetter.com) を運営しているトゥギャッター株式会社でエンジニアをしている [@MintoAoyama](http://twitter.com/MintoAoyama) です。

[**Togetter**](http://togetter.com) はツイートを始めとした様々な情報を組み合わせてコンテンツを作り出すキュレーションサービスです。
2009年に誕生してから**今年で13年目**に突入し、現在も**月間PV約1億、月間UU約1500万という規模感**で成長を続けています。

<a href="http://togetter.com"><img width="1064" alt="すごいぞTogetter" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7574/949b7081-eba6-7608-f78f-10a49550ec60.jpeg"></a>

そんなトゥギャッター社もコロナ禍に入り、**全従業員がフルリモートワーク体制に移行**しました。
思い返せば、

* [Googleスプレッドシート&Excelアドベントカレンダー2020](https://adventar.org/calendars/5097) の14日目 に書いた [【リモートワークを支える技術？】Spreadsheets + GAS でお手軽構築する雑談ネタ提供システム - min.t (ミント)](https://min.togetter.com/ejHo18b) 
* [AWS LambdaとServerless Advent Calendar 2021](https://qiita.com/advent-calendar/2021/lambda) の4日目 に書いた　[Lambda サブスクリプションフィルター + AWS WAF で実現する「フルリモートワーク時代のお手軽社内サイト」　](https://qiita.com/MintoAoyama/items/9bb5c7bbf499d3347fed)

など、**フルリモートワーク環境下に入って必要に迫られて作ったもの**が色々あることに気付きました。

その中にはコミュニケーションツールとしてフル活用させていただいている [**Slack**](https://slack.com/intl/ja-jp/) が関連しているものがあったりもします。
今回はその仕組みの一部についてご紹介します。

## 便利だけど見逃しがちなSlack投稿

[**Slack**](https://slack.com/intl/ja-jp/) 、便利ですよね。シンプルながらカスタマイズ性も高く、話題ごとにチャンネルを使い分けたりなど、スムーズなやり取りができるチャットツールだと思います。
色々な仕組みとのインテグレーション性の高さもメリットの一つで、些細な情報でもとりあえず流しておけたりします。

ただし、**Slackは "ストック" か "フロー" であれば "フロー" よりのツール**でもあります。
"未読" の概念こそありますが、 "全未読" 機能を使わない限りは覗いただけで既読の扱いになり、（ブックマークやピンなどを活用するテクニックもありますが）一度にきちんと目を通さない限り情報を見逃しがちです。

例えば、インテグレーション由来など流量が多めの情報になってくると、それらの課題は起こりがちになってきます。
代表的なのは「**TwitterなどSNSのエゴサーチ（エゴサ）情報**」です。プロダクトマーケティングの観点などからユーザーからの率直な意見を知りたい場合など、エゴサーチは必要不可欠だと考えている現場も多いと思います。
連携・自動化関連のサービスとして有名な [**IFTTT**](https://ifttt.com/) には、 [特定のキーワードなど条件に一致するツイートをSlackに投稿する仕組み](https://ifttt.com/applets/cMCd2Huw-post-to-slack-when-a-tweet-matches-your-search-term) などが用意されていたりします。それらのサービスを活用したり、独自の実装などで専用チャンネルを設置している現場もあると思います。
ただ、条件次第では流量が多くなってしまうことや、重要ではない投稿が含まれることもあり、見逃されがちです。

また、**社内向けのアナウンス情報**も見逃されがちです。
"フローな情報" はその場で見ている分には気付きやすいものですが、フルタイム勤務ではないメンバーなど、時々まとめて確認するようなケースだと見逃しがちです。そのため、なんらかの形で "ストックな情報" として残しておくことが求められます。

今回、 流量が多めな情報には **"ピックアップチャンネル"** 、 アナウンス情報には **"ストックスレッド"** と、それぞれ名付けた仕組みで改善に挑戦してみました。
結論としては、概ね良い評判を得られました。どういったものなのか、簡単な実装方法と合わせてご紹介していきます。

## ピックアップチャンネル

ピックアップチャンネルは **"「注目されていると思われる投稿」に、より気付きやすくなる仕組み"** です。その名の通り "チャンネル" そのものであり、そこに投稿する仕組みです。
**`#エゴサ`チャンネルが存在していたとしたら、`#エゴサピックアップ`チャンネルを立て、「リアクションが付いたら」または「スレッドが立ったら（スレッド内にコメントがあったら）」、対象の投稿を転送します。**

![エゴサピックアップハチだよ 気になった投稿を自動で別のチャンネルに送ってくれるすごいやつだよ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7574/eb995b36-3995-6d8d-3e87-91b85b8de1b0.jpeg)

普段からSlackの投稿を見ている方であれば、 **気になるものにリアクションを付ける** ことは珍しくないと思います。
もちろん無視すべき投稿・あまり重要ではない投稿に対してリアクションがつくことも珍しくはないとは思いますが、いずれにせよ**リアクションやスレッドができる投稿は「注目されている」と考えられる**、というわけです。

なお、これまでも気になる投稿に対して共有・言及する際、 `#雑談` などのチャンネルで転送することはありました。
しかし、「URLをコピーしつつ別なチャンネルに投稿する」というのは少なからず手間になります。
**「リアクションする "だけ" で」あるいは「スレッド内にコメントする "だけ" で」他のチャンネル・他のメンバーに対して投稿を共有できる**というのはかなり合理的な仕組みなのではないでしょうか。そういう意味では、全ての投稿をチェックする手間を惜しむ方にも便利だと思いますし、それ以外の方にもオススメできます。
個人的には、Slackの標準機能として欲しくなるくらいには重宝しています。（もしかしたらどこかのAppで提供されているものなのかもしれませんが、きちんと調べてません）

## ストックスレッド

ストックスレッドは **"行った投稿を一定条件にしぼりスレッドに貯めることで、遡りやすくする仕組み"** です。
わかりやすい例としては **期間別** です。
**例えば `#アナウンス` チャンネルが存在していたとしたら、その中に "12月専用のスレッド（投稿）" を立て、 "12月中に行われる投稿のURL" を自動的にスレッド内に投稿（引用）することで貯めていきます**。

![ストックスレッドロボだよ 自動で期間別の各スレッドに投稿を貯めてくれるすごいやつだよ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7574/0359c5ab-6d4e-cc79-33a7-73a39d020e37.jpeg)

Slackには　[様々なクエリ(モディファイア)を備えた検索機能](https://slack.com/intl/ja-jp/help/articles/202528808-Slack-%E5%86%85%E3%81%AE%E6%A4%9C%E7%B4%A2%E6%96%B9%E6%B3%95)　があります。一定期間に絞り込む条件も用意されているため、それらを利用するという手もあるものの、検索条件そのものを共有する仕組み（ハイパーリンクなど）が用意されていません。また、参画メンバーの皆さんに自主的に定期的に検索していただく事自体が現実的ではありません。

**スレッドとして特定の投稿を貯めておけば、それをチャンネル内にピン留めすることで、定期的に確認してもらいやすくなります。**
少し強引ではありますが、 "フローな情報" としての「日々の投稿」と、"ストックな情報" としての「スレッド投稿」という形で、それぞれのニーズにマッチしうる、加えて　"Slack内で完結する仕組み" として用意できたと思います。

なお、**条件に応じて異なるスレッドに貯められるということは、同じチャンネル内で特定のグループに対して @ などを付けて投稿している場合など、対象に応じて分けることも可能**だということです。
投稿ルールの統一次第ですが「アナウンスの種類に応じて分ける」など、様々な可能性を持った仕組みになると思います。

### Event Subscriptions　+ chat.postMessage + GAS（Google App Script） で実現するSlack投稿の転送

これらの仕組みですが、基本的には同じ機能を用いて実装できます。

一つは [**Event Subscriptions**](https://api.slack.com/apis/connections/events-api) です。
**「メッセージが投稿された」「メッセージにリアクションが付いた」「ユーザーがチャンネルにjoinした」「チャンネルにピンが追加された」など、Slackの様々なイベントをトリガーとして取得できる機能**です。[非常に多岐に渡るイベントタイプ](https://api.slack.com/events) が用意されています。

**対象のイベントが発生すると、任意のURLに対して POST リクエストを送信できます。**
「メッセージが投稿された」「メッセージにリアクションが付いた」などの場合、リクエストのPayloadの中には「メッセージの内容」「投稿者」「メッセージのタイプ（スレッド内の投稿か、画像の投稿かなど）」、関連する様々な情報が入ってきます。
期待している情報・状態がないかなどを確認したい場合は、 [Event type structure](https://api.slack.com/apis/connections/events-api#the-events-api__receiving-events__event-type-structure) を確認したり、実際に動かしてみたり試行錯誤しながら探っていくことになると思います。

もう一つは [**chat.postMessage**](https://api.slack.com/methods/chat.postMessage) です。
**「メンションを飛ばす」「スレッド内に投稿（返信）する」「メッセージを共有する」など、通常のSlackのメッセージ投稿に関わる様々な機能を実現できるAPIメソッド**になっています。

なお、Event Subscriptions　の設定　/ API利用のためのトークン発行 のためには **Slack App（Slackアプリ） の作成**が必要になります。詳細は　[Slack Events API　の利用方法](https://api.slack.com/apis/connections/events-api)　など、公式のドキュメントをご確認いただければと思います。

また、 **Event の送信先であり、Payloadを元にメッセージ投稿などのリクエストを行う Web API** を用意する必要もあります。

Web API は "Postリクエストを処理できる仕組み" さえ用意できればご都合に応じて様々な環境が選択肢になると思います。
比較的お手軽なものとしてオススメできるのは [**Google App Script**](https://workspace.google.co.jp/intl/ja/products/apps-script/) です。Google アカウントをお持ちであればどなたでも用意できる上、JavaScript　を知っていればインフラの知識も不要です 👍

[Web Apps  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/guides/web)

**すごく簡単な実装例** をご紹介します。

```javascript
function doPost(e) {
  // Postリクエストを受け取って処理する

  // Payload（JSON）をパースする
  const postData = JSON.parse(e.postData.getDataAsString())

  // メッセージのスレッド内に投稿があった場合
  // メッセージのスレッド元のリンク + スレッド内への投稿のリンク を組み合わせて投稿
  let messesage = 
    '<https://xxx.slack.com/archives/' + 'CHANNEL_ID' + '/p' + postData.event.thread_ts.replace('.', '') + '|[*元スレッド*]>' +
    '<https://xxx.slack.com/archives/' + 'CHANNEL_ID' + '/p' + postData.event.ts.replace('.', '') + '| >'

  // メッセージにリアクションがあった場合
  // リアクションの絵文字 + メッセージのスレッド元のリンク を組み合わせて投稿
  let messesage_part2 = '<https://xxx.slack.com/archives/' + channel + '/p' + postData.event.item.ts.replace('.', '') + '|:' + postData.event.reaction + ':>'

  // Slackに投稿する
  UrlFetchApp.fetch(
    'https://slack.com/api/chat.postMessage',
    {
      'method' : 'post',
      'payload' : {
      'token' : 'SLACK_BOTUSER_OAUTH_TOKEN',
      'channel' : 'CHANNEL_ID',
      'mrkdwn' : true,
      'text' : message
    }
  )
}
```

今回便宜上1つにまとめてしまっていますが、コメントの通り、以下のようなことを行った例です。

* **「メッセージのスレッド内に投稿があった場合」**は**「メッセージのスレッド元のリンク + スレッド内への投稿のリンク を組み合わせて投稿」**
* **「メッセージにリアクションがあった場合」**は**「リアクションの絵文字 + メッセージのスレッド元のリンク を組み合わせて投稿」**

"スレッド内への投稿" の場合は以下のような投稿イメージになります。

![メッセージの例.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7574/ddc14c8b-db92-f178-b752-627d5f0559c4.jpeg)

"リアクション" の場合は以下のような投稿イメージになります。

![スクリーンショット_2021-12-12_18_38_42.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7574/4eded610-44a7-48e2-b567-25b460704d74.jpeg)

"メッセージ投稿に必要なトークン"、 "対象のチャンネルID" などは適当な文字列として書いていますので、環境に応じて置き換えて考えていただければと思います。

### "考慮すべきポイント" 色々

実際の　Payload　を見ていただけると想像しやすいのですが、実装にあたっては "考慮すべきポイント" が多数存在します。上に挙げた実装例はかなり簡素で、様々なことが考慮されていません。

* 信頼できるPOSTデータであるかを確認
    * Slackの場合、Verification用のtokenが含まれているので検証する
* 想定したAppであることを確認
* チャンネルへの投稿であることを確認
* 想定したメッセージではない場合は対象としない
* そもそも「メッセージのスレッド内に投稿があった場合」 「メッセージにリアクションがあった場合」など、どのようなデータが送られてきたのか判定する必要がある など

要件に応じて様々ですので、しっかり整理・検証した上でリリースする必要があります。

また、　Payload　だけではなく、投稿にあたっても**「（同じ投稿に対して）既にリアクション・投稿していないか」**などの重複投稿のチェックなどの考慮が必要なこともあります。
簡易的な機能であれば気にするほどではないと判断できるケースもあるとは思いますが、要件に応じてSlackの各種APIメソッド（メッセージ検索用の [search.messages](https://api.slack.com/methods/search.messages) など）やデータ構造と向き合う必要が出てくると思います。

---

こんな感じでどうでしょうか。考えるポイントは様々ですが、様々な可能性を持った仕組みであることはお分かりいただけたかもしれません。
特に今回、具体的な活用事例を挙げたことでイメージしやすくなったかと思います。

Event Subscriptions の仕組みなど、ご存じなかった方は是非ご活用をご検討ください 💪💪💪

---

明日は @geeorgey さんによる「[Slack Connectでアプリを利用する時にハマったこと](https://qiita.com/advent-calendar/2021/slack)」です。
Slack Connect、弊社も無茶苦茶使っております…！ とても便利な機能ですが、「チャンネル名がエイリアスのように別で定義されている」ことに気付かずハマったり、地味に要注意な気がします。気になりますね。