Help us understand the problem. What is going on with this article?

小説家になろうの更新を通知するDiscord botを作った

はじめに

小説家になろうって、あるじゃないですか。
しょっちゅう形態素解析されたりスクレイピングされたりタイトルの文字数平均を求められたりしてる、アレですよ。
私の好きな「シャングリラ・フロンティア」は、不定期更新であり、いつ更新されるかわからないため2分に一度は目次をリロードして更新が来ていないか確認する必要があります。
これは非常にめんどくさいので、Discordを開いていれば勝手に知らせてくれるbotを作ることにしました。

環境

  • Python 3.8.1
  • pip 20.0.2
  • Discord.py 1.3.2

準備

ここに行ってNew ApplicationでBot -> Build-A-Botしてトークンをゲットし、OAuth2でbotにチェックを入れて本拠地となるDiscordのサーバーに招待しましょう。

作る

基本

とりあえず基本部分を作ります

DiscordBot.py
import discord
TOKEN = '****************'
CHANNEL_ID = ***************** #本拠地となるチャンネルのIDを入れる
client = discord.Client()

@client.event
async def on_ready():
    await channel.send("on ready")

client.run(TOKEN)

起動したら「on ready」と発言するだけの簡素なbotができました。

ループ

次にループ部分を作りましょう。tasks.loopでループさせ、10秒に一度requestsでaiohttpでAPIを叩きます。1

DiscordBot.py
import discord
from discord.ext import tasks
import aiohttp
TOKEN = '****************'
CHANNEL_ID = ***************** #本拠地となるチャンネルのIDを入れる
client = discord.Client()

@client.event
async def on_ready():
    loop.start()
    await channel.send("on ready")

@tasks.loop(seconds=10)
async def loop():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.syosetu.com/novelapi/api/?ncode=n6169dz&of=gl') as r:

client.run(TOKEN)

API処理

「返ってきたAPIの最終掲載日」と「それまで(10秒前)に叩いていたAPIの最終掲載日」を比較し、異なった場合は更新されたと判断してチャンネルにメッセージを送ります。

DiscordBot.py
import discord
from discord.ext import tasks
import aiohttp
import yaml
TOKEN = '****************'
CHANNEL_ID = ***************** #本拠地となるチャンネルのIDを入れる
client = discord.Client()
lastup = "null"

@client.event
async def on_ready():
    loop.start()
    await channel.send("on ready")

@tasks.loop(seconds=10)
async def loop():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.syosetu.com/novelapi/api/?ncode=n6169dz&of=gl') as r:
            new_lastup = yaml.safe_load(await r.text())[1]["general_lastup"]
            channel = client.get_channel(CHANNEL_ID)
            global lastup
            if lastup != new_lastup:
                lastup = new_lastup
                await channel.send('シャンフロ更新来た')
client.run(TOKEN)

とりあえずこれで、更新時に通知するbotはできました。
しかし、これでは「起動時に変数lastupはnullに設定されるため、起動直後のループ1周目で「更新が来た」と錯覚してしまう」という問題点があります。

起動時処理の補強

なので、起動時に一度APIを叩いてlastupに代入することで、それを解決します。

DiscordBot.py
import discord
from discord.ext import tasks
import aiohttp
import yaml
import datetime
TOKEN = '****************'
CHANNEL_ID = ***************** #本拠地となるチャンネルのIDを入れる
client = discord.Client()
lastup = "null"

@client.event
async def on_ready():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.syosetu.com/novelapi/api/?ncode=n6169dz&of=gl') as r:
            global lastup
            lastup = yaml.safe_load(await r.text())[1]["general_lastup"]
    loop.start()
    channel = client.get_channel(CHANNEL_ID)
    await channel.send("on ready")
    await channel.send("最終更新:" + str(lastup)) #ついでに起動時に最終更新日時を伝えるようにしました

@tasks.loop(seconds=10)
async def loop():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.syosetu.com/novelapi/api/?ncode=n6169dz&of=gl') as r:
            new_lastup = yaml.safe_load(await r.text())[1]["general_lastup"]
            channel = client.get_channel(CHANNEL_ID)
            global lastup
            if lastup != new_lastup:
                lastup = new_lastup
                await channel.send('シャンフロ更新来た')
client.run(TOKEN)

おまけ

これでもう完成といってもいいんですが、チャットボットなのに一方向に情報を伝える機能しかなくて、こちらから送れるコマンドが一つもないというのもアレなので、一つコマンドを追加します。
[最終更新からどれくらい?]と聞くと、timedeltaオブジェクトで答えてくれるようにします。

DiscordBot.py
import discord
from discord.ext import tasks
import aiohttp
import yaml
import datetime
TOKEN = '****************'
CHANNEL_ID = ***************** #本拠地となるチャンネルのIDを入れる
client = discord.Client()
lastup = "null"

@client.event
async def on_ready():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.syosetu.com/novelapi/api/?ncode=n6169dz&of=gl') as r:
            global lastup
            lastup = yaml.safe_load(await r.text())[1]["general_lastup"]
    loop.start()
    channel = client.get_channel(CHANNEL_ID)
    await channel.send("on ready")
    await channel.send("最終更新:" + str(lastup)) #ついでに起動時に最終更新日時を伝えるようにしました

@client.event
async def on_message(message):
    if message.author.bot:
        return
    if message.content == '[最終更新からどれくらい?]':
        dif = datetime.datetime.now() - lastup
        await message.channel.send(dif)

@tasks.loop(seconds=10)
async def loop():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.syosetu.com/novelapi/api/?ncode=n6169dz&of=gl') as r:
            new_lastup = yaml.safe_load(await r.text())[1]["general_lastup"]
            channel = client.get_channel(CHANNEL_ID)
            global lastup
            if lastup != new_lastup:
                lastup = new_lastup
                await channel.send('シャンフロ更新来た')
client.run(TOKEN)

これで完成です。

参考


  1. 非同期処理可能なaiohttpの方がいいらしいんですけど、よくわからないしちょっとくらいの遅延は許容範囲だろうと考えてrequestsにしましたaiohttpを使用するように変更しました。なおほぼDiscord.pyのドキュメントの丸写しです。 

gaqwest
学生です。使えるプログラミング言語はScratchと診断メーカーとBrainFuckです。最近暇だったのでRubyを始めました。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした