updated profile and centralized functions
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
const {
|
||||
SlashCommandBuilder,
|
||||
MessageFlags
|
||||
} = require(
|
||||
"discord.js"
|
||||
);
|
||||
|
||||
const {
|
||||
requireAdminAccess
|
||||
} = require(
|
||||
"../../utils/permissions"
|
||||
);
|
||||
|
||||
const {
|
||||
reconcileProfileAchievements,
|
||||
auditProfileSystem
|
||||
} = require(
|
||||
"../../utils/profileSystem"
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* COMMAND
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
data:
|
||||
new SlashCommandBuilder()
|
||||
.setName(
|
||||
"profileadmin"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
"Profile and achievement administration."
|
||||
)
|
||||
|
||||
/*
|
||||
* =================================================
|
||||
* RECONCILE
|
||||
* =================================================
|
||||
*/
|
||||
|
||||
.addSubcommand(
|
||||
subcommand =>
|
||||
subcommand
|
||||
.setName(
|
||||
"reconcile"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
"Re-evaluate stored statistics for achievements."
|
||||
)
|
||||
|
||||
.addUserOption(
|
||||
option =>
|
||||
option
|
||||
.setName(
|
||||
"user"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
"Only reconcile this user's profile."
|
||||
)
|
||||
|
||||
.setRequired(
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
.addBooleanOption(
|
||||
option =>
|
||||
option
|
||||
.setName(
|
||||
"dry-run"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
"Show what would change without unlocking anything."
|
||||
)
|
||||
|
||||
.setRequired(
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
/*
|
||||
* =================================================
|
||||
* AUDIT
|
||||
* =================================================
|
||||
*/
|
||||
|
||||
.addSubcommand(
|
||||
subcommand =>
|
||||
subcommand
|
||||
.setName(
|
||||
"audit"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
"Audit the profile and achievement system."
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
/*
|
||||
* ========================================================
|
||||
* EXECUTE
|
||||
* ========================================================
|
||||
*/
|
||||
|
||||
async execute(
|
||||
interaction
|
||||
) {
|
||||
/*
|
||||
* Runtime authorization.
|
||||
*
|
||||
* This intentionally replaces:
|
||||
*
|
||||
* .setDefaultMemberPermissions(
|
||||
* PermissionFlagsBits.Administrator
|
||||
* )
|
||||
*
|
||||
* so BOT_AUTHOR_ID can also invoke the command.
|
||||
*/
|
||||
|
||||
if (
|
||||
!await requireAdminAccess(
|
||||
interaction
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const subcommand =
|
||||
interaction.options
|
||||
.getSubcommand();
|
||||
|
||||
|
||||
switch (
|
||||
subcommand
|
||||
) {
|
||||
case "reconcile":
|
||||
|
||||
return runReconcile(
|
||||
interaction
|
||||
);
|
||||
|
||||
|
||||
case "audit":
|
||||
|
||||
return runAudit(
|
||||
interaction
|
||||
);
|
||||
|
||||
|
||||
default:
|
||||
|
||||
return interaction.reply({
|
||||
content:
|
||||
"Unknown profile administration action.",
|
||||
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* RECONCILE
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
async function runReconcile(
|
||||
interaction
|
||||
) {
|
||||
const target =
|
||||
interaction.options.getUser(
|
||||
"user"
|
||||
);
|
||||
|
||||
|
||||
const dryRun =
|
||||
interaction.options.getBoolean(
|
||||
"dry-run"
|
||||
)
|
||||
??
|
||||
false;
|
||||
|
||||
|
||||
/*
|
||||
* This can touch multiple Mongo documents,
|
||||
* so acknowledge the interaction first.
|
||||
*/
|
||||
|
||||
await interaction.deferReply({
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
|
||||
try {
|
||||
const result =
|
||||
await reconcileProfileAchievements(
|
||||
interaction.guildId,
|
||||
{
|
||||
userId:
|
||||
target?.id
|
||||
??
|
||||
null,
|
||||
|
||||
dryRun
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const lines = [
|
||||
dryRun
|
||||
? "### 🔎 Profile Reconciliation — Dry Run"
|
||||
: "### ✅ Profile Reconciliation Complete",
|
||||
|
||||
"",
|
||||
|
||||
target
|
||||
? `Target: ${target}`
|
||||
: "Target: **Entire Guild**",
|
||||
|
||||
"",
|
||||
|
||||
`Profiles checked: **${result.profilesChecked ?? 0}**`,
|
||||
`Achievement unlocks found: **${result.unlocksFound ?? 0}**`
|
||||
];
|
||||
|
||||
|
||||
if (
|
||||
!dryRun
|
||||
) {
|
||||
lines.push(
|
||||
`Achievements unlocked: **${result.unlocksApplied ?? 0}**`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
result.skippedEventOnly !==
|
||||
undefined
|
||||
) {
|
||||
lines.push(
|
||||
`Event-only checks skipped: **${result.skippedEventOnly}**`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
Array.isArray(
|
||||
result.details
|
||||
)
|
||||
&&
|
||||
result.details.length >
|
||||
0
|
||||
) {
|
||||
const preview =
|
||||
result.details
|
||||
.slice(
|
||||
0,
|
||||
15
|
||||
)
|
||||
.map(
|
||||
entry => {
|
||||
if (
|
||||
typeof entry ===
|
||||
"string"
|
||||
) {
|
||||
return `• ${entry}`;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
`• <@${entry.userId}> — ` +
|
||||
`${entry.achievementName ?? entry.achievementId}`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
lines.push(
|
||||
"",
|
||||
"### Changes",
|
||||
...preview
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
result.details.length >
|
||||
preview.length
|
||||
) {
|
||||
lines.push(
|
||||
"",
|
||||
`*...and ${result.details.length - preview.length} more.*`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
content:
|
||||
lines.join(
|
||||
"\n"
|
||||
)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[ProfileAdmin] Reconciliation failed:",
|
||||
error
|
||||
);
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
content:
|
||||
"Profile reconciliation failed. Check the bot logs for details."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* AUDIT
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
async function runAudit(
|
||||
interaction
|
||||
) {
|
||||
await interaction.deferReply({
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
|
||||
try {
|
||||
const result =
|
||||
await auditProfileSystem(
|
||||
interaction.guildId
|
||||
);
|
||||
|
||||
|
||||
const lines = [
|
||||
"### 🧾 Profile System Audit",
|
||||
"",
|
||||
|
||||
`Profile documents: **${result.profileDocuments ?? 0}**`,
|
||||
`Achievement documents: **${result.achievementDocuments ?? 0}**`,
|
||||
`Active achievement definitions: **${result.activeAchievements ?? 0}**`,
|
||||
`Legacy achievement definitions: **${result.legacyAchievements ?? 0}**`,
|
||||
`Guild badges: **${result.badges ?? 0}**`,
|
||||
`Claimed badges: **${result.claimedBadges ?? 0}**`,
|
||||
`Available badges: **${result.availableBadges ?? 0}**`
|
||||
];
|
||||
|
||||
|
||||
if (
|
||||
Array.isArray(
|
||||
result.warnings
|
||||
)
|
||||
&&
|
||||
result.warnings.length >
|
||||
0
|
||||
) {
|
||||
lines.push(
|
||||
"",
|
||||
"### ⚠️ Warnings",
|
||||
|
||||
...result.warnings.map(
|
||||
warning =>
|
||||
`• ${warning}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
else {
|
||||
lines.push(
|
||||
"",
|
||||
"✅ No obvious profile-system issues were found."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
content:
|
||||
lines.join(
|
||||
"\n"
|
||||
)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[ProfileAdmin] Audit failed:",
|
||||
error
|
||||
);
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
content:
|
||||
"Profile audit failed. Check the bot logs for details."
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user