def start(self, SERVER):  # Set the parameters
     self.RUNNING = True
     self.MESSAGES = []
     self.db = Database()
     self.SERVER = SERVER
     self.CHANNEL = ""
     self.ANNOUNCE = ""
async def MAIN(message, args, level, perms):
	db = Database()

	if perms < 2: # Direct database viewing is staff-only
		await message.channel.send("You don't have permission to run this subcommand.")
		return


	if level == 2: # If it's just `tc/db main`, list the tables using the get_tables() function.
		table_list = db.get_tables()
		table_list = [f'**{table.upper()}**' for table in table_list]
		await message.channel.send(f"Here's a list of Brain Database's tables: {grammar_list(table_list)}")
		return
	

	if args[1].lower() == "add": # `tc/db main add` creates a table

		if level == 2:
			await message.channel.send("Include a name for the new table!")
			return
		
		if level == 3:
			await message.channel.send("Include the table's columns!")
			return
		
		# I expect columns to be in the format `name-datatype name-datatype` and so on for each column.
		name = args[2].lower()
		columns = [x.lower().split("-") for x in args[3:]]

		db.add_table(name, columns)
		
		# Turn the `name-datatype` into `name datatype`, just for readability in the confirmation message
		columns = [" ".join(x) for x in columns]
		await message.channel.send(f"Table **{name.upper()}** created with columns {', '.join(columns)}")
		return


	if args[1].lower() == "remove": # `tc/db main remove` deletes (a.k.a. drops) a table

		if level == 2:
			await message.channel.send("Include the table you want to remove!")
			return

		name = args[2].lower()

		db.remove_table(name)
		
		await message.channel.send(f"Successfully deleted table **{name.upper()}**.")
		return
	

	if args[1].lower() == "layout": # `tc/db main layout` displays all columns and datatypes of a table

		if level == 2:
			await message.channel.send("Include the table you want to see the layout of!")
			return
	
		name = args[2].lower()

		columns = db.get_columns(name, include_type=True)

		# Make them bold, in the format `name - datatype` and with arrows
		formatted = "\n> ".join([f"**{r}**" for r in [" - ".join(c) for c in columns]])

		await message.channel.send(
			f"Here are the colums and datatypes from the table **{name.upper()}**.\n> {formatted}")
		return
	

	if args[1].lower() == "entries": # `tc/db main entries` Displays, adds or removes entries of a table

		if level == 2:
			await message.channel.send("Include the table you want to see the layout of!")
			return
		
		name = args[2].lower()
		get_entries = False

		# args[4] is the number of entries you want to see
		if level == 3: # If it's not specified, assume 10
			limit = 10
			get_entries = True

		elif args[3].lower() == "all": # If it's "all", just go with 99999. Better safe than sorry
			limit = 99999
			get_entries = True

		elif args[3].lower() not in ["add", "remove", "edit"]:
			# If it's not a further subcommand, interpret it as a number
			try:
				limit = int(args[3])
			except: # If it's not actually a number, just go with 10
				limit = 10
			get_entries = True
		
		# If get_entries is switched to true, that means the section above was able to extrapolate a number
		# from the command - meaning the user wants to see the table's entries, not add or remove them
		if get_entries:
			entries = db.get_entries(name, limit=limit)

			# "\t".join([str(h) for h in e]) returns a single entry, with all its columns joined by tabs
			# ["\t".join([str(h) for h in e]) for e in entries] returns a list of those entry strings
			# `formatted` is set to all those entries together, separated by newlines and in arrow text
			formatted = "\n> ".join(["\t".join([str(h) for h in e]) for e in entries])

			# Gotta have correct grammar
			reported_limit = 'all' if limit >= 99999 else limit
			plural = 'ies' if limit != 1 else 'y'

			to_send = [f"Here are {reported_limit} entr{plural} of **{name.upper()}**.\n> {formatted}"]

			# Limit messages to 1950 characters at most. Cut them off if bigger. Getting any closer to 2000
			# can cause errors regardless for some reason, so I try to avoid it
			if len(to_send[0]) > 1950:
				to_send.append(f"> {to_send[0][1947:3900]}")
				to_send[0] = to_send[0][:1947] + "..."

			for z in to_send:
				await message.channel.send(z)
			return
		
		
		if args[3].lower() == "add":
			# I expect arguments for this to be `value1 // value2 // value3` and so on
			arguments = " ".join(args[4:]).split(" // ")

			db.add_entry(name, arguments)
			
			await message.channel.send(f"Successfully added entry to table **{name.upper()}**!")
			return
		

		if args[3].lower() == "remove":

			if level == 4:
				await message.channel.send("Include a search column!")
				return

			if level == 5 and args[4].lower() == "all": # Delete all entries in the table
				await message.channel.send(f"""Are you sure you want to delete all entries in **{name.upper()}**? 
				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("Database command cancelled.")
					return

				db.remove_entry(name)

				await message.channel.send(f"Successfully cleared all entries from table **{name.upper()}**!")
				return
			

			# Arguments for this should be `column // value`, as a key to determine what should be deleted
			arguments = " ".join(args[4:]).split(" // ")
			
			if len(arguments) % 2 == 1: # Odd number of arguments means one key has no value
				await message.channel.send("Include a value for every updating column!")
				return
			
			conditions = {}
			for z in range(int(len(arguments) / 2)):
				conditions[arguments[z*2]] = arguments[z*2+1]
			
			db.remove_entry(name, conditions)

			await message.channel.send(f"Successfully deleted entries from table **{name.upper()}**!")
			return
		
		
		if args[3].lower() == "edit":

			if level < 7: # Requires at least 3 extra args: `upd_column // upd_key`
				await message.channel.send("Make sure to include the columns to update!")
				return
			
			if len(" ".join(args[4:]).split(" -> ")) == 2: # Split arguments into searching and updating
				searching_arguments = " ".join(args[4:]).split(" -> ")[0].split(" // ")
				updating_arguments = " ".join(args[4:]).split(" -> ")[1].split(" // ")
			else:
				searching_arguments = []
				updating_arguments = " ".join(args[4:]).split(" // ")

			if len(searching_arguments) % 2 == 1:
				await message.channel.send("Include a value for every search column!")
				return
			
			if len(updating_arguments) % 2 == 1:
				await message.channel.send("Include a value for every updating column!")
				return
			
			conditions = {}
			for z in range(int(len(searching_arguments) / 2)):
				conditions[searching_arguments[z*2]] = searching_arguments[z*2+1]
			
			entry = {}
			for z in range(int(len(updating_arguments) / 2)):
				entry[updating_arguments[z*2]] = updating_arguments[z*2+1]
			
			db.edit_entry(name, entry=entry, conditions=conditions)
			
			await message.channel.send(f"Successfully edited entries in **{name.upper()}**!")
			return
class EVENT:
    db = Database()

    # Executes when loaded
    def __init__(self):
        self.RUNNING = False
        self.param = {  # Define all the parameters necessary
            "CHANNEL": "general"
        }

    # Executes when activated
    def start(self, SERVER):  # Set the parameters
        self.SERVER = SERVER
        self.BIRTHDAY_ROLE = MAIN_SERVER["BIRTHDAY"]
        self.RUNNING = True

    # Executes when deactivated
    def end(self):  # Reset the parameters
        self.param = {
            "CHANNEL": "general",
        }
        self.RUNNING = False

    # Function that runs every hour
    async def on_one_hour(self):
        current_time = datetime.utcnow()
        hour = current_time.hour

        self.CHANNEL = discord.utils.get(self.SERVER["MAIN"].channels,
                                         name=self.param["CHANNEL"])

        day_change_tz = []
        for timezone in range(-12, 15):  # For each timezone
            if (hour + timezone
                ) % 24 == 0:  # If the day just changed in this timezone
                tz_info = [timezone
                           ]  # Timezone is the first element of the list

                tz_time = current_time + timedelta(hours=timezone)
                tz_info.append(f"{tz_time.day}/{tz_time.month}")
                tz_info.append(
                    tz_time)  # The day is the second element of the list

                day_change_tz.append(tz_info)

        print(day_change_tz)
        for tz in day_change_tz:
            l_d = tz[2] + timedelta(
                days=-1
            )  # Get the last day in the timezone that just switched days
            n_d = tz[2] + timedelta(days=1)

            n_d = f"{n_d.day}/{n_d.month}"
            l_d = f"{l_d.day}/{l_d.month}"
            '''
			# All people whose birthdays were yesterday
			found = self.db.get_entries("birthday", columns=["id", "timezone"], conditions={"birthday": l_d})
			for person in found: # Cycle through all
				# person[1] < tz[0] checks if the timezone whose day just changed is greater than the person's timezone
				# since the day flips over in greater timezones first and then gradually smaller values, this checks
				# to see if the day has flipped over to the current day (tz[1]) in the person's timezone yet.
				# If person[1] < tz[0], it hasn't, and it's still l_d ("yesterday", tz[1] - 1 day) in their timezone,
				# so it's still their birthday.
				# the second boolean just checks if the person already has the birthday role. If they don't and the
				# first boolean is true, that means this person's birthday was missed, so correct that
				if person[1] < tz[0] and (self.BIRTHDAY_ROLE not in self.SERVER["MAIN"].get_member(int(person[0])).roles):
					f_tz = ("+" if person[1] > 0 else "") + str(person[1])
					await self.CHANNEL.send(
					f"""🎉 It's no longer midnight on **{l_d} UTC {f_tz}**, 
					but happy birthday to <@{person[0]}> regardless! 🎉""".replace("\n", "").replace("\t", "")
					)
					await self.SERVER["MAIN"].get_member(int(person[0])).add_roles(self.BIRTHDAY_ROLE)
			
			# All people whose birthdays are today
			found = self.db.get_entries("birthday", columns=["id", "timezone"], conditions={"birthday": tz[1]})
			for person in found: # Cycle through all
				# person[1] > tz[0] checks if the timezone whose day just changed is smaller than the person's timezone
				# since the day flips over in greater timezones first and then gradually smaller values, this checks
				# to see if the day has flipped over to the next day (tz[1] + 1 day) in the person's timezone yet.
				# If person[1] > tz[0], it hasn't, and it's still tz[1] (today) in their timezone, so it's still their
				# birthday. If person[1] == tz[0], that means it just became their birthday, and that's covered later.
				# the second boolean just checks if the person already has the birthday role. If they don't and the
				# first boolean is true, that means this person's birthday was missed, so correct that
				if person[1] > tz[0] and (self.BIRTHDAY_ROLE not in self.SERVER["MAIN"].get_member(int(person[0])).roles):
					f_tz = ("+" if person[1] > 0 else "") + str(person[1])
					await self.CHANNEL.send(
					f"""🎉 It's no longer midnight on **{tz[1]} UTC {f_tz}**, 
					but happy birthday to <@{person[0]}> regardless! 🎉""".replace("\n", "").replace("\t", "")
					)
					await self.SERVER["MAIN"].get_member(int(person[0])).add_roles(self.BIRTHDAY_ROLE)
			
			found = self.db.get_entries("birthday", columns=["id", "timezone"], conditions={"birthday": n_d})
			for person in found:
				# person[1] - 24 > tz[0] checks if the timezone whose day just changed is smaller enough than the
				# person's timezone such that there's a difference of two days between the timezone that just changed
				# and the person's. This checks to see if it's already n_d ("tomorrow", tz[1] + 1 day) somewhere and if
				# it's also at least 1 AM, so that it's possible we missed someone there.
				# If person[1] - 24 > tz[0], it is, and it's already n_d in their timezone, so it's already their
				# birthday. If person[1] == tz[0], that means it just became their birthday, and that's covered later.
				# the second boolean just checks if the person already has the birthday role. If they don't and the
				# first boolean is true, that means this person's birthday was missed, so correct that
				if person[1] - 24 > tz[0] and (self.BIRTHDAY_ROLE not in self.SERVER["MAIN"].get_member(int(person[0])).roles):
					f_tz = ("+" if person[1] > 0 else "") + str(person[1])
					await self.CHANNEL.send(
					f"""🎉 It's no longer midnight on **{n_d} UTC {f_tz}**, 
					but happy birthday to <@{person[0]}> regardless! 🎉""".replace("\n", "").replace("\t", "")
					)
					await self.SERVER["MAIN"].get_member(int(person[0])).add_roles(self.BIRTHDAY_ROLE)
			'''

            # Find members whose birthdays just ended in that timezone (one day ago, same timezone = exactly 24h ago)
            found = self.db.get_entries("birthday",
                                        columns=["id"],
                                        conditions={
                                            "birthday": l_d,
                                            "timezone": tz[0]
                                        })
            print("No more birthday:", found)
            for member in found:  # Remove their birthday role, as their birthday just ended
                await self.SERVER["MAIN"].get_member(int(
                    member[0])).remove_roles(self.BIRTHDAY_ROLE)

            # Now, search for members whose birthday just started (today, in the day-changing timezone = it's midnight)
            found = self.db.get_entries("birthday",
                                        columns=["id"],
                                        conditions={
                                            "birthday": tz[1],
                                            "timezone": tz[0]
                                        })
            print("More birthday:", found)
            if len(found) == 0:  # If there are none, return
                return

            # If there are members, cycle through each of them.
            for member in found:
                if self.BIRTHDAY_ROLE in self.SERVER["MAIN"].get_member(
                        int(member[0])).roles:
                    found[found.index(
                        member
                    )] = 0  # If they already have the birthday role, they're being counted
                    continue  # again, and this is a mistake. Change their id in found to 0 and continue

                # If they don't have the birthday role, give it to them
                await self.SERVER["MAIN"].get_member(int(member[0])).add_roles(
                    self.BIRTHDAY_ROLE)

            found = [
                x for x in found if x != 0
            ]  # Remove those who already had their birthday counted to avoid
            # birthday ping repeats.

            print("Actual birthday announcements:", found)

            if len(found) == 0:
                return  # If nobody's birthday is supposed to be announced now, return

            # Specify the timezone the bot is covering in this message
            f_tz = ("+" if tz[0] > 0 else "") + str(tz[0])

            # Prepare pings for everyone having their birthday
            birthday_mentions = grammar_list([f"<@{x[0]}>" for x in found])

            await self.CHANNEL.send(
                f"🎉 It's now **{tz[1]} UTC {f_tz}**! Happy birthday to {birthday_mentions}! 🎉"
            )
        return

    # Change a parameter of the event
    async def edit_event(self, message, new_params):
        incorrect = []
        correct = []
        for parameter in new_params.keys():
            try:
                self.param[parameter] = new_params[parameter]
                correct.append(parameter)
            except KeyError:
                incorrect.append(parameter)

        if len(correct) > 0:
            await message.channel.send(
                f"Successfully changed the parameters: {grammar_list(correct)}"
            )
        if len(incorrect) > 0:
            await message.channel.send(
                f"The following parameters are invalid: {grammar_list(incorrect)}"
            )

        return
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
Exemple #5
0
class EVENT:
    db = Database()

    # Executes when loaded
    def __init__(self):
        self.RUNNING = False
        self.param = {  # Define all the parameters necessary
            "CHANNEL": "general"
        }

    # Executes when activated
    def start(self, SERVER):  # Set the parameters
        self.SERVER = SERVER
        self.BIRTHDAY_ROLE = discord.utils.get(SERVER["MAIN"].roles,
                                               id=MAIN_SERVER["BIRTHDAY"])
        self.RUNNING = True

    # Executes when deactivated
    def end(self):  # Reset the parameters
        self.param = {
            "CHANNEL": "general",
        }
        self.RUNNING = False

    # Function that runs every hour
    async def on_one_hour(self):
        current_time = datetime.utcnow()
        hour = current_time.hour

        self.CHANNEL = discord.utils.get(self.SERVER["MAIN"].channels,
                                         name=self.param["CHANNEL"])

        day_change_tz = []
        for timezone in range(-12, 15):  # For each timezone
            if (hour + timezone
                ) % 24 == 0:  # If the day just changed in this timezone
                tz_info = [timezone
                           ]  # Timezone is the first element of the list

                tz_time = current_time + timedelta(hours=timezone)
                tz_info.append(f"{tz_time.day}/{tz_time.month}")
                tz_info.append(
                    tz_time)  # The day is the second element of the list

                day_change_tz.append(tz_info)

        for tz in day_change_tz:
            l_d = tz[2] + timedelta(
                days=-1
            )  # Get the last day in the timezone that just switched days
            n_d = tz[2] + timedelta(days=1)

            n_d = f"{n_d.day}/{n_d.month}"
            l_d = f"{l_d.day}/{l_d.month}"

            found = self.db.get_entries("birthday",
                                        columns=["id", "timezone"],
                                        conditions={"birthday": l_d})
            for person in found:
                if person[1] < tz[0] and self.BIRTHDAY_ROLE not in self.SERVER[
                        "MAIN"].get_member(int(member[0])).roles:
                    f_tz = ("+" if person[1] > 0 else "") + str(person[1])
                    await self.CHANNEL.send(
                        f"🎉 It is no longer **{l_d} UTC {f_tz}**, but happy birthday to <@{person[0]}> regardless! 🎉"
                    )
                    await self.SERVER["MAIN"].get_member(int(
                        person[0])).add_roles(self.BIRTHDAY_ROLE)

            found = self.db.get_entries("birthday",
                                        columns=["id", "timezone"],
                                        conditions={"birthday": tz[1]})
            for person in found:
                if person[1] > tz[
                        0] and self.BIRTHDAY_ROLE not in SERVER.get_member(
                            int(member[0])).roles:
                    f_tz = ("+" if person[1] > 0 else "") + str(person[1])
                    await self.CHANNEL.send(
                        f"🎉 It is no longer **{tz[1]} UTC {f_tz}**, but happy birthday to <@{person[0]}> regardless! 🎉"
                    )
                    await self.SERVER["MAIN"].get_member(int(
                        person[0])).add_roles(self.BIRTHDAY_ROLE)

            found = self.db.get_entries("birthday",
                                        columns=["id", "timezone"],
                                        conditions={"birthday": n_d})
            for person in found:
                if person[1] - 24 > tz[
                        0] and self.BIRTHDAY_ROLE not in self.SERVER[
                            "MAIN"].get_member(int(member[0])).roles:
                    f_tz = ("+" if person[1] > 0 else "") + str(person[1])
                    await self.CHANNEL.send(
                        f"🎉 It is no longer **{n_d} UTC {f_tz}**, but happy birthday to <@{person[0]}> regardless! 🎉"
                    )
                    await self.SERVER["MAIN"].get_member(int(
                        person[0])).add_roles(self.BIRTHDAY_ROLE)

            # Find members whose birthdays just ended in that timezone
            found = self.db.get_entries("birthday",
                                        columns=["id"],
                                        conditions={
                                            "birthday": l_d,
                                            "timezone": tz[0]
                                        })
            for member in found:  # Remove their birthday role
                await self.SERVER["MAIN"].get_member(int(
                    member[0])).remove_roles(self.BIRTHDAY_ROLE)

            # Now, search for members whose birthday just started
            found = self.db.get_entries("birthday",
                                        columns=["id"],
                                        conditions={
                                            "birthday": tz[1],
                                            "timezone": tz[0]
                                        })

            if len(found) == 0:  # If there are none, return
                return

            # If there are members, cycle through each of them.
            for member in found:
                if self.BIRTHDAY_ROLE in self.SERVER["MAIN"].get_member(
                        int(member[0])).roles:
                    found[found.index(
                        member
                    )] = 0  # If they already have the birthday role, they're being counted
                    continue  # again, and this is a mistake. Change their id in found to 0 and continue

                # If they don't have the birthday role, give it to them
                await self.SERVER["MAIN"].get_member(int(member[0])).add_roles(
                    self.BIRTHDAY_ROLE)

            found = [
                x for x in found if x != 0
            ]  # Remove those who already had their birthday counted to avoid
            # birthday ping repeats.

            if len(found) == 0:
                return  # If nobody's birthday is supposed to be announced now, return

            # Specify the timezone the bot is covering in this message
            f_tz = ("+" if tz[0] > 0 else "") + str(tz[0])

            # Prepare pings for everyone having their birthday
            birthday_mentions = grammar_list([f"<@{x[0]}>" for x in found])

            await self.CHANNEL.send(
                f"🎉 It's now **{tz[1]} UTC {f_tz}**! Happy birthday to {birthday_mentions}! 🎉"
            )
        return

    # Change a parameter of the event
    async def edit_event(self, message, new_params):
        incorrect = []
        correct = []
        for parameter in new_params.keys():
            try:
                self.param[parameter] = new_params[parameter]
                correct.append(parameter)
            except KeyError:
                incorrect.append(parameter)

        if len(correct) > 0:
            await message.channel.send(
                f"Successfully changed the parameters: {grammar_list(correct)}"
            )
        if len(incorrect) > 0:
            await message.channel.send(
                f"The following parameters are invalid: {grammar_list(incorrect)}"
            )

        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
Exemple #7
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
Exemple #8
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
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", "{}")
Exemple #10
0
class EVENT:
    db = Database()

    # Executes when loaded
    def __init__(self):
        self.RUNNING = False
        self.param = {"HOUR_SPEED": 1}

    # Executes when activated
    def start(self, SERVER):  # Set the parameters
        self.SERVER = SERVER
        self.RUNNING = True

    # Executes when deactivated
    def end(self):  # Reset the parameters
        self.RUNNING = False

    # Function that runs every hour
    async def on_one_hour(self):
        if "BANNER" not in self.SERVER["MAIN"].features:
            return

        current_time = datetime.utcnow()
        hour = current_time.hour

        if hour % self.param["HOUR_SPEED"] != 0:
            return

        banner_ind, banner_list = self.db.get_entries("tcbanner")[0]
        banner_list = banner_list.split(" ")

        banner_ind += 1
        banner_ind %= len(banner_list)

        new_banner = banner_list[banner_ind]

        self.db.edit_entry("tcbanner",
                           entry={
                               "current": banner_ind,
                               "url": " ".join(banner_list)
                           })

        async with aiohttp.ClientSession() as session:
            try:
                async with session.get(new_banner) as resp:
                    if resp.status != 200:
                        return

                    data = io.BytesIO(await resp.read())
                    await self.SERVER["MAIN"].edit(banner=data.read())

            except aiohttp.client_exceptions.InvalidURL:
                pass
        return

    # Change a parameter of the event
    async def edit_event(self, message, new_params):
        incorrect = []
        correct = []
        for parameter in new_params.keys():
            try:
                self.param[parameter] = new_params[parameter]
                correct.append(parameter)
            except KeyError:
                incorrect.append(parameter)

        if len(correct) > 0:
            await message.channel.send(
                f"Successfully changed the parameters: {grammar_list(correct)}"
            )
        if len(incorrect) > 0:
            await message.channel.send(
                f"The following parameters are invalid: {grammar_list(incorrect)}"
            )

        return
async def MAIN(message, args, level, perms, SERVER):
    db = Database()

    months = [
        "January", "February", "March", "April", "May", "June", "July",
        "August", "September", "October", "November", "December"
    ]

    if level == 1:  # If just tc/bd, return info on their birthday
        found = db.get_entries("birthday",
                               conditions={"id": str(message.author.id)})

        if found == []:
            await message.channel.send(
                f"""You are yet to register your birthday!
			You can register by using **{SERVER["PREFIX"]}birthday register `DD/MM` `timezone`**"""
                .replace("\t", ""))
            return

        birthday, tz = found[0][1:]
        birthday = birthday.split("/")

        birthday_format = months[int(birthday[1]) - 1] + " " + str(birthday[0])
        timezone_f = ("+" if tz > 0 else "") + str(tz)

        await message.channel.send(
            f"""**{message.author.name}**'s birthday is set as **{birthday_format}** in **UTC {timezone_f}**."""
        )
        return

    if args[1].lower() == "month":
        if level == 2:
            chosen_month = datetime.now(timezone.utc).month

        elif is_whole(args[2]):
            if int(args[2]) > 12 or int(args[2]) < 1:
                await message.channel.send(
                    "Invalid month number. If you're picking a month number, choose one between 1 and 12!"
                )
                return

            chosen_month = int(args[2])

        else:
            possible_months = [
                x for x in months if x.lower().startswith(args[2].lower())
            ]

            if len(possible_months) == 0:
                new_possible = [
                    x for x in months if args[2].lower() in x.lower()
                ]

                if len(new_possible) == 0:
                    await message.channel.send(
                        "There's no valid month with that name!")
                    return

                if len(new_possible) > 1:
                    await message.channel.send(
                        f"""There are multiple months fitting that search key. Please specify which one you mean!
						`({', '.join(new_possible)})`""".replace("\t", ""))
                    return

                possible_months = new_possible

            if len(possible_months) > 1:
                await message.channel.send(
                    f"""There are multiple months fitting that search key. Please specify which one you mean!
					`({', '.join(possible_months)})`""".replace("\t", ""))
                return

            chosen_month = months.index(possible_months[0]) + 1

        found = db.get_entries("birthday")
        found = [k for k in found if int(k[1].split("/")[1]) == chosen_month]
        month_name = months[chosen_month - 1]

        if len(found) == 0:
            await message.channel.send(
                f"There are no registered birthdays in {month_name}!")
            return

        found = sorted(found, key=lambda k: int(k[1].split("/")[0]))

        messages = [
            f"Here are all the birthdays registered in {month_name}:\n\n"
        ]

        for bd in found:
            try:
                username = f"**{SERVER['MAIN'].get_member(int(bd[0])).name}**"
            except Exception:
                username = f"**`[{bd[0]}]`**"

            day = bd[1].split("/")[0]
            tz = ("+" if bd[2] > 0 else "") + str(bd[2])

            line = f"> {username} - {month_name} {day}, UTC {tz}\n"

            if len(messages[-1]) + len(line) > 1900:
                messages.append("")

            messages[-1] += line

        for m in messages:
            await message.channel.send(m)

        return

    if args[1].lower() == "next":
        found = db.get_entries("birthday")
        found = sorted(found, key=lambda k: int(k[1].split("/")[0]))
        found = sorted(found, key=lambda k: int(k[1].split("/")[1]))

        day, month = datetime.now(timezone.utc).day, datetime.now(
            timezone.utc).month

        for bd in found:
            if int(bd[1].split("/")[1]) > month:
                next_bd = bd
                break
            elif int(bd[1].split("/")[1]) == month and int(
                    bd[1].split("/")[0]) > day:
                next_bd = bd
                break
        else:
            next_bd = found[0]

        next_id, birthday, tz = next_bd
        birthday = birthday.split("/")
        birthday_format = months[int(birthday[1]) - 1] + " " + str(
            int(birthday[0]))
        timezone_f = ("+" if tz > 0 else "") + str(tz)

        try:
            username = SERVER["MAIN"].get_member(int(next_id)).name
        except AttributeError:
            username = next_id

        await message.channel.send(
            f"The next birthday is **{username}**'s, on **{birthday_format}** in **UTC {timezone_f}**."
        )
        return

    if args[1].lower() == "user":
        if level == 2:
            await message.channel.send(
                "Include the username or ID of the person whose birthday you want to check."
            )
            return

        rest = " ".join(args[2:])

        if rest.startswith("<@") and rest.endswith(">"):
            rest = rest[2:-1]

        if is_whole(rest):
            found = db.get_entries("birthday", conditions={"id": rest})

            try:
                username = SERVER["MAIN"].get_member(int(rest)).name
            except:
                username = rest

            if found == []:
                await message.channel.send(
                    f"**{username}** has not registered a birthday yet!")
                return

            user_bd = found[0]
            birthday, tz = user_bd[1:]
            birthday = birthday.split("/")
            birthday_format = months[int(birthday[1]) - 1] + " " + str(
                int(birthday[0]))
            timezone_f = ("+" if tz > 0 else "") + str(tz)

            await message.channel.send(
                f"**{username}**'s birthday is on **{birthday_format}** in **UTC {timezone_f}**."
            )
            return

        else:
            user = discord.utils.get(SERVER["MAIN"].members, name=rest)

            if user is None:
                await message.channel.send("That user is not in the server!")
                return

            found = db.get_entries("birthday", conditions={"id": str(user.id)})

            if found == []:
                await message.channel.send(
                    f"**{rest}** has not registered a birthday yet!")
                return

            user_bd = found[0]
            birthday, tz = user_bd[1:]
            birthday = birthday.split("/")
            birthday_format = months[int(birthday[1]) - 1] + " " + str(
                int(birthday[0]))
            timezone_f = ("+" if tz > 0 else "") + str(tz)

            await message.channel.send(
                f"**{rest}**'s birthday is on **{birthday_format}** in **UTC {timezone_f}**."
            )
            return

    if args[1].lower() == "remove":
        found = db.get_entries("birthday",
                               conditions={"id": str(message.author.id)})

        if found == []:
            await message.channel.send(
                "You haven't registered a birthday yet to remove it!")
            return

        await message.channel.send(
            f"""Are you sure you want to remove your birthday from the database? Send `confirm` 
		in this channel to confirm. Send anything else to cancel.""".replace(
                "\n", "").replace("\t", ""))

        # Wait for a message by the same author in the same channel
        msg = await BRAIN.wait_for(
            'message',
            check=(lambda m: m.channel == message.channel and m.author ==
                   message.author))

        if msg.content.lower(
        ) != "confirm":  # If it's not `confirm`, cancel command
            await message.channel.send("Birthday registering cancelled.")
            return

        db.remove_entry("birthday", conditions={"id": str(message.author.id)})
        await message.channel.send(
            "Your birthday has been removed from the database.")
        return

    if args[1].lower() == "register":
        if level == 2:
            await message.channel.send(
                "Include your birthday in `DD/MM` to register!")
            return

        # Check if the person is in the birthday database or not
        found = db.get_entries("birthday",
                               conditions={"id": str(message.author.id)})

        birthday = args[2].split("/")

        if level == 3:
            tz = 0

        elif not is_whole(args[3]):
            await message.channel.send(
                "Invalid timezone! Make sure it's a whole number from -12 to 14!"
            )
            return

        elif not -12 <= int(args[3]) <= 14:
            await message.channel.send(
                "Invalid timezone! Make sure it's a whole number from -12 to 14!"
            )
            return

        else:
            tz = int(args[3])

        if len(birthday) != 2:  # If it's not `n/n`
            await message.channel.send(
                "Invalid birthday! Make sure it's in the `DD/MM` format!")
            return

        if not is_whole(birthday[0]) or not is_whole(
                birthday[1]):  # If day and month aren't numbers
            await message.channel.send(
                "Invalid birthday! Make sure the day and month are both numbers!"
            )
            return

        # Transform into integers for these next two checks
        birthday[0] = int(birthday[0])
        birthday[1] = int(birthday[1])

        if not 1 <= birthday[1] <= 12:  # If month is invalid
            await message.channel.send(
                "Invalid month! Make sure it's between 1 and 12.")
            return

        if not 1 <= birthday[0] <= monthrange(
                2020, birthday[1])[1]:  # monthrange checks days in the month
            await message.channel.send(  # 2020 months because it's a leap year, and 29/02 should be available
                f"Invalid day! Make sure it's between 1 and {monthrange(2020, birthday[1])[1]} for that month."
            )
            return

        birthday_format = months[birthday[1] - 1] + " " + str(birthday[0])
        birthday = "/".join([str(x) for x in birthday
                             ])  # Join the list again for the next few lines
        timezone_f = ("+" if tz > 0 else "") + str(tz)

        # This confirmation message cannot be bypassed
        await message.channel.send(
            f"""Are you sure you want to record your birthday as {birthday_format} and your 
		timezone as UTC {timezone_f}? Send `confirm` in this channel to confirm.
		""".replace("\n", "").replace("\t", ""))

        # Wait for a message by the same author in the same channel
        msg = await BRAIN.wait_for(
            'message',
            check=(lambda m: m.channel == message.channel and m.author ==
                   message.author))

        if msg.content.lower(
        ) != "confirm":  # If it's not `confirm`, cancel command
            await message.channel.send("Birthday registering cancelled.")
            return

        # If confirmation passed, record the birthday
        if found == []:
            is_new = ""
            db.add_entry("birthday", [message.author.id, birthday, tz])
        else:
            is_new = "new "
            db.edit_entry("birthday",
                          entry={
                              "birthday": birthday,
                              "timezone": tz
                          },
                          conditions={"id": str(message.author.id)})

        await message.channel.send(
            f"Successfully recorded your {is_new}birthday as **{birthday} UTC {timezone_f}**!"
        )
        return
