LoginSignup
9
10

More than 3 years have passed since last update.

不審者情報をマップ付きで通知してくれるLINE BotをAWSで構築した

Last updated at Posted at 2018-08-12

はじめに

私は生まれてからずっと東京近辺に住んでいるのですが、1日1回は挙動のおかしい人を見かけるんですよね。
変な人から身を守るためには、事前に情報を入手することが必要です。
ということで、今回は警視庁が発表している不審者情報をLINEで通知してくれるBotを作成しました。

元ネタは以下の記事です。
東京23区の不審者情報をプッシュしてくれるLINE Botつくってみた|立花翔|note

今回の開発ではテキスト情報にプラスして、住所からマップ画像を生成して通知できるようにしました。

友達登録

husin.png

Zp7yCyhv-z.png
スマホの方はこちら

使い方

アカウントを登録し、通知対象のエリアを登録します。
IMG_3560.jpg

その後毎日1回、通知対象エリアの不審者情報がある場合は、情報が配信されます。
IMG_3558.jpg

ちなみにこのケースだと、おじさんは道を尋ねて見事不審者データベース入りを果たしました:cop_tone1:

構成

LINE不審者.jpg

まずLINEのBotアカウントをフォローすると、Webhook URLに設定したAPI Gatewayにリクエストが送られます。それはLambdaで受け取り、ユーザIDをDynamo DBに登録します。
その後ユーザより通知対象エリア(「足立区」「あきる野市」など)をテキストで送信してもらい、同様の流れでエリアをDB登録します。
あとは配信用のLambda関数を定期実行させて、通知対象エリアと一致するユーザに対して、Push APIで不審者情報とマップ画像を送信します。

手順

①Dynamo DBのテーブル作成
「ユーザID」と「通知対象エリア」を値として持つテーブルを作成します。
(プライマリキーはユーザID)

②Lambda関数の作成
言語はPython3を使用しました。

登録
import urllib.request
import json
import boto3
from boto3.dynamodb.conditions import Key

DYNAMO_DB = boto3.resource('dynamodb')
LINE_CHANNEL_ACCESS_TOKEN =''

def message(event):
    DYNAMO_DB.Table('SuspiciousPersonInfoUser').update_item(
        Key={
            'user_id': event['source']['userId']
        },  
        UpdateExpression='set area=:area',
        ExpressionAttributeValues={
            ':area': event['message']['text']
        }
    )

    REPLY_URL = 'https://api.line.me/v2/bot/message/reply'
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
    }
    body = {
        'replyToken': event['replyToken'],
        'messages': [
            {
                'type': 'text',
                'text': event['message']['text'] + 'を登録しました。'
            }
        ]
    }

    req = urllib.request.Request(REPLY_URL, data=json.dumps(body).encode('utf-8'), method='POST', headers=headers)
    urllib.request.urlopen(req)

def follow(event):
    DYNAMO_DB.Table('SuspiciousPersonInfoUser').put_item(
        Item={
            'user_id': event['source']['userId']
        }   
    )

def unfollow(event):
    DYNAMO_DB.Table('SuspiciousPersonInfoUser').delete_item(
        Key={
            'user_id': event['source']['userId']
        }
    )

def lambda_handler(request, context):
    for event in request['events']:
        if event['type'] == 'message':
            message(event)
        elif event['type'] == 'follow':
            follow(event)
        elif event['type'] == 'unfollow':
            unfollow(event)  
    return {'statusCode': 200, 'body': '{}'}
通知
import urllib.request
import urllib.parse
import datetime
import json
import boto3
from boto3.dynamodb.conditions import Key
from bs4 import BeautifulSoup

def push_message(user_id_list,encode_place,message):
    MULTICAST_URL = 'https://api.line.me/v2/bot/message/multicast'
    LINE_CHANNEL_ACCESS_TOKEN = ''
    GOOGLE_MAP_API_KEY = ''
    map_url = 'https://maps.google.com/maps/api/staticmap?center=' + encode_place + '&zoom=15&size=250x250&&scale=2&key=' + GOOGLE_MAP_API_KEY
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
    }
    data = {
        'to' : user_id_list,
        'messages' : [
            {
                'type' : 'text',
                'text' : message
            },
            {
                'type' : 'image',
                'originalContentUrl' : map_url,
                'previewImageUrl' : map_url
            }
        ]
    }

    request = urllib.request.Request(MULTICAST_URL, data=json.dumps(data).encode('utf-8'),method='POST', headers=headers)
    urllib.request.urlopen(request)

def main():
    day = datetime.date.today().day

    try:
        req = urllib.request.urlopen('http://www.keishicho.metro.tokyo.jp/kurashi/higai/kodomo/fushin/' + str(day) + '_fushin.html')
    except urllib.error.HTTPError as e:
        print('Error Code:' + str(e.code))
        return

    soup = BeautifulSoup(req,'html.parser')
    table = soup.find('table')
    td_list = table.find_all('td')
    th_list = table.find_all('th')

    for i,td in enumerate(td_list):
        place = td.text.split('、')[2].split('の')[0].split('付近')[0]
        encode_place = urllib.parse.quote(place)
        user_id_list = []

        DYNAMO_DB = boto3.resource('dynamodb')
        response = DYNAMO_DB.Table('SuspiciousPersonInfoUser').query(
            IndexName='area-index',
            KeyConditionExpression=Key('area').eq(th_list[i].text)
        )
        items = response['Items']

        for item in items:
            user_id_list.append(item['user_id'])

        message = str(td.find('p'))[3:-4].replace('<br/>','\r\n')

        if user_id_list:
            push_message(user_id_list,encode_place,message)

def lambda_handler(event, context):
    main()

DynamoDBへの読み書きが発生しますので、IAMで権限を設定する必要があります。

③API Gatewayの設定
Lambda関数をPOSTメソッドの送信先に設定します。
(登録用Lambda関数のARNを設定)

④LINE Messaging APIの設定
LINE Developerより、Webhook URLにAPI Gatewayで生成したURLを設定します。
hushin_developer.png

⑤CloudWatch Eventsで定期実行の設定
毎日12:10に通知用Lambda関数が実行されるようにcron式を設定する。

fin.

おわりに

このBotがあれば、東京へお出かけする時も安心です:relaxed:
あっ、ちなみに不審者情報は平日にしか更新されませんので、休日出かける際は役に立ちませんのでご注意ください:sweat_smile:

少しでも「面白い!」「参考になる!」と思いましたら、いいねボタンをクリックしていただけると幸いです:bow_tone1:

9
10
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
9
10