Farmstead Mods
RU

Votifier

Receives votes from server lists over the V1 (RSA) and V2 (NuVotifier) protocols.

On this page

Commands 5#

CommandSyntaxAliasesPermissionDescription
/togglevotebroadcast/togglevotebroadcast—neoessentials.votifier.togglebroadcastOpt out of vote broadcastsPlayer only plus neoessentials.votifier.togglebroadcast (does not work from the console). It only disables receiving other players' announcements: the player's own vote still broadcasts to everyone else.
/vote/vote—neoessentials.votifier.voteShow vote site linksThe root requires neoessentials.votifier.vote (the console passes). Prints key/value pairs from votifier.json → voteLinks as plain text (§e<name>§7: §f<link>) — links are not clickable, colour codes and gradients inside voteLinks are not processed; with an empty or missing voteLinks it replies "No vote links are configured".
/voteparty/voteparty—neoessentials.votifier.voteShow vote party progressSame node as /vote: neoessentials.votifier.vote. The counter is server-wide and cumulative, not the player's personal contribution; with voteParty.enabled = false the command still replies, it just shows a frozen value.
/votes/votes [player]—neoessentials.votifier.voteShow vote totalsWithout an argument it shows the sender's statistics (from the console that is a player named "Server"). There is no separate permission for others' statistics — anyone with neoessentials.votifier.vote can see any name's counter; completion offers only online players, but any word is accepted, and an unknown name silently shows 0 instead of an error.
/votifier/votifier reload | /votifier genkeys | /votifier testvote <site> [player]—neoessentials.votifier.adminManage the Votifier vote listenerThe root requires neoessentials.votifier.admin (the console passes). Without a subcommand the command does not run (the root node has no .executes) and shows a syntax hint. reload clears the cache of all the mod's configs but does not restart the listener socket; genkeys generates nothing, it only prints instructions; testvote gives a real reward and counts the vote in the statistics and the vote party.

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>.

broadcastMessage understands more than the config comment says#

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.

Configuration#

votifier.json#

KeyDefaultDescription
votifier
votifier.enabledtrueMaster switch — also see modules.votifierEnabled in config.json (that one requires a restart to take effect; this one is reloadable but only actually stops/starts the listener on the next server restart too, since the socket lifecycle is tied to server start/stop, not /neoe reload).
votifier.host"0.0.0.0"Bind address for the vote-listener socket — separate from the Minecraft server port.
votifier.port8192—
votifier.v2Token""Auto-generated on first boot if empty — this is the shared secret used to verify Votifier PROTOCOL V2 (NuVotifier-compatible) votes via HMAC-SHA256. Paste this into your vote site's panel wherever it asks for a "Votifier v2 token"/"NuVotifier token". Leave untouched once generated — changing it invalidates any vote site already configured with the old value.
votifier.sites — Per-vote-site reward configuration, keyed by the exact "serviceName" string the vote site sends (case-insensitive) — e.g. "PlanetMinecraft", "minecraftservers.org". A vote from a site with no matching entry here uses "default" instead.
votifier.sites.default
votifier.sites.default.commands — Console commands run on vote, {player} replaced with the voter's username.
votifier.sites.default.keys{}Crate keys granted per vote, by crate id -> amount. Requires modules.cratesEnabled and a matching crate defined in crates.json — silently skipped otherwise.
votifier.voteLinks — Shown by /vote — plain links/instructions for where to vote, not used for anything else.
votifier.voteLinks.example-site"https://example-vote-site.com/vote/YourServerName"—
votifier.broadcastMessage"&a{player} &7voted for the server! Thanks for your support &c&l❤"Broadcast to the whole server when someone votes. Empty = no broadcast.
votifier.voteParty — Players can opt out of seeing (or causing) the broadcast via /togglevotebroadcast — this only controls whether IT'S SENT to a given player, not whether their own vote triggers one for everyone else.
votifier.voteParty.enabledtrue—
votifier.voteParty.votesRequired50Total votes (server-wide, cumulative) needed to trigger the party
votifier.voteParty.commands
votifier.voteParty.resetOnRestartfalsefalse = the counter keeps counting across restarts toward the next party; true = it resets to 0 every time the server restarts

