-
Notifications
You must be signed in to change notification settings - Fork 2
/
discord_AI.py
201 lines (160 loc) · 6.48 KB
/
discord_AI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import asyncio
import discord
from discord.ext import commands
from discord import FFmpegOpusAudio
#You should replace these with your llm and tts of choice
#llm_dialo for fast but awful conversation
#llm_guan_3b for slow but better conversation
from modules import llm_guan_3b as llm, tts_windows as tts
from os import environ
from sys import exit
TOKEN = environ.get("DISCORD_TOKEN", None)
if TOKEN is None:
print("Need to set DISCORD_TOKEN in environmental variables. Exiting program.")
exit()
DEEPGRAM_API_KEY = environ.get("DEEPGRAM_API_KEY", None)
from sinks.deepgram_sink import DeepgramSink as Sink #Connects to deepgram, requires API key
sink_settings = Sink.SinkSettings(DEEPGRAM_API_KEY, 300, 1000, 25000, 2)
#from sinks.whisper_sink import WhisperSink as Sink #User whisper to transcribe audio and outputs to TTS
#sink_settings = Sink.SinkSettings(50000, 1.2, 1.8, 0.75, 30, 3, -1)
#This is who you allow to use commands with the bot, either by role, user or both.
#can be a list, both being empty means anyone can command the bot. Roles should be lowercase, USERS requires user IDs
COMMAND_ROLES = []
COMMANDS_USERS = []
#Enter the channel IDs for which channels you want the bot to reply to users. Keep empty to allow all channels.
REPLY_CHANNELS = []
loop = asyncio.get_event_loop()
intents = discord.Intents.all()
client = commands.Bot(command_prefix="!", intents=intents, loop=loop)
ai = llm.LLM()
speech = tts.TTS()
voice_channel = None
class DiscordUser:
def __init__(self):
self.user_id = None
self.username = None
async def add_user(self, user_id):
self.user_id = user_id
self.username = await get_username(user_id)
return self.username
#In a seperate async thread, recieves messages from STT
async def whisper_message(queue : asyncio.Queue):
#store user names based on their id
discord_users = []
while True:
response = await queue.get()
if response is None:
break
else:
user_id = response["user"]
text = response["result"]
#Check if user name already exists to reduce time calling get_username
user_exists= False
username = None
for discord_user in discord_users:
if discord_user.user_id == user_id:
username = discord_user.username
user_exists = True
break
if not user_exists:
discord_users.append(DiscordUser())
username = await discord_users[-1].add_user(user_id)
print(f"Detected Message: {text}")
if username is not None:
answer = await loop.run_in_executor(None, ai.chat, username, text)
await play_audio(answer)
else:
print(f"Error: Username is null")
@client.command()
async def quit(ctx):
client.close()
# join vc
@client.command()
async def join(ctx):
global voice_channel
if ctx.author.voice:
channel = ctx.message.author.voice.channel
try:
await channel.connect()
except Exception as e:
print(e)
voice_channel = ctx.guild.voice_client
#Replace Sink for either StreamSink or WhisperSink
queue = asyncio.Queue()
loop.create_task(whisper_message(queue))
whisper_sink = Sink(sink_settings=sink_settings, queue=queue, loop=loop)
voice_channel.start_recording(whisper_sink, callback, ctx)
await ctx.send("Joining.")
else:
await ctx.send("You are not in a VC channel.")
#When client stops recording, this is called
#Replace Sink for either StreamSink or WhisperSink
async def callback(sink: Sink, ctx):
sink.close()
# leave vc
@client.command()
async def leave(ctx):
global voice_channel
if ctx.voice_client:
await ctx.voice_client.disconnect()
voice_channel = None
else:
await ctx.send("Not in VC.")
@client.event
async def on_ready():
for guild in client.guilds:
print(
f'{client.user} is connected to the following guild:\n'
f'{guild.name}(id: {guild.id})'
)
print(f"We have logged in as {client.user}")
@client.event
async def on_message(message : discord.Message):
#Ignore your own message
if message.author == client.user:
return
#To ignore DMs
if hasattr(message.channel, 'DMChannel'):
print("Ignore DMS")
return
if len(message.content) > 0:
#! is a command message
if message.content[0] == "!":
if COMMAND_ROLES == [] and COMMANDS_USERS == []:
await client.process_commands(message)
elif message.author.id in COMMANDS_USERS:
await client.process_commands(message)
elif any(role.name in COMMAND_ROLES for role in message.author.roles):
await client.process_commands(message)
return
#If user @s or replies to your bot
if (client.user in message.mentions or client.user in message.role_mentions):
if REPLY_CHANNELS != [] and not any(message.channel.id == channel for channel in REPLY_CHANNELS):
return
text = message.content.replace(client.user.mention, '').strip()
if message.author.nick is not None:
username = message.author.nick.replace(".", " ")
elif message.author.display_name is not None:
username = message.author.display_name.replace(".", " ")
else:
username = message.author.name.replace(".", " ")
response = await loop.run_in_executor(None, ai.chat, username, text)
await message.reply(response, mention_author=False)
#Plays an audio file through discord. So far only audio files work, not streaming.
#TODO make voice_channel.play async. Probably need to use the callback feature.
async def play_audio(text):
global voice_channel
if voice_channel is not None:
audio_file = await loop.run_in_executor(None, speech.tts_wav, text)
if audio_file is not None:
while voice_channel.is_playing():
await asyncio.sleep(.1)
prepared_audio = FFmpegOpusAudio(audio_file, executable="ffmpeg")
voice_channel.play(prepared_audio)
#Stops the bot if they are speaking
@client.command()
async def stop(ctx):
ctx.guild.voice_client.stop()
async def get_username(user_id):
return await client.fetch_user(user_id)
client.run(TOKEN)