1
0
Fork 0
mirror of synced 2024-06-26 18:21:15 +12:00
This commit is contained in:
Phxntxm 2016-09-03 16:26:50 -05:00
commit 6de4800e54
20 changed files with 412 additions and 522 deletions

38
bot.py
View file

@ -23,42 +23,43 @@ extensions = ['cogs.interaction',
'cogs.links',
'cogs.tags',
'cogs.roles',
'cogs.statsupdate',
'cogs.strawpoll',
'cogs.tictactoe',
'cogs.hangman',
'cogs.steam']
'cogs.statsupdate']
bot = commands.Bot(command_prefix=config.commandPrefix, description=config.botDescription, pm_help=None)
discord_logger = logging.getLogger('discord')
discord_logger.setLevel(logging.WARNING)
opts = {'command_prefix': config.command_prefix,
'description': config.bot_description,
'pm_help': None,
'shard_count': config.shard_count,
'shard_id': config.shard_id}
log = logging.getLogger()
log.setLevel(logging.INFO)
handler = logging.FileHandler(filename='bonfire.log', encoding='utf-8', mode='a')
log.addHandler(handler)
bot = commands.Bot(**opts)
logging.basicConfig(level=logging.INFO, filename='bonfire.log')
@bot.event
async def on_ready():
# Change the status upon connection to the default status
await bot.change_status(discord.Game(name=config.defaultStatus, type=0))
channel_id = config.get_content('restart_server')
await bot.change_status(discord.Game(name=config.default_status, type=0))
channel_id = await config.get_content('restart_server')
channel_id = channel_id or 0
if not hasattr(bot, 'uptime'):
bot.uptime = pendulum.utcnow()
# Just in case the bot was restarted while someone was battling, clear it so they do not get stuck
config.save_content('battling', {})
await config.save_content('battling', {})
# Check if the bot was restarted, if so send a message to the channel the bot was restarted from
if channel_id != 0:
destination = discord.utils.find(lambda m: m.id == channel_id, bot.get_all_channels())
await bot.send_message(destination, "I have just finished restarting!")
config.save_content('restart_server', 0)
if not hasattr(bot, 'uptime'):
bot.uptime = pendulum.utcnow()
await config.save_content('restart_server', 0)
@bot.event
async def on_member_join(member):
notifications = config.get_content('user_notifications') or {}
notifications = await config.get_content('user_notifications')
server_notifications = notifications.get(member.server.id)
# By default, notifications should be off unless explicitly turned on
@ -71,7 +72,7 @@ async def on_member_join(member):
@bot.event
async def on_member_remove(member):
notifications = config.get_content('user_notifications') or {}
notifications = await config.get_content('user_notifications')
server_notifications = notifications.get(member.server.id)
# By default, notifications should be off unless explicitly turned on
@ -91,6 +92,7 @@ async def on_message(message):
await bot.process_commands(message)
@bot.event
async def on_command_error(error, ctx):
if isinstance(error, commands.BadArgument):
@ -125,4 +127,4 @@ async def on_command_error(error, ctx):
if __name__ == '__main__':
for e in extensions:
bot.load_extension(e)
bot.run(config.botToken)
bot.run(config.bot_token)

View file

