UIPosts.java v3.0
集成好友系统的增强贴子系统,支持社交标识、好友帖子、通知与透传API,数据库db后缀。
命令列表
- uiposts打开或操作多UI贴子界面,参数支持chest/book/friend/reload等
package org.sircustom.uiposts;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import javax.sql.DataSource;
import java.io.File;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
/**
* @pluginName UIPosts
* @author ScriptIrc Engine
* @version 3.0
* @description 集成好友系统的增强贴子系统,支持社交标识、好友帖子、通知与透传API,数据库db后缀。
* [command]uiposts|打开或操作多UI贴子界面,参数支持chest/book/friend/reload等[/command]
* [Permission]social.post|发帖权限[/Permission]
* [Permission]social.friend|好友模块权限[/Permission]
* [Permission]social.admin|社交系统管理权限[/Permission]
*/
public class UIPosts extends JavaPlugin implements Listener, TabCompleter {
// ------- 数据库文件路径及连接 -------
private static final String DB_FILE = "uiposts.db";
private Connection dataSource;
// ------- 好友系统API桥 -------
/**
* 好友API桥接口: 需FriendSystem插件注入此桥接实例
*/
public interface FriendApiBridge {
boolean isFriend(UUID viewer, UUID target);
List<UUID> getFriendUUIDs(UUID player);
String getNickname(UUID uuid); // 可选
}
private static FriendApiBridge friendApiBridge = null;
public static void setFriendApiBridge(FriendApiBridge bridge) { friendApiBridge = bridge; }
public static FriendApiBridge getFriendApiBridge() { return friendApiBridge; }
// ------- 配置、界面变量 -------
private Map<String, ConfigurationSection> guiConfig = new HashMap<>();
private final Map<UUID, Boolean> awaitingPostInput = new ConcurrentHashMap<>();
private final Map<UUID, Integer> awaitingReply = new ConcurrentHashMap<>();
private final Map<Integer, List<Reply>> replyCache = new ConcurrentHashMap<>();
private String cachedTime;
private static final int MAX_POST_LENGTH = 100, MAX_REPLY_LENGTH = 50, CACHE_TTL = 300, MAX_PAGES = 100;
// 社交特性配置
private boolean enableFriend = true, friendPostNotify = true;
private String friendSymbol = ChatColor.LIGHT_PURPLE + "❤";
@Override
public void onEnable() {
saveDefaultConfig();
reloadConfig();
this.loadFriendConfig();
Bukkit.getPluginManager().registerEvents(this, this);
if (getCommand("uiposts") != null) {
getCommand("uiposts").setTabCompleter(this);
}
this.initDatabase();
this.loadGuiConfig();
startScheduledTasks();
getLogger().info("UIPosts v3.0 社交增强版已启动 - 数据库已连接");
}
@Override
public void onDisable() {
replyCache.clear();
closeDatabase();
getLogger().info("UIPosts 已安全关闭");
}
// ------- 配置加载 -------
private void loadGuiConfig() {
FileConfiguration config = getConfig();
guiConfig.put("chest", config.getConfigurationSection("gui.chest"));
guiConfig.put("book", config.getConfigurationSection("gui.book"));
getLogger().info("已加载GUI配置");
}
// 读取社交增强配置
private void loadFriendConfig() {
FileConfiguration config = getConfig();
enableFriend = config.getBoolean("enable_friend_features", true);
friendPostNotify = config.getBoolean("friend_post_notify", true);
friendSymbol = ChatColor.translateAlternateColorCodes('&', config.getString("friend_mark_symbol", "&d❤"));
}
// ------- 数据库初始化/升级 -------
private void initDatabase() {
try {
File dbFile = new File(getDataFolder(), DB_FILE);
if (!getDataFolder().exists()) getDataFolder().mkdirs();
boolean isNewDB = !dbFile.exists();
if (isNewDB) dbFile.createNewFile();
Class.forName("org.sqlite.JDBC");
dataSource = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getPath());
// 新表结构支持UUID
try (Statement st = dataSource.createStatement()) {
st.executeUpdate("CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, player_uuid TEXT, player VARCHAR(16) NOT NULL, content TEXT NOT NULL, time VARCHAR(20) NOT NULL)");
st.executeUpdate("CREATE TABLE IF NOT EXISTS replies (id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL, player_uuid TEXT, player VARCHAR(16) NOT NULL, content TEXT NOT NULL, time VARCHAR(20) NOT NULL, FOREIGN KEY(post_id) REFERENCES posts(id))");
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_post_uuid ON posts(player_uuid)");
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_reply_postid ON replies(post_id)");
}
} catch (Exception e) {
getLogger().log(Level.SEVERE, "数据库初始化失败", e);
}
}
private void closeDatabase() {
try { if (dataSource != null && !dataSource.isClosed()) dataSource.close(); } catch (SQLException e) { getLogger().warning("关闭数据库失败: " + e.getMessage()); }
}
// ------- 指令 -------
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if (!(sender instanceof Player)) { sender.sendMessage(ChatColor.RED + "仅玩家可以使用此命令"); return true; }
Player player = (Player) sender;
// 管理编辑
if (args.length > 0 && "reload".equalsIgnoreCase(args[0]) && player.hasPermission("social.admin")) {
reloadConfig();
loadGuiConfig();
loadFriendConfig();
player.sendMessage(ChatColor.GREEN + "UIPosts 配置已重载"); return true;
}
// 好友贴子专属视图:/uiposts friend x
if (args.length > 0 && ("friend".equalsIgnoreCase(args[0])||"friends".equalsIgnoreCase(args[0]))) {
openFriendPostsGUI(player, args.length>=2? Math.max(1, Math.min(MAX_PAGES, getInt(args[1],1))) : 1);
return true;
}
String type = (args.length == 0) ? "chest" : args[0].toLowerCase();
int page = 1;
if (args.length >= 2) {
page = Math.max(1, Math.min(MAX_PAGES, getInt(args[1],1)));
}
switch (type) {
case "chest": openChestGUI(player, page); break;
case "book": openBookGUI(player, page); break;
default: player.sendMessage(ChatColor.YELLOW + "/uiposts [chest|book|friend] [页码]");
}
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) {
if (args.length == 1) return Arrays.asList("chest", "book", "friend", "reload");
return Collections.emptyList();
}
private int getInt(String s, int def) { try { return Integer.parseInt(s);}catch(Exception e){return def;} }
// ------- 核心功能:箱子界面UI --------
private void openChestGUI(Player player, int page) {
ConfigurationSection cfg = guiConfig.get("chest");
if (cfg == null) { player.sendMessage(ChatColor.RED + "未配置箱子界面"); return; }
int size = cfg.getInt("size", 27);
String title = parsePlaceholders(cfg.getString("title", "§b§l帖子广场"), player, page);
Inventory inv = Bukkit.createInventory(null, size, title);
int postsPerPage = cfg.getIntegerList("post_slot").size();
List<Post> posts = getPostsByPage(page, postsPerPage);
List<Integer> postSlots = cfg.getIntegerList("post_slot");
for (int i = 0; i < Math.min(posts.size(), postSlots.size()); i++) {
Post post = posts.get(i);
inv.setItem(postSlots.get(i), createPostItem(post, player));
}
inv.setItem(cfg.getInt("post_button_slot", 4), createButton(
Material.ANVIL,
cfg.getString("post_button", "§a§l我要发帖")
));
inv.setItem(cfg.getInt("book_button_slot", 22), createButton(
Material.WRITTEN_BOOK,
cfg.getString("jump_book", "§6查看书本模式")
));
// ★好友帖子入口
if (enableFriend) {
inv.setItem(cfg.getInt("friend_posts_slot", 24), createButton(
Material.PLAYER_HEAD,
ChatColor.AQUA + "好友帖子"
));
}
if (hasNextPage(page, postsPerPage)) {
inv.setItem(cfg.getInt("next_slot", 25), createButton(
Material.ARROW, "§e下一页"
));
}
if (page > 1) {
inv.setItem(cfg.getInt("prev_slot", 19), createButton(
Material.ARROW, "§e上一页"
));
}
player.openInventory(inv);
storePlayerState(player, "chest", page);
}
// 好友帖子界面(只显示好友的帖子)
public void openFriendPostsGUI(Player player, int page) {
if (!enableFriend || getFriendApiBridge()==null) {
player.sendMessage(ChatColor.RED + "好友功能未启用或未联动!");
return;
}
List<UUID> friends = getFriendApiBridge().getFriendUUIDs(player.getUniqueId());
if (friends.isEmpty()) {
player.sendMessage(ChatColor.YELLOW+"你还没有好友,快去添加吧!");
return;
}
ConfigurationSection cfg = guiConfig.get("chest");
int size = cfg.getInt("size", 27);
String title = ChatColor.AQUA+"好友帖子 §7第"+page+"页";
Inventory inv = Bukkit.createInventory(null, size, title);
int postsPerPage = cfg.getIntegerList("post_slot").size();
List<Post> posts = getPostsByPlayers(friends, page, postsPerPage);
List<Integer> postSlots = cfg.getIntegerList("post_slot");
for (int i = 0; i < Math.min(posts.size(), postSlots.size()); i++)
inv.setItem(postSlots.get(i), createPostItem(posts.get(i), player));
inv.setItem(cfg.getInt("post_button_slot", 4), createButton(
Material.ANVIL, cfg.getString("post_button", "§a§l我要发帖") ));
if (hasNextPageCustom(posts.size(), page, postsPerPage)) {
inv.setItem(cfg.getInt("next_slot", 25), createButton(
Material.ARROW, "§e下一页"
));
}
if (page > 1) {
inv.setItem(cfg.getInt("prev_slot", 19), createButton(
Material.ARROW, "§e上一页"
));
}
player.openInventory(inv);
storePlayerState(player, "friend", page);
}
// ---------------------- 事件监听/交互控制 -----------------------
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player)) return;
Player player = (Player) event.getWhoClicked();
ItemStack clicked = event.getCurrentItem();
if (clicked == null || !clicked.hasItemMeta()) return;
if (!isUIPostsInventory(event.getView().getTitle())) return;
event.setCancelled(true);
PlayerState state = getPlayerState(player);
if (state == null) return;
String displayName = ChatColor.stripColor(clicked.getItemMeta().getDisplayName());
if (clicked.getType() == Material.ANVIL && displayName.contains("发帖")) {
player.closeInventory();
askForPostInput(player);
} else if (clicked.getType() == Material.WRITTEN_BOOK && displayName.contains("书本模式")) {
player.closeInventory();
openBookGUI(player, 1);
} else if (clicked.getType() == Material.PLAYER_HEAD && displayName.contains("好友帖子")) {
player.closeInventory();
openFriendPostsGUI(player, 1);
} else if (clicked.getType() == Material.ARROW) {
if (state.guiType.equals("friend")) {
// 分页
int page = state.page;
if (displayName.contains("上一页")) openFriendPostsGUI(player, Math.max(1,page-1));
if (displayName.contains("下一页")) openFriendPostsGUI(player, page+1);
} else {
handlePaginationClick(player, state, displayName);
}
} else if (clicked.getType() == Material.WRITABLE_BOOK) {
handlePostClick(player, event.getSlot(), state.page);
}
}
// ------- 聊天栏发帖/回复 -------
@EventHandler
public void onChatInput(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
String message = event.getMessage();
if (awaitingPostInput.containsKey(uuid)) {
event.setCancelled(true);
if ("取消".equalsIgnoreCase(message)) {
player.sendMessage(ChatColor.RED + "已取消发帖");
awaitingPostInput.remove(uuid);
return;
}
Bukkit.getScheduler().runTask(this, () -> {
savePost(player, message);
awaitingPostInput.remove(uuid);
openChestGUI(player, 1);
});
return;
}
if (awaitingReply.containsKey(uuid)) {
event.setCancelled(true);
if ("取消".equalsIgnoreCase(message)) {
player.sendMessage(ChatColor.RED + "已取消留言");
awaitingReply.remove(uuid);
return;
}
int postId = awaitingReply.get(uuid);
Bukkit.getScheduler().runTask(this, () -> {
saveReply(player, postId, message);
awaitingReply.remove(uuid);
showBookForPost(player, getPost(postId), player);
});
}
}
// ------- 帖子相关操作核心 -------
private List<Post> getPostsByPage(int page, int perPage) {
int offset = (page - 1) * perPage;
List<Post> posts = new ArrayList<>();
if (perPage <= 0) return posts;
String sql = "SELECT id, player_uuid, player, content, time FROM posts ORDER BY id DESC LIMIT ? OFFSET ?";
try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
ps.setInt(1, perPage);
ps.setInt(2, offset);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
posts.add(new Post(
rs.getInt("id"),
rs.getString("player_uuid"),
rs.getString("player"),
rs.getString("content"),
rs.getString("time")
));
}
}
} catch (SQLException e) { getLogger().log(Level.SEVERE, "查询帖子失败", e); }
return posts;
}
// 查询多个UUID作者帖子(分页)
private List<Post> getPostsByPlayers(List<UUID> uuids, int page, int perPage) {
List<Post> posts = new ArrayList<>();
if (uuids.isEmpty()) return posts;
int offset = (page - 1) * perPage;
// 拼接uuid列表
StringBuilder sb = new StringBuilder();
for (int i = 0; i < uuids.size(); i++) {
sb.append("?"); if (i<uuids.size()-1) sb.append(",");
}
String sql = "SELECT id, player_uuid, player, content, time FROM posts WHERE player_uuid IN ("+sb+") ORDER BY id DESC LIMIT ? OFFSET ?";
try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
int idx = 1;
for (UUID uuid : uuids) ps.setString(idx++, uuid.toString());
ps.setInt(idx++, perPage);
ps.setInt(idx, offset);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
posts.add(new Post(
rs.getInt("id"),
rs.getString("player_uuid"),
rs.getString("player"),
rs.getString("content"),
rs.getString("time")
));
}
}
} catch (SQLException e) { getLogger().log(Level.SEVERE, "好友帖子查询失败", e); }
return posts;
}
// 新增:保存时支持uuid
private void savePost(Player player, String content) {
String time = getCurrentTime();
String playerName = player.getName();
String uuid = player.getUniqueId().toString();
if (content.length() > MAX_POST_LENGTH) { content = content.substring(0, MAX_POST_LENGTH); }
String sql = "INSERT INTO posts(player_uuid, player, content, time) VALUES(?,?,?,?)";
try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
ps.setString(1, uuid);
ps.setString(2, playerName);
ps.setString(3, content);
ps.setString(4, time);
ps.executeUpdate();
replyCache.clear();
player.sendMessage(ChatColor.GREEN + "帖子发布成功!");
// ★发帖通知
if (enableFriend && friendPostNotify && getFriendApiBridge()!=null) {
List<UUID> friends = getFriendApiBridge().getFriendUUIDs(player.getUniqueId());
for (UUID friend : friends) {
Player onlineFriend = Bukkit.getPlayer(friend);
if (onlineFriend != null) {
onlineFriend.sendMessage(ChatColor.YELLOW + player.getName() + " 发布了新帖子: " + truncate(content, 20));
}
}
}
} catch (SQLException e) {
getLogger().log(Level.SEVERE, "保存帖子失败", e);
player.sendMessage(ChatColor.RED + "发帖失败,请稍后再试");
}
}
private void saveReply(Player player, int postId, String content) {
String time = getCurrentTime();
String playerName = player.getName();
String uuid = player.getUniqueId().toString();
if (content.length() > MAX_REPLY_LENGTH) { content = content.substring(0, MAX_REPLY_LENGTH); }
String sql = "INSERT INTO replies(post_id, player_uuid, player, content, time) VALUES(?,?,?,?,?)";
try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
ps.setInt(1, postId);
ps.setString(2, uuid);
ps.setString(3, playerName);
ps.setString(4, content);
ps.setString(5, time);
ps.executeUpdate();
replyCache.remove(postId);
player.sendMessage(ChatColor.GREEN + "留言成功!");
} catch (SQLException e) {
getLogger().log(Level.SEVERE, "保存留言失败", e);
player.sendMessage(ChatColor.RED + "留言失败,请稍后再试");
}
}
// ------- 帖子、UI展示相关 -------
// 帖子条目(带好友标记)
private ItemStack createPostItem(Post post, Player viewer) {
ItemStack item = new ItemStack(Material.WRITABLE_BOOK);
ItemMeta meta = item.getItemMeta();
String authorDisplay = post.player;
boolean friend = false;
if (enableFriend && viewer!=null && getFriendApiBridge()!=null && post.player_uuid != null) {
try { UUID au = UUID.fromString(post.player_uuid); friend = getFriendApiBridge().isFriend(viewer.getUniqueId(), au); } catch(Exception ignore){}
}
if (friend) authorDisplay += " " + friendSymbol;
meta.setDisplayName(ChatColor.GREEN + "【" + authorDisplay + "】 - " + post.time);
List<String> lore = new ArrayList<>();
lore.add(ChatColor.WHITE + truncate(post.content, 25));
lore.add(friend ? (ChatColor.LIGHT_PURPLE + "您的好友发帖!") : ChatColor.GRAY + "点击查看/留言");
meta.setLore(lore);
item.setItemMeta(meta);
return item;
}
private ItemStack createButton(Material material, String name) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(name);
item.setItemMeta(meta);
return item;
}
// 书本内容格式化,带好友标识、回复标识
private String formatPostForBook(Post post, Player viewer) {
StringBuilder sb = new StringBuilder();
boolean isFriend = false;
if (enableFriend && viewer != null && getFriendApiBridge()!=null && post.player_uuid != null) try {
isFriend = getFriendApiBridge().isFriend(viewer.getUniqueId(), UUID.fromString(post.player_uuid));
} catch(Exception ignore){}
sb.append(ChatColor.BLUE).append("【").append(post.player).append(isFriend?friendSymbol:"").append("】 ")
.append(ChatColor.DARK_GRAY).append(post.time).append("\n");
sb.append(ChatColor.BLACK).append(post.content).append("\n\n");
List<Reply> replies = getReplies(post.id);
if (!replies.isEmpty()) {
sb.append(ChatColor.DARK_GRAY).append("--- 留言 ---\n");
int maxReplies = Math.min(replies.size(), 3);
for (int i = 0; i < maxReplies; i++) {
Reply reply = replies.get(i);
boolean friendReply = false;
if (enableFriend && viewer != null && getFriendApiBridge()!=null && reply.player_uuid != null) try {
friendReply = getFriendApiBridge().isFriend(viewer.getUniqueId(), UUID.fromString(reply.player_uuid));
} catch(Exception ignore2){}
sb.append(ChatColor.GRAY).append("• ").append(reply.player)
.append(friendReply?friendSymbol:"").append(": ")
.append(ChatColor.DARK_GREEN).append(reply.content)
.append("\n");
}
if (replies.size() > 3) {
sb.append(ChatColor.GRAY).append("... 还有").append(replies.size() - 3).append("条留言\n");
}
sb.append("\n").append(ChatColor.DARK_AQUA).append("点击书本外区域输入留言内容");
} else {
sb.append(ChatColor.GRAY).append("暂无留言\n");
}
sb.append("\n");
return sb.toString();
}
// 打开书本UI
private void openBookGUI(Player player, int page) {
ConfigurationSection cfg = guiConfig.get("book");
if (cfg == null) { player.sendMessage(ChatColor.RED + "未配置书本界面"); return; }
int postsPerPage = cfg.getInt("posts_per_page", 5);
ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
BookMeta meta = (BookMeta) book.getItemMeta();
meta.setTitle(parsePlaceholders(cfg.getString("title", "§b§l帖子广场"), player, page));
meta.setAuthor("UIPosts");
List<String> pages = generateBookPages(page, postsPerPage, player);
meta.setPages(pages);
book.setItemMeta(meta);
ItemStack oldItem = player.getInventory().getItemInMainHand().clone();
player.getInventory().setItemInMainHand(book);
new BukkitRunnable() {
@Override
public void run() {
player.openBook(book);
player.getInventory().setItemInMainHand(oldItem);
}
}.runTaskLater(this, 2L);
storePlayerState(player, "book", page);
}
private void showBookForPost(Player player, Post post, Player viewer) {
if (post == null) {
player.sendMessage(ChatColor.RED + "帖子不存在或已被删除");
return;
}
ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
BookMeta meta = (BookMeta) book.getItemMeta();
meta.setTitle("帖子详情");
meta.setAuthor("UIPosts");
meta.setPages(Collections.singletonList(formatPostForBook(post, viewer)));
book.setItemMeta(meta);
ItemStack oldItem = player.getInventory().getItemInMainHand().clone();
player.getInventory().setItemInMainHand(book);
new BukkitRunnable() {
@Override
public void run() {
player.openBook(book);
player.getInventory().setItemInMainHand(oldItem);
}
}.runTaskLater(this, 2L);
askForReplyInput(player, post.id);
}
private List<String> generateBookPages(int currentPage, int postsPerPage, Player viewer) {
List<String> pages = new ArrayList<>();
List<Post> posts = getPostsByPage(currentPage, postsPerPage);
if (posts.isEmpty()) {
pages.add("§7暂无帖子\n§a请使用箱子界面发帖!");
return pages;
}
StringBuilder currentContent = new StringBuilder();
for (Post post : posts) {
String postText = formatPostForBook(post, viewer);
if (currentContent.length() + postText.length() > 1500) {
pages.add(currentContent.toString());
currentContent = new StringBuilder();
}
currentContent.append(postText);
}
if (currentContent.length() > 0) {
pages.add(currentContent.toString());
}
pages.add(generateNavigationPage(currentPage));
return pages;
}
private String generateNavigationPage(int currentPage) {
int totalPages = getTotalPages(guiConfig.get("book").getInt("posts_per_page", 5));
StringBuilder sb = new StringBuilder("§l§n导航菜单§r\n\n");
sb.append("§7当前页: §e").append(currentPage).append("§7/§e").append(totalPages).append("\n\n");
sb.append("§6[命令导航]\n");
if (currentPage > 1) {
sb.append("§a/up book ").append(currentPage - 1).append(" §7- 上一页\n");
}
if (currentPage < totalPages) {
sb.append("§a/up book ").append(currentPage + 1).append(" §7- 下一页\n");
}
sb.append("§a/up chest §7- 返回箱子界面\n");
return sb.toString();
}
// ------------------- 通用方法与内部类 ---------------------
private List<Reply> getReplies(int postId) {
if (replyCache.containsKey(postId)) {
return replyCache.get(postId);
}
List<Reply> replies = new ArrayList<>();
String sql = "SELECT player_uuid, player, content, time FROM replies WHERE post_id = ? ORDER BY id ASC";
try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
ps.setInt(1, postId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
replies.add(new Reply(
rs.getString("player_uuid"),
rs.getString("player"),
rs.getString("content"),
rs.getString("time")
));
}
}
} catch (SQLException e) { getLogger().log(Level.SEVERE, "查询留言失败", e); }
replyCache.put(postId, replies);
return replies;
}
private void askForPostInput(Player player) {
player.sendMessage(ChatColor.GOLD + "请在聊天栏输入您的帖子内容(最多100字)");
player.sendMessage(ChatColor.GRAY + "输入" + ChatColor.RED + "取消" + ChatColor.GRAY + "来取消操作");
awaitingPostInput.put(player.getUniqueId(), true);
}
private void askForReplyInput(Player player, int postId) {
player.sendMessage(ChatColor.GOLD + "请输入您的留言内容(最多50字)");
player.sendMessage(ChatColor.GRAY + "输入" + ChatColor.RED + "取消" + ChatColor.GRAY + "来取消操作");
awaitingReply.put(player.getUniqueId(), postId);
}
private int getTotalPages(int perPage) {
if (perPage <= 0) return 1;
String sql = "SELECT COUNT(*) AS total FROM posts";
try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
int totalPosts = rs.getInt("total");
return (int) Math.ceil((double) totalPosts / perPage);
}
}
} catch (SQLException e) { getLogger().log(Level.SEVERE, "计算总页数失败", e); }
return 1;
}
private boolean hasNextPage(int currentPage, int perPage) {
int totalPages = getTotalPages(perPage);
return currentPage < totalPages;
}
private boolean hasNextPageCustom(int total, int curPage, int pageSize) {
int pageCt = (total + pageSize - 1) / pageSize;
return curPage < pageCt;
}
private void handlePaginationClick(Player player, PlayerState state, String buttonName) {
if (buttonName.contains("上一页") && state.page > 1) {
if ("chest".equals(state.guiType)) openChestGUI(player, state.page - 1);
else openBookGUI(player, state.page - 1);
} else if (buttonName.contains("下一页")) {
if ("chest".equals(state.guiType)) openChestGUI(player, state.page + 1);
else openBookGUI(player, state.page + 1);
}
}
private void handlePostClick(Player player, int slot, int page) {
ConfigurationSection cfg = guiConfig.get("chest");
if (cfg == null) return;
List<Integer> postSlots = cfg.getIntegerList("post_slot");
if (!postSlots.contains(slot)) return;
int index = postSlots.indexOf(slot);
int postsPerPage = postSlots.size();
int offset = (page - 1) * postsPerPage + index;
Post post = getPostByOffset(offset);
if (post != null) {
showBookForPost(player, post, player);
}
}
private Post getPostByOffset(int offset) {
String sql = "SELECT id, player_uuid, player, content, time FROM posts ORDER BY id DESC LIMIT 1 OFFSET ?";
try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
ps.setInt(1, offset);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return new Post(
rs.getInt("id"),
rs.getString("player_uuid"),
rs.getString("player"),
rs.getString("content"),
rs.getString("time")
);
}
}
} catch (SQLException e) { getLogger().log(Level.SEVERE, "按偏移量查询帖子失败", e); }
return null;
}
private Post getPost(int postId) {
String sql = "SELECT player_uuid, player, content, time FROM posts WHERE id = ?";
try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
ps.setInt(1, postId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return new Post(
postId,
rs.getString("player_uuid"),
rs.getString("player"),
rs.getString("content"),
rs.getString("time")
);
}
}
} catch (SQLException e) { getLogger().log(Level.SEVERE, "查询单个帖子失败", e); }
return null;
}
private String truncate(String text, int maxLength) { return text.length() > maxLength ? text.substring(0, maxLength) + "..." : text; }
private String getCurrentTime() { if (cachedTime != null) { return cachedTime; } return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); }
private void startScheduledTasks() {
new BukkitRunnable() { @Override public void run() { updateTimeCache(); } }.runTaskTimerAsynchronously(this, 0, 20 * 60 * 60);
new BukkitRunnable() { @Override public void run() { replyCache.clear(); } }.runTaskTimerAsynchronously(this, 0, 20 * CACHE_TTL);
}
private void updateTimeCache() {
try { cachedTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); } catch (Exception e) { getLogger().warning("时间更新失败: " + e.getMessage()); } }
private String parsePlaceholders(String text, Player player, int page) {
String parsed = text
.replace("{player}", player.getName())
.replace("{page}", String.valueOf(page))
.replace("{pages}", String.valueOf(getTotalPages(guiConfig.get("chest").getIntegerList("post_slot").size())));
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
try { return me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, parsed); } catch (Exception e) { getLogger().warning("PlaceholderAPI 处理失败: " + e.getMessage()); }
}
return parsed;
}
// --------------------- 内部数据结构 ---------------------
private static class Post {
final int id; final String player_uuid; final String player; final String content; final String time;
Post(int id, String player_uuid, String player, String content, String time) {
this.id = id; this.player_uuid = player_uuid; this.player = player;
this.content = content; this.time = time;
}
}
private static class Reply {
final String player_uuid; final String player; final String content; final String time;
Reply(String player_uuid, String player, String content, String time) {
this.player_uuid = player_uuid; this.player = player; this.content = content; this.time = time;
}
}
private static class PlayerState {
final String guiType; final int page;
PlayerState(String guiType, int page) { this.guiType = guiType; this.page = page; }
}
private void storePlayerState(Player player, String type, int page) {
player.setMetadata("uiposts_state", new FixedMetadataValue(this, type + ":" + page));
}
private PlayerState getPlayerState(Player player) {
if (!player.hasMetadata("uiposts_state")) return null;
String[] parts = player.getMetadata("uiposts_state").get(0).asString().split(":");
if (parts.length != 2) return null;
return new PlayerState(parts[0], Integer.parseInt(parts[1]));
}
private boolean isUIPostsInventory(String title) { return title.contains("帖子广场")||title.contains("好友帖子"); }
}