課題の神を創造した
なんでもう年末なんですかね、かげろんです。
10月末まで部長だったんですけど座を奪われました。
任期満了して今では老害です。
今回は部長になってもらったみやもっちゃんが12月にいつも部内でやっている発表会をやるとのことだったので、connpassを建てるだけ建てて高専という今までより大きな枠組みでやろう、そして運営してもらおうと推し進めました。
ここで発表した内容を書いておこうかと思います。
twitter.com
sanct.connpass.com
劣化コピー機完備
皆さんは課題を期日内に終わらせるように自力でやっていますか?
弊クラスでは〇〇塾という圧力団体が存在しており、課題やテストのログがアップロードされるため、朝学校に来てから課題を写す”劣化コピー機”がたくさん配備されています。
私たちとしてはとても楽ですが、教員からすれば面白いことではないでしょう。僕はクソ真面目な部分もあるのでまず課題を忘れたという言い訳ができないようにしようかと思います。
環境構築
Pythonを全く触ったことがないので学習を兼ねてPythonの環境を構築したいと思います。大分前にAnacondaがいいと言われましたが私は反抗してWSLを使っていきます。
以下のサイトを参考にして導入しました。
qiita.com
最初VSCodeで開発しようとしていたので上手くできず、さらにpipのディレクトリが変更されており爆破が決定しました。Install Battle敗北です。
Discord Bot作成
Discord Botを作成するためには、Discord Developer PortalでBotのアカウント作成とpip等でdiscord.pyを導入する必要があります。
以下のサイトを参考にしました。
qiita.com
カレンダーから予定を取得
今回はGoogleカレンダーから予定を取得します。
GoogleCalenderAPIを使えば予定の取得・追加ができるらしいのでぶち込みました。
qiita.com
プログラム完成
導入してサンプルを少し書き換えるとこうなりました。
from __future__ import print_function import datetime import pickle import os.path import discord import asyncio from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from discord.ext import tasks # If modifying these scopes, delete the file token.pickle. SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] TOKEN = Botのトークン(文字列) channelID = チャンネルID(数値) client = discord.Client() def GetEvents(): """Shows basic usage of the Google Calendar API. Prints the start and name of the next 10 events on the user's calendar. """ creds = None # The file token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server() # Save the credentials for the next run with open('token.pickle', 'wb') as token: pickle.dump(creds, token) service = build('calendar', 'v3', credentials=creds) # Call the Calendar API now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time events_result = service.events().list(calendarId=カレンダーID(文字列), timeMin=now, maxResults=10, singleEvents=True, orderBy='startTime').execute() events = events_result.get('items', []) return events # 起動時に動作する処理 @client.event async def on_ready(): # 起動したらループを開始する asyncio.ensure_future(time_check()) # 60秒に一回ループ async def time_check(): while True: # 予定の件数 plans = 0 # 現在の時刻 now = (datetime.datetime.now() + datetime.timedelta(hours=9)).strftime('%H:%M') # 明日の日付 tomorrow = (datetime.date.today() + datetime.timedelta(days=1)).strftime("%Y-%m-%d") if now == '21:00': channel = client.get_channel(channelID) date = str(tomorrow).split('-') messgae = date[0] + '年' + date[1] + '月' + date[2] + '日の課題/テストは' await channel.send(messgae) events = GetEvents() if not events: await channel.send('ありません.') else: for event in events: start = event['start'].get('dateTime', event['start'].get('date')) startDate = start.split('T') if startDate[0] == tomorrow: plans += 1 await channel.send(str(event['summary'])) if plans == 0: await channel.send('ありません.') else: await channel.send('の' + str(plans) + '件です.') await asyncio.sleep(60) # メッセージ受信時に動作する処理 @client.event async def on_message(message): # メッセージ送信者がBotだった場合は無視する if message.author.bot: return # 「/plans」か「/予定」と発言したらGoogleカレンダーの予定が返る処理 if message.content == '/plans' or message.content == '/予定': # 予定の件数 plans = 0 events = GetEvents() channel = client.get_channel(channelID) await channel.send('今後10件の予定は') if not events: await channel.send('ありません.') else: for event in events: start = event['start'].get('dateTime', event['start'].get('date')) # 得られた予定を文字列にして分解して編集 sentences=str(start).split('T') words = sentences[0].split('-') eventMessage = words[0] + '年' + words[1] + '月' + words[2] + '日 に ' # 時間指定もあれば追加 if len(sentences) == 2: sentences = sentences[1].split('+') words.extend(sentences[0].split(':')) eventMessage = eventMessage + words[3] + '時' + words[4] + '分 より ' plans += 1 eventMessage = eventMessage + str(event['summary']) await channel.send(eventMessage) if plans == 0: await channel.send('ありません.') else: await channel.send('の' + str(plans) + '件です.') # Botの起動とDiscordサーバーへの接続 client.run(TOKEN)
DiscordのチャンネルIDは ユーザー設定>テーマ から開発者モードをオンにすると取得できます。
カレンダーIDは Googleカレンダー>マイカレンダーの設定>カレンダーの統合 からコピーしてください。
大体望んだとおりの動作をしました。
では、これを常時起動していつでも予定を取得できるようにしよう、と言っても私は寮に入っているので朝の巡回時に電源を切られます。
そこでHerokuというPaaSを使うことにしました。
Herokuを使って常時稼働
Herokuを使うと常時実行できるらしいです。
signup.heroku.com
GitHubにソースコード等々を上げ、デプロイします。
このとき、使用するライブラリを記載するrequirements.txtと言語のバージョンを記載するruntime.txt、実行時のコマンドを記載するProcfileも必要です。
今回はBotのトークンやDiscordのチャンネルIDをソースコードにそのまま書いてあるためプライベートリポジトリとなっています。
Automatic deployを設定すると、コミットされても自動的にデプロイされます。世の中便利ですね。
そして、Dynosをオンにすると動作が開始します。
これらの手順についてはDiscord Bot作成で紹介した記事で詳しく説明されています。
完成
これで課題を忘れることがなくなったかと思います。
あとはGoogleカレンダーに予定を追加し忘れないかですね。
この機能についてもそのうち追加できたらなと思います。
実際に弊グループに導入したのですが、効果のほどはよく分かりません。
ただ、あまり情報系ではない人のほうが多いため「すごい」とか「bot有能」とか、褒められた気がします。
何かレスポンスがあるのがとてもいいですよね。
意見とか感想を貰えると開発意欲が湧くし、何よりやってよかったと思えます。
ぜひ皆さんも他人のためになる商品を作って金をぶんどってください。
おわりに
今回作ったBotは未完成です。
何か面白い機能の案があれば以下のフォームまでお願いします。私のタスクを増やしてください。
あとイスラム教の皆様におかれましては本当に申し訳ありませんでした。そのうち名前変えます。
docs.google.com
それでは、次回は怪文書を書けるよう精進してきます。