async def __main__(self, CommandArgs): target = CommandArgs.parsed_args["target"] or CommandArgs.message.author flags = CommandArgs.flags guild = CommandArgs.message.guild response = CommandArgs.response prefix = CommandArgs.prefix if target.bot: raise Message("Bots can't have Roblox accounts!", type="silly") valid_flags = ["username", "id", "avatar", "premium", "badges", "groups", "description", "age", "banned"] if not all(f in valid_flags for f in flags.keys()): raise Error(f"Invalid flag! Valid flags are: ``{', '.join(valid_flags)}``") #async with response.loading(): if guild: role_binds, group_ids, _ = await get_binds(guild_data=CommandArgs.guild_data, trello_board=CommandArgs.trello_board) else: role_binds, group_ids = {}, {} try: account, accounts = await get_user(*flags.keys(), author=target, guild=guild, group_ids=(group_ids, role_binds), send_embed=True, response=response, everything=not bool(flags), basic_details=not bool(flags)) except UserNotVerified: raise Error(f"**{target}** is not linked to Bloxlink.") else: if not account: raise Message(f"You have no primary account set! Please use ``{prefix}switchuser`` and set one.", type="silly")
async def __main__(self, CommandArgs): response = CommandArgs.response action = CommandArgs.parsed_args["action"] guild = CommandArgs.guild author = CommandArgs.author locale = CommandArgs.locale guild_data = CommandArgs.guild_data prefix = CommandArgs.prefix if self.redis: redis_cooldown_key = self.REDIS_COOLDOWN_KEY.format(release=RELEASE, id=guild.id) on_cooldown = await self.cache.get(redis_cooldown_key) if 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(locale("scans.queued")) elif on_cooldown == 2: raise Message(locale("scans.running")) elif on_cooldown == 3: cooldown_time = math.ceil(await self.redis.ttl(redis_cooldown_key)/60) raise Message(locale("scans.cooldown", cooldown=cooldown_time)) await self.redis.set(redis_cooldown_key, 1, ex=86400) await self.process_guild(guild_data, guild, author, action, response, locale, prefix)
async def __main__(self, CommandArgs): author = CommandArgs.author guild = CommandArgs.guild transfer_to = CommandArgs.parsed_args.get("user") response = CommandArgs.response prefix = CommandArgs.prefix if transfer_to.bot: raise Message("You cannot transfer your premium to bots!", type="confused") author_data = await self.r.db("bloxlink").table("users").get(str(author.id)).run() or {"id": str(author.id)} time_now = time.time() author_premium_data = author_data.get("premium", {}) transfer_cooldown = author_premium_data.get("transferCooldown") or 0 on_cooldown = transfer_cooldown > time_now if on_cooldown: days_left = math.ceil((transfer_cooldown - time_now)/86400) raise Message(f"You recently transferred your premium! You may transfer again in **{days_left}** day{days_left > 1 and 's' or ''}.", type="silly") if author_premium_data.get("transferTo"): raise Message(f"You are currently transferring your premium to another user! Please disable it with `{prefix}transfer " "disable` first.", type="info") elif author_premium_data.get("transferFrom"): raise Message("You may not transfer premium that someone else transferred to you. You must first revoke the transfer " f"with `{prefix}transfer disable`.", type="confused") prem_data, _ = await get_features(author, author_data=author_data, cache=False, rec=False, partner_check=False) if not prem_data.features.get("premium"): raise Error("You must have premium in order to transfer it!") recipient_data = await self.r.db("bloxlink").table("users").get(str(transfer_to.id)).run() or {} recipient_data_premium = recipient_data.get("premium", {}) if recipient_data_premium.get("transferFrom"): raise Error(f"Another user is already forwarding their premium to this user. The recipient must run `{prefix}transfer disable` " "to revoke the external transfer.") await CommandArgs.prompt([{ "prompt": f"Are you sure you want to transfer your premium to **{transfer_to}**?\n" "You will not be able transfer again for **5** days! We also do __not__ " "remove cool-downs for __any reason at all.__\n\nYou will be " f"able to cancel the transfer at anytime with `{prefix}transfer disable`.", "footer": "Please say **yes** to complete the transfer.", "type": "choice", "choices": ("yes",), "name": "_", "embed_title": "Premium Transfer Confirmation", "embed_color": BROWN_COLOR, "formatting": False }]) await transfer_premium(transfer_from=author, transfer_to=transfer_to, guild=guild, apply_cooldown=True) await response.success(f"Successfully **transferred** your premium to **{transfer_to}!**")
async def disable(self, CommandArgs): """disable your Bloxlink premium transfer""" author = CommandArgs.author guild = CommandArgs.guild response = CommandArgs.response author_data = await self.r.db("bloxlink").table("users").get(str(author.id)).run() or {"id": str(author.id)} author_data_premium = author_data.get("premium", {}) transfer_to = author_data_premium.get("transferTo") if not transfer_to: transfer_from = author_data_premium.get("transferFrom") if not transfer_from: raise Message("You've not received a premium transfer!", type="confused") # clear original transferee and recipient data transferee_data = await self.r.db("bloxlink").table("users").get(transfer_from).run() or {"id": transfer_from} transferee_data_premium = transferee_data.get("premium", {}) author_data_premium["transferFrom"] = None transferee_data_premium["transferTo"] = None author_data["premium"] = author_data_premium transferee_data["premium"] = transferee_data_premium await self.r.db("bloxlink").table("users").insert(author_data, conflict="update").run() await self.r.db("bloxlink").table("users").insert(transferee_data, conflict="update").run() await cache_pop(f"premium_cache:{author.id}") await cache_pop(f"premium_cache:{transfer_from}") await cache_pop(f"premium_cache:{guild.id}") raise Message("Successfully **disabled** the premium transfer!", type="success") else: recipient_data = await self.r.db("bloxlink").table("users").get(transfer_to).run() or {"id": str(transfer_to)} recipient_data_premium = recipient_data.get("premium", {}) author_data_premium["transferTo"] = None recipient_data_premium["transferFrom"] = None author_data["premium"] = author_data_premium recipient_data["premium"] = recipient_data_premium await self.r.db("bloxlink").table("users").insert(author_data, conflict="update").run() await self.r.db("bloxlink").table("users").insert(recipient_data, conflict="update").run() await cache_pop(f"premium_cache:{author.id}") await cache_pop(f"premium_cache:{transfer_to}") await cache_pop(f"premium_cache:{guild.id}") await response.success("Successfully **disabled** your premium transfer!")
async def __main__(self, CommandArgs): response = CommandArgs.response guild = CommandArgs.guild author = CommandArgs.author roblox_info = CommandArgs.parsed_args["roblox_name"] discord_user = CommandArgs.parsed_args["discord_user"] roblox_id = roblox_name = None if roblox_info and discord_user: raise Message( "Please only specify a Roblox username OR a Discord user!", type="silly") elif not (roblox_info or discord_user): discord_user = CommandArgs.author if roblox_info: roblox_id = self.autocomplete_regex.search(roblox_info) if not roblox_id: roblox_name = roblox_info else: roblox_id = roblox_id.group(1) try: roblox_user, _ = await get_user(user=discord_user, roblox_id=roblox_id, roblox_name=roblox_name, guild=guild, cache=True, includes=True) except RobloxNotFound: raise Error("This Roblox account doesn't exist.") except RobloxAPIError: raise Error( "The Roblox API appears to be down so I was unable to retrieve the information. Please try again later." ) except UserNotVerified: raise Error("This user is not linked to Bloxlink!") else: author_accounts = await get_accounts(author) card = Card(discord_user or author, author, author_accounts, roblox_user, "getinfo", guild, from_interaction=True) await card() card.response = response message = await response.send(files=[card.front_card_file], view=card.view) card.message = message card.view.message = message
async def __main__(self, ExtensionArgs): user = ExtensionArgs.resolved guild = ExtensionArgs.guild guild_data = ExtensionArgs.guild_data response = ExtensionArgs.response prefix = ExtensionArgs.prefix if user.bot: raise Error("Bots cannot have Roblox accounts!", hidden=True) if guild: role_binds, group_ids, _ = await get_binds(guild_data=guild_data) else: role_binds, group_ids = {}, {} try: account, accounts = await get_user(author=user, guild=guild, group_ids=(group_ids, role_binds), send_embed=True, send_ephemeral=True, response=response, everything=True) except UserNotVerified: raise Error(f"**{user}** is not linked to Bloxlink.", hidden=True) else: if not account: raise Message( f"This Discord user has no primary account set! They may use `{prefix}switchuser` to set one.", type="info", hidden=True)
async def view(self, CommandArgs): """view your log channels""" guild = CommandArgs.guild guild_data = CommandArgs.guild_data log_channels = guild_data.get("logChannels") or {} response = CommandArgs.response if not log_channels: raise Message("You have no log channels!", type="silly") embed = Embed(title="Bloxlink Log Channels") embed.set_footer(text="Powered by Bloxlink", icon_url=Bloxlink.user.avatar_url) embed.set_author(name=guild.name, icon_url=guild.icon_url) description = [] for log_type, log_channel_id in log_channels.items(): log_channel = guild.get_channel(int(log_channel_id)) description.append( f"`{log_type}` {ARROW} {log_channel and log_channel.mention or '(Deleted)'}" ) embed.description = "\n".join(description) await response.send(embed=embed)
async def remove(self, CommandArgs): """allow a user or group back in your server""" guild = CommandArgs.guild response = CommandArgs.response restrictions = await get_guild_value(guild, "restrictions") or {} remove_data = CommandArgs.parsed_args["restriction_data"] remove_data_match = self._remove_data_regex.search(remove_data) if not remove_data_match: raise Message("You must select an option from the dropdown!", type="silly") else: directory_name, remove_id = remove_data_match.group(1), remove_data_match.group(2) if directory_name and remove_id: if restrictions.get(directory_name, {}).get(remove_id): restrictions[directory_name].pop(remove_id) if not restrictions[directory_name]: restrictions.pop(directory_name, None) if not restrictions: restrictions = None await set_guild_value(guild, restrictions=restrictions) await response.success(f"Successfully **removed** this **{directory_name[:-1]}** from your restrictions.") else: raise Error(f"This **{directory_name[:-1]}** isn't restricted!")
async def unverified(self, CommandArgs): """set the DM message of people who are UNVERIFIED on Bloxlink""" author = CommandArgs.author guild = CommandArgs.guild guild_data = CommandArgs.guild_data unverifiedDM = guild_data.get("unverifiedDM") response = CommandArgs.response if unverifiedDM: response.delete(await response.send( "When people join your server and are **UNVERIFIED** on Bloxlink, they will " f"receive this DM:")) response.delete(await response.send(f"```{unverifiedDM}```")) parsed_args_1 = (await CommandArgs.prompt([{ "prompt": "Would you like to **change** the DM people get when they join and are unverified, or " "would you like to **disable** this feature?\n\nPlease specify: (change, disable)", "name": "option", "type": "choice", "choices": ("change", "disable") }]))["option"] if parsed_args_1 == "change": parsed_args_2 = (await CommandArgs.prompt([{ "prompt": "What would you like the text of the Unverified Join DM to be? You may use " f"these templates: ```{UNVERIFIED_TEMPLATES}```", "name": "text", "formatting": False }], last=True))["text"] guild_data["unverifiedDM"] = parsed_args_2 await set_guild_value(guild, "unverifiedDM", parsed_args_2) await self.r.table("guilds").insert(guild_data, conflict="update").run() elif parsed_args_1 == "disable": guild_data["unverifiedDM"] = None await set_guild_value(guild, "unverifiedDM", None) await self.r.table("guilds").insert(guild_data, conflict="replace").run() await post_event( guild, guild_data, "configuration", f"{author.mention} ({author.id}) has **changed** the `joinDM` option for `unverified` members.", BROWN_COLOR) raise Message(f"Successfully **{parsed_args_1}d** your DM message.", type="success")
async def backup(self, CommandArgs): """backup your Server Data""" author = CommandArgs.author guild = CommandArgs.guild response = CommandArgs.response author_id = str(author.id) user_data = await self.r.db("bloxlink").table("users").get( author_id).run() or { "id": author_id } user_backups = user_data.get("backups", []) if len(user_backups) >= LIMITS["BACKUPS"]: response.delete(await response.info( "You've exceeded the amount of backups you're able to create! Your next " "backup will replace your oldest backup.")) user_backups.pop(0) parsed_args = await CommandArgs.prompt([{ "prompt": "**Warning!** This will backup **all** of your Server Data, including " "Linked Groups, Role Binds, prefixes, etc to restore to a different server.\n" f"You are allowed to create **{LIMITS['BACKUPS']}** total backups for **all of your servers.**", "name": "_", "embed_title": "Warning!", "embed_color": ORANGE_COLOR, "footer": "Say anything to continue." }, { "prompt": "What would you like to name this backup? You may use up to 30 characters.", "name": "backup_name", "max": 30 }], last=True) backup_name = parsed_args["backup_name"] new_backup = await self._backup(guild, backup_name) if new_backup: user_backups.append(new_backup) else: raise Message( "There's nothing to save - your server has no saved data!", type="silly") user_data["backups"] = user_backups await self.r.db("bloxlink").table("users").insert( user_data, conflict="update").run() await response.success("Successfully saved your new backup!")
async def __main__(self, CommandArgs): trello_board = CommandArgs.trello_board guild_data = CommandArgs.guild_data guild = CommandArgs.guild author = CommandArgs.author response = CommandArgs.response prefix = CommandArgs.prefix trello_options = {} if trello_board: trello_options, _ = await get_options(trello_board) guild_data.update(trello_options) try: old_nickname = author.display_name added, removed, nickname, errors, warnings, roblox_user = await guild_obligations( author, guild = guild, guild_data = guild_data, join = True, roles = True, nickname = True, trello_board = CommandArgs.trello_board, cache = False, response = response, dm = False, exceptions = ("BloxlinkBypass", "Blacklisted", "UserNotVerified", "PermissionError", "RobloxDown", "RobloxAPIError") ) 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 isinstance(b.message, str): raise Error(f"{author.mention} has an active restriction for: `{b}`") else: raise Error(f"{author.mention} has an active restriction from Bloxlink.") except UserNotVerified: view = discord.ui.View() view.add_item(item=discord.ui.Button(style=discord.ButtonStyle.link, label="Verify with Bloxlink", url=VERIFY_URL, emoji="🔗")) view.add_item(item=discord.ui.Button(style=discord.ButtonStyle.link, label="Stuck? See a Tutorial", emoji="❔", url="https://www.youtube.com/watch?v=0SH3n8rY9Fg&list=PLz7SOP-guESE1V6ywCCLc1IQWiLURSvBE&index=2")) await response.send("To verify with Bloxlink, click the link below.", mention_author=True, view=view) except PermissionError as e: raise Error(e.message) else: welcome_message, embed, view = await format_update_embed(roblox_user, author, added=added, removed=removed, errors=errors, warnings=warnings, nickname=nickname if old_nickname != nickname else None, prefix=prefix, guild_data=guild_data) await post_event(guild, guild_data, "verification", f"{author.mention} ({author.id}) has **verified** as `{roblox_user.username}`.", GREEN_COLOR) await response.send(content=welcome_message, embed=embed, view=view, mention_author=True)
async def __main__(self, ExtensionArgs): user = ExtensionArgs.resolved guild = ExtensionArgs.guild guild_data = ExtensionArgs.guild_data response = ExtensionArgs.response if user.bot: raise Error("You cannot update bots!", hidden=True) if isinstance(user, discord.User): try: user = await guild.fetch_member(user.id) except discord.errors.NotFound: raise Error("This user isn't in your server!") try: added, removed, nickname, errors, warnings, roblox_user = await guild_obligations( user, guild=guild, guild_data=guild_data, roles=True, nickname=True, cache=False, dm=False, event=True, exceptions=("BloxlinkBypass", "Blacklisted", "CancelCommand", "UserNotVerified", "PermissionError", "RobloxDown", "RobloxAPIError")) await response.send( f"{REACTIONS['DONE']} **Updated** {user.mention}", hidden=True) except BloxlinkBypass: raise Message( "Since this user has the Bloxlink Bypass role, I was unable to update their roles/nickname.", type="info", hidden=True) except Blacklisted as b: if isinstance(b.message, str): raise Error( f"{user.mention} has an active restriction for: `{b}`", hidden=True) else: raise Error( f"{user.mention} has an active restriction from Bloxlink.", hidden=True) except CancelCommand: pass except UserNotVerified: raise Error("This user is not linked to Bloxlink.", hidden=True) except PermissionError as e: raise Error(e.message, hidden=True)
async def view(CommandArgs): """view your linked account(s)""" author = CommandArgs.author response = CommandArgs.response try: primary_account, accounts = await get_user("username", author=author, everything=False, basic_details=True) except UserNotVerified: raise Message("You have no accounts linked to Bloxlink!", type="silly") else: accounts = list(accounts) parsed_accounts = await parse_accounts(accounts, reverse_search=True) parsed_accounts_str = [] primary_account_str = "No primary account set" for roblox_username, roblox_data in parsed_accounts.items(): if roblox_data[1]: username_str = [] for discord_account in roblox_data[1]: username_str.append( f"{discord_account} ({discord_account.id})") username_str = ", ".join(username_str) if primary_account and roblox_username == primary_account.username: primary_account_str = f"**{roblox_username}** {ARROW} {username_str}" else: parsed_accounts_str.append( f"**{roblox_username}** {ARROW} {username_str}") else: parsed_accounts_str.append(f"**{roblox_username}**") parsed_accounts_str = "\n".join(parsed_accounts_str) embed = Embed(title="Linked Roblox Accounts") embed.add_field(name="Primary Account", value=primary_account_str) embed.add_field(name="Secondary Accounts", value=parsed_accounts_str or "No secondary account saved") embed.set_author(name=author, icon_url=author.avatar_url) await response.send(embed=embed, dm=True, hidden=False, strict_post=True)
async def __main__(self, CommandArgs): target = CommandArgs.parsed_args["roblox_name"] flags = CommandArgs.flags response = CommandArgs.response message = CommandArgs.message guild = CommandArgs.guild prefix = CommandArgs.prefix if message and message.mentions and CommandArgs.string_args: message.content = f"{prefix}getinfo {CommandArgs.string_args[0]}" return await parse_message(message) valid_flags = ["username", "id", "avatar", "premium", "badges", "groups", "description", "age", "banned", "devforum"] if not all(f in valid_flags for f in flags.keys()): raise Error(f"Invalid flag! Valid flags are: `{', '.join(valid_flags)}`") username = ID = False if "username" in flags: username = True flags.pop("username") elif target.isdigit(): ID = True else: username = True #async with response.loading(): if guild: role_binds, group_ids, _ = await get_binds(guild_data=CommandArgs.guild_data, trello_board=CommandArgs.trello_board) else: role_binds, group_ids = {}, {} try: _, _ = await get_user(*flags.keys(), username=username and target, roblox_id=ID and target, group_ids=(group_ids, role_binds), send_embed=True, guild=guild, response=response, everything=not bool(flags), basic_details=not bool(flags)) except RobloxNotFound: raise Error("This Roblox account doesn't exist.") except RobloxAPIError: if ID: try: await Bloxlink.fetch_user(int(target)) except NotFound: raise Error("This Roblox account doesn't exist.") else: if message: message.content = f"{prefix}getinfo {target}" return await parse_message(message) else: raise Message(f"To search with Discord IDs, please use the `{prefix}getinfo` command.\n" "This command only searches by Roblox username or ID.", hidden=True, type="info") else: raise Error("This Roblox account doesn't exist.")
async def __main__(self, CommandArgs): trello_board = CommandArgs.trello_board guild_data = CommandArgs.guild_data guild = CommandArgs.guild author = CommandArgs.author response = CommandArgs.response prefix = CommandArgs.prefix trello_options = {} if trello_board: trello_options, _ = await get_options(trello_board) guild_data.update(trello_options) try: old_nickname = author.display_name added, removed, nickname, errors, warnings, roblox_user = await guild_obligations( author, guild = guild, guild_data = guild_data, roles = True, nickname = True, trello_board = CommandArgs.trello_board, cache = False, response = response, dm = False, exceptions = ("BloxlinkBypass", "Blacklisted", "UserNotVerified", "PermissionError", "RobloxDown", "RobloxAPIError") ) 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 isinstance(b.message, str): raise Error(f"{author.mention} has an active restriction for: `{b}`") else: raise Error(f"{author.mention} has an active restriction from Bloxlink.") except UserNotVerified: await response.reply("To verify with Bloxlink, please visit our website at " f"<{VERIFY_URL}>. It won't take long!\nStuck? See this video: <https://www.youtube.com/watch?v=hq496NmQ9GU>", hidden=True) except PermissionError as e: raise Error(e.message) else: welcome_message, embed = await format_update_embed(roblox_user, author, added=added, removed=removed, errors=errors, warnings=warnings, nickname=nickname if old_nickname != author.display_name else None, prefix=prefix, guild_data=guild_data) await post_event(guild, guild_data, "verification", f"{author.mention} ({author.id}) has **verified** as `{roblox_user.username}`.", GREEN_COLOR) await response.send(content=welcome_message, embed=embed)
async def verified(self, CommandArgs): """set the DM message of people who are VERIFIED on Bloxlink""" author = CommandArgs.author guild = CommandArgs.guild verified_DM = await get_guild_value( guild, ["verifiedDM", DEFAULTS.get("welcomeMessage")]) response = CommandArgs.response if verified_DM: response.delete(await response.send( "When people join your server and are **VERIFIED** on Bloxlink, they will " f"receive this DM:")) response.delete(await response.send(f"```{verified_DM}```")) parsed_args_1 = (await CommandArgs.prompt([{ "prompt": "Would you like to **change** the DM people get when they join and are verified, or " "would you like to **disable** this feature?\n\nPlease specify: (change, disable)", "name": "option", "type": "choice", "choices": ("change", "disable") }]))["option"] if parsed_args_1 == "change": parsed_args_2 = (await CommandArgs.prompt([{ "prompt": "What would you like the text of the Verified Join DM to be? You may use " f"these templates: ```{NICKNAME_TEMPLATES}```", "name": "text", "formatting": False }], last=True))["text"] await set_guild_value(guild, verifiedDM=parsed_args_2) elif parsed_args_1 == "disable": await set_guild_value(guild, verifiedDM=None) await post_event( guild, "configuration", f"{author.mention} ({author.id}) has **changed** the `joinDM` option for `verified` members.", BROWN_COLOR) raise Message(f"Successfully **{parsed_args_1}d** your DM message.", type="success")
async def cleanup(self, CommandArgs): """free old cases from the database""" guild = CommandArgs.message.guild author = CommandArgs.message.author prefix = CommandArgs.prefix response = CommandArgs.response addon_data = await self.r.table("addonData").get(str(guild.id)).run() or {"id": str(guild.id)} court_data = addon_data.get("court") or {} cases = court_data.get("cases") or {} removed = 0 my_permissions = guild.me.guild_permissions if not (my_permissions.manage_channels and my_permissions.manage_roles): raise Error("I need both the ``Manage Channels`` and ``Manage Roles`` permissions.") elif not court_data: raise Error(f"You must set-up this add-on before you can use it! Please use ``{prefix}courtsetup`` " "to begin the set-up.") elif not cases: raise Message("Cannot clean cases: you have no cases saved to the database.", type="silly") for judge_role_id in court_data.get("judgeRoles", []): if find(lambda r: r.id == int(judge_role_id), author.roles): break else: raise Error("You must have a Judge role in order to run this command!") for channel_id, case in dict(cases).items(): case_channel = guild.get_channel(int(channel_id)) if not case_channel or (case_channel and case_channel.category and case_channel.category.id != case["archiveCategory"]): cases.pop(channel_id) removed += 1 court_data["cases"] = cases addon_data["court"] = court_data await self.r.table("addonData").insert(addon_data, conflict="replace").run() if removed: await response.success(f"Successfully removed **{removed}** old case(s) from the database.") else: await response.silly("No cases to clean: all case channels still exist in your server.")
async def unverified(self, CommandArgs): """set the join message of people who are UNVERIFIED on Bloxlink""" guild_data = CommandArgs.guild_data join_channel = guild_data.get("joinChannel") or {} unverified_message = join_channel.get("unverified") author = CommandArgs.author guild = CommandArgs.guild response = CommandArgs.response if unverified_message: response.delete(await response.send( "When people join your server and are **UNVERIFIED** on Bloxlink, this message " "will be posted:")) response.delete( await response.send(f"```{unverified_message['message']}```")) parsed_args_1 = (await CommandArgs.prompt([{ "prompt": "Would you like to **change** the message people get when they join and are unverified, or " "would you like to **disable** this feature?", "name": "option", "type": "choice", "components": [ discord.ui.Select( max_values=1, options=[ discord.SelectOption( label="Change message", description= "Change the message for unverified users."), discord.SelectOption( label="Disable", description="No join message for unverified users." ), ]) ], "choices": ("Change message", "Disable") }]))["option"][0] if parsed_args_1 == "Change message": parsed_args_2 = await CommandArgs.prompt([{ "prompt": "What would you like the text of the Unverified Join Message to be? You may use " f"these templates: ```{UNVERIFIED_TEMPLATES}```", "name": "text", "max": 1500, "formatting": False }, { "prompt": "Which **channel** would you like the join messages to be posted in?", "name": "channel", "type": "channel" }, { "prompt": "Would you like to keep the join messages in an embed format, or keep it as text?\nIt's " "recommended to say `embed` if you chose to include avatars for the `verified` message " "so the verified and unverified join messages look similar.", "components": [ discord.ui.Select( max_values=1, options=[ discord.SelectOption( label="Use embed format", description= "The join message will be in an embed."), discord.SelectOption( label="Use text format", description= "The join message will be in a standard message." ), ]) ], "name": "type", "type": "choice", "choices": ("Use embed format", "Use text format") }], last=True) channel = parsed_args_2["channel"] text = parsed_args_2["text"] embed_format = parsed_args_2["type"][0] == "Use embed format" includes = {} parsed_args_3 = await CommandArgs.prompt([ { "prompt": "Would you like this join message to ping people?", "name": "ping", "type": "choice", "components": [ discord.ui.Select( max_values=1, options=[ discord.SelectOption( label="Ping people", description="The message will ping people." ), discord.SelectOption( label="Don't ping people", description= "The message will NOT ping anyone."), ]) ], "choices": ("Ping people", "Don't ping people") }, ], last=True) if parsed_args_3["ping"][0] == "Ping people": includes["ping"] = True join_channel["unverified"] = { "channel": str(channel.id), "message": text, "includes": includes, "embed": embed_format } guild_data["joinChannel"] = join_channel await set_guild_value(guild, "joinChannel", join_channel) await self.r.table("guilds").insert(guild_data, conflict="replace").run() else: join_channel.pop("unverified", None) guild_data["joinChannel"] = join_channel await set_guild_value(guild, "joinChannel", join_channel) await self.r.table("guilds").insert(guild_data, conflict="replace").run() change_text = f"**{'changed' if parsed_args_1 == 'Change message' else 'disabled'}**" await post_event( guild, guild_data, "configuration", f"{author.mention} ({author.id}) has {change_text} the `joinChannel` option for `verified` members.", BROWN_COLOR) raise Message(f"Successfully {change_text} your join message.", type="success")
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 __main__(self, CommandArgs): response = CommandArgs.response user_slash = CommandArgs.parsed_args.get("user") role_slash = CommandArgs.parsed_args.get("role") users_ = CommandArgs.parsed_args.get("users") or ( [user_slash, role_slash] if user_slash or role_slash else None) prefix = CommandArgs.prefix message = CommandArgs.message author = CommandArgs.author guild = CommandArgs.guild guild_data = CommandArgs.guild_data users = [] if not (users_ and CommandArgs.has_permission): if not users_: if message: message.content = f"{prefix}getrole" return await parse_message(message) else: raise Message( f"To update yourself, please run the `{prefix}getrole` command.", hidden=True, type="info") else: raise Message( "You do not have permission to update users; you need the `Manage Roles` permission, or " "a role called `Bloxlink Updater`.", type="info", hidden=True) 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!", hidden=True) 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 #async with response.loading(): if len_users > 1: for user in users: if not user.bot: try: added, removed, nickname, errors, warnings, 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", "RobloxDown"), 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.message) 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, warnings, 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", "RobloxDown", "RobloxAPIError")) _, embed = await format_update_embed( roblox_user, user, added=added, removed=removed, errors=errors, warnings=warnings, nickname=nickname if old_nickname != nickname else None, prefix=prefix, guild_data=guild_data) await response.send(embed=embed) except BloxlinkBypass: raise Message( "Since this user has the Bloxlink Bypass role, I was unable to update their roles/nickname.", type="info") except Blacklisted as b: if isinstance(b.message, str): 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.message) 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): choice = CommandArgs.parsed_args["choice"] guild_data = CommandArgs.guild_data groups = CommandArgs.guild_data.get("groupLock", {}) guild = CommandArgs.guild author = CommandArgs.author prefix = CommandArgs.prefix response = CommandArgs.response if choice == "add": args = await CommandArgs.prompt([{ "prompt": "Please specify either the **Group ID** or **Group URL** that you would like " "to set as a requirement for new joiners.", "name": "group", "validation": self.validate_group }, { "prompt": "Should only a **specific Roleset** be allowed to join your server? You may specify a Roleset name or ID. You may " "provide them in a list, and you may negate the number to capture everyone catch everyone with the rank _and above_.\n" "Example: `-10, 5, VIP` means people who are ranked 5, VIP, or if their roleset is greater than 10 can join your server.", "name": "rolesets", "footer": "Say **skip** to skip this option; if skipped, the roleset people have wouldn't matter, they'll be able to enter " "your server as long as they're in your group.", "type": "list", "exceptions": ("skip", ), "max": 10, }, { "prompt": "Would you like people who are kicked to receive a custom DM? Please specify either `yes` or `no`.\n\n" "Note that Unverified users will receive a different DM on instructions to linking to Bloxlink.", "name": "dm_enabled", "type": "choice", "choices": ["yes", "no"] }]) group = args["group"] dm_enabled = args["dm_enabled"] == "yes" rolesets_raw = args[ "rolesets"] if args["rolesets"] != "skip" else None parsed_rolesets = [] if rolesets_raw: for roleset in rolesets_raw: if roleset.isdigit(): parsed_rolesets.append(int(roleset)) elif roleset[:1] == "-": try: roleset = int(roleset) except ValueError: pass else: parsed_rolesets.append(roleset) else: range_search = self._range_search.search(roleset) if range_search: num1, num2 = range_search.group( 1), range_search.group(2) parsed_rolesets.append([int(num1), int(num2)]) else: # they specified a roleset name as a string roleset_find = group.rolesets.get(roleset.lower()) if roleset_find: parsed_rolesets.append(roleset_find[1]) if not parsed_rolesets: raise Error( "Could not resolve any valid rolesets! Please make sure you're typing the Roleset name correctly." ) if len(groups) >= 15: raise Message( "15 groups is the max you can add to your group-lock! Please delete some before adding any more.", type="silly") profile, _ = await get_features(Object(id=guild.owner_id), guild=guild) if len(groups) >= 3 and not profile.features.get("premium"): raise Message( "If you would like to add more than **3** groups to your group-lock, then you need Bloxlink Premium.\n" f"Please use `{prefix}donate` for instructions on receiving Bloxlink Premium.\n" "Bloxlink Premium members may lock their server with up to **15** groups.", type="info") if dm_enabled: dm_message = (await CommandArgs.prompt([{ "prompt": "Please specify the text of the DM that people who are kicked will receive. A recommendation " "is to provide your Group Link and any other instructions for them.", "name": "dm_message", "max": 1500 }], last=True) )["dm_message"] else: dm_message = None groups[group.group_id] = { "groupName": group.name, "dmMessage": dm_message, "roleSets": parsed_rolesets } await self.r.table("guilds").insert( { "id": str(guild.id), "groupLock": groups }, conflict="update").run() await post_event( guild, guild_data, "configuration", f"{author.mention} ({author.id}) has **added** a group to the `server-lock`.", BROWN_COLOR) await set_guild_value(guild, "groupLock", groups) await response.success( f"Successfully added group **{group.name}** to your Server-Lock!" ) elif choice == "delete": group = (await CommandArgs.prompt([{ "prompt": "Please specify either the **Group URL** or **Group ID** to delete.", "name": "group", "validation": self.validate_group }], last=True))["group"] if not groups.get(group.group_id): raise Message("This group isn't in your server-lock!") del groups[group.group_id] guild_data["groupLock"] = groups if groups: await self.r.table("guilds").insert(guild_data, conflict="replace").run() else: guild_data.pop("groupLock") await self.r.table("guilds").insert(guild_data, conflict="replace").run() await post_event( guild, guild_data, "configuration", f"{author.mention} ({author.id}) has **deleted** a group from the `server-lock`.", BROWN_COLOR) await set_guild_value(guild, "groupLock", groups) await response.success( "Successfully **deleted** your group from the Server-Lock!") elif choice == "view": if not groups: raise Message("You have no groups added to your Server-Lock!", type="info") embed = Embed(title="Bloxlink Server-Lock") embed.set_footer(text="Powered by Bloxlink", icon_url=Bloxlink.user.avatar_url) embed.set_author(name=guild.name, icon_url=guild.icon_url) for group_id, data in groups.items(): embed.add_field(name=f"{data['groupName']} ({group_id})", value=data["dmMessage"], inline=False) await response.send(embed=embed)
async def __main__(self, CommandArgs): author = CommandArgs.author response = CommandArgs.response prefix = CommandArgs.prefix if not SELF_HOST: author_data = await self.r.db("bloxlink").table("users").get(str(author.id)).run() or {"id": str(author.id)} try: primary_account, accounts = await get_user("username", author=author, everything=False, basic_details=True) if accounts: parsed_accounts = await parse_accounts(accounts) parsed_accounts_str = ", ".join(parsed_accounts.keys()) parsed_args = await CommandArgs.prompt([ { "prompt": "This command will allow you to switch into an account you verified as in the past.\n" f"If you would like to link __a new account__, then please use `{prefix}verify add`.\n\n" "**__WARNING:__** This will remove __all of your roles__ in the server and give you " "new roles depending on the server configuration.", "footer": "Say **next** to continue.", "type": "choice", "choices": ["next"], "name": "_", "formatting": False }, { "prompt": "Are you trying to change your account for _this_ server? If so, simply say `next`.\nIf not, please provide " "the __Server ID__ of the server to switch as. Please see this article to find the Server ID: " "[click here](https://support.discordapp.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID->).", "name": "guild", "validation": self.validate_server, }, { "prompt": "We'll switch your account for the server **{guild.name}**.\n" "Please select an account to switch into:```" + parsed_accounts_str + "```", "name": "account", "type": "choice", "choices": parsed_accounts.keys() }, { "prompt": "Would you like to make this your __primary__ account? Please say **yes** or **no**.", "name": "primary", "type": "choice", "choices": ("yes", "no") } ], last=True) guild = parsed_args["guild"] username = parsed_args["account"] roblox_id = (parsed_accounts.get(username)).id guild_data = await self.r.table("guilds").get(str(guild.id)).run() or {"id": str(guild.id)} trello_board = await get_board(guild_data=guild_data, guild=guild) if trello_board: options_trello, _ = await get_options(trello_board) guild_data.update(options_trello) allow_reverify = guild_data.get("allowReVerify", DEFAULTS.get("allowReVerify")) roblox_accounts = author_data.get("robloxAccounts", {}) if guild and not allow_reverify: guild_accounts = roblox_accounts.get("guilds", {}) chosen_account = guild_accounts.get(str(guild.id)) if chosen_account and chosen_account != roblox_id: raise Error("You already selected your account for this server. `allowReVerify` must be " "enabled for you to change it.") try: member = await guild.fetch_member(author.id) except (Forbidden, NotFound): await verify_member(author, roblox_id, guild=guild, author_data=author_data, allow_reverify=allow_reverify, primary_account=parsed_args["primary"] == "yes") raise Message("You're not a member of the provided server, so I was only able to update your account internally.", type="success") try: username = await verify_as( member, guild, response = response, primary = parsed_args["primary"] == "yes", roblox_id = roblox_id, trello_board = trello_board, update_user = False) except Message as e: if e.type == "error": await response.error(e) else: await response.send(e) except Error as e: await response.error(e) else: role_binds, group_ids, _ = await get_binds(guild_data=guild_data, trello_board=trello_board) if count_binds(guild_data, role_binds=role_binds, group_ids=group_ids) and not find(lambda r: r.name == "Bloxlink Bypass", member.roles): for role in list(member.roles): if role != guild.default_role and role.name != "Muted": try: await member.remove_roles(role, reason="Switched User") except Forbidden: pass try: added, removed, nickname, errors, roblox_user = await update_member( member, guild = guild, roles = True, nickname = True, response = response, cache = False) except BloxlinkBypass: await response.info("Since you have the `Bloxlink Bypass` role, I was unable to update your roles/nickname; however, your account was still changed.") return except Blacklisted as b: if str(b): raise Error(f"{author.mention} has an active restriction for: `{b}`.") else: raise Error(f"{author.mention} has an active restriction from Bloxlink.") else: welcome_message = guild_data.get("welcomeMessage") or DEFAULTS.get("welcomeMessage") welcome_message = await get_nickname(author, welcome_message, guild_data=guild_data, roblox_user=roblox_user, is_nickname=False) await post_event(guild, guild_data, "verification", f"{author.mention} ({author.id}) has **switched their user** to `{username}`.", GREEN_COLOR) await CommandArgs.response.send(welcome_message) else: raise Message(f"You only have one account linked! Please use `{prefix}verify add` to add another.", type="silly") except UserNotVerified: raise Error(f"You're not linked to Bloxlink. Please use `{prefix}verify add`.") else: raise Message(f"{author.mention}, to verify with Bloxlink, please visit our website at " \ f"<{VERIFY_URL}>. It won't take long!\nStuck? See this video: <https://www.youtube.com/watch?v=hq496NmQ9GU>")
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 change(self, CommandArgs): """add/delete a log channel""" prefix = CommandArgs.prefix response = CommandArgs.response guild_data = CommandArgs.guild_data author = CommandArgs.author guild = CommandArgs.guild log_channels = guild_data.get("logChannels") or {} parsed_args = await CommandArgs.prompt([{ "prompt": "Please select an **event** to add/delete:\n" "`all` " + ARROW + " all events will be sent to your channel\n" "`verifications` " + ARROW + " user verifications will be logged " "to your channel\n" "`configurations` " + ARROW + " any Bloxlink setting alteration will be " "logged to your channel\n" "`inactivity notices` _(premium)_ " + ARROW + " user-set inactivity notices " "from `" + prefix + "profie` will " "be logged to your channel\n" "`binds` " + ARROW + " bind insertions/deletions will be logged to your channel\n" "`moderation` " + ARROW + " automatic moderation actions by certain features will be " "logged to your channel", "name": "log_type", "type": "choice", "choices": [ "all", "verifications", "configurations", "inactivity notices", "binds", "moderation" ] }, { "prompt": "Please either **mention a channel**, or say a **channel name.**\n" "Successful `{log_type}` events will be posted to this channel.\n\n" "**Please make sure Bloxlink has permission to send/read messages " "from the channel!**", "name": "log_channel", "footer": "Say **clear** to **delete** an already existing log channel of this type.", "type": "channel", "exceptions": ["clear", "delete"] }], last=True) log_type = parsed_args["log_type"] if log_type.endswith("s"): log_type = log_type[: -1] # remove ending "s" - looks better on embed titles log_channel = parsed_args["log_channel"] action = None if log_type == "inactivity notices": donator_profile, _ = await get_features(Object(id=guild.owner_id), guild=guild) if not donator_profile.features.get("premium"): raise Message( "Only premium subscribers can subscribe to `inactivity notices`!\n" f"Please use `{prefix}donate` for instructions on subscribing to premium.", type="silly") if log_channel in ("clear", "delete"): log_channels.pop(log_type, None) action = "deleted" else: log_channels[log_type] = str(log_channel.id) action = "saved" if not log_channels: guild_data.pop("logChannels", None) else: guild_data["logChannels"] = log_channels await self.r.table("guilds").insert(guild_data, conflict="replace").run() await set_guild_value(guild, "logChannels", log_channels) await post_event( guild, guild_data, "configuration", f"{author.mention} ({author.id}) has **changed** the `log channels`.", BROWN_COLOR) await response.success(f"Successfully **{action}** your log channel!")
async def __main__(self, CommandArgs): guild = CommandArgs.guild guild_data = CommandArgs.guild_data trello_board = CommandArgs.trello_board prefix = CommandArgs.prefix role_binds, group_ids, _ = await get_binds(guild_data=guild_data, trello_board=trello_board) if count_binds(guild_data, role_binds=role_binds, group_ids=group_ids) == 0: raise Message( f"You have no bounded roles! Please use `{CommandArgs.prefix}bind` " "to make a new role bind.", type="silly") embed = Embed(title="Bloxlink Role Binds") text = [] if group_ids: for group_id, group_data in group_ids.items(): text.append( f"**Group:** {group_data['groupName']} ({group_id}) {ARROW} **Nickname:** {group_data['nickname']}" ) text = "\n".join(text) embed.add_field(name="Linked Groups", value=text, inline=False) if role_binds: role_cache = {} for category, bind_data in role_binds.items(): if category == "groups": for group_id, group_data in bind_data.items(): text = [] for rank_id, rank_data in group_data.get("binds", {}).items(): role_names = set() if rank_data["roles"]: for role_ in rank_data["roles"]: role_cache_find = role_cache.get(role_) if role_cache_find: role_names.add(role_cache_find) else: for role in guild.roles: if role_ in (role.name, str(role.id)): role_names.add(role.name) role_cache[role_] = role.name break else: try: int(role_) except ValueError: role_names.add(role_) role_cache[role_] = role_ else: # deleted role # TODO: check if the role is saved in server settings, then delete it role_names.add( "(Deleted Role(s))") role_cache[ role_] = "(Deleted Role(s))" if rank_id in ("guest", "0"): text.append( f"**Rank:** (Guest Role) {ARROW} **Roles:** {', '.join(role_names)} {ARROW} **Nickname:** {rank_data['nickname']}" ) else: text.append( f"**Rank:** {rank_id} {ARROW} **Roles:** {', '.join(role_names)} {ARROW} **Nickname:** {rank_data['nickname']}" ) else: text.append( f"**Rank:** {rank_id} {ARROW} **Roles:** (Dynamic Roles) {ARROW} **Nickname:** {rank_data['nickname']}" ) for range_data in group_data.get("ranges", []): role_names = set() if range_data["roles"]: for role_ in range_data["roles"]: role_cache_find = role_cache.get(role_) if role_cache_find: role_names.add(role_cache_find) else: for role in guild.roles: if role_ in (role.name, str(role.id)): role_names.add(role.name) role_cache[role_] = role.name break else: try: int(role_) except ValueError: role_names.add(role_) role_cache[role_] = role_ else: # deleted role # TODO: check if the role is saved in server settings, then delete it role_names.add( "(Deleted Role(s))") role_cache[ role_] = "(Deleted Role(s))" text.append( f"**Rank Range:** {range_data['low']} - {range_data['high']} {ARROW} **Roles:** {', '.join(role_names)} {ARROW} **Nickname:** {range_data['nickname']}" ) else: text.append( f"**Rank Range:** {range_data['low']} - {range_data['high']} {ARROW} **Roles:** (Dynamic Roles) {ARROW} **Nickname:** {range_data['nickname']}" ) if text: text = "\n".join(text) try: group_name = group_data.get("groupName") or ( await get_group(group_id, full_group=True)).name except RobloxNotFound: # TODO: remove group pass else: embed.add_field( name=f"{group_name} ({group_id})", value=text, inline=False) else: text = [] if category == "gamePasses": category_non_plural = "gamePass" category_non_plural_title = "GamePass" category_title = "GamePasses" elif category == "devForum": category_title = "DevForum Members" elif category == "robloxStaff": category_title = "Roblox Staff" else: category_non_plural = category[:-1] category_non_plural_title = category_non_plural.title() category_title = category.title() if category in ("devForum", "robloxStaff"): role_names = set() if bind_data["roles"]: for role_ in bind_data["roles"]: role_cache_find = role_cache.get(role_) if role_cache_find: role_names.add(role_cache_find) else: for role in guild.roles: if role_ in (role.name, str(role.id)): role_names.add(role.name) role_cache[role_] = role.name break else: try: int(role_) except ValueError: role_names.add(role_) role_cache[role_] = role_ else: # deleted role # TODO: check if the role is saved in server settings, then delete it role_names.add("(Deleted Role(s))") role_cache[ role_] = "(Deleted Role(s))" text.append( f"**Roles:** {', '.join(role_names)} {ARROW} **Nickname:** {bind_data['nickname']}" ) else: text.append( f"**Roles:** (No Roles) {ARROW} **Nickname:** {bind_vg_data['nickname']}" ) else: for bind_id, bind_vg_data in bind_data.items(): display_name = bind_vg_data.get( "displayName") or "(No Name)" role_names = set() if bind_vg_data["roles"]: for role_ in bind_vg_data["roles"]: role_cache_find = role_cache.get(role_) if role_cache_find: role_names.add(role_cache_find) else: for role in guild.roles: if role_ in (role.name, str(role.id)): role_names.add(role.name) role_cache[role_] = role.name break else: try: int(role_) except ValueError: role_names.add(role_) role_cache[role_] = role_ else: # deleted role # TODO: check if the role is saved in server settings, then delete it role_names.add( "(Deleted Role(s))") role_cache[ role_] = "(Deleted Role(s))" text.append( f"**{category_non_plural_title}:** {display_name} ({bind_id}) {ARROW} **Roles:** {', '.join(role_names)} {ARROW} **Nickname:** {bind_vg_data['nickname']}" ) else: text.append( f"**{category_non_plural_title}:** {display_name} ({bind_id}) {ARROW} **Roles:** (No Roles) {ARROW} **Nickname:** {bind_vg_data['nickname']}" ) if text: text = "\n".join(text) embed.add_field(name=category_title, value=text, inline=False) embed.set_author(name="Powered by Bloxlink", icon_url=Bloxlink.user.avatar.url) embed.set_footer( text= f"Use {prefix}bind to make a new bind, or {prefix}delbind to delete a bind" ) await CommandArgs.response.send(embed=embed)
async def __main__(self, CommandArgs): trello_board = CommandArgs.trello_board guild_data = CommandArgs.guild_data guild = CommandArgs.guild author = CommandArgs.author response = CommandArgs.response prefix = CommandArgs.prefix if not guild: return await self.add(CommandArgs) if CommandArgs.flags.get("add") or CommandArgs.flags.get( "verify") or CommandArgs.flags.get("force"): await CommandArgs.response.error( f"`{CommandArgs.prefix}verify --force` is deprecated and will be removed in a future version of Bloxlink. " f"Please use `{prefix}verify add` instead.") return await self.add(CommandArgs) if CommandArgs.real_command_name in ("getrole", "getroles"): CommandArgs.string_args = [] trello_options = {} if trello_board: trello_options, _ = await get_options(trello_board) guild_data.update(trello_options) try: old_nickname = author.display_name added, removed, nickname, errors, roblox_user = await guild_obligations( CommandArgs.author, guild=guild, guild_data=guild_data, roles=True, nickname=True, trello_board=CommandArgs.trello_board, given_trello_options=True, cache=False, response=response, dm=False, exceptions=("BloxlinkBypass", "Blacklisted", "UserNotVerified", "PermissionError")) 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 isinstance(b.message, str): raise Error( f"{author.mention} has an active restriction for: `{b}`") else: raise Error( f"{author.mention} has an active restriction from Bloxlink." ) except UserNotVerified: await self.add(CommandArgs) except PermissionError as e: raise Error(e.message) else: welcome_message, embed = await format_update_embed( roblox_user, author, added=added, removed=removed, errors=errors, nickname=nickname if old_nickname != author.display_name else None, prefix=prefix, guild_data=guild_data) if embed: await post_event( guild, guild_data, "verification", f"{author.mention} ({author.id}) has **verified** as `{roblox_user.username}`.", GREEN_COLOR) else: embed = Embed( description= "This user is all up-to-date; no changes were made.") await response.send(content=welcome_message, embed=embed)
async def __main__(self, CommandArgs): response = CommandArgs.response command = CommandArgs.command user_slash = CommandArgs.parsed_args.get("user") role_slash = CommandArgs.parsed_args.get("role") users_ = CommandArgs.parsed_args.get("users") or ( [user_slash, role_slash] if user_slash or role_slash else None) author = CommandArgs.author guild = CommandArgs.guild users = [] if not (users_ and CommandArgs.has_permission): if not users_: await command.redirect(CommandArgs, "getrole") raise CancelCommand else: raise Message( "You do not have permission to update users; you need the `Manage Roles` permission, or " "a role called `Bloxlink Updater`.", type="info", hidden=True) if not guild.chunked: await guild.chunk() if users_[1]: role = users_[1] users += role.members if not users: raise Error("This role has no members in it!", hidden=True) if users_[0]: user = users_[0] users.append(user) len_users = len(users) for i, user in enumerate(users): if isinstance(user, User): try: user = await guild.fetch_member(user.id) except NotFound: raise Error("This user isn't in your server!") else: users[i] = user 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 has_premium(guild=guild) premium = "premium" in donator_profile.features if not premium: donator_profile = await has_premium(user=author) premium = "premium" in donator_profile.features 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 `/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) #async with response.loading(): if len_users > 1: await response.send(f"Updating **{len_users}** users...") for user in users: if not user.bot: try: added, removed, nickname, errors, warnings, roblox_user = await guild_obligations( user, guild=guild, roles=True, nickname=True, dm=False, exceptions=("BloxlinkBypass", "UserNotVerified", "Blacklisted", "PermissionError", "RobloxDown"), 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.message) except Blacklisted as b: if len_users <= 10: await response.send( f"{REACTIONS['ERROR']} {user.mention} has an active restriction." ) except CancelCommand: pass 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, warnings, roblox_user = await guild_obligations( user, guild=guild, roles=True, nickname=True, cache=False, dm=False, event=True, exceptions=("BloxlinkBypass", "Blacklisted", "CancelCommand", "UserNotVerified", "PermissionError", "RobloxDown", "RobloxAPIError")) _, card, embed = await format_update_embed( roblox_user, user, added=added, removed=removed, errors=errors, warnings=warnings, nickname=nickname if old_nickname != nickname else None, author=author, guild=guild, ) message = await response.send( embed=embed, files=[card.front_card_file] if card else None, view=card.view if card else None) if card: card.response = response card.message = message card.view.message = message except BloxlinkBypass: raise Message( "Since this user has the Bloxlink Bypass role, I was unable to update their roles/nickname.", type="info") except Blacklisted as b: if isinstance(b.message, str): 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.message) if cooldown: await self.redis.set(redis_cooldown_key, 3, ex=cooldown) if len_users > 10: await response.success("All users updated.")
async def verified(self, CommandArgs): """set the join message of people who are VERIFIED on Bloxlink""" guild_data = CommandArgs.guild_data join_channel = guild_data.get("joinChannel") or {} verified_message = join_channel.get("verified") author = CommandArgs.author guild = CommandArgs.guild response = CommandArgs.response if verified_message: response.delete(await response.send( "When people join your server and are **VERIFIED** on Bloxlink, this message " "will be posted:")) response.delete( await response.send(f"```{verified_message['message']}```")) parsed_args_1 = (await CommandArgs.prompt([{ "prompt": "Would you like to **change** the message people get when they join and are verified, or " "would you like to **disable** this feature?", "name": "option", "type": "choice", "components": [ discord.ui.Select( max_values=1, options=[ discord.SelectOption( label="Change message", description="Change the message for verified users." ), discord.SelectOption( label="Disable", description="No join message for verified users."), ]) ], "choices": ("Change message", "Disable") }]))["option"][0] if parsed_args_1 == "Change message": parsed_args_2 = await CommandArgs.prompt([{ "prompt": "What would you like the text of the Verified Join Message to be? You may use " f"these templates: ```{SERVER_VERIFIED_TEMPLATES}```", "name": "text", "max": 1500, "formatting": False }, { "prompt": "Which **channel** would you like the join messages to be posted in?", "name": "channel", "type": "channel" }, { "prompt": "Let's customize the join message!", "name": "features", "type": "choice", "components": [ discord.ui.Select( max_values=4, options=[ discord.SelectOption( label="Ping people", description="The embed will ping people."), discord.SelectOption( label="Include Roblox avatar", description= "The embed will show the user's Roblox avatar." ), discord.SelectOption( label="Include Roblox age", description= "The embed will show the user's Roblox age."), discord.SelectOption( label="Include Roblox username", description= "The embed will show the user's Roblox username." ), discord.SelectOption( label="None of the above", description="There will be no embed."), ]) ], "choices": ("Include Roblox avatar", "Ping people", "Include Roblox age", "Include Roblox username", "None of the above") }], last=True) channel = parsed_args_2["channel"] text = parsed_args_2["text"] features = parsed_args_2["features"] includes = {} if "None of the above" not in features: for feature in features: if feature == "Ping people": includes["ping"] = True elif feature == "Include Roblox avatar": includes["robloxAvatar"] = True elif feature == "Include Roblox age": includes["robloxAge"] = True elif feature == "Include Roblox username": includes["robloxUsername"] = True join_channel["verified"] = { "channel": str(channel.id), "message": text, "includes": includes } guild_data["joinChannel"] = join_channel await set_guild_value(guild, "joinChannel", join_channel) await self.r.table("guilds").insert(guild_data, conflict="replace").run() elif parsed_args_1 == "Disable": join_channel.pop("verified", None) guild_data["joinChannel"] = join_channel await set_guild_value(guild, "joinChannel", join_channel) await self.r.table("guilds").insert(guild_data, conflict="replace").run() change_text = f"**{'changed' if parsed_args_1 == 'Change message' else 'disabled'}**" await post_event( guild, guild_data, "configuration", f"{author.mention} ({author.id}) has {change_text} the `joinChannel` option for `verified` members.", BROWN_COLOR) raise Message(f"Successfully {change_text} your join message.", type="success")