Files
ShaiBot/utils/profileSystem.js
T
2026-08-22 20:27:42 +02:00

574 lines
26 KiB
JavaScript

const {
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
MessageFlags
} = require("discord.js");
const { mClient } = require("../index");
const {
getCookieBalance,
getUnixTime
} = require("./cookieEconomy");
const DATABASE_NAME = process.env.M_DB;
const COLLECTIONS = {
stats: "stats_profiles",
achievements: "items_achievements",
badges: "guild_badges",
achievementConfig: "config_achievements"
};
let profileSystemInitPromise = null;
const DEFAULT_ACHIEVEMENTS = [
{ achievementId: "stolen_1000", name: "Light Fingers", description: "Steal 1,000 Cookies from other players.", requirement: "Steal 1,000 Cookies total.", category: "stealing", stat: "economy.stolenTotal", target: 1000, emoji: "🧤" },
{ achievementId: "stolen_10000", name: "Career Criminal", description: "Steal 10,000 Cookies from other players.", requirement: "Steal 10,000 Cookies total.", category: "stealing", stat: "economy.stolenTotal", target: 10000, emoji: "🕵️" },
{ achievementId: "stolen_100000", name: "Grand Theft Cookie", description: "Steal 100,000 Cookies from other players.", requirement: "Steal 100,000 Cookies total.", category: "stealing", stat: "economy.stolenTotal", target: 100000, emoji: "💰" },
{ achievementId: "stolen_unique_3", name: "Making the Rounds", description: "Successfully steal from 3 different players.", requirement: "Steal from 3 unique players.", category: "stealing", stat: "unique.playersStolenFrom", measure: "arrayLength", target: 3, emoji: "🎯" },
{ achievementId: "stolen_unique_10", name: "Equal Opportunity Thief", description: "Successfully steal from 10 different players.", requirement: "Steal from 10 unique players.", category: "stealing", stat: "unique.playersStolenFrom", measure: "arrayLength", target: 10, emoji: "🗺️" },
{ achievementId: "stolen_from_me_1000", name: "Easy Pickings", description: "Have 1,000 Cookies stolen from you.", requirement: "Lose 1,000 Cookies to other players stealing.", category: "victim", stat: "economy.stolenFromMeTotal", target: 1000, emoji: "💸" },
{ achievementId: "stolen_from_me_10000", name: "Lock the Door", description: "Have 10,000 Cookies stolen from you.", requirement: "Lose 10,000 Cookies to other players stealing.", category: "victim", stat: "economy.stolenFromMeTotal", target: 10000, emoji: "🚪" },
{ achievementId: "stolen_by_unique_3", name: "Marked", description: "Be successfully stolen from by 3 different players.", requirement: "Be stolen from by 3 unique players.", category: "victim", stat: "unique.playersStolenBy", measure: "arrayLength", target: 3, emoji: "👀" },
{ achievementId: "stolen_by_unique_10", name: "Criminal Magnet", description: "Be successfully stolen from by 10 different players.", requirement: "Be stolen from by 10 unique players.", category: "victim", stat: "unique.playersStolenBy", measure: "arrayLength", target: 10, emoji: "🧲" },
{ achievementId: "obtained_10000", name: "Growing Stash", description: "Obtain 10,000 Cookies over your lifetime.", requirement: "Obtain 10,000 Cookies from all sources.", category: "economy", stat: "economy.cookiesObtainedTotal", target: 10000, emoji: "🍪" },
{ achievementId: "obtained_100000", name: "Cookie Magnate", description: "Obtain 100,000 Cookies over your lifetime.", requirement: "Obtain 100,000 Cookies from all sources.", category: "economy", stat: "economy.cookiesObtainedTotal", target: 100000, emoji: "💰" },
{ achievementId: "obtained_1000000", name: "Crumb Tycoon", description: "Obtain 1,000,000 Cookies over your lifetime.", requirement: "Obtain 1,000,000 Cookies from all sources.", category: "economy", stat: "economy.cookiesObtainedTotal", target: 1000000, emoji: "👑" },
{ achievementId: "vault_stolen_1000", name: "Testing the Locks", description: "Steal 1,000 Cookies from the Bot Vault.", requirement: "Steal 1,000 Cookies from the Bot Vault.", category: "vault", stat: "economy.vaultStolenTotal", target: 1000, emoji: "🏦" },
{ achievementId: "vault_stolen_10000", name: "Vault Regular", description: "Steal 10,000 Cookies from the Bot Vault.", requirement: "Steal 10,000 Cookies from the Bot Vault.", category: "vault", stat: "economy.vaultStolenTotal", target: 10000, emoji: "🥷" },
{ achievementId: "lost_10000", name: "Expensive Hobby", description: "Lose 10,000 Cookies.", requirement: "Lose 10,000 Cookies total.", category: "economy", stat: "economy.cookiesLostTotal", target: 10000, emoji: "🔥" },
{ achievementId: "lost_100000", name: "Financial Catastrophe", description: "Lose 100,000 Cookies.", requirement: "Lose 100,000 Cookies total.", category: "economy", stat: "economy.cookiesLostTotal", target: 100000, emoji: "💀" },
{ achievementId: "win_streak_3", name: "On a Roll", description: "Win 3 games in a row.", requirement: "Reach a 3-game winning streak.", category: "games", stat: "streaks.bestWin", target: 3, emoji: "🍀" },
{ achievementId: "win_streak_10", name: "Can't Stop Won't Stop", description: "Win 10 games in a row.", requirement: "Reach a 10-game winning streak.", category: "games", stat: "streaks.bestWin", target: 10, emoji: "🔥" },
{ achievementId: "loss_streak_3", name: "Rough Night", description: "Lose 3 games in a row.", requirement: "Reach a 3-game losing streak.", category: "games", stat: "streaks.bestLoss", target: 3, emoji: "😬" },
{ achievementId: "loss_streak_10", name: "Digging Deeper", description: "Lose 10 games in a row.", requirement: "Reach a 10-game losing streak.", category: "games", stat: "streaks.bestLoss", target: 10, emoji: "🕳️" },
{ achievementId: "given_10000", name: "Sharing Is Caring", description: "Give 10,000 Cookies to other players.", requirement: "Give 10,000 Cookies to other players total.", category: "economy", stat: "economy.cookiesGivenTotal", target: 10000, emoji: "🎁" },
{ achievementId: "given_50000", name: "Community Baker", description: "Give 50,000 Cookies to other players.", requirement: "Give 50,000 Cookies to other players total.", category: "economy", stat: "economy.cookiesGivenTotal", target: 50000, emoji: "🤝" },
{ achievementId: "games_won_10", name: "House Regular", description: "Win 10 Crumb Saucer games.", requirement: "Win 10 games total.", category: "games", stat: "games.total.won", target: 10, emoji: "🎲" },
{ achievementId: "games_won_50", name: "House Favorite", description: "Win 50 Crumb Saucer games.", requirement: "Win 50 games total.", category: "games", stat: "games.total.won", target: 50, emoji: "🏆" },
{ achievementId: "slots_won_10", name: "Reel Deal", description: "Win 10 Slots spins.", requirement: "Win 10 Slots games.", category: "games", stat: "games.slots.won", target: 10, emoji: "🎰" },
{ achievementId: "blackjack_won_10", name: "Card Counter-ish", description: "Win 10 Blackjack hands.", requirement: "Win 10 Blackjack games.", category: "games", stat: "games.blackjack.won", target: 10, emoji: "🃏" },
{ achievementId: "roulette_won_10", name: "Wheel Watcher", description: "Win 10 Roulette bets.", requirement: "Win 10 Roulette games.", category: "games", stat: "games.roulette.won", target: 10, emoji: "🎡" },
{ achievementId: "heist_won_10", name: "Professional", description: "Complete 10 successful Vault Heists.", requirement: "Win 10 Vault Heists.", category: "games", stat: "games.heist.won", target: 10, emoji: "🏦" }
];
const DEFAULT_BADGES = [
{ badgeId: "first_100k_cookies", name: "Cookie Pioneer", description: "First player to hold 100,000 Cookies.", emoji: "🍪", ownerId: null, unlockedAt: null },
{ badgeId: "first_1m_cookies", name: "Cookie Millionaire", description: "First player to hold 1,000,000 Cookies.", emoji: "👑", ownerId: null, unlockedAt: null },
{ badgeId: "first_100k_vault_stolen", name: "Vault Breaker", description: "First player to steal 100,000 Cookies from the Bot Vault.", emoji: "🏦", ownerId: null, unlockedAt: null }
];
function getProfileCollections() {
if (!DATABASE_NAME) throw new Error("M_DB is not configured in .env");
const db = mClient.db(DATABASE_NAME);
return {
db,
stats: db.collection(COLLECTIONS.stats),
achievements: db.collection(COLLECTIONS.achievements),
badges: db.collection(COLLECTIONS.badges),
achievementConfig: db.collection(COLLECTIONS.achievementConfig)
};
}
async function ensureProfileSystem() {
if (profileSystemInitPromise) return profileSystemInitPromise;
profileSystemInitPromise = (async () => {
const { stats, achievements, badges, achievementConfig } = getProfileCollections();
await stats.createIndex({ userId: 1, guildId: 1 }, { unique: true });
await achievements.createIndex({ userId: 1, guildId: 1 }, { unique: true });
await badges.createIndex({ guildId: 1, badgeId: 1 }, { unique: true });
await achievementConfig.createIndex({ achievementId: 1 }, { unique: true });
for (const achievement of DEFAULT_ACHIEVEMENTS) {
await achievementConfig.updateOne(
{ achievementId: achievement.achievementId },
{ $setOnInsert: achievement },
{ upsert: true }
);
}
})();
try {
return await profileSystemInitPromise;
} catch (error) {
profileSystemInitPromise = null;
throw error;
}
}
async function ensureGuildBadges(guildId) {
await ensureProfileSystem();
const { badges } = getProfileCollections();
for (const badge of DEFAULT_BADGES) {
await badges.updateOne(
{ guildId, badgeId: badge.badgeId },
{ $setOnInsert: { guildId, ...badge } },
{ upsert: true }
);
}
}
function getDefaultStats(userId, guildId) {
return {
userId,
guildId,
economy: {
cookiesObtainedTotal: 0,
cookiesLostTotal: 0,
cookiesGivenTotal: 0,
stolenTotal: 0,
stolenFromMeTotal: 0,
vaultStolenTotal: 0
},
unique: {
playersStolenFrom: [],
playersStolenBy: []
},
games: {
total: { played: 0, won: 0, lost: 0, pushed: 0 },
slots: { played: 0, won: 0, lost: 0, pushed: 0 },
blackjack: { played: 0, won: 0, lost: 0, pushed: 0 },
roulette: { played: 0, won: 0, lost: 0, pushed: 0 },
heist: { played: 0, won: 0, lost: 0, pushed: 0 }
},
streaks: {
currentWin: 0,
bestWin: 0,
currentLoss: 0,
bestLoss: 0
}
};
}
async function ensureStatsDocument(userId, guildId) {
const { stats } = getProfileCollections();
await stats.updateOne(
{ userId, guildId },
{ $setOnInsert: getDefaultStats(userId, guildId) },
{ upsert: true }
);
}
async function ensureAchievementDocument(userId, guildId) {
const { achievements } = getProfileCollections();
await achievements.updateOne(
{ userId, guildId },
{ $setOnInsert: { userId, guildId, unlocked: {} } },
{ upsert: true }
);
}
async function getProfileStats(userId, guildId) {
const { stats } = getProfileCollections();
return await stats.findOne({ userId, guildId }) ?? getDefaultStats(userId, guildId);
}
async function trackProfileEvent(interaction, userId, event) {
await ensureProfileSystem();
const guildId = interaction.guildId;
if (!guildId || !userId || !event?.type) return;
const { stats } = getProfileCollections();
await ensureStatsDocument(userId, guildId);
switch (event.type) {
case "cookie_gain":
await stats.updateOne({ userId, guildId }, { $inc: { "economy.cookiesObtainedTotal": event.amount } });
break;
case "cookie_loss":
await stats.updateOne({ userId, guildId }, { $inc: { "economy.cookiesLostTotal": event.amount } });
break;
case "cookies_given":
await stats.updateOne({ userId, guildId }, { $inc: { "economy.cookiesGivenTotal": event.amount } });
break;
case "steal_success":
await stats.updateOne(
{ userId, guildId },
{
$inc: { "economy.stolenTotal": event.amount },
$addToSet: { "unique.playersStolenFrom": event.targetUserId }
}
);
break;
case "stolen_from_me":
await stats.updateOne(
{ userId, guildId },
{
$inc: { "economy.stolenFromMeTotal": event.amount },
$addToSet: { "unique.playersStolenBy": event.thiefUserId }
}
);
break;
case "vault_steal":
await stats.updateOne({ userId, guildId }, { $inc: { "economy.vaultStolenTotal": event.amount } });
break;
case "game_result":
await trackGameResult(userId, guildId, event);
break;
default:
return;
}
const updatedStats = await getProfileStats(userId, guildId);
await evaluateAchievements(interaction, userId, updatedStats, event.type);
await evaluateBadges(interaction, userId, updatedStats);
return updatedStats;
}
async function trackGameResult(userId, guildId, event) {
const { stats } = getProfileCollections();
const game = String(event.game ?? "").toLowerCase();
if (!["slots", "blackjack", "roulette", "heist"].includes(game)) return;
const result = String(event.result ?? "").toLowerCase();
const increment = {
"games.total.played": 1,
[`games.${game}.played`]: 1
};
const set = {};
if (result === "win") {
increment["games.total.won"] = 1;
increment[`games.${game}.won`] = 1;
increment["streaks.currentWin"] = 1;
set["streaks.currentLoss"] = 0;
} else if (result === "loss") {
increment["games.total.lost"] = 1;
increment[`games.${game}.lost`] = 1;
increment["streaks.currentLoss"] = 1;
set["streaks.currentWin"] = 0;
} else if (result === "push") {
increment["games.total.pushed"] = 1;
increment[`games.${game}.pushed`] = 1;
} else {
return;
}
await stats.updateOne(
{ userId, guildId },
{
$inc: increment,
...(Object.keys(set).length ? { $set: set } : {})
}
);
const current = await stats.findOne({ userId, guildId });
await stats.updateOne(
{ userId, guildId },
{
$max: {
"streaks.bestWin": current?.streaks?.currentWin ?? 0,
"streaks.bestLoss": current?.streaks?.currentLoss ?? 0
}
}
);
}
async function evaluateAchievements(interaction, userId, statsDocument, eventType = null) {
const { achievements, achievementConfig } = getProfileCollections();
const guildId = interaction.guildId;
await ensureAchievementDocument(userId, guildId);
const relevantStats = getRelevantAchievementStats(eventType);
const definitions = await achievementConfig.find(
relevantStats ? { stat: { $in: relevantStats } } : {}
).toArray();
const unlockedDoc = await achievements.findOne({ userId, guildId });
const unlocked = unlockedDoc?.unlocked ?? {};
for (const definition of definitions) {
if (unlocked[definition.achievementId]) continue;
const currentValue = readStatValue(statsDocument, definition);
if (currentValue < definition.target) continue;
const field = `unlocked.${definition.achievementId}`;
const result = await achievements.updateOne(
{ userId, guildId, [field]: { $exists: false } },
{ $set: { [field]: getUnixTime() } }
);
if (result.modifiedCount === 1) {
await sendAchievementNotification(interaction, userId, definition);
}
}
}
async function evaluateBadges(interaction, userId, statsDocument) {
const guildId = interaction.guildId;
await ensureGuildBadges(guildId);
const balance = await getCookieBalance(userId);
if (balance >= 100000) await tryClaimBadge(interaction, userId, "first_100k_cookies");
if (balance >= 1000000) await tryClaimBadge(interaction, userId, "first_1m_cookies");
if ((statsDocument?.economy?.vaultStolenTotal ?? 0) >= 100000) {
await tryClaimBadge(interaction, userId, "first_100k_vault_stolen");
}
}
async function tryClaimBadge(interaction, userId, badgeId) {
const { badges } = getProfileCollections();
const guildId = interaction.guildId;
const badge = await badges.findOneAndUpdate(
{ guildId, badgeId, ownerId: null },
{ $set: { ownerId: userId, unlockedAt: getUnixTime() } },
{ returnDocument: "after" }
);
if (!badge || badge.ownerId !== userId) return;
try {
await interaction.channel.send({
embeds: [
new EmbedBuilder()
.setTitle("🏅 Unique Badge Claimed!")
.setDescription([
`${badge.emoji} **${badge.name}**`,
"",
`<@${userId}> claimed a guild-unique badge!`,
"",
badge.description,
"",
"*This badge can no longer be earned by another player.*"
].join("\n"))
]
});
} catch {}
}
async function sendAchievementNotification(interaction, userId, achievement) {
try {
await interaction.channel.send({
embeds: [
new EmbedBuilder()
.setTitle("🏆 Achievement Unlocked!")
.setDescription([
`<@${userId}>`,
"",
`${achievement.emoji} **${achievement.name}**`,
"",
achievement.description
].join("\n"))
]
});
} catch {}
}
function getRelevantAchievementStats(eventType) {
switch (eventType) {
case "cookie_gain": return ["economy.cookiesObtainedTotal"];
case "cookie_loss": return ["economy.cookiesLostTotal"];
case "cookies_given": return ["economy.cookiesGivenTotal"];
case "steal_success": return ["economy.stolenTotal", "unique.playersStolenFrom"];
case "stolen_from_me": return ["economy.stolenFromMeTotal", "unique.playersStolenBy"];
case "vault_steal": return ["economy.vaultStolenTotal"];
case "game_result": return ["streaks.bestWin", "streaks.bestLoss", "games.total.won", "games.slots.won", "games.blackjack.won", "games.roulette.won", "games.heist.won"];
default: return null;
}
}
function readStatValue(document, definition) {
const value = definition.stat
.split(".")
.reduce((current, key) => current?.[key], document);
if (definition.measure === "arrayLength") {
return Array.isArray(value) ? value.length : 0;
}
return Number(value ?? 0);
}
async function buildProfileOverview(guildId, user) {
await ensureProfileSystem();
const stats = await getProfileStats(user.id, guildId);
const balance = await getCookieBalance(user.id);
const { achievements, badges, achievementConfig } = getProfileCollections();
const [achievementDoc, achievementCount, ownedBadges] = await Promise.all([
achievements.findOne({ userId: user.id, guildId }),
achievementConfig.countDocuments(),
badges.find({ guildId, ownerId: user.id }).toArray()
]);
const unlockedCount = Object.keys(achievementDoc?.unlocked ?? {}).length;
return new EmbedBuilder()
.setTitle(`👤 ${user.globalName ?? user.username}'s Profile`)
.setThumbnail(user.displayAvatarURL({ size: 256 }))
.addFields(
{ name: "🍪 Cookies", value: balance.toLocaleString(), inline: true },
{ name: "🏆 Achievements", value: `${unlockedCount} / ${achievementCount}`, inline: true },
{ name: "🏅 Unique Badges", value: `${ownedBadges.length}`, inline: true },
{
name: "🎮 Games",
value: [
`Played: **${stats.games.total.played}**`,
`Won: **${stats.games.total.won}**`,
`Lost: **${stats.games.total.lost}**`,
`Pushes: **${stats.games.total.pushed}**`
].join("\n"),
inline: true
},
{
name: "🔥 Best Streaks",
value: [
`Wins: **${stats.streaks.bestWin}**`,
`Losses: **${stats.streaks.bestLoss}**`
].join("\n"),
inline: true
}
)
.setFooter({ text: `profile:${user.id}` });
}
async function buildBadgesEmbed(guildId, user) {
await ensureProfileSystem();
await ensureGuildBadges(guildId);
const { badges } = getProfileCollections();
const documents = await badges.find({ guildId }).toArray();
const owned = documents.filter(badge => badge.ownerId === user.id);
const other = documents.filter(badge => badge.ownerId && badge.ownerId !== user.id);
const unclaimed = documents.filter(badge => !badge.ownerId);
return new EmbedBuilder()
.setTitle(`🏅 ${user.globalName ?? user.username}'s Badges`)
.setDescription([
"### Owned",
owned.length
? owned.map(b => `${b.emoji} **${b.name}**\n${b.description}`).join("\n\n")
: "*No unique badges yet.*",
"",
"### Claimed by Others",
other.length
? other.map(b => `${b.emoji} **${b.name}** — <@${b.ownerId}>`).join("\n")
: "*None yet.*",
"",
"### Still Available",
unclaimed.length
? unclaimed.map(b => `${b.emoji} **${b.name}**\n${b.description}`).join("\n\n")
: "*All unique badges have been claimed.*"
].join("\n"))
.setFooter({ text: `profile:${user.id}` });
}
async function buildAchievementsEmbed(guildId, user) {
await ensureProfileSystem();
const { achievements, achievementConfig } = getProfileCollections();
const [unlockedDoc, definitions] = await Promise.all([
achievements.findOne({ userId: user.id, guildId }),
achievementConfig.find({}).sort({ category: 1, target: 1 }).toArray()
]);
const unlocked = unlockedDoc?.unlocked ?? {};
const stats = await getProfileStats(user.id, guildId);
const lines = definitions.map(a => {
const complete = Boolean(unlocked[a.achievementId]);
const current = readStatValue(stats, a);
const progress = complete
? "Unlocked"
: `${Math.min(current, a.target).toLocaleString()} / ${a.target.toLocaleString()}`;
return `${complete ? "✅" : "⬜"} ${a.emoji} **${a.name}** — ${a.requirement}\n↳ ${progress}`;
});
return new EmbedBuilder()
.setTitle(`🏆 ${user.globalName ?? user.username}'s Achievements`)
.setDescription(lines.join("\n") || "No achievements configured.")
.setFooter({ text: `profile:${user.id}` });
}
async function buildStatsEmbed(guildId, user) {
await ensureProfileSystem();
const stats = await getProfileStats(user.id, guildId);
const e = stats.economy;
const g = stats.games;
return new EmbedBuilder()
.setTitle(`📊 ${user.globalName ?? user.username}'s Statistics`)
.setThumbnail(user.displayAvatarURL({ size: 256 }))
.addFields(
{
name: "🍪 Economy",
value: [
`Obtained: **${e.cookiesObtainedTotal.toLocaleString()}**`,
`Lost: **${e.cookiesLostTotal.toLocaleString()}**`,
`Given: **${e.cookiesGivenTotal.toLocaleString()}**`,
`Stolen: **${e.stolenTotal.toLocaleString()}**`,
`Stolen from you: **${e.stolenFromMeTotal.toLocaleString()}**`,
`Bot Vault stolen: **${e.vaultStolenTotal.toLocaleString()}**`
].join("\n")
},
{
name: "🥷 Theft",
value: [
`Unique victims: **${stats.unique.playersStolenFrom.length}**`,
`Unique thieves: **${stats.unique.playersStolenBy.length}**`
].join("\n"),
inline: true
},
{
name: "🔥 Streaks",
value: [
`Current wins: **${stats.streaks.currentWin}**`,
`Best wins: **${stats.streaks.bestWin}**`,
`Current losses: **${stats.streaks.currentLoss}**`,
`Best losses: **${stats.streaks.bestLoss}**`
].join("\n"),
inline: true
},
{ name: "🎰 Slots", value: formatGameStats(g.slots), inline: true },
{ name: "🃏 Blackjack", value: formatGameStats(g.blackjack), inline: true },
{ name: "🎡 Roulette", value: formatGameStats(g.roulette), inline: true },
{ name: "🏦 Heist", value: formatGameStats(g.heist), inline: true }
)
.setFooter({ text: `profile:${user.id}` });
}
function formatGameStats(game) {
return [
`Played: **${game?.played ?? 0}**`,
`Won: **${game?.won ?? 0}**`,
`Lost: **${game?.lost ?? 0}**`,
`Pushes: **${game?.pushed ?? 0}**`
].join("\n");
}
function buildProfileButtons() {
return [
new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("profileOverview").setLabel("Overview").setEmoji("👤").setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId("profileStats").setLabel("Stats").setEmoji("📊").setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId("profileBadges").setLabel("Badges").setEmoji("🏅").setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId("profileAchievements").setLabel("Achievements").setEmoji("🏆").setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId("abort").setLabel("Close").setEmoji("✖️").setStyle(ButtonStyle.Secondary)
)
];
}
function getProfileTargetId(interaction) {
const footer = interaction.message?.embeds?.[0]?.footer?.text;
const [type, userId] = footer?.split(":") ?? [];
return type === "profile" ? userId : null;
}
module.exports = {
ensureProfileSystem,
ensureGuildBadges,
trackProfileEvent,
getProfileStats,
buildProfileOverview,
buildStatsEmbed,
buildBadgesEmbed,
buildAchievementsEmbed,
buildProfileButtons,
getProfileTargetId
};