Merge remote changes, keep v1.3

This commit is contained in:
ypsdf1
2026-04-30 17:07:58 +08:00
4 changed files with 365 additions and 11 deletions
+10
View File
@@ -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
+283
View File
@@ -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;
}
}
+63
View File
@@ -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 失败,已熔断自动更新逻辑以节省带宽。"
+9 -11
View File
@@ -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