Browse Source

Merged db into development.

tags/v2.2.0
Roxie Gibson 5 years ago
parent
commit
31f26f5eb2
18 changed files with 809 additions and 970 deletions
  1. +6
    -0
      .gitignore
  2. +1
    -0
      requirements.txt
  3. +9
    -15
      roxbot.py
  4. +2
    -2
      roxbot/__init__.py
  5. +3
    -2
      roxbot/checks.py
  6. +92
    -112
      roxbot/cogs/admin.py
  7. +41
    -99
      roxbot/cogs/ags.py
  8. +108
    -127
      roxbot/cogs/customcommands.py
  9. +79
    -80
      roxbot/cogs/joinleave.py
  10. +38
    -40
      roxbot/cogs/nsfw.py
  11. +65
    -59
      roxbot/cogs/selfassign.py
  12. +59
    -57
      roxbot/cogs/voice.py
  13. +113
    -146
      roxbot/core.py
  14. +62
    -0
      roxbot/db.py
  15. +0
    -230
      roxbot/guild_settings.py
  16. +130
    -0
      roxbot/scripts/JSONtoDB.py
  17. +0
    -0
      roxbot/settings/blacklist.txt
  18. +1
    -1
      roxbot/settings/roxbot_example.conf

+ 6
- 0
.gitignore View File

@@ -11,6 +11,8 @@ bot-env/

roxbot/settings/servers\.json

roxbot/settings/servers\.json

roxbot/settings/preferences.ini

env/
@@ -22,3 +24,7 @@ site/
roxbot/settings/roxbot\.conf

roxbot/settings/db\.sqlite

roxbot/settings/db.sqlite

roxbot/settings/servers\.old/

+ 1
- 0
requirements.txt View File

@@ -5,3 +5,4 @@ numpy==1.14.3
youtube_dl
emoji==0.5.1
dataset==1.1.0
pony==0.7.9

+ 9
- 15
roxbot.py View File

@@ -30,11 +30,11 @@ import time
import traceback

import discord
from discord.ext import commands

import roxbot
from roxbot import guild_settings as gs
from roxbot import db
from roxbot import core
from roxbot.scripts import JSONtoDB


class term:
@@ -84,22 +84,21 @@ bot = core.Roxbot(
async def on_ready():
print("Logged in as: {}".format(term.fOKGREEN.format(str(bot.user))), end="\n\n")

# this is so if we're added to a server while we're offline we deal with it
roxbot.guild_settings.error_check(bot.guilds, bot.cogs)

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

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


@bot.event
async def on_guild_join(guild):
gs.add_guild(guild, bot.cogs)
db.populate_single_settings(bot)


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


@bot.check
@@ -136,13 +135,6 @@ async def about(ctx):
return await ctx.channel.send(embed=em)


@commands.command(pass_context=False, hidden=True)
async def settings():
# This is to block any customcommand or command from being made with the same name.
# This is to avoid conflicts with the internal settings system.
raise commands.CommandNotFound("settings")


if __name__ == "__main__":
start_time = time.time()
print(term.fHEADER.format(term.fBOLD.format(term.title)))
@@ -160,13 +152,15 @@ if __name__ == "__main__":

# Load Extension Cogs
print("Cogs Loaded:")
for cog in roxbot.cogs:
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))

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


+ 2
- 2
roxbot/__init__.py View File

@@ -29,7 +29,7 @@ from .enums import EmbedColours
from .config import config
from .exceptions import UserError, CogSettingDisabled

from . import checks, http, guild_settings, converters, utils, roxbotfacts, exceptions
from . import checks, http, converters, utils, roxbotfacts, exceptions, db


command_prefix = config["Roxbot"]["Command_Prefix"]
@@ -46,7 +46,7 @@ backup_rate = int(config["Backups"]["rate"]) * 60 # Convert minutes to seconds
dev_mode = False
datetime = "{:%a %Y/%m/%d %H:%M:%S} UTC"

cogs = [
cog_list = [
"roxbot.cogs.admin",
"roxbot.cogs.customcommands",
"roxbot.cogs.fun",

+ 3
- 2
roxbot/checks.py View File

@@ -24,6 +24,7 @@


import roxbot
from roxbot.db import *

import discord
from discord.ext import commands
@@ -41,8 +42,8 @@ def is_nsfw():
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:
nsfw_enabled = bool(roxbot.guild_settings.get(ctx.guild)["nsfw"]["enabled"])
return nsfw_enabled
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)

+ 92
- 112
roxbot/cogs/admin.py View File

@@ -24,23 +24,25 @@


import datetime
import time

import discord
from discord.ext import commands

import roxbot
from roxbot import guild_settings as gs
from roxbot.db import *


def _is_admin_or_mod(message):
if message.author.id == roxbot.owner:
return True
perm_roles = gs.get(message.channel.guild).perm_roles
for role in message.author.roles:
if role.id in perm_roles.get("admin") or role.id in perm_roles.get("mod"):
return True
return False
class AdminSingle(db.Entity):
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)


class Admin(commands.Cog):
@@ -80,13 +82,7 @@ class Admin(commands.Cog):

def __init__(self, bot_client):
self.bot = bot_client
self.settings = {
"admin": {
"convert": {"warnings": "hide"},
"warning_limit": 0,
"warnings": {},
}
}
self.autogen_db = AdminSingle

@commands.guild_only()
@commands.has_permissions(manage_messages=True)
@@ -126,7 +122,7 @@ class Admin(commands.Cog):
@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):
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:
@@ -160,7 +156,7 @@ class Admin(commands.Cog):
raise commands.CommandNotFound("Subcommand '{}' does not exist.".format(ctx.subcommand_passed))

@warn.command()
async def add(self, ctx, user: discord.User=None, *, warning=""):
async def add(self, ctx, user: discord.User = None, *, warning=""):
"""Adds a warning to a user.

Options:
@@ -173,29 +169,19 @@ class Admin(commands.Cog):
"""

# Warning in the settings is a dictionary of user ids. The user ids are equal to a list of dictionaries.
settings = gs.get(ctx.guild)
warnings = settings["admin"]["warnings"]
warning_limit = settings["admin"]["warning_limit"]
warning_dict = {
"warned-by": ctx.author.id,
"date": time.time(),
"warning": warning
}
user_id = str(user.id)

if user_id not in warnings:
warnings[user_id] = []
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 len(warnings[user_id]) > warn_limit:
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)

warnings[user_id].append(warning_dict)
settings["admin"]["warnings"] = warnings
settings.update(settings["admin"], "admin")
with db_session:
AdminWarnings(user_id=user.id, warned_by=ctx.author.id, date=datetime.datetime.utcnow(), warning=warning, guild_id=ctx.guild.id)

amount_warnings = len(warnings[user_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))

@@ -203,7 +189,7 @@ class Admin(commands.Cog):
return await ctx.send(embed=embed)

@warn.command()
async def list(self, ctx, *, user: roxbot.converters.User=None):
async def list(self, ctx, *, user: roxbot.converters.User = None):
"""Lists all warning in this guild or all the warnings for one user.

Options:
@@ -215,35 +201,35 @@ class Admin(commands.Cog):
# List all warnings for @Roxbot
;warn list @Roxbot
"""
settings = gs.get(ctx.guild)
warnings = settings["admin"]["warnings"]

