Pythonで外部サービス(Discord,Twitter,YouTube,ブラウザ,Heroku)に触ってボットを作る

#はじめに
　この記事は、Pythonから外部サービス(Twitter,YouTube,ブラウザ)を利用して、Discordで動くボットを作成することを目指したものです。
　すでにPythonでDiscordのボットを動かす記事は多く出ているため、この記事ではPythonと外部サービスやAPIを連携させる際の、具体的な実装を書きました。
　そのため、重要でないプログラムや外部記事に詳細が載っているものは記載を割愛しています。

#なんちゃってUML

今回作るものの設計書です。経験少ないまま作成したものですのでご容赦ください。

【配置図】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/883d8d2d-3b69-9bff-8d9c-c6575a530a57.png)

【アクティビティ図】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/818656bd-8230-89c9-280b-357690e2ce70.png)

【クラス図】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/871b3b7b-f563-3c85-009c-138269cb6c13.png)

【シーケンス図】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/c98a51ab-c94a-212d-26e6-9d2f5d28768b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/b9a4fbed-b85d-1d24-53a6-3b47415f8892.png)

#作業手順

##1.YouTubeのチャンネル名から動画URL

###1-1.YouTube Data API v3の有効化（説明割愛）

(i)gcpのアドレスにアクセスしてプロジェクトを作成します。

https://console.developers.google.com/project

(ii)作成したプロジェクトを選択し、「YouTube Data API v3」を有効化し、APIキーを作成します。

※参考

https://qiita.com/g-k/items/7c98efe21257afac70e9

###1-2.YouTube APIの利用

(i)Pythonにrequestsパッケージをインストールします。

```shell
python -m pip install requests
```

(ii)YouTubeのAPIを用いたpythonのプログラムを作成します。
作成するプログラムは、なんちゃってUMLのクラス図におけるYoutubeApi.pyです。
プログラムは下記の通りです。

```python:YoutubeApi.py
import requests
import utilities

class YoutubeApi:
    def get_latest_movie(self, channel_name):
        API_KEY=作成したAPIキー

        url = utilities.get_url_with_params(
            url="https://www.googleapis.com/youtube/v3/search",
            key=API_KEY,
            maxResults = 1,
            part="snippet",
            q=channel_name,
            type="channel",
        )

        response = requests.get(url).json()
        channel_id = response["items"][0]["id"]["channelId"]

        url = utilities.get_url_with_params(
            url="https://www.googleapis.com/youtube/v3/search",
            key=API_KEY,
            maxResults = 1,
            order="date",
            part="snippet",
            channelId=channel_id,
            type="video",
        )

        response = requests.get(url).json()
        video_id = response["items"][0]["id"]["videoId"]

        video_url=f"https://www.youtube.com/watch?v={video_id}"
        return video_url

if __name__=="__main__":
    api = YoutubeApi()
    print(api.get_latest_movie("米津玄師"))
```

(iii)作成したプログラムを実行します。
正常に実行できれば、指定したチャンネルの最新動画のurlが表示されます。

※参考

https://developers.google.cn/youtube/v3/docs/search/list?hl=ja

https://qiita.com/yasudadesu/items/df76947f5b6ac955521f

https://stackoverflow.com/questions/44667751/youtube-v3-api-to-get-a-channel-id

##2.Twitterのアカウント名からツイートURL

###2-1.Twitter APIの利用（説明割愛）

(i)Twitterの開発者ページにアクセスします。

https://developer.twitter.com/en/apps/

(ii)「Create an app」ボタンから、APIの利用申請を行います。
申請が許可されればメールが届きます。

(iii)申請許可後、Twitterの開発者ページにて、「Create App」ボタンからアプリの作成を行います。

※参考

https://techacademy.jp/magazine/51411

###2-2.Twitter APIの利用

(i)pythonにてtweepyパッケージをインストールします。

(ii)TwitterのAPIを用いたPythonのプログラムを作成します。
作成するプログラムは、なんちゃってUMLのクラス図におけるTwitterApi.pyです。
プログラムは下記の通りです。

