どうも~、お久しぶりです。今回は初めてPythonだけの記事です~
今回の記事は、タイトルに「爆速」と付けてしまったので、
説明がなくても大丈夫な人は、説明もろもろ読み飛ばしていただき、コピペして貰えればオッケーなように作ったつもりです。
対象としてはPython初級者から中級にステップアップしたいくらいの人に読んでいただきたいです。
#discordに移行した話
前まで友達とのやり取りやTRPGのオンラインセッションにSkypeを使っていたのですが、どうも最近アプリ版もデスクトップ版も使いづらいので思い切ってdiscordに移行しました。
そしたら使いやすいのなんのって...特にTRPGオンセですと、セッションごとにチャンネルを作って通話すれば会話ログも後から見やすいし、ボイスチャンネルとテキストチャンネルが分かれているので通話しながら共有したファイルもわかりやい、自作絵文字を作れば内輪ネタで盛り上がるし、メンバーの管理もしやすいです。もう一つ、discordを使って面白かったのが、Botです。SkypeでもBot開発はできるらしいですが、あまりソースがないのでチャレンジしませんでした。しかし、discordのBotは、公式ドキュメントも充実してるし、言語ごとのライブラリもたくさんあります。今ではdiscordに移行して3週間くらいですが、鯖のメンバーは人:Bot=8:5という状況です。
#Botを作ろう!
まずは簡単な返答をするBotを作ります。といってもほぼ公式ドキュメントのコードなのですが。
開発環境は以下の通りで行います。
- python 3.6.6
- windows 10
- discord.py 0.16.12
なお、執筆当時(2018/08/23)では、discord.pyがpython3.7に対応していなかったので、最新バージョンの3.7ではなく、3.6系を使用しています。
##discordでBotを作るための準備
まず、https://discordapp.com/developers/applications/ にアクセスして、「Create an application」をクリックします。
そして下の画像のようにBotの名前を変更します
「Add Bot」を押し、次に出てくるポップアップで「Yes, do it!」をクリック。
そして、OAuth2でbotにクリックしたときに出てくるリンクに飛びます。
これで、Botが自分の鯖に現れます。ぜひ歓迎してあげてください。
ユーザが追加されたときのジョークもdiscordは面白いですよねぇ
これで、Bot側の準備が整いました。
次は開発環境を整えていきましょう。
##pythonの準備
それではプログラムを組んでいく準備をしましょう。といってもpythonで開発するなら、discord.py
というライブラリを入れるだけなのですが。今回はpython3.6でdiscord.py
をpipを使ってインストしますよ。
pip install -U discord
##簡単なBotを作ってみる。
さて、いよいよコーディングを開始します。その前に、discordのbotを動かすためには「トークン」というものが必要になります。それを取得しましょう。そのためには、botのapplicationページに行き、「bot」タブの「トークン」というところで「copy」を押します。
すると、クリップボートにトークンがコピーされます。後々必要となりますので、トークンの取得の方法は覚えておきましょう。
さて、トークンが取得出来たら、下記のコードを実行してみましょう。
import discord
client = discord.Client()
@client.event
async def on_ready():
print("logged in as " + client.user.name)
@client.event
async def on_message(message):
if message.author != client.user:
msg = message.author.mention + " Hi."
await client.send_message(message.channel, msg)
client.run("トークンをここに")
実行すると下の画像のように自分に対してのメンション付きで返信が返ってきます。
###解説
1行目で、discord.py
をインポートすることで、discordの機能を使えるようにしています。
3行目で、client
にこのBotの情報を格納します。
5行目~7行目までは、Botがログインした時のイベントハンドラです。
コンソールにBotの名前を表示します。
9行目~13行目は、メッセージを受信したときのイベントハンドラです。
if message.author != client.user:
この条件で自分以外からのメッセージのときに反応するようになっています。
また、
message.author.mention
でメッセージを送信した人に向かってメンションを投げることができます。
そして、
client.send_message(message.channel, msg)
で、特定のチャンネルにメッセージを送ります。
どうでしょうか?イメージがわいてきたと思います。例えば、
msg = message.author.mention + " Hi."
のところを
msg = message.author.mention + message.content
##天気予報周り実装
さて、天気予報ができるようにコーディングを進めていきましょう。とはいえ、ここはBot開発とは直接的な関係はないので簡単に済ませていきたいと思います。
必要なライブラリは
- re
- urllib
の二つです。どちらもpython3からは標準搭載ですのでpipでインストする必要はありません。
re
は正規表現を扱うライブラリです。urllib
は特定のurlにアクセスし、データを取得するためのライブラリになります。
ちなみに、python3から標準サポートされたurllib
はurllib.request
とurllib.error
に分かれてるみたいです。
Botの仕様なのですが、
- 「Bot君、~~の天気は?」と聞かれたときに反応する
- ~~の天気を知っていれば、今日、明日、明後日の天気を返す
- 知らなければ、知らない旨を返信する
今回の情報はlivedoor天気情報から取得します。
livedoorでは、http://weather.livedoor.com/forecast/webservice/json/v1?city=400040 のようなurlにアクセスすると、cityコードにあった都市の天気情報をjson形式でGETできます。これを使っていきましょう。なお、天気情報の詳細は、お天気Webサービス仕様にあります。
まずは、下記のコードを見てください。
import urllib.request
import json
citycode = '016010' #札幌の地域コード
resp = urllib.request.urlopen('http://weather.livedoor.com/forecast/webservice/json/v1?city=%s'%citycode).read()
resp = json.loads(resp.decode('utf-8'))
print(resp)
実行すると、下のように出力されます。
{'pinpointLocations': [{'link': 'http://weather.livedoor.com/area/forecast/0110000', 'name': '札幌市'}, {'link': 'http://weather.livedoor.com/area/forecast/0121700', 'name': '江別市'}, {'link': 'http://weather.livedoor.com/area/forecast/0122400', 'name': '千歳市'}, {'link': 'http://weather.livedoor.com/area/forecast/0123100', 'name': '恵庭
市'}, {'link': 'http://weather.livedoor.com/area/forecast/0123400', 'name': '北広島市'}, {'link': 'http://weather.livedoor.com/area/forecast/0123500', 'name': '石狩市'}, {'link': 'http://weather.livedoor.com/area/forecast/0130300', 'name': '当別町'}, {'link': 'http://weather.livedoor.com/area/forecast/0130400', 'name': '新篠津村'}], 'link': 'http://weather.livedoor.com/area/forecast/016010', 'forecasts': [{'dateLabel': '今日', 'telop': '晴れ', 'date':
'2018-08-23', 'temperature': {'min': None, 'max': None}, 'image': {'width': 50, 'url': 'http://weather.livedoor.com/img/icon/1.gif', 'title': '晴れ', 'height': 31}}, {'dateLabel': '明日', 'telop': '曇のち雨', 'date': '2018-08-24', 'temperature': {'min': {'celsius': '20', 'fahrenheit': '68.0'}, 'max': {'celsius': '27', 'fahrenheit': '80.6'}}, 'image': {'width': 50, 'url': 'http://weather.livedoor.com/img/icon/13.gif', 'title': '曇のち雨', 'height': 31}}, {'dateLabel': '明後日', 'telop': '雨時々曇', 'date': '2018-08-25', 'temperature': {'min': None, 'max': None}, 'image': {'width': 50, 'url': 'http://weather.livedoor.com/img/icon/17.gif', 'title': '雨時々曇', 'height': 31}}], 'location': {'city': '札幌', 'area': '北海道', 'prefecture': '道央'}, 'publicTime': '2018-08-23T17:00:00+0900', 'copyright': {'provider': [{'link': 'http://tenki.jp/', 'name': '日本気象協会'}], 'link': 'http://weather.livedoor.com/', 'title': '(C) LINE Corporation', 'image': {'width': 118, 'link': 'http://weather.livedoor.com/', 'url': 'http://weather.livedoor.com/img/cmn/livedoor.gif', 'title': 'livedoor 天気情報', 'height': 26}}, 'title': '道央 札幌
の天気', 'description': {'text': ' 北海道付近は、23日夜は、高気圧の張り出しの中となるでしょう。24\n日は台風第20号
から変わる低気圧が日本海から近づき、大気の状態が不安\n定となり、気圧の傾きも大きくなる見込みです。\n\n 石狩・空知
・後志地方の23日15時の天気は、晴れまたは曇りとなって\nいます。\n\n 23日夜は、晴れでしょう。\n\n 24日は、曇りのち雨で、夕方から所により雷を伴い激しく降る見込みで\nす。\n\n 海の波の高さは、23日夜は1メートルでしょう。24日は1メート
ルの\nち4メートルで、うねりを伴いしける見込みです。', 'publicTime': '2018-08-23T16:39:00+0900'}}
少しに見にくいですが、確かに3日分の天気情報がjsonデータで渡されていることがわかりますね。
たとえば、print(resp['location']['city'])
とすれば、札幌
と返ってきます。
必要な情報は、
- 都市の名前
- 今日の天気
- 明日の天気
- 明後日の天気
という感じでしょうか。これらのデータにアクセスするためには、
- resp['location']['city']で地域の名前を取得
- resp['forecasts']が長さ3のリストになっているので、forで回す
- 'dateLabel'キーで「今日」などの日にち情報が得れらる
- 'telop'で「晴れ」などの天気情報が得られる
上に書いたような処理をすればよいみたいです。
例えば、
import urllib.request
import json
citycode = '016010'
resp = urllib.request.urlopen('http://weather.livedoor.com/forecast/webservice/json/v1?city=%s'%citycode).read()
resp = json.loads(resp.decode('utf-8'))
print(resp['location']['city'] + "の天気は")
for f in resp['forecasts']:
print(f['dateLabel'] + "が" + f['telop'])
print("です.")
というコードを実行すると、
札幌の天気は
今日が晴れ
明日が曇のち雨
明後日が雨時々曇
です.
と出力されます。結構簡単にできてしまいましたね。
Botに天気を予報させる
さて、ここまでの知識を合わせて以下のコードを書いてみました。実行してみましょう。
import discord
import urllib.request
import json
import re
client = discord.Client()
citycode = '016010'
resp = urllib.request.urlopen('http://weather.livedoor.com/forecast/webservice/json/v1?city=%s'%citycode).read()
resp = json.loads(resp.decode('utf-8'))
@client.event
async def on_ready():
print("logged in as " + client.user.name)
@client.event
async def on_message(message):
if message.author != client.user:
if message.content == "Bot君、札幌の天気は?":
msg = resp['location']['city']
msg += "の天気は、\n"
for f in resp['forecasts']:
msg += f['dateLabel'] + "が" + f['telop'] + "\n"
msg += "です。"
await client.send_message(message.channel, message.author.mention + msg)
client.run("ここにトークン")
ちゃんと札幌の天気を予報できたみたいですね。とはいえ札幌の天気だけではつまらないですし、
実用的ではありませんね。(私も本州住みなので)
こんな風に改良してみるのはいかがでしょうか?
import discord
import urllib.request
import json
import re
client = discord.Client()
citycodes = {
"土浦": '080020',
"水戸": '080010',
"札幌": '016010',
"仙台": '040010',
"東京": '130010',
"横浜": '140010',
"名古屋": '230010',
"大阪": '270000',
"広島": '340010',
"福岡": '400010',
"鹿児島": '460010',
"那覇": '471010'
}
@client.event
async def on_ready():
print("logged in as " + client.user.name)
@client.event
async def on_message(message):
if message.author != client.user:
reg_res = re.compile(u"Bot君、(.+)の天気は?").search(message.content)
if reg_res:
if reg_res.group(1) in citycodes.keys():
citycode = citycodes[reg_res.group(1)]
resp = urllib.request.urlopen('http://weather.livedoor.com/forecast/webservice/json/v1?city=%s'%citycode).read()
resp = json.loads(resp.decode('utf-8'))
msg = resp['location']['city']
msg += "の天気は、\n"
for f in resp['forecasts']:
msg += f['dateLabel'] + "が" + f['telop'] + "\n"
msg += "です。"
await client.send_message(message.channel, message.author.mention + msg)
else:
await client.send_message(message.channel, "そこの天気はわかりません...")
client.run("ここにトークン")
見事、Botに天気予報をさせることに成功しました!やったぜ
##herokuにデプロイする
さて、この愛らしいBotにいつでも天気予報させられるようにデプロイしましょう。
なお、herokuのユーザ登録はすでに済んでいる全体で話を進めます。
まだのひとはこちらのherokuのサイトから登録しましょう!
まずは、herokuのダッシュボードでアプリを登録します。
右上のNewからCreate new appを選択しましょう
すると、このような画面に出ますので、アプリの名前を入力し,Enterキーをスッターンします。
今回はforecast-bot-sample
としました。
そうしてでてくるページをちょっと下にスクロールすれば
このようにガイドが書いてあるので、それに従って操作していきます。
ブラウザ側の処理は終わりました。
実はデプロイには3つドキュメントが必要です。
まずはそれらを整えていきましょう。
必要なドキュメントは、
- Procfile
- requirements.txt
- runtime.txt
です。これらをbotのプログラムファイルと同じディレクトリに配置します。
それぞれのファイルに載せてく情報は、
###Procfile
bot: python main.py
###requirements.txt
discord.py==0.16.12
###runtime.txt
python-3.6.6
となります。
ファイルが作れたら、まずはgitを追加して、herokuにpushします。
git init
git add .
git commit -m "first commit"
# ここまでgit関連
heroku login # ここでヘロクにログイン
heroku git:remote -a forecast-bot-sample
git push heroku master # ちょっと時間かかる
さぁ、デプロイできたでしょうか?
あれ、動かない...のでもう一度ダッシュボードに行ってみましょう。
あ、OFFって書いてある。
前は自動でonになってた気がするんですけど、気のせいですかね
ONにするためには、Resourceタブに移動し、「Free Dynos」でペンのボタンを押して状態を編集→confirmでonになります。
どうでしょうか、Botは動きましたか?
終わりに
今回は以上となります。お疲れさまでした。
初めてのプログラミング言語にpythonを選ぶ人が多いと思うのですが、やはりいろいろなことが簡単にできてしまうという強みがあるからですね。
Bot開発ではそれを改めて分かったような気がしました。
ちなみに、herokuは無料版だと5つのアプリまでしかデプロイできないようです。
私みたいにしょうもないBotでいっぱいにして、いざというときにデプロイできないなんていう状況に陥らないように、ご利用は計画的にどうぞ。
それでは最後まで読んでいただき、ありがとうございました!
参考
Pythonで天気予報情報を取得するときに参考にさせていただきました!
Bot入門時に参考にさせていただきました!
discord.pyのドキュメント
## 追記 2018/8/24
@khsk さん、編集リクエストありがとうございます!語句の間違いを訂正いたしました。