サークル向けにつくったDiscord BOTの中身
CIST Advent Calendar 2022 21目の記事です
CIST LTのサーバーで動いていた(Herokuがお亡くなりになっているので移住先を見つけるまでは停止中です)Discord botの中身についてまとめようと思います
つくったBOTのソースコード
備えている機能
- Times
timesチャンネルでの発言をすべてtimelineに流す - VC参加通知
誰かが特定のボイスチャットに参加したことをどこかのチャンネルに通知する - イベントリマインド・通知
google calendarに登録した予定を事前にリマインド・通知する
中身のはなし
起動時の処理
@client.event
async def on_ready():
print("on_ready")
print(client.user.name) #bot name
print(discord.__version__) #discord.pyのversion
print("--------")
print(f"waiting {60 - datetime.now().second} sec for loop to start")
time.sleep(60 - datetime.now().second)
scheduling_notice.start()
await client.change_presence(activity=discord.Game(name = ""))
上記on_ready()
が起動時に走る処理です
print(f"waiting {60 - datetime.now().second} sec for loop to start")
time.sleep(60 - datetime.now().second)
scheduling_notice.start()
これはgoogleカレンダーの通知に必要で、scheduling_notice
をhhmm00の状態から動かすための処理です
テキストチャンネル監視
@client.event
async def on_message(message):
#botの送信ははじく
if message.author.bot:
return
#times投稿
#timesカテゴリのみを監視、timelineチャンネルは無視
if message.channel.category_id == categoryId and message.channel.id != timesId:
await client.get_channel(timesId).send(message.channel.mention + " " + message.author.name + "\n" + message.content)
if message.attachments:
for i in message.attachments:
await client.get_channel(timesId).send(i)
サーバで投げられたテキストチャットを監視して、「TIMES
カテゴリ内の投稿である」かつ「チャットの投稿先がtimeline
でない」ときにtimeline
に受け取ったチャットの内容が送信されるようになっています。
例)TIMES
カテゴリのknot
チャンネルに送られたチャットはtimeline
チャンネルにも同じ内容が送信されます。
VC参加通知
#もくもく会入室通知
@client.event
async def on_voice_state_update(member, before, after):
if after.channel.id == mokumokuId and after is not before and after.self_mute is before.self_mute and after.self_stream is before.self_stream and after.self_deaf is before.self_deaf:
await client.get_channel(timesId).send("<#" + str(mokumokuId) + ">" + " " + member.name + "\n" + "もくもく会に参加しました")
クソ長条件文
on_voice_state_update
はボイスチャット内で何かが起こった時に呼ばれます。おそらく。
- 何かが起こったとき
- VCの入退出があった
- 誰かがマイクミュートにした
- 誰かがスピーカーミュートにした
など
if after.channel.id == mokumokuId and after is not before and after.self_mute is before.self_mute and after.self_stream is before.self_stream and after.self_deaf is before.self_deaf:
つまり、この条件文はVCの入室があったときにだけ通知を飛ばすためのものです。
細かい中身は覚えてないです。ごめんなさい
予定リマインド・通知
先にgooglecalendarの予定を取ってくる方からいきます。
カレンダーにある予定を3つ取ってくる
def calendar_info3():
"""Shows basic usage of the Google Calendar API.
Prints the start and name of the next 10 events on the user's calendar.
"""
# Load credential file for service account
creds = load_credentials_from_file(
'cistlt-calendar.json', SCOPES
)[0]
service = build('calendar', 'v3', credentials=creds)
# Call the Calendar API
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
# NOTE: Set your calendar id
events_result = service.events().list(calendarId='cist.lt.club@gmail.com', timeMin=now,
maxResults=3, singleEvents=True,
orderBy='startTime').execute()
events = events_result.get('items', [])
schedule = []
if not events:
return -1
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
schedule.append((start, event['summary']))
return schedule
これはテンプレを少しいじっただけだったはずなので中身よくわかってないですね。
予定を3つとってきて、それをひとつのリストにして返すといった感じです。
[('2022-12-24T21:30:00+09:00', '定例会'), ('2023-01-07T21:30:00+09:00', '定例会'), ('2023-01-21T21:30:00+09:00', '定例会')]
こんな感じ
datetime型になおしてやる
def to_datetime(schedule):
day_time = str(schedule[0]).split("T")
sche_day = str(day_time[0]).split("-")
sche_time = str(day_time[1]).split(":")
return datetime.datetime(year=int(sche_day[0]), month=int(sche_day[1]), day=int(sche_day[2]), hour=int(sche_time[0]), minute=int(sche_time[1]))
bot本体ではdatetime型で使いたいので、そのための関数です。
なんかもっといいやり方がありそう。
BOT本体
#予定通知
day = (datetime.now()).day
schedule = calendar_info3()
@tasks.loop(seconds=60)
async def scheduling_notice():
global day
global schedule
now = datetime.now()
for sche_index in schedule:
sche_datetime = to_datetime(sche_index)
if timedelta(hours=23, minutes=59, seconds=55) <= sche_datetime - now <= timedelta(days=1, seconds=5):
await client.get_channel(noticeId).send(f"`{sche_datetime}`より`{sche_index[1]}`があります")
if timedelta(seconds=-5) <= sche_datetime - now <= timedelta(seconds=5):
await client.get_channel(noticeId).send(f"@everyone 今から`{sche_index[1]}`が始まります")
if now.day - day == 1:
day = now.day
schedule = calendar_info3()
@tasks.loop(seconds=60)
で1分に1度scheduling_notice
が実行されます。
予定の1日前と予定開始時間に通知を飛ばします。
day
はbot起動した時の日付を覚えておいて、最後の条件文で1日経過したときに日付を更新し予定の取得をしています。つまり、1日に1回予定を取得するための処理です。
おわり
Herokuの代替を探すのサボっているので、気が向いたら探しに行きたいですね。
Discussion