if user is None:
paginator = commands.Paginator()
for member in warnings:
# Remove users with no warning here instead of remove cause im lazy
if not warnings[member]:
warnings.pop(member)
else:
member_obj = discord.utils.get(ctx.guild.members, id=int(member))
if member_obj:
paginator.add_line("{}: {} Warning(s)".format(str(member_obj), len(warnings[member])))
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:
paginator.add_line("{}: {} Warning(s)".format(member, len(warnings[member])))
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.
settings["admin"]["warnings"] = warnings
settings.update(settings["admin"], "admin")

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(embed=discord.Embed(description=page, colour=roxbot.EmbedColours.pink))
return await ctx.send(page)
else:
user_id = str(user.id)
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 warnings.get(user_id):
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)

@@ -251,20 +237,20 @@ class Admin(commands.Cog):
em.set_thumbnail(url=user.avatar_url)

x = 1
userlist = warnings[user_id]
for warning in userlist:
for warning in user_warnings:
try:
warned_by = str(await self.bot.get_user_info(warning["warned-by"]))
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 = roxbot.datetime.format(datetime.datetime.fromtimestamp(warning["date"]))
warn_reason = warning["warning"]
em.add_field(name="Warning %s" % x, value="Warned by: {}\nTime: {}\nReason: {}".format(warned_by, date, warn_reason))
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=None, index=None):
async def remove(self, ctx, user: roxbot.converters.User, index=None):
"""Removes one or all of the warnings for a user.

Options:=
@@ -277,44 +263,40 @@ class Admin(commands.Cog):
# Remove warning 2 for Roxbot
;warn remove Roxbot 2
"""
user_id = str(user.id)
settings = gs.get(ctx.guild)
warnings = settings["admin"]["warnings"]

if index:
try:
index = int(index)
index -= 1
warnings[user_id].pop(index)
if not warnings[user_id]:
warnings.pop(user_id)

settings["admin"]["warnings"] = warnings
settings.update(settings["admin"], "admin")
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(settings["warnings"][user_id]))
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
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:
raise e
return ctx.send(embed=embed)
else:
try:
warnings.pop(user_id)
settings["admin"]["warnings"] = warnings
settings.update(settings["admin"], "admin")
embed = discord.Embed(description=self.OK_WARN_REMOVE_REMOVED_WARNINGS.format(str(user)), colour=roxbot.EmbedColours.pink)
return await ctx.send(embed=embed)
except KeyError:
embed = discord.Embed(description=self.WARN_WARN_REMOVE_USER_NOT_FOUND.format(str(user)), colour=roxbot.EmbedColours.orange)
return await ctx.send(embed=embed)
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()
@@ -331,17 +313,15 @@ class Admin(commands.Cog):
# Dryrun the prune command to see how many warnings would be removed
;warn prune 1
"""
settings = gs.get(ctx.guild)
warnings = settings["admin"]["warnings"].copy()
count = 0
x = 0
for ban in await ctx.guild.bans():
for user in warnings:
if int(user) == ban.user.id:
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:
settings["admin"]["warnings"].pop(user)
count += 1
settings.update(settings["admin"], "admin")
embed = discord.Embed(description=self.OK_WARN_PRUNE_PRUNED.format(count), colour=roxbot.EmbedColours.pink)
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"])
@@ -353,10 +333,10 @@ class Admin(commands.Cog):
"""
if number_of_warnings < 0:
raise commands.BadArgument(self.ERROR_WARN_SL_NEG)
settings = gs.get(ctx.guild)
admin = settings["admin"]
admin["warning_limit"] = number_of_warnings
settings.update(admin, "admin")
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)

+ 41
- 99
roxbot/cogs/ags.py View File

@@ -24,137 +24,79 @@


import datetime
import typing

import discord
from discord.ext import commands

import roxbot

ags_id = 393764974444675073
selfieperms = 394939389823811584
nsfwimageperms = 394941004043649036
newbie = 450042170112475136
tat_token = roxbot.config["Tokens"]["Tatsumaki"]


def is_ags():
return commands.check(lambda ctx: ctx.guild.id == ags_id)


async def tatsumaki_api_call(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": tat_token})


class AssortedGenderSounds(commands.Cog):
"""Custom Cog for the AssortedGenderSounds Discord Server."""
def __init__(self, bot_client):
self.bot = bot_client
self.acceptable_roles = (nsfwimageperms, selfieperms)
self.settings = {
"limited_to_guild": str(ags_id),
"ags": {
"log_channel": "",
"required_days": "",
"required_score": "",
}
}
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.id == ags_id:
role = member.guild.get_role(newbie)
await member.add_roles(role, reason="Auto-add role on join")
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):
role = ctx.guild.get_role(newbie)
try:
return await ctx.author.remove_roles(role, reason="User has agreed the rules and has been given access to the server.")
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(hidden=True)
async def perms(self, ctx, role):
"""Shell command to do the perm assigning. Only should be invoked by another command."""
# Just in case some cunt looks at the source code and thinks they can give themselves Admin.
if role.id not in self.acceptable_roles:
return False
settings = roxbot.guild_settings.get(ctx.guild)
@commands.command(name="selfieperms")
async def selfie_perms(self, ctx):
"""Requests the selfie perm role."""
member = ctx.author
required_score = settings["ags"]["required_score"]
days = int(settings["ags"]["required_days"])
data = await tatsumaki_api_call(member, ctx.guild)

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 role in member.roles:
await member.remove_roles(role, reason="Requested removal of {0.name}".format(role))
return await ctx.send("You already had {0.name}. It has now been removed.".format(role))
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=days) and int(data["score"]) >= required_score:
await member.add_roles(role, reason="Requested {0.name}".format(role))
await ctx.send("You now have the {0.name} role".format(role))
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(required_score, days)
"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)
)

@is_ags()
@commands.command()
async def selfieperms(self, ctx):
"""Requests the selfie perm role."""
arg = None
for role in ctx.guild.roles:
if role.id == selfieperms:
arg = role
if not arg:
return ctx.send("Error, message roxie thanks.")
return await ctx.invoke(self.perms, role=arg)

#@is_not_nsfw_disabled()
@is_ags()
@commands.command(hidden=True, enabled=False)
async def nsfwperms(self, ctx):
"""Requests the NSFW Image Perm role."""
arg = None
for role in ctx.guild.roles:
if role.id == nsfwimageperms:
arg = role
if not arg:
return ctx.send("Error, message roxie thanks.")
return await ctx.invoke(self.perms, role=arg)

@commands.command()
async def ags(self, ctx, selection, amount: typing.Optional[int], *, channel: typing.Optional[discord.TextChannel] = None):
selection = selection.lower()
settings = roxbot.guild_settings.get(ctx.guild)
ags = settings["ags"]