```python:TwitterApi.py
import tweepy

class TwitterApi:
    def get_latest_tweet(self, account_name):
        CONSUMER_KEY = 作成したアプリのコンシューマーキー
        CONSUMER_SECRET = 作成したアプリのコンシューマーシークレット
        BEARER_TOKEN = 作成したアプリのベアラートークン
        ACCESS_TOKEN = 作成したアプリのアクセストークン
        ACCESS_TOKEN_SECRET = 作成したアプリのアクセストークンシークレット

        auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
        auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
        api = tweepy.API(auth, wait_on_rate_limit=True)

        tweet = api.user_timeline(account_name, count=1)[0]
        screen_name = tweet.user.screen_name
        id = tweet.id
        url = f"https://twitter.com/{screen_name}/status/{id}"
        return url

if __name__ == "__main__":
    api = TwitterApi()
    print(api.get_latest_tweet("@Genshin_7"))
```

(iii)作成したプログラムを実行します。
正常に実行されれば、指定したツイッターアカウントの最新ツイートのurlが表示されます。

※参考

https://aiacademy.jp/media/?p=117

##3.Chromiumを使ったGoogle検索

(i)Pythonに以下のパッケージをインストールします。
selenium,chromedriver_binary

(ii)Google検索を行うPythonのプログラムを作成します。

```python:ChromiumBrowser.py
import chromedriver_binary
from selenium import webdriver

class ChromiumBrowser:
    def get_url(self, q):
        driver = webdriver.Chrome()
        driver.get(f"https://www.google.com/search?q={q}")
        element = driver.find_element_by_class_name("yuRUbf")
        url = element.find_element_by_tag_name("a").get_attribute("href")
        driver.quit()
        return url

if __name__ == "__main__":
    browser = ChromeBrowser()
    print(browser.get_url("原神公式HP"))
```

(iii)作成したプログラムを実行します。
検索ワードに対する検索結果のURLが表示されます。

※参考

https://qiita.com/memakura/items/20a02161fa7e18d8a693

https://watlab-blog.com/2019/08/15/python-google-searchlist/#PythonGoogleURL

##4.HerokuでのDiscordのボットの操作

###4-1.Herokuのスケジューラー

(i)Herokuの仕様からボットの稼働時間をどのようにすればよいかを考えます。
目標は、毎日必要な時間にボットを稼働させることです。
問題はHerokuの無料枠ではボットを常に稼働させることができないことです。
元々、Herokuには無料で処理を定期実行できるschedulerがあります。
schedulerは、Herokuのウェブページでクレジットカードを登録することで、利用できます。
しかし、今回はschedulerを使いません！！自分で作ります！！
スケジューラーを自作するには、Herokuの仕様を知っておく必要があります。

以下にHerokuの起動と停止に関する仕様をまとめます。
●再起動でファイル変更が廃棄される
●再起動は24時間（+0~216分）のサイクルで実行
●プロセスによる停止後に自動で再起動が行われる
　・停止１回目：直ちに再起動される
　・停止２回目以降：２０分、４０分、６０分、１８０分、３２０分の順で再起動の間隔が長くなる
　・再起動が成功すると上記の間隔はリセットされる
●未認証アカウントの場合、無料の最大稼働時間は550時間/月である。

以上から、毎日指定時間のみ稼働させるための方法を考えます。
毎日稼働させるとすると、550/30≒18時間が最大となります。
また、停止後の再起動サイクルの最大値は320分であるため、稼働時間を5時間以下にすると、稼働しない日が発生する可能性があります。
そのため、毎日必ず稼働させるには、稼働時間を5時間より長く18時間より短くする必要があります。
また、稼働時間の指定は時刻の判定を使うことで実現できます。ただし、再起動サイクルが長いため、指定時間になったらすぐに起動する保証はありません。
とはいえ、ボットをざっくり毎日稼働させるプログラムを実装できることがわかりました。

※参考

https://devcenter.heroku.com/articles/dynos#restarting

(ii)pythonでボットを指定時間以内のみ動作させるプログラムを作成します。

