I need some help understanding how Django Channels is executing the code. I'm probably missing some understanding of sync vs async as it's not doing what I would expect.
In summary I wanted a consumer that would do the following:
Connect
On message from client
in a while loop:
execute a long running function (1 sec)
call group_send broadcasting results
This wasn't working, so I tried to create a very simple consumer to understand what's happening.
I create a WebsocketConsumer as I think I want it to run syncronously, i.e. I want each loop to execute and then broadcast the results, then execute again.
When I trigger the receive method I see the self.send happen instantly, but both the "2. First Group Call" and the "3. Second Group Call" happen after the long running function. I can see from the timestamps in the console.log that the chat_message() is only being executed for the "2. First Group Call" after the long running function, even though it appears before the long running function in the receive method.
The results in one of the browser console is:
received "1. Self send 21:45:22.060500"
received "2. 21:45:22.060500 first group call chat start: 21:45:24.529500"
received "3. 21:45:22.060500 second group send lrf start_time: 21:45:22.348000 lrf end_time: 21:45:24.349500 chat start: 21:45:24.537000"
The timestamps in bold are the timestamp when the receive method was started.
The "chat start" timestamp is when chat_message method is called.
class MyConsumer(WebsocketConsumer):
groups = ["my_group"]
def connect(self):
print("connected")
self.accept()
def receive(self, text_data=None, bytes_data=None):
ts = datetime.now().strftime("%H:%M:%S.%f")
self.send(text_data=json.dumps("1. Self send {}".format(ts)))
text_data = "2. {} first group call".format(ts)
async_to_sync(self.channel_layer.group_send)(
"my_group",
{
"type": "chat.message",
"text": text_data,
},
)
text_data = self.long_running_function(ts)
async_to_sync(self.channel_layer.group_send)(
"my_group",
{
"type": "chat.message",
"text": text_data,
},
)
def disconnect(self, close_code):
print("disconnected")
async_to_sync(self.channel_layer.group_discard)("my_group", self.channel_name)
def chat_message(self, event):
chat_msg_start_time = datetime.now().strftime("%H:%M:%S.%f")
text_data = "{} chat start: {}".format(event["text"], chat_msg_start_time)
self.send(text_data=json.dumps(text_data))
def long_running_function(self, ts):
start_time = datetime.now().strftime("%H:%M:%S.%f")
time.sleep(2)
end_time = datetime.now().strftime("%H:%M:%S.%f")
msg = "3. {} second group send lrf start_time: {} lrf end_time: {}".format(ts, start_time, end_time)
return msg
Related
This is the code I'm using to turn the pypresence on:
from pypresence import Presence
import time
start = int(time.time())
client_id = "XXXXXXXXXXXXXXXX"
RPC = Presence(client_id, pipe=0, loop=None, handler=None)
RPC.connect()
while True:
RPC.update(
details = "▬▬▬▬▬▬▬▬▬▬",
state = "All Systems Operational.",
large_image = "xxx_logo_rpc_v2",
large_text = "exe is running!",
small_image = "xx_green_circle_rpc_v2",
small_text = "Online",
start = start,
buttons = [{"label": "🌐XX 🌐", "url": "https://xx/"}, {"label": "xxxx", "url": "https://xxxx8"}]
)
time.sleep(60)
This is the code I'm trying to use to turn it off:
from pypresence import Presence
import os
client_id = "XXXXXXXXXXXXXX"
RPC = Presence(client_id, pipe=0, loop=None, handler=None)
RPC.clear(pid=os.getpid())
RPC.close()
The problem is that it shows me this error:
File "C:\Users\Foxii\AppData\Local\Programs\Python\Python310\lib\site- packages\pypresence\baseclient.py",
line 96, in send_data assert self.sock_writer is not None, "You must connect your client before sending events!" AssertionError: You must connect your client before sending events!
Well, the client seems to be connected when I run it with my custom Tkinter GUI but when I try to run the code to close it, it shows me that error.
This is how the application looks like: [1]: https://i.stack.imgur.com/HUaix.png
[2]: https://i.stack.imgur.com/CYWdS.png
The second [2] image shows how the application should look after pressing the "Connect" button. However, after pressing the button it looks the same (image [1]), although the Discord rich presence starts running.
def rpcstart():
os.system('start-rpc-script.py')
global rpc_on
if rpc_on:
connectbutton.config(image=rpcconnectedimage, state='disabled')
disconnectbutton.config(image=rpcdisconnectimage, state='normal')
else:
connectbutton.config(image=rpcconnectimage)
menubar.entryconfig(1,label="Status: Connected")
This is the part of my script that starts the Discord rich presence. There are no errors or anything similar. It just makes the application unresponsive. Is there any fix for that? Here's the pypresence script that I use:
from pypresence import Presence
import time
start = int(time.time())
client_id = "XXXXXX"
RPC = Presence(client_id)
RPC.connect()
while True:
RPC.update(
details = "▬▬▬▬▬▬▬▬▬▬",
state = "DEMO.",
large_image = "logo_rpc_v2",
large_text = "demo.exe is running!",
small_image = "green_circle_rpc_v2",
small_text = "Online",
start = start,
buttons = [{"label": "example.net ", "url": "https://example.net/"},
{"label": "example.net", "url": "https://example.net"}]
)
time.sleep(60)
Try running your RPC code in a separate thread
from threading import Thread
def _rpc_connect(client_id): # this will be your 'target' function, run in its own thread
start = int(time.time())
RPC = Presence(client_id)
RPC.connect()
while True:
RPC.update(
details = "▬▬▬▬▬▬▬▬▬▬",
state = "DEMO.",
large_image = "logo_rpc_v2",
large_text = "demo.exe is running!",
small_image = "green_circle_rpc_v2",
small_text = "Online",
start = start,
buttons = [
{"label": "example.net ", "url": "https://example.net/"},
{"label": "example.net", "url": "https://example.net"}
],
)
time.sleep(60)
def run_rpc(client_id)
t_rpc = Threading(
target=_rpc_connect,
args=(client_id,),
daemon=True,
)
t_rpc.start() # start the thread
To run the RPC thread, call the run_rpc function as usual
run_rpc(client_id='XXXXXX')
My Discord bot allows users to play a song starting from a timestamp.
The problem is that playback is delayed and audio plays faster and is jumbled if start times >= 30s are set.
Results from testing different start times. Same URL, 30 second duration:
Entered Start Time (s)
Playback Delay (s)
Song Playback Time (s)
0
3
30
30
10
22
60
17
17
120
31
2
150
120
<1
I am setting the start time using ffmpeg_options as suggested in this question.
Does anyone understand why the audio playback is being delayed/jumbled? How can I improve playback delay and allow users to start in the middle of a multi-chapter YouTube video?
Code:
import discord
import youtube_dl
import asyncio
# Suppress noise about console usage from errors
youtube_dl.utils.bug_reports_message = lambda: ""
ytdl_format_options = {
"format": "bestaudio/best",
"outtmpl": "%(extractor)s-%(id)s-%(title)s.%(ext)s",
"restrictfilenames": True,
"noplaylist": False,
"yesplaylist": True,
"nocheckcertificate": True,
"ignoreerrors": False,
"logtostderr": False,
"quiet": True,
"no_warnings": True,
"default_search": "auto",
"source_address": "0.0.0.0", # Bind to ipv4 since ipv6 addresses cause issues at certain times
}
ytdl = youtube_dl.YoutubeDL(ytdl_format_options)
class YTDLSource(discord.PCMVolumeTransformer):
def __init__(self, source: discord.AudioSource, *, data: dict, volume: float = 0.5):
super().__init__(source, volume)
self.data = data
self.title = data.get("title")
self.url = data.get("url")
#classmethod
async def from_url(cls, url, *, loop=None, stream=False, timestamp = 0):
ffmpeg_options = {
"options": f"-vn -ss {timestamp}"}
loop = loop or asyncio.get_event_loop()
data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
if "entries" in data:
# Takes the first item from a playlist
data = data["entries"][0]
filename = data["url"] if stream else ytdl.prepare_filename(data)
return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)
intents = discord.Intents.default()
bot = discord.Bot(intents=intents)
#bot.slash_command()
async def play(ctx, audio: discord.Option(), seconds: discord.Option(), timestamp: discord.Option()):
channel = ctx.author.voice.channel
voice = await channel.connect()
player = await YTDLSource.from_url(audio, loop=bot.loop, stream=True, timestamp=int(timestamp))
voice.play(player)
await asyncio.sleep(int(seconds))
await voice.disconnect()
token = token value
bot.run(token)
"my question is how to stop websocket running when the bid_sirt has changed, I want to make another calculation and then come back restart websocket"
bid_sirts = [1,1]
def ws_message(ws, message):
global bid_sirt
bid_sirt= json.loads(message)["result"]["bids"][0]
bid_sirts.append(bid_sirt)
def ws_open(ws):
ws.send(json.dumps({
"time": int(time.time()),
"channel": "spot.order_book",
"event": "subscribe", # "unsubscribe" for unsubscription
"payload": ["BTC_USDT", "10", "100ms"]
}))
def ws_close(ws):
print("ws closed")
def ws_orderbook():
ws = websocket.WebSocketApp("wss://api.gateio.ws/ws/v4/", on_open=ws_open, on_message=ws_message,on_close=ws_close)
ws.keep_running = False
if bid_sirts[-2] != bid_sirts[-1]:
ws.keep_running = True
print("ws closed")`enter code here`
I am new at python so sry if question is stupid but i hope u guys will help me.
Bot not updating status every 5 sec(i put more time like 5 min and it didn't work too). It shows number of servers and not changing to second status.
from discord.ext import commands, tasks
from itertools import cycle
#tasks.loop( seconds = 12 )
async def changeStatus():
status = cycle( [f' on { len(client.guilds) } servers', '~help'] )
await client.change_presence( activity = discord.Activity( type = discord.ActivityType.playing, name = next(status) ) )
#client.event
async def on_ready():
print( 'bot connected' )
changeStatus.start()
Since
status = cycle( [f' on { len(client.guilds) } servers', '~help'] )
is called everytime you call your function, it will be reinterpreted, which means that the next() function always return the first element. To fix this, you will need a different approach. For example, create a global iteration-variable and declare you cycle-list as only a list.
iterationPosition = 0
#tasks.loop( seconds = 12 )
async def changeStatus():
status = [f' on { len(client.guilds) } servers', '~help']
await client.change_presence(activity=discord.Activity(type=discord.ActivityType.playing, name=status[iterationPosition]))
iterationPosition = 0 if (iterationPosition == len(status) - 1) else (iterationPosition + 1)
You will need to keep track if you've reached the end of your list. This is done by the last line of code.
It is not changing the status as you got a Presence Update rate-limit of 5/60secs per-session. These rate-limits are dynamic so don't get too close to them
status = cycle( [f' on { len(client.guilds) } servers', '~help'] ) This line should be outside of the changeStatus function as whenever it is called, it makes [f' on { len(client.guilds) } servers the first item.