-
Notifications
You must be signed in to change notification settings - Fork 4
/
botScript.py
208 lines (163 loc) · 7.85 KB
/
botScript.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
202
203
204
205
206
207
208
# ----- Import Modules ----- #
import os
import subprocess
from configparser import ConfigParser
import random
import discord
# ----- Load config file ----- #
# Initialize ConfigParser
config = ConfigParser()
# Generate the absolute path of the config file (the directory the script is in + the name of the config file)
config_file_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'botconf', 'config.ini')
secrets_file_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'secrets.ini')
# Load the configuration settings from the config files.
print(f'Read config file(s): {config.read([config_file_path, secrets_file_path])}')
# ----- Load custom dependencies ----- #
if 'dependencies' in config and len(config['dependencies']) > 0:
print('Attempting to load dependencies...')
for mod, ver in config['dependencies'].items():
print(f'Installing {mod} {ver}')
# If a version is not given in `ver` then do not include in `pip` command (install the latest version)
if ver == '':
command = ['pip', 'install', f'{mod}']
else:
command = ['pip', 'install', f'{mod}=={ver}']
try:
# Run pip install
subprocess.run(command, check=True)
except subprocess.CalledProcessError as e:
print(e)
print(f'Could not install {mod, ver}. Check error logs above')
exit()
print(f'Installed {mod} {ver}')
print(f'Done loading dependencies...')
# After installing all the dependencies it can load the custom library
try:
import botconf.library as library
except ModuleNotFoundError as e:
print(f'Module not found: {e.msg}')
quit()
# ----- Load and check variables ----- #
# Load variables
TOKEN = config['settings']['token']
GUILD_ID = config['settings']['guild_id']
COMMAND_CHAR = config['settings'].get('command_prefix', '!')
# `COMMANDS` is the list of commands defined in the config file.
# If the section starts with `command.`, then the command is the second part of that section's name
COMMANDS = [section[8:] for section in config if section.startswith('command.')]
print(f'COMMANDS: {COMMANDS}')
GREETINGS = config['settings'].get('greetings', 'hello|hi').split('|')
# If any of these variables aren't defined, print an error and quit
if TOKEN == None or TOKEN == '':
print('Error: No `token` defined in config.ini')
quit()
if GUILD_ID == None or GUILD_ID == '':
print('Error: No `guild_id` defined in config.ini')
quit()
try:
GUILD_ID = int(GUILD_ID)
except ValueError:
print('Error: The `guild_id` specified in config.ini is not a valid integer.')
quit()
if COMMAND_CHAR == None or COMMAND_CHAR == '':
print('Error: No `command_prefix` defined in config.ini')
quit()
# Set the intents of the bot, to be given to the `Client` constructor
intents = discord.Intents.default()
intents.message_content = True # Privileged intent
intents.members = True # Privileged intent
client = discord.Client(intents=intents)
# ----- Client event functions ----- #
# Runs when the bot first connects to the server (successfully)
@client.event
async def on_ready():
guild = client.get_guild(GUILD_ID)
print(
f'{client.user} has connected to the guild:\n'
f'Name: {guild.name}, ID: {guild.id}'
)
members = '\n - '.join([member.name for member in guild.members])
print(f'Guild members:\n - {members}')
# Runs whenever a member joins
@client.event
async def on_member_join(member: discord.Member):
# Print to terminal
print(f'{member.name} joined the server.')
# Creates a DM channel with the member that just joined and sends them a friendly message
await member.create_dm()
await member.dm_channel.send(
f'Hello {member.name}. Welcome to the server'
)
# Runs every time it detects a message
@client.event
async def on_message(message: discord.Message):
# Print message to terminal
print(f'{message.author.name}: {message.content}')
# If the message is a command
if is_command(message.content.split()[0]) and message.author != client.user:
# If the message was in the correct guild
if message.guild != None:
if message.guild.id == GUILD_ID:
# `command` is the first word, `params` is the rest of the words as a single string.
command = message.content.split()[0]
params = ' '.join(message.content.split()[1:])
# Get the proper response
response = await handle_command(command, params, message, client, config)
# If the command generated a response
if response != None:
# Respond in the channel of the original message
await client.get_channel(message.channel.id).send(response)
# Log command and parameters
print(f'Command: {command}\nParameters: {params}')
# If the message mentions the bot and the first word in the message is a "greeting", bot will say hello
elif client.user in message.mentions and message.content.lower().startswith(tuple(GREETINGS)):
greeting_response: str = random.choice(GREETINGS)
await client.get_channel(message.channel.id).send(f'{greeting_response[0].upper() + greeting_response[1:]} {message.author.display_name}!')
# ----- Generic functions ----- #
# Checks if a word is a command
def is_command(word: str):
# If it doesn't start with the `COMMAND_CHAR` then it isn't a command
if not word.startswith(COMMAND_CHAR):
return False
# If the word (excluding the command prefix) is in `COMMANDS` then it's a command, otherwise, it isn't
if word[1:] in COMMANDS:
return True
else:
return False
# Take a command and its parameters and return the corresponding response
async def handle_command(command: str, params: str, message: discord.Message, client: discord.Client, config: ConfigParser):
command_section = f'command.{command[1:]}'
command_type = config[command_section]['type']
if command_type == 'static':
# If the command is static
# Return the content of the given command
return config[command_section]['content']
elif command_type == 'dynamic':
# If the command is dynamic
# Get the function in `library` that is associated with the command (defined in the config file)
func = getattr(library, config[command_section]['function'], None)
if func != None:
# If the command was successfully retrieved, run it and return its value
return func(params, message, client, config)
else:
# If the command was not present in the `library` module, return None and print an error.
print(f'The function in the `config.ini` file that is associated with the `{command}` command is not present in `library.py`')
return None
elif command_type == 'dynamic_async':
# If the command is dynamic and asynchronous
# Get the function in `library` that is associated with the command (defined in the config file)
func = getattr(library, config[command_section]['function'], None)
if func != None:
# If the command was successfully retrieved, run it and return its value
return await func(params, message, client, config)
else:
# If the command was not present in the `library` module, return None and print an error.
print(f'The function in the `config.ini` file that is associated with the `{command}` command is not present in `library.py`')
return None
else:
# If the command doesn't have a valid type, return none and print an error
print(f'The `{command}` command has an invalid `type` specified in the `config.ini` file')
return None
# ----- RUN ----- #
# Run the client. This method blocks so any code after will not run
client.run(TOKEN)