```python:BotScheduler.py
import sys
import utilities

class BotScheduler:
    def __init__(self):
        self.start_time_hour = 18
        self.running_hours = 8
        self.hour_count = 0

    def check_start_time(self):
        if utilities.get_jst_nowtime().hour < self.start_time_hour:
            print("bot is closed...")
            sys.exit()

    def check_end_time(self):
        if self.running_hours <= self.hour_count:
            print("shutting down...")
            sys.exit()
        else:
            self.hour_count += 1

if __name__ == "__main__":
    obj = BotScheduler()
    obj.check_start_time()
    obj.check_end_time()
```

(iii)作成したプログラムを実行します。
実行すると、現在時刻が18時前の時、「bot is closed...」と表示されます。
上記の変数の値では18時から8時間、稼働するように設定しています。

###4-2.Discordのボットの操作

####4-2-1Discordのボットの作成

(i)ボットのアプリケーションを作成します。
初めにDiscordの開発者ページにアクセスします。
https://discord.com/developers/applications

ボットの作成のために下記の項目を設定します。

|項目|値|
|---|---|
|NAME|MyTestBot（任意）|
|APP ICON|※下記の画像を利用（任意）|
|PUBLIC BOT|OFF|

※今回、アプリアイコンとして使用した画像
![アプリケーションアイコン](http://flat-icon-design.com/f/f_object_165/s256_f_object_165_0bg.png)

(ii)サーバーにBotを招待する
Discordのサーバーがなければ作成します。
ブラウザのボットのOAuth2ページのURLから、下記の表の項目を設定し、Botの招待を行います。

|項目|値|
|---|---|
|SCOPES|bot|
|BOT PERMISSIONS|Administrator|

※参考

https://note.com/exteoi/n/nf1c37cb26c41

http://flat-icon-design.com

####4-2-2.ボットの操作

(i)pythonにdiscord.py[voice]パッケージをインストールします。

(ii)discordパッケージを用いたpythonのプログラムを作成します。
作成するプログラムは、なんちゃってUMLのクラス図におけるMain.pyです。
プログラムは下記の通りです。

```python:Main.py
import discord
from discord.ext import tasks
from FunctionsFactory import FunctionFactory

factory = FunctionFactory()
factory.get_bot_scheduler().check_start_time()
client = discord.Client()

@tasks.loop(hours=1)
async def loop():
    factory.get_bot_scheduler().check_end_time()

@client.event
async def on_ready():
    print("hello bot!")

@client.event
async def on_message(message):
    command = message.content[0]
    option = message.content[2:]

    if command == "y":
        func = factory.get_discord_youtube()
        await func.send_message(message, option)
    elif command == "t":
        func = factory.get_discord_twitter()
        await func.send_message(message, option)
    elif command == "b":
        func = factory.get_discord_browser()
        await func.send_message(message, option)

TOKEN = ボットのトークン
client.run(TOKEN)
```

プログラム中のTOKENはボットのウェブページのBotから確認できます。

(iii)作成したプログラムを実行します。
正常に動けば、下の画像のようになると思います。
![スクリーンショット 2021-07-27 155503.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/190b0982-029c-4aaf-d019-9bf75c0688b6.png)

###4-3.Herokuでのボットの稼働（説明割愛）
Herokuへのデプロイの方法は下記のサイトに詳しい記載があるので、この記事では割愛します。
https://qiita.com/1ntegrale9/items/9d570ef8175cf178468f
この記事ではGithubを使う方法を採用しています。

※herokuにpythonのパッケージをインストールするため、Githubのリポジトリに下記のrequirements.txtを作成する必要があります。

```:requirements.txt
discord.py
tweepy
requests
selenium
chromedriver_binary==86.0.4240.22.0
```

※参考

https://qiita.com/1ntegrale9/items/9d570ef8175cf178468f

https://pyteyon.hatenablog.com/entry/2019/05/24/003924

https://qiita.com/nsuhara/items/76ae132734b7e2b352dd

#おわりに
　私自身、Pythonで色々と自動化したいと思い、関連する知識を学んだのは数年前ですが、改めて知識を整理するために記事を作成しました。同じようにPythonで色々動かしたいと思う方に役立つと幸いです。