Python, server - client connection coroutine issues asyncio - python

Been trying to create a server and client script that allows clients to connect to the server and join different rooms to chat. As of now everything is terminal based. The issue at hand is the following.
When a new clients connects to a existing room i get the following error:
Task exception was never retrieved
future: <Task finished name='Task-9' coro=<ChatServer.handle_client() done, defined at C:\Users\~~~~\server.py:10> exception=TypeError('a coroutine was expected, got None')>
Traceback (most recent call last):
File "C:\Users\~~~~\server.py", line 41, in handle_client
await self.broadcast(f'{addr} has joined room {room}!\n', room, writer)
File "C:\Users\~~~~\server.py", line 75, in broadcast
task = asyncio.create_task(client.write(message.encode()))
File "C:\Users\~~~~\AppData\Local\Programs\Python\Python39\lib\asyncio\tasks.py", line 361, in create_task
task = loop.create_task(coro)
File "C:\Users\~~~~\AppData\Local\Programs\Python\Python39\lib\asyncio\base_events.py", line 438, in create_task
task = tasks.Task(coro, loop=self, name=name)
TypeError: a coroutine was expected, got None
But, if the clients join separete rooms there is no error. If a client join an exists room with another client it inside, it gets that error. I have been trying to understand the error at hand but cant wrap my head around it.
Here is the code for the server:
import asyncio
#available_rooms = {"Room_1"}
class ChatServer:
def __init__(self):
self.clients = {}
self.rooms = {}
async def handle_client(self, reader, writer):
addr = writer.get_extra_info('peername')
print(f'Connected by {addr}')
# Send the list of available rooms to the client
available_rooms = '\n'.join(self.rooms.keys())
print("---->" + available_rooms)
writer.write(f'Available Rooms: {available_rooms}\n'.encode())
await writer.drain()
while True:
message = await reader.readline()
if not message:
break
message = message.decode().strip()
command = message.split()[0]
if command == "LIST":
available_rooms = '\n'.join(self.rooms.keys())
writer.write(f'Available Rooms: {available_rooms}\n'.encode())
await writer.drain()
elif command == "JOIN":
room = message.split()[1]
if room not in self.rooms:
writer.write(f"{room} room not found. creating it\n".encode())
self.rooms[room] = []
await writer.drain()
self.clients[writer] = room
self.rooms[room].append(writer)
writer.write(f'You joined room: {room}!\n'.encode())
await writer.drain()
await self.broadcast(f'{addr} has joined room {room}!\n', room, writer)
elif command == "LEAVE":
if writer not in self.clients:
writer.write(f'You are not currently in a room!\n'.encode())
await writer.drain()
else:
room = self.clients[writer]
writer.write(f'You left room {room}!\n'.encode())
await writer.drain()
self.clients.pop(writer)
self.rooms[room].remove(writer)
await self.broadcast(f'{addr} has left room {room}!\n', room, writer)
# remove the room if it is empty
if not self.rooms[room]:
self.rooms.pop(room)
else:
try:
await self.broadcast(f'{addr}: {message}\n', self.clients[writer], writer)
except KeyError:
writer.write(f'You are not currently in a room!\n'.encode())
await writer.drain()
continue
print(f'Disconnected {addr}')
writer.close()
async def broadcast(self, message, room, sender):
tasks = []
for client in self.rooms[room]:
# exclude sender from broadcast
if client != sender:
task = asyncio.create_task(client.write(message.encode()))
tasks.append(task)
if tasks:
await asyncio.gather(*tasks)
def start(self):
loop = asyncio.get_event_loop()
coro = asyncio.start_server(self.handle_client, '127.0.0.1', 5323, loop=loop)
#coro = asyncio.start_server(self.handle_client, '10.44.33.158', 5000, loop=loop)
server = loop.run_until_complete(coro)
# Serve requests until Ctrl+C is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
server = ChatServer()
server.start()
And here is the code for the client:
import asyncio
class ChatClient:
def __init__(self):
self.room = None
async def start(self, host, port):
self.reader, self.writer = await asyncio.open_connection(host, port)
# Get the list of available rooms
available_rooms = await self.reader.readline()
print(available_rooms.decode())
self.writer.write('JOIN myroom_2\n'.encode())
await self.writer.drain()
resp = await self.reader.readline()
if resp:
print(resp.decode())
self.room = "myroom_2"
while True:
message = await self.reader.readline()
print(message.decode())
message_to_send = input()
if message_to_send == "LEAVE":
self.writer.write("LEAVE\n".encode())
await self.writer.drain()
print("left the room")
break
elif message_to_send.startswith("JOIN"):
_, new_room = message_to_send.split()
self.writer.write(f"JOIN {new_room}\n".encode())
await self.writer.drain()
resp = await self.reader.readline()
if resp:
print(resp.decode())
self.room = new_room
continue
elif message_to_send == "LIST":
self.writer.write("LIST\n".encode())
await self.writer.drain()
response = await self.reader.readline()
print(response.decode())
continue
self.writer.write(f"{message_to_send}\n".encode())
await self.writer.drain()
async def list_rooms(self):
self.writer.write("LIST\n".encode())
await self.writer.drain()
try:
response = await asyncio.wait_for(self.reader.readline(), timeout=5)
print(response.decode())
except asyncio.TimeoutError:
print("timeout reached while waiting for a response")
async def change_room(self):
self.writer.write("Enter new room name:\n".encode())
await self.writer.drain()
new_room = input()
self.writer.write(f"JOIN {new_room}\n".encode())
await self.writer.drain()
response = await self.reader.readline()
print(response.decode())
self.room = new_room
async def write(self, message):
self.writer.write(message.encode())
await self.writer.drain()
client = ChatClient()
asyncio.run(client.start('127.0.0.1',5323 ))
I have tried different write methods but nothing works, unable to wrap my head around this issue.

