ベイスターズが勝ってる時のみ応答して、負けてる時はしらばっくれるAlexaスキルをつくった

#はじめに
私が物心ついたころ、日本で一番強いチームは横浜ベイスターズでした。
野手では谷繁、ローズ、駒田、鈴木尚、投手では三浦、斎藤隆、佐々木など…懐かしいですね。
やっぱり子供というものは、強いチームを好きになるもので、その時私はベイスターズのファンになりました。
ご存知の通り、ベイスターズはその後衰退の一途をたどるわけですが…

最近では仕事も忙しく、ベイスターズの試合を見ることは少なくなりましたが、いまだに試合結果だけは気になってちょくちょく見てしまいます。贔屓のチームが勝つと嬉しいですもんね。
ただ逆に、結果を見てベイスターズが負けていると萎えちゃいます。仕方ないですが。

そんな中、ある日ふと思ったんです。
「ベイスターズが勝ってる時だけ教えてくれれば、気分が落ち込まずに済むな…。」

#スキル
[ベイスターズ速報（非公式）](https://www.amazon.co.jp/dp/B07FVLLTKF/)
![bays.png](https://qiita-image-store.s3.amazonaws.com/0/104983/41a4be42-38c6-360b-25e1-2eb1b3a4c0a2.png)


#使い方
■勝ってる時
「ベイス速報を開いて」
↓
アレクサ「現在7回表です。ベイスターズが5対2で勝っています！」

■負けてる時
「ベイス速報を開いて」
↓
アレクサ「横浜Fマリノスの結果をお調べですか？」

これなら、もしピッチャーが大炎上していても、結果を知ることなく気持ち良く一日を過ごすことができますね:relaxed:

#構成
Amazon Echo Dot →　Alexa　→　AWS Lambda(Python) →　Sportsnavi(プロ野球)

Alexaスキルは、AWS Lambda上でNode.jsを使って開発するのがスタンダードかと思いますが、今回はスクレイピングにBeautifulSoupを使いたかったため、Python3で実装しました。

速報情報は[スポナビさん](https://baseball.yahoo.co.jp/npb/schedule/)のデータを利用させていただきました。

#ソースコード
Alexaコンソールの設定は、公式のチュートリアルなどをご参考にしていただければと思います。
今回はPythonスクリプトのみ掲載します。テンプレートとして「alexa-skills-kit-color-expert-python」を利用し、こちらのコードをベースに実装しました。
この記事を書いている時に気づいたのですが、[Alexa Skills Kit SDK for Pythonのベータ版](https://developer.amazon.com/ja/blogs/alexa/post/6175fab7-7cdc-4ac9-9f6b-2f0ee82906ae/announcing-an-easier-way-to-build-alexa-skills-using-python1)が6月にリリースされていたようで、こちらを利用すればもっとコード量を削減できるかと思います。

```python:lambda_function.py
import urllib.request
from bs4 import BeautifulSoup
import random

# --------------- Helpers that build all of the responses ----------------------

def build_speechlet_response(output):
    return {
        'outputSpeech': {
            'type': 'PlainText',
            'text': output
        },
        'card': {
            'type': 'Simple',
            'title': 'ベイスターズ速報',
            'content': output
        },
        'reprompt': {
            'outputSpeech': {
                'type': 'PlainText',
                'text': None
            }
        },
        'shouldEndSession': True
    }

def build_response(speechlet_response):
    return {
        'version': '1.0',
        'sessionAttributes': {},
        'response': speechlet_response
    }

# --------------- Functions that control the skill's behavior ------------------

def get_baystars_result():
    lose_text_list = ["今日はいい天気ですね","横浜Fマリノスの結果をお調べですか？","他人を応援するより自分が頑張ったほうが楽しいですよ","大魔神は今、馬ぬしをやっているそうです"]
                    
    html = urllib.request.urlopen('https://baseball.yahoo.co.jp/npb/schedule/').read()
    soup = BeautifulSoup(html, 'html.parser')
    

    #ベイスターズが含まれる対戦テーブルを取得
    #無い場合は試合無しと判断
    if soup.find(class_="yjMS",text="ＤｅＮＡ") == None:
        speech_output = "今日は試合が無いようです"
        return build_response(build_speechlet_response(speech_output))
    else:
        table = soup.find(class_="yjMS",text="ＤｅＮＡ").parent.parent

    #表の上のチーム:a,下のチーム:b
    team_a = table.select('.yjMS')[0].a.string
    team_b = table.select('.yjMS')[1].a.string
    
    team_a_score = table.select('.score_r')[0].string
    team_b_score = table.select('.score_r')[1].string
    
    game_status = table.select('.yjMSt')[0].string
    if game_status == "結果":
        lead_text = "試合終了！"
    elif game_status == "中止":
        lead_text = "試合は" + game_status + "になりました"
    else:
        lead_text = "現在" + game_status + "です"
    
    if game_status == "試合前" or game_status == "中止":
        speech_output = lead_text
    elif team_a == "ＤｅＮＡ" and int(team_a_score) > int(team_b_score):
        speech_output = lead_text + "ベイスターズは" + team_a_score + "対" + team_b_score + "で勝っています！" 
    elif team_b == "ＤｅＮＡ" and int(team_b_score) > int(team_a_score):
        speech_output = lead_text + "ベイスターズは" + team_b_score + "対" + team_a_score + "で勝っています！" 
    else:
        speech_output = random.choice(lose_text_list)
    
    return build_response(build_speechlet_response(speech_output))


def handle_session_end_request():
    speech_output = "Bye"
    return build_response(build_speechlet_response(speech_output))

# --------------- Events ------------------

def on_launch(launch_request):
    return get_baystars_result()

def on_intent(intent_request):
    intent_name = intent_request['intent']['name']

    if intent_name == "ResultIntent":
        return get_baystars_result()
    elif intent_name == "AMAZON.HelpIntent":
        return get_baystars_result()
    elif intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent":
        return handle_session_end_request()
    else:
        raise ValueError("Invalid intent")

# --------------- Main handler ------------------

def lambda_handler(event, context):
    if event['request']['type'] == "LaunchRequest":
        return on_launch(event['request'])
    elif event['request']['type'] == "IntentRequest":
        return on_intent(event['request'])
    elif event['request']['type'] == "SessionEndedRequest":
        return on_session_ended(event['request'])

```

#審査について
当初「ベイスターズ速報」で審査提出したのですが、チームから承認を得ていないと審査を通さないよと言われたため、「（非公式）」を付け足しました。スキル詳細についても同様で、非公式である旨を書き足しました。

また呼び出し名については、「ベイスターズ」のワードは使用しないように指摘があったため、「ベイス速報」としました。

1.非公式である旨を明記する
2.呼び出し名に固有名詞を使わない
の2点を守れば、審査は突破できそうです:thumbsup:

#おわりに
記事を見て少しでも面白い、参考になると思いましたら、いいねボタンを押していただけると幸いです。
また実装について、より良い方法などありましたら、コメントいただけると助かります:grinning:

その他いろいろと記事を書いてますので、興味のある方はぜひ！
[AIが選んだ本当に似ている有名人トップ10](https://qiita.com/Sinhalite/items/37a1c326cb2cd7034776)