Conversation
* Added the base module folders and module.json * Added all folders necessary and the configuration files for each folder * Added a test command * removed the manage file * Added, renamed and deleted some files as necessary and coded the models. * Renamed action.js to moderation.js, coded multiple things, added a new file for correct tracking. * Forgot to update module.json, now updated aswell * Added additional information in ping-protection.js * Disabled allowing reply pings, added the enable moderation and enable advanced configuration in moderation.json and made the choices inside depend on it because I forgot to :/ Added the options to enable/disable pings/modlogs/logs kept after leave and made the choices depend on it + made those choices with numbers select instead of integers for almost 0 user-error issues. * Added support for actually correct parameters and those parameters added into the message editor for the warning message * Added proper support for localization, and coded the events * Completed the full module and fixed some critical bugs that caused the bot to crash * Cleaned up some code notes I used for debugging * Completely finished the module and worked tirelessly for many hours to debug code, has been tested and is currently ongoing extensive testing to ensure absolutely everythig works as supposed to * Debugged absolutely everything, removed like 300 lines of code for polish while remaining the same functions. Removed a few locales that are unused and updated some locales for better understanding. Fully tested extensively. Not verified by GitHub because I code in VSCode. * Added the option to lower mod actions history * Made the deault value of pings to trigger action 10 instead of 5 in basic pings count config * Added the commands warnings for most commands Listed the warnings for all commands except the panel command as the bot already checks for administrator perms. * Almost completely rewrote the module to make sure the modules works as supposed to with SCNX. * Added "automod" abilities - Will now delete the original message by default with the message content and allows to configure both options * (not working correctly) added automod integration and some small changes * Fixed the * Removed the feature that didn't work (reposting), adds a custom message to the automod message block. Also the bot now deletes the rule it created if automod enabled = false * Fixed the bug of the bot still sending the warning and punishing if limit reached with reply pings even when it's allowed in config * Added a funny easter egg * Some QOL improvements, including merging the list commands * Added some new options in the config * Update configuration.json * Fix self-ping condition to allow self-pinging * Ping protection V1, in Discord.JS V14 * Ping Protection V1 * Changed code to the requested changes, and adjusted code logic to actually properly support multiple moderation actions (not tested before, and didn't work during testing) * Made adjustments to code as requested, and added an intent to main.js to make it work properly. * Fixed the missing footer on embeds. Fixed a small typo in the default waning message configuration and added an emoji to the why easter egg. --------- Co-authored-by: Kevinking500 <Kevinking500>
* Added the base module folders and module.json * Added all folders necessary and the configuration files for each folder * Added a test command * removed the manage file * Added, renamed and deleted some files as necessary and coded the models. * Renamed action.js to moderation.js, coded multiple things, added a new file for correct tracking. * Forgot to update module.json, now updated aswell * Added additional information in ping-protection.js * Disabled allowing reply pings, added the enable moderation and enable advanced configuration in moderation.json and made the choices inside depend on it because I forgot to :/ Added the options to enable/disable pings/modlogs/logs kept after leave and made the choices depend on it + made those choices with numbers select instead of integers for almost 0 user-error issues. * Added support for actually correct parameters and those parameters added into the message editor for the warning message * Added proper support for localization, and coded the events * Completed the full module and fixed some critical bugs that caused the bot to crash * Cleaned up some code notes I used for debugging * Completely finished the module and worked tirelessly for many hours to debug code, has been tested and is currently ongoing extensive testing to ensure absolutely everythig works as supposed to * Debugged absolutely everything, removed like 300 lines of code for polish while remaining the same functions. Removed a few locales that are unused and updated some locales for better understanding. Fully tested extensively. Not verified by GitHub because I code in VSCode. * Added the option to lower mod actions history * Made the deault value of pings to trigger action 10 instead of 5 in basic pings count config * Added the commands warnings for most commands Listed the warnings for all commands except the panel command as the bot already checks for administrator perms. * Almost completely rewrote the module to make sure the modules works as supposed to with SCNX. * Added "automod" abilities - Will now delete the original message by default with the message content and allows to configure both options * (not working correctly) added automod integration and some small changes * Fixed the * Removed the feature that didn't work (reposting), adds a custom message to the automod message block. Also the bot now deletes the rule it created if automod enabled = false * Fixed the bug of the bot still sending the warning and punishing if limit reached with reply pings even when it's allowed in config * Added a funny easter egg * Some QOL improvements, including merging the list commands * Added some new options in the config * Update configuration.json * Fix self-ping condition to allow self-pinging * Ping protection V1, in Discord.JS V14 * Ping Protection V1 * Changed code to the requested changes, and adjusted code logic to actually properly support multiple moderation actions (not tested before, and didn't work during testing) * Made adjustments to code as requested, and added an intent to main.js to make it work properly. * Fixed the missing footer on embeds. Fixed a small typo in the default waning message configuration and added an emoji to the why easter egg. * Ping Protection V1.1 * Quickly updated locales for better explanation about exceptions for whitelisted * Ping Protection V1.1 remastered * Another remastered version * Added categories in 2 configs * Used proper emoji's that SCNX supports * Used the new updated ones I suggested that were added (and hopefully also work) * Updated some small changes from Copilot --------- Co-authored-by: Kevinking500 <Kevinking500>
* feat(economy): implements "Artikel bearbeiten" Implements https://featureboard.net/suggestions/95f5a741-6d35-4b86-9c67-abd900dca76e * fix(economy): Adressed the issues copilot pointed out and also fixed them in createShopItem
* Weird things are happening. Tried to rebuild it * Weird things are happening. Tried to rebuild it * Next try, please work this time * temp-removed locales
|
"too complex to solve in the web edditor" what else am I supposed to do 😭 |
|
Oh, the latest commit for module name list cleaner temp removed locales which causes this.. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Staff Management System module that adds management workflows (infractions/suspensions, promotions, staff reviews, activity checks, LoA/RA status handling, and shift tracking) plus supporting configs, models, commands, events, and locale strings. It also includes a small core interaction handler tweak to support commands that use subcommand groups.
Changes:
- Add the
staff-management-systemmodule: logic layer, commands, component interaction handlers, scheduled jobs, and Sequelize models. - Add dashboard config schemas for the module’s features (infractions, promotions, reviews, shifts, status, profiles, activity checks).
- Extend localization (
locales/en.json) with staff-management-system strings (and unrelated guess-the-number strings).
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/events/interactionCreate.js | Treats SUB_COMMAND_GROUP as a subcommand container when dispatching slash commands. |
| modules/staff-management-system/staff-management.js | Core logic for staff management features (infractions, promotions, reviews, statuses, panels, activity checks). |
| modules/staff-management-system/module.json | Module metadata and config file registration. |
| modules/staff-management-system/models/ActivityCheck.js | Sequelize model for activity check records. |
| modules/staff-management-system/models/Infraction.js | Sequelize model for infractions/suspensions. |
| modules/staff-management-system/models/LoaRequest.js | Sequelize model for LoA/RA requests. |
| modules/staff-management-system/models/Promotion.js | Sequelize model for promotions. |
| modules/staff-management-system/models/StaffProfile.js | Sequelize model for staff profile + duty/status fields. |
| modules/staff-management-system/models/StaffReview.js | Sequelize model for staff reviews. |
| modules/staff-management-system/models/StaffShift.js | Sequelize model for shift logs. |
| modules/staff-management-system/events/interactionCreate.js | Component interaction routing for staff management UI, duty UI, LoA/RA actions, deletion. |
| modules/staff-management-system/events/guildMemberRemove.js | Auto-closes open shifts when a member leaves. |
| modules/staff-management-system/events/botReady.js | Schedules LoA/RA expiry jobs and checks expired suspensions periodically. |
| modules/staff-management-system/configs/configuration.json | Base roles + general log channel config schema. |
| modules/staff-management-system/configs/infractions.json | Infractions/suspensions config schema + templates. |
| modules/staff-management-system/configs/promotions.json | Promotions config schema + templates. |
| modules/staff-management-system/configs/reviews.json | Reviews config schema + templates. |
| modules/staff-management-system/configs/shifts.json | Shifts/leaderboard/quota config schema. |
| modules/staff-management-system/configs/status.json | LoA/RA status config schema + logging options. |
| modules/staff-management-system/configs/profiles.json | Staff profiles config schema + template. |
| modules/staff-management-system/configs/activity-checks.json | Activity check + automation config schema. |
| modules/staff-management-system/commands/staff-management.js | Slash command entrypoint for most staff management actions. |
| modules/staff-management-system/commands/status.js | Slash command for LoA/RA request/view/list/admin manage. |
| modules/staff-management-system/commands/duty.js | Slash command + button handlers for duty/shift tracking UX. |
| locales/en.json | Adds staff-management-system strings (and new guess-the-number strings). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
All changes have been fixed that Copilot mentioned here |
|
Just a quick note, I am quite the bit less available and more busy the last few days, and would likely stay like this for roughly the next week(s) as I am preparing and planning for my trip next month ^^ |
SCDerox
left a comment
There was a problem hiding this comment.
Great PR! Some small rather important things:
|
Due to a sudden finding of a critical bug (duty time still being recorded even while being on break) during the testing after changes, the next commit will unfortunately be delayed by quite the bit until I fix this ^^ |
|
Actually, no big delay (I sent that fairly late, I was working for this for more than 1 hour now). Will be committing it shortly |
SCDerox
left a comment
There was a problem hiding this comment.
Code Review
Found 9 issues (4 critical bugs, 1 security issue, 4 logic/data bugs):
del_allconfirmation bypassed — data deleted immediately without waiting for user confirmation (missingreturnafter collector setup)interaction.update()called onModalSubmitInteractionin LOA deny handler (method doesn't exist on modal submits)handleStatusEndcalled with wrong arguments —clientpassed whereinteractionexpected, crashes on every "End Early" button pressinteraction.update()called onModalSubmitInteractionin duty admin add-time handler (same root cause as #2)- Break recovery corrupts shift
startTimeon every bot restart by double-counting elapsed break time guildMemberRemoveusesfindOneinstead offindAll— orphans multiple open shifts- No authorization re-check on admin duty button handlers (force-end, void, add-time)
initActivityCheckAutomationnever called in botReady — automated checks never fire- Suspension
isSuspended: trueupsert only runs conditionally — leaves inconsistent state
Generated with Claude Code
| }); | ||
| } | ||
|
|
||
| await executeDataDeletion(client, targetId, selection); |
There was a problem hiding this comment.
Bug (Critical): del_all confirmation bypassed — data deleted immediately without user confirmation.
When selection === 'del_all', the if block (lines 202-283) sets up a confirmation dialog with a button collector. But after that block ends at line 283, execution falls through to this line which calls executeDataDeletion unconditionally. This means:
- Data is wiped immediately before the user clicks "Confirm"
interaction.reply()at line 297 crashes because it was already called at line 221- If the user then clicks Confirm, deletion runs a second time
Fix: Add return; after line 283 to prevent fall-through, or wrap lines 285-300 in an else block.
| name: localize('staff-management-system', 'general-rsn'), | ||
| value: reason | ||
| }); | ||
| return interaction.update({ |
There was a problem hiding this comment.
Bug (Critical): interaction.update() does not exist on ModalSubmitInteraction.
This handler runs on a modal submit (interaction.isModalSubmit() at line 421), but ModalSubmitInteraction in Discord.js v14 does not have an update() method — only MessageComponentInteraction does. This throws TypeError: interaction.update is not a function every time a supervisor denies a LOA/RA request.
Fix: Replace with interaction.reply() or use interaction.deferReply() + interaction.editReply().
| const type = action.startsWith('loa-') ? 'LOA' : 'RA'; | ||
| const base = action.replace(/^(loa|ra)-/, ''); | ||
|
|
||
| if (base === 'end') return handleStatusEnd(client, interaction, type); |
There was a problem hiding this comment.
Bug (Critical): Wrong argument order — client passed where interaction expected.
handleStatusEnd is defined in status.js:467 as handleStatusEnd(interaction, type) (2 params), but here it is called with (client, interaction, type) (3 args). Inside the function, interaction receives the client object, so interaction.customId.split('_') throws immediately. The "End Early" button is completely non-functional.
Fix: Change to handleStatusEnd(interaction, type).
| const targetMember = await interaction.guild.members.fetch(targetUserId).catch(() => null); | ||
| const payload = await buildDutyAdminPayload(client, targetMember, interaction.member); | ||
|
|
||
| return interaction.update(payload); |
There was a problem hiding this comment.
Bug (Critical): interaction.update() does not exist on ModalSubmitInteraction.
This handler is called for the duty-mgmt_admin-addtime-submit modal submission. ModalSubmitInteraction has no update() method — same issue as the LOA deny handler. Every admin attempt to add manual time crashes.
Fix: Replace with interaction.reply(payload) or interaction.editReply(payload) after deferring.
| const elapsedBreakMs = now.getTime() - breakStartedAt.getTime(); | ||
|
|
||
| await activeShift.update({ | ||
| startTime: new Date(new Date(activeShift.startTime).getTime() + elapsedBreakMs) |
There was a problem hiding this comment.
Bug: Break recovery corrupts shift startTime on every bot restart.
This adjusts startTime forward by the elapsed break time, then resets breakStartTime to now at line 111 — but leaves onBreak: true. On the next restart, recoverActiveBreaks runs again, calculates elapsed time since the last restart, and pushes startTime forward again. Each restart accumulates the error. After multiple restarts, staff on break will have zero-duration or negative-duration shifts.
Fix: Either end the break after recovery (onBreak: false, breakStartTime: null) or don't reset breakStartTime to now.
| const StaffProfile = client.models['staff-management-system']['StaffProfile']; | ||
|
|
||
| try { | ||
| const openShift = await StaffShift.findOne({ |
There was a problem hiding this comment.
Bug: findOne only closes one open shift — remaining shifts are orphaned permanently.
If a user has multiple open shifts, only the first is closed. The rest remain with endTime: null forever. Also, onBreak and breakStartTime are not reset (line 30 only sets onDuty: false).
Fix: Change to findAll and loop, matching handleDutyAdminForceEnd (duty.js:614-631). Also reset onBreak/breakStartTime.
| } | ||
|
|
||
| // ----- Admin handler ----- | ||
| async function handleDutyAdminForceEnd(client, interaction) { |
There was a problem hiding this comment.
Security: No authorization re-check on admin duty button handlers.
The /duty admin slash command checks permissions, but handleDutyAdminForceEnd (607), handleDutyAdminVoidActive (649), handleDutyAdminVoidAll (676), and handleDutyAdminAddTimeSubmit (768) have no permission check. handleDutyAdminVoidAll is especially dangerous — it permanently destroys all shift records.
Fix: Add a permission guard at the top of each handleDutyAdmin* function.
| const { Op } = require('sequelize'); | ||
| const { scheduleStatusExpiry } = require('../commands/status.js'); | ||
| const suspension_check_job = 'staff-management-checks'; | ||
|
|
There was a problem hiding this comment.
Bug: initActivityCheckAutomation is never imported or called — automated activity checks will never fire.
The function is exported from staff-management.js but never used here. Users who configure automated checks in activity-checks.json will see no checks run.
Fix: Import and call initActivityCheckAutomation(client) in this handler.
| const durationString = `${durationDays} ${localize('staff-management-system', 'label-days')}`; | ||
|
|
||
| const hierarchyRole = interaction.guild.roles.cache.get(config.suspensionHierarchyRole); | ||
| if (hierarchyRole) { |
There was a problem hiding this comment.
Bug: isSuspended: true upsert nested inside if (rolesToRemove.length) — never set when no roles to remove.
If no suspensionHierarchyRole is configured or user has no matching roles, this block is skipped. The suspension record exists but isSuspended stays false. When the suspension expires, cleanup in checkExpiredSuspensions (botReady.js:161) skips because profile?.isSuspended is false — suspension role is never removed.
Fix: Move the upsert outside the if (rolesToRemove.length) guard.
SCDerox
left a comment
There was a problem hiding this comment.
Automated Agentic Code Review
Hey Kevin, thanks for your contribution on the staff management system -- this is a big module and a lot of solid work!
Heads up: This is an automated agentic review test that Simon is running. An AI agent (Claude Code) independently reviewed every file in this PR using multiple parallel review passes (bug scanning, security audit, UX review, cross-file consistency, and code quality analysis). Each finding was then independently scored for confidence, and only issues scoring above the threshold were included.
Found 9 high-priority issues (from the previous review comment) and 15 lower-priority issues (marked as "Minor" in this review).
The high-priority ones include 4 critical runtime crashes (wrong method calls on modal submits, argument mismatch, missing return), 1 security concern (missing auth re-checks on admin handlers), and 4 data integrity bugs. The minor ones cover validation gaps, UX improvements, i18n issues, and edge cases.
Please feel free to:
- Dismiss any findings that are wrong or not applicable -- automated reviews can produce false positives
- Ask for clarification on any comment if the reasoning isn't clear
- Provide feedback on whether this type of agentic review is useful, too noisy, or could be improved
Simon is testing this workflow and your feedback would be valuable for tuning it.
Thanks again for the contribution!
Generated with Claude Code
| return interaction.showModal(modal); | ||
| } | ||
|
|
||
| async function handleStatusEndSubmit(client, interaction, type) { |
There was a problem hiding this comment.
Minor (Security): No authorization re-check on handleStatusEndSubmit and handleStatusExtendSubmit modal handlers.
Same pattern as the duty admin button issue — the slash command checks supervisor permissions, but these modal submission handlers don't re-verify. A forged modal customId could bypass the UI gate.
| } | ||
|
|
||
| // ----- Deny modal submission ----- | ||
| if (interaction.isModalSubmit() && action === 'loa-deny') { |
There was a problem hiding this comment.
Minor (Security): No authorization re-check on loa-deny modal submission.
The deny button handler (line 356) checks isSupervisor before showing the modal, but this modal submit handler doesn't re-check. Request IDs are sequential integers, making them enumerable.
| if (!config?.enableInfractions) return interaction.reply({ | ||
| content: localize('staff-management-system', 'err-feat-disabled', { feature: 'Infractions' }), | ||
| flags: MessageFlags.Ephemeral | ||
| }); |
There was a problem hiding this comment.
Minor (Validation): Infraction type not validated against configured types.
Discord autocomplete is advisory — users can submit arbitrary strings via the API. The code only rejects 'suspension' but doesn't validate against config.infractionTypes. An arbitrary type string gets stored in the DB and displayed in embeds.
| const minutesRaw = interaction.fields.getTextInputValue('minutes'); | ||
| const shiftType = interaction.fields.getTextInputValue('type'); | ||
|
|
||
| const minutes = parseInt(minutesRaw); |
There was a problem hiding this comment.
Minor (Validation): No upper bound on admin add-time minutes.
A supervisor can enter 999999999 minutes (~1900 years), creating a shift record that dominates leaderboard calculations and quota stats. Consider adding a reasonable cap (e.g. 10080 minutes = 1 week).
| } | ||
|
|
||
| // ----- Promotion history pagination ----- | ||
| if (action === 'prom-hist') { |
There was a problem hiding this comment.
Minor (UX): No deferUpdate() before DB queries in pagination handlers.
The promotion history, infraction history, and review history pagination handlers (lines 70-120) perform DB queries before replying without calling deferUpdate() first. If the database is slow, this risks Discord's 3-second interaction timeout, showing "This interaction failed" to the user.
|
|
||
| const oldEndDate = new Date(request.endDate); | ||
| const newEndDate = new Date(oldEndDate.getTime() + days * 24 * 60 * 60 * 1000); | ||
| await request.update({ endDate: newEndDate }); |
There was a problem hiding this comment.
Minor (Data): scheduleStatusExpiry may use stale in-memory endDate after extend.
After request.update({ endDate: newEndDate }), scheduleStatusExpiry(client, request) is called immediately. Depending on Sequelize version/config, the in-memory request.endDate may not reflect the new value, causing the expiry job to fire at the old (earlier) time.
Consider await request.reload() after the update.
| }, | ||
| type: { | ||
| type: DataTypes.STRING, | ||
| defaultValue: "General" |
There was a problem hiding this comment.
Minor (Data): Model default "General" conflicts with code fallback "Staff".
The model defines defaultValue: "General" but every fallback in duty.js uses 'Staff' (e.g. lines 141, 496, 534). Shifts inserted without an explicit type get "General" stored, but the UI and autocomplete only offer configured duty types (defaulting to ['Staff']). These shifts become invisible in per-type filtered queries.
| } | ||
| } | ||
|
|
||
| if (dataType === 'del_all') { |
There was a problem hiding this comment.
Minor (Data): del_all doesn't clear isSuspended or suspendedRoles on the profile.
The "delete all" data wipe clears shifts, infractions, promotions, reviews, status requests, and activity data, but doesn't reset isSuspended or suspendedRoles. A suspended user whose data is wiped retains stale suspension state, causing checkExpiredSuspensions to attempt restoring roles from stale data.
| flags: MessageFlags.Ephemeral | ||
| }); | ||
|
|
||
| let responded = JSON.parse(activeCheck.respondedUsers || '[]'); |
There was a problem hiding this comment.
Minor (Data): Race condition in activity check response recording.
The read-modify-write on respondedUsers (JSON text field) is non-atomic: parse JSON, check if user exists, push, write back. Two concurrent button clicks can both read the same state before either writes, silently dropping one response. Consider a separate ActivityCheckResponse table with a unique constraint, or a DB-level atomic update.
| // ----- Gets infraction history ----- | ||
| async function getInfractionHistory(client, interaction, targetUser) { | ||
| const response = await generateInfractionHistoryResponse(client, targetUser, 1); | ||
| if (response.content && response.content.startsWith('ℹ️')) return interaction.reply(response); |
There was a problem hiding this comment.
Minor (Fragile logic): Reply routing decided by emoji prefix of localized string.
This checks response.content.startsWith('ℹ️') to determine routing. If the locale string for the "clean record" key ever changes its prefix, this branch silently mis-routes. The same pattern exists at line 614 for promotion history. Consider using a separate flag or property instead of inspecting the localized string content.
Staff Management System: a new module designed for management, by management
(Idk that sounded cool)
This PR introduces the Staff Management System, which directly also correlates to Seppe's suggestion.
Features in this module:
This time, unlike the PP module, data will be kept until deleted manually by management roles and above. Though this might be changed.
AI usage in this module: