Голосование за сервер
Как голосуют игроки
Сейчас голосование работает у серверов Minecraft. Блок «Голосовать за сервер» стоит на странице сервера, пока сервер онлайн.
Дайте игрокам ссылку на страницу сервера с #vote в конце — она откроется сразу на блоке голосования. Вместо play.example.ru — адрес сервера, как он записан на monwave:
https://monwave.ru/minecraft/servers/play.example.ru#vote
Кнопка для сайта или форума:
<a href="https://monwave.ru/minecraft/servers/play.example.ru#vote" target="_blank">Голосовать за сервер на MonWave</a>
Два способа выдать награду
Оба способа подключает подтверждённый владелец сервера. Выберите один: если подключить оба и выдавать награду в каждом, игрок получит её дважды.
- Стандарт Minecraft-мониторингов: подходит любой плагин наград (VotingPlugin, SuperbVote и др.)
- Награда приходит сразу после голоса, даже если игрок не в игре — так решает плагин
- Нужен открытый наружу порт (по умолчанию 8192)
- Подходит, когда нет NuVotifier или порт открыть нельзя: свой плагин, Discord-бот, сайт сервера
- Типичная схема: игрок пишет в игре
/vote, плагин спрашивает monwave и выдаёт награду - Награда за один голос выдаётся ровно один раз — это проверяет monwave
Настройки награды и ключ API — на странице редактирования, она открыта владельцу. Войдите на сайт, откройте страницу своего сервера → кнопка «Подтвердить» (адрес вида monwave.ru/minecraft/servers/play.example.ru/access), впишите выданный код в MOTD сервера, перезапустите сервер и нажмите «Проверить». Код потом можно убрать. Настройки NuVotifier по вашей просьбе может поправить и администрация сайта, ключ API — только вы.
Сменился владелец — ключ API прежнего перестаёт работать, а его настройки NuVotifier отключаются: голоса на его адрес больше не уходят, токен новому владельцу не показывается. Новый владелец вписывает свои настройки и выпускает свой ключ.
Награда через NuVotifier — пошагово
Скачайте NuVotifier и положите в plugins сервера (Spigot, Paper и их форки). Сеть на BungeeCord или Velocity: NuVotifier ставят на прокси (голоса monwave приходят туда) и включают в его config.yml пересылку голосов на игровые серверы — раздел forwarding; на игровых серверах NuVotifier тоже нужен. Перезапустите сервер: плагин создаст plugins/Votifier/config.yml.
По умолчанию NuVotifier слушает порт 8192 (меняется в config.yml, параметр port). Порт должен быть доступен из интернета: откройте его в фаерволе и в панели хостинга. Если пускаете на порт только избранные адреса — разрешите IP-адрес monwave.ru (узнать: ping monwave.ru).
Токен — в plugins/Votifier/config.yml, в разделе tokens, строка default:
port: 8192 tokens: default: 1a2b3c4d5e6f7g8h9i0j
Старый классический Votifier (v1) токена не знает — вместо него нужен публичный ключ: содержимое файла plugins/Votifier/rsa/public.key одной строкой.
Страница сервера → «Редактировать» → блок «Награда за голос (NuVotifier)»: хост (IP или домен сервера), порт, протокол NuVotifier v2 — токен и сам токен. Нажмите «Проверить и сохранить» — monwave сразу отправит тестовый голос от ника, который вы укажете (или от MonWave), и напишет, принял ли его сервер, или что не так. Настройки сохраняются и при ошибке: исправьте и проверьте снова (не чаще раза в 10 секунд). В блоке потом виден статус: «Работает» или текст последней ошибки.
Награду выдаёт ваш плагин наград. Имя сервиса monwave — MonWave, именно так, с большими M и W: по нему плагин понимает, с какого мониторинга голос. Пример для VotingPlugin (VoteSites.yml):
VoteSites:
MonWave:
Enabled: true
ServiceSite: 'MonWave'
VoteURL: 'https://monwave.ru/minecraft/servers/play.example.ru#vote'
VoteDelay: 24
Rewards:
Commands:
- 'give %player% diamond 1'В других плагинах поле называется по-своему (service, serviceName) — значение то же: MonWave.
Имя сервиса MonWave, ник игрока, время голоса и адрес голосующего без последней части (вида 1.2.3.0) — полный IP игрока вашему серверу не нужен. monwave ждёт сервер до 4 секунд на каждом шаге (соединение, приветствие Votifier, ответ). По протоколу v1 сервер подтверждения не присылает, поэтому для него «Работает» значит «голос отправлен».
API с личным ключом
Страница сервера → «Редактировать» → блок «API-ключ сервера» → «Создать ключ». Ключ вида mw_ и 40 символов показывается один раз — сразу вставьте его в настройки плагина или бота. Потеряли — выпустите новый: старый тут же перестанет работать. Ключ выдаётся только владельцу сервера; администрация сайта его тоже не видит.
X-Api-Key: mw_0123456789abcdef0123456789abcdef01234567
Можно и Authorization: Bearer mw_…, а в POST-запросе — полем key в теле формы. В адресе (?key=) ключ не принимается: адрес оседает в журналах серверов и истории браузера. Только https:// — по http API владельца отвечает ошибкой.
Игрок пишет в игре /vote → плагин отправляет POST /votes/claim с его ником. Ответ "claimed": true — голос ваш, выдавайте награду. Повторный запрос по тому же голосу вернёт false, поэтому награду за один голос не выдать дважды, даже если запросы придут одновременно.
Методы
Адрес API: https://monwave.ru/api/v1. Ответы — JSON в кодировке UTF-8, время — в секундах Unix.
| Метод | Что делает |
|---|---|
GET /me | Ваш сервер: адрес, онлайн, игроки, ссылка для голосования и счётчики голосов votes: today — за сутки, month — за 30 дней, unclaimed_today — не забранные за сутки |
GET /votes/check?nick=Steve | Голосовал ли ник за последние сутки, когда, забран ли голос и когда этот ник сможет голосовать снова (next_vote_at; с того же IP голос может не пройти раньше). Ничего не меняет |
POST /votes/claim nick=Steve | Забрать голос ника за последние сутки, чтобы выдать награду. true — ровно один раз на голос |
GET /votes?after_id=0&limit=100 | Голоса по порядку после голоса с номером after_id (до 500 за раз) — для бота, который сам проверяет новые голоса. Голоса хранятся 90 дней |
Примеры ответов
POST /api/v1/votes/claim nick=Steve
{"data": {"nick": "Steve", "claimed": true, "reason": null, "voted_at": 1790880000}}
— уже забрали: "claimed": false, "reason": "already_claimed"
— голоса нет: "claimed": false, "reason": "no_vote"GET /api/v1/votes/check?nick=Steve
{"data": {"nick": "Steve", "voted": true, "voted_at": 1790880000, "claimed": false,
"claimed_at": null, "next_vote_at": 1790966400}}Код
# Проверить голос curl -H "X-Api-Key: $MONWAVE_KEY" "https://monwave.ru/api/v1/votes/check?nick=Steve" # Забрать голос и выдать награду curl -X POST -H "X-Api-Key: $MONWAVE_KEY" -d "nick=Steve" "https://monwave.ru/api/v1/votes/claim"
<?php
$key = 'mw_…'; // из настроек, не из кода в открытом репозитории
$ch = curl_init('https://monwave.ru/api/v1/votes/claim');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(['nick' => 'Steve']),
CURLOPT_HTTPHEADER => ['X-Api-Key: ' . $key],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$res = json_decode(curl_exec($ch), true);
if (!empty($res['data']['claimed'])) {
// выдать награду игроку
}import requests
KEY = "mw_…" # из настроек бота
r = requests.post("https://monwave.ru/api/v1/votes/claim",
headers={"X-Api-Key": KEY}, data={"nick": "Steve"}, timeout=5)
if r.status_code != 200:
# 401 — проверьте ключ; 429/503 — повторите через r.headers.get("Retry-After") секунд
print("monwave:", r.status_code, r.json().get("error"))
else:
data = r.json()["data"]
if data["claimed"]:
pass # выдать награду
elif data["reason"] == "no_vote":
print("Сначала проголосуйте на monwave")// Node.js 18+ (встроенный fetch)
const KEY = process.env.MONWAVE_KEY;
async function claimVote(nick) {
const res = await fetch("https://monwave.ru/api/v1/votes/claim", {
method: "POST",
headers: { "X-Api-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({ nick }),
});
const body = await res.json();
if (!res.ok) throw new Error(`monwave ${res.status}: ${body.error}`); // 429/503 — повторить позже
return body.data.claimed; // true — выдать награду
}// Запрос в отдельном потоке (runTaskAsynchronously), награду — в основном потоке
HttpClient http = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("https://monwave.ru/api/v1/votes/claim"))
.header("X-Api-Key", key)
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString("nick=" + URLEncoder.encode(player.getName(), StandardCharsets.UTF_8)))
.timeout(Duration.ofSeconds(5))
.build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
// Gson входит в Spigot/Paper. 401 — проверьте ключ, 429/503 — повторите позже
boolean claimed = res.statusCode() == 200 && JsonParser.parseString(res.body())
.getAsJsonObject().getAsJsonObject("data").get("claimed").getAsBoolean();Раз в 1–5 минут бот запрашивает GET /votes?after_id=N, где N — next_after_id из прошлого ответа (в первый раз — 0). Для каждого нового голоса бот вызывает POST /votes/claim с его ником и выдаёт награду, если пришло true. Так награда не задвоится, даже если бот перезапустится, и не потеряется, если бот простоял не дольше суток: забрать голос можно в течение 24 часов. Сохраняйте next_after_id между запусками — с after_id=0 бот заново пролистает всю историю голосов.
Ошибки и лимиты
| Код | Ответ | Что значит |
|---|---|---|
| 400 | bad_nick | Не передан ник или в нём недопустимые символы (1–32 символа) |
| 400 | key_in_url | Ключ передан в адресе (?key=) — передайте его заголовком X-Api-Key |
| 401 | invalid_key | Ключа нет, он неверный или отозван: выпущен новый, сменился владелец, сервер заблокирован |
| 403 | https_required | Запрос пришёл по http:// — нужен https:// |
| 405 | method_not_allowed | «Забрать голос» — только POST, остальное — только GET |
| 429 | rate_limited | Больше 120 запросов в минуту на ключ |
| 429 | too_many_requests | Больше 10 запросов в секунду с одного IP (короткий всплеск — до 20). Лимит общий для всех, кто ходит с этого IP |
| 429 | too_many_bad_keys | С этого IP за 10 минут пришло 20 запросов без ключа или с неверным ключом. До конца десятиминутки такие запросы получают паузу; запросы с верным ключом работают |
| 503 | busy | Сайт под высокой нагрузкой (или с одного IP больше 20 одновременных запросов) — повторите позже |
На 429 и 503 в ответе есть заголовок Retry-After — через столько секунд и повторяйте. Если не успеете, игрок не потеряет голос: забрать его можно в течение суток.
Как всё защищено
Не вставляйте ключ в клиентские моды, публичные репозитории, скриншоты и сообщения в чатах. Если ключ мог утечь — выпустите новый на странице редактирования: старый перестанет работать сразу.
Частые ошибки NuVotifier
Так их показывает проверка на странице редактирования — и что делать:
| Сообщение | Что сделать |
|---|---|
| Порт закрыт или сервер выключен | Проверьте, что сервер запущен и порт Votifier открыт наружу — в фаерволе и в панели хостинга. Порт в config.yml и на monwave должен совпадать |
| На этом порту отвечает не Votifier | Указан порт игры или другого сервиса. Нужен порт из config.yml NuVotifier (по умолчанию 8192) |
| Сервер ответил ошибкой — чаще всего неверный токен | Скопируйте токен из tokens → default ещё раз, без пробелов. После смены токена в config.yml перезапустите сервер |
| Сервер работает по старому протоколу | У вас классический Votifier: выберите «Votifier v1 — публичный ключ RSA» и вставьте содержимое rsa/public.key |
| Это не похоже на публичный ключ RSA | Для v1 нужен именно public.key (не private.key), целиком, одной строкой |
| Хост не находится в DNS | Домен указан с ошибкой или ещё не настроен. Впишите IP сервера |
| Адрес внутренний | Указан локальный адрес (127.0.0.1, 192.168…) или адрес самого monwave. Нужен внешний IP или домен вашего сервера |
Голос засчитывается в счётчик, даже если сервер его не принял: игрок видит «Голос засчитан, но сервер не принял его» и может написать вам.
Вопросы
Голоса поднимают сервер в списке?
Пока нет: это счётчик на странице и награда игрокам. Подняться в списке помогают продвижение и Премиум.
Можно подключить и NuVotifier, и API?
Можно, но выдавайте награду в одном месте. Например, NuVotifier выдаёт награду, а API используется только для проверки /votes/check — показать игроку, голосовал ли он.
Игрок проголосовал, когда сервер был выключен. Награда пропала?
С NuVotifier голос в этот момент не дойдёт до сервера, но останется в счётчике. С API голос можно забрать в течение суток после голосования — например, когда игрок зайдёт и напишет /vote.
У меня не Minecraft. Будет голосование для других игр?
API с ключом уже работает для любого сервера: /me отдаёт данные сервера. Голосование по нику пока включено только у Minecraft.
Ник с большими и маленькими буквами — это разные игроки?
Нет: Steve и steve для API — один и тот же ник.