0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-03

#はじめに
小説家になろうって、あるじゃないですか。
しょっちゅう形態素解析されたりスクレイピングされたりタイトルの文字数平均を求められたりしてる、アレですよ。
私の好きな「シャングリラ・フロンティア」は、不定期更新であり、いつ更新されるかわからないため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のドキュメントの丸写しです。

0
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?