def SLICE(a, b, c): if type(a) not in [list, str]: raise TypeError( f"First parameter of SLICE function must be a string or an array: {safe_cut(a)}" ) if not is_whole(b): raise TypeError( f"Second parameter of SLICE function must be an integer: {safe_cut(b)}" ) if not is_whole(c): raise TypeError( f"Third parameter of SLICE function must be an integer: {safe_cut(c)}" ) return a[int(b):int(c)]
def MATH(a, b, c): operations = "+-*/^" if not is_number(a): raise ValueError( f"First parameter of MATH function is not a number: {safe_cut(a)}") if b not in operations: raise ValueError( f"Operation parameter of MATH function not an operation: {safe_cut(b)}" ) if not is_number(c): raise ValueError( f"Second parameter of MATH function is not a number: {safe_cut(c)}" ) a = int(a) if is_whole(a) else float(a) c = int(c) if is_whole(c) else float(c) if b == "+": return a + c if b == "-": return a - c if b == "*": if abs(a) > 1e50: raise ValueError( f"First parameter of MATH function too large to safely multiply: {safe_cut(a)} (limit 10^50)" ) if abs(c) > 1e50: raise ValueError( f"Second parameter of MATH function too large to safely multiply: {safe_cut(a)} (limit 10^50)" ) return a * c if b == "/": if c == 0: raise ZeroDivisionError( f"Second parameter of MATH function in division cannot be zero" ) return a / c if b == "^": if abs(a) > 1024: raise ValueError( f"First parameter of MATH function too large to safely exponentiate: {safe_cut(a)} (limit 1024)" ) if abs(c) > 128: raise ValueError( f"Second parameter of MATH function too large to safely exponentiate: {safe_cut(c)} (limit 128)" ) return a**c
def MOD(a, b): if not is_number(a): raise ValueError( f"First parameter of MOD function is not a number: {safe_cut(a)}") if not is_number(b): raise ValueError( f"First parameter of MOD function is not a number: {safe_cut(a)}") a = int(a) if is_whole(a) else float(a) b = int(b) if is_whole(b) else float(b) if b == 0: raise ZeroDivisionError( f"Second parameter of MOD function cannot be zero") return a % b
def RANDINT(a, b): if not is_whole(a): raise ValueError( f"First parameter of RANDINT function is not an integer: {safe_cut(a)}" ) if not is_whole(b): raise ValueError( f"Second parameter of RANDINT function is not an integer: {safe_cut(b)}" ) ints = [int(a), int(b)] a, b = [min(ints), max(ints)] if a == b: b += 1 return random.randrange(a, b)
async def MAIN(message, args, level, perms, SERVER): if level == 1: await message.channel.send("Include a channel ID.") return if level == 2: await message.channel.send("Can't send an empty message.") return direct = False if not is_whole(args[1]): if args[1].lower() != "dm": await message.channel.send("Invalid channel ID.") return direct = True if direct: if not is_whole(args[2]): await message.channel.send("Invalid user ID.") return user_id = int(args[2]) target = SERVER["MAIN"].get_member(user_id) else: channel_id = int(args[1]) target = discord.utils.get(SERVER["MAIN"].channels, id=channel_id) if target is None: await message.channel.send( "Could not find a channel or user with that ID.") return message_to_send = " ".join(args[2 + int(direct):]) try: await target.send(message_to_send) except Exception as err: await message.channel.send(f"`{err}`: Could not send") return await message.channel.send( f"Sent successfully!\n>\t`{message_to_send[:1950]}`") return
def INDEX(a, b): if type(a) not in [list, str]: raise TypeError( f"First parameter of INDEX function must be a string or an array: {safe_cut(a)}" ) if not is_whole(b): raise TypeError( f"Second parameter of INDEX function must be an integer: {safe_cut(b)}" ) return a[int(b)]
async def MAIN(message, args, level, perms, TWOW_CENTRAL): if level < 3: await message.channel.send("Include both a message ID and an emoji!") return if not is_whole(args[1]): await message.channel.send("Provide a valid message ID!") return if level == 3: try: msg = await message.channel.fetch_message(int(args[1])) except discord.HTTPException: await message.channel.send("The message was not found!") elif is_whole(args[3][2:-1]): chnl = discord.utils.get(TWOW_CENTRAL.channels, id=int(args[3][2:-1])) if chnl is None: await message.channel.send("Provide a valid channel!") return try: msg = await chnl.fetch_message(int(args[1])) except discord.HTTPException: await message.channel.send("The message was not found!") else: print(args[3][2:-1]) print(is_whole(args[3][2:-1])) await message.channel.send("Provide a valid channel!") return try: await msg.add_reaction(args[2]) await message.channel.send( f"Added {args[2]} reaction to message {args[1]}.") except discord.HTTPException: await message.channel.send("Include a valid emoji!")
def array_to_list(raw): if "\t" not in raw: list_eq = raw[1:-1].split("; ") else: list_eq = raw[1:-1].split("\t ") for entry in range(len(list_eq)): if is_whole(list_eq[entry]): list_eq[entry] = int(list_eq[entry]) if is_float(list_eq[entry]): list_eq[entry] = float(list_eq[entry]) if list_eq[entry] in ["True", "False"]: list_eq[entry] = bool(list_eq[entry]) return list_eq
def REPEAT(a, b): if not is_whole(b): raise ValueError( f"Second parameter of REPEAT function is not an integer: {safe_cut(b)}" ) if type(a) != list: a = str(a) b = int(b) if b > 1024: raise ValueError( f"Second parameter of REPEAT function is too large: {safe_cut(b)} (limit 1024)" ) return a * b
async def MAIN(message, args, level, perms, SERVER): if level == 1: await message.channel.send("Provide a role ID!") return if not is_whole(args[1]): await message.channel.send("Invalid role ID!") return role_id = int(args[1]) role = discord.utils.get(SERVER["MAIN"].roles, id=role_id) if role is None: await message.channel.send("Invalid role ID!") return if level == 2: await message.channel.send("Specify whether you want to remove or add the roles to the users!") return if args[2] not in ["remove", "add"]: await message.channel.send("Specify whether you want to remove or add the roles to the users!") return if level == 3: await message.channel.send("Send a list of IDs that you want to do role manipulation with!") return for member_id in args[3:]: if member_id == "all" and args[2] == "remove": all_members = role.members for m in all_members: await m.remove_roles(role) else: member = SERVER["MAIN"].get_member(int(member_id)) try: if args[2] == "remove": await member.remove_roles(role) else: await member.add_roles(role) except: await message.channel.send(member_id + " not found") await message.channel.send(f"Successfully updated {role.name} members") return
async def MAIN(message, args, level, perms, SERVER, UNO_INFO): if level == 1: # If the command is just `tc/uno` await message.channel.send( "You must include a command mode for that command!") return if args[1].lower() == "join": if UNO_INFO["status"] != 1: # If it's not joining period await message.channel.send( "This Uno round is not in joining period!") return if message.author.id in UNO_INFO[ "players"]: # If the person is already a player UNO_INFO["players"].remove( message.author.id) # Remove them from the player list # Report the new player count await message.channel.send( f"""**<@{message.author.id}>**, you have quit this round of Uno. Now, there are **{len(UNO_INFO["players"])}** player{"s" if len(UNO_INFO["players"]) != 1 else ""}.""".replace( "\n", "").replace("\t", "")) return [4, "UNO", UNO_INFO] # Return a flag to change the UNO PARAMS entry # If the person is not already a player... UNO_INFO["players"].append( message.author.id) # Add them to the player list # Report the new player count await message.channel.send( f"""**<@{message.author.id}>**, you have joined this round of Uno! There are now **{len(UNO_INFO["players"])}** player{"s" if len(UNO_INFO["players"]) != 1 else ""}.""".replace( "\n", "").replace("\t", "")) return [4, "UNO", UNO_INFO] if args[1].lower() == "config": if UNO_INFO["status"] == 0: # If there's no Uno round right now await message.channel.send("There's no Uno round right now!") return if level == 2: # `tc/config` shows you the current configurations tag = str(time.time()).split(".")[ 1] # Get a pretty much random sequence of digits uno_image(4, tag, SERVER['PREFIX'], config=UNO_INFO["config"] ) # Generate the image with the [4] (config menu) flag await message.channel.send( "These is the current game options configuration.", file=discord.File(f"Images/current_card_image_{tag}.png") ) # Send the image os.remove(f"Images/current_card_image_{tag}.png" ) # And then promptly delete it return # If there are more than 2 arguments, that means the command is to change a configuration if UNO_INFO[ "host"] != message.author.id: # If the command isn't coming from the host await message.channel.send( "Only the current host can change the round options!") return if UNO_INFO["status"] != 1: # If the game is already goings await message.channel.send( "You can only change the configurations before the game has started!" ) return if not is_whole( args[2] ): # Options are numbered on the menu, args[2] represents the number you want await message.channel.send("That's not a valid option number!") return if not 1 <= int(args[2]) <= len( UNO_INFO["config"]): # If it's not a valid number... await message.channel.send("That's not a valid option number!") return chosen_index = int(args[2]) - 1 # Index in the list of dict keys config_key = list(UNO_INFO["config"].keys())[ chosen_index] # Get the key of chosen_index config_value = UNO_INFO["config"][ config_key] # The current value of the option chosen if type(config_value) == bool: # If config_value is a boolean current_state = UNO_INFO["config"][config_key] # Toggle the value UNO_INFO["config"][config_key] = not current_state await message.channel.send( # Report the toggled value f"The **{config_key}** option has been turned **{'ON' if current_state else 'OFF'}**!" ) return [4, "UNO", UNO_INFO] elif level == 3: # If it's not a boolean, this config parameter needs an extra argument to be changed await message.channel.send( "You must specify a number to change this configuration to!") return elif not is_whole( args[3] ): # So far, the only extra argument is an integer for card number await message.channel.send("That's not a valid card number!") return elif 2 > int(args[3]): # Currently this is quite a bit hardcoded await message.channel.send( "Too few cards! The minimum starting number is **2**.") return elif int(args[3]) > 30: # So maybe I'll change it in the future await message.channel.send( """I see you like making things excessive. Me too, honestly, but let's just keep it at **30** maximum, that's well excessive enough""".replace( "\n", "").replace("\t", "")) return else: UNO_INFO["config"][config_key] = int( args[3]) # Set the card number to the number specified await message.channel.send( f"The **{config_key}** option has been set to **{args[3]}**!") return [4, "UNO", UNO_INFO] if args[1].lower() == "start": # Command to start the round if UNO_INFO["host"] != message.author.id: # If it's not from the host await message.channel.send( "Only the current host of the game can start the Uno round!") return if UNO_INFO["status"] != 1: # If it's not joining period await message.channel.send( "You can only start a Uno round during joining period!") return if len(UNO_INFO["players"]) < 2: # If there are less than two playerss await message.channel.send( "You can only start a Uno round with two or more players!") returns UNO_INFO["status"] = 2 # Change the game status and distribute cards await message.channel.send( "**The Uno round is starting!** Currently distributing cards...") return [4, "UNO", UNO_INFO] if args[1].lower() == "play": if not UNO_INFO["running"] or UNO_INFO[ "status"] != 2: # If there's no Uno round being played right now await message.channel.send("There's no Uno round in progress!") return if UNO_INFO["order"][ 0] != message.author.id: # If it's not you who's ups await message.channel.send("It's not your turn to play!") return if level == 2: # `tc/uno play` by itself does nothing await message.channel.send( "You must include a card or an action to play!") return player_hand = UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] if is_whole( args[2] ): # If there's a number as args[2], interpret it as a card index if not 1 <= int(args[2]) <= len( player_hand): # If it's a number of cards you don't have await message.channel.send("That card number is not valid!") return played_card = player_hand[int( args[2]) - 1] # The card code for the card that was played if played_card[0] == "0": # 0C and OF are wild cards if level == 3: # If you just played a wild card without specifying the color you want await message.channel.send( # Warn about the color and how you can pick it f"""When playing a wild card, you must include the color you want to change the game to! To include a color, put another number next to the command, corresponding to the color you want. Add the number 1 for red, 2 for green, 3 for blue or 4 for yellow! Example: `{SERVER['PREFIX']}uno play {args[2]} 3`, will play the card and change the color to **blue.** """.replace("\t", "")) return if not is_whole(args[3]): # The color should be an integer await message.channel.send( "That's not a valid color number!") return if not 1 <= int( args[3]) <= 4: # If the color is not 1 through 4 await message.channel.send( "That's not a valid color number!") return if UNO_INFO[ "carryover"] > 0: # If the carryover is positive, there are cards to be drawn made up await message.channel.send( # of one or more +2s. f"""Someone has played a **+2** upon you. You must play another **+2** or draw the cards! To draw the cards instantly, use `{SERVER['PREFIX']}uno play draw`.""". replace("\t", "")) return if UNO_INFO[ "carryover"] < -3 and played_card != "0F": # If the carryover is negative and below -3, await message.channel.send( # There are cards to be drawn made up of one or more +4s f"""Someone has played a **+4** upon you. You must play another **+4** or draw the cards! To draw the cards instantly, use `{SERVER['PREFIX']}uno play draw`.""". replace("\t", "")) return if played_card == "0F": # If a +4 was played, make the carryover -4 if there weren't any # cards to be drawn prior, or increment by 4 any current card total to be drawn UNO_INFO["carryover"] = -4 if UNO_INFO[ "carryover"] > -3 else UNO_INFO["carryover"] - 4 # Remove the card from the player's hand UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)].remove(played_card) UNO_INFO["last_card"] = args[3] + played_card[ 1] # args[3] is chosen color, card code includes color player_hand = UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] # Redefine player_hand tag = str(time.time()).split(".")[ 1] # Make the updated image for the player's hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=player_hand) # Send the image then promptly delete it await message.channel.send( "You have successfully played a card!", file=discord.File(f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") UNO_INFO["current"] = 0 return [4, "UNO", UNO_INFO] elif (not played_card[0] == UNO_INFO["last_card"][0] and not played_card[1] == UNO_INFO["last_card"][1] ): # If neither the color nor the number match await message.channel.send("That card is not valid!") return if UNO_INFO[ "carryover"] < -3: # If the carryover is negative and below -3, await message.channel.send( # There are cards to be drawn made up of one or more +4s f"""Someone has played a **+4** upon you. You must play another **+4** or draw the cards! To draw the cards instantly, use `{SERVER['PREFIX']}uno play draw`.""". replace("\t", "")) return if UNO_INFO["carryover"] > 0 and played_card[ 1] != "D": # If the carryover is positive, there are cards await message.channel.send( # to be drawn made up of one or more +2s. f"""Someone has played a **+2** upon you. You must play another **+2** or draw the cards! To draw the cards instantly, use `{SERVER['PREFIX']}uno play draw`.""". replace("\t", "")) return if played_card[ 1] == "D": # If a +2 was played, make the carryover 2 if there weren't any # cards to be drawn prior, or increment by 2 any current card total to be drawn UNO_INFO["carryover"] = 2 if UNO_INFO[ "carryover"] < 2 else UNO_INFO["carryover"] + 2 seven_f = False # Variable for the case where the player switches hands with someone using special 7 if played_card[1] == "7" and UNO_INFO["config"][ "0-7"]: # If you played a 7 under the 0-7 rule if level == 3: # You need to specify a player to trade withs player_list = [] for player in range(len(UNO_INFO["players"]) ): # For each player that isn't you if UNO_INFO["players"][player] != message.author.id: player_name = SERVER["MAIN"].get_member( UNO_INFO["players"][player]).name player_hand_size = len(UNO_INFO["hands"][player]) player_list.append( f"[{player + 1}] - **{player_name}** (Card count: {player_hand_size})" ) player_list = "\n".join( player_list ) # Format the list of available players to switch hands with await message.channel.send( f"""You must pick someone to trade hands with! {player_list} To successfully play the card and trade hands with someone, use `{SERVER['PREFIX']}uno play {args[2]} x` *(x being the number corresponding to the player)* """.replace("\t", "")) return if not is_whole( args[3]): # If the player number is not a whole number await message.channel.send("That player number is invalid!" ) return if not 1 <= int(args[3]) <= len( UNO_INFO["players"] ): # If the number isn't in the amount of players await message.channel.send("That player number is invalid!" ) return if UNO_INFO["players"][ int(args[3]) - 1] == message.author.id: # If you chose yourself await message.channel.send( "You can't trade hands with yourself!") return seven_f = True # Set the variable to true for later await UNO_INFO["channel"].send( f"**{message.author.name}** is trading hands with someone..." ) UNO_INFO[ "last_card"] = played_card # Make the card played the last one, remove it from the player's hand UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)].remove(played_card) player_hand = UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] # Redefine player_hand tag = str(time.time()).split(".")[ 1] # Make the updated image for the player's new hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=player_hand) await message.channel.send( "You have successfully played a card!", # Send the image file=discord.File(f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png" ) # Then promptly delete it if played_card[1] == "S": # If it's a skip... UNO_INFO["carryover"] = -1 # -1 carryover means skip if played_card[1] == "R": # If it's a reverse card... UNO_INFO["carryover"] = -3 # -3 carryover means reverse if played_card[1] == "0" and UNO_INFO["config"][ "0-7"]: # If it's 0 under the 0-7 rule... UNO_INFO["hands"] += UNO_INFO["hands"][ 0:1] # A zero rotates everyone's hands around UNO_INFO["hands"] = UNO_INFO["hands"][1:] # Report that the hands were rotated await UNO_INFO["channel"].send( f"**{message.author.name} has rotated everyone's hand!**") for player in UNO_INFO[ "players"]: # Send every single person their new hand tag = str(time.time()).split(".")[ 1] # Make the image for their new hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=UNO_INFO["hands"][UNO_INFO["players"].index( player)]) await SERVER['MAIN'].get_member( player ).send( # Send the new hand "Everyone's hand has rotated! This is your new hand!", file=discord.File( f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png" ) # Delete the images if seven_f: # If it's a 7 under 0-7... provisory_hand = UNO_INFO["hands"][ int(args[3]) - 1] # Just a variable to hold the other player's hand UNO_INFO["hands"][ int(args[3]) - 1] = player_hand # Make the other player's hand your own # Make your own hand the other player's UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] = provisory_hand player_member = SERVER["MAIN"].get_member( UNO_INFO["players"][int(args[3]) - 1]) player_name = player_member.name await UNO_INFO["channel"].send( # Report the trade f"**{message.author.name}** trades hands with **{player_name}**!" ) player_hand = UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] tag = str(time.time()).split(".")[1] # Report your new hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=player_hand) await message.channel.send( "This is your new hand!", file=discord.File(f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") tag = str(time.time()).split(".")[ 1] # Show the other player their new hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=UNO_INFO["hands"][int(args[3]) - 1]) await SERVER["MAIN"].get_member(player_member).send( f"**{message.author.name}** has traded their hand with you! This is your new hand.", file=discord.File(f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") UNO_INFO["current"] = 0 # Signals that the turn is over return [4, "UNO", UNO_INFO] elif args[2].lower() == "draw": if UNO_INFO[ "carryover"] == -2: # carryover being -2 indicates you've already drawn await message.channel.send( "You've already drawn a card this turn!") return if UNO_INFO["carryover"] > 0 or UNO_INFO[ "carryover"] < -3: # If there are cards to be drawn card_n = numpy.abs( UNO_INFO["carryover"] ) # Add the card number to the player's hand UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] += UNO_INFO["deck"][:card_n] UNO_INFO["deck"] = UNO_INFO["deck"][card_n:] player_hand = UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] # Redefine player_hand # Sort the player's hand to show cards in the correct order UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] = list(sorted(player_hand)) player_hand = UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] UNO_INFO["carryover"] = 0 # Reset the carryover tag = str(time.time()).split(".")[1] # Report the new hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=player_hand) await message.channel.send( f"You draw **{card_n}** cards!", file=discord.File(f"Images/current_card_image_{tag}.png")) await UNO_INFO["channel"].send( f"**{message.author.name}** draws **{card_n}** cards!") os.remove(f"Images/current_card_image_{tag}.png") if UNO_INFO["config"][ "d-skip"]: # d-skip is whether or not drawing by +2 or +4 skips you. If it skips you... await message.channel.send("Your turn is over!") UNO_INFO["current"] = 0 # Signals that the turn is over return [4, "UNO", UNO_INFO] else: # If there's no carryover, just draw a card normally # Add the card to the player's hands UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] += [UNO_INFO["deck"][0]] UNO_INFO["deck"] = UNO_INFO["deck"][ 1:] # Remove the first card from the deck UNO_INFO["carryover"] = -2 # -2 carryover indicates drawing player_hand = UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] # Redefine player_hand # Sorts the player's hand UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] = list(sorted(player_hand)) player_hand = UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)] tag = str(time.time()).split(".")[1] # Report the new hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=player_hand) await message.channel.send( "You've drawn a card!", file=discord.File(f"Images/current_card_image_{tag}.png")) await UNO_INFO["channel"].send( f"**{message.author.name}** draws a card...") os.remove(f"Images/current_card_image_{tag}.png") for card in UNO_INFO["hands"][UNO_INFO["players"].index( message.author.id)]: if card[0] == UNO_INFO["last_card"][0] or card[1] == UNO_INFO[ "last_card"][1] or card[0] == "0": await message.channel.send( f"You have valid cards you can play! Use `{SERVER['PREFIX']}uno play` followed by a number to play one." ) return [4, "UNO", UNO_INFO] await message.channel.send( "You have no cards to play. Your turn is over!") if UNO_INFO["config"][ "no-cards"]: # no-cards is whether or not to publicly announce you have no cards await UNO_INFO["channel"].send( f"**{message.author.name}** has no cards to play!") UNO_INFO["current"] = 0 # Signals that the turn is overs return [4, "UNO", UNO_INFO] else: # If it's not "draw" or a card await message.channel.send( "That card number or action is not valid!") return if args[1].lower() == "quit": if not UNO_INFO["running"]: # If there's no round await message.channel.send("There's no Uno round to quit!") return if UNO_INFO["status"] == 1: # Redirect to tc/uno join await message.channel.send( f"To quit a game during signups, use `{SERVER['PREFIX']}uno join` again after already having joined." ) return if message.author.id not in UNO_INFO[ "players"]: # If you're not a player await message.channel.send( "You're not playing in the current uno round!") return player_index = UNO_INFO["players"].index( message.author.id) # Find the index of the player del UNO_INFO["players"][ player_index] # Delete the player from the players list del UNO_INFO["hands"][player_index] # Delete their hand too UNO_INFO["order"].remove( message.author.id) # Remove them from the order await UNO_INFO["channel"].send( f"**{message.author.name} has quit the round!**") UNO_INFO["current"] = 0 return [4, "UNO", UNO_INFO] if args[1].lower() == "end": if not UNO_INFO["running"]: # If there's no round await message.channel.send("There's no Uno round to end!") return if message.author.id != UNO_INFO[ "host"] and perms != 2: # Only the host and staff can end the game await message.channel.send("Only the host can end a game!") return await UNO_INFO["channel"].send("***The host has ended the round!***") UNO_INFO = uno_skip() # I've got a function for this, wow! return [4, "UNO", UNO_INFO] if args[1].lower() == "create": if UNO_INFO["running"]: # If a round has already been created await message.channel.send( "There's already a Uno round in progress!") return UNO_INFO["running"] = True # There is now a round UNO_INFO["status"] = 1 # Joining period UNO_INFO["host"] = message.author.id # Set the host UNO_INFO[ "channel"] = message.channel # The channel in which the game started is used for game updates await message.channel.send( f"<@{message.author.id}> is creating a Uno round! Send `{SERVER['PREFIX']}uno join` to join it." ) flag = False # Whether or not the timer is currently active sec = 0 # Variable to handle timers while True: # The signup while loop await asyncio.sleep(1) # Wait one second per iteration sec += 1 if sec == 120: # If 2 out of 3 minutes have passed, warn the host to start quickly await message.channel.send( f"""<@{UNO_INFO["host"]}>, you have 60 seconds to start the Uno round! Use `{SERVER['PREFIX']}uno start` to start it. If you don't do it in time, you'll be skipped as the host. """.replace("\n", "").replace("\t", "")) if len( UNO_INFO["players"] ) < 2: # If there are less than two players there is no timer sec = 0 if len( UNO_INFO["players"] ) >= 2 and not flag: # If the timer is not active but it should be flag = True await message.channel.send( f"""Two players have joined the round. <@{UNO_INFO["host"]}> now has **three minutes** to start it with `{SERVER['PREFIX']}uno start`!""".replace("\n", "").replace("\t", "")) if len( UNO_INFO["players"] ) < 2 and flag: # If the timer is active but it shouldn't be flag = False await message.channel.send( "There is no longer a sufficient number of players to start. The start timer will be reset." ) if sec >= 180: # If the host ran out of time await message.channel.send( f"**<@{UNO_INFO['host']}>** has been skipped from hosting the round." ) UNO_INFO = uno_skip() return [4, "UNO", UNO_INFO] if UNO_INFO["status"] == 2: # Represents that the game has started break UNO_INFO["players"] = list(OrderedDict.fromkeys(UNO_INFO["players"])) UNO_INFO["order"] = UNO_INFO["players"] # Prepare the order start_cards = UNO_INFO["config"][ "start"] # Amount of cards each person starts with if len(UNO_INFO["players"]) * start_cards + 10 > len( ORIGINAL_DECK): # If there are not enough cards on the # deck to suit the player count and/or starting card number # How many times to multiply the original deck and add it to the original to suit the card demand? multiplier = ((len(UNO_INFO["players"]) * start_cards + 10 - len(ORIGINAL_DECK)) // len(ORIGINAL_DECK)) + 1 UNO_INFO["deck"] += ORIGINAL_DECK * multiplier random.shuffle(UNO_INFO["deck"]) # Shuffle the deck UNO_INFO["deck"] = UNO_INFO["deck"][0:] for player in UNO_INFO["players"]: # For each player UNO_INFO["hands"].append( UNO_INFO["deck"][:start_cards]) # Give the player their cards UNO_INFO["deck"] = UNO_INFO["deck"][ start_cards:] # Remove those cards from the deck UNO_INFO["hands"][-1] = list(sorted( UNO_INFO["hands"][-1])) # Sort that hand tag = str(time.time()).split(".")[ 1] # Give the person their starting cards uno_image(2, tag, SERVER['PREFIX'], last="QC", hand=UNO_INFO["hands"][-1]) await SERVER['MAIN'].get_member(player).send( "The Uno round is starting! Here's your hand:", file=discord.File(f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") for cards in range(len(UNO_INFO["deck"])): # Go through the deck if UNO_INFO["deck"][cards][0] == "0": continue # Find the first card that isn't a wild card UNO_INFO["last_card"] = UNO_INFO["deck"][ cards] # Make that card the starting card UNO_INFO["deck"].remove( UNO_INFO["deck"][cards]) # Remove it from the deck break # Start it off await message.channel.send( "The round has started! The starting card has been placed.") while True: # This is the handler for the entire game. Each iteration is a player's turn if len(UNO_INFO["deck"]) <= 10: # If there are too few cards UNO_INFO[ "deck"] += ORIGINAL_DECK # Add the original deck to the current one random.shuffle(UNO_INFO["deck"]) # Reshuffle UNO_INFO["deck"] = UNO_INFO["deck"][0:] UNO_INFO["players"] = list( OrderedDict.fromkeys(UNO_INFO["players"])) current_player = UNO_INFO["order"][ 0] # Get the current player having their turn player_index = UNO_INFO["players"].index( current_player) # This becomes useful later on UNO_INFO["current"] = current_player # Record the current player player_member = SERVER['MAIN'].get_member(current_player) player_name = player_member.name # Their username tag = str(time.time()).split(".")[ 1] # Make the image reporting that it's the player's turn uno_image(1, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], name=player_name) await message.channel.send( file=discord.File(f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") if UNO_INFO["carryover"] > 1 or UNO_INFO[ "carryover"] < -3: # If there's cards to be drawn await message.channel.send( # Report so f"Someone played a **+{numpy.abs(UNO_INFO['carryover'])}** upon **{player_name}**!" ) await player_member.send( f"Someone played a **+{numpy.abs(UNO_INFO['carryover'])}** upon you!" ) defense = False # Can the player defend against the cards? for card in UNO_INFO["hands"][player_index]: if card[1] == "F" and UNO_INFO[ "carryover"] < -3: # If it's made of +4s and the player has one await SERVER['MAIN'].get_member(current_player).send( "**You must play a +4 or draw the cards!**") defense = True break if card[1] == "D" and UNO_INFO[ "carryover"] > 1: # If it's made of +2s and the player has one await SERVER['MAIN'].get_member(current_player).send( "**You must play a +2 or draw the cards!**") defense = True break if not defense: # If they can't defend, they have to draw dc = numpy.abs(UNO_INFO["carryover"] ) # Report that the player drew the cards await message.channel.send( f"**{player_name}** draws {dc} cards!") UNO_INFO["hands"][player_index] += UNO_INFO[ "deck"][:dc] # Add the cards to the player's hand UNO_INFO["hands"][player_index] = list( sorted( UNO_INFO["hands"][player_index])) # Sort the hand UNO_INFO["deck"] = UNO_INFO["deck"][ dc:] # Remove those cards from the deck tag = str(time.time()).split(".")[ 1] # Send the player their new hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=UNO_INFO["hands"][player_index]) await player_member.send( f"You drew **{dc}** cards!", file=discord.File( f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") UNO_INFO["carryover"] = 0 # Reset the carryovers if UNO_INFO["config"][ "d-skip"]: # d-skip is whether players who drew from +2 or +4 are skipped UNO_INFO["order"] += UNO_INFO["order"][0:1] UNO_INFO["order"] = UNO_INFO["order"][1:] continue # Do another iteration. If they weren't skipped, it's still their turn tag = str(time.time()).split(".")[1] # Show the player their cards uno_image(0, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=UNO_INFO["hands"][player_index]) await player_member.send( """It is now your turn to play a card! You have **1 minute** to play, or you'll be skipped.""", file=discord.File(f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") sec = 0 # Second number played = UNO_INFO[ "current"] # Memory for what the current player is supposed to be while True: # Handler that waits for a card to be played await asyncio.sleep(1) # One second per iteration sec += 1 if UNO_INFO[ "carryover"] == -2 and flag: # If the player has drawn sec = 0 # Reset their timer await player_member.send( "Since you've drawn a card, your timer has been reset. You have **1 minute** to play." ) flag = False if UNO_INFO["current"] != played: break # If the current player has been changed it means the turn ended if sec >= 60 and UNO_INFO[ "current"] == played: # If the 1 minute timer ran out, the player draws cards if UNO_INFO["carryover"] not in [ 0, 2 ]: # They draw the cards to be drawn if there are any dc = numpy.abs(UNO_INFO["carryover"]) else: # Otherwise they draw one dc = 1 UNO_INFO["hands"][player_index] += UNO_INFO[ "deck"][:dc] # Add the cards to the player's hand UNO_INFO["hands"][player_index] = list( sorted( UNO_INFO["hands"][player_index])) # Sort the hand UNO_INFO["deck"] = UNO_INFO["deck"][ dc:] # Remove those cards from the deck UNO_INFO["carryover"] = 0 # Reset the carryover grammar = "" if dc == 1 else "s" tag = str( time.time()).split(".")[1] # Report their new hand uno_image(2, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], hand=UNO_INFO["hands"][player_index]) await player_member.send( "You took too long to play! You will draw cards as punishment.", file=discord.File( f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") await player_member.send("Your turn is over!") await message.channel.send( f"""**{player_name}** has been skipped for taking too long, drawing **{dc}** card{'' if dc == 1 else 's'}.""".replace("\n", "").replace("\t", "")) break # Finish the while loop, turn ended if current_player not in UNO_INFO["players"]: continue if len(UNO_INFO["hands"][player_index] ) == 1: # If the player has only one card... UNO! await message.channel.send(f"**{player_name} is at UNO!**") if ( len(UNO_INFO["hands"][player_index]) == 0 or len(UNO_INFO["players"]) == 1 ): # If they have no cards, they win! Or also if everyone else quit tag = str(time.time()).split(".")[1] # Report the victory uno_image(3, tag, SERVER['PREFIX'], last=UNO_INFO["last_card"], name=player_name) await message.channel.send( f"**{player_name} WINS THE GAME!**", file=discord.File(f"Images/current_card_image_{tag}.png")) os.remove(f"Images/current_card_image_{tag}.png") break # End the while loop of the whole game # If the game didn't end, increment the order UNO_INFO["order"] += UNO_INFO["order"][0:1] UNO_INFO["order"] = UNO_INFO["order"][1:] if UNO_INFO["carryover"] == -1: # -1 carryover means a skip card skipped_player = UNO_INFO["order"][ 0] # The next player over is skipped await message.channel.send( f"**{player_name}** skips **{SERVER['MAIN'].get_member(skipped_player).name}**'s turn!" ) UNO_INFO["order"] += UNO_INFO["order"][ 0:1] # Increment order again UNO_INFO["order"] = UNO_INFO["order"][1:] if UNO_INFO[ "carryover"] == -3: # -3 carryover means a reverse card if len(UNO_INFO["players"] ) != 2: # Normal reverse card behavior UNO_INFO["order"] = UNO_INFO[ "order"][::-1] # Reverse the order list UNO_INFO["order"] += [current_player ] # Add player to the end UNO_INFO["order"] = UNO_INFO["order"][ 1:] # Increment order else: # If there are only two people, reverse is identical to skip UNO_INFO["order"] += UNO_INFO["order"][0:1] UNO_INFO["order"] = UNO_INFO["order"][1:] await message.channel.send( f"**{player_name}** reversed the order!") if UNO_INFO["carryover"] in [ -1, -2, -3 ]: # These three drawovers last for one turn only UNO_INFO["carryover"] = 0 # They're reset right after UNO_INFO = uno_skip() # The game is over! Reset the uno_info dict. return [4, "UNO", UNO_INFO]
async def MAIN(message, args, level, perms, SERVER): if level == 1: await message.channel.send("Include a subcommand!") return db = Database() if args[1].lower() == "info": tag_list = db.get_entries("b++2programs", columns=["name", "program", "author", "uses", "created", "lastused"]) tag_list = sorted(tag_list, reverse=True, key=lambda m: m[3]) tag_leaderboard = False if level == 2: # If it's not specified, assume it's the first page tag_list = tag_list[:10] page = 1 tag_leaderboard = True elif is_whole(args[2]): if (int(args[2]) - 1) * 10 >= len(tag_list): # Detect if the page number is too big await message.channel.send(f"There is no page {args[2]} on the New B++ program list!") return else: # This means the user specified a valid page number lower = (int(args[2]) - 1) * 10 upper = int(args[2]) * 10 tag_list = tag_list[lower:upper] page = int(args[2]) tag_leaderboard = True if tag_leaderboard: beginning = f"```diff\nB++ Programs Page {page}\n\n" for program in tag_list: r = tag_list.index(program) + 1 + (page - 1) * 10 line = f"{r}{' '*(2-len(str(r)))}: {program[0]} :: {program[3]} use{'s' if program[3] != 1 else ''}" member_id = program[2] try: # Try to gather a username from the ID member = SERVER["MAIN"].get_member(int(member_id)).name except: # If you can't, just display the ID member = str(member_id) created_on = dt.utcfromtimestamp(program[4]).strftime('%Y-%m-%d %H:%M:%S UTC') line += f" (written by {member} at {created_on})\n" beginning += line # Add this line to the final message beginning += "```" # Close off code block await message.channel.send(beginning) return tag_name = args[2] if tag_name not in [x[0] for x in tag_list]: await message.channel.send("That tag does not exist.") return program = tag_list[[x[0] for x in tag_list].index(tag_name)] member_id = program[2] try: # Try to gather a username from the ID member = SERVER["MAIN"].get_member(int(member_id)).name except: # If you can't, just display the ID member = str(member_id) created_on = dt.utcfromtimestamp(program[4]).strftime('%Y-%m-%d %H:%M:%S UTC') c_d = dt.now() - dt.utcfromtimestamp(program[4]) d = c_d.days h, rm = divmod(c_d.seconds, 3600) m, s = divmod(rm, 60) c_d = (('' if d==0 else f'{d} day{"s" if d!=1 else ""}, ') + ('' if h==0 else f'{h} hour{"s" if h!=1 else ""}, ') + ('' if m==0 else f'{m} minute{"s" if m!=1 else ""}, ') + (f'{s} second{"s" if s!=1 else ""}')) msg = f"**{program[0]}** -- by {member} -- {program[3]} use{'s' if program[3]!=1 else ''}\n" msg += f"Created on {created_on} `({c_d} ago)`\n" if program[5] != 0: last_used = dt.utcfromtimestamp(program[5]).strftime('%Y-%m-%d %H:%M:%S UTC') u_d = dt.now() - dt.utcfromtimestamp(program[5]) d = u_d.days h, rm = divmod(u_d.seconds, 3600) m, s = divmod(rm, 60) u_d = (('' if d==0 else f'{d} day{"s" if d!=1 else ""}, ') + ('' if h==0 else f'{h} hour{"s" if h!=1 else ""}, ') + ('' if m==0 else f'{m} minute{"s" if m!=1 else ""}, ') + (f'{s} second{"s" if s!=1 else ""}')) msg += f"Last used on {last_used} `({u_d} ago)`\n" if len(program[1]) > 1700: msg += f"The program is too long to be included in the message, so it's in the file below:" open(f'program_{program[0]}.txt', 'w', encoding="utf-8").write(program[1]) await message.channel.send(msg, file=discord.File(f'program_{program[0]}.txt')) os.remove(f'program_{program[0]}.txt') else: msg += f"```{program[1]}```" await message.channel.send(msg) return if args[1].lower() == "create": if level == 2: await message.channel.send("Include the name of your new program!") return tag_name = args[2] if re.search(r"[^0-9A-Za-z_]", tag_name) or re.search(r"[0-9]", tag_name[0]): await message.channel.send( "Tag name can only contain letters, numbers and underscores, and cannot start with a number!") return if tag_name in ["create", "edit", "delete", "info", "run", "help"]: await message.channel.send("The tag name must not be a reserved keyword!") return if len(tag_name) > 30: await message.channel.send("That tag name is too long. 30 characters maximum.") return if level > 3: program = " ".join(args[3:]) elif len(message.attachments) != 0: try: if message.attachments[0].size >= 20000: await message.channel.send("Your program must be under **20KB**.") return await message.attachments[0].save(f"Config/{message.id}.txt") except Exception: await message.channel.send("Include a valid program to save!") return program = open(f"Config/{message.id}.txt", "r", encoding="utf-8").read() os.remove(f"Config/{message.id}.txt") else: await message.channel.send("Include a valid program to save!") return while program.startswith("`") and program.endswith("`"): program = program[1:-1] program.replace("{}", "\t") if (tag_name,) in db.get_entries("b++2programs", columns=["name"]): await message.channel.send("There's already a program with that name!") return db.add_entry("b++2programs", [tag_name, program, message.author.id, 0, time.time(), 0]) await message.channel.send(f"Successfully created program `{tag_name}`!") return if args[1].lower() == "edit": if level == 2: await message.channel.send("Include the name of the program you want to edit!") return tag_name = args[2] tag_list = db.get_entries("b++2programs", columns=["name", "author"]) if tag_name not in [x[0] for x in tag_list]: await message.channel.send(f"There's no program under the name `{tag_name}`!") return ind = [x[0] for x in tag_list].index(tag_name) if tag_list[ind][1] != str(message.author.id) and perms < 2: await message.channel.send(f"You can only edit a program if you created it or if you're a staff member!") return if level > 3: program = " ".join(args[3:]) elif len(message.attachments) != 0: try: if message.attachments[0].size >= 20000: await message.channel.send("Your program must be under **20KB**.") return await message.attachments[0].save(f"Config/{message.id}.txt") except Exception: await message.channel.send("Include a valid program to run!") return program = open(f"Config/{message.id}.txt", "r", encoding="utf-8").read() os.remove(f"Config/{message.id}.txt") else: await message.channel.send("Include a valid program to run!") return while program.startswith("`") and program.endswith("`"): program = program[1:-1] program = program.replace("{}", "\v") db.edit_entry("b++2programs", entry={"program": program}, conditions={"name": tag_name}) await message.channel.send(f"Succesfully edited program {tag_name}!") return if args[1].lower() == "delete": if level == 2: await message.channel.send("Include the name of the program you want to delete!") return tag_name = args[2] tag_list = db.get_entries("b++2programs", columns=["name", "author"]) if tag_name not in [x[0] for x in tag_list]: await message.channel.send(f"There's no program under the name `{tag_name}`!") return ind = [x[0] for x in tag_list].index(tag_name) if tag_list[ind][1] != str(message.author.id) and perms < 2: await message.channel.send(f"You can only edit a program if you created it or if you're a staff member!") return db.remove_entry("b++2programs", conditions={"name": tag_name}) await message.channel.send(f"Succesfully deleted program {tag_name}!") return if args[1].lower() == "run": if level > 2: program = " ".join(args[2:]) elif len(message.attachments) != 0: try: if message.attachments[0].size >= 20000: await message.channel.send("Your program must be under **20KB**.") return await message.attachments[0].save(f"Config/{message.id}.txt") except Exception: await message.channel.send("Include a valid program to run!") return program = open(f"Config/{message.id}.txt", "r", encoding="utf-8").read() os.remove(f"Config/{message.id}.txt") else: await message.channel.send("Include a valid program to run!") return while program.startswith("`") and program.endswith("`"): program = program[1:-1] program = program.replace("{}", "\v") program_args = [] author = message.author.id else: tag_name = args[1] tag_list = db.get_entries("b++2programs", columns=["name", "program", "author", "uses"]) if tag_name not in [x[0] for x in tag_list]: await message.channel.send(f"There's no program under the name `{tag_name}`!") return tag_info = [x for x in tag_list if x[0] == tag_name][0] program = tag_info[1] uses = tag_info[3] + 1 db.edit_entry("b++2programs", entry={"uses": uses, "lastused": time.time()}, conditions={"name": tag_name}) program_args = args[2:] author = tag_info[2] try: program_output = run_bpp_program(program, program_args, author) program_output = program_output.replace("<@", "<\\@") except Exception as e: await message.channel.send(f'{type(e).__name__}:\n```{e}```') return if len(program_output) > 1950: program_output = "⚠️ `Output too long! First 1900 characters:`\n\n" + program_output[:1900] if len(program_output.strip()) == 0: program_output = "\u200b" await message.channel.send(program_output) return
async def MAIN(message, args, level, perms, TWOW_CENTRAL): db = Database() months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] if level == 1: # If just tc/bd, return info on their birthday found = db.get_entries("birthday", conditions={"id": str(message.author.id)}) if found == []: await message.channel.send( f"""You are yet to register your birthday! You can register by using **{PREFIX}birthday register `DD/MM` `timezone`**""" .replace("\t", "")) return birthday, tz = found[0][1:] birthday = birthday.split("/") birthday_format = months[int(birthday[1]) - 1] + " " + str(birthday[0]) timezone_f = ("+" if tz > 0 else "") + str(tz) await message.channel.send( f"""**{message.author.name}**'s birthday is set as **{birthday_format}** in **UTC {timezone_f}**.""" ) return if args[1].lower() == "view": if level == 2: found = db.get_entries("birthday") found = sorted(found, key=lambda k: int(k[1].split("/")[0])) found = sorted(found, key=lambda k: int(k[1].split("/")[1])) day, month = datetime.now(timezone.utc).day, datetime.now( timezone.utc).month for bd in found: if int(bd[1].split("/")[1]) > month: next_bd = bd break elif int(bd[1].split("/")[1]) == month and int( bd[1].split("/")[0]) > day: next_bd = bd break else: next_bd = found[0] next_id, birthday, tz = next_bd birthday = birthday.split("/") birthday_format = months[int(birthday[1]) - 1] + " " + str( int(birthday[0])) timezone_f = ("+" if tz > 0 else "") + str(tz) try: username = TWOW_CENTRAL.get_member(int(next_id)).name except AttributeError: username = next_id await message.channel.send( f"The next birthday is **{username}**'s, on **{birthday_format}** in **UTC {timezone_f}**." ) return rest = " ".join(args[2:]) if rest.startswith("<@") and rest.endswith(">"): rest = rest[2:-1] if is_whole(rest): found = db.get_entries("birthday", conditions={"id": rest}) try: username = TWOW_CENTRAL.get_member(int(rest)).name except: username = rest if found == []: await message.channel.send( f"**{username}** has not registered a birthday yet!") return user_bd = found[0] birthday, tz = user_bd[1:] birthday = birthday.split("/") birthday_format = months[int(birthday[1]) - 1] + " " + str( int(birthday[0])) timezone_f = ("+" if tz > 0 else "") + str(tz) await message.channel.send( f"**{username}**'s birthday is on **{birthday_format}** in **UTC {timezone_f}**." ) return else: user = discord.utils.get(TWOW_CENTRAL.members, name=rest) if user is None: await message.channel.send("That user is not in the server!") return found = db.get_entries("birthday", conditions={"id": str(user.id)}) if found == []: await message.channel.send( f"**{rest}** has not registered a birthday yet!") return user_bd = found[0] birthday, tz = user_bd[1:] birthday = birthday.split("/") birthday_format = months[int(birthday[1]) - 1] + " " + str( int(birthday[0])) timezone_f = ("+" if tz > 0 else "") + str(tz) await message.channel.send( f"**{rest}**'s birthday is on **{birthday_format}** in **UTC {timezone_f}**." ) return if args[1].lower() == "register": if level == 2: await message.channel.send( "Include your birthday in `DD/MM` to register!") return # Check if the person is in the birthday database or not found = db.get_entries("birthday", conditions={"id": str(message.author.id)}) print(found) birthday = args[2].split("/") if level == 3: tz = 0 elif not is_whole(args[3]): await message.channel.send( "Invalid timezone! Make sure it's a whole number from -12 to 14!" ) return elif not -12 <= int(args[3]) <= 14: await message.channel.send( "Invalid timezone! Make sure it's a whole number from -12 to 14!" ) return else: tz = int(args[3]) if len(birthday) != 2: # If it's not `n/n` await message.channel.send( "Invalid birthday! Make sure it's in the `DD/MM` format!") return if not is_whole(birthday[0]) or not is_whole( birthday[1]): # If day and month aren't numbers await message.channel.send( "Invalid birthday! Make sure the day and month are both numbers!" ) return # Transform into integers for these next two checks birthday[0] = int(birthday[0]) birthday[1] = int(birthday[1]) if not 1 <= birthday[1] <= 12: # If month is invalid await message.channel.send( "Invalid month! Make sure it's between 1 and 12.") return if not 1 <= birthday[0] <= monthrange( 2020, birthday[1])[1]: # monthrange checks days in the month await message.channel.send( # 2020 months because it's a leap year, and 29/02 should be available f"Invalid day! Make sure it's between 1 and {monthrange(2020, birthday[1])[1]} for that month." ) return birthday_format = months[birthday[1] - 1] + " " + str(birthday[0]) birthday = "/".join([str(x) for x in birthday ]) # Join the list again for the next few lines timezone_f = ("+" if tz > 0 else "") + str(tz) # This confirmation message cannot be bypassed await message.channel.send( f"""Are you sure you want to record your birthday as {birthday_format} and your timezone as UTC {timezone_f}? Send `confirm` in this channel to confirm. """.replace("\n", "").replace("\t", "")) # Wait for a message by the same author in the same channel msg = await BRAIN.wait_for( 'message', check=(lambda m: m.channel == message.channel and m.author == message.author)) if msg.content.lower( ) != "confirm": # If it's not `confirm`, cancel command await message.channel.send("Birthday registering cancelled.") return # If confirmation passed, record the birthday if found == []: is_new = "" db.add_entry("birthday", [message.author.id, birthday, tz]) else: is_new = "new " db.edit_entry("birthday", entry={ "birthday": birthday, "timezone": tz }, conditions={"id": str(message.author.id)}) await message.channel.send( f"Successfully recorded your {is_new}birthday as **{birthday} UTC {timezone_f}**!" ) return
def ARGS(a): if not is_whole(a): raise ValueError( f"ARGS function index must be an integer: {safe_cut(a)}") return ("a", int(a))
async def MAIN(message, args, level, perms, SERVER): if level == 1: await message.channel.send("Include a subcommand!") return args = message.content.split("\n")[0].split(" ") level = len(args) if args[1].lower() == "add": if level == 2: await message.channel.send( "Include a user to warn along with warning information!") return user = "******".join(args[2:]).strip() if user.startswith("<@") and user.endswith(">"): user = user[2:-1] memberbase = SERVER["MAIN"].members m_names = [m.name for m in memberbase] m_ids = [m.id for m in memberbase] matched = False if is_whole(user): if int(user) in m_ids: matched = memberbase[m_ids.index(int(user))] if not matched: m_l_names = [n.lower() for n in m_names] if user in m_names: matched = memberbase[m_names.index(user)] if not matched and user.lower() in m_l_names: matched = memberbase[m_l_names.index(user.lower())] if not matched: start_match = [n for n in m_names if n.startswith(user)] if len(start_match) == 1: matched = memberbase[m_names.index(start_match[0])] else: start_match_l = [ n for n in m_l_names if n.startswith(user.lower()) ] if len(start_match_l) == 1: matched = memberbase[m_l_names.index(start_match_l[0])] if not matched: mid_match = [n for n in m_names if user in n] if len(mid_match) == 1: matched = memberbase[m_names.index(mid_match[0])] else: mid_match_l = [n for n in m_l_names if user.lower() in n] if len(mid_match_l) == 1: matched = memberbase[m_l_names.index(mid_match_l[0])] if not matched: await message.channel.send( "Cannot recognize that user as someone in the server!") return added_info = message.content.split("\n")[1:] if len(added_info) == 0: await message.channel.send("Include warning information!") return data = { "name": f"{matched.name}#{matched.discriminator}", "id": str(matched.id), "count": None, "desc": None, "proof": None } for line in added_info: l_args = line.split(" ") l_args = [l for l in l_args if l != ""] if l_args[0].lower().startswith("count"): for i in l_args[1:]: if is_whole(i): data["count"] = int(i) if data["count"] == None: await message.channel.send( f"Invalid warning count `{' '.join(l_args[1:])}`!") return if l_args[0].lower().startswith("desc"): data["desc"] = ' '.join(l_args[1:]) if l_args[0].lower().startswith("proof"): data["proof"] = ' '.join(l_args[1:]) if data["count"] == None: await message.channel.send("Missing the warning count!") return if data["desc"] == None: await message.channel.send("Missing the warning description!") return if data["proof"] == None: await message.channel.send("Missing the warning proof!") return s = "" if data["count"] == 1 else "s" embed = discord.Embed(color=0x31D8B1) embed.title = f"{data['count']} warning{s}" embed.description = f"{data['name']}\n{data['id']}\n<@{data['id']}>" embed.add_field(name="Description", value=data['desc'], inline=False) embed.add_field(name="Proof", value=data['proof']) embed.set_thumbnail(url=matched.avatar_url_as(static_format="png")) msg = await message.channel.send( "**__Is this information correct?__**", embed=embed) await msg.add_reaction("🇾") await msg.add_reaction("🇳") def check(r, u): return (u == message.author and str(r.emoji) in ["🇾", "🇳"] and r.message.id == msg.id) try: r, _ = await BRAIN.wait_for('reaction_add', timeout=30, check=check) except asyncio.TimeoutError: await message.channel.send("Warning command timed out.") return else: if str(r.emoji) == "🇳": await message.channel.send(f"Warning command cancelled.") return x = requests.post(WARNING_APP, data=data) if not x.ok: await message.channel.send( "Something went wrong while sending the HTTP request!") raise ConnectionError(x.text) new_count = int(x.text) start = f"Successfully logged **{data['count']}** warning{s} for **{data['name']}**!\n" s = "" if new_count == 1 else "s" start += f"They now have a total of **{new_count}** warning{s}.\n\n" if new_count >= 6: start += "__This user has passed the warning ban threshold! **Do you wish to ban them?**__" msg = await message.channel.send(start) await msg.add_reaction("🇾") await msg.add_reaction("🇳") def check(r, u): return (u == message.author and str(r.emoji) in ["🇾", "🇳"] and r.message.id == msg.id) try: r, _ = await BRAIN.wait_for('reaction_add', timeout=30, check=check) except asyncio.TimeoutError: await message.channel.send("Ban function timed out.") return else: if str(r.emoji) == "🇳": await message.channel.send( f"**{data['name']}** will not be banned.") return try: await SERVER["MAIN"].ban( matched, reason=f"Acquired a total of {new_count} warnings.", delete_message_days=0) except discord.Forbidden: await message.channel.send( f"No permission to ban **{data['name']}**.") else: await message.channel.send(start) return
async def MAIN(message, args, level, perms, SERVER): if level == 1: await message.channel.send("Include a subcommand!") return if args[1].lower() == "setup": if level == 2: n = 10 elif is_whole(args[2]): n = int(args[2]) else: n = 10 msg_ids = [str(message.channel.id)] for _ in range(n): msg = await message.channel.send("\u200b") msg_ids.append(str(msg.id)) await message.channel.send(" ".join(msg_ids)) return if args[1].lower() == "update": await message.channel.send("Updating list...") await SERVER["EVENTS"]["SIGNUPS"].update_list(update_channel=True) await message.channel.send("Updated list!") return if args[1].lower() == "edit": msg = message.content if "name:[" not in msg: await message.channel.send( "Include the name of the TWOW you want to edit!") return db = Database() starting_bound = msg[msg.find("name:[") + 6:] twow_name = starting_bound[:starting_bound.find("]")] twow_list = db.get_entries("signuptwows", conditions={"name": twow_name}) if len(twow_list) == 0: await message.channel.send( f"There's no TWOW named **{twow_name}** in the signup list!") return old_entry = twow_list[0] old_entry = dict( zip(["name", "hosts", "link", "description", "time", "verified"], old_entry)) entry = { "name": None, "hosts": None, "link": None, "description": None, "time": None, "verified": None } cond = {"name": twow_name} if "newname:[" in msg: starting_bound = msg[msg.find("newname:[") + 9:] new_name = starting_bound[:starting_bound.find("]")] entry["name"] = new_name if "host:[" in msg: starting_bound = msg[msg.find("host:[") + 6:] hosts = starting_bound[:starting_bound.find("]")] entry["hosts"] = hosts if "link:[" in msg: starting_bound = msg[msg.find("link:[") + 6:] link = starting_bound[:starting_bound.find("]")].replace( "<", "").replace(">", "") entry["link"] = link if "desc:[" in msg: starting_bound = msg[msg.find("desc:[") + 6:] desc = starting_bound[:starting_bound.find("]")] entry["description"] = desc if "deadline:[" in msg: starting_bound = msg[msg.find("deadline:[") + 10:] dl_string = starting_bound[:starting_bound.find("]")] deadline = datetime.datetime.strptime(dl_string, "%d/%m/%Y %H:%M") deadline = deadline.replace( tzinfo=datetime.timezone.utc).timestamp() entry["time"] = deadline if "verified:[" in msg: starting_bound = msg[msg.find("verified:[") + 10:] verified = starting_bound[:starting_bound.find("]")] if verified in ["0", ""]: vf = 0 else: vf = 1 entry["verified"] = vf entry = {k: d for k, d in entry.items() if d is not None} if len(entry.keys()) == 0: await message.channel.send("You've made no edits to this TWOW!") return db.edit_entry("signuptwows", entry=entry, conditions=cond) announce = "dont_announce" not in msg await SERVER["EVENTS"]["SIGNUPS"].update_list(announce=announce) old_info_string = "" for k, v in old_entry.items(): if v != "": tag = k if k == "hosts": tag = "host" if k == "time": tag = "deadline" if k == "description": tag = "desc" old_info_string += f"{tag}:[{v}] " for k, v in entry.items(): old_entry[k] = v new_info_string = "" for k, v in old_entry.items(): if v != "": tag = k if k == "hosts": tag = "host" if k == "time": tag = "deadline" if k == "description": tag = "desc" if k == "link": v = f"<{v}>" new_info_string += f"{tag}:[{v}] " await message.channel.send( f"""**{cond['name']}** has been edited in the signup list. **Old TWOW Info**: {old_info_string} **New TWOW Info**: {new_info_string}""".replace("\t", "")) if args[1].lower() == "remove": msg = message.content if level == 2: await message.channel.send( "Include the name of the TWOW you want to remove!") return db = Database() if "dont_announce" in msg: twow_name = " ".join(args[2:-1]) else: twow_name = " ".join(args[2:]) twow_list = db.get_entries("signuptwows", conditions={"name": twow_name}) if len(twow_list) == 0: await message.channel.send( f"There's no TWOW named **{twow_name}** in the signup list!") return twow_info = twow_list[0] dl_format = datetime.datetime.utcfromtimestamp( twow_info[4]).strftime("%d/%m/%Y %H:%M") db.remove_entry("signuptwows", conditions={"name": twow_name}) announce = "dont_announce" not in msg await SERVER["EVENTS"]["SIGNUPS"].update_list(announce=announce) await message.channel.send( f"""**{twow_info[0]}** has been removed from the signup list! **TWOW Info**: """.replace("\t", "") + f"""name:[{twow_info[0]}] host:[{twow_info[1]}] link:[{twow_info[2]}] desc:[{twow_info[3]}] deadline:[{dl_format}] {'is_verified' if bool(twow_info[5]) else ''}""".replace("\n", "").replace( "\t", "")) return if args[1].lower() == "add": msg = message.content if "name:[" not in msg: await message.channel.send( "Include the name of the TWOW you want to add!") return db = Database() starting_bound = msg[msg.find("name:[") + 6:] twow_name = starting_bound[:starting_bound.find("]")] entry = [twow_name, "", "", "", 0, 0, ""] if "host:[" in msg: starting_bound = msg[msg.find("host:[") + 6:] hosts = starting_bound[:starting_bound.find("]")] entry[1] = hosts if "link:[" in msg: starting_bound = msg[msg.find("link:[") + 6:] link = starting_bound[:starting_bound.find("]")].replace( "<", "").replace(">", "") entry[2] = link if "desc:[": starting_bound = msg[msg.find("desc:[") + 6:] desc = starting_bound[:starting_bound.find("]")] entry[3] = desc if "deadline:[" in msg: starting_bound = msg[msg.find("deadline:[") + 10:] deadline = starting_bound[:starting_bound.find("]")] entry[6] = deadline deadline = datetime.datetime.strptime(deadline, "%d/%m/%Y %H:%M") deadline = deadline.replace( tzinfo=datetime.timezone.utc).timestamp() entry[4] = deadline vf = 0 if "verified:[" in msg: starting_bound = msg[msg.find("verified:[") + 10:] verified = starting_bound[:starting_bound.find("]")] if verified not in ["0", ""]: vf = 1 entry[5] = vf db.add_entry("signuptwows", entry[:6]) announce = "dont_announce" not in msg await SERVER["EVENTS"]["SIGNUPS"].update_list(announce=announce) await message.channel.send( f"""**{entry[0]}** has been added to the list of TWOWs in signups! **Host:** {entry[1]} **Description:** {entry[3]} **Deadline:** {entry[6]} **Deadline Timestamp:** {entry[4]} **Link:** <{entry[2]}>""".replace("\t", ""))
def run_bpp_program(code, p_args, author): # Pointers for tag and function organization tag_level = 0 tag_code = [] tag_str = lambda: ' '.join([str(s) for s in tag_code]) backslashed = False # Flag for whether to unconditionally escape the next character functions = {} # Dict flattening a tree of all functions to be evaluated current = [ "", False ] # Raw text of what's being parsed right now + whether it's a string output = "" # Stores the final output of the program goto = 0 # Skip characters in evaluating the code for ind, char in enumerate(list(code)): normal_case = True if ind < goto: continue if backslashed: if tag_code == []: output += char else: current[0] += char backslashed = False continue if char == "\\": backslashed = True continue if char == "[" and not current[1]: tag_level += 1 if tag_level == 1: try: tag_code = [ max([int(k) for k in functions if is_whole(k)]) + 1 ] except ValueError: tag_code = [0] output += "{}" found_f = "" for f_name in FUNCTIONS.keys(): try: if ''.join(code[ind + 1:ind + len(f_name) + 2]).upper() == f_name + " ": found_f = f_name goto = ind + len(f_name) + 2 except IndexError: pass if found_f == "": end_of_f = min(code.find(" ", ind + 1), code.find("]", ind + 1)) called_f = ''.join(code[ind + 1:end_of_f]) raise NameError(f"Function {called_f} does not exist") functions[tag_str()] = [found_f] else: old_tag_code = tag_str() k = 1 while old_tag_code + f" {k}" in functions.keys(): k += 1 new_tag_code = old_tag_code + f" {k}" found_f = "" for f_name in FUNCTIONS.keys(): try: if ''.join(code[ind + 1:ind + len(f_name) + 2]).upper() == f_name + " ": found_f = f_name goto = ind + len(f_name) + 2 except IndexError: pass if found_f == "": end_of_f = min(code.find(" ", ind + 1), code.find("]", ind + 1)) called_f = ''.join(code[ind + 1:end_of_f]) raise NameError(f"Function {called_f} does not exist") functions[new_tag_code] = [found_f] functions[tag_str()].append((new_tag_code, )) tag_code.append(k) normal_case = False if char == "]" and not current[1]: if current[0] != "": functions[tag_str()].append(current[0]) current = ["", False] tag_level -= 1 normal_case = False if char == " ": if not current[1] and tag_level != 0: if current[0] != "": functions[tag_str()].append(current[0]) current = ["", False] normal_case = False if char in '"“”': if current[0] == "" and not current[1]: current[1] = True elif current[1]: functions[tag_str()].append(current[0]) current = ["", False] normal_case = False if normal_case: if tag_level == 0: output += char else: current[0] += char tag_code = tag_code[:tag_level] tag_code += [1] * (tag_level - len(tag_code)) VARIABLES = {} base_keys = [k for k in functions if is_whole(k)] db = Database() type_list = [int, float, str, list] def var_type(v): try: return type_list.index(type(v)) except IndexError: raise TypeError( f"Value {safe_cut(v)} could not be attributed to any valid data type" ) def evaluate_result(k): v = functions[k] if type(v) == tuple: k1 = v[0] functions[k] = evaluate_result(k1) return functions[k] args = v[1:] for i, a in enumerate(args): if v[0] == "IF" and is_whole(v[1]) and int(v[1]) != 2 - i: continue if type(a) == tuple: k1 = a[0] functions[k][i + 1] = evaluate_result(k1) args = v[1:] result = FUNCTIONS[v[0]](*args) # Tuples indicate special behavior necessary if type(result) == tuple: if result[0] == "d": VARIABLES[args[0]] = result[1] result = "" elif result[0] == "v": try: result = VARIABLES[args[0]] except KeyError: raise NameError( f"No variable by the name {safe_cut(args[0])} defined") elif result[0] == "a": if result[1] >= len(p_args) or -result[1] >= len(p_args) + 1: result = "" else: result = p_args[result[1]] elif result[0] == "gd": v_name = args[0] if (v_name, ) not in db.get_entries("b++2variables", columns=["name"]): v_value = express_array(result[1]) if type( result[1]) == list else result[1] db.add_entry( "b++2variables", [v_name, str(v_value), var_type(v_value), str(author)]) result = "" else: v_list = db.get_entries("b++2variables", columns=["name", "owner"]) v_owner = [v for v in v_list if v_name == v[0]][0][1] if v_owner != str(author): raise PermissionError( f"Only the author of the {v_name} variable can edit its value ({v_owner})" ) db.edit_entry("b++2variables", entry={ "value": str(result[1]), "type": var_type(result[1]) }, conditions={"name": v_name}) result = "" elif result[0] == "gv": v_name = args[0] if (v_name, ) not in db.get_entries("b++2variables", columns=["name"]): raise NameError( f"No global variable by the name {safe_cut(v_name)} defined" ) v_list = db.get_entries("b++2variables", columns=["name", "value", "type"]) v_value, v_type = [v[1:3] for v in v_list if v[0] == v_name][0] v_value = type_list[v_type](v_value) result = v_value functions[k] = result return result for k in base_keys: evaluate_result(k) for k in base_keys: if type(functions[k]) == tuple: evaluate_result(k) results = [] for k, v in functions.items(): if is_whole(k): if type(v) == list: v = express_array(v) results.append(v) output = output.replace("{}", "\t").replace("{", "{{").replace( "}", "}}").replace("\t", "{}") return output.format(*results).replace("\v", "{}")
def operation_check(block): matching_ops = [] # List of operations that match for possible_op in FUNCTIONS.keys(): # Check through each operation letter_expect = False op_regex = possible_op # Prepare a variable to serve as the regex for whether or not the code matches # this operation for param in range(possible_op.count( "?")): # Each ? is a parameter of the function alph = ALPHABET.lower()[param] types = FUNCTIONS[possible_op]["TYPES"][ alph] # Find out what type is expected for that parameter if "STRING" in types or "BOOLEAN" in types or "ARRAY STRING" in types or "ARRAY BOOLEAN" in types: letter_expect = True dot_exp = r"([^\^!-&\*-\/<-@_]{1,})" if possible_op == "out{?}": dot_exp = r"([^\^]{1,})" elif types == ["NUMBER"] or types == ["INTEGER"] or types == [ "ARRAY NUMBER" ]: dot_exp = r"([^\\A-z]{1,})" '''if len(strip_alpha(possible_op)) == 0: dot_exp = r"([^\^\\!-\/<-@]{1,})" elif len(types) == 0: dot_exp = r"([^\\]{1,})" elif types == ["NUMBER"] or types == ["INTEGER"] or types == ["ARRAY NUMBER"]: # If it's expecting a number of some kind, the parameter shouldn't have any letters in it, and so we # can use a regex that does not count letter characters to detect this parameter dot_exp = r"([^A-z]{1,})"''' # If any type is allowed (len(types) == 0) or the type is BOOLEAN or STRING (which contain letters) # Use the regex that matches all characters except for backslashes op_regex = op_regex.replace( "?", dot_exp, 1 ) # Replace the _ placeholder with the matching group for that # parameter match = re.search( op_regex, block ) # Try to match this operation (the previously generated regex) with the code if match: # If it's a match, append it to the matching operations array if possible_op.count("?") == 2 and "\\" == match.group(1)[-1]: continue if not possible_op.startswith("?") and match.span( )[0] != 0 and block[match.span()[0] - 1] == "\\": continue matching_ops.append([possible_op, match, letter_expect]) if "out{?}" in matching_ops or "out{" in block: # If the operation is out{}, leave a flag for it. It'll be handled substring = block[block.find("out{") + 4:block.find("}")] return ["out", f"({substring})", False] # in parenthesis_parser if len(matching_ops ) == 0: # If no operations match the code, return it unchanged return [False, block, False] letter_operations = [x for x in matching_ops if strip_alpha(x[0]) != "" ] # Operations that don't use letters are if len(letter_operations) == 0 and strip_alpha(block) != "": if True not in [x[2] for x in matching_ops]: return [False, block, False] if len(letter_operations) == 0 and len( matching_ops ) != 0: # all either math operations or other operations that # can be used with eachother. If this statement is composed entirely of them, then we can treat it differently operators = [[ x[0].replace("?", "").replace("\\", ""), FUNCTIONS[x[0]]["PRIORITY"], x[0] ] for x in matching_ops] full_token_list = [] current_index = 0 omit_bracket = False while True: operator_indices = [] for op in operators: found = block.find(op[0], current_index) if found != -1: operator_indices.append(found) else: operator_indices.append(len(block)) min_op = min(operator_indices) now_op = operators[operator_indices.index(min_op)][0] if min_op == len(block): full_token_list.append(block[current_index:]) break to_add = block[current_index:min_op] full_token_list.append(to_add) full_token_list.append(now_op) current_index = min_op + len(now_op) #full_token_list.append(block[current_index:]) #operator_order = sorted(operator_order, key=lambda o: (5 - o[1])) priority_limit = max([x[1] for x in operators]) op_list = [x[0] for x in operators] for p in range(priority_limit + 1): current_priority = priority_limit - p for token in full_token_list: if token in op_list: ind = op_list.index(token) if operators[ind][1] != current_priority: continue token_ind = full_token_list.index(token) operation_name = operators[ind][2] param_info = FUNCTIONS[operation_name]["TYPES"] params = [] params_ind = [] if len(param_info.keys()) != 1: for r in range(token_ind): search_ind = token_ind - r - 1 if full_token_list[search_ind] != []: c_param = full_token_list[search_ind] param_name = "a" expected_types = param_info[ param_name] # The allowed types for param fit_type = "" # The type this parameter will fit under for expect in expected_types: if expect == "NUMBER": if is_whole(c_param): c_param = int(c_param) fit_type = expect break if is_float(c_param): c_param = float(c_param) fit_type = expect break if expect == "INTEGER": if is_whole(c_param): c_param = int(c_param) fit_type = expect break if expect == "BOOLEAN": if c_param in ["True", "False"]: c_param = bool(c_param) fit_type = expect break if expect.startswith("ARRAY"): if c_param.startswith( "[") and c_param.endswith("]"): c_param = array_to_list(c_param) fit_type = expect break if expect == "STRING": if (not is_float(c_param) and not is_whole(c_param) and c_param not in ["True", "False"]): fit_type = expect break if fit_type == "" and len(expected_types) > 0: #return [True, [operation_name, param_name, c_param], True] return [True, block, True] params_ind.append(search_ind) params.append(c_param) break for search_ind in range(token_ind + 1, len(full_token_list)): if full_token_list[search_ind] != []: c_param = full_token_list[search_ind] param_name = "a" if len( param_info.keys()) == 1 else "b" expected_types = param_info[param_name] fit_type = "" # The type this parameter will fit under for expect in expected_types: if expect == "NUMBER": if is_whole(c_param): c_param = int(c_param) fit_type = expect break if is_float(c_param): c_param = float(c_param) fit_type = expect break if expect == "INTEGER": if is_whole(c_param): c_param = int(c_param) fit_type = expect break if expect == "BOOLEAN": if c_param in ["True", "False"]: c_param = bool(c_param) fit_type = expect break if expect.startswith("ARRAY"): if c_param.startswith( "[") and c_param.endswith("]"): c_param = array_to_list(c_param) fit_type = expect break if expect == "STRING": if (not is_float(c_param) and not is_whole(c_param) and c_param not in ["True", "False"]): fit_type = expect break if fit_type == "" and len(expected_types) > 0: #return [True, [operation_name, param_name, c_param], True] return [True, block, True] params_ind.append(search_ind) params.append(c_param) break result = FUNCTIONS[operation_name]["MAIN"](*params) if type(result) is list: result = list_to_array(result) full_token_list[token_ind] = str(result) for x in params_ind: full_token_list[x] = [] full_token_list = [x for x in full_token_list if x != []] full_token_list = [x for x in full_token_list if x.strip() != ""] return [True, full_token_list[0], False] # There was an operation and no error. Pass the result # Just in case there's more than 1 matching operation, run this sorted command. It takes the span of the operation # match (the start and end indices for when the operation applies), and subtracts the start index from end index # to determine how long the span is. Put the one with the longest span first matching_ops = sorted(matching_ops, reverse=True, key=(lambda m: len(m[0]))) matching_ops = sorted(matching_ops, reverse=True, key=(lambda m: m[1].span()[1] - m[1].span()[0])) # Assume it's the one with the longest span. This prevents subsets of operations being counted instead of the operation, match, lt = matching_ops[ 0] # actual operation specified. Define the operation and the match object parameters = [ ] # List of parameters that will be used in the operation function for g in range(operation.count("?")): # For each parameter necessary... param = match.group(g + 1).strip( ) # Match the group of the parameter (group 0 is the entire block; start at 1) param_name = ALPHABET.lower( )[g] # Get the parameter name (which is just the corresponding letter in alphabet) expected_types = FUNCTIONS[operation]["TYPES"][ param_name] # The allowed types for this parameter fit_type = "" # The type this parameter will fit under for expect in expected_types: if expect == "NUMBER": # NUMBER can be both int and float if is_whole( param): # Detect if it can be interpreted as an int param = int(param) # If so, make it an int fit_type = expect break if is_float( param): # Detect if it can be interpreted as a float param = float(param) # If so, make it a float fit_type = expect break if expect == "INTEGER": # INTEGER is exclusively int if is_whole(param): # Detect if it can be an int param = int(param) # If so, make it an int fit_type = expect break if expect == "BOOLEAN": # BOOLEAN can only be True or False if param in ["True", "False"]: # Detect if it's one of those param = bool(param) # If so, turn it into a bool fit_type = expect break if expect.startswith("ARRAY"): # ARRAYs are defined with brackets if param.startswith("[") and param.endswith("]"): param = array_to_list(param) fit_type = expect break if expect == "STRING": # STRING can be anything that isn't instantly defined as one of the others if not is_float(param) and not is_whole( param) and param not in ["True", "False" ]: # Detect that it's # not any of the other types. param is already a str, so it doesn't need to be made into one fit_type = expect break if fit_type == "" and len( expected_types ) > 0: # If there ARE expected types but the parameter can't fit them #return [True, [operation_name, param_name, c_param], True] return [True, block, True] # There IS an operation but there is an error. # Return the operation and param name for error specification parameters.append( param ) # If everything went alright, add param to the parameter list result = FUNCTIONS[operation]["MAIN"]( *parameters) # Extract a result from the operation function return [True, result, False] # There was an operation and no error. Pass the result
async def MAIN(message, args, level, perms, SERVER): db = Database() months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] if level == 1: # If just tc/bd, return info on their birthday found = db.get_entries("birthday", conditions={"id": str(message.author.id)}) if found == []: await message.channel.send( f"""You are yet to register your birthday! You can register by using **{SERVER["PREFIX"]}birthday register `DD/MM` `timezone`**""" .replace("\t", "")) return birthday, tz = found[0][1:] birthday = birthday.split("/") birthday_format = months[int(birthday[1]) - 1] + " " + str(birthday[0]) timezone_f = ("+" if tz > 0 else "") + str(tz) await message.channel.send( f"""**{message.author.name}**'s birthday is set as **{birthday_format}** in **UTC {timezone_f}**.""" ) return if args[1].lower() == "month": if level == 2: chosen_month = datetime.now(timezone.utc).month elif is_whole(args[2]): if int(args[2]) > 12 or int(args[2]) < 1: await message.channel.send( "Invalid month number. If you're picking a month number, choose one between 1 and 12!" ) return chosen_month = int(args[2]) else: possible_months = [ x for x in months if x.lower().startswith(args[2].lower()) ] if len(possible_months) == 0: new_possible = [ x for x in months if args[2].lower() in x.lower() ] if len(new_possible) == 0: await message.channel.send( "There's no valid month with that name!") return if len(new_possible) > 1: await message.channel.send( f"""There are multiple months fitting that search key. Please specify which one you mean! `({', '.join(new_possible)})`""".replace("\t", "")) return possible_months = new_possible if len(possible_months) > 1: await message.channel.send( f"""There are multiple months fitting that search key. Please specify which one you mean! `({', '.join(possible_months)})`""".replace("\t", "")) return chosen_month = months.index(possible_months[0]) + 1 found = db.get_entries("birthday") found = [k for k in found if int(k[1].split("/")[1]) == chosen_month] month_name = months[chosen_month - 1] if len(found) == 0: await message.channel.send( f"There are no registered birthdays in {month_name}!") return found = sorted(found, key=lambda k: int(k[1].split("/")[0])) messages = [ f"Here are all the birthdays registered in {month_name}:\n\n" ] for bd in found: try: username = f"**{SERVER['MAIN'].get_member(int(bd[0])).name}**" except Exception: username = f"**`[{bd[0]}]`**" day = bd[1].split("/")[0] tz = ("+" if bd[2] > 0 else "") + str(bd[2]) line = f"> {username} - {month_name} {day}, UTC {tz}\n" if len(messages[-1]) + len(line) > 1900: messages.append("") messages[-1] += line for m in messages: await message.channel.send(m) return if args[1].lower() == "next": found = db.get_entries("birthday") found = sorted(found, key=lambda k: int(k[1].split("/")[0])) found = sorted(found, key=lambda k: int(k[1].split("/")[1])) day, month = datetime.now(timezone.utc).day, datetime.now( timezone.utc).month for bd in found: if int(bd[1].split("/")[1]) > month: next_bd = bd break elif int(bd[1].split("/")[1]) == month and int( bd[1].split("/")[0]) > day: next_bd = bd break else: next_bd = found[0] next_id, birthday, tz = next_bd birthday = birthday.split("/") birthday_format = months[int(birthday[1]) - 1] + " " + str( int(birthday[0])) timezone_f = ("+" if tz > 0 else "") + str(tz) try: username = SERVER["MAIN"].get_member(int(next_id)).name except AttributeError: username = next_id await message.channel.send( f"The next birthday is **{username}**'s, on **{birthday_format}** in **UTC {timezone_f}**." ) return if args[1].lower() == "user": if level == 2: await message.channel.send( "Include the username or ID of the person whose birthday you want to check." ) return rest = " ".join(args[2:]) if rest.startswith("<@") and rest.endswith(">"): rest = rest[2:-1] if is_whole(rest): found = db.get_entries("birthday", conditions={"id": rest}) try: username = SERVER["MAIN"].get_member(int(rest)).name except: username = rest if found == []: await message.channel.send( f"**{username}** has not registered a birthday yet!") return user_bd = found[0] birthday, tz = user_bd[1:] birthday = birthday.split("/") birthday_format = months[int(birthday[1]) - 1] + " " + str( int(birthday[0])) timezone_f = ("+" if tz > 0 else "") + str(tz) await message.channel.send( f"**{username}**'s birthday is on **{birthday_format}** in **UTC {timezone_f}**." ) return else: user = discord.utils.get(SERVER["MAIN"].members, name=rest) if user is None: await message.channel.send("That user is not in the server!") return found = db.get_entries("birthday", conditions={"id": str(user.id)}) if found == []: await message.channel.send( f"**{rest}** has not registered a birthday yet!") return user_bd = found[0] birthday, tz = user_bd[1:] birthday = birthday.split("/") birthday_format = months[int(birthday[1]) - 1] + " " + str( int(birthday[0])) timezone_f = ("+" if tz > 0 else "") + str(tz) await message.channel.send( f"**{rest}**'s birthday is on **{birthday_format}** in **UTC {timezone_f}**." ) return if args[1].lower() == "remove": found = db.get_entries("birthday", conditions={"id": str(message.author.id)}) if found == []: await message.channel.send( "You haven't registered a birthday yet to remove it!") return await message.channel.send( f"""Are you sure you want to remove your birthday from the database? Send `confirm` in this channel to confirm. Send anything else to cancel.""".replace( "\n", "").replace("\t", "")) # Wait for a message by the same author in the same channel msg = await BRAIN.wait_for( 'message', check=(lambda m: m.channel == message.channel and m.author == message.author)) if msg.content.lower( ) != "confirm": # If it's not `confirm`, cancel command await message.channel.send("Birthday registering cancelled.") return db.remove_entry("birthday", conditions={"id": str(message.author.id)}) await message.channel.send( "Your birthday has been removed from the database.") return if args[1].lower() == "register": if level == 2: await message.channel.send( "Include your birthday in `DD/MM` to register!") return # Check if the person is in the birthday database or not found = db.get_entries("birthday", conditions={"id": str(message.author.id)}) birthday = args[2].split("/") if level == 3: tz = 0 elif not is_whole(args[3]): await message.channel.send( "Invalid timezone! Make sure it's a whole number from -12 to 14!" ) return elif not -12 <= int(args[3]) <= 14: await message.channel.send( "Invalid timezone! Make sure it's a whole number from -12 to 14!" ) return else: tz = int(args[3]) if len(birthday) != 2: # If it's not `n/n` await message.channel.send( "Invalid birthday! Make sure it's in the `DD/MM` format!") return if not is_whole(birthday[0]) or not is_whole( birthday[1]): # If day and month aren't numbers await message.channel.send( "Invalid birthday! Make sure the day and month are both numbers!" ) return # Transform into integers for these next two checks birthday[0] = int(birthday[0]) birthday[1] = int(birthday[1]) if not 1 <= birthday[1] <= 12: # If month is invalid await message.channel.send( "Invalid month! Make sure it's between 1 and 12.") return if not 1 <= birthday[0] <= monthrange( 2020, birthday[1])[1]: # monthrange checks days in the month await message.channel.send( # 2020 months because it's a leap year, and 29/02 should be available f"Invalid day! Make sure it's between 1 and {monthrange(2020, birthday[1])[1]} for that month." ) return birthday_format = months[birthday[1] - 1] + " " + str(birthday[0]) birthday = "/".join([str(x) for x in birthday ]) # Join the list again for the next few lines timezone_f = ("+" if tz > 0 else "") + str(tz) # This confirmation message cannot be bypassed await message.channel.send( f"""Are you sure you want to record your birthday as {birthday_format} and your timezone as UTC {timezone_f}? Send `confirm` in this channel to confirm. """.replace("\n", "").replace("\t", "")) # Wait for a message by the same author in the same channel msg = await BRAIN.wait_for( 'message', check=(lambda m: m.channel == message.channel and m.author == message.author)) if msg.content.lower( ) != "confirm": # If it's not `confirm`, cancel command await message.channel.send("Birthday registering cancelled.") return # If confirmation passed, record the birthday if found == []: is_new = "" db.add_entry("birthday", [message.author.id, birthday, tz]) else: is_new = "new " db.edit_entry("birthday", entry={ "birthday": birthday, "timezone": tz }, conditions={"id": str(message.author.id)}) await message.channel.send( f"Successfully recorded your {is_new}birthday as **{birthday} UTC {timezone_f}**!" ) return
async def MAIN(message, args, level, perms, SERVER): if level == 1: await message.channel.send( "Pick a mode for the guessing game! Current available modes are `simple`, `factors` and `digits`." ) return mode = args[1].lower() if level == 2: upper_bound = 100 elif not is_whole(args[2]): await message.channel.send( "Pick an integer between 2 and 1000000000 for the upper bound!") return elif 2 > int(args[2]) or int(args[2]) > 1000000000: await message.channel.send( "Pick an integer between 2 and 1000000000 for the upper bound!") return elif int(args[2]) > 100000 and mode == "factors": await message.channel.send( "For the `factors` mode, pick an integer between 2 and 100000!") return else: upper_bound = int(args[2]) number = random.randint(1, upper_bound) hint = "" if mode != "simple": if mode == "digits": hint = list(str(number)) blanks = random.sample(range(len(hint)), int(len(hint) / 2)) for ind in blanks: hint[ind] = "█" hint = f"**{''.join(hint)}**" elif mode == "factors": factors = [] for i in range(2, number): if number % i == 0: factors.append(i) if len(factors) == 0: hint = "The number is prime!" elif len(factors) == 1: hint = "The number is a perfect square (only one non-trivial factor)." else: factor_list = ", ".join( [str(x) for x in random.sample(factors, 2)]) hint = f"Two of its factors are **{factor_list}**." else: await message.channel.send( "That mode does not exist! Current available modes are `simple`, `factors` and `digits`." ) return hint = f"**Number Hint** : {hint}" await message.channel.send( f"""**Generated a number between 1 and {upper_bound}.** Send a guess for the number! {hint}""".replace("\t", "")) msg = await BRAIN.wait_for( 'message', check=lambda m: (m.author == message.author and m.channel == message.channel)) if not is_whole(msg.content): await message.channel.send("Invalid number. Guess command cancelled.") return guess = int(msg.content) result = "**You've guessed correctly!**" if guess == number else "You were wrong." await message.channel.send(f"""{message.author.mention} {result} The number generated between **1** and **{upper_bound}** was **{number}**. Your guess was **{guess}**. You played on **`{mode}`** mode.""".replace("\t", "")) return
async def MAIN(message, args, level, perms, SERVER): if isinstance(message.channel, discord.DMChannel ): # Everyone should be able to see button presses await message.channel.send("This command cannot be used in DMs!") return db = Database() if level == 1: # `tc/button` will check the current button try: # "public.bigredbutton" always has a single entry unless there has never been a button. If len(button_info) # is 0, that's the case, and this will throw an error caught by the try except block. button_info = db.get_entries("bigredbutton", columns=["button", "info"])[0] except IndexError: button_info = db.get_entries("bigredbutton", columns=["button", "info"]) if len(button_info) == 0: # If there is no button, create one button_number = 1 # It'd be the first ever button serial_number = key_generator(random.randrange( 8, 15)) # Generate serial exploding_chance = random.randrange( 15, 51) # 15 to 50% chance of exploding inspector = number_key(3) # Factory inspector code db.add_entry( "bigredbutton", [1, f"{serial_number} {exploding_chance} {inspector}", "", ""]) # Insert the new button info. Now there is a button, so skip to [*] elif button_info[ 1] == "PRESSED": # This is the 3 second interval after a button is pressed and before return # the outcome is announced. Ignore button checks for this period elif len(button_info[1].split(" ")) == 1: # [#] # Button, when in cooldown, has a single code for its info column, representing when it was pressed and # if it blew up or not. This code, unlike the normal `serial exp_chance` information, has no space in # it. This section detects that code pressing_time = int( button_info[1][2:]) # Time at which the button was pressed if button_info[1].startswith( "0-"): # `0-time` means button didn't blow up left = int((pressing_time + 15) - time.time()) # Time left for button to come back if left < 0: # If it's negative, the button should've returned by now but didn't # So generate the button on the spot button_number = button_info[ 0] + 1 # Increment button number serial_number = key_generator(random.randrange( 8, 15)) # Generate serial exploding_chance = random.randrange( 15, 51) # 15 to 50% chance of exploding inspector = number_key(3) # Factory inspector code n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) # Update with the new button else: # If it's not negative, and the timer hasn't ended, report the amount of time remaining await message.channel.send( f"The new button is currently being prepared! {left}s remain!" ) return if button_info[1].startswith( "1-"): # `1-time` means the button blew up left = int((pressing_time + 300) - time.time()) # Time left for button to come back if left < 0: # Read above about the timer being negative button_number = button_info[0] + 1 serial_number = key_generator(random.randrange(8, 15)) exploding_chance = random.randrange(15, 51) inspector = number_key(3) n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) # Update with new button else: # Report the actual timer if it hasn't ended yet mn = int(left / 60) # Extract minutes sc = left % 60 # Extract seconds await message.channel.send( f"The new button is being reconstructed. {mn}min {sc}s remain!" ) return else: # If there's a button and that's it button_number = button_info[0] # Report the button number... serial_number = button_info[1].split(" ")[0] # Its serial... exploding_chance = button_info[1].split(" ")[ 1] # ...Its explosion chance inspector = button_info[1].split(" ")[2] # And its inspector # [*] Report the current button await message.channel.send( f"""<:bigredbutton:654042578617892893> This is **Big Red Button #{button_number}** It has a **{exploding_chance}%** chance of exploding. The serial number is `{serial_number}`. It was inspected and approved by Factory Inspector #{inspector}. Use `tc/bigredbutton press` to press this button!""".replace("\t", "")) return if args[1].lower() == "top": # Points leaderboard unformatted_points = db.get_entries("bigredbutton", columns=["points"])[0] unformatted_points = [ x.split("-") for x in unformatted_points[0].split(" ") ] # unformatted_points might include empty strings from how the points value is formatted points = [] # This variable will be the clean version for x in unformatted_points: try: x[0] = int(x[0]) x[1] = int(x[1]) points.append( x) # If it passed the int tests, append it to points except ValueError: # If it fails to convert to integers, it's an empty string and is ignored continue points = sorted(points, reverse=True, key=lambda x: x[1]) # Sort the leaderboard player_info = [x for x in points if x[0] == message.author.id][0] player_ind = points.index(player_info) # args[2] is the page number. if level == 2: # If it's not specified, assume it's the first page points = points[:10] page = 1 elif not is_whole( args[2] ): # If it's not a valid integer, assume it's first page also points = points[:10] page = 1 elif (int(args[2]) - 1) * 10 >= len(points): # Detect if the page number is too big await message.channel.send( f"There is no page {args[2]} on Big Red Button!") return else: # This means the user specified a valid page number lower = (int(args[2]) - 1) * 10 upper = int(args[2]) * 10 points = points[lower:upper] page = int(args[2]) # Top of the message beginning = f"```diff\n---⭐ Big Red Button Points Leaderboard Page {page} ⭐---\n" beginning += "\n Rank | Name | Points\n" for person in points: r = points.index(person) + 1 + (page - 1) * 10 if r == 1: # + if the person is first line = f"+ {r}{' ' * (4 - len(str(r)))}| " else: # - otherwise line = f"- {r}{' ' * (4 - len(str(r)))}| " try: # Try to gather a username from the ID member = SERVER["MAIN"].get_member(int(person[0])).name except: # If you can't, just display the ID member = str(person[0]) line += f"{member[:20]}{' ' * (22 - len(member[:20]))}| " # Trim usernames to 20 characters long line += str(person[1]) + "\n" # Add points value and newline beginning += line # Add this line to the final message beginning += f"\nYour rank is {player_ind+1}, with {player_info[1]} points." beginning += "```" # Close off code block await message.channel.send(beginning) return if args[1].lower() == "press": # Press the button! button_info = db.get_entries("bigredbutton")[0] # This segment is almost an exact repeat of [#] up above if len(button_info[1].split(" ")) == 1 and button_info[1] != "PRESSED": pressing_time = int(button_info[1][2:]) if button_info[1].startswith("0-"): left = int((pressing_time + 15) - time.time()) if left < 0: button_number = button_info[0] + 1 serial_number = key_generator(random.randrange(8, 15)) exploding_chance = random.randrange(15, 51) inspector = number_key(3) n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) await message.channel.send( f"""Big Red Button #{button_number} has arrived from inspection by Factory Inspector #{inspector}, now with a {exploding_chance}% chance to explode and a serial number of `{serial_number}`!""".replace("\n", "").replace("\t", "")) return else: await message.channel.send( f"The new button is currently being prepared! {left}s remain!" ) return if button_info[1].startswith("1-"): left = int((pressing_time + 300) - time.time()) if left < 0: button_number = button_info[0] + 1 serial_number = key_generator(random.randrange(8, 15)) exploding_chance = random.randrange(15, 51) inspector = number_key(3) n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) await message.channel.send( f"""Big Red Button #{button_number} has arrived from inspection by Factory Inspector #{inspector}, now with a {exploding_chance}% chance to explode and a serial number of `{serial_number}`!""".replace("\n", "").replace("\t", "")) return else: mn = int(left / 60) sc = left % 60 await message.channel.send( f"The new button is being reconstructed. {mn}min {sc}s remain!" ) return # We already checked button_info[1], check two others now button_number = button_info[0] incapacitated = button_info[2] if str(message.author.id) in incapacitated: # If you're incapacitated incapacitated = incapacitated.split(" ") ind = 0 # Find the author in the incapacitated list and extract their explosion time for incap in incapacitated: if incap.split("-")[0] == str(message.author.id): ind = incapacitated.index(incap) explosion_t = int(incap.split("-")[1]) break # Calculate how long it'll be before they can recover delta = (explosion_t + 21600) - time.time() if delta < 0: # If it's negative, then they already recovered and can go on del incapacitated[ind] # Delete the entry for this person incapacitated = " ".join(incapacitated) # Join the list # Update with the new incapacitated list db.edit_entry("bigredbutton", entry={"incapacitated": incapacitated}) else: # If it's not negative, they still have to wait a little abs_delta = [ int(delta), # Seconds int(delta / 60), # Minutes int(delta / (60 * 60)) ] # Hours sc = abs_delta[0] % 60 mn = abs_delta[1] % 60 hr = abs_delta[2] await message.channel.send( f"You are still incapacitated! Wait {hr}h {mn}min {sc}s to press again." ) return if strip_alpha(message.author.name) == "": return # Don't try to cheese it by having no letters if button_info[ 1] == "PRESSED": # If it's currently being pressed, ignore this press return else: # Mark this button as being pressed so nobody else presses it during the 3 second interval db.edit_entry("bigredbutton", entry={"info": "PRESSED"}) # Gather serial_number and exploding_chance for calculations serial_number = button_info[1].split(" ")[0] exploding_chance = int(button_info[1].split(" ")[1]) inspector = list(button_info[1].split(" ")[2]) new_chance = exploding_chance # Clean slate variable if str(message.author.id)[-1] in serial_number: new_chance *= 0.67 # If last digit of ID is in serial... if strip_alpha(message.author.name)[0].upper() in serial_number: new_chance *= 2 # If first letter of username is in serial... point_retention = 0.5 share_count = 0 disc = list(str(message.author.discriminator)) for x in range(len(list(inspector))): if inspector[x] in disc: share_count += 1 disc.remove(inspector[x]) inspector[x] = "-" if share_count == 2: point_retention = 0.75 if share_count == 3: point_retention = 0.9 seed = random.uniform( 0, 100) # Has to be above the explosion chance otherwise it explodes await message.channel.send( f"**{message.author.name}** presses the button, and...") await asyncio.sleep(3) # Suspense! if seed <= new_chance: # If it's under the explosion chance, it blows up n_button_info = f"1-{int(time.time())}" # Remember, `1-time` is the explosion flag new_inc = f" {message.author.id}-{n_button_info[2:]}" # Add this person to the incapacitated list points = button_info[3].split(" ") # Get the points list new_points = 0 ind = -1 # Find the player in the points list and halve their point count for player in points: if player.split("-")[0] == str(message.author.id): ind = points.index(player) new_points = int( int(player.split("-")[1]) * point_retention) points[ind] = f"{message.author.id}-{new_points}" if ind == -1: # If ind is still -1, then the player wasn't found in the points list so create a new points.append(f"{message.author.id}-{new_points}" ) # entry for them with 0 points points = " ".join(points) db.edit_entry("bigredbutton", entry={ "info": n_button_info, "points": points, "incapacitated": incapacitated + new_inc }) # Update with the explosion info, the new incapacitated list, and the new points list await message.channel.send( f"""<:bigredbutton:654042578617892893> ***The #{button_number} Big Red Button blew up!*** <@{message.author.id}> has been incapacitated. Their point total is now **{new_points}**. They cannot press any more buttons for 6 hours. The button is broken. It'll take **5 minutes** to rebuild it.""".replace( "\t", "")) await asyncio.sleep(300) # Five minutes until the button is back else: # If seed > new_chance, it doesn't blow up points = button_info[3].split(" ") # Get points list to add points n_button_info = f"0-{int(time.time())}" # `0-time` means no explosion ind = -1 # Find player in points list and add the new points for player in points: if player.split("-")[0] == str(message.author.id): ind = points.index(player) # Note: the points they gain is ALWAYS the nominal value for the exploding chance, not the # modified serial number chance that was used to calculate explosions new_points = int(player.split("-")[1]) + exploding_chance points[ind] = f"{message.author.id}-{new_points}" if ind == -1: # If they're not in the points list, add them with the new point value points.append(f"{message.author.id}-{exploding_chance}") points = " ".join(points) db.edit_entry("bigredbutton", entry={ "info": n_button_info, "points": points }) # Update with the pressing info and the new points list await message.channel.send(f""" <:bigredbutton:654042578617892893> The #{button_number} Big Red Button did nothing. <@{message.author.id}> gained {exploding_chance} points. Another button arrives in **15 seconds**. """.replace("\t", "")) await asyncio.sleep(15) # Fifteen seconds until the button is back # Generate new serial_number and exploding_chance button_number += 1 serial_number = key_generator(random.randrange(8, 15)) exploding_chance = random.randrange(15, 51) inspector = number_key(3) n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) # Update table with the new button # Announce the new button await message.channel.send( f"""Big Red Button #{button_number} has arrived from inspection by Factory Inspector #{inspector}, now with a {exploding_chance}% chance to explode and a serial number of `{serial_number}`!""".replace("\n", "").replace("\t", "")) return
def evaluate_result(k): v = functions[k] if type(v) == tuple: k1 = v[0] functions[k] = evaluate_result(k1) return functions[k] args = v[1:] for i, a in enumerate(args): if v[0] == "IF" and is_whole(v[1]) and int(v[1]) != 2 - i: continue if type(a) == tuple: k1 = a[0] functions[k][i + 1] = evaluate_result(k1) args = v[1:] result = FUNCTIONS[v[0]](*args) # Tuples indicate special behavior necessary if type(result) == tuple: if result[0] == "d": VARIABLES[args[0]] = result[1] result = "" elif result[0] == "v": try: result = VARIABLES[args[0]] except KeyError: raise NameError( f"No variable by the name {safe_cut(args[0])} defined") elif result[0] == "a": if result[1] >= len(p_args) or -result[1] >= len(p_args) + 1: result = "" else: result = p_args[result[1]] elif result[0] == "gd": v_name = args[0] if (v_name, ) not in db.get_entries("b++2variables", columns=["name"]): v_value = express_array(result[1]) if type( result[1]) == list else result[1] db.add_entry( "b++2variables", [v_name, str(v_value), var_type(v_value), str(author)]) result = "" else: v_list = db.get_entries("b++2variables", columns=["name", "owner"]) v_owner = [v for v in v_list if v_name == v[0]][0][1] if v_owner != str(author): raise PermissionError( f"Only the author of the {v_name} variable can edit its value ({v_owner})" ) db.edit_entry("b++2variables", entry={ "value": str(result[1]), "type": var_type(result[1]) }, conditions={"name": v_name}) result = "" elif result[0] == "gv": v_name = args[0] if (v_name, ) not in db.get_entries("b++2variables", columns=["name"]): raise NameError( f"No global variable by the name {safe_cut(v_name)} defined" ) v_list = db.get_entries("b++2variables", columns=["name", "value", "type"]) v_value, v_type = [v[1:3] for v in v_list if v[0] == v_name][0] v_value = type_list[v_type](v_value) result = v_value functions[k] = result return result
async def MAIN(message, args, level, perms, EVENTS): if level == 1: await message.channel.send("Include events to call!") return if args[1].lower() == "list": # List the events from the dict event_list = [ f'`{x}` - **{"ON" if EVENTS[x].RUNNING else "OFF"}**\n' for x in EVENTS.keys() ] await message.channel.send( f"Here is a list of bot events:\n\n{''.join(event_list)}") return if args[1].upper() not in EVENTS.keys(): # Event doesn't exist await message.channel.send("Invalid event.") return event = args[1].upper() if level != 2: # If it's not just `tc/event [event_name]`, check for subcommands if args[2].lower() == "edit": if not EVENTS[event].RUNNING: await message.channel.send( "You can only change an event that's currently running.") return parsing_index = 0 # Helps detect keywords and the brackets' boundaries config_dict = {} # Stores the values to be changed no_value = [ ] # Stores keywords that the user didn't provide values for, so they can be specified later while True: # Advances through the entire message until break is activated found = message.content[parsing_index + 1:].find("[") if found == -1: # If you can't find [ anymore, parsing is over break parsing_index += found + 2 # Brings parsing_index to the index of the character after [ reach_index = parsing_index + message.content[parsing_index + 1:].find("]") + 1 # Brings reach_index to the next ] # Contents of the brackets config_args = message.content[parsing_index:reach_index].split( " ") if len(config_args) == 0: continue # If it's just [], then there's nothing to do here key = config_args[ 0] # By default, the first word in the brackets is the parameter key if len( config_args ) == 1: # If it's the *only* word, then no value was specified no_value.append(key) continue elif len( config_args ) == 2: # If it's two words, the value is a string of the second word if is_whole(config_args[1]): value = int(config_args[1]) elif is_float(config_args[1]): value = float(config_args[1]) else: value = config_args[1] else: # If it's more than two words, the value is a list of all words past the first one value = config_args[1:] # Update the config_dict with the new value config_dict[key] = value if len(config_dict.keys()) == 0: # If no parameters were found await message.channel.send( "Include parameters you want to edit!") return if len( no_value ) > 0: # If there are parameters whose value weren't specified await message.channel.send( f"Include the new value for {grammar_list(no_value)}!") return await EVENTS[event].edit_event(message, config_dict) return # If everything went alright, edit the event parameters # If the command is purely `tc/event [event_name]`, it interprets as to toggle the event if EVENTS[event].RUNNING: action = "END" else: action = "START" # Confirmation can be bypassed by including `confirm` as an argument in the command if "confirm" not in [x.lower() for x in args]: await message.channel.send( f"""To confirm that you want to **{action} {event}**, send `confirm` in this channel. Send anything else to cancel the command.""".replace('\t', '')) # The check lambda waits for a message from the same user and in the same channel msg = await BRAIN.wait_for( 'message', check=lambda m: (m.author == message.author and m.channel == message.channel)) if msg.content.lower( ) != "confirm": # If that message isn't confirm, cancel await message.channel.send("Event command cancelled.") return # Trigger the action. This is done automatically if `confirm` is included await message.channel.send(f"{event} will now {action.lower()}.") return [1, event] # Flag to main.py to toggle an event
def ABS(a): if not is_number(a): raise TypeError( f"Parameter of ABS function must be a number: {safe_cut(a)}") return abs(int(a) if is_whole(a) else float(a))
async def MAIN(message, args, level, perms, TWOW_CENTRAL, EVENT, GAME_CHANNEL): if not isinstance( message.channel, discord.DMChannel) and message.channel.id != GAME_CHANNEL: await message.channel.send( f"MiniMiniTWOW commands can only be used in <#{GAME_CHANNEL}>!") return if level == 1: await message.channel.send("Include a subcommand!") return # Shorten the notation for convenience mmt = EVENT["MMT"] if args[1].lower() == "stats": if level == 2: await message.channel.send( "Include the type of stat you want to see!") return db = Database() # public.mmtstats contains ids, placements in each round, and MMT wins from everyone who's participated # in any MMT rounds so far data = db.get_entries("mmtstats") if len(data) == 0: # Just in case it gets reset one day await message.channel.send("There's no data yet!") return leaderboard = [] if args[2].lower() == "points": for person in data: # `person` is a table entry, (id, placements, wins) try: # Try to find that person's username through TWOW Central member = TWOW_CENTRAL.get_member(int(person[0])).name if member is None: member = person[0] except Exception: # If you can't find them, just use the ID instead member = person[0] # MMT seasons are joined with tabs and MMT rounds are joined with spaces in the database ranks = [x.split(" ") for x in person[1].split("\t")] score = 0 rounds = 0 for twow in ranks: # For each season... for p_round in twow: # For each placement in a round... if p_round.strip() == "": continue numbers = p_round.split( "/") # Get a list of [rank, contestantcount] # Points adds the amount of people you beat each round score += int(numbers[1]) - int(numbers[0]) rounds += 1 # Keep track of the round count too leaderboard.append([member, score, rounds]) elif args[2].lower() == "wins": for person in data: try: member = TWOW_CENTRAL.get_member(int(person[0])).name if member is None: member = person[0] except Exception: member = person[0] leaderboard.append([ member, person[2] ]) # person[2] is MMT season wins, so that's all we need elif args[2].lower() == "roundwins": for person in data: try: member = TWOW_CENTRAL.get_member(int(person[0])).name if member is None: member = person[0] except Exception: member = person[0] ranks = [x.split(" ") for x in person[1].split("\t")] wins = 0 for twow in ranks: for p_round in twow: if p_round.strip() == "": continue numbers = p_round.split("/") if int(numbers[0] ) == 1: # Count up for each `1` placement wins += 1 leaderboard.append( [member, wins]) # wins is the total number of round wins elif args[2].lower() == "nr": for person in data: try: member = TWOW_CENTRAL.get_member(int(person[0])).name if member is None: member = person[0] except Exception: member = person[0] ranks = [x.split(" ") for x in person[1].split("\t")] total = 0 rounds = 0 for twow in ranks: for p_round in twow: if p_round.strip() == "": continue numbers = p_round.split("/") # Add this round's NR to the total total += (int(numbers[1]) - int(numbers[0])) / (int(numbers[1]) - 1) rounds += 1 if rounds == 0: continue # Format those as percentage strings average = "{:.2%}".format(total / rounds) total = "{:.2%}".format(total) leaderboard.append([member, total, average]) else: return player_count = len(leaderboard) if args[2].lower( ) == "points": # Sort by points descending, then rounds ascending leaderboard = sorted(leaderboard, key=lambda c: c[2]) leaderboard = sorted(leaderboard, reverse=True, key=lambda c: c[1]) if args[2].lower( ) == "nr": # Sort by total NR -- remove the percentage and convert to float first leaderboard = sorted(leaderboard, reverse=True, key=lambda c: float(c[1][:-1])) if args[2].lower() in [ "wins", "roundwins" ]: # Just sort by wins ascending. Remove people who have none leaderboard = sorted(leaderboard, reverse=True, key=lambda c: c[1]) leaderboard = [x for x in leaderboard if x[1] != 0] for line in range(len( leaderboard)): # Add the rank to each line of the leaderboard leaderboard[line] = [line + 1] + leaderboard[line] # args[3] is the page number. if level == 3: # If it's not specified, assume it's the first page leaderboard = leaderboard[:10] page = 1 elif not is_whole( args[3] ): # If it's not a valid integer, assume it's first page also leaderboard = leaderboard[:10] page = 1 elif (int(args[3]) - 1) * 10 >= len( leaderboard): # Detect if the page number is too big await message.channel.send( f"There is no page {args[3]} of this stat!") return else: # This means the user specified a valid page number lower = (int(args[3]) - 1) * 10 upper = int(args[3]) * 10 leaderboard = leaderboard[lower:upper] page = args[3] # Headers for each stat if args[2].lower() == "points": final_message = f"```diff\n---⭐ MiniMiniTWOW Point Leaderboard Page {page} ⭐---\n\n" final_message += " Rank | Name | Pts. | Rounds\n" spacing = 6 if args[2].lower() == "nr": final_message = f"```diff\n---⭐ MiniMiniTWOW Normalized Rank Leaderboard Page {page} ⭐---\n\n" final_message += " Rank | Name | Total | Average\n" spacing = 9 if args[2].lower() == "wins": final_message = f"```diff\n---⭐ MiniMiniTWOW Wins Leaderboard Page {page} ⭐---\n\n" final_message += " Rank | Name | Wins\n" spacing = 4 if args[2].lower() == "roundwins": final_message = f"```diff\n---⭐ MiniMiniTWOW Round Wins Leaderboard Page {page} ⭐---\n\n" final_message += " Rank | Name | Round Wins\n" spacing = 5 # Composition of each line of the leaderboard for line in leaderboard: symbol = "+" if line[0] == 1 else "-" spaced_rank = f"{line[0]}{' ' * (4 - len(str(line[0])))}" spaced_name = f"{line[1][:23]}{' '*(24 - len(str(line[1])))}" spaced_points = f"{line[2]}{' '*(spacing - len(str(line[2])))}" try: # Some stats will have two number columns... formatted = f"{symbol} {spaced_rank}| {spaced_name}| {spaced_points}| {line[3]}\n" except: # ...but others will have one. This try except block detects each one. formatted = f"{symbol} {spaced_rank}| {spaced_name}| {spaced_points}\n" final_message += formatted # Add the line to the message final_message += "```" # Close off the code block await message.channel.send(final_message) return if args[1].lower() == "end": if not mmt.RUNNING: await message.channel.send( "There's no MiniMiniTWOW running right now!") return if message.author.id == mmt.info["GAME"][ "HOST"]: # Automatically end MMT if it's the host trying to end it await message.channel.send( "**The current MiniMiniTWOW has been ended by the host! The queue moves on.**" ) mmt.force_skip() return if perms == 2: # Do the same if it's a staff member await message.channel.send( "**The current MiniMiniTWOW has been ended by staff! The queue moves on.**" ) mmt.force_skip() return if mmt.info["GAME"][ "PERIOD"] == 1: # Spectator votes have to wait until post-start, though await message.channel.send( "You can only vote to end a MiniMiniTWOW that has already started!" ) return spect = len(mmt.info["SPECTATORS"]) # Number of spectators necessary = np.ceil( spect**(4 / 5) + 0.8 ) # By the formula, the number of votes necessary to end the MMT # If there are less than 4 spectators, you can't end an MMT by spectator vote. As such, there's not much # reason to bother including the amount of votes necessary if necessary > spect, so include it only if # necessary <= spect instead nec_seg = "" if necessary <= spect: nec_seg = f"/{necessary}" if message.author.id in mmt.info["GAME"]["END_VOTES"]: # Filter the user's ID out of the list of end voters mmt.info["GAME"]["END_VOTES"] = [ x for x in mmt.info["GAME"]["END_VOTES"] if x != message.author.id ] votes = len(mmt.info["GAME"] ["END_VOTES"]) # Calculate the new number of votes await message.channel.send( f"""🚪 {message.author.mention} removes their vote to end the MiniMiniTWOW. There are now **{votes}{nec_seg}** votes.""") return mmt.info["GAME"]["END_VOTES"].append( message.author.id) # Add user's ID to list of end votes votes = len( mmt.info["GAME"]["END_VOTES"]) # Calculate new number of votes await message.channel.send( f"""🚪 **{message.author.mention} voted to end the MiniMiniTWOW!** There are now **{votes}{nec_seg}** votes.""") if votes >= necessary: # If the amount of votes reaches the required... await message.channel.send( """**The current MiniMiniTWOW has been ended by public vote! The queue moves on.**""".replace("\n", "").replace("\t", "")) mmt.force_skip( ) # Function to skip a host, also used in Events/mmt.py return if args[1].lower() == "transfer": if not mmt.RUNNING: await message.channel.send( "There's no MiniMiniTWOW running right now!") return if mmt.info["GAME"]["PERIOD"] == 0: await message.channel.send( "Host transfers can only occur with MiniMiniTWOWs that have been created." ) return if mmt.info["GAME"]["HOST"] != message.author.id: await message.channel.send( "Only the host can transfer the MiniMiniTWOW to someone else!") return if level == 2: await message.channel.send("Choose a user to transfer it to!") return # You have to ping the person you wanna transfer it to mention = args[2] try: user_id = int(mention[2:-1]) except: await message.channel.send( "Invalid user! Ping the user you want to transfer the MiniMiniTWOW to!" ) return # Try to find the person in TWOW Central person = TWOW_CENTRAL.get_member(user_id) if person is None: # If they're not in TWOW Central, they can't become the new host await message.channel.send( "Invalid user! Ping the user you want to transfer the MiniMiniTWOW to!" ) return if 'confirm' not in [ x.lower() for x in args ]: # Confirmation can be bypassed by including `confirm` await message.channel.send( f"""Are you sure you want to transfer the MiniMiniTWOW to **{person.name}**? Send `confirm` to transfer.""".replace("\n", "").replace("\t", "")) # Check for the next message by the same person in the same channel msg = await BRAIN.wait_for( 'message', check=lambda m: (m.author == message.author and m.channel == message.channel)) if msg.content.lower( ) != "confirm": # If it's not `confirm`, cancel the command await message.channel.send("Transfer command cancelled.") return msg_send = f"Successfully transfered host position to **{person.name}**!" # Make the new person the host mmt.info["GAME"]["HOST"] = user_id # Reset the timers for host actions if necessary if mmt.info["GAME"][ "PERIOD"] == 1 and mmt.info["GAME"]["PERIOD_START"] != 0: mmt.info["GAME"]["PERIOD_START"] = time.time() msg_send += f""" The timer to start the MiniMiniTWOW is reset. {mention} has {mmt.param["S_DEADLINE"]} seconds to start it with `tc/mmt start`.""".replace("\n", "").replace("\t", "") if mmt.info["GAME"]["PERIOD"] == 2: mmt.info["GAME"]["PERIOD_START"] = time.time() msg_send += f""" The timer to set the prompt is reset. {mention} has {mmt.param["P_DEADLINE"]} seconds to decide on the prompt with `tc/mmt prompt`.""".replace( "\n", "").replace("\t", "") await message.channel.send(msg_send) return if args[1].lower() == "queue": if level == 2: # If it's just `tc/mmt queue` if not mmt.RUNNING: # The event is counted as off if there's nobody in queue. To check the queue, it needs # to be on, so being the first to join the queue will start up the event mmt.start(TWOW_CENTRAL) if message.author.id in mmt.info[ "HOST_QUEUE"]: # If you're already in queue, leaves the queue mmt.info["HOST_QUEUE"] = [ x for x in mmt.info["HOST_QUEUE"] if x != message.author.id ] await mmt.MMT_C.send( f"🎩 <@{message.author.id}> has been removed from queue.") return mmt.info["HOST_QUEUE"].append( message.author.id ) # If you're not in queue, you're added to queue await message.channel.send( f"🎩 <@{message.author.id}> has been added to queue at position **{len(mmt.info['HOST_QUEUE'])}**." ) return if args[2].lower() == "list": if len( mmt.info["HOST_QUEUE"] ) == 0: # If the event is not running, the host queue's length is also 0 await message.channel.send("The queue is empty!") return # Array I use to split the list into multiple messages if necessary init = ["**This is the current MiniMiniTWOW hosting queue:**\n\n"] for person in mmt.info["HOST_QUEUE"]: # `person` is an ID member = TWOW_CENTRAL.get_member( person) # Try to find the user if member is None: # If you can't find the user, just use their ID and format it member = f"`{person}`" else: # If you found the user, use their username member = member.name # Define the line that's about to be added line = f"🎩 [{mmt.info['HOST_QUEUE'].index(person) + 1}] - **{member}**\n" # If adding this line steps over the character limit, add another string to the array # 1950 instead of 2000 because having over 1950 can cause issues for like no reason if len(init[-1] + line) > 1950: line.append("") init[-1] += line # Add the line to this new string for z in init: # Send each of those strings as a separate message await message.channel.send(z) return if args[1].lower() == "create": if not mmt.RUNNING: # If the event isn't running await message.channel.send( f"There's no host to create a MiniMiniTWOW! Join the queue with `{PREFIX}mmt queue` to host!" ) return if mmt.info["GAME"]["PERIOD"] != 0: # If it's not time to create one await message.channel.send( "There's already a MiniMiniTWOW running!") return if message.author.id != mmt.info["GAME"]["HOST"]: # If you're not host await message.channel.send( "You can only create a MiniMiniTWOW if you're up on the queue!" ) return # Switch the period to 1 (signups) mmt.info["GAME"]["PERIOD"] = 1 mmt.info["GAME"]["PERIOD_START"] = 0 await message.channel.send( f"🎩 <@{message.author.id}> has created a MiniMiniTWOW! Use `{PREFIX}mmt join` to join it!" ) return if args[1].lower() == "spectate": if not mmt.RUNNING: # If the event isn't running await message.channel.send( "There's no MiniMiniTWOW running right now!") return if mmt.info["GAME"][ "PERIOD"] == 0: # If `tc/mmt create` hasn't been run await message.channel.send( "You can only spectate a MiniMiniTWOW that has been created!") return if message.author.id in mmt.info[ "SPECTATORS"]: # If you're a spectator... # ...You're removed from the list of spectators mmt.info["SPECTATORS"] = [ x for x in mmt.info["SPECTATORS"] if x != message.author.id ] await mmt.MMT_C.send( f"👁️ <@{message.author.id}> is no longer spectating.") return mmt.info["SPECTATORS"].append( message.author.id) # Adds user to the list of spectators await mmt.MMT_C.send( f"""👁️ <@{message.author.id}> is now spectating, and will receive voting screens for future rounds.""".replace("\n", "").replace("\t", "")) return if args[1].lower() == "join": if not mmt.RUNNING: # If the event is not running await message.channel.send( "There's no MiniMiniTWOW running right now!") return if mmt.info["GAME"]["PERIOD"] != 1: # If it's not signup period await message.channel.send( "You can only join the MiniMiniTWOW if it's in signups!") return if message.author.id in mmt.info[ "PLAYERS"]: # If you're already a player # Remove you both from the player list and the spectator list mmt.info["PLAYERS"] = [ x for x in mmt.info["PLAYERS"] if x != message.author.id ] mmt.info["SPECTATORS"] = [ x for x in mmt.info["SPECTATORS"] if x != message.author.id ] await mmt.MMT_C.send( f"🏁 <@{message.author.id}> left the MiniMiniTWOW. Our player count is {len(mmt.info['PLAYERS'])}." ) if len( mmt.info['PLAYERS'] ) == 1: # The timer is cancelled if there's no longer 2 players await mmt.MMT_C.send( "🏁 We no longer have two players. The three minute start timer is now reset." ) return mmt.info["PLAYERS"].append(message.author.id) # Adds user as a player if message.author.id not in mmt.info["SPECTATORS"]: mmt.info["SPECTATORS"].append( message.author.id ) # And a spectator, if they're not already one await mmt.MMT_C.send( f"🏁 **<@{message.author.id}> joined the MiniMiniTWOW!** Our player count is now {len(mmt.info['PLAYERS'])}!" ) if len(mmt.info['PLAYERS'] ) == 2: # If there are two players, the signup timer begins await mmt.MMT_C.send( f"""🏁 We have two players! <@{mmt.info["GAME"]["HOST"]}> has three minutes to start the MiniMiniTWOW with `{PREFIX}mmt start`.""".replace( "\n", "").replace("\t", "")) mmt.info["GAME"]["PERIOD_START"] = time.time() return if args[1].lower() == "start": if not mmt.RUNNING: # If the event isn't running await message.channel.send( "There's no MiniMiniTWOW running right now!") return if mmt.info["GAME"]["PERIOD"] != 1: # If it's not signup period await message.channel.send( "You can only start a MiniMiniTWOW if it's in signups!") return if message.author.id != mmt.info["GAME"]["HOST"]: # If user isn't host await message.channel.send( "Only the host can start a MiniMiniTWOW!") return if len(mmt.info["PLAYERS"]) < 2: # If there aren't two contestants await message.channel.send( "You need at least two contestants to start a MiniMiniTWOW!") return # Set it to round 1 with period 2 (prompt deciding) mmt.info["GAME"]["ROUND"] = 1 mmt.info["GAME"]["PERIOD"] = 2 mmt.info["GAME"]["PERIOD_START"] = time.time() await mmt.MMT_C.send( f"""🏁 <@{message.author.id}> has started the MiniMiniTWOW with {len(mmt.info["PLAYERS"])} contestants. Nobody can sign up anymore.""". replace("\n", "").replace("\t", "")) return if args[1].lower() == "prompt": if not mmt.RUNNING: # If the event isn't running await message.channel.send( "There's no MiniMiniTWOW running right now!") return if mmt.info["GAME"][ "PERIOD"] != 2: # If it's not prompt decision period await message.channel.send( "You can only set a prompt inbetween rounds!") return if message.author.id != mmt.info["GAME"]["HOST"]: # If user isn't host await message.channel.send("Only the host can set a prompt!") return if level == 2: await message.channel.send("You need to include a prompt!") return # Remove formatting and characters that might cause errors prompt = " ".join(args[2:]).replace("`", "").replace("\t", "").replace("\n", "") if len(prompt) > 200: await message.channel.send( "That prompt is too long! It must be 200 characters at most.") return # Switch to responding period, set the rpompt, prepare the responses array mmt.info["GAME"]["PERIOD"] = 3 mmt.info["GAME"]["PERIOD_START"] = time.time() mmt.info["GAME"]["PROMPT"] = prompt mmt.info["RESPONSES"] = [""] * len(mmt.info["PLAYERS"]) await mmt.MMT_C.send( f"""📝 **Round {mmt.info["GAME"]["ROUND"]} Responding** has started! The prompt is: ```{prompt}``` Our contestants have {mmt.param["R_DEADLINE"]} seconds to respond to it.""". replace("\n", "").replace("\t", "")) for player in mmt.info["PLAYERS"]: try: # DM everyone the prompt await TWOW_CENTRAL.get_member(player).send(f""" 📝 **Round {mmt.info["GAME"]["ROUND"]} Responding** has started! The prompt is: ```{prompt}``` You must respond in {mmt.param["R_DEADLINE"]} seconds using `{PREFIX}mmt respond`! """.replace("\n", "").replace("\t", "")) except Exception: # If something goes wrong, skip the person pass return if args[1].lower() == "respond": if not isinstance(message.channel, discord.DMChannel): # If not in DMs await mmt.MMT_C.send("This command can only be used in DMs!") return if not mmt.RUNNING: # If event isn't running await message.channel.send( "There's no MiniMiniTWOW running right now!") return if mmt.info["GAME"]["PERIOD"] != 3: # If it's not submission period await message.channel.send( "You can only respond during a submission period!") return if message.author.id not in mmt.info[ "PLAYERS"]: # If you're not a player await message.channel.send("Only alive contestants can respond!") return if level == 2: await message.channel.send("You need to include a response!") return # Run the formatting_fix function, remove characters that might cause issues and strip unnecessary whitespace response = formatting_fix(" ".join(args[2:]).replace("`", "").replace( "\t", "").replace("\n", "")).strip() if len(response) > 120: # Too long await message.channel.send( "Your response is too long! It must be 120 characters at most." ) return if len(response) == 0: # Literally empty await message.channel.send( "Your response evaluates to an empty string.") return ind = mmt.info["PLAYERS"].index( message.author.id) # Index of the user in the players array new = not mmt.info["RESPONSES"][ ind] == "" # Detects if this is a new response or an edit while response in mmt.info[ "RESPONSES"]: # If this response has already been submitted... response += "\u200b" # ...distinguish between them by adding a zero-width space to the end of one # Keep doing so until it's unique (in the case there's more than two identical entries) mmt.info["RESPONSES"][ ind] = response # Set the response to the one submitted await message.channel.send( f"""Your {'new ' if new else ''}response to the prompt has been recorded as: ```{response}```> **Word count:** {word_count(response)}""".replace( "\n", "").replace("\t", "")) return if args[1].lower() == "vote": if not isinstance(message.channel, discord.DMChannel): # If not in DMs await mmt.MMT_C.send("This command can only be used in DMs!") return if not mmt.RUNNING: # If event isn't running await message.channel.send( "There's no MiniMiniTWOW running right now!") return if message.author.id not in mmt.info["VOTES"][ "ID"]: # If you didn't receive a voting screen await message.channel.send( "You can only vote if you received a voting screen!") return if mmt.info["GAME"]["PERIOD"] != 4: # If it's not voting period await message.channel.send( "You can only vote during a voting period!") return if level == 2: await message.channel.send("You need to include a vote!") return vote = args[2].upper() # Always convert vote to uppercase ind = mmt.info["VOTES"]["ID"].index( message.author.id) # Index of user in the votes arrays screen = mmt.info["VOTES"]["RESP"][ ind] # Check the screen the user received # The screen has `n` responses, so the vote should be composed of the first `n` letters of the alphabet # Otherwise, it's invalid if sorted(list(vote)) != sorted(list(ALPHABET[:len(screen)])): await message.channel.send( """Your vote is invalid. Make sure you're not missing or repeating any letters, or including any invalid characters.""".replace("\n", "").replace("\n", "")) return # Instantly convert the vote to the score it'll give to each response parsed_vote = [] for z in range(len(vote)): score = (len(vote) - 1 - vote.find(ALPHABET[z])) / (len(vote) - 1) parsed_vote.append(str(score)) new = not mmt.info["VOTES"]["VOTE"][ ind] == "" # Detect if the vote is new or an edit mmt.info["VOTES"]["VOTE"][ind] = " ".join( parsed_vote) # Join the list of values and record it # Display the vote back as the letters await message.channel.send( f"""Your {'new ' if new else ''}vote has been recorded as: ```{vote}```""".replace("\n", "").replace("\t", "")) return
async def MAIN(message, args, level, perms, SERVER): if level == 1: await message.channel.send("Include a subcommand!") return VARIABLES = {} db = Database() if args[1].lower() == "info": tag_list = db.get_entries("b++programs", columns=["name", "program", "author", "uses"]) tag_list = sorted(tag_list, reverse=True, key=lambda m: m[3]) tag_leaderboard = False if level == 2: # If it's not specified, assume it's the first page tag_list = tag_list[:10] page = 1 tag_leaderboard = True elif is_whole(args[2]): if (int(args[2]) - 1) * 10 >= len(tag_list): # Detect if the page number is too big await message.channel.send(f"There is no page {args[2]} on the B++ program list!") return else: # This means the user specified a valid page number lower = (int(args[2]) - 1) * 10 upper = int(args[2]) * 10 tag_list = tag_list[lower:upper] page = int(args[2]) tag_leaderboard = True if tag_leaderboard: beginning = f"```diff\nOld B++ Programs Page {page}\n\n" for program in tag_list: r = tag_list.index(program) + 1 + (page - 1) * 10 line = f"{r}{' '*(2-len(str(r)))}: {program[0]} :: {program[3]} use{'s' if program[3] != 1 else ''}" member_id = program[2] try: # Try to gather a username from the ID member = SERVER["MAIN"].get_member(int(member_id)).name except: # If you can't, just display the ID member = str(member_id) line += f" (written by {member})\n" beginning += line # Add this line to the final message beginning += "```" # Close off code block await message.channel.send(beginning) return tag_name = args[2] if tag_name not in [x[0] for x in tag_list]: await message.channel.send("That tag does not exist.") return program = tag_list[[x[0] for x in tag_list].index(tag_name)] member_id = program[2] try: # Try to gather a username from the ID member = SERVER["MAIN"].get_member(int(member_id)).name except: # If you can't, just display the ID member = str(member_id) if len(program[1]) + 6 >= 1900: program_code_msg = program[1] if len(program_code_msg) >= 1990: await message.channel.send(f"**{program[0]}** (written by {member})") await message.channel.send(f"```{program_code_msg[:1000]}```") await message.channel.send(f"```{program_code_msg[1000:]}```") await message.channel.send(f"Used {program[3]} time{'s' if program[3] != 1 else ''}") else: await message.channel.send(f"**{program[0]}** (written by {member})") await message.channel.send(f"```{program_code_msg}```") await message.channel.send(f"Used {program[3]} time{'s' if program[3] != 1 else ''}") else: await message.channel.send(f"""**{program[0]}** (written by {member}) ```{program[1]}``` Used {program[3]} time{'s' if program[3] != 1 else ''}""".replace("\n", "").replace("\t", "")) return if args[1].lower() == "delete": if level == 2: await message.channel.send("Include the name of the program you want to delete!") return tag_name = args[2] tag_list = db.get_entries("b++programs", columns=["name", "author"]) if tag_name in [x[0] for x in tag_list]: ind = [x[0] for x in tag_list].index(tag_name) if tag_list[ind][1] != str(message.author.id) and perms != 2: await message.channel.send(f"You can only delete a program you created!") return db.remove_entry("b++programs", conditions={"name": tag_name}) await message.channel.send(f"Succesfully deleted program {tag_name}!") else: await message.channel.send(f"There's no program under the name `{tag_name}`!") return if args[1].lower() == "edit": if level == 2: await message.channel.send("Include the name of the program you want to edit!") return if level == 3: await message.channel.send("Include the new code for the program you want to edit!") return tag_name = args[2] program = " ".join(args[3:]) if program.startswith("```") and program.endswith("```"): program = program[3:-3] if program.startswith("``") and program.endswith("``"): program = program[2:-2] if program.startswith("`") and program.endswith("`"): program = program[1:-1] tag_list = db.get_entries("b++programs", columns=["name", "author"]) if tag_name in [x[0] for x in tag_list]: ind = [x[0] for x in tag_list].index(tag_name) if tag_list[ind][1] != str(message.author.id) and perms != 2: await message.channel.send(f"You can only edit a program you created!") return db.edit_entry("b++programs", entry={"program": program}, conditions={"name": tag_name}) await message.channel.send(f"Succesfully edited program {tag_name}!") else: await message.channel.send(f"There's no program under the name `{tag_name}`!") return if args[1].lower() not in ["run", "create"]: tag_name = args[1] if (tag_name,) in db.get_entries("b++programs", columns=["name"]): program, uses = db.get_entries( "b++programs", columns=["program", "uses"], conditions={"name": tag_name})[0] uses += 1 db.edit_entry("b++programs", entry={"uses": uses}, conditions={"name": tag_name}) else: await message.channel.send(f"There's no tag under the name `{args[1]}`!") return else: if args[1].lower() == "run": if level == 2: await message.channel.send("Include a program to run!") return program = " ".join(args[2:]) if args[1].lower() == "create": if level == 2: await message.channel.send("Include the name of your new program!") return if level == 3: await message.channel.send("Include the code for your program!") return tag_name = args[2] if len(tag_name) > 30: await message.channel.send("That tag name is too long. 30 characters maximum.") return program = " ".join(args[3:]) if program.startswith("```") and program.endswith("```"): program = program[3:-3] if program.startswith("``") and program.endswith("``"): program = program[2:-2] if program.startswith("`") and program.endswith("`"): program = program[1:-1] if args[1].lower() == "create": if (tag_name,) in db.get_entries("b++programs", columns=["name"]): await message.channel.send("There's already a program with that name!") return db.add_entry("b++programs", [tag_name, program, message.author.id, 0]) await message.channel.send(f"Successfully created program `{tag_name}`!") return semicolon_inds = find_all(program, ";") semicolon_inds = [x for x in semicolon_inds if program[x-1] != "\\"] program_chars = list(program) for ind in semicolon_inds: program_chars[ind] = "\n" program = ''.join(program_chars).replace("\t", "") lines = program.split("\n") context = [] OUTPUT = "" try: try: tag_vars = db.get_entries("b++variables", columns=["name", "value"], conditions={"tag": tag_name}) for var in tag_vars: value = var[1] if value.startswith("[") and value.endswith("]"): value = array_to_list(value) VARIABLES[var[0]] = value except: pass for line in lines: c_line = line if len(context) == 0: declaration = " = " in c_line array_context = "[" in c_line.replace("\[", "") and "]" not in c_line.replace("\]", "") if array_context: context.append(["array", c_line, c_line[c_line.find("["):]]) continue else: context_list = [x[0] for x in context] if "array" in context_list: this_context = context[context_list.index("array")] this_context[1] += "\t" + c_line if "]" not in line.replace("\]", ""): this_context[2] += "\t" + c_line continue else: this_context[2] += c_line[:c_line.find("]")+1] c_line = this_context[1] del context[context_list.index("array")] declaration = " = " in c_line if declaration: # Deal with variable declaration c_line = c_line.replace("\\=", "\n") c_line = c_line.replace("==", "\t\t") sides = c_line.split("=") sides[1] = "=".join(sides[1:]) sides = [x.replace("\n", "\=") for x in sides] sides = [x.replace("\t\t", "==") for x in sides] c_line = c_line.replace("\n", "=") c_line = c_line.replace("\t\t", "==") sides[0] = parenthesis_parser(sides[0].strip(), VARIABLES, OUTPUT)[0] sides[1] = parenthesis_parser(strip_front(sides[1]), VARIABLES, OUTPUT, var=True)[0] VARIABLES[sides[0]] = sides[1] continue line_info, OUTPUT = parenthesis_parser(c_line.strip(), VARIABLES, OUTPUT) except Exception as e: await message.channel.send(f'{type(e).__name__} in line `{c_line}`:\n\t{e}') return try: await message.channel.send( OUTPUT.replace("<@", "<\@").replace("\\\\", "\t\t").replace("\\", "").replace("\t\t", "\\").replace(u"\uF000","\n",50)[:1950]) except discord.errors.HTTPException: pass try: tag_name tag_vars = db.get_entries("b++variables", columns=["name"], conditions={"tag": tag_name}) for var in VARIABLES.keys(): if var.startswith("__"): if type(VARIABLES[var]) == list: VARIABLES[var] = list_to_array(VARIABLES[var]) if (var,) in tag_vars: db.edit_entry("b++variables", entry={"value": str(VARIABLES[var])}, conditions={"name": var}) continue db.add_entry("b++variables", entry=[var, str(VARIABLES[var]), tag_name]) except: pass return