Examples#
Test a reward before connecting your first voting site#
You wrote a reward in votifier.sites.default and want to make sure it is given without waiting for a real vote from a site.
/votifier reload
/votes Steve
/votifier testvote default Steve
/votes Steve
After reload — "Votifier config reloaded.". The first /votes shows Steve's current counter, testvote replies "Simulated a vote from default for Steve.", Steve (if online) receives the reward and sees the broadcast from broadcastMessage, and the second /votes shows the counter one higher. The vote party counter also grows by 1 — testvote publishes a real vote event, not a simulation.
Test the vote party without collecting 50 votes#
Make sure the threshold fires and voteParty.commands actually run before setting the production votesRequired value.
/votifier reload
/voteparty
/votifier testvote default Steve
/votifier testvote default Steve
/voteparty
Temporarily set voteParty.votesRequired = 2 in votifier.json before reload. The first /voteparty shows "Vote Party: 0/2"; after the second testvote the console shows "Vote party triggered!", the commands from voteParty.commands run, and the counter resets to 0 immediately — the final /voteparty shows "Vote Party: 0/2" again. The site reward was given twice, so test on a service account.
A vote arrived while the player was offline#
Check that a vote reward is not lost if the player voted on a site and left the game (or never joined).
/votifier testvote default Steve
/votes Steve
Steve is offline — the console shows "Steve voted while offline — reward queued for next login", the reward goes into the votifier_pending queue, there is NO broadcast (it is sent only for an online voter), but the statistics and the vote party counter increase immediately — /votes Steve already shows +1. On his next login Steve sees "Claimed 1 pending vote reward(s)!" and receives all accumulated rewards at once.
Reissue the V1 protocol RSA key#
The private key leaked or you are moving the server and want a clean key pair for voting sites using the old V1 protocol.
/votifier genkeys
The command generates and deletes nothing — it only prints instructions: "Delete config/neoessentials/votifier/public.pem and private.pem, then restart the server to generate a new V1 keypair." You do it by hand: stop the server, delete both .pem files, start it again. At startup the new public key is printed to the console with the line "Votifier V1 public key (paste into your vote site's panel):" — you have to paste it into every voting site's panel again, otherwise their votes will no longer decrypt.
Edge cases#
host, port and v2Token are read only at server start#
VotifierServer.start() is called once, on ServerStartedEvent: it reads host/port, opens the socket and stores v2Token in its own field. /votifier reload only clears the config cache — the already open socket stays on the old address, and V2 signature verification keeps using the old token. The comment in votifier.json warns about this too: enabled: false does not stop a listener that is already running either.
Only sections that are re-read on every use are picked up live via reload: sites, voteLinks, broadcastMessage and voteParty.
What to do: After changing host, port, v2Token or enabled, restart the whole server. Keep /votifier reload for rewards, links, the broadcast and vote party settings.
/votifier reload clears the cache of all the mod's configs, not just votifier.json#
The subcommand calls ConfigManager.getInstance().clearCache(), which clears the shared configCache entirely. This means that along with votifier.json, economy.json, kits.json, crates.json and all the others are re-read from disk — including unfinished edits you left in the editor.
What to do: Do not run /votifier reload while half-edited configs of other systems are on disk. If you only need Votifier, first save everything else in a consistent state.
Two independent switches, and they turn off different things#
modules.votifierEnabled in config.json is checked at command registration: with false the commands /vote, /votes, /voteparty, /togglevotebroadcast and /votifier do not appear in the tree at all — neither in completion nor in help.
votifier.enabled in votifier.json is checked separately, at listener start: the commands stay, but the TCP port is not opened. Side effect: voteParty.resetOnRestart is handled on the line after the listener start, so with Votifier disabled the party counter is not reset on restart even if the flag is true.
What to do: To remove the system from the game entirely — modules.votifierEnabled: false. To temporarily stop accepting votes while keeping commands and statistics — votifier.enabled: false plus a server restart.
Site matching is case-insensitive; the only fallback is "default"#
resolveSiteConfig() iterates the sites keys and compares them with the received serviceName via equalsIgnoreCase, so case in the site name does not matter. If there is no exact match, the default entry is used. If that is missing too (or there is no sites section at all), no reward is given, silently — no commands, no crate keys, no error message. The player's statistics and the vote party counter still increase.
What to do: Always keep a default entry in sites. Take the exact serviceName value from the voting site's panel, and test with /votifier testvote <serviceName> <name>.
Crate keys require the Crates module to be enabled#
The sites.<site>.keys block is processed only if ConfigManager.isCratesModuleEnabled(). With modules.cratesEnabled: false keys are silently not given, while the commands from sites.<site>.commands run as usual — the vote reward becomes partial without a single warning.
What to do: If crate keys are the main vote reward, check modules.cratesEnabled in config.json and make sure the balance grows: /crate keys <name>.
The text goes through RichTextFormatter.processTablistText(), so besides the declared {player} these work: the {site} substitution (the voting site name), & codes, gradients and rainbow, named colour tags and even {animation:name} tokens from animations.json. Hover/click markup, however, is stripped — you cannot put a clickable link in a vote announcement.
An empty broadcastMessage string disables the announcement entirely.
What to do: Style the announcement like tablist lines; keep the link to the voting site for /vote.
Statistics and the reward queue are tied to the name, not the UUID#
The votifier_stats and votifier_pending collections (in neoessentials/store/) are keyed by username.toLowerCase() — the name the voting site sent. Case does not matter, but a name change does: after renaming, /votes shows 0, and an already queued reward stays under the old name and is never delivered. The UUID is used here only for giving crate keys and for remembering the broadcast opt-out (votifier_broadcast_toggle).
What to do: When a player renames, move their records in neoessentials/store/votifier_stats.json and votifier_pending.json by hand on a stopped server.
From the console /votes and /votifier testvote substitute the name "Server"#
Without a player argument both commands use source.getTextName(). For the console that is the string "Server", so /votes from the console always shows "Server has 0 total vote(s).", and /votifier testvote default without a name counts a vote for the nonexistent player "Server": it increases the vote party counter, creates a statistics record under the key server and queues a reward (there is no such player online).
What to do: From the console always specify the name explicitly: /votes Steve, /votifier testvote default Steve.
Permission checks let non-players through#
/vote, /votes, /voteparty and /votifier are written as p == null || PermissionAPI.hasPermission(...): the console, a command block and a function always pass, regardless of the neoessentials.votifier.vote and neoessentials.votifier.admin nodes. /togglevotebroadcast requires both a player and neoessentials.votifier.togglebroadcast.
What to do: Remember that a command block with /votifier testvote gives a reward without any permissions — restrict access to command blocks with the usual Minecraft means.
Known issues#
The voting site reports "vote sent", but nothing happens on the server and the log is empty#
All vote rejections are logged via NeoLog.debug(..., LogCategory.VOTIFIER, ...): an invalid V2 HMAC signature, a mismatched challenge, a broken 256-byte V1 block, any exception while parsing the connection. In config.json logging.categories.votifier.debug is false by default, and the debug level is written only to logs/debug.log, not the console. As a result the most common setup error — a V2 token or V1 public key copied incompletely — looks like total silence.
What to do: Set logging.categories.votifier.debug: true in config.json, run /neoessentials reload and check logs/debug.log; it will show "Invalid V2 vote signature" or "Invalid V2 vote challenge". A successfully accepted vote, in contrast, is logged at info level with the line "Received V1/V2 vote from ... for player ...".
A typo in sites.*.keys silently gives a key for a nonexistent crate#
The comment in votifier.json promises that without a matching crate in crates.json the key is "silently skipped". In reality VoteRewardManager calls CrateKeyManager.addKeys() directly and does not check that the crate exists: the key balance is written to the crate_keys collection under the given id. The player sees the keys in /crate keys, but /crate open <id> replies "crate not found", and there is no automatic way to move those keys to a real crate.
What to do: Match the ids in sites.<site>.keys with the crate ids in crates.json character for character. After editing the config test the whole chain: /votifier testvote <site> <name> → /crate keys <name> → /crate open <id>.
/votifier genkeys generates nothing#
Both the subcommand name and its description in the command registry promise key generation. The implementation is a single sendSuccess with a hint text: keys are created only inside RSAKeyManager.load(), which runs at server start and only if config/neoessentials/votifier/public.pem and private.pem are not on disk.
What to do: Treat genkeys as help. Real regeneration: stop the server, delete both .pem files, start the server, take the new public key from the console and paste it into every voting site panel.
V2 votes stop arriving after every server restart#
If v2Token in votifier.json is empty, the server generates a random UUID and tries to save it back to the config. When the write fails (file permissions, a read-only config, a read-only volume), the log gets an ERROR "Failed to save auto-generated Votifier V2 token — it will regenerate every restart until this is fixed", and on the next start the token is different — the one you pasted into the voting site panel becomes invalid.
What to do: Find this ERROR in the log, fix the permissions on config/neoessentials/votifier.json or write a permanent v2Token by hand, and restart the server.
/votifier testvote is not a safe check but a real vote#
The subcommand builds a Vote object and publishes PlayerVoteEvent directly, bypassing the whole protocol layer. All subscribers run for real: reward commands execute (including giving money and items), crate keys are credited, the player's statistics grow, the vote party counter grows and may cross the threshold right there, and all online players get the "X voted" broadcast. There is no confirmation and no undo.
What to do: Test on a service account, not a real player. Undoing the effects is manual: neoessentials/store/votifier_stats.json and votifier_voteparty.json on a stopped server.
The V1 protocol has no replay protection#
In V2 the server sends a one-time challenge in the greeting and rejects the vote if the received challenge does not match — protection against resending an intercepted packet. V1 has no such field in the protocol, and handleV1() checks nothing similar: the same 256-byte RSA block can be sent to the port again and earn the reward as many times as you like.
What to do: Where the voting site supports NuVotifier (V2), configure that. Do not expose the listener port (8192 by default) to the internet more widely than necessary — restrict access with a firewall to the voting sites' addresses.
Votifier messages are not translated into Russian#
The shipped data/lang/ru_ru.json has no commands.neoessentials.votifier.* keys — all 10 of the system's strings exist only in en_us.json. With localization.language: ru_ru the loader fills in missing keys with the English fallback, so both players and admins see "Vote Party: 3/50", "Claimed 1 pending vote reward(s)!" and the other messages in English.
What to do: These strings are automatically added to neoessentials/languages/custom/ru_ru.json during the merge at startup — translate them right there and restart the server. Do not enable localization.preserveCustomTranslations until the translation is done, otherwise merging new keys is disabled.
A vote-listener compatible with both Votifier protocol versions (V1/RSA and V2/NuVotifier), so
vote-site relay services (PlanetMinecraft, minecraftservers.org, etc.) can notify this server
when a player votes. Runs on its own TCP port, separate from the Minecraft server port.
Both wire protocols are auto-detected on the same port per connection (byte-verified against
the reference NeoForge implementation, github.com/uberswe/votifier, rather than reconstructed
from memory):