New Features I think

This commit is contained in:
Adriene Hutchins 2020-03-19 14:16:22 -04:00
parent 946e9a4869
commit 61c2f6aecd
7 changed files with 240 additions and 64 deletions

6
.gitignore vendored
View File

@ -1,6 +1,8 @@
# Bot Related # Bot Related
config.json config.json
extensions/__pycache__/ extensions/__pycache__/
utils/__pycache__/
.mypy_cache/
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
@ -52,6 +54,4 @@ coverage.xml
# Sphinx documentation # Sphinx documentation
docs/_build/ docs/_build/
extensions/__pycache__/core.cpython-38.pyc
extensions/__pycache__/core.cpython-38.pyc
extensions/__pycache__/core.cpython-38.pyc

View File

@ -1,23 +1,39 @@
{ {
"VERSION": "x.x type", "VERSION": "1.9 testing",
"DESCRIPTION": "a basic base for bots, designed by taciturasa.", "DESCRIPTION": "a minimalist search utility bot for discord, designed by taciturasa.",
"REPO": "", "REPO": "https://github.com/taciturasa/searchbot-discord",
"SERVER": "", "SERVER": "https://discord.gg/4BpReNV",
"MAINTENANCE": false,
"TOKEN": "", "TOKEN": "",
"DBL": "", "PREFIX": ["search!"],
"DBOTS": "",
"BOD": "", "MAINTENANCE": false,
"DBLCOM": "",
"PREFIX": ["basicbot!"],
"CASE_INSENSITIVE": true, "CASE_INSENSITIVE": true,
"PREFIXLESS_DMS": true, "PREFIXLESS_DMS": true,
"MENTION_ASSIST": true, "MENTION_ASSIST": true,
"CUSTOM_HELP": true, "CUSTOM_HELP": true,
"INFO_HOOK": "",
"WARN_HOOK": "", "PERMS": 378944,
"ERROR_HOOK": "", "BLOCKED": [],
"DEBUG_HOOK": "",
"PERMS": null, "BOTLISTS": {
"BLOCKED": [] "DBL": "",
} "DBOTS": "",
"BOD": "",
"DBLCOM": ""
},
"HOOKS": {
"INFO_HOOK": "",
"WARN_HOOK": "",
"ERROR_HOOK": "",
"DEBUG_HOOK": ""
},
"RETHINK": {
"DB": "",
"USERNAME": "",
"PASSWORD": "",
"HOST": "",
"PORT": null
}
}

View File

