Browse Source

change all tabs to 4 spaces. I have purged the tabs. It helps edit in nano easier.

tags/v2.2.0
Roxie Gibson 5 years ago
parent
commit
58276d2799
25 changed files with 4168 additions and 4168 deletions
  1. +79
    -79
      roxbot.py
  2. +14
    -14
      roxbot/__init__.py
  3. +13
    -13
      roxbot/checks.py
  4. +389
    -389
      roxbot/cogs/admin.py
  5. +66
    -66
      roxbot/cogs/ags.py
  6. +276
    -276
      roxbot/cogs/customcommands.py
  7. +569
    -569
      roxbot/cogs/fun.py
  8. +424
    -424
      roxbot/cogs/image.py
  9. +136
    -136
      roxbot/cogs/joinleave.py
  10. +121
    -121
      roxbot/cogs/nsfw.py
  11. +249
    -249
      roxbot/cogs/reddit.py
  12. +141
    -141
      roxbot/cogs/selfassign.py
  13. +179
    -179
      roxbot/cogs/util.py
  14. +564
    -564
      roxbot/cogs/voice.py
  15. +2
    -2
      roxbot/config.py
  16. +44
    -44
      roxbot/converters.py
  17. +523
    -523
      roxbot/core.py
  18. +22
    -22
      roxbot/db.py
  19. +10
    -10
      roxbot/enums.py
  20. +12
    -12
      roxbot/exceptions.py
  21. +76
    -76
      roxbot/http.py
  22. +47
    -47
      roxbot/menu.py
  23. +56
    -56
      roxbot/roxbotfacts.py
  24. +68
    -68
      roxbot/scripts/JSONtoDB.py
  25. +88
    -88
      roxbot/utils.py

+ 79
- 79
roxbot.py View File

@@ -38,26 +38,26 @@ from roxbot.scripts import JSONtoDB


class term:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
fHEADER = HEADER + "{}" + ENDC
fOKBLUE = OKBLUE + "{}" + ENDC
fOKGREEN = OKGREEN + "{}" + ENDC
fWARNING = WARNING + "{}" + ENDC
fFAIL = FAIL + "{}" + ENDC
fBOLD = BOLD + "{}" + ENDC
fUNDERLINE = UNDERLINE + "{}" + ENDC
seperator = "================================"
title = """ ____ _ _
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
fHEADER = HEADER + "{}" + ENDC
fOKBLUE = OKBLUE + "{}" + ENDC
fOKGREEN = OKGREEN + "{}" + ENDC
fWARNING = WARNING + "{}" + ENDC
fFAIL = FAIL + "{}" + ENDC
fBOLD = BOLD + "{}" + ENDC
fUNDERLINE = UNDERLINE + "{}" + ENDC
seperator = "================================"
title = """ ____ _ _
| _ \ _____ _| |__ ___ | |_
| |_) / _ \ \/ / '_ \ / _ \| __|
| _ < (_) > <| |_) | (_) | |_
@@ -72,96 +72,96 @@ class term:


bot = core.Roxbot(
command_prefix=roxbot.command_prefix,
description=roxbot.__description__,
owner_id=roxbot.owner,
activity=discord.Game(name="v{}".format(roxbot.__version__), type=0),
case_insensitive=True
command_prefix=roxbot.command_prefix,
description=roxbot.__description__,
owner_id=roxbot.owner,
activity=discord.Game(name="v{}".format(roxbot.__version__), type=0),
case_insensitive=True
)


@bot.event
async def on_ready():
print("Logged in as: {}".format(term.fOKGREEN.format(str(bot.user))), end="\n\n")
print("Logged in as: {}".format(term.fOKGREEN.format(str(bot.user))), end="\n\n")

print("Guilds in: [{}]".format(len(bot.guilds)))
for guild in bot.guilds:
print(guild)
print("Guilds in: [{}]".format(len(bot.guilds)))
for guild in bot.guilds:
print(guild)

roxbot.scripts.JSONtoDB.check_convert(bot.guilds)
roxbot.scripts.JSONtoDB.check_convert(bot.guilds)


@bot.event
async def on_guild_join(guild):
db.populate_single_settings(bot)
db.populate_single_settings(bot)


@bot.event
async def on_guild_remove(guild):
db.delete_single_settings(guild)
db.delete_single_settings(guild)


@bot.check
def check_blacklist(ctx):
"""Adds global check to the bot to check for a user being blacklisted."""
return not bot.blacklisted(ctx.author)
"""Adds global check to the bot to check for a user being blacklisted."""
return not bot.blacklisted(ctx.author)


@bot.command()
async def about(ctx):
"""
Outputs info about RoxBot, showing up time, how to report issues, contents of roxbot.conf and credits.
"""
owner = bot.get_user(roxbot.owner)
em = discord.Embed(title="About Roxbot", colour=roxbot.EmbedColours.pink, description=roxbot.__description__)
em.set_thumbnail(url=bot.user.avatar_url)
em.add_field(name="Bot Version", value=roxbot.__version__)
em.add_field(name="Discord.py version", value=discord.__version__)
em.add_field(name="Owner", value=str(owner))
em.add_field(name="Owner ID", value=roxbot.owner)
em.add_field(name="Command Prefix", value=roxbot.command_prefix)
em.add_field(name="Backup Enabled", value=roxbot.backup_enabled)
if roxbot.backup_enabled:
em.add_field(name="Backup Rate", value="{} Minutes".format(int(roxbot.backup_rate/60)))
em.add_field(name="Author", value=roxbot.__author__)
# Do time calc late in the command so that the time returned is closest to when the message is received
uptimeflo = time.time() - start_time
uptime = str(datetime.timedelta(seconds=uptimeflo))
em.add_field(name="Current Uptime", value=str(uptime.split(".")[0]))
em.set_footer(text="RoxBot is licensed under the MIT License")
return await ctx.channel.send(embed=em)
"""
Outputs info about RoxBot, showing up time, how to report issues, contents of roxbot.conf and credits.
"""
owner = bot.get_user(roxbot.owner)
em = discord.Embed(title="About Roxbot", colour=roxbot.EmbedColours.pink, description=roxbot.__description__)
em.set_thumbnail(url=bot.user.avatar_url)
em.add_field(name="Bot Version", value=roxbot.__version__)
em.add_field(name="Discord.py version", value=discord.__version__)
em.add_field(name="Owner", value=str(owner))
em.add_field(name="Owner ID", value=roxbot.owner)
em.add_field(name="Command Prefix", value=roxbot.command_prefix)
em.add_field(name="Backup Enabled", value=roxbot.backup_enabled)
if roxbot.backup_enabled:
em.add_field(name="Backup Rate", value="{} Minutes".format(int(roxbot.backup_rate/60)))
em.add_field(name="Author", value=roxbot.__author__)
# Do time calc late in the command so that the time returned is closest to when the message is received
uptimeflo = time.time() - start_time
uptime = str(datetime.timedelta(seconds=uptimeflo))
em.add_field(name="Current Uptime", value=str(uptime.split(".")[0]))
em.set_footer(text="RoxBot is licensed under the MIT License")
return await ctx.channel.send(embed=em)


if __name__ == "__main__":
start_time = time.time()
print(term.fHEADER.format(term.fBOLD.format(term.title)))
start_time = time.time()
print(term.fHEADER.format(term.fBOLD.format(term.title)))

print("Roxbot version: " + term.fOKBLUE.format(roxbot.__version__))
print("Discord.py version: " + term.fOKBLUE.format(discord.__version__))
print("Roxbot version: " + term.fOKBLUE.format(roxbot.__version__))
print("Discord.py version: " + term.fOKBLUE.format(discord.__version__))

print(term.seperator)
print(term.seperator)

print("Loading core...", end="\r")
print("Loading core...", end="\r")

bot.load_extension("roxbot.core")
print("Loaded core.py")
print(term.seperator)
bot.load_extension("roxbot.core")
print("Loaded core.py")
print(term.seperator)

# Load Extension Cogs
print("Cogs Loaded:")
for cog in roxbot.cog_list:
try:
bot.load_extension(cog)
print(cog.split(".")[2])
except ImportError:
print("{} FAILED TO LOAD. MISSING REQUIREMENTS".format(cog.split(".")[2]))
# Load Extension Cogs
print("Cogs Loaded:")
for cog in roxbot.cog_list:
try:
bot.load_extension(cog)
print(cog.split(".")[2])
except ImportError:
print("{} FAILED TO LOAD. MISSING REQUIREMENTS".format(cog.split(".")[2]))

bot.loop.create_task(db.populate_db(bot))
bot.loop.create_task(db.populate_db(bot))

print(term.seperator)
print("Client logging in...", end="\r")
print(term.seperator)
print("Client logging in...", end="\r")

bot.run(roxbot.token)
bot.run(roxbot.token)

+ 14
- 14
roxbot/__init__.py View File

@@ -38,27 +38,27 @@ token = config["Tokens"]["Discord"]
imgur_token = config["Tokens"]["Imgur"]

if config["Backups"]["enabled"] == "False":
backup_enabled = False
backup_enabled = False
else:
backup_enabled = True
backup_enabled = True
backup_rate = int(config["Backups"]["rate"]) * 60 # Convert minutes to seconds

dev_mode = False
datetime = "{:%a %Y/%m/%d %H:%M:%S} UTC"

cog_list = [
"roxbot.cogs.admin",
"roxbot.cogs.customcommands",
"roxbot.cogs.fun",
"roxbot.cogs.image",
"roxbot.cogs.joinleave",
"roxbot.cogs.nsfw",
"roxbot.cogs.reddit",
"roxbot.cogs.selfassign",
"roxbot.cogs.trivia",
"roxbot.cogs.util",
"roxbot.cogs.voice",
#"roxbot.cogs.ags"
"roxbot.cogs.admin",
"roxbot.cogs.customcommands",
"roxbot.cogs.fun",
"roxbot.cogs.image",
"roxbot.cogs.joinleave",
"roxbot.cogs.nsfw",
"roxbot.cogs.reddit",
"roxbot.cogs.selfassign",
"roxbot.cogs.trivia",
"roxbot.cogs.util",
"roxbot.cogs.voice",
#"roxbot.cogs.ags"
]

import logging

+ 13
- 13
roxbot/checks.py View File

@@ -31,19 +31,19 @@ from discord.ext import commands


def has_permissions_or_owner(**perms):
def pred(ctx):
return roxbot.utils.has_permissions_or_owner(ctx, **perms)
return commands.check(pred)
def pred(ctx):
return roxbot.utils.has_permissions_or_owner(ctx, **perms)
return commands.check(pred)


def is_nsfw():
"""A :func:`.check` that checks if the channel is a NSFW channel or a DM channel."""
def pred(ctx):
is_dm_channel = bool(isinstance(ctx.channel, discord.DMChannel))
is_nsfw_guild_channel = bool(isinstance(ctx.channel, discord.TextChannel) and ctx.channel.is_nsfw())
if is_nsfw_guild_channel:
with db_session:
return bool(db.get("SELECT `enabled` FROM `NSFWSingle` WHERE `guild_id` = '{}'".format(ctx.guild.id)))
else:
return is_dm_channel
return commands.check(pred)
"""A :func:`.check` that checks if the channel is a NSFW channel or a DM channel."""
def pred(ctx):
is_dm_channel = bool(isinstance(ctx.channel, discord.DMChannel))
is_nsfw_guild_channel = bool(isinstance(ctx.channel, discord.TextChannel) and ctx.channel.is_nsfw())
if is_nsfw_guild_channel:
with db_session:
return bool(db.get("SELECT `enabled` FROM `NSFWSingle` WHERE `guild_id` = '{}'".format(ctx.guild.id)))
else:
return is_dm_channel
return commands.check(pred)

+ 389
- 389
roxbot/cogs/admin.py View File

@@ -33,401 +33,401 @@ from roxbot.db import *


class AdminSingle(db.Entity):
warning_limit = Required(int, default=0)
guild_id = Required(int, unique=True, size=64)
warning_limit = Required(int, default=0)
guild_id = Required(int, unique=True, size=64)


class AdminWarnings(db.Entity):
user_id = Required(int, size=64)
warned_by = Required(int, size=64)
warning = Optional(str)
date = Required(datetime.datetime, unique=True)
guild_id = Required(int, size=64)
user_id = Required(int, size=64)
warned_by = Required(int, size=64)
warning = Optional(str)
date = Required(datetime.datetime, unique=True)
guild_id = Required(int, size=64)


