5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 1 -グループ管理-

Last updated at Posted at 2020-06-20

bot用の管理機能を作るのは面倒だからLIFFでしたい!

LINEBotで色々開発をしていると、当然管理者用のAPIが生えて来たり、管理者用のリッチメニューが欲しくなったりしますよね。
今までの開発方法だと、管理者用のシステムを一つ作って、そこにソーシャルログインを連携させて、メンバー管理画面を作って、、、と平気でひと月やふた月飛んでいきます。
そこで、今回は、

  1. 管理画面はLIFFで作る
  2. 管理者用のLINEグループを一つ作る
  3. そこにメンバーを招待したら管理者にする (DBにユーザIDを追加)
  4. そこからメンバーを退出させたら管理者から除外する (DBからユーザIDを削除)
  5. ついでにリッチメニューもユーザー用と管理者用で切り替える
  6. LIFFでアクセストークンを取得してサーバ側で認証する (アクセストークンからユーザIDを取得し、DBにあるかチェックする)
よくあるパターン 新しいやつ
管理仮面 PCorスマホ用管理画面 LIFF
アクセス方法 URLを教える リッチメニューからアクセス
ログイン方法 ソーシャルログインなり、メアドなり実装する LINEログインを使う
というかリッチメニュー見ている時点でログイン済み
認証方法 自分でトークン管理する LIFFのアクセストークン
メンバー管理 メンバー追加画面を自分で実装してそこから追加する LINEの管理用グループを作ってそこに招待する

というのをやりました。

開発環境

  • Go 1.12
  • Rust 1.4.2
  • DynamoDB
  • Amazon Lambda

BotやLambdaの連携や設定、基本的なコードについては、[AWSLambda x LINEBot] GoとRustでも連携がしたい をご覧ください。

LINEグループで管理者のメンバー管理をする

管理画面の何がめんどいって、メンバーの追加・削除画面とログインと機能ですよね。
そこでまず、メンバー管理をLINEグループへの招待・退出だけでできるようにします。
LINEのグループは作った人以外でも招待・退出・追放できます。
仮にメンバーの誰かにグループを乗っ取られても、新しくグループを作ってそこのグループIDを処理対象にすれば良いのでセキュアです。

グループIDを取得するためのコードを書く

まず、グループIDを調べるために、botにグループ招待された時のイベント処理コードを書きます。
それ以外にグループIDを知るすべはなさそうです。
対象となるイベントは 参加イベント join です。

line/line.go

func (r *Line) EventRouter(eve []*linebot.Event) {
	for _, event := range eve {
		switch event.Type {
		case linebot.EventTypeMessage:
            // テキストメッセージが来た時の処理とか
		case linebot.EventTypeJoin:
            // これ
			log.Printf("Join group id : %v", event.Source.GroupID)
		case linebot.EventTypeMemberJoined:
            // グループにメンバーを招待した時の処理。後ほど記載
		case linebot.EventTypeMemberLeft:
            // グループからメンバーが退出した時の処理。後ほど記載
		}
	}
}

LINEグループを作成し、グループIDを取得する

お手元のLINEで、新しいグループを一つ作りまして、グループ名を 管理者部屋 とでもしておきます。
そして、botをグループに招待します。
すると、ログ(今回はCloudWatch)にグループIDが出力されるので、それをメモしておきます。

グループへの招待・退出イベントを実装する

先ほどと同じbotのイベント処理に追加します。

line/line.go
func (r *Line) EventRouter(eve []*linebot.Event) {
	for _, event := range eve {
		switch event.Type {
		case linebot.EventTypeMessage:
           // ...
		case linebot.EventTypeJoin:
			log.Printf("Join group id : %v", event.Source.GroupID)
		case linebot.EventTypeMemberJoined:
           // 管理者部屋にメンバーが追加された時の処理
			if event.Source.GroupID == 管理部屋のグループID {
				if event.Joined != nil {
					// handleMemberJoined(r, event.Joined.Members)
				} else {
					handleMemberJoined(r, event.Members, event.ReplyToken)
				}
			}
		case linebot.EventTypeMemberLeft:
           // 管理者部屋からメンバーが退出した時の処理
			if event.Source.GroupID == 管理部屋のグループID {
				if event.Left != nil {
					// handleMemberLeft(r, event.Left.Members)
				} else {
					handleMemberLeft(r, event.Members)
				}
			}
		}
	}
}

