391 lines
14 KiB
Python
391 lines
14 KiB
Python
import discord
|
|
from discord.ext import commands
|
|
|
|
import utils
|
|
|
|
import random
|
|
import re
|
|
import calendar
|
|
import inspect
|
|
import io
|
|
import os
|
|
import pendulum
|
|
import datetime
|
|
import psutil
|
|
|
|
|
|
def _command_signature(cmd):
|
|
result = [cmd.qualified_name]
|
|
if cmd.usage:
|
|
result.append(cmd.usage)
|
|
return " ".join(result)
|
|
|
|
params = cmd.clean_params
|
|
if not params:
|
|
return " ".join(result)
|
|
|
|
for name, param in params.items():
|
|
if param.default is not param.empty:
|
|
# We don't want None or '' to trigger the [name=value] case and instead it should
|
|
# do [name] since [name=None] or [name=] are not exactly useful for the user.
|
|
should_print = (
|
|
param.default
|
|
if isinstance(param.default, str)
|
|
else param.default is not None
|
|
)
|
|
if should_print:
|
|
result.append(f"[{name}={param.default!r}]")
|
|
else:
|
|
result.append(f"[{name}]")
|
|
elif param.kind == param.VAR_POSITIONAL:
|
|
result.append(f"[{name}...]")
|
|
else:
|
|
result.append(f"<{name}>")
|
|
|
|
return " ".join(result)
|
|
|
|
|
|
class Miscellaneous(commands.Cog):
|
|
"""Core commands, these are the miscallaneous commands that don't fit into other categories'"""
|
|
|
|
process = psutil.Process()
|
|
process.cpu_percent()
|
|
|
|
@commands.command()
|
|
@commands.cooldown(1, 3, commands.cooldowns.BucketType.user)
|
|
@utils.can_run(send_messages=True)
|
|
async def help(self, ctx, *, command: str = None):
|
|
"""Shows help about a command or the bot"""
|
|
try:
|
|
if command is None:
|
|
p = await utils.HelpPaginator.from_bot(ctx)
|
|
else:
|
|
entity = ctx.bot.get_cog(command) or ctx.bot.get_command(command)
|
|
|
|
if entity is None:
|
|
clean = command.replace("@", "@\u200b")
|
|
return await ctx.send(f'Command or category "{clean}" not found.')
|
|
elif isinstance(entity, commands.Command):
|
|
p = await utils.HelpPaginator.from_command(ctx, entity)
|
|
else:
|
|
p = await utils.HelpPaginator.from_cog(ctx, entity)
|
|
|
|
await p.paginate()
|
|
except utils.CannotPaginate:
|
|
await ctx.send(
|
|
"I need embed links permissions in order to do this command :("
|
|
)
|
|
|
|
@commands.command()
|
|
@utils.can_run(send_messages=True)
|
|
async def ping(self, ctx):
|
|
"""Returns the latency between the server websocket, and between reading messages"""
|
|
msg_latency = datetime.datetime.utcnow() - ctx.message.created_at
|
|
fmt = "Message latency {0:.2f} seconds".format(
|
|
msg_latency.seconds + msg_latency.microseconds / 1000000
|
|
)
|
|
fmt += "\nWebsocket latency {0:.2f} seconds".format(ctx.bot.latency)
|
|
await ctx.send(fmt)
|
|
|
|
@commands.command(aliases=["coin"])
|
|
@utils.can_run(send_messages=True)
|
|
async def coinflip(self, ctx):
|
|
"""Flips a coin and responds with either heads or tails
|
|
|
|
EXAMPLE: !coinflip
|
|
RESULT: Heads!"""
|
|
|
|
result = "Heads!" if random.SystemRandom().randint(0, 1) else "Tails!"
|
|
await ctx.send(result)
|
|
|
|
@commands.command()
|
|
@utils.can_run(send_messages=True)
|
|
async def say(self, ctx, *, msg: str):
|
|
"""Tells the bot to repeat what you say
|
|
|
|
EXAMPLE: !say I really like orange juice
|
|
RESULT: I really like orange juice"""
|
|
fmt = "\u200B{}".format(msg)
|
|
await ctx.send(fmt)
|
|
try:
|
|
await ctx.message.delete()
|
|
except Exception:
|
|
pass
|
|
|
|
@commands.command()
|
|
@utils.can_run(send_messages=True)
|
|
async def calendar(self, ctx, month: str = None, year: int = None):
|
|
"""Provides a printout of the current month's calendar
|
|
Provide month and year to print the calendar of that year and month
|
|
|
|
EXAMPLE: !calendar january 2011"""
|
|
|
|
# calendar takes in a number for the month, not the words
|
|
# so we need this dictionary to transform the word to the number
|
|
months = {
|
|
"january": 1,
|
|
"february": 2,
|
|
"march": 3,
|
|
"april": 4,
|
|
"may": 5,
|
|
"june": 6,
|
|
"july": 7,
|
|
"august": 8,
|
|
"september": 9,
|
|
"october": 10,
|
|
"november": 11,
|
|
"december": 12,
|
|
}
|
|
# In month was not passed, use the current month
|
|
if month is None:
|
|
month = datetime.date.today().month
|
|
else:
|
|
month = months.get(month.lower())
|
|
if month is None:
|
|
await ctx.send("Please provide a valid Month!")
|
|
return
|
|
# If year was not passed, use the current year
|
|
if year is None:
|
|
year = datetime.datetime.today().year
|
|
# Here we create the actual "text" calendar that we are printing
|
|
cal = calendar.TextCalendar().formatmonth(year, month)
|
|
await ctx.send("```\n{}```".format(cal))
|
|
|
|
@commands.command(aliases=["about"])
|
|
@utils.can_run(send_messages=True)
|
|
async def info(self, ctx):
|
|
"""This command can be used to print out some of my information"""
|
|
# fmt is a dictionary so we can set the key to it's output, then print both
|
|
# The only real use of doing it this way is easier editing if the info
|
|
# in this command is changed
|
|
|
|
# Create the original embed object
|
|
# Set the description include dev server (should be required) and the optional patreon link
|
|
description = "[Dev Server]({})".format(utils.dev_server)
|
|
if utils.patreon_link:
|
|
description += "\n[Patreon]({})".format(utils.patreon_link)
|
|
# Now creat the object
|
|
opts = {
|
|
"title": "Bonfire",
|
|
"description": description,
|
|
"colour": discord.Colour.green(),
|
|
}
|
|
|
|
# Set the owner
|
|
embed = discord.Embed(**opts)
|
|
if hasattr(ctx.bot, "owner"):
|
|
embed.set_author(name=str(ctx.bot.owner), icon_url=ctx.bot.owner.avatar_url)
|
|
|
|
# Setup the process statistics
|
|
name = "Process statistics"
|
|
value = ""
|
|
|
|
memory_usage = self.process.memory_full_info().uss / 1024 ** 2
|
|
cpu_usage = self.process.cpu_percent()
|
|
value += "Memory: {:.2f} MiB".format(memory_usage)
|
|
value += "\nCPU: {}%".format(cpu_usage)
|
|
if hasattr(ctx.bot, "uptime"):
|
|
value += "\nUptime: {}".format(
|
|
(pendulum.now(tz="UTC") - ctx.bot.uptime).in_words()
|
|
)
|
|
embed.add_field(name=name, value=value, inline=False)
|
|
|
|
# Setup the user and guild statistics
|
|
name = "User/Guild statistics"
|
|
value = ""
|
|
|
|
value += "Channels: {}".format(len(list(ctx.bot.get_all_channels())))
|
|
value += "\nUsers: {}".format(len(ctx.bot.users))
|
|
value += "\nServers: {}".format(len(ctx.bot.guilds))
|
|
embed.add_field(name=name, value=value, inline=False)
|
|
|
|
# The game statistics
|
|
name = "Game statistics"
|
|
# To get the newlines right, since we're not sure what will and won't be included
|
|
# Lets make this one a list and join it at the end
|
|
value = []
|
|
|
|
hm = ctx.bot.get_cog("Hangman")
|
|
ttt = ctx.bot.get_cog("TicTacToe")
|
|
bj = ctx.bot.get_cog("Blackjack")
|
|
interaction = ctx.bot.get_cog("Interaction")
|
|
|
|
if hm:
|
|
value.append("Hangman games: {}".format(len(hm.games)))
|
|
if ttt:
|
|
value.append("TicTacToe games: {}".format(len(ttt.boards)))
|
|
if bj:
|
|
value.append("Blackjack games: {}".format(len(bj.games)))
|
|
if interaction:
|
|
count_battles = 0
|
|
for battles in ctx.bot.get_cog("Interaction").battles.values():
|
|
count_battles += len(battles)
|
|
value.append("Battles running: {}".format(len(bj.games)))
|
|
embed.add_field(name=name, value="\n".join(value), inline=False)
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
@commands.command()
|
|
@utils.can_run(send_messages=True)
|
|
async def uptime(self, ctx):
|
|
"""Provides a printout of the current bot's uptime
|
|
|
|
EXAMPLE: !uptime
|
|
RESULT: A BAJILLION DAYS"""
|
|
if hasattr(ctx.bot, "uptime"):
|
|
await ctx.send(
|
|
"Uptime: ```\n{}```".format(
|
|
(pendulum.now(tz="UTC") - ctx.bot.uptime).in_words()
|
|
)
|
|
)
|
|
else:
|
|
await ctx.send(
|
|
"I've just restarted and not quite ready yet...gimme time I'm not a morning pony :c"
|
|
)
|
|
|
|
@commands.command(aliases=["invite"])
|
|
@utils.can_run(send_messages=True)
|
|
async def addbot(self, ctx):
|
|
"""Provides a link that you can use to add me to a server
|
|
|
|
EXAMPLE: !addbot
|
|
RESULT: http://discord.gg/yo_mama"""
|
|
perms = discord.Permissions.none()
|
|
perms.read_messages = True
|
|
perms.send_messages = True
|
|
perms.manage_roles = True
|
|
perms.ban_members = True
|
|
perms.kick_members = True
|
|
perms.manage_messages = True
|
|
perms.embed_links = True
|
|
perms.read_message_history = True
|
|
perms.attach_files = True
|
|
perms.speak = True
|
|
perms.connect = True
|
|
perms.attach_files = True
|
|
perms.add_reactions = True
|
|
app_info = await ctx.bot.application_info()
|
|
await ctx.send(
|
|
"Use this URL to add me to a server that you'd like!\n<{}>".format(
|
|
discord.utils.oauth_url(app_info.id, perms)
|
|
)
|
|
)
|
|
|
|
@commands.command()
|
|
@utils.can_run(send_messages=True)
|
|
async def source(self, ctx, *, command: str = None):
|
|
"""Displays my full source code or for a specific command.
|
|
|
|
EXAMPLE: !source source
|
|
RESULTS: Shows the code for this command!
|
|
"""
|
|
source_url = "https://github.com/Phxntxm/Bonfire"
|
|
branch = "master"
|
|
if command is None:
|
|
return await ctx.send(source_url)
|
|
|
|
obj = ctx.bot.get_command(command)
|
|
if obj is None:
|
|
return await ctx.send(f"Could not find command {command}")
|
|
|
|
# Get source from the callback
|
|
src = obj.callback.__code__
|
|
lines, firstlineno = inspect.getsourcelines(src)
|
|
location = os.path.relpath(src.co_filename).replace("\\", "/")
|
|
|
|
final_url = f"<{source_url}/blob/{branch}/{location}#L{firstlineno}-L{firstlineno + len(lines) - 1}>"
|
|
# Provide the source as a file, for the preview file thing
|
|
src = io.StringIO(inspect.getsource(src))
|
|
await ctx.send(final_url, file=discord.File(src, filename=f"{command}.py"))
|
|
|
|
@commands.command(enabled=False)
|
|
@utils.can_run(send_messages=True)
|
|
async def joke(self, ctx):
|
|
"""Prints a random riddle
|
|
|
|
EXAMPLE: !joke
|
|
RESULT: An absolutely terrible joke."""
|
|
# Currently disabled until I can find a free API
|
|
pass
|
|
|
|
@commands.command()
|
|
@utils.can_run(send_messages=True)
|
|
async def roll(self, ctx, *, notation: str = "d6"):
|
|
"""Rolls a die based on the notation given
|
|
Format should be #d#
|
|
|
|
EXAMPLE: !roll d50
|
|
RESULT: 51 :^)"""
|
|
# Use regex to get the notation based on what was provided
|
|
try:
|
|
# We do not want to try to convert the dice, because we want d# to
|
|
# be a valid notation
|
|
dice = re.search(r"(\d*)d(\d*)", notation).group(1)
|
|
num = int(re.search(r"(\d*)d(\d*)", notation).group(2))
|
|
# Attempt to get addition/subtraction
|
|
add = re.search(r"\+ ?(\d+)", notation)
|
|
subtract = re.search(r"- ?(\d+)", notation)
|
|
# Check if something like ed3 was provided, or something else entirely
|
|
# was provided
|
|
except (AttributeError, ValueError):
|
|
await ctx.send("Please provide the die notation in #d#!")
|
|
return
|
|
|
|
# Dice will be None if d# was provided, assume this means 1d#
|
|
dice = dice or 1
|
|
# Since we did not try to convert to int before, do it now after we
|
|
# have it set
|
|
dice = int(dice)
|
|
if dice > 30:
|
|
await ctx.send("I'm not rolling more than 30 dice, I have tiny hands")
|
|
return
|
|
if num > 100:
|
|
await ctx.send("What die has more than 100 sides? Please, calm down")
|
|
return
|
|
if num <= 1:
|
|
await ctx.send(
|
|
"A {} sided die? You know that's impossible right?".format(num)
|
|
)
|
|
return
|
|
|
|
nums = [random.SystemRandom().randint(1, num) for _ in range(0, int(dice))]
|
|
subtotal = total = sum(nums)
|
|
# After totalling, if we have add/subtract seperately, apply them
|
|
if add:
|
|
add = int(add.group(1))
|
|
total += add
|
|
if subtract:
|
|
subtract = int(subtract.group(1))
|
|
total -= subtract
|
|
value_str = ", ".join("{}".format(x) for x in nums)
|
|
|
|
if dice == 1:
|
|
fmt = "{0.message.author.name} has rolled a {1} sided die and got the number {2}!".format(
|
|
ctx, num, value_str
|
|
)
|
|
if add or subtract:
|
|
fmt += "\nTotal: {} ({}".format(total, subtotal)
|
|
if add:
|
|
fmt += " + {}".format(add)
|
|
if subtract:
|
|
fmt += " - {}".format(subtract)
|
|
fmt += ")"
|
|
else:
|
|
fmt = "{0.message.author.name} has rolled {1}, {2} sided dice and got the numbers {3}!".format(
|
|
ctx, dice, num, value_str
|
|
)
|
|
if add or subtract:
|
|
fmt += "\nTotal: {} ({}".format(total, subtotal)
|
|
if add:
|
|
fmt += " + {}".format(add)
|
|
if subtract:
|
|
fmt += " - {}".format(subtract)
|
|
fmt += ")"
|
|
else:
|
|
fmt += "\nTotal: {}".format(total)
|
|
await ctx.send(fmt)
|
|
|
|
|
|
def setup(bot):
|
|
bot.add_cog(Miscellaneous(bot))
|