-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
276 lines (223 loc) · 8.81 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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# -*- coding: utf-8 -*-
# search - a tiny little search utility bot for discord.
# All original work by taciturasa, with some code by ry00001.
# Used and modified with permission.
# See LICENSE for license information.
'''Main File'''
import discord
from discord.ext import commands
import traceback
import json
import os
import sys
import asyncio
import aiohttp
import rethinkdb
from typing import List, Optional
class Bot(commands.Bot):
"""Custom Bot Class that subclasses the commands.ext one"""
def __init__(self, **options):
"""Initializes the main parts of the bot."""
# Initializes parent class
super().__init__(self._get_prefix_new, **options)
# Setup
self.extensions_list: List[str] = []
with open('config.json') as f:
self.config = json.load(f)
# Info
self.prefix: List[str] = self.config['PREFIX']
self.version: str = self.config['VERSION']
self.description: str = self.config['DESCRIPTION']
self.repo: str = self.config['REPO']
self.support_server: str = self.config['SERVER']
self.perms: int = self.config['PERMS']
# Toggles
self.maintenance: bool = self.config['MAINTENANCE']
self.case_insensitive: bool = self.config['CASE_INSENSITIVE']
self.custom_help: bool = self.config['CUSTOM_HELP']
self.mention_assist: bool = self.config['MENTION_ASSIST']
self.prefixless_dms: bool = self.config['PREFIXLESS_DMS']
# RethinkDB
if self.config['RETHINK']['DB']:
self.re = rethinkdb.RethinkDB()
self.re.set_loop_type('asyncio')
self.rdb: str = self.config['RETHINK']['DB']
self.conn = None
self.rtables: List[str] = []
def _init_extensions(self):
"""Initializes extensions."""
# Utils
# Avoids race conditions with online
utils_dir = os.listdir('extensions/utils')
if 'online.py' in utils_dir:
utils_dir.remove('online.py')
bot.load_extension('extensions.utils.online')
# Rest of utils
for ext in utils_dir:
if ext.endswith('.py'):
try:
bot.load_extension(f'extensions.utils.{ext[:-3]}')
self.extensions_list.append(
f'extensions.utils.{ext[:-3]}')
except Exception as e:
print(e)
# Models
for ext in os.listdir('extensions/models'):
if ext.endswith('.py'):
try:
bot.load_extension(f'extensions.models.{ext[:-3]}')
self.extensions_list.append(
f'extensions.models.{ext[:-3]}')
except Exception as e:
print(e)
# Extensions
for ext in os.listdir('extensions'):
if ext.endswith('.py'):
try:
bot.load_extension(f'extensions.{ext[:-3]}')
self.extensions_list.append(
f'extensions.{ext[:-3]}')
except Exception as e:
print(e)
async def _init_rethinkdb(self):
"""Initializes RethinkDB."""
# Prerequisites
dbc = self.config['RETHINK']
# Error handling the initialization
try:
# Create connection
self.conn = await self.re.connect(
host=dbc['HOST'],
port=dbc['PORT'],
db=dbc['DB'],
user=dbc['USERNAME'],
password=dbc['PASSWORD']
)
# Create or get database
dbs = await self.re.db_list().run(self.conn)
if self.rdb not in dbs:
print('Database not present. Creating...')
await self.re.db_create(self.rdb).run(self.conn)
# Append any existing tables to rtables
tables = await self.re.db(self.rdb).table_list().run(self.conn)
self.rtables.extend(tables)
# Exit if fails bc bot can't run without db
except Exception as e:
print('RethinkDB init error!\n{}: {}'.format(type(e).__name__, e))
sys.exit(1)
print('RethinkDB initialisation successful.')
async def _get_prefix_new(self, bot, msg):
"""More flexible check for prefix."""
# Adds empty prefix if in DMs
if isinstance(msg.channel, discord.DMChannel) and self.prefixless_dms:
plus_empty = self.prefix.copy()
plus_empty.append('')
return commands.when_mentioned_or(*plus_empty)(bot, msg)
# Keeps regular if not
else:
return commands.when_mentioned_or(*self.prefix)(bot, msg)
async def on_ready(self):
"""Initializes the main portion of the bot once it has connected."""
print('Connected.\n')
# Prerequisites
if not hasattr(self, 'request'):
self.request = aiohttp.ClientSession()
if not hasattr(self, 'appinfo'):
self.appinfo = await self.application_info()
if self.description == '':
self.description = self.appinfo.description
# Maintenance Mode
if self.maintenance:
await self.change_presence(
activity=discord.Activity(
name="Maintenance",
type=discord.ActivityType.watching
),
status=discord.Status.dnd
)
else:
await self.change_presence(
activity=discord.Activity(
name=f"@{self.user.name}",
type=discord.ActivityType.listening
),
status=discord.Status.online
)
# NOTE Rethink Entry Point
# Initializes all rethink stuff
if hasattr(self.rdb, self) and not self.rtables:
await self._init_rethinkdb()
# NOTE Extension Entry Point
# Loads core, which loads all other extensions
if not self.extensions_list:
self._init_extensions()
print('Initialized.\n')
# Logging
msg = "ALL ENGINES GO!\n"
msg += "-----------------------------\n"
msg += f"ACCOUNT: {bot.user}\n"
msg += f"OWNER: {self.appinfo.owner}\n"
msg += "-----------------------------\n"
print(msg)
await self.logging.info(content=msg, name="On Ready")
async def on_message(self, message):
"""Handles what the bot does whenever a message comes across."""
# Prerequisites
mentions = [self.user.mention, f'<@!{self.user.id}>']
ctx = await self.get_context(message)
# Avoid warnings while loading
if not hasattr(bot, 'appinfo'):
return
# Handling
# Turn away bots
elif message.author.bot:
return
# Ignore blocked users
elif message.author.id in self.config.get('BLOCKED'):
return
# Maintenance mode
elif self.maintenance and not message.author.id == bot.appinfo.owner.id:
return
# Empty ping for assistance
elif message.content in mentions and self.mention_assist:
assist_msg = (
"**Hi there! How can I help?**\n\n"
# Two New Lines Here
f"You may use **{self.user.mention} `term here`** to search, "
f"or **{self.user.mention} `help`** for assistance.")
await ctx.send(assist_msg)
# Move on to command handling
else:
await self.process_commands(message)
# Creates Bot object
bot = Bot()
@bot.listen()
async def on_command_error(ctx, error):
"""Handles all errors stemming from ext.commands."""
# Lets other cogs handle CommandNotFound.
# Change this if you want command not found handling
if isinstance(error, commands.CommandNotFound)or isinstance(error, commands.CheckFailure):
return
# Provides a very pretty embed if something's actually a dev's fault.
elif isinstance(error, commands.CommandInvokeError):
# Prerequisites
embed_fallback = f"**An error occured: {type(error).__name__}. Please contact {bot.appinfo.owner}.**"
error_embed = await bot.logging.error(
error, ctx,
ctx.command.cog.qualified_name if ctx.command.cog.qualified_name
else "DMs"
)
# Sending
await ctx.send(embed_fallback, embed=error_embed)
# If anything else goes wrong, just go ahead and send it in chat.
else:
await bot.logging.error(
error, ctx,
ctx.command.cog.qualified_name if ctx.command.cog.qualified_name
else "DMs"
)
await ctx.send(error)
# NOTE Bot Entry Point
# Starts the bot
print("Connecting...\n")
bot.run(bot.config['TOKEN'])