Slackに追加された絵文字を自動通知するBotを復旧させた話

この記事は [ハンズラボ AdventCalendar2022](https://qiita.com/advent-calendar/2022/handslab) 8日目の記事です。

# はじめに
私がハンズラボに入社した当時、Slackの #random チャンネルには多種多様のおもしろbotが動いていました。その中の一つが **EmojiBot** です。
![スクリーンショット 2022-12-05 10.39.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501670/8680bb91-30bf-308f-620b-80cd84a9d0f9.png)

このbotはGASで作成されており、その日に新規に登録された絵文字を自動的に検知して通知してくれるというものでした。Slackでのコミュニケーションの活性化に一役買っており私も大好きなbotでした。しかし、SlackのAPIの仕様が変更されたためか、またはGASの管理者の退職の影響か、ある日動かなくなってしまいました。

とてもとても寂しかったので、AWS＋Pythonで書き直しました。名付けて「**帰ってきたEmojibot**」。


# 事前準備(Slack)

[SlackのApp管理画面](https://api.slack.com/apps)を開き、Appを新規作成してOAuth token を入手してください。必要な権限は以下の通りです。

- chat:write
- chat:write.public
- emoji:read


![スクリーンショット 2022-12-05 11.13.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501670/b7a055a8-f856-c13a-e378-dedd0a15506b.png)


OAuth token の詳細な入手手順は本記事では説明しませんので、各自検索してみてください。
トークンだけでなく、アイコンや表示名も必要に応じてここで設定します。

![スクリーンショット 2022-12-05 11.13.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501670/2c5c2e41-8f34-cfcf-d47a-a3d8a6327049.png)

# コード(Python)

```emojibot.py
import os
from datetime import datetime, timezone
from dotenv import load_dotenv
import boto3
from slack_sdk.web import WebClient

# ローカル起動とLambda起動で環境変数を切替
if os.environ.get('AWS_LAMBDA_FUNCTION_NAME'):
    dynamodb = boto3.resource('dynamodb')
else:
    load_dotenv()
    dynamodb = boto3.resource('dynamodb', endpoint_url='http://localhost:8000')

table = dynamodb.Table('SlackEmojiList')
slack = WebClient(token=os.environ["SLACK_API_TOKEN"])


def getEmojiList():
    # emoji一覧の入手
    response = slack.emoji_list()
    if not response['ok']:
        print(f'emoji_list failed: {response}')
        return

    # DynamoDBに登録済みかチェック
    for emoji, url in response['emoji'].items():
        result = table.get_item(
            Key={
                'emoji': emoji
                }
        )

        # 新規絵文字の場合はSlackへ投稿
        if 'Item' not in result:
            postSlack(emoji, url)

    return


def postSlack(emoji, url):

    # エイリアスの追加は通知しない
    if 'alias' not in url:
        text = "Here comes new emoji!!\n" + "`:" + emoji + ":` " + url
        response = slack.chat_postMessage(text=text, channel="#random")
        if not response['ok']:
            print(f'{emoji} chat_postMessage failed: {response}')
            return

    # DynamoDBに保存
    now = datetime.now(timezone.utc)
    table.put_item(
        Item={
            'emoji': emoji,
            'url': url,
            'create_at': now.isoformat()
        }
    )

    return


def handler(event, context):
    getEmojiList()
    return


if __name__ == "__main__":
    getEmojiList()
```

## 解説
1. [SlackSDK](https://slack.dev/python-slack-sdk/web/index.html)を利用しての絵文字一覧APIを実行して絵文字一覧を入手
1. DynamoDBに保存してある前日分との差分との比較
1. 新規絵文字の場合はSlackへ投稿
1. 投稿したらDynamoDBに保存

という、非常にシンプルなコードです。絵文字一覧APIには画像URLが含まれていますので、IDと合わせてSlackに投稿しています。絵文字の画像URLはパブリック(！)のため、URLを投稿すればSlack上でプレビューされます。


# コード(AWS)

```serverless.yml
service: emojibot
frameworkVersion: ">=2.0.0 <3.0.0"

plugins:
  - serverless-python-requirements

custom:
  defaultStage: common
  pythonRequirements:
    dockerizePip: true
  slackAPIToken: ${ssm:/emojibot/slackAPIToken}

provider:
  name: aws
  runtime: python3.8
  region: ap-northeast-1
  stage: ${opt:stage, self:custom.defaultStage}
  environment:
    TZ: Asia/Tokyo
    SLACK_API_TOKEN: ${self:custom.slackAPIToken}
  lambdaHashingVersion: 20201221
  logRetentionInDays: 7
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:Query
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
            - dynamodb:DescribeTable
            - dynamodb:Scan
          Resource:
            - !GetAtt EmojiListDynamoDBTable.Arn

package:
  patterns:
    - '!node_modules/**'
    - '!.github/'
    - '!.vscode/'
    - '!.venv/'
    - '!tests/'
    - '!Pipfile'
    - '!Pipfile.lock'
    - '!README.md'
    - '!package.json'
    - '!yarn.lock'

functions:
  EmojiBot:
    handler: emojibot.handler
    timeout: 900
    events:
      # 毎日18:00に起動
       - schedule: cron(0 9 * * ? *)  

resources:
  Resources:
    # -----
    # DynamoDB作成
    # -----
    EmojiListDynamoDBTable:
      Type: 'AWS::DynamoDB::Table'
      Properties:
        TableName: SlackEmojiList
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: emoji
            AttributeType: S
        KeySchema:
          - AttributeName: emoji
            KeyType: HASH
        PointInTimeRecoverySpecification:
          PointInTimeRecoveryEnabled: true
```

## 解説

毎度おなじみの[Serverless Framework](https://www.serverless.com/)です。[serverless-python-requirements](https://github.com/serverless/serverless-python-requirements)を利用してPythonコードをLambdaとしてデプロイするほか、DynamoDBも新規作成しています。slackAPITokenにセットする OAuth token は秘密情報にあたるため、ssmパラメタストアに格納してデプロイ時に参照させています。

※ Serverless Framework の詳細な使い方はこの記事では説明しません
※ 少し前に書いたコードのため、最新バージョンの Serverless Framework で動作させたい場合は修正が必要かもしれません

上記コードをデプロイ後、リリース当日までに登録済みの絵文字データをDynamoDBに移行してやれば準備は完了です。

# 動作確認
新規に絵文字を登録後、18時まで待つと……

![スクリーンショット 2022-12-05 11.12.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501670/69f4c855-9a69-6b1d-1310-78b21bf245fd.png)

やったぜ。

# おわりに
絵文字の追加が通知されるようになると、絵文字の追加を利用者の皆に気づいてもらえるようになりコミュニケーションが活性化します。ネタ絵文字をこっそり追加して、通知されるのを黙って待つなどの奥ゆかしい使い方もできます笑。また、個人の権限に依存しがちなGASからAWSでの管理に切り替えたこともあり、複数人でのメンテナンスが容易になったこともメリットの一つです。今後も止まることなくずっと動き続けて欲しいものです。