Skip to content

Add option for moderator approval for declarations and actions#1954

Draft
Copilot wants to merge 5 commits intodevelopfrom
copilot/feature-moderator-approval-declarations
Draft

Add option for moderator approval for declarations and actions#1954
Copilot wants to merge 5 commits intodevelopfrom
copilot/feature-moderator-approval-declarations

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 22, 2026

Adds a configurable moderator approval system for faction declarations (war, alliance, vassalization). When enabled via config, these actions require a moderator with the mf.approve permission to approve or deny them before they take effect. Players can optionally include a reason with their request.

Changes Made

  • Data Model: New MfApprovalRequest, MfApprovalRequestId, and MfApprovalRequestType classes in the approval/ package
  • Service: MfApprovalRequestService using in-memory ConcurrentHashMap, integrated into Services.kt and MedievalFactions.kt
  • New Commands:
    • /f approve [id] — Approves a pending action and executes it
    • /f deny [id] — Denies a pending action and notifies the requesting faction
    • /f pendingactions — Lists all pending actions with details
  • Modified Commands: /f declarewar, /f ally, /f vassalize now check config and create pending approval requests when required, with optional reason support via -- reason text
  • Configuration: Three new boolean config options under factions.: warDeclarationRequiresApproval, allyDeclarationRequiresApproval, vassalizeDeclarationRequiresApproval (all default false)
  • Permissions: mf.approve permission node (default: op, included in mf.admin)
  • Localization: 34 new language keys added to all 5 language files (en_US, en_GB, de_DE, fr_FR, pt_BR)
  • Help Menu: New commands added to /f help output
  • Documentation: CONFIG.md and COMMANDS.md updated with new options and commands
  • Tests: 9 unit tests for the approval service covering add, get, remove, duplicate detection, and null reason handling
Original prompt

This section details on the original issue you should resolve

<issue_title>[Feature] Option for moderator approval to be required for declarations and other actions</issue_title>
<issue_description>## Is your feature request related to a problem?
My server has a system where players are required to declare a war/alliance in my Discord server AND get it approved by a moderator before they are allowed to run the /declarewar or /ally commands. This is because we have rules on when you are/aren't allowed to declare wars, and we want to have our moderators look over every war request.

Unfortunately, even if a declaration has not been approved (or has been denied) by moderators, the Medieval Factions plugin will not stop players from simply running /declarewar or /ally.

Describe the solution you'd like

I think there should be a permission node that can be given to a player, which will then allow that player to approve/deny individual war/allyship declarations; such that no declaration will proceed without being approved first.

If this is implemented, it will probably be important that it is configurable. Some servers will have less strict rules and will not want moderators to be required to approve or deny any declarations. Maybe one server owner wants allyship to proceed without approval, but still wants wars to require approval? Or maybe a server owner wants vassalization to require approval, while most other servers do not want that. I think you should be able to determine which faction leader commands should need moderator approval.

And you should be able to send a short message along with your declaration that has the reasoning for your declaration (would be useful for moderators to look at before making the decision to deny/approve).</issue_description>

Comments on the Issue (you are @copilot in this section)


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Copilot AI and others added 3 commits March 22, 2026 20:33
Append localized approval system keys (approve, deny, pending actions,
notifications) to four language files with proper translations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add MfApprovalRequest data model with ID, type, and reason
- Add MfApprovalRequestService with in-memory ConcurrentHashMap storage
- Add /f approve, /f deny, and /f pendingactions commands
- Modify /f declarewar, /f ally, /f vassalize to check config and
  create pending approval requests when required
- Add config options: warDeclarationRequiresApproval,
  allyDeclarationRequiresApproval, vassalizeDeclarationRequiresApproval
- Add mf.approve permission node (default: op)
- Add language keys to all 5 language files (en_US, en_GB, fr_FR, de_DE, pt_BR)
- Update CONFIG.md and COMMANDS.md documentation

Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Dans-Plugins/Medieval-Factions/sessions/a413e2e4-ef4a-4d92-b66e-837051e6ded7
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Dans-Plugins/Medieval-Factions/sessions/a413e2e4-ef4a-4d92-b66e-837051e6ded7
@dmccoystephenson
Copy link
Copy Markdown
Member

@copilot continue work

@dmccoystephenson
Copy link
Copy Markdown
Member

@copilot continue work

Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Dans-Plugins/Medieval-Factions/sessions/26f346bc-04f2-4d17-9b83-0e148b4d2597
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot continue work

All remaining tasks are now complete. In commit 84657fa, I added the approve/deny/pendingactions commands to the help menu, which was the only missing piece. Code review and security checks passed with no issues.

Summary of what's implemented:

  • 3 new commands: /f approve [id], /f deny [id], /f pendingactions
  • 3 config options: warDeclarationRequiresApproval, allyDeclarationRequiresApproval, vassalizeDeclarationRequiresApproval (all default false)
  • Permission node: mf.approve (default: op, included in mf.admin)
  • Reason support: Players can add -- reason text when approval is required
  • Moderator notifications: Online players with mf.approve are notified of new requests
  • 9 unit tests for the approval service
  • All 5 language files updated
  • CONFIG.md and COMMANDS.md documentation updated

Note: The CI build shows "action_required" because it needs first-run approval from repo maintainers. The build may also fail on dependency resolution since repo.dansplugins.com (hosting Ponder) appears to be down — this is a pre-existing issue not related to these changes.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • hub.spigotmc.org
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • jitpack.io
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.dansplugins.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.extendedclip.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.mikeprimm.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.rpkit.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title [WIP] Add option for moderator approval for declarations and actions Add option for moderator approval for declarations and actions Mar 23, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional moderator approval workflow for faction declarations (war, alliance, vassalization) so that actions can be queued and only executed once a moderator approves/denies them.

Changes:

  • Introduces an in-memory approval-request model + service (approval/) and wires it into the plugin’s service container.
  • Adds moderator commands to approve/deny/list pending actions, and updates declare/ally/vassalize commands to optionally queue requests with an optional -- <reason>.
  • Updates config/docs/plugin metadata and adds unit tests for the approval request service.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/main/kotlin/com/dansplugins/factionsystem/approval/MfApprovalRequest.kt New data model for a pending approval request.
src/main/kotlin/com/dansplugins/factionsystem/approval/MfApprovalRequestId.kt New request id wrapper + generator.
src/main/kotlin/com/dansplugins/factionsystem/approval/MfApprovalRequestService.kt In-memory storage/service for pending requests.
src/main/kotlin/com/dansplugins/factionsystem/approval/MfApprovalRequestType.kt Enum for request types (WAR/ALLY/VASSALIZE).
src/main/kotlin/com/dansplugins/factionsystem/MedievalFactions.kt Instantiates and registers the approval request service.
src/main/kotlin/com/dansplugins/factionsystem/service/Services.kt Extends the service container to include approvalRequestService.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/MfFactionCommand.kt Registers new subcommands/aliases for approve/deny/pendingactions.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/approve/MfFactionApproveCommand.kt New /f approve implementation to execute queued actions.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/deny/MfFactionDenyCommand.kt New /f deny implementation to reject queued actions.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/pendingactions/MfFactionPendingActionsCommand.kt New /f pendingactions command to list queued actions.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/declarewar/MfFactionDeclareWarCommand.kt Adds optional approval gating + reason parsing for war declarations.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/ally/MfFactionAllyCommand.kt Adds optional approval gating + reason parsing for alliance requests.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/vassalize/MfFactionVassalizeCommand.kt Adds optional approval gating + reason parsing for vassalization requests.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/help/MfFactionHelpCommand.kt Extends /f help output to include new moderator commands.
src/main/resources/config.yml Adds 3 config toggles to require approval per action type.
src/main/resources/plugin.yml Adds usage/help lines + mf.approve permission node.
src/main/resources/lang/lang_en_US.properties Adds new language keys for approval workflow.
src/main/resources/lang/lang_en_GB.properties Adds new language keys for approval workflow.
src/main/resources/lang/lang_de_DE.properties Adds new language keys for approval workflow.
src/main/resources/lang/lang_fr_FR.properties Adds new language keys for approval workflow.
src/main/resources/lang/lang_pt_BR.properties Adds new language keys for approval workflow.
CONFIG.md Documents the new config options.
COMMANDS.md Documents new moderator commands and the -- <reason> syntax.
src/test/kotlin/com/dansplugins/factionsystem/approval/MfApprovalRequestServiceTest.kt Adds unit tests for the approval request service.

Comment on lines +11 to +12
pendingRequests[request.id] = request
return request
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addRequest/hasPendingRequest don’t provide an atomic way to prevent duplicate pending requests for the same (factionId, targetId, type). Since callers do a check-then-add, concurrent executions can still insert duplicates (and addRequest itself always overwrites by id). Consider keying the map by a composite key (factionId+targetId+type), or adding an addRequestIfAbsent(...)/getOrCreate(...) method that performs the check and insert atomically.

