AWS+Lambda+API+GOでオウム返しLine Bot


# はじめに
- 「AWSとGoを勉強したい！」ということで、オウム返しをするLine Botを作成しようと思いました。
- AWSを使ったサーバーレスの基本の勉強にもなってよかったです！


# 目標
- 以下の感じでオウム返ししてくれるbotを作成します。
![oumu_kansei.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/a1e1c91b-42f7-49c5-9445-6436b0eeb809.png)


# 全体の構造
- Lineにメッセージを送ると、LINEプラットフォームは設定されたWebhook URLに対してPOSTリクエストを送信する。
- API Gatewayは、このリクエストをトリガーにLambda関数を実行する。
- Lambda関数はGo言語で書かれたコードを実行する。

![line_bot_aws_oumu.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/d942153a-c30b-20a8-c4ab-c53021db30b4.png)


# 作成手順

## 手順１：LINE Developersの作成とログイン
- 以下のサイトからDevelopersの作成とログインが行えます。

https://developers.line.biz/ja/

- 作り方がわからない人は以下が参考になります。
- 作成したボットを友達追加しとくと便利です！

https://qiita.com/cog1t0/items/cc7779345a01192d8f01


## 手順２：IAMロールの作成


- Lambdaを使う際には、IAMロールを作成して、権限を設定する必要があります。
- 作成しなくてもデフォルトロールが割り振られますが、デフォルトロールに権限のない操作(データベースの読み書きなど)を行うとエラーが出るので、IAMロールをあらかじめ作っておくのがおすすめです。

### 作成方法

- 「IAM>ロール」からロールを作成する
- 信頼されたエンティティタイプ：AWSのサービス
- サービスまたはユースケース：Lambda
- `AWSLambdaBasicExecutionRole`を検索して付与
- ロール名を任意に設定して作成します。


## lambdaの作成
### 作成方法
- lambda>関数の作成
- 「一から作成」を選択
- 関数名：「EchoLineBotHandler」に設定します。
- ランタイム：「Amazon Linux 2023」に設定します。
- デフォルトの実行ロール変更>既存のロールを使用から、先ほど作成したIAMロールを指定します。

