2020-02-19 19:29:27 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2020-02-19 21:04:07 +00:00
|
|
|
# search - a tiny little search utility bot for discord.
|
2020-02-19 19:29:27 +00:00
|
|
|
# All original work by taciturasa, with some code by ry00001.
|
2020-02-19 21:04:07 +00:00
|
|
|
# Used and modified with permission.
|
|
|
|
# See LICENSE for license information.
|
2020-02-19 19:29:27 +00:00
|
|
|
|
|
|
|
'''Main File'''
|
|
|
|
|
2020-04-08 22:23:14 +00:00
|
|
|
import asyncio
|
2020-02-19 19:29:27 +00:00
|
|
|
import json
|
2020-02-22 21:42:46 +00:00
|
|
|
import os
|
2020-03-19 18:16:22 +00:00
|
|
|
import sys
|
2020-04-06 16:26:15 +00:00
|
|
|
from typing import List
|
|
|
|
|
2020-02-19 19:29:27 +00:00
|
|
|
import aiohttp
|
2020-04-06 16:26:15 +00:00
|
|
|
import discord
|
|
|
|
from discord.ext import commands
|
2020-03-19 18:16:22 +00:00
|
|
|
import rethinkdb
|
2020-04-06 16:26:15 +00:00
|
|
|
|
2020-04-09 02:05:37 +00:00
|
|
|
from extensions.models import searchexceptions
|
2020-03-19 18:16:22 +00:00
|
|
|
|
2020-02-19 19:29:27 +00:00
|
|
|
|
|
|
|
class Bot(commands.Bot):
|
2020-03-01 01:51:03 +00:00
|
|
|
"""Custom Bot Class that subclasses the commands.ext one"""
|
2020-02-19 19:29:27 +00:00
|
|
|
|
|
|
|
def __init__(self, **options):
|
2020-03-01 01:51:03 +00:00
|
|
|
"""Initializes the main parts of the bot."""
|
|
|
|
|
2020-03-02 07:01:28 +00:00
|
|
|
# Initializes parent class
|
|
|
|
super().__init__(self._get_prefix_new, **options)
|
|
|
|
|
2020-03-02 20:47:58 +00:00
|
|
|
# Setup
|
2020-03-19 18:16:22 +00:00
|
|
|
self.extensions_list: List[str] = []
|
2020-03-24 23:09:01 +00:00
|
|
|
self.debug_toggle = False
|
2020-03-19 18:16:22 +00:00
|
|
|
|
2020-02-19 19:29:27 +00:00
|
|
|
with open('config.json') as f:
|
|
|
|
self.config = json.load(f)
|
2020-02-22 21:42:46 +00:00
|
|
|
|
2020-03-19 18:16:22 +00:00
|
|
|
# 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']
|
2020-03-21 03:49:10 +00:00
|
|
|
if not self.config['CACHE']:
|
|
|
|
self.max_messages = None
|
2020-03-19 18:16:22 +00:00
|
|
|
|
|
|
|
# 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] = []
|
2020-02-22 21:42:46 +00:00
|
|
|
|
2020-03-02 20:47:58 +00:00
|
|
|
def _init_extensions(self):
|
|
|
|
"""Initializes extensions."""
|
|
|
|
|
2020-03-02 23:37:34 +00:00
|
|
|
# Utils
|
2020-03-03 04:56:30 +00:00
|
|
|
# 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:
|
2020-03-02 20:47:58 +00:00
|
|
|
if ext.endswith('.py'):
|
|
|
|
try:
|
2020-03-02 23:37:34 +00:00
|
|
|
bot.load_extension(f'extensions.utils.{ext[:-3]}')
|
2020-03-02 20:47:58 +00:00
|
|
|
self.extensions_list.append(
|
2020-03-02 23:37:34 +00:00
|
|
|
f'extensions.utils.{ext[:-3]}')
|
2020-03-02 20:47:58 +00:00
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
2020-03-02 23:37:34 +00:00
|
|
|
|
2020-03-02 21:14:43 +00:00
|
|
|
# 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)
|
|
|
|
|
2020-03-02 23:37:34 +00:00
|
|
|
# Extensions
|
|
|
|
for ext in os.listdir('extensions'):
|
2020-03-02 21:14:43 +00:00
|
|
|
if ext.endswith('.py'):
|
|
|
|
try:
|
2020-03-02 23:37:34 +00:00
|
|
|
bot.load_extension(f'extensions.{ext[:-3]}')
|
2020-03-02 21:14:43 +00:00
|
|
|
self.extensions_list.append(
|
2020-03-02 23:37:34 +00:00
|
|
|
f'extensions.{ext[:-3]}')
|
2020-03-02 21:14:43 +00:00
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
2020-03-19 18:16:22 +00:00
|
|
|
|
|
|
|
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.')
|
2020-03-02 20:47:58 +00:00
|
|
|
|
2020-02-29 04:14:34 +00:00
|
|
|
async def _get_prefix_new(self, bot, msg):
|
2020-03-02 07:01:28 +00:00
|
|
|
"""More flexible check for prefix."""
|
2020-02-29 04:14:34 +00:00
|
|
|
|
2020-03-02 07:01:28 +00:00
|
|
|
# Adds empty prefix if in DMs
|
2020-03-19 18:16:22 +00:00
|
|
|
if isinstance(msg.channel, discord.DMChannel) and self.prefixless_dms:
|
2020-03-02 07:01:28 +00:00
|
|
|
plus_empty = self.prefix.copy()
|
|
|
|
plus_empty.append('')
|
|
|
|
return commands.when_mentioned_or(*plus_empty)(bot, msg)
|
2020-03-19 18:16:22 +00:00
|
|
|
|
2020-03-02 07:01:28 +00:00
|
|
|
# Keeps regular if not
|
2020-02-23 22:47:51 +00:00
|
|
|
else:
|
|
|
|
return commands.when_mentioned_or(*self.prefix)(bot, msg)
|
2020-02-19 19:29:27 +00:00
|
|
|
|
2020-02-22 21:42:46 +00:00
|
|
|
async def on_ready(self):
|
2020-03-01 01:51:03 +00:00
|
|
|
"""Initializes the main portion of the bot once it has connected."""
|
|
|
|
|
2020-03-02 23:37:34 +00:00
|
|
|
print('Connected.\n')
|
|
|
|
|
2020-03-01 01:51:03 +00:00
|
|
|
# Prerequisites
|
2020-03-02 21:22:28 +00:00
|
|
|
if not hasattr(self, 'request'):
|
|
|
|
self.request = aiohttp.ClientSession()
|
|
|
|
if not hasattr(self, 'appinfo'):
|
|
|
|
self.appinfo = await self.application_info()
|
2020-03-01 01:29:41 +00:00
|
|
|
if self.description == '':
|
|
|
|
self.description = self.appinfo.description
|
|
|
|
|
2020-03-03 04:26:43 +00:00
|
|
|
# Maintenance Mode
|
|
|
|
if self.maintenance:
|
2020-04-08 22:23:14 +00:00
|
|
|
async def presence_task():
|
|
|
|
await self.change_presence(
|
|
|
|
activity=discord.Activity(
|
|
|
|
name="Maintenance",
|
|
|
|
type=discord.ActivityType.watching
|
|
|
|
),
|
|
|
|
status=discord.Status.dnd
|
|
|
|
)
|
2020-03-03 04:26:43 +00:00
|
|
|
else:
|
2020-04-08 22:23:14 +00:00
|
|
|
async def presence_task():
|
|
|
|
await self.change_presence(
|
|
|
|
activity=discord.Activity(
|
|
|
|
name=f"@{self.user.name}",
|
|
|
|
type=discord.ActivityType.listening
|
|
|
|
),
|
|
|
|
status=discord.Status.online
|
|
|
|
)
|
|
|
|
|
|
|
|
asyncio.create_task(presence_task())
|
2020-03-03 04:26:43 +00:00
|
|
|
|
2020-03-19 18:16:22 +00:00
|
|
|
# NOTE Rethink Entry Point
|
|
|
|
# Initializes all rethink stuff
|
|
|
|
if hasattr(self, 'rdb') and not self.rtables:
|
|
|
|
await self._init_rethinkdb()
|
|
|
|
|
2020-03-02 07:01:28 +00:00
|
|
|
# NOTE Extension Entry Point
|
|
|
|
# Loads core, which loads all other extensions
|
2020-03-19 18:16:22 +00:00
|
|
|
if not self.extensions_list:
|
2020-03-02 21:22:28 +00:00
|
|
|
self._init_extensions()
|
2020-02-22 21:42:46 +00:00
|
|
|
|
2020-03-02 23:37:34 +00:00
|
|
|
print('Initialized.\n')
|
|
|
|
|
2020-03-01 01:51:03 +00:00
|
|
|
# Logging
|
2020-03-02 23:37:34 +00:00
|
|
|
msg = "ALL ENGINES GO!\n"
|
2020-02-19 19:29:27 +00:00
|
|
|
msg += "-----------------------------\n"
|
|
|
|
msg += f"ACCOUNT: {bot.user}\n"
|
2020-02-27 19:09:44 +00:00
|
|
|
msg += f"OWNER: {self.appinfo.owner}\n"
|
2020-02-19 19:29:27 +00:00
|
|
|
msg += "-----------------------------\n"
|
|
|
|
print(msg)
|
2020-03-03 04:44:36 +00:00
|
|
|
|
2020-04-08 22:24:41 +00:00
|
|
|
# pylint: disable=no-member
|
2020-04-08 22:23:14 +00:00
|
|
|
self.logging.info(content=msg, name="On Ready")
|
2020-02-19 19:29:27 +00:00
|
|
|
|
|
|
|
async def on_message(self, message):
|
2020-03-02 07:01:28 +00:00
|
|
|
"""Handles what the bot does whenever a message comes across."""
|
2020-03-01 01:51:03 +00:00
|
|
|
|
|
|
|
# Prerequisites
|
2020-03-02 04:58:05 +00:00
|
|
|
mentions = [self.user.mention, f'<@!{self.user.id}>']
|
2020-02-22 18:11:48 +00:00
|
|
|
ctx = await self.get_context(message)
|
|
|
|
|
2020-03-03 04:49:21 +00:00
|
|
|
# Avoid warnings while loading
|
|
|
|
if not hasattr(bot, 'appinfo'):
|
|
|
|
return
|
|
|
|
|
2020-03-01 01:51:03 +00:00
|
|
|
# Handling
|
2020-03-02 07:01:28 +00:00
|
|
|
# Turn away bots
|
2020-03-03 04:49:21 +00:00
|
|
|
elif message.author.bot:
|
2020-02-19 19:29:27 +00:00
|
|
|
return
|
2020-03-02 07:01:28 +00:00
|
|
|
|
|
|
|
# Ignore blocked users
|
2020-02-22 18:11:48 +00:00
|
|
|
elif message.author.id in self.config.get('BLOCKED'):
|
2020-02-19 19:29:27 +00:00
|
|
|
return
|
2020-03-02 07:01:28 +00:00
|
|
|
|
|
|
|
# Maintenance mode
|
2020-03-19 22:31:08 +00:00
|
|
|
elif (
|
2020-03-24 23:09:01 +00:00
|
|
|
self.maintenance
|
2020-03-19 22:31:08 +00:00
|
|
|
and not message.author.id == bot.appinfo.owner.id
|
|
|
|
):
|
2020-02-19 19:29:27 +00:00
|
|
|
return
|
2020-03-02 07:01:28 +00:00
|
|
|
|
|
|
|
# Empty ping for assistance
|
2020-03-19 18:16:22 +00:00
|
|
|
elif message.content in mentions and self.mention_assist:
|
2020-02-22 18:11:48 +00:00
|
|
|
assist_msg = (
|
|
|
|
"**Hi there! How can I help?**\n\n"
|
2020-02-22 21:42:46 +00:00
|
|
|
# Two New Lines Here
|
2020-03-20 18:25:59 +00:00
|
|
|
f"You may use **{self.user.mention}** `term here` to search, "
|
|
|
|
f"or **{self.user.mention}** `help` for assistance.")
|
2020-02-22 18:11:48 +00:00
|
|
|
await ctx.send(assist_msg)
|
2020-03-02 07:01:28 +00:00
|
|
|
|
|
|
|
# Move on to command handling
|
2020-02-22 18:11:48 +00:00
|
|
|
else:
|
|
|
|
await self.process_commands(message)
|
2020-02-19 19:29:27 +00:00
|
|
|
|
2020-03-02 07:02:11 +00:00
|
|
|
|
2020-03-02 07:01:28 +00:00
|
|
|
# Creates Bot object
|
|
|
|
bot = Bot()
|
2020-02-19 19:29:27 +00:00
|
|
|
|
2020-03-02 07:02:11 +00:00
|
|
|
|
2020-02-22 18:11:48 +00:00
|
|
|
@bot.listen()
|
2020-02-19 19:29:27 +00:00
|
|
|
async def on_command_error(ctx, error):
|
2020-03-02 07:01:28 +00:00
|
|
|
"""Handles all errors stemming from ext.commands."""
|
|
|
|
|
|
|
|
# Lets other cogs handle CommandNotFound.
|
2020-03-24 23:09:01 +00:00
|
|
|
# Change this if you want command not found handling.
|
2020-03-19 22:31:08 +00:00
|
|
|
if (
|
|
|
|
isinstance(error, commands.CommandNotFound)
|
|
|
|
or isinstance(error, commands.CheckFailure)
|
|
|
|
):
|
2020-02-22 21:42:46 +00:00
|
|
|
return
|
2020-03-01 01:51:03 +00:00
|
|
|
|
2020-03-24 23:09:01 +00:00
|
|
|
# Custom message for if an argument is missing.
|
|
|
|
elif isinstance(error, commands.MissingRequiredArgument):
|
|
|
|
await ctx.send(
|
|
|
|
f"**Missing Argument!** A `{error.param.name}` is needed."
|
|
|
|
)
|
|
|
|
|
2020-04-09 02:05:37 +00:00
|
|
|
elif isinstance(error, searchexceptions.SafesearchFail):
|
2020-03-24 23:09:01 +00:00
|
|
|
await ctx.send(
|
|
|
|
"**Sorry!** That query included language "
|
|
|
|
"we cannot accept in a non-NSFW channel. "
|
|
|
|
"Please try again in an NSFW channel."
|
|
|
|
)
|
|
|
|
|
2020-03-02 07:01:28 +00:00
|
|
|
# Provides a very pretty embed if something's actually a dev's fault.
|
2020-02-22 21:42:46 +00:00
|
|
|
elif isinstance(error, commands.CommandInvokeError):
|
2020-03-02 23:37:34 +00:00
|
|
|
|
2020-03-01 01:51:03 +00:00
|
|
|
# Prerequisites
|
2020-03-19 22:31:08 +00:00
|
|
|
embed_fallback = (
|
|
|
|
f"**An error occured: {type(error).__name__}. "
|
|
|
|
f"Please contact {bot.appinfo.owner}.**"
|
|
|
|
)
|
2020-04-08 22:24:41 +00:00
|
|
|
error_embed = await bot.logging.error( # pylint: disable=no-member
|
2020-03-19 18:16:22 +00:00
|
|
|
error, ctx,
|
2020-03-04 00:48:40 +00:00
|
|
|
ctx.command.cog.qualified_name if ctx.command.cog.qualified_name
|
|
|
|
else "DMs"
|
2020-03-19 18:16:22 +00:00
|
|
|
)
|
2020-03-01 01:51:03 +00:00
|
|
|
|
2020-03-02 07:01:28 +00:00
|
|
|
# Sending
|
2020-02-22 21:42:46 +00:00
|
|
|
await ctx.send(embed_fallback, embed=error_embed)
|
2020-03-01 01:51:03 +00:00
|
|
|
|
2020-03-02 07:01:28 +00:00
|
|
|
# If anything else goes wrong, just go ahead and send it in chat.
|
2020-02-19 19:29:27 +00:00
|
|
|
else:
|
2020-04-08 22:24:41 +00:00
|
|
|
|
|
|
|
await bot.logging.error( # pylint: disable=no-member
|
2020-03-19 18:16:22 +00:00
|
|
|
error, ctx,
|
2020-03-04 00:48:40 +00:00
|
|
|
ctx.command.cog.qualified_name if ctx.command.cog.qualified_name
|
|
|
|
else "DMs"
|
|
|
|
)
|
2020-02-19 19:29:27 +00:00
|
|
|
await ctx.send(error)
|
2020-03-02 07:01:28 +00:00
|
|
|
# NOTE Bot Entry Point
|
|
|
|
# Starts the bot
|
2020-03-02 23:37:34 +00:00
|
|
|
print("Connecting...\n")
|
2020-03-24 23:09:01 +00:00
|
|
|
bot.run(bot.config['TOKEN'])
|