@ -63,19 +63,22 @@ class Core:
# The only real use of doing it this way is easier editing if the info in this command is changed
fmt = {}
all_members = list(self.bot.get_all_members())
bot_data = await config.get_content('bot_data')
total_data = {}
for shard, values in bot_data.items():
for key, value in values.items():
if key in total_data:
total_data[key] += value
else:
total_data[key] = value
# We can pretty safely assume that the author is going to be in at least one channel with the bot
# So find the author based on that list
authors = []
for author_id in config.owner_ids:
authors.append(discord.utils.get(all_members, id=author_id).name)
fmt['Official Bot Server'] = config.dev_server
fmt['Author'] = ", ".join(authors)
fmt['Uptime'] = (pendulum.utcnow() - self.bot.uptime).in_words()
fmt['Total Servers'] = len(self.bot.servers)
fmt['Total Members'] = len(all_members)
fmt['Total Servers'] = total_data.get('server_count')
fmt['Total Members'] = total_data.get('member_count')
fmt['Description'] = self.bot.description
servers_playing_music = len([server_id for server_id, state in self.bot.get_cog('Music').voice_states.items() if
@ -123,9 +126,9 @@ class Core:
await self.bot.say("Use this URL to add me to a server that you'd like!\n{}"
.format(discord.utils.oauth_url(app_info.id, perms)))
@commands.command(pass_context=True)
@commands.command()
@checks.custom_perms(send_messages=True)
async def doggo(self, ctx):
async def doggo(self):
"""Use this to print a random doggo image.
Doggo is love, doggo is life."""
# Find a random image based on how many we currently have
@ -133,9 +136,9 @@ class Core:
with open(f, 'rb') as f:
await self.bot.upload(f)
@commands.command(pass_context=True)
@commands.command()
@checks.custom_perms(send_messages=True)
async def snek(self, ctx):
async def snek(self):
"""Use this to print a random snek image.
Sneks are o3o"""
# Find a random image based on how many we currently have

View file

@ -4,14 +4,46 @@ from discord.ext.commands.cooldowns import BucketType
from .utils import checks
import re
import random
phrases = ["Eat My Hat", "Par For the Course", "Raining Cats and Dogs", "Roll With the Punches",
"Curiosity Killed The Cat", "Man of Few Words", "Cry Over Spilt Milk", "Scot-free", "Rain on Your Parade",
"Go For Broke", "Shot In the Dark", "Mountain Out of a Molehill", "Jaws of Death", "A Dime a Dozen",
"Jig Is Up", "Elvis Has Left The Building", "Wake Up Call", "Jumping the Gun", "Up In Arms",
"Beating Around the Bush", "Flea Market", "Playing For Keeps", "Cut To The Chase", "Fight Fire With Fire",
"Keep Your Shirt On", "Poke Fun At", "Everything But The Kitchen Sink", "Jaws of Life",
"What Goes Up Must Come Down", "Give a Man a Fish", "Plot Thickens - The",
"Not the Sharpest Tool in the Shed", "Needle In a Haystack", "Right Off the Bat", "Throw In the Towel",
"Down To Earth", "Lickety Split", "I Smell a Rat", "Long In The Tooth",
"You Can't Teach an Old Dog New Tricks", "Back To the Drawing Board", "Down For The Count",
"On the Same Page", "Under Your Nose", "Cut The Mustard",
"If You Can't Stand the Heat, Get Out of the Kitchen", "Knock Your Socks Off", "Playing Possum",
"No-Brainer", "Money Doesn't Grow On Trees", "In a Pickle", "In the Red", "Fit as a Fiddle", "Hear, Hear",
"Hands Down", "Off One's Base", "Wild Goose Chase", "Keep Your Eyes Peeled", "A Piece of Cake",
"Foaming At The Mouth", "Go Out On a Limb", "Quick and Dirty", "Hit Below The Belt",
"Birds of a Feather Flock Together", "Wouldn't Harm a Fly", "Son of a Gun",
"Between a Rock and a Hard Place", "Down And Out", "Cup Of Joe", "Down To The Wire",
"Don't Look a Gift Horse In The Mouth", "Talk the Talk", "Close But No Cigar",
"Jack of All Trades Master of None", "High And Dry", "A Fool and His Money are Soon Parted",
"Every Cloud Has a Silver Lining", "Tough It Out", "Under the Weather", "Happy as a Clam",
"An Arm and a Leg", "Read 'Em and Weep", "Right Out of the Gate", "Know the Ropes",
"It's Not All It's Cracked Up To Be", "On the Ropes", "Burst Your Bubble", "Mouth-watering",
"Swinging For the Fences", "Fool's Gold", "On Cloud Nine", "Fish Out Of Water", "Ring Any Bells?",
"There's No I in Team", "Ride Him, Cowboy!", "Top Drawer", "No Ifs, Ands, or Buts",
"You Can't Judge a Book By Its Cover", "Don't Count Your Chickens Before They Hatch", "Cry Wolf",
"Beating a Dead Horse", "Goody Two-Shoes", "Heads Up", "Drawing a Blank", "Keep On Truckin'", "Tug of War",
"Short End of the Stick", "Hard Pill to Swallow", "Back to Square One", "Love Birds", "Dropping Like Flies",
"Break The Ice", "Knuckle Down", "Lovey Dovey", "Greased Lightning", "Let Her Rip", "All Greek To Me",
"Two Down, One to Go", "What Am I, Chopped Liver?", "It's Not Brain Surgery", "Like Father Like Son",
"Easy As Pie", "Elephant in the Room", "Quick On the Draw", "Barking Up The Wrong Tree",
"A Chip on Your Shoulder", "Put a Sock In It", "Quality Time", "Yada Yada", "Head Over Heels",
"My Cup of Tea", "Ugly Duckling", "Drive Me Nuts", "When the Rubber Hits the Road"]
class Game:
def __init__(self, word, creator):
def __init__(self, word):
self.word = word
self.creator = creator
# This converts everything but spaces to a blank
# TODO: Only convert [a-zA-Z0-9]
self.blanks = "".join(letter if not re.search("[a-zA-Z0-9]", letter) else "_" for letter in word)
self.failed_letters = []
self.guessed_letters = []
@ -71,7 +103,7 @@ class Hangman:
def create(self, word, ctx):
# Create a new game, then save it as the server's game
game = Game(word, ctx.message.author)
game = Game(word)
self.games[ctx.message.server.id] = game
return game
@ -84,22 +116,9 @@ class Hangman:
if not game:
await self.bot.say("There are currently no hangman games running!")
return
# This check is here in case we failed in creating the hangman game
# It is easier to make this check here, instead of throwing try except's around the creation
# As there are multiple places we can fail...if we can't send a message/PM a user
if game == "placeholder":
self.games[ctx.ctx.message.server.id] = None
await self.bot.say("Sorry but the attempt to setup a game on this server failed!\n"
"This is most likely due to me not being able to PM the user who created the game\n"
"Make sure you allow this feature if you want to be able to create a hangman game")
return
if ctx.message.author == game.creator:
await self.bot.say("You can't guess at your own hangman game! :S")
return
# Check if we are guessing a letter or a phrase. Only one letter can be guessed at a time
# So if anything more than one was provided, we're guessing at the phrase
# We're creating a fmt variable, so that we can add a message for if a guess was correct or not
# And also add a message for a loss/win
if len(guess) == 1:
@ -141,34 +160,11 @@ class Hangman:
await self.bot.say("Sorry but only one Hangman game can be running per server!")
return
# Make sure the phrase is less than 30 characters
check = lambda m: len(m.content) < 30
# We want to send this message instead of just PM'ing the creator
# As some people have PM's turned off/ don't pay attention to them
await self.bot.say(
"I have just PM'd you {}, please respond there with the phrase you want to start a new"
" hangman game with".format(ctx.message.author.display_name))
# The only reason we save this variable, is so that we can retrieve
# The PrivateChannel for it, for use in our wait_for_message command
_msg = await self.bot.whisper("Please respond with the phrase you would like to use for your new hangman game\n"
"Please note that it must be under 30 characters long")
msg = await self.bot.wait_for_message(timeout=60.0, channel=_msg.channel, check=check)
# Doing this so that while we wait for the phrase, another one cannot be started.
self.games[ctx.message.server.id] = "placeholder"
if not msg:
del self.games[ctx.message.server.id]
await self.bot.whisper("Sorry, you took too long.")
return
else:
game = self.create(msg.content, ctx)
game = self.create(phrases[random.SystemRandom().randint(0, len(phrases) - 1)], ctx)
# Let them know the game has started, then print the current game so that the blanks are shown
await self.bot.say(
"Alright, a hangman game has just started, you can start guessing now!\n{}".format(str(game)))
@hangman.command(name='delete', aliases=['stop', 'remove', 'end'], pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
async def stop_game(self, ctx):
@ -178,7 +174,7 @@ class Hangman:
if self.games.get(ctx.message.server.id) is None:
await self.bot.say("There are no Hangman games running on this server!")
return
del self.games[ctx.message.server.id]
await self.bot.say("I have just stopped the game of Hangman, a new should be able to be started now!")

View file

@ -6,10 +6,10 @@ import discord
import random
def update_battle_records(winner, loser):
async def update_battle_records(winner, loser):
# We're using the Harkness scale to rate
# http://opnetchessclub.wikidot.com/harkness-rating-system
battles = config.get_content('battle_records') or {}
battles = await config.get_content('battle_records')
# Start ratings at 1000 if they have no rating
winner_stats = battles.get(winner.id) or {}
@ -52,7 +52,7 @@ def update_battle_records(winner, loser):
battles[winner.id] = winner_stats
battles[loser.id] = loser_stats
return config.save_content('battle_records', battles)
return await config.save_content('battle_records', battles)
class Interaction:
@ -62,7 +62,7 @@ class Interaction:
self.bot = bot
# Format for battles: {'serverid': {'player1': 'player2', 'player1': 'player2'}}
self.battles = {}
def user_battling(self, ctx, player2=None):
battling = self.battles.get(ctx.message.server.id)
@ -77,14 +77,15 @@ class Interaction:
return True
# If neither are found, no one is battling
return False
# Handles removing the author from the dictionary of battles
def battling_off(self, ctx):
battles = self.battles.get(ctx.message.server.id) or {}
player_id = ctx.message.author.id
# Create a new dictionary, exactly the way the last one was setup
# But don't include any that have the author's ID
self.battles[ctx.message.server.id] = {p1: p2 for p1, p2 in battles.items() if not p2 == player_id and not p1 == player_id}
self.battles[ctx.message.server.id] = {p1: p2 for p1, p2 in battles.items() if
not p2 == player_id and not p1 == player_id}
@commands.group(pass_context=True, no_pm=True, invoke_without_command=True)
@commands.cooldown(1, 180, BucketType.user)
@ -102,9 +103,6 @@ class Interaction:
return
# Add the author and player provided in a new battle
#battling = config.get_content('battling') or {}
#battling[ctx.message.author.id] = player2.id
#config.save_content('battling', battling)
battles = self.battles.get(ctx.message.server.id) or {}
battles[ctx.message.author.id] = player2.id
self.battles[ctx.message.server.id] = battles
@ -130,7 +128,7 @@ class Interaction:
battleP2 = ctx.message.author
# Get a random win message from our list
fmt = config.battleWins[random.SystemRandom().randint(0, len(config.battleWins) - 1)]
fmt = config.battle_wins[random.SystemRandom().randint(0, len(config.battle_wins) - 1)]
# Due to our previous checks, the ID should only be in the dictionary once, in the current battle we're checking
self.battling_off(ctx)
@ -138,11 +136,10 @@ class Interaction:
# All we need to do is change what order the challengers are printed/added as a paramater
if random.SystemRandom().randint(0, 1):
await self.bot.say(fmt.format(battleP1.mention, battleP2.mention))
update_battle_records(battleP1, battleP2)
await update_battle_records(battleP1, battleP2)
else:
await self.bot.say(fmt.format(battleP2.mention, battleP1.mention))
update_battle_records(battleP2, battleP1)
await update_battle_records(battleP2, battleP1)
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@ -176,7 +173,7 @@ class Interaction:
await self.bot.say("Why the heck are you booping me? Get away from me >:c")
return
boops = config.get_content('boops') or {}
boops = await config.get_content('boops')
# This is only used to print the amount of times they've booped someone
# Set to 1 for the first time someone was booped
@ -198,7 +195,7 @@ class Interaction:
booper_boops[boopee.id] = amount
boops[ctx.message.author.id] = booper_boops
config.save_content('boops', boops)
await config.save_content('boops', boops)
fmt = "{0.mention} has just booped you {1.mention}! That's {2} times now!"
await self.bot.say(fmt.format(booper, boopee, amount))

View file

@ -69,12 +69,15 @@ class Links:
if len(search) > 0:
# This sets the url as url?q=search+terms
url = 'https://derpibooru.org/search.json?q={}'.format('+'.join(search))
nsfw_channels = config.get_content("nsfw_channels") or {}
nsfw_channels = await config.get_content("nsfw_channels")
nsfw_channels = nsfw_channels.get('registered') or []
# If this is a nsfw channel, we just need to tack on 'explicit' to the terms
# Also use the custom filter that I have setup, that blocks some certain tags
# If the channel is not nsfw, we don't need to do anything, as the default filter blocks explicit
if ctx.message.channel.id in nsfw_channels:
url += ",+explicit&filter_id=95938"
url += ",+%28explicit+OR+suggestive%29&filter_id=95938"
else:
url += ",+safe"
# Get the response from derpibooru and parse the 'search' result from it
async with self.session.get(url, headers=self.headers) as r:
@ -123,7 +126,8 @@ class Links:
# Due to this, send a message saying we're looking up the information first
await self.bot.say("Looking up an image with those tags....")
nsfw_channels = config.get_content("nsfw_channels") or {}
nsfw_channels = await config.get_content("nsfw_channels")
nsfw_channels = nsfw_channels.get('registered') or []
# e621 by default does not filter explicit content, so tack on
# safe/explicit based on if this channel is nsfw or not
if ctx.message.channel.id in nsfw_channels:

View file

@ -19,10 +19,10 @@ class Mod:
async def alerts(self, ctx, channel: discord.Channel):
"""This command is used to set a channel as the server's 'notifications' channel
Any notifications (like someone going live on Twitch, or Picarto) will go to that channel"""
server_alerts = config.get_content('server_alerts') or {}
server_alerts = await config.get_content('server_alerts')
# This will update/add the channel if an entry for this server exists or not
server_alerts[ctx.message.server.id] = channel.id
config.save_content('server_alerts', server_alerts)
await config.save_content('server_alerts', server_alerts)
await self.bot.say("I have just changed this server's 'notifications' channel"
"\nAll notifications will now go to `{}`".format(channel))
@ -36,9 +36,9 @@ class Mod:
# So we base this channel on it's own and not from alerts
# When mod logging becomes available, that will be kept to it's own channel if wanted as well
on_off = ctx.message.channel.id if re.search("(on|yes|true)", on_off.lower()) else None
notifications = config.get_content('user_notifications') or {}
notifications = await config.get_content('user_notifications')
notifications[ctx.message.server.id] = on_off
config.save_content('user_notifications', notifications)
await config.save_content('user_notifications', notifications)
fmt = "notify" if on_off else "not notify"
await self.bot.say("This server will now {} if someone has joined or left".format(fmt))
@ -53,25 +53,28 @@ class Mod:
@checks.custom_perms(kick_members=True)
async def nsfw_add(self, ctx):
"""Registers this channel as a 'nsfw' channel"""
nsfw_channels = config.get_content('nsfw_channels') or {}
nsfw_channels = await config.get_content('nsfw_channels')
# rethinkdb cannot save a list as a field, so we need a dict with one elemtn to store our list
nsfw_channels = nsfw_channels.get('registered') or []
if ctx.message.channel.id in nsfw_channels:
await self.bot.say("This channel is already registered as 'nsfw'!")
else:
# Append instead of setting to a certain channel, so that multiple channels can be nsfw
nsfw_channels.append(ctx.message.channel.id)
config.save_content('nsfw_channels', nsfw_channels)
await config.save_content('nsfw_channels', {'registered': nsfw_channels})
await self.bot.say("This channel has just been registered as 'nsfw'! Have fun you naughties ;)")
@nsfw.command(name="remove", aliases=["delete"], pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
async def nsfw_remove(self, ctx):
"""Removes this channel as a 'nsfw' channel"""
nsfw_channels = config.get_content('nsfw_channels') or {}
nsfw_channels = await config.get_content('nsfw_channels')
nsfw_channels = nsfw_channels.get('registered') or []
if ctx.message.channel.id not in nsfw_channels:
await self.bot.say("This channel is not registered as a ''nsfw' channel!")
else:
nsfw_channels.remove(ctx.message.channel.id)
config.save_content('nsfw_channels', nsfw_channels)
await config.save_content('nsfw_channels', {'registered': nsfw_channels})
await self.bot.say("This channel has just been unregistered as a nsfw channel")
@commands.command(pass_context=True, no_pm=True)
@ -95,9 +98,9 @@ class Mod:
"Valid permissions are: ```\n{}```".format("\n".join("{}".format(i) for i in valid_perms)))
return
custom_perms = config.get_content('custom_permissions') or {}
custom_perms = await config.get_content('custom_permissions')
server_perms = custom_perms.get(ctx.message.server.id) or {}
cmd = None
# This is the same loop as the add command, we need this to get the
# command object so we can get the qualified_name
@ -110,7 +113,7 @@ class Mod:
except AttributeError:
cmd = None
break
if cmd is None:
await self.bot.say("That is not a valid command!")
return
@ -122,18 +125,22 @@ class Mod:
try:
custom_perms = [func for func in cmd.checks if "custom_perms" in func.__qualname__][0]
except IndexError:
# Loop through and check if there is a check called is_owner, if we loop through and don't find one
# This means that the only other choice is to be able to manage the server (for the checks on perm commands)
# Loop through and check if there is a check called is_owner
# Ff we loop through and don't find one
# This means that the only other choice is to be
# Able to manage the server (for the checks on perm commands)
for func in cmd.checks:
if "is_owner" in func.__qualname__:
await self.bot.say("You need to own the bot to run this command")
return
await self.bot.say("You are required to have `manage_server` permissions to run `{}`".format(cmd.qualified_name))
await self.bot.say(
"You are required to have `manage_server` permissions to run `{}`".format(cmd.qualified_name))
return
# Perms will be an attribute if custom_perms is found no matter what, so need to check this
perms = "\n".join(attribute for attribute, setting in custom_perms.perms.items() if setting)
await self.bot.say("You are required to have `{}` permissions to run `{}`".format(perms, cmd.qualified_name))
await self.bot.say(
"You are required to have `{}` permissions to run `{}`".format(perms, cmd.qualified_name))
else:
# Permissions are saved as bit values, so create an object based on that value
# Then check which permission is true, that is our required permission
@ -201,13 +208,13 @@ class Mod:
.format(permissions, "\n".join(valid_perms)))
return
custom_perms = config.get_content('custom_permissions') or {}
custom_perms = await config.get_content('custom_permissions')
server_perms = custom_perms.get(ctx.message.server.id) or {}
# Save the qualified name, so that we don't get screwed up by aliases
server_perms[cmd.qualified_name] = perm_value
custom_perms[ctx.message.server.id] = server_perms
config.save_content('custom_permissions', custom_perms)
await config.save_content('custom_permissions', custom_perms)
await self.bot.say("I have just added your custom permissions; "
"you now need to have `{}` permissions to use the command `{}`".format(permissions, command))
@ -215,7 +222,7 @@ class Mod:
@commands.has_permissions(manage_server=True)
async def remove_perms(self, ctx, *command: str):
"""Removes the custom permissions setup on the command specified"""
custom_perms = config.get_content('custom_permissions') or {}
custom_perms = await config.get_content('custom_permissions')
server_perms = custom_perms.get(ctx.message.server.id) or {}
if server_perms is None:
await self.bot.say("There are no custom permissions setup on this server yet!")
@ -224,7 +231,7 @@ class Mod:
cmd = None
# This is the same loop as the add command, we need this to get the
# command object so we can get the qualified_name
for part in command[0:len(command)]:
for part in command:
try:
if cmd is None:
cmd = self.bot.commands.get(part)
@ -245,9 +252,19 @@ class Mod:
return
del custom_perms[ctx.message.server.id][cmd.qualified_name]
config.save_content('custom_permissions', custom_perms)
await config.save_content('custom_permissions', custom_perms)
await self.bot.say("I have just removed the custom permissions for {}!".format(cmd))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(manage_server=True)
async def prefix(self, ctx, *, prefix: str):
"""This command can be used to set a custom prefix per server"""
prefixes = await config.get_content('prefixes')
prefixes[ctx.message.server.id] = prefix
await config.save_content('prefixes', prefixes)
await self.bot.say(
"I have just updated the prefix for this server; you now need to call commands with `{}`".format(prefix))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(manage_messages=True)
async def purge(self, ctx, limit: int = 100):
@ -294,7 +311,7 @@ class Mod:
@checks.custom_perms(send_messages=True)
async def rules(self, ctx):
"""This command can be used to view the current rules on the server"""
rules = config.get_content('rules') or {}
rules = await config.get_content('rules')
server_rules = rules.get(ctx.message.server.id)
if server_rules is None or len(server_rules) == 0:
await self.bot.say("This server currently has no rules on it! I see you like to live dangerously...")
@ -308,11 +325,11 @@ class Mod:
async def rules_add(self, ctx, *, rule: str):
"""Adds a rule to this server's rules"""
# Nothing fancy here, just get the rules, append the rule, and save it
rules = config.get_content('rules') or {}
rules = await config.get_content('rules')
server_rules = rules.get(ctx.message.server.id) or []
server_rules.append(rule)
rules[ctx.message.server.id] = server_rules
config.save_content('rules', rules)
await config.save_content('rules', rules)
await self.bot.say("I have just saved your new rule, use the rules command to view this server's current rules")
@rules.command(name='remove', aliases=['delete'], pass_context=True, no_pm=True)
@ -321,8 +338,8 @@ class Mod:
"""Removes one of the rules from the list of this server's rules
Provide a number to delete that rule; if no number is provided
I'll print your current rules and ask for a number"""
rules = config.get_content('rules') or {}
server_rules = rules.get(ctx.message.server.id)
rules = await config.get_content('rules')
server_rules = rules.get(ctx.message.server.id) or []
if server_rules is None or len(server_rules) == 0:
await self.bot.say(
"This server currently has no rules on it! Can't remove something that doesn't exist bro")
@ -344,7 +361,7 @@ class Mod:
return
del server_rules[int(msg.content) - 1]
rules[ctx.message.server.id] = server_rules
config.save_content('rules', rules)
await config.save_content('rules', rules)
await self.bot.say("I have just removed that rule from your list of rules!")
return
@ -352,7 +369,7 @@ class Mod:
try:
del server_rules[rule - 1]
rules[ctx.message.server.id] = server_rules
config.save_content('rules', rules)
await config.save_content('rules', rules)
await self.bot.say("I have just removed that rule from your list of rules!")
except IndexError:
await self.bot.say("That is not a valid rule number, try running the command again. "

View file

@ -4,7 +4,6 @@ from discord.ext import commands
import discord
import aiohttp
import json
base_url = "https://api.owapi.net/api/v2/u/"
# This is a list of the possible things that we may want to retrieve from the stats
@ -34,13 +33,13 @@ class Overwatch:
@ow.command(name="stats", pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
async def ow_stats(self, ctx, user: discord.Member=None, hero: str=""):
async def ow_stats(self, ctx, user: discord.Member = None, hero: str = ""):
"""Prints out a basic overview of a member's stats
Provide a hero after the member to get stats for that specific hero"""
if user is None:
user = ctx.message.author
ow_stats = config.get_content('overwatch') or {}
ow_stats = await config.get_content('overwatch')
bt = ow_stats.get(user.id)
if bt is None:
@ -48,17 +47,18 @@ class Overwatch:
return
# This API sometimes takes a while to look up information, so send a message saying we're processing
await self.bot.say("Searching profile information....")
if hero == "":
# If no hero was provided, we just want the base stats for a player
async with self.session.get(base_url + "{}/stats/general".format(bt), headers=self.headers) as r:
data = await r.json()
# Here is our list comprehension to get what kind of data we want.
fmt = "\n".join("{}: {}".format(i, r) for i, r in data['game_stats'].items() if i in check_g_stats)
fmt += "\n"
fmt += "\n".join("{}: {}".format(i, r) for i, r in data['overall_stats'].items() if i in check_o_stats)
# title and replace are used to format things nicely, while not having to have information for every piece of data
# title and replace are used to format things nicely
# while not having to have information for every piece of data
await self.bot.say(
"Overwatch stats for {}: ```py\n{}```".format(user.name, fmt.title().replace("_", " ")))
else:
@ -77,12 +77,13 @@ class Overwatch:
fmt = "{} is not an actual hero!".format(hero.title())
await self.bot.say(fmt)
return
# Same list comprehension as before
fmt = "\n".join("{}: {}".format(i, r) for i, r in data['general_stats'].items() if i in check_g_stats)
# Someone was complaining there was no KDR provided, so I made one myself and added that to the list
if data['general_stats'].get('eliminations') and data['general_stats'].get('deaths'):
fmt += "\nKill Death Ratio: {0:.2f}".format(data['general_stats'].get('eliminations')/data['general_stats'].get('deaths'))
fmt += "\nKill Death Ratio: {0:.2f}".format(
data['general_stats'].get('eliminations') / data['general_stats'].get('deaths'))
fmt += "\n"
fmt += "\n".join("{}: {}".format(i, r) for i, r in data['hero_stats'].items())
# Same formatting as above
@ -96,11 +97,11 @@ class Overwatch:
# Battletags are normally provided like name#id
# However the API needs this to be a -, so repliace # with - if it exists
bt = bt.replace("#", "-")
# This API sometimes takes a while to look up information, so send a message saying we're processing
await self.bot.say("Looking up your profile information....")
url = base_url + "{}/stats/general".format(bt)
# All we're doing here is ensuring that the status is 200 when looking up someone's general information
# If it's not, let them know exactly how to format their tag
async with self.session.get(url, headers=self.headers) as r:
@ -108,24 +109,24 @@ class Overwatch:
await self.bot.say("Profile does not exist! Battletags are picky, "
"format needs to be `user#xxxx`. Capitalization matters")
return
# Now just save the battletag
ow = config.get_content('overwatch') or {}
ow = await config.get_content('overwatch')
ow[ctx.message.author.id] = bt
config.save_content('overwatch', ow)
await config.save_content('overwatch', ow)
await self.bot.say("I have just saved your battletag {}".format(ctx.message.author.mention))
@ow.command(pass_context=True, name="delete", aliases=['remove'], no_pm=True)
@checks.custom_perms(send_messages=True)
async def delete(self, ctx):
"""Removes your battletag from the records"""
result = config.get_content('overwatch') or {}
result = await config.get_content('overwatch')
if result.get(ctx.message.author.id):
del result[ctx.message.author.id]
await self.bot.say("I no longer have your battletag saved {}".format(ctx.message.author.mention))
else:
await self.bot.say("I don't even have your battletag saved {}".format(ctx.message.author.mention))
del result[ctx.message.author.id]
await self.bot.say("I have just removed your battletag!")

View file

@ -18,14 +18,14 @@ class Owner:
def __init__(self, bot):
self.bot = bot
@commands.command()
@commands.check(checks.is_owner)
async def running(self):
"""This command detects all things currently running
This includes music played, tictactoe games, hangman games, and battles"""
servers_playing_music = len([server_id for server_id, state in self.bot.get_cog('Music').voice_states.items() if
state.is_playing()])
state.is_playing()])
hm_games = len([server_id for server_id, game in self.bot.get_cog('Hangman').games.items()])
ttt_games = len([server_id for server_id, game in self.bot.get_cog('TicTacToe').boards.items()])
count_battles = 0
@ -40,12 +40,11 @@ class Owner:
fmt += "{} different TicTacToe games running\n".format(ttt_games)
if count_battles:
fmt += "{} different battles going on\n".format(count_battles)
if not fmt:
fmt = "Nothing currently running!"
await self.bot.say(fmt)
@commands.command(pass_context=True)
@commands.check(checks.is_owner)
async def saferestart(self, ctx):
@ -60,7 +59,7 @@ class Owner:
await self.bot.say("Sorry, it's not safe to restart. I am currently playing a song on {} servers".format(
len(servers_playing_music)))
else:
config.save_content('restart_server', ctx.message.channel.id)
await config.save_content('restart_server', ctx.message.channel.id)
await self.bot.say("Restarting; see you in the next life {0}!".format(ctx.message.author.mention))
python = sys.executable
os.execl(python, python, *sys.argv)
@ -70,7 +69,7 @@ class Owner:
async def restart(self, ctx):
"""Forces the bot to restart"""
# This command is left in so that we can invoke it from saferestart, or we need a restart no matter what
config.save_content('restart_server', ctx.message.channel.id)
await config.save_content('restart_server', ctx.message.channel.id)
await self.bot.say("Restarting; see you in the next life {0}!".format(ctx.message.author.mention))
python = sys.executable
os.execl(python, python, *sys.argv)

View file

@ -46,7 +46,7 @@ class Picarto:
await self.bot.wait_until_ready()
# This is a loop that runs every 30 seconds, checking if anyone has gone online
while not self.bot.is_closed:
picarto = config.get_content('picarto') or {}
picarto = await config.get_content('picarto')
# Get all online users before looping, so that only one request is needed
online_users_list = await online_users()
old_online_users = {m_id: data for m_id, data in picarto.items() if
@ -63,7 +63,7 @@ class Picarto:
for server_id in r['servers']:
# Get the channel to send the message to, based on the saved alert's channel
server = self.bot.get_server(server_id)
server_alerts = config.get_content('server_alerts') or {}
server_alerts = await config.get_content('server_alerts')
channel_id = server_alerts.get(server_id) or server_id
channel = self.bot.get_channel(channel_id)
# Get the member that has just gone live
@ -72,7 +72,7 @@ class Picarto:
fmt = "{} has just gone live! View their stream at {}".format(member.display_name, url)
await self.bot.send_message(channel, fmt)
picarto[m_id]['live'] = 1
config.save_content('picarto', picarto)
await config.save_content('picarto', picarto)
for m_id, r in old_online_users.items():
# Get their url and their user based on that url
url = r['picarto_url']
@ -82,7 +82,7 @@ class Picarto:
for server_id in r['servers']:
# Get the channel to send the message to, based on the saved alert's channel
server = self.bot.get_server(server_id)
server_alerts = config.get_content('server_alerts') or {}
server_alerts = await config.get_content('server_alerts')
channel_id = server_alerts.get(server_id) or server_id
channel = self.bot.get_channel(channel_id)
# Get the member that has just gone live
@ -91,7 +91,7 @@ class Picarto:
member.display_name, url)
await self.bot.send_message(channel, fmt)
picarto[m_id]['live'] = 0
config.save_content('picarto', picarto)
await config.save_content('picarto', picarto)
await asyncio.sleep(30)
@commands.group(pass_context=True, invoke_without_command=True)
@ -100,7 +100,7 @@ class Picarto:
"""This command can be used to view Picarto stats about a certain member"""
# If member is not given, base information on the author
member = member or ctx.message.author
picarto_urls = config.get_content('picarto') or {}
picarto_urls = await config.get_content('picarto')
try:
member_url = picarto_urls.get(member.id)['picarto_url']
except:
@ -156,7 +156,7 @@ class Picarto:
"What would be the point of adding a nonexistant Picarto user? Silly")
return
picarto_urls = config.get_content('picarto') or {}
picarto_urls = await config.get_content('picarto')
result = picarto_urls.get(ctx.message.author.id)
# If information for this user already exists, override just the url, and not the information
@ -168,7 +168,7 @@ class Picarto:
picarto_urls[ctx.message.author.id] = {'picarto_url': url,
'servers': [ctx.message.server.id],
'notifications_on': 1, 'live': 0}
config.save_content('picarto', picarto_urls)
await config.save_content('picarto', picarto_urls)
await self.bot.say(
"I have just saved your Picarto url {}, this server will now be notified when you go live".format(
ctx.message.author.mention))
@ -177,10 +177,10 @@ class Picarto:
@checks.custom_perms(send_messages=True)
async def remove_picarto_url(self, ctx):
"""Removes your picarto URL"""
picarto = config.get_content('picarto') or {}
picarto = await config.get_content('picarto')
if picarto.get(ctx.message.author.id) is not None:
del picarto[ctx.message.author.id]
config.save_content('picarto', picarto)
await config.save_content('picarto', picarto)
await self.bot.say("I am no longer saving your picarto URL {}".format(ctx.message.author.mention))
else:
await self.bot.say(
@ -195,7 +195,7 @@ class Picarto:
member = ctx.message.author
# If this user's picarto URL is not saved, no use in adding this server to the list that doesn't exist
picarto = config.get_content('picarto') or {}
picarto = await config.get_content('picarto')
result = picarto.get(member.id)
if result is None:
await self.bot.say(
@ -204,7 +204,7 @@ class Picarto:
# Append this server's ID and save the new content
picarto[member.id]['servers'].append(ctx.message.server.id)
config.save_content('picarto', picarto)
await config.save_content('picarto', picarto)
await self.bot.say(
"I have just changed which channel will be notified when you go live, to `{}`".format(
ctx.message.channel.name))
@ -213,7 +213,7 @@ class Picarto:
@checks.custom_perms(send_messages=True)
async def notify_on(self, ctx):
"""Turns picarto notifications on"""
picarto = config.get_content('picarto') or {}
picarto = await config.get_content('picarto')
result = picarto.get(ctx.message.author.id)
# Check if this user has saved their picarto URL first
if result is None:
@ -226,7 +226,7 @@ class Picarto:
ctx.message.author.mention))
else:
picarto[ctx.message.author.id]['notifications_on'] = 1
config.save_content('picarto', picarto)
await config.save_content('picarto', picarto)
await self.bot.say("I will notify if you go live {}, you'll get a bajillion followers I promise c:".format(
ctx.message.author.mention))
@ -234,7 +234,7 @@ class Picarto:
@checks.custom_perms(send_messages=True)
async def notify_off(self, ctx):
"""Turns picarto notifications off"""
picarto = config.get_content('picarto') or {}
picarto = await config.get_content('picarto')
# Check if this user has saved their picarto URL first
if picarto.get(ctx.message.author.id) is None:
await self.bot.say(
@ -246,7 +246,7 @@ class Picarto:
ctx.message.author.mention))
else:
picarto[ctx.message.author.id]['notifications_on'] = 0
config.save_content('picarto', picarto)
await config.save_content('picarto', picarto)
await self.bot.say(
"I will not notify if you go live anymore {}, "
"are you going to stream some lewd stuff you don't want people to see?~".format(

View file

@ -73,6 +73,7 @@ class VoiceState:
'default_search': 'auto',
'quiet': True
}
self.volume = 50
def is_playing(self):
# If our VoiceClient or current VoiceEntry do not exist, then we are not playing a song
@ -123,6 +124,7 @@ class VoiceState:
after=self.toggle_next)
# Now we can start actually playing the song
self.current.player.start()
self.current.player.volume = self.volume / 100
# Wait till the Event has been set, before doing our task again
await self.play_next_song.wait()
@ -309,6 +311,7 @@ class Music:
if value > 200:
await self.bot.say("Sorry but the max volume is 200")
return
state.volume = value
if state.is_playing():
player = state.player
player.volume = value / 100