これでイベントはフックできるので、後はユーザIDを取得してDBに入れればOKです。
で、このコードのここの部分なんですが、

				if event.Joined != nil {
					// handleMemberJoined(r, event.Joined.Members)
				} else {
					handleMemberJoined(r, event.Members, event.ReplyToken)
				}

実は公式ドキュメント通りにやると、 event.Joined == nil となっていてぬるぽで落ちます。
公式ドキュメントでは joined または left にメンバーが入ってくると記載されていますが、

LINE公式ドキュメントより
{
  "replyToken": "0f3779fba3b349968c5d07db31eabf65",
  "type": "memberJoined",
  "mode": "active",
  "timestamp": 1462629479859,
  "source": {
    "type": "group",
    "groupId": "C4af4980629..."
  },
  "joined": {
    "members": [
      {
       "type": "user",
        "userId": "U4af4980629..."
      },
      {
        "type": "user",
        "userId": "U91eeaf62d9..."
      }
    ]
  }
}

だが、実際には
{
  "replyToken": "hoge",
  "type": "memberJoined",
  "mode": "active",
  "timestamp": 11111111111,
  "source": {
    "type": "group",
    "groupId": "管理部屋のグループID"
  },
  "members": [
    {
     "type": "user",
      "userId": "U4af4980629..."
    },
    {
      "type": "user",
      "userId": "U91eeaf62d9..."
    }
  ]
}

実際にはevent直下の members にメンバー情報が入って来ます。
そのうち修正されるかもしれないので、if で正しいと思われるコードを残してあります。(line-sdk-go のバージョンは7.4.0)

管理者情報をDynamoDBに保存し、リッチメニューを切り替える

LINEのユーザIDを取得し、DynamoDBに追加すれば完了です。
今回はDynamoDBの処理はRustで書いているので、Lambdaを呼び出すだけになっています。
リッチメニューについては下の方でまとめて記載します。

func handleMemberJoined(r *Line, users []*linebot.EventSource, replyToken string) {
	type MemberEvent struct {
		UserIDs []string `json:"user_ids"`
	}
	userIDs := make([]string, len(users))
	for i, u := range users {
		userIDs[i] = u.UserID
	}
    // リッチメニューを管理者用に切り替える
	r.LinkRichMenu(userIDs)

	payload, _ := json.Marshal(MemberEvent{
		UserIDs: userIDs,
	})

	_, err := lambda.New(session.New()).Invoke(&lambda.InvokeInput{
		FunctionName:   aws.String(ARN + "myproject-" + os.Getenv("ENV") + "-addUsers"),
		Payload:        payload,
		InvocationType: aws.String("RequestResponse"),
	})
	if err != nil {
		log.Println(err)
	}

    // このイベントには応答できるので、一言挨拶があると嬉しいかも?
	r.ReplyTextMessage(replyToken, "よろしくね")
}

func handleMemberLeft(r *Line, users []*linebot.EventSource) {
	type MemberEvent struct {
		UserIDs []string `json:"user_ids"`
	}
	userIDs := make([]string, len(users))
	for i, u := range users {
		userIDs[i] = u.UserID
	}
        // 管理者用のリッチメニューを解除 (デフォルトのユーザ用のメニューになる)
	r.LinkRichMenu(userIDs)

	payload, _ := json.Marshal(MemberEvent{
		UserIDs: userIDs,
	})

	_, err := lambda.New(session.New()).Invoke(&lambda.InvokeInput{
		FunctionName:   aws.String(ARN + "myproject-" + os.Getenv("ENV") + "-removeUsers"),
		Payload:        payload,
		InvocationType: aws.String("RequestResponse"),
	})
	if err != nil {
		log.Println(err)
	}
}

自分自身を追加するのを忘れずに。。

グループを作るときは、必ず自分が作ったものにbotを招待しないといけないので、 自分自身を招待する ということができません。
その為、誰かに管理者部屋に参加してもらってから自分は一旦抜け、改めて招待してもらうか、直接DynamoDBをいじるなりして自分のユーザIDヲDBに追加する必要があります。

長くなって来たので一旦ここまで、次に続く。

リッチメニューを切り替える

[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 2 -リッチメニュー切替-

LIFFでアクセストークンを取得し、APIで認証する

(明日といいつつ、来週かな)

20200622 ソースコード修正

handleMemberJoined()とhandleMemberLeft()のユーザIDを配列にしているところで、
makeした直後にappendにしているのを修正しました。
なんか空のユーザIDが入ってきておかしいなと思った。

5
7
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
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?