ContractS開発者ブログ

契約マネジメントシステム「ContractS CLM」の開発者ブログです。株式会社HolmesはContractS株式会社に社名変更しました。

SpringBoot+Slackアプリでメッセージ通知を実装してみる

こんにちは、id:c-terashimaです。
技術書典11で無料配布している「Holmes Tech Book」ですが、多くの方にダウンロードしていただいております!ありがとうございます!!
ドメイン駆動設計やmiroアプリの作成などバラエティに富んだ内容になっていますので、ぜひダウンロードしていただけると幸いです。

techbookfest.org

さて、今回はJava(SpringBoot)からSlackへメッセージ送信を試してみました。

Slackアプリを作る

Slackとアプリのやり取りを行うためのアプリを作成します。作成するSlackアプリからDMが届くことを想定しています。

  1. SlackAPIのアプリページに移動して、「Create New App」をクリックします f:id:c-terashima:20210719152014p:plain
  2. 「App Name」に作成するアプリ名、「Pick a workspace to develop your app in:」に使用するワークスペースを選択し、「Create App」ボタンをクリックします f:id:c-terashima:20210719152450p:plain
  3. 「OAuth&Permissions」にアプリに許可させたい権限を付与させます。今回は「chat:write」を付与します f:id:c-terashima:20210719162232p:plain
  4. 「Redirect URLs」に認証時に呼び出されるエンドポイントを指定します。後述で作成する「/slack/oauth」を指定します。SSL化されたエンドポイントを指定する必要があるのでご注意ください。(画像はダミー) f:id:c-terashima:20210720094057p:plain
  5. 「App Home」に移動して「Display Name」と「Default username」を入力します f:id:c-terashima:20210719163418p:plain
  6. 「OAuth&Permissions」の設定が完了したら、作ったアプリをワークスペースにインストールします f:id:c-terashima:20210719162650p:plain
  7. 「Basic Information」に移動して「Client ID」と「Client Secret」をメモします。この2つはホームズクラウドなどのアプリケーションからメッセージを書き込む際に必要となります

アプリとの認証

サンプルは以下の環境で動作確認をしております。

  • Java8
  • SpringBoot 2.3

Slack SDK

build.gradledependencies に以下を追加してSlack SDKをインストールします。

dependencies {
    implementation 'com.slack.api:slack-api-client:1.7.1'
}

アプリケーションとの認証

Slackユーザとアプリケーションユーザの紐付けを行います。まずは認証用のボタンを表示します。

<a href="https://slack.com/oauth/v2/authorize?client_id=「Client ID」&scope=chat:write&state=「任意のデータ」">
  <img alt="Add to Slack"
    height="40"
    width="139"
    src="https://platform.slack-edge.com/img/add_to_slack.png"
    srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" />
</a>

client_id には作成したSlackアプリの「Client ID」を指定します。
state はSlackからアプリケーションの認証エンドポイントが実行された際に設定される値になります。認証用エンドポイントの呼び元がこのアプリによって実行されたのかをチェックするのに利用します。

次にSlackから呼ばれるエンドポイントを作成します。

@RestController
@RequiredArgsConstructor
@Slf4j
public class SlackController {
    private static final String STATE = "state_id";
    private final String slackClientId = "client_id";
    private final String slackClientSecret = "client_secret";

    @GetMapping("/slack/oauth")
    @SneakyThrows
    public ResponseEntity<Void> oauth(@RequestParam("code") String code, @RequestParam("state") String state) {
        // stateの確認
        if (!Objects.equals(state, STATE)) {
            log.error("stateが一致しません。{}", state);
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }

        try (Slack slack = Slack.getInstance()) {
            // Slackアプリとの認証
            OAuthV2AccessResponse response = slack.methods()
                    .oauthV2Access(req -> req.code(code).clientId(slackClientId).clientSecret(slackClientSecret));

            if (!response.isOk()) {
                log.error("認証に失敗しました。{}", response.getError());
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
            }

            // SlackユーザID、Slackアプリのトークンを取得
            // メッセージを送信するのに必要になるため、DBかファイルに保存しておこう
            String slackUserId = response.getAuthedUser().getId();
            String slackAccessToken = response.getAccessToken();
        }

        // 認証後のリダイレクト先を指定する
        HttpHeaders headers = new HttpHeaders();
        UriComponentsBuilder builder =
                UriComponentsBuilder.fromUri(URI.create("http://localhost:8080/slack"));
        headers.setLocation(URI.create(builder.toUriString()));

        return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY);
    }
}

「Add to Slack」ボタンをクリックすると次の画面が表示されますので、「許可をする」ボタンをクリックすることで上で作ったエンドポイントが呼び出されます。
f:id:c-terashima:20210719184234p:plain

Slackへ通知

通知するメッセージはJSON形式で指定します。(Block Kit Builder)https://app.slack.com/block-kit-builder/T3HBJHYRHを利用するとかんたんに作ることができます。
以下のメッセージを送ってみたいと思います。
f:id:c-terashima:20210719185834p:plain

認証を行ったユーザにメッセージを送るソースは次になります。

private String slackAccessToken = "accessToken";
private String slackUserId = "userId";

@SneakyThrows
public void postApprovalRequestMessage() {
    try (Slack slack = Slack.getInstance()) {
        MethodsClient client = slack.methods(slackAccessToken);

        ConversationsOpenResponse openResponse =
                client.conversationsOpen(req -> req.users(Arrays.asList(slackUserId)));
        if (!openResponse.isOk()) {
            throw new Exception("DMを開くことができませんでした");
        }

        String message =
                "[\n" +
                "    {\n" +
                "        \"type\": \"section\",\n" +
                "        \"text\": {\n" +
                "            \"type\": \"mrkdwn\",\n" +
                "            \"text\": \"こんにちは!メッセージを送るよ!!\"\n" +
                "        }\n" +
                "    }\n" +
                "]\n";


        ChatPostMessageResponse messageResponse = client.chatPostMessage(req ->
                req.channel(openResponse.getChannel().getId())
                .blocksAsString(message)
        );
        if (!messageResponse.isOk()) {
            throw new Exception("DMを送ることができませんでした." + messageResponse.getError());
        }

        ConversationsCloseResponse closeResponse = client.conversationsClose(req ->
                req.channel(openResponse.getChannel().getId()));
        if (!closeResponse.isOk()) {
            throw new Exception("DMを閉じることができませんでした");
        }
    }
}

実行するとメッセージが!!
f:id:c-terashima:20210719191910p:plain

まとめ

Slackは情報も豊富でかんたんにDMを送ることが出来ました。
Block Kitのテンプレートを見ていただくとボタンやテキストボックスをメッセージで送ることができますので、ぜひ色々試していただければ楽しいと思います。
参考にさせていただいた資料は下記に載せておきます。

参考資料


Holmesではエンジニア・デザイナーを募集しております。ご興味がある方はこちらからご連絡ください。

lab.holmescloud.com

lab.holmescloud.com