@ -23,10 +23,10 @@ class BotList(commands.Cog, name='Bot List'):
self.emoji = "\U0001F5F3" self.emoji = "\U0001F5F3"
# List Tokens # List Tokens
self.dbl_token = bot.config['DBL'] self.dbl_token = bot.config['BOTLISTS']['DBL']
self.dbots_token = bot.config['DBOTS'] self.dbots_token = bot.config['BOTLISTS']['DBOTS']
self.bod_token = bot.config['BOD'] self.bod_token = bot.config['BOTLISTS']['BOD']
self.dblcom_token = bot.config['DBLCOM'] self.dblcom_token = bot.config['BOTLISTS']['DBLCOM']
# top.gg client # top.gg client
self.dbl_client = dbl.DBLClient( self.dbl_client = dbl.DBLClient(
@ -139,7 +139,7 @@ class BotList(commands.Cog, name='Bot List'):
@tasks.loop(minutes=15.0) @tasks.loop(minutes=15.0)
async def update_stats(self): async def update_stats(self):
"""Automatically updates statistics every 15 minutes.""" """Automatically updates statistics every 15 minutes."""
responses = await self._update_logic() responses = await self._update_logic()
print(responses) # TODO See other todo print(responses) # TODO See other todo

View File

@ -15,6 +15,7 @@ import cpuinfo
import math import math
import psutil import psutil
from extensions.models.help import TaciHelpCommand from extensions.models.help import TaciHelpCommand
from typing import List, Optional
class Core(commands.Cog): class Core(commands.Cog):
@ -29,11 +30,37 @@ class Core(commands.Cog):
# Help Command # Help Command
self._original_help_command = bot.help_command self._original_help_command = bot.help_command
if bot.config['CUSTOM_HELP']: if bot.custom_help:
bot.help_command = TaciHelpCommand() bot.help_command = TaciHelpCommand()
bot.help_command.cog = self bot.help_command.cog = self
def _humanbytes(self, B) -> str: # function lifted from StackOverflow def _create_tutorial(self, guild) -> str:
"""Creates the tutorial message."""
prefixes: str = f"`@{self.bot.user.name}`"
if self.bot.prefix:
others: str = ', '.join(f'`{p}`' for p in self.bot.prefix)
prefixes += f', {others}'
msg: str = (
f"**Hi!** Thanks for adding me to `{guild.name}`.\n\n"
f"I'm **{self.bot.user.name}** - _{self.bot.description}_\n\n"
f"My prefix{'es are' if self.bot.prefix else ' is'}: "
f"{prefixes}.\n\n"
"You may find more information with `help`.\n\n"
"_Please note that this bot may log errors, guild names, "
"command calls/contents, and the names of command users "
"for debug and maintenance purposes. "
"These logs are shared with nobody "
"other than those who help develop this bot. "
"If you do not agree to this, please remove this bot._\n\n"
"_You may recall this message at any time with `tutorial`._"
)
return msg
# function lifted from StackOverflow
def _humanbytes(self, B) -> str:
"""Return the given bytes as a human friendly KB, MB, GB, or TB string.""" """Return the given bytes as a human friendly KB, MB, GB, or TB string."""
B = float(B) B = float(B)
@ -53,6 +80,8 @@ class Core(commands.Cog):
return '{0:.2f} GB'.format(B/GB) return '{0:.2f} GB'.format(B/GB)
elif TB <= B: elif TB <= B:
return '{0:.2f} TB'.format(B/TB) return '{0:.2f} TB'.format(B/TB)
else:
return 'ERROR'
@commands.command(aliases=['info', 'source', 'server']) @commands.command(aliases=['info', 'source', 'server'])
async def about(self, ctx): async def about(self, ctx):
@ -60,10 +89,10 @@ class Core(commands.Cog):
msg = f"__**{self.bot.user.name}**__ - _{self.bot.description}_\n\n" msg = f"__**{self.bot.user.name}**__ - _{self.bot.description}_\n\n"
msg += f"This instance by **{self.bot.appinfo.owner}.**\n\n" msg += f"This instance by **{self.bot.appinfo.owner}.**\n\n"
if self.bot.config['REPO']: if self.bot.repo:
msg += f"**Source Code:** _<{self.bot.config['REPO']}>_\n" msg += f"**Source Code:** _<{self.bot.repo}>_\n"
if self.bot.config['SERVER:']: if self.bot.support_server:
msg += f"**Support Server:** _<{self.bot.config['SERVER']}>_\n\n" msg += f"**Support Server:** _<{self.bot.support_server}>_\n\n"
msg += "_Note: Please attempt to contact the hoster of any separate instances before this server._\n" msg += "_Note: Please attempt to contact the hoster of any separate instances before this server._\n"
msg += f"_See **{ctx.prefix}**`help` for help, `invite` to add the bot, and `stats` for statistics._" msg += f"_See **{ctx.prefix}**`help` for help, `invite` to add the bot, and `stats` for statistics._"
@ -79,13 +108,23 @@ class Core(commands.Cog):
f"*<https://discordapp.com/oauth2/authorize?client_id={self.bot.user.id}&scope=bot" f"*<https://discordapp.com/oauth2/authorize?client_id={self.bot.user.id}&scope=bot"
) )
if self.bot.config['PERMS'] is not None: if self.bot.perms:
msg += f"&permissions={self.bot.config['PERMS']}>*" msg += f"&permissions={self.bot.perms}>*"
else: else:
msg += ">*" msg += ">*"
await ctx.send(msg) await ctx.send(msg)
@commands.command()
async def tutorial(self, ctx):
"""Resends the tutorial message."""
if ctx.guild:
msg: str = self._create_tutorial(ctx.guild)
else:
msg: str = "**Cannot send tutorial in DMs!**"
await ctx.send(msg)
@commands.command() @commands.command()
async def stats(self, ctx): async def stats(self, ctx):
"""Provides statistics on the bot itself.""" """Provides statistics on the bot itself."""
@ -116,10 +155,10 @@ Number of extensions present: {len(ctx.bot.cogs)}
ping = (after - before) * 1000 ping = (after - before) * 1000
await pong.edit(content="`PING discordapp.com {}ms`".format(int(ping))) await pong.edit(content="`PING discordapp.com {}ms`".format(int(ping)))
@commands.group(aliases=['extensions', 'ext'], @commands.group(aliases=['extensions', 'ext'],
invoke_without_command=True) invoke_without_command=True)
@commands.is_owner() @commands.is_owner()
async def extend(self, ctx, name:str = None): async def extend(self, ctx, name: str = None):
"""Provides status of extensions and lets you hotswap extensions.""" """Provides status of extensions and lets you hotswap extensions."""
# Provides status of extension # Provides status of extension
@ -212,19 +251,81 @@ Number of extensions present: {len(ctx.bot.cogs)}
@commands.is_owner() @commands.is_owner()
async def leave(self, ctx): async def leave(self, ctx):
"""Makes the bot leave the server this was called in.""" """Makes the bot leave the server this was called in."""
if ctx.guild: if ctx.guild:
await ctx.send( await ctx.send(
"\U0001F4A8 **Leaving server.** " "\U0001F4A8 **Leaving server.** "
"_If you want me back, add me or get an admin to._") "_If you want me back, add me or get an admin to._"
)
await ctx.guild.leave() await ctx.guild.leave()
else: else:
await ctx.send( await ctx.send(
"**Can't leave!** _This channel is not inside a guild._") "**Can't leave!** _This channel is not inside a guild._"
)
def cog_unload(self): def cog_unload(self):
self.bot.help_command = self._original_help_command self.bot.help_command = self._original_help_command
@commands.Cog.listener()
async def on_guild_join(self, guild):
"""Sends owner notification and guild tutorial."""
# Prerequisites
guild_msg: str = self._create_tutorial(guild)
channel: Optional[discord.TextChannel] = None
owner: discord.Member = guild.owner
# Tutorial Message
# Get text channels
text_channels = []
for c in guild.channels:
if type(c) is discord.TextChannel:
text_channels.append(c)
# Sets channel to general if it exists
for c in guild.channels:
if c.name == 'general':
channel = c
# XXX This looks like garbage
# Else posts in first open channel
if not channel:
for c in guild.channels:
if c.permissions_for(guild.me).send_messages:
channel = c
# Send tutorial message
if channel:
await channel.send(guild_msg)
else:
guild_msg += (
"\n\n_I am sending this message to you as there were no "
"channels I could send messages to in your server. "
"Please give me send message permissions in the channels "
"You wish to use me in!_"
)
await guild.owner.send(guild_msg)
return # Ends here if there are no good channels to send to
# Owner Disclosure
# Message Building
owner_msg = (
"**Hi there!**\n\n"
f"I am **{self.bot.user.name}** - _{self.bot.description}_\n\n"
"I am messaging you to inform you I was added to your server, "
f"`{guild.name}`, by someone "
"with **Manage Server** permissions.\n\n"
f"I have sent a tutorial message to `{channel.name}` "
"describing how I may be used.\n\n"
"If you do not wish to have me there, "
"simply kick me from the server.\n\n"
"_Thanks for your time!_"
)
# Send owner disclosure
await guild.owner.send(owner_msg)
def setup(bot): def setup(bot):
bot.add_cog(Core(bot)) bot.add_cog(Core(bot))

