Discord Bot retrieving XKCD Comics

Discord + XKCD = Another usless app

Discord, FYI, is a popular instant messanger platform for gamers, people, knitting clubs, and at this point, everyone else. Most should be aware of Discord and how it generally works, which I will not be explaining. The purpose of this post is to show my Baby’s first Discord bot which retrieves XKCD comics and puts them in the chat.

The code that the bot runs is written in Python using the Pycord library (see code below). The Pycord library is an intuitive and easy-to-use library for developing Discord Bots. One of things I already like about this library is you can create sub-commands or /slash commands easily with the library. As opposed to creating seperate commands like /xkcd-get, /xkcd-random, /xkcd-explained, and so on, we can group them under one command group where /xkcd contains the sub-commands for all three (get, random, and explained).

There is another library for developing bots in Python called Discord.py which I initially wrote this code with but found it less intuitive (i.e., more work to reinvent the wheel).

The Code

import discord
import xkcd
import os
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN') # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bot
LAST_COMIC_ID = os.getenv('LAST_XKCD_COMIC_ID')  # comic_id

async def save_xkcd_comic_id(xkcd_comic_id):
    with open(".env", mode="r") as envf:
        envd = envf.readlines()
    envd[4] = f"LAST_XKCD_COMIC_ID = {xkcd_comic_id} # last xkcd comic sent by bot"
    with open(".env", mode="w") as envf:
        envf.writelines(envd)

bot = discord.Bot()

xkcd_comics = bot.create_group(name="xkcd", description="Interact with XKCD comics.")

@xkcd_comics.command()
async def get(ctx: discord.Bot, number: int=None):
    number = xkcd.getLatestComicNum() if number == None else number
    comic = xkcd.getComic(number=number) if number else xkcd.getLatestComic()
    await save_xkcd_comic_id(comic.number)
    embed = discord.Embed(title = f'#{comic.number}: {comic.getTitle()}', description = comic.getAltText(), color = discord.Colour.blue())
    embed.set_image(url = comic.getImageLink())
    await ctx.respond(embed=embed)

@xkcd_comics.command()
async def random(ctx: discord.Bot):
    comic = xkcd.getRandomComic()
    await save_xkcd_comic_id(comic.number)
    embed = discord.Embed(title = f'#{comic.number}: {comic.getTitle()}', description = comic.getAltText(), color = discord.Colour.blue())
    embed.set_image(url = comic.getImageLink())
    await ctx.respond(embed=embed)

@xkcd_comics.command()
async def explained(ctx: discord.Bot, number: int=None):
    number = LAST_COMIC_ID if number == None else number
    comic = xkcd.getComic(number=number)
    embed = discord.Embed(title = f'#{comic.number}: {comic.getTitle()}', description = comic.getAltText(), url=comic.getExplanation(), color = discord.Colour.blue())
    await ctx.respond(embed=embed)

bot.run(TOKEN)

The Issues

I encountered issues making the explained command work properly. The function explained attempts to retrieve the LAST_COMIC_ID which is stored and retrived from an environment variable on the system running the bot OR returns a user-specified comic-id number. Unfortunately, it is buggy as it is supposed to (by default) retrieve the last requested XKCD Comic and return its Explained article. Instead, it will lag behind the last retrieved XKCD comic, making it less useful than desired.

In addition, I was also experimenting with the Wikimedia API to extract the explanation text to embed into the bots response with limited success. It is not shown, but it retrieved plain text extracts of the explanation text, leaving unformatted, non-beautified text, which cluttered up and took away from the purpose of the bot’s response.

Going from Here

The ultimate goal of this little experiment was to see how feasable using Discord as a UI was going to be. I can say that on that end, it is a success. The code is simple and not fully comprehensive as I would like, but I do not always get the chance to practice my programming skills very often, so when I can, the better.

From here I will likely improve the code as is and update it and/or go on to integrating what I have learned into future experiments and projects.