bot用の管理機能を作るのは面倒だからLIFFでしたい!
LINEBotで色々開発をしていると、当然管理者用のAPIが生えて来たり、管理者用のリッチメニューが欲しくなったりしますよね。
今までの開発方法だと、管理者用のシステムを一つ作って、そこにソーシャルログインを連携させて、メンバー管理画面を作って、、、と平気でひと月やふた月飛んでいきます。
そこで、今回は、
- 管理画面はLIFFで作る
- 管理者用のLINEグループを一つ作る
- そこにメンバーを招待したら管理者にする (DBにユーザIDを追加)
- そこからメンバーを退出させたら管理者から除外する (DBからユーザIDを削除)
- ついでにリッチメニューもユーザー用と管理者用で切り替える
- 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
です。
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のイベント処理に追加します。
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が入ってきておかしいなと思った。