From 0693950468dda67350429bc63dfc6d16d9eeb279 Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 10 Oct 2025 12:21:34 -0400 Subject: [PATCH 1/9] feat(userinfo): initial commit --- cogs/general/__init__.py | 25 +- cogs/general/userinfo.py | 608 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 630 insertions(+), 3 deletions(-) create mode 100644 cogs/general/userinfo.py diff --git a/cogs/general/__init__.py b/cogs/general/__init__.py index c82a93a..56826ff 100644 --- a/cogs/general/__init__.py +++ b/cogs/general/__init__.py @@ -1,4 +1,5 @@ import discord +from discord import app_commands from discord.ext import commands from discord.ext.commands import Context @@ -6,6 +7,7 @@ from .ping import ping_command from .uptime import uptime_command from .serverinfo import serverinfo_command from .feedback import feedback_command +from .userinfo import userinfo_command def _require_group_prefix(context: Context) -> bool: @@ -31,13 +33,13 @@ class General(commands.GroupCog, name="general"): color=0x7289DA ) embed.set_author(name="General", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - embed.add_field(name="Available", value="ping, uptime, serverinfo, feedback", inline=False) + embed.add_field(name="Available", value="ping, uptime, serverinfo, userinfo, feedback", inline=False) await context.send(embed=embed) - async def _invoke_hybrid(self, context: Context, name: str): + async def _invoke_hybrid(self, context: Context, name: str, **kwargs): command = self.bot.get_command(name) if command is not None: - await context.invoke(command) + await context.invoke(command, **kwargs) else: await context.send(f"Unknown general command: {name}") @@ -53,6 +55,10 @@ class General(commands.GroupCog, name="general"): async def general_group_serverinfo(self, context: Context): await self._invoke_hybrid(context, "serverinfo") + @general_group.command(name="userinfo") + async def general_group_userinfo(self, context: Context, user: discord.User = None, user_id: str = None): + await self._invoke_hybrid(context, "userinfo", user=user, user_id=user_id) + @general_group.command(name="feedback") async def general_group_feedback(self, context: Context): await self._invoke_hybrid(context, "feedback") @@ -81,6 +87,18 @@ class General(commands.GroupCog, name="general"): async def serverinfo(self, context): return await serverinfo_command()(self, context) + @commands.check(_require_group_prefix) + @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): + return await userinfo_command()(self, context, user=user, user_id=user_id) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="feedback", @@ -96,4 +114,5 @@ async def setup(bot) -> None: bot.logger.info("Loaded extension 'general.ping'") bot.logger.info("Loaded extension 'general.uptime'") bot.logger.info("Loaded extension 'general.serverinfo'") + bot.logger.info("Loaded extension 'general.userinfo'") bot.logger.info("Loaded extension 'general.feedback'") diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py new file mode 100644 index 0000000..2cdf30b --- /dev/null +++ b/cogs/general/userinfo.py @@ -0,0 +1,608 @@ +import discord +from discord.ext import commands +from discord import app_commands +import aiohttp +import asyncio +from io import BytesIO +from PIL import Image, ImageDraw +import colorsys +import re +from datetime import datetime, timezone + +ONE_MONTH = 2628000 + +vencord_fetch = 0 +vencord_badges = {} +vencord_contributors = set() + +quests_fetch = 0 +quest_data = [] + +REGEX_DEVS = re.compile(r'id: (\d+)n(,\n\s+badge: false)?') + +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: '<:i:1392584288515395635>', + 2: '<:i:1392584301367001148>', + 3: '<:i:1392584313786208296>' +} + +def murmurhash3_32(key, seed=0): + key = str(key).encode('utf-8') + length = len(key) + h = seed + c1 = 0xcc9e2d51 + c2 = 0x1b873593 + + for i in range(0, length - 3, 4): + k = int.from_bytes(key[i:i+4], 'little') + k = (k * c1) & 0xffffffff + k = ((k << 15) | (k >> 17)) & 0xffffffff + k = (k * c2) & 0xffffffff + + h ^= k + h = ((h << 13) | (h >> 19)) & 0xffffffff + h = ((h * 5) + 0xe6546b64) & 0xffffffff + + tail = key[length - (length % 4):] + k = 0 + if len(tail) >= 3: + k ^= tail[2] << 16 + if len(tail) >= 2: + k ^= tail[1] << 8 + if len(tail) >= 1: + k ^= tail[0] + k = (k * c1) & 0xffffffff + k = ((k << 15) | (k >> 17)) & 0xffffffff + k = (k * c2) & 0xffffffff + h ^= k + + h ^= length + h ^= (h >> 16) + h = (h * 0x85ebca6b) & 0xffffffff + h ^= (h >> 13) + h = (h * 0xc2b2ae35) & 0xffffffff + h ^= (h >> 16) + + return h + +def pastelize(user_id): + hue = murmurhash3_32(user_id) % 360 + r, g, b = colorsys.hls_to_rgb(hue / 360, 0.60, 0.75) + return int(r * 255) << 16 | int(g * 255) << 8 | int(b * 255) + +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 + +def get_top_color(member, fallback=0x7289da): + if not member: + return fallback + + roles = [r for r in member.roles if r.color.value != 0] + if not roles: + return fallback + + roles.sort(key=lambda r: r.position, reverse=True) + return roles[0].color.value + +async def fetch_vencord_data(): + global vencord_fetch, vencord_badges, vencord_contributors + + async with aiohttp.ClientSession() as session: + async with session.get( + 'https://badges.vencord.dev/badges.json', + headers={'User-Agent': 'HiddenPhox/userinfo'} + ) as resp: + badges = await resp.json() + vencord_badges = badges + + async with session.get( + 'https://raw.githubusercontent.com/Vendicated/Vencord/main/src/utils/constants.ts' + ) as resp: + constants = await resp.text() + vencord_contributors.clear() + + for match in REGEX_DEVS.finditer(constants): + user_id, no_badge = match.groups() + if no_badge or user_id == '0': + continue + vencord_contributors.add(user_id) + + vencord_fetch = int(datetime.now().timestamp() * 1000) + 3600000 + +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() + +async def get_guild_member(bot, guild_id, 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/guilds/{guild_id}/members/{user_id}', + headers=headers + ) as resp: + if resp.status != 200: + return None + return await resp.json() + +async def get_guild_data(bot, guild_id): + headers = {'Authorization': f'Bot {bot.http.token}'} + + async with aiohttp.ClientSession() as session: + async with session.get( + f'https://discord.com/api/v10/discovery/{guild_id}/clan', + headers=headers + ) as resp: + if resp.status == 200: + return await resp.json() + + return None + +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) > vencord_fetch: + try: + await fetch_vencord_data() + except: + pass + + if int(datetime.now().timestamp() * 1000) > quests_fetch: + try: + await fetch_quest_data() + except: + pass + + badges = [] + flags = user_data.get('public_flags', 0) + + if 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']})") + + boosting_member = member + boosting_since = None + if member and hasattr(member, 'premium_since') and member.premium_since: + boosting_since = member.premium_since + boosting_member = member + else: + for g in bot.guilds: + m = g.get_member(target_user.id) + if m and hasattr(m, 'premium_since') and m.premium_since: + boosting_since = m.premium_since + boosting_member = m + break + + if boosting_since: + delta = (datetime.now(timezone.utc) - boosting_since).total_seconds() + icon = BADGE_ICONS['guild_booster_lvl1'] + + if delta >= ONE_MONTH * 2: + icon = BADGE_ICONS['guild_booster_lvl2'] + if delta >= ONE_MONTH * 3: + icon = BADGE_ICONS['guild_booster_lvl3'] + if delta >= ONE_MONTH * 6: + icon = BADGE_ICONS['guild_booster_lvl4'] + if delta >= ONE_MONTH * 9: + icon = BADGE_ICONS['guild_booster_lvl5'] + if delta >= ONE_MONTH * 12: + icon = BADGE_ICONS['guild_booster_lvl6'] + if delta >= ONE_MONTH * 15: + icon = BADGE_ICONS['guild_booster_lvl7'] + if delta >= ONE_MONTH * 18: + icon = BADGE_ICONS['guild_booster_lvl8'] + if delta >= ONE_MONTH * 24: + icon = BADGE_ICONS['guild_booster_lvl9'] + + 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 str(target_user.id) in vencord_contributors: + badges.append('[<:VencordContributor:1273333728709574667>](https://vencord.dev)') + + 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 + if banner_hash: + ext = 'gif' if banner_hash.startswith('a_') else 'png' + banner_url = f'https://cdn.discordapp.com/banners/{target_user.id}/{banner_hash}.{ext}?size=4096' + + images = [f'[Avatar]({avatar_url})'] + + if banner_url: + images.append(f'[Banner]({banner_url})') + + decoration_data = None + if avatar_decoration: + decoration_data = 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})') + + nameplate_url = None + collectibles = user_data.get('collectibles') + if collectibles and collectibles.get('nameplate'): + nameplate = collectibles['nameplate'] + nameplate_asset = nameplate['asset'] + nameplate_url = f"https://cdn.discordapp.com/assets/collectibles/{nameplate_asset}static.png" + images.append(f'[Nameplate]({nameplate_url})') + + 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=get_top_color(member, user_data.get('accent_color') or pastelize(target_user.id)), + 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) + + 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)) + member_since = f"" + if member and hasattr(member, 'joined_at') and member.joined_at: + joined_timestamp = int(member.joined_at.timestamp()) + member_since += f" • " + + embed.add_field(name='Member Since', value=member_since, inline=False) + + if avatar_decoration: + animated = ' (Animated)' if avatar_decoration['asset'].startswith('a_') else '' + decoration_name = decoration_data.get('sku', {}).get('name') if decoration_data else (quest_decoration_name or '*Unknown*') + decoration_link = f"https://discord.com/shop#itemSkuId={avatar_decoration['sku_id']}" if decoration_data else decoration_name + embed.add_field( + name=f'Avatar Deco{animated}', + value=f"[{decoration_name}]({decoration_link})\n-# {avatar_decoration['sku_id']}" if decoration_data else f"{decoration_name}\n-# {avatar_decoration['sku_id']}", + inline=True + ) + + vc_badges = vencord_badges.get(str(target_user.id)) + if vc_badges: + vc_badge_list = [f'"[{b["tooltip"]}]({b["badge"]})"' for b in vc_badges] + embed.add_field( + name=f'Vencord Donator Badge{"s" if len(vc_badges) > 1 else ""} ({len(vc_badges)})' if len(vc_badges) > 1 else 'Vencord Donator Badge', + value=', '.join(vc_badge_list), + inline=True + ) + + if member and member.roles[1:]: + role_list = ' '.join([f'<@&{r.id}>' for r in sorted(member.roles[1:], key=lambda r: r.position, reverse=True)]) + embed.add_field(name=f'Roles ({len(member.roles) - 1})', value=role_list, inline=False) + + if primary_guild and primary_guild.get('identity_guild_id'): + primary_guild_data = await get_guild_data(bot, primary_guild['identity_guild_id']) + guild_name = primary_guild_data.get('name', '') if primary_guild_data else '' + embed.add_field( + name='Server Tag', + value=f"{guild_name}\n*{primary_guild['identity_guild_id']}*", + inline=True + ) + + if collectibles and collectibles.get('nameplate'): + nameplate = collectibles['nameplate'] + nameplate_listing = await get_published_listing(bot, nameplate['sku_id']) + nameplate_name = nameplate_listing.get('sku', {}).get('name', '*Unknown*') if nameplate_listing else '*Unknown*' + + embed.add_field( + name='Nameplate', + value=f"[{nameplate_name}](https://discord.com/shop#itemSkuId={nameplate['sku_id']})\n*Palette: {nameplate.get('palette', 'Unknown')}*", + inline=True + ) + + if images: + embed.add_field(name='\u200b', value='\u3000'.join(images), inline=False) + + embed.set_footer(text=f'ID: {target_user.id}') + + await context.send(embed=embed) + + return userinfo + From a94a99ac29524360dd387eab451ccef29cad5185 Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 10 Oct 2025 13:33:18 -0400 Subject: [PATCH 2/9] feat(bot): more intents --- bot.py | 34 ++++++++----------- cogs/general/userinfo.py | 73 ++++++++++------------------------------ 2 files changed, 31 insertions(+), 76 deletions(-) diff --git a/bot.py b/bot.py index de37a47..bc29256 100644 --- a/bot.py +++ b/bot.py @@ -19,25 +19,6 @@ from utils import ascii, setup_logger, get_uptime, setup_signal_handlers load_dotenv() -""" -Default Intents: -intents.emojis_and_stickers = True -intents.guild_messages = True -intents.guild_reactions = True -intents.guild_scheduled_events = True -intents.guild_typing = True -intents.guilds = True -intents.integrations = True -intents.invites = True -intents.reactions = True -intents.typing = True -intents.voice_states = True -intents.webhooks = True - -intents.members = True -intents.presences = True -""" - intents = discord.Intents.default() intents.message_content = True intents.bans = True @@ -46,7 +27,20 @@ intents.dm_reactions = True intents.dm_typing = True intents.emojis = True intents.messages = True - +intents.reactions = True +intents.typing = True +intents.voice_states = True +intents.webhooks = True +intents.emojis_and_stickers = True +intents.guild_messages = True +intents.guild_reactions = True +intents.guild_scheduled_events = True +intents.guild_typing = True +intents.guilds = True +intents.integrations = True +intents.invites = True +intents.presences = True +intents.members = True diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index 2cdf30b..b602033 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -354,39 +354,27 @@ def userinfo_command(): 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']})") - boosting_member = member - boosting_since = None - if member and hasattr(member, 'premium_since') and member.premium_since: + if member and member.premium_since: boosting_since = member.premium_since - boosting_member = member - else: - for g in bot.guilds: - m = g.get_member(target_user.id) - if m and hasattr(m, 'premium_since') and m.premium_since: - boosting_since = m.premium_since - boosting_member = m - break - - if boosting_since: delta = (datetime.now(timezone.utc) - boosting_since).total_seconds() icon = BADGE_ICONS['guild_booster_lvl1'] - if delta >= ONE_MONTH * 2: - icon = BADGE_ICONS['guild_booster_lvl2'] - if delta >= ONE_MONTH * 3: - icon = BADGE_ICONS['guild_booster_lvl3'] - if delta >= ONE_MONTH * 6: - icon = BADGE_ICONS['guild_booster_lvl4'] - if delta >= ONE_MONTH * 9: - icon = BADGE_ICONS['guild_booster_lvl5'] - if delta >= ONE_MONTH * 12: - icon = BADGE_ICONS['guild_booster_lvl6'] - if delta >= ONE_MONTH * 15: - icon = BADGE_ICONS['guild_booster_lvl7'] - if delta >= ONE_MONTH * 18: - icon = BADGE_ICONS['guild_booster_lvl8'] 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']})") @@ -554,15 +542,8 @@ def userinfo_command(): embed.add_field(name='Member Since', value=member_since, inline=False) - if avatar_decoration: - animated = ' (Animated)' if avatar_decoration['asset'].startswith('a_') else '' - decoration_name = decoration_data.get('sku', {}).get('name') if decoration_data else (quest_decoration_name or '*Unknown*') - decoration_link = f"https://discord.com/shop#itemSkuId={avatar_decoration['sku_id']}" if decoration_data else decoration_name - embed.add_field( - name=f'Avatar Deco{animated}', - value=f"[{decoration_name}]({decoration_link})\n-# {avatar_decoration['sku_id']}" if decoration_data else f"{decoration_name}\n-# {avatar_decoration['sku_id']}", - inline=True - ) + is_bot = user_data.get('bot', False) + embed.add_field(name='Is Bot', value='True' if is_bot else 'False', inline=True) vc_badges = vencord_badges.get(str(target_user.id)) if vc_badges: @@ -577,26 +558,6 @@ def userinfo_command(): role_list = ' '.join([f'<@&{r.id}>' for r in sorted(member.roles[1:], key=lambda r: r.position, reverse=True)]) embed.add_field(name=f'Roles ({len(member.roles) - 1})', value=role_list, inline=False) - if primary_guild and primary_guild.get('identity_guild_id'): - primary_guild_data = await get_guild_data(bot, primary_guild['identity_guild_id']) - guild_name = primary_guild_data.get('name', '') if primary_guild_data else '' - embed.add_field( - name='Server Tag', - value=f"{guild_name}\n*{primary_guild['identity_guild_id']}*", - inline=True - ) - - if collectibles and collectibles.get('nameplate'): - nameplate = collectibles['nameplate'] - nameplate_listing = await get_published_listing(bot, nameplate['sku_id']) - nameplate_name = nameplate_listing.get('sku', {}).get('name', '*Unknown*') if nameplate_listing else '*Unknown*' - - embed.add_field( - name='Nameplate', - value=f"[{nameplate_name}](https://discord.com/shop#itemSkuId={nameplate['sku_id']})\n*Palette: {nameplate.get('palette', 'Unknown')}*", - inline=True - ) - if images: embed.add_field(name='\u200b', value='\u3000'.join(images), inline=False) From 8f962b2fdea698e11eee0d084341f8ac636c917d Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 10 Oct 2025 15:31:19 -0400 Subject: [PATCH 3/9] refactory(userinfo): embed color --- cogs/general/userinfo.py | 58 +--------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index b602033..dc6fc95 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -5,7 +5,6 @@ import aiohttp import asyncio from io import BytesIO from PIL import Image, ImageDraw -import colorsys import re from datetime import datetime, timezone @@ -112,50 +111,6 @@ ACTIVITY_TYPE_ICONS = { 3: '<:i:1392584313786208296>' } -def murmurhash3_32(key, seed=0): - key = str(key).encode('utf-8') - length = len(key) - h = seed - c1 = 0xcc9e2d51 - c2 = 0x1b873593 - - for i in range(0, length - 3, 4): - k = int.from_bytes(key[i:i+4], 'little') - k = (k * c1) & 0xffffffff - k = ((k << 15) | (k >> 17)) & 0xffffffff - k = (k * c2) & 0xffffffff - - h ^= k - h = ((h << 13) | (h >> 19)) & 0xffffffff - h = ((h * 5) + 0xe6546b64) & 0xffffffff - - tail = key[length - (length % 4):] - k = 0 - if len(tail) >= 3: - k ^= tail[2] << 16 - if len(tail) >= 2: - k ^= tail[1] << 8 - if len(tail) >= 1: - k ^= tail[0] - k = (k * c1) & 0xffffffff - k = ((k << 15) | (k >> 17)) & 0xffffffff - k = (k * c2) & 0xffffffff - h ^= k - - h ^= length - h ^= (h >> 16) - h = (h * 0x85ebca6b) & 0xffffffff - h ^= (h >> 13) - h = (h * 0xc2b2ae35) & 0xffffffff - h ^= (h >> 16) - - return h - -def pastelize(user_id): - hue = murmurhash3_32(user_id) % 360 - r, g, b = colorsys.hls_to_rgb(hue / 360, 0.60, 0.75) - return int(r * 255) << 16 | int(g * 255) << 8 | int(b * 255) - def format_username(user): if user.discriminator and user.discriminator != '0': return f'{user.name}#{user.discriminator}' @@ -171,17 +126,6 @@ def get_default_avatar(user_id, discriminator=None): def snowflake_to_timestamp(snowflake): return ((int(snowflake) >> 22) + 1420070400000) / 1000 -def get_top_color(member, fallback=0x7289da): - if not member: - return fallback - - roles = [r for r in member.roles if r.color.value != 0] - if not roles: - return fallback - - roles.sort(key=lambda r: r.position, reverse=True) - return roles[0].color.value - async def fetch_vencord_data(): global vencord_fetch, vencord_badges, vencord_contributors @@ -517,7 +461,7 @@ def userinfo_command(): desc_lines.extend(activity_lines) embed = discord.Embed( - color=get_top_color(member, user_data.get('accent_color') or pastelize(target_user.id)), + color=0x7289DA, description='\n'.join(desc_lines) ) From 89f5aecfd5f15e18e3359e52b9d094c5173132e0 Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 10 Oct 2025 15:49:44 -0400 Subject: [PATCH 4/9] fix(userinfo): banner sizes --- cogs/general/userinfo.py | 67 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index dc6fc95..9a3f46d 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -395,14 +395,70 @@ def userinfo_command(): 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: - ext = 'gif' if banner_hash.startswith('a_') else 'png' - banner_url = f'https://cdn.discordapp.com/banners/{target_user.id}/{banner_hash}.{ext}?size=4096' + 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: + new_width = 1100 + aspect_ratio = img.height / img.width + new_height = int(new_width * aspect_ratio) + + if is_animated: + frames = [] + durations = [] + + try: + while True: + frame = img.copy().convert('RGBA') + frame = frame.resize((new_width, new_height), Image.Resampling.LANCZOS) + 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((new_width, new_height), Image.Resampling.LANCZOS) + + 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]({banner_url})') + images.append(f'[Banner]({original_banner_link})') decoration_data = None if avatar_decoration: @@ -507,7 +563,10 @@ def userinfo_command(): embed.set_footer(text=f'ID: {target_user.id}') - await context.send(embed=embed) + if banner_file: + await context.send(embed=embed, file=banner_file) + else: + await context.send(embed=embed) return userinfo From b1a5267f028baaedbac19fa477df583ed42b3602 Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 10 Oct 2025 15:56:19 -0400 Subject: [PATCH 5/9] fix(userinfo): fallback for no guild --- cogs/general/userinfo.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index 9a3f46d..53cdce2 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -525,6 +525,8 @@ def userinfo_command(): 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 From 1e36534b4107d213e4958fafdf2bd9f45a1ca65e Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 10 Oct 2025 22:28:43 -0400 Subject: [PATCH 6/9] feat(userinfo): custom badges for owners yay --- cogs/general/userinfo.py | 43 ++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index 53cdce2..b6d30fe 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -268,7 +268,23 @@ def userinfo_command(): badges = [] flags = user_data.get('public_flags', 0) - if flags & USER_FLAGS['STAFF']: + 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']) + badges.append(BADGE_ICONS['opal']) + 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']})") @@ -537,12 +553,14 @@ def userinfo_command(): embed.set_image(url=banner_url) created_timestamp = int(snowflake_to_timestamp(target_user.id)) - member_since = f"" + 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()) - member_since += f" • " - - embed.add_field(name='Member Since', value=member_since, inline=False) + 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) @@ -557,7 +575,20 @@ def userinfo_command(): ) if member and member.roles[1:]: - role_list = ' '.join([f'<@&{r.id}>' for r in sorted(member.roles[1:], key=lambda r: r.position, reverse=True)]) + 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: From 7a4e4ed0f850d1bbdab8867bfa82545aff1a08f5 Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 10 Oct 2025 22:32:15 -0400 Subject: [PATCH 7/9] fix(userinfo): remove unused functions --- cogs/general/userinfo.py | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index b6d30fe..4c55b44 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -4,7 +4,7 @@ from discord import app_commands import aiohttp import asyncio from io import BytesIO -from PIL import Image, ImageDraw +from PIL import Image import re from datetime import datetime, timezone @@ -198,31 +198,6 @@ async def get_published_listing(bot, sku_id): return None return await resp.json() -async def get_guild_member(bot, guild_id, 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/guilds/{guild_id}/members/{user_id}', - headers=headers - ) as resp: - if resp.status != 200: - return None - return await resp.json() - -async def get_guild_data(bot, guild_id): - headers = {'Authorization': f'Bot {bot.http.token}'} - - async with aiohttp.ClientSession() as session: - async with session.get( - f'https://discord.com/api/v10/discovery/{guild_id}/clan', - headers=headers - ) as resp: - if resp.status == 200: - return await resp.json() - - return None - def userinfo_command(): @commands.hybrid_command( name="userinfo", @@ -279,7 +254,6 @@ def userinfo_command(): 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']) - badges.append(BADGE_ICONS['opal']) elif str(target_user.id) == '1376728824108286034': badges.append(BADGE_ICONS['automod']) badges.append(BADGE_ICONS['verified_developer']) @@ -476,19 +450,16 @@ def userinfo_command(): if banner_url: images.append(f'[Banner]({original_banner_link})') - decoration_data = None if avatar_decoration: - decoration_data = await get_published_listing(bot, avatar_decoration['sku_id']) + 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})') - nameplate_url = None collectibles = user_data.get('collectibles') if collectibles and collectibles.get('nameplate'): nameplate = collectibles['nameplate'] nameplate_asset = nameplate['asset'] - nameplate_url = f"https://cdn.discordapp.com/assets/collectibles/{nameplate_asset}static.png" - images.append(f'[Nameplate]({nameplate_url})') + 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') From d381c5cbf04b01cd631282c69b39b796549f9edd Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 10 Oct 2025 23:46:23 -0400 Subject: [PATCH 8/9] fix(userinfo): icons --- cogs/general/userinfo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index 4c55b44..121027e 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -106,9 +106,9 @@ BADGE_ICONS = { } ACTIVITY_TYPE_ICONS = { - 0: '<:i:1392584288515395635>', - 2: '<:i:1392584301367001148>', - 3: '<:i:1392584313786208296>' + 0: '<:gaming:1426409065701048451>', + 2: '<:music:1426409047132737586>', + 3: '<:watching:1426409475450863778>' } def format_username(user): From 2dab7ade22ca8d839cd5e1b994985b0e7851364a Mon Sep 17 00:00:00 2001 From: neoarz Date: Sat, 11 Oct 2025 00:39:47 -0400 Subject: [PATCH 9/9] fix(userinfo): remove badge --- TODO.md | 2 +- cogs/general/userinfo.py | 75 +++++++++++----------------------------- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/TODO.md b/TODO.md index c6afc5f..6ce745b 100644 --- a/TODO.md +++ b/TODO.md @@ -53,7 +53,7 @@ - [ ] add [tts](https://developer.puter.com/tutorials/free-unlimited-text-to-speech-api/) command -- [ ] update botinfo command +- [x] update botinfo command - [ ] Clean tag system from [tags branch](https://github.com/neoarz/Syntrel/tree/tags) (make sure db is persistent) diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index 121027e..b4bfe73 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -10,15 +10,9 @@ from datetime import datetime, timezone ONE_MONTH = 2628000 -vencord_fetch = 0 -vencord_badges = {} -vencord_contributors = set() - quests_fetch = 0 quest_data = [] -REGEX_DEVS = re.compile(r'id: (\d+)n(,\n\s+badge: false)?') - ACTIVITY_TYPE_NAMES = [ "Playing", "Streaming", @@ -126,31 +120,6 @@ def get_default_avatar(user_id, discriminator=None): def snowflake_to_timestamp(snowflake): return ((int(snowflake) >> 22) + 1420070400000) / 1000 -async def fetch_vencord_data(): - global vencord_fetch, vencord_badges, vencord_contributors - - async with aiohttp.ClientSession() as session: - async with session.get( - 'https://badges.vencord.dev/badges.json', - headers={'User-Agent': 'HiddenPhox/userinfo'} - ) as resp: - badges = await resp.json() - vencord_badges = badges - - async with session.get( - 'https://raw.githubusercontent.com/Vendicated/Vencord/main/src/utils/constants.ts' - ) as resp: - constants = await resp.text() - vencord_contributors.clear() - - for match in REGEX_DEVS.finditer(constants): - user_id, no_badge = match.groups() - if no_badge or user_id == '0': - continue - vencord_contributors.add(user_id) - - vencord_fetch = int(datetime.now().timestamp() * 1000) + 3600000 - async def fetch_quest_data(): global quests_fetch, quest_data @@ -228,12 +197,6 @@ def userinfo_command(): guild = context.guild member = guild.get_member(target_user.id) if guild else None - if int(datetime.now().timestamp() * 1000) > vencord_fetch: - try: - await fetch_vencord_data() - except: - pass - if int(datetime.now().timestamp() * 1000) > quests_fetch: try: await fetch_quest_data() @@ -344,9 +307,6 @@ def userinfo_command(): 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 str(target_user.id) in vencord_contributors: - badges.append('[<:VencordContributor:1273333728709574667>](https://vencord.dev)') - if user_data.get('legacy_username'): badges.append(BADGE_ICONS['username']) @@ -401,9 +361,23 @@ def userinfo_command(): img = Image.open(BytesIO(banner_data)) if img.width < 1100: - new_width = 1100 - aspect_ratio = img.height / img.width - new_height = int(new_width * aspect_ratio) + 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 = [] @@ -412,7 +386,8 @@ def userinfo_command(): try: while True: frame = img.copy().convert('RGBA') - frame = frame.resize((new_width, new_height), Image.Resampling.LANCZOS) + 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) @@ -433,7 +408,8 @@ def userinfo_command(): banner_file = discord.File(output, filename='banner.gif') banner_url = 'attachment://banner.gif' else: - img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) + img = img.resize((scale_width, scale_height), Image.Resampling.LANCZOS) + img = img.crop((left, top, right, bottom)) output = BytesIO() img.save(output, format='PNG') @@ -536,15 +512,6 @@ def userinfo_command(): is_bot = user_data.get('bot', False) embed.add_field(name='Is Bot', value='True' if is_bot else 'False', inline=True) - vc_badges = vencord_badges.get(str(target_user.id)) - if vc_badges: - vc_badge_list = [f'"[{b["tooltip"]}]({b["badge"]})"' for b in vc_badges] - embed.add_field( - name=f'Vencord Donator Badge{"s" if len(vc_badges) > 1 else ""} ({len(vc_badges)})' if len(vc_badges) > 1 else 'Vencord Donator Badge', - value=', '.join(vc_badge_list), - 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]