if selection == "log_channel":
if not channel:
channel = ctx.channel
elif ctx.message.channel_mentions:
channel = ctx.channel_mentions[0]
else:
channel = ctx.guild.get_channel(channel)
ags["log_channel"] = channel.id
await ctx.send("Logging Channel set to '{}'".format(channel.name))
elif selection == "required_days":
ags["required_days"] = amount
await ctx.send("Required days set to '{}'".format(str(amount)))
elif selection == "required_score":
ags["required_score"] = amount
await ctx.send("Required score set to '{}'".format(str(amount)))
else:
return await ctx.send("No valid option given.")
return settings.update(ags, "ags")


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

+ 108
- 127
roxbot/cogs/customcommands.py View File

@@ -29,6 +29,16 @@ import discord
from discord.ext import commands

import roxbot
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)


class CustomCommands(commands.Cog):
@@ -52,21 +62,11 @@ class CustomCommands(commands.Cog):
def __init__(self, bot_client):
self.bot = bot_client
self.embed_fields = ("title", "description", "colour", "color", "footer", "image", "thumbnail", "url")
self.settings = {
"custom_commands": {
"0": {},
"1": {},
"2": {},
"convert": {"0": "hide", "1": "hide", "2": "hide"}
}
}

@staticmethod
def _get_output(command):
# Check for a list as the output. If so, randomly select a item from the list.
if isinstance(command, list):
command = random.choice(command)
return command
return random.choice(command)

@staticmethod
def _cc_to_embed(command_output):
@@ -121,26 +121,32 @@ class CustomCommands(commands.Cog):

# Emulate Roxbot's blacklist system
if self.bot.blacklisted(message.author):
raise commands.CheckFailure()
raise commands.CheckFailure

settings = roxbot.guild_settings.get(message.guild)
msg = message.content.lower()
channel = message.channel
if msg.startswith(self.bot.command_prefix):
command = msg.split(self.bot.command_prefix)[1]
if command in settings["custom_commands"]["1"]:
command_output = self._get_output(settings["custom_commands"]["1"][command])
return await channel.send(command_output)

elif command in settings["custom_commands"]["2"]:
command_output = self._get_output(settings["custom_commands"]["2"][command])
embed = self._cc_to_embed(command_output)
return await channel.send(embed=embed)
else:
for command in settings["custom_commands"]["0"]:
if msg == command:
command_output = self._get_output(settings["custom_commands"]["0"][command])
return await channel.send(command_output)

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"])
@@ -175,18 +181,16 @@ class CustomCommands(commands.Cog):
# 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.
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"
if len(output) == 1:
output = output[0]
command_type = 0
elif command_type in ("1", "prefix"):
command_type = "1"
if len(output) == 1:
output = output[0]
command_type = 1
elif command_type in ("2", "embed"):
command_type = "2"
command_type = 2
if len(output) < 2:
raise roxbot.UserError(self.ERROR_EMBED_VALUE)
try:
@@ -196,26 +200,22 @@ class CustomCommands(commands.Cog):
else:
raise roxbot.UserError(self.ERROR_INCORRECT_TYPE)

settings = roxbot.guild_settings.get(ctx.guild)
no_prefix_commands = settings["custom_commands"]["0"]
prefix_commands = settings["custom_commands"]["1"]
embed_commands = settings["custom_commands"]["2"]
command = command.lower()
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 command in no_prefix_commands or command in prefix_commands or command in embed_commands:
raise roxbot.UserError(self.ERROR_COMMAND_EXISTS)
elif len(command.split(" ")) > 1 and command_type == "1":
raise roxbot.UserError(self.ERROR_PREFIX_SPACE)
settings["custom_commands"][command_type][command] = output
settings.update(settings["custom_commands"], "custom_commands")
return await ctx.send(self.OUTPUT_ADD.format(command, output))
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()
@@ -229,41 +229,29 @@ class CustomCommands(commands.Cog):
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.
"""
settings = roxbot.guild_settings.get(ctx.guild)
no_prefix_commands = settings["custom_commands"]["0"]
prefix_commands = settings["custom_commands"]["1"]
embed_commands = settings["custom_commands"]["2"]

if ctx.message.mentions or ctx.message.mention_everyone or ctx.message.role_mentions:
raise roxbot.UserError(self.ERROR_AT_MENTION)

if command in no_prefix_commands:
if len(edit) == 1:
edit = edit[0]
settings["custom_commands"]["0"][command] = edit
settings.update(settings["custom_commands"], "custom_commands")
return await ctx.send(self.OUTPUT_EDIT.format(command, edit))

elif command in prefix_commands:
if len(edit) == 1:
edit = edit[0]
settings["custom_commands"]["1"][command] = edit
settings.update(settings["custom_commands"], "custom_commands")
return await ctx.send(self.OUTPUT_EDIT.format(command, edit))

elif command in embed_commands:
if len(edit) < 2:
raise roxbot.UserError(self.ERROR_EMBED_VALUE)
try:
edit = self._embed_parse_options(edit)
except ValueError:
raise roxbot.UserError(self.ERROR_OUTPUT_TOO_LONG)
settings["custom_commands"]["2"][command] = edit
settings.update(settings["custom_commands"], "custom_commands")
return await ctx.send(self.OUTPUT_EDIT.format(command, edit))

else:
raise roxbot.UserError(self.ERROR_COMMAND_NULL)
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()
@@ -274,61 +262,54 @@ class CustomCommands(commands.Cog):
# Remove custom command called "test"
;cc remove test
"""
settings = roxbot.guild_settings.get(ctx.guild)

command = command.lower()
no_prefix_commands = settings["custom_commands"]["0"]
prefix_commands = settings["custom_commands"]["1"]
embed_commands = settings["custom_commands"]["2"]

if command in no_prefix_commands:
command_type = "0"
elif command in prefix_commands:
command_type = "1"
elif command in embed_commands:
command_type = "2"
else:
raise roxbot.UserError(self.ERROR_COMMAND_NULL)

settings["custom_commands"][command_type].pop(command)
settings.update(settings["custom_commands"], "custom_commands")
return await ctx.send(self.OUTPUT_REMOVE.format(command))
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"
settings = roxbot.guild_settings.get(ctx.guild)
cc = settings["custom_commands"]
no_prefix_commands = cc["0"]
prefix_commands = {**cc["1"], **cc["2"]}

paginator = commands.Paginator()
paginator.add_line("Here is the list of Custom Commands...")
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("Commands that require Prefix:")
if not prefix_commands:
paginator.add_line("There are no commands setup.")
else:
for command in prefix_commands:
if debug == "1":
command += " = '{}'".format(prefix_commands[command])
paginator.add_line("- " + command)
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:")
if not no_prefix_commands:
paginator.add_line("There are no commands setup.")
else:
for command in no_prefix_commands:
if debug == "1":
command += " = '{}'".format(no_prefix_commands[command])
paginator.add_line("- " + command)

output = paginator.pages
for page in output:
paginator.add_line("__Commands that don't require prefix:__")
add_commands(no_prefix_commands, paginator)

for page in paginator.pages:
await ctx.send(page)



+ 79
- 80
roxbot/cogs/joinleave.py View File

@@ -29,48 +29,49 @@ import discord
from discord.ext import commands

import roxbot
from roxbot import guild_settings
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)