async def MAIN(message, args, level, perms, SERVER):
    if level == 1:
        await message.channel.send("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", ""))
Exemple #13
0
class EVENT:
    db = Database()

    # Executes when loaded
    def __init__(self):
        self.RUNNING = False

        self.CHANNEL_NAME = "game-room"

        self.info = {
            "HOST_QUEUE": [],
            "PLAYERS": [],
            "RESPONSES": [],
            "SPECTATORS": [],
            "VOTES": {
                "ID": [],
                "RESP": [],
                "VOTE": []
            },
            "GAME": {
                "ROUND": 0,
                "PERIOD": 0,
                "PROMPT": "",
                "HOST": 0,
                "PERIOD_START": 0,
                "END_VOTES": [],
            }
        }

        self.param = {  # Define all the parameters necessary
            "ELIM_RATE": 0.2,
            "R_DEADLINE": 180,
            "V_DEADLINE": 150,
            "Q_DEADLINE": 60,
            "S_DEADLINE": 180,
            "P_DEADLINE": 120
        }

    # Executes when activated
    def start(self, SERVER):  # Set the parameters
        self.MMT_C = discord.utils.get(SERVER["MAIN"].channels,
                                       name=self.CHANNEL_NAME)
        self.RUNNING = True
        self.SERVER = SERVER
        self.PREFIX = SERVER["PREFIX"]
        self.info["GAME"]["PERIOD_START"] = time.time()

    # Executes when deactivated
    def end(self):  # Reset the parameters
        self.info = {
            "HOST_QUEUE": [],
            "PLAYERS": [],
            "SPECTATORS": [],
            "RESPONSES": [],
            "VOTES": {
                "ID": [],
                "RESP": [],
                "VOTE": []
            },
            "GAME": {
                "ROUND": 0,
                "PERIOD": 0,
                "PROMPT": "",
                "HOST": 0,
                "PERIOD_START": 0,
                "END_VOTES": [],
            }
        }
        self.RUNNING = False

    # [Event-exclusive] Ends the current MiniMiniTWOW, moving on to the next host in queue
    def force_skip(self):  # Resets just about everything except the queue
        self.info["PLAYERS"] = []
        self.info["SPECTATORS"] = []
        self.info["RESPONSES"] = []
        self.info["VOTES"] = {"ID": [], "RESP": [], "VOTE": []}
        self.info["GAME"] = {
            "ROUND": 0,
            "PERIOD": 0,
            "PROMPT": "",
            "HOST": 0,
            "PERIOD_START": 0,
            "END_VOTES": [],
        }

    # Function that runs every two seconds
    async def on_two_second(self):
        if len(self.info["HOST_QUEUE"]
               ) == 0 and self.info["GAME"]["HOST"] == 0:
            await self.MMT_C.send(
                "🎩 There are no more people in queue to host a MiniMiniTWOW.")
            self.end()
            return

        if self.info["GAME"][
                "HOST"] == 0:  # When there's no host, the queue moves on
            self.info["GAME"]["HOST"] = self.info["HOST_QUEUE"][
                0]  # Turn first person into host
            self.info["HOST_QUEUE"] = self.info["HOST_QUEUE"][
                1:]  # Remove first person from queue

            await self.MMT_C.send(
                f"""🎩 <@{self.info["GAME"]["HOST"]}> is now up on the queue! Send 
			`{self.PREFIX}mmt create` to create a MiniMiniTWOW within the next {self.param["Q_DEADLINE"]} seconds, 
			or you'll be skipped from queue!""".replace("\n", "").replace("\t", ""))

            self.info["GAME"]["PERIOD_START"] = time.time(
            )  # Start the mmt create timer
            return

        if self.info["GAME"][
                "PERIOD"] == 0:  # The host has to create the MiniMiniTWOW
            # How much time has passed since the start of the period
            remain = round(time.time() - self.info["GAME"]["PERIOD_START"])

            # If there's around 30 seconds left to create the MMT
            if remain in [
                    self.param["Q_DEADLINE"] - 30,
                    self.param["Q_DEADLINE"] - 31
            ]:
                await self.MMT_C.send(
                    f"""🎩 <@{self.info["GAME"]["HOST"]}>, you have 30 seconds to create the 
				MiniMiniTWOW with `{self.PREFIX}mmt create`. Do it, or you'll be skipped from queue!
				""".replace("\n", "").replace("\t", ""))

            # If it's past the deadline
            if remain >= self.param["Q_DEADLINE"]:
                await self.MMT_C.send(
                    f"""🎩 <@{self.info["GAME"]["HOST"]}> has been skipped for taking too long to 
				create the MiniMiniTWOW.""".replace("\n", "").replace("\t", ""))
                self.force_skip()

            return

        if self.info["GAME"][
                "PERIOD"] == 1:  # The host has to start the MiniMiniTWOW
            if self.info["GAME"][
                    "PERIOD_START"] != 0:  # If it's 0, the two-player timer hasn't been triggered yet
                remain = round(time.time() - self.info["GAME"]["PERIOD_START"])

                # If there's around 30 seconds left to start the MMT
                if remain in [
                        self.param["S_DEADLINE"] - 30,
                        self.param["S_DEADLINE"] - 31
                ]:
                    await self.MMT_C.send(
                        f"""🏁 <@{self.info["GAME"]["HOST"]}>, you have 30 seconds to end signups 
					start the MiniMiniTWOW with `{self.PREFIX}mmt start`. Do it, or you'll be skipped from queue!
					""".replace("\n", "").replace("\t", ""))
                    return

                # If it's past the deadline
                if remain >= self.param["S_DEADLINE"]:
                    await self.MMT_C.send(
                        f"""🏁 <@{self.info["GAME"]["HOST"]}> has been skipped for taking too long 
					to start the MiniMiniTWOW.""".replace("\n", "").replace("\t", ""))
                    self.force_skip()

            return

        if self.info["GAME"][
                "PERIOD"] == 2:  # The host has to pick a prompt for the MiniMiniTWOW
            remain = time.time() - self.info["GAME"]["PERIOD_START"]

            if remain < 1.95:  # If the prompt decision period *just started*
                await self.MMT_C.send(
                    f"""📰 <@{self.info["GAME"]["HOST"]}> has {self.param["P_DEADLINE"]} seconds 
				to decide on the Round {self.info["GAME"]["ROUND"]} Prompt by using `{self.PREFIX}mmt prompt`.
				""".replace("\n", "").replace("\t", ""))
                return

            # If there's around 30 seconds left to decide the prompt
            if round(remain) in [
                    self.param["P_DEADLINE"] - 30,
                    self.param["P_DEADLINE"] - 31
            ]:
                await self.MMT_C.send(
                    f"""📰 <@{self.info["GAME"]["HOST"]}>, you have 30 seconds to decide on a prompt 
				with `{self.PREFIX}mmt prompt`. Do it, or you'll be skipped from queue!
				""".replace("\n", "").replace("\t", ""))
                return

            # If it's past the deadline
            if remain >= self.param["P_DEADLINE"]:
                await self.MMT_C.send(
                    f"""📰 <@{self.info["GAME"]["HOST"]}> has been skipped for taking too long 
				to write a prompt for the MiniMiniTWOW.""".replace("\n",
                                                       "").replace("\t", ""))
                self.force_skip()

            return

        if self.info["GAME"][
                "PERIOD"] == 3:  # The contestants have to submit to the MiniMiniTWOW
            remain = round(time.time() - self.info["GAME"]["PERIOD_START"])

            # If everyone submitted, and the deadline *can* be shortened to 15 seconds
            if self.info["RESPONSES"].count(
                    "") == 0 and remain < self.param["R_DEADLINE"] - 15:
                await self.MMT_C.send(
                    "📝 Everyone has responded! Voting will begin in 15 seconds."
                )
                self.info["GAME"]["PERIOD_START"] += (
                    time.time() - self.info["GAME"]["PERIOD_START"] -
                    self.param["R_DEADLINE"] + 15)

            # If there's around 60 seconds left to respond
            if remain in [
                    self.param["R_DEADLINE"] - 60,
                    self.param["R_DEADLINE"] - 61
            ]:
                for player in self.info["PLAYERS"]:
                    ind = self.info["PLAYERS"].index(player)

                    if self.info["RESPONSES"][ind] == "":
                        try:
                            await self.SERVER["MAIN"].get_member(player).send(
                                f"""📝 <@{player}>, you have 1 minute to submit to the current prompt!
							```{self.info["GAME"]["PROMPT"]}```
							If you don't respond, you'll be eliminated!""".replace("\n",
                                                              "").replace(
                                                                  "\t", ""))
                        except Exception:
                            pass

            # If it's past the deadline
            if remain >= self.param["R_DEADLINE"]:
                dnr_list = []  # Make a list of contestants who DNRd
                for player in self.info["PLAYERS"]:
                    ind = self.info["PLAYERS"].index(player)

                    if self.info["RESPONSES"][ind] == "":
                        dnr_list.append(player)
                        self.info["PLAYERS"][ind] = ""

                # Remove DNRers
                self.info["PLAYERS"] = [
                    x for x in self.info["PLAYERS"] if x != ""
                ]
                self.info["RESPONSES"] = [
                    x for x in self.info["RESPONSES"] if x != ""
                ]

                # Format the DNR list with pings
                dnr_list = [f"<@{x}>" for x in dnr_list]

                players = len(
                    self.info["PLAYERS"])  # Report the new number of players

                initial = f'📝 **Round {self.info["GAME"]["ROUND"]} Responding** is over!\n'

                # Specify whether or not anyone DNRd
                if len(dnr_list) > 0:
                    initial += f"""The following people did not respond: {grammar_list(dnr_list)}. We are down 
					to {players} player{'s' if players != 1 else ''}.""".replace("\n",
                                                                  "").replace(
                                                                      "\t", "")
                else:
                    initial += "Everyone responded."

                await self.MMT_C.send(initial)

                if players == 1:  # If only one person remains, they win
                    winner = self.info["PLAYERS"][0]
                    await self.MMT_C.send(
                        f"""🏆 <@{winner}> wins the MiniMiniTWOW!""")

                    # Update database, increment their win count

                    # Search for them in the mmtstats table
                    found = self.db.get_entries("mmtstats",
                                                columns=["id"],
                                                conditions={"id": str(winner)})

                    if len(
                            found
                    ) == 0:  # If they're not in the mmtstats table, add them
                        self.db.add_entry("mmtstats", entry=[winner, "", 1])
                    else:  # Increment their win count in mmtstats
                        wins = self.db.get_entries(
                            "mmtstats",
                            columns=["wins"],
                            conditions={"id": str(winner)})[0][0]
                        self.db.edit_entry("mmtstats",
                                           entry={"wins": wins + 1},
                                           conditions={"id": str(winner)})

                    self.force_skip()
                    return

                if players == 0:  # If everyone DNRd
                    await self.MMT_C.send(
                        f"""🏆 Everyone is eliminated. The MiniMiniTWOW ends with no winner!"""
                    )
                    self.force_skip()
                    return

                # Switch to voting period
                self.info["GAME"]["PERIOD"] = 4

                # Prepare the voting arrays
                self.info["VOTES"]["ID"] = []
                self.info["VOTES"]["RESP"] = []
                self.info["VOTES"]["VOTE"] = []

                for spec in self.info["SPECTATORS"]:
                    # Generate a random list of all or 8 of the responses as the screen
                    if spec not in self.info["PLAYERS"]:
                        screen = random.sample(
                            self.info["RESPONSES"],
                            min(8, len(self.info["RESPONSES"])))
                    else:  # If the voter is alive, the screen must contain the voter's response
                        ind = self.info["PLAYERS"].index(spec)
                        player_response = self.info["RESPONSES"][
                            ind]  # This is the response that the voter submitted
                        other_responses = [
                            resp for resp in self.info["RESPONSES"]
                            if resp != player_response
                        ]
                        screen = [player_response] + random.sample(
                            other_responses, min(7, len(other_responses)))
                        random.shuffle(
                            screen
                        )  # So that the player's response isn't always A

                    # Add the voter and the screen to the vote arrays
                    self.info["VOTES"]["ID"].append(spec)
                    self.info["VOTES"]["RESP"].append(screen)
                    self.info["VOTES"]["VOTE"].append("")

                    message = f"""🗳️ **Round {self.info["GAME"]["ROUND"]} Voting**
					```{self.info["GAME"]["PROMPT"]}```
					Cast your vote on the entries below by using `{self.PREFIX}mmt vote` followed by the letters 
					of each response ordered from best to worst in your opinion. You have {self.param["V_DEADLINE"]} 
					seconds to vote!""".replace("\n", "").replace("\t", "")

                    # Format the screen properly
                    screen = "\n".join([
                        f"""`({ALPHABET[screen.index(resp)]})` {resp} `({word_count(resp)} 
						word{'s' if word_count(resp) != 1 else ''})`""".replace(
                            "\n", "").replace("\t", "") for resp in screen
                    ])

                    message += "\n" + screen

                    try:  # Try to send the screen to the member
                        await self.SERVER["MAIN"].get_member(spec).send(message
                                                                        )
                    except Exception:  # If you can't, skip them
                        pass

                # Start the voting timer and announce voting
                self.info["GAME"]["PERIOD_START"] = time.time()
                await self.MMT_C.send(
                    f"""🗳️ **Round {self.info["GAME"]["ROUND"]} Voting** has started! Spectators 
				have been sent the voting screens, and have {self.param["V_DEADLINE"]} seconds to vote.
				""".replace("\n", "").replace("\t", ""))

            return

        if self.info["GAME"]["PERIOD"] == 4:  # The spectators have to vote
            remain = round(time.time() - self.info["GAME"]["PERIOD_START"])

            # If every spectator voted and the deadline *can* be shortened to 10 seconds
            if self.info["VOTES"]["VOTE"].count(
                    "") == 0 and remain < self.param["V_DEADLINE"] - 10:
                await self.MMT_C.send(
                    "🗳️ Every spectator has voted! Voting ends in 10 seconds.")
                self.info["GAME"]["PERIOD_START"] += (
                    time.time() - self.info["GAME"]["PERIOD_START"] -
                    self.param["V_DEADLINE"] + 10)

            # If around 45 seconds remain to vote
            if remain in [
                    self.param["V_DEADLINE"] - 45,
                    self.param["V_DEADLINE"] - 46
            ]:
                await self.MMT_C.send(
                    "🗳️ 45 seconds left to vote! There are still spectators that haven't voted."
                )

            # If it's past the deadline
            if remain >= self.param["V_DEADLINE"]:
                await self.MMT_C.send(
                    f'🗳️ **Round {self.info["GAME"]["ROUND"]} Voting** is over!'
                )

                # Prepare the leaderboard
                leaderboard = []

                # Add the first two columns of the leaderboard (player ping and their response), plus an array
                # for vote placements, used for calculation
                for r in range(len(self.info["RESPONSES"])):
                    leaderboard.append([
                        f'<@{self.info["PLAYERS"][r]}>',
                        self.info["RESPONSES"][r], []
                    ])

                for vote in range(len(
                        self.info["VOTES"]["VOTE"])):  # For each vote...
                    scores = self.info["VOTES"]["VOTE"][vote].split(
                        " ")  # Separate the scores
                    responses = self.info["VOTES"]["RESP"][
                        vote]  # And the responses those scores correspond to

                    if len(
                            scores
                    ) < 2:  # Detects placeholder vote strings (spectators that didn't vote)
                        continue  # Skips the vote

                    for s in range(len(scores)):  # For each score...
                        resp = responses[s]  # Identify the response
                        score = float(scores[s])  # Convert score to float
                        ind = self.info["RESPONSES"].index(
                            resp
                        )  # Find the response's index on the response list

                        leaderboard[ind][2].append(
                            score
                        )  # Add the score to the response on the leaderboard
                        # The response list indices and the leaderboard indices will coincide

                for resp in leaderboard:
                    try:  # Try to calculate the average
                        mean = np.mean(resp[2])
                    except:
                        mean = "N/A"

                    try:  # Try to calculate the stdev
                        stdev = statistics.stdev(resp[2])
                    except:
                        stdev = "N/A"

                    # Add average, stdev and vote count to the leaderboard
                    resp += [mean, stdev, len(resp[2])]
                    del resp[2]  # Delete the raw vote column

                leaderboard = sorted(
                    leaderboard, key=lambda x: x[3])  # Sort by stdev ascending
                leaderboard = sorted(
                    leaderboard, reverse=True,
                    key=lambda x: x[2])  # Sort by score descending

                # Array for splitting up the message, yada yada
                post_leaderboard = [
                    f"🏆 **Round {self.info['GAME']['ROUND']} Results**\nThe prompt: `{self.info['GAME']['PROMPT']}`.\n\n"
                ]

                # Useful arrays to deal with eliminations and ranks
                elim_pings = []
                elim_players = []
                player_ranks = [""] * len(self.info["PLAYERS"])

                for r in range(len(leaderboard)):
                    player = int(leaderboard[r][0][2:-1])  # Get their id
                    # Add their placement to player_ranks, which will later be added to the database
                    player_ranks[self.info["PLAYERS"].index(
                        player)] = f"{r+1}/{len(leaderboard)}"

                    if leaderboard[r][
                            2] != "N/A":  # If average is a number, turn it into a percentile
                        leaderboard[r][2] = "{:.2%}".format(leaderboard[r][2])
                    if leaderboard[r][
                            3] != "N/A":  # If stdev is a number, turn it into a percentile
                        leaderboard[r][3] = "{:.2%}".format(leaderboard[r][3])
                    # Format the vote count and add proper grammar
                    leaderboard[r][4] = str(
                        leaderboard[r]
                        [4]) + " vote" + "s" if leaderboard[r][4] != 1 else ""

                    # Add brackets where necessary
                    line = [f'[{r+1}]'] + leaderboard[r][:2] + [
                        f'[{", ".join(leaderboard[r][2:])}]'
                    ]

                    # Determine the amount of contestants eliminated
                    elim = elim_prize(len(leaderboard),
                                      elim_rate=self.param["ELIM_RATE"])[0]
                    # Determine which circle emoji to use, based on if contestant is eliminated or not
                    emoji = "🟡" if r == 0 else (
                        "🟢" if len(leaderboard) - r > elim else "🔴")

                    line = emoji + " " + " - ".join(
                        line
                    ) + "\n"  # Join the leaderboard line together with separators

                    if len(
                            post_leaderboard[-1] + line
                    ) > 1950:  # Split into multiple messages if necessary
                        post_leaderboard.append("")
                    post_leaderboard[-1] += line

                    # Add separators on boundaries of win, safe and elim regions
                    if r == 0 or len(leaderboard) - r - 1 == elim or len(
                            leaderboard) - r == 1:
                        separator = f"`{'-' * 60}`\n"
                        if len(post_leaderboard[-1] + separator) > 1950:
                            post_leaderboard.append("")
                        post_leaderboard[-1] += separator

                    if emoji == "🔴":  # If the person got eliminated (red circle emoji), add them to the elim arrays
                        elim_pings.append(leaderboard[r][0])
                        elim_players.append(int(leaderboard[r][0][2:-1]))

                for z in post_leaderboard:  # Send leaderboard messages
                    await self.MMT_C.send(z)

                await self.MMT_C.send(
                    f"""❌ {grammar_list(elim_pings)} ha{'s' if len(elim_pings) == 1 else 've'} 
				been eliminated.""".replace("\n", "").replace("\t", ""))

                # Add each person's rankings to the database
                for p in range(len(self.info["PLAYERS"])):
                    player = self.info["PLAYERS"][p]  # `player` is an ID
                    rank_str = player_ranks[p]  # Their rank this round

                    # Search for the player in mmtstats
                    found = self.db.get_entries("mmtstats",
                                                columns=["id", "ranks"],
                                                conditions={"id": str(player)})

                    if len(
                            found
                    ) == 0:  # If they're not in mmtstats, add them with the new rank value
                        self.db.add_entry("mmtstats",
                                          entry=[player, rank_str, 0])

                    else:
                        # Add the new rank to their rank history
                        current_rank = found[0][1]
                        if self.info["GAME"]["ROUND"] == 1:
                            current_rank += "\t"
                        else:
                            current_rank += " "
                        current_rank += rank_str

                        # Update mmtstats with the new rank history
                        self.db.edit_entry("mmtstats",
                                           entry={"ranks": current_rank},
                                           conditions={"id": str(player)})

                # Remove players that were eliminated
                self.info["PLAYERS"] = [
                    x for x in self.info["PLAYERS"] if x not in elim_players
                ]

                if len(self.info["PLAYERS"]
                       ) == 1:  # If there's one player left, they win
                    winner = self.info['PLAYERS'][0]
                    await self.MMT_C.send(
                        f"🏆 <@{winner}> wins the MiniMiniTWOW!")
                    self.force_skip()  # Skip the host since the game ended

                    wins = self.db.get_entries("mmtstats",
                                               columns=["wins"],
                                               conditions={"id": str(winner)
                                                           })[0][0] + 1
                    # Increment the player's win count
                    self.db.edit_entry("mmtstats",
                                       entry={"wins": wins},
                                       conditions={"id": str(winner)})
                    return

                # Turn the period into prompt decision, incremenet the round, and prepare prompt and response variables
                self.info["GAME"]["PERIOD"] = 2
                self.info["GAME"]["ROUND"] += 1
                self.info["GAME"]["PERIOD_START"] = time.time()
                self.info["GAME"]["PROMPT"] = ""
                self.info["RESPONSES"] = [""] * len(self.info["PLAYERS"])
                return

            return

    # Change a parameter of the event
    async def edit_event(self, message, new_params):
        incorrect = []
        correct = []
        for parameter in new_params.keys():
            try:
                self.param[parameter] = new_params[parameter]
                correct.append(parameter)
            except KeyError:
                incorrect.append(parameter)

        if len(correct) > 0:
            await message.channel.send(
                f"Successfully changed the parameters: {grammar_list(correct)}"
            )
        if len(incorrect) > 0:
            await message.channel.send(
                f"The following parameters are invalid: {grammar_list(incorrect)}"
            )

        return
