discord bot and websocket server in one application

Viewed 2797

I want to make an application in heroku that would take some data as a websocket server and send it as a discord bot.

This is how I tried to implement this idea:

# -*- coding: utf-8 -*-  
import asyncio, websockets, discord   

client = discord.Client() 
channel = client.get_channel(...)
print(channel)

async def response(websocket, path): 
    global channel 
    r = await websocket.recv()
    print(r)  
    await channel.send(str(r))

PORT = os.environ.get('PORT')
start_server = websockets.serve(response, '0.0.0.0', os.environ.get('PORT'))
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

But I don't know how to launch a discord bot in such a code so that it works in parallel with the websocket server. Please tell me how to do this.

1 Answers

My first idea was to use threading to run websockets in separated thread but it couldn't use asyncio.get_event_loop() in thread.


Because websockets uses asyncio like discord so I tried to start both using the same loop in asyncio.

After digging in source code of discord.run() I found it uses asyncio.get_event_loop().run_forever() so I tried to start websockets without asyncio.get_event_loop().run_forever() and it works for me.

# --- start ---

# - websockets -

server = websockets.serve(response, '0.0.0.0', '8000')
asyncio.get_event_loop().run_until_complete(server)
# without `run_forever()` because `client.run()` will run `run_forever()`

# - discord -

TOKEN = os.getenv('DISCORD_TOKEN')
client.run(TOKEN)  # `client.run()` will run `run_forever()`

Minimal working code which I used to test it

main.py (with discord client and websockets server)

import os
import asyncio
import discord
import websockets

# --- websockets ----

async def response(websocket, path): 
    message = await websocket.recv()
    print(f"[ws server] message  < {message}")
    
    #answer = f"[{message}]"
    
    #await websocket.send(answer)   # if client expect `response` then server has to send `response`
    #print(f"[ws server] answer > {answer}")    

    # `get_channel()` has to be used after `client.run()`
    channel = client.get_channel(MY_CHANNEL_ID)  # access to channel
    
    await channel.send(f'websockets: {message}')
    
# --- discord ---

MY_CHANNEL_ID = 709507681441808388  # you have to use own number

client = discord.Client() 

# `get_channel()` has to be used after `client.run()`
#print(client.get_channel(MY_CHANNEL_ID))  # None

@client.event
async def on_message(message):
    if message.author != client.user:   
        print('message.content:', message.content)

# --- start ---

# - websockets -

print('running websockets ws://0.0.0.0:8000')
server = websockets.serve(response, '0.0.0.0', '8000')
asyncio.get_event_loop().run_until_complete(server)
# without `run_forever()` because `client.run()` will run `run_forever()`

# - discord -

print('running discord')
TOKEN = os.getenv('DISCORD_TOKEN')
client.run(TOKEN)

client.py (websockets client which I used to test websockets server)

import asyncio
import websockets

uri = 'ws://0.0.0.0:8000'

async def send_message():
    async with websockets.connect(uri) as websocket:
        message = input("msg: ")

        await websocket.send(message)
        print(f"[ws client] message  > {message}")

        #answer = await websocket.recv()
        #print(f"[ws client] answer < {answer}")

asyncio.get_event_loop().run_until_complete(send_message())

BTW:

You can't use get_channel() before client.run() because it gives None and I use it inside response()


EDIT:

I used on_ready() to use get_channel() - and I also check in response() if I have access to channel.

To make sure I set channel = None before starting discord.

main.py

import os
import asyncio
import discord
import websockets


# --- websockets ----

async def response(websocket, path): 
    global channel
    
    message = await websocket.recv()
    print(f"[ws server] message  < {message}")
    
    #answer = f"my answer: [{message}]"
    
    #await websocket.send(answer)   # if client expect `response` then server has to send `response`
    #print(f"[ws server] answer > {answer}")    

    # `get_channel()` has to be used after `client.run()`
    
    if not channel:
        print('[ws server] getting discord channel:', MY_CHANNEL_ID)
        channel = client.get_channel(MY_CHANNEL_ID)  # access to channel

    if not channel:
        print("[ws server] can't access channel:", MY_CHANNEL_ID)
    else:        
        print('[ws server] channel:', channel, 'message:', message)
        #await channel.send(f'websockets: {message}')
        await channel.send(message)    
        
# --- discord ---

MY_CHANNEL_ID = 709507681441808388  # you have to use own number

client = discord.Client() 

# `get_channel()` has to be used after `client.run()`
#print(client.get_channel(MY_CHANNEL_ID))  # None

@client.event
async def on_ready():
    global channel 
    
    if not channel:
        print('[on_ready] getting discord channel:', MY_CHANNEL_ID)
        channel = client.get_channel(MY_CHANNEL_ID)  # access to channel
    
    if not channel:
        print("[on_ready] can't access channel:", MY_CHANNEL_ID)
    else:        
        print('[on_ready] channel:', channel)

@client.event
async def on_message(message):
    if message.author != client.user:   
        print('[on_message] message.content:', message.content)

# --- start ---

# - websockets -

print('running websockets ws://0.0.0.0:8000')
server = websockets.serve(response, '0.0.0.0', '8000')
asyncio.get_event_loop().run_until_complete(server)
# without `run_forever()` because `client.run()` will run `run_forever()`

# - discord -

channel = None   # set default value at start

print('running discord')
TOKEN = os.getenv('DISCORD_TOKEN')
client.run(TOKEN)

And the same for bot

main-bot.py

import os
import asyncio
import discord
from discord.ext import commands
import websockets

# --- websockets ----

async def response(websocket, path): 
    global channel

    message = await websocket.recv()
    print(f"[ws server] message  < {message}")
    
    #answer = f"my answer: [{message}]"
    
    #await websocket.send(answer)   # if client expect `response` then server has to send `response`
    #print(f"[ws server] answer > {answer}")    

    # `get_channel()` has to be used after `client.run()`
    
    if not channel:
        print('[ws server] getting discord channel:', MY_CHANNEL_ID)
        channel = bot.get_channel(MY_CHANNEL_ID)  # access to channel

    if not channel:
        print("[ws server] can't access channel:", MY_CHANNEL_ID)
    else:        
        print('[ws server] channel:', channel, 'message:', message)
        #await channel.send(f'websockets: {message}')
        await channel.send(message)
    
# --- discord ---

MY_CHANNEL_ID = 709507681441808388  # you have to use own number

bot = commands.Bot(command_prefix="!")

# `get_channel()` has to be used after `client.run()`
#print(client.get_channel(MY_CHANNEL_ID))  # None

@bot.event
async def on_ready():
    global channel 
    
    if not channel:
        print('[on_ready] getting discord channel:', MY_CHANNEL_ID)
        channel = bot.get_channel(MY_CHANNEL_ID)  # access to channel
    
    if not channel:
        print("[on_ready] can't access channel:", MY_CHANNEL_ID)
    else:        
        print('[on_ready] channel:', channel)

#@bot.event
#async def on_message(message):
#    if message.author != bot.user:   
#        print('message.content:', message.content)

@bot.command()
async def ping(ctx):
    await ctx.send('pong')
    
# --- start ---

# - websockets -

print('running websockets ws://0.0.0.0:8000')
server = websockets.serve(response, '0.0.0.0', '8000')
asyncio.get_event_loop().run_until_complete(server)
# without `run_forever()` because `client.run()` will run `run_forever()`

# - discord -

channel = None   # set default value at start

print('running discord')
TOKEN = os.getenv('DISCORD_TOKEN')
bot.run(TOKEN)
Related