class JoinLeave(commands.Cog):
"""JoinLeave is a cog that allows you to create custom welcome and goodbye messages for your Discord server. """
def __init__(self, Bot):
self.bot = Bot
self.settings = {
"greets": {
"enabled": 0,
"convert": {"enabled": "bool", "welcome-channel": "channel"},
"welcome-channel": 0,
"custom-message": "",
"default-message": "Be sure to read the rules."
},
"goodbyes": {
"enabled": 0,
"convert": {"enabled": "bool", "goodbye-channel": "channel"},
"goodbye-channel": 0,
}
}

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.
"""
settings = guild_settings.get(member.guild)
if not settings["greets"]["enabled"]:
if member == self.bot.user:
return
with db_session:
settings = JoinLeaveSingle.get(guild_id=member.guild.id)

if not settings.greets_enabled:
return

if settings["greets"]["custom-message"]:
message = settings["greets"]["custom-message"]
else:
message = settings["greets"]["default-message"]
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"]["welcome-channel"])
channel = member.guild.get_channel(settings.greets_channel_id)
return await channel.send(embed=em)

@commands.Cog.listener()
@@ -78,27 +79,27 @@ class JoinLeave(commands.Cog):
"""
The same but the opposite
"""
settings = guild_settings.get(member.guild)
channel = settings["goodbyes"]["goodbye-channel"]
if not settings["goodbyes"]["enabled"]:
if member == self.bot.user:
return
else:
channel = member.guild.get_channel(channel)
return await channel.send(embed=discord.Embed(
description="{}#{} has left or been beaned.".format(member.name, member.discriminator), colour=roxbot.EmbedColours.pink))
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."""
settings = guild_settings.get(channel.guild)
greets = settings["greets"]
goodbyes = settings["goodbyes"]
if channel.id == greets["welcome-channel"]:
greets["welcome-channel"] = 0
settings.update(greets, "greets")
if channel.id == goodbyes["goodbye-channel"]:
goodbyes["goodbye-channel"] = 0
settings.update(goodbyes, "goodbyes")
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)
@@ -117,28 +118,28 @@ class JoinLeave(commands.Cog):
`;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.
"""
if (not channel and not text):
raise commands.MissingRequiredArgument("Missing at least one of: `channel` or `text`")
setting = setting.lower()
settings = guild_settings.get(ctx.guild)
greets = settings["greets"]
if setting == "enable":
greets["enabled"] = 1
await ctx.send("'greets' was enabled!")
elif setting == "disable":
greets["enabled"] = 0
await ctx.send("'greets' was disabled :cry:")
elif setting in ("channel", "welcome-channel", "greet-channel"):
if channel is None:
channel = ctx.channel
greets["welcome-channel"] = channel.id
await ctx.send("Set greets channel to {}".format(channel.mention))
elif setting in ("message", "custom-message"):
greets["custom-message"] = text
await ctx.send("Custom message set to '{}'".format(text))
else:
return await ctx.send("No valid option given.")
return settings.update(greets, "greets")
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)
@@ -156,22 +157,20 @@ class JoinLeave(commands.Cog):
`;goodbyes channel #logs`
"""
setting = setting.lower()
settings = guild_settings.get(ctx.guild)
goodbyes = settings["goodbyes"]
if setting == "enable":
goodbyes["enabled"] = 1
await ctx.send("'goodbyes' was enabled!")
elif setting == "disable":
goodbyes["enabled"] = 0
await ctx.send("'goodbyes' was disabled :cry:")
elif setting in ("channel", "goodbye-channel"):
if channel is None:
channel = ctx.channel
goodbyes["goodbye-channel"] = channel.id
await ctx.send("Set goodbye channel to {}".format(channel.mention))
else:
return await ctx.send("No valid option given.")
return settings.update(goodbyes, "goodbyes")

def setup(Bot):
Bot.add_cog(JoinLeave(Bot))
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))

+ 38
- 40
roxbot/cogs/nsfw.py View File

@@ -27,32 +27,33 @@ import discord
from discord.ext import commands

import roxbot
from roxbot import guild_settings as gs
from roxbot.db import *


def tag_blacklist(guild):
blacklist = ""
for tag in gs.get(guild)["nsfw"]["blacklist"]:
blacklist += " -{}".format(tag)
return blacklist
class NSFWSingle(db.Entity):
enabled = Required(bool, default=False)
blacklisted_tags = Optional(StrArray)
guild_id = Required(int, unique=True, size=64)


class NFSW(commands.Cog):
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.settings = {
"nsfw": {
"enabled": 0,
"convert": {"enabled": "bool"},
"blacklist": []
}
}
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 = tag_blacklist(ctx.guild)
banned_tags = self.tag_blacklist(ctx.guild)
else:
banned_tags = ""

@@ -131,32 +132,29 @@ class NFSW(commands.Cog):
# Remove "Roxbot" as a blacklisted tag
;nsfw removebadtag Roxbot
"""
setting = setting.lower()
settings = roxbot.guild_settings.get(ctx.guild)
nsfw = settings["nsfw"]

if setting == "enable":
nsfw["enabled"] = 1
await ctx.send("'nsfw' was enabled!")
elif setting == "disable":
nsfw["enabled"] = 0
await ctx.send("'nsfw' was disabled :cry:")
elif setting == "addbadtag":
if changes not in nsfw["blacklist"]:
nsfw["blacklist"].append(changes)
await ctx.send("'{}' has been added to the blacklisted tag list.".format(changes))
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("'{}' is already in the list.".format(changes))
elif setting == "removebadtag":
try:
nsfw["blacklist"].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.")
return settings.update(nsfw, "nsfw")
return await ctx.send("No valid option given.")


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

+ 65
- 59
roxbot/cogs/selfassign.py View File

@@ -27,30 +27,32 @@ import discord
from discord.ext import commands

import roxbot
from roxbot import guild_settings as gs
from roxbot.db import *


class SelfAssignRoles(db.Entity):
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)


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.settings = {
"self_assign": {
"enabled": 0,
"convert": {"enabled": "bool", "roles": "role"},
"roles": []
}
}
self.autogen_db = SelfAssignSingle

@commands.Cog.listener()
async def on_guild_role_delete(self, role):
"""Cleans up settings on removal of stored IDs."""
settings = gs.get(role.guild)
sa = settings["self_assign"]
for sa_role in sa["roles"]:
if int(sa_role) == role.id:
sa["roles"].remove(role.id)
return settings.update(sa, "self_assign")
with db_session:
query = SelfAssignRoles.get(role_id=role.id)
if query:
query.delete()