![Untitled (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/51591a5c-995e-7481-19f5-88b5f542f60c.png)



## Goコードを作成

- 次にGoでオウム返しするコードを書きます。以下がコードになります。

```go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/line/line-bot-sdk-go/linebot"
)

type Webhook struct {
	Destination string           `json:"destination"`
	Events      []*linebot.Event `json:"events"`
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	// LINE Botの初期化
	bot, err := linebot.New(
		os.Getenv("LINE_CHANNEL_SECRET"),
		os.Getenv("LINE_CHANNEL_ACCESS_TOKEN"),
	)
	if err != nil {
		log.Print(err)
		return events.APIGatewayProxyResponse{
			StatusCode: http.StatusInternalServerError,
			Body:       fmt.Sprintf(`{"message":"%s"}`, http.StatusText(http.StatusInternalServerError)),
		}, nil
	}

	// リクエストボディを解析
	var webhook Webhook
	if err := json.Unmarshal([]byte(request.Body), &webhook); err != nil {
		log.Print(err)
		return events.APIGatewayProxyResponse{
			StatusCode: http.StatusBadRequest,
			Body:       fmt.Sprintf(`{"message":"%s"}`, http.StatusText(http.StatusBadRequest)),
		}, nil
	}

	// イベント処理
	for _, event := range webhook.Events {
		// イベントが一般的なメッセージイベントの場合
		if event.Type == linebot.EventTypeMessage {
			switch message := event.Message.(type) {
			// メッセージイベントの中身がテキストの場合(画像とかではなく)
			case *linebot.TextMessage:
				//オウム返し
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(message.Text)).Do(); err != nil {
					log.Print(err)
					return events.APIGatewayProxyResponse{
						StatusCode: http.StatusInternalServerError,
						Body:       fmt.Sprintf(`{"message":"%s"}`, http.StatusText(http.StatusInternalServerError)),
					}, nil
				}
			}
		}
	}

	return events.APIGatewayProxyResponse{
		StatusCode: 200,
		Body:       `{"message":"Success"}`,
	}, nil
}

func main() {
	lambda.Start(handler)
}

```

## 手順３：Goコードをzip化
- LambdaにGoコードをアップロードするにはzipファイルである必要があります。
- Goコードをzipファイルにするやり方は環境によって違い、以下のリンクを参考に行うことができます。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-package.html

<details><summary>私は、windowsでPowerShelです。人によって違うので参考程度に！</summary><div>

```bash
# lambdaパッケージをインストール。
# Lambdaと関数コードの間の相互作用を管理するランタイムインターフェイスの実装を提供
go get github.com/aws/aws-lambda-go/lambda

# 実行可能ファイルと必要なファイル群を正しくZIP形式でパッケージングしてくれるツール
go install github.com/aws/aws-lambda-go/cmd/build-lambda-zip@latest

# power shellでビルドした
# このコードの開設は、GPTがしてくれたよ！
$env:GOOS = "linux"
$env:GOARCH = "amd64"
$env:CGO_ENABLED = "0"
go build -tags lambda.norpc -o bootstrap main.go
~\Go\Bin\build-lambda-zip.exe -o myFunction.zip bootstrap

```

> GPTのお言葉
> 
> 1. **環境変数の設定**:
>     - **`$env:GOOS = "linux"`**: Goのコンパイルターゲットのオペレーティングシステム(OS)をLinuxに設定します。AWS Lambdaの実行環境はLinuxであるため、これに合わせてコンパイルする必要があります。
>     - **`$env:GOARCH = "amd64"`**: コンパイルターゲットのアーキテクチャをamd64（x86_64）に設定します。これは現在のところ、AWS Lambdaでサポートされている主なアーキテクチャです。
>     - **`$env:CGO_ENABLED = "0"`**: CGOを無効にします。CGOはC言語のコードをGoコードに組み込むために使用されますが、Lambda環境ではC言語のライブラリを利用することが推奨されていません。これにより、ポータビリティと互換性を高めることができます。
> 2. **コンパイルコマンド**:
>     - **`go build -tags lambda.norpc -o bootstrap main.go`**: Goのビルドコマンドを使用して、**`main.go`**ファイルから実行可能ファイルをコンパイルします。ビルドタグ**`lambda.norpc`**を使用して、特定のLambdaの最適化を有効にする場合があります。出力ファイル名は**`bootstrap`**と指定されています。AWS Lambdaでは、カスタムランタイムを使用する場合、実行ファイルの名前を**`bootstrap`**にする必要があります。
> 3. **パッケージングコマンド**:
>     - **`~\Go\Bin\build-lambda-zip.exe -o myFunction.zip bootstrap`**: 事前にインストールした**`build-lambda-zip`**ツールを使用して、**`bootstrap`**実行ファイルを含むZIPファイルを作成します。このZIPファイルは、Lambdaへのアップロードに使用され、**`myFunction.zip`**という名前で出力されます。


</div></details>





- 「lambda>関数>コード」から、zipファイル化出来たらLambda関数に作成したzipファイルをアップロードします。

![upload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/c13a2c4d-d9ad-06d7-673d-b68324287964.png)


## 手順４：Lambda関数で環境変数を設定
- Goコード内には環境変数を使っているので、環境変数を設定する必要があります。
- 具体的には以下のコード部分です。


```go
os.Getenv("LINE_CHANNEL_SECRET"),
os.Getenv("LINE_CHANNEL_ACCESS_TOKEN"),
```

- 「lambda>関数>設定>環境変数」から、設定します。
- それぞれの値に関しては、Line Debeloppersから確認できます。
    - キー：LINE_CHANNEL_SECRET　値：Line Debeloppersから確認
    - キー：LINE_CHANNEL_ACCESS_TOKEN　値：Line Debeloppersから確認

## 手順５：APIの作成
- 「API Gateway>API」からAPIを作成します。
    - APIタイプ：「Rest API」
    - API名：「LineEchoAPI」

![rest_api.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/85f66b66-5789-11b0-6fe9-66d85164755c.png)

- 次は「API Gateway>API>リソース」から、リソースを作成します。
    - リソース名：「echo」
    - CORSにチェックを入れる

![resosu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/0ac2fea1-1821-74d4-dd29-9edf1eb89f15.png)

- 「API Gateway>API>リソース」から、メソッドを作成します。
    - メソッドタイプ：「POST」
    - 統合タイプ：「Lambda関数」
    - Lambdaプロキシ統合にチェックを入れる
    - Lambda関数を、先ほど作った関数に指定

![mesod-new.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/5185a975-6f1e-0dc6-51bc-160556f58e0e.png)


- 「APIデプロイ」を押してデプロイします。
- ステージ名は、開発フェーズならdev、本番ならprodとかおすすめです。
    - ステージ：「新しいステージ」
    - ステージ名：「dev」

![deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/67e9a0c4-f8bc-4665-d98c-a2ea5a679d9f.png)

- 最後に、「API Gateway>API>ステージ」から、作成したAPIのURLをコピーしておきます。

![Untitled (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/d78d2179-511e-1ae3-e709-78566adb191d.png)


## 手順６：Line DeveloperのWebhookを設定
- 自身のLine Developerから、「Messaging API設定>Webhook設定」を開きます。
    - Webhook URL:先ほどコピーしたURL/リソース名
    - Webhookの利用にチェックを入れる


![Untitled (10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/98dbe7ba-27ae-7a9d-6042-6c1f158fcdaa.png)


## 手順７：完成！！
- 結構大変でした。。。

![oumu_kansei.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525798/a1e1c91b-42f7-49c5-9445-6436b0eeb809.png)