The problem is in how you are using asyncio.create_task.
In the Server.broadcast function you call:
task = asyncio.create_task(client.write(message.encode()))
asyncio.create_task expects a coroutine but you are giving it a normal funciton. client in this context will be a asyncio.StreamWriter, and the write method is a normal function. you should probably create a task from the client.drain coroutine:
client.write(message.encode())
task = asyncio.create_task(client.drain())

Related

Music Discord Bot Looping Music

I'm working on an discord music bot with python, I already have a function that plays the songs that are in a queue but I would like to add a command that plays the queue in an infinite loop. How can I do this?
Those are the commands that set the loop value to True or False
#client.command(name='loop', help='Enable loop')
async def loop_on(ctx):
global loop
loop = True
await ctx.send('Looping')
#client.command(name='dunloop', help='Disable loop')
async def loop_off(ctx):
global loop
loop = False
await ctx.send('Loop disabled')
And here the code that plays the music
#client.command(name='play', help='Plays Music')
async def play(ctx):
global queue
if not ctx.message.author.voice:
await ctx.send("You are not connected to a voice channel")
return
else:
channel = ctx.message.author.voice.channel
try: await channel.connect()
except: pass
server = ctx.message.guild
voice_channel = server.voice_client
try:
async with ctx.typing():
player = await YTDLSource.from_url(queue[0], loop=client.loop)
voice_channel.play(player, after=lambda e: print('Player error: %s' % e) if e else None)
if loop:
queue.append(queue[0])
del(queue[0])
await ctx.send('**Playing :** {}'.format(player.title))
except:
await ctx.send('The queue is empty .Use ?queue to add a song !')
And now I would like to add a command that plays the queue without stopping while the loop value is set to True.

How to detect closed websocket in asyncio.gather(*tasks)