@commands.guild_only()
@commands.has_permissions(manage_roles=True)
@@ -67,37 +69,34 @@ class SelfAssign(commands.Cog):
`;sa enable`
`;sa add ROLE`
"""
settings = roxbot.guild_settings.get(ctx.guild)
self_assign = settings["self_assign"]
setting = setting.lower()


if setting == "enable":
self_assign["enabled"] = 1
await ctx.send("'self_assign' was enabled!")
elif setting == "disable":
self_assign["enabled"] = 0
await ctx.send("'self_assign' was disabled :cry:")
elif setting == "add":
try:
if role.id in self_assign["roles"]:
return await ctx.send("{} is already a self-assignable role.".format(role.name))
self_assign["roles"].append(role.id)
await ctx.send('Role "{}" added'.format(str(role)))
except AttributeError:
raise commands.BadArgument("Could not find that role.")
elif setting == "remove":
try:
if role.id in self_assign["roles"]:
self_assign["roles"].remove(role.id)
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.")
return settings.update(self_assign, "self_assign")
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)
@@ -105,18 +104,21 @@ class SelfAssign(commands.Cog):
"""
List's all roles that can be self-assigned on this server.
"""
settings = gs.get(ctx.guild)
paginator = commands.Paginator(prefix="`", suffix="`")
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 settings["self_assign"]["enabled"]:
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 settings["self_assign"]["roles"]:
for serverrole in ctx.guild.roles:
if role == serverrole.id:
paginator.add_line("- {}".format(serverrole.name))

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)
@@ -130,9 +132,11 @@ class SelfAssign(commands.Cog):
Example:
.iam OverwatchPing
"""
settings = gs.get(ctx.guild)
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
query = SelfAssignRoles.get(role_id=role.id)

if not settings["self_assign"]["enabled"]:
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)

@@ -141,7 +145,7 @@ class SelfAssign(commands.Cog):
if role in member.roles:
return await ctx.send("You already have that role.")

if role.id in settings["self_assign"]["roles"]:
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:
@@ -156,18 +160,20 @@ class SelfAssign(commands.Cog):
Example:
.iamn OverwatchPing
"""
settings = gs.get(ctx.guild)
with db_session:
self_assign = SelfAssignSingle.get(guild_id=ctx.guild.id)
query = SelfAssignRoles.get(role_id=role.id)

if not settings["self_assign"]["enabled"]:
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 role.id in settings["self_assign"]["roles"]:
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 role.id in settings["self_assign"]["roles"]:
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.")

+ 59
- 57
roxbot/cogs/voice.py View File

@@ -33,6 +33,7 @@ import youtube_dl
from discord.ext import commands

import roxbot
from roxbot.db import *

# Suppress noise about console usage from errors
youtube_dl.utils.bug_reports_message = lambda: ''
@@ -58,14 +59,22 @@ ffmpeg_options = {
ytdl = youtube_dl.YoutubeDL(ytdl_format_options)


class VoiceSingle(db.Entity):
need_perms = Required(bool, default=False)
skip_voting = Required(bool, default=False)
skip_ratio = Required(float, default=0.6, py_check=lambda v: 0 <= v <= 1)
max_length = Required(int, default=600)
guild_id = Required(int, size=64, unique=True)


def need_perms():
def predicate(ctx):
gs = roxbot.guild_settings.get(ctx.guild)
if gs["voice"]["need_perms"]:
with db_session:
settings = VoiceSingle.get(guild_id=ctx.guild.id)
if settings.need_perms:
return roxbot.utils.has_permissions_or_owner(ctx, manage_channels=True)
else:
return True

return commands.check(predicate)


@@ -167,15 +176,7 @@ class Voice(commands.Cog):

# Setup variables and then add dictionary entries for all guilds the bot can see on boot-up.
self.bot = bot
self.settings = {
"voice": {
"need_perms": 0,
"skip_voting": 0,
"skip_ratio": 0.6,
"convert": {"need_perms": "bool", "skip_voting": "bool"},
"max_length": 600
}
}
self.autogen_db = VoiceSingle
# TODO: Make this into a on roxbot joining voice thing instead of generating this for all servers on boot.
self.refresh_rate = 1/60 # 60hz
self._volume = {}
@@ -290,7 +291,8 @@ class Voice(commands.Cog):
;play https://www.youtube.com/watch?v=3uOPGkEJ56Q
"""
guild = ctx.guild
voice = roxbot.guild_settings.get(guild)["voice"]
with db_session:
max_duration = VoiceSingle.get(guild_id=guild.id).max_length

# Checks if invoker is in voice with the bot. Skips admins and mods and owner and if the song was queued previously.
if not (roxbot.utils.has_permissions_or_owner(ctx, manage_channels=True) or from_queue):
@@ -318,7 +320,7 @@ class Voice(commands.Cog):
video = video["entries"][0]

# Duration limiter handling
if video.get("duration", 1) > voice["max_length"] and not roxbot.utils.has_permissions_or_owner(ctx, manage_channels=True):
if video.get("duration", 1) > max_duration and not roxbot.utils.has_permissions_or_owner(ctx, manage_channels=True):
raise commands.CommandError("Cannot play video, duration is bigger than the max duration allowed.")

# Actual playing stuff section.
@@ -420,8 +422,9 @@ class Voice(commands.Cog):
# Force skip a video
;skip --force
"""
voice = roxbot.guild_settings.get(ctx.guild)["voice"]
if voice["skip_voting"] and not (option == "--force" and ctx.author.guild_permissions.manage_channels): # Admin force skipping
with db_session:
voice = VoiceSingle.get(guild_id=ctx.guild.id)
if voice.skip_voting and not (option == "--force" and ctx.author.guild_permissions.manage_channels): # Admin force skipping
if ctx.author in self.skip_votes[ctx.guild.id]:
return await ctx.send("You have already voted to skip the current track.")
else:
@@ -429,7 +432,7 @@ class Voice(commands.Cog):
# -1 due to the bot being counted in the members generator
ratio = len(self.skip_votes[ctx.guild.id]) / (len(ctx.voice_client.channel.members) - 1)
needed_users = ceil((len(ctx.voice_client.channel.members) - 1) * voice["skip_ratio"])
if ratio >= voice["skip_ratio"]:
if ratio >= voice.skip_ratio:
await ctx.send("{} voted the skip the video.".format(ctx.author))
await ctx.send("Votes to skip now playing has been met. Skipping video...")
self.skip_votes[ctx.guild.id] = []
@@ -584,47 +587,46 @@ class Voice(commands.Cog):
"""
setting = setting.lower()
change = change.lower()
settings = roxbot.guild_settings.get(ctx.guild)
voice = settings["voice"]

if setting == "enable":
if change in ("needperms", "need_perms"):
voice["need_perms"] = 1
await ctx.send("'{}' has been enabled!".format(change))
elif change in ("skipvoting", "skip_voting"):
voice["skip_voting"] = 1
await ctx.send("'{}' has been enabled!".format(change))
else:
return await ctx.send("Not a valid change.")
elif setting == "disable":
if change == "needperms":
voice["need_perms"] = 1
await ctx.send("'{}' was disabled :cry:".format(change))
elif change == "skipvoting":
voice["skip_voting"] = 1
await ctx.send("'{}' was disabled :cry:".format(change))
else:
return await ctx.send("Not a valid change.")
elif setting in ("skipratio", "skip_ratio") :
change = float(change)
if 1 > change > 0:
voice["skip_ratio"] = change
elif 0 < change <= 100:
change = change/10
voice["skip_ratio"] = change
else:
return await ctx.send("Valid ratio not given.")
await ctx.send("Skip Ratio was set to {}".format(change))
elif setting in ("maxlength", "max_length"):
change = int(change)
if change >= 1:
voice["max_length"] = change

