Discord Bot, make it do something without an event - python

Hi im trying to send commands to a discord throug a webinterface but without any of the #bot.event definitions.
Only problem is I don't get a referenced ctx object that I can use to find what channel the bot is in etc.
Is there any way to make this work without the bot events? I've looked at few other codes but nothing comes close to what i'm trying to achieve.
I created a work around by getting the Guild_ID as a global from the on_ready event. But can seem to find the ctx for the bot. Any ideas or references to other work is very welcome so I can continue this project :)
bot.py:
import discord
from datetime import datetime
from discord.ext import commands
from discord.utils import get
import sys
import asyncio
import tornado.web
import tornado.websocket
import os
import socket
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
soundFolderLocation = os.path.dirname(os.path.abspath(__file__)) + "\\sounds\\"
bot = commands.Bot(command_prefix = '.')
GUILD_ID = ''
BOT_TOKEN = ''
#bot.event
async def on_ready():
print('Bot is ready.')
#channel = list(bot.get_all_channels())
#length = len(channel)
#for i in range(length):
#print(channel[i].name + ' id:' + str(channel[i].id))
print('logged in')
print('bot name : '+str(bot.user.name))
print('bot ID : '+str(bot.user.id))
print('discord version : '+str(discord.__version__))
print('GuildID : '+str(bot.guilds[0]))
GUILD_ID = bot.guilds[0]
class BaseHandler(tornado.web.RequestHandler):
# you can pass whatever you want
def initialize(self, bot, db):
self.bot = bot
async def prepare(self):
await self.bot.wait_until_ready()
class SoundboardHandler(tornado.web.RequestHandler):
def tornado_sounds(self):
serverFolder = soundFolderLocation + "\\"
soundList = []
for root, dirs, files in os.walk(serverFolder):
for filename in files:
sound = filename.replace(".mp3", "")
soundList.append(sound)
soundsofawsome = soundList
return soundsofawsome
def get(self):
items = self.tornado_sounds()
self.render("bot_template.html", title="Thanos", items=items)
class tornado_playsound(tornado.web.RequestHandler):
async def post(self):
global GUILD_ID
global bot
serverFolder = soundFolderLocation + "\\"
voice = get(bot.voice_clients, guild=GUILD_ID)
for member in bot.get_all_members():
if 'Thanos' in str(member):
user = member
print("Member has been found:" +str(member))
#### everything stops working here!!
channel = member.voice.channel
playthissound = serverFolder + str(self.request.body) + ".mp3"
if voice and voice.is_connected():
if ctx.message.author.voice.channel != voice.channel:
await voice.move_to(channel)
else:
voice = await channel.connect()
voice.source = discord.PCMVolumeTransformer(voice.source)
voice.source.volume = 0.5
voice.play(
discord.FFmpegPCMAudio(playthissound),
after=lambda e: print(
f"[SOUND] [{ctx.guild.id}] [{ctx.guild}] [{ctx.author.name}] [STOP] {self.request.body}.mp3"
),
)
# what is passed to inilialize
extra = {
"my_bot_instance": bot,
"my_database": "I'm a database",
}
routes = [
(r"/", SoundboardHandler),
(r"/playsound", tornado_playsound),
]
settings = {
"cookie_secret": "1234",
}
app = tornado.web.Application(routes, settings)
app.listen(4242)
myIP = socket.gethostbyname(socket.gethostname())
print('*** Websocket Server Started at %s***' % myIP)
loop = asyncio.get_event_loop()
asyncio.ensure_future(bot.start(BOT_TOKEN), loop=loop)
loop.run_forever()
template:
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script>
$(document).off('click').on('click', 'body', function(event){
if ( $( event.target ).is( ":button" ) ) {
respons = event.target.id
$.ajax({
type: 'POST',
url: "/playsound",
contentType: 'text/plain',
data: respons,
success: function (data) {
$("#debugfield").text("clicked something and it worked" + respons);
},
error: function (error) {
$("#debugfield").text("Error to load API");
}
});
}
});
</script>
<ul id="soundbuttons">
{% for item in items %}
<li>
<button class="button" id="{{escape(item)}}" type="button">{{escape(item)}}</button>
<br/>
</li>
{% end %}
</ul>
<span id="responsefield"></span>
</div>
</body>
</html>

Related

Django Channels Consumer won't execute method

