async def update_nickname(self, name): await self.wait_until_ready() log.debug( f"{self.user} ({self.pk_member_id}): Updating nickname in guilds") return_value = 0 if len(name or "") > 32: return -1 if name is None or name == "": name = self.user.display_name[2:] conn.execute( "UPDATE members SET nickname = ? WHERE id == ?", [name, self.user.id], ) conn.commit() for guild in self.guilds: try: await guild.get_member(self.user.id).edit(nick=name) log.debug( f"{self.user} ({self.pk_member_id}): Updated nickname to {name} on guild {guild.name}" ) except AttributeError: log.debug( f"{self.user} ({self.pk_member_id}): Failed to update nickname to {name} on guild {guild.name}" ) return_value += 1 pass return return_value
async def disable(self, ctx: commands.context, system_member: discord.Member): """ Disables a system member permanently by deleting it from the database and kicking it from the server. Bot token cannot be reused. :param ctx: Discord Context :param system_member: System Member """ await ctx.message.delete() member = conn.execute( "SELECT * FROM members WHERE id = ?", [system_member.id], ).fetchone() if member is not None: log.debug(f"Disabling {system_member}") confirmation = BotConfirmation(ctx, discord.Color.red()) await confirmation.confirm( f"Disable member __{system_member}__ **permanently?**") if confirmation.confirmed: await confirmation.message.delete() conn.execute( "DELETE FROM members WHERE id = ?", [system_member.id], ) conn.commit() await ctx.send(embed=discord.Embed( description= f":ballot_box_with_check: {system_member.mention} **permanently disabled** by {ctx.author.mention}", color=discord.Color.dark_green(), )) log.info( f"{system_member} has been permanently by {ctx.message.author}" ) else: await confirmation.message.delete() await ctx.send( embed=discord.Embed( description= f":information_source: {system_member.mention} was __not__ disabled", color=discord.Color.blue(), ), delete_after=10, ) log.info(f"{system_member} not disabled") else: await ctx.send( embed=discord.Embed( description= f":x: {system_member.mention} not found in database", color=discord.Color.red(), ), delete_after=10, )
async def edit( self, ctx: commands.context, message: Optional[discord.Message] = None, *, content: str, ): """ edit (message id) [message]: Edits the last message or message with ID :param ctx: Discord Context :param message: (optional) ID of message :param content: Message Content """ await ctx.message.delete() if message is not None: member = conn.execute( "SELECT * FROM members WHERE main_account_id == ? AND id == ? AND member_enabled = 1", [ctx.author.id, message.author.id], ).fetchone() if member: log.debug( f"Editing message {message.id} by {message.author} for {ctx.author}" ) while await helper.edit_as( message, content, member["token"], ) is False: await reset() else: log.debug(f"Editing last Polyphony message for {ctx.author}") member_ids = [ member["id"] for member in conn.execute( "SELECT * FROM members WHERE main_account_id == ?", [ctx.author.id], ).fetchall() ] async for message in ctx.channel.history(): if message.author.id in member_ids: while await helper.edit_as( message, content, conn.execute( "SELECT * FROM members WHERE id == ?", [message.author.id], ).fetchone()["token"], ) is False: await reset() break
async def on_reaction_add(self, reaction: discord.Reaction, user: discord.Member): member = conn.execute( "SELECT * FROM members WHERE main_account_id == ? AND id = ? AND member_enabled = 1", [user.id, reaction.message.author.id], ).fetchone() # Check for correct user if member is not None: # Delete React if type(reaction.emoji) is str: if (emoji.demojize(reaction.emoji) or "") == ":cross_mark:": # Discord name: x await reaction.message.delete() # Edit React if (emoji.demojize(reaction.emoji) or "") == ":memo:": # Discord name: pencil self.edit_session.append(user.id) embed = discord.Embed( description= f"You are now editing a [message]({reaction.message.jump_url})\nYour next message will replace it's contents.", color=discord.Color.orange(), ) embed.set_footer(text='Type "cancel" to cancel edit') instructions = await reaction.message.channel.send( f"{user.mention}", embed=embed) try: # Wait 30 seconds for new message message = await self.bot.wait_for( "message", check=lambda message: message.author.id == member[ "main_account_id"], timeout=30, ) # On new message, do all the things # If message isn't "cancel" then momentarily switch bot tokens and edit the message if message.content.lower() != "cancel": while await helper.edit_as( reaction.message, message.content, member["token"]) is False: await reset() # Delete instructions and edit message with main bot (again, low-level is easier without ctx) await instructions.delete() # bot.http.delete_message(instructions.channel.id, instructions.id), await message.delete() # bot.http.delete_message(message.channel.id, message.id), await reaction.remove(user) # On timeout, delete instructions and reaction except asyncio.TimeoutError: # Delete instructions with main bot await asyncio.gather(instructions.delete(), reaction.remove(user)) self.edit_session.remove(user.id)
async def enable(self, ctx: commands.context, system_member: discord.Member): """ Reintroduce a suspended instance into the wild :param ctx: Discord Context :param system_member: System Member """ await ctx.message.delete() member = conn.execute( "SELECT * FROM members WHERE id = ?", [system_member.id], ).fetchone() if member is not None: if member["member_enabled"] == 1: await ctx.send( embed=discord.Embed( description= f":information_source: {system_member.mention} already enabled", color=discord.Color.blue(), ), delete_after=10, ) else: conn.execute( "UPDATE members SET member_enabled = 1 WHERE id = ?", [system_member.id], ) conn.commit() await ctx.send(embed=discord.Embed( description= f":white_check_mark: {system_member.mention} enabled by {ctx.author.mention}", color=discord.Color.green(), )) log.info( f"{system_member} has been enabled by {ctx.message.author}" ) else: await ctx.send( embed=discord.Embed( description= f":x: {system_member.mention} not found in database", color=discord.Color.red(), ), delete_after=10, )
async def syncall(self, ctx: commands.context): if ctx.invoked_subcommand is not None: return await sync( ctx, conn.execute( "SELECT * FROM members WHERE member_enabled = 1").fetchall(), )
async def nick(self, ctx: commands.context, ctx_member: discord.Member, *, nickname: str = ""): member = conn.execute( "SELECT * FROM members WHERE main_account_id == ? AND id == ?", [ctx.author.id, ctx_member.id], ).fetchone() if member is not None: with ctx.channel.typing(): embed = discord.Embed( description= f":hourglass: {'Updating' if nickname != '' else 'Clearing'} nickname for {ctx_member.mention}...", color=discord.Color.orange(), ) embed.set_author(name=ctx_member.display_name, icon_url=ctx_member.avatar_url) status_msg = await ctx.channel.send(embed=embed) # Create instance instance = PolyphonyInstance(member["pk_member_id"]) asyncio.run_coroutine_threadsafe( instance.start(member["token"]), self.bot.loop) await instance.wait_until_ready() out = await instance.update_nickname(nickname) await instance.close() if out < 0: embed = discord.Embed( title=f":x: **Could not update nickname**\n", description= f":warning: Nickname `{(nickname[:42] + '...') if len(nickname or '') > 42 else nickname}` is too long", color=discord.Color.red(), ) embed.set_author(name=ctx_member.display_name, icon_url=ctx_member.avatar_url) embed.set_footer( text="Nicknames must be 32 characters or less") else: embed = discord.Embed( description= f":white_check_mark: Nickname {'updated' if nickname != '' else 'cleared'} for {ctx_member.mention}", color=discord.Color.green(), ) embed.set_author(name=ctx_member.display_name, icon_url=ctx_member.avatar_url) if out > 0: embed.set_footer( text="With errors: did not update in all guilds") await status_msg.edit(embed=embed)
async def member(self, ctx: commands.context, system_member: discord.User): """ Sync system member with PluralKit :param system_member: User to sync :param ctx: Discord Context """ await sync( ctx, conn.execute("SELECT * FROM members WHERE id = ?", [system_member.id]).fetchall(), )
async def system(self, ctx: commands.context, main_user: discord.User): """ Sync system members with PluralKit :param main_user: User to sync for :param ctx: Discord Context """ await sync( ctx, conn.execute("SELECT * FROM members WHERE main_account_id = ?", [main_user.id]).fetchall(), )
async def sync(self, ctx: commands.context): """ Sync system members with PluralKit :param ctx: Discord Context """ if ctx.invoked_subcommand is not None: return await sync( ctx, conn.execute("SELECT * FROM members WHERE main_account_id = ?", [ctx.author.id]).fetchall(), )
async def delete( self, ctx: commands.context, message: Optional[discord.Message] = None, ): """ del (id): Deletes the last message unless a message ID parameter is provided. Can be run multiple times. n max limited by config. :param ctx: Discord Context :param message: ID of message to delete """ await ctx.message.delete() if message is not None: member = conn.execute( "SELECT * FROM members WHERE main_account_id == ? AND id == ? AND member_enabled = 1", [ctx.author.id, message.author.id], ).fetchone() if member: log.debug( f"Deleting message {message.id} by {message.author} for {ctx.author}" ) message = await self.bot.get_channel(message.channel.id ).fetch_message(message.id ) await message.delete() else: log.debug(f"Deleting last Polyphony message for {ctx.author}") member_ids = [ member["id"] for member in conn.execute( "SELECT * FROM members WHERE main_account_id == ?", [ctx.author.id], ).fetchall() ] async for message in ctx.channel.history(): if message.author.id in member_ids: await message.delete() break
async def autoproxy(self, ctx: commands.context, arg: Union[discord.Member, str]): await ctx.message.delete() embed = None if (type(arg) is discord.Member and conn.execute( "SELECT * FROM members WHERE main_account_id == ? AND id == ?", [ctx.author.id, arg.id], ).fetchone() is not None): conn.execute( "UPDATE users SET autoproxy_mode = 'member', autoproxy = ? WHERE id == ?", [arg.id, ctx.author.id], ) conn.commit() embed = discord.Embed( description=f"Autoproxy set to {arg.mention}") embed.set_footer(text="Use ;;ap off to turn autoproxy off") elif arg == "latch": conn.execute( "UPDATE users SET autoproxy_mode = 'latch', autoproxy = NULL WHERE id == ?", [ctx.author.id], ) conn.commit() embed = discord.Embed( description=f"Autoproxy set to **latch mode**") embed.set_footer(text="Use ;;ap off to turn autoproxy off") elif arg == "off": conn.execute( "UPDATE users SET autoproxy_mode = NULL WHERE id == ?", [ctx.author.id], ) conn.commit() embed = discord.Embed(description=f"Autoproxy is now **off**") if embed is None and type(arg) is discord.Member: embed = discord.Embed( description=f"{arg.mention} is not a member of your system", color=discord.Color.red(), ) embed.set_author(name=ctx.author, icon_url=ctx.author.avatar_url) await ctx.send(embed=embed, delete_after=10)
async def on_message(self, msg: discord.Message): start = time.time() # For benchmark debug message # Cancel if using bot command prefix (allows bot commands to run) if msg.content.startswith(COMMAND_PREFIX): return # Skip for edit react if msg.author.id in self.edit_session: return # Get the system system = conn.execute( "SELECT * FROM members WHERE main_account_id == ? AND member_enabled = 1", [msg.author.id], ).fetchall() ping_suppress = False # Set to suppress ping. Used to prevent double ping forwarding with a proxied ping. # Set datastructures member_data = { "prefix": None, "suffix": None, } member = None # Compile tags with member objects tags = [] for m in system: for t in json.loads(m["pk_proxy_tags"]): tags.append([t, m]) # Check tags and set member for tag in tags: if msg.content.startswith(tag[0].get("prefix") or "") and msg.content.endswith( tag[0].get("suffix") or ""): member_data["prefix"] = tag[0].get("prefix") or "" member_data["suffix"] = tag[0].get("suffix") or "" member = tag[1] break # Get autoproxy status ap_data = {"mode": None, "user": None} db_user = conn.execute("SELECT * FROM users WHERE id == ?", [msg.author.id]).fetchone() if db_user is not None: ap_data["mode"] = db_user["autoproxy_mode"] ap_data["user"] = db_user["autoproxy"] # Check for autoproxy if member is None and ap_data["mode"] is not None: member = conn.execute( "SELECT * FROM members WHERE id == ? AND member_enabled = 1", [ap_data["user"]], ).fetchone() # Send message if member is set if member is not None: log.debug( f"""{member['member_name']} ({member["pk_member_id"]}): Processing new message in {msg.channel} => "{msg.content}" (attachments: {len(msg.attachments)})""" ) # Set autoproxy latch if ap_data["mode"] == "latch" and ( member_data["prefix"] is not None or member_data["suffix"] is not None): log.debug( f"Setting autoproxy latch to {member['display_name']}") # Update database to remember current latch conn.execute( "UPDATE users SET autoproxy = ? WHERE id == ?", [member["id"], msg.author.id], ) conn.commit() # Remove prefix/suffix message = msg.content[len(member_data["prefix"] or "") or None: -len(member_data["suffix"] or "") or None] # Send proxied message while await helper.send_as( msg, message, member["token"], files=[await file.to_file() for file in msg.attachments], reference=msg.reference, ) is False: await reset() await msg.delete() # Server log channel message deletion handler (cleans up logging channel) new_proxied_message(msg) end = time.time() # For benchmarking purposes log.debug( f"{member['member_name']} ({member['pk_member_id']}): Benchmark: {timedelta(seconds=end - start)} | Protocol Roundtrip: {timedelta(seconds=self.bot.latency)}" ) ping_suppress = True # Message was proxied so suppress it # Check for ping all_members = conn.execute( "SELECT * FROM members WHERE member_enabled = 1").fetchall() # Get the database entry for the current member (if any) msg_member = conn.execute("SELECT * FROM members WHERE id = ?", [msg.author.id]).fetchone() # Get the system of the current member (if any) if msg_member is not None: system = conn.execute( "SELECT * FROM members WHERE main_account_id = ?", [msg_member["main_account_id"]], ).fetchall() # If a message was proxied, the ping is suppressed to avoid a double-ping from the instance and the original message if not ping_suppress: for member in all_members: # Check for a valid ping if (member["id"] in [m.id for m in msg.mentions] and msg.author.id != member["main_account_id"] and msg.author.id != member["id"] and msg.author.id != self.bot.user.id and not msg.content.startswith(COMMAND_PREFIX)): embed = discord.Embed( description= f"Originally to {self.bot.get_user(member['id']).mention}\n[Highlight Message]({msg.jump_url})" ) embed.set_author( name=f"From {msg.author}", icon_url=msg.author.avatar_url, ) # Check if ping is from another Polyphony instance if msg.author.bot is True and msg.author.id in [ m["id"] for m in all_members ]: # Check member isn't part of author's own system if member["id"] not in [m["id"] for m in system]: # Forward Ping from Instance log.debug( f"Forwarding ping from {member['id']} to {member['main_account_id']} (from proxy)" ) await self.bot.get_channel(msg.channel.id).send( f"{self.bot.get_user(member['main_account_id']).mention}", embed=embed, ) else: # Forward Ping from non-polyphony instance log.debug( f"Forwarding ping from {member['id']} to {member['main_account_id']}" ) await self.bot.get_channel(msg.channel.id).send( f"{self.bot.get_user(member['main_account_id']).mention}", embed=embed, ) break # Delete logging channel message if DELETE_LOGS_USER_ID is not None and DELETE_LOGS_CHANNEL_ID is not None: if (msg.channel.id == DELETE_LOGS_CHANNEL_ID or msg.author.id == DELETE_LOGS_USER_ID): try: embed_text = msg.embeds[0].description except IndexError: return for oldmsg in recently_proxied_messages: member_ids = [m["id"] for m in system] if str(oldmsg.id) in embed_text and not any([ str(member_id) in embed_text for member_id in member_ids ]): log.debug( f"Deleting delete log message {msg.id} (was about {oldmsg.id})" ) await msg.delete()
async def register( self, ctx: commands.context, pluralkit_member_id: str, account: discord.Member, ): """ Creates a new Polyphony member instance :param ctx: Discord Context :param pluralkit_member_id: PluralKit system or member id :param account: Main Account to be extended from """ log.debug("Registering new member...") logger = LogMessage(ctx, title="Registering...") await logger.init() async with ctx.channel.typing(): # Error: Account is not a user if account.bot is True: await logger.set( title="Error Registering: Bad Account Pairing", color=discord.Color.red(), ) await logger.log(f"{account.mention} is a bot user") return # Get available tokens token = conn.execute( "SELECT * FROM tokens WHERE used == 0").fetchone() # Error: No Slots Available if not token: await logger.set( title=":x: Error Registering: No Slots Available", color=discord.Color.red(), ) await logger.log( f":x: No tokens in queue. Run `{self.bot.command_prefix}tokens` for information on how to add more." ) return # Error: Duplicate Registration check_duplicate = conn.execute( "SELECT * FROM members WHERE pk_member_id == ?", [pluralkit_member_id], ).fetchone() if check_duplicate: await logger.set( title=":x: Error Registering: Member Already Registered", color=discord.Color.red(), ) await logger.log( f":x: Member ID `{pluralkit_member_id}` is already registered with instance {self.bot.get_user(check_duplicate['id'])}" ) return # Fetch member from PluralKit await logger.log(":hourglass: Fetching member from PluralKit...") member = await pk_get_member(pluralkit_member_id) # Error: Member not found if member is None: await logger.set( title=":x: Error Registering: Member ID invalid", color=discord.Color.red(), ) await logger.log( f":x: Member ID `{pluralkit_member_id}` was not found") return # Error: Missing PluralKit Data if (member["name"] is None or member["avatar_url"] is None or member["proxy_tags"] is None): await logger.set( title=":x: Error Registering: Missing PluralKit Data", color=discord.Color.red(), ) if member["name"] is None: await logger.log(":warning: Member is missing a name") if member["avatar_url"] is None: await logger.log(":warning: Member is missing an avatar") if member["proxy_tags"] is None: await logger.log(":warning: Member is missing proxy tags") await logger.log( "\n:x: *Please check the privacy settings on PluralKit*") return system_name = f"__**{member['name']}**__ (`{member['id']}`)" await logger.edit( -1, f":white_check_mark: Fetched member __**{member['name']}**__ (`{member['id']}`)", ) # Confirm add confirmation = BotConfirmation(ctx, discord.Color.blue()) await confirmation.confirm( f":grey_question: Create member for {account} with member {system_name}?" ) if confirmation.confirmed: await confirmation.message.delete() else: await confirmation.message.delete() await logger.set(title=":x: Registration Cancelled", color=discord.Color.red()) await logger.log(":x: Registration cancelled by user") return # Check if user is new to Polyphony if (conn.execute("SELECT * FROM users WHERE id = ?", [account.id]).fetchone() is None): await logger.log( f":tada: {account.mention} is a new user! Registering them with Polyphony" ) conn.execute("INSERT INTO users VALUES (?, NULL, NULL)", [account.id]) conn.commit() # Insert member into database await logger.log(":hourglass: Adding to database...") try: insert_member( token["token"], member["id"], account.id, decode_token(token["token"]), member["name"], member["display_name"], member["avatar_url"], member["proxy_tags"], member["keep_proxy"], member_enabled=True, ) await logger.edit(-1, ":white_check_mark: Added to database") # Error: Database Error except sqlite3.Error as e: log.error(e) await logger.set( title=":x: Error Registering: Database Error", color=discord.Color.red(), ) await logger.edit(-1, ":x: An unknown database error occurred") return # Mark token as used conn.execute( "UPDATE tokens SET used = 1 WHERE token = ?", [token["token"]], ) conn.commit() # Create Instance await logger.log(":hourglass: Syncing Instance...") instance = PolyphonyInstance(pluralkit_member_id) asyncio.run_coroutine_threadsafe(instance.start(token["token"]), self.bot.loop) await instance.wait_until_ready() sync_error_text = "" # Update Username await logger.edit(-1, f":hourglass: Syncing Username...") out = await instance.update_username(member["name"]) if out != 0: sync_error_text += f"> {out}\n" # Update Avatar URL await logger.edit(-1, f":hourglass: Syncing Avatar...") out = await instance.update_avatar(member["avatar_url"]) if out != 0: sync_error_text += f"> {out}\n" # Update Nickname await logger.edit(-1, f":hourglass: Syncing Nickname...") out = await instance.update_nickname(member["display_name"]) if out < 0: sync_error_text += f"> PluralKit display name must be 32 or fewer in length if you want to use it as a nickname" elif out > 0: sync_error_text += f"> Nick didn't update on {out} guild(s)\n" # Update Roles await logger.edit(-1, f":hourglass: Updating Roles...") out = await instance.update_default_roles() if out: sync_error_text += f"> {out}\n" if sync_error_text == "": await logger.edit(-1, ":white_check_mark: Synced instance") else: await logger.edit(-1, ":warning: Synced instance with errors:") await logger.log(sync_error_text) # Success State logger.content = [] await logger.set( title=f":white_check_mark: Registered __{member['name']}__", color=discord.Color.green(), ) slots = conn.execute("SELECT * FROM tokens WHERE used = 0").fetchall() await logger.log(f":arrow_forward: **User is {instance.user.mention}**" ) if sync_error_text != "": await logger.log(":warning: Synced instance with errors:") await logger.log(sync_error_text) await logger.log(f"*There are now {len(slots)} slots available*") log.info( f"{instance.user} ({instance.pk_member_id}): New member instance registered ({len(slots)} slots left)" ) await instance.close()
async def tokens(self, ctx: commands.context, *tokens: str): """ Add tokens to queue :param ctx: Discord Context :param tokens: List of tokens """ async def session(self, author: discord.Member): self.token_session.append(author) await asyncio.sleep(300) self.token_session.remove(author) if (ctx.channel.type is discord.ChannelType.private and ctx.message.author in self.token_session): await ctx.send("Adding tokens...") for index, token in enumerate(tokens): logger = LogMessage(ctx, title=f"Adding token #{index+1}...") await logger.init() # Check token await logger.log(f"Checking token #{index+1}...") all_tokens = conn.execute("SELECT * FROM tokens").fetchall() chk = False token_client = decode_token(token) for chk_token in all_tokens: if decode_token(str(chk_token["token"])) == token_client: chk = True check_result, client_id = await check_token(token) if chk: logger.title = f"Token #{index + 1} Client ID already in database" logger.color = discord.Color.red() await logger.log("Client ID already exists in database") elif not check_result: logger.title = f"Token #{index+1} Invalid" logger.color = discord.Color.red() await logger.log("Bot token is invalid") else: await logger.log("Token valid") if (conn.execute("SELECT * FROM tokens WHERE token = ?", [token]).fetchone() is None): conn.execute("INSERT INTO tokens VALUES(?, ?)", [token, False]) conn.commit() logger.title = f"Bot token #{index+1} added" logger.color = discord.Color.green() c.execute("SELECT * FROM tokens WHERE used = 0") slots = c.fetchall() from polyphony.bot import bot await logger.send( f"[Invite to Server]({discord.utils.oauth_url(client_id, permissions=discord.Permissions(DEFAULT_INSTANCE_PERMS), guild=bot.get_guild(GUILD_ID))})\n\n**Client ID:** {client_id}\nThere are now {len(slots)} slot(s) available" ) log.info( f"New token added by {ctx.author} (There are now {len(slots)} slots)" ) else: logger.title = f"Token #{index+1} already in database" logger.color = discord.Color.orange() await logger.log("Bot token already in database") elif ctx.channel.type is not discord.ChannelType.private: await ctx.message.delete() if any(role.name in MODERATOR_ROLES for role in ctx.message.author.roles) or ctx.bot.is_owner( ctx.author): try: await ctx.message.author.send( f"Token mode enabled for 5 minutes. Add tokens with `{self.bot.command_prefix}tokens [token] (more tokens...)` right here.\n\n*Don't paste a bot token in a server*" ) except discord.errors.Forbidden: await ctx.send("Enable server DMs to use token command", delete_after=10.0) await session(self, ctx.message.author) elif any(role.name in MODERATOR_ROLES for role in ctx.message.author.roles) or ctx.bot.is_owner( ctx.author): await ctx.channel.send( f"To add tokens, execute `{self.bot.command_prefix}tokens` as a moderator on a server **WITHOUT A BOT TOKEN**. Then in DMs, use `{self.bot.command_prefix}tokens [token] (more tokens...)`\n\n*Seriously don't paste a bot token in a server*", delete_after=10.0, )
async def sync( ctx: commands.context, query: List[sqlite3.Row], message=":hourglass: Syncing Members", ) -> NoReturn: logger = LogMessage(ctx, message) logger.color = discord.Color.orange() await logger.init() for i, member in enumerate(query): # Create instance instance = PolyphonyInstance(member["pk_member_id"]) from polyphony.bot import bot asyncio.run_coroutine_threadsafe(instance.start(member["token"]), bot.loop) await instance.wait_until_ready() log.debug(f"Syncing {instance.user}") await logger.log( f":hourglass: Syncing {instance.user.mention}... ({i+1}/{len(query)})" ) # Pull from PluralKit pk_member = await pk_get_member(member["pk_member_id"]) if pk_member is None: await logger.edit( -1, f":x: Failed to sync {instance.user.mention} from PluralKit") log.debug(f"Failed to sync {instance.user}") await instance.close() continue await instance.wait_until_ready() error_text = "" # Update Proxy Tags conn.execute( "UPDATE members SET pk_proxy_tags = ? WHERE pk_member_id = ?", [json.dumps(pk_member.get("proxy_tags")), member["pk_member_id"]], ) # Update Username if (instance.user.display_name != pk_member.get("name") and pk_member.get("name") is not None): await logger.edit( -1, f":hourglass: Syncing {instance.user.mention} Username... ({i}/{len(query)})", ) conn.execute( "UPDATE members SET display_name = ? WHERE pk_member_id = ?", [pk_member.get("name"), member["pk_member_id"]], ) out = await instance.update_username(pk_member.get("name")) if out != 0: error_text += f"> {out}\n" # Update Avatar URL if pk_member.get("avatar_url") is not None: await logger.edit( -1, f":hourglass: Syncing {instance.user.mention} Avatar... ({i}/{len(query)})", ) conn.execute( "UPDATE members SET pk_avatar_url = ? WHERE pk_member_id = ?", [pk_member.get("avatar_url"), member["pk_member_id"]], ) out = await instance.update_avatar(pk_member.get("avatar_url")) if out != 0: error_text += f"> {out}\n" # Update Nickname # Check if nickname is set await logger.edit( -1, f":hourglass: Syncing {instance.user.mention} Nickname... ({i}/{len(query)})", ) if member["nickname"] != None: out = await instance.update_nickname(member["nickname"]) if out < 0: error_text += f"> Nickname must be 32 characters or fewer in length\n" elif out > 0: error_text += f"> Nick didn't update on {out} guild(s)\n" # Otherwise use display_name if it exists else: conn.execute( "UPDATE members SET display_name = ? WHERE pk_member_id = ?", [pk_member.get("display_name"), member["pk_member_id"]], ) out = await instance.update_nickname( pk_member.get("display_name") or pk_member.get("name")) if out < 0: error_text += f"> PluralKit display name must be 32 characters or fewer in length if you want to use it as a nickname\n" elif out > 0: error_text += f"> Nick didn't update on {out} guild(s)\n" # Update Roles await logger.edit( -1, f":hourglass: Syncing {instance.user.mention} Roles... ({i}/{len(query)})", ) out = await instance.update_default_roles() if out: error_text += f"> {out}\n" if error_text == "": await logger.edit( -1, f":white_check_mark: Synced {instance.user.mention}") else: logger.content[ -1] = f":warning: Synced {instance.user.mention} with errors:" await logger.log( error_text if error_text.endswith("\n") else f"{error_text}\n") conn.commit() log.debug(f"Synced {instance.user}") await instance.close() await logger.set(":white_check_mark: Sync Complete", discord.Color.green())