with db_session:
voice = VoiceSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
if change in ("needperms", "need_perms"):
voice.need_perms = True
await ctx.send("'{}' has been enabled!".format(change))
elif change in ("skipvoting", "skip_voting"):
voice.skip_voting = True
await ctx.send("'{}' has been enabled!".format(change))
else:
return await ctx.send("Not a valid change.")
elif setting == "disable":
if change in ("skipvoting", "skip_voting"):
voice.need_perms = False
await ctx.send("'{}' was disabled :cry:".format(change))
elif change in ("skipvoting", "skip_voting"):
voice.skip_voting = False
await ctx.send("'{}' was disabled :cry:".format(change))
else:
return await ctx.send("Not a valid change.")
elif setting in ("skipratio", "skip_ratio"):
change = float(change)
if 1 > change > 0:
voice.skip_ratio = change
elif 0 < change <= 100:
change = change/10
voice.skip_ratio = change
else:
return await ctx.send("Valid ratio not given.")
await ctx.send("Skip Ratio was set to {}".format(change))
elif setting in ("maxlength", "max_length"):
change = int(change)
if change >= 1:
voice.max_length = change
else:
return await ctx.send("Valid max duration not given.")
await ctx.send("Max Duration was set to {}".format(change))
else:
return await ctx.send("Valid max duration not given.")
await ctx.send("Max Duration was set to {}".format(change))
else:
return await ctx.send("Valid option not given.")
return settings.update(voice, "voice")
return await ctx.send("Valid option not given.")


def setup(bot_client):

+ 113
- 146
roxbot/core.py View File

@@ -34,6 +34,17 @@ import youtube_dl
from discord.ext import commands

import roxbot
from roxbot.db import *


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


class Blacklist(db.Entity):
user_id = Required(int, unique=True, size=64)


class Roxbot(commands.Bot):
@@ -51,11 +62,8 @@ class Roxbot(commands.Bot):
Returns
=======
If the user is blacklisted: bool"""
with open("roxbot/settings/blacklist.txt", "r") as fp:
for line in fp.readlines():
if str(user.id) + "\n" == line:
return True
return False
with db_session:
return select(u for u in Blacklist if u.user_id == user.id).exists()

async def delete_option(self, message, delete_emoji=None, timeout=20):
"""Utility function that allows for you to add a delete option to the end of a command.
@@ -107,9 +115,10 @@ class Roxbot(commands.Bot):

"""
if guild:
logging = roxbot.guild_settings.get(guild)["logging"]
channel = discord.utils.get(guild.channels, id=logging["channel"])
if logging["enabled"]:
with db_session:
logging = LoggingSingle.get(guild_id=guild.id)
if logging.enabled and logging.logging_channel_id:
channel = self.get_channel(logging.logging_channel_id)
embed = discord.Embed(title="{} command logging".format(command_name), colour=roxbot.EmbedColours.pink)
for key, value in kwargs.items():
embed.add_field(name=key, value=value)
@@ -144,21 +153,15 @@ class Core(commands.Cog):
self.bot.add_listener(self.log_member_join, "on_member_join")
self.bot.add_listener(self.log_member_remove, "on_member_remove")

self.settings = {
"logging": {
"enabled": 0,
"convert": {"enabled": "bool", "channel": "channel"},
"channel": 0
}
}
self.autogen_db = LoggingSingle


@staticmethod
def command_not_found_check(ctx, error):
try:
# Sadly this is the only part that makes a cog not modular. I have tried my best though to make it usable without the cog.
cc = roxbot.guild_settings.get(ctx.guild)["custom_commands"]
is_custom_command = bool(ctx.invoked_with in cc["1"] or ctx.invoked_with in cc["2"])
with roxbot.db.db_session:
is_custom_command = roxbot.db.db.exists('SELECT * FROM CCCommands WHERE name = "{}" AND type IN (1, 2) AND guild_id = {}'.format(ctx.invoked_with, ctx.guild.id))
is_emoticon_face = bool(any(x in string.punctuation for x in ctx.message.content.strip(ctx.prefix)[0]))
is_too_short = bool(len(ctx.message.content) <= 2)
if is_emoticon_face:
@@ -250,31 +253,40 @@ class Core(commands.Cog):
@staticmethod
async def cleanup_logging_settings(channel):
"""Cleans up settings on removal of stored IDs."""
settings = roxbot.guild_settings.get(channel.guild)
r_logging = settings["logging"]
if channel.id == r_logging["channel"]:
r_logging["channel"] = 0
settings.update(r_logging, "logging")
with db_session:
settings = LoggingSingle.get(guild_id=channel.guild.id)
if settings.logging_channel_id == channel.id:
settings.logging_channel_id = None

async def log_member_join(self, member):
r_logging = roxbot.guild_settings.get(member.guild)["logging"]
if r_logging["enabled"]:
channel = self.bot.get_channel(r_logging["channel"])
with db_session:
settings = LoggingSingle.get(guild_id=member.guild.id)
if settings.enabled:
channel = member.guild.get_channel(settings.logging_channel_id)
embed = discord.Embed(title="{} joined the server".format(member), colour=roxbot.EmbedColours.pink)
embed.add_field(name="ID", value=member.id)
embed.add_field(name="Mention", value=member.mention)
embed.add_field(name="Date Account Created", value=roxbot.datetime.format(member.created_at))
embed.add_field(name="Date Joined", value=roxbot.datetime.format(member.joined_at))
embed.set_thumbnail(url=member.avatar_url)
return await channel.send(embed=embed)
try:
return await channel.send(embed=embed)
except AttributeError:
pass

async def log_member_remove(self, member):
# TODO: Add some way of detecting whether a user left/was kicked or was banned.
r_logging = roxbot.guild_settings.get(member.guild)["logging"]
if r_logging["enabled"]:
channel = self.bot.get_channel(r_logging["channel"])
if member == self.bot.user:
return
with db_session:
settings = LoggingSingle.get(guild_id=member.guild.id)
if settings.enabled:
channel = member.guild.get_channel(settings.logging_channel_id)
embed = discord.Embed(description="{} left the server".format(member), colour=roxbot.EmbedColours.pink)
return await channel.send(embed=embed)
try:
return await channel.send(embed=embed)
except AttributeError:
pass

@commands.has_permissions(manage_channels=True)
@commands.guild_only()
@@ -288,22 +300,22 @@ class Core(commands.Cog):
"""

setting = setting.lower()
settings = roxbot.guild_settings.get(ctx.guild)
if setting == "enable":
settings["logging"]["enabled"] = 1
await ctx.send("'logging' was enabled!")
elif setting == "disable":
settings["logging"]["enabled"] = 0
await ctx.send("'logging' was disabled :cry:")
elif setting == "channel":
if not channel:
channel = ctx.channel
settings["logging"]["channel"] = channel.id
await ctx.send("{} has been set as the logging channel!".format(channel.mention))
else:
return await ctx.send("No valid option given.")
return settings.update(settings["logging"], "logging")
with db_session:
settings = LoggingSingle.get(guild_id=ctx.guild.id)
if setting == "enable":
settings.enabled = 1
return await ctx.send("'logging' was enabled!")
elif setting == "disable":
settings.enabled = 0
return await ctx.send("'logging' was disabled :cry:")
elif setting == "channel":
if not channel:
channel = ctx.channel
settings.enabled = channel.id
return await ctx.send("{} has been set as the logging channel!".format(channel.mention))
else:
return await ctx.send("No valid option given.")

