Send Discord Message from a looped function

Viewed 192

So I've run into a wall trying to get this code to work.

I'm writing a fairly simple Discord bot that does two things.

The main part of the bot is to run an API request when the bot initializes to get a base value of a return. I then have a function that I want to run every X seconds, that checks the value again, and if it changes, send a message to a Discord Channel and then update the variable to the new value, so that the loop can report when it changes again.

The second part, which is not as critical but I still want to include it, is that a user can send a bot command to report the current value of the return.

Problem #1: Arises when I start the loop without the code to send the result via discord message. If i don't include the line to send the discord message in the loop code, the loop runs and prints out the results to the console (for test purposes). However, when the loop is running, I can no longer get the bot to respond to the command.

Problem #2: If I include the code to send the discord message, the whole thing immediately fails and gives me the following error:


  File "bot.py", line 67, in <module>

    asyncio.run(update_status())

  File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run

    return loop.run_until_complete(main)

  File "/usr/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete

    return future.result()

  File "bot.py", line 63, in update_status

    await channel.send(f'Server status is still: {updatestatus}.')

  File "/usr/local/lib/python3.7/dist-packages/discord/abc.py", line 905, in send

    nonce=nonce, allowed_mentions=allowed_mentions)

  File "/usr/local/lib/python3.7/dist-packages/discord/http.py", line 185, in request

    async with self.__session.request(method, url, **kwargs) as r:

  File "/usr/local/lib/python3.7/dist-packages/aiohttp/client.py", line 1012, in __aenter__

    self._resp = await self._coro

  File "/usr/local/lib/python3.7/dist-packages/aiohttp/client.py", line 357, in _request

    raise RuntimeError('Session is closed')

RuntimeError: Session is closed

Problem #3: More of a minor nuisance, but if I set my code up so that it works in Problem #1, the loop does not start automatically, but only after I press Ctrl+C (note: I am writing the code on a Raspi 4 via PuTTY.

Any thoughts?

Full Code:

# bot.py
import os
import requests
import discord
import json
import asyncio

from dotenv import load_dotenv
from discord.ext import commands
from discord.ext import tasks
from discord.utils import get

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
CLIENTID = os.getenv('CLIENT_ID')
CLIENTSECRET = os.getenv('CLIENT_SECRET')

bot = commands.Bot(command_prefix='+')

@bot.event
async def on_ready():
        print(f'{bot.user.name} has connected to Discord!')
        channel = bot.get_channel(795162312112865280)
        await channel.send('MurkyBot has connected')

def create_access_token(client_id, client_secret, region = 'us'):
        data = { 'grant_type': 'client_credentials' }
        response = requests.post('https://%s.battle.net/oauth/token' % region, data=data, auth=(client_id, client_secret))
        return response.json()

tokenresponse = create_access_token(CLIENTID, CLIENTSECRET)
accesstoken = tokenresponse["access_token"]
print(accesstoken)

initialrequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
initialrequest = initialrequest.json()
initialstatus = initialrequest['status']['type']
print(f'Initial status: {initialstatus}')

@bot.command(name='status', help='Gets the current server status')
async def manual_status(ctx):
       manualrequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
       manualrequest = manualrequest.json()
       manualstatus = manualrequest['status']['type']
       channel = bot.get_channel(795162312112865280)
       await ctx.send(f'Current world server status is: {manualstatus}')

bot.run(TOKEN)

async def update_status():
        while True:
                global initialstatus
                channel = bot.get_channel(795162312112865280)
                updaterequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
                updaterequest = updaterequest.json()
                updatestatus = updaterequest['status']['type']
                if updatestatus != initialstatus:
                        await channel.send(f'Server status has changed to: {updatestatus}!')
                        initialstatus = updatestatus
                        print('Status Change')
                        await asyncio.sleep(5)
                else:
                        await channel.send(f'Server status is still: {updatestatus}.')
                        print('No Change')
                        await asyncio.sleep(5)

asyncio.run(update_status())
1 Answers

.run is a blocking call, meaning that everything stops and waits for it to complete. Therefore, the second part can only run after you ctrl+c, ending the script however.

You may want to check out discord.ext.tasks, it will have the intended functionality.

For what you're trying to do, here would be the updated code using discord.ext.tasks:

from dotenv import load_dotenv
from discord.ext import commands
from discord.ext import tasks
from discord.utils import get

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
CLIENTID = os.getenv('CLIENT_ID')
CLIENTSECRET = os.getenv('CLIENT_SECRET')

bot = commands.Bot(command_prefix='+')

@tasks.loop(seconds=60.0)
async def update_status():
        while True:
                global initialstatus
                channel = bot.get_channel(795162312112865280)
                updaterequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
                updaterequest = updaterequest.json()
                updatestatus = updaterequest['status']['type']
                if updatestatus != initialstatus:
                        await channel.send(f'Server status has changed to: {updatestatus}!')
                        initialstatus = updatestatus
                        print('Status Change')
                        await asyncio.sleep(5)
                else:
                        await channel.send(f'Server status is still: {updatestatus}.')
                        print('No Change')
                        await asyncio.sleep(5)


@bot.event
async def on_ready():
        update_status.start()
        print(f'{bot.user.name} has connected to Discord!')
        channel = bot.get_channel(795162312112865280)
        await channel.send('MurkyBot has connected')

def create_access_token(client_id, client_secret, region = 'us'):
        data = { 'grant_type': 'client_credentials' }
        response = requests.post('https://%s.battle.net/oauth/token' % region, data=data, auth=(client_id, client_secret))
        return response.json()

tokenresponse = create_access_token(CLIENTID, CLIENTSECRET)
accesstoken = tokenresponse["access_token"]
print(accesstoken)

initialrequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
initialrequest = initialrequest.json()
initialstatus = initialrequest['status']['type']
print(f'Initial status: {initialstatus}')

@bot.command(name='status', help='Gets the current server status')
async def manual_status(ctx):
       manualrequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
       manualrequest = manualrequest.json()
       manualstatus = manualrequest['status']['type']
       channel = bot.get_channel(795162312112865280)
       await ctx.send(f'Current world server status is: {manualstatus}')



bot.run(TOKEN)
Related