このページについて
タイトルの通りの作業を行った作業ログ。serverless framework を使っている。
slack に slash command 登録する
今は manifest ファイルを編集して登録させるのがいい模様。GUI からポチポチが何かうまく行かなかったのでこちらにトライした。
_metadata:
major_version: 1
minor_version: 1
display_information:
name: <command-name>
features:
app_home:
home_tab_enabled: true
messages_tab_enabled: true
messages_tab_read_only_enabled: false
bot_user:
display_name: <command-name>
always_online: true
slash_commands:
- command: /<your-command-name>
url: <serverless framework で作成した api endpoint>
description: <description>
usage_hint: /<your-command-name> help
should_escape: true
oauth_config:
scopes:
bot:
- commands
- chat:write
- chat:write.public
settings:
org_deploy_enabled: false
socket_mode_enabled: false
is_hosted: false
token_rotation_enabled: false
これを書いたあとは、Install Apps
リンクからアプリケーションのインストールを実行させる。
これで、Slack 内で /<your-command-name>
が叩けるようになる。
api の実装
ひとまずlambda までリクエストが到達したら呼ばれたチャンネルへテキスト返すまで。SlackRequest に ResponseURL があり(期限付き)、そこに対して Post を行うとメッセージが返せる。Slash Command の応答は 3秒以内だが、この値を使うと一次受けの lambda では後続の処理に投げたあと素早く 200 を返すようなこともできる。
slash command から送られてくる request body は url encode の形なので github.com/hetiansu5/urlquery を使って unmarshal してみた。
Text は space 区切りで文字列が入るので、split して、0 番目の値によって処理を分けて上げればよさそう。
package main
import (
"bytes"
"net/http"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/google/uuid"
"github.com/hetiansu5/urlquery"
"go.uber.org/zap"
"encoding/json"
"fmt"
"os"
)
type Item struct {
Id string `json:"id,omitempty"`
Title string `json:"title"`
Details string `json:"details"`
}
// https://api.slack.com/interactivity/slash-commands#app_command_handling
type SlackRequest struct {
Token string `query:"token"`
TeamID string `query:"team_id"`
TeamDomain string `query:"team_domain"`
EnterpriseID string `query:"enterprise_id"`
EnterpriseName string `query:"enterprise_name"`
ChannelID string `query:"channel_id"`
ChannelName string `query:"channel_name"`
UserID string `query:"user_id"`
Command string `query:"command"`
Text string `query:"text"`
ResponseURL string `query:"response_url"`
TriggerID string `query:"trigger_id"`
APIAppID string `query:"api_app_id"`
}
type SlackResponse struct {
Text string `json:"text"`
}
func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
zl, _ := zap.NewProduction()
v := &SlackRequest{}
if err := urlquery.Unmarshal([]byte(request.Body), v); err != nil {
zl.Error("", zap.Error(err))
return events.APIGatewayProxyResponse{StatusCode: 400}, nil
}
bss, _ := json.MarshalIndent(v, "", " ")
fmt.Printf("%v\n", string(bss))
bss, _ = json.MarshalIndent(request.Headers, "", " ")
fmt.Printf("%v\n", string(bss))
// Unmarshal to Item to access object properties
itemString := request.Body
itemStruct := Item{}
json.Unmarshal([]byte(itemString), &itemStruct)
res := SlackResponse{
Text: helpText,
}
bs, _ := json.Marshal(res)
req, err := http.NewRequest(
"POST",
v.ResponseURL,
bytes.NewBuffer(bs),
)
if err != nil {
return events.APIGatewayProxyResponse{StatusCode: 400}, nil
}
// Content-Type 設定
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return events.APIGatewayProxyResponse{StatusCode: 400}, nil
}
defer resp.Body.Close()
return events.APIGatewayProxyResponse{StatusCode: 200}, nil
}