Merge branch 'master' of https://github.com/Phxntxm/Bonfire
This commit is contained in:
commit
6de4800e54
20 changed files with 412 additions and 522 deletions
38
bot.py
38
bot.py
|
@ -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)
|
||||
|
|
25
cogs/core.py
25
cogs/core.py
|
@ -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
|
||||
|
|
|
@ -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,30 +160,7 @@ 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)))
|
||||
|
|
|
@ -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:
|
||||
|
@ -84,7 +84,8 @@ class Interaction:
|
|||
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))
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
67
cogs/mod.py
67
cogs/mod.py
|
@ -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,7 +98,7 @@ 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
|
||||
|
@ -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. "
|
||||
|
|
|
@ -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
|
||||
|
@ -40,7 +39,7 @@ class Overwatch:
|
|||
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:
|
||||
|
@ -58,7 +57,8 @@ class Overwatch:
|
|||
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:
|
||||
|
@ -82,7 +82,8 @@ class Overwatch:
|
|||
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
|
||||
|
@ -110,16 +111,16 @@ class Overwatch:
|
|||
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))
|
||||
|
|
|
@ -45,7 +45,6 @@ class Owner:
|
|||
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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
262
cogs/steam.py
262
cogs/steam.py
|
@ -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))
|
|
@ -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())
|
||||
|
|
17
cogs/tags.py
17
cogs/tags.py
|
@ -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):
|
||||
|
|
|
@ -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,13 +258,18 @@ 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)
|
||||
|
|
|
@ -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,7 +53,7 @@ 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
|
||||
|
@ -63,7 +62,7 @@ class Twitch:
|
|||
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,15 +72,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 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)
|
||||
|
@ -91,7 +91,7 @@ class Twitch:
|
|||
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))
|
||||
|
@ -137,7 +137,7 @@ 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
|
||||
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,25 +13,8 @@ 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")
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue