-
-
Save Apfelin/c9cbb7988a9d8e55d77b06473b72dd57 to your computer and use it in GitHub Desktop.
| import discord | |
| import asyncio | |
| import speech_recognition as sr | |
| from threading import Thread | |
| # bot token and wit.ai api key | |
| TOKEN = "" | |
| WIT_AI_KEY = "" | |
| # we need a sink for the listen function, so we just define our own | |
| # extremely simple: just appends data to a byte array buffer | |
| class BufSink(discord.reader.AudioSink): | |
| def __init__(self): | |
| # byte array to store stuff | |
| self.bytearr_buf = bytearray() | |
| # sample width, which is (bit_rate/8) * channels | |
| self.sample_width = 2 | |
| # 48000Hz sampling rate | |
| # doubled, because speech_recognition needs mono and we've got stereo | |
| self.sample_rate = 96000 | |
| # calculated bytes per second, sample_rate * sample_width | |
| # we need this to know what slices we can take from the buffer | |
| # would be 96000, but mono | |
| self.bytes_ps = 192000 | |
| # just append data to the byte array | |
| def write(self, data): | |
| self.bytearr_buf += data.data | |
| # to prevent the buffer from getting immense, we just cut the part we've | |
| # just read from it, using the index calculated when we extracted the part | |
| def freshen(self, idx): | |
| self.bytearr_buf = self.bytearr_buf[idx:] | |
| # global var - needed to stop the thread | |
| close_flag = False | |
| # client bot class | |
| class Deffy(discord.Client): | |
| # init variables | |
| def __init__(self): | |
| super().__init__() | |
| # save the channel we need to post to | |
| self.target_channel = None | |
| # the thread object | |
| self.post_thread = None | |
| # buffer to hold info | |
| self.buffer = BufSink() | |
| # post some sanity messages on start-up | |
| async def on_ready(self): | |
| print() | |
| print("Logged in as") | |
| print(self.user.name) | |
| print(self.user.id) | |
| print("----------") | |
| print("Discord.py version") | |
| print(discord.__version__) | |
| print("----------") | |
| print() | |
| # wait for a message to interact with the user | |
| async def on_message(self, message): | |
| # notify the thread we're closing | |
| global close_flag | |
| # don't respond to ourselves | |
| if message.author == self.user: | |
| return False | |
| # handle closing | |
| if message.content.lower().startswith("$close"): | |
| # send a message to ack the command | |
| await message.channel.send("Got it, shutting down...") | |
| # the polite thing to do is close any active voice connections properly | |
| if self.voice_clients: | |
| for vc in self.voice_clients: | |
| await vc.disconnect() | |
| # set the flag and wait for the thread to end | |
| close_flag = True | |
| self.post_thread.join() | |
| # shut down the bot, then quit the program | |
| await self.close() | |
| quit() | |
| # handle disconnecting | |
| if message.content.lower().startswith("$leave"): | |
| # close any active voice connections. in theory, there's only one, but | |
| # could be extended for more | |
| if self.voice_clients: | |
| for vc in self.voice_clients: | |
| await vc.disconnect() | |
| # set the flag and wait for the thread to end | |
| close_flag = True | |
| self.post_thread.join() | |
| else: | |
| await message.channel.send("Sorry, you're not in a voice channel.") | |
| # handle summoning | |
| if message.content.lower().startswith("$here"): | |
| # if the user is not connect to a voice channel, but tries to summon, | |
| # just send a message and exit | |
| if message.author.voice is None: | |
| await message.channel.send("Sorry, you're not in a voice channel.") | |
| else: | |
| # check if we already have an active voice connection, and use that | |
| # one instead of creating another one | |
| if self.voice_clients: | |
| # store the channel we need to post our output to | |
| self.target_channel = message.channel | |
| # ack the command and inform the user | |
| await message.channel.send("Got it, moving to voice channel " + | |
| message.author.voice.channel.name + " and directing output to " + | |
| self.target_channel.name + ".") | |
| # use the existing voice connection to move to the new voice channel | |
| await self.voice_clients[0].move_to(message.author.voice.channel) | |
| # start a thread that will handle voice analysis | |
| # if it doesn't exist already | |
| if self.post_thread is None: | |
| self.post_thread = Thread(target=poster, | |
| args=(self, self.buffer, self.target_channel)) | |
| self.post_thread.start() | |
| # start listening - user filter just listens to a certain user | |
| self.voice_clients[0].listen(discord.reader.UserFilter( | |
| self.buffer, message.author)) | |
| else: | |
| # if we don't have an already active connection, create a new one | |
| self.target_channel = message.channel | |
| await message.channel.send("Got it, moving to voice channel " + | |
| message.author.voice.channel.name + " and directing output to " + | |
| self.target_channel.name + ".") | |
| # create a new voice client | |
| await message.author.voice.channel.connect() | |
| # start a thread that will handle voice analysis, | |
| # if it doesn't exist already | |
| if self.post_thread is None: | |
| self.post_thread = Thread(target=poster, | |
| args=(self, self.buffer, self.target_channel)) | |
| self.post_thread.start() | |
| # start listening - user filter just listens to a certain user | |
| self.voice_clients[0].listen(discord.reader.UserFilter( | |
| self.buffer, message.author)) | |
| # thread that handles message posting and voice analysis | |
| def poster(bot, buffer, target_channel): | |
| global close_flag | |
| # instantiate our recognizer object | |
| recog = sr.Recognizer() | |
| # we don't want the thread to end, so just loop forever | |
| while True: | |
| # useless to try anything if we don't have anything in the buffer | |
| # wait until we have enough data for a 5-second voice clip in the buffer | |
| if len(buffer.bytearr_buf) > 960000: | |
| # get 5 seconds worth of data from the buffer | |
| idx = buffer.bytes_ps * 5 | |
| slice = buffer.bytearr_buf[:idx] | |
| # if the slice isn't all 0s, create an AudioData instance with it, | |
| # needed by the speech_recognition lib | |
| if any(slice): | |
| # trim leading zeroes, should be more accurate | |
| idx_strip = slice.index(next(filter(lambda x: x!=0, slice))) | |
| if idx_strip: | |
| buffer.freshen(idx_strip) | |
| slice = buffer.bytearr_buf[:idx] | |
| # create the AudioData object | |
| audio = sr.AudioData(bytes(slice), buffer.sample_rate, | |
| buffer.sample_width) | |
| # send the data to get recognized | |
| try: | |
| msg = recog.recognize_wit(audio, key=WIT_AI_KEY) | |
| except sr.UnknownValueError: | |
| print("ERROR: Couldn't understand.") | |
| except sr.RequestError as e: | |
| print("ERROR: Could not request results from Wit.ai service; {0}".format(e)) | |
| # if we send a msg with all 0s or something unintelligible, | |
| # we'll get a message, but it'll be empty | |
| if msg: | |
| # send the message to the async routine | |
| asyncio.run_coroutine_threadsafe(target_channel.send(msg), bot.loop) | |
| # cut the part we just read from the buffer | |
| buffer.freshen(idx) | |
| # since it's an infinite loop, we need some way to break out, once the | |
| # program shuts down | |
| if close_flag: | |
| break | |
| client = Deffy() | |
| client.run(TOKEN) |
AttributeError: module 'discord' has no attribute 'reader'
i get this error. do you know how to fix it ?
AttributeError: module 'discord' has no attribute 'reader'
i get this error. do you know how to fix it ?
To get voice support, you should use discord.py[voice] instead of discord.py.
installing discord.py
Same issue here that iHazzu lists. Using anaconda in an anaconda created environment. Using python 3.10. Installed in the activated environment with pip install -U discord.py[voice].
Same here using a virtual environment using discord.py[voice]
I guess the used library of Discord.py is outdated because for example there doesn’t exist a class discord.reader.AudioSink anymore. You can read it in the Discord.py docs: https://discordpy.readthedocs.io/en/stable/api.html
how to create a simple join to a voice channel?
is it possible to use vosk now as it can generate a fsater response. google works like a grandma