Exemple #14
0
        "USAGE":
        f"""Using `{PREFIX}typingtest` will prompt you to type a sequence of random common English words,
		and will report your speed and accuracy when you finish.""".replace(
            "\n", "").replace("\t", ""),
        "HIDE":
        1
    }


a = "tc/db add typingtest id-text totype-text start-text best-text"

PERMS = 1  # Members
ALIASES = ["TT"]
REQ = []

db = Database()
zws = "​"


async def MAIN(message, args, level, perms, SERVER):
    if level == 1:
        totype = " ".join(random.sample(WORDS, random.randrange(
            45, 61, 1)))  # creates list of words

        # adding zero-width spaces between words to prevent copy+paste
        spaced_text = ""
        for i in range(len(totype)):
            spaced_text += totype[i]
            if totype[i] == " ":
                spaced_text += zws
Exemple #15
0
async def MAIN(message, args, level, perms, SERVER):
	db = Database()

	id_search_key = str(SERVER["MAIN"].id)

	try:
		roles, contestants, contnames, emojis, limit = db.get_entries(
			"teamdata", columns=["roles", "contestants", "contname", "emojis", "teamlimit"],
			conditions={"server":id_search_key}
		)[0]
	except IndexError:
		await message.channel.send("There are no team flairs in this server!")
		return

	flair_list = False
	count = False
	if level == 1:
		flair_list = True
	elif args[1].lower() in ["teams", "list", "options"]:
		flair_list = True
	elif args[1].lower() == "count":
		flair_list = True
		count = True
	
	if flair_list:
		role_list = roles.split(" ")

		if contnames != "":
			cont_list = contnames.split(" / ")
		
		if emojis != "":
			emoji_list = emojis.split(" ")

		if count:
			output = [f"Here's the current team member standings.\n\n"]

			teams = []
			for t in range(len(role_list)):
				team_info = []
				
				try:
					team_info.append(emoji_list[t])
				except:
					pass

				try:
					team_info.append("**" + cont_list[t] + "** -")
				except:
					team_info.append("<@&" + role_list[t] + "> -")
				
				try:
					counter = len(discord.utils.get(SERVER["MAIN"].roles, id=int(role_list[t])).members)
				except:
					counter = 0
				
				team_info.append(counter)
				team_info.append("member" + ('' if counter == 1 else 's') + "\n")
			
				teams.append(team_info)
			
			teams = sorted(teams, reverse=True, key=lambda m: m[1])

			teams = [" ".join([str(a) for a in t]) for t in teams]

			for t in teams:
				if len(output[-1]) + len(t) > 1980:
					output.append("")
				
				output[-1] += t

		else:
			output = [f"Here's a list of available team flairs.\nYou can pick **{limit}** at most.\n\n"]

			for t in range(len(role_list)):
				line = ""

				try:
					line += emoji_list[t] + " - "
				except:
					pass

				try:
					line += "**" + cont_list[t] + "** - "
				except:
					pass

				line += "<@&" + role_list[t] + ">\n"

				if len(output[-1]) + len(line) > 1980:
					output.append("")
				
				output[-1] += line

		for m in output:
			await message.channel.send(m)
		return
	
	if args[1].lower() in ["get", "remove"]:
		if level == 2:
			await message.channel.send(f"Specify the team flair you want to {args[1].lower()}!")
			return
		
		cont_list = contnames.split(" / ")
		cont_id_list = contestants.split(" ")
		role_list = roles.split(" ")

		cont_list_l = [x.lower() for x in cont_list]

		key = " ".join(args[2:]).lower()

		found_first = False
		picked = []

		found_list = [x for x in cont_list_l if x.startswith(key)]

		if len(found_list) == 1:
			ind = cont_list_l.index(found_list[0])
			picked = [cont_list[ind], cont_id_list[ind], role_list[ind]]
		elif len(found_list) > 1:
			found_first = True
		
		if picked == []:
			role_name_list = [
				discord.utils.get(SERVER["MAIN"].roles, id=int(x)).name for x in role_list
				if discord.utils.get(SERVER["MAIN"].roles, id=int(x)) != None]

			found_list = [x for x in role_name_list if x.lower().startswith(key)]
			
			if len(found_list) == 1:
				ind = role_name_list.index(found_list[0])
				picked = [cont_list[ind], cont_id_list[ind], role_list[ind]]
			elif len(found_list) > 1:
				found_first = True
		
		if picked == []:
			if found_first:
				await message.channel.send(
					"There seem to be multiple flairs matching that search key. Please specify more!")
			else:
				await message.channel.send("I couldn't find a team flair matching that search key!")
			return

		author_member = SERVER["MAIN"].get_member(message.author.id)
		current_flair_list = [str(x.id) for x in author_member.roles if str(x.id) in role_list]

		if args[1].lower() == "get":
			if picked[1] == str(message.author.id):
				await message.channel.send("You can't pick your own team flair!")
				return
			
			if picked[2] in current_flair_list:
				await message.channel.send("You already have that team flair!")
				return
			
			if len(current_flair_list) >= limit:
				await message.channel.send(
					f"You already have {limit} flair{'' if limit==1 else 's'}. You can't get any more!")
				return
			
			team_role = discord.utils.get(SERVER["MAIN"].roles, id=int(picked[2]))
			await author_member.add_roles(team_role)
			await message.channel.send(f"You have been added to **{picked[0]}**'s team!")
			return
		
		if args[1].lower() == "remove":
			if picked[2] not in current_flair_list:
				await message.channel.send("You don't have that team flair!")
			
			team_role = discord.utils.get(SERVER["MAIN"].roles, id=int(picked[2]))
			await author_member.remove_roles(team_role)
			await message.channel.send(f"You are no longer on **{picked[0]}**'s team.")
			return
Exemple #16
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