CDVote — исходный код

CDVote — наш бесплатный плагин наград за голосование. Ниже весь его код: один файл, 210 строк. Ставить его не обязательно: голоса с Cloud Delight понимает любой плагин наград — как подключить VotingPlugin или SuperbVote.

Что делает плагин. Слушает событие голоса от NuVotifier, выполняет команды наград из вашего config.yml, хранит очередь наград для тех, кто был не в сети, показывает ссылку по /vote и напоминает в чате. В сеть не выходит, ничего не скачивает и не обновляет себя: в коде нет ни одного сетевого вызова — проверьте по списку import в начале файла.

Скачать CDVote.java Готовый jar в каталоге

Собрать самому

Нужны JDK от 11 до 21 и spigot-api любой версии от 1.13. Скомпилируйте код, положите в папку build файлы plugin.yml и config.yml из готового jar (это обычный zip-архив) и упакуйте:

javac --release 8 -encoding UTF-8 -cp spigot-api.jar -d build CDVote.java
jar cf CDVote.jar -C build .

CDVote.java

package ru.clouddelight.cdvote;import org.bukkit.Bukkit;import org.bukkit.ChatColor;import org.bukkit.command.Command;import org.bukkit.command.CommandSender;import org.bukkit.configuration.file.YamlConfiguration;import org.bukkit.entity.Player;import org.bukkit.event.Event;import org.bukkit.event.EventHandler;import org.bukkit.event.EventPriority;import org.bukkit.event.Listener;import org.bukkit.event.player.PlayerJoinEvent;import org.bukkit.plugin.EventExecutor;import org.bukkit.plugin.java.JavaPlugin;import java.io.File;import java.io.IOException;import java.util.Locale;/** * CDVote — награды за голосование в мониторинге Cloud Delight (и любом другом, что шлёт голоса через Votifier). * Голос принимает NuVotifier / azuvotifier, CDVote выдаёт награду: команды из config.yml, офлайн-игрокам — при входе, * /vote — ссылка на страницу голосования, напоминание в чат. Зависимость от Votifier — во время работы (через * рефлексию), поэтому один jar подходит и к NuVotifier, и к форкам, и к старому Votifier 1.x. */public final class CDVote extends JavaPlugin implements Listener {    private File pendingFile;    private YamlConfiguration pending;    private int reminderTask = -1;    @Override    public void onEnable() {        saveDefaultConfig();        pendingFile = new File(getDataFolder(), "pending.yml");        pending = YamlConfiguration.loadConfiguration(pendingFile);        getServer().getPluginManager().registerEvents(this, this);        if (!hookVotifier()) {            getLogger().warning("Не найден Votifier! Поставьте NuVotifier или azuvotifier — без него голоса не дойдут до сервера.");        }        startReminder();    }    @Override    public void onDisable() {        savePending();    }    /** Подписка на com.vexsoftware.votifier.model.VotifierEvent без зависимости при сборке. */    @SuppressWarnings("unchecked")    private boolean hookVotifier() {        try {            Class<? extends Event> cls = (Class<? extends Event>) Class.forName("com.vexsoftware.votifier.model.VotifierEvent");            EventExecutor ex = (listener, event) -> {                if (!cls.isInstance(event)) return;                try {                    Object vote = event.getClass().getMethod("getVote").invoke(event);                    String user = String.valueOf(vote.getClass().getMethod("getUsername").invoke(vote)).trim();                    String service = String.valueOf(vote.getClass().getMethod("getServiceName").invoke(vote));                    Bukkit.getScheduler().runTask(this, () -> onVote(user, service));                } catch (ReflectiveOperationException e) {                    getLogger().warning("Не удалось прочитать голос: " + e);                }            };            getServer().getPluginManager().registerEvent(cls, this, EventPriority.NORMAL, ex, this);            getLogger().info("Votifier найден — ждём голоса.");            return true;        } catch (ClassNotFoundException e) {            return false;        }    }    private void onVote(String user, String service) {        if (user.isEmpty() || !user.matches("[A-Za-z0-9_]{2,16}")) {            getLogger().warning("Голос с непонятным ником «" + user + "» от " + service + " — пропущен");            return;        }        if (user.equalsIgnoreCase(getConfig().getString("test-nick", "CloudDelightTest"))) {            getLogger().info("Тестовый голос от " + service + " получен — связь работает, награда не выдаётся.");            return;        }        getLogger().info("Голос: " + user + " (" + service + ")");        pending.set("voted." + user.toLowerCase(Locale.ROOT), System.currentTimeMillis());   // напоминание его сутки не трогает        Player p = findOnline(user);        if (p != null) {            reward(p.getName(), service);        } else if (getConfig().getBoolean("offline-rewards", true)) {            String key = "queue." + user.toLowerCase(Locale.ROOT);            pending.set(key, pending.getInt(key, 0) + 1);        }        savePending();        String bc = getConfig().getString("messages.broadcast", "");        if (!bc.isEmpty()) {            Bukkit.broadcastMessage(color(bc.replace("%player%", user).replace("%service%", service)));        }    }    private void reward(String name, String service) {        for (String cmd : getConfig().getStringList("rewards")) {            String c = cmd.replace("%player%", name).replace("%service%", service);            if (c.startsWith("/")) c = c.substring(1);            try {                Bukkit.dispatchCommand(Bukkit.getConsoleSender(), c);            } catch (Exception e) {                getLogger().warning("Команда награды не выполнилась: " + c + " — " + e.getMessage());            }        }        Player p = Bukkit.getPlayerExact(name);        String msg = getConfig().getString("messages.thanks", "");        if (p != null && !msg.isEmpty()) p.sendMessage(color(msg.replace("%player%", name).replace("%service%", service)));    }    @EventHandler    public void onJoin(PlayerJoinEvent e) {        String key = "queue." + e.getPlayer().getName().toLowerCase(Locale.ROOT);        int n = pending.getInt(key, 0);        if (n <= 0) return;        pending.set(key, null);        savePending();        String name = e.getPlayer().getName();        Bukkit.getScheduler().runTaskLater(this, () -> {            for (int i = 0; i < n; i++) reward(name, "офлайн");            Player p = Bukkit.getPlayerExact(name);            String msg = getConfig().getString("messages.offline", "");            if (p != null && !msg.isEmpty()) p.sendMessage(color(msg.replace("%count%", String.valueOf(n))));        }, 40L);    }    private Player findOnline(String user) {        Player p = Bukkit.getPlayerExact(user);        if (p != null) return p;        for (Player o : Bukkit.getOnlinePlayers()) if (o.getName().equalsIgnoreCase(user)) return o;        return null;    }    private void startReminder() {        if (reminderTask != -1) Bukkit.getScheduler().cancelTask(reminderTask);        reminderTask = -1;        int min = getConfig().getInt("reminder.minutes", 60);        String text = getConfig().getString("reminder.text", "");        if (!getConfig().getBoolean("reminder.enabled", true) || min <= 0 || text.isEmpty()) return;        long t = min * 60L * 20L;        reminderTask = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {            long day = System.currentTimeMillis() - 24L * 3600L * 1000L;            for (Player p : Bukkit.getOnlinePlayers()) {                if (pending.getLong("voted." + p.getName().toLowerCase(Locale.ROOT), 0L) < day) sendLink(p, text);            }        }, t, t);    }    /** Кликабельная ссылка через tellraw — работает на 1.8–1.21 без библиотек чата. */    private void sendLink(Player p, String text) {        String url = getConfig().getString("vote-url", "");        String t = ChatColor.translateAlternateColorCodes('&', text);        if (url.isEmpty() || url.contains("ВАШ-СЕРВЕР")) {            p.sendMessage(t);            return;        }        String json = "{\"text\":\"" + esc(t) + "\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"" + esc(url)                + "\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":\"" + esc(ChatColor.YELLOW + url) + "\"}}";        Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + p.getName() + " " + json);    }    @Override    public boolean onCommand(CommandSender s, Command cmd, String label, String[] a) {        if (cmd.getName().equalsIgnoreCase("vote")) {            if (s instanceof Player) {                sendLink((Player) s, getConfig().getString("messages.vote", "&eНажми, чтобы проголосовать"));            } else {                s.sendMessage("Ссылка на голосование: " + getConfig().getString("vote-url", ""));            }            return true;        }        if (!s.hasPermission("cdvote.admin")) {            s.sendMessage(ChatColor.RED + "Недостаточно прав");            return true;        }        if (a.length >= 1 && a[0].equalsIgnoreCase("reload")) {            reloadConfig();            startReminder();            s.sendMessage(ChatColor.GREEN + "CDVote: конфиг перезагружен");            return true;        }        if (a.length >= 2 && a[0].equalsIgnoreCase("test")) {            onVote(a[1], "тест " + s.getName());            s.sendMessage(ChatColor.GREEN + "CDVote: тестовый голос за " + a[1] + " — награда выдана (или ждёт входа игрока)");            return true;        }        s.sendMessage(ChatColor.YELLOW + "/cdvote reload" + ChatColor.GRAY + " — перечитать конфиг");        s.sendMessage(ChatColor.YELLOW + "/cdvote test <ник>" + ChatColor.GRAY + " — выдать награду как за голос (проверка наград)");        return true;    }    private void savePending() {        try {            pending.save(pendingFile);        } catch (IOException e) {            getLogger().warning("Не сохранился pending.yml: " + e.getMessage());        }    }    private static String color(String s) {        return ChatColor.translateAlternateColorCodes('&', s);    }    private static String esc(String s) {        return s.replace("\\", "\\\\").replace("\"", "\\\"");    }}