diff --git a/README.md b/README.md
index 7efbfc6..7862fc7 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,8 @@ chatchart - Generates a pie chart to display chat activity over the last 5000 me
dungeon - New users with new accounts will be shuffled off to a locked channel on-join to help mitigate raiders. Please see the dungeon_readme.md file on this repo for more information.
+joinleave - Alerts a set channel when an account under a certain age of days joins a server and leaves within the set time... any server the bot can see. This is a global cog with owner only commands. I wrote this for myself so I won't be adding user requests for the most part, and will be using it to combat the recent user scrapers found joining and leaving servers quickly.
+
inspirobot - Fetch "inspirational" messages from inspirobot.me with [p]inspireme.
nolinks - A very blunt hammer to remove anything that looks like a link. Roles can be whitelisted and it can watch multiple channels.
diff --git a/cah/__init__.py b/cah/__init__.py
new file mode 100644
index 0000000..e0c6273
--- /dev/null
+++ b/cah/__init__.py
@@ -0,0 +1,8 @@
+from redbot.core import commands, data_manager
+from .cah import CardsAgainstHumanity
+
+
+def setup(bot: commands.Bot):
+ n = CardsAgainstHumanity(bot)
+ data_manager.load_bundled_data(n, __file__)
+ bot.add_cog(n)
diff --git a/cah/cah.py b/cah/cah.py
new file mode 100644
index 0000000..4d98e93
--- /dev/null
+++ b/cah/cah.py
@@ -0,0 +1,1836 @@
+import asyncio
+import discord
+import html
+import json
+import random
+import time
+from random import shuffle
+from redbot.core import commands
+from redbot.core.data_manager import bundled_data_path
+
+
+BaseCog = getattr(commands, "Cog", object)
+
+
+class CardsAgainstHumanity(BaseCog):
+ def __init__(self, bot):
+ self.bot = bot
+ self.games = []
+ self.maxBots = 5 # Max number of bots that can be added to a game - don't count toward max players
+ self.maxPlayers = 10 # Max players for ranjom joins
+ self.maxDeadTime = 3600 # Allow an hour of dead time before killing a game
+ self.checkTime = 300 # 5 minutes between dead time checks
+ self.winAfter = 10 # 10 wins for the game
+ self.botWaitMin = 5 # Minimum number of seconds before the bot makes a decision (default 5)
+ self.botWaitMax = 30 # Max number of seconds before a bot makes a decision (default 30)
+ self.userTimeout = 500 # 5 minutes to timeout
+ self.utCheck = 30 # Check timeout every 30 seconds
+ self.utWarn = 60 # Warn the user if they have 60 seconds or less before being kicked
+ self.charset = "1234567890"
+ self.botName = "Rando Cardrissian"
+ self.minMembers = 3
+
+ self.bot.loop.create_task(self.checkDead())
+ self.bot.loop.create_task(self.checkUserTimeout())
+
+ def cleanJson(self, json):
+ json = html.unescape(json)
+ # Clean out html formatting
+ json = json.replace("_", "[blank]")
+ json = json.replace("
", "\n")
+ json = json.replace("
", "\n")
+ json = json.replace("", "*")
+ json = json.replace("", "*")
+ return json
+
+ def displayname(self, member: discord.Member):
+ # A helper function to return the member's display name
+ nick = name = None
+ try:
+ nick = member.nick
+ except AttributeError:
+ pass
+ try:
+ name = member.name
+ except AttributeError:
+ pass
+ if nick:
+ return nick
+ if name:
+ return name
+ return None
+
+ def memberforname(self, name, server):
+ # Check nick first - then name
+ for member in server.members:
+ if member.nick:
+ if member.nick.lower() == name.lower():
+ return member
+ for member in server.members:
+ if member.name.lower() == name.lower():
+ return member
+ # No member yet - try ID
+ memID = "".join(list(filter(str.isdigit, name)))
+ newMem = memberForID(memID, server)
+ if newMem:
+ return newMem
+ return None
+
+ def getreadabletimebetween(self, first, last):
+ # A helper function to make a readable string between two times
+ timeBetween = int(last - first)
+ weeks = int(timeBetween / 604800)
+ days = int((timeBetween - (weeks * 604800)) / 86400)
+ hours = int((timeBetween - (days * 86400 + weeks * 604800)) / 3600)
+ minutes = int((timeBetween - (hours * 3600 + days * 86400 + weeks * 604800)) / 60)
+ seconds = int(timeBetween - (minutes * 60 + hours * 3600 + days * 86400 + weeks * 604800))
+ msg = ""
+
+ if weeks > 0:
+ if weeks == 1:
+ msg = "{}{} week, ".format(msg, str(weeks))
+ else:
+ msg = "{}{} weeks, ".format(msg, str(weeks))
+ if days > 0:
+ if days == 1:
+ msg = "{}{} day, ".format(msg, str(days))
+ else:
+ msg = "{}{} days, ".format(msg, str(days))
+ if hours > 0:
+ if hours == 1:
+ msg = "{}{} hour, ".format(msg, str(hours))
+ else:
+ msg = "{}{} hours, ".format(msg, str(hours))
+ if minutes > 0:
+ if minutes == 1:
+ msg = "{}{} minute, ".format(msg, str(minutes))
+ else:
+ msg = "{}{} minutes, ".format(msg, str(minutes))
+ if seconds > 0:
+ if seconds == 1:
+ msg = "{}{} second, ".format(msg, str(seconds))
+ else:
+ msg = "{}{} seconds, ".format(msg, str(seconds))
+
+ if not msg:
+ return "0 seconds"
+ else:
+ return msg[:-2]
+
+ async def checkUserTimeout(self):
+ while True:
+ # Wait first - then check
+ await asyncio.sleep(self.utCheck)
+ for game in self.games:
+ if not game["Timeout"]:
+ continue
+ if len(game["Members"]) >= self.minMembers:
+ # Game is started
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ if game["Judging"]:
+ if not member == game["Members"][game["Judge"]]:
+ # Not the judge - don't hold against the user
+ member["Time"] = int(time.time())
+ continue
+ else:
+ # Not judging
+ if member == game["Members"][game["Judge"]]:
+ # The judge - don't hold that against them
+ member["Time"] = int(time.time())
+ continue
+ currentTime = int(time.time())
+ userTime = member["Time"]
+ downTime = currentTime - userTime
+ # Check if downTime results in a kick
+ if downTime >= self.userTimeout:
+ # You gettin kicked, son.
+ await self.removeMember(member["User"])
+ self.checkGame(game)
+ continue
+ # Check if downTime is in warning time
+ if downTime >= (self.userTimeout - self.utWarn):
+ # Check if we're at warning phase
+ if self.userTimeout - downTime >= (self.utWarn - self.utCheck):
+ kickTime = self.userTimeout - downTime
+ if kickTime % self.utCheck:
+ # Kick time isn't exact time - round out to the next loop
+ kickTime = kickTime - (kickTime % self.utCheck) + self.utCheck
+ # Warning time!
+ timeString = self.getreadabletimebetween(0, kickTime)
+ msg = "**WARNING** - You will be kicked from the game if you do not make a move in *{}!*".format(
+ timeString
+ )
+ await member["User"].send(msg)
+ else:
+ for member in game["Members"]:
+ # Reset timer
+ member["Time"] = int(time.time())
+
+ async def checkDead(self):
+ while True:
+ # Wait first - then check
+ await asyncio.sleep(self.checkTime)
+ for game in self.games:
+ gameTime = game["Time"]
+ currentTime = int(time.time())
+ timeRemain = currentTime - gameTime
+ if timeRemain > self.maxDeadTime:
+ # Game is dead - quit it and alert members
+ for member in game["Members"]:
+ if member["IsBot"]:
+ # Clear pending tasks and set to None
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ task.cancel()
+ member["Task"] = None
+ continue
+ msg = "Game id: *{}* has been closed due to inactivity.".format(game["ID"])
+ await member["User"].send(msg)
+
+ # Set running to false
+ game["Running"] = False
+ self.games.remove(game)
+
+ async def checkPM(self, message):
+ # Checks if we're talking in PM, and if not - outputs an error
+ if isinstance(message.channel, discord.abc.PrivateChannel):
+ # PM
+ return True
+ else:
+ # Not in PM
+ await message.channel.send("Cards Against Humanity commands must be run in PM.")
+ return False
+
+ def randomID(self, length=8):
+ # Create a random id that doesn't already exist
+ while True:
+ # Repeat until found
+ newID = "".join(random.choice(self.charset) for i in range(length))
+ exists = False
+ for game in self.games:
+ if game["ID"] == newID:
+ exists = True
+ break
+ if not exists:
+ break
+ return newID
+
+ def randomBotID(self, game, length=4):
+ # Returns a random id for a bot that doesn't already exist
+ while True:
+ # Repeat until found
+ newID = "".join(random.choice(self.charset) for i in range(length))
+ exists = False
+ for member in game["Members"]:
+ if member["ID"] == newID:
+ exists = True
+ break
+ if not exists:
+ break
+ return newID
+
+ async def userGame(self, user):
+ # Returns the game the user is currently in
+ if not len(str(user)) == 4:
+ if not type(user) is int:
+ # Assume it's a discord.Member/User
+ user = user.id
+
+ for game in self.games:
+ for member in game["Members"]:
+ if member["ID"] == user:
+ # Found our user
+ return game
+ return None
+
+ def gameForID(self, id):
+ # Returns the game with the passed id
+ for game in self.games:
+ if game["ID"] == id:
+ return game
+ return None
+
+ async def removeMember(self, user, game=None):
+ if not len(str(user)) == 4:
+ if not type(user) is int:
+ # Assume it's a discord.Member/User
+ user = user.id
+ outcome = False
+ removed = None
+ if not game:
+ game = await self.userGame(user)
+ if game:
+ for member in game["Members"]:
+ if member["ID"] == user:
+ removed = member
+ outcome = True
+ judgeChanged = False
+ # Reset judging flag to retrigger actions
+ game["Judging"] = False
+ # Get current Judge - only if game has started
+ if len(game["Members"]) >= self.minMembers:
+ judge = game["Members"][game["Judge"]]
+ game["Members"].remove(member)
+ # Check if we're removing the current judge
+ if judge == member:
+ # Judge will change
+ judgeChanged = True
+ # Find out if our member was the last in line
+ if game["Judge"] >= len(game["Members"]):
+ game["Judge"] = 0
+ # Reset judge var
+ judge = game["Members"][game["Judge"]]
+ else:
+ # Judge didn't change - so let's reset judge index
+ index = game["Members"].index(judge)
+ game["Judge"] = index
+ else:
+ judge = None
+ # Just remove the member
+ game["Members"].remove(member)
+
+ if member["Creator"]:
+ # We're losing the game creator - pick a new one
+ for newCreator in game["Members"]:
+ if not newCreator["IsBot"]:
+ newCreator["Creator"] = True
+ await newCreator["User"].send(
+ "The creator of this game left. **YOU** are now the creator."
+ )
+ break
+
+ # Remove submissions
+ for sub in game["Submitted"]:
+ # Remove deleted member and new judge's submissions
+ if sub["By"] == member or sub["By"] == judge:
+ # Found it!
+ game["Submitted"].remove(sub)
+ break
+ if member["IsBot"]:
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ task.cancel()
+ member["Task"] = None
+ else:
+ msg = "**You were removed from game id:** ***{}.***".format(game["ID"])
+ await member["User"].send(msg)
+ # Removed, no need to finish the loop
+ break
+ if not outcome:
+ return outcome
+ # We removed someone - let's tell the world
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ if removed["IsBot"]:
+ msg = "***{} ({})*** **left the game - reorganizing...**".format(
+ self.botName, removed["ID"]
+ )
+ else:
+ msg = "***{}*** **left the game - reorganizing...**".format(
+ self.displayname(removed["User"])
+ )
+ # Check if the judge changed
+ if judgeChanged:
+ # Judge changed
+ newJudge = game["Members"][game["Judge"]]
+ if newJudge["IsBot"]:
+ msg += "\n\n***{} ({})*** **is now judging!**".format(
+ self.botName, newJudge["ID"]
+ )
+ # Schedule judging task
+ else:
+ if newJudge == member:
+ msg += "\n\n***YOU*** **are now judging!**"
+ else:
+ msg += "\n\n***{}*** **is now judging!**".format(
+ self.displayname(newJudge["User"])
+ )
+ await member["User"].send(msg)
+ return game
+
+ def checkGame(self, game):
+ for member in game["Members"]:
+ if not member["IsBot"]:
+ return True
+ # If we got here - only bots, or empty game
+ # Kill all bots' loops
+ for member in game["Members"]:
+ if member["IsBot"]:
+ # Clear pending tasks and set to None
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ task.cancel()
+ member["Task"] = None
+ # Set running to false
+ game["Running"] = False
+ self.games.remove(game)
+ return False
+
+ async def typing(self, game, typeTime=5):
+ # Allows us to show the bot typing
+ waitTime = random.randint(self.botWaitMin, self.botWaitMax)
+ preType = waitTime - typeTime
+ if preType > 0:
+ await asyncio.sleep(preType)
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ await asyncio.sleep(0.1)
+ await asyncio.sleep(typeTime)
+ else:
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ await asyncio.sleep(0.1)
+ await asyncio.sleep(waitTime)
+
+ async def botPick(self, ctx, bot, game):
+ # Has the bot pick their card
+ blackNum = game["BlackCard"]["Pick"]
+ if blackNum == 1:
+ cardSpeak = "card"
+ else:
+ cardSpeak = "cards"
+ i = 0
+ cards = []
+ while i < blackNum:
+ randCard = random.randint(0, len(bot["Hand"]) - 1)
+ cards.append(bot["Hand"].pop(randCard)["Text"])
+ i += 1
+
+ await self.typing(game)
+
+ # Make sure we haven't laid any cards
+ if bot["Laid"] == False and game["Judging"] == False:
+ newSubmission = {"By": bot, "Cards": cards}
+ game["Submitted"].append(newSubmission)
+ # Shuffle cards
+ shuffle(game["Submitted"])
+ bot["Laid"] = True
+ game["Time"] = currentTime = int(time.time())
+ await self.checkSubmissions(ctx, game, bot)
+
+ async def botPickWin(self, ctx, game):
+ totalUsers = len(game["Members"]) - 1
+ submitted = len(game["Submitted"])
+ if submitted >= totalUsers:
+ # Judge is a bot - and all cards are in!
+ await self.typing(game)
+ # Pick a winner
+ winner = random.randint(0, totalUsers - 1)
+ await self.winningCard(ctx, game, winner)
+
+ async def checkSubmissions(self, ctx, game, user=None):
+ totalUsers = len(game["Members"]) - 1
+ submitted = len(game["Submitted"])
+ for member in game["Members"]:
+ msg = ""
+ # Is the game running?
+ if len(game["Members"]) < self.minMembers:
+ if member["IsBot"]:
+ # Clear pending tasks and set to None
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ # Task isn't finished - we're on a new hand, cancel it
+ task.cancel()
+ member["Task"] = None
+ continue
+ # not enough members - send the embed
+ prefix = await self.bot.db.prefix()
+ stat_embed = discord.Embed(color=discord.Color.red())
+ stat_embed.set_author(
+ name="Not enough players to continue! ({}/{})".format(
+ len(game["Members"]), self.minMembers
+ )
+ )
+ stat_embed.set_footer(
+ text="Have other users join with: {}joincah {}".format(prefix[0], game["ID"])
+ )
+ await member["User"].send(embed=stat_embed)
+ continue
+ if member["IsBot"] == True:
+ continue
+ # Check if we have a user
+ if user:
+ blackNum = game["BlackCard"]["Pick"]
+ if blackNum == 1:
+ card = "card"
+ else:
+ card = "cards"
+ if user["IsBot"]:
+ msg = "*{} ({})* submitted their {}! ".format(self.botName, user["ID"], card)
+ else:
+ if not member == user:
+ # Don't say this to the submitting user
+ msg = "*{}* submitted their {}! ".format(
+ self.displayname(user["User"]), card
+ )
+ if submitted < totalUsers:
+ msg += "{}/{} cards submitted...".format(submitted, totalUsers)
+ if len(msg):
+ # We have something to say
+ await member["User"].send(msg)
+
+ async def checkCards(self, ctx, game):
+ while True:
+ if not game["Running"]:
+ break
+ # wait for 1 second
+ await asyncio.sleep(1)
+ # Check for all cards
+ if len(game["Members"]) < self.minMembers:
+ # Not enough members
+ continue
+ # Enough members - let's check if we're judging
+ if game["Judging"]:
+ continue
+ # Enough members, and not judging - let's check cards
+ totalUsers = len(game["Members"]) - 1
+ submitted = len(game["Submitted"])
+ if submitted >= totalUsers:
+ game["Judging"] = True
+ # We have enough cards
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ msg = "All cards have been submitted!"
+ # if
+ await member["User"].send(msg)
+ await self.showOptions(ctx, member["User"])
+
+ # Check if a bot is the judge
+ judge = game["Members"][game["Judge"]]
+ if not judge["IsBot"]:
+ continue
+ # task = self.bot.loop.create_task(self.botPickWin(ctx, game))
+ task = asyncio.ensure_future(self.botPickWin(ctx, game))
+ judge["Task"] = task
+
+ async def winningCard(self, ctx, game, card):
+ # Let's pick our card and alert everyone
+ winner = game["Submitted"][card]
+ if winner["By"]["IsBot"]:
+ winnerName = "{} ({})".format(self.botName, winner["By"]["ID"])
+ winner["By"]["Points"] += 1
+ winner["By"]["Won"].append(game["BlackCard"]["Text"])
+ else:
+ winnerName = self.displayname(winner["By"]["User"])
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ stat_embed = discord.Embed(color=discord.Color.gold())
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(game["ID"]))
+ index = game["Members"].index(member)
+ if index == game["Judge"]:
+ stat_embed.set_author(name="You picked {}'s card!".format(winnerName))
+ elif member == winner["By"]:
+ stat_embed.set_author(name="YOU WON!!")
+ member["Points"] += 1
+ member["Won"].append(game["BlackCard"]["Text"])
+ else:
+ stat_embed.set_author(name="{} won!".format(winnerName))
+ if len(winner["Cards"]) == 1:
+ msg = "The **Winning** card was:\n\n{}".format(
+ "{}".format(" - ".join(winner["Cards"]))
+ )
+ else:
+ msg = "The **Winning** cards were:\n\n{}".format(
+ "{}".format(" - ".join(winner["Cards"]))
+ )
+ await member["User"].send(embed=stat_embed)
+ await member["User"].send(msg)
+ await asyncio.sleep(0.1)
+
+ # await self.nextPlay(ctx, game)
+
+ # Start the game loop
+ event = game["NextHand"]
+ self.bot.loop.call_soon_threadsafe(event.set)
+ game["Time"] = currentTime = int(time.time())
+
+ async def gameCheckLoop(self, ctx, game):
+ task = game["NextHand"]
+ while True:
+ if not game["Running"]:
+ break
+ # Clear the pending task
+ task.clear()
+ # Queue up the next hand
+ await self.nextPlay(ctx, game)
+ # Wait until our next clear
+ await task.wait()
+
+ async def messagePlayers(self, ctx, message, game, judge=False):
+ # Messages all the users on in a game
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ # Not bots
+ if member is game["Members"][game["Judge"]]:
+ # Is the judge
+ if judge:
+ await member["User"].send(message)
+ else:
+ # Not the judge
+ await member["User"].send(message)
+
+ ################################################
+
+ async def showPlay(self, ctx, user):
+ # Creates an embed and displays the current game stats
+ stat_embed = discord.Embed(color=discord.Color.blue())
+ game = await self.userGame(user)
+ if not game:
+ return
+ # Get the judge's name
+ if game["Members"][game["Judge"]]["User"] == user:
+ judge = "**YOU** are"
+ else:
+ if game["Members"][game["Judge"]]["IsBot"]:
+ # Bot
+ judge = "*{} ({})* is".format(self.botName, game["Members"][game["Judge"]]["ID"])
+ else:
+ judge = "*{}* is".format(self.displayname(game["Members"][game["Judge"]]["User"]))
+
+ # Get the Black Card
+ try:
+ blackCard = game["BlackCard"]["Text"]
+ blackNum = game["BlackCard"]["Pick"]
+ except Exception:
+ blackCard = "None."
+ blackNum = 0
+
+ msg = "{} the judge.\n\n".format(judge)
+ msg += "__Black Card:__\n\n**{}**\n\n".format(blackCard)
+
+ totalUsers = len(game["Members"]) - 1
+ submitted = len(game["Submitted"])
+ if len(game["Members"]) >= self.minMembers:
+ if submitted < totalUsers:
+ msg += "{}/{} cards submitted...".format(submitted, totalUsers)
+ else:
+ msg += "All cards have been submitted!"
+ await self.showOptions(ctx, user)
+ return
+ prefix = await self.bot.db.prefix()
+ if not judge == "**YOU** are":
+ # Judge doesn't need to lay a card
+ if blackNum == 1:
+ # Singular
+ msg += "\n\nLay a card with `{}lay [card number]`".format(prefix[0])
+ elif blackNum > 1:
+ # Plural
+ msg += "\n\nLay **{} cards** with `{}lay [card numbers separated by commas (1,2,3)]`".format(
+ blackNum, prefix[0]
+ )
+
+ stat_embed.set_author(name="Current Play")
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(game["ID"]))
+ await user.send(embed=stat_embed)
+ await user.send(msg)
+
+ async def showHand(self, ctx, user):
+ # Shows the user's hand in an embed
+ stat_embed = discord.Embed(color=discord.Color.green())
+ game = await self.userGame(user)
+ if not game:
+ return
+ i = 0
+ msg = ""
+ points = "? points"
+ for member in game["Members"]:
+ if member["ID"] == user.id:
+ # Got our user
+ if member["Points"] == 1:
+ points = "1 point"
+ else:
+ points = "{} points".format(member["Points"])
+ for card in member["Hand"]:
+ i += 1
+ msg += "{}. {}\n".format(i, card["Text"])
+
+ try:
+ blackCard = "**{}**".format(game["BlackCard"]["Text"])
+ except Exception:
+ blackCard = "**None.**"
+ stat_embed.set_author(name="Your Hand - {}".format(points))
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(game["ID"]))
+ await user.send(embed=stat_embed)
+ await user.send(msg)
+
+ async def showOptions(self, ctx, user):
+ # Shows the judgement options
+ stat_embed = discord.Embed(color=discord.Color.orange())
+ game = await self.userGame(user)
+ if not game:
+ return
+ # Add title
+ stat_embed.set_author(name="JUDGEMENT TIME!!")
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(game["ID"]))
+ await user.send(embed=stat_embed)
+
+ if game["Members"][game["Judge"]]["User"] == user:
+ judge = "**YOU** are"
+ else:
+ if game["Members"][game["Judge"]]["IsBot"]:
+ # Bot
+ judge = "*{} ({})* is".format(self.botName, game["Members"][game["Judge"]]["ID"])
+ else:
+ judge = "*{}* is".format(self.displayname(game["Members"][game["Judge"]]["User"]))
+ blackCard = game["BlackCard"]["Text"]
+
+ msg = "{} judging.\n\n".format(judge)
+ msg += "__Black Card:__\n\n**{}**\n\n".format(blackCard)
+ msg += "__Submitted White Cards:__\n\n"
+
+ i = 0
+ for sub in game["Submitted"]:
+ i += 1
+ msg += "{}. {}\n".format(i, " - ".join(sub["Cards"]))
+ if judge == "**YOU** are":
+ prefix = await self.bot.db.prefix()
+ msg += "\nPick a winner with `{}pick [submission number]`.".format(prefix[0])
+ await user.send(msg)
+
+ async def drawCard(self, game):
+ with open(str(bundled_data_path(self)) + "/deck.json", 'r') as deck_file:
+ deck = json.load(deck_file)
+ # Draws a random unused card and shuffles the deck if needed
+ totalDiscard = len(game["Discard"])
+ for member in game["Members"]:
+ totalDiscard += len(member["Hand"])
+ if totalDiscard >= len(deck["whiteCards"]):
+ # Tell everyone the cards were shuffled
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ user = member["User"]
+ await user.send("Shuffling white cards...")
+ # Shuffle the cards
+ self.shuffle(game)
+ while True:
+ # Random grab a unique card
+ index = random.randint(0, len(deck["whiteCards"]) - 1)
+ if not index in game["Discard"]:
+ game["Discard"].append(index)
+ text = deck["whiteCards"][index]
+ text = self.cleanJson(text)
+ card = {"Index": index, "Text": text}
+ return card
+
+ def shuffle(self, game):
+ # Adds discards back into the deck
+ game["Discard"] = []
+ for member in game["Members"]:
+ for card in member["Hand"]:
+ game["Discard"].append(card["Index"])
+
+ async def drawCards(self, user, cards=10):
+ if not len(str(user)) == 4:
+ if not type(user) is int:
+ # Assume it's a discord.Member/User
+ user = user.id
+ # fills the user's hand up to number of cards
+ game = await self.userGame(user)
+ for member in game["Members"]:
+ if member["ID"] == user:
+ # Found our user - let's draw cards
+ i = len(member["Hand"])
+ while i < cards:
+ # Draw unique cards until we fill our hand
+ newCard = await self.drawCard(game)
+ member["Hand"].append(newCard)
+ i += 1
+
+ async def drawBCard(self, game):
+ with open(str(bundled_data_path(self)) + "/deck.json", 'r') as deck_file:
+ deck = json.load(deck_file)
+ # Draws a random black card
+ totalDiscard = len(game["BDiscard"])
+ if totalDiscard >= len(deck["blackCards"]):
+ # Tell everyone the cards were shuffled
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ user = member["User"]
+ await user.send("Shuffling black cards...")
+ # Shuffle the cards
+ game["BDiscard"] = []
+ while True:
+ # Random grab a unique card
+ index = random.randint(0, len(deck["blackCards"]) - 1)
+ if not index in game["BDiscard"]:
+ game["BDiscard"].append(index)
+ text = deck["blackCards"][index]["text"]
+ text = self.cleanJson(text)
+ game["BlackCard"] = {"Text": text, "Pick": deck["blackCards"][index]["pick"]}
+ return game["BlackCard"]
+
+ async def nextPlay(self, ctx, game):
+ # Advances the game
+ if len(game["Members"]) < self.minMembers:
+ prefix = await self.bot.db.prefix()
+ stat_embed = discord.Embed(color=discord.Color.red())
+ stat_embed.set_author(
+ name="Not enough players to continue! ({}/{})".format(
+ len(game["Members"]), self.minMembers
+ )
+ )
+ stat_embed.set_footer(
+ text="Have other users join with: {}joincah {}".format(prefix[0], game["ID"])
+ )
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ await member["User"].send(embed=stat_embed)
+ return
+
+ # Find if we have a winner
+ winner = False
+ stat_embed = discord.Embed(color=discord.Color.lighter_grey())
+ for member in game["Members"]:
+ if member["IsBot"]:
+ # Clear pending tasks and set to None
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ # Task isn't finished - we're on a new hand, cancel it
+ task.cancel()
+ member["Task"] = None
+ if member["Points"] >= self.winAfter:
+ # We have a winner!
+ winner = True
+ if member["IsBot"]:
+ stat_embed.set_author(
+ name="{} ({}) is the WINNER!!".format(self.botName, member["ID"])
+ )
+ else:
+ stat_embed.set_author(
+ name="{} is the WINNER!!".format(self.displayname(member["User"]))
+ )
+ stat_embed.set_footer(text="Congratulations!".format(game["ID"]))
+ break
+ if winner:
+ for member in game["Members"]:
+ if not member["IsBot"]:
+ await member["User"].send(embed=stat_embed)
+ # Reset all users
+ member["Hand"] = []
+ member["Points"] = 0
+ member["Won"] = []
+ member["Laid"] = False
+ member["Refreshed"] = False
+ return
+
+ game["Judging"] = False
+ # Clear submitted cards
+ game["Submitted"] = []
+ # We have enough members
+ if game["Judge"] == -1:
+ # First game - randomize judge
+ game["Judge"] = random.randint(0, len(game["Members"]) - 1)
+ else:
+ game["Judge"] += 1
+ # Reset the judge if out of bounds
+ if game["Judge"] >= len(game["Members"]):
+ game["Judge"] = 0
+
+ # Draw the next black card
+ bCard = await self.drawBCard(game)
+
+ # Draw cards
+ for member in game["Members"]:
+ member["Laid"] = False
+ await self.drawCards(member["ID"])
+
+ # Show hands
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ await self.showPlay(ctx, member["User"])
+ index = game["Members"].index(member)
+ if not index == game["Judge"]:
+ await self.showHand(ctx, member["User"])
+ await asyncio.sleep(0.1)
+
+ # Have the bots lay their cards
+ for member in game["Members"]:
+ if not member["IsBot"]:
+ continue
+ if member["ID"] == game["Members"][game["Judge"]]["ID"]:
+ continue
+ # Not a human player, and not the judge
+ # task = self.bot.loop.create_task(self.botPick(ctx, member, game))\
+ task = asyncio.ensure_future(self.botPick(ctx, member, game))
+ member["Task"] = task
+ # await self.botPick(ctx, member, game)
+
+ @commands.command()
+ async def game(self, ctx, *, message=None):
+ """Displays the game's current status."""
+ if not await self.checkPM(ctx.message):
+ return
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ await self.showPlay(ctx, ctx.message.author)
+
+ @commands.command()
+ async def chat(self, ctx, *, message=None):
+ """Broadcasts a message to the other players in your game."""
+ if not await self.checkPM(ctx.message):
+ return
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ userGame["Time"] = int(time.time())
+ if message == None:
+ msg = "Ooookay, you say *nothing...*"
+ await ctx.message.author.send(msg)
+ return
+ msg = "*{}* says: {}".format(ctx.message.author.name, message)
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ continue
+ # Tell them all!!
+ if not member["User"] == ctx.message.author:
+ # Don't tell yourself
+ await member["User"].send(msg)
+ else:
+ # Update member's time
+ member["Time"] = int(time.time())
+ await ctx.message.author.send("Message sent!")
+
+ @commands.command()
+ async def lay(self, ctx, *, card=None):
+ """Lays a card or cards from your hand. If multiple cards are needed, separate them by a comma (1,2,3)."""
+ if not await self.checkPM(ctx.message):
+ return
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ userGame["Time"] = int(time.time())
+ for member in userGame["Members"]:
+ if member["User"] == ctx.message.author:
+ member["Time"] = int(time.time())
+ user = member
+ index = userGame["Members"].index(member)
+ if index == userGame["Judge"]:
+ await ctx.message.author.send(
+ "You're the judge. You don't get to lay cards this round."
+ )
+ return
+ for submit in userGame["Submitted"]:
+ if submit["By"]["User"] == ctx.message.author:
+ await ctx.message.author.send("You already made your submission this round.")
+ return
+ if card == None:
+ await ctx.message.author.send("You need you input *something.*")
+ return
+ card = card.strip()
+ card = card.replace(" ", "")
+ # Not the judge
+ if len(userGame["Members"]) < self.minMembers:
+ stat_embed = discord.Embed(color=discord.Color.red())
+ stat_embed.set_author(
+ name="Not enough players to continue! ({}/{})".format(
+ len(userGame["Members"]), self.minMembers
+ )
+ )
+ stat_embed.set_footer(
+ text="Have other users join with: {}joincah {}".format(prefix[0], userGame["ID"])
+ )
+ await ctx.message.author.send(embed=stat_embed)
+ return
+
+ numberCards = userGame["BlackCard"]["Pick"]
+ cards = []
+ if numberCards > 1:
+ cardSpeak = "cards"
+ try:
+ card = card.split(",")
+ except Exception:
+ card = []
+ if not len(card) == numberCards:
+ msg = "You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`".format(
+ numberCards, prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ # Got something
+ # Check for duplicates
+ if not len(card) == len(set(card)):
+ msg = "You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`".format(
+ numberCards, prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ # Works
+ for c in card:
+ try:
+ c = int(c)
+ except Exception:
+ msg = "You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`".format(
+ numberCards, prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+
+ if c < 1 or c > len(user["Hand"]):
+ msg = "Card numbers must be between 1 and {}.".format(len(user["Hand"]))
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ cards.append(user["Hand"][c - 1]["Text"])
+ # Remove from user's hand
+ card = sorted(card, key=lambda card: int(card), reverse=True)
+ for c in card:
+ user["Hand"].pop(int(c) - 1)
+ # Valid cards
+
+ newSubmission = {"By": user, "Cards": cards}
+ else:
+ cardSpeak = "card"
+ try:
+ card = int(card)
+ except Exception:
+ msg = "You need to lay a valid card with `{}lay [card number]`".format(prefix[0])
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ if card < 1 or card > len(user["Hand"]):
+ msg = "Card numbers must be between 1 and {}.".format(len(user["Hand"]))
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ # Valid card
+ newSubmission = {"By": user, "Cards": [user["Hand"].pop(card - 1)["Text"]]}
+ userGame["Submitted"].append(newSubmission)
+
+ # Shuffle cards
+ shuffle(userGame["Submitted"])
+
+ user["Laid"] = True
+ await ctx.message.author.send("You submitted your {}!".format(cardSpeak))
+ await self.checkSubmissions(ctx, userGame, user)
+
+ @commands.command()
+ async def pick(self, ctx, *, card=None):
+ """As the judge - pick the winning card(s)."""
+ prefix = await self.bot.db.prefix()
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ userGame["Time"] = int(time.time())
+ isJudge = False
+ for member in userGame["Members"]:
+ if member["User"] == ctx.message.author:
+ member["Time"] = int(time.time())
+ user = member
+ index = userGame["Members"].index(member)
+ if index == userGame["Judge"]:
+ isJudge = True
+ if not isJudge:
+ msg = "You're not the judge - I guess you'll have to wait your turn.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ # Am judge
+ totalUsers = len(userGame["Members"]) - 1
+ submitted = len(userGame["Submitted"])
+ if submitted < totalUsers:
+ if totalUsers - submitted == 1:
+ msg = "Still waiting on 1 card..."
+ else:
+ msg = "Still waiting on {} cards...".format(totalUsers - submitted)
+ await ctx.message.author.send(msg)
+ return
+ try:
+ card = int(card) - 1
+ except Exception:
+ card = -1
+ if card < 0 or card >= totalUsers:
+ msg = "Your pick must be between 1 and {}.".format(totalUsers)
+ await ctx.message.author.send(msg)
+ return
+ # Pick is good!
+ await self.winningCard(ctx, userGame, card)
+
+ @commands.command()
+ async def hand(self, ctx):
+ """Shows your hand."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ await self.showHand(ctx, ctx.message.author)
+ userGame["Time"] = currentTime = int(time.time())
+
+ @commands.command()
+ async def newcah(self, ctx):
+ """Starts a new Cards Against Humanity game."""
+ # if not await self.checkPM(ctx.message):
+ # return
+ # Check if the user is already in game
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ if userGame:
+ # Already in a game
+ msg = "You're already in a game (id: *{}*)\nType `{}leavecah` to leave that game.".format(
+ userGame["ID"], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+
+ # Not in a game - create a new one
+ gameID = self.randomID()
+ currentTime = int(time.time())
+ newGame = {
+ "ID": gameID,
+ "Members": [],
+ "Discard": [],
+ "BDiscard": [],
+ "Judge": -1,
+ "Time": currentTime,
+ "BlackCard": None,
+ "Submitted": [],
+ "NextHand": asyncio.Event(),
+ "Judging": False,
+ "Timeout": True,
+ }
+ member = {
+ "ID": ctx.message.author.id,
+ "User": ctx.message.author,
+ "Points": 0,
+ "Won": [],
+ "Hand": [],
+ "Laid": False,
+ "Refreshed": False,
+ "IsBot": False,
+ "Creator": True,
+ "Task": None,
+ "Time": currentTime,
+ }
+ newGame["Members"].append(member)
+ newGame["Running"] = True
+ task = self.bot.loop.create_task(self.gameCheckLoop(ctx, newGame))
+ task = self.bot.loop.create_task(self.checkCards(ctx, newGame))
+ self.games.append(newGame)
+ # Tell the user they created a new game and list its ID
+ await ctx.message.channel.send("You created game id: *{}*".format(gameID))
+ await self.drawCards(ctx.message.author)
+ # await self.showHand(ctx, ctx.message.author)
+ # await self.nextPlay(ctx, newGame)
+
+ @commands.command()
+ async def leavecah(self, ctx):
+ """Leaves the current game you're in."""
+ removeCheck = await self.removeMember(ctx.message.author)
+ if not removeCheck:
+ msg = "You are not in a game."
+ await ctx.message.channel.send(msg)
+ return
+ if self.checkGame(removeCheck):
+ # await self.nextPlay(ctx, removeCheck)
+
+ """# Start the game loop
+ event = removeCheck['NextHand']
+ self.bot.loop.call_soon_threadsafe(event.set)"""
+ # Player was removed - try to handle it calmly...
+ await self.checkSubmissions(ctx, removeCheck)
+
+ @commands.command()
+ async def joincah(self, ctx, *, id=None):
+ """Join a Cards Against Humanity game. If no id or user is passed, joins a random game."""
+ # if not await self.checkPM(ctx.message):
+ # return
+ # Check if the user is already in game
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ isCreator = False
+ if userGame:
+ # Already in a game
+ msg = "You're already in a game (id: *{}*)\nType `{}leavecah` to leave that game.".format(
+ userGame["ID"], prefix[0]
+ )
+ await ctx.message.channel.send(msg)
+ return
+ if len(self.games):
+ if id:
+ game = self.gameForID(id)
+ if game == None:
+ # That id doesn't exist - or is possibly a user
+ # If user, has to be joined from server chat
+ if not ctx.message.guild:
+ msg = "I couldn't find a game attached to that id. If you are trying to join a user - run the `{}joincah [user]` command in a channel on a server you share with that user.".format(
+ prefix[0]
+ )
+ await ctx.message.channel.send(msg)
+ return
+ else:
+ # We have a server - let's try for a user
+ member = self.memberforname(id, ctx.message.guild)
+ if not member:
+ # Couldn't find user!
+ msg = "I couldn't find a game attached to that id. If you are trying to join a user - run the `{}joincah [user]` command in a channel on a server you share with that user.".format(
+ prefix[0]
+ )
+ await ctx.message.channel.send(msg)
+ return
+ # Have a user - check if they're in a game
+ game = await self.userGame(member)
+ if not game:
+ # That user is NOT in a game!
+ msg = "That user doesn't appear to be playing."
+ await ctx.message.channel.send(msg)
+ return
+
+ else:
+ game = random.choice(self.games)
+ else:
+ # No games - create a new one
+ gameID = self.randomID()
+ currentTime = int(time.time())
+ game = {
+ "ID": gameID,
+ "Members": [],
+ "Discard": [],
+ "BDiscard": [],
+ "Judge": -1,
+ "Time": currentTime,
+ "BlackCard": None,
+ "Submitted": [],
+ "NextHand": asyncio.Event(),
+ "Judging": False,
+ "Timeout": True,
+ }
+ game["Running"] = True
+ task = self.bot.loop.create_task(self.gameCheckLoop(ctx, game))
+ task = self.bot.loop.create_task(self.checkCards(ctx, game))
+ self.games.append(game)
+ # Tell the user they created a new game and list its ID
+ await ctx.message.channel.send("**You created game id:** ***{}***".format(gameID))
+ isCreator = True
+
+ # Tell everyone else you joined
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ await member["User"].send(
+ "***{}*** **joined the game!**".format(self.displayname(ctx.message.author))
+ )
+
+ # We got a user!
+ currentTime = int(time.time())
+ member = {
+ "ID": ctx.message.author.id,
+ "User": ctx.message.author,
+ "Points": 0,
+ "Won": [],
+ "Hand": [],
+ "Laid": False,
+ "Refreshed": False,
+ "IsBot": False,
+ "Creator": isCreator,
+ "Task": None,
+ "Time": currentTime,
+ }
+ game["Members"].append(member)
+ await self.drawCards(ctx.message.author)
+ if len(game["Members"]) == 1:
+ # Just created the game
+ await self.drawCards(ctx.message.author)
+ else:
+ msg = "**You've joined game id:** ***{}!***\n\nThere are *{} users* in this game.".format(
+ game["ID"], len(game["Members"])
+ )
+ await ctx.message.channel.send(msg)
+
+ # Check if adding put us at minimum members
+ if len(game["Members"]) - 1 < self.minMembers:
+ # It was - *actually* start a game
+ event = game["NextHand"]
+ self.bot.loop.call_soon_threadsafe(event.set)
+ else:
+ # It was not - just incorporate new players
+ await self.checkSubmissions(ctx, game)
+ # Reset judging flag to retrigger actions
+ game["Judging"] = False
+ # Show the user the current card and their hand
+ await self.showPlay(ctx, member["User"])
+ await self.showHand(ctx, member["User"])
+ event = game["NextHand"]
+
+ game["Time"] = int(time.time())
+
+ @commands.command()
+ async def joinbot(self, ctx):
+ """Adds a bot to the game. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can add bots."
+ await ctx.message.author.send(msg)
+ return
+ member["Time"] = int(time.time())
+ # We are the creator - let's check the number of bots
+ if botCount >= self.maxBots:
+ # Too many bots!
+ msg = "You already have enough bots (max is {}).".format(self.maxBots)
+ await ctx.message.author.send(msg)
+ return
+ # We can get another bot!
+ botID = self.randomBotID(userGame)
+ lobot = {
+ "ID": botID,
+ "User": None,
+ "Points": 0,
+ "Won": [],
+ "Hand": [],
+ "Laid": False,
+ "Refreshed": False,
+ "IsBot": True,
+ "Creator": False,
+ "Task": None,
+ }
+ userGame["Members"].append(lobot)
+ await self.drawCards(lobot["ID"])
+ msg = "***{} ({})*** **joined the game!**".format(self.botName, botID)
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ continue
+ await member["User"].send(msg)
+ # await self.nextPlay(ctx, userGame)
+
+ # Check if adding put us at minimum members
+ if len(userGame["Members"]) - 1 < self.minMembers:
+ # It was - *actually* start a game
+ event = userGame["NextHand"]
+ self.bot.loop.call_soon_threadsafe(event.set)
+ else:
+ # It was not - just incorporate new players
+ await self.checkSubmissions(ctx, userGame)
+ # Reset judging flag to retrigger actions
+ userGame["Judging"] = False
+ # Schedule stuff
+ task = asyncio.ensure_future(self.botPick(ctx, lobot, userGame))
+ lobot["Task"] = task
+
+ @commands.command()
+ async def joinbots(self, ctx, number=None):
+ """Adds bots to the game. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ prefix = await self.bot.db.prefix()
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can add bots."
+ await ctx.message.author.send(msg)
+ return
+ member["Time"] = int(time.time())
+ if number == None:
+ # No number specified - let's add the max number of bots
+ number = self.maxBots - botCount
+
+ try:
+ number = int(number)
+ except Exception:
+ msg = "Number of bots to add must be an integer."
+ await ctx.message.author.send(msg)
+ return
+
+ # We are the creator - let's check the number of bots
+ if botCount >= self.maxBots:
+ # Too many bots!
+ msg = "You already have enough bots (max is {}).".format(self.maxBots)
+ await ctx.message.author.send(msg)
+ return
+
+ if number > (self.maxBots - botCount):
+ number = self.maxBots - botCount
+
+ if number == 1:
+ msg = "**Adding {} bot:**\n\n".format(number)
+ else:
+ msg = "**Adding {} bots:**\n\n".format(number)
+
+ newBots = []
+ for i in range(0, number):
+ # We can get another bot!
+ botID = self.randomBotID(userGame)
+ lobot = {
+ "ID": botID,
+ "User": None,
+ "Points": 0,
+ "Won": [],
+ "Hand": [],
+ "Laid": False,
+ "Refreshed": False,
+ "IsBot": True,
+ "Creator": False,
+ "Task": None,
+ }
+ userGame["Members"].append(lobot)
+ newBots.append(lobot)
+ await self.drawCards(lobot["ID"])
+ msg += "***{} ({})*** **joined the game!**\n".format(self.botName, botID)
+ # await self.nextPlay(ctx, userGame)
+
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ continue
+ await member["User"].send(msg)
+
+ # Check if adding put us at minimum members
+ if len(userGame["Members"]) - number < self.minMembers:
+ # It was - *actually* start a game
+ event = userGame["NextHand"]
+ self.bot.loop.call_soon_threadsafe(event.set)
+ else:
+ # It was not - just incorporate new players
+ await self.checkSubmissions(ctx, userGame)
+ # Reset judging flag to retrigger actions
+ game["Judging"] = False
+ for bot in newBots:
+ # Schedule stuff
+ task = asyncio.ensure_future(self.botPick(ctx, bot, userGame))
+ bot["Task"] = task
+
+ @commands.command()
+ async def removebot(self, ctx, *, id=None):
+ """Removes a bot from the game. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can remove bots."
+ await ctx.message.author.send(msg)
+ return
+ member["Time"] = int(time.time())
+ # We are the creator - let's check the number of bots
+ if id == None:
+ # Just remove the first bot we find
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ await self.removeMember(member["ID"])
+ """# Start the game loop
+ event = userGame['NextHand']
+ self.bot.loop.call_soon_threadsafe(event.set)"""
+ # Bot was removed - try to handle it calmly...
+ await self.checkSubmissions(ctx, userGame)
+ return
+ msg = "No bots to remove!"
+ await ctx.message.author.send(msg)
+ return
+ else:
+ # Remove a bot by id
+ if not await self.removeMember(id):
+ # not found
+ msg = "I couldn't locate that bot on this game. If you're trying to remove a player, try the `{}removeplayer [name]` command.".format(
+ prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ # await self.nextPlay(ctx, userGame)
+
+ """# Start the game loop
+ event = userGame['NextHand']
+ self.bot.loop.call_soon_threadsafe(event.set)"""
+ # Bot was removed - let's try to handle it calmly...
+ await self.checkSubmissions(ctx, userGame)
+
+ @commands.command()
+ async def cahgames(self, ctx):
+ """Displays up to 10 CAH games in progress."""
+ shuffledGames = list(self.games)
+ random.shuffle(shuffledGames)
+ if not len(shuffledGames):
+ await ctx.message.channel.send("No games being played currently.")
+ return
+
+ max = 10
+ if len(shuffledGames) < 10:
+ max = len(shuffledGames)
+ msg = "__Current CAH Games__:\n\n"
+
+ for i in range(0, max):
+ playerCount = 0
+ botCount = 0
+ gameID = shuffledGames[i]["ID"]
+ for j in shuffledGames[i]["Members"]:
+ if j["IsBot"]:
+ botCount += 1
+ else:
+ playerCount += 1
+ botText = "{} bot".format(botCount)
+ if not botCount == 1:
+ botText += "s"
+ playerText = "{} player".format(playerCount)
+ if not playerCount == 1:
+ playerText += "s"
+
+ msg += "{}. {} - {} | {}\n".format(i + 1, gameID, playerText, botText)
+
+ await ctx.message.channel.send(msg)
+
+ @commands.command()
+ async def score(self, ctx):
+ """Display the score of the current game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ stat_embed = discord.Embed(color=discord.Color.purple())
+ stat_embed.set_author(name="Current Score")
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(userGame["ID"]))
+ await ctx.message.author.send(embed=stat_embed)
+ users = sorted(userGame["Members"], key=lambda card: int(card["Points"]), reverse=True)
+ msg = ""
+ i = 0
+ if len(users) > 10:
+ msg += "__10 of {} Players:__\n\n".format(len(users))
+ else:
+ msg += "__Players:__\n\n"
+ for user in users:
+ i += 1
+ if i > 10:
+ break
+ if user["Points"] == 1:
+ if user["User"]:
+ # Person
+ msg += "{}. *{}* - 1 point\n".format(i, self.displayname(user["User"]))
+ else:
+ # Bot
+ msg += "{}. *{} ({})* - 1 point\n".format(i, self.botName, user["ID"])
+ else:
+ if user["User"]:
+ # Person
+ msg += "{}. *{}* - {} points\n".format(
+ i, self.displayname(user["User"]), user["Points"]
+ )
+ else:
+ # Bot
+ msg += "{}. *{} ({})* - {} points\n".format(
+ i, self.botName, user["ID"], user["Points"]
+ )
+ await ctx.message.author.send(msg)
+
+ @commands.command()
+ async def laid(self, ctx):
+ """Shows who laid their cards and who hasn't."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ stat_embed = discord.Embed(color=discord.Color.purple())
+ stat_embed.set_author(name="Card Check")
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(userGame["ID"]))
+ await ctx.message.author.send(embed=stat_embed)
+ users = sorted(userGame["Members"], key=lambda card: int(card["Laid"]))
+ msg = ""
+ i = 0
+ if len(users) > 10:
+ msg += "__10 of {} Players:__\n\n".format(len(users))
+ else:
+ msg += "__Players:__\n\n"
+ for user in users:
+ if len(userGame["Members"]) >= self.minMembers:
+ if user == userGame["Members"][userGame["Judge"]]:
+ continue
+ i += 1
+ if i > 10:
+ break
+
+ if user["Laid"]:
+ if user["User"]:
+ # Person
+ msg += "{}. *{}* - Cards are in.\n".format(i, self.displayname(user["User"]))
+ else:
+ # Bot
+ msg += "{}. *{} ({})* - Cards are in.\n".format(i, self.botName, user["ID"])
+ else:
+ if user["User"]:
+ # Person
+ msg += "{}. *{}* - Waiting for cards...\n".format(
+ i, self.displayname(user["User"])
+ )
+ else:
+ # Bot
+ msg += "{}. *{} ({})* - Waiting for cards...\n".format(
+ i, self.botName, user["ID"]
+ )
+ await ctx.message.author.send(msg)
+
+ @commands.command()
+ async def removeplayer(self, ctx, *, name=None):
+ """Removes a player from the game. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can remove players."
+ await ctx.message.author.send(msg)
+ return
+ member["Time"] = int(time.time())
+ # We are the creator - let's check the number of bots
+ if name == None:
+ # Nobody named!
+ msg = "Okay, I removed... no one from the game..."
+ await ctx.message.author.send(msg)
+ return
+
+ # Let's get the person either by name, or by id
+ nameID = "".join(list(filter(str.isdigit, name)))
+ for member in userGame["Members"]:
+ toRemove = False
+ if member["IsBot"]:
+ continue
+ if name.lower() == self.displayname(member["User"]).lower():
+ # Got em!
+ toRemove = True
+ elif nameID == member["ID"]:
+ # Got em!
+ toRemove = True
+ if toRemove:
+ await self.removeMember(member["ID"])
+ break
+ # await self.nextPlay(ctx, userGame)
+
+ if toRemove:
+ """# Start the game loop
+ event = userGame['NextHand']
+ self.bot.loop.call_soon_threadsafe(event.set)"""
+ # Player was removed - try to handle it calmly...
+ await self.checkSubmissions(ctx, userGame)
+ else:
+ prefix = await self.bot.db.prefix()
+ msg = "I couldn't locate that player on this game. If you're trying to remove a bot, try the `{}removebot [id]` command.".format(
+ prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+
+ @commands.command()
+ async def flushhand(self, ctx):
+ """Flushes the cards in your hand - can only be done once per game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ if userGame["Judge"] == -1:
+ msg = "The game hasn't started yet. Probably not worth it to flush your hand before you get it..."
+ await ctx.message.author.send(msg)
+ return
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ continue
+ if member["User"] == ctx.message.author:
+ member["Time"] = int(time.time())
+ # Found us!
+ if member["Refreshed"]:
+ # Already flushed their hand
+ msg = "You have already flushed your hand this game."
+ await ctx.message.author.send(msg)
+ return
+ else:
+ member["Hand"] = []
+ await self.drawCards(member["ID"])
+ member["Refreshed"] = True
+ msg = "Flushing your hand!"
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+
+ @commands.command()
+ async def idlekick(self, ctx, *, setting=None):
+ """Sets whether or not to kick members if idle for 5 minutes or more. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can remove bots."
+ await ctx.message.author.send(msg)
+ return
+ # We are the creator - let's check the number of bots
+ if setting == None:
+ # Output idle kick status
+ if userGame["Timeout"]:
+ await ctx.message.channel.send("Idle kick is enabled.")
+ else:
+ await ctx.message.channel.send("Idle kick is disabled.")
+ return
+ elif setting.lower() == "yes" or setting.lower() == "on" or setting.lower() == "true":
+ setting = True
+ elif setting.lower() == "no" or setting.lower() == "off" or setting.lower() == "false":
+ setting = False
+ else:
+ setting = None
+
+ if setting == True:
+ if userGame["Timeout"] == True:
+ msg = "Idle kick remains enabled."
+ else:
+ msg = "Idle kick now enabled."
+ for member in userGame["Members"]:
+ member["Time"] = int(time.time())
+ else:
+ if userGame["Timeout"] == False:
+ msg = "Idle kick remains disabled."
+ else:
+ msg = "Idle kick now disabled."
+ userGame["Timeout"] = setting
+
+ await ctx.message.channel.send(msg)
+
+ @commands.command()
+ async def cahcredits(self, ctx):
+ """Code credits."""
+ await ctx.send(
+ "```\nThis cog is made possible by CorpBot.\nPlease visit https://github.com/corpnewt/CorpBot.py for more information.\n```"
+ )
diff --git a/cah/data/deck.json b/cah/data/deck.json
new file mode 100644
index 0000000..208bf32
--- /dev/null
+++ b/cah/data/deck.json
@@ -0,0 +1,2434 @@
+{
+ "blackCards":[
+ {
+ "text":"Why can't I sleep at night?",
+ "pick":1
+ },
+ {
+ "text":"I got 99 problems but _ ain't one.",
+ "pick":1
+ },
+ {
+ "text":"What's a girl's best friend?",
+ "pick":1
+ },
+ {
+ "text":"What's that smell?",
+ "pick":1
+ },
+ {
+ "text":"This is the way the world ends \/ This is the way the world ends \/ Not with a bang but with _.",
+ "pick":1
+ },
+ {
+ "text":"What is Batman's guilty pleasure?",
+ "pick":1
+ },
+ {
+ "text":"TSA guidelines now prohibit _ on airplanes.",
+ "pick":1
+ },
+ {
+ "text":"What ended my last relationship?",
+ "pick":1
+ },
+ {
+ "text":"MTV's new reality show features eight washed-up celebrities living with _.",
+ "pick":1
+ },
+ {
+ "text":"I drink to forget _.",
+ "pick":1
+ },
+ {
+ "text":"I'm sorry, Professor, but I couldn't complete my homework because of _.",
+ "pick":1
+ },
+ {
+ "text":"Alternative medicine is now embracing the curative powers of _.",
+ "pick":1
+ },
+ {
+ "text":"What's that sound?",
+ "pick":1
+ },
+ {
+ "text":"What's the next Happy Meal toy?",
+ "pick":1
+ },
+ {
+ "text":"It's a pity that kids these days are all getting involved with _.",
+ "pick":1
+ },
+ {
+ "text":"In the new Disney Channel Original Movie, Hannah Montana struggles with _ for the first time.",
+ "pick":1
+ },
+ {
+ "text":"_. That's how I want to die.",
+ "pick":1
+ },
+ {
+ "text":"What does Dick Cheney prefer?",
+ "pick":1
+ },
+ {
+ "text":"What's the most emo?",
+ "pick":1
+ },
+ {
+ "text":"Instead of coal, Santa now gives the bad children _.",
+ "pick":1
+ },
+ {
+ "text":"Next from J.K. Rowling: Harry Potter and the Chamber of _.",
+ "pick":1
+ },
+ {
+ "text":"A romantic, candlelit dinner would be incomplete without _.",
+ "pick":1
+ },
+ {
+ "text":"White people like _.",
+ "pick":1
+ },
+ {
+ "text":"_. Betcha can't have just one!",
+ "pick":1
+ },
+ {
+ "text":"War! What is it good for?",
+ "pick":1
+ },
+ {
+ "text":"BILLY MAYS HERE FOR _.",
+ "pick":1
+ },
+ {
+ "text":"_. High five, bro.",
+ "pick":1
+ },
+ {
+ "text":"During sex, I like to think about _.",
+ "pick":1
+ },
+ {
+ "text":"What did I bring back from Mexico?",
+ "pick":1
+ },
+ {
+ "text":"What are my parents hiding from me?",
+ "pick":1
+ },
+ {
+ "text":"What will always get you laid?",
+ "pick":1
+ },
+ {
+ "text":"What would grandma find disturbing, yet oddly charming?",
+ "pick":1
+ },
+ {
+ "text":"What did the U.S. airdrop to the children of Afghanistan?",
+ "pick":1
+ },
+ {
+ "text":"What helps Obama unwind?",
+ "pick":1
+ },
+ {
+ "text":"What's there a ton of in heaven?",
+ "pick":1
+ },
+ {
+ "text":"Major League Baseball has banned _ for giving players an unfair advantage.",
+ "pick":1
+ },
+ {
+ "text":"When I am a billionaire, I shall erect a 50-foot statue to commemorate _.",
+ "pick":1
+ },
+ {
+ "text":"What's the new fad diet?",
+ "pick":1
+ },
+ {
+ "text":"When I am the President of the United States, I will create the Department of _.",
+ "pick":1
+ },
+ {
+ "text":"_. It's a trap!",
+ "pick":1
+ },
+ {
+ "text":"How am I maintaining my relationship status?",
+ "pick":1
+ },
+ {
+ "text":"What will I bring back in time to convince people that I am a powerful wizard?",
+ "pick":1
+ },
+ {
+ "text":"While the United States raced the Soviet Union to the moon, the Mexican government funneled millions of pesos into research on _.",
+ "pick":1
+ },
+ {
+ "text":"Coming to Broadway this season, _: The Musical.",
+ "pick":1
+ },
+ {
+ "text":"What's my secret power?",
+ "pick":1
+ },
+ {
+ "text":"What gives me uncontrollable gas?",
+ "pick":1
+ },
+ {
+ "text":"But before I kill you, Mr. Bond, I must show you _.",
+ "pick":1
+ },
+ {
+ "text":"What never fails to liven up the party?",
+ "pick":1
+ },
+ {
+ "text":"What am I giving up for Lent?",
+ "pick":1
+ },
+ {
+ "text":"What do old people smell like? ",
+ "pick":1
+ },
+ {
+ "text":"The class field trip was completely ruined by _.",
+ "pick":1
+ },
+ {
+ "text":"When Pharaoh remained unmoved, Moses called down a plague of _.",
+ "pick":1
+ },
+ {
+ "text":"I do not know with which weapons World War III will be fought, but World War IV will be fought with _.",
+ "pick":1
+ },
+ {
+ "text":"What's Teach for America using to inspire inner city students to succeed?",
+ "pick":1
+ },
+ {
+ "text":"In Michael Jackson's final moments, he thought about _.",
+ "pick":1
+ },
+ {
+ "text":"Why do I hurt all over?",
+ "pick":1
+ },
+ {
+ "text":"Studies show that lab rats navigate mazes 50% faster after being exposed to _.",
+ "pick":1
+ },
+ {
+ "text":"Why am I sticky?",
+ "pick":1
+ },
+ {
+ "text":"What's my anti-drug?",
+ "pick":1
+ },
+ {
+ "text":"And the Academy Award for _ goes to _.",
+ "pick":2
+ },
+ {
+ "text":"For my next trick, I will pull _ out of _.",
+ "pick":2
+ },
+ {
+ "text":"_: Good to the last drop.",
+ "pick":1
+ },
+ {
+ "text":"What did Vin Diesel eat for dinner?",
+ "pick":1
+ },
+ {
+ "text":"_: kid-tested, mother-approved.",
+ "pick":1
+ },
+ {
+ "text":"What gets better with age?",
+ "pick":1
+ },
+ {
+ "text":"I never truly understood _ until I encountered _.",
+ "pick":2
+ },
+ {
+ "text":"Rumor has it that Vladimir Putin's favorite delicacy is _ stuffed with _.",
+ "pick":2
+ },
+ {
+ "text":"Lifetime presents _, the story of _.",
+ "pick":2
+ },
+ {
+ "text":"Make a haiku.",
+ "pick":3
+ },
+ {
+ "text":"In M. Night Shyamalan's new movie, Bruce Willis discovers that _ had really been _ all along.",
+ "pick":2
+ },
+ {
+ "text":"_ is a slippery slope that leads to _.",
+ "pick":2
+ },
+ {
+ "text":"In a world ravaged by _, our only solace is _.",
+ "pick":2
+ },
+ {
+ "text":"That's right, I killed _. How, you ask? _.",
+ "pick":2
+ },
+ {
+ "text":"When I was tripping on acid, _ turned into _.",
+ "pick":2
+ },
+ {
+ "text":"_ + _ = _.",
+ "pick":3
+ },
+ {
+ "text":"What's the next superhero\/sidekick duo?",
+ "pick":2
+ },
+ {
+ "text":"Dear Abby, I'm having some trouble with _ and would like your advice.",
+ "pick":1
+ },
+ {
+ "text":"After the earthquake, Sean Penn brought _ to the people of Haiti.",
+ "pick":1
+ },
+ {
+ "text":"In L.A. County Jail, word is you can trade 200 cigarettes for _.",
+ "pick":1
+ },
+ {
+ "text":"Maybe she's born with it. Maybe it's _.",
+ "pick":1
+ },
+ {
+ "text":"Life for American Indians was forever changed when the White Man introduced them to _.",
+ "pick":1
+ },
+ {
+ "text":"Next on ESPN2, the World Series of _.",
+ "pick":1
+ },
+ {
+ "text":"Step 1: _. Step 2: _. Step 3: Profit.",
+ "pick":2
+ },
+ {
+ "text":"Here is the church... Here is the steeple... Open the doors... And there is _.",
+ "pick":1
+ },
+ {
+ "text":"How did I lose my virginity?",
+ "pick":1
+ },
+ {
+ "text":"During his childhood, Salvador Dali produced hundreds of paintings of _.",
+ "pick":1
+ },
+ {
+ "text":"In 1,000 years, when paper money is a distant memory, how will we pay for goods and services?",
+ "pick":1
+ },
+ {
+ "text":"What don't you want to find in your Kung Pao chicken?",
+ "pick":1
+ },
+ {
+ "text":"The Smithsonian Museum of Natural History has just opened an exhibit on _.",
+ "pick":1
+ },
+ {
+ "text":"Daddy, why is Mommy crying?",
+ "pick":1
+ },
+ {
+ "text":"An international tribunal has found _ guilty of _.",
+ "pick":2
+ },
+ {
+ "text":"And I would have gotten away with it, too, if it hadn't been for _!",
+ "pick":1
+ },
+ {
+ "text":"Dear Sir or Madam, We regret to inform you that the Office of _ has denied your request for _.",
+ "pick":2
+ },
+ {
+ "text":"He who controls _ controls the world.",
+ "pick":1
+ },
+ {
+ "text":"I learned the hard way that you can't cheer up a grieving friend with _.",
+ "pick":1
+ },
+ {
+ "text":"In a pinch, _ can be a suitable substitute for _.",
+ "pick":2
+ },
+ {
+ "text":"In his new self-produced album, Kanye West raps over the sounds of _.",
+ "pick":1
+ },
+ {
+ "text":"In its new tourism campaign, Detroit proudly proclaims that it has finally eliminated _.",
+ "pick":1
+ },
+ {
+ "text":"In Rome, there are whisperings that the Vatican has a secret room devoted to _.",
+ "pick":1
+ },
+ {
+ "text":"In the distant future, historians will agree that _ marked the beginning of America's decline.",
+ "pick":1
+ },
+ {
+ "text":"Michael Bay's new three-hour action epic pits _ against _.",
+ "pick":2
+ },
+ {
+ "text":"My plan for world domination begins with _.",
+ "pick":1
+ },
+ {
+ "text":"Next season on Man vs, Wild, Bear Grylls must survive the depths of the Amazon with only _ and his wits.",
+ "pick":1
+ },
+ {
+ "text":"Science will never explain _.",
+ "pick":1
+ },
+ {
+ "text":"Science will never explain the origin of _.",
+ "pick":1
+ },
+ {
+ "text":"The CIA now interrogates enemy agents by repeatedly subjecting them to _.",
+ "pick":1
+ },
+ {
+ "text":"The secret to a lasting marriage is communication, communication, and _.",
+ "pick":1
+ },
+ {
+ "text":"The socialist governments of Scandinavia have declared that access to _ is a basic human right.",
+ "pick":1
+ },
+ {
+ "text":"This season on Man vs. Wild, Bear Grylls must survive in the depths of the",
+ "pick":1
+ },
+ {
+ "text":"Amazon with only _ and his wits.",
+ "pick":1
+ },
+ {
+ "text":"What brought the orgy to a grinding halt?",
+ "pick":1
+ },
+ {
+ "text":"What has been making life difficult at the nudist colony?",
+ "pick":1
+ },
+ {
+ "text":"What's the gift that keeps on giving?",
+ "pick":1
+ },
+ {
+ "text":"When all else fails, I can always masturbate to _.",
+ "pick":1
+ },
+ {
+ "text":"When I pooped, what came out of my butt?",
+ "pick":1
+ },
+ {
+ "text":"_ would be woefully incomplete without _.",
+ "pick":2
+ },
+ {
+ "text":"After months of debate, the Occupy Wall Street General Assembly could only agree on \"More _!\"",
+ "pick":1
+ },
+ {
+ "text":"Before _, all we had was _.",
+ "pick":2
+ },
+ {
+ "text":"Before I run for president, I must destroy all evidence of my involvement with _.",
+ "pick":1
+ },
+ {
+ "text":"Charades was ruined for me forever when my mom had to act out _.",
+ "pick":1
+ },
+ {
+ "text":"During his midlife crisis, my dad got really into _.",
+ "pick":1
+ },
+ {
+ "text":"Everyone down on the ground! We don't want to hurt anyone. We're just here for _.",
+ "pick":1
+ },
+ {
+ "text":"I spent my whole life working toward _, only to have it ruined by _.",
+ "pick":2
+ },
+ {
+ "text":"I went from _ to _, all thanks to _.",
+ "pick":3
+ },
+ {
+ "text":"If God didn't want us to enjoy _, he wouldn't have given us _.",
+ "pick":2
+ },
+ {
+ "text":"In his newest and most difficult stunt, David Blaine must escape from _.",
+ "pick":1
+ },
+ {
+ "text":"Little Miss Muffet Sat on a tuffet, Eating her curds and _.",
+ "pick":1
+ },
+ {
+ "text":"Members of New York's social elite are paying thousands of dollars just to experience _.",
+ "pick":1
+ },
+ {
+ "text":"My country, 'tis of thee, sweet land of _.",
+ "pick":1
+ },
+ {
+ "text":"My mom freaked out when she looked at my browser history and found _.com\/_.",
+ "pick":2
+ },
+ {
+ "text":"My new favorite porn star is Joey \"_\" McGee.",
+ "pick":1
+ },
+ {
+ "text":"Next time on Dr. Phil: How to talk to your child about _.",
+ "pick":1
+ },
+ {
+ "text":"Only two things in life are certain: death and _.",
+ "pick":1
+ },
+ {
+ "text":"The Five Stages of Grief: denial, anger, bargaining, _, acceptance.",
+ "pick":1
+ },
+ {
+ "text":"The healing process began when I joined a support group for victims of _.",
+ "pick":1
+ },
+ {
+ "text":"The votes are in, and the new high school mascot is _.",
+ "pick":1
+ },
+ {
+ "text":"This is your captain speaking. Fasten your seatbelts and prepare for _.",
+ "pick":1
+ },
+ {
+ "text":"This month's Cosmo: \"Spice up your sex life by bringing _ into the bedroom.\"",
+ "pick":1
+ },
+ {
+ "text":"Tonight on 20\/20: What you don't know about _ could kill you.",
+ "pick":1
+ },
+ {
+ "text":"You haven't truly lived until you've experienced _ and _ at the same time.",
+ "pick":2
+ },
+ {
+ "text":"A remarkable new study has shown that chimps have evolved their own primitive version of _.",
+ "pick":1
+ },
+ {
+ "text":"What's harshing my mellow, man?",
+ "pick":1
+ },
+ {
+ "text":"Your persistence is admirable, my dear Prince. But you cannot win my heart with _ alone.",
+ "pick":1
+ },
+ {
+ "text":"In the seventh circle of Hell, sinners must endure _ for all eternity.",
+ "pick":1
+ },
+ {
+ "text":"A successful job interview begins with a firm handshake and ends with _.",
+ "pick":1
+ },
+ {
+ "text":"Lovin' you is easy 'cause you're _.",
+ "pick":1
+ },
+ {
+ "text":"My life is ruled by a vicious cycle of _ and _.",
+ "pick":2
+ },
+ {
+ "text":"The blind date was going horribly until we discovered our shared interest in _.",
+ "pick":1
+ },
+ {
+ "text":"_. Awesome in theory, kind of a mess in practice.",
+ "pick":1
+ },
+ {
+ "text":"I'm not like the rest of you. I'm too rich and busy for _.",
+ "pick":1
+ },
+ {
+ "text":"_: Hours of fun. Easy to use. Perfect for _!",
+ "pick":2
+ },
+ {
+ "text":"What left this stain on my couch?",
+ "pick":1
+ },
+ {
+ "text":"Call the law offices of Goldstein & Goldstein, because no one should have to tolerate _ in the workplace.",
+ "pick":1
+ },
+ {
+ "text":"When you get right down to it, _ is just _.",
+ "pick":2
+ },
+ {
+ "text":"Turns out that _-Man was neither the hero we needed nor wanted.",
+ "pick":1
+ },
+ {
+ "text":"As part of his daily regimen, Anderson Cooper sets aside 15 minutes for _.",
+ "pick":1
+ },
+ {
+ "text":"Money can't buy me love, but it can buy me _.",
+ "pick":1
+ },
+ {
+ "text":"With enough time and pressure, _ will turn into _.",
+ "pick":2
+ },
+ {
+ "text":"And what did you bring for show and tell?",
+ "pick":1
+ },
+ {
+ "text":"During high school I never really fit in until I found _ club.",
+ "pick":1
+ },
+ {
+ "text":"Hey baby, come back to my place and I'll show you _.",
+ "pick":1
+ },
+ {
+ "text":"After months of practice with _, I think I'm finally ready for _.",
+ "pick":2
+ },
+ {
+ "text":"To prepare for his upcoming role, Daniel Day-Lewis immersed himself in the world of _.",
+ "pick":1
+ },
+ {
+ "text":"Finally! A service that delivers _ right to your door.",
+ "pick":1
+ },
+ {
+ "text":"My gym teacher got fired for adding _ to the obstacle course.",
+ "pick":1
+ },
+ {
+ "text":"Having problems with _? Try _!",
+ "pick":2
+ },
+ {
+ "text":"As part of his contract, Prince won't perform without _ in his dressing room.",
+ "pick":1
+ },
+ {
+ "text":"Listen, son. If you want to get involved with _, I won't stop you. Just steer clear of _.",
+ "pick":2
+ },
+ {
+ "text":"What's fun until it gets weird?",
+ "pick":1
+ },
+ {
+ "text":"In the beginning, there was _. And the Lord said, \"Let there be _.\"",
+ "pick":2
+ },
+ {
+ "text":"Wes Anderson's new film tells the story of a precocious child coming to terms with _.",
+ "pick":1
+ },
+ {
+ "text":"_ will never be the same after _.",
+ "pick":2
+ },
+ {
+ "text":"I'm sorry, sir, but we don't allow _ at the country club.",
+ "pick":1
+ },
+ {
+ "text":"How am I compensating for my tiny penis?",
+ "pick":1
+ },
+ {
+ "text":"You've seen the bearded lady! You've seen the ring of fire! Now, ladies and gentlemen, feast your eyes upon _!",
+ "pick":1
+ },
+ {
+ "text":"We never did find _, but along the way we sure learned a lot about _.",
+ "pick":2
+ },
+ {
+ "text":"She's up all night for good fun. I'm up all night for _.",
+ "pick":1
+ },
+ {
+ "text":"_ may pass, but _ will last forever.",
+ "pick":2
+ },
+ {
+ "text":"Dear Leader Kim Jong-un, our village praises your",
+ "pick":1
+ },
+ {
+ "text":"infinite wisdom with a humble offering of _.",
+ "pick":1
+ },
+ {
+ "text":"Man, this is bullshit. Fuck _.",
+ "pick":1
+ },
+ {
+ "text":"You guys, I saw this crazy movie last night. It opens on _, and then there's some stuff about _, and then it ends with _.",
+ "pick":3
+ },
+ {
+ "text":"In return for my soul, the Devil promised me _, but all I got was _.",
+ "pick":2
+ },
+ {
+ "text":"The Japanese have developed a smaller, more efficient version of _.",
+ "pick":1
+ },
+ {
+ "text":"Alright, bros. Our frat house is condemned, and all the hot slampieces are over at Gamma Phi. That time has come to commence Operation _.",
+ "pick":1
+ },
+ {
+ "text":"This is the prime of my life. I'm young, hot, and full of _.",
+ "pick":1
+ },
+ {
+ "text":"I'm pretty sure I'm high right now, because I'm absolutely mesmerized by _.",
+ "pick":1
+ },
+ {
+ "text":"It lurks in the night. It hungers for flesh. This summer, no one is safe from _.",
+ "pick":1
+ },
+ {
+ "text":"If you can't handle _, you'd better stay away from _.",
+ "pick":2
+ },
+ {
+ "text":"Forget everything you know about _, because now we've supercharged it with _!",
+ "pick":2
+ },
+ {
+ "text":"Honey, I have a new role-play I want to try tonight!",
+ "pick":1
+ },
+ {
+ "text":"You can be _, and I'll be _.",
+ "pick":2
+ },
+ {
+ "text":"This year's hottest album is \"_\" by _.",
+ "pick":2
+ },
+ {
+ "text":"Every step towards _ gets me a little bit closer to _.",
+ "pick":2
+ },
+ {
+ "text":"Oprah's book of the month is \"_ For _: A Story of Hope.\"",
+ "pick":2
+ },
+ {
+ "text":"Do not fuck with me! I am literally _ right now.",
+ "pick":1
+ },
+ {
+ "text":"2 AM in the city that never sleeps. The door swings open and she walks in, legs up to here. Something in her eyes tells me she's looking for _.",
+ "pick":1
+ },
+ {
+ "text":"As king, how will I keep the peasants in line?",
+ "pick":1
+ },
+ {
+ "text":"Adventure. Romance. _. From Paramount Pictures, \"_.\"",
+ "pick":2
+ },
+ {
+ "text":"I am become _, destroyer of _!",
+ "pick":2
+ },
+ {
+ "text":"And today's soup is Cream of _.",
+ "pick":1
+ },
+ {
+ "text":"Armani suit: $1,000. Dinner for two at that swanky restaurant: $300. The look on her face when you surprise her with _: priceless.",
+ "pick":1
+ },
+ {
+ "text":"Do the Dew with our most extreme flavor yet! Get ready for Mountain Dew _!",
+ "pick":1
+ },
+ {
+ "text":"Do you lack energy? Does it sometimes feel like the whole world is _ ? Zoloft.",
+ "pick":1
+ },
+ {
+ "text":"Don't forget! Beginning this week, Casual Friday will officially become \"_ Friday.\"",
+ "pick":1
+ },
+ {
+ "text":"Get ready for the movie of the summer! One cop plays by the book. The other's only interested in one thing: _.",
+ "pick":1
+ },
+ {
+ "text":"Having the worst day EVER. #_",
+ "pick":1
+ },
+ {
+ "text":"Heed my voice, mortals! I am the god of _ , and I will not tolerate _!",
+ "pick":2
+ },
+ {
+ "text":"Help me doctor, I've got _ in my butt!",
+ "pick":1
+ },
+ {
+ "text":"Here at the Academy for Gifted Children, we all students to explore _ at their own pace.",
+ "pick":1
+ },
+ {
+ "text":"Hi MTV! My name is Kendra, I live in Malibu, I'm into _, and I love to have a good time.",
+ "pick":1
+ },
+ {
+ "text":"Hi, this is Jim from accounting. We noticed a $1,200 charge labeled \"_.\" Can you explain?",
+ "pick":1
+ },
+ {
+ "text":"I don't mean to brag, but they call me the Micheal Jordan of _.",
+ "pick":1
+ },
+ {
+ "text":"In his farewell address, George Washington famously warned Americans about the dangers of _.",
+ "pick":1
+ },
+ {
+ "text":"In his new action comedy, Jackie Chan must fend off ninjas while also dealing with _.",
+ "pick":1
+ },
+ {
+ "text":"Life's pretty tough in the fast lane. That's why I never leave the house without _.",
+ "pick":1
+ },
+ {
+ "text":"Now in bookstores: \"The Audacity of _\" by Barack Obama.",
+ "pick":1
+ },
+ {
+ "text":"Patient presents with _ . Likely a result of _ .",
+ "pick":2
+ },
+ {
+ "text":"Well if _ is good enough for _, it's good enough for me.",
+ "pick":2
+ },
+ {
+ "text":"Well what do you have to say for yourself, Casey? This is the third time you've been sent to the principal's office for _.",
+ "pick":1
+ },
+ {
+ "text":"What killed my boner?",
+ "pick":1
+ },
+ {
+ "text":"What's making things awkward in the sauna?",
+ "pick":1
+ },
+ {
+ "text":"WHOOO! God damn I love _!",
+ "pick":1
+ },
+ {
+ "text":"Why am I broke?",
+ "pick":1
+ },
+ {
+ "text":"Yo' mama's so fat she _!",
+ "pick":1
+ },
+ {
+ "text":"I work my ass off all day for this family, and this what I come home to? _!?",
+ "pick":1
+ },
+ {
+ "text":"I have a strict policy. First date, dinner. Second date, kiss. Third date, _.",
+ "pick":1
+ },
+ {
+ "text":"When I was a kid we used to play Cowboys and _.",
+ "pick":1
+ },
+ {
+ "text":"This is America. If you don't work hard, you don't succeed. I don't care if you're black, white, purple, or _.",
+ "pick":1
+ },
+ {
+ "text":"You Won't Believe These 15 Hilarious _ Bloopers!",
+ "pick":1
+ },
+ {
+ "text":"James is a lonely boy. But when he discovers a secret door in his attic, he meets a magical new friend: _.",
+ "pick":1
+ },
+ {
+ "text":"Don't worry, kid. It gets better. I've been living with _ for 20 years.",
+ "pick":1
+ },
+ {
+ "text":"My grandfather worked his way up from nothing.",
+ "pick":1
+ },
+ {
+ "text":"When he came to this country, all he had was the shoes on his feet and _.",
+ "pick":1
+ },
+ {
+ "text":"Behind every powerful man is _.",
+ "pick":1
+ },
+ {
+ "text":"You are not alone. Millions of Americans struggle with _ every day.",
+ "pick":1
+ },
+ {
+ "text":"Come to Dubai, where you can relax in our world-famous spas, experience the nightlife, or simply enjoy _ by the poolside.",
+ "pick":1
+ },
+ {
+ "text":"'This is madness!' 'No. THIS IS _!'",
+ "pick":1
+ },
+ {
+ "text":"Listen, Gary, I like you. But if you want that corner office, you're going to have to show me _.",
+ "pick":1
+ },
+ {
+ "text":"I went to the desert and ate of the peyote cactus.",
+ "pick":1
+ },
+ {
+ "text":"Turns out my spirit animal is _.",
+ "pick":1
+ },
+ {
+ "text":"And would you like those buffalo wings mild, hot, or _?",
+ "pick":1
+ },
+ {
+ "text":"The six things I could never do without: oxygen, facebook, chocolate, netflix, friends, and _ LOL!",
+ "pick":1
+ },
+ {
+ "text":"Why won't you make love to me anymore? Is it _?",
+ "pick":1
+ },
+ {
+ "text":"Puberty is a time of change. You might notice hair growing in new places. You might develop an interest in _. This is normal.",
+ "pick":1
+ },
+ {
+ "text":"I'm sorry, Mrs. Chen, but there was nothing we could do. At 4:15 this morning, your son succumbed to _.",
+ "pick":1
+ },
+ {
+ "text":"I'm Miss Tennessee, and if I could make the world better by changing one thing, I would get rid of _.",
+ "pick":1
+ },
+ {
+ "text":"Tonight, we will have sex. And afterwards, if you'd like, a little bit of _.",
+ "pick":1
+ },
+ {
+ "text":"Everybody join hands and close your eyes. Do you sense that? That's the presence of _ in this room.",
+ "pick":1
+ },
+ {
+ "text":"To become a true Yanomamo warrior, you must prove that you can withstand _ without crying out.",
+ "pick":1
+ },
+ {
+ "text":"Y'all ready to get this thing started? I'm Nick Cannon, and this is America's Got _.",
+ "pick":1
+ },
+ {
+ "text":"If you had to describe me, the Card Czar, using only one of the cards in your hand, which one would it be?",
+ "pick":1
+ },
+ {
+ "text":"I may not be much to look at, but I fuck like _.",
+ "pick":1
+ },
+ {
+ "text":"Errbody in the club _.",
+ "pick":1
+ },
+ {
+ "text":"The top Google auto-complete results for \"Barack Obama\": Barack Obama Height. Barack Obama net worth. Barack Obama _.",
+ "pick":1
+ },
+ {
+ "text":"Barack Obama Height.",
+ "pick":1
+ },
+ {
+ "text":"Barack Obama net worth.",
+ "pick":1
+ },
+ {
+ "text":"Barack Obama ___.",
+ "pick":1
+ },
+ {
+ "text":"Son, take it from someone who's been around the block a few times. Nothin' puts her in the mood like _.",
+ "pick":1
+ },
+ {
+ "text":"Art isn't just a painting in a stuffy museum. Art is alive. Art is _.",
+ "pick":1
+ },
+ {
+ "text":"You won't believe what's in my pussy. It's _.",
+ "pick":1
+ },
+ {
+ "text":"What's the most problematic?",
+ "pick":1
+ },
+ {
+ "text":"Feeling so grateful! #amazing #mylife #_.",
+ "pick":1
+ },
+ {
+ "text":"What's a total waste of Hillary Clinton's time?",
+ "pick":1
+ },
+ {
+ "text":"What's the gayest?",
+ "pick":1
+ },
+ {
+ "text":"_ be all like _.",
+ "pick":2
+ },
+ {
+ "text":"Girls just wanna have _.",
+ "pick":1
+ },
+ {
+ "text":"One more thing. Watch out for Big Mike. They say he killed a man with _.",
+ "pick":1
+ },
+ {
+ "text":"Well, shit. My eyes ain't so good, but I'll eat my own boot if that ain't _!",
+ "pick":1
+ },
+ {
+ "text":"If at first you don't succeed, try _.",
+ "pick":1
+ },
+ {
+ "text":"What will end racism once and for all?",
+ "pick":1
+ },
+ {
+ "text":"I'll take the BBQ bacon burger with friend egg and fuck it how about _.",
+ "pick":1
+ },
+ {
+ "text":"Well if _ is a crime, then lock me up!",
+ "pick":1
+ },
+ {
+ "text":"We do not shake with our left hands in this country. That is the hand we use for _.",
+ "pick":1
+ },
+ {
+ "text":"You know who else liked _? Hitler.",
+ "pick":1
+ },
+ {
+ "text":"Poor Brandon, still living in his parent's basement. I heard he never got over _.",
+ "pick":1
+ },
+ {
+ "text":"What totally destroyed my asshole?",
+ "pick":1
+ },
+ {
+ "text":"I don't believe in God. I believe in _.",
+ "pick":1
+ },
+ {
+ "text":"Then the princess kissed the frog, and all of a sudden the frog was _!",
+ "pick":1
+ },
+ {
+ "text":"I got rhythm, I've got music, I've got _. Who could ask for anything more?",
+ "pick":1
+ },
+ {
+ "text":"No, no, no, no, no, no, NO! I will NOT let _ ruin this wedding.",
+ "pick":1
+ },
+ {
+ "text":"Coming to Red Lobster this month, _.",
+ "pick":1
+ },
+ {
+ "text":"Ooo, daddy like _.",
+ "pick":1
+ },
+ {
+ "text":"Best you go back where you came from, now. We don't take too kindly to _ in these parts.",
+ "pick":1
+ },
+ {
+ "text":"Summer lovin', had me a blast. _, happened so fast.",
+ "pick":1
+ },
+ {
+ "text":"LSD + _ = really bad time.",
+ "pick":1
+ },
+ {
+ "text":"What are all those whales singing about?",
+ "pick":1
+ },
+ {
+ "text":"As Teddy Roosevelt said, the four manly virtues are honor, temperance, industry, and _.",
+ "pick":1
+ },
+ {
+ "text":"I tell you, it was a non-stop fuckfest. When it was over, my asshole looked like _.",
+ "pick":1
+ },
+ {
+ "text":"What turned me into a Republican?",
+ "pick":1
+ },
+ {
+ "text":"I'm sorry, sir, but your insurance plan doesn't cover injuries caused by _.",
+ "pick":1
+ },
+ {
+ "text":"Run, run, as fast as you can! You can't catch me, I'm _!",
+ "pick":1
+ },
+ {
+ "text":"There is no God. It's just _ and then you die.",
+ "pick":1
+ },
+ {
+ "text":"She's just one of the guys, you know? She likes beer, and football, and _.",
+ "pick":1
+ },
+ {
+ "text":"This Friday at the Liquid Lunge, it's _ Night! Ladies drink free.",
+ "pick":1
+ },
+ {
+ "text":"What sucks balls?",
+ "pick":1
+ },
+ {
+ "text":"I've had a horrible vision, father. I saw mountains crumbling, stars falling from the sky. I saw _.",
+ "pick":1
+ },
+ {
+ "text":"Oh no! Siri, how do I fix _?",
+ "pick":1
+ },
+ {
+ "text":"Dance like there's nobody watching, love like you'll never be hurt, and live like you're _.",
+ "pick":1
+ },
+ {
+ "text":"Mom's to-do list: Buy Groceries Clean up ___. Soccer Practice.",
+ "pick":1
+ },
+ {
+ "text":"Why am I laughing and crying and taking off my clothes?",
+ "pick":1
+ },
+ {
+ "text":"She's a lady in the streets, _ in the sheets.",
+ "pick":1
+ },
+ {
+ "text":"What's about to take dance floor to the next level?",
+ "pick":1
+ },
+ {
+ "text":"CNN breaking news! Scientists discover _.",
+ "pick":1
+ },
+ {
+ "text":"Most Americans would not vote for a candidate who is openly _.",
+ "pick":1
+ },
+ {
+ "text":"Congratulations! You have been selected for our summer internship program. While we are unable to offer a salary, we can offer you _.",
+ "pick":1
+ },
+ {
+ "text":"In the 1950s, psychologists prescribed _ as a cure for homosexually.",
+ "pick":1
+ },
+ {
+ "text":"As reparations for slavery, all African Americans will receive _.",
+ "pick":1
+ },
+ {
+ "text":"With a one-time gift of just $10, you can save this child from _.",
+ "pick":1
+ },
+ {
+ "text":"Google Calendar alert: _ in 10 minutes.",
+ "pick":1
+ }
+ ],
+ "whiteCards":[
+ "Coat hanger abortions.",
+ "Man meat.",
+ "Autocannibalism.",
+ "Vigorous jazz hands.",
+ "Flightless birds.",
+ "Pictures of boobs.",
+ "Doing the right thing.",
+ "The violation of our most basic human rights.",
+ "Viagra.",
+ "Self-loathing.",
+ "Spectacular abs.",
+ "A balanced breakfast.",
+ "Roofies.",
+ "Concealing a boner.",
+ "Amputees.",
+ "The Big Bang.",
+ "Former President George W. Bush.",
+ "The Rev. Dr. Martin Luther King, Jr.",
+ "Smegma.",
+ "Being marginalized.",
+ "Cuddling.",
+ "Laying an egg.",
+ "The Pope.",
+ "Aaron Burr.",
+ "Genital piercings.",
+ "Fingering.",
+ "A bleached asshole.",
+ "Horse meat.",
+ "Fear itself.",
+ "Science.",
+ "Elderly Japanese men.",
+ "Stranger danger.",
+ "The terrorists.",
+ "Praying the gay away.",
+ "Same-sex ice dancing.",
+ "Ethnic cleansing.",
+ "Cheating in the Special Olympics.",
+ "German dungeon porn.",
+ "Bingeing and purging.",
+ "Making a pouty face.",
+ "William Shatner.",
+ "Heteronormativity.",
+ "Nickelback.",
+ "Tom Cruise.",
+ "The profoundly handicapped.",
+ "The placenta.",
+ "Chainsaws for hands.",
+ "Arnold Schwarzenegger.",
+ "An icepick lobotomy.",
+ "Goblins.",
+ "Object permanence.",
+ "Dying.",
+ "Foreskin.",
+ "A falcon with a cap on its head.",
+ "Hormone injections.",
+ "Dying of dysentery.",
+ "Sexy pillow fights.",
+ "The invisible hand.",
+ "A really cool hat.",
+ "Sean Penn.",
+ "Heartwarming orphans.",
+ "The clitoris.",
+ "The Three-Fifths compromise.",
+ "A sad handjob.",
+ "Men.",
+ "Historically black colleges.",
+ "A micropenis.",
+ "Raptor attacks.",
+ "Agriculture.",
+ "Vikings.",
+ "Pretending to care.",
+ "The Underground Railroad.",
+ "My humps.",
+ "Being a dick to children.",
+ "Geese.",
+ "Bling.",
+ "Sniffing glue.",
+ "The South.",
+ "An Oedipus complex.",
+ "Eating all of the cookies before the AIDS bake-sale.",
+ "Sexting.",
+ "YOU MUST CONSTRUCT ADDITIONAL PYLONS.",
+ "Mutually-assured destruction.",
+ "Sunshine and rainbows.",
+ "Count Chocula.",
+ "Sharing needles.",
+ "Being rich.",
+ "Skeletor.",
+ "A sausage festival.",
+ "Michael Jackson.",
+ "Emotions.",
+ "Farting and walking away.",
+ "The Chinese gymnastics team.",
+ "Necrophilia.",
+ "Spontaneous human combustion.",
+ "Yeast.",
+ "Leaving an awkward voicemail.",
+ "Dick Cheney.",
+ "White people.",
+ "Penis envy.",
+ "Teaching a robot to love.",
+ "Sperm whales.",
+ "Scrubbing under the folds.",
+ "Panda sex.",
+ "Whipping it out.",
+ "Catapults.",
+ "Masturbation.",
+ "Natural selection.",
+ "Opposable thumbs.",
+ "A sassy black woman.",
+ "AIDS.",
+ "The KKK.",
+ "Figgy pudding.",
+ "Seppuku.",
+ "Gandhi.",
+ "Preteens.",
+ "Toni Morrison's vagina.",
+ "Five-Dollar Footlongs.",
+ "Land mines.",
+ "A sea of troubles.",
+ "A zesty breakfast burrito.",
+ "Christopher Walken.",
+ "Friction.",
+ "Balls.",
+ "Dental dams.",
+ "A can of whoop-ass.",
+ "A tiny horse.",
+ "Waiting 'til marriage.",
+ "Authentic Mexican cuisine.",
+ "Genghis Khan.",
+ "Old-people smell.",
+ "Feeding Rosie O'Donnell.",
+ "Pixelated bukkake.",
+ "Friends with benefits.",
+ "The token minority.",
+ "The Tempur-Pedic Swedish Sleep System.",
+ "A thermonuclear detonation.",
+ "Take-backsies.",
+ "The Rapture.",
+ "A cooler full of organs.",
+ "Sweet, sweet vengeance.",
+ "RoboCop.",
+ "Keanu Reeves.",
+ "Drinking alone.",
+ "Giving 110%.",
+ "Flesh-eating bacteria.",
+ "The American Dream.",
+ "Taking off your shirt.",
+ "Me time.",
+ "A murder most foul.",
+ "The inevitable heat death of the universe.",
+ "The folly of man.",
+ "That thing that electrocutes your abs.",
+ "Cards Against Humanity.",
+ "Fiery poops.",
+ "Poor people.",
+ "Edible underpants.",
+ "Britney Spears at 55.",
+ "All-you-can-eat shrimp for $4.99.",
+ "Pooping back and forth. Forever.",
+ "Fancy Feast.",
+ "Jewish fraternities.",
+ "Being a motherfucking sorcerer.",
+ "Pulling out.",
+ "Picking up girls at the abortion clinic.",
+ "The homosexual agenda.",
+ "The Holy Bible.",
+ "Passive-agression.",
+ "Ronald Reagan.",
+ "Vehicular manslaughter.",
+ "Nipple blades.",
+ "Assless chaps.",
+ "Full frontal nudity.",
+ "Hulk Hogan.",
+ "Daddy issues.",
+ "The hardworking Mexican.",
+ "Natalie Portman.",
+ "Waking up half-naked in a Denny's parking lot.",
+ "God.",
+ "Sean Connery.",
+ "Saxophone solos.",
+ "Gloryholes.",
+ "The World of Warcraft.",
+ "Homeless people.",
+ "Scalping.",
+ "Darth Vader.",
+ "Eating the last known bison.",
+ "Guys who don't call.",
+ "Hot Pockets.",
+ "A time travel paradox.",
+ "The milk man.",
+ "Testicular torsion.",
+ "Dropping a chandelier on your enemies and riding the rope up.",
+ "World peace.",
+ "A salty surprise.",
+ "Poorly-timed Holocaust jokes.",
+ "Smallpox blankets.",
+ "Licking things to claim them as your own.",
+ "The heart of a child.",
+ "Robert Downey, Jr.",
+ "Lockjaw.",
+ "Eugenics.",
+ "A good sniff.",
+ "Friendly fire.",
+ "The taint; the grundle; the fleshy fun-bridge.",
+ "Wearing underwear inside-out to avoid doing laundry.",
+ "Hurricane Katrina.",
+ "Free samples.",
+ "Jerking off into a pool of children's tears.",
+ "A foul mouth.",
+ "The glass ceiling.",
+ "Republicans.",
+ "Explosions.",
+ "Michelle Obama's arms.",
+ "Getting really high.",
+ "Attitude.",
+ "Sarah Palin.",
+ "The Ubermensch.",
+ "Altar boys.",
+ "My soul.",
+ "My sex life.",
+ "Pedophiles.",
+ "72 virgins.",
+ "Pabst Blue Ribbon.",
+ "Domino's Oreo Dessert Pizza.",
+ "A snapping turtle biting the tip of your penis.",
+ "The Blood of Christ.",
+ "Half-assed foreplay.",
+ "My collection of high-tech sex toys.",
+ "A middle-aged man on roller skates.",
+ "Bitches.",
+ "Bill Nye the Science Guy.",
+ "Italians.",
+ "A windmill full of corpses.",
+ "Adderall.",
+ "Crippling debt.",
+ "A stray pube.",
+ "Prancing.",
+ "Passing a kidney stone.",
+ "A brain tumor.",
+ "Leprosy.",
+ "Puppies!",
+ "Bees?",
+ "Frolicking.",
+ "Repression.",
+ "Road head.",
+ "A bag of magic beans.",
+ "An asymmetric boob job.",
+ "Dead parents.",
+ "Public ridicule.",
+ "A mating display.",
+ "A mime having a stroke.",
+ "Stephen Hawking talking dirty.",
+ "African children.",
+ "Mouth herpes.",
+ "Overcompensation.",
+ "Riding off into the sunset.",
+ "Being on fire.",
+ "Tangled Slinkys.",
+ "Civilian casualties.",
+ "Auschwitz.",
+ "My genitals.",
+ "Not reciprocating oral sex.",
+ "Lactation.",
+ "Being fabulous.",
+ "Shaquille O'Neal's acting career.",
+ "My relationship status.",
+ "Asians who aren't good at math.",
+ "Alcoholism.",
+ "Incest.",
+ "Grave robbing.",
+ "Hope.",
+ "8 oz. of sweet Mexican black-tar heroin.",
+ "Kids with ass cancer.",
+ "Winking at old people.",
+ "The Jews.",
+ "Justin Bieber.",
+ "Doin' it in the butt.",
+ "A lifetime of sadness.",
+ "The Hamburglar.",
+ "Swooping.",
+ "Classist undertones.",
+ "New Age music.",
+ "Not giving a shit about the Third World.",
+ "The Kool-Aid Man.",
+ "A hot mess.",
+ "Tentacle porn.",
+ "Lumberjack fantasies.",
+ "The gays.",
+ "Scientology.",
+ "Estrogen.",
+ "GoGurt.",
+ "Judge Judy.",
+ "Dick fingers.",
+ "Racism.",
+ "Surprise sex!",
+ "Police brutality.",
+ "Passable transvestites.",
+ "The Virginia Tech Massacre.",
+ "When you fart and a little bit comes out.",
+ "Oompa-Loompas.",
+ "A fetus.",
+ "Obesity.",
+ "Tasteful sideboob.",
+ "Hot people.",
+ "BATMAN!!!",
+ "Black people.",
+ "A gassy antelope.",
+ "Sexual tension.",
+ "Third base.",
+ "Racially-biased SAT questions.",
+ "Porn stars.",
+ "A Super Soaker full of cat pee.",
+ "Muhammed (Praise Be Unto Him).",
+ "Puberty.",
+ "A disappointing birthday party.",
+ "An erection that lasts longer than four hours.",
+ "White privilege.",
+ "Getting so angry that you pop a boner.",
+ "Wifely duties.",
+ "Two midgets shitting into a bucket.",
+ "Queefing.",
+ "Wiping her butt.",
+ "Golden showers.",
+ "Barack Obama.",
+ "Nazis.",
+ "A robust mongoloid.",
+ "An M. Night Shyamalan plot twist.",
+ "Getting drunk on mouthwash.",
+ "Lunchables.",
+ "Women in yogurt commercials.",
+ "John Wilkes Booth.",
+ "Powerful thighs.",
+ "Mr. Clean, right behind you.",
+ "Multiple stab wounds.",
+ "Cybernetic enhancements.",
+ "Serfdom.",
+ "Kanye West.",
+ "Women's suffrage.",
+ "Children on leashes.",
+ "Harry Potter erotica.",
+ "The Dance of the Sugar Plum Fairy.",
+ "Lance Armstrong's missing testicle.",
+ "Parting the Red Sea.",
+ "The Amish.",
+ "Dead babies.",
+ "Child beauty pageants.",
+ "AXE Body Spray.",
+ "Centaurs.",
+ "Copping a feel.",
+ "Grandma.",
+ "Famine.",
+ "The Trail of Tears.",
+ "The miracle of childbirth.",
+ "Finger painting.",
+ "A monkey smoking a cigar.",
+ "The Make-A-Wish Foundation.",
+ "Anal beads.",
+ "The Force.",
+ "Kamikaze pilots.",
+ "Dry heaving.",
+ "Active listening.",
+ "Ghosts.",
+ "The Hustle.",
+ "Peeing a little bit.",
+ "Another goddamn vampire movie.",
+ "Shapeshifters.",
+ "The Care Bear Stare.",
+ "Hot cheese.",
+ "A mopey zoo lion.",
+ "A defective condom.",
+ "Teenage pregnancy.",
+ "A Bop It.",
+ "Expecting a burp and vomiting on the floor.",
+ "Horrifying laser hair removal accidents.",
+ "Boogers.",
+ "Unfathomable stupidity.",
+ "Breaking out into song and dance.",
+ "Soup that is too hot.",
+ "Morgan Freeman's voice.",
+ "Getting naked and watching Nickelodeon.",
+ "MechaHitler.",
+ "Flying sex snakes.",
+ "The true meaning of Christmas.",
+ "My inner demons.",
+ "Pac-Man uncontrollably guzzling cum.",
+ "My vagina.",
+ "A homoerotic volleyball montage.",
+ "Actually taking candy from a baby.",
+ "Crystal meth.",
+ "Exactly what you'd expect.",
+ "Natural male enhancement.",
+ "Passive-aggressive Post-it notes.",
+ "Inappropriate yodeling.",
+ "Lady Gaga.",
+ "The Little Engine That Could.",
+ "Vigilante justice.",
+ "A death ray.",
+ "Poor life choices.",
+ "A gentle caress of the inner thigh.",
+ "Embryonic stem cells.",
+ "Nicolas Cage.",
+ "Firing a rifle into the air while balls deep in a squealing hog.",
+ "Switching to Geico.",
+ "The chronic.",
+ "Erectile dysfunction.",
+ "Home video of Oprah sobbing into a Lean Cuisine.",
+ "A bucket of fish heads.",
+ "50,000 volts straight to the nipples.",
+ "Being fat and stupid.",
+ "Hospice care.",
+ "A pyramid of severed heads.",
+ "Getting married, having a few kids, buying some stuff, retiring to Florida, and dying.",
+ "A subscription to Men's Fitness.",
+ "Crucifixion.",
+ "A micropig wearing a tiny raincoat and booties.",
+ "Some god-damn peace and quiet.",
+ "Used panties.",
+ "A tribe of warrior women.",
+ "The penny whistle solo from \"My Heart Will Go On.\"",
+ "An oversized lollipop.",
+ "Helplessly giggling at the mention of Hutus and Tutsis.",
+ "Not wearing pants.",
+ "Consensual sex.",
+ "Her Majesty, Queen Elizabeth II.",
+ "Funky fresh rhymes.",
+ "The art of seduction.",
+ "The Devil himself.",
+ "Advice from a wise, old black man.",
+ "Destroying the evidence.",
+ "The light of a billion suns.",
+ "Wet dreams.",
+ "Synergistic management solutions.",
+ "Growing a pair.",
+ "Silence.",
+ "An M16 assault rifle.",
+ "Poopy diapers.",
+ "A live studio audience.",
+ "The Great Depression.",
+ "A spastic nerd.",
+ "Rush Limbaugh's soft, shitty body.",
+ "Tickling Sean Hannity, even after he tells you to stop.",
+ "Stalin.",
+ "Brown people.",
+ "Rehab.",
+ "Capturing Newt Gingrich and forcing him to dance in a monkey suit.",
+ "Battlefield amputations.",
+ "An uppercut.",
+ "Shiny objects.",
+ "An ugly face.",
+ "Menstrual rage.",
+ "A bitch slap.",
+ "One trillion dollars.",
+ "Chunks of dead prostitute.",
+ "The entire Mormon Tabernacle Choir.",
+ "The female orgasm.",
+ "Extremely tight pants.",
+ "The Boy Scouts of America.",
+ "Stormtroopers.",
+ "Throwing a virgin into a volcano.",
+ "24-hour media coverage",
+ "A beached whale.",
+ "A big black dick.",
+ "A bloody pacifier.",
+ "A crappy little hand.",
+ "A fat bald man from the internet.",
+ "A low standard of living.",
+ "A nuanced critique.",
+ "A panty raid.",
+ "A passionate Latino lover.",
+ "A plunger to the face.",
+ "A rival dojo.",
+ "A smiling black man, a latina businesswoman, a cool asian, and some whites.",
+ "A web of lies.",
+ "A woman scorned.",
+ "An atomic wedgie.",
+ "An Etsy steampunk strap-on.",
+ "An evil man in evil clothes.",
+ "AndrÈ the Giant's enormous, leathery scrotum.",
+ "Apologizing.",
+ "Appreciative snapping.",
+ "Ashton Kutcher.",
+ "Beating your wives.",
+ "Being a busy adult with many important things to do.",
+ "Being a dinosaur.",
+ "Blaxploitation.",
+ "Bosnian chicken farmers.",
+ "Breaking nip slip news.",
+ "Carnies.",
+ "Clams.",
+ "Clenched butt cheeks.",
+ "Coughing into a vagina.",
+ "Cutting.",
+ "Dancing with a broom.",
+ "Deflowering a princess.",
+ "Deflowering the princess.",
+ "Dorito breath.",
+ "Eating an albino.",
+ "Enormous Scandinavian women.",
+ "Fabricating statistics.",
+ "Finding a skeleton.",
+ "Gandalf.",
+ "Genetically engineered super-soldiers.",
+ "George Clooney's musk.",
+ "Getting abducted by Peter Pan.",
+ "Getting in her pants, politely.",
+ "Gladiatorial combat.",
+ "Good grammar.",
+ "having a penis",
+ "Hipsters.",
+ "Historical revisionism.",
+ "Insatiable bloodlust.",
+ "Jafar.",
+ "Jean-Claude Van Damme in slow motion.",
+ "Jean-Claude Van Damme.",
+ "Just the tip.",
+ "Leveling up.",
+ "Literally eating shit.",
+ "Mad hacky-sack skills.",
+ "Making the penises kiss.",
+ "Media coverage.",
+ "Medieval Times Dinner & Tournament.",
+ "Mom.",
+ "Moral ambiguity.",
+ "My machete.",
+ "Neil Patrick Harris.",
+ "NOOOOOOOOO!!!",
+ "Nubile slave boys.",
+ "Ominous background music.",
+ "One thousand Slim Jims.",
+ "Overpowering your father.",
+ "Panty raids.",
+ "Pistol-whipping a hostage.",
+ "Quiche.",
+ "Quivering jowls.",
+ "Revenge fucking.",
+ "Ripping into a man's chest and pulling out his still-beating heart.",
+ "Ryan Gosling riding in on a white horse.",
+ "Salvia.",
+ "Sanding off a man's nose.",
+ "Santa Claus.",
+ "Savagely beating a mascot",
+ "Scrotum tickling.",
+ "Sexual humiliation.",
+ "Sexy Siamese twins.",
+ "Shaft.",
+ "Slow motion.",
+ "Space muffins.",
+ "Statistically validated stereotypes.",
+ "Stockholm syndrome",
+ "Sudden Poop Explosion Disease.",
+ "Suicidal thoughts.",
+ "Syphilitic insanity",
+ "The boners of the elderly.",
+ "The economy.",
+ "The Fanta girls.",
+ "The four arms of Vishnu.",
+ "The gulags.",
+ "The harsh light of day.",
+ "The hiccups.",
+ "The ooze",
+ "The shambling corpse of Larry King.",
+ "This guy!",
+ "Tripping balls.",
+ "Walking in on Dad peeing into Mom's mouth.",
+ "Words, words, words.",
+ "Zeus's sexual appetites.",
+ "A 55-gallon drum of lube.",
+ "A bigger, blacker dick.",
+ "A Burmese tiger pit.",
+ "A dollop of sour cream.",
+ "A fortuitous turnip harvest.",
+ "A magic hippie love cloud.",
+ "A man in yoga pants with a ponytail and feather earrings.",
+ "A piÒata full of scorpions",
+ "A sad fat dragon with no friends.",
+ "A slightly shittier parallel universe.",
+ "A sofa that says \"I have style, but I like to be comfortable.\"",
+ "A soulful rendition of \"Ol' Man River.\"",
+ "A squadron of moles wearing aviator goggles.",
+ "A sweaty, panting leather daddy.",
+ "A sweet spaceship.",
+ "All of this blood.",
+ "An army of skeletons.",
+ "An ether-soaked rag.",
+ "An unhinged ferris wheel rolling toward the sea.",
+ "Another shot of morphine.",
+ "Basic human decency.",
+ "Beefin' over turf.",
+ "Being awesome at sex.",
+ "Boris the Soviet Love Hammer.",
+ "Bullshit.",
+ "Catastrophic urethral trauma.",
+ "Crushing Mr. Peanut's brittle body.",
+ "Daddy's belt.",
+ "Death by Steven Seagal.",
+ "Dennis the Menace.",
+ "Dining with cardboard cutouts of the cast of \"Friends.\"",
+ "Double penetration.",
+ "Existing.",
+ "Fetal alcohol syndrome.",
+ "Finding Waldo.",
+ "Fuck Mountain.",
+ "Getting hilariously gang-banged by the Blue Man Group.",
+ "Grandpa's ashes.",
+ "Graphic violence, adult language, and some sexual content.",
+ "Hillary Clinton's death stare.",
+ "Intimacy problems.",
+ "Jeff Goldblum.",
+ "Living in a trashcan.",
+ "Loki, the trickster god.",
+ "Making a friend.",
+ "Maximal insertion.",
+ "Me.",
+ "Mild autism.",
+ "Mooing.",
+ "My first kill.",
+ "Nunchuck moves.",
+ "Oncoming traffic.",
+ "One Ring to rule them all.",
+ "Power",
+ "Pretty Pretty Princess Board Game.",
+ "Pumping out a baby every nine months.",
+ "Rising from the grave.",
+ "Scrotal frostbite.",
+ "Some really fucked-up shit.",
+ "Special musical guest, Cher.",
+ "Spring break!",
+ "Subduing a grizzly bear and making her your wife.",
+ "Survivor's guilt.",
+ "Swiftly achieving orgasm.",
+ "Taking a man's eyes and balls out and putting his eyes where his balls go and then his balls in the eye holes.",
+ "The black Power Ranger",
+ "The corporations.",
+ "The day the birds attacked.",
+ "The Google.",
+ "The grey nutrient broth that sustains Mitt Romney.",
+ "The human body.",
+ "The mere concept of Applebee's.",
+ "The mixing of the races.",
+ "The new Radiohead album.",
+ "Tiny nipples.",
+ "Tongue.",
+ "Upgrading homeless people to mobile hotspots.",
+ "Weapons-grade plutonium.",
+ "Wearing an octopus for a hat.",
+ "Whining like a little bitch.",
+ "Whipping a disobedient slave.",
+ "That ass.",
+ "Nothing.",
+ "Shutting the fuck up.",
+ "The primal, ball-slapping sex your parents are having right now.",
+ "A cat video so cute that your eyes roll back and your spine slides out of your anus.",
+ "Cock.",
+ "A cop who is also a dog.",
+ "Dying alone and in pain.",
+ "Gay aliens.",
+ "The way white people is.",
+ "Reverse cowgirl.",
+ "The Quesadilla Explosion Salad from Chili's.",
+ "Actually getting shot, for real.",
+ "Not having sex.",
+ "Vietnam flashbacks.",
+ "Running naked through a mall, pissing and shitting everywhere.",
+ "Warm, velvety muppet sex.",
+ "Self-flagellation.",
+ "The systematic destruction of an entire people and their way of life.",
+ "Samuel L. Jackson.",
+ "A boo-boo.",
+ "Going around punching people.",
+ "The entire Internet.",
+ "Some kind of bird-man.",
+ "Chugging a lava lamp.",
+ "Having sex on top of a pizza.",
+ "Indescribable loneliness.",
+ "An ass disaster.",
+ "All my friends dying.",
+ "Putting an entire peanut butter and jelly sandwich into the VCR.",
+ "Spending lots of money.",
+ "Some douche with an acoustic guitar.",
+ "Flying robots that kill people.",
+ "A greased-up Matthew McConaughey.",
+ "An unstoppable wave of fire ants.",
+ "Not contributing to society in any meaningful way.",
+ "An all-midget production of Shakespeare's Richard III.",
+ "Screaming like a maniac.",
+ "The moist, demanding chasm of his mouth.",
+ "Filling every orifice with butterscotch pudding.",
+ "Unlimited soup, salad, and breadsticks.",
+ "Crying into the pages of Sylvia Plath.",
+ "Velcro.",
+ "A PowerPoint presentation.",
+ "A surprising amount of hair.",
+ "Eating Tom Selleck's mustache to gain his powers.",
+ "Roland the Farter, flatulist to the king.",
+ "A pile of squirming bodies.",
+ "Buying the right pants to be, cool.",
+ "Blood farts.",
+ "Three months in the hole.",
+ "A botched circumcision.",
+ "The Land of Chocolate.",
+ "Slapping a racist old lady.",
+ "A lamprey swimming up the toilet and latching onto your taint.",
+ "Jumping out at people.",
+ "A black male in his early 20s, last seen wearing a hoodie.",
+ "Mufasa's death scene.",
+ "Bill Clinton, naked on a bearskin rug with a saxophone.",
+ "Demonic possession.",
+ "The Harlem Globetrotters.",
+ "Vomiting mid-blowjob.",
+ "My manservant, Claude.",
+ "Having shotguns for legs.",
+ "Letting everyone down.",
+ "A spontaneous conga line.",
+ "A vagina that leads to another dimension.",
+ "Disco fever.",
+ "Getting your dick stuck in a Chinese finger trap with another dick.",
+ "Fisting.",
+ "The thin veneer of situational causality that underlies porn.",
+ "Girls that always be textin'.",
+ "Blowing some dudes in an alley.",
+ "Drinking ten 5-hour ENERGYs to get fifty continuous hours of energy.",
+ "Sneezing, farting, and coming at the same time.",
+ "A bunch of idiots playing a card game instead of interacting like normal humans.",
+ "A sex goblin with a carnival penis.",
+ "Lots and lots of abortions.",
+ "Injecting speed into one arm and horse tranquilizer into the other.",
+ "Sharks with legs.",
+ "A sex comet from Neptune that plunges the Earth into eternal sexiness.",
+ "How awesome I am.",
+ "Smoking crack, for instance.",
+ "A dance move that's just sex.",
+ "A hopeless amount of spiders.",
+ "Drinking responsibly.",
+ "Angelheaded hipsters burning for the ancient heavenly connection to the starry dynamo in the machinery of the night.",
+ "Bouncing up and down.",
+ "A shiny rock that proves I love you.",
+ "Crazy opium eyes.",
+ "Moderate-to-severe joint pain.",
+ "Actual mutants with medical conditions and no superpowers.",
+ "The complex geopolitical quagmire that is the Middle East.",
+ "Finally finishing off the Indians.",
+ "Neil Diamond's Greatest Hits.",
+ "No clothes on, penis in vagina.",
+ "Whispering all sexy.",
+ "A horse with no legs.",
+ "Depression.",
+ "Almost giving money to a homeless person.",
+ "Interspecies marriage.",
+ "Blackula.",
+ "What Jesus would do.",
+ "A manhole.",
+ "My dad's dumb fucking face.",
+ "A Ugandan warlord.",
+ "My worthless son.",
+ "A Native American who solves crimes by going into the spirit world.",
+ "A kiss on the lips.",
+ "A fart.",
+ "The peaceful and nonthreatening rise of China.",
+ "Snorting coke off a clown's boner.",
+ "Three consecutive seconds of happiness.",
+ "Falling into the toilet.",
+ "Ass to mouth.",
+ "Some sort of Asian.",
+ "The size of my penis.",
+ "The safe word.",
+ "Party Mexicans.",
+ "Ambiguous sarcasm.",
+ "Jizz.",
+ "An interracial handshake.",
+ "Incredible Facts About the Anus.",
+ "The secret formula for ultimate female satisfaction.",
+ "Sugar madness.",
+ "Calculating every mannerism so as not to suggest homosexuality.",
+ "Fucking a corpse back to life.",
+ "All the single ladies.",
+ "Whatever a McRib is made of.",
+ "Africa.",
+ "The euphoric rush of strangling a drifter.",
+ "Khakis.",
+ "A gender identity that can only be conveyed through slam poetry.",
+ "Stuffing a child's face with Fun Dip until he starts having fun.",
+ "A for-real lizard that spits blood from its eyes.",
+ "The tiniest shred of evidence that God is real.",
+ "Prince Ali, fabulous he, Ali Ababwa.",
+ "Dem titties.",
+ "Exploding pigeons.",
+ "My sex dungeon.",
+ "Child Protective Services.",
+ "Doo-doo.",
+ "Sports.",
+ "Unquestioning obedience.",
+ "Grammar nazis who are also regular Nazis.",
+ "40 acres and a mule.",
+ "A crazy little thing called love.",
+ "A disappointing salad.",
+ "A face full of horse cum.",
+ "A giant powdery manbaby.",
+ "A mouthful of potato salad.",
+ "A one-way ticket to Gary, Indiana.",
+ "A powered exoskeleton.",
+ "A reason not to commit suicide.",
+ "A team of lawyers.",
+ "A whole new kind of porn.",
+ "A zero-risk way to make $2,000 from home.",
+ "AIDS monkeys.",
+ "All these decorative pillows.",
+ "An unforgettable quinceanera.",
+ "An uninterrupted history of imperialism and exploitation.",
+ "Anal fissures like you wouldn't believe.",
+ "Ancient Athenian boy-fucking.",
+ "Backwards knees.",
+ "Being nine years old.",
+ "Being paralyzed from the neck down.",
+ "Being worshiped as the one true God.",
+ "Blackface.",
+ "Blowjobs for everyone.",
+ "Boring Vaginal sex.",
+ "Butt stuff.",
+ "Changing a person's mind with logic and facts.",
+ "Child support payments.",
+ "Cutting off a flamingo's legs with garden shears.",
+ "Daddy's credit card.",
+ "Deez nuts.",
+ "Denzel.",
+ "Doing the right stuff to her nipples.",
+ "Ejaculating live bees and the bees are angry.",
+ "Ennui.",
+ "Figuring out how to have sex with a dolphin.",
+ "Free ice cream, yo.",
+ "Genghis Khan's DNA.",
+ "Getting caught by the police and going to jail.",
+ "Getting drive-by shot.",
+ "Getting eaten alive by Guy Fieri.",
+ "Giant sperm from outer space.",
+ "Going to a high school reunion on ketamine.",
+ "Having been dead a while.",
+ "Mom's new boyfriend.",
+ "My boyfriends stupid penis.",
+ "My dead son's baseball glove.",
+ "My first period.",
+ "Not believing in giraffes.",
+ "Oil!",
+ "Out-of-this-world bazongas.",
+ "P.F. Chang himself.",
+ "Russian super-tuberculosis.",
+ "Seeing my village burned and my family slaughtered before my eyes.",
+ "Seeing things from Hitler's perspective.",
+ "September 11th, 2001.",
+ "Slowly easing down onto a cucumber.",
+ "Social justice warriors with flamethrowers of compassion.",
+ "Some shit-hot guitar licks.",
+ "The Abercrombie & Fitch lifestyle.",
+ "The basic suffering that pervades all of existence.",
+ "The eight gay warlocks who dictate the rules of fashion.",
+ "The ghost of Marlon Brando.",
+ "The inability to form meaningful relationships.",
+ "The passage of time.",
+ "The swim team, all at once.",
+ "The tiger that killed my father.",
+ "The unbelievable world of mushrooms.",
+ "The black half of Barack Obama.",
+ "The white half of Barack Obama.",
+ "Too much cocaine.",
+ "Unrelenting genital punishment.",
+ "Vegetarian options.",
+ "Wearing glasses and sounding smart.",
+ "Western standards of beauty.",
+ "A bass drop so huge it tears the starry vault asunder to reveal the face of God.",
+ "Growing up chained to a radiator in perpetual darkness.",
+ "Shitting all over the floor like a bad, bad girl.",
+ "A buttload of candy.",
+ "Sucking all the milk out of a yak.",
+ "Bullets.",
+ "A man who is so cool that he rides on a motorcycle.",
+ "Sudden penis loss.",
+ "Getting all offended.",
+ "Crying and shitting and eating spaghetti.",
+ "One unforgettable night of passion.",
+ "Being popular and good at sports.",
+ "Filling a man's anus with concrete.",
+ "Two whales fucking the shit out of each other.",
+ "Cool, relatable cancer teens.",
+ "The amount of gay I am.",
+ "A possible Muslim.",
+ "Unsheathing my massive horse cock.",
+ "A bowl of gourds.",
+ "The male gaze.",
+ "The power of the Dark Side.",
+ "Ripping a dog in half.",
+ "A constant need for validation.",
+ "Meaningless sex.",
+ "Such a big boy.",
+ "Throwing stones at a man until he dies.",
+ "Cancer.",
+ "Like a million alligators.",
+ "Eating together like a god damn family for once.",
+ "Cute boys.",
+ "Pussy.",
+ "Being a terrible mother.",
+ "Never having sex again.",
+ "A pizza guy who fucked up.",
+ "A whole lotta woman.",
+ "The all-new Nissan Pathfinder with 0.9% APR financing!",
+ "A peyote-fueled vision quest.",
+ "Kale.",
+ "Breastfeeding a ten year old.",
+ "Crippling social anxiety.",
+ "Immortality cream.",
+ "Texas.",
+ "Teaching a girl how to handjob the penis.",
+ "A turd.",
+ "Shapes and colors.",
+ "Whatever you wish, mother.",
+ "The haunting stare of an Iraqi child.",
+ "Robots who just want to party.",
+ "A self-microwaving burrito.",
+ "Forgetting grandma's first name.",
+ "Our new Buffalo Chicken Dippers!",
+ "Treasures beyond your wildest dreams.",
+ "Getting shot out of a cannon.",
+ "The sweet song of sword against sword and the braying of mighty war beasts.",
+ "Walking into a glass door.",
+ "The color \"puce.\"",
+ "Every ounce of charisma left in Mick Jagger's tired body.",
+ "The eighth graders.",
+ "Setting my balls on fire and cartwheeling to Ohio.",
+ "The dentist.",
+ "Gwyneth Paltrow's opinions.",
+ "Turning the rivers red with the blood of infidels.",
+ "Rabies.",
+ "Important news about Taylor Swift.",
+ "Ejaculating inside another man's wife.",
+ "Owls, the perfect predator.",
+ "Being John Malkovich.",
+ "Bathing in moonsblood and dancing around the ancient oak.",
+ "An oppressed people with a vibrant culture.",
+ "An overwhelming variety of cheeses.",
+ "Reading the entire End-User License Agreement.",
+ "Morpheus.",
+ "Peeing into a girl's butt to make a baby.",
+ "Generally having no idea what's going on.",
+ "No longer finding any Cards Against Humanity card funny.",
+ "Content.",
+ "Fucking me good and taking me to Red Lobster.",
+ "Self-identifying as a DJ.",
+ "Getting high with mom.",
+ "Beyonce.",
+ "Gazpacho.",
+ "Discovering that what I really want in life is to kill people and have sex with their corpses.",
+ "A man with the head of a goat and the body of a goat.",
+ "How good lead paint taste.",
+ "Dropping dead in a Sbarro's bathroom and not being found for 72 hours.",
+ "Eating too many Cinnabons and then vomiting and then eating the vomit.",
+ "Some of that good dick.",
+ "Two shitty kids and a garbage husband.",
+ "Pooping in the potty.",
+ "Bad emotions I don't want.",
+ "Mixing M&Ms and Skittles like some kind of psychopath.",
+ "Fucking my therapist.",
+ "The best, deepest quotes from The Dark Knight.",
+ "Meatloaf, the man.",
+ "Meatloaf, the food.",
+ "My huge penis and substantial fortune.",
+ "Hot lettuce.",
+ "It being too late to stop having sex with a horse.",
+ "Becoming the President of the United States.",
+ "Microaggressions.",
+ "Getting the Dorito crumbs out of my purse.",
+ "The sweet, forbidden meat of the monkey.",
+ "Consensual, nonreproductive incest.",
+ "Grunting for ten minutes and then peeing sand.",
+ "Prematurely ejaculating like a total loser.",
+ "Jazz.",
+ "Straight blazin' 24/7.",
+ "Having sex with a beautiful person.",
+ "Going around pulling people's tampons out.",
+ "Reaching an age where barbecue chips are better than sex.",
+ "Daddy going away forever.",
+ "Three hours of nonstop penetration.",
+ "Holding the proper political beliefs of my time to attract a mate.",
+ "Scissoring, if that's a thing.",
+ "Creamy slices of real, California avocado.",
+ "ISIS.",
+ "A weird guy who says weird stuff and weirds me out.",
+ "Rubbing my bush all over your bald head.",
+ "Farting all over my face with your tight little asshole.",
+ "Quinoa.",
+ "How sad it will be when Morgan Freeman dies.",
+ "A cheerfulness that belies a deep-seated self-loathing.",
+ "Farting a huge shit out of my pussy.",
+ "Defeating a gorilla in single combat.",
+ "A big ol' plate of fettuccine alfredo.",
+ "Brunch.",
+ "Anal.",
+ "A women's perspective.",
+ "A long business meeting with no obvious purpose.",
+ "Thinking about what eating even is.",
+ "Doing a somersault and barfing.",
+ "Trees.",
+ "Hating Jews.",
+ "Whooping your ass at Mario Kart.",
+ "A massive collection of child pornography.",
+ "Systems and policies designed to preserve centuries-old power structures.",
+ "Having an awesome time drinking and driving.",
+ "Muchin' puss.",
+ "Moon people.",
+ "Picking up a glass of water and taking a sip and being the president.",
+ "Critical thinking.",
+ "Showing all the boys my pussy.",
+ "Homework.",
+ "China.",
+ "Putting more black people in jail.",
+ "The ol' penis-in-the-popcorn surprise.",
+ "One of them big-city Jew lawyers.",
+ "Informing you that I am a registered sex offender.",
+ "Rolling so hard.",
+ "Who really did 9\/11.",
+ "Being turned into sausages.",
+ "Eating ass.",
+ "A dolphin that learns to talk and becomes the Dead of Harvard Law School.",
+ "Gay thoughts.",
+ "My dog dying.",
+ "Dominating a man by peeing on his eldest son.",
+ "Dis bitch.",
+ "A strong horse and enough rations for thirty days.",
+ "Feminism.",
+ "A cold and indifferent universe.",
+ "An incurable homosexual.",
+ "The amount of baby carrots I can fit up my ass.",
+ "Huge big balls full of jizz.",
+ "Gregor, my largest son.",
+ "Esmeralda, my most beautiful daughter.",
+ "Trevor, the world's greatest boyfriend.",
+ "Jason, the teen mayor.",
+ "That bitch, Stacy.",
+ "Gayle from HR.",
+ "Gary.",
+ "Just now finding out about the Armenian Genocide.",
+ "Opening your mouth to talk and a big penis fops out.",
+ "Twisting my cock and balls into a balloon poodle.",
+ "The wind.",
+ "A gun that shoots cobras.",
+ "Out-of-control teenage blowjob parties.",
+ "A black friend.",
+ "The body of a 46-year-old man.",
+ "Art.",
+ "Water.",
+ "Doritos and a Fruit Roll-Up.",
+ "Sucking each other's penises for hours on end.",
+ "10,000 shrieking teenage girls.",
+ "Whomsoever let the dogs out.",
+ "The chicken from Popeyes.",
+ "Assassinating the president.",
+ "Having sex with a man and then eating his head.",
+ "A burrito that's just sour cream.",
+ "An arrangement wherein I give a person money they have sex with me.",
+ "Facilitating dialogue and deconstructing binaries.",
+ "Taking the form of a falcon.",
+ "Watching you die.",
+ "An X-Man whose power is that he has sex with dogs and children.",
+ "Loud, scary thunder.",
+ "Every man's ultimate fantasy: a perfectly cylindrical vagina.",
+ "Tiny, rancid girl farts.",
+ "Math.",
+ "Founding a major world religion.",
+ "Plowing that ass like a New England corn farmer.",
+ "Period poops.",
+ "The feeling of going to McDonald's as a 6-year-old.",
+ "Misogyny.",
+ "Tables.",
+ "Feeling the emotion of anger.",
+ "One of those \"blow jobs\" I've been hearing so much about.",
+ "A creepy child singing a nursery rhyme.",
+ "Blossoming into a beautiful young woman.",
+ "The secret to truly resilient hair.",
+ "Rock-hard tits and a huge vagina.",
+ "The lived experience of African Americans.",
+ "Mental illness.",
+ "Getting eaten out by a dog.",
+ "10 football players with erections barreling towards you at full speed.",
+ "Starting a shitty podcast.",
+ "Overthrowing the democratically-elected government of Chile.",
+ "Guns.",
+ "A hug.",
+ "Getting aborted.",
+ "Crazy anal orgasms.",
+ "Getting this party started!",
+ "Being sexually attracted to children.",
+ "Antidepressants.",
+ "Getting trapped in a conversation about Ayn Rand.",
+ "Swearing praise upon the Sultan's hideous daughters.",
+ "Turning 32.",
+ "Sudden and unwanted slam poetry.",
+ "A negative body image that is totally justified.",
+ "Exploring each other's buttholes.",
+ "You.",
+ "Quacking like a duck in lieu of a cogent argument.",
+ "Catching a live salmon in your mouth.",
+ "Eating people.",
+ "Our baby.",
+ "Breastfeeding in public like a radiant earth goddess.",
+ "Big, smart money boys tap-tapping on their keyboards.",
+ "Finding a nice elevator to poop in.",
+ "The mysterious fog rolling into town.",
+ "Two beautiful pig sisters.",
+ "Condoleezza Rice.",
+ "The full force of the American military.",
+ "A woman's right to choose.",
+ "A terrified fat child who won't come out of the bushes.",
+ "A medium horchata.",
+ "How great my ass looks in these jeans.",
+ "Raising three kids on minimum wage.",
+ "The bond between a woman and her horse.",
+ "Slamming a dunk.",
+ "Chris Hemsworth.",
+ "Ejaculating at the apex of a cartwheel.",
+ "Child labor.",
+ "An older man.",
+ "Crushing the patriarchy.",
+ "Denying the Holocaust.",
+ "Falling into a pit of waffles.",
+ "Objectifying women.",
+ "Everything.",
+ "A creature made of penises that must constantly arouse itself to survive.",
+ "Getting blasted in the face by a t-shirt cannon.",
+ "Onions.",
+ "Dumpster juice.",
+ "Forty-five minutes of finger blasting.",
+ "Film roles for actresses over 40.",
+ "Having sex with your mom.",
+ "Having a vagina.",
+ "Regurgitating a half-digested sparrow.",
+ "The hottest MILF in Dallas.",
+ "Some real spicy shrimps.",
+ "The Rwandan Genocide.",
+ "The LGBT community.",
+ "Twenty bucks.",
+ "The full blown marginalization of ugly people.",
+ "A finger up the butt.",
+ "A big, beautiful mouth packed to the brim with sparkling teeth.",
+ "Getting laid like all the time.",
+ "Happy daddies with happy sandals.",
+ "Libertarians.",
+ "Late-stage dementia.",
+ "How strange it is to be anything at all.",
+ "Pooping in a leotard and hoping no one notices.",
+ "Restoring Germany to its former glory.",
+ "Participating.",
+ "Going to bed at a reasonable hour.",
+ "Smashing my balls at the moment of climax.",
+ "Making out and stuff.",
+ "Menopause.",
+ "The government.",
+ "The graceful path of an autumn leaf as it falls to its earthen cradle.",
+ "Tender chunks of all-white-meat chicken.",
+ "Twenty cheerleaders laughing at your tiny penis.",
+ "Mommy and daddy fighting all the time.",
+ "Playing my asshole like a trumpet.",
+ "Getting naked too soon.",
+ "A slowly encroaching circle of wolves.",
+ "A man in a suit with perfect hair who tells you beautiful lies.",
+ "Seizing control of the means of production.",
+ "Comprehensive immigration reform.",
+ "Awesome pictures of planets and stuff.",
+ "Aborting the shit out of a fetus.",
+ "A genetic predisposition for alcoholism.",
+ "The flaming wreckage of the International Space Station.",
+ "Waking up inside of a tornado.",
+ "When the big truck goes \"Toot! Toot!\"",
+ "The fear and hatred in men's hearts.",
+ "Getting killed and dragged up a tree by a leopard.",
+ "Eternal screaming madness.",
+ "My brother's hot friends.",
+ "Salsa Night at Dave's Cantina.",
+ "The clown that followed me home from the grocery store.",
+ "Watching a hot person eat.",
+ "Pretending to be one of the guys but actually being the spider god.",
+ "Getting pegged.",
+ "An empowered woman.",
+ "An old dog full of tumors.",
+ "A duffel bag full of lizards.",
+ "All these people I've killed.",
+ "Working so hard to have muscles and then having them.",
+ "Political correctness.",
+ "A tiny fireman who puts out tiny fires.",
+ "Albert Einstein but if he had a huge muscles and a rhinoceros cock.",
+ "Chipotle."
+ ]
+}
diff --git a/chatchart/chatchart.py b/chatchart/chatchart.py
index d22a617..1238a3c 100644
--- a/chatchart/chatchart.py
+++ b/chatchart/chatchart.py
@@ -119,6 +119,10 @@ class Chatchart(BaseCog):
msg_data["users"][whole_name]["msgcount"] = 1
msg_data["total count"] += 1
+ if msg_data['users'] == {}:
+ await em.delete()
+ return await ctx.message.channel.send(f'Only bots have sent messages in {channel.mention}')
+
for usr in msg_data["users"]:
pd = float(msg_data["users"][usr]["msgcount"]) / float(msg_data["total count"])
msg_data["users"][usr]["percent"] = round(pd * 100, 1)
diff --git a/dungeon/dungeon.py b/dungeon/dungeon.py
index f499cb9..3c48054 100644
--- a/dungeon/dungeon.py
+++ b/dungeon/dungeon.py
@@ -75,7 +75,7 @@ class Dungeon(BaseCog):
@commands.group(autohelp=True)
@commands.guild_only()
- @checks.admin_or_permissions(manage_server=True)
+ @checks.admin_or_permissions(manage_guild=True)
async def dungeon(self, ctx):
"""Main dungeon commands."""
pass
@@ -380,7 +380,7 @@ class Dungeon(BaseCog):
await member.send(auto_ban_msg)
except discord.Forbidden:
if announce_channel:
- return await channel_object.send(
+ await channel_object.send(
f"I couldn't DM {member} ({member.id}) to let them know they've been banned, they've blocked me."
)
else:
@@ -460,6 +460,8 @@ class Dungeon(BaseCog):
await member.add_roles(user_role_obj, reason="Adding member role to new user.")
except discord.Forbidden:
pass
+ except AttributeError:
+ pass
@staticmethod
def _dynamic_time(time):
diff --git a/joinleave/__init__.py b/joinleave/__init__.py
new file mode 100644
index 0000000..6d961dd
--- /dev/null
+++ b/joinleave/__init__.py
@@ -0,0 +1,5 @@
+from .joinleave import JoinLeave
+
+
+def setup(bot):
+ bot.add_cog(JoinLeave(bot))
diff --git a/joinleave/joinleave.py b/joinleave/joinleave.py
new file mode 100644
index 0000000..b227f66
--- /dev/null
+++ b/joinleave/joinleave.py
@@ -0,0 +1,256 @@
+import asyncio
+import datetime
+import discord
+import re
+from redbot.core import Config, commands, checks
+from redbot.core.utils.chat_formatting import bold
+
+BaseCog = getattr(commands, "Cog", object)
+
+
+class JoinLeave(BaseCog):
+ """Report users that join and leave quickly, with new accounts."""
+ def __init__(self, bot):
+ self.bot = bot
+ self.config = Config.get_conf(self, 2713731002, force_registration=True)
+
+ default_global = {
+ "announce_channel": None,
+ "join_days": 7,
+ "toggle": False,
+ "cooldown": 120,
+ "pingrole": None,
+ "pingserver": None,
+ }
+
+ default_user = {"last_join": "2018-01-01 00:00:00.000001", "new": True}
+
+ self.config.register_user(**default_user)
+ self.config.register_global(**default_global)
+
+ @commands.group()
+ @commands.guild_only()
+ @checks.is_owner()
+ async def joinleave(self, ctx):
+ """Main joinleave commands."""
+ pass
+
+ @joinleave.command()
+ async def channel(self, ctx, channel: discord.TextChannel):
+ """Sets the announcement channel."""
+ await self.config.announce_channel.set(channel.id)
+ announce_channel_id = await self.config.announce_channel()
+ await ctx.send(
+ f"User announcement channel set to: {self.bot.get_channel(announce_channel_id).mention}."
+ )
+
+ @joinleave.command()
+ async def cooldown(self, ctx, cooldown_time: int = 0):
+ """Set the time window in seconds for a valid join/leave flag.
+
+ Leave time blank to reset to default (2m)."""
+ cooldown = await self.config.cooldown()
+ if not cooldown_time:
+ await self.config.cooldown.set(120)
+ await ctx.send("Join/leave time window reset to 2m.")
+ else:
+ await self.config.cooldown.set(cooldown_time)
+ await ctx.send(
+ f"Join/leave time window set to {self._dynamic_time(int(cooldown_time))}."
+ )
+
+ @joinleave.command()
+ async def days(self, ctx, days: int):
+ """Set how old an account needs to be a trusted user."""
+ await self.config.join_days.set(days)
+ await ctx.send(f"Users must have accounts older than {days} day(s) to be ignored.")
+
+ @joinleave.command()
+ async def role(self, ctx, *, role_name: discord.Role = None):
+ """Set the role to ping on a first sighting. Leave blank to turn off."""
+ if not role_name:
+ await self.config.pingrole.set(None)
+ await self.config.pingserver.set(None)
+ return await ctx.send(
+ "Role has been removed. No pinging will occur on a first sighting."
+ )
+
+ await self.config.pingserver.set(ctx.guild.id)
+ await self.config.pingrole.set(role_name.id)
+ role_obj = discord.utils.get(ctx.guild.roles, id=await self.config.pingrole())
+ await ctx.send(f"Pingable role set to: {role_obj.name}.")
+
+ @joinleave.command()
+ async def settings(self, ctx):
+ """Show the current settings."""
+ data = await self.config.all()
+
+ try:
+ achannel = self.bot.get_channel(data["announce_channel"]).name
+ except AttributeError:
+ achannel = None
+ joinleave_enabled = data["toggle"]
+ join_days = data["join_days"]
+ cooldown = data["cooldown"]
+ pingrole_id = data["pingrole"]
+ ping_guild = self.bot.get_guild(data["pingserver"])
+ if not pingrole_id:
+ pingrole = "None"
+ else:
+ try:
+ pingrole_obj = discord.utils.get(ping_guild.roles, id=pingrole_id)
+ pingrole = pingrole_obj.name
+ except AttributeError:
+ pingrole = "None"
+
+ msg = (
+ "```ini\n---JoinLeave Settings--- \n"
+ f"Announce Channel: [{achannel}]\n"
+ f"Join/Leave Span: [{self._dynamic_time(int(cooldown))}]\n"
+ f"Day Threshold: [{str(join_days)}]\n"
+ f"Ping role: [{pingrole}]\n"
+ f"JoinLeave Active: [{joinleave_enabled}]\n```"
+ )
+
+ embed = discord.Embed(colour=ctx.guild.me.top_role.colour, description=msg)
+ return await ctx.send(embed=embed)
+
+ @joinleave.command()
+ async def toggle(self, ctx):
+ """Toggle the joinleave on or off. This is global."""
+ joinleave_enabled = await self.config.toggle()
+ announce_channel = await self.config.announce_channel()
+ if not announce_channel:
+ await self.config.announce_channel.set(ctx.message.channel.id)
+ await self.config.toggle.set(not joinleave_enabled)
+ await ctx.send(f"JoinLeave enabled: {not joinleave_enabled}.")
+ if not announce_channel:
+ await ctx.send(f"JoinLeave report channel set to: {ctx.message.channel.mention}.")
+
+ async def on_member_join(self, member):
+ global_data = await self.config.all()
+ if not global_data["toggle"]:
+ return
+
+ join_date = datetime.datetime.strptime(str(member.created_at), "%Y-%m-%d %H:%M:%S.%f")
+ now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
+ since_join = now - join_date
+
+ if since_join.days < global_data["join_days"]:
+ await self.config.user(member).last_join.set(str(now))
+
+ async def on_member_remove(self, member):
+ user_data = await self.config.user(member).all()
+ global_data = await self.config.all()
+ if not global_data["toggle"]:
+ return
+
+ channel_obj = self.bot.get_channel(global_data["announce_channel"])
+
+ if not channel_obj:
+ print("joinleave.py: toggled on but no announce channel")
+ return
+
+ last_time = datetime.datetime.strptime(str(user_data["last_join"]), "%Y-%m-%d %H:%M:%S.%f")
+
+ join_date = datetime.datetime.strptime(str(member.created_at), "%Y-%m-%d %H:%M:%S.%f")
+ now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
+ since_join = now - join_date
+
+ if int((now - last_time).total_seconds()) < global_data["cooldown"]:
+ await channel_obj.send(f"**{member.id}**")
+ msg = f"User: {member} ({member.id})\nServer: {member.guild.name} ({member.guild.id})\nAccount is {self._dynamic_time(int(since_join.total_seconds()))} old"
+ if user_data["new"]:
+ await self.config.user(member).new.set(False)
+ if not global_data["pingrole"]:
+ return await channel_obj.send(f"\N{WARNING SIGN} First sighting\n{msg}")
+ else:
+ ping_guild = self.bot.get_guild(await self.config.pingserver())
+ role_obj = discord.utils.get(ping_guild.roles, id=global_data["pingrole"])
+ try:
+ await role_obj.edit(mentionable=True)
+ await channel_obj.send(
+ f"{role_obj.mention}\n\N{WARNING SIGN} First sighting\n{msg}"
+ )
+ return await role_obj.edit(mentionable=False)
+ except AttributeError:
+ return await channel_obj.send(
+ f"I can't find the role that's set to ping (is it on another server?)\n\N{WARNING SIGN} First sighting\n{msg}"
+ )
+ except discord.errors.Forbidden:
+ return await channel_obj.send(
+ f"I tried to ping for this alert but I don't have permissions to manage roles!\n\N{WARNING SIGN} First sighting\n{msg}"
+ )
+ else:
+ await channel_obj.send(msg)
+
+ async def on_message(self, message):
+ global_data = await self.config.all()
+ if not global_data["toggle"]:
+ return
+ channel_obj = self.bot.get_channel(global_data["announce_channel"])
+ if not channel_obj:
+ return
+ if message.author.bot:
+ return
+ date_join = datetime.datetime.strptime(str(message.author.created_at), "%Y-%m-%d %H:%M:%S.%f")
+ date_now = datetime.datetime.now(datetime.timezone.utc)
+ date_now = date_now.replace(tzinfo=None)
+ since_join = date_now - date_join
+ if since_join.days < global_data["join_days"]:
+ sentence = message.content.split()
+ for word in sentence:
+ if self._match_url(word):
+ await channel_obj.send(bold(f"{message.author.id}"))
+ msg = "**Message posted in** {} ({})\n".format(message.guild.name, message.guild.id)
+ msg += "**Message posted in** {} ({})\n".format(message.channel.mention, message.channel.id)
+ msg += "**Message sent by**: {} ({})\n".format(message.author.name, message.author.id)
+ msg += "**Message content**:\n{}".format(message.content)
+
+ if not global_data["pingrole"]:
+ return await channel_obj.send(f"\N{WARNING SIGN} \N{LINK SYMBOL} New user link post\n{msg}")
+ else:
+ ping_guild = self.bot.get_guild(await self.config.pingserver())
+ try:
+ role_obj = discord.utils.get(ping_guild.roles, id=global_data["pingrole"])
+ await role_obj.edit(mentionable=True)
+ await channel_obj.send(
+ f"{role_obj.mention}\n\N{WARNING SIGN} \N{LINK SYMBOL} New user link post\n{msg}"
+ )
+ return await role_obj.edit(mentionable=False)
+ except AttributeError:
+ return await channel_obj.send(
+ f"I can't find the role that's set to ping (is it on another server?)\n\N{WARNING SIGN} \N{LINK SYMBOL} New user link post\n{msg}"
+ )
+ except discord.errors.Forbidden:
+ return await channel_obj.send(
+ f"I tried to ping for this alert but I don't have permissions to manage roles!\n\N{WARNING SIGN} \N{LINK SYMBOL} New user link post\n{msg}"
+ )
+
+ @staticmethod
+ def _dynamic_time(time):
+ m, s = divmod(time, 60)
+ h, m = divmod(m, 60)
+ d, h = divmod(h, 24)
+
+ if d > 0:
+ msg = "{0}d {1}h"
+ elif d == 0 and h > 0:
+ msg = "{1}h {2}m"
+ elif d == 0 and h == 0 and m > 0:
+ msg = "{2}m {3}s"
+ elif d == 0 and h == 0 and m == 0 and s > 0:
+ msg = "{3}s"
+ else:
+ msg = ""
+ return msg.format(d, h, m, s)
+
+ @staticmethod
+ def _match_url(url):
+ regex = re.compile(
+ "(([\w]+:)?//)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,63}(:[\d]+)?(/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?"
+ )
+ if regex.match(url):
+ return True
+ else:
+ return False
diff --git a/partycrash/info.json b/partycrash/info.json
index 282d6ba..9527a60 100644
--- a/partycrash/info.json
+++ b/partycrash/info.json
@@ -5,11 +5,11 @@
"description": "Posts invites to servers, if the bot is allowed to view them. Does not generate new invites.",
"install_msg": "Thanks for installing, have fun.",
"permissions" : [
- "manage_server"
+ "manage_guild"
],
"short": "Post server invites.",
"tags": [
"invite"
],
"type": "COG"
-}
\ No newline at end of file
+}
diff --git a/timezone/timezone.py b/timezone/timezone.py
index 1b8ffd6..8a60426 100644
--- a/timezone/timezone.py
+++ b/timezone/timezone.py
@@ -110,7 +110,7 @@ class Timezone(BaseCog):
)
@time.command()
- @checks.admin_or_permissions(manage_server=True)
+ @checks.admin_or_permissions(manage_guild=True)
async def set(self, ctx, user: discord.Member, *, tz):
"""Allows the mods to edit timezones."""
author = ctx.message.author
diff --git a/tools/tools.py b/tools/tools.py
index 1fc8d6c..580673c 100644
--- a/tools/tools.py
+++ b/tools/tools.py
@@ -11,9 +11,9 @@ from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
from tabulate import tabulate
from contextlib import suppress as sps
-
BaseCog = getattr(commands, "Cog", object)
+
class Tools(BaseCog):
"""Mod and Admin tools."""
@@ -170,7 +170,7 @@ class Tools(BaseCog):
@commands.guild_only()
@commands.command()
- @checks.mod_or_permissions(manage_server=True)
+ @checks.mod_or_permissions(manage_guild=True)
async def banlist(self, ctx):
"""Displays the server's banlist."""
try:
@@ -271,7 +271,7 @@ class Tools(BaseCog):
@commands.guild_only()
@commands.command()
- @checks.mod_or_permissions(manage_server=True)
+ @checks.mod_or_permissions(manage_guild=True)
async def inrole(self, ctx, *, rolename):
"""Check members in the role specified."""
guild = ctx.guild
@@ -359,9 +359,52 @@ class Tools(BaseCog):
)
await awaiter.edit(embed=embed)
+ @commands.command(name='listguilds', aliases=['listservers', 'guildlist', 'serverlist'])
+ @checks.mod_or_permissions()
+ async def listguilds(self, ctx):
+ """
+ List the guilds|servers the bot is in
+ """
+ asciidoc = lambda m: "```asciidoc\n{}\n```".format(m)
+ guilds = sorted(self.bot.guilds, key=lambda g: -g.member_count)
+ header = ("```\n"
+ "The bot is in the following {} server{}\n"
+ "```").format(len(guilds), 's' if len(guilds) > 1 else '')
+
+ max_zpadding = max([len(str(g.member_count)) for g in guilds])
+ form = "{gid} :: {mems:0{zpadding}} :: {name}"
+ all_forms = [form.format(gid=g.id, mems=g.member_count, name=g.name, zpadding=max_zpadding) for g in guilds]
+ final = '\n'.join(all_forms)
+
+ await ctx.send(header)
+ for page in cf.pagify(final, delims=['\n'], shorten_by=16):
+ await ctx.send(asciidoc(page))
+
+ @commands.guild_only()
+ @checks.mod_or_permissions(manage_channels=True)
+ @commands.command(name='listchannel', aliases=['channellist'])
+ async def listchannel(self, ctx):
+ """
+ List the channels of the current server
+ """
+ asciidoc = lambda m: "```asciidoc\n{}\n```".format(m)
+ channels = ctx.guild.channels
+ top_channels, category_channels = self.sort_channels(ctx.guild.channels)
+
+ topChannels_formed = '\n'.join(self.channels_format(top_channels))
+ categories_formed = '\n\n'.join([self.category_format(tup) for tup in category_channels])
+
+ await ctx.send(f"{ctx.guild.name} has {len(channels)} channel{'s' if len(channels) > 1 else ''}.")
+
+ for page in cf.pagify(topChannels_formed, delims=['\n'], shorten_by=16):
+ await ctx.send(asciidoc(page))
+
+ for page in cf.pagify(categories_formed, delims=['\n\n'], shorten_by=16):
+ await ctx.send(asciidoc(page))
+
@commands.guild_only()
@commands.command()
- @checks.mod_or_permissions(manage_server=True)
+ @checks.mod_or_permissions(manage_guild=True)
async def newusers(self, ctx, count: int = 5, fm: str = "py"):
"""Lists the newest 5 members."""
guild = ctx.guild
@@ -410,7 +453,7 @@ class Tools(BaseCog):
@commands.guild_only()
@commands.command()
- @checks.mod_or_permissions(manage_server=True)
+ @checks.mod_or_permissions(manage_guild=True)
async def perms(self, ctx, user: discord.Member = None):
"""Fetch a specific user's permissions."""
if user is None:
@@ -641,16 +684,20 @@ class Tools(BaseCog):
data += "[ID]: {}\n".format(user.id)
data += "[Status]: {}\n".format(user.status)
data += "[Servers]: {} shared\n".format(seen)
- if user.activity is None:
+ act = user.activity
+ if act is None:
pass
- elif user.activity.url is None:
- if user.activity.type == discord.ActivityType.playing:
- data += "[Playing]: {}\n".format(cf.escape(str(user.activity.name)))
- elif user.activity.type == discord.ActivityType.listening:
- data += "[Listening]: {}\n".format(cf.escape(str(user.activity.name)))
- elif user.activity.type == discord.ActivityType.listening:
- data += "[Watching]: {}\n".format(cf.escape(str(user.activity.name)))
+ elif act.type == discord.ActivityType.playing:
+ data += "[Playing]: {}\n".format(cf.escape(str(act.name)))
+ elif act.type == discord.ActivityType.listening:
+ if isinstance(act, discord.Spotify):
+ _form = act.title
+ else:
+ _form = act.name
+ data += "[Listening]: {}\n".format(cf.escape(_form))
+ elif act.type == discord.ActivityType.listening:
+ data += "[Watching]: {}\n".format(cf.escape(str(user.activity.name)))
else:
data += "[Streaming]: [{}]({})\n".format(
cf.escape(str(user.activity.name)), cf.escape(user.activity.url)
@@ -742,3 +789,48 @@ class Tools(BaseCog):
roles = guild.roles
role = discord.utils.find(lambda r: r.name.lower() == str(rolename).lower(), roles)
return role
+
+ def sort_channels(self, channels):
+ temp = dict()
+
+ channels = sorted(channels, key=lambda c: c.position)
+
+ for c in channels[:]:
+ if isinstance(c, discord.CategoryChannel):
+ channels.pop(channels.index(c))
+ temp[c] = list()
+
+ for c in channels[:]:
+ if c.category:
+ channels.pop(channels.index(c))
+ temp[c.category].append(c)
+
+ category_channels = sorted([(cat, sorted(chans, key=lambda c: c.position)) for cat, chans in temp.items()], key=lambda t: t[0].position)
+ return channels, category_channels
+
+ def channels_format(self, channels: list):
+ if channels == []:
+ return []
+
+ channel_form = "{name} :: {ctype} :: {cid}"
+
+ def type_name(channel):
+ return channel.__class__.__name__[:-7]
+
+ name_justify = max([len(c.name[:24]) for c in channels])
+ type_justify = max([len(type_name(c)) for c in channels])
+
+ return [channel_form.format(name=c.name[:24].ljust(name_justify), ctype=type_name(c).ljust(type_justify), cid=c.id) for c in channels]
+
+
+ def category_format(self, cat_chan_tuple: tuple):
+
+ cat = cat_chan_tuple[0]
+ chs = cat_chan_tuple[1]
+
+ chfs = self.channels_format(chs)
+ if chfs != []:
+ ch_forms = ['\t' + f for f in chfs]
+ return '\n'.join([f'{cat.name} :: {cat.id}'] + ch_forms)
+ else:
+ return '\n'.join([f'{cat.name} :: {cat.id}'] + ['\tNo Channels'])
diff --git a/trickortreat/trickortreat.py b/trickortreat/trickortreat.py
index 61d148d..aac7878 100644
--- a/trickortreat/trickortreat.py
+++ b/trickortreat/trickortreat.py
@@ -248,7 +248,7 @@ class TrickOrTreat(BaseCog):
@commands.guild_only()
@commands.cooldown(1, 600, discord.ext.commands.BucketType.user)
@commands.command()
- async def pick(self, ctx):
+ async def pickup(self, ctx):
"""Pick up some candy, if there is any."""
candies = await self.config.user(ctx.author).candies()
to_pick = await self.config.guild(ctx.guild).pick()
diff --git a/wolfram/wolfram.py b/wolfram/wolfram.py
index 926ddf6..a7f2b49 100644
--- a/wolfram/wolfram.py
+++ b/wolfram/wolfram.py
@@ -1,16 +1,19 @@
-import os
import aiohttp
+import os
from redbot.core import Config, commands, checks
+from redbot.core.utils.chat_formatting import box
import xml.etree.ElementTree as ET
BaseCog = getattr(commands, "Cog", object)
+
class Wolfram(BaseCog):
- """Ask Wolfram Alpha a question."""
+ """Ask Wolfram Alpha any question."""
def __init__(self, bot):
self.bot = bot
+ self.session = aiohttp.ClientSession()
default_global = {"WOLFRAM_API_KEY": None}
@@ -18,21 +21,18 @@ class Wolfram(BaseCog):
self.config.register_guild(**default_global)
@commands.command(name="wolfram", aliases=["ask"])
- async def _wolfram(self, ctx, *arguments: str):
- """
- Ask Wolfram Alpha any question.
- """
+ async def _wolfram(self, ctx, *question: str):
+ """Ask Wolfram Alpha any question."""
+
api_key = await self.config.WOLFRAM_API_KEY()
+
if api_key:
url = "http://api.wolframalpha.com/v2/query?"
- query = " ".join(arguments)
+ query = " ".join(question)
payload = {"input": query, "appid": api_key}
headers = {"user-agent": "Red-cog/2.0.0"}
- conn = aiohttp.TCPConnector(verify_ssl=False)
- session = aiohttp.ClientSession(connector=conn)
- async with session.get(url, params=payload, headers=headers) as r:
+ async with self.session.get(url, params=payload, headers=headers) as r:
result = await r.text()
- session.close()
root = ET.fromstring(result)
a = []
for pt in root.findall(".//plaintext"):
@@ -43,17 +43,17 @@ class Wolfram(BaseCog):
else:
message = "\n".join(a[0:3])
else:
- message = (
- "No API key set for Wolfram Alpha. Get one at http://products.wolframalpha.com/api/"
- )
- await ctx.send("```{0}```".format(message))
+ message = "No API key set for Wolfram Alpha. Get one at http://products.wolframalpha.com/api/"
+ await ctx.send(box(message))
- @commands.command(name="setwolframapi", aliases=["setwolfram"])
@checks.is_owner()
+ @commands.command(name="setwolframapi", aliases=["setwolfram"])
async def _setwolframapi(self, ctx, key: str):
- """
- Set the api-key.
- """
+ """Set the api-key."""
+
if key:
await self.config.WOLFRAM_API_KEY.set(key)
await ctx.send("Key set.")
+
+ def __unload(self):
+ self.bot.loop.create_task(self.session.close())