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)
Пример #5
0
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)]
Пример #7
0
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!")
Пример #8
0
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
Пример #10
0
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
Пример #11
0
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]
Пример #12
0
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
Пример #13
0
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))
Пример #15
0
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
Пример #16
0
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", "{}")
Пример #18
0
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
Пример #19
0
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
Пример #20
0
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
Пример #23
0
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))
Пример #25
0
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
Пример #26
0
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