-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.py
196 lines (156 loc) · 7.98 KB
/
main.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
from discord.ext import bridge, commands
from dotenv import load_dotenv
from google import genai
from google.genai import types
from inspect import cleandoc
from os import chdir, mkdir, environ
from pathlib import Path
import aiohttp
import aiofiles.os
import discord
import importlib
import logging
import yaml
# Go to project root directory
chdir(Path(__file__).parent.resolve())
# Load environment variables
load_dotenv("dev.env")
# Logging
logging.basicConfig(format='%(levelname)s %(asctime)s [%(filename)s:%(lineno)d - %(funcName)s()]: %(message)s',
datefmt='%m/%d/%Y %I:%M:%S %p',
level=logging.INFO)
# Check if TOKEN is set
if "TOKEN" in environ and (environ.get("TOKEN") == "INSERT_DISCORD_TOKEN") or (environ.get("TOKEN") is None) or (environ.get("TOKEN") == ""):
raise Exception("Please insert a valid Discord bot token")
# Intents
intents = discord.Intents.default()
intents.message_content = True
intents.members = True
# Subclass this bot
class InitBot(bridge.Bot):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Prepare temporary directory
if environ.get("TEMP_DIR") is not None:
if Path(environ.get("TEMP_DIR")).exists():
for file in Path(environ.get("TEMP_DIR", "temp")).iterdir():
file.unlink()
else:
mkdir(environ.get("TEMP_DIR"))
else:
environ["TEMP_DIR"] = "temp"
mkdir(environ.get("TEMP_DIR"))
# Wavelink
self._wavelink = None
try:
self._wavelink = importlib.import_module("wavelink")
except ModuleNotFoundError as e:
logging.warning("Playback support is disabled: %s", e)
self._wavelink = None
# Gemini API Client
self._gemini_api_client = genai.Client(api_key=environ.get("GEMINI_API_KEY"))
# Everything else (mostly GET requests)
self._aiohttp_main_client_session = aiohttp.ClientSession(loop=self.loop)
# Shutdown the bot
async def close(self):
# Close aiohttp client sessions
await self._aiohttp_main_client_session.close()
# Remove temp files
if Path(environ.get("TEMP_DIR", "temp")).exists():
for file in Path(environ.get("TEMP_DIR", "temp")).iterdir():
await aiofiles.os.remove(file)
await super().close()
bot = InitBot(command_prefix=environ.get("BOT_PREFIX", "$"), intents = intents)
###############################################
# ON READY
###############################################
@bot.event
async def on_ready():
# start wavelink setup if playback support is enabled
if bot._wavelink is not None:
try:
# https://wavelink.dev/en/latest/recipes.html
ENV_LAVALINK_URI = environ.get("ENV_LAVALINK_URI") if environ.get("ENV_LAVALINK_URI") is not None else "http://127.0.0.1:2222"
ENV_LAVALINK_PASS = environ.get("ENV_LAVALINK_PASS") if environ.get("ENV_LAVALINK_PASS") is not None else "youshallnotpass"
ENV_LAVALINK_IDENTIFIER = environ.get("ENV_LAVALINK_IDENTIFIER") if environ.get("ENV_LAVALINK_IDENTIFIER") is not None else "main"
node = bot._wavelink.Node(
identifier=ENV_LAVALINK_IDENTIFIER,
uri=ENV_LAVALINK_URI,
password=ENV_LAVALINK_PASS,
retries=0 # Only connect once to save time
)
await bot._wavelink.Pool.connect(
client=bot,
nodes=[node]
)
except Exception as e:
logging.error("Failed to setup wavelink: %s... Disabling playback support", e)
bot._wavelink = None
#https://stackoverflow.com/a/65780398 - for multiple statuses
await bot.change_presence(activity=discord.Game(f"/ask me anything or {bot.command_prefix}help"))
logging.info("%s is ready and online!", bot.user)
###############################################
# ON USER MESSAGE
###############################################
@bot.event
async def on_message(message):
# https://discord.com/channels/881207955029110855/1146373275669241958
await bot.process_commands(message)
if message.author == bot.user:
return
if bot.user.mentioned_in(message) and message.content == f"<@{bot.user.id}>".strip():
await message.channel.send(cleandoc(f"""Hello <@{message.author.id}>! I am **{bot.user.name}** ✨
I am an AI bot and I can also make your server fun and entertaining! 🎉
You just pinged me, but what can I do for you? 🤔
- You can ask me anything by typing **/ask** and get started or by mentioning me again but with a message
- You can access most of my useful commands with **/**slash commands or use `{bot.command_prefix}help` to see the list prefixed commands I have.
- You can access my apps by **tapping and holding any message** or **clicking the three-dots menu** and click **Apps** to see the list of apps I have
You can ask me questions, such as:
- **@{bot.user.name}** How many R's in the word strawberry?
- **/ask** `prompt:`Can you tell me a joke?
- Hey **@{bot.user.name}** can you give me quotes for today?
If you have any questions, you can visit my [documentation or contact me here](https://zavocc.github.io)"""))
with open('commands.yaml', 'r') as file:
cog_commands = yaml.safe_load(file)
for command in cog_commands:
# Disable voice commands if playback support is not enabled
if "voice" in command and not bot._wavelink:
logging.warning("Skipping %s... Playback support is disabled", command)
continue
try:
bot.load_extension(f'cogs.{command}')
except Exception as e:
logging.error("cogs.%s failed to load, skipping... The following error of the cog: %s", command, e)
continue
# Initialize custom help
class CustomHelp(commands.MinimalHelpCommand):
def __init__(self):
super().__init__()
self.no_category = "Misc"
# Override "get_opening_note" with custom implementation
def get_opening_note(self):
"""Returns help command's opening note. This is mainly useful to override for i18n purposes.
The default implementation returns ::
Use `{prefix}{command_name} [command]` for more info on a command.
You can also use `{prefix}{command_name} [category]` for more info on a category.
Returns
-------
:class:`str`
The help command opening note.
"""
command_name = self.invoked_with
return (
cleandoc(f"""**{bot.user.name}** help
Welcome! here are the prefix commands that you can use!
You can access my slash commands by just typing **/** and find the commands that is associated to me.
Slash commands are self documented, I will be constantly updated to update my slash command documentation
Use `{self.context.clean_prefix}{command_name} [command]` for more info on a command.
You can also use `{self.context.clean_prefix}{command_name} [category]` for more info on a category.""")
)
async def send_pages(self):
destination = self.get_destination()
for page in self.paginator.pages:
embed = discord.Embed(description=page, color=discord.Color.random())
await destination.send(embed=embed)
bot.help_command = CustomHelp()
bot.run(environ.get('TOKEN'))