I'm trying to develop a chat application that saves messages in the database via a WebSocket with Django Channels.
settings.py:
ASGI_APPLICATION = "app.asgi.application"
CHANNEL_LAYERS = {
'default': {
'BACKEND': "channels.layers.InMemoryChannelLayer",
},
}
asgi.py:
from api.routing import websocket_urlpatterns
application = ProtocolTypeRouter({
"http" : get_asgi_application(),
"websocket": JWTAuthMiddleware(
URLRouter(
websocket_urlpatterns
)
),
})
routing.py:
websocket_urlpatterns = [
path('ws/chat/<chat_uuid>/', consumers.ChatConsumer.as_asgi())
]
consumers.py
class ChatConsumer(AsyncJsonWebsocketConsumer):
#database_sync_to_async
def create_message(self, sender, message, chat):
if sender:
if content_validation(message):
chat = Chat.objects.filter(uuid=chat)
if chat.exists():
msg = Message.objects.create(sent_by=sender, content=message)
chat.first().messages.add(msg)
return msg
return None
#database_sync_to_async
def check_security(self, user, uuid):
# Chat has to exist and user has to be part of it
chat = Chat.objects.filter(Q(Q(user1=user) | Q(user2=user)) & Q(uuid=uuid))
if chat.exists():
return True
return False
async def connect(self):
self.chat_uuid = self.scope['url_route']['kwargs']['chat_uuid']
self.room_group_name = 'chat_%s' % self.chat_uuid
user = self.scope["user"]
if user:
check = await self.check_security(user, self.chat_uuid)
if check == True:
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
await self.disconnect(401)
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
name = text_data_json['name']
print(text_data_json)
user = self.scope["user"]
# Is this smart? User not receiving a message for whatever reason won't trigger the create event
new_msg = await self.create_message(user, message, self.chat_uuid)
if new_msg != None:
# Send message to room group
print("Receiving")
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'name': name
}
)
else:
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'content_guideline_violation',
'message': "",
'name': name
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
name = event['name']
print(event)
# Send message to WebSocket
print("Sending")
await self.send(text_data=json.dumps({
'message': message,
'name': name
}))
I won't post the JWTAuthMiddleware for now since everything worked fine after first implementing it.
The problem is that even though connect and receive are being executed just fine, it just won't execute chat_message anymore for whatever reason.
My Postman requests are looking like the following:
ws://127.0.0.1:8000/ws/chat/<chat_uuid>/?Bearer=<jwt_token>
Postman is able to connect to the websocket and it sends messages as well as saves them in a database but it doesn't show these messages for the other user in the chat.

Django Channels consumers.py scope['user'] returns anonymousUser

I'm trying to create one to one chat but when I'm trying to get
self.scope['user']
it returns AnonymousUser
consumers.py
class ChatConsumer(SyncConsumer):
def websocket_connect(self,event):
#error
me = self.scope['user']
other_username = self.scope['url_route']['kwargs']['username']
other_user = User.objects.get(username=other_username)
self.thread_obj = Thread.objects.get_or_create_personal_thread(me,other_user)
self.room_name = f'{self.thread_obj.id}_service'
print(f"{self.channel_name} connected")
async_to_sync(self.channel_layer.group_add)(self.room_name,self.channel_name)
self.send({
'type':'websocket.accept'
})
def websocket_receive(self,event):
print(f"{self.channel_name} message received {event['text']}")
msg = json.dumps({
'text':event.get('text'),
'username':self.scope['user'].username
})
async_to_sync(self.channel_layer.group_send)(
self.room_name,
{
'type':'websocket.message',
'text':msg
}
)
def websocket_message(self,event):
print(f"{self.channel_name} message sent {event['text']}")
self.send({
'type':'websocket.send',
'text':event.get('text')
})
def websocket_disconnect(self,event):
print(f"{self.channel_name} disconnected")
async_to_sync(self.channel_layer.group_discard)(self.room_name,self.channel_name)
print(event)
routing.py
from channels.routing import ProtocolTypeRouter,URLRouter
from django.urls import path
from .consumers import ChatConsumer,EchoConsumer
from channels.auth import AuthMiddlewareStack
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
'websocket': AuthMiddlewareStack(
URLRouter([
path('ws/chat/',EchoConsumer()),
path('ws/chat/<str:username>/',ChatConsumer()),
])
)
})
I think the problem is here in auth.py:
#database_sync_to_async
def get_user(scope):
"""
Return the user model instance associated with the given scope.
If no user is retrieved, return an instance of `AnonymousUser`.
"""
# postpone model import to avoid ImproperlyConfigured error before Django
# setup is complete.
from django.contrib.auth.models import AnonymousUser
if "session" not in scope:
raise ValueError(
"Cannot find session in scope. You should wrap your consumer in "
"SessionMiddleware."
)
session = scope["session"]
user = None
try:
user_id = _get_user_session_key(session)
backend_path = session[BACKEND_SESSION_KEY]
except KeyError:
pass
else:
if backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
user = backend.get_user(user_id)
# Verify the session
if hasattr(user, "get_session_auth_hash"):
session_hash = session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash, user.get_session_auth_hash()
)
if not session_hash_verified:
session.flush()
user = None
return user or AnonymousUser()
because codes under else not running
I've made some changes to the code that I took from the documentation and the result is like this
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
me = self.scope['user']
other_username = self.scope['url_route']['kwargs']['username']
other_user =await sync_to_async(User.objects.get)(username=other_username)
thread_obj =await sync_to_async(Thread.objects.get_or_create_personal_thread)(me,other_user)
self.room_name = f'{thread_obj.id}_service'
self.room_group_name = self.room_name
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
await self.send(text_data=json.dumps({
'message': message
}))

