LoginSignup
1
1

More than 1 year has passed since last update.

Google Cloud Platform, Cloud FunctionsでつくるLINE Bot(2)

Last updated at Posted at 2023-01-28

第1回でCloud FunctionsとLINE Botを作成し、Cloud Functionsにログの出力されることを確認しました。
第2回では受信したメッセージをそのまま返信する機能(オウム返し)を実装します。

完成イメージ

line-parrot.png

メッセージを受信する

受信するWebHookは以下のようなJSONになっています。
Messaging APIリファレンスも参考にしてください。

{
    "events": [
      {
        "source": {
          "userId": "U8510be0371fb4cf398a62d75427928b6",
          "type": "user"
        },
        "webhookEventId": "01GQRGNPKWAB8PJX98PZGPZP5Y",
        "deliveryContext": {
          "isRedelivery": false
        },
        "replyToken": "b9de30e38be744f192141be6b0fb5b0c",
        "timestamp": 1674786297985,
        "type": "message",
        "message": {
          "type": "text",
          "text": "こんにちは",
          "id": "17539623968937"
        },
        "mode": "active"
      }
    ]
}

したがって以下のようなGo構造体でデコードできます。

type MessageEvents struct {
	Events []MessageEvent `json:"events"`
}

type MessageEvent struct {
	ReplyToken string  `json:"replyToken"`
	Message    Message `json:"message"`
}

type Message struct {
	Type string `json:"type"`
	Text string `json:"text"`
}

メッセージの返信に以下2つが必要です。

  • (オウム返しなので) Message 構造体の Text 部分
  • 認証のため MessageEvent 構造体の ReplyToken 部分

受信部分の処理は以下のとおりです。
受信したJSONを MessageEvents 構造体にデコードするだけです。

func receive(r *http.Request) (MessageEvents, error) {
	var events MessageEvents
	reqBytes, err := io.ReadAll(r.Body)
	if err != nil {
		return events, fmt.Errorf("failed to read request body; %w", err)
	}
	defer r.Body.Close()

	if err := json.Unmarshal(reqBytes, &events); err != nil {
		return events, fmt.Errorf("failed to decode JSON; %w", err)
	}

	return events, nil
}

メッセージを返信する

応答メッセージを送るにしたがいコーディングします。

認証のためチャネルアクセストークンを付与します。
チャネルアクセストークンには3つ種類があります。

  • v2.1
  • 短期
  • 長期

今回はもっとも簡単な「長期」のチャネルアクセストークンを使用することにします。
LINE Developersのコンソール画面で、長期のチャネルアクセストークンを発行します。

発行したチャネルアクセストークンをSecret Managerに登録します。
secret-manager.png

さらにSecretをCloud Functionsの環境変数に設定します。
コンソールやgcloud CLIで設定できます。
私はTerraformの機能を使いました。
cloud-functions-secret.png

これでCloud Functionsから安全にチャネルアクセストークンを取得できるようになります。

返信部分の処理は以下のとおりです。
受信したReplyTokenとオウム返しを ReplyMessage 構造体につめ、JSONにエンコードします。
またチャネルアクセストークンを Authorization Bearer ヘッダーにセットします。

func reply(replyToken, text string) error {
	url := "https://api.line.me/v2/bot/message/reply"
	body, err := json.Marshal(ReplyMessage{
		ReplyToken: replyToken,
		Messages:   []Message{{Type: "text", Text: text}},
	})
	if err != nil {
		return fmt.Errorf("failed to encode JSON; %w", err)
	}
	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("failed to make new request; %w", err)
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Authorization", "Bearer "+os.Getenv("CHANNEL_ACCESS_TOKEN"))

	reqDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		return fmt.Errorf("failed to dump HTTP request; %w", err)
	}
	log.Print("dump HTTP request")
	log.Print(string(reqDump))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send HTTP request; %w", err)
	}
	defer resp.Body.Close()

	respDump, err := httputil.DumpResponse(resp, true)
	if err != nil {
		return fmt.Errorf("failed to dump HTTP response; %w", err)
	}
	log.Print("dump HTTP response")
	log.Print(string(respDump))

	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
		return fmt.Errorf("got error HTTP response; %d; %s", resp.StatusCode, resp.Status)
	}
	return nil
}

ソースコード全体

ソースコード全体は以下のとおりです。
https://github.com/hsmtkk/qiita-gcp-cf-line-bot/blob/main/parrot/parrot.go

次回

以上でオウム返しの処理を実装できました。
しかし、今の実装ではLINEプラットフォーム外からのアクセスも受け入れてしまう問題があります。
次回はLINEプラットフォームからのアクセスだけ処理するように実装を改善します。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1