View File

@ -23,9 +23,13 @@ class Search(commands.Cog):
self.info = bot.logging.info self.info = bot.logging.info
self.warn = bot.logging.warn self.warn = bot.logging.warn
self.request = bot.request self.request = bot.request
self.instances = bot.instances
self.emoji = "\U0001F50D" self.emoji = "\U0001F50D"
# Get Instances
with open('searxes.txt') as f:
self.instances = f.read().split('\n')
async def _search_logic(self, query: str, is_nsfw: bool = False, async def _search_logic(self, query: str, is_nsfw: bool = False,
category: str = None) -> str: category: str = None) -> str:
"""Provides search logic for all search commands.""" """Provides search logic for all search commands."""

View File

@ -22,22 +22,22 @@ class Logging():
# Sets info hook first # Sets info hook first
self.info_hook = self.online.get_webhook( self.info_hook = self.online.get_webhook(
bot.config['INFO_HOOK'] if bot.config['INFO_HOOK'] bot.config['HOOKS']['INFO_HOOK'] if bot.config['HOOKS']['INFO_HOOK']
else None else None
) )
# Sets other hooks or defaults them # Sets other hooks or defaults them
if self.info_hook: if self.info_hook:
self.warn_hook = self.online.get_webhook( self.warn_hook = self.online.get_webhook(
bot.config['WARN_HOOK'] if bot.config['WARN_HOOK'] bot.config['HOOKS']['WARN_HOOK'] if bot.config['HOOKS']['WARN_HOOK']
else self.info_hook else self.info_hook
) )
self.error_hook = self.online.get_webhook( self.error_hook = self.online.get_webhook(
bot.config['ERROR_HOOK'] if bot.config['ERROR_HOOK'] bot.config['HOOKS']['ERROR_HOOK'] if bot.config['HOOKS']['ERROR_HOOK']
else self.info_hook else self.info_hook
) )
self.debug_hook = self.online.get_webhook( self.debug_hook = self.online.get_webhook(
bot.config['DEBUG_HOOK'] if bot.config['DEBUG_HOOK'] bot.config['HOOKS']['DEBUG_HOOK'] if bot.config['HOOKS']['DEBUG_HOOK']
else self.info_hook else self.info_hook
) )

