async def __main__(self, CommandArgs): choice = CommandArgs.string_args and CommandArgs.string_args[0].lower() if choice not in ("view", "change", "reset"): parsed_args = await CommandArgs.prompt([ { "prompt": "Would you like to **view** your settings, **change** them, or " "**reset** all of your settings?\nIf you don't understand an option, " "then say **help**.\nValid choices: (change/view/reset/help)", "name": "choice", "type": "choice", "choices": ("change", "view", "reset", "help") } ]) choice = parsed_args["choice"] if choice in ("change", "reset"): if not CommandArgs.has_permission: raise PermissionError("You do not have the required permissions to change server settings.") if choice == "view": await self.view(CommandArgs) elif choice == "change": await self.change(CommandArgs) elif choice == "reset": await self.reset(CommandArgs) elif choice == "help": await self.help(CommandArgs)
async def __main__(self, CommandArgs): channel = CommandArgs.channel response = CommandArgs.response locale = CommandArgs.locale t_1 = time.perf_counter() if response.webhook_only: m = await response.send(locale("commands.ping.pinging")) else: try: await channel.trigger_typing() except NotFound: pass t_2 = time.perf_counter() time_delta = round((t_2 - t_1) * 1000) if response.webhook_only: try: await m.delete() except NotFound: pass except Forbidden: raise PermissionError( locale("permissions.oneError", permission="Manage Messages")) else: await response.send( locale("commands.ping.pong", time_delta=time_delta)) else: await response.send( locale("commands.ping.pong", time_delta=time_delta))
async def __main__(self, CommandArgs): response = CommandArgs.response author = CommandArgs.author guild = CommandArgs.guild guild_data = CommandArgs.guild_data new_prefix = CommandArgs.parsed_args.get("new_prefix") if new_prefix: if not CommandArgs.has_permission: raise PermissionError("You do not meet the required permissions for this command.") if RELEASE == "PRO": prefix_name = "proPrefix" else: prefix_name = "prefix" await self.r.table("guilds").insert({ "id": str(guild.id), prefix_name: new_prefix }, conflict="update").run() trello_board = CommandArgs.trello_board if trello_board: _, card = await get_prefix(guild=guild, trello_board=trello_board) if card: try: if card.name == prefix_name: await card.edit(desc=new_prefix) else: await card.edit(name=f"{prefix_name}:{new_prefix}") except TrelloUnauthorized: await response.error("In order for me to edit your Trello settings, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass else: await trello_board.sync(card_limit=TRELLO["CARD_LIMIT"], list_limit=TRELLO["LIST_LIMIT"]) await post_event(guild, guild_data, "configuration", f"{author.mention} ({author.id}) has **changed** the `prefix` option.", BROWN_COLOR) await set_guild_value(guild, prefix_name, new_prefix) await response.success("Your prefix was successfully changed!") else: old_prefix = CommandArgs.real_prefix await response.send(f"Your prefix used for Bloxlink: `{old_prefix}`.\n" "Change it with `@Bloxlink prefix <new prefix>`.")
async def restore(self, CommandArgs): """restore your Server Data""" message = CommandArgs.message author = CommandArgs.message.author guild = CommandArgs.message.guild response = CommandArgs.response prefix = CommandArgs.prefix if author.id == OWNER: if message.attachments: attachment = message.attachments[0] if not attachment.height: file_data = await attachment.read() json_data = file_data.decode("utf8").replace("'", '"') json_data = json.loads(json_data) json_data["id"] = str(guild.id) if json_data.get("roleBinds"): role_map = {} for bind_type, bind_data in json_data.get("roleBinds", {}).items(): if bind_type == "groups": for group_id, group_data in bind_data.items(): for rank, rank_data in group_data.get("binds", {}).items(): for role_id in rank_data.get("roles", []): if not guild.get_role(int(role_id)): role_map_find = role_map.get(role_id) if not role_map_find: role = await guild.create_role(name=rank*6) role_map[role_id] = str(role.id) role_map_find = str(role.id) json_data["roleBinds"]["groups"][group_id]["binds"][rank]["roles"].remove(role_id) json_data["roleBinds"]["groups"][group_id]["binds"][rank]["roles"].append(role_map_find) await self.r.table("guilds").insert(json_data, conflict="replace").run() return await response.success("Successfully **restored** this server's data.") else: raise Error("You must supply a non-image file for data restore.") user_data = await self.r.db("bloxlink").table("users").get(str(author.id)).run() or {} user_backups = user_data.get("backups", []) if not user_backups: raise Message(f"You don't have any backups created! You may create them with ``{prefix}data backup``.", type="silly") embed = Embed(title="Bloxlink Data Restore", description="Please select the backup you could like to restore with the reactions.") for i, backup in enumerate(user_backups): guild_data = backup["data"] backup_name = backup["backupName"] timestamp = datetime.datetime.fromtimestamp(backup["timestamp"]) trello_board = await get_board(guild_data=guild_data, guild=guild) prefix, _ = await get_prefix(guild=guild, trello_board=trello_board) backup["prefix"] = prefix backup["trello_board"] = trello_board, backup["timestamp"] = timestamp backup["nickname_template"] = guild_data.get("nicknameTemplate", DEFAULTS.get("nicknameTemplate")) if trello_board: trello_options, _ = await get_options(trello_board) guild_data.update(trello_options) len_role_binds = count_binds(guild_data) backup["len_role_binds"] = len_role_binds embed.add_field(name=f"{INT_REACTIONS[i]} {ARROW} {backup_name}", value="\n".join([ f"**Role Binds** {ARROW} {len_role_binds}", f"**Prefix** {ARROW} {prefix}", f"**Nickname Template** {ARROW} {backup['nickname_template']}", f"**Created on ** {timestamp.strftime('%b. %d, %Y (%A)')}" ])) message = await response.send(embed=embed) if message: response.delete(message) for i, _ in enumerate(user_backups): emote_string = INT_REACTIONS[i] try: await message.add_reaction(emote_string) except Forbidden: raise PermissionError("I'm missing permission to add reactions to your message!") try: reaction, _ = await Bloxlink.wait_for("reaction_add", timeout=PROMPT["PROMPT_TIMEOUT"], check=self._reaction_check(author)) except TimeoutError: raise CancelledPrompt(f"timeout ({PROMPT['PROMPT_TIMEOUT']}s)") else: chosen_backup = None str_reaction = str(reaction) for i, reaction_string in enumerate(INT_REACTIONS): if str_reaction == reaction_string: chosen_backup = user_backups[i] if chosen_backup: parsed_args = await CommandArgs.prompt([ { "prompt": "**Warning!** This will **__restore__ ALL OF YOUR SETTINGS** including:\n" f"**{chosen_backup['len_role_binds']}** Role Binds\n" f"**{chosen_backup['prefix']}** prefix\n" f"**{chosen_backup['nickname_template']}** Nickname Template\n" "Continue? ``Y/N``", "name": "confirm", "type": "choice", "formatting": False, "choices": ("yes", "no"), "embed_title": "Warning!", "embed_color": ORANGE_COLOR, "footer": "Say **yes** to continue, or **no** to cancel." } ]) if parsed_args["confirm"] == "yes": await self._restore(guild, chosen_backup) await response.success("Successfully **restored** your backup!") else: raise CancelledPrompt("cancelled restore")
async def reset(self, CommandArgs): """reset either ALL of your settings, or just your binds""" if not CommandArgs.has_permission: raise PermissionError("You do not have the required permissions to change server settings.") prefix = CommandArgs.prefix response = CommandArgs.response trello_board = CommandArgs.trello_board author = CommandArgs.message.author guild = CommandArgs.message.guild guild_data = CommandArgs.guild_data parsed_arg = (await CommandArgs.prompt([{ "prompt": f"Which setting would you like to clear? Valid choices: ``{RESET_CHOICES}``", "name": "choice", "type": "choice", "formatting": False, "choices": RESET_CHOICES }]))["choice"] if parsed_arg == "everything": cont = (await CommandArgs.prompt([{ "prompt": "**Warning!** This will clear **all of your settings** including binds, " f"saved group information, etc. You'll need to run ``{prefix}setup`` " "and set-up the bot again. Continue? ``Y/N``", "name": "continue", "choices": ("yes", "no"), "embed_title": "Warning!", "embed_color": ORANGE_COLOR, "type": "choice", "footer": "Say **yes** to clear all of your settings, or **no** to cancel." }]))["continue"] if cont == "no": raise CancelledPrompt await self.r.table("guilds").get(str(guild.id)).delete().run() if trello_board: trello_options, _ = await get_options(trello_board, return_cards=True) try: if trello_options: for option_name, option in trello_options.items(): await option[1].archive() trello_binds_list = await trello_board.get_list(lambda l: l.name == "Bloxlink Binds") if trello_binds_list: for card in await trello_binds_list.get_cards(limit=TRELLO["CARD_LIMIT"]): await card.archive() trello_binds_list.parsed_bind_data = None except TrelloUnauthorized: await response.error("In order for me to edit your Trello settings, please add ``@bloxlink`` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass else: await trello_board.sync(card_limit=TRELLO["CARD_LIMIT"], list_limit=TRELLO["LIST_LIMIT"]) await post_event(guild, guild_data, "configuration", f"{author.mention} ({author.id}) has **deleted** all server information.", BROWN_COLOR) await clear_guild_data(guild) raise Message("Your server information was successfully cleared.", type="success") elif parsed_arg == "binds": # delete all binds from db and trello cont = (await CommandArgs.prompt([{ "prompt": "**Warning!** This will clear **all of your binds**. You'll need to " f"run ``{prefix}bind`` to set up your binds again. Continue? ``Y/N``", "name": "continue", "choices": ("yes", "no"), "type": "choice", "embed_title": "Warning!", "embed_color": ORANGE_COLOR, "formatting": False, "footer": "Say **yes** to clear all of your binds, or **no** to cancel." }]))["continue"] if cont == "no": raise CancelledPrompt guild_data = CommandArgs.guild_data role_binds = guild_data.get("roleBinds", {}) if role_binds: role_ids = set() for group_id, group_data in role_binds.get("groups", {}).items(): for rank_id, rank_data in group_data.get("binds", {}).items(): for role_id in rank_data["roles"]: role_ids.add(int(role_id)) for range_data in group_data.get("ranges", []): if range_data["roles"]: for role_id in range_data["roles"]: role_ids.add(int(role_id)) if role_ids: delete_roles = (await CommandArgs.prompt([{ "prompt": "Would you like me to **delete these roles from your server as well?** If yes, " f"then this will delete **{len(role_ids)}** role(s). ``Y/N``", "name": "delete_roles", "choices": ("yes", "no"), "type": "choice", "embed_title": "Warning!", "embed_color": ORANGE_COLOR, "formatting": False, "footer": "Say **yes** to delete these roles, or **no** to cancel." }]))["delete_roles"] if delete_roles == "yes": for role_id in role_ids: role = guild.get_role(role_id) if role: try: await role.delete(reason=f"{author} chose to delete bound roles through {prefix}settings") except Forbidden: pass await response.success("Your bound roles were deleted.") guild_data.pop("roleBinds", None) guild_data.pop("groupIDs", None) await self.r.table("guilds").insert(guild_data, conflict="replace").run() if trello_board: try: trello_binds_list = await trello_board.get_list(lambda l: l.name == "Bloxlink Binds") if trello_binds_list: for card in await trello_binds_list.get_cards(limit=TRELLO["CARD_LIMIT"]): await card.archive() trello_binds_list.parsed_bind_data = None except TrelloUnauthorized: await response.error("In order for me to edit your Trello settings, please add ``@bloxlink`` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass else: await trello_board.sync(card_limit=TRELLO["CARD_LIMIT"], list_limit=TRELLO["LIST_LIMIT"]) await post_event(guild, guild_data, "configuration", f"{author.mention} ({author.id}) has **deleted** all binds.", BROWN_COLOR) await clear_guild_data(guild) raise Message("Successfully **cleared** all of your bound roles.", type="success")
async def change(self, CommandArgs): """change your Bloxlink settings""" if not CommandArgs.has_permission: raise PermissionError("You do not have the required permissions to change server settings.") prefix = CommandArgs.prefix response = CommandArgs.response message = CommandArgs.message author = CommandArgs.message.author guild = CommandArgs.message.guild guild_data = CommandArgs.guild_data parsed_args = await CommandArgs.prompt([{ "prompt": "What value would you like to change? Note that some settings you can't change " "from this command due to the extra complexity, but I will tell you the " f"appropriate command to use.\n\nOptions: ``{options_strings}``\n\nPremium-only options: ``{premium_options_strings}``", "name": "choice", "type": "choice", "formatting": False, "footer": f"Use ``{prefix}settings help`` to view a description of all choices.", "choices": options_combined }]) choice = parsed_args["choice"] if choice == "trelloID": raise Message(f"You can link your Trello board from ``{prefix}setup``!", type="success") elif choice == "Linked Groups": raise Message(f"You can link your group from ``{prefix}bind``!", type="success") elif choice == "joinDM": message.content = f"{prefix}joindm" return await parse_message(message) elif choice == "groupShoutChannel": message.content = f"{prefix}shoutproxy" return await parse_message(message) elif choice == "whiteLabel": message.content = f"{prefix}whitelabel" return await parse_message(message) option_find = OPTIONS.get(choice) if option_find: if option_find[3]: profile, _ = await get_features(Object(id=guild.owner_id), guild=guild) if not profile.features.get("premium"): raise Error("This option is premium-only! The server owner must have premium for it to be changed.\n" f"Use ``{prefix}donate`` for more instructions on getting premium.") option_type = option_find[1] trello_board = CommandArgs.trello_board card = success_text = parsed_value = None desc = option_find[4].format(prefix=CommandArgs.prefix, templates=NICKNAME_TEMPLATES) if trello_board: options_trello_data, trello_binds_list = await get_options(trello_board, return_cards=True) options_trello_find = options_trello_data.get(choice) if options_trello_find: card = options_trello_find[1] if option_type == "boolean": parsed_value = await CommandArgs.prompt([{ "prompt": f"Would you like to **enable** or **disable** ``{choice}``?\n\n" f"**Option description:**\n{desc}", "name": "choice", "type": "choice", "footer": "Say **clear** to set as the default value.", "formatting": False, "choices": ("enable", "disable", "clear") }]) parsed_bool_choice = parsed_value["choice"] if parsed_bool_choice == "clear": parsed_value = DEFAULTS.get(choice) else: parsed_value = parsed_bool_choice == "enable" await self.r.table("guilds").insert({ "id": str(guild.id), choice: parsed_value }, conflict="update").run() success_text = f"Successfully **{parsed_bool_choice}d** ``{choice}``!" elif option_type == "string": parsed_value = (await CommandArgs.prompt([{ "prompt": f"Please specify a new value for ``{choice}``.\n\n" f"**Option description:**\n{desc}", "name": "choice", "type": "string", "footer": "Say **clear** to set as the default value.", "formatting": False, "max": option_find[2] }]))["choice"] if parsed_value == "clear": parsed_value = DEFAULTS.get(choice) await self.r.table("guilds").insert({ "id": str(guild.id), choice: parsed_value }, conflict="update").run() success_text = f"Successfully saved your new {choice}!" elif option_type == "role": parsed_value = (await CommandArgs.prompt([{ "prompt": f"Please specify a role for ``{choice}``.\n\n" f"**Option description:**\n{desc}", "name": "role", "type": "role", "exceptions": ("clear",), "footer": "Say **clear** to set as the default value.", "formatting": False }]))["role"] if parsed_value == "clear": parsed_value = DEFAULTS.get(choice) else: parsed_value = str(parsed_value.id) await self.r.table("guilds").insert({ "id": str(guild.id), choice: parsed_value }, conflict="update").run() success_text = f"Successfully saved your new ``{choice}``!" elif option_type == "number": parsed_value = (await CommandArgs.prompt([{ "prompt": f"Please specify a new integer for ``{choice}``.\n\n" f"**Option description:**\n{desc}", "name": "choice", "type": "number", "footer": "Say **clear** to set as the default value.", "formatting": False, "exceptions": ("clear",), "max": option_find[2] }]))["choice"] if parsed_value == "clear": parsed_value = DEFAULTS.get(choice) await self.r.table("guilds").insert({ "id": str(guild.id), choice: parsed_value }, conflict="update").run() success_text = f"Successfully saved your new ``{choice}``!" elif option_type == "choice": choices = ", ".join(option_find[2]) parsed_value = (await CommandArgs.prompt([{ "prompt": f"Please pick a new value for ``{choice}``: ``{choices}``\n\n" f"**Option description:**\n{desc}", "name": "choice", "type": "choice", "footer": "Say **clear** to set as the default value.", "formatting": False, "exceptions": ["clear"], "choices": option_find[2] }]))["choice"] if parsed_value == "clear": parsed_value = DEFAULTS.get(choice) await self.r.table("guilds").insert({ "id": str(guild.id), choice: parsed_value }, conflict="update").run() success_text = f"Successfully saved your new ``{choice}``!" else: raise Error("An unknown type was specified.") else: raise Error("An unknown option was specified.") if trello_board: try: if card: if card.name == choice: await card.edit(desc=str(parsed_value)) else: await card.edit(name=f"{choice}:{parsed_value}") else: trello_settings_list = await trello_board.get_list(lambda L: L.name == "Bloxlink Settings") \ or await trello_board.create_list(name="Bloxlink Settings") await trello_settings_list.create_card(name=choice, desc=str(parsed_value)) if trello_binds_list: await trello_binds_list.sync(card_limit=TRELLO["CARD_LIMIT"]) except TrelloUnauthorized: await response.error("In order for me to edit your Trello settings, please add ``@bloxlink`` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass await set_guild_value(guild, choice, parsed_value) await post_event(guild, guild_data, "configuration", f"{author.mention} ({author.id}) has **changed** the ``{choice}`` option.", BROWN_COLOR) raise Message(success_text, type="success")
async def __main__(self, CommandArgs): guild = CommandArgs.guild response = CommandArgs.response guild_data = CommandArgs.guild_data trello_board = CommandArgs.trello_board prefix = CommandArgs.prefix author = CommandArgs.author locale = CommandArgs.locale role_binds_trello, group_ids_trello, trello_binds_list = await get_binds(guild=guild, trello_board=trello_board) bind_count = count_binds(guild_data, role_binds=role_binds_trello, group_ids=group_ids_trello) if bind_count >= FREE_BIND_COUNT: profile, _ = await get_features(Object(id=guild.owner_id), guild=guild) if not profile.features.get("premium"): raise Error(locale("commands.bind.errors.noPremiumBindLimitExceeded", prefix=prefix, free_bind_count=FREE_BIND_COUNT, prem_bind_count=PREM_BIND_COUNT)) if bind_count >= PREM_BIND_COUNT: raise Error(locale("commands.bind.errors.premiumBindLimitExceeded", prefix=prefix, prem_bind_count=PREM_BIND_COUNT)) parsed_args = await CommandArgs.prompt([ { "prompt": f"{locale('commands.bind.prompts.bindTypePrompt.line_1', arrow=ARROW)}\n" f"{locale('commands.bind.prompts.bindTypePrompt.line_2', arrow=ARROW)}\n" f"{locale('commands.bind.prompts.bindTypePrompt.line_3', arrow=ARROW)}\n" f"{locale('commands.bind.prompts.bindTypePrompt.line_4', arrow=ARROW)}\n" f"{locale('commands.bind.prompts.bindTypePrompt.line_5', arrow=ARROW)}", "name": "bind_choice", "type": "choice", "choices": locale("commands.bind.prompts.bindTypePrompt.choices"), "formatting": False }, { "prompt": locale("commands.bind.prompts.nicknamePrompt.line", prefix=prefix, nickname_templates=NICKNAME_TEMPLATES), "name": "nickname", "max": 100, "type": "string", "footer": locale("commands.bind.prompts.nicknamePrompt.footer"), "formatting": False } ]) bind_choice = parsed_args["bind_choice"].lower() nickname = parsed_args["nickname"] if trello_board: trello_binds_list = await trello_board.get_list(lambda l: l.name.lower() == "bloxlink binds") if not trello_binds_list: try: trello_binds_list = await trello_board.create_list(name="Bloxlink Binds") except TrelloUnauthorized: await response.error(locale("commands.bind.errors.trelloError")) except (TrelloNotFound, TrelloBadRequest): pass trello_card_binds, _ = await parse_trello_binds(trello_board=trello_board, trello_binds_list=trello_binds_list) else: trello_binds_list = None trello_group_bind = None trello_card_binds = { "groups": { "entire group": {}, "binds": {} }, "assets": {}, "badges": {}, "gamePasses": {} } if nickname.lower() in (locale("prompt.skip"), locale("prompt.done"), locale("prompt.next")): nickname = None nickname_lower = None else: nickname_lower = nickname.lower() if bind_choice == locale("commands.bind.group"): parsed_args_group = await CommandArgs.prompt([ { "prompt": locale("commands.bind.prompts.groupPrompt.line"), "name": "group", "validation": self.validate_group }, { "prompt": f"{locale('commands.bind.prompts.groupBindMode.line_1', arrow=ARROW)}\n" f"{locale('commands.bind.prompts.groupBindMode.line_2', arrow=ARROW)}\n" f"{locale('commands.bind.prompts.groupBindMode.line_3', arrow=ARROW)}", "name": "type", "type": "choice", "choices": locale("commands.bind.prompts.groupBindMode.choices") } ]) group = parsed_args_group["group"] group_id = group.group_id group_ids = guild_data.get("groupIDs", {}) found_group = trello_card_binds["groups"]["entire group"].get(group_id) or group_ids.get(group_id) trello_group_bind = trello_card_binds["groups"]["entire group"].get(group_id) if parsed_args_group["type"] == locale("commands.bind.entireGroup"): if found_group: if nickname and found_group["nickname"] != nickname: group_ids[group_id] = {"nickname": nickname, "groupName": group.name} guild_data["groupIDs"] = group_ids await self.r.table("guilds").insert(guild_data, conflict="update").run() trello_group_bind = trello_card_binds["groups"]["entire group"].get(group_id) make_trello_card = True if trello_group_bind and trello_group_bind["nickname"]: for card_data in trello_group_bind["trello"].get("cards", []): card = card_data["card"] try: await card.edit(desc=card.description.replace(trello_group_bind["nickname"], nickname)) except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass make_trello_card = False if make_trello_card: try: await trello_binds_list.create_card(name="Bloxlink Group Bind", desc=f"Group: {group_id}\nNickname: {nickname}") except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass if trello_binds_list: trello_binds_list.parsed_bind_data = None ending_s = group.name.endswith("s") and "'" or "'s" await post_event(guild, guild_data, "bind", f"{author.mention} ({author.id}) has **changed** `{group.name}`{ending_s} nickname template.", BLURPLE_COLOR) await clear_guild_data(guild) raise Message("Since your group is already linked, the nickname was updated.", type="success") else: raise Message("This group is already linked.", type="silly") for roleset in group.rolesets: roleset_name = roleset.get("name") roleset_rank = roleset.get("rank") if roleset_rank: discord_role = find(lambda r: r.name == roleset_name, guild.roles) if not discord_role: try: discord_role = await guild.create_role(name=roleset_name) except Forbidden: raise PermissionError("I was unable to create the Discord role. Please ensure my role has the `Manage Roles` permission.") # add group to guild_data.groupIDs group_ids[group_id] = {"nickname": nickname not in ("skip", "next") and nickname, "groupName": group.name} guild_data["groupIDs"] = group_ids await self.r.table("guilds").insert(guild_data, conflict="update").run() if trello_binds_list: try: await trello_binds_list.create_card(name="Bloxlink Group Bind", desc=f"Group: {group_id}\nNickname: {nickname}") except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass await post_event(guild, guild_data, "bind", f"{author.mention} ({author.id}) has **linked** group `{group.name}`.", BLURPLE_COLOR) await clear_guild_data(guild) raise Message("Success! Your group was successfully linked.", type="success") else: # select ranks from their group # ask if they want to auto-create the binds or select a specific role # shows confirmation embed with arrows from rank to discord role discord_role = await CommandArgs.prompt([ { "prompt": "Please provide **Discord role name(s)** for this bind, separated by commas.", "name": "role", "type": "role", "multiple": True, "max": 10 } ]) discord_roles = discord_role["role"] new_ranks = {"binds":[], "ranges": []} role_binds = guild_data.get("roleBinds") or {} if isinstance(role_binds, list): role_binds = role_binds[0] role_binds["groups"] = role_binds.get("groups") or {} # {"groups": {"ranges": {}, "binds": {}}} role_binds["groups"][group_id] = role_binds["groups"].get(group_id) or {} role_binds["groups"][group_id]["binds"] = role_binds["groups"][group_id].get("binds") or {} role_binds["groups"][group_id]["ranges"] = role_binds["groups"][group_id].get("ranges") or {} role_binds["groups"][group_id]["groupName"] = group.name rolesets_embed = Embed(title=f"{group.name} Rolesets", description="\n".join(f"**{x.get('name')}** {ARROW} {x.get('rank')}" for x in group.rolesets if x.get('rank'))) rolesets_embed = await CommandArgs.response.send(embed=rolesets_embed) response.delete(rolesets_embed) failures = 0 while True: if failures == 5: raise Error("Too many failed attempts. Please run this command again.") selected_ranks = await CommandArgs.prompt([ { "prompt": f"Please select the rolesets that should receive the role(s) **{', '.join([r.name for r in discord_roles])}**. " "You may specify the roleset name or ID. You may provide them in a list, " "or as a range. You may also say `everyone` to capture everyone in the group; " "and you can negate the number to catch everyone with the rank _and above._\n" "You can also say `guest` to include **all non-group members**.\n" "Example 1: `1,4,-6,VIP, 10, 50-100, Staff Members, 255`.\nExample 2: `" "-100` means everyone with rank 100 _and above._\nExample 3: `everyone` " "means everyone in the group.\n\n" "For your convenience, your Rolesets' names and IDs were sent above.", "name": "ranks", "formatting": False } ], last=True) pending_roleset_names = [] for rank in selected_ranks["ranks"].split(","): rank = rank.strip() if rank.isdigit(): new_ranks["binds"].append(str(rank)) elif rank in ("all", "everyone"): new_ranks["binds"].append("all") elif rank in ("0", "guest"): new_ranks["binds"].append("0") elif rank[:1] == "-": try: int(rank) except ValueError: pass else: new_ranks["binds"].append(rank) else: range_search = bind_num_range.search(rank) if range_search: num1, num2 = range_search.group(1), range_search.group(2) new_ranks["ranges"].append([num1, num2]) else: # they specified a roleset name as a string pending_roleset_names.append(rank) if pending_roleset_names: found = False for roleset in group.rolesets: roleset_name = roleset.get("name") roleset_rank = roleset.get("rank") if roleset_name in pending_roleset_names and roleset_name not in new_ranks["binds"]: new_ranks["binds"].append(str(roleset_rank)) found = True if not found: response.delete(await response.error("Could not find a matching Roleset name. Please try again.")) failures += 1 continue break if new_ranks["binds"]: for x in new_ranks["binds"]: rank = role_binds["groups"][group_id].get("binds", {}).get(x, {}) if not isinstance(rank, dict): rank = {"nickname": nickname_lower, "roles": [str(rank)]} for discord_role in discord_roles: role_id = str(discord_role.id) if role_id not in rank["roles"]: rank["roles"].append(role_id) else: for discord_role in discord_roles: role_id = str(discord_role.id) if role_id not in rank.get("roles", []): rank["roles"] = rank.get("roles") or [] rank["roles"].append(role_id) if nickname_lower: rank["nickname"] = nickname else: if not rank.get("nickname"): rank["nickname"] = None role_binds["groups"][group_id]["binds"][x] = rank # trello binds: # rank is in list of ranks # update nickname # append role # else: make new card if trello_binds_list: make_binds_card = True if trello_card_binds: trello_bind_group = trello_card_binds["groups"]["binds"].get(group_id, {}).get("binds") if trello_bind_group: card_data_ = trello_bind_group.get(x) if card_data_: for card in card_data_.get("trello", {}).get("cards", []): trello_card = card["card"] trello_ranks = card.get("ranks") or [] if (x in trello_ranks or x == "all") and len(trello_ranks) == 1: trello_bind_roles = card.get("roles", set()) card_bind_data = [ f"Group: {group_id}", f"Nickname: {(nickname != 'skip' and nickname) or rank.get('nickname') or card_data_.get('nickname') or 'None'}", ] for discord_role in discord_roles: trello_bind_roles.add(discord_role.name) card_bind_data.append(f"Roles: {', '.join(trello_bind_roles)}") card_bind_data.append(f"Ranks: {card['trello_str']['ranks']}") trello_card_desc = "\n".join(card_bind_data) if trello_card_desc != trello_card.description: trello_card.description = trello_card_desc try: await trello_card.edit(desc=trello_card_desc) except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass trello_binds_list.parsed_bind_data = None make_binds_card = False break if make_binds_card: card_bind_data = [ f"Group: {group_id}", f"Nickname: {nickname != 'skip' and nickname or 'None'}", f"Roles: {', '.join([r.name for r in discord_roles])}", ] if x != "all": card_bind_data.append(f"Ranks: {x}") trello_card_desc = "\n".join(card_bind_data) try: card = await trello_binds_list.create_card(name="Bloxlink Bind", desc=trello_card_desc) except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass trello_binds_list.parsed_bind_data = None if new_ranks["ranges"]: role_binds["groups"][group_id]["ranges"] = role_binds["groups"][group_id].get("ranges") or [] for x in new_ranks["ranges"]: # list of dictionaries: [{"high": 10, "low": 1, "nickname": ""},...] range_, num = self.find_range(x, role_binds["groups"][group_id]["ranges"]) found = bool(range_) for discord_role in discord_roles: role_id = str(discord_role.id) if not role_id in range_.get("roles", []): range_["roles"] = range_.get("roles") or [] range_["roles"].append(role_id) if nickname_lower: range_["nickname"] = nickname else: if not range_.get("nickname"): range_["nickname"] = None if found: role_binds["groups"][group_id]["ranges"][num] = range_ else: range_["low"] = int(x[0]) range_["high"] = int(x[1]) role_binds["groups"][group_id]["ranges"].append(range_) if trello_binds_list: make_binds_card = True if trello_card_binds: trello_range_group = trello_card_binds["groups"]["binds"].get(group_id, {}).get("ranges") if trello_range_group: for trello_range in trello_range_group: trello_data = trello_range["trello"] for card in trello_data.get("cards", []): trello_card = card["card"] trello_ranks = card.get("ranks", []) if trello_range["low"] == range_["low"] and trello_range["high"] == range_["high"] and len(trello_ranks) == 1: trello_data = trello_range["trello"] trello_bind_roles = trello_range.get("roles", set()) card_bind_data = [ f"Group: {group_id}", f"Nickname: {(nickname != 'skip' and nickname) or trello_range.get('nickname') or 'None'}", ] for discord_role in discord_roles: trello_bind_roles.add(discord_role.name) card_bind_data.append(f"Roles: {', '.join(trello_bind_roles)}") card_bind_data.append(f"Ranks: {card['trello_str']['ranks']}") trello_card_desc = "\n".join(card_bind_data) if trello_card_desc != trello_card.description: trello_card.description = trello_card_desc try: await trello_card.edit(desc=trello_card_desc) except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass trello_binds_list.parsed_bind_data = None make_binds_card = False break if make_binds_card: card_bind_data = [ f"Group: {group_id}", f"Nickname: {nickname != 'skip' and nickname or 'None'}", f"Roles: {', '.join([r.name for r in discord_roles])}", f"Ranks: {range_['low']}-{range_['high']}" ] trello_card_desc = "\n".join(card_bind_data) try: card = await trello_binds_list.create_card(name="Bloxlink Range Bind", desc=trello_card_desc) except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass trello_binds_list.parsed_bind_data = None await self.r.table("guilds").insert({ "id": str(guild.id), "roleBinds": role_binds }, conflict="update").run() text = ["Successfully **bound** rank ID(s): `"] if new_ranks["binds"]: text.append(", ".join(new_ranks["binds"])) if new_ranks["ranges"]: text2 = "" if new_ranks["binds"]: text2 = "; " text.append(f"{text2}ranges: {', '.join([r[0] + ' - ' + r[1] for r in new_ranks['ranges']])}") text.append(f"` with Discord role(s) **{', '.join([r.name for r in discord_roles])}**.") text = "".join(text) await post_event(guild, guild_data, "bind", f"{author.mention} ({author.id}) has **bound** group `{group.name}`.", BLURPLE_COLOR) await clear_guild_data(guild) await response.success(text) elif bind_choice in ("asset", "badge", "gamepass"): if bind_choice == "gamepass": bind_choice_title = "GamePass" bind_choice_plural = "gamePasses" else: bind_choice_title = bind_choice.title() bind_choice_plural = f"{bind_choice}s" vg_parsed_args = await CommandArgs.prompt([ { "prompt": f"Please provide the **{bind_choice_title} ID** to use for this bind.", "name": "bind_id", "type": "number", "formatting": False }, { "prompt": "Please provide **Discord role name(s)** for this bind, separated by commas.", "name": "role", "type": "role", "multiple": True, "max": 10 }, ], last=True) discord_roles = vg_parsed_args["role"] bind_id = str(vg_parsed_args["bind_id"]) if bind_choice == "asset": try: text, response_ = await fetch(f"{API_URL}/marketplace/productinfo?assetId={bind_id}") except RobloxNotFound: raise Error(f"An Asset with ID `{bind_id}` does not exist.") json_data = await response_.json() display_name = json_data.get("Name") elif bind_choice == "badge": try: text, response_ = await fetch(f"https://badges.roblox.com/v1/badges/{bind_id}") except RobloxNotFound: raise Error(f"A Badge with ID `{bind_id}` does not exist.") json_data = await response_.json() display_name = json_data.get("displayName") elif bind_choice == "gamepass": bind_choice_title = "GamePass" bind_choice_plural = "gamePasses" try: text, response_ = await fetch(f"http://api.roblox.com/marketplace/game-pass-product-info?gamePassId={bind_id}") except (RobloxNotFound, RobloxAPIError): raise Error(f"A GamePass with ID `{bind_id}` does not exist.") json_data = await response_.json() if json_data.get("ProductType") != "Game Pass": raise Error(f"A GamePass with ID `{bind_id}` does not exist.") display_name = json_data.get("Name") role_binds = guild_data.get("roleBinds") or {} if isinstance(role_binds, list): role_binds = role_binds[0] role_binds[bind_choice_plural] = role_binds.get(bind_choice_plural) or {} role_binds[bind_choice_plural][bind_id] = role_binds[bind_choice_plural].get(bind_id) or {} role_binds[bind_choice_plural][bind_id]["nickname"] = nickname role_binds[bind_choice_plural][bind_id]["displayName"] = display_name role_binds[bind_choice_plural][bind_id]["roles"] = role_binds[bind_choice_plural][bind_id].get("roles", []) roles = role_binds[bind_choice_plural][bind_id]["roles"] for discord_role in discord_roles: role_id = str(discord_role.id) if not role_id in roles: roles.append(role_id) role_binds[bind_choice_plural][bind_id]["roles"] = roles if trello_binds_list: make_binds_card = True if trello_card_binds: trello_bind_vg = trello_card_binds.get(bind_choice_plural, {}).get(bind_id) if trello_bind_vg: trello_bind_roles = set(trello_bind_vg.get("roles", set())) for card in trello_bind_vg.get("trello", {})["cards"]: trello_card = card["card"] card_bind_data = [ f"{bind_choice_title} ID: {bind_id}", f"Display Name: {display_name}", f"Nickname: {(nickname != 'skip' and nickname) or trello_bind_vg.get('nickname') or 'None'}", ] for discord_role in discord_roles: trello_bind_roles.add(discord_role.name) card_bind_data.append(f"Roles: {', '.join(trello_bind_roles)}") trello_card_desc = "\n".join(card_bind_data) if trello_card_desc != trello_card.description: trello_card.description = trello_card_desc try: await trello_card.edit(desc=trello_card_desc) except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass trello_binds_list.parsed_bind_data = None make_binds_card = False break if make_binds_card: card_bind_data = [ f"{bind_choice_title} ID: {bind_id}", f"Display Name: {display_name}", f"Nickname: {nickname != 'skip' and nickname or 'None'}", f"Roles: {', '.join([d.name for d in discord_roles])}", ] trello_card_desc = "\n".join(card_bind_data) try: card = await trello_binds_list.create_card(name=f"Bloxlink {bind_choice_title} Bind", desc=trello_card_desc) except TrelloUnauthorized: await response.error("In order for me to edit your Trello binds, please add `@bloxlink` to your " "Trello board.") except (TrelloNotFound, TrelloBadRequest): pass trello_binds_list.parsed_bind_data = None await self.r.table("guilds").insert({ "id": str(guild.id), "roleBinds": role_binds }, conflict="update").run() await post_event(guild, guild_data, "bind", f"{author.mention} ({author.id}) has **bound** {bind_choice_title} `{display_name}`.", BLURPLE_COLOR) await clear_guild_data(guild) await response.success(f"Successfully **bound** {bind_choice_title} `{display_name}` ({bind_id}) with Discord role(s) **{', '.join([r.name for r in discord_roles])}!**")
async def __main__(self, CommandArgs): response = CommandArgs.response users_ = CommandArgs.parsed_args["users"] prefix = CommandArgs.prefix message = CommandArgs.message author = message.author guild = message.guild guild_data = CommandArgs.guild_data users = [] if not users_: message.content = f"{prefix}getrole" return await parse_message(message) if not CommandArgs.has_permission: if users_[0] == author: message.content = f"{prefix}getrole" return await parse_message(message) else: raise PermissionError( "You do not have permission to update arbitrary users or roles!" ) if isinstance(users_[0], Role): if not guild.chunked: await guild.chunk() for role in users_: users += role.members if not users: raise Error("These role(s) have no members in it!") else: users = users_ len_users = len(users) if self.redis: redis_cooldown_key = self.REDIS_COOLDOWN_KEY.format( release=RELEASE, id=guild.id) on_cooldown = await self.redis.get(redis_cooldown_key) if len_users > 3 and on_cooldown: cooldown_time = math.ceil( await self.redis.ttl(redis_cooldown_key) / 60) if not cooldown_time or cooldown_time == -1: await self.redis.delete(redis_cooldown_key) on_cooldown = None if on_cooldown: if on_cooldown == 1: raise Message(f"This server is still queued.") elif on_cooldown == 2: raise Message( "This server's scan is currently running.") elif on_cooldown == 3: cooldown_time = math.ceil( await self.redis.ttl(redis_cooldown_key) / 60) raise Message( f"This server has an ongoing cooldown! You must wait **{cooldown_time}** more minutes." ) donator_profile, _ = await get_features(Object(id=guild.owner_id), guild=guild) premium = donator_profile.features.get("premium") if not premium: donator_profile, _ = await get_features(author) premium = donator_profile.features.get("premium") cooldown = 0 if len_users > 10: if not premium: raise Error( "You need premium in order to update more than 10 members at a time! " f"Use ``{prefix}donate`` for instructions on donating." ) if len_users >= 100: cooldown = math.ceil(((len_users / 1000) * 120) * 60) else: cooldown = 120 if self.redis: await self.redis.set(redis_cooldown_key, 2, ex=86400) trello_board = CommandArgs.trello_board #trello_binds_list = trello_board and await trello_board.get_list(lambda l: l.name.lower() == "bloxlink binds") #async with response.loading(): if len_users > 1: for user in users: if not user.bot: try: added, removed, nickname, errors, roblox_user = await guild_obligations( user, guild=guild, guild_data=guild_data, trello_board=trello_board, roles=True, nickname=True, dm=False, exceptions=("BloxlinkBypass", "UserNotVerified", "Blacklisted", "PermissionError"), cache=False) except BloxlinkBypass: if len_users <= 10: await response.info( f"{user.mention} **bypassed**") except UserNotVerified: if len_users <= 10: await response.send( f"{REACTIONS['ERROR']} {user.mention} is **not linked to Bloxlink**" ) except PermissionError as e: raise Error(e) except Blacklisted as b: if len_users <= 10: await response.send( f"{REACTIONS['ERROR']} {user.mention} has an active restriction." ) else: if len_users <= 10: await response.send( f"{REACTIONS['DONE']} **Updated** {user.mention}" ) else: user = users[0] if user.bot: raise Message("Bots can't have Roblox accounts!", type="silly") old_nickname = user.display_name try: added, removed, nickname, errors, roblox_user = await guild_obligations( user, guild=guild, guild_data=guild_data, trello_board=trello_board, roles=True, nickname=True, cache=False, dm=False, event=True, exceptions=("BloxlinkBypass", "Blacklisted", "CancelCommand", "UserNotVerified", "PermissionError")) _, embed = await format_update_embed( roblox_user, user, added=added, removed=removed, errors=errors, nickname=nickname if old_nickname != user.display_name else None, prefix=prefix, guild_data=guild_data) if embed: await response.send(embed=embed) else: await response.success( "This user is all up-to-date; no changes were made." ) except BloxlinkBypass: raise Message( "Since you have the ``Bloxlink Bypass`` role, I was unable to update your roles/nickname.", type="info") except Blacklisted as b: if str(b): raise Error( f"{user.mention} has an active restriction for: ``{b}``" ) else: raise Error( f"{user.mention} has an active restriction from Bloxlink." ) except CancelCommand: pass except UserNotVerified: raise Error("This user is not linked to Bloxlink.") except PermissionError as e: raise Error(e) if cooldown: await self.redis.set(redis_cooldown_key, 3, ex=cooldown) if len_users > 10: await response.success("All users updated.")
async def __main__(self, CommandArgs): response = CommandArgs.response guild_data = CommandArgs.guild_data prefix = CommandArgs.prefix guild = CommandArgs.message.guild try: category = find(lambda c: c.name == "Verification", guild.categories) or \ await guild.create_category("Verification") verify_info = find(lambda t: t.name == "verify-instructions", category.channels) or \ await guild.create_text_channel("verify-instructions", category=category) verify_channel = find(lambda t: t.name == "verify", category.channels) or \ await guild.create_text_channel("verify", category=category) sample_channel = await guild.create_text_channel("sample-channel") except Forbidden: raise PermissionError("I was unable to create the necessary channels. Please ensure I have the " "``Manage Channels`` permission.") except HTTPException: raise Error("You have too many channels or categories! Please delete some before continuing.") try: await verify_info.send("This server uses Bloxlink to manage Roblox verification. In " "order to unlock all the features of this server, you'll need " "to verify your Roblox account with your Discord account!\n\nTo " f"do this, run ``{prefix}verify`` in {verify_channel.mention} and follow the instructions.") await sample_channel.send("This is a sample channel that only Verified users " \ "can read. This channel is not important, you may freely delete it.\n" \ "To create another sample channel, right click this channel and click 'Clone " \ "Text Channel', or just run this command again.") except (Forbidden, NotFound): raise PermissionError("I was unable to send messages to the created channels. Please give me the " "proper permissions.") try: await verify_info.set_permissions(guild.me, send_messages=True, read_messages=True) verified_role_name = guild_data.get("verifiedRoleName", DEFAULTS.get("verifiedRoleName")) for role in guild.roles: if role.name != guild.me.name: await verify_info.set_permissions(role, send_messages=False, read_messages=True) await verify_channel.set_permissions(role, send_messages=True, read_messages=True) if role.name == verified_role_name: for target, overwrite in sample_channel.overwrites.items(): await sample_channel.set_permissions(target, overwrite=None) await sample_channel.set_permissions(guild.default_role, send_messages=False, read_messages=False) await sample_channel.set_permissions(role, send_messages=True, read_messages=True) except Forbidden: raise PermissionError("Unable to set permissions to the channels. Please ensure I have the " "``Manage Channels`` and ``Manage Roles`` permission.") except NotFound: raise Error("Please do not delete the created channels while I'm setting them up...") await self.r.table("guilds").insert({ "id": str(guild.id), "verifyChannel": str(verify_channel.id) }, conflict="update").run() await response.success(f"All done! Your new verification channel is {verify_channel.mention} and " \ "is now managed by Bloxlink.")
async def __main__(self, CommandArgs): guild = CommandArgs.guild response = CommandArgs.response author = CommandArgs.author locale = CommandArgs.locale role_binds_trello, group_ids_trello = await get_binds(guild=guild) bind_count = await count_binds(guild) if bind_count >= FREE_BIND_COUNT: if not "premium" in (await has_premium(guild=guild)).features: raise Error( locale("commands.bind.errors.noPremiumBindLimitExceeded", free_bind_count=FREE_BIND_COUNT, prem_bind_count=PREM_BIND_COUNT)) if bind_count >= PREM_BIND_COUNT: raise Error( locale("commands.bind.errors.premiumBindLimitExceeded", prem_bind_count=PREM_BIND_COUNT)) parsed_args = await CommandArgs.prompt([{ "prompt": f"{locale('commands.bind.prompts.bindTypePrompt.line_1', arrow=ARROW)}\n", "name": "bind_choice", "type": "choice", "components": [ discord.ui.Select( max_values=1, options=[ discord.SelectOption( label="Group", description="Users of your group will get a role." ), discord.SelectOption( label="Asset", description="Users need to own this catalog item." ), discord.SelectOption( label="Badge", description="Users need to own this badge."), discord.SelectOption( label="GamePass", description="Users need to own this GamePass."), discord.SelectOption( label="DevForum Members", description= "Users need to be a member of the DevForum."), discord.SelectOption( label="Roblox Staff", description="Users need to be Roblox Staff members." ), ]) ], "choices": locale("commands.bind.prompts.bindTypePrompt.choices"), "formatting": False }, { "prompt": locale("commands.bind.prompts.nicknamePrompt.line", nickname_templates=NICKNAME_TEMPLATES), "name": "nickname", "max": 100, "type": "string", "footer": locale("commands.bind.prompts.nicknamePrompt.footer"), "formatting": False }, { "prompt": "Should any **additional** roles be **removed from the user** if they meet the bind conditions? You can specify multiple roles.\n\n" "Note that this is an **advanced option**, so you most likely should `skip` this. Bloxlink will already remove \"old\" Group roles.\n\n" "This option really exists if there are non-group roles that another bot in your server gives.", "name": "remove_roles", "multiple": True, "type": "role", "max": 10, "exceptions": ("skip", ), "footer": "Say **skip** to skip this option." }]) bind_choice = parsed_args["bind_choice"][0].lower() nickname = parsed_args["nickname"] if "display-name" in nickname: display_name_confirm = (await CommandArgs.prompt([{ "prompt": "**Warning!** You chose Display Names for your Nickname Template.\n" "Display Names **aren't unique** and can **lead to impersonation.** Are you sure you want to use this? yes/no", "type": "choice", "components": [ discord.ui.Select(max_values=1, options=[ discord.SelectOption(label="Yes"), discord.SelectOption(label="No"), ]) ], "choices": ("yes", "no"), "name": "confirm", "embed_title": "Display Names Confirmation", "embed_color": BROWN_COLOR, "formatting": False }]))["confirm"][0] if display_name_confirm == "no": raise CancelledPrompt remove_roles = [str(r.id) for r in parsed_args["remove_roles"] ] if parsed_args["remove_roles"] != "skip" else [] if nickname.lower() in (locale("prompt.skip"), locale("prompt.done"), locale("prompt.next")): nickname = None nickname_lower = None else: nickname_lower = nickname.lower() if bind_choice == locale("commands.bind.group"): parsed_args_group = await CommandArgs.prompt([{ "prompt": locale("commands.bind.prompts.groupPrompt.line"), "name": "group", "validation": self.validate_group }, { "prompt": f"{locale('commands.bind.prompts.groupBindMode.line_1', arrow=ARROW)}\n" f"{locale('commands.bind.prompts.groupBindMode.line_2', arrow=ARROW)}\n" f"{locale('commands.bind.prompts.groupBindMode.line_3', arrow=ARROW)}", "name": "type", "components": [ discord.ui.Select( max_values=1, options=[ discord.SelectOption( label="Link my entire group", description= "Roleset names must match with Discord role names." ), discord.SelectOption( label="Select specific rolesets", description= "You can choose how the roles are called."), ]) ], "type": "choice", "choices": ("link my entire group", "select specific rolesets") }]) group = parsed_args_group["group"] group_id = group.group_id group_ids = await get_guild_value(guild, "groupIDs") or {} found_group = group_ids.get(group_id) if parsed_args_group["type"][0] == "link my entire group": if found_group: if (nickname and found_group["nickname"] != nickname) or ( sorted(remove_roles) != sorted( found_group.get("removeRoles", []))): group_ids[group_id] = { "nickname": nickname, "groupName": group.name, "removeRoles": remove_roles } await set_guild_value(group, groupIDs=group_ids) ending_s = group.name.endswith("s") and "'" or "'s" await post_event( guild, "bind", f"{author.mention} ({author.id}) has **changed** `{group.name}`{ending_s} nickname template.", BLURPLE_COLOR) raise Message( "Since your group is already linked, the nickname was updated.", type="success") else: raise Message("This group is already linked!", type="silly") for _, roleset_data in group.rolesets.items(): discord_role = discord.utils.find( lambda r: r.name == roleset_data[0], guild.roles) if not discord_role: try: discord_role = await guild.create_role( name=roleset_data[0]) except discord.errors.Forbidden: raise PermissionError( "I was unable to create the Discord role. Please ensure my role has the `Manage Roles` permission." ) # add group to guild_data.groupIDs group_ids[group_id] = { "nickname": nickname not in ("skip", "next") and nickname, "groupName": group.name, "removeRoles": remove_roles } await set_guild_value(guild, groupIDs=group_ids) await post_event( guild, "bind", f"{author.mention} ({author.id}) has **linked** group `{group.name}`.", BLURPLE_COLOR) raise Message("Success! Your group was successfully linked.", type="success") else: # select ranks from their group # ask if they want to auto-create the binds or select a specific role # shows confirmation embed with arrows from rank to discord role discord_role = await CommandArgs.prompt([{ "prompt": "Please provide **Discord role name(s)** for this bind, separated by commas.", "name": "role", "type": "role", "multiple": True, "max": 10 }]) discord_roles = discord_role["role"] new_ranks = {"binds": [], "ranges": []} role_binds = await get_guild_value(guild, "roleBinds") or {} if isinstance(role_binds, list): role_binds = role_binds[0] role_binds["groups"] = role_binds.get("groups") or { } # {"groups": {"ranges": {}, "binds": {}}} role_binds["groups"][group_id] = role_binds["groups"].get( group_id) or {} role_binds["groups"][group_id]["binds"] = role_binds["groups"][ group_id].get("binds") or {} role_binds["groups"][group_id]["ranges"] = role_binds[ "groups"][group_id].get("ranges") or {} role_binds["groups"][group_id]["groupName"] = group.name role_binds["groups"][group_id]["removeRoles"] = remove_roles rolesets_embed = discord.Embed( title=f"{group.name} Rolesets", description="\n".join(f"**{x[0]}** {ARROW} {x[1]}" for x in group.rolesets.values())) rolesets_embed = await CommandArgs.response.send( embed=rolesets_embed) response.delete(rolesets_embed) failures = 0 while True: if failures == 5: raise Error( "Too many failed attempts. Please run this command again." ) selected_ranks = await CommandArgs.prompt([{ "prompt": f"Please select the rolesets that should receive the role(s) **{', '.join([r.name for r in discord_roles])}**. " "You may specify the roleset name or ID. You may provide them in a list, " "or as a range. You may also say `everyone` to capture everyone in the group; " "and you can negate the number to catch everyone with the rank _and above._\n" "You can also say `guest` to include **all non-group members**.\n\n" "Example 1: `1,4,-6,VIP, 10, 50-100, Staff Members, 255`.\nExample 2: `" "-100` means everyone with rank 100 _and above._\nExample 3: `everyone` " "means everyone in the group.\n\n" "For your convenience, your Rolesets' names and IDs were sent above.", "name": "ranks", "formatting": False }], last=True) for rank in selected_ranks["ranks"].split(","): rank = rank.strip() if rank.isdigit() and rank != "0": if 1 <= int(rank) <= 255: new_ranks["binds"].append(str(rank)) else: response.delete(await response.error( "Ranks must be an integer between [1-255]" )) failures += 1 break elif rank in ("all", "everyone"): new_ranks["binds"].append("all") elif rank in ("0", "guest"): new_ranks["binds"].append("0") elif rank[:1] == "-": try: int(rank) except ValueError: pass else: new_ranks["binds"].append(rank) else: range_search = bind_num_range.search(rank) if range_search: num1, num2 = range_search.group( 1), range_search.group(2) if (1 <= int(num1) <= 255) and (1 <= int(num2) <= 255): new_ranks["ranges"].append([num1, num2]) else: response.delete(await response.error( "Ranges must be between [1-255].")) failures += 1 break else: # they specified a roleset name as a string roleset_find = group.rolesets.get(rank.lower()) if roleset_find: new_ranks["binds"].append( str(roleset_find[1])) else: response.delete(await response.error( "Could not find a matching Roleset name. Please try again." )) failures += 1 break else: break if new_ranks["binds"]: for x in new_ranks["binds"]: rank = role_binds["groups"][group_id].get("binds", {}).get( x, {}) if not isinstance(rank, dict): rank = { "nickname": nickname_lower, "roles": [str(rank)], "removeRoles": remove_roles } for discord_role in discord_roles: role_id = str(discord_role.id) if role_id not in rank["roles"]: rank["roles"].append(role_id) else: for discord_role in discord_roles: role_id = str(discord_role.id) if role_id not in rank.get("roles", []): rank["roles"] = rank.get("roles") or [] rank["roles"].append(role_id) if nickname_lower: rank["nickname"] = nickname else: if not rank.get("nickname"): rank["nickname"] = None rank["removeRoles"] = remove_roles role_binds["groups"][group_id]["binds"][x] = rank if new_ranks["ranges"]: role_binds["groups"][group_id]["ranges"] = role_binds[ "groups"][group_id].get("ranges") or [] for x in new_ranks[ "ranges"]: # list of dictionaries: [{"high": 10, "low": 1, "nickname": ""},...] range_, num = self.find_range( x, role_binds["groups"][group_id]["ranges"]) found = bool(range_) for discord_role in discord_roles: role_id = str(discord_role.id) if not role_id in range_.get("roles", []): range_["roles"] = range_.get("roles") or [] range_["roles"].append(role_id) if nickname_lower: range_["nickname"] = nickname else: if not range_.get("nickname"): range_["nickname"] = None range_["removeRoles"] = remove_roles if found: role_binds["groups"][group_id]["ranges"][ num] = range_ else: range_["low"] = int(x[0]) range_["high"] = int(x[1]) role_binds["groups"][group_id]["ranges"].append( range_) await set_guild_value(guild, roleBinds=role_binds) text = ["Successfully **bound** rank ID(s): `"] if new_ranks["binds"]: text.append(", ".join(new_ranks["binds"])) if new_ranks["ranges"]: text2 = "" if new_ranks["binds"]: text2 = "; " text.append( f"{text2}ranges: {', '.join([r[0] + ' - ' + r[1] for r in new_ranks['ranges']])}" ) text.append( f"` with Discord role(s) **{', '.join([r.name for r in discord_roles])}**." ) text = "".join(text) await post_event( guild, "bind", f"{author.mention} ({author.id}) has **bound** group `{group.name}`.", BLURPLE_COLOR) await response.success(text) else: no_id_bind = False if bind_choice == "gamepass": bind_choice_title = "GamePass" bind_choice_plural = "gamePasses" elif bind_choice == "devforum members": bind_choice_title = "DevForum Members" bind_choice_plural = "devForum" no_id_bind = True elif bind_choice == "roblox staff": bind_choice_title = "Roblox Staff" bind_choice_plural = "robloxStaff" no_id_bind = True else: bind_choice_title = bind_choice.title() bind_choice_plural = f"{bind_choice}s" if not no_id_bind: vg_parsed_args_1 = await CommandArgs.prompt([{ "prompt": f"Please provide the **{bind_choice_title} ID** to use for this bind.", "name": "bind_id", "type": "number", "formatting": False }]) vg_parsed_args_2 = await CommandArgs.prompt([ { "prompt": "Please provide **Discord role name(s)** for this bind, separated by commas.", "name": "role", "type": "role", "multiple": True, "max": 10 }, ], last=True) discord_roles = vg_parsed_args_2["role"] bind_id = str( vg_parsed_args_1["bind_id"]) if not no_id_bind else None display_name = None if bind_choice == "asset": try: json_data, response_ = await fetch( f"{API_URL}/marketplace/productinfo?assetId={bind_id}", json=True) except (RobloxNotFound, RobloxAPIError): raise Error( f"An Asset with ID `{bind_id}` does not exist.") display_name = json_data.get("Name") elif bind_choice == "badge": try: json_data, response_ = await fetch( f"https://badges.roblox.com/v1/badges/{bind_id}", json=True) except (RobloxNotFound, RobloxAPIError): raise Error(f"A Badge with ID `{bind_id}` does not exist.") display_name = json_data.get("displayName") elif bind_choice == "gamepass": bind_choice_title = "GamePass" bind_choice_plural = "gamePasses" try: json_data, response_ = await fetch( f"http://api.roblox.com/marketplace/game-pass-product-info?gamePassId={bind_id}", json=True) except (RobloxNotFound, RobloxAPIError): raise Error( f"A GamePass with ID `{bind_id}` does not exist.") if json_data.get("ProductType") != "Game Pass": raise Error( f"A GamePass with ID `{bind_id}` does not exist.") display_name = json_data.get("Name") role_binds = await get_guild_value(guild, "roleBinds") or {} if isinstance(role_binds, list): role_binds = role_binds[0] role_binds[bind_choice_plural] = role_binds.get( bind_choice_plural) or {} if bind_id: role_binds[bind_choice_plural][bind_id] = role_binds[ bind_choice_plural].get(bind_id) or {} data_point = role_binds[bind_choice_plural][bind_id] else: role_binds[bind_choice_plural] = role_binds.get( bind_choice_plural) or {} data_point = role_binds[bind_choice_plural] data_point["nickname"] = nickname data_point["displayName"] = display_name data_point["removeRoles"] = remove_roles data_point["roles"] = data_point.get("roles", []) roles = data_point["roles"] for discord_role in discord_roles: role_id = str(discord_role.id) if not role_id in roles: roles.append(role_id) await set_guild_value(guild, roleBinds=role_binds) if display_name: await post_event( guild, "bind", f"{author.mention} ({author.id}) has **bound** {bind_choice_title} `{display_name}`.", BLURPLE_COLOR) await response.success( f"Successfully **bound** {bind_choice_title} `{display_name}` ({bind_id}) with Discord role(s) **{', '.join([r.name for r in discord_roles])}!**" ) else: await post_event( guild, "bind", f"{author.mention} ({author.id}) has **bound** {bind_choice_title}.", BLURPLE_COLOR) await response.success( f"Successfully **bound** {bind_choice_title} with Discord role(s) **{', '.join([r.name for r in discord_roles])}!**" )