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
config.json
extensions/__pycache__/
utils/__pycache__/
.mypy_cache/
# Byte-compiled / optimized / DLL files
__pycache__/
@ -52,6 +54,4 @@ coverage.xml
# Sphinx documentation
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",
"DESCRIPTION": "a basic base for bots, designed by taciturasa.",
"REPO": "",
"SERVER": "",
"MAINTENANCE": false,
"VERSION": "1.9 testing",
"DESCRIPTION": "a minimalist search utility bot for discord, designed by taciturasa.",
"REPO": "https://github.com/taciturasa/searchbot-discord",
"SERVER": "https://discord.gg/4BpReNV",
"TOKEN": "",
"DBL": "",
"DBOTS": "",
"BOD": "",
"DBLCOM": "",
"PREFIX": ["basicbot!"],
"PREFIX": ["search!"],
"MAINTENANCE": false,
"CASE_INSENSITIVE": true,
"PREFIXLESS_DMS": true,
"MENTION_ASSIST": true,
"CUSTOM_HELP": true,
"INFO_HOOK": "",
"WARN_HOOK": "",
"ERROR_HOOK": "",
"DEBUG_HOOK": "",
"PERMS": null,
"BLOCKED": []
}
"PERMS": 378944,
"BLOCKED": [],
"BOTLISTS": {
"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"
# List Tokens
self.dbl_token = bot.config['DBL']
self.dbots_token = bot.config['DBOTS']
self.bod_token = bot.config['BOD']
self.dblcom_token = bot.config['DBLCOM']
self.dbl_token = bot.config['BOTLISTS']['DBL']
self.dbots_token = bot.config['BOTLISTS']['DBOTS']
self.bod_token = bot.config['BOTLISTS']['BOD']
self.dblcom_token = bot.config['BOTLISTS']['DBLCOM']
# top.gg client
self.dbl_client = dbl.DBLClient(
@ -139,7 +139,7 @@ class BotList(commands.Cog, name='Bot List'):
@tasks.loop(minutes=15.0)
async def update_stats(self):
"""Automatically updates statistics every 15 minutes."""
responses = await self._update_logic()
print(responses) # TODO See other todo

View File

@ -15,6 +15,7 @@ import cpuinfo
import math
import psutil
from extensions.models.help import TaciHelpCommand
from typing import List, Optional
class Core(commands.Cog):
@ -29,11 +30,37 @@ class Core(commands.Cog):
# 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.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."""
B = float(B)
@ -53,6 +80,8 @@ class Core(commands.Cog):
return '{0:.2f} GB'.format(B/GB)
elif TB <= B:
return '{0:.2f} TB'.format(B/TB)
else:
return 'ERROR'
@commands.command(aliases=['info', 'source', 'server'])
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"This instance by **{self.bot.appinfo.owner}.**\n\n"
if self.bot.config['REPO']:
msg += f"**Source Code:** _<{self.bot.config['REPO']}>_\n"
if self.bot.config['SERVER:']:
msg += f"**Support Server:** _<{self.bot.config['SERVER']}>_\n\n"
if self.bot.repo:
msg += f"**Source Code:** _<{self.bot.repo}>_\n"
if self.bot.support_server:
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 += 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"
)
if self.bot.config['PERMS'] is not None:
msg += f"&permissions={self.bot.config['PERMS']}>*"
if self.bot.perms:
msg += f"&permissions={self.bot.perms}>*"
else:
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()
async def stats(self, ctx):
"""Provides statistics on the bot itself."""
@ -116,10 +155,10 @@ Number of extensions present: {len(ctx.bot.cogs)}
ping = (after - before) * 1000
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)
@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 extension
@ -212,19 +251,81 @@ Number of extensions present: {len(ctx.bot.cogs)}
@commands.is_owner()
async def leave(self, ctx):
"""Makes the bot leave the server this was called in."""
if ctx.guild:
await ctx.send(
"\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()
else:
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):
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):
bot.add_cog(Core(bot))

View File

@ -23,9 +23,13 @@ class Search(commands.Cog):
self.info = bot.logging.info
self.warn = bot.logging.warn
self.request = bot.request
self.instances = bot.instances
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,
category: str = None) -> str:
"""Provides search logic for all search commands."""

View File

@ -22,22 +22,22 @@ class Logging():
# Sets info hook first
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
)
# Sets other hooks or defaults them
if self.info_hook:
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
)
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
)
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
)

97
main.py
View File

@ -12,10 +12,12 @@ from discord.ext import commands
import traceback
import json
import os
import sys
import asyncio
import aiohttp
import logging
import random
import rethinkdb
from typing import List, Optional
class Bot(commands.Bot):
"""Custom Bot Class that subclasses the commands.ext one"""
@ -27,24 +29,38 @@ class Bot(commands.Bot):
super().__init__(self._get_prefix_new, **options)
# Setup
self.extensions_list = []
self.extensions_list: List[str] = []
with open('config.json') as 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
with open('searxes.txt') as f:
self.instances = f.read().split('\n')
# 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:
@ -80,16 +96,50 @@ class Bot(commands.Bot):
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.config['PREFIXLESS_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)
@ -125,9 +175,14 @@ class Bot(commands.Bot):
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
# Loads core, which loads all other extensions
if self.extensions_list == []:
if not self.extensions_list:
self._init_extensions()
print('Initialized.\n')
@ -167,7 +222,7 @@ class Bot(commands.Bot):
return
# 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 = (
"**Hi there! How can I help?**\n\n"
# Two New Lines Here
@ -197,12 +252,12 @@ async def on_command_error(ctx, error):
elif isinstance(error, commands.CommandInvokeError):
# 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, ctx,
error, ctx,
ctx.command.cog.qualified_name if ctx.command.cog.qualified_name
else "DMs"
)
)
# Sending
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.
else:
await bot.logging.error(
error, ctx,
error, ctx,
ctx.command.cog.qualified_name if ctx.command.cog.qualified_name
else "DMs"
)
@ -218,4 +273,4 @@ async def on_command_error(ctx, error):
# NOTE Bot Entry Point
# Starts the bot
print("Connecting...\n")
bot.run(bot.config['TOKEN'])
bot.run(bot.config['TOKEN'])