I have a list of asyncio tasks which contains with connecting,handshaking and receiving data from a websocket. This process is running correctly but sometimes the connection of one of the websocket (or maybe all of them) is closed.
How can I detect and make a new conncetion to the closed one?
Here is the code which I use:
async def main(_id):
try:
async with websockets.connect("wss://ws.bitpin.ir/", extra_headers = request_header, timeout=10, ping_interval=None) as websocket:
await websocket.send('{"method":"sub_to_price_info"}')
recv_msg = await websocket.recv()
if recv_msg == '{"message": "sub to price info"}':
await websocket.send(json.dumps({"method":"sub_to_market","id":_id}))
recv_msg = await websocket.recv()
print(recv_msg)
counter = 1
task = asyncio.create_task(ping_func(websocket))
while True:
msg = await websocket.recv()
return_func(msg, counter, asyncio.current_task().get_name()) ## Return recieved message
counter+=1
except Exception as e:
err_log(name='Error in main function', text=str(e))
async def ping_func(websocket):
try:
while True:
await websocket.send('{"message":"PING"}')
# print('------ ping')
await asyncio.sleep(5)
except Exception as e:
err_log(name='Error in ping function', text=str(e))
def return_func(msg, counter, task_name):
if msg != '{"message": "PONG"}' and len(json.loads(msg)) <20:
print(task_name, counter, msg[:100])
else:
print(task_name, counter)
async def handler():
try:
tasks = []
for _id in symbols_id_dict.values():
tasks.append(asyncio.create_task(main(_id), name='task{}'.format(_id)))
responses = await asyncio.gather(*tasks)
except Exception as e:
err_log(name='Error in handler function', text=str(e))
try:
if __name__ == '__main__':
asyncio.run(handler())
else:
os._exit(0)
except Exception as e:
err_log(name='Error in running asyncio handler', text=str(e))
finally:
os._exit(0)
According to the below line, each task is specified with a name:
tasks.append(asyncio.create_task(main(_id), name='task{}'.format(_id)))
So each task can be detected. How can I use this feature to detect closed websocket.
try:
data = await ws.recv()
except (ConnectionClosed):
print("Connection is Closed")
data = None
print('Reconnecting')
websocket = await websockets.connect(params)

websockets.exceptions.ConnectionClosedOK: code = 1000 (OK), no reason

I am trying to receive data from a website which use websocket. This acts like this:
websocket handshaking
Here is the code to catch data:
async def hello(symb_id: int):
async with websockets.connect("wss://ws.bitpin.ir/", extra_headers = request_header, timeout=15) as websocket:
await websocket.send('{"method":"sub_to_price_info"}')
recv_msg = await websocket.recv()
if recv_msg == '{"message": "sub to price info"}':
await websocket.send(json.dumps({"method":"sub_to_market","id":symb_id}))
recv_msg = await websocket.recv()
counter = 1
while(1):
msg = await websocket.recv()
print(counter, msg[:100], end='\n\n')
counter+=1
asyncio.run(hello(1))
After receiving about 100 message, I am facing with this error:
websockets.exceptions.ConnectionClosedOK: code = 1000 (OK), no reason
I tried to set timeout and request headers but these were not helpful
I solved this error by creating a task for sending PING.
async def hello(symb_id: int):
async with websockets.connect("wss://ws.bitpin.ir/", extra_headers = request_header, timeout=10, ping_interval=None) as websocket:
await websocket.send('{"method":"sub_to_price_info"}')
recv_msg = await websocket.recv()
if recv_msg == '{"message": "sub to price info"}':
await websocket.send(json.dumps({"method":"sub_to_market","id":symb_id}))
recv_msg = await websocket.recv()
counter = 1
task = asyncio.create_task(ping(websocket))
while True:
msg = await websocket.recv()
await return_func(msg)
print(counter, msg[:100], end='\n\n')
counter+=1
async def ping(websocket):
while True:
await websocket.send('{"message":"PING"}')
print('------ ping')
await asyncio.sleep(5)

Pythonping not working with a discord bot

