import discord from discord.ext import commands from discord import app_commands import aiohttp import asyncio from io import BytesIO from PIL import Image import re from datetime import datetime, timezone ONE_MONTH = 2628000 quests_fetch = 0 quest_data = [] ACTIVITY_TYPE_NAMES = [ "Playing", "Streaming", "Listening to", "Watching", "Custom Status", "Competing in", "Hang Status", ] USER_FLAGS = { "STAFF": 1 << 0, "PARTNER": 1 << 1, "HYPESQUAD": 1 << 2, "BUG_HUNTER_LEVEL_1": 1 << 3, "HYPESQUAD_ONLINE_HOUSE_1": 1 << 6, "HYPESQUAD_ONLINE_HOUSE_2": 1 << 7, "HYPESQUAD_ONLINE_HOUSE_3": 1 << 8, "PREMIUM_EARLY_SUPPORTER": 1 << 9, "BUG_HUNTER_LEVEL_2": 1 << 14, "VERIFIED_DEVELOPER": 1 << 17, "CERTIFIED_MODERATOR": 1 << 18, "ACTIVE_DEVELOPER": 1 << 22, } APPLICATION_FLAGS = { "APPLICATION_COMMAND_BADGE": 1 << 23, "AUTO_MODERATION_RULE_CREATE_BADGE": 1 << 6, } BADGE_URLS = { "staff": "https://discord.com/company", "partner": "https://discord.com/partners", "certified_moderator": "https://discord.com/safety", "hypesquad": "https://discord.com/hypesquad", "hypesquad_house_1": "https://discord.com/settings/hypesquad-online", "hypesquad_house_2": "https://discord.com/settings/hypesquad-online", "hypesquad_house_3": "https://discord.com/settings/hypesquad-online", "bug_hunter_level_1": "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs", "bug_hunter_level_2": "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs", "active_developer": "https://support-dev.discord.com/hc/en-us/articles/10113997751447?ref=badge", "early_supporter": "https://discord.com/settings/premium", "premium": "https://discord.com/settings/premium", "bot_commands": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge", "quest_completed": "https://discord.com/settings/inventory", } BADGE_ICONS = { "staff": "<:discordstaff:1426051878155845702>", "partner": "<:discordpartner:1426051933608873986>", "certified_moderator": "<:discordmod:1426051921826943050>", "hypesquad": "<:hypesquadevents:1426051833536970852>", "hypesquad_house_1": "<:hypesquadbravery:1426051916739383297>", "hypesquad_house_2": "<:hypesquadbrilliance:1426051849433387068>", "hypesquad_house_3": "<:hypesquadbalance:1426051905179750495>", "bug_hunter_level_1": "<:discordbughunter1:1426052002193997895>", "bug_hunter_level_2": "<:discordbughunter2:1426052028257406987>", "active_developer": "<:activedeveloper:1426051981658685552>", "verified_developer": "<:discordbotdev:1426051827077480570>", "early_supporter": "<:discordearlysupporter:1426052023165517924>", "premium": "<:discordnitro:1426051911123206296>", "guild_booster_lvl1": "<:discordboost1:1426052007294144605>", "guild_booster_lvl2": "<:discordboost2:1426051986985582692>", "guild_booster_lvl3": "<:discordboost3:1426051991812964434>", "guild_booster_lvl4": "<:discordboost4:1426051955473645671>", "guild_booster_lvl5": "<:discordboost5:1426051960456609824>", "guild_booster_lvl6": "<:discordboost6:1426051976583712918>", "guild_booster_lvl7": "<:discordboost7:1426051965808410634>", "guild_booster_lvl8": "<:discordboost8:1426051844014342225>", "guild_booster_lvl9": "<:discordboost9:1426051855015743558>", "bot_commands": "<:supportscommands:1426051872476889171>", "automod": "<:automod:1426051939103146115>", "quest_completed": "<:quest:1426051817946611784>", "username": "<:username:1426051894371160115>", "premium_bot": "<:premiumbot:1426051888272638025>", "orb": "<:orb:1426051861605126289>", "bronze": "<:bronze:1426051866969772034>", "silver": "<:silver:1426051928575709286>", "gold": "<:gold:1426052012352737333>", "platinum": "<:platinum:1426052018040082545>", "diamond": "<:diamond:1426051944685895771>", "emerald": "<:emerald:1426051812313792537>", "ruby": "<:ruby:1426051838645637150>", "opal": "<:opal:1426051883247603762>", } ACTIVITY_TYPE_ICONS = { 0: "<:gaming:1426409065701048451>", 2: "<:music:1426409047132737586>", 3: "<:watching:1426409475450863778>", } def format_username(user): if user.discriminator and user.discriminator != "0": return f"{user.name}#{user.discriminator}" return f"@{user.name}" def get_default_avatar(user_id, discriminator=None): if discriminator and int(discriminator) > 0: index = int(discriminator) % 5 else: index = (int(user_id) >> 22) % 6 return f"https://cdn.discordapp.com/embed/avatars/{index}.png" def snowflake_to_timestamp(snowflake): return ((int(snowflake) >> 22) + 1420070400000) / 1000 async def fetch_quest_data(): global quests_fetch, quest_data async with aiohttp.ClientSession() as session: async with session.get( "https://raw.githubusercontent.com/aamiaa/discord-api-diff/refs/heads/main/quests.json" ) as resp: quest_data = await resp.json() quests_fetch = int(datetime.now().timestamp() * 1000) + 3600000 async def get_user_data(bot, user_id): headers = {"Authorization": f"Bot {bot.http.token}"} async with aiohttp.ClientSession() as session: async with session.get( f"https://discord.com/api/v10/users/{user_id}", headers=headers ) as resp: if resp.status == 404: return None return await resp.json() async def get_application_data(bot, app_id): headers = {"Authorization": f"Bot {bot.http.token}"} async with aiohttp.ClientSession() as session: async with session.get( f"https://discord.com/api/v10/applications/{app_id}/rpc", headers=headers ) as resp: if resp.status in [404, 403, 10002]: return None return await resp.json() async def get_published_listing(bot, sku_id): headers = {"Authorization": f"Bot {bot.http.token}"} async with aiohttp.ClientSession() as session: async with session.get( f"https://discord.com/api/v10/store/published-listings/skus/{sku_id}", headers=headers, ) as resp: if resp.status != 200: return None return await resp.json() def userinfo_command(): @commands.hybrid_command( name="userinfo", description="Get information on a user.", ) @app_commands.describe( user="User to get info for", user_id="User ID to get info for" ) async def userinfo(self, context, user: discord.User = None, user_id: str = None): await context.defer() bot = self.bot target_user = user if user else context.author if user_id: try: target_user = await bot.fetch_user(int(user_id)) except: await context.send("User not found.") return user_data = await get_user_data(bot, target_user.id) if not user_data: await context.send("Failed to fetch user data.") return guild = context.guild member = guild.get_member(target_user.id) if guild else None if int(datetime.now().timestamp() * 1000) > quests_fetch: try: await fetch_quest_data() except: pass badges = [] flags = user_data.get("public_flags", 0) if str(target_user.id) == "1015372540937502851": badges.append(f"[{BADGE_ICONS['staff']}]({BADGE_URLS['staff']})") badges.append(f"[{BADGE_ICONS['partner']}]({BADGE_URLS['partner']})") badges.append( f"[{BADGE_ICONS['certified_moderator']}]({BADGE_URLS['certified_moderator']})" ) badges.append( f"[{BADGE_ICONS['hypesquad_house_1']}]({BADGE_URLS['hypesquad_house_1']})" ) badges.append( f"[{BADGE_ICONS['bug_hunter_level_2']}]({BADGE_URLS['bug_hunter_level_2']})" ) badges.append(BADGE_ICONS["verified_developer"]) badges.append( f"[{BADGE_ICONS['early_supporter']}]({BADGE_URLS['early_supporter']})" ) badges.append( f"[{BADGE_ICONS['guild_booster_lvl9']}]({BADGE_URLS['premium']})" ) badges.append( f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})" ) badges.append(BADGE_ICONS["username"]) elif str(target_user.id) == "1376728824108286034": badges.append(BADGE_ICONS["automod"]) badges.append(BADGE_ICONS["verified_developer"]) badges.append(BADGE_ICONS["premium_bot"]) elif flags & USER_FLAGS["STAFF"]: badges.append(f"[{BADGE_ICONS['staff']}]({BADGE_URLS['staff']})") if flags & USER_FLAGS["PARTNER"]: badges.append(f"[{BADGE_ICONS['partner']}]({BADGE_URLS['partner']})") if flags & USER_FLAGS["CERTIFIED_MODERATOR"]: badges.append( f"[{BADGE_ICONS['certified_moderator']}]({BADGE_URLS['certified_moderator']})" ) if flags & USER_FLAGS["HYPESQUAD"]: badges.append(f"[{BADGE_ICONS['hypesquad']}]({BADGE_URLS['hypesquad']})") if flags & USER_FLAGS["HYPESQUAD_ONLINE_HOUSE_1"]: badges.append( f"[{BADGE_ICONS['hypesquad_house_1']}]({BADGE_URLS['hypesquad_house_1']})" ) if flags & USER_FLAGS["HYPESQUAD_ONLINE_HOUSE_2"]: badges.append( f"[{BADGE_ICONS['hypesquad_house_2']}]({BADGE_URLS['hypesquad_house_2']})" ) if flags & USER_FLAGS["HYPESQUAD_ONLINE_HOUSE_3"]: badges.append( f"[{BADGE_ICONS['hypesquad_house_3']}]({BADGE_URLS['hypesquad_house_3']})" ) if flags & USER_FLAGS["BUG_HUNTER_LEVEL_1"]: badges.append( f"[{BADGE_ICONS['bug_hunter_level_1']}]({BADGE_URLS['bug_hunter_level_1']})" ) if flags & USER_FLAGS["BUG_HUNTER_LEVEL_2"]: badges.append( f"[{BADGE_ICONS['bug_hunter_level_2']}]({BADGE_URLS['bug_hunter_level_2']})" ) if flags & USER_FLAGS["ACTIVE_DEVELOPER"]: badges.append( f"[{BADGE_ICONS['active_developer']}]({BADGE_URLS['active_developer']})" ) if flags & USER_FLAGS["VERIFIED_DEVELOPER"]: badges.append(BADGE_ICONS["verified_developer"]) if flags & USER_FLAGS["PREMIUM_EARLY_SUPPORTER"]: badges.append( f"[{BADGE_ICONS['early_supporter']}]({BADGE_URLS['early_supporter']})" ) avatar_hash = user_data.get("avatar", "") banner_hash = user_data.get("banner") if ( banner_hash or (avatar_hash and avatar_hash.startswith("a_")) ) and not user_data.get("bot"): badges.append(f"[{BADGE_ICONS['premium']}]({BADGE_URLS['premium']})") if member and member.premium_since: boosting_since = member.premium_since delta = (datetime.now(timezone.utc) - boosting_since).total_seconds() icon = BADGE_ICONS["guild_booster_lvl1"] if delta >= ONE_MONTH * 24: icon = BADGE_ICONS["guild_booster_lvl9"] elif delta >= ONE_MONTH * 18: icon = BADGE_ICONS["guild_booster_lvl8"] elif delta >= ONE_MONTH * 15: icon = BADGE_ICONS["guild_booster_lvl7"] elif delta >= ONE_MONTH * 12: icon = BADGE_ICONS["guild_booster_lvl6"] elif delta >= ONE_MONTH * 9: icon = BADGE_ICONS["guild_booster_lvl5"] elif delta >= ONE_MONTH * 6: icon = BADGE_ICONS["guild_booster_lvl4"] elif delta >= ONE_MONTH * 3: icon = BADGE_ICONS["guild_booster_lvl3"] elif delta >= ONE_MONTH * 2: icon = BADGE_ICONS["guild_booster_lvl2"] badges.append(f"[{icon}]({BADGE_URLS['premium']})") bot_deleted = False if user_data.get("bot"): app_data = await get_application_data(bot, target_user.id) if app_data: app_flags = app_data.get("flags", 0) if app_flags & APPLICATION_FLAGS["APPLICATION_COMMAND_BADGE"]: badges.append( f"[{BADGE_ICONS['bot_commands']}]({BADGE_URLS['bot_commands']})" ) if app_flags & APPLICATION_FLAGS["AUTO_MODERATION_RULE_CREATE_BADGE"]: badges.append(BADGE_ICONS["automod"]) else: bot_deleted = True if user_data.get("system"): bot_deleted = False quest_decoration_name = None avatar_decoration = user_data.get("avatar_decoration_data") if avatar_decoration and avatar_decoration.get("sku_id"): for quest in quest_data: config = quest.get("config", {}) rewards = config.get("rewards_config", {}).get( "rewards", [] ) or config.get("rewards", []) for reward in rewards: if ( reward.get("type") == 3 and reward.get("sku_id") == avatar_decoration["sku_id"] ): quest_decoration_name = ( reward.get("name") or reward.get("messages", {}).get("name") or "*Unknown*" ).replace("Avatar Decoration", "Avatar Deco") badges.append( f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})" ) break if quest_decoration_name: break elif avatar_decoration and ( avatar_decoration.get("expires_at") or avatar_decoration.get("sku_id") == "1226939756617793606" ): badges.append( f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})" ) if user_data.get("legacy_username"): badges.append(BADGE_ICONS["username"]) if user_data.get("bot") and user_data.get("approximated_guild_count"): badges.append(BADGE_ICONS["premium_bot"]) profile_effect = user_data.get("profile_effect") if profile_effect: effect_id = profile_effect.get("id") if effect_id: orb_tier = None if "1139323098643333240" in effect_id: orb_tier = "opal" elif "1139323095841308733" in effect_id: orb_tier = "ruby" elif "1139323090842013756" in effect_id: orb_tier = "emerald" elif "1139323087608832090" in effect_id: orb_tier = "diamond" elif "1144286544523669516" in effect_id: orb_tier = "platinum" elif "1139323084127289374" in effect_id: orb_tier = "gold" elif "1139323078435717220" in effect_id: orb_tier = "silver" elif "1139323075214307448" in effect_id: orb_tier = "bronze" else: orb_tier = "orb" if orb_tier: badges.append(BADGE_ICONS[orb_tier]) default_avatar = get_default_avatar( target_user.id, user_data.get("discriminator", "0") ) avatar_url = target_user.avatar.url if target_user.avatar else default_avatar banner_url = None banner_file = None original_banner_link = None if banner_hash: is_animated = banner_hash.startswith("a_") ext = "gif" if is_animated else "png" original_banner_url = f"https://cdn.discordapp.com/banners/{target_user.id}/{banner_hash}.{ext}?size=4096" original_banner_link = original_banner_url try: async with aiohttp.ClientSession() as session: async with session.get(original_banner_url) as resp: if resp.status == 200: banner_data = await resp.read() img = Image.open(BytesIO(banner_data)) if img.width < 1100: target_width = 1100 target_height = 440 img_aspect = img.width / img.height target_aspect = target_width / target_height if img_aspect > target_aspect: scale_height = target_height scale_width = int(scale_height * img_aspect) else: scale_width = target_width scale_height = int(scale_width / img_aspect) left = (scale_width - target_width) // 2 top = (scale_height - target_height) // 2 right = left + target_width bottom = top + target_height if is_animated: frames = [] durations = [] try: while True: frame = img.copy().convert("RGBA") frame = frame.resize( (scale_width, scale_height), Image.Resampling.LANCZOS, ) frame = frame.crop( (left, top, right, bottom) ) frames.append(frame) durations.append( img.info.get("duration", 100) ) img.seek(img.tell() + 1) except EOFError: pass output = BytesIO() frames[0].save( output, format="GIF", save_all=True, append_images=frames[1:], duration=durations, loop=0, optimize=False, ) output.seek(0) banner_file = discord.File( output, filename="banner.gif" ) banner_url = "attachment://banner.gif" else: img = img.resize( (scale_width, scale_height), Image.Resampling.LANCZOS, ) img = img.crop((left, top, right, bottom)) output = BytesIO() img.save(output, format="PNG") output.seek(0) banner_file = discord.File( output, filename="banner.png" ) banner_url = "attachment://banner.png" else: banner_url = original_banner_url except: banner_url = original_banner_url images = [f"[Avatar]({avatar_url})"] if banner_url: images.append(f"[Banner]({original_banner_link})") if avatar_decoration: await get_published_listing(bot, avatar_decoration["sku_id"]) decoration_url = f"https://cdn.discordapp.com/avatar-decoration-presets/{avatar_decoration['asset']}.png?size=4096&passthrough=true" images.append(f"[Avatar Deco]({decoration_url})") collectibles = user_data.get("collectibles") if collectibles and collectibles.get("nameplate"): nameplate = collectibles["nameplate"] nameplate_asset = nameplate["asset"] images.append( f"[Nameplate](https://cdn.discordapp.com/assets/collectibles/{nameplate_asset}static.png)" ) mutual_guilds = [g for g in bot.guilds if g.get_member(target_user.id)] display_name = user_data.get("global_name") or user_data.get("username") desc_lines = [ f"# {member.nick if member and member.nick else display_name}", f"{format_username(target_user).replace('@', '')} • <@{target_user.id}>", ] subline = "" if badges: subline += "".join(badges) activity_lines = [] if member and member.activities: for activity in member.activities: if activity.type == discord.ActivityType.custom: activity_lines.append(ACTIVITY_TYPE_NAMES[4]) elif activity.type in [ discord.ActivityType.playing, discord.ActivityType.listening, discord.ActivityType.watching, ]: name = activity.name activity_lines.append( f"{ACTIVITY_TYPE_ICONS.get(activity.type.value, '')} {ACTIVITY_TYPE_NAMES[activity.type.value]} **{name}**".strip() ) if subline: desc_lines.append(subline) if mutual_guilds: desc_lines.append( f"-# {len(mutual_guilds)} Bot Mutual Server{'s' if len(mutual_guilds) > 1 else ''}" ) else: desc_lines.append("") is_system = user_data.get("system") or user_data.get("discriminator") == "0000" if bot_deleted and not is_system: desc_lines.append( "*This bot's application has been deleted*\n-# (or app ID and user ID desync)" ) if is_system: desc_lines.append("**System account**") desc_lines.append("") if activity_lines: desc_lines.extend(activity_lines) embed = discord.Embed(color=0x7289DA, description="\n".join(desc_lines)) primary_guild = user_data.get("primary_guild") if primary_guild and primary_guild.get("identity_guild_id"): clan_badge_url = f"https://cdn.discordapp.com/clan-badges/{primary_guild['identity_guild_id']}/{primary_guild['badge']}.png?size=4096" embed.set_author(name=primary_guild.get("tag", ""), icon_url=clan_badge_url) else: embed.set_author( name="User Information", icon_url="https://yes.nighty.works/raw/gSxqzV.png", ) if member and member.nick and member.nick != display_name: embed.title = display_name embed.set_thumbnail(url=avatar_url) if banner_url: embed.set_image(url=banner_url) created_timestamp = int(snowflake_to_timestamp(target_user.id)) created_date = f"" embed.add_field(name="Created Date", value=created_date, inline=False) if member and hasattr(member, "joined_at") and member.joined_at: joined_timestamp = int(member.joined_at.timestamp()) join_date = f"" embed.add_field(name="Join Date", value=join_date, inline=False) is_bot = user_data.get("bot", False) embed.add_field(name="Is Bot", value="True" if is_bot else "False", inline=True) if member and member.roles[1:]: roles = sorted(member.roles[1:], key=lambda r: r.position, reverse=True) role_mentions = [f"<@&{r.id}>" for r in roles] role_list = " ".join(role_mentions) if len(role_list) > 1024: truncated_roles = [] current_length = 0 for mention in role_mentions: if current_length + len(mention) + 1 > 1020: break truncated_roles.append(mention) current_length += len(mention) + 1 role_list = " ".join(truncated_roles) + " ..." embed.add_field( name=f"Roles ({len(member.roles) - 1})", value=role_list, inline=False ) if images: embed.add_field(name="\u200b", value="\u3000".join(images), inline=False) embed.set_footer(text=f"ID: {target_user.id}") if banner_file: await context.send(embed=embed, file=banner_file) else: await context.send(embed=embed) return userinfo