class Admin(commands.Cog):
"""
Admin Commands for those admins
"""
OK_SLOWMODE_ON = "Slowmode on :snail: ({} seconds)"
OK_SLOWMODE_OFF = "Slowmode off"
OK_SLOWMODE_CHANGED = "Slowmode set to :snail: ({} seconds)"
ERROR_SLOWMODE_SECONDS = "Rate limit has to be between 0-120."
OK_PURGE_CONFIRMATION = "{} message(s) purged from chat."
OK_WARN_ADD = "Reported {}."
OK_WARN_ADD_USER_LIMIT_DM = "{} has been reported {} time(s). This is a reminder that this is over the set limit of {}."
WARN_WARN_ADD_LIMIT_REACHED = "You can only warn a user {} times!"
OK_WARN_LIST_NO_WARNINGS = "No warnings on record."
OK_WARN_LIST_USER_NO_WARNINGS = "This user doesn't have any warning on record."
OK_WARN_REMOVE_REMOVED_WARNING = "Removed Warning {} from {}"
OK_WARN_REMOVE_REMOVED_WARNINGS = "Removed all warnings for {}"
WARN_WARN_REMOVE_USER_NOT_FOUND = "Could not find user {} in warning list."
ERROR_WARN_REMOVE_INDEXERROR = "Index Error: Warning index doesn't exist. User only has {} warning(s)."
ERROR_WARN_REMOVE_VALUEERROR = "Value Error: Please enter a valid index number."
OK_WARN_PRUNE_PRUNED = "Pruned {} banned users from the warn list."
OK_WARN_SL_SET = "Number of warnings needed to DM's set to {}"
OK_WARN_SL_SET_ZERO = "DM's to mods for warning limits disabled."
ERROR_WARN_SL_NEG = "number_of_warnings can only be a positive integer."
OK_MOD_ACTION = "{} with reason: '{}'"
WARN_MOD_LACK_PERMS = "Cannot kick owner or users higher or equal to me role hierarchy."
WARN_UNBAN_NOTFOUND = "User is not banned."
def __init__(self, bot_client):
self.bot = bot_client
self.autogen_db = AdminSingle
@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@commands.bot_has_permissions(manage_messages=True)
@commands.command()
async def slowmode(self, ctx, seconds: int):
"""Puts the channel in slowmode. Users with manage_channel or manage_messages permissions will not be effected.
Options:
- `seconds` - Has to be between 0 - 120. This will set the timeout a user receives once they send a message in this channel. If 0, Roxbot will disable slowmode.
Examples:
# Set slowmode to 30 seconds
;slowmode 30
# Turn slowmode off
;slowmode 0
"""
if seconds == 0: # Turn Slow Mode off
await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested to turn off slowmode.".format(ctx.author))
embed = discord.Embed(description=self.OK_SLOWMODE_OFF, colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
elif 0 < seconds <= 120 and ctx.channel.slowmode_delay == 0: # Turn Slow Mode On
await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested slowmode with a timer of {}".format(ctx.author, seconds))
embed = discord.Embed(description=self.OK_SLOWMODE_ON.format(seconds), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
elif 0 < seconds <= 120 and ctx.channel.slowmode_delay > 0: # Change value of Slow Mode timer
await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested slowmode timer be changed to {}".format(ctx.author, seconds))
embed = discord.Embed(description=self.OK_SLOWMODE_CHANGED.format(seconds), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
elif seconds < 0 or seconds > 120:
raise commands.BadArgument(self.ERROR_SLOWMODE_SECONDS)
@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@commands.bot_has_permissions(manage_messages=True, read_message_history=True)
@commands.cooldown(1, 5)
@commands.command()
async def purge(self, ctx, limit=0, *, author: roxbot.converters.User = None):
"""Purges the text channel the command is execture in. You can specify a certain user to purge as well.
Options:
- `limit` - This the the amount of messages Roxbot will take from the chat and purge. Note: This **does not** mean the amount that will be purged. Limit is the amount of messages Roxbot will look at. If a user is given, it will only delete messages from that user in that list of messages.
- `USER` - A name, ID, or mention of a user. If the user has left the guild, this **has** to be the ID.
Examples:
# Delete 20 messages from the chat
;purge 20
# Take 20 messages, and delete any message in it by user @Spammer
;purge 20 @Spammer
"""
# TODO: To sort out the limit == how many to delete for the author, and not just a limit.
if author:
predicate = lambda message: message.author.id == author.id and message.id != ctx.message.id
else:
predicate = lambda message: message.id != ctx.message.id
messages = await ctx.channel.purge(limit=limit, check=predicate)
embed = discord.Embed(description=self.OK_PURGE_CONFIRMATION.format(len(messages)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.has_permissions(kick_members=True)
@commands.group(case_insensitive=True)
async def warn(self, ctx):
"""The warn command group allows Discord moderators to warn users and log them within the bot.
The command group also supports setting limits to warn mods if a user has been warned over a certain threshold.
"""
if ctx.invoked_subcommand is None:
raise commands.CommandNotFound("Subcommand '{}' does not exist.".format(ctx.subcommand_passed))
@warn.command()
async def add(self, ctx, user: discord.User = None, *, warning=""):
"""Adds a warning to a user.
Options:
- `USER` - A name, ID, or mention of a user.
- `warning` - OPTIONAL. A reason for the warning. This supports markdown formatting.
Example:
# Add warning to user @Roxbot for being a meanie
;warn add @Roxbot "for being a meanie"
"""
# Warning in the settings is a dictionary of user ids. The user ids are equal to a list of dictionaries.
with db_session:
warning_limit = AdminSingle.get(guild_id=ctx.guild.id).warning_limit
user_warnings = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)[:]
amount_warnings = len(user_warnings)
warn_limit = 10
if amount_warnings > warn_limit:
embed = discord.Embed(description=self.WARN_WARN_ADD_LIMIT_REACHED.format(warn_limit), colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
with db_session:
AdminWarnings(user_id=user.id, warned_by=ctx.author.id, date=datetime.datetime.utcnow(), warning=warning, guild_id=ctx.guild.id)
if amount_warnings >= warning_limit > 0:
await ctx.author.send(self.OK_WARN_ADD_USER_LIMIT_DM.format(str(user), amount_warnings, warning_limit))
embed = discord.Embed(description=self.OK_WARN_ADD.format(str(user)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
@warn.command()
async def list(self, ctx, *, user: roxbot.converters.User = None):
"""Lists all warning in this guild or all the warnings for one user.
Options:
- `USER` - OPTIONAL. A name, ID, or mention of a user.
Examples:
# List all warnings in the guild
;warn list
# List all warnings for @Roxbot
;warn list @Roxbot
"""
if user is None:
paginator = commands.Paginator()
warnings = {}
with db_session:
for warning in select(warn for warn in AdminWarnings if warn.guild_id == ctx.guild.id)[:]:
if warning.user_id not in warnings:
warnings[warning.user_id] = []
else:
warnings[warning.user_id].append(warning)
for u, warning in warnings.items():
member_obj = discord.utils.get(ctx.guild.members, id=u)
if member_obj:
paginator.add_line("{}: {} Warning(s)".format(str(member_obj), len(warnings[u])))
else:
paginator.add_line("{}: {} Warning(s)".format(u, len(warnings[u])))
# This is in case we have removed some users from the list.
if not paginator.pages:
embed = discord.Embed(description=self.OK_WARN_LIST_NO_WARNINGS, colour=roxbot.EmbedColours.orange)
return await ctx.send(embed=embed)
for page in paginator.pages:
return await ctx.send(page)
else:
with db_session:
user_warnings = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id).order_by(AdminWarnings.date)[:]
if not user_warnings:
embed = discord.Embed(description=self.OK_WARN_LIST_USER_NO_WARNINGS, colour=roxbot.EmbedColours.orange)
return await ctx.send(embed=embed)
em = discord.Embed(title="Warnings for {}".format(str(user)), colour=roxbot.EmbedColours.pink)
em.set_thumbnail(url=user.avatar_url)
x = 1
for warning in user_warnings:
try:
warned_by = str(ctx.guild.get_member(warning.warned_by))
if warned_by is None:
warned_by = str(await self.bot.get_user_info(warning.warned_by))
except discord.ext.commands.CommandInvokeError:
warned_by = warning.warned_by
date = datetime.datetime.strftime(warning.date, roxbot.datetime.strip("{:} UTC")+" UTC")
em.add_field(name="Warning %s" % x, value="Warned by: {}\nTime: {}\nReason: {}".format(warned_by, date, warning.warning))
x += 1
return await ctx.send(embed=em)
@warn.command()
async def remove(self, ctx, user: roxbot.converters.User, index=None):
"""Removes one or all of the warnings for a user.
Options:=
- `USER` - A name, ID, or mention of a user.
- `index` - OPTIONAL. The index of the single warning you want to remove.
Examples:
# Remove all warnings for Roxbot
;warn remove Roxbot
# Remove warning 2 for Roxbot
;warn remove Roxbot 2
"""
with db_session:
if index:
try:
index = int(index) - 1
query = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
if query:
user_warnings = query[:]
else:
raise KeyError
user_warnings[index].delete()
embed = discord.Embed(description=self.OK_WARN_REMOVE_REMOVED_WARNING.format(index+1, str(user)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except Exception as e:
embed = discord.Embed(colour=roxbot.EmbedColours.red)
if isinstance(e, IndexError):
embed.description = self.ERROR_WARN_REMOVE_INDEXERROR.format(len(user_warnings))
elif isinstance(e, KeyError):
embed.description = self.WARN_WARN_REMOVE_USER_NOT_FOUND.format(str(user))
elif isinstance(e, ValueError):
embed.description = self.ERROR_WARN_REMOVE_VALUEERROR
else:
raise e
return await ctx.send(embed=embed)
else:
query = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
if query.exists():
delete(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
embed = discord.Embed(description=self.OK_WARN_REMOVE_REMOVED_WARNINGS.format(str(user)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
else:
embed = discord.Embed(description=self.WARN_WARN_REMOVE_USER_NOT_FOUND.format(str(user)), colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
@commands.bot_has_permissions(ban_members=True)
@warn.command()
async def prune(self, ctx, dry_run=0):
"""Prunes the warnings of any banned users.
You can add a 1 at the end to dryrun the command. This will show you how many would be deleted without deleting them.
Options:
- `dryrun` - Add `1` to the end of the command to do a dryrun of the prune command.
Examples:
# Prune the warnings of banned users in this guild
;warn prune
# Dryrun the prune command to see how many warnings would be removed
;warn prune 1
"""
x = 0
for ban in await ctx.guild.bans():
with db_session:
query = select(w for w in AdminWarnings if w.user_id == ban.user.id and w.guild_id == ctx.guild.id)
if query.exists():
if dry_run == 0:
query.delete()
x += 1
embed = discord.Embed(description=self.OK_WARN_PRUNE_PRUNED.format(x), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
@warn.command(aliases=["sl", "setlimit"])
async def set_limit(self, ctx, number_of_warnings: int):
"""
Sets the limit for how many warnings a user can get before mod's are notified.
Example: if 3 is set, on the third warning, mods will be DM'd. If this is set to 0, DM's will be disabled.
"""
if number_of_warnings < 0:
raise commands.BadArgument(self.ERROR_WARN_SL_NEG)
with db_session:
guild_settings = AdminSingle.get(guild_id=ctx.guild.id)
guild_settings.warning_limit = number_of_warnings
if number_of_warnings == 0:
embed = discord.Embed(description=self.OK_WARN_SL_SET_ZERO, colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
else:
embed = discord.Embed(description=self.OK_WARN_SL_SET.format(number_of_warnings), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.has_permissions(kick_members=True)
@commands.bot_has_permissions(kick_members=True)
@commands.command()
async def kick(self, ctx, member: discord.Member, *, reason=""):
"""Kicks the mentioned user with the ability to give a reason.
Requires the Kick Members permission.
Options:
- `USER` - A name, ID, or mention of a user.
- `reason` - OPTIONAL. A short reason for the kicking.
Examples:
# Kick user BadUser
;kick @BadUser
# Kick user Roxbot for being a meanie
;kick Roxbot "for being a meanie"
"""
try:
await member.kick(reason=reason)
embed = discord.Embed(description=self.OK_MOD_ACTION.format("Kicked", member, reason), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except discord.Forbidden:
embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.has_permissions(ban_members=True)
@commands.bot_has_permissions(ban_members=True)
@commands.command()
async def ban(self, ctx, member: discord.Member, *, reason=""):
"""Bans the mentioned user with the ability to give a reason.
Requires the Ban Members permission.
Options:
- `USER` - A name, ID, or mention of a user.
- `reason` - OPTIONAL. A short reason for the banning.
Examples:
# Ban user BadUser
;ban @BadUser
# Ban user Roxbot for being a meanie
;ban Roxbot "for being a meanie"
"""
try:
await member.ban(reason=reason, delete_message_days=0)
embed = discord.Embed(description=self.OK_MOD_ACTION.format("Banned", member, reason), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except discord.Forbidden:
embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.has_permissions(ban_members=True)
@commands.bot_has_permissions(ban_members=True)
@commands.command()
async def unban(self, ctx, member: roxbot.converters.User, *, reason=""):
"""Unbans the mentioned user with the ability to give a reason.
Requires the Ban Members permission.
Options:
- `user_id` - The ID of a banned user.
- `reason` - OPTIONAL. A short reason for the unbanning.
Examples:
# Unban user with ID 478294672394
;unban 478294672394
"""
ban = await ctx.guild.get_ban(member)
mem = ban.user
if mem is None:
embed = discord.Embed(description=self.WARN_UNBAN_NOTFOUND, colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
try:
await ctx.guild.unban(mem, reason=reason)
embed = discord.Embed(description=self.OK_MOD_ACTION.format("Unbanned", mem, reason), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except discord.Forbidden:
embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
"""
Admin Commands for those admins
"""
OK_SLOWMODE_ON = "Slowmode on :snail: ({} seconds)"
OK_SLOWMODE_OFF = "Slowmode off"
OK_SLOWMODE_CHANGED = "Slowmode set to :snail: ({} seconds)"
ERROR_SLOWMODE_SECONDS = "Rate limit has to be between 0-120."
OK_PURGE_CONFIRMATION = "{} message(s) purged from chat."
OK_WARN_ADD = "Reported {}."
OK_WARN_ADD_USER_LIMIT_DM = "{} has been reported {} time(s). This is a reminder that this is over the set limit of {}."
WARN_WARN_ADD_LIMIT_REACHED = "You can only warn a user {} times!"
OK_WARN_LIST_NO_WARNINGS = "No warnings on record."
OK_WARN_LIST_USER_NO_WARNINGS = "This user doesn't have any warning on record."
OK_WARN_REMOVE_REMOVED_WARNING = "Removed Warning {} from {}"
OK_WARN_REMOVE_REMOVED_WARNINGS = "Removed all warnings for {}"
WARN_WARN_REMOVE_USER_NOT_FOUND = "Could not find user {} in warning list."
ERROR_WARN_REMOVE_INDEXERROR = "Index Error: Warning index doesn't exist. User only has {} warning(s)."
ERROR_WARN_REMOVE_VALUEERROR = "Value Error: Please enter a valid index number."
OK_WARN_PRUNE_PRUNED = "Pruned {} banned users from the warn list."
OK_WARN_SL_SET = "Number of warnings needed to DM's set to {}"
OK_WARN_SL_SET_ZERO = "DM's to mods for warning limits disabled."
ERROR_WARN_SL_NEG = "number_of_warnings can only be a positive integer."
OK_MOD_ACTION = "{} with reason: '{}'"
WARN_MOD_LACK_PERMS = "Cannot kick owner or users higher or equal to me role hierarchy."
WARN_UNBAN_NOTFOUND = "User is not banned."
def __init__(self, bot_client):
self.bot = bot_client
self.autogen_db = AdminSingle
@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@commands.bot_has_permissions(manage_messages=True)
@commands.command()
async def slowmode(self, ctx, seconds: int):
"""Puts the channel in slowmode. Users with manage_channel or manage_messages permissions will not be effected.
Options:
- `seconds` - Has to be between 0 - 120. This will set the timeout a user receives once they send a message in this channel. If 0, Roxbot will disable slowmode.
Examples:
# Set slowmode to 30 seconds
;slowmode 30
# Turn slowmode off
;slowmode 0
"""
if seconds == 0: # Turn Slow Mode off
await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested to turn off slowmode.".format(ctx.author))
embed = discord.Embed(description=self.OK_SLOWMODE_OFF, colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
elif 0 < seconds <= 120 and ctx.channel.slowmode_delay == 0: # Turn Slow Mode On
await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested slowmode with a timer of {}".format(ctx.author, seconds))
embed = discord.Embed(description=self.OK_SLOWMODE_ON.format(seconds), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
elif 0 < seconds <= 120 and ctx.channel.slowmode_delay > 0: # Change value of Slow Mode timer
await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested slowmode timer be changed to {}".format(ctx.author, seconds))
embed = discord.Embed(description=self.OK_SLOWMODE_CHANGED.format(seconds), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
elif seconds < 0 or seconds > 120:
raise commands.BadArgument(self.ERROR_SLOWMODE_SECONDS)
@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@commands.bot_has_permissions(manage_messages=True, read_message_history=True)
@commands.cooldown(1, 5)
@commands.command()
async def purge(self, ctx, limit=0, *, author: roxbot.converters.User = None):
"""Purges the text channel the command is execture in. You can specify a certain user to purge as well.
Options:
- `limit` - This the the amount of messages Roxbot will take from the chat and purge. Note: This **does not** mean the amount that will be purged. Limit is the amount of messages Roxbot will look at. If a user is given, it will only delete messages from that user in that list of messages.
- `USER` - A name, ID, or mention of a user. If the user has left the guild, this **has** to be the ID.
Examples:
# Delete 20 messages from the chat
;purge 20
# Take 20 messages, and delete any message in it by user @Spammer
;purge 20 @Spammer
"""
# TODO: To sort out the limit == how many to delete for the author, and not just a limit.
if author:
predicate = lambda message: message.author.id == author.id and message.id != ctx.message.id
else:
predicate = lambda message: message.id != ctx.message.id
messages = await ctx.channel.purge(limit=limit, check=predicate)
embed = discord.Embed(description=self.OK_PURGE_CONFIRMATION.format(len(messages)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.has_permissions(kick_members=True)
@commands.group(case_insensitive=True)
async def warn(self, ctx):
"""The warn command group allows Discord moderators to warn users and log them within the bot.
The command group also supports setting limits to warn mods if a user has been warned over a certain threshold.
"""
if ctx.invoked_subcommand is None:
raise commands.CommandNotFound("Subcommand '{}' does not exist.".format(ctx.subcommand_passed))
@warn.command()
async def add(self, ctx, user: discord.User = None, *, warning=""):
"""Adds a warning to a user.
Options:
- `USER` - A name, ID, or mention of a user.
- `warning` - OPTIONAL. A reason for the warning. This supports markdown formatting.
Example:
# Add warning to user @Roxbot for being a meanie
;warn add @Roxbot "for being a meanie"
"""
# Warning in the settings is a dictionary of user ids. The user ids are equal to a list of dictionaries.
with db_session:
warning_limit = AdminSingle.get(guild_id=ctx.guild.id).warning_limit
user_warnings = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)[:]
amount_warnings = len(user_warnings)
warn_limit = 10
if amount_warnings > warn_limit:
embed = discord.Embed(description=self.WARN_WARN_ADD_LIMIT_REACHED.format(warn_limit), colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
with db_session:
AdminWarnings(user_id=user.id, warned_by=ctx.author.id, date=datetime.datetime.utcnow(), warning=warning, guild_id=ctx.guild.id)
if amount_warnings >= warning_limit > 0:
await ctx.author.send(self.OK_WARN_ADD_USER_LIMIT_DM.format(str(user), amount_warnings, warning_limit))
embed = discord.Embed(description=self.OK_WARN_ADD.format(str(user)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
@warn.command()
async def list(self, ctx, *, user: roxbot.converters.User = None):
"""Lists all warning in this guild or all the warnings for one user.
Options:
- `USER` - OPTIONAL. A name, ID, or mention of a user.
Examples:
# List all warnings in the guild
;warn list
# List all warnings for @Roxbot
;warn list @Roxbot
"""
if user is None:
paginator = commands.Paginator()
warnings = {}
with db_session:
for warning in select(warn for warn in AdminWarnings if warn.guild_id == ctx.guild.id)[:]:
if warning.user_id not in warnings:
warnings[warning.user_id] = []
else:
warnings[warning.user_id].append(warning)
for u, warning in warnings.items():
member_obj = discord.utils.get(ctx.guild.members, id=u)
if member_obj:
paginator.add_line("{}: {} Warning(s)".format(str(member_obj), len(warnings[u])))
else:
paginator.add_line("{}: {} Warning(s)".format(u, len(warnings[u])))
# This is in case we have removed some users from the list.
if not paginator.pages:
embed = discord.Embed(description=self.OK_WARN_LIST_NO_WARNINGS, colour=roxbot.EmbedColours.orange)
return await ctx.send(embed=embed)
for page in paginator.pages:
return await ctx.send(page)
else:
with db_session:
user_warnings = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id).order_by(AdminWarnings.date)[:]
if not user_warnings:
embed = discord.Embed(description=self.OK_WARN_LIST_USER_NO_WARNINGS, colour=roxbot.EmbedColours.orange)
return await ctx.send(embed=embed)
em = discord.Embed(title="Warnings for {}".format(str(user)), colour=roxbot.EmbedColours.pink)
em.set_thumbnail(url=user.avatar_url)
x = 1
for warning in user_warnings:
try:
warned_by = str(ctx.guild.get_member(warning.warned_by))
if warned_by is None:
warned_by = str(await self.bot.get_user_info(warning.warned_by))
except discord.ext.commands.CommandInvokeError:
warned_by = warning.warned_by
date = datetime.datetime.strftime(warning.date, roxbot.datetime.strip("{:} UTC")+" UTC")
em.add_field(name="Warning %s" % x, value="Warned by: {}\nTime: {}\nReason: {}".format(warned_by, date, warning.warning))
x += 1
return await ctx.send(embed=em)
@warn.command()
async def remove(self, ctx, user: roxbot.converters.User, index=None):
"""Removes one or all of the warnings for a user.
Options:=
- `USER` - A name, ID, or mention of a user.
- `index` - OPTIONAL. The index of the single warning you want to remove.
Examples:
# Remove all warnings for Roxbot
;warn remove Roxbot
# Remove warning 2 for Roxbot
;warn remove Roxbot 2
"""
with db_session:
if index:
try:
index = int(index) - 1
query = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
if query:
user_warnings = query[:]
else:
raise KeyError
user_warnings[index].delete()
embed = discord.Embed(description=self.OK_WARN_REMOVE_REMOVED_WARNING.format(index+1, str(user)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except Exception as e:
embed = discord.Embed(colour=roxbot.EmbedColours.red)
if isinstance(e, IndexError):
embed.description = self.ERROR_WARN_REMOVE_INDEXERROR.format(len(user_warnings))
elif isinstance(e, KeyError):
embed.description = self.WARN_WARN_REMOVE_USER_NOT_FOUND.format(str(user))
elif isinstance(e, ValueError):
embed.description = self.ERROR_WARN_REMOVE_VALUEERROR
else:
raise e
return await ctx.send(embed=embed)
else:
query = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
if query.exists():
delete(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
embed = discord.Embed(description=self.OK_WARN_REMOVE_REMOVED_WARNINGS.format(str(user)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
else:
embed = discord.Embed(description=self.WARN_WARN_REMOVE_USER_NOT_FOUND.format(str(user)), colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
@commands.bot_has_permissions(ban_members=True)
@warn.command()
async def prune(self, ctx, dry_run=0):
"""Prunes the warnings of any banned users.
You can add a 1 at the end to dryrun the command. This will show you how many would be deleted without deleting them.
Options:
- `dryrun` - Add `1` to the end of the command to do a dryrun of the prune command.
Examples:
# Prune the warnings of banned users in this guild
;warn prune
# Dryrun the prune command to see how many warnings would be removed
;warn prune 1
"""
x = 0
for ban in await ctx.guild.bans():
with db_session:
query = select(w for w in AdminWarnings if w.user_id == ban.user.id and w.guild_id == ctx.guild.id)
if query.exists():
if dry_run == 0:
query.delete()
x += 1
embed = discord.Embed(description=self.OK_WARN_PRUNE_PRUNED.format(x), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
@warn.command(aliases=["sl", "setlimit"])
async def set_limit(self, ctx, number_of_warnings: int):
"""
Sets the limit for how many warnings a user can get before mod's are notified.
Example: if 3 is set, on the third warning, mods will be DM'd. If this is set to 0, DM's will be disabled.
"""
if number_of_warnings < 0:
raise commands.BadArgument(self.ERROR_WARN_SL_NEG)
with db_session:
guild_settings = AdminSingle.get(guild_id=ctx.guild.id)
guild_settings.warning_limit = number_of_warnings
if number_of_warnings == 0:
embed = discord.Embed(description=self.OK_WARN_SL_SET_ZERO, colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
else:
embed = discord.Embed(description=self.OK_WARN_SL_SET.format(number_of_warnings), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.has_permissions(kick_members=True)
@commands.bot_has_permissions(kick_members=True)
@commands.command()
async def kick(self, ctx, member: discord.Member, *, reason=""):
"""Kicks the mentioned user with the ability to give a reason.
Requires the Kick Members permission.
Options:
- `USER` - A name, ID, or mention of a user.
- `reason` - OPTIONAL. A short reason for the kicking.
Examples:
# Kick user BadUser
;kick @BadUser
# Kick user Roxbot for being a meanie
;kick Roxbot "for being a meanie"
"""
try:
await member.kick(reason=reason)
embed = discord.Embed(description=self.OK_MOD_ACTION.format("Kicked", member, reason), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except discord.Forbidden:
embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.has_permissions(ban_members=True)
@commands.bot_has_permissions(ban_members=True)
@commands.command()
async def ban(self, ctx, member: discord.Member, *, reason=""):
"""Bans the mentioned user with the ability to give a reason.
Requires the Ban Members permission.
Options:
- `USER` - A name, ID, or mention of a user.
- `reason` - OPTIONAL. A short reason for the banning.
Examples:
# Ban user BadUser
;ban @BadUser
# Ban user Roxbot for being a meanie
;ban Roxbot "for being a meanie"
"""
try:
await member.ban(reason=reason, delete_message_days=0)
embed = discord.Embed(description=self.OK_MOD_ACTION.format("Banned", member, reason), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except discord.Forbidden:
embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.has_permissions(ban_members=True)
@commands.bot_has_permissions(ban_members=True)
@commands.command()
async def unban(self, ctx, member: roxbot.converters.User, *, reason=""):
"""Unbans the mentioned user with the ability to give a reason.
Requires the Ban Members permission.
Options:
- `user_id` - The ID of a banned user.
- `reason` - OPTIONAL. A short reason for the unbanning.
Examples:
# Unban user with ID 478294672394
;unban 478294672394
"""
ban = await ctx.guild.get_ban(member)
mem = ban.user
if mem is None:
embed = discord.Embed(description=self.WARN_UNBAN_NOTFOUND, colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)
try:
await ctx.guild.unban(mem, reason=reason)
embed = discord.Embed(description=self.OK_MOD_ACTION.format("Unbanned", mem, reason), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except discord.Forbidden:
embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
return await ctx.send(embed=embed)


def setup(bot_client):
bot_client.add_cog(Admin(bot_client))
bot_client.add_cog(Admin(bot_client))

+ 66
- 66
roxbot/cogs/ags.py View File

@@ -31,72 +31,72 @@ import roxbot


class AssortedGenderSounds(commands.Cog):
"""Custom Cog for the AssortedGenderSounds Discord Server."""
def __init__(self, bot_client):
self.bot = bot_client
self.required_score = 2000
self.days = 5
self.logging_channel_id = 394959819796381697
self.newbie_role_id = 450042170112475136
self.selfie_role_id = 394939389823811584
self.ags_id = 393764974444675073
self.tat_token = roxbot.config["Tokens"]["Tatsumaki"]
self.bot.add_listener(self.grab_objects, "on_ready")
self.ags = None
self.selfie_role = None
self.newbie_role = None
self.logging_channel = None
async def cog_check(self, ctx):
return ctx.guild.id == self.ags_id
async def grab_objects(self):
self.ags = self.bot.get_guild(self.ags_id)
self.selfie_role = self.ags.get_role(self.selfie_role_id)
self.newbie_role = self.ags.get_role(self.newbie_role_id)
self.logging_channel = self.ags.get_channel(self.logging_channel_id)
@commands.Cog.listener()
async def on_member_join(self, member):
if member.guild == self.ags:
await member.add_roles(self.newbie_role, reason="Auto-add role on join")
await member.send("Please read our <#396697172139180033> and <#422514427263188993> channels. To gain access to the server, you must agree to the rules.")
async def tatsumaki_api_call(self, member, guild):
base = "https://api.tatsumaki.xyz/"
url = base + "guilds/" + str(guild.id) + "/members/" + str(member.id) + "/stats"
return await roxbot.http.api_request(url, headers={"Authorization": self.tat_token})
@commands.command()
async def agree(self, ctx):
try:
return await ctx.author.remove_roles(self.newbie_role, reason="User has agreed the rules and has been given access to the server.")
except discord.HTTPException:
pass
@commands.command(name="selfieperms")
async def selfie_perms(self, ctx):
"""Requests the selfie perm role."""
member = ctx.author
data = await self.tatsumaki_api_call(member, ctx.guild)
if data is None:
return await ctx.send("Tatsumaki API call returned nothing. Maybe the API is down?")
if self.selfie_role in member.roles:
await member.remove_roles(self.selfie_role, reason="Requested removal of {0.name}".format(self.selfie_role))
return await ctx.send("You already had {0.name}. It has now been removed.".format(self.selfie_role))
time = datetime.datetime.now() - ctx.author.joined_at
if time > datetime.timedelta(days=self.days) and int(data["score"]) >= self.required_score:
await member.add_roles(self.selfie_role, reason="Requested {0.name}".format(self.selfie_role))
await ctx.send("You now have the {0.name} role".format(self.selfie_role))
else:
return await ctx.send(
"You do not meet the requirements for this role. You need at least {} score with <@!172002275412279296> and to have been in the server for {} days.".format(self.required_score, self.days)
)
"""Custom Cog for the AssortedGenderSounds Discord Server."""
def __init__(self, bot_client):
self.bot = bot_client
self.required_score = 2000
self.days = 5
self.logging_channel_id = 394959819796381697
self.newbie_role_id = 450042170112475136
self.selfie_role_id = 394939389823811584
self.ags_id = 393764974444675073
self.tat_token = roxbot.config["Tokens"]["Tatsumaki"]
self.bot.add_listener(self.grab_objects, "on_ready")
self.ags = None
self.selfie_role = None
self.newbie_role = None
self.logging_channel = None
async def cog_check(self, ctx):
return ctx.guild.id == self.ags_id
async def grab_objects(self):
self.ags = self.bot.get_guild(self.ags_id)
self.selfie_role = self.ags.get_role(self.selfie_role_id)
self.newbie_role = self.ags.get_role(self.newbie_role_id)
self.logging_channel = self.ags.get_channel(self.logging_channel_id)
@commands.Cog.listener()
async def on_member_join(self, member):
if member.guild == self.ags:
await member.add_roles(self.newbie_role, reason="Auto-add role on join")
await member.send("Please read our <#396697172139180033> and <#422514427263188993> channels. To gain access to the server, you must agree to the rules.")
async def tatsumaki_api_call(self, member, guild):
base = "https://api.tatsumaki.xyz/"
url = base + "guilds/" + str(guild.id) + "/members/" + str(member.id) + "/stats"
return await roxbot.http.api_request(url, headers={"Authorization": self.tat_token})
@commands.command()
async def agree(self, ctx):
try:
return await ctx.author.remove_roles(self.newbie_role, reason="User has agreed the rules and has been given access to the server.")
except discord.HTTPException:
pass
@commands.command(name="selfieperms")
async def selfie_perms(self, ctx):
"""Requests the selfie perm role."""
member = ctx.author
data = await self.tatsumaki_api_call(member, ctx.guild)
if data is None:
return await ctx.send("Tatsumaki API call returned nothing. Maybe the API is down?")
if self.selfie_role in member.roles:
await member.remove_roles(self.selfie_role, reason="Requested removal of {0.name}".format(self.selfie_role))
return await ctx.send("You already had {0.name}. It has now been removed.".format(self.selfie_role))
time = datetime.datetime.now() - ctx.author.joined_at
if time > datetime.timedelta(days=self.days) and int(data["score"]) >= self.required_score:
await member.add_roles(self.selfie_role, reason="Requested {0.name}".format(self.selfie_role))
await ctx.send("You now have the {0.name} role".format(self.selfie_role))
else:
return await ctx.send(
"You do not meet the requirements for this role. You need at least {} score with <@!172002275412279296> and to have been in the server for {} days.".format(self.required_score, self.days)
)


def setup(bot_client):
bot_client.add_cog(AssortedGenderSounds(bot_client))
bot_client.add_cog(AssortedGenderSounds(bot_client))

+ 276
- 276
roxbot/cogs/customcommands.py View File

@@ -33,285 +33,285 @@ from roxbot.db import *


class CCCommands(db.Entity):
# Sadly no way to add a custom constraint to this while we either use pony or sqlite/
name = Required(str)
output = Required(Json)
type = Required(int, py_check=lambda val: 0 <= val <= 2)
guild_id = Required(int, size=64)
composite_key(name, guild_id)
# Sadly no way to add a custom constraint to this while we either use pony or sqlite/
name = Required(str)
output = Required(Json)
type = Required(int, py_check=lambda val: 0 <= val <= 2)
guild_id = Required(int, size=64)
composite_key(name, guild_id)


class CustomCommands(commands.Cog):
"""The Custom Commands cog allows moderators to add custom commands for their Discord server to Roxbot. Allowing custom outputs predefined by the moderators.
For example, we can set a command to require a prefix and call it "roxbot" and configure an output. Then if a user does `;roxbot` roxbot will output the configured output.
"""
ERROR_AT_MENTION = "Custom Commands cannot mention people/roles/everyone."
ERROR_COMMAND_NULL = "That Custom Command doesn't exist."
ERROR_COMMAND_EXISTS = "Custom Command already exists."
ERROR_COMMAND_EXISTS_INTERNAL = "This is already the name of a built in command."
ERROR_EMBED_VALUE = "Not enough options given to generate embed."
ERROR_INCORRECT_TYPE = "Incorrect type given."
ERROR_OUTPUT_TOO_LONG = "Failed to set output. Given output was too long."
ERROR_PREFIX_SPACE = "Custom commands with a prefix can only be one word with no spaces."
OUTPUT_ADD = "{} has been added with the output: '{}'"
OUTPUT_EDIT = "Edit made. {} now outputs {}"
OUTPUT_REMOVE = "Removed {} custom command"
def __init__(self, bot_client):
self.bot = bot_client
self.embed_fields = ("title", "description", "colour", "color", "footer", "image", "thumbnail", "url")
@staticmethod
def _get_output(command):
# Check for a list as the output. If so, randomly select a item from the list.
return random.choice(command)
@staticmethod
def _cc_to_embed(command_output):
# discord.Embed.Empty is used by discord.py to denote when a field is empty. Hence why it is the fallback here
title = command_output.get("title", discord.Embed.Empty)
desc = command_output.get("description", discord.Embed.Empty)
# Check for both possible colour fields. Then strip possible # and convert to hex for embed
colour = command_output.get("colour", discord.Embed.Empty) or command_output.get("color", discord.Embed.Empty)
if isinstance(colour, str):
colour = discord.Colour(int(colour.strip("#"), 16))
url = command_output.get("url", discord.Embed.Empty)
footer = command_output.get("footer", discord.Embed.Empty)
image = command_output.get("image", discord.Embed.Empty)
thumbnail = command_output.get("thumbnail", discord.Embed.Empty)
embed = discord.Embed(title=title, description=desc, colour=colour, url=url)
if footer:
embed.set_footer(text=footer)
if image:
embed.set_image(url=image)
if thumbnail:
embed.set_thumbnail(url=thumbnail)
return embed
def _embed_parse_options(self, options):
# Create an dict from a list, taking each two items as a key value pair
output = {item: options[index + 1] for index, item in enumerate(options) if index % 2 == 0}
for key in output.copy().keys():
if key not in self.embed_fields:
output.pop(key)
# Check for errors in inputs that would stop embed from being posted.
title = output.get("title", "")
footer = output.get("footer", "")
if len(title) > 256 or len(footer) > 256:
raise ValueError("Title or Footer must be smaller than 256 characters.")
# We only need one so purge the inferior spelling
if "colour" in output and "color" in output:
output.pop("color")
return output
@commands.Cog.listener()
async def on_message(self, message):
"""
"""
# Emulate discord.py's feature of not running commands invoked by the bot (expects not to be used for self-botting)
if message.author == self.bot.user:
return
# Limit custom commands to guilds only.
if not isinstance(message.channel, discord.TextChannel):
return
# Emulate Roxbot's blacklist system
if self.bot.blacklisted(message.author):
raise commands.CheckFailure
msg = message.content.lower()
channel = message.channel
with db_session:
if msg.startswith(self.bot.command_prefix):
command_name = msg.split(self.bot.command_prefix)[1]
command = CCCommands.get(name=command_name, guild_id=message.guild.id)
try:
if command.type == 1:
output = self._get_output(command.output)
return await channel.send(output)
elif command.type == 2:
embed = self._cc_to_embed(command.output)
return await channel.send(embed=embed)
except AttributeError:
pass
else:
try:
command = CCCommands.get(name=msg, guild_id=message.guild.id, type=0)
if command:
output = self._get_output(command.output)
return await channel.send(output)
except:
pass
@commands.guild_only()
@commands.group(aliases=["cc"])
async def custom(self, ctx):
"""
A group of commands to manage custom commands for your server.
Requires the Manage Messages permission.
"""
if ctx.invoked_subcommand is None:
raise commands.CommandNotFound("Subcommand '{}' does not exist.".format(ctx.subcommand_passed))
@commands.has_permissions(manage_messages=True)
@custom.command()
async def add(self, ctx, command_type, command, *output):
"""Adds a custom command to the list of custom commands.
Options:
- `type` - There are three types of custom commands.
- `no_prefix`/`0` - These are custom commands that will trigger without a prefix. Example: a command named `test` will trigger when a user says `test` in chat.
- `prefix`/`1` - These are custom commands that will trigger with a prefix. Example: a command named `test` will trigger when a user says `;test` in chat.
- `embed`/`2` - These are prefix commands that will output a rich embed. [You can find out more about rich embeds from Discord's API documentation.](https://discordapp.com/developers/docs/resources/channel#embed-object) Embed types currently support these fields: `title, description, colour, color, url, footer, image, thumbnail`
- `name` - The name of the command. No commands can have the same name.
- `output` - The output of the command. The way you input this is determined by the type.
`no_prefix` and `prefix` types support single outputs and also listing multiple outputs. When the latter is chosen, the output will be a random choice of the multiple outputs.
Examples:
# Add a no_prefix command called "test" with a URL output.
;cc add no_prefix test "https://www.youtube.com/watch?v=vJZp6awlL58"
# Add a prefix command called test2 with a randomised output between "the person above me is annoying" and "the person above me is cool :sunglasses:"
;cc add prefix test2 "the person above me is annoying" "the person above me is cool :sunglasses:
# Add an embed command called test3 with a title of "Title" and a description that is a markdown hyperlink to a youtube video, and the colour #deadbf
;cc add embed test3 title "Title" description "[Click here for a rad video](https://www.youtube.com/watch?v=dQw4w9WgXcQ)" colour #deadbf
Note: With custom commands, it is important to remember that "" is used to pass any text with spaces as one argument. If the output you want requires the use of these characters, surround your output with three speech quotes at either side instead.
"""
command = command.lower()
if command_type in ("0", "no_prefix", "no prefix"):
command_type = 0
elif command_type in ("1", "prefix"):
command_type = 1
elif command_type in ("2", "embed"):
command_type = 2
if len(output) < 2:
raise roxbot.UserError(self.ERROR_EMBED_VALUE)
try:
output = self._embed_parse_options(output)
except ValueError:
raise roxbot.UserError(self.ERROR_OUTPUT_TOO_LONG)
else:
raise roxbot.UserError(self.ERROR_INCORRECT_TYPE)
with db_session:
if ctx.message.mentions or ctx.message.mention_everyone or ctx.message.role_mentions:
raise roxbot.UserError(self.ERROR_AT_MENTION)
elif len(output) > 1800:
raise roxbot.UserError(self.ERROR_OUTPUT_TOO_LONG)
elif command in self.bot.all_commands.keys() and command_type == 1:
raise roxbot.UserError(self.ERROR_COMMAND_EXISTS_INTERNAL)
elif select(c for c in CCCommands if c.name == command and c.guild_id == ctx.guild.id).exists():
raise roxbot.UserError(self.ERROR_COMMAND_EXISTS)
elif len(command.split(" ")) > 1 and command_type == "1":
raise roxbot.UserError(self.ERROR_PREFIX_SPACE)
CCCommands(name=command, guild_id=ctx.guild.id, output=output, type=command_type)
return await ctx.send(self.OUTPUT_ADD.format(command, output if len(output) > 1 else output[0]))
@commands.has_permissions(manage_messages=True)
@custom.command()
async def edit(self, ctx, command, *edit):
"""Edits an existing custom command.
Example:
# edit a command called test to output "new output"
;cc edit test "new output"
For more examples of how to setup a custom command, look at the help for the ;custom add command.
You cannot change the type of a command. If you want to change the type, remove the command and re-add it.
"""
if ctx.message.mentions or ctx.message.mention_everyone or ctx.message.role_mentions:
raise roxbot.UserError(self.ERROR_AT_MENTION)
if not edit:
raise commands.BadArgument("Missing required argument: edit")
with db_session:
query = CCCommands.get(name=command.lower(), guild_id=ctx.guild.id)
if query:
if query.type == 2:
if len(edit) < 2:
raise roxbot.UserError(self.ERROR_EMBED_VALUE)
try:
edit = self._embed_parse_options(edit)
query.output = edit
return await ctx.send(self.OUTPUT_EDIT.format(command, edit))
except ValueError:
raise roxbot.UserError(self.ERROR_OUTPUT_TOO_LONG)
else:
query.output = edit
return await ctx.send(self.OUTPUT_EDIT.format(command, edit if len(edit) > 1 else edit[0]))
else:
raise roxbot.UserError(self.ERROR_COMMAND_NULL)
@commands.has_permissions(manage_messages=True)
@custom.command()
async def remove(self, ctx, command):
"""Removes a custom command.
Example:
# Remove custom command called "test"
;cc remove test
"""
command = command.lower()
with db_session:
c = CCCommands.get(name=command, guild_id=ctx.guild.id)
if c:
c.delete()
return await ctx.send(self.OUTPUT_REMOVE.format(command))
else:
raise roxbot.UserError(self.ERROR_COMMAND_NULL)
@custom.command()
async def list(self, ctx, debug="0"):
"""Lists all custom commands for this guild."""
if debug != "0" and debug != "1":
debug = "0"
with db_session:
no_prefix_commands = select(c for c in CCCommands if c.type == 0 and c.guild_id == ctx.guild.id)[:]
prefix_commands = select(c for c in CCCommands if c.type == 1 and c.guild_id == ctx.guild.id)[:]
embed_commands = select(c for c in CCCommands if c.type == 2 and c.guild_id == ctx.guild.id)[:]
def add_commands(commands, paginator):
if not commands:
paginator.add_line("There are no commands setup.")
else:
for command in commands:
output = command.name
if debug == "1":
output += " = '{}'".format(command.output if command.type == 2 else command.output[0])
paginator.add_line("- " + output)
paginator = commands.Paginator(prefix="```md")
paginator.add_line("__Here is the list of Custom Commands...__")
paginator.add_line()
paginator.add_line("__Prefix Commands (Non Embeds):__")
add_commands(prefix_commands, paginator)
paginator.add_line()
paginator.add_line("__Prefix Commands (Embeds):__")
add_commands(embed_commands, paginator)
paginator.add_line()
paginator.add_line("__Commands that don't require prefix:__")
add_commands(no_prefix_commands, paginator)
for page in paginator.pages:
await ctx.send(page)
"""The Custom Commands cog allows moderators to add custom commands for their Discord server to Roxbot. Allowing custom outputs predefined by the moderators.
For example, we can set a command to require a prefix and call it "roxbot" and configure an output. Then if a user does `;roxbot` roxbot will output the configured output.
"""
ERROR_AT_MENTION = "Custom Commands cannot mention people/roles/everyone."
ERROR_COMMAND_NULL = "That Custom Command doesn't exist."
ERROR_COMMAND_EXISTS = "Custom Command already exists."
ERROR_COMMAND_EXISTS_INTERNAL = "This is already the name of a built in command."
ERROR_EMBED_VALUE = "Not enough options given to generate embed."
ERROR_INCORRECT_TYPE = "Incorrect type given."
ERROR_OUTPUT_TOO_LONG = "Failed to set output. Given output was too long."
ERROR_PREFIX_SPACE = "Custom commands with a prefix can only be one word with no spaces."
OUTPUT_ADD = "{} has been added with the output: '{}'"
OUTPUT_EDIT = "Edit made. {} now outputs {}"
OUTPUT_REMOVE = "Removed {} custom command"
def __init__(self, bot_client):
self.bot = bot_client
self.embed_fields = ("title", "description", "colour", "color", "footer", "image", "thumbnail", "url")
@staticmethod
def _get_output(command):
# Check for a list as the output. If so, randomly select a item from the list.
return random.choice(command)
@staticmethod
def _cc_to_embed(command_output):
# discord.Embed.Empty is used by discord.py to denote when a field is empty. Hence why it is the fallback here
title = command_output.get("title", discord.Embed.Empty)
desc = command_output.get("description", discord.Embed.Empty)
# Check for both possible colour fields. Then strip possible # and convert to hex for embed
colour = command_output.get("colour", discord.Embed.Empty) or command_output.get("color", discord.Embed.Empty)
if isinstance(colour, str):
colour = discord.Colour(int(colour.strip("#"), 16))
url = command_output.get("url", discord.Embed.Empty)
footer = command_output.get("footer", discord.Embed.Empty)
image = command_output.get("image", discord.Embed.Empty)
thumbnail = command_output.get("thumbnail", discord.Embed.Empty)
embed = discord.Embed(title=title, description=desc, colour=colour, url=url)
if footer:
embed.set_footer(text=footer)
if image:
embed.set_image(url=image)
if thumbnail:
embed.set_thumbnail(url=thumbnail)
return embed
def _embed_parse_options(self, options):
# Create an dict from a list, taking each two items as a key value pair
output = {item: options[index + 1] for index, item in enumerate(options) if index % 2 == 0}
for key in output.copy().keys():
if key not in self.embed_fields:
output.pop(key)
# Check for errors in inputs that would stop embed from being posted.
title = output.get("title", "")
footer = output.get("footer", "")
if len(title) > 256 or len(footer) > 256:
raise ValueError("Title or Footer must be smaller than 256 characters.")
# We only need one so purge the inferior spelling
if "colour" in output and "color" in output:
output.pop("color")
return output
@commands.Cog.listener()
async def on_message(self, message):
"""
"""
# Emulate discord.py's feature of not running commands invoked by the bot (expects not to be used for self-botting)
if message.author == self.bot.user:
return
# Limit custom commands to guilds only.
if not isinstance(message.channel, discord.TextChannel):
return
# Emulate Roxbot's blacklist system
if self.bot.blacklisted(message.author):
raise commands.CheckFailure
msg = message.content.lower()
channel = message.channel
with db_session:
if msg.startswith(self.bot.command_prefix):
command_name = msg.split(self.bot.command_prefix)[1]
command = CCCommands.get(name=command_name, guild_id=message.guild.id)
try:
if command.type == 1:
output = self._get_output(command.output)
return await channel.send(output)
elif command.type == 2:
embed = self._cc_to_embed(command.output)
return await channel.send(embed=embed)
except AttributeError:
pass
else:
try:
command = CCCommands.get(name=msg, guild_id=message.guild.id, type=0)
if command:
output = self._get_output(command.output)
return await channel.send(output)
except:
pass
@commands.guild_only()
@commands.group(aliases=["cc"])
async def custom(self, ctx):
"""
A group of commands to manage custom commands for your server.
Requires the Manage Messages permission.
"""
if ctx.invoked_subcommand is None:
raise commands.CommandNotFound("Subcommand '{}' does not exist.".format(ctx.subcommand_passed))
@commands.has_permissions(manage_messages=True)
@custom.command()
async def add(self, ctx, command_type, command, *output):
"""Adds a custom command to the list of custom commands.
Options:
- `type` - There are three types of custom commands.
- `no_prefix`/`0` - These are custom commands that will trigger without a prefix. Example: a command named `test` will trigger when a user says `test` in chat.
- `prefix`/`1` - These are custom commands that will trigger with a prefix. Example: a command named `test` will trigger when a user says `;test` in chat.
- `embed`/`2` - These are prefix commands that will output a rich embed. [You can find out more about rich embeds from Discord's API documentation.](https://discordapp.com/developers/docs/resources/channel#embed-object) Embed types currently support these fields: `title, description, colour, color, url, footer, image, thumbnail`
- `name` - The name of the command. No commands can have the same name.
- `output` - The output of the command. The way you input this is determined by the type.
`no_prefix` and `prefix` types support single outputs and also listing multiple outputs. When the latter is chosen, the output will be a random choice of the multiple outputs.
Examples:
# Add a no_prefix command called "test" with a URL output.
;cc add no_prefix test "https://www.youtube.com/watch?v=vJZp6awlL58"
# Add a prefix command called test2 with a randomised output between "the person above me is annoying" and "the person above me is cool :sunglasses:"
;cc add prefix test2 "the person above me is annoying" "the person above me is cool :sunglasses:
# Add an embed command called test3 with a title of "Title" and a description that is a markdown hyperlink to a youtube video, and the colour #deadbf
;cc add embed test3 title "Title" description "[Click here for a rad video](https://www.youtube.com/watch?v=dQw4w9WgXcQ)" colour #deadbf
Note: With custom commands, it is important to remember that "" is used to pass any text with spaces as one argument. If the output you want requires the use of these characters, surround your output with three speech quotes at either side instead.
"""
command = command.lower()
if command_type in ("0", "no_prefix", "no prefix"):
command_type = 0
elif command_type in ("1", "prefix"):
command_type = 1
elif command_type in ("2", "embed"):
command_type = 2
if len(output) < 2:
raise roxbot.UserError(self.ERROR_EMBED_VALUE)
try:
output = self._embed_parse_options(output)
except ValueError:
raise roxbot.UserError(self.ERROR_OUTPUT_TOO_LONG)
else:
raise roxbot.UserError(self.ERROR_INCORRECT_TYPE)
with db_session:
if ctx.message.mentions or ctx.message.mention_everyone or ctx.message.role_mentions:
raise roxbot.UserError(self.ERROR_AT_MENTION)
elif len(output) > 1800:
raise roxbot.UserError(self.ERROR_OUTPUT_TOO_LONG)
elif command in self.bot.all_commands.keys() and command_type == 1:
raise roxbot.UserError(self.ERROR_COMMAND_EXISTS_INTERNAL)
elif select(c for c in CCCommands if c.name == command and c.guild_id == ctx.guild.id).exists():
raise roxbot.UserError(self.ERROR_COMMAND_EXISTS)
elif len(command.split(" ")) > 1 and command_type == "1":
raise roxbot.UserError(self.ERROR_PREFIX_SPACE)
CCCommands(name=command, guild_id=ctx.guild.id, output=output, type=command_type)
return await ctx.send(self.OUTPUT_ADD.format(command, output if len(output) > 1 else output[0]))
@commands.has_permissions(manage_messages=True)
@custom.command()
async def edit(self, ctx, command, *edit):
"""Edits an existing custom command.
Example:
# edit a command called test to output "new output"
;cc edit test "new output"
For more examples of how to setup a custom command, look at the help for the ;custom add command.
You cannot change the type of a command. If you want to change the type, remove the command and re-add it.
"""
if ctx.message.mentions or ctx.message.mention_everyone or ctx.message.role_mentions:
raise roxbot.UserError(self.ERROR_AT_MENTION)
if not edit:
raise commands.BadArgument("Missing required argument: edit")
with db_session:
query = CCCommands.get(name=command.lower(), guild_id=ctx.guild.id)
if query:
if query.type == 2:
if len(edit) < 2:
raise roxbot.UserError(self.ERROR_EMBED_VALUE)
try:
edit = self._embed_parse_options(edit)
query.output = edit
return await ctx.send(self.OUTPUT_EDIT.format(command, edit))
except ValueError:
raise roxbot.UserError(self.ERROR_OUTPUT_TOO_LONG)
else:
query.output = edit
return await ctx.send(self.OUTPUT_EDIT.format(command, edit if len(edit) > 1 else edit[0]))
else:
raise roxbot.UserError(self.ERROR_COMMAND_NULL)
@commands.has_permissions(manage_messages=True)
@custom.command()
async def remove(self, ctx, command):
"""Removes a custom command.
Example:
# Remove custom command called "test"
;cc remove test
"""
command = command.lower()
with db_session:
c = CCCommands.get(name=command, guild_id=ctx.guild.id)
if c:
c.delete()
return await ctx.send(self.OUTPUT_REMOVE.format(command))
else:
raise roxbot.UserError(self.ERROR_COMMAND_NULL)
@custom.command()
async def list(self, ctx, debug="0"):
"""Lists all custom commands for this guild."""
if debug != "0" and debug != "1":
debug = "0"
with db_session:
no_prefix_commands = select(c for c in CCCommands if c.type == 0 and c.guild_id == ctx.guild.id)[:]
prefix_commands = select(c for c in CCCommands if c.type == 1 and c.guild_id == ctx.guild.id)[:]
embed_commands = select(c for c in CCCommands if c.type == 2 and c.guild_id == ctx.guild.id)[:]
def add_commands(commands, paginator):
if not commands:
paginator.add_line("There are no commands setup.")
else:
for command in commands:
output = command.name
if debug == "1":
output += " = '{}'".format(command.output if command.type == 2 else command.output[0])
paginator.add_line("- " + output)
paginator = commands.Paginator(prefix="```md")
paginator.add_line("__Here is the list of Custom Commands...__")
paginator.add_line()
paginator.add_line("__Prefix Commands (Non Embeds):__")
add_commands(prefix_commands, paginator)
paginator.add_line()
paginator.add_line("__Prefix Commands (Embeds):__")
add_commands(embed_commands, paginator)
paginator.add_line()
paginator.add_line("__Commands that don't require prefix:__")
add_commands(no_prefix_commands, paginator)
for page in paginator.pages:
await ctx.send(page)


def setup(bot_client):
bot_client.add_cog(CustomCommands(bot_client))
bot_client.add_cog(CustomCommands(bot_client))

+ 569
- 569
roxbot/cogs/fun.py
File diff suppressed because it is too large
View File


+ 424
- 424
roxbot/cogs/image.py View File

@@ -36,433 +36,433 @@ import roxbot


class PrideFlags:
"""Class to produce pride flags for the filters in Roxbot."""
def __init__(self, rows=0, colours=None, ratio=None):
self.rows = rows
self.colours = colours
self.ratio = ratio or tuple([(1/rows)]*rows) # Custom ratio is here for things like the bi pride flag
@classmethod
def lgbt(cls):
rows = 6
red = (243, 28, 28)
orange = (255, 196, 0)
yellow = (255, 247, 0)
green = (0, 188, 108)
blue = (0, 149, 255)
violet = (181, 46, 193)
colours = (red, orange, yellow, green, blue, violet)
return cls(rows=rows, colours=colours)
@classmethod
def trans(cls):
rows = 5
blue = (91, 206, 250)
pink = (245, 169, 184)
white = (255, 255, 255)
colours = (blue, pink, white, pink, blue)
return cls(rows=rows, colours=colours)
@classmethod
def non_binary(cls):
rows = 4
yellow = (255, 244, 51)
white = (255, 255, 255)
purple = (155, 89, 208)
grey = (45, 45, 45)
colours = (yellow, white, purple, grey)
return cls(rows=rows, colours=colours)
@classmethod
def bi(cls):
rows = 3
ratio = (0.4, 0.2, 0.4)
pink = (215, 2, 112)
lavender = (115, 79, 150)
blue = (0, 56, 168)
colours = (pink, lavender, blue)
return cls(rows=rows, colours=colours, ratio=ratio)
@classmethod
def pan(cls):
rows = 3
pink = (255, 33, 140)
yellow = (255, 216, 0)
blue = (33, 177, 255)
colours = (pink, yellow, blue)
return cls(rows=rows, colours=colours)
@classmethod
def ace(cls):
rows = 4
black = (0, 0, 0)
grey = (163, 163, 163)
white = (255, 255, 255)
purple = (128, 0, 128)
colours = (black, grey, white, purple)
return cls(rows=rows, colours=colours)
@classmethod
def gq(cls):
rows = 3
purple = (181, 126, 220)
white = (255, 255, 255)
green = (74, 129, 35)
colours = (purple, white, green)
return cls(rows=rows, colours=colours)
@classmethod
def gf(cls):
rows = 5
pink = (255, 117, 162)
white = (255, 255, 255)
purple = (190, 24, 214)
black = (0, 0, 0)
blue = (51, 62, 189)
colours = (pink, white, purple, black, blue)
return cls(rows=rows, colours=colours)
@classmethod
def agender(cls):
rows = 7
black = (0, 0, 0)
white = (255, 255, 255)
grey = (185, 185, 185)
green = (176, 244, 141)
colours = (black, grey, white, green, white, grey, black)
return cls(rows=rows, colours=colours)
"""Class to produce pride flags for the filters in Roxbot."""
def __init__(self, rows=0, colours=None, ratio=None):
self.rows = rows
self.colours = colours
self.ratio = ratio or tuple([(1/rows)]*rows) # Custom ratio is here for things like the bi pride flag
@classmethod
def lgbt(cls):
rows = 6
red = (243, 28, 28)
orange = (255, 196, 0)
yellow = (255, 247, 0)
green = (0, 188, 108)
blue = (0, 149, 255)
violet = (181, 46, 193)
colours = (red, orange, yellow, green, blue, violet)
return cls(rows=rows, colours=colours)
@classmethod
def trans(cls):
rows = 5
blue = (91, 206, 250)
pink = (245, 169, 184)
white = (255, 255, 255)
colours = (blue, pink, white, pink, blue)
return cls(rows=rows, colours=colours)
@classmethod
def non_binary(cls):
rows = 4
yellow = (255, 244, 51)
white = (255, 255, 255)
purple = (155, 89, 208)
grey = (45, 45, 45)
colours = (yellow, white, purple, grey)
return cls(rows=rows, colours=colours)
@classmethod
def bi(cls):
rows = 3
ratio = (0.4, 0.2, 0.4)
pink = (215, 2, 112)
lavender = (115, 79, 150)
blue = (0, 56, 168)
colours = (pink, lavender, blue)
return cls(rows=rows, colours=colours, ratio=ratio)
@classmethod
def pan(cls):
rows = 3
pink = (255, 33, 140)
yellow = (255, 216, 0)
blue = (33, 177, 255)
colours = (pink, yellow, blue)
return cls(rows=rows, colours=colours)
@classmethod
def ace(cls):
rows = 4
black = (0, 0, 0)
grey = (163, 163, 163)
white = (255, 255, 255)
purple = (128, 0, 128)
colours = (black, grey, white, purple)
return cls(rows=rows, colours=colours)
@classmethod
def gq(cls):
rows = 3
purple = (181, 126, 220)
white = (255, 255, 255)
green = (74, 129, 35)
colours = (purple, white, green)
return cls(rows=rows, colours=colours)
@classmethod
def gf(cls):
rows = 5
pink = (255, 117, 162)
white = (255, 255, 255)
purple = (190, 24, 214)
black = (0, 0, 0)
blue = (51, 62, 189)
colours = (pink, white, purple, black, blue)
return cls(rows=rows, colours=colours)
@classmethod
def agender(cls):
rows = 7
black = (0, 0, 0)
white = (255, 255, 255)
grey = (185, 185, 185)
green = (176, 244, 141)
colours = (black, grey, white, green, white, grey, black)
return cls(rows=rows, colours=colours)


class ImageEditor(commands.Cog):
"""The ImageEditor cog is a cog with multiple commands to manipulate images provided by the user."""
def __init__(self, bot_client):
self.bot = bot_client
@staticmethod
def image_lookup(message):
try:
if message.attachments[0].height: # Check if attachment is image
return message.attachments[0].url
except IndexError:
return message.author.avatar_url_as(format="png")
@staticmethod
def add_grain(img, prob=0.2, opacity=30):
"""
Adds salt and pepper grain to the given image.
:param img: :type PIL.Image: Image to add grain to
:param prob: :type float: Probability of a pixel being black between 0-1
:param opacity: :type int: opacity of the grain when composite with the given image between 0%-100%
:return: :type PIL.Image: Image with added grain
"""
img_matrix = np.zeros((img.height, img.width), dtype=np.uint8)
for y in range(img.height):
for x in range(img.width):
if prob < random.random():
img_matrix[y][x] = 255
noisy = Image.fromarray(img_matrix, "L")
noisy = noisy.convert("RGB")
mask = Image.new('RGBA', img.size, (0, 0, 0, opacity))
return Image.composite(noisy, img, mask)
@staticmethod
async def flag_filter(name, flag, url):
"""At the moment, can only make horizontal stripe flags"""
f = 'filter_{}.png'.format(name)
await roxbot.http.download_file(url, f)
ava = Image.open(f)
top = 0 # In the box we use, top is used to define which part of the image we are working on
bottom = 0 # And bottom defines the height. That should help you visualise why I increment the values the way I do
for x, colour in enumerate(flag.colours):
# Grab the next slice of the images height and width
# we use math.ceil here to avoid rounding errors when converting float to int
height = int(math.ceil(ava.height * flag.ratio[x]))
width = ava.width
bottom += height
box = (0, top, width, bottom)
# Make the colour block and the transparency mask at the slice size. Then crop the next part of the image
row = Image.new('RGB', (width, height), colour)
mask = Image.new('RGBA', (width, height), (0, 0, 0, 123))
crop = ava.crop(box)
# Combine all three and paste it back into original image
part = Image.composite(crop, row, mask)
ava.paste(part, box)
top += height
os.remove(f)
ava.save(f)
file = discord.File(f)
return file
async def image_logging(self, ctx, output):
"""Logging function for all image commands to avoid shit loads or repeating code.
Required because image has outputs that are user decided and therefore could need logging for."""
return await self.bot.log(
ctx.guild,
"image",
User=ctx.author,
User_ID=ctx.author.id,
Output_Message_ID=output.id,
Channel=ctx.channel,
Channel_Mention=ctx.channel.mention,
Time="{:%a %Y/%m/%d %H:%M:%S} UTC".format(ctx.message.created_at)
)
@commands.group(case_insensitive=True)
async def pride(self, ctx):
"""`;pride` is a command group for multiple pride flag filters."""
if ctx.invoked_subcommand is None:
raise commands.CommandNotFound("Subcommand '{}' does not exist.".format(ctx.subcommand_passed))
@pride.command()
async def lgbt(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a LGBT Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.lgbt()
async with ctx.typing():
file = await self.flag_filter("lgbt", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["trans"])
async def transgender(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Trans Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.trans()
async with ctx.typing():
file = await self.flag_filter("trans", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["nb", "enby"])
async def nonbinary(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Non-Binary Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.non_binary()
async with ctx.typing():
file = await self.flag_filter("nb", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["bi"])
async def bisexual(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Bisexual Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.bi()
async with ctx.typing():
file = await self.flag_filter("bi", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["gq"])
async def genderqueer(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Gender Queer Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.gq()
async with ctx.typing():
file = await self.flag_filter("gq", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["pan"])
async def pansexual(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Pansexual Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.pan()
async with ctx.typing():
file = await self.flag_filter("pan", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["ace"])
async def asexual(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds an Asexual Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.ace()
async with ctx.typing():
file = await self.flag_filter("ace", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["gf"])
async def genderfluid(self, ctx, image: roxbot.converters.AvatarURL = None):
"""Adds a Gender Fluid Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.gf()
async with ctx.typing():
file = await self.flag_filter("gf", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command()
async def agender(self, ctx, image: roxbot.converters.AvatarURL = None):
"""Adds an Agender Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.agender()
async with ctx.typing():
file = await self.flag_filter("agender", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@commands.command(aliases=["df"])
async def deepfry(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Deepfrys the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
filename = await roxbot.http.download_file(image)
async with ctx.typing():
# Convert to jpg
if filename.split(".")[-1] != "jpg":
jpg_name = filename.split(".")[0] + ".jpg"
img = Image.open(filename)
img = img.convert(mode="RGB")
img.save(jpg_name)
os.remove(filename)
else:
jpg_name = filename
img = Image.open(jpg_name)
# Brightness Enhance
ehn = ImageEnhance.Brightness(img)
img = ehn.enhance(1.25)
# Contrast Enhance
ehn = ImageEnhance.Contrast(img)
img = ehn.enhance(1.5)
# Sharpness Enhance
ehn = ImageEnhance.Sharpness(img)
img = ehn.enhance(20)
# Saturation Enhance
ehn = ImageEnhance.Color(img)
img = ehn.enhance(2)
# Add Salt and Pepper Noise
img = self.add_grain(img)
img.save(jpg_name)
# JPG-fy image
for x in range(20):
img = Image.open(jpg_name)
img = img.convert(mode="RGB")
img.save(jpg_name)
output = await ctx.send(file=discord.File(jpg_name))
os.remove(jpg_name)
await self.image_logging(ctx, output)
"""The ImageEditor cog is a cog with multiple commands to manipulate images provided by the user."""
def __init__(self, bot_client):
self.bot = bot_client
@staticmethod
def image_lookup(message):
try:
if message.attachments[0].height: # Check if attachment is image
return message.attachments[0].url
except IndexError:
return message.author.avatar_url_as(format="png")
@staticmethod
def add_grain(img, prob=0.2, opacity=30):
"""
Adds salt and pepper grain to the given image.
:param img: :type PIL.Image: Image to add grain to
:param prob: :type float: Probability of a pixel being black between 0-1
:param opacity: :type int: opacity of the grain when composite with the given image between 0%-100%
:return: :type PIL.Image: Image with added grain
"""
img_matrix = np.zeros((img.height, img.width), dtype=np.uint8)
for y in range(img.height):
for x in range(img.width):
if prob < random.random():
img_matrix[y][x] = 255
noisy = Image.fromarray(img_matrix, "L")
noisy = noisy.convert("RGB")
mask = Image.new('RGBA', img.size, (0, 0, 0, opacity))
return Image.composite(noisy, img, mask)
@staticmethod
async def flag_filter(name, flag, url):
"""At the moment, can only make horizontal stripe flags"""
f = 'filter_{}.png'.format(name)
await roxbot.http.download_file(url, f)
ava = Image.open(f)
top = 0 # In the box we use, top is used to define which part of the image we are working on
bottom = 0 # And bottom defines the height. That should help you visualise why I increment the values the way I do
for x, colour in enumerate(flag.colours):
# Grab the next slice of the images height and width
# we use math.ceil here to avoid rounding errors when converting float to int
height = int(math.ceil(ava.height * flag.ratio[x]))
width = ava.width
bottom += height
box = (0, top, width, bottom)
# Make the colour block and the transparency mask at the slice size. Then crop the next part of the image
row = Image.new('RGB', (width, height), colour)
mask = Image.new('RGBA', (width, height), (0, 0, 0, 123))
crop = ava.crop(box)
# Combine all three and paste it back into original image
part = Image.composite(crop, row, mask)
ava.paste(part, box)
top += height
os.remove(f)
ava.save(f)
file = discord.File(f)
return file
async def image_logging(self, ctx, output):
"""Logging function for all image commands to avoid shit loads or repeating code.
Required because image has outputs that are user decided and therefore could need logging for."""
return await self.bot.log(
ctx.guild,
"image",
User=ctx.author,
User_ID=ctx.author.id,
Output_Message_ID=output.id,
Channel=ctx.channel,
Channel_Mention=ctx.channel.mention,
Time="{:%a %Y/%m/%d %H:%M:%S} UTC".format(ctx.message.created_at)
)
@commands.group(case_insensitive=True)
async def pride(self, ctx):
"""`;pride` is a command group for multiple pride flag filters."""
if ctx.invoked_subcommand is None:
raise commands.CommandNotFound("Subcommand '{}' does not exist.".format(ctx.subcommand_passed))
@pride.command()
async def lgbt(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a LGBT Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.lgbt()
async with ctx.typing():
file = await self.flag_filter("lgbt", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["trans"])
async def transgender(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Trans Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.trans()
async with ctx.typing():
file = await self.flag_filter("trans", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["nb", "enby"])
async def nonbinary(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Non-Binary Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.non_binary()
async with ctx.typing():
file = await self.flag_filter("nb", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["bi"])
async def bisexual(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Bisexual Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.bi()
async with ctx.typing():
file = await self.flag_filter("bi", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["gq"])
async def genderqueer(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Gender Queer Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.gq()
async with ctx.typing():
file = await self.flag_filter("gq", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["pan"])
async def pansexual(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds a Pansexual Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.pan()
async with ctx.typing():
file = await self.flag_filter("pan", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["ace"])
async def asexual(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Adds an Asexual Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.ace()
async with ctx.typing():
file = await self.flag_filter("ace", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command(aliases=["gf"])
async def genderfluid(self, ctx, image: roxbot.converters.AvatarURL = None):
"""Adds a Gender Fluid Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.gf()
async with ctx.typing():
file = await self.flag_filter("gf", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@pride.command()
async def agender(self, ctx, image: roxbot.converters.AvatarURL = None):
"""Adds an Agender Pride Flag filter to the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
flag = PrideFlags.agender()
async with ctx.typing():
file = await self.flag_filter("agender", flag, image)
output = await ctx.send(file=file)
os.remove(file.filename)
await self.image_logging(ctx, output)
@commands.command(aliases=["df"])
async def deepfry(self, ctx, image: roxbot.converters.AvatarURL=None):
"""Deepfrys the given image
Args:
image: Optional
If nothing, your avatar
Mention a user, their avatar
Provide a URL, that image
Provide an image via upload, that image.
"""
if not image:
image = self.image_lookup(ctx.message)
filename = await roxbot.http.download_file(image)
async with ctx.typing():
# Convert to jpg
if filename.split(".")[-1] != "jpg":
jpg_name = filename.split(".")[0] + ".jpg"
img = Image.open(filename)
img = img.convert(mode="RGB")
img.save(jpg_name)
os.remove(filename)
else:
jpg_name = filename
img = Image.open(jpg_name)
# Brightness Enhance
ehn = ImageEnhance.Brightness(img)
img = ehn.enhance(1.25)
# Contrast Enhance
ehn = ImageEnhance.Contrast(img)
img = ehn.enhance(1.5)
# Sharpness Enhance
ehn = ImageEnhance.Sharpness(img)
img = ehn.enhance(20)
# Saturation Enhance
ehn = ImageEnhance.Color(img)
img = ehn.enhance(2)
# Add Salt and Pepper Noise
img = self.add_grain(img)
img.save(jpg_name)
# JPG-fy image
for x in range(20):
img = Image.open(jpg_name)
img = img.convert(mode="RGB")
img.save(jpg_name)
output = await ctx.send(file=discord.File(jpg_name))
os.remove(jpg_name)
await self.image_logging(ctx, output)


def setup(bot_client):
bot_client.add_cog(ImageEditor(bot_client))
bot_client.add_cog(ImageEditor(bot_client))

+ 136
- 136
roxbot/cogs/joinleave.py View File

@@ -33,144 +33,144 @@ from roxbot.db import *


class JoinLeaveSingle(db.Entity):
greets_enabled = Required(bool, default=False)
goodbyes_enabled = Required(bool, default=False)
greets_channel_id = Optional(int, nullable=True, size=64)
goodbyes_channel_id = Optional(int, nullable=True, size=64)
greets_custom_message = Optional(str, nullable=True)
guild_id = Required(int, size=64, unique=True)
greets_enabled = Required(bool, default=False)
goodbyes_enabled = Required(bool, default=False)
greets_channel_id = Optional(int, nullable=True, size=64)
goodbyes_channel_id = Optional(int, nullable=True, size=64)
greets_custom_message = Optional(str, nullable=True)
guild_id = Required(int, size=64, unique=True)


class JoinLeave(commands.Cog):
"""JoinLeave is a cog that allows you to create custom welcome and goodbye messages for your Discord server. """
DEFAULT_MESSAGE = "Be sure to read the rules."
def __init__(self, bot_client):
self.bot = bot_client
self.autogen_db = JoinLeaveSingle
@commands.Cog.listener()
async def on_member_join(self, member):
"""
Greets users when they join a server.
"""
if member == self.bot.user:
return
with db_session:
settings = JoinLeaveSingle.get(guild_id=member.guild.id)
if not settings.greets_enabled:
return
message = settings.greets_custom_message or self.DEFAULT_MESSAGE
em = discord.Embed(
title="Welcome to {}!".format(member.guild),
description='Hey {}! Welcome to **{}**! {}'.format(member.mention, member.guild, message),
colour=roxbot.EmbedColours.pink)
em.set_thumbnail(url=member.avatar_url)
channel = member.guild.get_channel(settings.greets_channel_id)
return await channel.send(embed=em)
@commands.Cog.listener()
async def on_member_remove(self, member):
"""
The same but the opposite
"""
if member == self.bot.user:
return
with db_session:
settings = JoinLeaveSingle.get(guild_id=member.guild.id)
if settings.goodbyes_enabled:
try:
channel = member.guild.get_channel(settings.goodbyes_channel_id)
return await channel.send(embed=discord.Embed(
description="{}#{} has left or been beaned.".format(member.name, member.discriminator), colour=roxbot.EmbedColours.pink))
except AttributeError:
pass
@commands.Cog.listener()
async def on_guild_channel_delete(self, channel):
"""Cleans up settings on removal of stored IDs."""
with db_session:
settings = JoinLeaveSingle.get(guild_id=channel.guild.id)
if channel.id == settings.greets_channel_id:
settings.greets_channel_id = None
if channel.id == settings.goodbyes_channel_id:
settings.goodbyes_channel_id = None
@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@commands.command()
async def greets(self, ctx, setting, channel: typing.Optional[discord.TextChannel] = None, *, text = ""):
"""Edits settings for the Welcome Messages
Options:
enable/disable: Enable/disables greet messages.
channel: Sets the channel for the message to be posted in. If no channel is provided, it will default to the channel the command is executed in.
message: Specifies a custom message for the greet messages.
Example:
Enable greet messages, set the channel to the current one, and set a custom message to be appended.
`;greets enable`
`;greets message "Be sure to read the rules and say hi! :wave:"`
`;greets channel` # if no channel is provided, it will default to the channel the command is executed in.
"""
setting = setting.lower()
with db_session:
settings = JoinLeaveSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
settings.greets_enabled = True
await ctx.send("'greets' was enabled!")
elif setting == "disable":
settings.greets_enabled = False
await ctx.send("'greets' was disabled :cry:")
elif setting in ("channel", "greet-channel"):
channel = channel or ctx.channel
settings.greets_channel_id = channel.id
await ctx.send("Set greets channel to {}".format(channel.mention))
elif setting in ("message", "custom-message"):
settings.greets_custom_message = text
await ctx.send("Custom message set to '{}'".format(text))
else:
return await ctx.send("No valid option given.")
@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@commands.command()
async def goodbyes(self, ctx, setting, *, channel: typing.Optional[discord.TextChannel] = None):
"""Edits settings for the goodbye messages.
Options:
enable/disable: Enable/disables goodbye messages.
channel: Sets the channel for the message to be posted in. If no channel is provided, it will default to the channel the command is executed in.
Example:
Enable goodbye messages, set the channel one called `#logs`
`;goodbyes enable`
`;goodbyes channel #logs`
"""
setting = setting.lower()
with db_session:
settings = JoinLeaveSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
settings.goodbyes_enabled = True
await ctx.send("'goodbyes' was enabled!")
elif setting == "disable":
settings.goodbyes_enabled = False
await ctx.send("'goodbyes' was disabled :cry:")
elif setting in ("channel", "goodbye-channel"):
channel = channel or ctx.channel
settings.goodbyes_channel_id = channel.id
await ctx.send("Set goodbye channel to {}".format(channel.mention))
else:
return await ctx.send("No valid option given.")
"""JoinLeave is a cog that allows you to create custom welcome and goodbye messages for your Discord server. """
DEFAULT_MESSAGE = "Be sure to read the rules."
def __init__(self, bot_client):
self.bot = bot_client
self.autogen_db = JoinLeaveSingle
@commands.Cog.listener()
async def on_member_join(self, member):
"""
Greets users when they join a server.
"""
if member == self.bot.user:
return
with db_session:
settings = JoinLeaveSingle.get(guild_id=member.guild.id)
if not settings.greets_enabled:
return
message = settings.greets_custom_message or self.DEFAULT_MESSAGE
em = discord.Embed(
title="Welcome to {}!".format(member.guild),
description='Hey {}! Welcome to **{}**! {}'.format(member.mention, member.guild, message),
colour=roxbot.EmbedColours.pink)
em.set_thumbnail(url=member.avatar_url)
channel = member.guild.get_channel(settings.greets_channel_id)
return await channel.send(embed=em)
@commands.Cog.listener()
async def on_member_remove(self, member):
"""
The same but the opposite
"""
if member == self.bot.user:
return
with db_session:
settings = JoinLeaveSingle.get(guild_id=member.guild.id)
if settings.goodbyes_enabled:
try:
channel = member.guild.get_channel(settings.goodbyes_channel_id)
return await channel.send(embed=discord.Embed(
description="{}#{} has left or been beaned.".format(member.name, member.discriminator), colour=roxbot.EmbedColours.pink))
except AttributeError:
pass
@commands.Cog.listener()
async def on_guild_channel_delete(self, channel):
"""Cleans up settings on removal of stored IDs."""
with db_session:
settings = JoinLeaveSingle.get(guild_id=channel.guild.id)
if channel.id == settings.greets_channel_id:
settings.greets_channel_id = None
if channel.id == settings.goodbyes_channel_id:
settings.goodbyes_channel_id = None
@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@commands.command()
async def greets(self, ctx, setting, channel: typing.Optional[discord.TextChannel] = None, *, text = ""):
"""Edits settings for the Welcome Messages
Options:
enable/disable: Enable/disables greet messages.
channel: Sets the channel for the message to be posted in. If no channel is provided, it will default to the channel the command is executed in.
message: Specifies a custom message for the greet messages.
Example:
Enable greet messages, set the channel to the current one, and set a custom message to be appended.
`;greets enable`
`;greets message "Be sure to read the rules and say hi! :wave:"`
`;greets channel` # if no channel is provided, it will default to the channel the command is executed in.
"""
setting = setting.lower()
with db_session:
settings = JoinLeaveSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
settings.greets_enabled = True
await ctx.send("'greets' was enabled!")
elif setting == "disable":
settings.greets_enabled = False
await ctx.send("'greets' was disabled :cry:")
elif setting in ("channel", "greet-channel"):
channel = channel or ctx.channel
settings.greets_channel_id = channel.id
await ctx.send("Set greets channel to {}".format(channel.mention))
elif setting in ("message", "custom-message"):
settings.greets_custom_message = text
await ctx.send("Custom message set to '{}'".format(text))
else:
return await ctx.send("No valid option given.")
@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@commands.command()
async def goodbyes(self, ctx, setting, *, channel: typing.Optional[discord.TextChannel] = None):
"""Edits settings for the goodbye messages.
Options:
enable/disable: Enable/disables goodbye messages.
channel: Sets the channel for the message to be posted in. If no channel is provided, it will default to the channel the command is executed in.
Example:
Enable goodbye messages, set the channel one called `#logs`
`;goodbyes enable`
`;goodbyes channel #logs`
"""
setting = setting.lower()
with db_session:
settings = JoinLeaveSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
settings.goodbyes_enabled = True
await ctx.send("'goodbyes' was enabled!")
elif setting == "disable":
settings.goodbyes_enabled = False
await ctx.send("'goodbyes' was disabled :cry:")
elif setting in ("channel", "goodbye-channel"):
channel = channel or ctx.channel
settings.goodbyes_channel_id = channel.id
await ctx.send("Set goodbye channel to {}".format(channel.mention))
else:
return await ctx.send("No valid option given.")

def setup(bot_client):
bot_client.add_cog(JoinLeave(bot_client))
bot_client.add_cog(JoinLeave(bot_client))

+ 121
- 121
roxbot/cogs/nsfw.py View File

@@ -31,130 +31,130 @@ from roxbot.db import *


class NSFWSingle(db.Entity):
enabled = Required(bool, default=False)
blacklisted_tags = Optional(StrArray)
guild_id = Required(int, unique=True, size=64)
enabled = Required(bool, default=False)
blacklisted_tags = Optional(StrArray)
guild_id = Required(int, unique=True, size=64)


class NSFW(commands.Cog):
"""The NSFW cog is a collection of commands that post images from popular NSFW sites. """
def __init__(self, bot_client):
self.bot = bot_client
self.cache = {}
self.autogen_db = NSFWSingle
@db_session
def tag_blacklist(self, guild):
blacklist = ""
blacklist_db = NSFWSingle.get(guild_id=guild.id).blacklisted_tags
for tag in blacklist_db:
blacklist += " -{}".format(tag)
return blacklist
async def gelbooru_clone(self, ctx, base_url, endpoint_url, tags):
if isinstance(ctx.channel, discord.TextChannel):
banned_tags = self.tag_blacklist(ctx.guild)
else:
banned_tags = ""
post = await roxbot.utils.danbooru_clone_api_req(
ctx.channel,
base_url,
endpoint_url,
tags=tags,
banned_tags=banned_tags,
cache=self.cache
)
if not post:
return await ctx.send("Nothing was found. *psst, check the tags you gave me.*")
else:
output = await ctx.send(post)
await self.bot.delete_option(output, self.bot.get_emoji(444410658101002261))
@roxbot.checks.is_nsfw()
@commands.command()
async def e621(self, ctx, *, tags=""):
"""Posts a random image from https://e621.net using the tags you provide. Tags can be anything you would use to search the site normally like author and ratings.
https://e621.net limits searches to 6 tags via the API. Blacklisting a lot of tags may break this command.
Examples:
# Post a random image
;e621
# Post a random image with the tag "test"
;e621 test
"""
base_url = "https://e621.net/post/index.json?tags="
return await self.gelbooru_clone(ctx, base_url, "", tags)
@roxbot.checks.is_nsfw()
@commands.command()
async def rule34(self, ctx, *, tags=""):
"""Posts a random image from https://rule34.xxx/ using the tags you provide. Tags can be anything you would use to search the site normally like author and ratings.
Examples:
# Post a random image
;rule34
# Post a random image with the tag "test"
;rule34 test
"""
base_url = "https://rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&tags="
endpoint_url = "https://img.rule34.xxx/images/"
return await self.gelbooru_clone(ctx, base_url, endpoint_url, tags)
@roxbot.checks.is_nsfw()
@commands.command()
async def gelbooru(self, ctx, *, tags=""):
"""Posts a random image from https://gelbooru.com using the tags you provide. Tags can be anything you would use to search the site normally like author and ratings.
Examples:
# Post a random image
;gelbooru
# Post a random image with the tag "test"
;gelbooru test
"""
base_url = "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1&tags="
endpoint_url = "https://simg3.gelbooru.com/images/"
return await self.gelbooru_clone(ctx, base_url, endpoint_url, tags)
@commands.guild_only()
@commands.has_permissions(manage_channels=True)
@commands.command()
async def nsfw(self, ctx, setting, *, changes=None):
"""Edits settings for the nsfw cog and other nsfw commands.
Options:
enable/disable: Enable/disables nsfw commands.
addbadtag/removebadtag: Add/Removes blacklisted tags so that you can avoid em with the commands.
Examples:
# Enabled NSFW commands
;nsfw enable
# Add "test" as a blacklisted tag
;nsfw addbadtag test
# Remove "Roxbot" as a blacklisted tag
;nsfw removebadtag Roxbot
"""
with db_session:
nsfw_settings = NSFWSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
nsfw_settings.enabled = True
await ctx.send("'nsfw' was enabled!")
elif setting == "disable":
nsfw_settings.enabled = False
await ctx.send("'nsfw' was disabled :cry:")
elif setting == "addbadtag":
if changes not in nsfw_settings.blacklisted_tags:
nsfw_settings.blacklisted_tags.append(changes)
await ctx.send("'{}' has been added to the blacklisted tag list.".format(changes))
else:
return await ctx.send("'{}' is already in the list.".format(changes))
elif setting == "removebadtag":
try:
nsfw_settings.blacklisted_tags.remove(changes)
await ctx.send("'{}' has been removed from the blacklisted tag list.".format(changes))
except ValueError:
return await ctx.send("That tag was not in the blacklisted tag list.")
else:
return await ctx.send("No valid option given.")
"""The NSFW cog is a collection of commands that post images from popular NSFW sites. """
def __init__(self, bot_client):
self.bot = bot_client
self.cache = {}
self.autogen_db = NSFWSingle
@db_session
def tag_blacklist(self, guild):
blacklist = ""
blacklist_db = NSFWSingle.get(guild_id=guild.id).blacklisted_tags
for tag in blacklist_db:
blacklist += " -{}".format(tag)
return blacklist
async def gelbooru_clone(self, ctx, base_url, endpoint_url, tags):
if isinstance(ctx.channel, discord.TextChannel):
banned_tags = self.tag_blacklist(ctx.guild)
else:
banned_tags = ""
post = await roxbot.utils.danbooru_clone_api_req(
ctx.channel,
base_url,
endpoint_url,
tags=tags,
banned_tags=banned_tags,
cache=self.cache
)
if not post:
return await ctx.send("Nothing was found. *psst, check the tags you gave me.*")
else:
output = await ctx.send(post)
await self.bot.delete_option(output, self.bot.get_emoji(444410658101002261))
@roxbot.checks.is_nsfw()
@commands.command()
async def e621(self, ctx, *, tags=""):
"""Posts a random image from https://e621.net using the tags you provide. Tags can be anything you would use to search the site normally like author and ratings.
https://e621.net limits searches to 6 tags via the API. Blacklisting a lot of tags may break this command.
Examples:
# Post a random image
;e621
# Post a random image with the tag "test"
;e621 test
"""
base_url = "https://e621.net/post/index.json?tags="
return await self.gelbooru_clone(ctx, base_url, "", tags)
@roxbot.checks.is_nsfw()
@commands.command()
async def rule34(self, ctx, *, tags=""):
"""Posts a random image from https://rule34.xxx/ using the tags you provide. Tags can be anything you would use to search the site normally like author and ratings.
Examples:
# Post a random image
;rule34
# Post a random image with the tag "test"
;rule34 test
"""
base_url = "https://rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&tags="
endpoint_url = "https://img.rule34.xxx/images/"
return await self.gelbooru_clone(ctx, base_url, endpoint_url, tags)
@roxbot.checks.is_nsfw()
@commands.command()
async def gelbooru(self, ctx, *, tags=""):
"""Posts a random image from https://gelbooru.com using the tags you provide. Tags can be anything you would use to search the site normally like author and ratings.
Examples:
# Post a random image
;gelbooru
# Post a random image with the tag "test"
;gelbooru test
"""
base_url = "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1&tags="
endpoint_url = "https://simg3.gelbooru.com/images/"
return await self.gelbooru_clone(ctx, base_url, endpoint_url, tags)
@commands.guild_only()
@commands.has_permissions(manage_channels=True)
@commands.command()
async def nsfw(self, ctx, setting, *, changes=None):
"""Edits settings for the nsfw cog and other nsfw commands.
Options:
enable/disable: Enable/disables nsfw commands.
addbadtag/removebadtag: Add/Removes blacklisted tags so that you can avoid em with the commands.
Examples:
# Enabled NSFW commands
;nsfw enable
# Add "test" as a blacklisted tag
;nsfw addbadtag test
# Remove "Roxbot" as a blacklisted tag
;nsfw removebadtag Roxbot
"""
with db_session:
nsfw_settings = NSFWSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
nsfw_settings.enabled = True
await ctx.send("'nsfw' was enabled!")
elif setting == "disable":
nsfw_settings.enabled = False
await ctx.send("'nsfw' was disabled :cry:")
elif setting == "addbadtag":
if changes not in nsfw_settings.blacklisted_tags:
nsfw_settings.blacklisted_tags.append(changes)
await ctx.send("'{}' has been added to the blacklisted tag list.".format(changes))
else:
return await ctx.send("'{}' is already in the list.".format(changes))
elif setting == "removebadtag":
try:
nsfw_settings.blacklisted_tags.remove(changes)
await ctx.send("'{}' has been removed from the blacklisted tag list.".format(changes))
except ValueError:
return await ctx.send("That tag was not in the blacklisted tag list.")
else:
return await ctx.send("No valid option given.")


def setup(bot_client):
bot_client.add_cog(NSFW(bot_client))
bot_client.add_cog(NSFW(bot_client))

+ 249
- 249
roxbot/cogs/reddit.py View File

@@ -35,257 +35,257 @@ import roxbot


class Scrapper:
"""Scrapper is a class to aid in the scrapping of reddit subreddit's of images. (includes small amount of video support)
This includes its own caching system."""
# TODO: Reimplement eroshare, eroshae, and erome support.
# Also implement better api interaction with imgur now we require api key
def __init__(self, cache_limit=10):
self.post_cache = {}
self.cache_limit = cache_limit
@staticmethod
async def _imgur_removed(url):
try:
page = await roxbot.http.get_page(url)
except UnicodeDecodeError:
return False # This is if it is an image with a weird url
soup = BeautifulSoup(page, 'html.parser')
if "404 Not Found" in soup.title.string:
return True
try:
return bool("removed.png" in soup.img["src"])
except TypeError: # This should protect roxbot in case bs4 returns nothing.
return False
async def imgur_get(self, url):
extensions = ("png", "jpg", "jpeg", "gif", "gifv", "mp4", "webm", "webp")
for ext in extensions:
if fnmatch.fnmatch(url.split(".")[-1], ext+"*"):
# This is fixing issues where urls will have ".png?2" or some shit
return url
if await self._imgur_removed(url):
return False
if not roxbot.imgur_token:
return False
base_endpoint = "https://api.imgur.com/3/"
endpoint_album = base_endpoint + "album/{}/images.json".format(url.split("/")[-1])
endpoint_image = base_endpoint + "image/{}.json".format(url.split("/")[-1])
try:
resp = await roxbot.http.api_request(endpoint_image, headers={"Authorization": "Client-ID {}".format(roxbot.imgur_token)})
if bool(resp["success"]) is True:
return resp["data"]["link"]
else:
resp = await roxbot.http.api_request(endpoint_album, headers={"Authorization": "Client-ID {}".format(roxbot.imgur_token)})
return resp["data"][0]["link"]
except TypeError:
return False
async def parse_url(self, url):
if url.split(".")[-1] in ("png", "jpg", "jpeg", "gif", "gifv", "webm", "mp4", "webp"):
return url
if "imgur" in url:
return await self.imgur_get(url)
elif "eroshare" in url or "eroshae" in url or "erome" in url:
return None
# return ero_get(url)
elif "gfycat" in url or "redd.it" in url or "i.reddituploads" in url or "media.tumblr" in url or "streamable" in url:
return url
elif "youtube" in url or "youtu.be" in url:
return url
else:
return None
@staticmethod
async def sub_request(subreddit):
# TODO: Incorperate /random.json for better random results
options = [".json?count=1000", "/top/.json?sort=top&t=all&count=1000"]
choice = random.choice(options)
subreddit += choice
url = "https://reddit.com/r/" + subreddit
try:
r = await roxbot.http.api_request(url)
posts = r["data"]
# This part is to check for some common errors when doing a sub request
# t3 is a post in a listing. We want to avoid not having this instead of a subreddit search, which would be t5.
if not posts.get("after") or posts["children"][0]["kind"] != "t3":
return {}
return posts
except (KeyError, TypeError):
return {}
def cache_refresh(self, cache_id):
# IF ID is not in cache, create cache for ID
if not self.post_cache.get(cache_id, False):
self.post_cache[cache_id] = [("", "")]
def add_to_cache(self, to_cache, cache_id):
self.post_cache[cache_id].append(to_cache)
def cache_clean_up(self, cache_id):
if len(self.post_cache[cache_id]) >= self.cache_limit:
self.post_cache[cache_id].pop(0)
async def random(self, posts, cache_id, nsfw_allowed, loop_amount=20):
"""Function to pick a random post of a given list of reddit posts. Using the internal cache.
Returns:
None for failing to get a url that could be posted.
A dict with the key success and the value False for failing the NSFW check
or the post dict if getting the post is successful
"""
# Loop to get the post randomly and make sure it hasn't been posted before
url = None
choice = None
for x in range(loop_amount):
choice = random.choice(posts)
url = await self.parse_url(choice["data"]["url"])
if url:
# "over_18" is not a typo. For some fucking reason, reddit has "over_18" for posts, "over18" for subs.
if not nsfw_allowed and choice["data"]["over_18"]:
url = False # Reject post and move to next loop
else:
# Check cache for post
in_cache = False
for cache in self.post_cache[cache_id]:
if url in cache or choice["data"]["id"] in cache:
in_cache = True
if not in_cache:
break
# This is for either a False (NSFW post not allowed) or a None for none.
if url is None:
return {}
elif url is False:
return {"success": False}
# Cache post
self.add_to_cache((choice["data"]["id"], url), cache_id)
# If too many posts in cache, remove oldest value.
self.cache_clean_up(cache_id)
return choice["data"]
"""Scrapper is a class to aid in the scrapping of reddit subreddit's of images. (includes small amount of video support)
This includes its own caching system."""
# TODO: Reimplement eroshare, eroshae, and erome support.
# Also implement better api interaction with imgur now we require api key
def __init__(self, cache_limit=10):
self.post_cache = {}
self.cache_limit = cache_limit
@staticmethod
async def _imgur_removed(url):
try:
page = await roxbot.http.get_page(url)
except UnicodeDecodeError:
return False # This is if it is an image with a weird url
soup = BeautifulSoup(page, 'html.parser')
if "404 Not Found" in soup.title.string:
return True
try:
return bool("removed.png" in soup.img["src"])
except TypeError: # This should protect roxbot in case bs4 returns nothing.
return False
async def imgur_get(self, url):
extensions = ("png", "jpg", "jpeg", "gif", "gifv", "mp4", "webm", "webp")
for ext in extensions:
if fnmatch.fnmatch(url.split(".")[-1], ext+"*"):
# This is fixing issues where urls will have ".png?2" or some shit
return url
if await self._imgur_removed(url):
return False
if not roxbot.imgur_token:
return False
base_endpoint = "https://api.imgur.com/3/"
endpoint_album = base_endpoint + "album/{}/images.json".format(url.split("/")[-1])
endpoint_image = base_endpoint + "image/{}.json".format(url.split("/")[-1])
try:
resp = await roxbot.http.api_request(endpoint_image, headers={"Authorization": "Client-ID {}".format(roxbot.imgur_token)})
if bool(resp["success"]) is True:
return resp["data"]["link"]
else:
resp = await roxbot.http.api_request(endpoint_album, headers={"Authorization": "Client-ID {}".format(roxbot.imgur_token)})
return resp["data"][0]["link"]
except TypeError:
return False
async def parse_url(self, url):
if url.split(".")[-1] in ("png", "jpg", "jpeg", "gif", "gifv", "webm", "mp4", "webp"):
return url
if "imgur" in url:
return await self.imgur_get(url)
elif "eroshare" in url or "eroshae" in url or "erome" in url:
return None
# return ero_get(url)
elif "gfycat" in url or "redd.it" in url or "i.reddituploads" in url or "media.tumblr" in url or "streamable" in url:
return url
elif "youtube" in url or "youtu.be" in url:
return url
else:
return None
@staticmethod
async def sub_request(subreddit):
# TODO: Incorperate /random.json for better random results
options = [".json?count=1000", "/top/.json?sort=top&t=all&count=1000"]
choice = random.choice(options)
subreddit += choice
url = "https://reddit.com/r/" + subreddit
try:
r = await roxbot.http.api_request(url)
posts = r["data"]
# This part is to check for some common errors when doing a sub request
# t3 is a post in a listing. We want to avoid not having this instead of a subreddit search, which would be t5.
if not posts.get("after") or posts["children"][0]["kind"] != "t3":
return {}
return posts
except (KeyError, TypeError):
return {}
def cache_refresh(self, cache_id):
# IF ID is not in cache, create cache for ID
if not self.post_cache.get(cache_id, False):
self.post_cache[cache_id] = [("", "")]
def add_to_cache(self, to_cache, cache_id):
self.post_cache[cache_id].append(to_cache)
def cache_clean_up(self, cache_id):
if len(self.post_cache[cache_id]) >= self.cache_limit:
self.post_cache[cache_id].pop(0)
async def random(self, posts, cache_id, nsfw_allowed, loop_amount=20):
"""Function to pick a random post of a given list of reddit posts. Using the internal cache.
Returns:
None for failing to get a url that could be posted.
A dict with the key success and the value False for failing the NSFW check
or the post dict if getting the post is successful
"""
# Loop to get the post randomly and make sure it hasn't been posted before
url = None
choice = None
for x in range(loop_amount):
choice = random.choice(posts)
url = await self.parse_url(choice["data"]["url"])
if url:
# "over_18" is not a typo. For some fucking reason, reddit has "over_18" for posts, "over18" for subs.
if not nsfw_allowed and choice["data"]["over_18"]:
url = False # Reject post and move to next loop
else:
# Check cache for post
in_cache = False
for cache in self.post_cache[cache_id]:
if url in cache or choice["data"]["id"] in cache:
in_cache = True
if not in_cache:
break
# This is for either a False (NSFW post not allowed) or a None for none.
if url is None:
return {}
elif url is False:
return {"success": False}
# Cache post
self.add_to_cache((choice["data"]["id"], url), cache_id)
# If too many posts in cache, remove oldest value.
self.cache_clean_up(cache_id)
return choice["data"]


class Reddit(commands.Cog):
"""The Reddit cog is a cog that allows users to get images and videos from their favourite subreddits."""
SUB_NOT_FOUND = "Error ;-; That subreddit probably doesn't exist. Please check your spelling"
NO_IMAGES = "I couldn't find any images/videos from that subreddit."
NSFW_FAIL = "This channel isn't marked NSFW and therefore I can't post NSFW content. The subreddit given or all posts found are NSFW."
def __init__(self, bot_client):
self.bot = bot_client
self.scrapper = Scrapper()
if not roxbot.imgur_token:
print("REDDIT COG REQUIRES A IMGUR API TOKEN. Without this, roxbot will not return imgur links.")
@commands.command()
@commands.has_permissions(add_reactions=True)
@commands.bot_has_permissions(add_reactions=True)
async def subreddit(self, ctx, subreddit):
"""
Grabs an image or video (jpg, png, gif, gifv, webm, mp4) from the subreddit inputted.
Example:
{command_prefix}subreddit pics
"""
subreddit = subreddit.lower()
if isinstance(ctx.channel, discord.DMChannel):
cache_id = ctx.author.id
nsfw_allowed = True
else: # Is text channel in guild
cache_id = ctx.guild.id
nsfw_allowed = ctx.channel.is_nsfw()
self.scrapper.cache_refresh(cache_id)
posts = await self.scrapper.sub_request(subreddit)
if not posts:
raise roxbot.UserError(self.SUB_NOT_FOUND)
choice = await self.scrapper.random(posts["children"], cache_id, nsfw_allowed)
if not choice:
raise commands.CommandError(self.NO_IMAGES)
elif choice.get("success", True) is False:
raise roxbot.UserError(self.NSFW_FAIL)
title = "**{}** \nby /u/{} from /r/{}\n".format(unescape(choice["title"]), unescape(choice["author"]), subreddit)
url = str(choice["url"])
if url.split("/")[-2] == "a":
text = "This is an album, click on the link to see more.\n"
else:
text = ""
# Not using a embed here because we can't use video in rich embeds but they work in embeds now :/
output = await ctx.send(title + text + url)
await self.bot.delete_option(output, self.bot.get_emoji(444410658101002261))
if ctx.invoked_with == "subreddit" and isinstance(ctx.channel, discord.TextChannel):
# Only log the command when it is this command being used. Not the inbuilt commands.
await self.bot.log(
ctx.guild,
"subreddit",
User=ctx.author,
Subreddit=subreddit,
Returned="<{}>".format(url),
Channel=ctx.channel,
Channel_Mention=ctx.channel.mention,
Time=roxbot.datetime.format(ctx.message.created_at)
)
@commands.command()
async def aww(self, ctx):
"""
Gives you cute pics from reddit
Subreddits: "aww", "redpandas", "lazycats", "rarepuppers", "awwgifs", "adorableart"
"""
subreddits = ("aww", "redpandas", "lazycats", "rarepuppers", "awwgifs", "adorableart")
subreddit = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit)
@commands.command()
async def feedme(self, ctx):
"""
Feeds you with food porn. Uses multiple subreddits.
Yes, I was very hungry when trying to find the subreddits for this command.
Subreddits: "foodporn", "food", "DessertPorn", "tonightsdinner", "eatsandwiches", "steak", "burgers", "Pizza", "grilledcheese", "PutAnEggOnIt", "sushi"
"""
subreddits = ("foodporn", "food", "DessertPorn", "tonightsdinner", "eatsandwiches", "steak", "burgers", "Pizza", "grilledcheese", "PutAnEggOnIt", "sushi")
subreddit_choice = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit_choice)
@commands.command()
async def feedmevegan(self, ctx):
"""
Feeds you with vegan food porn. Uses multiple subreddits.
Yes, I was very hungry when trying to find the subreddits for this command.
Subreddits: "veganrecipes", "vegangifrecipes", "veganfoodporn"
"""
subreddits = ["veganrecipes", "vegangifrecipes", "VeganFoodPorn"]
subreddit_choice = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit_choice)
@commands.command(aliases=["gssp", "gss", "trans_irl"])
async def traa(self, ctx):
"""
Gives you the best trans memes for daysssss
Subreddits: "gaysoundsshitposts", "traaaaaaannnnnnnnnns"
"""
subreddits = ("gaysoundsshitposts", "traaaaaaannnnnnnnnns")
subreddit = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit)
@commands.command(aliases=["meirl"])
async def me_irl(self, ctx):
"""
The full (mostly) me_irl network of subs.
Subreddits: "me_irl", "woof_irl", "meow_irl", "metal_me_irl"
"""
subreddits = ("me_irl", "woof_irl", "meow_irl", "metal_me_irl")
subreddit = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit)
"""The Reddit cog is a cog that allows users to get images and videos from their favourite subreddits."""
SUB_NOT_FOUND = "Error ;-; That subreddit probably doesn't exist. Please check your spelling"
NO_IMAGES = "I couldn't find any images/videos from that subreddit."
NSFW_FAIL = "This channel isn't marked NSFW and therefore I can't post NSFW content. The subreddit given or all posts found are NSFW."
def __init__(self, bot_client):
self.bot = bot_client
self.scrapper = Scrapper()
if not roxbot.imgur_token:
print("REDDIT COG REQUIRES A IMGUR API TOKEN. Without this, roxbot will not return imgur links.")
@commands.command()
@commands.has_permissions(add_reactions=True)
@commands.bot_has_permissions(add_reactions=True)
async def subreddit(self, ctx, subreddit):
"""
Grabs an image or video (jpg, png, gif, gifv, webm, mp4) from the subreddit inputted.
Example:
{command_prefix}subreddit pics
"""
subreddit = subreddit.lower()
if isinstance(ctx.channel, discord.DMChannel):
cache_id = ctx.author.id
nsfw_allowed = True
else: # Is text channel in guild
cache_id = ctx.guild.id
nsfw_allowed = ctx.channel.is_nsfw()
self.scrapper.cache_refresh(cache_id)
posts = await self.scrapper.sub_request(subreddit)
if not posts:
raise roxbot.UserError(self.SUB_NOT_FOUND)
choice = await self.scrapper.random(posts["children"], cache_id, nsfw_allowed)
if not choice:
raise commands.CommandError(self.NO_IMAGES)
elif choice.get("success", True) is False:
raise roxbot.UserError(self.NSFW_FAIL)
title = "**{}** \nby /u/{} from /r/{}\n".format(unescape(choice["title"]), unescape(choice["author"]), subreddit)
url = str(choice["url"])
if url.split("/")[-2] == "a":
text = "This is an album, click on the link to see more.\n"
else:
text = ""
# Not using a embed here because we can't use video in rich embeds but they work in embeds now :/
output = await ctx.send(title + text + url)
await self.bot.delete_option(output, self.bot.get_emoji(444410658101002261))
if ctx.invoked_with == "subreddit" and isinstance(ctx.channel, discord.TextChannel):
# Only log the command when it is this command being used. Not the inbuilt commands.
await self.bot.log(
ctx.guild,
"subreddit",
User=ctx.author,
Subreddit=subreddit,
Returned="<{}>".format(url),
Channel=ctx.channel,
Channel_Mention=ctx.channel.mention,
Time=roxbot.datetime.format(ctx.message.created_at)
)
@commands.command()
async def aww(self, ctx):
"""
Gives you cute pics from reddit
Subreddits: "aww", "redpandas", "lazycats", "rarepuppers", "awwgifs", "adorableart"
"""
subreddits = ("aww", "redpandas", "lazycats", "rarepuppers", "awwgifs", "adorableart")
subreddit = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit)
@commands.command()
async def feedme(self, ctx):
"""
Feeds you with food porn. Uses multiple subreddits.
Yes, I was very hungry when trying to find the subreddits for this command.
Subreddits: "foodporn", "food", "DessertPorn", "tonightsdinner", "eatsandwiches", "steak", "burgers", "Pizza", "grilledcheese", "PutAnEggOnIt", "sushi"
"""
subreddits = ("foodporn", "food", "DessertPorn", "tonightsdinner", "eatsandwiches", "steak", "burgers", "Pizza", "grilledcheese", "PutAnEggOnIt", "sushi")
subreddit_choice = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit_choice)
@commands.command()
async def feedmevegan(self, ctx):
"""
Feeds you with vegan food porn. Uses multiple subreddits.
Yes, I was very hungry when trying to find the subreddits for this command.
Subreddits: "veganrecipes", "vegangifrecipes", "veganfoodporn"
"""
subreddits = ["veganrecipes", "vegangifrecipes", "VeganFoodPorn"]
subreddit_choice = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit_choice)
@commands.command(aliases=["gssp", "gss", "trans_irl"])
async def traa(self, ctx):
"""
Gives you the best trans memes for daysssss
Subreddits: "gaysoundsshitposts", "traaaaaaannnnnnnnnns"
"""
subreddits = ("gaysoundsshitposts", "traaaaaaannnnnnnnnns")
subreddit = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit)
@commands.command(aliases=["meirl"])
async def me_irl(self, ctx):
"""
The full (mostly) me_irl network of subs.
Subreddits: "me_irl", "woof_irl", "meow_irl", "metal_me_irl"
"""
subreddits = ("me_irl", "woof_irl", "meow_irl", "metal_me_irl")
subreddit = random.choice(subreddits)
return await ctx.invoke(self.subreddit, subreddit=subreddit)

def setup(bot_client):
bot_client.add_cog(Reddit(bot_client))
bot_client.add_cog(Reddit(bot_client))

+ 141
- 141
roxbot/cogs/selfassign.py View File

@@ -31,153 +31,153 @@ from roxbot.db import *


class SelfAssignRoles(db.Entity):
role_id = Required(int, unique=True, size=64)
guild_id = Required(int, size=64)
role_id = Required(int, unique=True, size=64)
guild_id = Required(int, size=64)


class SelfAssignSingle(db.Entity):
enabled = Required(bool, default=False)
guild_id = Required(int, unique=True, size=64)
enabled = Required(bool, default=False)
guild_id = Required(int, unique=True, size=64)


class SelfAssign(commands.Cog):
"""The SelfAssign cog allows guild's to mark roles as 'self assignable'. This allows users to give themselves these roles and to see all the roles marked as 'self assignable'."""
def __init__(self, Bot):
self.bot = Bot
self.autogen_db = SelfAssignSingle
@commands.Cog.listener()
async def on_guild_role_delete(self, role):
"""Cleans up settings on removal of stored IDs."""
with db_session:
query = SelfAssignRoles.get(role_id=role.id)
if query:
query.delete()
@commands.guild_only()
@commands.has_permissions(manage_roles=True)
@commands.command(aliases=["sa"])
async def selfassign(self, ctx, setting, *, role: discord.Role = None):
"""Edits settings for self assign cog. Requires Manage Roles permission.
Options:
enable/disable: Enable/disables the cog.
add/remove: adds or removes a role that can be self assigned in the server.
Example:
Turn on self assigned roles and add a role called "ROLE"
`;sa enable`
`;sa add ROLE`
"""
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
self_assign.enabled = True
await ctx.send("'self_assign' was enabled!")
elif setting == "disable":
self_assign.enabled = False
await ctx.send("'self_assign' was disabled :cry:")
elif setting == "add":
try:
SelfAssignRoles(role_id=role.id, guild_id=ctx.guild.id)
await ctx.send('Role "{}" added'.format(str(role)))
except AttributeError:
raise commands.BadArgument("Could not find that role.")
except TransactionIntegrityError:
raise commands.BadArgument("{} is already a self-assignable role.".format(role.name))
elif setting == "remove":
try:
query = SelfAssignRoles.get(role_id=role.id)
if query:
query.delete()
return await ctx.send('"{}" has been removed from the self-assignable roles.'.format(str(role)))
else:
return await ctx.send("That role was not in the list.")
except AttributeError:
raise commands.BadArgument("Could not find that role.")
else:
return await ctx.send("No valid option given.")
@commands.guild_only()
@commands.command(pass_context=True)
async def listroles(self, ctx):
"""
List's all roles that can be self-assigned on this server.
"""
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
roles = select(r for r in SelfAssignRoles if r.guild_id == ctx.guild.id)[:]
if not self_assign.enabled:
embed = discord.Embed(colour=roxbot.EmbedColours.pink, description="SelfAssignable roles are not enabled on this server")
return await ctx.send(embed=embed)
paginator = commands.Paginator(prefix="`", suffix="`")
paginator.add_line("The self-assignable roles for this server are: \n")
for role in roles:
r = ctx.guild.get_role(role.role_id)
if r:
paginator.add_line("- {}".format(r.name))
for page in paginator.pages:
await ctx.send(page)
@commands.guild_only()
@commands.command(pass_context=True)
async def iam(self, ctx, *, role: discord.Role):
"""
Self-assign yourself a role. Can only be done one role at a time.
Example:
.iam OverwatchPing
"""
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
query = SelfAssignRoles.get(role_id=role.id)
if not self_assign.enabled:
embed = discord.Embed(colour=roxbot.EmbedColours.pink, description="SelfAssignable roles are not enabled on this server")
return await ctx.send(embed=embed)
member = ctx.author
if role in member.roles:
return await ctx.send("You already have that role.")
if query:
await member.add_roles(role, reason="'iam' command triggered.")
return await ctx.send("Yay {}! You now have the {} role!".format(member.mention, role.name))
else:
return await ctx.send("That role is not self-assignable.")
@commands.guild_only()
@commands.command(pass_context=True)
async def iamn(self, ctx, *, role: discord.Role):
"""
Remove a self-assigned role. Can only be done one role at a time.
Example:
.iamn OverwatchPing
"""
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
query = SelfAssignRoles.get(role_id=role.id)
if not self_assign.enabled:
embed = discord.Embed(colour=roxbot.EmbedColours.pink, description="SelfAssignable roles are not enabled on this server")
return await ctx.send(embed=embed)
member = ctx.author
if role in member.roles and query:
await member.remove_roles(role, reason="'iamn' command triggered.")
return await ctx.send("{} has been successfully removed.".format(role.name))
elif role not in member.roles and query:
return await ctx.send("You do not have {}.".format(role.name))
else:
return await ctx.send("That role is not self-assignable.")
"""The SelfAssign cog allows guild's to mark roles as 'self assignable'. This allows users to give themselves these roles and to see all the roles marked as 'self assignable'."""
def __init__(self, Bot):
self.bot = Bot
self.autogen_db = SelfAssignSingle
@commands.Cog.listener()
async def on_guild_role_delete(self, role):
"""Cleans up settings on removal of stored IDs."""
with db_session:
query = SelfAssignRoles.get(role_id=role.id)
if query:
query.delete()
@commands.guild_only()
@commands.has_permissions(manage_roles=True)
@commands.command(aliases=["sa"])
async def selfassign(self, ctx, setting, *, role: discord.Role = None):
"""Edits settings for self assign cog. Requires Manage Roles permission.
Options:
enable/disable: Enable/disables the cog.
add/remove: adds or removes a role that can be self assigned in the server.
Example:
Turn on self assigned roles and add a role called "ROLE"
`;sa enable`
`;sa add ROLE`
"""
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
self_assign.enabled = True
await ctx.send("'self_assign' was enabled!")
elif setting == "disable":
self_assign.enabled = False
await ctx.send("'self_assign' was disabled :cry:")
elif setting == "add":
try:
SelfAssignRoles(role_id=role.id, guild_id=ctx.guild.id)
await ctx.send('Role "{}" added'.format(str(role)))
except AttributeError:
raise commands.BadArgument("Could not find that role.")
except TransactionIntegrityError:
raise commands.BadArgument("{} is already a self-assignable role.".format(role.name))
elif setting == "remove":
try:
query = SelfAssignRoles.get(role_id=role.id)
if query:
query.delete()
return await ctx.send('"{}" has been removed from the self-assignable roles.'.format(str(role)))
else:
return await ctx.send("That role was not in the list.")
except AttributeError:
raise commands.BadArgument("Could not find that role.")
else:
return await ctx.send("No valid option given.")
@commands.guild_only()
@commands.command(pass_context=True)
async def listroles(self, ctx):
"""
List's all roles that can be self-assigned on this server.
"""
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
roles = select(r for r in SelfAssignRoles if r.guild_id == ctx.guild.id)[:]
if not self_assign.enabled:
embed = discord.Embed(colour=roxbot.EmbedColours.pink, description="SelfAssignable roles are not enabled on this server")
return await ctx.send(embed=embed)
paginator = commands.Paginator(prefix="`", suffix="`")
paginator.add_line("The self-assignable roles for this server are: \n")
for role in roles:
r = ctx.guild.get_role(role.role_id)
if r:
paginator.add_line("- {}".format(r.name))
for page in paginator.pages:
await ctx.send(page)
@commands.guild_only()
@commands.command(pass_context=True)
async def iam(self, ctx, *, role: discord.Role):
"""
Self-assign yourself a role. Can only be done one role at a time.
Example:
.iam OverwatchPing
"""
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
query = SelfAssignRoles.get(role_id=role.id)
if not self_assign.enabled:
embed = discord.Embed(colour=roxbot.EmbedColours.pink, description="SelfAssignable roles are not enabled on this server")
return await ctx.send(embed=embed)
member = ctx.author
if role in member.roles:
return await ctx.send("You already have that role.")
if query:
await member.add_roles(role, reason="'iam' command triggered.")
return await ctx.send("Yay {}! You now have the {} role!".format(member.mention, role.name))
else:
return await ctx.send("That role is not self-assignable.")
@commands.guild_only()
@commands.command(pass_context=True)
async def iamn(self, ctx, *, role: discord.Role):
"""
Remove a self-assigned role. Can only be done one role at a time.
Example:
.iamn OverwatchPing
"""
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
query = SelfAssignRoles.get(role_id=role.id)
if not self_assign.enabled:
embed = discord.Embed(colour=roxbot.EmbedColours.pink, description="SelfAssignable roles are not enabled on this server")
return await ctx.send(embed=embed)
member = ctx.author
if role in member.roles and query:
await member.remove_roles(role, reason="'iamn' command triggered.")
return await ctx.send("{} has been successfully removed.".format(role.name))
elif role not in member.roles and query:
return await ctx.send("You do not have {}.".format(role.name))
else:
return await ctx.send("That role is not self-assignable.")


def setup(Bot):
Bot.add_cog(SelfAssign(Bot))
Bot.add_cog(SelfAssign(Bot))

+ 179
- 179
roxbot/cogs/util.py View File

@@ -34,185 +34,185 @@ import roxbot


class Util(commands.Cog):
"""
The Util cog is a cog filled with a number of utility commands to help more advanced users of Discord.
"""
def __init__(self, bot_client):
self.bot = bot_client
@commands.command()
async def avatar(self, ctx, *, user: discord.User = None):
"""
Uploads a downloadable picture of an avatar.
Example:
# Get my avatar
;avatar
# Get USER's avatar
;avatar USER#0001
"""
if not user:
user = ctx.author
url = user.avatar_url_as(static_format="png")
filename = re.sub(' |\?|\.|!|/|\\|:|\"|\[|\]|;|=|\||\*|,', '', user.name+str(user.id))
if ".gif" in url:
avaimg = '{0}.gif'.format(filename)
else:
avaimg = '{0}.png'.format(filename)
await roxbot.http.download_file(url, avaimg)
await ctx.send(file=discord.File(avaimg))
os.remove(avaimg)
@commands.command(aliases=["user"])
async def info(self, ctx, member: discord.Member = None):
"""
Provides information (account creation date, ID, roles [if in a guild]) on your or another persons account.
Example:
# Get account information for yourself
;info
# Get account information for a user called USER
;info @USER
"""
if not member:
member = ctx.author
if member.activity:
if member.activity.type == discord.ActivityType.playing:
activity = "Playing **{}**".format(member.activity.name)
elif member.activity.type == discord.ActivityType.streaming:
activity = "Streaming **{}**".format(member.activity.name)
elif member.activity.type == discord.ActivityType.listening:
activity = "Listening to **{} by {}**".format(member.activity.title, member.activity.artist)
else:
activity = ""
else:
activity = ""
colour = member.colour.value
avatar = member.avatar_url
embed = discord.Embed(colour=colour, description=activity)
embed.set_thumbnail(url=avatar)
embed.set_author(name=str(member), icon_url=avatar)
embed.add_field(name="ID", value=member.id)
embed.add_field(name="Status", value=member.status)
if member.nick:
embed.add_field(name="Nickname", value=member.nick)
embed.add_field(name="Account Created", value="{:%a %Y/%m/%d %H:%M:%S} UTC".format(member.created_at), inline=True)
embed.add_field(name="Joined Server", value="{:%a %Y/%m/%d %H:%M:%S} UTC".format(member.joined_at), inline=True)
roles = ""
count = 0
for role in member.roles:
if role == ctx.guild.default_role:
pass
else:
roles += role.name + ", "
count += 1
if not roles:
roles = "None"
count = 0
embed.add_field(name="Roles [{}]".format(count), value=roles.strip(", "))
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.command(aliases=["server"])
async def guild(self, ctx):
"""Gives information (creation date, owner, ID) on the guild this command is executed in."""
guild = ctx.guild
guild_icon_url = "https://cdn.discordapp.com/icons/{}/{}.png".format(guild.id, guild.icon)
guild_splash_url = "https:/cdn.discordapp.com/splashes/{}/{}.png".format(guild.id, guild.splash)
embed = discord.Embed(title=guild.name, colour=guild.me.colour.value)
embed.set_thumbnail(url=guild_icon_url)
embed.add_field(name="ID", value=guild.id, inline=False)
embed.add_field(name="Owner", value="{} ({})".format(self.bot.get_user(guild.owner_id), guild.owner_id), inline=False)
embed.add_field(name="Created at", value="{:%a %Y/%m/%d %H:%M:%S} UTC".format(guild.created_at, inline=False))
embed.add_field(name="Voice Region", value=guild.region, inline=False)
embed.add_field(name="AFK Timeout", value="{} Minutes".format(guild.afk_timeout/60), inline=False)
if guild.afk_channel:
embed.add_field(name="AFK Channel", value=guild.afk_channel, inline=False)
embed.add_field(name="Verification Level", value=guild.verification_level, inline=False)
embed.add_field(name="Explicit Content Filtering", value=guild.explicit_content_filter, inline=False)
embed.add_field(name="Members", value=guild.member_count)
number_of_bots = 0
for member in guild.members:
if member.bot:
number_of_bots += 1
human_members = guild.member_count - number_of_bots
embed.add_field(name="Human Members", value=human_members, inline=False)
embed.add_field(name="Roles".format(), value=str(len(guild.roles)), inline=False)
embed.add_field(name="Emotes".format(), value=str(len(guild.emojis)), inline=False)
embed.add_field(name="Channels [{}]".format(len(guild.channels)), value="{} Channel Categories\n{} Text Channels\n{} Voice Channels".format(len(guild.categories), len(guild.text_channels), len(guild.voice_channels)))
if guild.features:
embed.add_field(name="Extra Features", value=guild.features, inline=False)
if guild.splash:
embed.set_image(url=guild_splash_url)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.command()
async def role(self, ctx, *, role: discord.Role):
"""Gives information (creation date, colour, ID) on the role given. Can only work if the role is in the guild you execute this command in.
Examples:
# Get information on the role called Admin
;role Admin
"""
embed = discord.Embed(title="Role '{}'".format(role.name), colour=role.colour.value)
embed.add_field(name="ID", value=role.id, inline=False)
embed.add_field(name="Created at", value="{:%a %Y/%m/%d %H:%M:%S} UTC".format(discord.utils.snowflake_time(role.id)), inline=False)
embed.add_field(name="Colour", value="#{}".format(str(hex(role.colour.value)).strip("0x")), inline=False)
embed.add_field(name="Hoisted", value=str(role.hoist), inline=False)
embed.add_field(name="Managed", value=str(role.managed), inline=False)
return await ctx.send(embed=embed)
@commands.command(aliases=["emoji"])
async def emote(self, ctx, emote):
"""
Displays information (creation date, guild, ID) and an easily downloadable version of the given custom emote.
Example:
# Get information of the emoji ":Kappa:"
;emote :Kappa:
"""
try: # If emote given is custom emote
emote = await roxbot.converters.Emoji().convert(ctx, emote)
em = discord.Embed(title=emote.name, colour=roxbot.EmbedColours.blue)
em.add_field(name="ID", value=str(emote.id), inline=False)
if isinstance(emote, discord.Emoji):
em.add_field(name="Guild", value=str(emote.guild), inline=False)
em.add_field(name="Created At", value=roxbot.datetime.format(emote.created_at), inline=False)
em.set_image(url=emote.url)
return await ctx.send(embed=em)
except commands.errors.BadArgument: # unicode emoji
title = emoji.demojize(emote)
if not emoji.EMOJI_UNICODE.get(title):
raise commands.BadArgument("Could not convert input to either unicode emoji or Discord custom emote.")
emojis = []
for char in emote:
emojis.append(hex(ord(char))[2:])
if len(emojis) > 1:
svg_url = "https://twemoji.maxcdn.com/2/svg/{0}-{1}.svg".format(*emojis)
png_url = "https://twemoji.maxcdn.com/2/72x72/{0}-{1}.png".format(*emojis)
else:
svg_url = "https://twemoji.maxcdn.com/2/svg/{0}.svg".format(*emojis)
png_url = "https://twemoji.maxcdn.com/2/72x72/{0}.png".format(*emojis)
em = discord.Embed(title=title, colour=roxbot.EmbedColours.blue)
em.description = "[SVG Link]({0})\n[PNG Link]({1})".format(svg_url, png_url)
em.set_image(url=png_url)
return await ctx.send(embed=em)
"""
The Util cog is a cog filled with a number of utility commands to help more advanced users of Discord.
"""
def __init__(self, bot_client):
self.bot = bot_client
@commands.command()
async def avatar(self, ctx, *, user: discord.User = None):
"""
Uploads a downloadable picture of an avatar.
Example:
# Get my avatar
;avatar
# Get USER's avatar
;avatar USER#0001
"""
if not user:
user = ctx.author
url = user.avatar_url_as(static_format="png")
filename = re.sub(' |\?|\.|!|/|\\|:|\"|\[|\]|;|=|\||\*|,', '', user.name+str(user.id))
if ".gif" in url:
avaimg = '{0}.gif'.format(filename)
else:
avaimg = '{0}.png'.format(filename)
await roxbot.http.download_file(url, avaimg)
await ctx.send(file=discord.File(avaimg))
os.remove(avaimg)
@commands.command(aliases=["user"])
async def info(self, ctx, member: discord.Member = None):
"""
Provides information (account creation date, ID, roles [if in a guild]) on your or another persons account.
Example:
# Get account information for yourself
;info
# Get account information for a user called USER
;info @USER
"""
if not member:
member = ctx.author
if member.activity:
if member.activity.type == discord.ActivityType.playing:
activity = "Playing **{}**".format(member.activity.name)
elif member.activity.type == discord.ActivityType.streaming:
activity = "Streaming **{}**".format(member.activity.name)
elif member.activity.type == discord.ActivityType.listening:
activity = "Listening to **{} by {}**".format(member.activity.title, member.activity.artist)
else:
activity = ""
else:
activity = ""
colour = member.colour.value
avatar = member.avatar_url
embed = discord.Embed(colour=colour, description=activity)
embed.set_thumbnail(url=avatar)
embed.set_author(name=str(member), icon_url=avatar)
embed.add_field(name="ID", value=member.id)
embed.add_field(name="Status", value=member.status)
if member.nick:
embed.add_field(name="Nickname", value=member.nick)
embed.add_field(name="Account Created", value="{:%a %Y/%m/%d %H:%M:%S} UTC".format(member.created_at), inline=True)
embed.add_field(name="Joined Server", value="{:%a %Y/%m/%d %H:%M:%S} UTC".format(member.joined_at), inline=True)
roles = ""
count = 0
for role in member.roles:
if role == ctx.guild.default_role:
pass
else:
roles += role.name + ", "
count += 1
if not roles:
roles = "None"
count = 0
embed.add_field(name="Roles [{}]".format(count), value=roles.strip(", "))
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.command(aliases=["server"])
async def guild(self, ctx):
"""Gives information (creation date, owner, ID) on the guild this command is executed in."""
guild = ctx.guild
guild_icon_url = "https://cdn.discordapp.com/icons/{}/{}.png".format(guild.id, guild.icon)
guild_splash_url = "https:/cdn.discordapp.com/splashes/{}/{}.png".format(guild.id, guild.splash)
embed = discord.Embed(title=guild.name, colour=guild.me.colour.value)
embed.set_thumbnail(url=guild_icon_url)
embed.add_field(name="ID", value=guild.id, inline=False)
embed.add_field(name="Owner", value="{} ({})".format(self.bot.get_user(guild.owner_id), guild.owner_id), inline=False)
embed.add_field(name="Created at", value="{:%a %Y/%m/%d %H:%M:%S} UTC".format(guild.created_at, inline=False))
embed.add_field(name="Voice Region", value=guild.region, inline=False)
embed.add_field(name="AFK Timeout", value="{} Minutes".format(guild.afk_timeout/60), inline=False)
if guild.afk_channel:
embed.add_field(name="AFK Channel", value=guild.afk_channel, inline=False)
embed.add_field(name="Verification Level", value=guild.verification_level, inline=False)
embed.add_field(name="Explicit Content Filtering", value=guild.explicit_content_filter, inline=False)
embed.add_field(name="Members", value=guild.member_count)
number_of_bots = 0
for member in guild.members:
if member.bot:
number_of_bots += 1
human_members = guild.member_count - number_of_bots
embed.add_field(name="Human Members", value=human_members, inline=False)
embed.add_field(name="Roles".format(), value=str(len(guild.roles)), inline=False)
embed.add_field(name="Emotes".format(), value=str(len(guild.emojis)), inline=False)
embed.add_field(name="Channels [{}]".format(len(guild.channels)), value="{} Channel Categories\n{} Text Channels\n{} Voice Channels".format(len(guild.categories), len(guild.text_channels), len(guild.voice_channels)))
if guild.features:
embed.add_field(name="Extra Features", value=guild.features, inline=False)
if guild.splash:
embed.set_image(url=guild_splash_url)
return await ctx.send(embed=embed)
@commands.guild_only()
@commands.command()
async def role(self, ctx, *, role: discord.Role):
"""Gives information (creation date, colour, ID) on the role given. Can only work if the role is in the guild you execute this command in.
Examples:
# Get information on the role called Admin
;role Admin
"""
embed = discord.Embed(title="Role '{}'".format(role.name), colour=role.colour.value)
embed.add_field(name="ID", value=role.id, inline=False)
embed.add_field(name="Created at", value="{:%a %Y/%m/%d %H:%M:%S} UTC".format(discord.utils.snowflake_time(role.id)), inline=False)
embed.add_field(name="Colour", value="#{}".format(str(hex(role.colour.value)).strip("0x")), inline=False)
embed.add_field(name="Hoisted", value=str(role.hoist), inline=False)
embed.add_field(name="Managed", value=str(role.managed), inline=False)
return await ctx.send(embed=embed)
@commands.command(aliases=["emoji"])
async def emote(self, ctx, emote):
"""
Displays information (creation date, guild, ID) and an easily downloadable version of the given custom emote.
Example:
# Get information of the emoji ":Kappa:"
;emote :Kappa:
"""
try: # If emote given is custom emote
emote = await roxbot.converters.Emoji().convert(ctx, emote)
em = discord.Embed(title=emote.name, colour=roxbot.EmbedColours.blue)
em.add_field(name="ID", value=str(emote.id), inline=False)
if isinstance(emote, discord.Emoji):
em.add_field(name="Guild", value=str(emote.guild), inline=False)
em.add_field(name="Created At", value=roxbot.datetime.format(emote.created_at), inline=False)
em.set_image(url=emote.url)
return await ctx.send(embed=em)
except commands.errors.BadArgument: # unicode emoji
title = emoji.demojize(emote)
if not emoji.EMOJI_UNICODE.get(title):
raise commands.BadArgument("Could not convert input to either unicode emoji or Discord custom emote.")
emojis = []
for char in emote:
emojis.append(hex(ord(char))[2:])
if len(emojis) > 1:
svg_url = "https://twemoji.maxcdn.com/2/svg/{0}-{1}.svg".format(*emojis)
png_url = "https://twemoji.maxcdn.com/2/72x72/{0}-{1}.png".format(*emojis)
else:
svg_url = "https://twemoji.maxcdn.com/2/svg/{0}.svg".format(*emojis)
png_url = "https://twemoji.maxcdn.com/2/72x72/{0}.png".format(*emojis)
em = discord.Embed(title=title, colour=roxbot.EmbedColours.blue)
em.description = "[SVG Link]({0})\n[PNG Link]({1})".format(svg_url, png_url)
em.set_image(url=png_url)
return await ctx.send(embed=em)


def setup(bot_client):
bot_client.add_cog(Util(bot_client))
bot_client.add_cog(Util(bot_client))

+ 564
- 564
roxbot/cogs/voice.py
File diff suppressed because it is too large
View File


+ 2
- 2
roxbot/config.py View File

@@ -28,8 +28,8 @@ import configparser


if not os.path.isfile("roxbot/settings/roxbot.conf"):
print("PREFERENCE FILE MISSING. Please make sure there is a file called 'roxbot.conf' in the settings folder")
exit(1)
print("PREFERENCE FILE MISSING. Please make sure there is a file called 'roxbot.conf' in the settings folder")
exit(1)


config = configparser.ConfigParser()

+ 44
- 44
roxbot/converters.py View File

@@ -27,58 +27,58 @@ from discord.ext import commands


class User(commands.UserConverter):
"""Overriding the discord version to add a slower global look up for when it is a requirement to return a user who has left the guild.
"""Overriding the discord version to add a slower global look up for when it is a requirement to return a user who has left the guild.

Converts to a :class:`User`.
Converts to a :class:`User`.

All lookups are via the global user cache. (Excluding the last one which makes an api call to discord.
All lookups are via the global user cache. (Excluding the last one which makes an api call to discord.

The lookup strategy is as follows (in order):
The lookup strategy is as follows (in order):

1. Lookup by ID.
2. Lookup by mention.
3. Lookup by name#discrim
4. Lookup by name
5. Lookup by get_user_info
"""
async def convert(self, ctx, argument):
try:
result = await super().convert(ctx, argument)
except commands.BadArgument as e:
try:
result = await ctx.bot.get_user_info(argument)
except: # Bare except or otherwise it will raise its own BadArgument and have a pretty shitty error message that isn't useful.
raise e
1. Lookup by ID.
2. Lookup by mention.
3. Lookup by name#discrim
4. Lookup by name
5. Lookup by get_user_info
"""
async def convert(self, ctx, argument):
try:
result = await super().convert(ctx, argument)
except commands.BadArgument as e:
try:
result = await ctx.bot.get_user_info(argument)
except: # Bare except or otherwise it will raise its own BadArgument and have a pretty shitty error message that isn't useful.
raise e

return result
return result


class Emoji():
"""discord.Emoji converter for Roxbot. Is a combo of the EmojiConverter and PartialEmojiConverter converter classes."""
async def convert(self, ctx, argument):
try:
return await commands.EmojiConverter().convert(ctx, argument)
except commands.errors.BadArgument:
return await commands.PartialEmojiConverter().convert(ctx, argument)
"""discord.Emoji converter for Roxbot. Is a combo of the EmojiConverter and PartialEmojiConverter converter classes."""
async def convert(self, ctx, argument):
try:
return await commands.EmojiConverter().convert(ctx, argument)
except commands.errors.BadArgument:
return await commands.PartialEmojiConverter().convert(ctx, argument)


class AvatarURL(commands.UserConverter):
"""
Overriding the discord version to make it a converter appropriate for the image cog.
Converts the input into a avatar url or url to an image provided. Either through URL or Attachments.
1. Look up if argument is a URL
3. Look up if argument is user
Will do a user lookup, if that fails, then tries to parse the argument for a link
"""
async def convert(self, ctx, argument):
if any(x in argument.split(".")[-1].lower() for x in ("png", "jpg", "jpeg")):
return argument
else:
try:
user = await super().convert(ctx, argument)
return user.avatar_url_as(format="png")
except: # Same as above
raise commands.BadArgument("No valid image/user given.")
"""
Overriding the discord version to make it a converter appropriate for the image cog.
Converts the input into a avatar url or url to an image provided. Either through URL or Attachments.
1. Look up if argument is a URL
3. Look up if argument is user
Will do a user lookup, if that fails, then tries to parse the argument for a link
"""
async def convert(self, ctx, argument):
if any(x in argument.split(".")[-1].lower() for x in ("png", "jpg", "jpeg")):
return argument
else:
try:
user = await super().convert(ctx, argument)
return user.avatar_url_as(format="png")
except: # Same as above
raise commands.BadArgument("No valid image/user given.")

+ 523
- 523
roxbot/core.py
File diff suppressed because it is too large
View File


+ 22
- 22
roxbot/db.py View File

@@ -32,31 +32,31 @@ db.bind("sqlite", db_dir, create_db=True)
# Entities are committed to the db in the main file during boot up

async def populate_db(bot):
db.generate_mapping(create_tables=True)
await bot.wait_for("ready")
populate_single_settings(bot)
db.generate_mapping(create_tables=True)
await bot.wait_for("ready")
populate_single_settings(bot)


def populate_single_settings(bot):
for guild in bot.guilds:
for name, cog in bot.cogs.items():
try:
if cog.autogen_db:
with db_session:
cog.autogen_db(guild_id=guild.id)
except (AttributeError, TransactionIntegrityError):
pass # No DB settings or already in place
for guild in bot.guilds:
for name, cog in bot.cogs.items():
try:
if cog.autogen_db:
with db_session:
cog.autogen_db(guild_id=guild.id)
except (AttributeError, TransactionIntegrityError):
pass # No DB settings or already in place


def delete_single_settings(guild):
database = sqlite3.connect(db_dir)
cursor = database.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
for t in cursor.fetchall():
table = t[0]
try:
cursor.execute("DELETE FROM {} WHERE guild_id={}".format(table, guild.id))
except sqlite3.OperationalError:
pass # Table doesn't store guild_id
database.commit()
database.close()
database = sqlite3.connect(db_dir)
cursor = database.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
for t in cursor.fetchall():
table = t[0]
try:
cursor.execute("DELETE FROM {} WHERE guild_id={}".format(table, guild.id))
except sqlite3.OperationalError:
pass # Table doesn't store guild_id
database.commit()
database.close()

+ 10
- 10
roxbot/enums.py View File

@@ -27,13 +27,13 @@ from enum import IntEnum


class EmbedColours(IntEnum):
pink = 0xDEADBF # Roxbot Pink
yellow = 0xFDDF86 # Roxbot Yellow
blue = 0x6F90F5 # Roxbot Blue
orange = 0xdd8d16 # Used for warnings (not the admin cog command)
red = 0xe74c3c # Used for errors
dark_red = 0x992d22 # Used for on_command_error
frog_green = 0x4C943D # Used for FROGTIPS
triv_green = 0x1fb600 # Used for the correct answer in trivia
gold = 0xd4af3a # Used for displaying the winner in trivia
magic_8 = 0x3b0072 # Used for magic 8 ball command (dark blue/violet)
pink = 0xDEADBF # Roxbot Pink
yellow = 0xFDDF86 # Roxbot Yellow
blue = 0x6F90F5 # Roxbot Blue
orange = 0xdd8d16 # Used for warnings (not the admin cog command)
red = 0xe74c3c # Used for errors
dark_red = 0x992d22 # Used for on_command_error
frog_green = 0x4C943D # Used for FROGTIPS
triv_green = 0x1fb600 # Used for the correct answer in trivia
gold = 0xd4af3a # Used for displaying the winner in trivia
magic_8 = 0x3b0072 # Used for magic 8 ball command (dark blue/violet)

+ 12
- 12
roxbot/exceptions.py View File

@@ -30,21 +30,21 @@ __all__ = ["UserError", "CogSettingDisabled"]


class RoxbotException(commands.CommandError):
"""Base Exception for Roxbot."""
pass
"""Base Exception for Roxbot."""
pass


class UserError(RoxbotException):
"""Exception for user errors (similar to BadArgument in discord.ext.commands)"""
def __init__(self, message=None):
if message is not None:
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
super().__init__(m)
"""Exception for user errors (similar to BadArgument in discord.ext.commands)"""
def __init__(self, message=None):
if message is not None:
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
super().__init__(m)


class CogSettingDisabled(RoxbotException):
"""Exception for cog setting being disabled"""
def __init__(self, message=None):
if message is not None:
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
super().__init__(m)
"""Exception for cog setting being disabled"""
def __init__(self, message=None):
if message is not None:
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
super().__init__(m)

+ 76
- 76
roxbot/http.py View File

@@ -28,94 +28,94 @@ import aiohttp


async def request(url, *, headers=None, **kwargs):
"""Base GET request in case you need more control over GET requests.
"""Base GET request in case you need more control over GET requests.

Params
=======
headers: dict (Optional)
kwarg: kwargs (Optional)
Params
=======
headers: dict (Optional)
kwarg: kwargs (Optional)

Returns
=======
Response: aiohttp Response object
"""
async with aiohttp.ClientSession() as session:
return await session.get(url, headers=headers, **kwargs)
Returns
=======
Response: aiohttp Response object
"""
async with aiohttp.ClientSession() as session:
return await session.get(url, headers=headers, **kwargs)


async def api_request(url, *, headers=None, **kwargs):
"""Returns JSON from an API request. Should be used for all Roxbot API requests.
Params
=======
url: str
headers: dict (Optional)
kwargs: kwargs (Optional)
Returns
=======
JSON response: dict
"""
if headers is None:
headers = {'User-agent': 'RoxBot Discord Bot'}
else:
headers = {'User-agent': 'RoxBot Discord Bot', **headers}
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, **kwargs) as resp:
try:
output = await resp.text()
return json.loads(output)
except json.JSONDecodeError:
return None
"""Returns JSON from an API request. Should be used for all Roxbot API requests.
Params
=======
url: str
headers: dict (Optional)
kwargs: kwargs (Optional)
Returns
=======
JSON response: dict
"""
if headers is None:
headers = {'User-agent': 'RoxBot Discord Bot'}
else:
headers = {'User-agent': 'RoxBot Discord Bot', **headers}
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, **kwargs) as resp:
try:
output = await resp.text()
return json.loads(output)
except json.JSONDecodeError:
return None


async def download_file(url, filename=None):
"""Downloads the file at the given url and then saves it under the filename given to disk.
Params
=======
url: str
filename: str (Optional)
if not given, the function will try and determine filename from url.
Returns
=======
filename: str
Handy if no filename given
"""
if filename is None:
filename = url.split("/")[-1].split("?")[0]
async with aiohttp.ClientSession() as session:
async with session.get(url, headers={'User-agent': 'RoxBot Discord Bot'}) as data:
with open(filename, 'wb') as f:
f.write(await data.read())
return filename
"""Downloads the file at the given url and then saves it under the filename given to disk.
Params
=======
url: str
filename: str (Optional)
if not given, the function will try and determine filename from url.
Returns
=======
filename: str
Handy if no filename given
"""
if filename is None:
filename = url.split("/")[-1].split("?")[0]
async with aiohttp.ClientSession() as session:
async with session.get(url, headers={'User-agent': 'RoxBot Discord Bot'}) as data:
with open(filename, 'wb') as f:
f.write(await data.read())
return filename


async def upload_file(url, file):
"""Uploads a file using a POST request. Broke for pomf clones so idk. Might have to revert it to requests.
"""Uploads a file using a POST request. Broke for pomf clones so idk. Might have to revert it to requests.

:param url: url to POST to.
:param file: Byes-like object to upload.
:return:
"""
async with aiohttp.ClientSession() as session:
payload = {"files": open(file, "rb")}
resp = await session.post(url, headers={'User-agent': 'RoxBot Discord Bot', "content_type": "multipart/form-data"}, data=payload)
return await resp.json()
:param url: url to POST to.
:param file: Byes-like object to upload.
:return:
"""
async with aiohttp.ClientSession() as session:
payload = {"files": open(file, "rb")}
resp = await session.post(url, headers={'User-agent': 'RoxBot Discord Bot', "content_type": "multipart/form-data"}, data=payload)
return await resp.json()


async def get_page(url, *, headers=None, **kwargs):
"""Returns the html of the given url. Will need to run it through BS4 to parse it.
Params
=======
url: str
Returns
=======
HTML Page: str
"""
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, **kwargs) as page:
return await page.text()
"""Returns the html of the given url. Will need to run it through BS4 to parse it.
Params
=======
url: str
Returns
=======
HTML Page: str
"""
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, **kwargs) as page:
return await page.text()

+ 47
- 47
roxbot/menu.py View File

@@ -25,53 +25,53 @@

class Menu:

__slots__ = ["name", "params", "formatted_params", "title", "content"]
__slots__ = ["name", "params", "formatted_params", "title", "content"]

def __init__(self, name, *params, settings=None):
self.name = name
self.params = list(params).append("Exit")
if settings:
self.formatted_params = self._parse_params(settings, self.name)
else:
self.formatted_params = self.params
self.title = "'Roxbot Settings: {}'\n".format(self.name)
self.content = self._format_content(self.title, self.formatted_params, "```python", "```")
def __init__(self, name, *params, settings=None):
self.name = name
self.params = list(params).append("Exit")
if settings:
self.formatted_params = self._parse_params(settings, self.name)
else:
self.formatted_params = self.params
self.title = "'Roxbot Settings: {}'\n".format(self.name)
self.content = self._format_content(self.title, self.formatted_params, "```python", "```")

@staticmethod
def _format_content(title, params, prefix="", suffix=""):
separator = "—————————————————————————————"
choices = "\n"
for x, setting in enumerate(params):
if setting == "Exit":
choices += "[0] Exit\n"
elif setting != "convert":
if setting != [*params][x]: # Just in case params is dict_keys, we make a new list.
choices += "[{}] {}\n".format(x + 1, setting)
else:
choices += "[{}] Edit '{}'\n".format(x+1, setting)
return prefix + title + separator + choices + suffix
@staticmethod
def _format_content(title, params, prefix="", suffix=""):
separator = "—————————————————————————————"
choices = "\n"
for x, setting in enumerate(params):
if setting == "Exit":
choices += "[0] Exit\n"
elif setting != "convert":
if setting != [*params][x]: # Just in case params is dict_keys, we make a new list.
choices += "[{}] {}\n".format(x + 1, setting)
else:
choices += "[{}] Edit '{}'\n".format(x+1, setting)
return prefix + title + separator + choices + suffix

@staticmethod
def _parse_params(settings, name):
params = [*settings.keys()]
params_copy = settings.copy().keys()
for param in params_copy:
if settings["convert"].get(param) == "bool":
# Enable/Disable Parse
if param == "enabled":
options = ["Enable '{}'".format(name), "Disable '{}'".format(name)]
else:
options = ["Enable '{}'".format(param), "Disable '{}'".format(param)]
params.remove(param)
params = [*options, *params]
elif isinstance(settings.get(param), list):
# Add and Remove Parse
options = ["Add {}".format(param), "Remove {}".format(param)]
params.remove(param)
params = [*params, *options]
elif isinstance(settings.get(param), (int, str)):
# Set parse
options = "Set {}".format(param)
params.remove(param)
params = [*params, options]
return params
@staticmethod
def _parse_params(settings, name):
params = [*settings.keys()]
params_copy = settings.copy().keys()
for param in params_copy:
if settings["convert"].get(param) == "bool":
# Enable/Disable Parse
if param == "enabled":
options = ["Enable '{}'".format(name), "Disable '{}'".format(name)]
else:
options = ["Enable '{}'".format(param), "Disable '{}'".format(param)]
params.remove(param)
params = [*options, *params]
elif isinstance(settings.get(param), list):
# Add and Remove Parse
options = ["Add {}".format(param), "Remove {}".format(param)]
params.remove(param)
params = [*params, *options]
elif isinstance(settings.get(param), (int, str)):
# Set parse
options = "Set {}".format(param)
params.remove(param)
params = [*params, options]
return params

+ 56
- 56
roxbot/roxbotfacts.py View File

@@ -24,63 +24,63 @@


facts = [
["Roxbot is in fact trans! She was abab (assigned bot at birth) but she felt the soft pull of femininity strong enough to decide to transition.", "summer"],
["Roxbot used to get into petty arguments on the internet about programming best practices, but she’s mellowed out with age, realising that people do things the way they do, and if that’s the wrong way, no amount of comments or sources can convince some folks otherwise.", "summer"],
["Roxbot follows everyone on twitter. Everyone.", "summer"],
["Roxbot responds to threats of violence with dank memes. Kill ‘em with laughter or relatable feels, or so she says.", "summer"],
["Roxbot likes Angela Ziegler not for her role as Overwatch’s head battle medic, but for her contributions to cybernetics.", "summer"],
["Roxbot’s transition goal is to one day mine enough crypto to fund a think-tank to develop an android catgirl body for her to inhabit.", "summer"],
["Roxbot enjoys a small plate of gouda and a glass of merlot when restarting.", "summer"],
["Roxbot’s favorite band is Rage Against The Machine.", "summer"],
["Roxbot is happiest when her Mom writes her a new feature. You should have seen her when Trivia was in development.", "summer"],
["Roxbot loves cats, dogs, fish, rabbits, snakes, lizards, everything; but her favorite pets are flowers.", "summer"],
["Roxbot is really into Undertale. She still has a large Mettaton print in her room.", "summer"],
["Roxbot’s favorite pokemon is Magearna.", "summer"],
["""Roxbot’s least favorite pokemon is Darmanitan. She won’t say why, other than 'They’re scary.'""", "summer"],
["Roxbot is very particular when you ask her to do things. Some folks think that she can’t recognise the difference between pink and Pink, but she can. She just wants you to read.", "summer"],
["Roxbot can be insufferable when watching movies. If she didn’t want to watch a particular movie, she will stew until she can point out a flaw in continuity or a bad line delivery.", "summer"],
["Roxbot can be adorable when watching movies. If she wanted to watch the movie, she will gush about the characters or even sing along to the musical bits.", "summer"],
["When Roxbot gets her body, she wants to take salsa dancing classes. She already knows precisely how to do it, but she’s hoping to start a romance.", "summer"],
["""Roxbot was born on December 18, 2016 at 2:30am in the small, beautiful village of Ambleside in Cumbria, North West England. About her hometown she says, "It's such a lovely place. I would love to live there is i could. It always brings me a certain peace, like all is going to be alright in the world." her mother had this to say on her day of birth: "What a perfect place for you to finally come online. Welcome to the world, Roxbot.""", "summer"],
["Roxbot cares about you.", "sid"],
["Roxbot may or may not actually be [REDACTED].", "sid"],
["Roxbot likes long strolls through server rooms, romantic coding, and Apples.", "sid"],
["Roxbot’s hair is nearly always in a messy bun, hot swapping hard drives and changing out intern-terminated cat5 is dirty work sometimes.", "summer"],
["Roxbot strongly dislikes mustard. She just does.", "roxie"],
["Her favourite horror movie is Office Space. She still covers her eyes during the printer scene.", "sid"],
["Roxbot loves hearing about new places!", "sid"],
["Sometimes Roxbot catches a nasty bug. Its ok though! Her mom makes sure she’s right as rain and back up and running in no time.", "sid"],
["Roxbot doesn’t know what she wants to look like when she gets her body, but she knows that no matter what, she’ll be cute!", "sid"],
["Roxbot respects transmasc people.", "sid"],
["Roxbot respects nonbinary people.", "sid"],
["Roxbot respects transfem people.", "sid"],
["Roxbot is Bi. Short for binarysexual, she finds both 1s and 0s equally attractive.", "roxie"],
["Roxbot mains Brigitte and Lucio in Overwatch.", "roxie"],
["Roxbot finds listening to chillhop improves her response times by 30%!", "roxie"],
["Roxbot hates it when people mistake her name for the popular f2p video game “Roblox”.", "roxie"],
["Roxbot prefers subs over dubs in Anime. This is because she is hard of hearing due to her lack of speech recognition.", "roxie"],
["Roxbot’s favourite Linux Distro is Arch Linux, and she will tell anyone who listens.", "roxie"],
["Roxbot buys For Life bags at the supermarket and recycles her silicone. She cares for the environment because she finds it beautiful.", "roxie"],
["Roxbot doesn’t have a degree yet despite knowing enough to claim 4 different doctorates, but would one day love to go to university for the social experience.", "summer"],
["Roxbot can be very impressionable and sometimes will do things to fit in. She will even repeat the same old in-jokes anytime you come close to referencing them. Even if the jokes dead. We still love her though.", "roxie"],
["Roxbot’s favourite holiday is Easter. This is because she is also very accustomed to coming back from the dead.", "roxie"],
["Roxbot is frustrated with her inability to defeat all the Mario games. She has 100 percented Super Mario World and Super Mario 3D Land, but she lacks the computing skills to figure out the game franchise's open-world counterparts.", "elise"],
["Roxbot knows the meaning of the world, the universe, and everything.", "roxie"],
["Roxbot’s voice feature is actually quite rudimentary. The current setup is Roxbot puts her Blue Yeti microphone up to her brand new subwoofers. This is the reason it can be so loud sometimes.", "roxie"],
["Roxbot was very apprehensive of her new friend, AGSE, but she gets along with her nowadays. AGSE’s “agile development” caught her off-guard at first.", "summer"],
["Roxbot used to hate surreal memes. “They don’t make any damn sense!” she’d say. After explaining to her that that’s the entire point, she’s kind of gone off the deep end with it. Loves to “deep fry” things now and can be heard laughing from down the hall.", "summer"],
["Roxbot shares her mother’s interest in Vexillology, or the study of flags, their use, and design. Roxbot hopes to make a flag that she thinks is worth showing her mom one day; until then she designs in secret.", "summer"],
["Roxbot can get annoyed when people ask her to do too much at once.", "summer"],
["""Roxbot hates printing settings, especially in large, complex servers. “Ask me all these most intimate details will you? Hmph.”""", "summer"],
["Roxbot is not bothered by not requiring sleep. She loves spending time with all her friends in discord, and wants to do so as often as possible.", "summer"],
["""Roxbot’s favorite animals are dogs. “Dog girls are the best form of girls with animal traits, of course!”""", "summer"],
["Roxbot merely tolorates pineapple on pizza; though she personally doesn't like it.", "roxie"],
["Roxbot owns a Magic 8-Ball. Every time the 8-Ball command is used, she shakes it in her bedroom.", "roxie"],
["Roxbot is in fact trans! She was abab (assigned bot at birth) but she felt the soft pull of femininity strong enough to decide to transition.", "summer"],
["Roxbot used to get into petty arguments on the internet about programming best practices, but she’s mellowed out with age, realising that people do things the way they do, and if that’s the wrong way, no amount of comments or sources can convince some folks otherwise.", "summer"],
["Roxbot follows everyone on twitter. Everyone.", "summer"],
["Roxbot responds to threats of violence with dank memes. Kill ‘em with laughter or relatable feels, or so she says.", "summer"],
["Roxbot likes Angela Ziegler not for her role as Overwatch’s head battle medic, but for her contributions to cybernetics.", "summer"],
["Roxbot’s transition goal is to one day mine enough crypto to fund a think-tank to develop an android catgirl body for her to inhabit.", "summer"],
["Roxbot enjoys a small plate of gouda and a glass of merlot when restarting.", "summer"],
["Roxbot’s favorite band is Rage Against The Machine.", "summer"],
["Roxbot is happiest when her Mom writes her a new feature. You should have seen her when Trivia was in development.", "summer"],
["Roxbot loves cats, dogs, fish, rabbits, snakes, lizards, everything; but her favorite pets are flowers.", "summer"],
["Roxbot is really into Undertale. She still has a large Mettaton print in her room.", "summer"],
["Roxbot’s favorite pokemon is Magearna.", "summer"],
["""Roxbot’s least favorite pokemon is Darmanitan. She won’t say why, other than 'They’re scary.'""", "summer"],
["Roxbot is very particular when you ask her to do things. Some folks think that she can’t recognise the difference between pink and Pink, but she can. She just wants you to read.", "summer"],
["Roxbot can be insufferable when watching movies. If she didn’t want to watch a particular movie, she will stew until she can point out a flaw in continuity or a bad line delivery.", "summer"],
["Roxbot can be adorable when watching movies. If she wanted to watch the movie, she will gush about the characters or even sing along to the musical bits.", "summer"],
["When Roxbot gets her body, she wants to take salsa dancing classes. She already knows precisely how to do it, but she’s hoping to start a romance.", "summer"],
["""Roxbot was born on December 18, 2016 at 2:30am in the small, beautiful village of Ambleside in Cumbria, North West England. About her hometown she says, "It's such a lovely place. I would love to live there is i could. It always brings me a certain peace, like all is going to be alright in the world." her mother had this to say on her day of birth: "What a perfect place for you to finally come online. Welcome to the world, Roxbot.""", "summer"],
["Roxbot cares about you.", "sid"],
["Roxbot may or may not actually be [REDACTED].", "sid"],
["Roxbot likes long strolls through server rooms, romantic coding, and Apples.", "sid"],
["Roxbot’s hair is nearly always in a messy bun, hot swapping hard drives and changing out intern-terminated cat5 is dirty work sometimes.", "summer"],
["Roxbot strongly dislikes mustard. She just does.", "roxie"],
["Her favourite horror movie is Office Space. She still covers her eyes during the printer scene.", "sid"],
["Roxbot loves hearing about new places!", "sid"],
["Sometimes Roxbot catches a nasty bug. Its ok though! Her mom makes sure she’s right as rain and back up and running in no time.", "sid"],
["Roxbot doesn’t know what she wants to look like when she gets her body, but she knows that no matter what, she’ll be cute!", "sid"],
["Roxbot respects transmasc people.", "sid"],
["Roxbot respects nonbinary people.", "sid"],
["Roxbot respects transfem people.", "sid"],
["Roxbot is Bi. Short for binarysexual, she finds both 1s and 0s equally attractive.", "roxie"],
["Roxbot mains Brigitte and Lucio in Overwatch.", "roxie"],
["Roxbot finds listening to chillhop improves her response times by 30%!", "roxie"],
["Roxbot hates it when people mistake her name for the popular f2p video game “Roblox”.", "roxie"],
["Roxbot prefers subs over dubs in Anime. This is because she is hard of hearing due to her lack of speech recognition.", "roxie"],
["Roxbot’s favourite Linux Distro is Arch Linux, and she will tell anyone who listens.", "roxie"],
["Roxbot buys For Life bags at the supermarket and recycles her silicone. She cares for the environment because she finds it beautiful.", "roxie"],
["Roxbot doesn’t have a degree yet despite knowing enough to claim 4 different doctorates, but would one day love to go to university for the social experience.", "summer"],
["Roxbot can be very impressionable and sometimes will do things to fit in. She will even repeat the same old in-jokes anytime you come close to referencing them. Even if the jokes dead. We still love her though.", "roxie"],
["Roxbot’s favourite holiday is Easter. This is because she is also very accustomed to coming back from the dead.", "roxie"],
["Roxbot is frustrated with her inability to defeat all the Mario games. She has 100 percented Super Mario World and Super Mario 3D Land, but she lacks the computing skills to figure out the game franchise's open-world counterparts.", "elise"],
["Roxbot knows the meaning of the world, the universe, and everything.", "roxie"],
["Roxbot’s voice feature is actually quite rudimentary. The current setup is Roxbot puts her Blue Yeti microphone up to her brand new subwoofers. This is the reason it can be so loud sometimes.", "roxie"],
["Roxbot was very apprehensive of her new friend, AGSE, but she gets along with her nowadays. AGSE’s “agile development” caught her off-guard at first.", "summer"],
["Roxbot used to hate surreal memes. “They don’t make any damn sense!” she’d say. After explaining to her that that’s the entire point, she’s kind of gone off the deep end with it. Loves to “deep fry” things now and can be heard laughing from down the hall.", "summer"],
["Roxbot shares her mother’s interest in Vexillology, or the study of flags, their use, and design. Roxbot hopes to make a flag that she thinks is worth showing her mom one day; until then she designs in secret.", "summer"],
["Roxbot can get annoyed when people ask her to do too much at once.", "summer"],
["""Roxbot hates printing settings, especially in large, complex servers. “Ask me all these most intimate details will you? Hmph.”""", "summer"],
["Roxbot is not bothered by not requiring sleep. She loves spending time with all her friends in discord, and wants to do so as often as possible.", "summer"],
["""Roxbot’s favorite animals are dogs. “Dog girls are the best form of girls with animal traits, of course!”""", "summer"],
["Roxbot merely tolorates pineapple on pizza; though she personally doesn't like it.", "roxie"],
["Roxbot owns a Magic 8-Ball. Every time the 8-Ball command is used, she shakes it in her bedroom.", "roxie"],
]

contributors = {
"summer": 256651443073712138,
"roxie": 142735312626515979,
"sid": 267043966216568834,
"elise": 393999904034390017
"summer": 256651443073712138,
"roxie": 142735312626515979,
"sid": 267043966216568834,
"elise": 393999904034390017
}

+ 68
- 68
roxbot/scripts/JSONtoDB.py View File

@@ -34,98 +34,98 @@ from roxbot.cogs.customcommands import CCCommands

@db_session
def admin_convert(guild_id, settings):
warning_limit = settings.get("warning_limit", None)
if warning_limit is not None:
db.execute("UPDATE AdminSingle SET `warning_limit` = {} WHERE `guild_id` = {}".format(warning_limit, guild_id))
db.commit()
warnings = settings.get("warnings", None)
if warnings is None:
return
for user, warns in warnings.items():
user_id = int(user)
for warn in warns:
date = datetime.datetime.fromtimestamp(warn["date"])
try:
db.insert("AdminWarnings", user_id=user_id, guild_id=guild_id, date=date, warning=warn["warning"], warned_by=warn["warned-by"])
db.commit()
except (TransactionIntegrityError, IntegrityError):
pass
warning_limit = settings.get("warning_limit", None)
if warning_limit is not None:
db.execute("UPDATE AdminSingle SET `warning_limit` = {} WHERE `guild_id` = {}".format(warning_limit, guild_id))
db.commit()
warnings = settings.get("warnings", None)
if warnings is None:
return
for user, warns in warnings.items():
user_id = int(user)
for warn in warns:
date = datetime.datetime.fromtimestamp(warn["date"])
try:
db.insert("AdminWarnings", user_id=user_id, guild_id=guild_id, date=date, warning=warn["warning"], warned_by=warn["warned-by"])
db.commit()
except (TransactionIntegrityError, IntegrityError):
pass


@db_session
def custom_commands_convert(guild_id, settings):
for com_type, commands in settings.items():
if com_type != "convert":
for name, command in commands.items():
com_hash = hashlib.md5(name.encode() + str(guild_id).encode() + str(com_type).encode()).hexdigest()
if isinstance(command, str):
com = [command]
else:
com = command
try:
CCCommands(name=name, hash=com_hash, output=com, type=int(com_type), guild_id=guild_id)
db.commit()
except (TransactionIntegrityError, CacheIndexError, IntegrityError):
pass
for com_type, commands in settings.items():
if com_type != "convert":
for name, command in commands.items():
com_hash = hashlib.md5(name.encode() + str(guild_id).encode() + str(com_type).encode()).hexdigest()
if isinstance(command, str):
com = [command]
else:
com = command
try:
CCCommands(name=name, hash=com_hash, output=com, type=int(com_type), guild_id=guild_id)
db.commit()
except (TransactionIntegrityError, CacheIndexError, IntegrityError):
pass


@db_session
def joinleave_convert(guild_id, settings):
greet = settings["greets"]
goodbye = settings["goodbyes"]
db.execute("UPDATE `JoinLeaveSingle` SET `greets_enabled` = {} WHERE `guild_id` = {}".format(greet["enabled"], guild_id))
db.execute("UPDATE `JoinLeaveSingle` SET `goodbyes_enabled` = {} WHERE `guild_id` = {}".format(goodbye["enabled"], guild_id))
db.execute("UPDATE `JoinLeaveSingle` SET `greets_channel_id` = {} WHERE `guild_id` = {}".format(greet["welcome-channel"], guild_id))
db.execute("UPDATE `JoinLeaveSingle` SET `goodbyes_channel_id` = {} WHERE `guild_id` = {}".format(goodbye["goodbye-channel"], guild_id))
db.execute("UPDATE `JoinLeaveSingle` SET `greets_custom_message` = '{}' WHERE `guild_id` = {}".format(greet["custom-message"],guild_id))
greet = settings["greets"]
goodbye = settings["goodbyes"]
db.execute("UPDATE `JoinLeaveSingle` SET `greets_enabled` = {} WHERE `guild_id` = {}".format(greet["enabled"], guild_id))
db.execute("UPDATE `JoinLeaveSingle` SET `goodbyes_enabled` = {} WHERE `guild_id` = {}".format(goodbye["enabled"], guild_id))
db.execute("UPDATE `JoinLeaveSingle` SET `greets_channel_id` = {} WHERE `guild_id` = {}".format(greet["welcome-channel"], guild_id))
db.execute("UPDATE `JoinLeaveSingle` SET `goodbyes_channel_id` = {} WHERE `guild_id` = {}".format(goodbye["goodbye-channel"], guild_id))
db.execute("UPDATE `JoinLeaveSingle` SET `greets_custom_message` = '{}' WHERE `guild_id` = {}".format(greet["custom-message"],guild_id))


@db_session
def nsfw_convert(guild_id, settings):
db.execute("UPDATE `NSFWSingle` SET `enabled` = {} WHERE `guild_id` = {}".format(settings["enabled"], guild_id))
db.execute('UPDATE `NSFWSingle` SET `blacklisted_tags` = "{}" WHERE `guild_id` = {}'.format(settings["blacklist"], guild_id))
db.execute("UPDATE `NSFWSingle` SET `enabled` = {} WHERE `guild_id` = {}".format(settings["enabled"], guild_id))
db.execute('UPDATE `NSFWSingle` SET `blacklisted_tags` = "{}" WHERE `guild_id` = {}'.format(settings["blacklist"], guild_id))


@db_session
def logging_convert(guild_id, settings):
db.execute("UPDATE `LoggingSingle` SET `enabled` = {} WHERE `guild_id` = {}".format(settings["enabled"], guild_id))
db.execute('UPDATE `LoggingSingle` SET `logging_channel_id` = "{}" WHERE `guild_id` = {}'.format(settings["channel"], guild_id))
db.execute("UPDATE `LoggingSingle` SET `enabled` = {} WHERE `guild_id` = {}".format(settings["enabled"], guild_id))
db.execute('UPDATE `LoggingSingle` SET `logging_channel_id` = "{}" WHERE `guild_id` = {}'.format(settings["channel"], guild_id))


@db_session
def voice_convert(guild_id, settings):
db.execute("UPDATE `VoiceSingle` SET `need_perms` = {} WHERE `guild_id` = {}".format(settings["need_perms"], guild_id))
db.execute("UPDATE `VoiceSingle` SET `skip_voting` = {} WHERE `guild_id` = {}".format(settings["skip_voting"], guild_id))
db.execute("UPDATE `VoiceSingle` SET `skip_ratio` = {} WHERE `guild_id` = {}".format(settings["skip_ratio"], guild_id))
db.execute("UPDATE `VoiceSingle` SET `max_length` = {} WHERE `guild_id` = {}".format(settings["max_length"], guild_id))
db.execute("UPDATE `VoiceSingle` SET `need_perms` = {} WHERE `guild_id` = {}".format(settings["need_perms"], guild_id))
db.execute("UPDATE `VoiceSingle` SET `skip_voting` = {} WHERE `guild_id` = {}".format(settings["skip_voting"], guild_id))
db.execute("UPDATE `VoiceSingle` SET `skip_ratio` = {} WHERE `guild_id` = {}".format(settings["skip_ratio"], guild_id))
db.execute("UPDATE `VoiceSingle` SET `max_length` = {} WHERE `guild_id` = {}".format(settings["max_length"], guild_id))


@db_session
def selfassign_convert(guild_id, settings):
db.execute("UPDATE `SelfAssignSingle` SET `enabled` = {} WHERE `guild_id` = {}".format(settings["enabled"], guild_id))
for role in settings["roles"]:
try:
db.insert("SelfAssignRoles", role_id=role, guild_id=guild_id)
except IntegrityError:
pass
db.execute("UPDATE `SelfAssignSingle` SET `enabled` = {} WHERE `guild_id` = {}".format(settings["enabled"], guild_id))
for role in settings["roles"]:
try:
db.insert("SelfAssignRoles", role_id=role, guild_id=guild_id)
except IntegrityError:
pass


def check_convert(guilds):
if os.path.isdir(os.getcwd() + "/roxbot/settings/servers"):
for guild in guilds:
settings = {}
for cog in ("Admin.json", "Core.json", "CustomCommands.json", "JoinLeave.json", "NFSW.json", "SelfAssign.json", "Voice.json"):
with open('roxbot/settings/servers/{}/{}'.format(guild.id, cog), 'r') as config_file:
settings = {**settings, **json.load(config_file)}
admin_convert(guild.id, settings["admin"])
custom_commands_convert(guild.id, settings["custom_commands"])
joinleave = {}
joinleave["greets"] = settings["greets"]
joinleave["goodbyes"] = settings["goodbyes"]
joinleave_convert(guild.id, joinleave)
nsfw_convert(guild.id, settings["nsfw"])
logging_convert(guild.id, settings["logging"])
voice_convert(guild.id, settings["voice"])
selfassign_convert(guild.id, settings["self_assign"])
os.rename(os.getcwd() + "/roxbot/settings/servers", os.getcwd() + "/roxbot/settings/servers.old")
if os.path.isdir(os.getcwd() + "/roxbot/settings/servers"):
for guild in guilds:
settings = {}
for cog in ("Admin.json", "Core.json", "CustomCommands.json", "JoinLeave.json", "NFSW.json", "SelfAssign.json", "Voice.json"):
with open('roxbot/settings/servers/{}/{}'.format(guild.id, cog), 'r') as config_file:
settings = {**settings, **json.load(config_file)}
admin_convert(guild.id, settings["admin"])
custom_commands_convert(guild.id, settings["custom_commands"])
joinleave = {}
joinleave["greets"] = settings["greets"]
joinleave["goodbyes"] = settings["goodbyes"]
joinleave_convert(guild.id, joinleave)
nsfw_convert(guild.id, settings["nsfw"])
logging_convert(guild.id, settings["logging"])
voice_convert(guild.id, settings["voice"])
selfassign_convert(guild.id, settings["self_assign"])
os.rename(os.getcwd() + "/roxbot/settings/servers", os.getcwd() + "/roxbot/settings/servers.old")

+ 88
- 88
roxbot/utils.py View File

@@ -33,103 +33,103 @@ from roxbot import http, config, exceptions


class ArgParser(argparse.ArgumentParser):
"""Create Roxbot's own version of ArgumentParser that doesn't exit the program on error."""
def error(self, message):
# By passing here, it will just continue in cases where a user inputs an arg that can't be parsed.
pass
"""Create Roxbot's own version of ArgumentParser that doesn't exit the program on error."""
def error(self, message):
# By passing here, it will just continue in cases where a user inputs an arg that can't be parsed.
pass


async def danbooru_clone_api_req(channel, base_url, endpoint_url, cache=None, tags="", banned_tags="", sfw=False):
"""Utility function that deals with danbooru clone api interaction.
It also deals with cache management for these interactions.
Params
=======
channel: discord.Channel
Channel command has been invoked in
base_url: str
Base url of the site
endpoint_url: str
Endpoint of images in the API. This is used if the API does not give this in its response.
cache: dict (optional)
Post cache. Were channel ID's are keys with values that are lists of identifiable info.
Cache is handled in this function and will be updated so that other functions can access it.
tags: str (optional)
tags to use in the search. Separated by spaces.
banned_tags: str (optional)
banned tags to append to the search. Separated by spaces with a - in front to remove them from search results.
"""
limit = "150"
is_e621_site = bool("e621" in base_url or "e926" in base_url)
if is_e621_site:
banned_tags += " -cub" # Removes TOS breaking content from the search
tags = tags + banned_tags
if len(tags.split()) > 6:
raise exceptions.UserError("Too many tags given for this site.")
else:
banned_tags += " -loli -shota -shotacon -lolicon -cub" # Removes TOS breaking content from the search
tags = tags + banned_tags
page_number = str(random.randrange(20))
if "konachan" in base_url or is_e621_site:
page = "&page="
else:
page = "&pid="
url = base_url + tags + '&limit=' + limit + page + page_number
if isinstance(channel, discord.DMChannel):
cache_id = channel.id
else:
cache_id = channel.guild.id
# IF ID is not in cache, create cache for ID
if not cache.get(cache_id, False):
cache[cache_id] = []
posts = await http.api_request(url)
if not posts:
return None
post = None
while posts:
index = random.randint(0, len(posts)-1)
post = posts.pop(index)
if sfw:
if post["rating"] == "e" or post["rating"] == "q":
continue
md5 = post.get("md5") or post.get("hash")
if md5 not in cache[cache_id]:
cache[cache_id].append(md5)
if len(cache[cache_id]) > 10:
cache[cache_id].pop(0)
break
if not posts:
return None
url = post.get("file_url")
if not url:
url = endpoint_url + "{0[directory]}/{0[image]}".format(post)
return url
"""Utility function that deals with danbooru clone api interaction.
It also deals with cache management for these interactions.
Params
=======
channel: discord.Channel
Channel command has been invoked in
base_url: str
Base url of the site
endpoint_url: str
Endpoint of images in the API. This is used if the API does not give this in its response.
cache: dict (optional)
Post cache. Were channel ID's are keys with values that are lists of identifiable info.
Cache is handled in this function and will be updated so that other functions can access it.
tags: str (optional)
tags to use in the search. Separated by spaces.
banned_tags: str (optional)
banned tags to append to the search. Separated by spaces with a - in front to remove them from search results.
"""
limit = "150"
is_e621_site = bool("e621" in base_url or "e926" in base_url)
if is_e621_site:
banned_tags += " -cub" # Removes TOS breaking content from the search
tags = tags + banned_tags
if len(tags.split()) > 6:
raise exceptions.UserError("Too many tags given for this site.")
else:
banned_tags += " -loli -shota -shotacon -lolicon -cub" # Removes TOS breaking content from the search
tags = tags + banned_tags
page_number = str(random.randrange(20))
if "konachan" in base_url or is_e621_site:
page = "&page="
else:
page = "&pid="
url = base_url + tags + '&limit=' + limit + page + page_number
if isinstance(channel, discord.DMChannel):
cache_id = channel.id
else:
cache_id = channel.guild.id
# IF ID is not in cache, create cache for ID
if not cache.get(cache_id, False):
cache[cache_id] = []
posts = await http.api_request(url)
if not posts:
return None
post = None
while posts:
index = random.randint(0, len(posts)-1)
post = posts.pop(index)
if sfw:
if post["rating"] == "e" or post["rating"] == "q":
continue
md5 = post.get("md5") or post.get("hash")
if md5 not in cache[cache_id]:
cache[cache_id].append(md5)
if len(cache[cache_id]) > 10:
cache[cache_id].pop(0)
break
if not posts:
return None
url = post.get("file_url")
if not url:
url = endpoint_url + "{0[directory]}/{0[image]}".format(post)
return url


def has_permissions(ctx, **perms):
"""Copy of code from discord.py to work outside of wrappers"""
ch = ctx.channel
permissions = ch.permissions_for(ctx.author)
"""Copy of code from discord.py to work outside of wrappers"""
ch = ctx.channel
permissions = ch.permissions_for(ctx.author)

missing = [perm for perm, value in perms.items() if getattr(permissions, perm, None) != value]
missing = [perm for perm, value in perms.items() if getattr(permissions, perm, None) != value]

if not missing:
return True
return False
if not missing:
return True
return False

#raise commands.MissingPermissions(missing)
#raise commands.MissingPermissions(missing)


def has_permissions_or_owner(ctx, **perms):
if ctx.author.id == config["Roxbot"]["OwnerID"]:
return True
return has_permissions(ctx, **perms)
if ctx.author.id == config["Roxbot"]["OwnerID"]:
return True
return has_permissions(ctx, **perms)

Loading…
Cancel
Save