97
main.py
View File

@ -12,10 +12,12 @@ from discord.ext import commands
import traceback import traceback
import json import json
import os import os
import sys
import asyncio import asyncio
import aiohttp import aiohttp
import logging import rethinkdb
import random from typing import List, Optional
class Bot(commands.Bot): class Bot(commands.Bot):
"""Custom Bot Class that subclasses the commands.ext one""" """Custom Bot Class that subclasses the commands.ext one"""
@ -27,24 +29,38 @@ class Bot(commands.Bot):
super().__init__(self._get_prefix_new, **options) super().__init__(self._get_prefix_new, **options)
# Setup # Setup
self.extensions_list = [] self.extensions_list: List[str] = []
with open('config.json') as f: with open('config.json') as f:
self.config = json.load(f) self.config = json.load(f)
self.prefix = self.config['PREFIX']
self.version = self.config['VERSION']
self.maintenance = self.config['MAINTENANCE']
self.description = self.config['DESCRIPTION']
self.case_insensitive = self.config['CASE_INSENSITIVE']
# Get Instances # Info
with open('searxes.txt') as f: self.prefix: List[str] = self.config['PREFIX']
self.instances = f.read().split('\n') 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): def _init_extensions(self):
"""Initializes extensions.""" """Initializes extensions."""
# Utils # Utils
# Avoids race conditions with online # Avoids race conditions with online
utils_dir = os.listdir('extensions/utils') utils_dir = os.listdir('extensions/utils')
if 'online.py' in utils_dir: if 'online.py' in utils_dir:
@ -80,16 +96,50 @@ class Bot(commands.Bot):
f'extensions.{ext[:-3]}') f'extensions.{ext[:-3]}')
except Exception as e: except Exception as e:
print(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): async def _get_prefix_new(self, bot, msg):
"""More flexible check for prefix.""" """More flexible check for prefix."""
# Adds empty prefix if in DMs # Adds empty prefix if in DMs
if isinstance(msg.channel, discord.DMChannel) and self.config['PREFIXLESS_DMS']: if isinstance(msg.channel, discord.DMChannel) and self.prefixless_dms:
plus_empty = self.prefix.copy() plus_empty = self.prefix.copy()
plus_empty.append('') plus_empty.append('')
return commands.when_mentioned_or(*plus_empty)(bot, msg) return commands.when_mentioned_or(*plus_empty)(bot, msg)
# Keeps regular if not # Keeps regular if not
else: else:
return commands.when_mentioned_or(*self.prefix)(bot, msg) return commands.when_mentioned_or(*self.prefix)(bot, msg)
@ -125,9 +175,14 @@ class Bot(commands.Bot):
status=discord.Status.online status=discord.Status.online
) )
# NOTE Rethink Entry Point
# Initializes all rethink stuff
if hasattr(self, 'rdb') and not self.rtables:
await self._init_rethinkdb()
# NOTE Extension Entry Point # NOTE Extension Entry Point
# Loads core, which loads all other extensions # Loads core, which loads all other extensions
if self.extensions_list == []: if not self.extensions_list:
self._init_extensions() self._init_extensions()
print('Initialized.\n') print('Initialized.\n')
@ -167,7 +222,7 @@ class Bot(commands.Bot):
return return
# Empty ping for assistance # Empty ping for assistance
elif message.content in mentions and self.config.get('MENTION_ASSIST'): elif message.content in mentions and self.mention_assist:
assist_msg = ( assist_msg = (
"**Hi there! How can I help?**\n\n" "**Hi there! How can I help?**\n\n"
# Two New Lines Here # Two New Lines Here
@ -197,12 +252,12 @@ async def on_command_error(ctx, error):
elif isinstance(error, commands.CommandInvokeError): elif isinstance(error, commands.CommandInvokeError):
# Prerequisites # Prerequisites
embed_fallback = f"**An error occured: {type(error).__name__}. Please contact {bot.appinfo.owner}.**" embed_fallback = f"**An error occured: {type(error).__name__}. Please contact {bot.appinfo.owner}.**"
error_embed = await bot.logging.error( error_embed = await bot.logging.error(
error, ctx, error, ctx,
ctx.command.cog.qualified_name if ctx.command.cog.qualified_name ctx.command.cog.qualified_name if ctx.command.cog.qualified_name
else "DMs" else "DMs"
) )
# Sending # Sending
await ctx.send(embed_fallback, embed=error_embed) await ctx.send(embed_fallback, embed=error_embed)
@ -210,7 +265,7 @@ async def on_command_error(ctx, error):
# If anything else goes wrong, just go ahead and send it in chat. # If anything else goes wrong, just go ahead and send it in chat.
else: else:
await bot.logging.error( await bot.logging.error(
error, ctx, error, ctx,
ctx.command.cog.qualified_name if ctx.command.cog.qualified_name ctx.command.cog.qualified_name if ctx.command.cog.qualified_name
else "DMs" else "DMs"
) )
@ -218,4 +273,4 @@ async def on_command_error(ctx, error):
# NOTE Bot Entry Point # NOTE Bot Entry Point
# Starts the bot # Starts the bot
print("Connecting...\n") print("Connecting...\n")
bot.run(bot.config['TOKEN']) bot.run(bot.config['TOKEN'])