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!