専用アプリはもう要らない?LINE BotからAkerunを操作する(その2)

この記事は Akerun Advent Calendar 2020 - Qiita の13日目の記事です。

WebエンジニアのBunです。主にiOSアプリの開発を担当しています。

ちょうど1年前、「LINE BotからAkerunを操作する(その1)」を書かせていただきました。 LINE Botのグループ機能を使ったBotLINE BOT AWARDS グループトーク部門賞)を作成した経験もあるので、今回はその続きの内容(LINEグループでAkerunを操作する)になります。

akerun.hateblo.jp

17日目の記事(LINE Botから施錠解錠する)も書いたので、こちらも読んでみてください。

akerun.hateblo.jp

事前準備

LINE Botの設定

その1を参考にLINE Botを作成し、Botと友達になります。

Akerun合鍵発行

対象となるAkerunの合鍵を発行します。弊社のAkerun Developersをご参照ください。

developers.akerun.com

LINE Botのリッチメニューからインタラクティブ形式で複数の組織、Akerun、合鍵の設定も可能ですが、長くなってしまうので、別の機会で書きたいと思います。

データ保存用DB

グループ、ユーザー情報を保存する必要があるので、Bot側でデータベースを使う必要があります。Firebase、AWSなど各自の環境に合わせてDBを構築すれば良いと思います。 今回は、軽量ドキュメントデータベースのTinyDBを使いますが、DB周りの詳細処理は割愛します。

LINE Loginチャンネル作成

LINE Developersにログインし(アカウント、Providerが無い場合作成する)、LINE Loginチャンネルを作成します。

  • チャンネル作成

チャンネル作成

  • Channel IDとChannel secret

環境変数に設定する必要があるので、Channel IDとChannel secretをメモします。

Channel ID

Channel secret

  • Callback URLを設定

Botに合わせてドメインとPathを設定します。

Callback URL

LINEのグループ(或いはルーム)を作成

LINEで任意のグループを作成し、Akerunを使うユーザーを招待します。Botと連携してから招待しても問題ありません。

Botをグループに招待する

通常ユーザーの招待と同じ方法で既に友達になったBotをグループに招待します。

  • JoinEvent

Botをグループに追加した場合、JoinEventが発生します。eventからグループID(或いはルームID)を取得し、DBにグループを追加します。

  • LINE Login用Link作成

グループ内のユーザーがBotと友達になるようにLINE LoginのLinkを作成し、グループにメッセージを通知します。 LinkにLINE LoginチャンネルのChannel IDとCallback URLを指定します。 LINE Loginボタン(画像デザイン)について、以下をご参照ください。

developers.line.biz

# BotをGourpに追加した時、/callbackが呼ばれて、その後JointEventが呼ばれる
# 同じグループに複数Bot追加できない。2個目からは招待待ちになる。
@handler.add(JoinEvent)
def handle_join_message(event):
  print(event)
  if event.source.type == 'group':
    gid = event.source.group_id
  elif event.source.type == 'room':
    gid = event.source.room_id
  
  # 既に存在する場合追加されない
  db.add_group(gid, event.source.type)

  msgs=[]

  text = u'下のログインボタンで鍵を申請してください。'
  msgs.append(TextSendMessage(text = text))
  
  link_uri = 'https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id={}&redirect_uri={}&bot_prompt=normal&scope=profile&state={}'.format(line_login_channel_id, urllib.parse.quote(auth_url), gid)

  print(link_uri)
  msgs.append(ImagemapSendMessage(
    base_url=base_url + '/images/LINELogin',
    alt_text='this is an imagemap',
    base_size=BaseSize(height=302, width=1040),
    actions=[
      URIImagemapAction(
        link_uri=link_uri,
        area=ImagemapArea(x=0, y=0, width=1040, height=302)
      ),
    ]
  ))

  line_bot_api.reply_message(event.reply_token, msgs)

LINE Loginでログインし、Botと友達になる

グループ内のユーザーはLINE Loginボタンを押してBotと友達になります。 Botと友達になった場合、LINE Loginチャンネルで設定されたCallback URLにリダイレクトされます。ここでログインユーザー情報を取得し、DB上のグループにユーザーを追加します。LINEグループにもメッセージを通知します。

# グループに入るユーザーにログイン用ボタンLink(clientID/redirectURL)を用意し、ログインしてもらう
# ユーザーがログインボタンを押して、Botと友たちになる時、/authが呼ばれる
# ログイン成功後、/authにcodeが発行される(10min)ので、アクセストークンを取得する
@app.route("/auth", methods=['GET'])
def auth_callback():  
  print(request)
  code = request.args.get('code')
  gid = request.args.get('state')

  # 認証エラー
  if(code is None):
    #print 'Auth error: '
    error = request.args.get('error')
    errorState = request.args.get('state')
    errorMessage = request.args.get('error_description')
    print(error)
    print(errorState)
    print(errorMessage)
    return 'Auth Error'

  # token取得
  token = line_login_get_access_token(code)
  profile = line_login_get_user_profiles(token)
  uid = profile.user_id
  name = profile.display_name

  db.add_user(uid, name)

  msgs = []
  msgs.append(TextSendMessage(text = u'{}さんがグループに入りました'.format(name)))

  line_bot_api.push_message(gid, msgs)

  # ログイン成功時の画面を用意
  return render_template("index.html", title="Akerun Login", message=u"ログイン成功", friend_url=line_friend_url, qr_url=line_qr_url)