Suggested change
pendingRequests[request.id] = request
return request
synchronized(this) {
val existing = pendingRequests.values.firstOrNull {
it.factionId == request.factionId &&
it.targetId == request.targetId &&
it.type == request.type
}
if (existing != null) {
return existing
}
pendingRequests[request.id] = request
return request
}

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +41
if (requests.isEmpty()) {
sender.sendMessage("$GRAY${plugin.language["CommandFactionPendingActionsNone"]}")
return@Runnable
}
val factionService = plugin.services.factionService
sender.sendMessage("$AQUA${plugin.language["CommandFactionPendingActionsHeader"]}")
for (request in requests) {
val faction = factionService.getFaction(request.factionId)
val target = factionService.getFaction(request.targetId)
val factionName = faction?.name ?: "Unknown"
val targetName = target?.name ?: "Unknown"
val reasonText = if (request.reason != null) " - ${request.reason}" else ""
sender.sendMessage(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, request.type.name.lowercase(), factionName, targetName]}$GRAY$reasonText"
)
}
sender.sendMessage("$GRAY${plugin.language["CommandFactionPendingActionsFooter"]}")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This async task calls sender.sendMessage(...) multiple times. On Paper/Spigot, sending messages to players (and other Bukkit API calls) is expected on the main server thread and can trigger async-catcher errors. Consider building the output asynchronously, then scheduling a synchronous runTask to send the messages.