Group name not taking spaces django-channels

I am working on a chat app with django channels, everthing is working but messages don't get send when room name has spaces or some other special carachters, ideally I'd like to be possible to the user to be free and name their room whatever they want and include spaces as well.
/*
consumers.py:
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from web socket
async def receive(self, text_data):
data = json.loads(text_data)
message = data['message']
username = data['username']
room = data['room']
await self.save_message(username, room, message)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'username': username
}
)
async def chat_message(self, event):
message = event['message']
username = event['username']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message,
'username': username,
'timestamp': timezone.now().isoformat()
}))
#sync_to_async
def save_message(self, username, room, message):
if len(message) > 0:
Message.objects.create(username=username, room=room, content=message)
routing.py:
websocket_urlpatterns = [
path('ws/<str:room_name>/', consumers.ChatConsumer.as_asgi()),
]
urls.py:
urlpatterns = [
path('', views.index, name='index'),
path('<str:room_name>/', views.room, name='room'),
]
index.html:
<script>
document.querySelector('#room-name-input').focus();
document.querySelector("#room-name-input, #username-input").onkeyup = function(e) {
if (e.keyCode === 13) {
document.querySelector('#room-name-submit').click();
}
};
document.querySelector('#room-name-submit').onclick = function(e) {
var roomName = document.querySelector('#room-name-input').value;
var userName = document.querySelector('#username-input').value;
window.location.replace(roomName + '/?username=' + userName);
};
</script>
*/

Quart & websocket: how to send data to selected users only (private message)

I know how to broadcast but i can not target clients.
Here is my script:
import json
import trio
from quart import render_template, websocket, render_template_string
from quart_trio import QuartTrio
from quart_auth import current_user,login_required
from quart_auth import AuthUser, login_user, logout_user, AuthManager
import random
connections = set()
app = QuartTrio(__name__)
AuthManager(app)
app.secret_key = "secret key"
#app.route("/")
async def index():
clean_guy = await current_user.is_authenticated
if not clean_guy:
fake_ID = random.randrange(0, 9999) #quick dirty to test
login_user(AuthUser(fake_ID))
return await render_template_string("{{ current_user.__dict__ }}")
return await render_template_string("{{ current_user.__dict__ }}")
#app.websocket("/ws")
async def chat():
try:
connections.add(websocket._get_current_object())
async with trio.open_nursery() as nursery:
nursery.start_soon(heartbeat)
while True:
message = await websocket.receive()
await broadcast(message)
finally:
connections.remove(websocket._get_current_object())
async def broadcast(message):
for connection in connections:
await connection.send(json.dumps({"type": "message", "value": message}))
async def heartbeat():
while True:
await trio.sleep(1)
await websocket.send(json.dumps({"type": "heartbeat"}))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Here is my template:
<div>
<div>
<ul>
</ul>
</div>
<form>
<input type="text">
<button type="submit">Send</button>
</form>
</div>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
const ws = new WebSocket(`ws://${window.location.host}/ws`);
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === "message") {
const ulDOM = document.querySelectorAll("ul")[0];
const liDOM = document.createElement("li");
liDOM.innerText = data.value;
ulDOM.appendChild(liDOM);
}
}
document.querySelectorAll("form")[0].onsubmit = function(event) {
event.preventDefault();
const inputDOM = document.querySelectorAll("input")[0];
ws.send(inputDOM.value);
inputDOM.value = "";
return false;
};
});
</script>
Also one problem:
if i use this in my script:
return await render_template("{{ current_user.__dict__ }}")
i am not able to display it with my jinja template even if i add {{ current_user.dict }} in my template.
I also noticed that:
with mozilla: i get something stable like
{'_auth_id': 9635, 'action': <Action.PASS: 2>}
with chrome: it changes on each refresh, it looks like
{'_auth_id': 529, 'action': <Action.WRITE: 3>}
I need to display the author, and the destination , and an input with a send button, how to fix the template ?
Is it also possible to send messages to targeted users with post via curl or websocat ? how to do that ?
Quart-Auth uses cookies to identify the user on each request/websocket-request so you can always get the identity of the user from the current_user if request is authenticated. Then for your need you will need to map websocket connections to each user (so you can target messages), hence the connections mapping should be a dictionary of connections, e.g.
import random
from collections import defaultdict
from quart import request, websocket
from quart_trio import QuartTrio
from quart_auth import (
AuthUser, current_user, login_required, login_user, logout_user, AuthManager
)
connections = defaultdict(set)
app = QuartTrio(__name__)
AuthManager(app)
app.secret_key = "secret key"
#app.route("/login", methods=["POST"])
async def login():
# Figure out who the user is,
user_id = random.randrange(0, 9999)
login_user(AuthUser(fake_ID))
return {}
#app.websocket("/ws")
#login_required
async def chat():
user_id = await current_user.auth_id
try:
connections[user_id].add(websocket._get_current_object())
while True:
data = await websocket.receive_json()
await broadcast(data["message"])
finally:
connections[user_id].remove(websocket._get_current_object())
#app.route('/broadcast', methods=['POST'])
#login_required
async def send_broadcast():
data = await request.get_json()
await broadcast(data["message"], data.get("target_id"))
return {}
async def broadcast(message, target = None):
if target is None:
for user_connections in connections.values():
for connection in user_connections:
await connection.send_json({"type": "message", "value": message})
else:
for connection in connections[target]:
await connection.send_json({"type": "message", "value": message})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
You can then send to the /broadcast either JSON that is just a message {"message": "something"} or a message with an id to target someone specifically {"message": "something for user 2", "target_id": 2}. Note as well the #login_required decorator ensures the route handler is only called for logged in users.

