0

Been working on a Discord bot that talks as a character.ai character, I utilized mainly an unofficial characterai API (https://github.com/kramcat/CharacterAI) and the discord.py library. Code below:

import tracemalloc
tracemalloc.start()
import asyncio
import discord
from discord.ext import commands
import characterai

character_ai_token = "character ai token here"
intents = discord.Intents.all()
client = characterai.pyCAI(character_ai_token)
bot = commands.Bot(command_prefix='!', intents=intents)


@bot.event
async def on_ready():
    print(f'Bot is ready. Logged in as {bot.user.name}')


target_channel_id = "channel_id_here"


@bot.event
async def on_message(message):
    data = await client.chat.send_message('CHAR', message, wait=True)
    if message.author == bot.user:
        return
    if message.channel.id != target_channel_id:
        return
    if message.content.startswith(bot.command_prefix):
        await bot.process_commands(message)
        return

    response = await data['replies'][0]['text']
    await message.channel.send(response)


async def start_bot():
    await bot.start("bot token")

async def main():
    await asyncio.create_task(start_bot())

asyncio.run(main())

I tried using asyncio.create_task(), asyncio.get_event_loop(), loop.create_task(). I tried using asyncio.run() without putting the start_bot function into main()

Traceback (most recent call last):
  File "C:\Users\hp\Desktop\projects\discoBotchai\main.py", line 43, in <module>
    asyncio.run(main())
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\asyncio\runners.py", line 33, in run
    raise RuntimeError(
RuntimeError: asyncio.run() cannot be called from a running event loop
sys:1: RuntimeWarning: coroutine 'main' was never awaited
Object allocated at (most recent call last):
  File "C:\Users\hp\Desktop\projects\discoBotchai\main.py", lineno 43

Process finished with exit code 1
5
  • Are you using a jupyter notebook or the Spyder IDE (if you don't know what these are, the answer is probably "no")? Commented May 28, 2023 at 14:46
  • Nope, neither of them Commented May 28, 2023 at 14:50
  • You've posted the last line of the error message. The full traceback starts on the line where it says "Traceback (most recent call last)". Post the full traceback, starting from there. Commented May 28, 2023 at 14:51
  • @user2357112 There Commented May 28, 2023 at 14:58
  • stackoverflow.com/a/75341431/4935162 Commented May 28, 2023 at 15:00

1 Answer 1

2

I see you are trying to start your given Discord bot using await bot.start('...') in a start_bot function. Additionally, I see that you use the client variable in your on_message function to respond as the "character.ai character". I see a couple of issues unrelated to starting your Discord bot. Let us look at these, then look at how we can solve them.

General Issues

  • Starting your bot is currently redundant.
  • You should be using listeners for on_message instead of @bot.event. This is so you do not run into issues with processing commands and do not have to do it in your message listener.
  • Your target channel ID is a string, Discord.py uses integers for channel IDs.
  • When calling client.chat.send_message, you are passing the discord.Message object into the message parameter. This will cause issues, you need to be passing the message's clean content, if it has it.
  • You created an instance of pyCAI for your client, not an instance of pyAsyncCAI. pyCAI is not async, you need to be using the async client instance.
  • When retrieving the response from your client, you await the data. This will cause issues at runtime.

Getting the Bot Running

Let's take a look at your current block of code for starting your bot, then elaborate on how we can fix this to get you running.

async def start_bot():
    await bot.start("bot token")

async def main():
    await asyncio.create_task(start_bot())

asyncio.run(main())

In your current example, your main() function gets called, which spawns a task that runs the start_bot function. This function then calls await bot.start('bot token'). Your current implementation, unfortunately, neglects to start your async characterAI client as shown on the Github. To achieve this, we need to adjust your main function to launch both the bot and the client.

async def wrapped_start_bot():
    async with bot:
        await bot.start('bot token')

async def main():
    tasks = [
        asyncio.create_task(wrapped_start_bot()),
        asyncio.create_task(client.start(headless=True)
    ]
    await asyncio.wait(tasks)

In our adjusted version, we create two tasks: One that starts the bot, and another that starts our async character AI client. Afterward, we use asyncio.wait which will suspend the main coroutine until all tasks are done, or in our case, until the end of the bot's lifetime. Great, let's push all these changes together.

Finalized Revisions

import asyncio
import characterai

import discord
from discord.ext import commands

import tracemalloc
tracemalloc.start()

character_ai_token = "character ai token here"
intents = discord.Intents.all()
client = characterai.pyAsyncCAI(character_ai_token)
bot = commands.Bot(command_prefix='!', intents=intents)


@bot.event
async def on_ready():
    print(f'Bot is ready. Logged in as {bot.user.name}')


# Set target channel ID to be an integer here:
target_channel_id: int = 000000001


# Utilizing bot.listen() instead of @bot.event as to 
# not worry about manually processing commands.
@bot.listen('on_message')
async def on_message(message: discord.Message):
    if not message.content:
        # This message has no content!
        return

    if message.author.bot:
        # This message has been sent by a bot!
        return

    if message.channel.id != target_channel_id:
        # This is not in the correct channel!
        return 

    # But how do we know this isn't a command? Let's ensure this isn't a valid command!
    context = await bot.get_context(message)
    if context.valid:
        # This is a valid command, which means our bot should not respond to it!
        return 

    # We know all the following items:
    # - This message has content.
    # - This message was not sent by a bot.
    # - This message is in the correct channel.
    # - This message is not a bot command.


    data = await client.chat.send_message('CHAR', message.clean_content, wait=True)
    response = data['replies'][0]['text']
    await message.reply(response, mention_author=False)

async def wrapped_start_bot():
    async with bot:
        await bot.start('bot token')

async def main():
    tasks = [
        asyncio.create_task(wrapped_start_bot()),
        asyncio.create_task(client.start(headless=True)
    ]
    await asyncio.wait(tasks)

Great. We've corrected launching the bot and client as well as fixed some other general issues with your current implementation!

Sign up to request clarification or add additional context in comments.

1 Comment

So sorry for the late answer lool, you gave such a straight-forward beginner friendly explanation i couldn't even believe i understood it all so fast!! Thank you so much

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.