💬

Goで作るSlack Modal Applicationサンプルコード 4/4

2020/09/25に公開

STEP4 完了メッセージを送信

では、いよいよ最後です。注文をうけつけたメッセージを送信します。

  • リクエストの認証(自分のWorkspaceからのリクエストかどうか)
  • リクエストをStructにマッピング
  • 注文内容を取得
  • Chipをバリデーション(数値かどうか)
  • メッセージを作成、送付

の5つ。だいたいやることがテンプレ化されてきましたね。

上3つは、前のSTEPとかぶるので、説明から省きます。

handlerのソースはこちらです。(各関数の実装含めた完全版は、GitHub参照)

func handleConfirmationModalSubmissionRequest(message slack.InteractionCallback) (events.APIGatewayProxyResponse, error) {
	// Validate a message.
	if err := validateChip(message); err != nil {
		// Create validation failed response.
		errors := map[string]string{
			"block_id_chip": "[ERROR] Please enter a number.",
		}

		resAction := slack.NewErrorsViewSubmissionResponse(errors)
		bytes, err := json.Marshal(resAction)
		if err != nil {
			return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to marshal a validation failed message: %w", err)
		}

		return events.APIGatewayProxyResponse{
			StatusCode:      200,
			IsBase64Encoded: false,
			Headers: map[string]string{
				"Content-Type": "application/json",
			},
			Body: string(bytes),
		}, nil
	}

	// Get private metadata
	var privateMeta privateMeta
	if err := json.Unmarshal([]byte(message.View.PrivateMetadata), &privateMeta); err != nil {
		return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to unmarshal private metadata: %w", err)
	}

	// Send a complession message.
	// - Create message options
	option, err := createOption(message, privateMeta)
	if err != nil {
		return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to create message options: %w", err)
	}

	// - Post a message
	api := slack.New(tokenBotUser)
	if _, _, err := api.PostMessage(privateMeta.ChannelID, option); err != nil {
		return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to send a message: %w", err)
	}

	return events.APIGatewayProxyResponse{StatusCode: 200}, nil
}

Chipをバリデーション(数値かどうか)

まずはChipに「数値以外」が入力されていないかを確認します。数値以外が入力されていた場合は、エラーメッセージが出るようにしてみましょう。

今回はParseFloatでerrorを返さないかで判断してみましょう。以下のような関数としてみました。

func validateChip(message slack.InteractionCallback) error {
	// Get an input value.
	chip := message.View.State.Values["block_id_chip"]["action_id_chip"].Value

	// Chech if the value is number or not.
	if _, err := strconv.ParseFloat(chip, 64); err != nil {
		return err
	}
	return nil
}

バリデーションに失敗した場合は、

  • どのフィールドになんのエラーメッセージを表示するか定義したmapを作成
  • NewErrorsViewSubmissionResponseでレスポンスを生成
  • レスポンスとして返す

の流れでエラーメッセージを表示します。

// Validate a message.
if err := validateChip(message); err != nil {
    // Create validation failed response.
    errors := map[string]string{
        "block_id_chip": "[ERROR] Please enter a number.",
    }

    resAction := slack.NewErrorsViewSubmissionResponse(errors)
    bytes, err := json.Marshal(resAction)
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to marshal a validation failed message: %w", err)
    }

    return events.APIGatewayProxyResponse{
        StatusCode:      200,
        IsBase64Encoded: false,
        Headers: map[string]string{
            "Content-Type": "application/json",
        },
        Body: string(bytes),
    }, nil
}

メッセージを作成、送付

さて、いよいよこれで最後です。注文完了のメッセージを作成して送信します。

Private Metadataに格納して引き継ぎ続けてきた「チャンネルID」、「注文内容」はここで使います。

まずは、Private Metadataを取得しましょう。

// Get private metadata
var privateMeta privateMeta
if err := json.Unmarshal([]byte(message.View.PrivateMetadata), &privateMeta); err != nil {
    return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to unmarshal private metadata: %w", err)
}

つづいてメッセージの見た目を生成。もう慣れたものですね。

// Send a complession message.
// - Create message options
option, err := createOption(message, privateMeta)
if err != nil {
    return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to create message options: %w", err)
}
func createOption(message slack.InteractionCallback, privateMeta privateMeta) (slack.MsgOption, error) {

	// Text section
	titleText := slack.NewTextBlockObject("mrkdwn", ":hamburger: *Thank you for your order !!*", false, false)
	titleTextSection := slack.NewSectionBlock(titleText, nil, nil)

	// Divider
	dividerBlock := slack.NewDividerBlock()

	// Text section
	sMenuText := slack.NewTextBlockObject("mrkdwn", "*Menu*\n"+burgers[privateMeta.Menu], false, false)
	sMenuTextSection := slack.NewSectionBlock(sMenuText, nil, nil)

	// Text section
	sSteakText := slack.NewTextBlockObject("mrkdwn", "*How do you like your steak?*\n"+privateMeta.Steak, false, false)
	sSteakTextSection := slack.NewSectionBlock(sSteakText, nil, nil)

	// Text section
	sNoteText := slack.NewTextBlockObject("mrkdwn", "*Anything else you want to tell us?*\n"+privateMeta.Note, false, false)
	sNoteTextSection := slack.NewSectionBlock(sNoteText, nil, nil)

	// Text section
	amount, err := strconv.ParseFloat(privateMeta.Amount, 64)
	if err != nil {
		return nil, fmt.Errorf("failed to convert amount to float64: %w", err)
	}

	chip, err := strconv.ParseFloat(message.View.State.Values["block_id_chip"]["action_id_chip"].Value, 64)
	if err != nil {
		return nil, fmt.Errorf("failed to convert amount to float64: %w", err)
	}

	amountText := slack.NewTextBlockObject("mrkdwn", "*Total amount :moneybag:*\n$ "+strconv.FormatFloat(amount+chip, 'f', 2, 64), false, false)
	amountTextSection := slack.NewSectionBlock(amountText, nil, nil)

	// Blocks
	blocks := slack.MsgOptionBlocks(
		titleTextSection,
		dividerBlock,
		sMenuTextSection,
		sSteakTextSection,
		sNoteTextSection,
		dividerBlock,
		amountTextSection,
	)
	return blocks, nil
}

最後に送信は、PostMessageです!

// - Post a message
api := slack.New(tokenBotUser)
if _, _, err := api.PostMessage(privateMeta.ChannelID, option); err != nil {
    return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to send a message: %w", err)
}

長かったですが、これがModalアプリのシンプルな例。
ソースの全文は、[GitHub](https://github.com/nicoJN/slack-modal-examples)に置いてあるので、適宜改良して使ってみてください。

:::message
本記事は、[著者のブログ](https://jimon.info/slack-modal-example-go/)からの転載です

長くなったので、STEP別に4つの記事に分割しています
- **STEP1:** [作るものの紹介+Botを呼び出して、トリガーメッセージを送付](https://zenn.dev/jimon/articles/d4bc599285e9aa79b0fb)
- **STEP2:** [お店情報のModalを送付](https://zenn.dev/jimon/articles/f948664f3968882d4afc)
- **STEP3:** [注文確認画面のModalを送付](https://zenn.dev/jimon/articles/93dccdc70cd0a8ce8d31)
- **STEP4:** 完了メッセージを送付**←今ココ!**
:::

Discussion