Complete rearranging for modularity and dev cmds
This commit is contained in:
parent
8c29e6683a
commit
9da190a078
Binary file not shown.
|
@ -0,0 +1,170 @@
|
|||
import discord
|
||||
import os
|
||||
from discord.ext import commands
|
||||
import time
|
||||
import asyncio
|
||||
import sys
|
||||
import cpuinfo
|
||||
import math
|
||||
import psutil
|
||||
|
||||
|
||||
class Core(commands.Cog):
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.settings = {
|
||||
'extensions': []
|
||||
}
|
||||
|
||||
self._init_extensions()
|
||||
|
||||
def _init_extensions(self):
|
||||
"""Initializes extensions."""
|
||||
|
||||
for ext in os.listdir('extensions'):
|
||||
if ext.endswith('.py') and not ext.startswith('core'):
|
||||
try:
|
||||
self.bot.load_extension(f'extensions.{ext[:-3]}')
|
||||
self.settings['extensions'].append(
|
||||
f'extensions.{ext[:-3]}')
|
||||
except:
|
||||
pass
|
||||
|
||||
def _humanbytes(self, B) -> str: # function lifted from StackOverflow
|
||||
"""Return the given bytes as a human friendly KB, MB, GB, or TB string."""
|
||||
|
||||
B = float(B)
|
||||
KB = float(1024)
|
||||
MB = float(KB ** 2) # 1,048,576
|
||||
GB = float(KB ** 3) # 1,073,741,824
|
||||
TB = float(KB ** 4) # 1,099,511,627,776
|
||||
|
||||
if B < KB:
|
||||
return '{0} {1}'.format(
|
||||
B, 'Bytes' if 0 == B > 1 else 'Byte')
|
||||
elif KB <= B < MB:
|
||||
return '{0:.2f} KB'.format(B/KB)
|
||||
elif MB <= B < GB:
|
||||
return '{0:.2f} MB'.format(B/MB)
|
||||
elif GB <= B < TB:
|
||||
return '{0:.2f} GB'.format(B/GB)
|
||||
elif TB <= B:
|
||||
return '{0:.2f} TB'.format(B/TB)
|
||||
|
||||
@commands.command(aliases=['info', 'source', 'server'])
|
||||
async def about(self, ctx):
|
||||
"""Returns information about this bot."""
|
||||
|
||||
msg = f"**{self.bot.description}**\n"
|
||||
msg += f"Created by **taciturasa#4365**, this instance by **{self.bot.appinfo.owner}.**\n\n"
|
||||
msg += "**Source Code:** _<https://github.com/taciturasa/searchbot-discord>_\n"
|
||||
msg += "**Support Server:** _<https://discord.gg/4BpReNV>_\n"
|
||||
msg += "_Note: Please attempt to contact the hoster of any separate instances before this server._\n\n"
|
||||
msg += f"_See **{ctx.prefix}** `help` for help, `invite` to add the bot, and `stats` for statistics._"
|
||||
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command(aliases=['addbot', 'connect', 'join'])
|
||||
async def invite(self, ctx):
|
||||
"""Gets a link to invite this bot to your server."""
|
||||
|
||||
msg = (
|
||||
"**Thanks for checking me out!**\n\n"
|
||||
"Use the following link to add me:\n"
|
||||
f"*<https://discordapp.com/oauth2/authorize?client_id={self.bot.user.id}&permissions={self.bot.config['PERMS']}&scope=bot>*"
|
||||
)
|
||||
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command()
|
||||
async def stats(self, ctx):
|
||||
"""Provides statistics on the bot itself."""
|
||||
|
||||
mem = psutil.virtual_memory()
|
||||
currproc = psutil.Process(os.getpid())
|
||||
total_ram = self._humanbytes(mem[0])
|
||||
available_ram = self._humanbytes(mem[1])
|
||||
usage = self._humanbytes(currproc.memory_info().rss)
|
||||
msg = f"""
|
||||
```
|
||||
Total RAM: {total_ram}
|
||||
Available RAM: {available_ram}
|
||||
RAM used by bot: {usage}
|
||||
Number of bot commands: {len(ctx.bot.commands)}
|
||||
Number of extensions present: {len(ctx.bot.cogs)}
|
||||
```
|
||||
"""
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command()
|
||||
async def ping(self, ctx):
|
||||
"""Checks the ping of the bot."""
|
||||
|
||||
before = time.monotonic()
|
||||
pong = await ctx.send("...")
|
||||
after = time.monotonic()
|
||||
ping = (after - before) * 1000
|
||||
await pong.edit(content="`PING discordapp.com {}ms`".format(int(ping)))
|
||||
|
||||
@commands.command()
|
||||
@commands.is_owner()
|
||||
async def load(self, ctx, name: str):
|
||||
"""Load an extension into the bot."""
|
||||
m = await ctx.send(f'Loading {name}')
|
||||
extension_name = 'extensions.{0}'.format(name)
|
||||
if extension_name not in self.settings['extensions']:
|
||||
try:
|
||||
self.bot.load_extension(extension_name)
|
||||
self.settings['extensions'].append(extension_name)
|
||||
await m.edit(content='Extension loaded.')
|
||||
except Exception as e:
|
||||
await m.edit(
|
||||
content=f'Error while loading {name}\n`{type(e).__name__}: {e}`')
|
||||
else:
|
||||
await m.edit(content='Extension already loaded.')
|
||||
|
||||
@commands.command(aliases=["ule", "ul"])
|
||||
@commands.is_owner()
|
||||
async def unload(self, ctx, name: str):
|
||||
"""Unload an extension from the bot."""
|
||||
|
||||
m = await ctx.send(f'Unloading {name}')
|
||||
extension_name = 'extensions.{0}'.format(name)
|
||||
if extension_name in self.settings['extensions']:
|
||||
self.bot.unload_extension(extension_name)
|
||||
self.settings['extensions'].remove(extension_name)
|
||||
await m.edit(content='Extension unloaded.')
|
||||
else:
|
||||
await m.edit(content='Extension not found or not loaded.')
|
||||
|
||||
@commands.command(aliases=["rle", "rl"])
|
||||
@commands.is_owner()
|
||||
async def reload(self, ctx, name: str):
|
||||
"""Reload an extension of the bot."""
|
||||
|
||||
m = await ctx.send(f'Reloading {name}')
|
||||
extension_name = 'extensions.{0}'.format(name)
|
||||
if extension_name in self.settings['extensions']:
|
||||
self.bot.unload_extension(extension_name)
|
||||
try:
|
||||
self.bot.load_extension(extension_name)
|
||||
await m.edit(content='Extension reloaded.')
|
||||
except Exception as e:
|
||||
self.settings['extensions'].remove(extension_name)
|
||||
await m.edit(
|
||||
content=f'Failed to reload extension\n`{type(e).__name__}: {e}`')
|
||||
else:
|
||||
await m.edit(content='Extension isn\'t loaded.')
|
||||
|
||||
@commands.command(aliases=['exit', 'reboot'])
|
||||
@commands.is_owner()
|
||||
async def restart(self, ctx):
|
||||
"""Turns the bot off."""
|
||||
|
||||
await ctx.send(':zzz: **Restarting.**')
|
||||
exit()
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Core(bot))
|
|
@ -8,111 +8,519 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
import os
|
||||
# import psutil
|
||||
import aiohttp
|
||||
import random
|
||||
import datetime
|
||||
import collections
|
||||
from contextlib import redirect_stdout
|
||||
import traceback
|
||||
import time
|
||||
import io
|
||||
import inspect
|
||||
import textwrap
|
||||
import subprocess
|
||||
|
||||
|
||||
class Developer(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.request = bot.request
|
||||
self.instances = bot.instances
|
||||
self.repl_sessions = {}
|
||||
self.repl_embeds = {}
|
||||
self._eval = {}
|
||||
|
||||
async def _instance_check(self, instance, info):
|
||||
'''Checks the quality of an instance.'''
|
||||
def _cleanup_code(self, content):
|
||||
"""Automatically removes code blocks from the code."""
|
||||
# remove ```py\n```
|
||||
if content.startswith('```') and content.endswith('```'):
|
||||
return '\n'.join(content.split('\n')[1:-1])
|
||||
|
||||
# Makes sure proper values exist
|
||||
if 'error' in info:
|
||||
return False
|
||||
if not ('engines' in info and 'initial' in info['timing']):
|
||||
return False
|
||||
if not ('google' in info['engines'] and 'enabled' in info['engines']['google']):
|
||||
return False
|
||||
# remove `foo`
|
||||
return content.strip('` \n')
|
||||
|
||||
# Makes sure google is enabled
|
||||
if not info['engines']['google']['enabled']:
|
||||
return False
|
||||
def _get_syntax_error(self, err):
|
||||
"""Returns SyntaxError formatted for repl reply."""
|
||||
return '```py\n{0.text}{1:>{0.offset}}\n{2}: {0}```'.format(
|
||||
err,
|
||||
'^',
|
||||
type(err).__name__)
|
||||
|
||||
# Makes sure is not Tor
|
||||
if info['network_type'] != 'normal':
|
||||
return False
|
||||
async def _post_to_hastebin(self, string):
|
||||
"""Posts a string to hastebin."""
|
||||
url = "https://hastebin.com/documents"
|
||||
data = string.encode('utf-8')
|
||||
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
|
||||
|
||||
# Only picks instances that are fast enough
|
||||
timing = int(info['timing']['initial'])
|
||||
if timing > 0.20:
|
||||
return False
|
||||
@commands.group(name='shell',
|
||||
aliases=['ipython', 'repl', 'longexec'])
|
||||
async def repl(self, ctx, *, name: str = None):
|
||||
"""Head on impact with an interactive python shell."""
|
||||
# TODO Minimize local variables
|
||||
# TODO Minimize branches
|
||||
|
||||
session = ctx.message.channel.id
|
||||
|
||||
embed = discord.Embed(
|
||||
description="_Enter code to execute or evaluate. "
|
||||
"`exit()` or `quit` to exit._",
|
||||
timestamp=datetime.datetime.now())
|
||||
|
||||
embed.set_footer(
|
||||
text="Interactive Python Shell",
|
||||
icon_url="https://upload.wikimedia.org/wikipedia/commons/thumb"
|
||||
"/c/c3/Python-logo-notext.svg/1024px-Python-logo-notext.svg.png")
|
||||
|
||||
if name is not None:
|
||||
embed.title = name.strip(" ")
|
||||
|
||||
history = collections.OrderedDict()
|
||||
|
||||
variables = {
|
||||
'ctx': ctx,
|
||||
'bot': self.bot,
|
||||
'message': ctx.message,
|
||||
'server': ctx.message.guild,
|
||||
'channel': ctx.message.channel,
|
||||
'author': ctx.message.author,
|
||||
'discord': discord,
|
||||
'_': None
|
||||
}
|
||||
|
||||
if session in self.repl_sessions:
|
||||
error_embed = discord.Embed(
|
||||
color=15746887,
|
||||
description="**Error**: "
|
||||
"_Shell is already running in channel._")
|
||||
await ctx.send(embed=error_embed)
|
||||
return
|
||||
|
||||
shell = await ctx.send(embed=embed)
|
||||
|
||||
self.repl_sessions[session] = shell
|
||||
self.repl_embeds[shell] = embed
|
||||
|
||||
while True:
|
||||
response = await self.bot.wait_for(
|
||||
'message',
|
||||
check=lambda m: m.content.startswith(
|
||||
'`') and m.author == ctx.author and m.channel == ctx.channel
|
||||
)
|
||||
cleaned = self._cleanup_code(response.content)
|
||||
shell = self.repl_sessions[session]
|
||||
|
||||
# Regular Bot Method
|
||||
try:
|
||||
await ctx.message.channel.fetch_message(
|
||||
self.repl_sessions[session].id)
|
||||
except discord.NotFound:
|
||||
new_shell = await ctx.send(embed=self.repl_embeds[shell])
|
||||
self.repl_sessions[session] = new_shell
|
||||
|
||||
embed = self.repl_embeds[shell]
|
||||
del self.repl_embeds[shell]
|
||||
self.repl_embeds[new_shell] = embed
|
||||
|
||||
shell = self.repl_sessions[session]
|
||||
|
||||
try:
|
||||
await response.delete()
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
|
||||
if len(self.repl_embeds[shell].fields) >= 7:
|
||||
self.repl_embeds[shell].remove_field(0)
|
||||
|
||||
if cleaned in ('quit', 'exit', 'exit()'):
|
||||
self.repl_embeds[shell].color = 16426522
|
||||
|
||||
if self.repl_embeds[shell].title is not discord.Embed.Empty:
|
||||
history_string = "History for {}\n\n\n".format(
|
||||
self.repl_embeds[shell].title)
|
||||
else:
|
||||
history_string = "History for latest session\n\n\n"
|
||||
|
||||
for item in history.keys():
|
||||
history_string += ">>> {}\n{}\n\n".format(
|
||||
item,
|
||||
history[item])
|
||||
|
||||
haste_url = await self._post_to_hastebin(history_string)
|
||||
return_msg = "[`Leaving shell session. "\
|
||||
"History hosted on hastebin.`]({})".format(
|
||||
haste_url)
|
||||
|
||||
self.repl_embeds[shell].add_field(
|
||||
name="`>>> {}`".format(cleaned),
|
||||
value=return_msg,
|
||||
inline=False)
|
||||
|
||||
await self.repl_sessions[session].edit(
|
||||
embed=self.repl_embeds[shell])
|
||||
|
||||
del self.repl_embeds[shell]
|
||||
del self.repl_sessions[session]
|
||||
return
|
||||
|
||||
executor = exec
|
||||
if cleaned.count('\n') == 0:
|
||||
# single statement, potentially 'eval'
|
||||
try:
|
||||
code = compile(cleaned, '<repl session>', 'eval')
|
||||
except SyntaxError:
|
||||
pass
|
||||
else:
|
||||
executor = eval
|
||||
|
||||
if executor is exec:
|
||||
try:
|
||||
code = compile(cleaned, '<repl session>', 'exec')
|
||||
except SyntaxError as err:
|
||||
self.repl_embeds[shell].color = 15746887
|
||||
|
||||
return_msg = self._get_syntax_error(err)
|
||||
|
||||
history[cleaned] = return_msg
|
||||
|
||||
if len(cleaned) > 800:
|
||||
cleaned = "<Too big to be printed>"
|
||||
if len(return_msg) > 800:
|
||||
haste_url = await self._post_to_hastebin(return_msg)
|
||||
return_msg = "[`SyntaxError too big to be printed. "\
|
||||
"Hosted on hastebin.`]({})".format(
|
||||
haste_url)
|
||||
|
||||
self.repl_embeds[shell].add_field(
|
||||
name="`>>> {}`".format(cleaned),
|
||||
value=return_msg,
|
||||
inline=False)
|
||||
|
||||
await self.repl_sessions[session].edit(
|
||||
embed=self.repl_embeds[shell])
|
||||
continue
|
||||
|
||||
variables['message'] = response
|
||||
|
||||
fmt = None
|
||||
stdout = io.StringIO()
|
||||
|
||||
try:
|
||||
with redirect_stdout(stdout):
|
||||
result = executor(code, variables)
|
||||
if inspect.isawaitable(result):
|
||||
result = await result
|
||||
except Exception as err:
|
||||
self.repl_embeds[shell].color = 15746887
|
||||
value = stdout.getvalue()
|
||||
fmt = '```py\n{}{}\n```'.format(
|
||||
value,
|
||||
traceback.format_exc())
|
||||
else:
|
||||
self.repl_embeds[shell].color = 4437377
|
||||
|
||||
value = stdout.getvalue()
|
||||
|
||||
if result is not None:
|
||||
fmt = '```py\n{}{}\n```'.format(
|
||||
value,
|
||||
result)
|
||||
|
||||
variables['_'] = result
|
||||
elif value:
|
||||
fmt = '```py\n{}\n```'.format(value)
|
||||
|
||||
history[cleaned] = fmt
|
||||
|
||||
if len(cleaned) > 800:
|
||||
cleaned = "<Too big to be printed>"
|
||||
|
||||
try:
|
||||
if fmt is not None:
|
||||
if len(fmt) >= 800:
|
||||
haste_url = await self._post_to_hastebin(fmt)
|
||||
self.repl_embeds[shell].add_field(
|
||||
name="`>>> {}`".format(cleaned),
|
||||
value="[`Content too big to be printed. "
|
||||
"Hosted on hastebin.`]({})".format(
|
||||
haste_url),
|
||||
inline=False)
|
||||
|
||||
await self.repl_sessions[session].edit(
|
||||
embed=self.repl_embeds[shell])
|
||||
else:
|
||||
self.repl_embeds[shell].add_field(
|
||||
name="`>>> {}`".format(cleaned),
|
||||
value=fmt,
|
||||
inline=False)
|
||||
|
||||
await self.repl_sessions[session].edit(
|
||||
embed=self.repl_embeds[shell])
|
||||
else:
|
||||
self.repl_embeds[shell].add_field(
|
||||
name="`>>> {}`".format(cleaned),
|
||||
value="`Empty response, assumed successful.`",
|
||||
inline=False)
|
||||
|
||||
await self.repl_sessions[session].edit(
|
||||
embed=self.repl_embeds[shell])
|
||||
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
|
||||
except discord.HTTPException as err:
|
||||
error_embed = discord.Embed(
|
||||
color=15746887,
|
||||
description='**Error**: _{}_'.format(err))
|
||||
await ctx.send(embed=error_embed)
|
||||
|
||||
@repl.command(name='jump',
|
||||
aliases=['hop', 'pull', 'recenter', 'whereditgo'])
|
||||
async def repljump(self, ctx):
|
||||
"""Brings the shell back down so you can see it again."""
|
||||
|
||||
session = ctx.message.channel.id
|
||||
|
||||
if session not in self.repl_sessions:
|
||||
error_embed = discord.Embed(
|
||||
color=15746887,
|
||||
description="**Error**: _No shell running in channel._")
|
||||
await ctx.send(embed=error_embed)
|
||||
return
|
||||
|
||||
shell = self.repl_sessions[session]
|
||||
embed = self.repl_embeds[shell]
|
||||
|
||||
# Check for Google captcha
|
||||
test_search = f'{instance}/search?q=test&format=json&lang=en-US'
|
||||
try:
|
||||
async with self.request.get(test_search) as resp:
|
||||
response = await resp.json()
|
||||
response['results'][0]['content']
|
||||
except (aiohttp.ClientError, KeyError, IndexError):
|
||||
return False
|
||||
await ctx.message.delete()
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
|
||||
# Reached if passes all checks
|
||||
return True
|
||||
try:
|
||||
await shell.delete()
|
||||
except discord.errors.NotFound:
|
||||
pass
|
||||
new_shell = await ctx.send(embed=embed)
|
||||
|
||||
self.repl_sessions[session] = new_shell
|
||||
|
||||
del self.repl_embeds[shell]
|
||||
self.repl_embeds[new_shell] = embed
|
||||
|
||||
@repl.command(name='clear',
|
||||
aliases=['clean', 'purge', 'cleanup',
|
||||
'ohfuckme', 'deletthis'])
|
||||
async def replclear(self, ctx):
|
||||
"""Clears the fields of the shell and resets the color."""
|
||||
|
||||
session = ctx.message.channel.id
|
||||
|
||||
if session not in self.repl_sessions:
|
||||
error_embed = discord.Embed(
|
||||
color=15746887,
|
||||
description="**Error**: _No shell running in channel._")
|
||||
await ctx.send(embed=error_embed)
|
||||
return
|
||||
|
||||
shell = self.repl_sessions[session]
|
||||
|
||||
self.repl_embeds[shell].color = discord.Color.default()
|
||||
self.repl_embeds[shell].clear_fields()
|
||||
|
||||
try:
|
||||
await ctx.message.delete()
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
await shell.edit(embed=self.repl_embeds[shell])
|
||||
|
||||
@commands.command(name='eval')
|
||||
async def eval_cmd(self, ctx, *, code: str):
|
||||
"""Evaluates Python code."""
|
||||
|
||||
if self._eval.get('env') is None:
|
||||
self._eval['env'] = {}
|
||||
if self._eval.get('count') is None:
|
||||
self._eval['count'] = 0
|
||||
|
||||
codebyspace = code.split(" ")
|
||||
print(codebyspace)
|
||||
silent = False
|
||||
if codebyspace[0] == "--silent" or codebyspace[0] == "-s":
|
||||
silent = True
|
||||
codebyspace = codebyspace[1:]
|
||||
code = " ".join(codebyspace)
|
||||
|
||||
self._eval['env'].update({
|
||||
'self': self.bot,
|
||||
'ctx': ctx,
|
||||
'message': ctx.message,
|
||||
'channel': ctx.message.channel,
|
||||
'guild': ctx.message.guild,
|
||||
'author': ctx.message.author,
|
||||
})
|
||||
|
||||
# let's make this safe to work with
|
||||
code = code.replace('```py\n', '').replace('```', '').replace('`', '')
|
||||
|
||||
_code = (
|
||||
'async def func(self):\n try:\n{}\n '
|
||||
'finally:\n self._eval[\'env\'].update(locals())').format(
|
||||
textwrap.indent(code, ' '))
|
||||
|
||||
before = time.monotonic()
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
exec(_code, self._eval['env'])
|
||||
func = self._eval['env']['func']
|
||||
output = await func(self)
|
||||
|
||||
if output is not None:
|
||||
output = repr(output)
|
||||
except Exception as e:
|
||||
output = '{}: {}'.format(type(e).__name__, e)
|
||||
after = time.monotonic()
|
||||
self._eval['count'] += 1
|
||||
count = self._eval['count']
|
||||
|
||||
code = code.split('\n')
|
||||
if len(code) == 1:
|
||||
_in = 'In [{}]: {}'.format(count, code[0])
|
||||
else:
|
||||
_first_line = code[0]
|
||||
_rest = code[1:]
|
||||
_rest = '\n'.join(_rest)
|
||||
_countlen = len(str(count)) + 2
|
||||
_rest = textwrap.indent(_rest, '...: ')
|
||||
_rest = textwrap.indent(_rest, ' ' * _countlen)
|
||||
_in = 'In [{}]: {}\n{}'.format(count, _first_line, _rest)
|
||||
|
||||
message = '```py\n{}'.format(_in)
|
||||
if output is not None:
|
||||
message += '\nOut[{}]: {}'.format(count, output)
|
||||
ms = int(round((after - before) * 1000))
|
||||
if ms > 100: # noticeable delay
|
||||
message += '\n# {} ms\n```'.format(ms)
|
||||
else:
|
||||
message += '\n```'
|
||||
|
||||
try:
|
||||
if ctx.author.id == self.bot.user.id:
|
||||
await ctx.message.edit(content=message)
|
||||
else:
|
||||
if not silent:
|
||||
await ctx.send(message)
|
||||
except discord.HTTPException:
|
||||
if not silent:
|
||||
with aiohttp.ClientSession() as sesh:
|
||||
async with sesh.post(
|
||||
"https://hastebin.com/documents/",
|
||||
data=output,
|
||||
headers={"Content-Type": "text/plain"}) as r:
|
||||
r = await r.json()
|
||||
embed = discord.Embed(
|
||||
description=(
|
||||
"[View output - click]"
|
||||
"(https://hastebin.com/raw/{})").format(
|
||||
r["key"]))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(aliases=['sys', 'sh'])
|
||||
async def system(self, ctx, *, command: str):
|
||||
"""Runs system commands."""
|
||||
|
||||
message = await ctx.send('<a:typing:401162479041773568> Processing...')
|
||||
result = []
|
||||
try:
|
||||
process = subprocess.Popen(command.split(
|
||||
' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
result = process.communicate()
|
||||
except FileNotFoundError:
|
||||
stderr = f'Command not found: {command}'
|
||||
embed = discord.Embed(
|
||||
title="Command output"
|
||||
)
|
||||
if len(result) >= 1 and result[0] in [None, b'']:
|
||||
stdout = 'No output.'
|
||||
if len(result) >= 2 and result[0] in [None, b'']:
|
||||
stderr = 'No output.'
|
||||
if len(result) >= 1 and result[0] not in [None, b'']:
|
||||
stdout = result[0].decode('utf-8')
|
||||
if len(result) >= 2 and result[1] not in [None, b'']:
|
||||
stderr = result[1].decode('utf-8')
|
||||
string = ""
|
||||
if len(result) >= 1:
|
||||
if (len(result[0]) >= 1024):
|
||||
stdout = result[0].decode('utf-8')
|
||||
string = string + f'[[STDOUT]]\n{stdout}'
|
||||
link = await self._post_to_hastebin(string)
|
||||
await message.edit(
|
||||
content=f":x: Content too long. {link}",
|
||||
embed=None)
|
||||
return
|
||||
if len(result) >= 2:
|
||||
if (len(result[1]) >= 1024):
|
||||
stdout = result[0].decode('utf-8')
|
||||
string = string + f'[[STDERR]]\n{stdout}'
|
||||
link = await self._post_to_hastebin(string)
|
||||
await message.edit(
|
||||
content=f":x: Content too long. {link}",
|
||||
embed=None)
|
||||
return
|
||||
embed.add_field(
|
||||
name="stdout",
|
||||
value=f'```{stdout}```' if 'stdout' in locals() else 'No output.',
|
||||
inline=False)
|
||||
embed.add_field(
|
||||
name="stderr",
|
||||
value=f'```{stderr}```' if 'stderr' in locals() else 'No output.',
|
||||
inline=False)
|
||||
await message.edit(content='', embed=embed)
|
||||
|
||||
# @commands.command()
|
||||
# async def git(self, ctx, sub, flags=""):
|
||||
# """Runs some git commands in Discord."""
|
||||
|
||||
# if sub == "gud":
|
||||
# if not flags:
|
||||
# return await ctx.send("```You are now so gud!```")
|
||||
# else:
|
||||
# return await ctx.send(
|
||||
# "```{} is now so gud!```".format(flags))
|
||||
# elif sub == "rekt":
|
||||
# if not flags:
|
||||
# return await ctx.send("```You got #rekt!```")
|
||||
# else:
|
||||
# return await ctx.send(
|
||||
# "```{} got #rekt!```".format(flags))
|
||||
# else:
|
||||
# process_msg = await ctx.send(
|
||||
# "<a:typing:401162479041773568> Processing...")
|
||||
# process = subprocess.Popen(
|
||||
# f"git {sub + flags}",
|
||||
# stdout=subprocess.PIPE,
|
||||
# stderr=subprocess.PIPE)
|
||||
# res = process.communicate()
|
||||
# if res[0] == b'':
|
||||
# content = "Successful!"
|
||||
# else:
|
||||
# content = res[0].decode("utf8")
|
||||
# return await process_msg.edit(content=f"```{content}```")
|
||||
|
||||
@commands.command()
|
||||
async def rejson(self, ctx):
|
||||
'''Refreshes the list of instances for searx.'''
|
||||
|
||||
msg = await ctx.send('<a:updating:403035325242540032> Refreshing instance list...\n\n'
|
||||
'(Due to extensive quality checks, this may take a bit.)')
|
||||
plausible = []
|
||||
|
||||
# Get, parse, and quality check all instances
|
||||
async with self.request.get('https://searx.space/data/instances.json') as r:
|
||||
# Parsing
|
||||
searx_json = await r.json()
|
||||
instances = searx_json['instances']
|
||||
|
||||
# Quality Check
|
||||
for i in instances:
|
||||
info = instances.get(i)
|
||||
is_good = await self._instance_check(i, info)
|
||||
if is_good:
|
||||
plausible.append(i)
|
||||
|
||||
# Save new list
|
||||
self.instances = plausible
|
||||
with open('searxes.txt', 'w') as f:
|
||||
f.write('\n'.join(plausible))
|
||||
|
||||
await msg.edit(content='Instances refreshed!')
|
||||
|
||||
@commands.command(aliases=['exit', 'reboot'])
|
||||
async def restart(self, ctx):
|
||||
await ctx.send(':zzz: **Restarting.**')
|
||||
exit()
|
||||
|
||||
# @commands.command()
|
||||
# async def stats(self, ctx):
|
||||
# mem = psutil.virtual_memory()
|
||||
# currproc = psutil.Process(os.getpid())
|
||||
# total_ram = self.humanbytes(mem[0])
|
||||
# available_ram = self.humanbytes(mem[1])
|
||||
# usage = self.humanbytes(currproc.memory_info().rss)
|
||||
# text = f"""
|
||||
# ```
|
||||
# Total RAM: {total_ram}
|
||||
# Available RAM: {available_ram}
|
||||
# RAM used by bot: {usage}
|
||||
# Number of bot commands: {len(ctx.bot.commands)}
|
||||
# Number of extensions present: {len(ctx.bot.cogs)}
|
||||
# Number of users: {len(ctx.bot.users)}
|
||||
# ```
|
||||
# """
|
||||
# await ctx.send(text)
|
||||
|
||||
@commands.command(hidden=True)
|
||||
async def error(self, ctx):
|
||||
"""Makes the bot error out."""
|
||||
|
||||
3 / 0
|
||||
|
||||
async def cog_check(self, ctx):
|
||||
|
||||
return (ctx.author.id == self.bot.appinfo.owner.id)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Developer(bot))
|
||||
bot.add_cog(Developer(bot))
|
||||
|
|
|
@ -11,8 +11,9 @@ import aiohttp
|
|||
import random
|
||||
import sys
|
||||
|
||||
|
||||
class Search(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.request = bot.request
|
||||
self.instances = bot.instances
|
||||
|
@ -52,8 +53,8 @@ class Search(commands.Cog):
|
|||
|
||||
# Error Template
|
||||
error_msg = ("**An error occured!**\n\n"
|
||||
f"There was a problem with `{instance}`. Please try again later.\n"
|
||||
f"_If problems with this instance persist, contact`{self.bot.appinfo.owner}` to have it removed._")
|
||||
f"There was a problem with `{instance}`. Please try again later.\n"
|
||||
f"_If problems with this instance persist, contact`{self.bot.appinfo.owner}` to have it removed._")
|
||||
|
||||
# Create the URL to make an API call to
|
||||
call = f'{instance}/search?q={query}&format=json&language=en-US'
|
||||
|
@ -103,17 +104,54 @@ class Search(commands.Cog):
|
|||
[f"**{entry['title']}** <{entry['url']}>" for entry in results[1:5]])
|
||||
# Instance Info
|
||||
msg += f"\n\n_Results retrieved from instance `{instance}`._"
|
||||
|
||||
|
||||
# Reached if error with returned results
|
||||
except (KeyError, IndexError) as e:
|
||||
# Logging
|
||||
print(f"{e} with instance {instance}, trying again.")
|
||||
|
||||
self.instances.remove(instance) # Weed the instance out
|
||||
return await self._search_logic(query, is_nsfw) # Recurse until good response
|
||||
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.'''
|
||||
|
||||
# Makes sure proper values exist
|
||||
if 'error' in info:
|
||||
return False
|
||||
if not ('engines' in info and 'initial' in info['timing']):
|
||||
return False
|
||||
if not ('google' in info['engines'] and 'enabled' in info['engines']['google']):
|
||||
return False
|
||||
|
||||
# Makes sure google is enabled
|
||||
if not info['engines']['google']['enabled']:
|
||||
return False
|
||||
|
||||
# Makes sure is not Tor
|
||||
if info['network_type'] != 'normal':
|
||||
return False
|
||||
|
||||
# Only picks instances that are fast enough
|
||||
timing = int(info['timing']['initial'])
|
||||
if timing > 0.20:
|
||||
return False
|
||||
|
||||
# Check for Google captcha
|
||||
test_search = f'{instance}/search?q=test&format=json&lang=en-US'
|
||||
try:
|
||||
async with self.request.get(test_search) as resp:
|
||||
response = await resp.json()
|
||||
response['results'][0]['content']
|
||||
except (aiohttp.ClientError, KeyError, IndexError):
|
||||
return False
|
||||
|
||||
# Reached if passes all checks
|
||||
return True
|
||||
|
||||
@commands.command()
|
||||
async def search(self, ctx, *, query: str):
|
||||
"""Search online for results."""
|
||||
|
@ -126,12 +164,41 @@ class Search(commands.Cog):
|
|||
msg = await self._search_logic(query, ctx.channel.is_nsfw())
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command()
|
||||
@commands.is_owner()
|
||||
async def rejson(self, ctx):
|
||||
'''Refreshes the list of instances for searx.'''
|
||||
|
||||
msg = await ctx.send('<a:updating:403035325242540032> Refreshing instance list...\n\n'
|
||||
'(Due to extensive quality checks, this may take a bit.)')
|
||||
plausible = []
|
||||
|
||||
# Get, parse, and quality check all instances
|
||||
async with self.request.get('https://searx.space/data/instances.json') as r:
|
||||
# Parsing
|
||||
searx_json = await r.json()
|
||||
instances = searx_json['instances']
|
||||
|
||||
# Quality Check
|
||||
for i in instances:
|
||||
info = instances.get(i)
|
||||
is_good = await self._instance_check(i, info)
|
||||
if is_good:
|
||||
plausible.append(i)
|
||||
|
||||
# Save new list
|
||||
self.instances = plausible
|
||||
with open('searxes.txt', 'w') as f:
|
||||
f.write('\n'.join(plausible))
|
||||
|
||||
await msg.edit(content='Instances refreshed!')
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx, error):
|
||||
"""Listener makes no command fallback to searching."""
|
||||
|
||||
if isinstance(error, commands.CommandNotFound) or \
|
||||
isinstance(error, commands.CheckFailure):
|
||||
isinstance(error, commands.CheckFailure):
|
||||
# Logging
|
||||
print(f"\n\nNEW CALL: {ctx.author} from {ctx.guild}.\n")
|
||||
|
||||
|
@ -147,4 +214,4 @@ class Search(commands.Cog):
|
|||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Search(bot))
|
||||
bot.add_cog(Search(bot))
|
||||
|
|
35
main.py
35
main.py
|
@ -21,7 +21,7 @@ class Bot(commands.Bot):
|
|||
'''Custom Bot Class that overrides the commands.ext one'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super().__init__(self.get_prefix_new, **options)
|
||||
super().__init__(self._get_prefix_new, **options)
|
||||
print('Performing initialization...\n')
|
||||
|
||||
# Get Config Values
|
||||
|
@ -37,7 +37,9 @@ class Bot(commands.Bot):
|
|||
|
||||
print('Initialization complete.\n\n')
|
||||
|
||||
async def get_prefix_new(self, bot, msg):
|
||||
async def _get_prefix_new(self, bot, msg):
|
||||
'''Full flexible check for prefix.'''
|
||||
|
||||
if isinstance(msg.channel, discord.DMChannel) and self.config['PREFIXLESS_DMS']:
|
||||
plus_none = self.prefix.copy()
|
||||
plus_none.append('')
|
||||
|
@ -45,18 +47,11 @@ class Bot(commands.Bot):
|
|||
else:
|
||||
return commands.when_mentioned_or(*self.prefix)(bot, msg)
|
||||
|
||||
def init_extensions(self):
|
||||
for ext in os.listdir('extensions'):
|
||||
if ext.endswith('.py'):
|
||||
self.load_extension(f'extensions.{ext[:-3]}')
|
||||
|
||||
async def start(self, *args, **kwargs):
|
||||
async with aiohttp.ClientSession() as self.request:
|
||||
self.init_extensions()
|
||||
await super().start(*args, **kwargs)
|
||||
|
||||
async def on_ready(self):
|
||||
self.request = aiohttp.ClientSession()
|
||||
self.appinfo = await self.application_info()
|
||||
# EXTENSION ENTRY POINT
|
||||
self.load_extension('extensions.core')
|
||||
|
||||
msg = "CONNECTED!\n"
|
||||
msg += "-----------------------------\n"
|
||||
|
@ -92,24 +87,8 @@ bot = Bot(
|
|||
case_insensitive=True)
|
||||
|
||||
|
||||
@bot.command(aliases=['info', 'source', 'server'])
|
||||
async def about(ctx):
|
||||
'''Returns information about this bot.'''
|
||||
appinfo = await bot.application_info()
|
||||
|
||||
msg = f"**{bot.description}**\n"
|
||||
msg += f"Created by **taciturasa#4365**, this instance by **{appinfo.owner}.**\n\n"
|
||||
msg += "**Source Code:** _<https://github.com/taciturasa/searchbot-discord>_\n"
|
||||
msg += "**Support Server:** _<https://discord.gg/4BpReNV>_\n"
|
||||
msg += "_Note: Please attempt to contact the hoster of any separate instances before this server._\n\n"
|
||||
msg += f"_See **{ctx.prefix}** `help` for help, and `stats` for statistics._"
|
||||
|
||||
await ctx.send(msg)
|
||||
|
||||
|
||||
@bot.listen()
|
||||
async def on_command_error(ctx, error):
|
||||
|
||||
if isinstance(error, commands.CommandNotFound):
|
||||
return
|
||||
elif isinstance(error, commands.CommandInvokeError):
|
||||
|
|
Loading…
Reference in New Issue