Permissions 3#

NodeIn registryWhat it grants
neoessentials.votifier.adminregistry: noManage the Votifier listener (/votifier)
neoessentials.votifier.togglebroadcastregistry: noOpt out of vote broadcasts (/togglevotebroadcast)
neoessentials.votifier.voteregistry: noUse /vote, /votes, and /voteparty

Detailed guide#

The section below is the project's original documentation (docs/Wiki/votifier.md). Links to other wiki pages are rewritten to this site; the guides themselves are not edited, so upstream syncs stay conflict-free.

Config file: config/neoessentials/votifier.json Module toggle: modules.votifierEnabled (config.json) Introduced: v1.0.6


Overview#

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.

Votifier can be configured to grant Crate keys as a vote reward — the common "vote crate" server setup — but the two systems work fully independently too.


Setup#

  1. Start the server once with modules.votifierEnabled: true (default). The console logs: - A V1 public key (base64) — paste this into your vote site's panel wherever it asks for a Votifier public key. - A V2 token — paste this wherever the vote site asks for a "NuVotifier token"/"Votifier v2 token". This is auto-generated once and saved to votifier.json — don't regenerate it unless you also update every vote site using it.
  2. Point your vote site(s) at this server's IP and the configured port (default 8192) — not the Minecraft server port.
  3. Configure a reward under votifier.sites for each vote site's exact serviceName (the vote site's panel usually shows you this string), or rely on the "default" entry for any site without a specific match.

Protocol#

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):

  • V1 — a 256-byte RSA/PKCS1-encrypted block, no framing. Decrypted plaintext is 5 newline-separated fields: VOTE, serviceName, username, address, timestamp.
  • V2 (NuVotifier-compatible) — magic bytes 0x73 0x3A, a 2-byte big-endian length prefix, then that many bytes of {"payload": "<json>", "signature": "<base64 HMAC-SHA256>"}. The signature is HMAC-SHA256 over the raw payload string, keyed by the shared V2 token. The inner payload carries serviceName/username/address/timestamp/challenge (the challenge must match this connection's greeting — replay protection).

Config (votifier.json)#

Key Default Description
enabled true Master switch
host / port 0.0.0.0 / 8192 Bind address/port for the vote-listener socket
v2Token (auto-generated) Shared secret for V2 signature verification
sites.<serviceName>.commands (list) Console commands run on vote, {player} substituted
sites.<serviceName>.keys (map) Crate id → key amount granted on vote (requires modules.cratesEnabled)
voteLinks (map) Shown by /vote — plain links/instructions, not functional
broadcastMessage (text) Server-wide "X voted!" message, {player}/{site}. Empty = no broadcast
voteParty.enabled true Cumulative server-wide vote counter with a bonus at a threshold
voteParty.votesRequired 50 Total votes (server-wide) needed to trigger
voteParty.resetOnRestart false Whether the counter resets every restart

A vote from an offline player is queued, not dropped — delivered automatically the next time that player logs in.

Commands#

Command Permission Description
/vote neoessentials.votifier.vote Show configured vote links
/votes [player] neoessentials.votifier.vote Show total vote count
/togglevotebroadcast none Opt out of seeing the vote broadcast
/voteparty neoessentials.votifier.vote Show vote party progress
/votifier reload neoessentials.votifier.admin Reload votifier.json
/votifier testvote <site> [player] neoessentials.votifier.admin Simulate a vote — verify reward config without a real vote site round-trip
/votifier genkeys neoessentials.votifier.admin Instructions to regenerate the V1 RSA keypair

Placeholders#

{votifier_total}, {votifier_voteparty_progress}, {votifier_voteparty_required}.


Back to Wiki Home · See also Crates