Django-channels sending message when model changes

I'm using django-channels to organize my websockets on backend.
Right now everything is working alright except of message sending to the front-end when info in db is changed. There is http endpoint to change model.
Here is my websocket consumer
import asyncio
from asgiref.sync import async_to_sync, sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from rest_framework.response import Response
from rest_framework import status
from channels.db import database_sync_to_async
# models and serializers
from billing.models import Billing
from billing.serializers import BillingSerializer
from customers.models import Customer
from customers.serializers import CustomersSerializer
from shipping.models import Shipping
from shipping.serializers import ShippingSerializer
from .models import Order
from .serializers import OrdersSerializer
from .views import OrdersViewSet
from .exceptions import ClientError
from .utils import get_orders_or_error, get_orders_or_warning
class OrdersConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
orders = await get_orders_or_warning()
if 'user' in self.scope:
await self.close()
else:
await self.accept()
self.orders = set(orders)
# await self.create_order(content)
async def receive_json(self, content):
command = content.get('command', None)
orders = await get_orders_or_warning()
# print(list(self.orders))
# print(set(orders))
try:
if command == "join":
await self.join_room(JWT_Token=content['token'])
elif command == "leave":
await self.leave_room()
elif command == "send":
await self.send_room(content['message'])
except ClientError as e:
await self.send_json({"error": e.code})
async def disconnect(self, code):
for room_id in list(self.rooms):
try:
self.send_json({
'type': 'CLOSE',
'message': "Socket closed"
})
await self.leave_room(content['token'])
except ClientError:
pass
async def join_room(self, JWT_Token):
orders = await get_orders_or_warning()
serializer = OrdersSerializer(orders, many=True)
# print('serializer', serializer)
if self.orders != set(orders):
await self.send_json(
{
'type': 'orders',
'data': json.dumps(serializer.data),
},
)
await self.send_json(
{
'type': 'orders',
'data': json.dumps(serializer.data),
},
)
async def leave_room(self, JWT_Token):
await self.channel_layer.group_send(
orders.group_name,
{
'type': 'orders.leave',
'JWT_Token': JWT_Token
}
)
self.rooms.discard()
await self.channel_layer.group_discard(
orders.group_name,
self.channel_name
)
await self.send_json({
"leave": str(room_id),
})
async def send_room(self, message):
if room_id not in self.rooms:
raise ClientError("ROOM_ACCESS_DENIED")
orders = await get_orders_or_warning()
serializer = OrdersSerializer(orders, many=True)
await self.send_json(
{
'type': 'orders.give',
'data': json.dumps(serializer.data)
}
)
await self.send(text_data=json.dumps({
'message': message
}))
async def orders_leave(self, event):
await self.send_json(
{
'type': 'LEAVE',
'room': event["room_id"],
'username': event["username"]
}
)
Here is my routing file
application = ProtocolTypeRouter({
'websocket': (
URLRouter([
url('orders/', OrdersConsumer),
])
)
})
I want to get all data with front-end when some changes happened. Can this be done with current consumer? And if yes, how? I've looked too many info sources i think and now I'm kinda confused how i can actually do this.
I rly don't want to rewrite the structure, if it's possible. If you can explain how and why I need to write it the way you say I will be very grateful
You can use signals to monitor the change and send a message to the consumer using the approach defined in the docs on how to interact with the consumer from the outside

Categories

Resources