I want to make it so that this discord bot can ping,portscan and whois Servers and IPs.All of the commands work perfectly fine but when I try to use pythonping i get a error which i can't see.i get this message in discord, <coroutine object Command.call at 0x04E55368> and this error in the Visual Studio Code terminal i get this error, C:\Users\swevl\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py:85: RuntimeWarning: coroutine 'Command.__call__' was never awaited ret = await coro(*args, **kwargs) RuntimeWarning: Enable tracemalloc to get the object allocation traceback.
Here is my code:
from discord.ext import commands
import json
import os
import random
import string
import time
import colorama
from colorama import Fore, Back, Style
import string
from os import system, name
import aiohttp
from pythonping import ping
import requests
# # # # # # # # # # Bot Config and Token # # # # # # # # # #
def clear():
if name == 'nt':
_ = system('cls')
else:
_ = system('clear')
clear()
os.system("cls && title Gen Bot")
token = "Censored Bot Token"
client = commands.Bot(command_prefix='a!', case_insensitive=True)
client.remove_command('help')
print(f" {Fore.RED}╔═╗╦ ╔═╗╦ ╦╔═╗ ╔╗ ╔═╗╔╦╗")
print(f" {Fore.RED}╠═╣║ ╠═╝╠═╣╠═╣ ╠╩╗║ ║ ║ ")
print(f" {Fore.RED}╩ ╩╩═╝╩ ╩ ╩╩ ╩ ╚═╝╚═╝ ╩ ")
print(f"{Fore.YELLOW}I-----------------------------------------------------I")
#client.event
async def on_ready():
# login of client
await client.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name='a!help'))
print(f"{Fore.RED}[{Fore.YELLOW}STATUS{Fore.RED}] The bot just went online!".format(client))
#####Utiity Commands#####
#client.command()
async def clear(ctx, amount=100):
await ctx.channel.purge(limit=amount)
await ctx.send("Messages have been removed!", delete_after=4)
print (f'{Fore.RED}[{Fore.YELLOW}LOGS{Fore.RED}] {ctx.author} cleared {amount} messages')
#########################################################################################################################################
#client.command(name='whois')
async def whois(ctx, arg1):
if arg1 == "":
await ctx.send("Invalid IP!")
else:
async with aiohttp.ClientSession() as session:
async with session.get(f"http://ip-api.com/json/{arg1}?fields=66846719") as r:
js = await r.json()
myip = ('')
if myip == (js["query"]):
await ctx.send('Invalid Ip!')
else:
cont = (js["continent"])
country = (js["country"])
region = (js["regionName"])
city = (js["city"])
zipcode = (js["zip"])
iso = (js["isp"])
org = (js["org"])
reverse = (js["reverse"])
mobile = (js["mobile"])
proxy = (js["proxy"])
hosting = (js["hosting"])
embed1 = discord.Embed(title=(js["query"]), color = discord.Color.red())
embed1.add_field(name="info", value=(f"{ctx.author.mention}\n"
f"Continent: {cont} \n"
f"country: {country} \n"
f"Region: {region}\n"
f"City: {city} \n"
f"Zip: {zipcode} \n"
f"ISP: {iso} \n"
f"Org: {org} \n"
f"Reverse: {reverse} \n"
f"Mobile: {mobile} \n"
f"Proxy: {proxy} \n"
f"Hosting: {hosting}"), inline=False)
await ctx.send(embed=embed1)
print (f'{Fore.RED}[{Fore.YELLOW}LOGS{Fore.RED}] {ctx.author} used the whois comand on {arg1}')
#client.command()
async def portscan(ctx, arg1):
if arg1 == '':
await ctx.send("Invalid IP!")
else:
print (f'{Fore.RED}[{Fore.YELLOW}LOGS{Fore.RED}] {ctx.author} portscanned {arg1}')
async with aiohttp.ClientSession() as session:
async with session.get(f"https://api.hackertarget.com/nmap/?q={arg1}") as r:
if r.status == 200:
text = await r.text()
embed1 = discord.Embed(title=(f'Results from {arg1}'), description=(text), color = discord.Color.red())
await ctx.send(embed=embed1)
else:
await ctx.send("API is offline, try again later...")
#client.command()
async def ping(ctx, arg1):
if arg1 == '':
await ctx.send("Invalid IP!")
else:
await ctx.send(ping(arg1))
client.run(token)```
I can walk you through the error from your code:
#client.command()
async def ping(ctx, arg1):
if arg1 == '':
await ctx.send("Invalid IP!")
else:
await ctx.send(ping(arg1))
When you hit the else statement, you call the ping function again. You never awaited the ping function (it is a coro), which is why the error is being raised. From what you provided here, it seems this command would do nothing but eventually crash your discord bot. It would keep calling itself over and over again (infinite recursion).
What I think you're trying to do is have a command that pings an ip and returns a response. You need to use a different function to ping, you can't call the same one over and over again.
async def ping_ip(ip) -> str: # The function returns a string
... # Do pinging here and return a str
#client.command()
async def ping(ctx, ip: str):
data = await ping_ip(ip)
return await ctx.send(data)
i use this code to find the bot ping
#client.command()
#commands.guild_only()
async def ping(ctx):
embed = discord.Embed(title="Bot ping",
description=f"{round(client.latency * 1000)}ms",
color=discord.Colour.purple())
await ctx.send(embed=embed)

Sending and receiving messages in parallel does not work

I have a simple asyncio tcp chat server/client.
But there is an issue with, it seems to me, sending messages. Instead of receiving and sending messages in parallel, if I have 2 or more clients, they receive other messages only after they send theirs.
How to solve this problem?
import asyncio
list_of_users = {}
async def handle_echo(reader, writer):
name = await reader.read(1024)
name.decode()
print(name)
addr = writer
list_of_users[addr]
while True:
data = await reader.read(1024)
message = data.decode()
if not message:
del list_of_users[addr]
break
msg(message)
def msg(message):
for user in list_of_users:
print('send message - ', message)
user.write(message.encode())
async def main():
server = await asyncio.start_server(
handle_echo, '127.0.0.1', 8888)
addr = server.sockets[0].getsockname()
print(f'Serving on {addr}')
async with server:
await server.serve_forever()
asyncio.run(main())
import asyncio
from aioconsole import ainput
async def tcp_echo_client():
reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
name = input('Enter your name: ')
writer.write(name.encode())
while True:
await output_messages(writer)
await incoming_messages(reader)
async def output_messages(writer):
message = await ainput()
writer.write(message.encode())
async def incoming_messages(reader):
input_message = await reader.read(1024)
print('print incoming message', input_message)
async def main():
await tcp_echo_client()
asyncio.run(main())
The main issue was that the coroutine incoming_messages did not run in the background. Instead it was repeatedly called after output_messages finished.
I also had to make other adjustments to get the code running on Python 3.6
run incoming_messages in the background
list_of_users is now a list
no context handler for the server
no serve_forever
Try this code:
Server
import asyncio
list_of_users = []
async def handle_echo(reader, writer):
name = await reader.read(1024)
name.decode()
print(name)
addr = writer
list_of_users.append(addr)
while True:
data = await reader.read(1024)
message = data.decode()
if not message:
list_of_users.remove(addr)
break
msg(message)
def msg(message):
for user in list_of_users:
print('send message - ', message)
user.write(message.encode())
async def main():
server = await asyncio.start_server(
handle_echo, '127.0.0.1', 8888)
addr = server.sockets[0].getsockname()
print(f'Serving on {addr}')
await asyncio.sleep(3600)
asyncio.ensure_future(main())
loop = asyncio.get_event_loop()
loop.run_forever()
Client
import asyncio
from aioconsole import ainput
async def tcp_echo_client():
reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
name = input('Enter your name: ')
writer.write(name.encode())
asyncio.ensure_future(incoming_messages(reader))
while True:
await output_messages(writer)
async def output_messages(writer):
message = await ainput()
writer.write(message.encode())
async def incoming_messages(reader):
while True:
input_message = await reader.read(1024)
print('print incoming message', input_message)
async def main():
await tcp_echo_client()
asyncio.get_event_loop().run_until_complete(main())

Categories

Resources