View file

@ -194,7 +194,8 @@ class Roles:
await self.bot.say("Sounds fancy! Here is a list of all the permissions available. Please respond with just "
"the numbers, seperated by commas, of the permissions you want this role to have.\n"
"```\n{}```".format(fmt))
# For this we're going to give a couple extra minutes before we timeout, as it might take a bit to figure out which permissions they want
# For this we're going to give a couple extra minutes before we timeout
# as it might take a bit to figure out which permissions they want
msg = await self.bot.wait_for_message(timeout=180.0, author=author, channel=channel, check=num_separated_check)
if msg is None:
await self.bot.say("You took too long. I'm impatient, don't make me wait")

View file

@ -14,7 +14,7 @@ class Stats:
@checks.custom_perms(send_messages=True)
async def mostboops(self, ctx):
"""Shows the person you have 'booped' the most, as well as how many times"""
boops = config.get_content('boops') or {}
boops = await config.get_content('boops')
if not boops.get(ctx.message.author.id):
await self.bot.say("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
return
@ -38,7 +38,7 @@ class Stats:
@checks.custom_perms(send_messages=True)
async def listboops(self, ctx):
"""Lists all the users you have booped and the amount of times"""
boops = config.get_content('boops') or {}
boops = await config.get_content('boops')
booped_members = boops.get(ctx.message.author.id)
if booped_members is None:
await self.bot.say("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
@ -58,7 +58,7 @@ class Stats:
@checks.custom_perms(send_messages=True)
async def leaderboard(self, ctx):
"""Prints a leaderboard of everyone in the server's battling record"""
battles = config.get_content('battle_records') or {}
battles = await config.get_content('battle_records')
# Same concept as mostboops
server_member_ids = [member.id for member in ctx.message.server.members]
@ -84,7 +84,7 @@ class Stats:
"""Prints the battling stats for you, or the user provided"""
member = member or ctx.message.author
all_members = config.get_content('battle_records') or {}
all_members = await config.get_content('battle_records')
if member.id not in all_members:
await self.bot.say("That user has not battled yet!")
return

View file

@ -19,18 +19,21 @@ class StatsUpdate:
def __unload(self):
config.loop.create_task(self.session.close())
async def update(self):
async def update(self, data):
server_count = 0
for d in data.values():
server_count += d.get('server_count')
carbon_payload = {
'key': config.carbon_key,
'servercount': len(self.bot.servers)
'servercount': server_count
}
async with self.session.post(carbonitex_url, data=carbon_payload) as resp:
log.info('Carbonitex statistics returned {} for {}'.format(resp.status, carbon_payload))
payload = json.dumps({
'server_count': len(self.bot.servers)
'server_count': server_count
})
headers = {
@ -43,13 +46,31 @@ class StatsUpdate:
log.info('bots.discord.pw statistics returned {} for {}'.format(resp.status, payload))
async def on_server_join(self, server):
await self.update()
data = await config.get_content('bot_data')
shard_data = data.get('shard_{}'.format(config.shard_id)) or {}
shard_data['server_count'] = len(self.bot.servers)
shard_data['member_count'] = len(set(self.bot.get_all_members()))
data['shard_{}'.format(config.shard_id)] = shard_data
await config.save_content('bot_data', data)
await self.update(data)
async def on_server_leave(self, server):
await self.update()
data = await config.get_content('bot_data')
shard_data = data.get('shard_{}'.format(config.shard_id)) or {}
shard_data['server_count'] = len(self.bot.servers)
shard_data['member_count'] = len(set(self.bot.get_all_members()))
data['shard_{}'.format(config.shard_id)] = shard_data
await config.save_content('bot_data', data)
await self.update(data)
async def on_ready(self):
await self.update()
data = await config.get_content('bot_data')
shard_data = data.get('shard_{}'.format(config.shard_id)) or {}
shard_data['server_count'] = len(self.bot.servers)
shard_data['member_count'] = len(set(self.bot.get_all_members()))
data['shard_{}'.format(config.shard_id)] = shard_data
await config.save_content('bot_data', data)
await self.update(data)
def setup(bot):

View file

@ -1,262 +0,0 @@
from cogs.utils import config
from discord.ext import commands
from .utils import checks
from lxml import etree
import aiohttp
import re
import discord
import pendulum
from fuzzywuzzy import process
base_url = "http://api.steampowered.com"
app_id_map = {"portal 2": 620,
"portal": 400,
"gmod": 4000,
"garry's mod": 4000,
"team fortress 2": 440,
"tf2": 440,
"csgo": 430,
"counter strike: global offensive": 430,
"css": 240,
"counter strike: source": 240,
"cs": 10,
"counter strike": 10,
"dota 2": 570,
"skyrim": 72850,
"oblivion": 22330,
"starbound": 211820,
"terraria": 105600,
"l4d": 500,
"left 4 dead": 500,
"l4d2": 550,
"left 4 dead 2": 550,
"xcom 2": 268500,
"xcom": 200510,
"ds2": 236430,
"dark souls 2": 236430,
"ds3": 374320,
"dark souls 3": 374320,
"unturned": 304930,
"dead island": 91310,
"dead island 2": 268150,
"don't starve": 219740,
"don't starve together": 322330,
"undertale": 391540,
"amnesia": 57300,
"borderlands 2": 49520,
"borderlands": 8980,
"soma": 282140,
"stanley parable": 221910,
"fallout": 38400,
"fallout 2": 38410,
"fallout 3": 22300,
"fallout 4": 377160,
"fallout new vegas": 22490
}
def get_app_id(game: str):
best_match = process.extractOne(game, app_id_map.keys())
if best_match[1] > 80:
return app_id_map.get(best_match[0])
else:
return None
class Steam:
def __init__(self, bot):
self.bot = bot
self.headers = {"User-Agent": "Bonfire/1.0.0"}
self.session = aiohttp.ClientSession()
self.key = config.steam_key
async def find_id(self, user: str):
# Get the profile link based on the user provided, and request the xml data for it
url = 'http://steamcommunity.com/id/{}/?xml=1'.format(user)
async with self.session.get(url, headers=self.headers) as response:
data = await response.text()
# Remove the xml version content, it breaks etree.fromstring
data = re.sub('<\?xml.*\?>', '', data)
tree = etree.fromstring(data)
# Try to find the steam ID, it will be the first item in the list, so try to convert to an int
# If it can't be converted to an int, we know that this profile doesn't exist
# The text will be "The specified profile could not be found." but we don't care about the specific text
# Through testing, it appears even if a profile is private, the steam ID is still public
try:
return int(tree[0].text)
except ValueError:
return None
@commands.group(pass_context=True, invoke_without_command=True)
@checks.custom_perms(send_messages=True)
async def steam(self, ctx, member: discord.Member, *option: str):
"""Handles linking/looking information for a certain user
Call this command by itself with a user, to view some information
about that user's steam account, if it is linked
Option can be achievements, games, info and will default to info"""
# First make sure the person requested isn't the bot
# Need to keep up the bot's sassy attitude, right?
if member == ctx.message.server.me:
await self.bot.say(
"I don't play video games anymore, I crushed everyone so badly I made everyone cry last time.")
return
# Get the user's steam id if it exists
try:
steam_id = config.get_content('steam_users').get(member.id).get('steam_id')
except AttributeError:
await self.bot.say("Sorry, but I don't have that user's steam account saved!")
return
# Set the url based on which option is provided
# If no option is provided, we'll hit the IndexError, assume info for that
try:
if option[0] == "info":
url = "{}/ISteamUser/GetPlayerSummaries/v0002/?key={}&steamids={}".format(base_url, self.key, steam_id)
elif option[0] == "achievements":
# Attempt to convert the given argument to an int
# If we can't convert, then an app_id wasn't given, try to find it based on our map of games
# If the option list doesn't have an index of 1, then no game was given
try:
game = " ".join(option[1:])
if game == "":
await self.bot.say("Please provide a game you would like to get the achievements for!")
return
app_id = int(option[1])
except ValueError:
app_id = get_app_id(game.lower())
if app_id is None:
await self.bot.say("Sorry, I couldn't find a close match for the game {}".format(game))
return
url = "{}/ISteamUserStats/GetPlayerAchievements/v0001/?key={}&steamid={}&appid={}".format(base_url,
self.key,
steam_id,
app_id)
elif option[0] == "games":
await self.bot.say("Currently disabled, only achievements and info are available as options")
return
except IndexError:
option = ['info']
url = "{}/ISteamUser/GetPlayerSummaries/v0002/?key={}&steamids={}".format(base_url, self.key, steam_id)
# Make the request and get the data in json response, all url's we're using will give a json response
try:
async with self.session.get(url, headers=self.headers) as response:
data = await response.json()
except:
await self.bot.say("Sorry, I failed looking up that user's information. I'll try better next time ;-;")
return
if option[0] == "info":
data = data['response']['players'][0]
# We need to take into account private profiles, so first add public stuff
# This maps the number that's returned, to what it means
status_map = {0: "Offline",
1: "Online",
2: "Busy",
3: "Away",
4: "Snooze",
5: "Looking to trade",
6: "Looking to play"}
# This is an epoch timestamp for when the user last was "Online"
# Why this is called 'lastlogoff' I'll never know, ask Steam
last_seen_date = pendulum.from_timestamp(data.get('lastlogoff'))
# We want the difference between now and then, to see when they were actually last Online
# Instead of printing the exact time they were online
last_seen_delta = pendulum.utcnow() - last_seen_date
fmt = {"URL": data.get('profileurl'),
"Display Name": data.get('personaname'),
"Online status": status_map[data.get('personastate')],
"Last seen": "{} ago".format(last_seen_delta.in_words())}
# Now check to see if things exist, and add them to the output if they do
created_date_timestamp = data.get('timecreated')
if created_date_timestamp:
created_date = pendulum.from_timestamp(created_date_timestamp)
fmt["Signup Date"] = created_date.to_date_string()
# Going to add if value in here just in case, to ensure no blank content is printed
fmt_string = "\n".join("{}: {}".format(key, value) for key, value in fmt.items() if value)
await self.bot.say("```\n{}```".format(fmt_string))
elif option[0] == 'achievements':
# First ensure that the profile is not private, and the user has played it before
if not data['playerstats']['success']:
if data['playerstats']['error'] == "Requested app has no stats":
await self.bot.say("{} has no achievements on the game {}".format(member.display_name, game))
elif data['playerstats']['error'] == "Profile is not public":
await self.bot.say(
"Sorry, {} has a private steam account! I cannot lookup their achievements!".format(
member.display_name))
return
# Get all achievements for this game, making sure they actually exist
try:
all_achievements = data['playerstats']['achievements']
except KeyError:
await self.bot.say("Sorry, the game {} has no achievements!".format(game))
return
# Now get all achievements that the user has achieved
successful_achievements = [data for data in all_achievements if data['achieved'] == 1]
await self.bot.say("{} has achieved {}/{} achievements on the game {}".format(member.display_name,
len(successful_achievements),
len(all_achievements), game))
@steam.command(name='add', aliases=['link', 'create'], pass_context=True)
@checks.custom_perms(send_messages=True)
async def add_steam(self, ctx, profile: str):
"""This command can be used to link a steam profile to your user"""
# Attempt to find the user/steamid based on the url provided
# If a url is not provided that matches steamcommunity.com, assume they provided just the user/id
try:
user = re.search("((?<=://)?steamcommunity.com/(id|profile)/)+(.*)", profile).group(3)
except AttributeError:
user = profile
# To look up userdata, we need the steam ID. Try to convert to an int, if we can, it's the steam ID
# If we can't convert to an int, use our method to find the steam ID for a certain user
try:
steam_id = int(user)
except ValueError:
steam_id = await self.find_id(user)
if steam_id is None:
await self.bot.say("Sorry, couldn't find that Steam user!")
return
# Save the author's steam ID, ensuring to only overwrite the steam id if they already exist
author = ctx.message.author
steam_users = config.get_content('steam_users') or {}
if steam_users.get(author.id):
steam_users[author.id]['steam_id'] = steam_id
else:
steam_users[author.id] = {'steam_id': steam_id}
config.save_content('steam_users', steam_users)
await self.bot.say(
"I have just saved your steam account, you should now be able to view stats for your account!")
@commands.command(pass_context=True)
@checks.custom_perms(send_messages=True)
async def csgo(self, ctx, member: discord.Member = None):
"""This command can be used to lookup csgo stats for a user"""
member = member or ctx.message.author
try:
steam_id = config.get_content('steam_users').get(member.id).get('steam_id')
except AttributeError:
await self.bot.say("Sorry, but I don't have that user's steam account saved!")
return
url = "{}/ISteamUserStats/GetUserStatsForGame/v0002/?key={}&appid=730&steamid={}".format(base_url, self.key,
steam_id)
async with self.session.get(url, headers=self.headers) as response:
data = await response.json()
stuff_to_print = ['total_kills', 'total_deaths', 'total_wins', 'total_mvps']
try:
stats = "\n".join(
"{}: {}".format(d['name'], d['value']) for d in data['playerstats']['stats'] if d['name'] in stuff_to_print)
except KeyError:
await self.bot.say("Sorry, {} has no CS:GO stats!".format())
await self.bot.say(
"CS:GO Stats for user {}: \n```\n{}```".format(member.display_name, stats.title().replace("_", " ")))
def setup(bot):
bot.add_cog(Steam(bot))

View file

@ -36,7 +36,7 @@ class Strawpoll:
"""This command can be used to show a strawpoll setup on this server"""
# Strawpolls cannot be 'deleted' so to handle whether a poll is running or not on a server
# Just save the poll in the config file, which can then be removed when it should not be "running" anymore
all_polls = config.get_content('strawpolls') or {}
all_polls = await config.get_content('strawpolls')
server_polls = all_polls.get(ctx.message.server.id) or {}
if not server_polls:
await self.bot.say("There are currently no strawpolls running on this server!")
@ -107,11 +107,11 @@ class Strawpoll:
return
# Save this strawpoll in the list of running strawpolls for a server
all_polls = config.get_content('strawpolls') or {}
all_polls = await config.get_content('strawpolls')
server_polls = all_polls.get(ctx.message.server.id) or {}
server_polls[data['id']] = {'author': ctx.message.author.id, 'date': str(pendulum.utcnow()), 'title': title}
all_polls[ctx.message.server.id] = server_polls
config.save_content('strawpolls', all_polls)
await config.save_content('strawpolls', all_polls)
await self.bot.say("Link for your new strawpoll: https://strawpoll.me/{}".format(data['id']))
@ -121,7 +121,7 @@ class Strawpoll:
"""This command can be used to delete one of the existing strawpolls
If you don't provide an ID it will print the list of polls available"""
all_polls = config.get_content('strawpolls') or {}
all_polls = await config.get_content('strawpolls')
server_polls = all_polls.get(ctx.message.server.id) or {}
# Check if a poll_id was provided, if it is then we can continue, if not print the list of current polls
@ -136,7 +136,7 @@ class Strawpoll:
# Delete the poll that was just found
del server_polls[poll_id]
all_polls[ctx.message.server.id] = server_polls
config.save_content('strawpolls', all_polls)
await config.save_content('strawpolls', all_polls)
await self.bot.say("I have just removed the poll with the ID {}".format(poll_id))
else:
fmt = "\n".join("{}: {}".format(data['title'], _poll_id) for _poll_id, data in server_polls.items())

View file

@ -14,7 +14,8 @@ class Tags:
@checks.custom_perms(send_messages=True)
async def tags(self, ctx):
"""Prints all the custom tags that this server currently has"""
tags = config.get_content('tags') or {}
tags = await config.get_content('tags')
tags = tags['tags']
# Simple generator that adds a tag to the list to print, if the tag is for this server
fmt = "\n".join("{}".format(tag['tag']) for tag in tags if tag['server_id'] == ctx.message.server.id)
await self.bot.say('```\n{}```'.format(fmt))
@ -24,7 +25,8 @@ class Tags:
async def tag(self, ctx, *, tag: str):
"""This can be used to call custom tags
The format to call a custom tag is !tag <tag>"""
tags = config.get_content('tags') or {}
tags = await config.get_content('tags')
tags = tags['tags']
# Same generator as the method for tags, other than the second check to get the tag that is provided
result = [t for t in tags if t['tag'] == tag and t['server_id'] == ctx.message.server.id]
if len(result) == 0:
@ -53,26 +55,27 @@ class Tags:
"Please provide the format for the tag in: {}tag add <tag> - <result>".format(ctx.prefix))
return
tags = config.get_content('tags') or {}
tags = await config.get_content('tags')
tags = tags['tags']
for t in tags:
# Attempt to find a tag with that name, so that we update it instead of making a duplicate
if t['tag'] == tag and t['server_id'] == ctx.message.server.id:
t['result'] = tag_result
await self.bot.say(
"I have just updated the tag `{0}`! You can call this tag by entering !tag {0}".format(tag))
return
# If we haven't found one, append a new one to the list
tags.append({'server_id': ctx.message.server.id, 'tag': tag, 'result': tag_result})
config.save_content('tags', tags)
await self.bot.say(
"I have just added the tag `{0}`! You can call this tag by entering !tag {0}".format(tag))
await config.save_content('tags', {'tags': tags})
@tag.command(name='delete', aliases=['remove', 'stop'], pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
async def del_tag(self, ctx, *, tag: str):
"""Use this to remove a tag that from use for this server
Format to delete a tag is !tag delete <tag>"""
tags = config.get_content('tags') or {}
tags = await config.get_content('tags')
tags = tags['tags']
# Get a list of the tags that match this server, and the name provided (should only ever be one if any)
result = [t for t in tags if t['tag'] == tag and t['server_id'] == ctx.message.server.id]
# If we haven't found one, can't delete it
@ -84,7 +87,7 @@ class Tags:
# Since there should never be more than one result due to our checks we've made, just remove the first result
tags.remove(result[0])
await self.bot.say('I have just removed the tag `{}`'.format(tag))
config.save_content('tags', tags)
await config.save_content('tags', {'tags': tags})
def setup(bot):

View file

@ -98,10 +98,10 @@ class Board:
return "```\n{}```".format(_board)
def update_records(winner, loser):
async def update_records(winner, loser):
# This is the exact same formula as the battling update.
# The only difference is I use the word "match" instead of "battle"
matches = config.get_content('tictactoe') or {}
matches = await config.get_content('tictactoe')
winner_stats = matches.get(winner.id) or {}
winner_rating = winner_stats.get('rating') or 1000
@ -137,7 +137,7 @@ def update_records(winner, loser):
matches[winner.id] = winner_stats
matches[loser.id] = loser_stats
return config.save_content('tictactoe', matches)
await config.save_content('tictactoe', matches)
class TicTacToe:
@ -189,6 +189,8 @@ class TicTacToe:
await self.bot.say("Please provide a valid location to play!")
return
x = 0
y = 0
# Simple assignments
if top:
x = 0
@ -228,7 +230,7 @@ class TicTacToe:
await self.bot.say("{} has won this game of TicTacToe, better luck next time {}".format(winner.display_name,
loser.display_name))
# Handle updating ratings based on the winner and loser
update_records(winner, loser)
await update_records(winner, loser)
# This game has ended, delete it so another one can be made
del self.boards[ctx.message.server.id]
else:
@ -237,11 +239,10 @@ class TicTacToe:
await self.bot.say("This game has ended in a tie!")
del self.boards[ctx.message.server.id]
# If no one has won, and the game has not ended in a tie, print the new updated board
# TODO: edit in place instead of printing?
else:
player_turn = board.challengers.get('x') if board.X_turn else board.challengers.get('o')
fmt = str(board) + "\nIt is now your turn "
await self.bot.say(str(board))
fmt = str(board) + "\n{} It is now your turn to play!".format(player_turn.display_name)
await self.bot.say(fmt)
@tictactoe.command(name='start', aliases=['challenge', 'create'], pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@ -257,15 +258,20 @@ class TicTacToe:
if player2 == ctx.message.server.me:
await self.bot.say("You want to play? Alright lets play.\n\nI win, so quick you didn't even notice it.")
return
# Create the board and return who has been decided to go first
x_player = self.create(ctx.message.server.id, player1, player2)
fmt = "A tictactoe game has just started between {} and {}".format(player1.display_name, player2.display_name)
# Print the board too just because
fmt += str(self.boards[ctx.message.server.id])
# We don't need to do anything weird with assigning x_player to something, it is already a member object, just use it
fmt += "I have decided at random, and {} is going to be x's this game. It is your turn first!\nUse the {}tictactoe command, and a position, to choose where you want to play".format(x_player.display_name, ctx.prefix)
# We don't need to do anything weird with assigning x_player to something
# it is already a member object, just use it
fmt += "I have decided at random, and {} is going to be x's this game. It is your turn first!" \
"Use the {}tictactoe command, and a position, to choose where you want to play"\
.format(x_player.display_name, ctx.prefix)
await self.bot.say(fmt)
@tictactoe.command(name='delete', aliases=['stop', 'remove', 'end'], pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
async def stop_game(self, ctx):
@ -275,7 +281,7 @@ class TicTacToe:
if self.boards.get(ctx.message.server.id) is None:
await self.bot.say("There are no tictactoe games running on this server!")
return
del self.boards[ctx.message.server.id]
await self.bot.say("I have just stopped the game of TicTacToe, a new should be able to be started now!")

View file

@ -14,7 +14,7 @@ async def channel_online(channel: str):
with aiohttp.ClientSession() as s:
async with s.get(url) as r:
response = await r.text()
# For some reason Twitch's API call is not reliable, sometimes it returns stream as None
# That is what we're checking specifically, sometimes it doesn't exist in the returned JSON at all
# Sometimes it returns something that cannot be decoded with JSON
@ -22,9 +22,7 @@ async def channel_online(channel: str):
try:
data = json.loads(response)
return data['stream'] is not None
except KeyError:
return False
except json.JSONDecodeError:
except (KeyError, json.JSONDecodeError):
return False
@ -40,11 +38,12 @@ class Twitch:
await self.bot.wait_until_ready()
# Loop through as long as the bot is connected
while not self.bot.is_closed:
twitch = config.get_content('twitch') or {}
twitch = await config.get_content('twitch')
# Online/offline is based on whether they are set to such, in the config file
# This means they were detected as online/offline before and we check for a change
online_users = {m_id: data for m_id, data in twitch.items() if data['notifications_on'] and data['live']}
offline_users = {m_id: data for m_id, data in twitch.items() if data['notifications_on'] and not data['live']}
offline_users = {m_id: data for m_id, data in twitch.items() if
data['notifications_on'] and not data['live']}
for m_id, r in offline_users.items():
# Get their url and their user based on that url
url = r['twitch_url']
@ -54,16 +53,16 @@ class Twitch:
for server_id in r['servers']:
# Get the channel to send the message to, based on the saved alert's channel
server = self.bot.get_server(server_id)
server_alerts = config.get_content('server_alerts') or {}
server_alerts = await config.get_content('server_alerts')
channel_id = server_alerts.get(server_id) or server_id
channel = self.bot.get_channel(channel_id)
# Get the member that has just gone live
member = discord.utils.get(server.members, id=m_id)
fmt = "{} has just gone live! View their stream at {}".format(member.display_name, url)
await self.bot.send_message(channel, fmt)
twitch[m_id]['live'] = 1
config.save_content('twitch', twitch)
await config.save_content('twitch', twitch)
for m_id, r in online_users.items():
# Get their url and their user based on that url
url = r['twitch_url']
@ -73,25 +72,26 @@ class Twitch:
for server_id in r['servers']:
# Get the channel to send the message to, based on the saved alert's channel
server = self.bot.get_server(server_id)
server_alerts = config.get_content('server_alerts') or {}
server_alerts = await config.get_content('server_alerts')
channel_id = server_alerts.get(server_id) or server_id
channel = self.bot.get_channel(channel_id)
# Get the member that has just gone live
member = discord.utils.get(server.members, id=m_id)
fmt = "{} has just gone offline! Catch them next time they stream at {}".format(member.display_name, url)
fmt = "{} has just gone offline! Catch them next time they stream at {}".format(
member.display_name, url)
await self.bot.send_message(channel, fmt)
twitch[m_id]['live'] = 0
config.save_content('twitch', twitch)
await config.save_content('twitch', twitch)
await asyncio.sleep(30)
@commands.group(no_pm=True, invoke_without_command=True, pass_context=True)
@checks.custom_perms(send_messages=True)
async def twitch(self, ctx, *, member: discord.Member=None):
async def twitch(self, ctx, *, member: discord.Member = None):
"""Use this command to check the twitch info of a user"""
if member is None:
member = ctx.message.author
twitch_channels = config.get_content('twitch') or {}
twitch_channels = await config.get_content('twitch')
result = twitch_channels.get(ctx.message.author.id)
if result is None:
await self.bot.say("{} has not saved their twitch URL yet!".format(member.name))
@ -128,7 +128,7 @@ class Twitch:
url = "https://www.twitch.tv/{}".format(url)
else:
url = "https://www.{}".format(url)
# Try to find the channel provided, we'll get a 404 response if it does not exist
with aiohttp.ClientSession() as s:
async with s.get(url) as r:
@ -137,9 +137,9 @@ class Twitch:
"What would be the point of adding a nonexistant twitch user? Silly")
return
twitch = config.get_content('twitch') or {}
twitch = await config.get_content('twitch')
result = twitch.get(ctx.message.author.id)
# Check to see if this user has already saved a twitch URL
# If they have, update the URL, otherwise create a new entry
# Assuming they're not live, and notifications should be on
@ -148,19 +148,19 @@ class Twitch:
else:
twitch[ctx.message.author.id] = {'twitch_url': url, 'servers': [ctx.message.server.id],
'notifications_on': 1, 'live': 0}
config.save_content('twitch', twitch)
await config.save_content('twitch', twitch)
await self.bot.say("I have just saved your twitch url {}".format(ctx.message.author.mention))
@twitch.command(name='remove', aliases=['delete'], pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
async def remove_twitch_url(self, ctx):
"""Removes your twitch URL"""
twitch = config.get_content('twitch') or {}
twitch = await config.get_content('twitch')
# Make sure the user exists before trying to delete them from the list
if twitch.get(ctx.message.author.id) is not None:
# Simply remove this user from the list, and save
del twitch[ctx.message.author.id]
config.save_content('twitch', twitch)
await config.save_content('twitch', twitch)
await self.bot.say("I am no longer saving your twitch URL {}".format(ctx.message.author.mention))
else:
await self.bot.say(
@ -172,7 +172,7 @@ class Twitch:
async def notify(self, ctx):
"""This can be used to modify notification settings for your twitch user
Call this command by itself to add 'this' server as one that will be notified when you on/offline"""
twitch = config.get_content('twitch') or {}
twitch = await config.get_content('twitch')
result = twitch.get(ctx.message.author.id)
# Check if this user is saved at all
if result is None:
@ -182,14 +182,14 @@ class Twitch:
# Otherwise we just need to append the server's ID to the servers list
else:
twitch[ctx.message.author.id]['servers'].append(ctx.message.server.id)
config.save_content('twitch', twitch)
await config.save_content('twitch', twitch)
@notify.command(name='on', aliases=['start,yes'], pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
async def notify_on(self, ctx):
"""Turns twitch notifications on"""
# Make sure this user is saved before we attempt to modify their information
twitch = config.get_content('twitch') or {}
twitch = await config.get_content('twitch')
result = twitch.get(ctx.message.author.id)
if result is None:
await self.bot.say(
@ -202,7 +202,7 @@ class Twitch:
# Otherwise, turn on notifications
else:
twitch[ctx.message.author.id]['notifications_on'] = 1
config.save_content('twitch', twitch)
await config.save_content('twitch', twitch)
await self.bot.say("I will notify if you go live {}, you'll get a bajillion followers I promise c:".format(
ctx.message.author.mention))
@ -211,7 +211,7 @@ class Twitch:
async def notify_off(self, ctx):
"""Turns twitch notifications off"""
# This method is exactly the same, except for turning off notifcations instead of on
twitch = config.get_content('twitch') or {}
twitch = await config.get_content('twitch')
if twitch.get(ctx.message.author.id) is None:
await self.bot.say(
"I do not have your twitch URL added {}. You can save your twitch url with !twitch add".format(

View file

@ -1,7 +1,11 @@
import asyncio
from discord.ext import commands
import discord
from . import config
loop = asyncio.get_event_loop()
def is_owner(ctx):
return ctx.message.author.id in config.owner_ids
@ -18,14 +22,13 @@ def custom_perms(**perms):
setattr(default_perms, perm, setting)
try:
required_perm_value = config.get_content('custom_permissions')[ctx.message.server.id][
ctx.command.qualified_name]
perm_values = config.cache.get('custom_permissions')
required_perm_value = perm_values[ctx.message.server.id][ctx.command.qualified_name]
required_perm = discord.Permissions(required_perm_value)
except KeyError:
required_perm = default_perms
except TypeError:
except (KeyError, TypeError):
required_perm = default_perms
return member_perms >= required_perm
predicate.perms = perms
return commands.check(predicate)

View file

@ -1,6 +1,7 @@
import yaml
import ruamel.yaml as yaml
import asyncio
import json
import rethinkdb as r
import pendulum
loop = asyncio.get_event_loop()
@ -12,30 +13,13 @@ except FileNotFoundError:
print("You have no config file setup! Please use config.yml.sample to setup a valid config file")
quit()
# Default bot's description
botDescription = global_config.get("description")
# Bot's default prefix for commands
commandPrefix = global_config.get("command_prefix", "!")
# The key for bots.discord.pw and carbonitex
discord_bots_key = global_config.get('discord_bots_key', "")
carbon_key = global_config.get('carbon_key', "")
# The invite link for the server made for the bot
dev_server = global_config.get("dev_server", "")
# A list of all the outputs for the battle command
battleWins = global_config.get("battleWins", [])
# The default status the bot will use
defaultStatus = global_config.get("default_status", "")
# The steam API key
steam_key = global_config.get("steam_key", "")
try:
botToken = global_config["bot_token"]
bot_token = global_config["bot_token"]
except KeyError:
print("You have no bot_token saved, this is a requirement for running a bot.")
print("Please use config.yml.sample to setup a valid config file")
quit()
try:
owner_ids = global_config["owner_id"]
except KeyError:
@ -44,25 +28,137 @@ except KeyError:
quit()
def save_content(key: str, content):
try:
with open("config.json", "r+") as jf:
data = json.load(jf)
data[key] = content
jf.seek(0)
json.dumps(data)
jf.truncate()
json.dump(data, jf, indent=4)
except FileNotFoundError:
with open("config.json", "w+") as jf:
json.dump({key: content}, jf, indent=4)
# This is a simple class for the cache concept, all it holds is it's own key and the values
# With a method that gets content based on it's key
class Cache:
def __init__(self, key):
self.key = key
self.values = {}
self.refreshed = pendulum.utcnow()
loop.create_task(self.update())
async def update(self):
self.values = await _get_content(self.key)
self.refreshed = pendulum.utcnow()
def get_content(key: str):
# Default bot's description
bot_description = global_config.get("description")
# Bot's default prefix for commands
default_prefix = global_config.get("command_prefix", "!")
# The key for bots.discord.pw and carbonitex
discord_bots_key = global_config.get('discord_bots_key', "")
carbon_key = global_config.get('carbon_key', "")
# The invite link for the server made for the bot
dev_server = global_config.get("dev_server", "")
# The variables needed for sharding
shard_count = global_config.get('shard_count', '')
shard_id = global_config.get('shard_id', '')
# A list of all the outputs for the battle command
battle_wins = global_config.get("battleWins", [])
# The default status the bot will use
default_status = global_config.get("default_status", "")
# The steam API key
steam_key = global_config.get("steam_key", "")
# The rethinkdb hostname
db_host = global_config.get('db_host', 'localhost')
# The rethinkdb database name
db_name = global_config.get('db_name', 'Discord_Bot')
# The rethinkdb certification
db_cert = global_config.get('db_cert', '')
# The rethinkdb port
db_port = global_config.get('db_port', 28015)
# The user and password assigned
db_user = global_config.get('db_user', 'admin')
db_pass = global_config.get('db_pass', '')
# We've set all the options we need to be able to connect
# so create a dictionary that we can use to unload to connect
# db_opts = {'host': db_host, 'db': db_name, 'port': db_port, 'ssl':
# {'ca_certs': db_cert}, 'user': db_user, 'password': db_pass}
db_opts = {'host': db_host, 'db': db_name, 'port': db_port, 'user': db_user, 'password': db_pass}
possible_keys = ['prefixes', 'battling', 'battle_records', 'boops', 'server_alerts', 'user_notifications',
'nsfw_channels', 'custom_permissions', 'rules', 'overwatch', 'picarto', 'twitch', 'strawpolls', 'tags',
'tictactoe', 'bot_data']
# This will be a dictionary that holds the cache object, based on the key that is saved
cache = {}
sharded_data = {}
# Populate cache with each object
for k in possible_keys:
cache[k] = Cache(k)
def command_prefix(bot, message):
# We do not want to make a query for every message that is sent
# So assume it's in cache, or it doesn't exist
# If the prefix does exist in the database and isn't in our cache; too bad, something has messed up
# But it is not worth a query for every single message the bot detects, to fix
try:
with open("config.json", "r+") as jf:
return json.load(jf)[key]
prefix = cache['prefixes'].values.get(message.server.id)
return prefix or default_prefix
except KeyError:
return None
except FileNotFoundError:
return None
return default_prefix
async def save_content(table: str, content):
# We need to make sure we're using asyncio
r.set_loop_type("asyncio")
# Just connect to the database
conn = await r.connect(**db_opts)
# We need to make at least one query to ensure the key exists, so attempt to create it as our query
try:
await r.table_create(table).run(conn)
except r.ReqlOpFailedError:
pass
# So the table already existed, or it has now been created, we can update the data now
# Since we're handling everything that is rewritten in the code itself, we just need to delete then insert
await r.table(table).delete().run(conn)
await r.table(table).insert(content).run(conn)
await conn.close()
# Now that we've saved the new content, we should update our cache
cached = cache.get(table)
# While this should theoretically never happen, we just want to make sure
if cached is None or isinstance(cached, dict) or len(cached.values) == 0:
cache[table] = Cache(table)
else:
await cached.update()
async def get_content(key: str):
cached = cache.get(key)
# We want to check here if the key exists in cache, and it was not created more than an hour ago
# We also want to make sure that if what we're getting in cache has content
# if not, lets make sure something didn't go awry, by getting from the database instead
if cached is None or isinstance(cached, dict) or len(cached.values) == 0 or (
pendulum.utcnow() - cached.refreshed).hours >= 1:
value = await _get_content(key)
# If we found this object not cached, cache it
cache[key] = value
else:
value = cached.values
return value
# This is our internal method to get content from the database
async def _get_content(key: str):
# We need to make sure we're using asyncio
r.set_loop_type("asyncio")
# Just connect to the database
conn = await r.connect(**db_opts)
# We should only ever get one result, so use it if it exists, otherwise return none
try:
cursor = await r.table(key).run(conn)
await conn.close()
items = list(cursor.items)[0]
except (IndexError, r.ReqlOpFailedError):
await conn.close()
return {}
# Rethink db stores an internal id per table, delete this and return the rest
del items['id']
return items