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
|
name: sdf1
|
||||||
version: 1.1
|
version: 1.3
|
||||||
author: Sdf1_team
|
author: Sdf1_team
|
||||||
# 这里的路径必须指向你的 Main 类,注意大小写!
|
main: com.sdf1.Main
|
||||||
main: com.sdf1.Main
|
api-version: 1.21
|
||||||
# 必须声明 API 版本,否则 1.21.4 会报错
|
softdepend: [CY_beibao]
|
||||||
api-version: 1.21
|
commands:
|
||||||
softdepend: [CY_beibao]
|
sdf1:
|
||||||
commands:
|
description: 主控激活口令指令
|
||||||
sdf1:
|
|
||||||
description: 主控激活口令指令
|
|
||||||
usage: /sdf1
|
usage: /sdf1
|
||||||
Reference in New Issue
Block a user