#############
# Backups #
@@ -311,19 +323,14 @@ class Core(commands.Cog):

async def auto_backups(self):
await self.bot.wait_until_ready()
raw_settings = {}
for guild in self.bot.guilds:
directory = os.listdir('roxbot/settings/servers/{}'.format(guild.id))
raw_settings = {**raw_settings, **roxbot.guild_settings._open_config(guild, directory)}
while not self.bot.is_closed():
current_settings = {}
for guild in self.bot.guilds:
directory = os.listdir('roxbot/settings/servers/{}'.format(guild.id))
current_settings = {**current_settings, **roxbot.guild_settings._open_config(guild, directory)}
if raw_settings != current_settings:
raw_settings = current_settings
time = datetime.datetime.now()
roxbot.guild_settings.backup("{:%Y.%m.%d %H:%M:%S} Auto Backup".format(time))
time = datetime.datetime.now()
filename = "{}/roxbot/settings/backups/{:%Y.%m.%d %H:%M:%S} Auto Backup.sql".format(os.getcwd(), time)
con = sqlite3.connect(os.getcwd() + "/roxbot/settings/db.sqlite")
with open(filename, 'w') as f:
for line in con.iterdump():
f.write('%s\n' % line)
con.close()
await asyncio.sleep(roxbot.backup_rate)

@commands.command(enabled=roxbot.backup_enabled)
@@ -336,9 +343,13 @@ class Core(commands.Cog):
Using only this and not the automatic backups is not recommend.
"""
time = datetime.datetime.now()
filename = "{:%Y.%m.%d %H:%M:%S} Manual Backup".format(time)
roxbot.guild_settings.backup(filename)
return await ctx.send("Settings file backed up as a folder named '{}".format(filename))
filename = "{}/roxbot/settings/backups/{:%Y.%m.%d %H:%M:%S} Auto Backup.sql".format(os.getcwd(), time)
con = sqlite3.connect(os.getcwd() + "/roxbot/settings/db.sqlite")
with open(filename, 'w') as f:
for line in con.iterdump():
f.write('%s\n' % line)
con.close()
return await ctx.send("Settings file backed up as a folder named '{}".format(filename.split("/")[-1]))

############################
# Bot Managment Commands #
@@ -369,32 +380,24 @@ class Core(commands.Cog):
await ctx.send("The owner cannot be blacklisted.")
users.remove(user)

if option in ['+', 'add']:
with open("roxbot/settings/blacklist.txt", "r") as fp:
for user in users:
for line in fp.readlines():
if user.id + "\n" in line:
users.remove(user)

with open("roxbot/settings/blacklist.txt", "a+") as fp:
lines = fp.readlines()
with db_session:
if option in ['+', 'add']:
for user in users:
if user.id not in lines:
fp.write("{}\n".format(user.id))
try:
Blacklist(user_id=user.id)
blacklist_amount += 1
return await ctx.send('{} user(s) have been added to the blacklist'.format(blacklist_amount))
except TransactionIntegrityError:
await ctx.send("{} is already in the blacklist.".format(user))
return await ctx.send('{} user(s) have been added to the blacklist'.format(blacklist_amount))

elif option in ['-', 'remove']:
with open("roxbot/settings/blacklist.txt", "r") as fp:
lines = fp.readlines()
with open("roxbot/settings/blacklist.txt", "w") as fp:
elif option in ['-', 'remove']:
for user in users:
for line in lines:
if str(user.id) + "\n" != line:
fp.write(line)
else:
fp.write("")
blacklist_amount += 1
u = Blacklist.get(user_id=user.id)
if u:
u.delete()
blacklist_amount += 1
else:
await ctx.send("{} isn't in the blacklist".format(user))
return await ctx.send('{} user(s) have been removed from the blacklist'.format(blacklist_amount))

@commands.command(aliases=["setavatar"])
@@ -488,55 +491,6 @@ class Core(commands.Cog):
await self.bot.change_presence(status=discord_status)
await ctx.send("**:ok:** Status set to {}".format(discord_status))

@staticmethod
def _parse_setting(ctx, settings_to_copy, raw=False):
settingcontent = ""
setting = settings_to_copy.copy()
convert = setting.get("convert", None)

if convert is not None and not raw:
for x in convert.keys():
converter = None
if convert[x] == "bool":
if setting[x] == 0:
setting[x] = False
else:
setting[x] = True
elif convert[x] == "channel":
converter = ctx.guild.get_channel
elif convert[x] == "role":
converter = ctx.guild.get_role
elif convert[x] in ("user", "member"):
converter = ctx.guild.get_member
elif convert[x] == "hide":
converter = None
setting[x] = "This is hidden. Please use other commands to get this data."
else:
converter = None

if converter:
if isinstance(setting[x], list):
if len(setting[x]) >= 60:
setting[x] = "There is too many {}s to display. Please use other commands to get this data.".format(convert[x])
else:
new_entries = []
for entry in setting[x]:
try:
new_entries.append(str(converter(entry)))
except AttributeError:
new_entries.append(entry)
setting[x] = new_entries
else:
try:
setting[x] = converter(setting[x])
except AttributeError:
pass

for x in setting.items():
if x[0] != "convert":
settingcontent += str(x).strip("()") + "\n"
return settingcontent

@commands.guild_only()
@commands.command(aliases=["printsettingsraw"])
@commands.has_permissions(manage_guild=True)
@@ -551,26 +505,39 @@ class Core(commands.Cog):
# print settings just for the Admin cog.
;printsettings Admin
"""
option = option.lower()
config = roxbot.guild_settings.get(ctx.guild)
settings = dict(config.settings.copy()) # Make a copy of settings so we don't change the actual settings.
if option:
option = option.lower()

entities = {}
for name, cog in self.bot.cogs.items():
try:
entities[name.lower()] = cog.autogen_db
except AttributeError:
pass

paginator = commands.Paginator(prefix="```py")
paginator.add_line("{} settings for {}.\n".format(self.bot.user.name, ctx.message.guild.name))
if option in settings:
raw = bool(ctx.invoked_with == "printsettingsraw")
settingcontent = self._parse_setting(ctx, settings[option], raw=raw)
if option in entities:
#raw = bool(ctx.invoked_with == "printsettingsraw")
with db_session:
settings = entities[option].get(guild_id=ctx.guild.id).to_dict()
settings.pop("id")
settings.pop("guild_id")
paginator.add_line("@{}".format(option))
paginator.add_line(settingcontent)
paginator.add_line(str(settings))
for page in paginator.pages:
await ctx.send(page)
else:
for setting in settings:
raw = bool(ctx.invoked_with == "printsettingsraw")
settingcontent = self._parse_setting(ctx, settings[setting], raw=raw)
paginator.add_line("@{}".format(setting))
paginator.add_line(settingcontent)
for page in paginator.pages:
await ctx.send(page)
with db_session:
for name, entity in entities.items():
settings = entity.get(guild_id=ctx.guild.id).to_dict()
settings.pop("id")
settings.pop("guild_id")
#raw = bool(ctx.invoked_with == "printsettingsraw")
paginator.add_line("@{}".format(name))
paginator.add_line(str(settings))
for page in paginator.pages:
await ctx.send(page)

