デカい(絵)文字を投稿するSlackアプリを作ってみた

# はじめに

この記事は[Hamee Advent Calendar 2020](https://qiita.com/advent-calendar/2020/hamee)の18日目の記事です。

# 概要

スラッシュコマンドでチームメンバーへの溢れる感謝や賛辞を伝えることができます。

![awesome.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456198/53a0aca5-e2b8-2fe1-c337-df96b8741e02.gif)


スラッシュコマンドが入力されると、GoogleAppsScriptにデプロイ済みのAPIにPOSTリクエストが送られたのち、処理結果がSlackに投稿されます。

# 事前準備

透過色だけの絵文字を用意して、ワークスペースのカスタム絵文字に`blank`として登録しておきます。
こちらのサイトを利用させていただきました。
https://zk-phi.github.io/MEGAMOJI/

本記事ではGASのデプロイやスラッシュコマンドの導入については省略します。以下の記事を参考にさせていただきました。
[GASで作るSlack Slash Commands入門](https://qiita.com/zama_8722/items/5893d968f120dad90f11)

# GASのコード

POSTパラメータの`text`フィールドに入力内容が含まれているので取り出します。
(`/big awesome :parrot:`と入力した場合`awesome :parrot:`)

入力メッセージを事前に定義した「アルファベット→デカ文字」のマッピング配列に基づいて
指定された絵文字に変換しています。
（マッピングの定義を書くのが一番大変でした）


```js

// アルファベットのみ対応、小文字は自動で大文字に変換
// 長すぎると邪魔なので最大長を設ける
const MESSAGE_MAX_LENGTH = 7;
const BLANK_EMOJI = ':blank:';
const MARK_EXIST = '1';
const MARK_NONE  = '0';

function doPost(e) {
  let message = e.parameter.text.split(' ')[0];
  let emoji = e.parameter.text.split(' ')[1];
  let response = {};
  
  try {
    response = makeBigMoji(message, emoji);
  } catch(err) {
    response = {
      'response_type': 'ephemeral',
      'text': err.message
    };
  }
  
  Logger.log(response);

  return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}

function makeBigMoji(message, emoji) {
  if (message.match(/[^A-Za-z]/)) {
    throw new Error('アルファベットのみで入力してください。');
  }
  if (message.length > MESSAGE_MAX_LENGTH) {
    throw new Error(MESSAGE_MAX_LENGTH + '文字以内で入力してください。');
  }
  
  let message_capital = message.toUpperCase();
  let characters = message_capital.split('');
  let mojis = ['', '', '', '', ''];
  for (let character of characters) {
    let moji_before = MOJI[character];
    for (let index in moji_before) {
      mojis[index] += moji_before[index].replace(/0/gi, BLANK_EMOJI + ' ').replace(/1/gi, emoji + ' ') + BLANK_EMOJI + ' ';
    }
  }
  
  return {
    'response_type': 'in_channel',
    'text': mojis.join("\n")
  };
}
```
<details><summary>マッピング配列</summary><div>

```js
const MOJI = {
  'A': [
    '0110',
    '1001',
    '1111',
    '1001',
    '1001',
  ],
  'B': [
    '1110',
    '1001',
    '1110',
    '1001',
    '1110',
  ],
  'C': [
    '0111',
    '1000',
    '1000',
    '1000',
    '0111',
  ],
  'D': [
    '1110',
    '1001',
    '1001',
    '1001',
    '1110',
  ],
  'E': [
    '1111',
    '1000',
    '1111',
    '1000',
    '1111',
  ],
  'F': [
    '1111',
    '1000',
    '1111',
    '1000',
    '1000',
  ],
  'G': [
    '0111',
    '1000',
    '1011',
    '1001',
    '0111',
  ],
  'H': [
    '1001',
    '1001',
    '1111',
    '1001',
    '1001',
  ],
  'I': [
    '111',
    '010',
    '010',
    '010',
    '111',
  ],
  'J': [
    '0111',
    '0010',
    '0010',
    '1010',
    '0100',
  ],
  'K': [
    '1001',
    '1010',
    '1100',
    '1010',
    '1001',
  ],
  'L': [
    '1000',
    '1000',
    '1000',
    '1000',
    '1111',
  ],
  'M': [
    '10001',
    '11011',
    '10101',
    '10001',
    '10001',
  ],
  'N': [
    '10001',
    '11001',
    '10101',
    '10011',
    '10001',
  ],
  'O': [
    '01110',
    '10001',
    '10001',
    '10001',
    '01110',
  ],
  'P': [
    '1110',
    '1001',
    '1110',
    '1000',
    '1000',
  ],
  'Q': [
    '01110',
    '10001',
    '10101',
    '10011',
    '01110',
  ],
  'R': [
    '1110',
    '1001',
    '1110',
    '1001',
    '1001',
  ],
  'S': [
    '0111',
    '1000',
    '0110',
    '0001',
    '1110',
  ],
  'T': [
    '11111',
    '00100',
    '00100',
    '00100',
    '00100',
  ],
  'U': [
    '10001',
    '10001',
    '10001',
    '10001',
    '01110',
  ],
  'V': [
    '10001',
    '10001',
    '01010',
    '01010',
    '00100',
  ],
  'W': [
    '10101',
    '10101',
    '10101',
    '10101',
    '01010',
  ],
  'X': [
    '10001',
    '01010',
    '00100',
    '01010',
    '10001',
  ],
  'Y': [
    '10001',
    '01010',
    '00100',
    '00100',
    '00100',
  ],
  'Z': [
    '11111',
    '00010',
    '00100',
    '01000',
    '11111',
  ],
};
```
</div></details>

`response_field`パラメータに`in_channel`を指定するとコマンドの呼び出しと実行結果がチャンネルのメンバー全員に見える形で投稿されます。
![visible.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456198/7d3fd538-cda3-745c-24cb-e36e88a3cbc1.png)

一方、`'response_type': 'ephemeral'`を指定するとコマンドを実行した本人にしか見えない形でメッセージが投稿されるため、入力エラーの通知に使っています。
![invisible.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456198/b2ee295c-54bd-f558-e46a-f79571d3b15e.png)

なお、コマンドの呼び出し自体 (`/big good :parrot:`など) を非表示にすることはできないようです（違ってたらこっそり教えて下さい）。
また、[リクエストの認証](https://api.slack.com/authentication/verifying-requests-from-slack)は~~面倒なので~~していませんが、APIを一般公開などする際には対応したほうが良いと思います。

# おわりに

リクエスト・レスポンスパラメータの仕様さえ分かってしまえば意外と簡単にSlackのスラッシュコマンドの実装ができました。今回はネタの実装でしたが、うまく使えば色々便利ツールが出来そうな気がします。


# 参考

[GASで作るSlack Slash Commands入門](https://qiita.com/zama_8722/items/5893d968f120dad90f11)
[Enabling interactivity with Slash Commands](https://api.slack.com/interactivity/slash-commands)