searchbot-discord/main.py

313 lines
10 KiB
Python
Raw Permalink Normal View History

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'''
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']
2020-09-29 20:22:50 +00:00
if self.config['REMOVE_MENTIONS']:
self.allowed_mentions = discord.AllowedMentions.none()
else:
self.allowed_mentions = discord.AllowedMentions.all()
2020-03-19 18:16:22 +00:00
# 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
# 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'):
if ext.endswith('.py'):
try:
2020-03-02 23:37:34 +00:00
bot.load_extension(f'extensions.{ext[:-3]}')
self.extensions_list.append(
2020-03-02 23:37:34 +00:00
f'extensions.{ext[:-3]}')
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
async def _get_prefix_new(self, bot, msg):
2020-03-02 07:01:28 +00:00
"""More flexible check for prefix."""
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
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
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
2020-03-03 04:26:43 +00:00
# Maintenance Mode
if self.maintenance:
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:
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:
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
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-09-29 20:22:50 +00:00
bot.run(bot.config['TOKEN'])