updated badges

This commit is contained in:
DeSqBlocki
2026-08-22 22:46:40 +02:00
parent 9e0870c1ed
commit 08d1a8c5cd
11 changed files with 1047 additions and 261 deletions
+41 -9
View File
@@ -1,22 +1,54 @@
const { const {
buildAchievementsEmbed, buildAchievementsEmbed,
buildProfileButtons, buildProfileButtons,
getProfileTargetId getProfileContextFromFooter,
} = require("../../utils/profileSystem"); requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = { module.exports = {
name: "profileAchievements", name:
"profileAchievements",
async execute(interaction) { async execute(
const userId = getProfileTargetId(interaction); interaction
if (!userId) return; ) {
const context =
await requireProfileOwner(
interaction
);
const user = await interaction.client.users.fetch(userId); if (!context) {
const embed = await buildAchievementsEmbed(interaction.guildId, user); return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildAchievementsEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
0
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({ return interaction.update({
embeds: [embed], embeds: [embed],
components: buildProfileButtons() components:
buildProfileButtons(
"achievements",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
}); });
} }
}; };
+54
View File
@@ -0,0 +1,54 @@
const {
buildAchievementsEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileAchievementsLeft",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildAchievementsEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
context.page - 1
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"achievements",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+54
View File
@@ -0,0 +1,54 @@
const {
buildAchievementsEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileAchievementsRight",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildAchievementsEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
context.page + 1
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"achievements",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+41 -9
View File
@@ -1,22 +1,54 @@
const { const {
buildBadgesEmbed, buildBadgesEmbed,
buildProfileButtons, buildProfileButtons,
getProfileTargetId getProfileContextFromFooter,
} = require("../../utils/profileSystem"); requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = { module.exports = {
name: "profileBadges", name:
"profileBadges",
async execute(interaction) { async execute(
const userId = getProfileTargetId(interaction); interaction
if (!userId) return; ) {
const context =
await requireProfileOwner(
interaction
);
const user = await interaction.client.users.fetch(userId); if (!context) {
const embed = await buildBadgesEmbed(interaction.guildId, user); return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildBadgesEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
0
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({ return interaction.update({
embeds: [embed], embeds: [embed],
components: buildProfileButtons() components:
buildProfileButtons(
"badges",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
}); });
} }
}; };
+54
View File
@@ -0,0 +1,54 @@
const {
buildBadgesEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileBadgesLeft",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildBadgesEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
context.page - 1
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"badges",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+54
View File
@@ -0,0 +1,54 @@
const {
buildBadgesEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileBadgesRight",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildBadgesEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
context.page + 1
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"badges",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+60
View File
@@ -0,0 +1,60 @@
const {
EmbedBuilder
} = require(
"discord.js"
);
const {
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
function delay(
ms
) {
return new Promise(
resolve =>
setTimeout(
resolve,
ms
)
);
}
module.exports = {
name:
"profileClose",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
await interaction.update({
embeds: [
new EmbedBuilder()
.setTitle(
"Profile Closed"
)
.setDescription(
"Closing profile..."
)
],
components: []
});
await delay(1200);
try {
await interaction.deleteReply();
} catch {}
}
};
+34 -9
View File
@@ -1,22 +1,47 @@
const { const {
buildProfileOverview, buildProfileOverview,
buildProfileButtons, buildProfileButtons,
getProfileTargetId requireProfileOwner
} = require("../../utils/profileSystem"); } = require(
"../../utils/profileSystem"
);
module.exports = { module.exports = {
name: "profileOverview", name:
"profileOverview",
async execute(interaction) { async execute(
const userId = getProfileTargetId(interaction); interaction
if (!userId) return; ) {
const context =
await requireProfileOwner(
interaction
);
const user = await interaction.client.users.fetch(userId); if (!context) {
const embed = await buildProfileOverview(interaction.guildId, user); return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildProfileOverview(
interaction.guildId,
user,
context.sessionOwnerId
);
return interaction.update({ return interaction.update({
embeds: [embed], embeds: [embed],
components: buildProfileButtons() components:
buildProfileButtons(
"overview",
0,
1
)
}); });
} }
}; };
+34 -9
View File
@@ -1,22 +1,47 @@
const { const {
buildStatsEmbed, buildStatsEmbed,
buildProfileButtons, buildProfileButtons,
getProfileTargetId requireProfileOwner
} = require("../../utils/profileSystem"); } = require(
"../../utils/profileSystem"
);
module.exports = { module.exports = {
name: "profileStats", name:
"profileStats",
async execute(interaction) { async execute(
const userId = getProfileTargetId(interaction); interaction
if (!userId) return; ) {
const context =
await requireProfileOwner(
interaction
);
const user = await interaction.client.users.fetch(userId); if (!context) {
const embed = await buildStatsEmbed(interaction.guildId, user); return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildStatsEmbed(
interaction.guildId,
user,
context.sessionOwnerId
);
return interaction.update({ return interaction.update({
embeds: [embed], embeds: [embed],
components: buildProfileButtons() components:
buildProfileButtons(
"stats",
0,
1
)
}); });
} }
}; };
+11 -11
View File
@@ -11,7 +11,6 @@ const {
"../../utils/profileSystem" "../../utils/profileSystem"
); );
module.exports = { module.exports = {
data: data:
new SlashCommandBuilder() new SlashCommandBuilder()
@@ -32,32 +31,33 @@ module.exports = {
) )
), ),
async execute( async execute(
interaction interaction
) { ) {
const user = const user =
interaction.options.getUser( interaction.options.getUser(
"user" "user"
) ) ??
??
interaction.user; interaction.user;
const sessionOwnerId =
interaction.user.id;
const embed = const embed =
await buildProfileOverview( await buildProfileOverview(
interaction.guildId, interaction.guildId,
user user,
sessionOwnerId
); );
return interaction.reply({ return interaction.reply({
embeds: [ embeds: [embed],
embed
],
components: components:
buildProfileButtons() buildProfileButtons(
"overview",
0,
1
)
}); });
} }
}; };
+609 -213
View File
@@ -654,9 +654,7 @@ async function sendAchievementNotification(
achievement.description achievement.description
].join("\n") ].join("\n")
) )
], ]
flags:
MessageFlags.Ephemeral
}); });
} catch {} } catch {}
} }
@@ -1020,7 +1018,8 @@ async function tryClaimBadge(
async function buildProfileOverview( async function buildProfileOverview(
guildId, guildId,
user user,
sessionOwnerId = user.id
) { ) {
const stats = const stats =
await getProfileStats( await getProfileStats(
@@ -1115,13 +1114,102 @@ async function buildProfileOverview(
) )
.setFooter({ .setFooter({
text: text:
`profile:${user.id}` buildProfileFooter(
sessionOwnerId,
user.id,
"overview",
0,
1
)
});
}
async function buildStatsEmbed(
guildId,
user,
sessionOwnerId = user.id
) {
const stats =
await getProfileStats(
user.id,
guildId
);
const gameLines = [
["🎰 Slots", stats.games.slots],
["🃏 Blackjack", stats.games.blackjack],
["🎡 Roulette", stats.games.roulette],
["🏦 Heist", stats.games.heist]
].map(
([name, game]) => [
`**${name}**`,
`Played ${game?.played ?? 0} • Won ${game?.won ?? 0} • Lost ${game?.lost ?? 0} • Push ${game?.pushed ?? 0}`,
`Net tracked: **+${(game?.cookiesWon ?? 0).toLocaleString()} / -${(game?.cookiesLost ?? 0).toLocaleString()} 🍪**`
].join("\n")
);
return new EmbedBuilder()
.setTitle(
`📊 ${user.globalName ?? user.username}'s Stats`
)
.setThumbnail(
user.displayAvatarURL({
size: 256
})
)
.addFields(
{
name: "🍪 Economy",
value: [
`Obtained: **${(stats.economy?.cookiesObtainedTotal ?? 0).toLocaleString()} 🍪**`,
`Lost: **${(stats.economy?.cookiesLostTotal ?? 0).toLocaleString()} 🍪**`,
`Given: **${(stats.economy?.cookiesGivenTotal ?? 0).toLocaleString()} 🍪**`,
`Stolen: **${(stats.economy?.stolenTotal ?? 0).toLocaleString()} 🍪**`,
`Stolen from you: **${(stats.economy?.stolenFromMeTotal ?? 0).toLocaleString()} 🍪**`,
`Vault stolen: **${(stats.economy?.vaultStolenTotal ?? 0).toLocaleString()} 🍪**`
].join("\n")
},
{
name: "🥷 Unique Players",
value: [
`Stolen from: **${stats.unique?.playersStolenFrom?.length ?? 0}**`,
`Stolen by: **${stats.unique?.playersStolenBy?.length ?? 0}**`
].join("\n"),
inline: true
},
{
name: "🔥 Streaks",
value: [
`Current wins: **${stats.streaks?.currentWin ?? 0}**`,
`Best wins: **${stats.streaks?.bestWin ?? 0}**`,
`Current losses: **${stats.streaks?.currentLoss ?? 0}**`,
`Best losses: **${stats.streaks?.bestLoss ?? 0}**`
].join("\n"),
inline: true
},
{
name: "🎮 Per Game",
value:
gameLines.join("\n\n")
}
)
.setFooter({
text:
buildProfileFooter(
sessionOwnerId,
user.id,
"stats",
0,
1
)
}); });
} }
async function buildBadgesEmbed( async function buildBadgesEmbed(
guildId, guildId,
user user,
sessionOwnerId = user.id,
page = 0
) { ) {
const { badges } = const { badges } =
getProfileCollections(); getProfileCollections();
@@ -1129,6 +1217,9 @@ async function buildBadgesEmbed(
const documents = const documents =
await badges.find({ await badges.find({
guildId guildId
}).sort({
category: 1,
name: 1
}).toArray(); }).toArray();
const owned = const owned =
@@ -1138,117 +1229,177 @@ async function buildBadgesEmbed(
user.id user.id
); );
const claimedByOthers = const pages = [];
documents.filter(
badge => pages.push({
badge.ownerId && title: "Owned",
badge.ownerId !== content:
user.id owned.length
? owned.map(
badge =>
formatBadgeLine(
badge,
user.id,
true
)
).join("\n\n")
: "*No unique badges yet.*"
});
const categoryOrder = [
"economy",
"stealing",
"slots",
"blackjack",
"roulette",
"heist",
"collector"
];
const categories =
[...new Set(
documents.map(
badge =>
badge.category ??
"other"
)
)].sort(
(a, b) => {
const ai =
categoryOrder.indexOf(a);
const bi =
categoryOrder.indexOf(b);
if (
ai === -1 &&
bi === -1
) {
return a.localeCompare(b);
}
if (ai === -1) {
return 1;
}
if (bi === -1) {
return -1;
}
return ai - bi;
}
); );
const unclaimed = for (const category of categories) {
documents.filter( const categoryBadges =
badge => documents.filter(
!badge.ownerId badge =>
(badge.category ?? "other") ===
category
);
if (!categoryBadges.length) {
continue;
}
const chunks =
paginateLines(
categoryBadges.map(
badge =>
formatBadgeLine(
badge,
user.id,
false
)
),
3200,
"\n\n"
);
for (
let index = 0;
index < chunks.length;
index++
) {
pages.push({
title:
`${formatCategoryName(category)}${chunks.length > 1 ? ` ${index + 1}/${chunks.length}` : ""}`,
content:
chunks[index]
});
}
}
const safePage =
clampPage(
page,
pages.length
); );
const selected =
pages[safePage] ?? {
title: "Badges",
content:
"No badges configured."
};
return new EmbedBuilder() return new EmbedBuilder()
.setTitle( .setTitle(
`🏅 ${user.globalName ?? user.username}'s Badges` `🏅 ${user.globalName ?? user.username}'s Badges`
) )
.setDescription( .setDescription(
[ [
"### Owned", `### ${selected.title}`,
owned.length selected.content
? owned.map(
badge =>
`${badge.emoji} **${badge.name}**\n${badge.description}`
).join("\n\n")
: "*No unique badges yet.*",
"",
"### Claimed by Others",
claimedByOthers.length
? claimedByOthers.map(
badge =>
`${badge.emoji} **${badge.name}** — <@${badge.ownerId}>`
).join("\n")
: "*None yet.*",
"",
"### Still Available",
unclaimed.length
? unclaimed.map(
badge =>
`${badge.emoji} **${badge.name}**\n${badge.description}`
).join("\n\n")
: "*All unique badges have been claimed.*"
].join("\n") ].join("\n")
) )
.setFooter({ .setFooter({
text: text:
`profile:${user.id}` buildProfileFooter(
sessionOwnerId,
user.id,
"badges",
safePage,
pages.length
)
}); });
} }
async function buildAchievementsEmbed( async function buildAchievementsEmbed(
guildId, guildId,
user, user,
sessionOwnerId = user.id,
page = 0 page = 0
) { ) {
const { const {
achievements, achievements,
achievementConfig achievementConfig
} = } = getProfileCollections();
getProfileCollections();
const [ const [
unlockedDoc, unlockedDoc,
definitions definitions
] = ] = await Promise.all([
await Promise.all([ achievements.findOne({
achievements.findOne({ userId: user.id,
userId: guildId
user.id, }),
guildId
}),
achievementConfig
.find({
active: {
$ne:
false
}
})
.sort({
category:
1,
target:
1
})
.toArray()
]);
achievementConfig
.find({
active: {
$ne: false
}
})
.sort({
category: 1,
target: 1,
name: 1
})
.toArray()
]);
const unlocked = const unlocked =
unlockedDoc?.unlocked ?? unlockedDoc?.unlocked ?? {};
{};
/*
* Keep pages comfortably below Discord's
* 4096-character description limit.
*
* Using a character limit instead of a fixed
* number of achievements also protects us
* against achievements with long names or
* requirements.
*/
const MAX_PAGE_LENGTH =
3500;
const lines = const lines =
definitions.map( definitions.map(
@@ -1256,171 +1407,83 @@ async function buildAchievementsEmbed(
const complete = const complete =
Boolean( Boolean(
unlocked[ unlocked[
achievement achievement.achievementId
.achievementId
] ]
); );
return [ return [
complete complete ? "✅" : "⬜",
? "✅"
: "⬜",
" ", " ",
achievement.emoji ?? "🏆",
achievement.emoji ??
"🏆",
" **", " **",
achievement.name, achievement.name,
"** — ", "** — ",
achievement.requirement ??
achievement.requirement achievement.description ??
??
achievement.description
??
"Requirement unavailable." "Requirement unavailable."
].join(""); ].join("");
} }
); );
/*
* Build pages dynamically.
*/
const pages = const pages =
[]; paginateLines(
lines,
let currentPage = 3400,
[]; "\n"
for (
const line
of lines
) {
const test =
[
...currentPage,
line
].join(
"\n"
);
if (
test.length >
MAX_PAGE_LENGTH
&&
currentPage.length >
0
) {
pages.push(
currentPage.join(
"\n"
)
);
currentPage =
[
line
];
}
else {
currentPage.push(
line
);
}
}
if (
currentPage.length >
0
) {
pages.push(
currentPage.join(
"\n"
)
); );
}
if (!pages.length) {
/*
* No achievements configured.
*/
if (
pages.length ===
0
) {
pages.push( pages.push(
"No achievements configured." "No achievements configured."
); );
} }
/*
* Clamp page so old buttons can't request
* a page that no longer exists after DB
* changes.
*/
const safePage = const safePage =
Math.max( clampPage(
0, page,
pages.length
Math.min(
Number(page) || 0,
pages.length - 1
)
); );
const unlockedCount = const unlockedCount =
definitions.filter( definitions.filter(
achievement => achievement =>
Boolean( Boolean(
unlocked[ unlocked[
achievement achievement.achievementId
.achievementId
] ]
) )
).length; ).length;
return new EmbedBuilder() return new EmbedBuilder()
.setTitle( .setTitle(
`🏆 ${user.globalName ?? user.username}'s Achievements` `🏆 ${user.globalName ?? user.username}'s Achievements`
) )
.setDescription( .setDescription(
pages[ pages[safePage]
safePage
]
) )
.addFields({ .addFields({
name: name: "Progress",
"Progress",
value: value:
`**${unlockedCount} / ${definitions.length}** achievements unlocked` `**${unlockedCount} / ${definitions.length}** achievements unlocked`
}) })
.setFooter({ .setFooter({
text: text:
`profile:${user.id}:achievements:${safePage}:${pages.length}` buildProfileFooter(
sessionOwnerId,
user.id,
"achievements",
safePage,
pages.length
)
}); });
} }
function buildProfileButtons() { function buildProfileButtons(
return [ section = "overview",
page = 0,
pageCount = 1
) {
const rows = [
new ActionRowBuilder() new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
@@ -1434,7 +1497,25 @@ function buildProfileButtons() {
"👤" "👤"
) )
.setStyle( .setStyle(
ButtonStyle.Primary section === "overview"
? ButtonStyle.Primary
: ButtonStyle.Secondary
),
new ButtonBuilder()
.setCustomId(
"profileStats"
)
.setLabel(
"Stats"
)
.setEmoji(
"📊"
)
.setStyle(
section === "stats"
? ButtonStyle.Primary
: ButtonStyle.Secondary
), ),
new ButtonBuilder() new ButtonBuilder()
@@ -1448,7 +1529,9 @@ function buildProfileButtons() {
"🏅" "🏅"
) )
.setStyle( .setStyle(
ButtonStyle.Secondary section === "badges"
? ButtonStyle.Primary
: ButtonStyle.Secondary
), ),
new ButtonBuilder() new ButtonBuilder()
@@ -1462,12 +1545,14 @@ function buildProfileButtons() {
"🏆" "🏆"
) )
.setStyle( .setStyle(
ButtonStyle.Secondary section === "achievements"
? ButtonStyle.Primary
: ButtonStyle.Secondary
), ),
new ButtonBuilder() new ButtonBuilder()
.setCustomId( .setCustomId(
"abort" "profileClose"
) )
.setLabel( .setLabel(
"Close" "Close"
@@ -1476,13 +1561,99 @@ function buildProfileButtons() {
"✖️" "✖️"
) )
.setStyle( .setStyle(
ButtonStyle.Secondary ButtonStyle.Danger
) )
) )
]; ];
if (
[
"badges",
"achievements"
].includes(section) &&
pageCount > 1
) {
const prefix =
section === "badges"
? "profileBadges"
: "profileAchievements";
rows.push(
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(
`${prefix}Left`
)
.setLabel(
"Previous"
)
.setEmoji(
"◀️"
)
.setDisabled(
page <= 0
)
.setStyle(
ButtonStyle.Secondary
),
new ButtonBuilder()
.setCustomId(
`${prefix}Page`
)
.setLabel(
`${page + 1} / ${pageCount}`
)
.setDisabled(
true
)
.setStyle(
ButtonStyle.Secondary
),
new ButtonBuilder()
.setCustomId(
`${prefix}Right`
)
.setLabel(
"Next"
)
.setEmoji(
"▶️"
)
.setDisabled(
page >=
pageCount - 1
)
.setStyle(
ButtonStyle.Secondary
)
)
);
}
return rows;
} }
function getProfileTargetId( function buildProfileFooter(
sessionOwnerId,
profileUserId,
section,
page,
pageCount
) {
return [
"profile",
sessionOwnerId,
profileUserId,
section,
page,
pageCount
].join(":");
}
function getProfileContext(
interaction interaction
) { ) {
const footer = const footer =
@@ -1491,23 +1662,248 @@ function getProfileTargetId(
?.footer ?.footer
?.text; ?.text;
return getProfileContextFromFooter(
footer
);
}
function getProfileContextFromFooter(
footer
) {
const [ const [
type, type,
userId sessionOwnerId,
profileUserId,
section = "overview",
rawPage = "0",
rawPageCount = "1"
] = footer?.split(":") ?? []; ] = footer?.split(":") ?? [];
return type === "profile" if (
? userId type !== "profile" ||
: null; !sessionOwnerId ||
!profileUserId
) {
return null;
}
return {
sessionOwnerId,
profileUserId,
section,
page:
Number(rawPage) || 0,
pageCount:
Math.max(
1,
Number(rawPageCount) || 1
)
};
}
function getProfileTargetId(
interaction
) {
return getProfileContext(
interaction
)?.profileUserId ?? null;
}
async function requireProfileOwner(
interaction
) {
const context =
getProfileContext(
interaction
);
if (!context) {
await replyProfileError(
interaction,
"This profile session is no longer valid."
);
return null;
}
if (
context.sessionOwnerId !==
interaction.user.id
) {
await replyProfileError(
interaction,
"This isn't your profile session."
);
return null;
}
return context;
}
async function replyProfileError(
interaction,
content
) {
const payload = {
content,
flags:
MessageFlags.Ephemeral
};
if (
interaction.replied ||
interaction.deferred
) {
return interaction.followUp(
payload
);
}
return interaction.reply(
payload
);
}
function paginateLines(
lines,
maxLength = 3400,
separator = "\n"
) {
const pages = [];
let current = [];
for (const line of lines) {
const candidate =
[...current, line].join(
separator
);
if (
candidate.length >
maxLength &&
current.length
) {
pages.push(
current.join(
separator
)
);
current = [line];
}
else {
current.push(line);
}
}
if (current.length) {
pages.push(
current.join(
separator
)
);
}
return pages;
}
function clampPage(
page,
pageCount
) {
return Math.max(
0,
Math.min(
Number(page) || 0,
Math.max(
0,
pageCount - 1
)
)
);
}
function formatCategoryName(
category
) {
const labels = {
economy:
"🍪 Economy",
stealing:
"🥷 Stealing",
slots:
"🎰 Slots",
blackjack:
"🃏 Blackjack",
roulette:
"🎡 Roulette",
heist:
"🏦 Heist",
collector:
"🏆 Collector",
other:
"🏅 Other"
};
return labels[category] ??
`🏅 ${category.charAt(0).toUpperCase()}${category.slice(1)}`;
}
function formatBadgeLine(
badge,
profileUserId,
ownedPage
) {
const emoji =
badge.emoji ?? "🏅";
const description =
badge.description ??
"No description available.";
if (ownedPage) {
return `${emoji} **${badge.name}**\n${description}`;
}
let status;
if (
badge.ownerId ===
profileUserId
) {
status = "✅ **Owned by this player**";
}
else if (badge.ownerId) {
status = `🔒 Claimed by <@${badge.ownerId}>`;
}
else {
status = "🟢 **Available**";
}
return [
`${emoji} **${badge.name}**`,
description,
status
].join("\n");
} }
module.exports = { module.exports = {
ensureProfileSystem, ensureProfileSystem,
trackProfileEvent, trackProfileEvent,
getProfileStats, getProfileStats,
buildProfileOverview, buildProfileOverview,
buildStatsEmbed,
buildBadgesEmbed, buildBadgesEmbed,
buildAchievementsEmbed, buildAchievementsEmbed,
buildProfileButtons, buildProfileButtons,
getProfileTargetId
getProfileContext,
getProfileContextFromFooter,
getProfileTargetId,
requireProfileOwner
}; };