async def MAIN(message, args, level, perms, SERVER): if isinstance(message.channel, discord.DMChannel ): # Everyone should be able to see button presses await message.channel.send("This command cannot be used in DMs!") return db = Database() if level == 1: # `tc/button` will check the current button try: # "public.bigredbutton" always has a single entry unless there has never been a button. If len(button_info) # is 0, that's the case, and this will throw an error caught by the try except block. button_info = db.get_entries("bigredbutton", columns=["button", "info"])[0] except IndexError: button_info = db.get_entries("bigredbutton", columns=["button", "info"]) if len(button_info) == 0: # If there is no button, create one button_number = 1 # It'd be the first ever button serial_number = key_generator(random.randrange( 8, 15)) # Generate serial exploding_chance = random.randrange( 15, 51) # 15 to 50% chance of exploding inspector = number_key(3) # Factory inspector code db.add_entry( "bigredbutton", [1, f"{serial_number} {exploding_chance} {inspector}", "", ""]) # Insert the new button info. Now there is a button, so skip to [*] elif button_info[ 1] == "PRESSED": # This is the 3 second interval after a button is pressed and before return # the outcome is announced. Ignore button checks for this period elif len(button_info[1].split(" ")) == 1: # [#] # Button, when in cooldown, has a single code for its info column, representing when it was pressed and # if it blew up or not. This code, unlike the normal `serial exp_chance` information, has no space in # it. This section detects that code pressing_time = int( button_info[1][2:]) # Time at which the button was pressed if button_info[1].startswith( "0-"): # `0-time` means button didn't blow up left = int((pressing_time + 15) - time.time()) # Time left for button to come back if left < 0: # If it's negative, the button should've returned by now but didn't # So generate the button on the spot button_number = button_info[ 0] + 1 # Increment button number serial_number = key_generator(random.randrange( 8, 15)) # Generate serial exploding_chance = random.randrange( 15, 51) # 15 to 50% chance of exploding inspector = number_key(3) # Factory inspector code n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) # Update with the new button else: # If it's not negative, and the timer hasn't ended, report the amount of time remaining await message.channel.send( f"The new button is currently being prepared! {left}s remain!" ) return if button_info[1].startswith( "1-"): # `1-time` means the button blew up left = int((pressing_time + 300) - time.time()) # Time left for button to come back if left < 0: # Read above about the timer being negative button_number = button_info[0] + 1 serial_number = key_generator(random.randrange(8, 15)) exploding_chance = random.randrange(15, 51) inspector = number_key(3) n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) # Update with new button else: # Report the actual timer if it hasn't ended yet mn = int(left / 60) # Extract minutes sc = left % 60 # Extract seconds await message.channel.send( f"The new button is being reconstructed. {mn}min {sc}s remain!" ) return else: # If there's a button and that's it button_number = button_info[0] # Report the button number... serial_number = button_info[1].split(" ")[0] # Its serial... exploding_chance = button_info[1].split(" ")[ 1] # ...Its explosion chance inspector = button_info[1].split(" ")[2] # And its inspector # [*] Report the current button await message.channel.send( f"""<:bigredbutton:654042578617892893> This is **Big Red Button #{button_number}** It has a **{exploding_chance}%** chance of exploding. The serial number is `{serial_number}`. It was inspected and approved by Factory Inspector #{inspector}. Use `tc/bigredbutton press` to press this button!""".replace("\t", "")) return if args[1].lower() == "top": # Points leaderboard unformatted_points = db.get_entries("bigredbutton", columns=["points"])[0] unformatted_points = [ x.split("-") for x in unformatted_points[0].split(" ") ] # unformatted_points might include empty strings from how the points value is formatted points = [] # This variable will be the clean version for x in unformatted_points: try: x[0] = int(x[0]) x[1] = int(x[1]) points.append( x) # If it passed the int tests, append it to points except ValueError: # If it fails to convert to integers, it's an empty string and is ignored continue points = sorted(points, reverse=True, key=lambda x: x[1]) # Sort the leaderboard player_info = [x for x in points if x[0] == message.author.id][0] player_ind = points.index(player_info) # args[2] is the page number. if level == 2: # If it's not specified, assume it's the first page points = points[:10] page = 1 elif not is_whole( args[2] ): # If it's not a valid integer, assume it's first page also points = points[:10] page = 1 elif (int(args[2]) - 1) * 10 >= len(points): # Detect if the page number is too big await message.channel.send( f"There is no page {args[2]} on Big Red Button!") return else: # This means the user specified a valid page number lower = (int(args[2]) - 1) * 10 upper = int(args[2]) * 10 points = points[lower:upper] page = int(args[2]) # Top of the message beginning = f"```diff\n---⭐ Big Red Button Points Leaderboard Page {page} ⭐---\n" beginning += "\n Rank | Name | Points\n" for person in points: r = points.index(person) + 1 + (page - 1) * 10 if r == 1: # + if the person is first line = f"+ {r}{' ' * (4 - len(str(r)))}| " else: # - otherwise line = f"- {r}{' ' * (4 - len(str(r)))}| " try: # Try to gather a username from the ID member = SERVER["MAIN"].get_member(int(person[0])).name except: # If you can't, just display the ID member = str(person[0]) line += f"{member[:20]}{' ' * (22 - len(member[:20]))}| " # Trim usernames to 20 characters long line += str(person[1]) + "\n" # Add points value and newline beginning += line # Add this line to the final message beginning += f"\nYour rank is {player_ind+1}, with {player_info[1]} points." beginning += "```" # Close off code block await message.channel.send(beginning) return if args[1].lower() == "press": # Press the button! button_info = db.get_entries("bigredbutton")[0] # This segment is almost an exact repeat of [#] up above if len(button_info[1].split(" ")) == 1 and button_info[1] != "PRESSED": pressing_time = int(button_info[1][2:]) if button_info[1].startswith("0-"): left = int((pressing_time + 15) - time.time()) if left < 0: button_number = button_info[0] + 1 serial_number = key_generator(random.randrange(8, 15)) exploding_chance = random.randrange(15, 51) inspector = number_key(3) n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) await message.channel.send( f"""Big Red Button #{button_number} has arrived from inspection by Factory Inspector #{inspector}, now with a {exploding_chance}% chance to explode and a serial number of `{serial_number}`!""".replace("\n", "").replace("\t", "")) return else: await message.channel.send( f"The new button is currently being prepared! {left}s remain!" ) return if button_info[1].startswith("1-"): left = int((pressing_time + 300) - time.time()) if left < 0: button_number = button_info[0] + 1 serial_number = key_generator(random.randrange(8, 15)) exploding_chance = random.randrange(15, 51) inspector = number_key(3) n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) await message.channel.send( f"""Big Red Button #{button_number} has arrived from inspection by Factory Inspector #{inspector}, now with a {exploding_chance}% chance to explode and a serial number of `{serial_number}`!""".replace("\n", "").replace("\t", "")) return else: mn = int(left / 60) sc = left % 60 await message.channel.send( f"The new button is being reconstructed. {mn}min {sc}s remain!" ) return # We already checked button_info[1], check two others now button_number = button_info[0] incapacitated = button_info[2] if str(message.author.id) in incapacitated: # If you're incapacitated incapacitated = incapacitated.split(" ") ind = 0 # Find the author in the incapacitated list and extract their explosion time for incap in incapacitated: if incap.split("-")[0] == str(message.author.id): ind = incapacitated.index(incap) explosion_t = int(incap.split("-")[1]) break # Calculate how long it'll be before they can recover delta = (explosion_t + 21600) - time.time() if delta < 0: # If it's negative, then they already recovered and can go on del incapacitated[ind] # Delete the entry for this person incapacitated = " ".join(incapacitated) # Join the list # Update with the new incapacitated list db.edit_entry("bigredbutton", entry={"incapacitated": incapacitated}) else: # If it's not negative, they still have to wait a little abs_delta = [ int(delta), # Seconds int(delta / 60), # Minutes int(delta / (60 * 60)) ] # Hours sc = abs_delta[0] % 60 mn = abs_delta[1] % 60 hr = abs_delta[2] await message.channel.send( f"You are still incapacitated! Wait {hr}h {mn}min {sc}s to press again." ) return if strip_alpha(message.author.name) == "": return # Don't try to cheese it by having no letters if button_info[ 1] == "PRESSED": # If it's currently being pressed, ignore this press return else: # Mark this button as being pressed so nobody else presses it during the 3 second interval db.edit_entry("bigredbutton", entry={"info": "PRESSED"}) # Gather serial_number and exploding_chance for calculations serial_number = button_info[1].split(" ")[0] exploding_chance = int(button_info[1].split(" ")[1]) inspector = list(button_info[1].split(" ")[2]) new_chance = exploding_chance # Clean slate variable if str(message.author.id)[-1] in serial_number: new_chance *= 0.67 # If last digit of ID is in serial... if strip_alpha(message.author.name)[0].upper() in serial_number: new_chance *= 2 # If first letter of username is in serial... point_retention = 0.5 share_count = 0 disc = list(str(message.author.discriminator)) for x in range(len(list(inspector))): if inspector[x] in disc: share_count += 1 disc.remove(inspector[x]) inspector[x] = "-" if share_count == 2: point_retention = 0.75 if share_count == 3: point_retention = 0.9 seed = random.uniform( 0, 100) # Has to be above the explosion chance otherwise it explodes await message.channel.send( f"**{message.author.name}** presses the button, and...") await asyncio.sleep(3) # Suspense! if seed <= new_chance: # If it's under the explosion chance, it blows up n_button_info = f"1-{int(time.time())}" # Remember, `1-time` is the explosion flag new_inc = f" {message.author.id}-{n_button_info[2:]}" # Add this person to the incapacitated list points = button_info[3].split(" ") # Get the points list new_points = 0 ind = -1 # Find the player in the points list and halve their point count for player in points: if player.split("-")[0] == str(message.author.id): ind = points.index(player) new_points = int( int(player.split("-")[1]) * point_retention) points[ind] = f"{message.author.id}-{new_points}" if ind == -1: # If ind is still -1, then the player wasn't found in the points list so create a new points.append(f"{message.author.id}-{new_points}" ) # entry for them with 0 points points = " ".join(points) db.edit_entry("bigredbutton", entry={ "info": n_button_info, "points": points, "incapacitated": incapacitated + new_inc }) # Update with the explosion info, the new incapacitated list, and the new points list await message.channel.send( f"""<:bigredbutton:654042578617892893> ***The #{button_number} Big Red Button blew up!*** <@{message.author.id}> has been incapacitated. Their point total is now **{new_points}**. They cannot press any more buttons for 6 hours. The button is broken. It'll take **5 minutes** to rebuild it.""".replace( "\t", "")) await asyncio.sleep(300) # Five minutes until the button is back else: # If seed > new_chance, it doesn't blow up points = button_info[3].split(" ") # Get points list to add points n_button_info = f"0-{int(time.time())}" # `0-time` means no explosion ind = -1 # Find player in points list and add the new points for player in points: if player.split("-")[0] == str(message.author.id): ind = points.index(player) # Note: the points they gain is ALWAYS the nominal value for the exploding chance, not the # modified serial number chance that was used to calculate explosions new_points = int(player.split("-")[1]) + exploding_chance points[ind] = f"{message.author.id}-{new_points}" if ind == -1: # If they're not in the points list, add them with the new point value points.append(f"{message.author.id}-{exploding_chance}") points = " ".join(points) db.edit_entry("bigredbutton", entry={ "info": n_button_info, "points": points }) # Update with the pressing info and the new points list await message.channel.send(f""" <:bigredbutton:654042578617892893> The #{button_number} Big Red Button did nothing. <@{message.author.id}> gained {exploding_chance} points. Another button arrives in **15 seconds**. """.replace("\t", "")) await asyncio.sleep(15) # Fifteen seconds until the button is back # Generate new serial_number and exploding_chance button_number += 1 serial_number = key_generator(random.randrange(8, 15)) exploding_chance = random.randrange(15, 51) inspector = number_key(3) n_info = f"{serial_number} {exploding_chance} {inspector}" db.edit_entry("bigredbutton", entry={ "button": button_number, "info": n_info }) # Update table with the new button # Announce the new button await message.channel.send( f"""Big Red Button #{button_number} has arrived from inspection by Factory Inspector #{inspector}, now with a {exploding_chance}% chance to explode and a serial number of `{serial_number}`!""".replace("\n", "").replace("\t", "")) return
def operation_check(block): matching_ops = [] # List of operations that match for possible_op in FUNCTIONS.keys(): # Check through each operation letter_expect = False op_regex = possible_op # Prepare a variable to serve as the regex for whether or not the code matches # this operation for param in range(possible_op.count( "?")): # Each ? is a parameter of the function alph = ALPHABET.lower()[param] types = FUNCTIONS[possible_op]["TYPES"][ alph] # Find out what type is expected for that parameter if "STRING" in types or "BOOLEAN" in types or "ARRAY STRING" in types or "ARRAY BOOLEAN" in types: letter_expect = True dot_exp = r"([^\^!-&\*-\/<-@_]{1,})" if possible_op == "out{?}": dot_exp = r"([^\^]{1,})" elif types == ["NUMBER"] or types == ["INTEGER"] or types == [ "ARRAY NUMBER" ]: dot_exp = r"([^\\A-z]{1,})" '''if len(strip_alpha(possible_op)) == 0: dot_exp = r"([^\^\\!-\/<-@]{1,})" elif len(types) == 0: dot_exp = r"([^\\]{1,})" elif types == ["NUMBER"] or types == ["INTEGER"] or types == ["ARRAY NUMBER"]: # If it's expecting a number of some kind, the parameter shouldn't have any letters in it, and so we # can use a regex that does not count letter characters to detect this parameter dot_exp = r"([^A-z]{1,})"''' # If any type is allowed (len(types) == 0) or the type is BOOLEAN or STRING (which contain letters) # Use the regex that matches all characters except for backslashes op_regex = op_regex.replace( "?", dot_exp, 1 ) # Replace the _ placeholder with the matching group for that # parameter match = re.search( op_regex, block ) # Try to match this operation (the previously generated regex) with the code if match: # If it's a match, append it to the matching operations array if possible_op.count("?") == 2 and "\\" == match.group(1)[-1]: continue if not possible_op.startswith("?") and match.span( )[0] != 0 and block[match.span()[0] - 1] == "\\": continue matching_ops.append([possible_op, match, letter_expect]) if "out{?}" in matching_ops or "out{" in block: # If the operation is out{}, leave a flag for it. It'll be handled substring = block[block.find("out{") + 4:block.find("}")] return ["out", f"({substring})", False] # in parenthesis_parser if len(matching_ops ) == 0: # If no operations match the code, return it unchanged return [False, block, False] letter_operations = [x for x in matching_ops if strip_alpha(x[0]) != "" ] # Operations that don't use letters are if len(letter_operations) == 0 and strip_alpha(block) != "": if True not in [x[2] for x in matching_ops]: return [False, block, False] if len(letter_operations) == 0 and len( matching_ops ) != 0: # all either math operations or other operations that # can be used with eachother. If this statement is composed entirely of them, then we can treat it differently operators = [[ x[0].replace("?", "").replace("\\", ""), FUNCTIONS[x[0]]["PRIORITY"], x[0] ] for x in matching_ops] full_token_list = [] current_index = 0 omit_bracket = False while True: operator_indices = [] for op in operators: found = block.find(op[0], current_index) if found != -1: operator_indices.append(found) else: operator_indices.append(len(block)) min_op = min(operator_indices) now_op = operators[operator_indices.index(min_op)][0] if min_op == len(block): full_token_list.append(block[current_index:]) break to_add = block[current_index:min_op] full_token_list.append(to_add) full_token_list.append(now_op) current_index = min_op + len(now_op) #full_token_list.append(block[current_index:]) #operator_order = sorted(operator_order, key=lambda o: (5 - o[1])) priority_limit = max([x[1] for x in operators]) op_list = [x[0] for x in operators] for p in range(priority_limit + 1): current_priority = priority_limit - p for token in full_token_list: if token in op_list: ind = op_list.index(token) if operators[ind][1] != current_priority: continue token_ind = full_token_list.index(token) operation_name = operators[ind][2] param_info = FUNCTIONS[operation_name]["TYPES"] params = [] params_ind = [] if len(param_info.keys()) != 1: for r in range(token_ind): search_ind = token_ind - r - 1 if full_token_list[search_ind] != []: c_param = full_token_list[search_ind] param_name = "a" expected_types = param_info[ param_name] # The allowed types for param fit_type = "" # The type this parameter will fit under for expect in expected_types: if expect == "NUMBER": if is_whole(c_param): c_param = int(c_param) fit_type = expect break if is_float(c_param): c_param = float(c_param) fit_type = expect break if expect == "INTEGER": if is_whole(c_param): c_param = int(c_param) fit_type = expect break if expect == "BOOLEAN": if c_param in ["True", "False"]: c_param = bool(c_param) fit_type = expect break if expect.startswith("ARRAY"): if c_param.startswith( "[") and c_param.endswith("]"): c_param = array_to_list(c_param) fit_type = expect break if expect == "STRING": if (not is_float(c_param) and not is_whole(c_param) and c_param not in ["True", "False"]): fit_type = expect break if fit_type == "" and len(expected_types) > 0: #return [True, [operation_name, param_name, c_param], True] return [True, block, True] params_ind.append(search_ind) params.append(c_param) break for search_ind in range(token_ind + 1, len(full_token_list)): if full_token_list[search_ind] != []: c_param = full_token_list[search_ind] param_name = "a" if len( param_info.keys()) == 1 else "b" expected_types = param_info[param_name] fit_type = "" # The type this parameter will fit under for expect in expected_types: if expect == "NUMBER": if is_whole(c_param): c_param = int(c_param) fit_type = expect break if is_float(c_param): c_param = float(c_param) fit_type = expect break if expect == "INTEGER": if is_whole(c_param): c_param = int(c_param) fit_type = expect break if expect == "BOOLEAN": if c_param in ["True", "False"]: c_param = bool(c_param) fit_type = expect break if expect.startswith("ARRAY"): if c_param.startswith( "[") and c_param.endswith("]"): c_param = array_to_list(c_param) fit_type = expect break if expect == "STRING": if (not is_float(c_param) and not is_whole(c_param) and c_param not in ["True", "False"]): fit_type = expect break if fit_type == "" and len(expected_types) > 0: #return [True, [operation_name, param_name, c_param], True] return [True, block, True] params_ind.append(search_ind) params.append(c_param) break result = FUNCTIONS[operation_name]["MAIN"](*params) if type(result) is list: result = list_to_array(result) full_token_list[token_ind] = str(result) for x in params_ind: full_token_list[x] = [] full_token_list = [x for x in full_token_list if x != []] full_token_list = [x for x in full_token_list if x.strip() != ""] return [True, full_token_list[0], False] # There was an operation and no error. Pass the result # Just in case there's more than 1 matching operation, run this sorted command. It takes the span of the operation # match (the start and end indices for when the operation applies), and subtracts the start index from end index # to determine how long the span is. Put the one with the longest span first matching_ops = sorted(matching_ops, reverse=True, key=(lambda m: len(m[0]))) matching_ops = sorted(matching_ops, reverse=True, key=(lambda m: m[1].span()[1] - m[1].span()[0])) # Assume it's the one with the longest span. This prevents subsets of operations being counted instead of the operation, match, lt = matching_ops[ 0] # actual operation specified. Define the operation and the match object parameters = [ ] # List of parameters that will be used in the operation function for g in range(operation.count("?")): # For each parameter necessary... param = match.group(g + 1).strip( ) # Match the group of the parameter (group 0 is the entire block; start at 1) param_name = ALPHABET.lower( )[g] # Get the parameter name (which is just the corresponding letter in alphabet) expected_types = FUNCTIONS[operation]["TYPES"][ param_name] # The allowed types for this parameter fit_type = "" # The type this parameter will fit under for expect in expected_types: if expect == "NUMBER": # NUMBER can be both int and float if is_whole( param): # Detect if it can be interpreted as an int param = int(param) # If so, make it an int fit_type = expect break if is_float( param): # Detect if it can be interpreted as a float param = float(param) # If so, make it a float fit_type = expect break if expect == "INTEGER": # INTEGER is exclusively int if is_whole(param): # Detect if it can be an int param = int(param) # If so, make it an int fit_type = expect break if expect == "BOOLEAN": # BOOLEAN can only be True or False if param in ["True", "False"]: # Detect if it's one of those param = bool(param) # If so, turn it into a bool fit_type = expect break if expect.startswith("ARRAY"): # ARRAYs are defined with brackets if param.startswith("[") and param.endswith("]"): param = array_to_list(param) fit_type = expect break if expect == "STRING": # STRING can be anything that isn't instantly defined as one of the others if not is_float(param) and not is_whole( param) and param not in ["True", "False" ]: # Detect that it's # not any of the other types. param is already a str, so it doesn't need to be made into one fit_type = expect break if fit_type == "" and len( expected_types ) > 0: # If there ARE expected types but the parameter can't fit them #return [True, [operation_name, param_name, c_param], True] return [True, block, True] # There IS an operation but there is an error. # Return the operation and param name for error specification parameters.append( param ) # If everything went alright, add param to the parameter list result = FUNCTIONS[operation]["MAIN"]( *parameters) # Extract a result from the operation function return [True, result, False] # There was an operation and no error. Pass the result