Token取得処理は下記通りです。LINE LoginチャンネルのChannel IDとChannel Secretが必要です。

def line_login_get_access_token(code):
  headers = {'Content-Type': 'application/x-www-form-urlencoded'}
  payload = {
    'grant_type': 'authorization_code',
    'client_id': line_login_channel_id,
    'client_secret': line_login_channel_secret,
    'code': code,
    'redirect_uri': auth_url
    }

  obj_request = Request(
          "POST",
          'https://api.line.me/oauth2/v2.1/token',
          headers = headers,
          data = payload
  )

  obj_session = Session()
  obj_prepped = obj_session.prepare_request(obj_request)
  obj_response = obj_session.send(obj_prepped,
                  verify=True,
                  timeout=60
                  )
  print('status_code:' + str(obj_response.status_code))
  print('obj_response.text:' + obj_response.text)
  response_dict = json.loads(obj_response.text)
  print(response_dict)

  #{
  #  "access_token": "xxxx",
  #  "expires_in": 2592000,
  #  "id_token": "xxx",
  #  "refresh_token": "xxx",
  #  "scope": "profile",
  #  "token_type": "Bearer"
  #}

  return response_dict['access_token']

以下でログインユーザーの情報を取得できます。

def line_login_get_user_profiles(token):
  headers = {'Authorization': 'Bearer ' + token}

  obj_request = Request(
          "GET",
          'https://api.line.me/v2/profile',
          headers = headers,
  )
  obj_session = Session()
  obj_prepped = obj_session.prepare_request(obj_request)
  obj_response = obj_session.send(obj_prepped,
                  verify=True,
                  timeout=60
                  )
  print('status_code:' + str(obj_response.status_code))
  # {
  # 'userId': 'xxxx', 
  # 'displayName': 'xxxx', 
  # 'pictureUrl': 'https://profile.line-scdn.net/xxxx'
  # }
  print('obj_response.text:' + obj_response.text)
  response_dict = json.loads(obj_response.text)
  print(response_dict)

  return response_dict

LINEグループでも誰がBotと友達になったか分かります。

ログイン

施錠解錠する

Botと友達になったユーザーは事前に設定されたAkerunに対して施錠解錠操作ができます。DBに施錠解錠時のログを追加すれば、履歴を管理することも可能です。

lock unlock メニュー

施錠解錠処理についてはその1もご参照ください。

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
  reply_msgs = []
  push_msgs = []
  uid = get_id(event.source)

  #profile.display_name #-> 表示名
  #profile.user_id #-> ユーザーID
  #profile.image_url #-> 画像のURL
  #profile.status_message #-> ステータスメッセージ
  profile = line_bot_api.get_profile(event.source.user_id)
  name = profile.display_name
  if event.message.type == 'text':
    if event.message.text[0] == cmd_prefix:
      cmd = event.message.text
      if cmd == cmd_prefix + 'lock':
        job_id = akerun_bot_lock('akerun api token', '組織ID', 'AkerunID')
        reply_msgs.append(TextSendMessage(text=u'施錠'))
        push_msgs.append(TextSendMessage(text = u'{}さんが解錠しました'.format(name)))
      elif cmd == cmd_prefix + 'unlock':
        job_id = akerun_bot_unlock('akerun api token', '組織ID', 'AkerunID')
        reply_msgs.append(TextSendMessage(text=u'解錠'))
        push_msgs.append(TextSendMessage(text = u'{}さんが施錠しました'.format(name)))

  # todo:pooling -> push
  if len(reply_msgs) > 0:
    line_bot_api.reply_message(
      event.reply_token,
      reply_msgs)
  if len(push_msgs) > 0:
    line_bot_api.push_message(test_group_id, push_msgs)

施錠解錠時、LINEグループにもメッセージを通知できるので、誰が操作したが分かります。

施錠解錠

まとめ

  • LINEグループ機能を活用すれば、簡単に複数のユーザーに合鍵を共有し、管理できるので、楽ですね。
  • 追加対応すれば、複数のAkerun、合鍵の設定と管理も全てLINE上で完結できるので、更に便利になります。
  • その1で既にAkerunを施錠解錠するBotを作成したので、グループでの施錠解錠は割と簡単にできました。ただ、DB周りもしっかり作る場合、そこそこ手間がかかりそうです。
  • LINE Bot、グループの制限かもしれませんが、ユーザーは同じBotが所属している複数のグループを同時に使うことができないので、複数のBotを作るなど工夫する必要があります。

そのうちLIFFも試しみたいと思います。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com