Slackのカスタム絵文字の変更を監視する
この記事は、NAVITIME JAPAN Advent Calendar 2020の 7日目の記事です。
こんにちは、ツチノコです。
ナビタイムジャパンで社内APIの開発を担当しています。
弊社では、コミュニケーションツールとしてSlackを採用しています。
Slackは、チャットベースのコミュニケーションツールです。エンジニアに特化したコミュニケーションツールで、APIや外部サービスとの連係に優れるなど、よりスムーズにコミュニケーションをとり、開発効率を上げるための様々な工夫がされています。
カスタム絵文字とは?
Slackには、カスタム絵文字という機能があります。通常の絵文字とは別に、好きな画像をワークスペース固有の絵文字として追加できる機能です。
追加した絵文字は、
- 投稿された内容にリアクションを付ける
- 投稿内容の検索条件として利用する
- ワークフローのトリガーとして設定する
などに利用できます。
また、カスタム絵文字に似た機能に絵文字エイリアスがあります。
絵文字にエイリアスを追加することができ、エイリアスを指定することでも元の絵文字と同じ絵文字を利用できます。検索条件に絵文字を指定した場合、オリジナルの絵文字と同一の絵文字として検索できます。
なぜカスタム絵文字の変更を監視しようと思ったのか?
カスタム絵文字は、ワークスペースに属している個人が自由に追加でき、自身が追加したものであれば自由に削除できます。
カスタム絵文字が無秩序に追加/削除されると、
- 同じ意味を持つ絵文字が重複してしまい、投稿内容の検索性が低下する
- ワークフローのトリガーが消えてしまう
- 愛用していた絵文字がいつの間にか無くなっていると悲しくなる😢
といった状況が発生してしまい、カスタム絵文字が持つメリットが薄れてしまいます。
カスタム絵文字の変更を事前にキャッチできれば、
- 同じ意味を持つ絵文字はエイリアスとして登録する
- 消えてしまった絵文字を再登録する
といった対応で検索性を向上させたり、絵文字を復活させたりできます。
実現方法
Slack APIのemoji_changed イベントを利用して、絵文字の追加/削除を検知します。
ワークスペースのカスタム絵文字が追加/削除された場合、 emoji_changed イベントがワークスペース利用者全てに通知されます。
emoji_changedイベントを受け取ったら、subtypeを元に追加/削除を判別し、通知用チャンネルにメッセージを流します。
絵文字監視用Botの作成
今回はemoji_changed イベントを受け取るために、カスタム絵文字監視用のSlackBotを作成しました。
Slackから提供されているBotsというSlackアプリを利用して、Botを作成します。
Add to SlackボタンからBotを作成しましょう。
Bot作成後の画面ではAPI Tokenが表示されています。このトークン情報を利用して、絵文字監視用Botを作成します。トークンは流出してしまうとBotが乗っ取られてしまうため、トークンの保管には十分気をつけましょう。
次に、絵文字監視用Botの中身を作成します。絵文字監視用Botの作成にあたり、Slackから提供されているPython Slack SDKを利用しています。
絵文字監視用Botのプログラム全体像はこのようになりました。
import certifi
import logging
import ssl as ssl_lib
import yaml
from slack_sdk.rtm import RTMClient
with open('config.yml', 'r', encoding='utf-8') as yml:
conf = yaml.safe_load(yml)
def create_block(data):
if data.get("subtype").lower() == "remove":
names = data.get("names")
icons = '\n'.join(names)
text = (
"絵文字がなくなっちゃいました :cry:\n\n"
f"{icons}"
)
elif data.get("subtype").lower() == "add":
name = data.get("name")
text = (
f"{name}の絵文字が追加されました!\n\n"
f":{name}:"
)
else:
return
return {
"type": "section",
"text": {
"type": "mrkdwn",
"text": text,
},
}
@RTMClient.run_on(event="emoji_changed")
def notify_emoji_changed(**payload):
data = payload["data"]
web_client = payload["web_client"]
message = {
"channel": conf['notify_to'],
"username": "emoji-notifier",
"icon_emoji": ":horse:",
"blocks": [
create_block(data)
]
}
web_client.chat_postMessage(**message)
if __name__ == "__main__":
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
ssl_context = ssl_lib.create_default_context(cafile=certifi.where())
rtm_client = RTMClient(token=conf['bot_token'], ssl=ssl_context)
rtm_client.start()
まずはRTMClientを作成し、先ほど作成したBotユーザーと接続しましょう。
if __name__ == "__main__":
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
ssl_context = ssl_lib.create_default_context(cafile=certifi.where())
rtm_client = RTMClient(token=conf['bot_token'], ssl=ssl_context)
rtm_client.start()
外部の設定ファイルにトークン情報を持たせることで、トークン情報が漏洩しないようにしています。
それではemoji_changed イベントを受け取りましょう。イベントを受け取るにはRTMClientのrun_on関数をデコレータとして利用します。
@RTMClient.run_on(event="emoji_changed")
def notify_emoji_changed(**payload):
data = payload["data"]
...
RTMClient.run_on関数の引数であるeventには受け取りたいイベントタイプを指定します。
Slack APIのレスポンスは自作関数の引数に渡ります。レスポンスのdataフィールドに受け取ったイベントの内容が格納されます。
続いて、カスタム絵文字の追加/削除を判定して、メッセージを作成します。
message = {
"channel": conf['notify_to'],
"username": "emoji-notifier",
"icon_emoji": ":horse:",
"blocks": [
create_block(data)
]
}
通知メッセージの投稿先や投稿者名、アイコンはここで設定しています。自由にカスタマイズしましょう。投稿者名やアイコンを設定していない場合、Botsで設定された投稿者名・アイコンが利用されます。
create_block関数で、emoji_changedイベントの内容を確認し、通知メッセージを作成しています。
def create_block(data):
if data.get("subtype").lower() == "remove":
names = data.get("names")
icons = '\n'.join(names)
text = (
"絵文字がなくなっちゃいました :cry:\n\n"
f"{icons}"
)
elif data.get("subtype").lower() == "add":
name = data.get("name")
text = (
f"{name}の絵文字が追加されました!\n\n"
f":{name}:"
)
else:
return
return {
"type": "section",
"text": {
"type": "mrkdwn",
"text": text,
},
}
subtypeがaddの場合は、nameフィールドを参照することで絵文字の名称を取得できます。
一方で、subtypeがremoveの場合は、namesフィールドを参照する必要があります。これはエイリアス元の絵文字が削除された場合に、エイリアスを含めた名称が全て返却される仕様のためです。
最後に、通知用のチャンネルに作成したメッセージを流して完了です。
web_client.chat_postMessage(**message)
それではBotを起動し、カスタム絵文字を追加して動作確認してみましょう。
無事に通知されました🎉
絵文字監視用Botを常時稼働させる
ローカル環境で絵文字監視用Botを起動していると、自身の端末を起動している間しかカスタム絵文字の変更を検知できません。そこで、常時監視できるよう絵文字監視用Botをサーバ上で動作させます。
絵文字監視用Botは常時稼働してほしいので、systemdを利用して自動起動を実現しました。
それではsystemdに登録するserviceファイルを作成します。
[Unit]
Description=A bot that notifies changing of slack custom emoji
After=multi-user.target
[Service]
ExecStart=/var/bot/slack-emoji-notifier/etc/boot.sh
Restart=always
Type=simple
[Install]
WantedBy=multi-user.target
ExecStartに自動起動したいパスを指定します。絵文字監視用BotはPythonの仮想環境で動かしたいため、起動用のシェルを別途用意しています。
Restartには常に稼働させたいのでalwaysを指定します。
WantedByに指定した対象の実行時に、絵文字監視用Botが起動するよう依存関係を設定します。
systemdに登録する際には、serviceファイルを /etc/systemd/system に配置します。
cp slack-emoji-notifier.service /etc/systemd/system
この時に/etc/systemd/system にファイル配置ではなく、シンボリックリンクを配置してしまうと、サーバを再起動しても自動起動しませんので注意が必要です。
systemdに作成したserviceファイルを認識させます。
$ sudo systemctl daemon-reload
自動起動設定を登録します。
$ sudo systemctl enable slack-emoji-notifier.service
Created symlink from /etc/systemd/system/multi-user.target.wants/slack-emoji-notifier.service to /etc/systemd/system/slack-emoji-notifier.service.
これで絵文字監視用Botが常時稼働するようになりました🎉
絵文字の重複に対する課題
追加された絵文字がどの既存の絵文字と意味が重複しているのかの判別はできていません。通知用チャンネルにjoinしているユーザーが、類似した絵文字があったときに、Botの投稿に類似した絵文字のリアクションを付けています。
絵文字の重複の判別方法として、以下のような方法が考えられます。
- emoji.list APIを利用して、絵文字一覧から追加された文字列を含む、もしくは類似した名前の絵文字を検索する
- 機械学習を使った画像処理で類似した絵文字を検索する
おわりに
本記事ではSlackのカスタム絵文字の変更を監視する方法について紹介しました。
Slack APIのemoji_changed イベントを利用することで簡単に検知することができます。
また、systemdを利用して絵文字監視用Botを常時起動させ、変更を常に監視できるようにしました。
カスタム絵文字を活用して、スムーズに楽しくコミュニケーションを取り、業務を効率化していきましょう!
それでは良いSlackライフを!👋