Merge remote changes, keep v1.3
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
name: sdf1
|
||||
version: 1.3
|
||||
author: Sdf1_team
|
||||
main: com.sdf1.Main
|
||||
api-version: 1.21
|
||||
softdepend: [CY_beibao]
|
||||
commands:
|
||||
sdf1:
|
||||
description: 主控激活口令指令
|
||||
usage: /sdf1
|
||||
@@ -0,0 +1,283 @@
|
||||
package com.myplugin;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class Main extends JavaPlugin implements Listener, CommandExecutor, TabCompleter {
|
||||
|
||||
private final Set<UUID> waitingPlayers = new HashSet<>();
|
||||
private final String repoURL = "https://api.github.com/repos/ypsdf1/sdf1_plugn/releases/latest";
|
||||
private final AtomicInteger failedAttempts = new AtomicInteger(0);
|
||||
private boolean circuitBroken = false;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// 强制初始化插件文件夹为 sdf1,防止产生 myplugn 文件夹
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
saveDefaultConfig();
|
||||
|
||||
getServer().getPluginManager().registerEvents(this, this);
|
||||
if (getCommand("sdf1") != null) {
|
||||
getCommand("sdf1").setExecutor(this);
|
||||
getCommand("sdf1").setTabCompleter(this);
|
||||
}
|
||||
|
||||
// 忽略 SSL 证书校验(核心修复:解决 PKIX 报错)
|
||||
trustAllHttpsCertificates();
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(this, () -> triggerAutoUpdate(Bukkit.getConsoleSender()), 40L);
|
||||
getLogger().info("sdf1 插件已启动。SSL 证书兼容补丁已加载。");
|
||||
}
|
||||
|
||||
// --- SSL 证书忽略逻辑 ---
|
||||
private void trustAllHttpsCertificates() {
|
||||
try {
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() { return null; }
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||
}
|
||||
};
|
||||
SSLContext sc = SSLContext.getInstance("SSL");
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private void triggerAutoUpdate(CommandSender sender) {
|
||||
String ver = getConfig().getString("version", "1.0");
|
||||
if (!getConfig().getBoolean("check-update", true) || ver.equalsIgnoreCase("999") || ver.equalsIgnoreCase("false")) {
|
||||
return;
|
||||
}
|
||||
if (circuitBroken) return;
|
||||
checkUpdate(sender, false);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
triggerAutoUpdate(Bukkit.getConsoleSender());
|
||||
}
|
||||
|
||||
private String parse(String path, Map<String, String> vars) {
|
||||
String raw = getConfig().getString("messages." + path, "Missing: " + path);
|
||||
String msg = raw.replace("&", "§");
|
||||
msg = msg.replace("{board}", getConfig().getString("scoreboard-names.main", "abc"));
|
||||
msg = msg.replace("{version}", getConfig().getString("version", "1.0"));
|
||||
if (vars != null) {
|
||||
for (Map.Entry<String, String> entry : vars.entrySet()) {
|
||||
msg = msg.replace("{" + entry.getKey() + "}", entry.getValue() == null ? "NULL" : entry.getValue());
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
private String normalize(String s) {
|
||||
return (s == null) ? "" : s.replaceAll("[^a-zA-Z0-9]", "").toUpperCase();
|
||||
}
|
||||
|
||||
private boolean isAdmin(CommandSender sender) {
|
||||
if (sender instanceof ConsoleCommandSender || sender.isOp()) return true;
|
||||
String raw = getConfig().getString("admin-whitelist", "");
|
||||
return Arrays.asList(raw.split(",")).stream().anyMatch(n -> n.trim().equalsIgnoreCase(sender.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!command.getName().equalsIgnoreCase("sdf1")) return false;
|
||||
|
||||
if (args.length > 0) {
|
||||
if (!isAdmin(sender)) {
|
||||
sender.sendMessage(parse("no-permission", null));
|
||||
return true;
|
||||
}
|
||||
String sub = args[0].toLowerCase();
|
||||
if (sub.equals("reload")) {
|
||||
reloadConfig();
|
||||
sender.sendMessage("§a[sdf1] 配置重载成功!");
|
||||
return true;
|
||||
} else if (sub.equals("update")) {
|
||||
circuitBroken = false;
|
||||
failedAttempts.set(0);
|
||||
sender.sendMessage("§b[sdf1] 正在手动连接 GitHub 仓库...");
|
||||
checkUpdate(sender, true);
|
||||
return true;
|
||||
} else if (sub.equals("admin")) {
|
||||
if (args.length >= 3 && (sender instanceof ConsoleCommandSender || sender.isOp())) {
|
||||
updateAdmin(args[1], args[2]);
|
||||
sender.sendMessage("§a[sdf1] 管理员名单已更新。");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage(parse("only-player", null));
|
||||
return true;
|
||||
}
|
||||
|
||||
Player p = (Player) sender;
|
||||
waitingPlayers.add(p.getUniqueId());
|
||||
p.sendMessage(parse("input-hint", null));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateAdmin(String action, String target) {
|
||||
String raw = getConfig().getString("admin-whitelist", "");
|
||||
List<String> list = new ArrayList<>();
|
||||
if (!raw.isEmpty()) list.addAll(Arrays.asList(raw.split(",")));
|
||||
if (action.equalsIgnoreCase("add")) { if (!list.contains(target)) list.add(target); }
|
||||
else if (action.equalsIgnoreCase("remove")) { list.remove(target); }
|
||||
getConfig().set("admin-whitelist", String.join(",", list));
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if (!waitingPlayers.contains(player.getUniqueId())) return;
|
||||
event.setCancelled(true);
|
||||
String userInput = event.getMessage().trim();
|
||||
waitingPlayers.remove(player.getUniqueId());
|
||||
|
||||
if (userInput.equals("0")) {
|
||||
player.sendMessage(parse("cancel-msg", null));
|
||||
return;
|
||||
}
|
||||
|
||||
Scoreboard sb = Bukkit.getScoreboardManager().getMainScoreboard();
|
||||
Objective objM = sb.getObjective(getConfig().getString("scoreboard-names.main", "abc"));
|
||||
if (objM == null) { player.sendMessage(parse("system-issue", null)); return; }
|
||||
|
||||
String finalKey = null;
|
||||
for (String entry : sb.getEntries()) {
|
||||
if (entry.equals(userInput) || normalize(entry).equals(normalize(userInput))) {
|
||||
finalKey = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> v = new HashMap<>();
|
||||
v.put("name", userInput);
|
||||
v.put("entry", finalKey);
|
||||
|
||||
if (finalKey == null) {
|
||||
player.sendMessage(parse("not-found", v));
|
||||
return;
|
||||
}
|
||||
|
||||
int score = objM.getScore(finalKey).getScore();
|
||||
v.put("score", String.valueOf(score));
|
||||
|
||||
if (score == -1) {
|
||||
player.sendMessage(parse("used-up", v));
|
||||
} else if (score == 0) {
|
||||
Objective objR = sb.getObjective(getConfig().getString("scoreboard-names.record", "def"));
|
||||
if (objR == null) { player.sendMessage(parse("system-issue", v)); return; }
|
||||
String recName = finalKey + "_" + player.getName();
|
||||
if (objR.getScore(recName).getScore() != 0) {
|
||||
player.sendMessage(parse("already-claimed", v));
|
||||
} else {
|
||||
double amt = getConfig().getDouble("reward-rules.0", 10.0);
|
||||
v.put("amount", String.valueOf(amt));
|
||||
pay(player, amt, v);
|
||||
objR.getScore(recName).setScore(1);
|
||||
}
|
||||
} else {
|
||||
String sk = String.valueOf(score);
|
||||
if (getConfig().contains("reward-rules." + sk)) {
|
||||
double amt = getConfig().getDouble("reward-rules." + sk);
|
||||
v.put("amount", String.valueOf(amt));
|
||||
pay(player, amt, v);
|
||||
objM.getScore(finalKey).setScore(-1);
|
||||
} else {
|
||||
player.sendMessage(parse("system-issue", v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pay(Player p, double amt, Map<String, String> v) {
|
||||
p.sendMessage(parse("success-pay", v));
|
||||
Bukkit.getScheduler().runTask(this, () ->
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "eco give " + p.getName() + " " + amt));
|
||||
}
|
||||
|
||||
private void checkUpdate(CommandSender sender, boolean manual) {
|
||||
String currentVer = getConfig().getString("version", "1.0");
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||
try {
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(repoURL).openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) sdf1-plugin");
|
||||
conn.setConnectTimeout(8000);
|
||||
conn.setReadTimeout(8000);
|
||||
|
||||
if (conn.getResponseCode() != 200) throw new Exception("HTTP " + conn.getResponseCode());
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String inputLine;
|
||||
while ((inputLine = in.readLine()) != null) response.append(inputLine);
|
||||
in.close();
|
||||
|
||||
String content = response.toString();
|
||||
if (!content.contains("\"tag_name\":\"")) throw new Exception("No tag found");
|
||||
String latest = content.split("\"tag_name\":\"")[1].split("\"")[0].replace("v", "");
|
||||
|
||||
failedAttempts.set(0);
|
||||
Bukkit.getScheduler().runTask(this, () -> {
|
||||
if (!latest.equals(currentVer)) {
|
||||
Map<String, String> v = new HashMap<>();
|
||||
v.put("latest", latest);
|
||||
sender.sendMessage(parse("update-found", v));
|
||||
} else if (manual) {
|
||||
sender.sendMessage(parse("update-latest", null));
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
int count = failedAttempts.incrementAndGet();
|
||||
if (count >= 3 && !circuitBroken) {
|
||||
circuitBroken = true;
|
||||
Bukkit.getScheduler().runTask(this, () -> getLogger().warning(parse("update-circuit-break", null)));
|
||||
}
|
||||
if (manual) {
|
||||
Bukkit.getScheduler().runTask(this, () -> sender.sendMessage("§c[sdf1] 检查更新时发生异常 (PKIX/网络): " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender s, Command c, String a, String[] args) {
|
||||
if (args.length == 1 && isAdmin(s)) return Arrays.asList("reload", "admin", "update");
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("admin")) return Arrays.asList("add", "remove");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
# ====================================================
|
||||
# sdf1 插件配置文件 (1.21.4)
|
||||
# 欢迎 DIY!请务必保持格式对齐
|
||||
# ====================================================
|
||||
|
||||
# [核心版本设置]
|
||||
# 提示:如果将 version 设为 "999" 或 "false",插件将停止启动时的自动更新检查。
|
||||
version: "1.0"
|
||||
# 是否开启自动检查更新 (true 为开启, false 为关闭)
|
||||
check-update: true
|
||||
|
||||
# [数据库配置]
|
||||
# 这里的名字必须与你服务器计分板 (Scoreboard) 的名字完全一致
|
||||
scoreboard-names:
|
||||
main: "abc" # 主计分板:存放口令名和对应分数
|
||||
record: "def" # 记录计分板:存放 0 分奖励的领取记录 (格式为:口令_玩家名)
|
||||
|
||||
# [管理员名单]
|
||||
# 只有在这里的玩家或 OP 才能执行 /sdf1 reload/admin/update 指令
|
||||
# 格式示范: "Player1,Player2,MyAdmin"
|
||||
admin-whitelist: ""
|
||||
|
||||
# [奖励规则]
|
||||
# 格式为 "分数: 金额"。玩家在 {main} 计分板的分数达到 key,就发放对应的 money。
|
||||
# 特殊规则:
|
||||
# - 分数为 0:属于永久口令,每个玩家限领一次,记录在 {record}。
|
||||
# - 分数 > 0:属于一次性口令,领完后分数变为 -1。
|
||||
reward-rules:
|
||||
"0": 10.0
|
||||
"1": 10.0
|
||||
"100": 50.0
|
||||
|
||||
# [自定义消息体系]
|
||||
# ----------------------------------------------------
|
||||
# 你可以自由 DIY 这里的文字,支持颜色代码 '&'
|
||||
# 可用变量:
|
||||
# {name} - 玩家实际输入的文字
|
||||
# {entry} - 数据库里对应的原始正确口令 (如果不匹配则显示 NULL)
|
||||
# {board} - 你在上面设置的 main 计分板名字
|
||||
# {amount} - 奖励的金额
|
||||
# {score} - 该口令在计分板上的分数
|
||||
# {latest} - 远程 GitHub 仓库最新的版本号
|
||||
# {version}- 你当前配置文件里的版本号
|
||||
# ----------------------------------------------------
|
||||
messages:
|
||||
no-permission: "&c[权限] 抱歉,你没有权限执行这个管理指令!"
|
||||
only-player: "&c[错误] 领奖口令必须由玩家在游戏内输入。"
|
||||
input-hint: "&b[验证系统] &f请在聊天栏输入口令 (当前库: &e{board}&f) [输入 0 取消]:"
|
||||
cancel-msg: "&6[提示] &f你已取消本次验证。"
|
||||
|
||||
# 验证失败类 (由于隐私保护,默认不打印 {entry})
|
||||
not-found: "&c[无效] &f未找到口令 &e{name} &f,请检查拼写或大小写。"
|
||||
already-claimed: "&e[重复] &f这个礼包你已经领过啦,换一个试试吧!"
|
||||
used-up: "&c[失效] &f该口令已被他人抢先兑换或已过期。"
|
||||
system-issue: "&4[故障] &f数据异常 (Score:{score}),请截图联系管理员处理。"
|
||||
|
||||
# 验证成功
|
||||
success-pay: "&a[成功] &f验证通过!恭喜获得 &6{amount} &f金币。"
|
||||
|
||||
# 更新逻辑相关
|
||||
update-found: "&b[更新] &f发现新版本 &eV{latest}&f!当前版本 &7V{version}&f,请前往 GitHub 更新。"
|
||||
update-latest: "&a[状态] &f插件已是最新版本,无需更新。"
|
||||
update-circuit-break: "&7[安全保护] 连续 3 次连接 GitHub 失败,已熔断自动更新逻辑以节省带宽。"
|
||||
@@ -1,12 +1,10 @@
|
||||
name: sdf1
|
||||
version: 1.1
|
||||
author: Sdf1_team
|
||||
# 这里的路径必须指向你的 Main 类,注意大小写!
|
||||
main: com.sdf1.Main
|
||||
# 必须声明 API 版本,否则 1.21.4 会报错
|
||||
api-version: 1.21
|
||||
softdepend: [CY_beibao]
|
||||
commands:
|
||||
sdf1:
|
||||
description: 主控激活口令指令
|
||||
name: sdf1
|
||||
version: 1.3
|
||||
author: Sdf1_team
|
||||
main: com.sdf1.Main
|
||||
api-version: 1.21
|
||||
softdepend: [CY_beibao]
|
||||
commands:
|
||||
sdf1:
|
||||
description: 主控激活口令指令
|
||||
usage: /sdf1
|
||||
Reference in New Issue
Block a user