Suggested change
if (requests.isEmpty()) {
sender.sendMessage("$GRAY${plugin.language["CommandFactionPendingActionsNone"]}")
return@Runnable
}
val factionService = plugin.services.factionService
sender.sendMessage("$AQUA${plugin.language["CommandFactionPendingActionsHeader"]}")
for (request in requests) {
val faction = factionService.getFaction(request.factionId)
val target = factionService.getFaction(request.targetId)
val factionName = faction?.name ?: "Unknown"
val targetName = target?.name ?: "Unknown"
val reasonText = if (request.reason != null) " - ${request.reason}" else ""
sender.sendMessage(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, request.type.name.lowercase(), factionName, targetName]}$GRAY$reasonText"
)
}
sender.sendMessage("$GRAY${plugin.language["CommandFactionPendingActionsFooter"]}")
val messages = mutableListOf<String>()
if (requests.isEmpty()) {
messages.add("$GRAY${plugin.language["CommandFactionPendingActionsNone"]}")
} else {
val factionService = plugin.services.factionService
messages.add("$AQUA${plugin.language["CommandFactionPendingActionsHeader"]}")
for (request in requests) {
val faction = factionService.getFaction(request.factionId)
val target = factionService.getFaction(request.targetId)
val factionName = faction?.name ?: "Unknown"
val targetName = target?.name ?: "Unknown"
val reasonText = if (request.reason != null) " - ${request.reason}" else ""
messages.add(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, request.type.name.lowercase(), factionName, targetName]}$GRAY$reasonText"
)
}
messages.add("$GRAY${plugin.language["CommandFactionPendingActionsFooter"]}")
}
plugin.server.scheduler.runTask(
plugin,
Runnable {
for (message in messages) {
sender.sendMessage(message)
}
}
)

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +45
val factionName = faction?.name ?: "Unknown"
val targetName = target?.name ?: "Unknown"
approvalService.removeRequest(requestId)
sender.sendMessage("$GREEN${plugin.language["CommandFactionDenySuccess", factionName, targetName, request.type.name.lowercase()]}")
plugin.server.scheduler.runTask(
plugin,
Runnable {
faction?.sendMessage(
plugin.language["FactionApprovalDeniedNotificationTitle"],
plugin.language["FactionApprovalDeniedNotificationBody", request.type.name.lowercase(), targetName]
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid hard-coded "Unknown" here; there are existing language keys like UnknownFaction/UnknownPlayer. Also, request.type.name.lowercase() is not localized—map the request type to a language key before inserting it into messages.

Suggested change
val factionName = faction?.name ?: "Unknown"
val targetName = target?.name ?: "Unknown"
approvalService.removeRequest(requestId)
sender.sendMessage("$GREEN${plugin.language["CommandFactionDenySuccess", factionName, targetName, request.type.name.lowercase()]}")
plugin.server.scheduler.runTask(
plugin,
Runnable {
faction?.sendMessage(
plugin.language["FactionApprovalDeniedNotificationTitle"],
plugin.language["FactionApprovalDeniedNotificationBody", request.type.name.lowercase(), targetName]
val unknownFactionName = plugin.language["UnknownFaction"]
val factionName = faction?.name ?: unknownFactionName
val targetName = target?.name ?: unknownFactionName
val requestTypeKey = "ApprovalRequestType.${request.type.name}"
val requestType = plugin.language[requestTypeKey]
approvalService.removeRequest(requestId)
sender.sendMessage("$GREEN${plugin.language["CommandFactionDenySuccess", factionName, targetName, requestType]}")
plugin.server.scheduler.runTask(
plugin,
Runnable {
faction?.sendMessage(
plugin.language["FactionApprovalDeniedNotificationTitle"],
plugin.language["FactionApprovalDeniedNotificationBody", requestType, targetName]

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +45
approvalService.removeRequest(requestId)
sender.sendMessage("$GREEN${plugin.language["CommandFactionDenySuccess", factionName, targetName, request.type.name.lowercase()]}")
plugin.server.scheduler.runTask(
plugin,
Runnable {
faction?.sendMessage(
plugin.language["FactionApprovalDeniedNotificationTitle"],
plugin.language["FactionApprovalDeniedNotificationBody", request.type.name.lowercase(), targetName]
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The denial notification uses request.type.name.lowercase() in the user-facing body, which won’t be localized. Prefer passing a localized request/action label (from the language files) instead of the enum name.

Suggested change
approvalService.removeRequest(requestId)
sender.sendMessage("$GREEN${plugin.language["CommandFactionDenySuccess", factionName, targetName, request.type.name.lowercase()]}")
plugin.server.scheduler.runTask(
plugin,
Runnable {
faction?.sendMessage(
plugin.language["FactionApprovalDeniedNotificationTitle"],
plugin.language["FactionApprovalDeniedNotificationBody", request.type.name.lowercase(), targetName]
val requestTypeLabel = plugin.language["ApprovalRequestType.${request.type.name.lowercase()}"]
approvalService.removeRequest(requestId)
sender.sendMessage("$GREEN${plugin.language["CommandFactionDenySuccess", factionName, targetName, requestTypeLabel]}")
plugin.server.scheduler.runTask(
plugin,
Runnable {
faction?.sendMessage(
plugin.language["FactionApprovalDeniedNotificationTitle"],
plugin.language["FactionApprovalDeniedNotificationBody", requestTypeLabel, targetName]

Copilot uses AI. Check for mistakes.
plugin.server.onlinePlayers
.filter { it.hasPermission("mf.approve") }
.forEach { moderator ->
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, "war", target.name, request.id.value]}")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action string passed into ApprovalRequestNotification is hard-coded ("war"). In non-English locales this will show up untranslated inside an otherwise localized message. Consider mapping the request type to a localized label before formatting the notification.

Suggested change
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, "war", target.name, request.id.value]}")
val warActionLabel = plugin.language["ApprovalRequestTypeWar"]
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, warActionLabel, target.name, request.id.value]}")

Copilot uses AI. Check for mistakes.
plugin.server.onlinePlayers
.filter { it.hasPermission("mf.approve") }
.forEach { moderator ->
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, "ally", target.name, request.id.value]}")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action string passed into ApprovalRequestNotification is hard-coded ("ally"). In non-English locales this will show up untranslated inside an otherwise localized message. Consider mapping the request type to a localized label before formatting the notification.

Suggested change
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, "ally", target.name, request.id.value]}")
val actionLabel = plugin.language["ApprovalRequestActionAlly"]
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, actionLabel, target.name, request.id.value]}")

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +38
val factionName = faction?.name ?: "Unknown"
val targetName = target?.name ?: "Unknown"
val reasonText = if (request.reason != null) " - ${request.reason}" else ""
sender.sendMessage(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, request.type.name.lowercase(), factionName, targetName]}$GRAY$reasonText"
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command constructs user-facing fallback/formatting strings directly ("Unknown" and the " - " prefix for the reason). Please use existing language keys (e.g. UnknownFaction) and move the reason formatting into the lang string so the whole line is localizable.

Suggested change
val factionName = faction?.name ?: "Unknown"
val targetName = target?.name ?: "Unknown"
val reasonText = if (request.reason != null) " - ${request.reason}" else ""
sender.sendMessage(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, request.type.name.lowercase(), factionName, targetName]}$GRAY$reasonText"
val factionName = faction?.name ?: plugin.language["UnknownFaction"]
val targetName = target?.name ?: plugin.language["UnknownFaction"]
val reason = request.reason ?: ""
sender.sendMessage(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, request.type.name.lowercase(), factionName, targetName, reason]}$GRAY"

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +38
sender.sendMessage(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, request.type.name.lowercase(), factionName, targetName]}$GRAY$reasonText"
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request.type.name.lowercase() is not localized, so non-English locales will still see English enum values. Consider mapping MfApprovalRequestType to language keys (e.g. Ally/AtWar/Vassal or new approval-specific keys) before displaying it.

Suggested change
sender.sendMessage(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, request.type.name.lowercase(), factionName, targetName]}$GRAY$reasonText"
val requestTypeLanguageKey = "ApprovalRequestType.${request.type.name}"
val requestTypeLocalized = plugin.language[requestTypeLanguageKey]
sender.sendMessage(
"$YELLOW${plugin.language["CommandFactionPendingActionsEntry", request.id.value, requestTypeLocalized, factionName, targetName]}$GRAY$reasonText"

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +118
faction.sendMessage(
plugin.language["FactionAllyRequestSentNotificationTitle", request.requesterId.toBukkitPlayer().name ?: "Unknown", target.name],
plugin.language["FactionAllyRequestSentNotificationBody", request.requesterId.toBukkitPlayer().name ?: "Unknown", target.name]
)
target.sendMessage(
plugin.language["FactionAllyRequestReceivedNotificationTitle", request.requesterId.toBukkitPlayer().name ?: "Unknown", faction.name],
plugin.language["FactionAllyRequestReceivedNotificationBody", request.requesterId.toBukkitPlayer().name ?: "Unknown", faction.name]
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback player name "Unknown" is hard-coded and bypasses the language system. Prefer plugin.language["UnknownPlayer"] (and consider storing request.requesterId.toBukkitPlayer() / name once to avoid repeating the lookup).

Suggested change
faction.sendMessage(
plugin.language["FactionAllyRequestSentNotificationTitle", request.requesterId.toBukkitPlayer().name ?: "Unknown", target.name],
plugin.language["FactionAllyRequestSentNotificationBody", request.requesterId.toBukkitPlayer().name ?: "Unknown", target.name]
)
target.sendMessage(
plugin.language["FactionAllyRequestReceivedNotificationTitle", request.requesterId.toBukkitPlayer().name ?: "Unknown", faction.name],
plugin.language["FactionAllyRequestReceivedNotificationBody", request.requesterId.toBukkitPlayer().name ?: "Unknown", faction.name]
val requesterName = request.requesterId.toBukkitPlayer().name ?: plugin.language["UnknownPlayer"]
faction.sendMessage(
plugin.language["FactionAllyRequestSentNotificationTitle", requesterName, target.name],
plugin.language["FactionAllyRequestSentNotificationBody", requesterName, target.name]
)
target.sendMessage(
plugin.language["FactionAllyRequestReceivedNotificationTitle", requesterName, faction.name],
plugin.language["FactionAllyRequestReceivedNotificationBody", requesterName, faction.name]

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +119
plugin.server.scheduler.runTask(
plugin,
Runnable {
plugin.server.onlinePlayers
.filter { it.hasPermission("mf.approve") }
.forEach { moderator ->
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, "vassalize", target.name, request.id.value]}")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action string passed into ApprovalRequestNotification is hard-coded ("vassalize"). In non-English locales this will show up untranslated inside an otherwise localized message. Consider mapping the request type to a localized label before formatting the notification.

Suggested change
plugin.server.scheduler.runTask(
plugin,
Runnable {
plugin.server.onlinePlayers
.filter { it.hasPermission("mf.approve") }
.forEach { moderator ->
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, "vassalize", target.name, request.id.value]}")
val actionLabel = plugin.language["ApprovalRequestActionVassalize"]
plugin.server.scheduler.runTask(
plugin,
Runnable {
plugin.server.onlinePlayers
.filter { it.hasPermission("mf.approve") }
.forEach { moderator ->
moderator.sendMessage("${ChatColor.YELLOW}${plugin.language["ApprovalRequestNotification", faction.name, actionLabel, target.name, request.id.value]}")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants