Голосование за сервер

Игроки голосуют за ваш сервер на monwave — а вы выдаёте им награду в игре. Здесь всё для владельца: как устроено голосование, как подключить награду через NuVotifier или через API с личным ключом, как это защищено и что делать, если не работает.

Как голосуют игроки

Сейчас голосование работает у серверов 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>

Два способа выдать награду

Оба способа подключает подтверждённый владелец сервера. Выберите один: если подключить оба и выдавать награду в каждом, игрок получит её дважды.

NuVotifier
monwave сам отправляет голос на ваш сервер
  • Стандарт Minecraft-мониторингов: подходит любой плагин наград (VotingPlugin, SuperbVote и др.)
  • Награда приходит сразу после голоса, даже если игрок не в игре — так решает плагин
  • Нужен открытый наружу порт (по умолчанию 8192)
Подключить NuVotifier
API с ключом
ваш плагин или бот сам спрашивает monwave
  • Подходит, когда нет NuVotifier или порт открыть нельзя: свой плагин, Discord-бот, сайт сервера
  • Типичная схема: игрок пишет в игре /vote, плагин спрашивает monwave и выдаёт награду
  • Награда за один голос выдаётся ровно один раз — это проверяет monwave
Подключить API
Сначала — подтвердите владение

Настройки награды и ключ API — на странице редактирования, она открыта владельцу. Войдите на сайт, откройте страницу своего сервера → кнопка «Подтвердить» (адрес вида monwave.ru/minecraft/servers/play.example.ru/access), впишите выданный код в MOTD сервера, перезапустите сервер и нажмите «Проверить». Код потом можно убрать. Настройки NuVotifier по вашей просьбе может поправить и администрация сайта, ключ API — только вы.

Сменился владелец — ключ API прежнего перестаёт работать, а его настройки NuVotifier отключаются: голоса на его адрес больше не уходят, токен новому владельцу не показывается. Новый владелец вписывает свои настройки и выпускает свой ключ.

Награда через NuVotifier — пошагово

1 Установите NuVotifier

Скачайте NuVotifier и положите в plugins сервера (Spigot, Paper и их форки). Сеть на BungeeCord или Velocity: NuVotifier ставят на прокси (голоса monwave приходят туда) и включают в его config.yml пересылку голосов на игровые серверы — раздел forwarding; на игровых серверах NuVotifier тоже нужен. Перезапустите сервер: плагин создаст plugins/Votifier/config.yml.

2 Откройте порт

По умолчанию NuVotifier слушает порт 8192 (меняется в config.yml, параметр port). Порт должен быть доступен из интернета: откройте его в фаерволе и в панели хостинга. Если пускаете на порт только избранные адреса — разрешите IP-адрес monwave.ru (узнать: ping monwave.ru).

3 Возьмите токен

Токен — в plugins/Votifier/config.yml, в разделе tokens, строка default:

port: 8192
tokens:
  default: 1a2b3c4d5e6f7g8h9i0j

Старый классический Votifier (v1) токена не знает — вместо него нужен публичный ключ: содержимое файла plugins/Votifier/rsa/public.key одной строкой.

4 Впишите на monwave и проверьте

Страница сервера → «Редактировать» → блок «Награда за голос (NuVotifier)»: хост (IP или домен сервера), порт, протокол NuVotifier v2 — токен и сам токен. Нажмите «Проверить и сохранить» — monwave сразу отправит тестовый голос от ника, который вы укажете (или от MonWave), и напишет, принял ли его сервер, или что не так. Настройки сохраняются и при ошибке: исправьте и проверьте снова (не чаще раза в 10 секунд). В блоке потом виден статус: «Работает» или текст последней ошибки.

5 Настройте награду в плагине

Награду выдаёт ваш плагин наград. Имя сервиса 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 с личным ключом

1 Получите ключ

Страница сервера → «Редактировать» → блок «API-ключ сервера» → «Создать ключ». Ключ вида mw_ и 40 символов показывается один раз — сразу вставьте его в настройки плагина или бота. Потеряли — выпустите новый: старый тут же перестанет работать. Ключ выдаётся только владельцу сервера; администрация сайта его тоже не видит.

2 Передавайте ключ в заголовке
X-Api-Key: mw_0123456789abcdef0123456789abcdef01234567

Можно и Authorization: Bearer mw_…, а в POST-запросе — полем key в теле формы. В адресе (?key=) ключ не принимается: адрес оседает в журналах серверов и истории браузера. Только https:// — по http API владельца отвечает ошибкой.

3 Выдавайте награду через «забрать голос»

Игрок пишет в игре /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 бот заново пролистает всю историю голосов.

Ошибки и лимиты

КодОтветЧто значит
400bad_nickНе передан ник или в нём недопустимые символы (1–32 символа)
400key_in_urlКлюч передан в адресе (?key=) — передайте его заголовком X-Api-Key
401invalid_keyКлюча нет, он неверный или отозван: выпущен новый, сменился владелец, сервер заблокирован
403https_requiredЗапрос пришёл по http:// — нужен https://
405method_not_allowed«Забрать голос» — только POST, остальное — только GET
429rate_limitedБольше 120 запросов в минуту на ключ
429too_many_requestsБольше 10 запросов в секунду с одного IP (короткий всплеск — до 20). Лимит общий для всех, кто ходит с этого IP
429too_many_bad_keysС этого IP за 10 минут пришло 20 запросов без ключа или с неверным ключом. До конца десятиминутки такие запросы получают паузу; запросы с верным ключом работают
503busyСайт под высокой нагрузкой (или с одного IP больше 20 одновременных запросов) — повторите позже

На 429 и 503 в ответе есть заголовок Retry-After — через столько секунд и повторяйте. Если не успеете, игрок не потеряет голос: забрать его можно в течение суток.

Как всё защищено

Только владельцу. Ключ выпускает только подтверждённый владелец сервера — ни модераторы, ни администрация сайта выпустить его не могут.
Ключ не хранится. В базе — только его отпечаток (SHA-256). Даже при утечке базы ключ не восстановить. Поэтому он и показывается один раз.
Привязан к владельцу. Сменился владелец сервера — ключ прежнего перестаёт работать сам, его настройки NuVotifier отключаются, а токен новому владельцу не показывается.
Одна награда на голос. «Забрать голос» — одна атомарная операция в базе: два одновременных запроса не выдадут награду дважды.
Лимиты. 120 запросов в минуту на ключ, пауза после 20 неверных ключей с одного IP (верный ключ с того же IP при этом работает), общий лимит на частоту запросов.
Только https. Ключ принимается только в заголовке защищённого запроса — не в адресе и не по http, где его видно по дороге.
Видно, кто пользуется. На странице редактирования — когда ключ использовали последний раз и сколько было запросов. Заметили чужое использование — выпустите новый ключ.
Не из чужого браузера. Страницы других сайтов не могут прочитать ответы API владельца: ключ должен жить в плагине или боте, а не в коде веб-страницы.
Данные игроков. Серверу уходит ник и IP без последней части — полного адреса игрока сервер не получает. monwave хранит IP голосующего 30 дней, сам голос — 90 дней, потом удаляет (политика конфиденциальности).
Только внешние адреса. monwave отправляет голос Votifier только на публичный IP — не во внутренние сети и не на сам сайт: настройку не превратить в способ стучаться туда, куда нельзя.
Берегите ключ

Не вставляйте ключ в клиентские моды, публичные репозитории, скриншоты и сообщения в чатах. Если ключ мог утечь — выпустите новый на странице редактирования: старый перестанет работать сразу.

Частые ошибки 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 — один и тот же ник.