@commands.command()
@commands.is_owner()

+ 62
- 0
roxbot/db.py View File

@@ -0,0 +1,62 @@
#
# Copyright (c) 2017-2018 Roxanne Gibson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sqlite3
from os import getcwd
from pony.orm import *


db_dir = getcwd() + "/roxbot/settings/db.sqlite"
db = Database()
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)


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


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()

+ 0
- 230
roxbot/guild_settings.py View File

@@ -1,230 +0,0 @@
# -*- coding: utf-8 -*-

# MIT License
#
# Copyright (c) 2017-2018 Roxanne Gibson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


import os
import json
import errno
import shutil


def _open_config(server, cogs):
"""Opens the guild settings file

servers: server id str
cogs: list of cog names

Returns
=======
servers.json: dict
"""
settings = {}
for cog in cogs:
with open('roxbot/settings/servers/{}/{}'.format(server, cog), 'r') as config_file:
settings = {**settings, **json.load(config_file)}
return settings


def _write_changes(guild_id, cogs, config):
"""

:param guild_id:
:param cogs:
:param config:
:return:
"""
for cog in cogs:
with open('roxbot/settings/servers/{}/{}'.format(guild_id, cog), "r") as conf_file:
file = json.load(conf_file)
with open('roxbot/settings/servers/{}/{}'.format(guild_id, cog), "w") as conf_file:
for key in config.keys():
if key in file.keys():
file[key] = config[key]
json.dump(file, conf_file)


def backup(name):
try:
shutil.copytree('roxbot/settings/servers', 'roxbot/settings/backups/{}'.format(name))
except OSError as e:
# If the error was caused because the source wasn't a directory
if e.errno == errno.ENOTDIR:
shutil.copy('roxbot/settings/servers', 'roxbot/settings/backups/{}'.format(name))
else:
print('Directory not copied. Error: %s' % e)


def _make_cog_json_file(server_id, name, data):
with open("roxbot/settings/servers/{}/{}.json".format(server_id, name), "w") as fp:
return json.dump(data, fp)


def _check_for_missing_cog(server_id, name, cog):
try:
if cog.settings:
settings = cog.settings.copy()
limited = settings.get("limited_to_guild")
if limited:
if limited != server_id:
return False
else:
settings.pop("limited_to_guild")

if "{}.json".format(name) not in os.listdir("roxbot/settings/servers/{}".format(server_id)):
_make_cog_json_file(server_id, name, settings)
return True
except AttributeError:
pass # If Cog has no settings
return False


def _make_server_folder(server, cogs):
os.mkdir("roxbot/settings/servers/{}".format(str(server.id)))
for name, cog in cogs.items():
_check_for_missing_cog(str(server.id), name, cog)


def error_check(servers, cogs):
# Check for missing servers folder
if "servers" not in os.listdir("roxbot/settings/"):
print("WARNING: Settings folder not found, making new default settings folder.")
os.mkdir("roxbot/settings/servers")
for server in servers:
_make_server_folder(server, cogs)

else:
for server in servers:
# Check for missing server
if str(server.id) not in os.listdir("roxbot/settings/servers/"):
_make_server_folder(server, cogs)
print("WARNING: The settings folder for {} was not found. The defaults have been created.".format(str(server)))

# Check for missing cog settings
for name, cog in cogs.items():
resp = _check_for_missing_cog(str(server.id), name, cog)
if resp:
print("WARNING: The settings folder for {} is missing the file {}. The defaults have been created.".format(str(server), name))


def remove_guild(guild):
"""Removes given guild from settings folders and saves changes."""
shutil.rmtree('roxbot/settings/server/{}'.format(guild.id))


def add_guild(guild, cogs):
"""Adds given guild to settings folder saves changes."""
_make_server_folder(guild, cogs)


def get(guild):
"""
Gets a single GuildSettings Object representing the settings of that guild
:param guild: :type discord.Guild:
:return Single GuildSettings Object: :type GuildSettings:
"""
return GuildSettings(guild)


class GuildSettings(object):
"""
An Object to store all settings for one guild.
The goal is to make editing settings a lot easier and make it so you don't have to handle things like ID's which caused a lot of issues when moving over to discord.py 1.0
"""
__slots__ = ["settings", "id", "name", "cogs"]

def __init__(self, guild):
self.id = guild.id
self.name = str(guild)
self.cogs = os.listdir("roxbot/settings/servers/{}".format(self.id))
self.settings = self.refresh()

def __str__(self):
return self.name

def __iter__(self):
list_settings = list(self.settings)
list_settings.sort()
for setting in list_settings:
yield setting

def __getitem__(self, key):
return self.settings[key]

@staticmethod
def _convert(settings, option="int"):
"""
Converts values between int and str in the settings dict/json. Required due to how ID's are ints in discord.py
and we can't store int's that big in a json file.
:param settings:
:param option:
:return:
"""
for key, setting in settings.items():
if setting.get("convert"):
for x in setting["convert"].keys():
if setting["convert"][x] not in ("bool", "hide"):
if isinstance(setting[x], list):
for y, value in enumerate(setting[x]):
if option == "str":
setting[x][y] = str(value)
else:
setting[x][y] = int(value)
else:
if option == "str":
setting[x] = str(setting[x])
else:
setting[x] = int(setting[x])
settings[key] = setting
return settings

def refresh(self):
"""
Open the settings, convert them to a usable format, and return for roxbot usage.
:return:
"""
settings = _open_config(self.id, self.cogs)
self._convert(settings)
return settings

def update(self, changed_dict, setting = None):
"""
Get latest settings, and update them with a change.
:param changed_dict:
:param setting: Setting should be a str of the setting key.
If nothing is passed, it is assumed changed_dict is the settings file for the whole server.
THIS IS NOT RECOMMENED. Always try and just pass the cogs settings and not a whole settings file.
:return:
"""
self.settings = self.refresh()
settings = self.settings.copy()
if setting is not None:
settings[setting] = changed_dict
elif isinstance(changed_dict, dict):
settings = changed_dict
elif isinstance(changed_dict, GuildSettings):
settings = changed_dict.settings
else:
raise TypeError("changed_dict can only be a dict or GuildSettings object.")
settings = self._convert(settings, "str")
_write_changes(self.id, self.cogs, settings)

+ 130
- 0
roxbot/scripts/JSONtoDB.py View File

@@ -0,0 +1,130 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# MIT License
#
# Copyright (c) 2017-2018 Roxanne Gibson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import os
import json
import hashlib
import datetime
from roxbot.db import *

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


@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

@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))


@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_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_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_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


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")

+ 0
- 0
roxbot/settings/blacklist.txt View File


+ 1
- 1
roxbot/settings/roxbot_example.conf View File

@@ -20,4 +20,4 @@ Imgur=TokenHere
; enabled: Whether or not backups should be enabled. This is heavily recommened to be kept on.
; rate: The amount of time in minutes that the bot will check for changes in the settings file to backup. Default: 5 minutes
enabled=True
rate=5
rate=30

Loading…
Cancel
Save