New get_webhook and logging features

This commit is contained in:
Adriene Hutchins 2020-03-02 23:08:20 -05:00
parent 7ae19ef8a6
commit a824c068e7
4 changed files with 254 additions and 100 deletions

View File

@ -19,6 +19,8 @@ class Search(commands.Cog):
# Main Stuff
self.bot = bot
self.info = bot.logging.info
self.warn = bot.logging.warn
self.request = bot.request
self.instances = bot.instances
self.emoji = "\U0001F50D"
@ -57,7 +59,6 @@ class Search(commands.Cog):
with open('searxes.txt') as f:
self.instances = f.read().split('\n')
instance = random.sample(self.instances, k=1)[0]
print(f"Attempting to use {instance}")
# Error Template
error_msg = (
@ -81,9 +82,6 @@ class Search(commands.Cog):
# Figure out engines for different categories to get decent results.
if category == 'videos':
call += '&engines=bing+videos,google+videos'
print(call)
# Make said API call
try:
async with self.request.get(call) as resp:
@ -121,17 +119,21 @@ class Search(commands.Cog):
# Instance Info
msg += f"\n\n_Results retrieved from instance `{instance}`._"
return msg
# Reached if error with returned results
except (KeyError, IndexError) as e:
# Logging
print(f"{e} with instance {instance}, trying again.")
self.warn(
f"A user encountered a(n) `{e}` with <{instance}> when searching for `{query}`. "
"Consider removing it or looking into it.",
name="Failed Instance"
)
self.instances.remove(instance) # Weed the instance out
# Recurse until good response
return await self._search_logic(query, is_nsfw)
return msg
async def _instance_check(self, instance, info):
"""Checks the quality of an instance."""
@ -172,84 +174,94 @@ class Search(commands.Cog):
async def search(self, ctx, *, query: str):
"""Search online for general results."""
# Logging
print(f"\n\nNEW CALL: {ctx.author} from {ctx.guild}.\n")
# Handling
async with ctx.typing():
msg = await self._search_logic(query, ctx.channel.is_nsfw())
await self.info(
content=f"**{ctx.author}** searched for `{query}` in \"{ctx.guild}\" and got this:"
f"\n\n{msg}",
name="Search Results"
)
await ctx.send(msg)
@commands.command(aliases=['video'])
async def videos(self, ctx, *, query: str):
"""Search online for videos."""
# Logging
print(f"\n\nNEW VIDEO CALL: {ctx.author} from {ctx.guild}.\n")
# Handling
async with ctx.typing():
msg = await self._search_logic(query, ctx.channel.is_nsfw(), 'videos')
await self.info(
content=f"**{ctx.author}** searched for `{query}` videos in \"{ctx.guild}\" and got this:"
f"\n\n{msg}",
name="Search Results"
)
await ctx.send(msg)
@commands.command()
async def music(self, ctx, *, query: str):
"""Search online for music."""
# Logging
print(f"\n\nNEW MUSIC CALL: {ctx.author} from {ctx.guild}.\n")
# Handling
async with ctx.typing():
msg = await self._search_logic(query, ctx.channel.is_nsfw(), 'music')
await self.info(
content=f"**{ctx.author}** searched for `{query}` music in \"{ctx.guild}\" and got this:"
f"\n\n{msg}",
name="Search Results"
)
await ctx.send(msg)
@commands.command(aliases=['file'])
async def files(self, ctx, *, query: str):
"""Search online for files."""
# Logging
print(f"\n\nNEW FILES CALL: {ctx.author} from {ctx.guild}.\n")
# Handling
async with ctx.typing():
msg = await self._search_logic(query, ctx.channel.is_nsfw(), 'files')
await self.info(
content=f"**{ctx.author}** searched for `{query}` files in \"{ctx.guild}\" and got this:"
f"\n\n{msg}",
name="Search Results"
)
await ctx.send(msg)
@commands.command(aliases=['image'])
async def images(self, ctx, *, query: str):
"""Search online for images."""
# Logging
print(f"\n\nNEW IMAGES CALL: {ctx.author} from {ctx.guild}.\n")
# Handling
async with ctx.typing():
msg = await self._search_logic(query, ctx.channel.is_nsfw(), 'images')
await self.info(
content=f"**{ctx.author}** searched for `{query}` images in \"{ctx.guild}\" and got this:"
f"\n\n{msg}",
name="Search Results"
)
await ctx.send(msg)
@commands.command()
async def it(self, ctx, *, query: str):
"""Search online for IT-related information."""
# Logging
print(f"\n\nNEW IT CALL: {ctx.author} from {ctx.guild}.\n")
# Handling
async with ctx.typing():
msg = await self._search_logic(query, ctx.channel.is_nsfw(), 'it')
await self.info(
content=f"**{ctx.author}** searched for `{query}` IT in \"{ctx.guild}\" and got this:"
f"\n\n{msg}",
name="Search Results"
)
await ctx.send(msg)
@commands.command(aliases=['map'])
async def maps(self, ctx, *, query: str):
"""Search online for map information."""
# Logging
print(f"\n\nNEW MAP CALL: {ctx.author} from {ctx.guild}.\n")
# Handling
async with ctx.typing():
msg = await self._search_logic(query, ctx.channel.is_nsfw(), 'map')
await self.info(
content=f"**{ctx.author}** searched for `{query}` maps in \"{ctx.guild}\" and got this:"
f"\n\n{msg}",
name="Search Results"
)
await ctx.send(msg)
@commands.command()
@ -287,9 +299,7 @@ class Search(commands.Cog):
if isinstance(error, commands.CommandNotFound) or \
isinstance(error, commands.CheckFailure):
# Logging
print(f"\n\nNEW CALL: {ctx.author} from {ctx.guild}.\n")
# Handling
async with ctx.typing():
# Prepares term
@ -297,6 +307,15 @@ class Search(commands.Cog):
term = term.lstrip(' ')
# Does search
msg = await self._search_logic(term, ctx.channel.is_nsfw())
# Logging
await self.info(
content=f"**{ctx.author}** searched for `{term}` in \"{ctx.guild}\" and got this:"
f"\n\n{msg}",
name="Search Results"
)
# Sends result
await ctx.send(msg)

181
extensions/utils/logging.py Normal file
View File

@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
# tacibot logging util
# Provides utils for logging via webhooks.
'''Online File'''
import discord
from discord.ext.commands import Context
import traceback
from typing import Optional
class Logging():
"""Provides logging utilities for bots."""
def __init__(self, bot):
self.bot = bot
self.request = bot.request
self.online = bot.online
self.maintenance = bot.maintenance
# Sets info hook first
self.info_hook = self.online.get_webhook(
bot.config['INFO_HOOK'] if bot.config['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']
else self.info_hook
)
self.error_hook = self.online.get_webhook(
bot.config['ERROR_HOOK'] if bot.config['ERROR_HOOK']
else self.info_hook
)
self.debug_hook = self.online.get_webhook(
bot.config['DEBUG_HOOK'] if bot.config['DEBUG_HOOK']
else self.info_hook
)
# If no hooks, nothing is there
else:
self.warn_hook = self.error_hook = self.debug_hook = None
async def _create_error_embed(self, error: Exception, ctx: Context):
"""Creates a new basic error embed."""
# Prerequisites
formatted_tb = traceback.format_tb(error.__traceback__)
formatted_tb = ''.join(formatted_tb)
original_exc = traceback.format_exception(
type(error), error, error.__traceback__)
print(original_exc)
# Hastebins Traceback
try:
url = await self.online.hastebin(
''.join(original_exc))
except Exception as e:
url = None
print(e)
# Embed Building
error_embed = discord.Embed(
title=(
f"{type(error).__name__} "
f"{'(Click for Hastebin)' if url else ''}"
),
url=url if url else None,
color=0xFF0000
)
# Formats Traceback
trace_content = (
"```py\n\nTraceback (most recent call last):"
"\n{}{}: {}```").format(
formatted_tb,
type(error).__name__,
error)
# Adds Traceback
error_embed.add_field(
name=(
f"`{type(error).__name__}` in "
f"command `{ctx.command.qualified_name}`"
),
value=(trace_content[:1018] + '...```')
if len(trace_content) > 1024
else trace_content
)
# Provides completed embed
return error_embed
async def info(self, content: str,
embed: Optional[discord.Embed] = None,
name: Optional[str] = None):
"""Logs info and sends it to the appropriate places."""
if self.info_hook:
return await self.info_hook.send(
content=content,
username=f"{self.bot.user.name} - {name if name else 'unknown'}",
avatar_url=str(self.bot.user.avatar_url),
embed=embed
)
else:
return
async def warn(self, content: str,
embed: Optional[discord.Embed] = None,
name: Optional[str] = None):
"""Logs warnings and sends them to the appropriate places."""
if self.warn_hook:
return await self.warn_hook.send(
content=content,
username=f"{self.bot.user.name} - {name if name else 'unknown'}",
avatar_url=str(self.bot.user.avatar_url),
embed=embed
)
else:
return
async def error(self, error: Exception, ctx: Context, name: Optional[str]):
"""Logs errors and sends them to the appropriate places."""
# Prerequisites
error_embed = await self._create_error_embed(error, ctx)
# Log and say so
if self.error_hook:
# Log
fallback = (
f"**{ctx.author}** encountered a(n) "
f"`{type(error).__name__}` when attempting to use "
f"`{ctx.command}`."
)
await self.error_hook.send(
content=fallback,
username=f"{self.bot.user.name} - {name if name else 'unknown'}",
avatar_url=str(self.bot.user.avatar_url),
embed=error_embed
)
# Say so
is_reported = "has been automatically reported, but"
# Say hasn't been logged
else:
is_reported = "has not been automatically reported, so"
# Added reported info and return
error_embed.description = (
f"This is (probably) a bug. This {is_reported} "
f"please give **{self.bot.appinfo.owner}** a heads-up in DMs."
)
return error_embed
async def debug(self, content: str,
embed: Optional[discord.Embed] = None,
name: Optional[str] = None):
"""Logs warnings and sends them to the appropriate places."""
if self.debug_hook and self.maintenance:
return await self.debug_hook.send(
content=content,
username=f"{self.bot.user.name} - {name if name else 'unknown'}",
avatar_url=str(self.bot.user.avatar_url),
embed=embed
)
else:
return
def setup(bot):
bot.logging = Logging(bot)

View File

@ -5,7 +5,12 @@
'''Online File'''
import discord
class Online():
"""Provides various online utilities for your bot."""
def __init__(self, bot):
self.bot = bot
self.request = bot.request
@ -18,11 +23,16 @@ class Online():
async with self.request.post(url=url, data=data) as haste_response:
haste_key = (await haste_response.json())['key']
haste_url = f"http://hastebin.com/{haste_key}"
# data = {'sprunge': ''}
# data['sprunge'] = string
# haste_url = await self.aioclient.post(url='http://sprunge.us',
# data=data)
return haste_url
def get_webhook(self, url: str):
"""Easily gets a webhook from a url."""
return discord.Webhook.from_url(
url,
adapter=discord.AsyncWebhookAdapter(self.request)
)
def setup(bot):
bot.online = Online(bot)

66
main.py
View File

@ -14,18 +14,15 @@ import json
import os
import asyncio
import aiohttp
import logging
import random
class Bot(commands.Bot):
"""Custom Bot Class that subclasses the commands.ext one"""
def __init__(self, **options):
"""Initializes the main parts of the bot."""
# Logging
print('Initializing...\n')
# Initializes parent class
super().__init__(self._get_prefix_new, **options)
@ -43,8 +40,6 @@ class Bot(commands.Bot):
with open('searxes.txt') as f:
self.instances = f.read().split('\n')
# Logging
def _init_extensions(self):
"""Initializes extensions."""
@ -163,73 +158,22 @@ async def on_command_error(ctx, error):
# Lets other cogs handle CommandNotFound.
# Change this if you want command not found handling
if isinstance(error, commands.CommandNotFound):
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
original_error = error.original
traceback_orig = traceback.format_tb(original_error.__traceback__)
traceback_orig = ''.join(traceback_orig)
appinfo = await bot.application_info()
original_exc = traceback.format_exception(
type(original_error), original_error, original_error.__traceback__)
print(original_exc)
# Main Message
embed_fallback = f"**An error occured: {type(original_error).__name__}. Please contact {appinfo.owner}.**"
# Hastebins Traceback
try:
url = await bot.online.hastebin(
''.join(original_exc))
except Exception as e:
url = None
print(e)
# Embed Building
error_embed = discord.Embed(
title=(
f"{type(original_error).__name__} "
f"{'(Click for Hastebin)' if url else ''}"
),
url=url if url else None,
color=0xFF0000,
description=( # TODO Change if has logging
"This is (probably) a bug. This has not been automatically "
f"reported, so please give **{appinfo.owner}** a heads-up in DMs.")
)
# Formats Traceback
trace_content = (
"```py\n\nTraceback (most recent call last):"
"\n{}{}: {}```").format(
traceback_orig,
type(original_error).__name__,
original_error)
# Adds Traceback
error_embed.add_field(
name=(
f"`{type(original_error).__name__}` in "
f"command `{ctx.command.qualified_name}`"
),
value=(trace_content[:1018] + '...```')
if len(trace_content) > 1024
else trace_content
)
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.__name__)
# Sending
await ctx.send(embed_fallback, embed=error_embed)
# If anything else goes wrong, just go ahead and send it in chat.
else:
original_error = error
original_exc = traceback.format_exception(
type(original_error), original_error, original_error.__traceback__)
print(''.join(original_exc))
await bot.logging.error(error, ctx, ctx.command.cog.__name__)
await ctx.send(error)
# NOTE Bot Entry Point
# Starts the bot