feat: Paper plugin + external API POST endpoint
This commit is contained in:
@@ -19,6 +19,41 @@ function externalAuth(req, res, next) {
|
|||||||
|
|
||||||
router.use(externalAuth);
|
router.use(externalAuth);
|
||||||
|
|
||||||
|
router.post('/tickets', async (req, res) => {
|
||||||
|
const { type, title, reporter_game_name, reporter_game_uid,
|
||||||
|
target_game_name, target_game_uid, reason, description } = req.body;
|
||||||
|
|
||||||
|
if (!type || !['report','suggestion','appeal'].includes(type)) return res.status(400).json({ error: '类型不正确' });
|
||||||
|
if (!title) return res.status(400).json({ error: '标题不能为空' });
|
||||||
|
if (!reporter_game_name || !reporter_game_uid) return res.status(400).json({ error: '请填写游戏名称和UID' });
|
||||||
|
|
||||||
|
if (type === 'report') {
|
||||||
|
if (!reason) return res.status(400).json({ error: '请填写举报原因' });
|
||||||
|
}
|
||||||
|
if (type === 'suggestion' && !description) return res.status(400).json({ error: '建议内容不能为空' });
|
||||||
|
if (type === 'appeal') {
|
||||||
|
if (!reason) return res.status(400).json({ error: '请填写申诉理由' });
|
||||||
|
if (!description) return res.status(400).json({ error: '请填写详细申诉内容' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const countRow = await getRow("SELECT COUNT(*) as c FROM tickets WHERE (reporter_game_name = ? OR reporter_game_uid = ?) AND status IN ('pending','processing','awaiting_info','appealing')", [reporter_game_name, reporter_game_uid]);
|
||||||
|
if (countRow.c >= 5) return res.status(400).json({ error: '待处理工单已达上限' });
|
||||||
|
|
||||||
|
const trackingToken = require('uuid').v4();
|
||||||
|
const [r] = await query(`INSERT INTO tickets(type,title,reporter_game_name,reporter_game_uid,
|
||||||
|
target_game_name,target_game_uid,reason,description,tracking_token) VALUES (?,?,?,?,?,?,?,?,?)`,
|
||||||
|
[type, title, reporter_game_name, reporter_game_uid,
|
||||||
|
(type==='report')?(target_game_name||''):null,
|
||||||
|
(type==='report')?(target_game_uid||''):null,
|
||||||
|
(type==='report'||type==='appeal')?reason:null,
|
||||||
|
(type==='suggestion'||type==='appeal')?description:'',
|
||||||
|
trackingToken]);
|
||||||
|
|
||||||
|
await query("INSERT INTO responses(ticket_id, content, is_staff) VALUES (?,?,0)", [r.insertId, `游戏内${type==='report'?'举报':type==='appeal'?'申诉':'建议'}\n提交人: ${reporter_game_name} (UID: ${reporter_game_uid})\n${reason||description||''}`]);
|
||||||
|
|
||||||
|
res.status(201).json({ id: r.insertId, tracking_token: trackingToken });
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/tickets', async (req, res) => {
|
router.get('/tickets', async (req, res) => {
|
||||||
const { type, status, page, limit } = req.query;
|
const { type, status, page, limit } = req.query;
|
||||||
let q = `SELECT t.id, t.type, t.title, t.status, t.priority,
|
let q = `SELECT t.id, t.type, t.title, t.status, t.priority,
|
||||||
|
|||||||
47
mc-report-plugin/pom.xml
Normal file
47
mc-report-plugin/pom.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.mcreport</groupId>
|
||||||
|
<artifactId>mc-report-plugin</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc</id>
|
||||||
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.papermc.paper</groupId>
|
||||||
|
<artifactId>paper-api</artifactId>
|
||||||
|
<version>1.20.4-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.mcreport.plugin;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ApiClient {
|
||||||
|
|
||||||
|
private final String apiUrl;
|
||||||
|
private final String externalKey;
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
|
public ApiClient(String apiUrl, String externalKey) {
|
||||||
|
this.apiUrl = apiUrl;
|
||||||
|
this.externalKey = externalKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean submitTicket(String type, String title, String reporterName, UUID reporterUuid,
|
||||||
|
String targetName, UUID targetUuid, String reason, String description) {
|
||||||
|
try {
|
||||||
|
JsonObject body = new JsonObject();
|
||||||
|
body.addProperty("type", type);
|
||||||
|
body.addProperty("title", title);
|
||||||
|
body.addProperty("reporter_game_name", reporterName);
|
||||||
|
body.addProperty("reporter_game_uid", reporterUuid != null ? reporterUuid.toString() : "");
|
||||||
|
|
||||||
|
if (targetName != null && !targetName.isEmpty()) {
|
||||||
|
body.addProperty("target_game_name", targetName);
|
||||||
|
}
|
||||||
|
if (targetUuid != null) {
|
||||||
|
body.addProperty("target_game_uid", targetUuid.toString());
|
||||||
|
}
|
||||||
|
if (reason != null && !reason.isEmpty()) {
|
||||||
|
body.addProperty("reason", reason);
|
||||||
|
}
|
||||||
|
if (description != null && !description.isEmpty()) {
|
||||||
|
body.addProperty("description", description);
|
||||||
|
}
|
||||||
|
|
||||||
|
URL url = new URI(apiUrl + "/tickets").toURL();
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("POST");
|
||||||
|
conn.setRequestProperty("Content-Type", "application/json");
|
||||||
|
conn.setRequestProperty("x-external-key", externalKey);
|
||||||
|
conn.setDoOutput(true);
|
||||||
|
conn.setConnectTimeout(5000);
|
||||||
|
conn.setReadTimeout(5000);
|
||||||
|
|
||||||
|
try (OutputStream os = conn.getOutputStream()) {
|
||||||
|
byte[] input = gson.toJson(body).getBytes(StandardCharsets.UTF_8);
|
||||||
|
os.write(input, 0, input.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
conn.disconnect();
|
||||||
|
return code == 200 || code == 201;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveUuid(String playerName) {
|
||||||
|
try {
|
||||||
|
URL url = new URI("https://api.mojang.com/users/profiles/minecraft/" + playerName).toURL();
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setConnectTimeout(5000);
|
||||||
|
conn.setReadTimeout(5000);
|
||||||
|
if (conn.getResponseCode() != 200) {
|
||||||
|
conn.disconnect();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String response = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
conn.disconnect();
|
||||||
|
JsonObject json = gson.fromJson(response, JsonObject.class);
|
||||||
|
return json != null ? json.get("id").getAsString() : null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.mcreport.plugin;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class AppealCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final MCReportPlugin plugin;
|
||||||
|
|
||||||
|
public AppealCommand(MCReportPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("§c仅玩家可使用此命令");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
sender.sendMessage("§e/appeal <原因> §7- 提交申诉");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String reason = String.join(" ", args);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
boolean ok = plugin.getApiClient().submitTicket(
|
||||||
|
"appeal",
|
||||||
|
"游戏内申诉: " + player.getName(),
|
||||||
|
player.getName(),
|
||||||
|
player.getUniqueId(),
|
||||||
|
null, null,
|
||||||
|
reason,
|
||||||
|
reason
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
player.sendMessage("§a申诉已提交!请等待处理");
|
||||||
|
} else {
|
||||||
|
player.sendMessage("§c提交失败,请联系管理员");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.mcreport.plugin;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public final class MCReportPlugin extends JavaPlugin {
|
||||||
|
|
||||||
|
private ApiClient apiClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
saveDefaultConfig();
|
||||||
|
String apiUrl = getConfig().getString("api_url", "http://localhost:3100/api/external");
|
||||||
|
String externalKey = getConfig().getString("external_key", "");
|
||||||
|
apiClient = new ApiClient(apiUrl, externalKey);
|
||||||
|
|
||||||
|
getCommand("report").setExecutor(new ReportCommand(this));
|
||||||
|
getCommand("sugg").setExecutor(new SuggCommand(this));
|
||||||
|
getCommand("appeal").setExecutor(new AppealCommand(this));
|
||||||
|
|
||||||
|
getLogger().info("MC举报系统插件已启用");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
getLogger().info("MC举报系统插件已卸载");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiClient getApiClient() {
|
||||||
|
return apiClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.mcreport.plugin;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class ReportCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final MCReportPlugin plugin;
|
||||||
|
|
||||||
|
public ReportCommand(MCReportPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("§c仅玩家可使用此命令");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
sender.sendMessage("§6===== MC举报系统 ======");
|
||||||
|
sender.sendMessage("§e/report <玩家名> <原因> §7- 举报玩家");
|
||||||
|
sender.sendMessage("§e/report <原因> §7- 提交事项举报");
|
||||||
|
sender.sendMessage("§e/sugg <内容> §7- 提交建议");
|
||||||
|
sender.sendMessage("§e/appeal <原因> §7- 提交申诉");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[0]);
|
||||||
|
String targetName = null;
|
||||||
|
String targetUuid = null;
|
||||||
|
int reasonStart = 0;
|
||||||
|
|
||||||
|
if (target != null && target.isOnline()) {
|
||||||
|
targetName = target.getName();
|
||||||
|
targetUuid = target.getUniqueId().toString();
|
||||||
|
reasonStart = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length <= reasonStart) {
|
||||||
|
sender.sendMessage("§c请填写举报原因");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String reason = String.join(" ", java.util.Arrays.copyOfRange(args, reasonStart, args.length));
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
boolean ok = plugin.getApiClient().submitTicket(
|
||||||
|
"report",
|
||||||
|
"游戏内举报: " + player.getName(),
|
||||||
|
player.getName(),
|
||||||
|
player.getUniqueId(),
|
||||||
|
targetName,
|
||||||
|
targetUuid != null ? java.util.UUID.fromString(targetUuid) : null,
|
||||||
|
reason,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
player.sendMessage("§a举报已提交!工作人员将尽快处理");
|
||||||
|
} else {
|
||||||
|
player.sendMessage("§c举报提交失败,请联系管理员");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.mcreport.plugin;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class SuggCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final MCReportPlugin plugin;
|
||||||
|
|
||||||
|
public SuggCommand(MCReportPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("§c仅玩家可使用此命令");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
sender.sendMessage("§e/sugg <内容> §7- 提交建议");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = String.join(" ", args);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
boolean ok = plugin.getApiClient().submitTicket(
|
||||||
|
"suggestion",
|
||||||
|
"游戏内建议: " + player.getName(),
|
||||||
|
player.getName(),
|
||||||
|
player.getUniqueId(),
|
||||||
|
null, null, null,
|
||||||
|
content
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
player.sendMessage("§a建议已提交!感谢反馈");
|
||||||
|
} else {
|
||||||
|
player.sendMessage("§c提交失败,请联系管理员");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
mc-report-plugin/src/main/resources/config.yml
Normal file
5
mc-report-plugin/src/main/resources/config.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# MC举报系统 - 插件配置
|
||||||
|
# API地址 (web服务地址)
|
||||||
|
api_url: "http://localhost:3100/api/external"
|
||||||
|
# 外部API密钥 (安装时生成)
|
||||||
|
external_key: ""
|
||||||
25
mc-report-plugin/src/main/resources/plugin.yml
Normal file
25
mc-report-plugin/src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: MCReport
|
||||||
|
version: 1.0.0
|
||||||
|
main: com.mcreport.plugin.MCReportPlugin
|
||||||
|
api-version: 1.20
|
||||||
|
author: Sea-Studio
|
||||||
|
description: MC举报系统 - 游戏内举报插件
|
||||||
|
|
||||||
|
commands:
|
||||||
|
report:
|
||||||
|
description: 提交玩家举报
|
||||||
|
usage: /<command> <玩家名> [原因]
|
||||||
|
aliases: [jubao,举报]
|
||||||
|
sugg:
|
||||||
|
description: 提交建议
|
||||||
|
usage: /<command> <内容>
|
||||||
|
aliases: [jianyi,建议]
|
||||||
|
appeal:
|
||||||
|
description: 提交申诉
|
||||||
|
usage: /<command> <原因>
|
||||||
|
aliases: [shensu,申诉]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
mcreport.use:
|
||||||
|
description: 使用举报功能
|
||||||
|
default: true
|
||||||
Reference in New Issue
Block a user