第58批功能1+2: 信誉积分管理系统 + AI聊天介入
Check Source / check (push) Successful in 4s
Auto Release / build (push) Successful in 2m31s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 1s

信誉积分管理系统(CreditManager):
- /credits 上报好人好事/举报,每人初始化100积分
- 管理员审批后加分/扣分,低于阈值触发行为限制(可配置规则)
- PHP端可提交线索,admin审批接口
- GUI: 待审批/历史记录/限制规则
- DB: credit_records + credit_rules 表 + users.credit_score列

AI聊天介入(AIChatManager):
- 兼容openai协议,读models.json/供应商.json/提示词.txt配置
- @//模型名/前缀触发直接提问,私聊回复
- 空闲1-10分钟自动抓聊天互动,广播回复
- 黑名单拦截不浪费token
- 对话留档issue.json,HTTP异步请求不阻塞主线程
- 已解除.gitignore对openai资源的屏蔽
This commit is contained in:
youpaishidifu
2026-10-28 10:49:12 +08:00
parent cf84426bf6
commit a0f84ee542
9 changed files with 1464 additions and 2 deletions
+4 -2
View File
@@ -123,5 +123,7 @@ test.*
!readme.md !readme.md
!src/main/resources !src/main/resources
!src/main/resources/**/*.* !src/main/resources/**/*.*
# 临时屏蔽resources的开发文件 # openai资源文件已启用(第58批 AI聊天功能)
src/main/resources/openai/* !src/main/resources/openai/
!src/main/resources/openai/*.json
!src/main/resources/openai/*.txt
+674
View File
@@ -0,0 +1,674 @@
package Sdf1_login;
import com.google.gson.*;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
import java.io.*;
import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* ★ 第58批功能2: AI聊天介入
*
* 核心逻辑:
* - 兼容 openai 协议,根据模型名取 api 地址和 key
* - @//模型名/ 前缀触发直接提问
* - 空闲时 1-10 分钟自动抓聊天互动
* - 黑名单拦截不浪费 token
* - 对话留档 issue.json
*/
public class AIChatManager {
private final Main plugin;
// 配置数据
private JsonObject modelsConfig;
private JsonObject providersConfig;
private final Map<String, String> aliasToFullModel = new HashMap<>(); // 简称 → 模型全名
private final Map<String, ModelEndpoint> modelEndpoints = new HashMap<>(); // 模型全名 → 端点
private final List<String> blacklist = new ArrayList<>();
// 默认模型
private String defaultModelFull = "";
// 人设
private String serverName = "";
private String assistantName = "";
private String writingStyle = "";
// 发言间隔(分钟)
private int minIntervalMin = 1;
private int maxIntervalMin = 10;
// 默认上下文长度
private String defaultContextLength = "200000";
// 空闲调度器
private BukkitTask autoChatTask = null;
private long lastAiReplyTime = 0;
private final AtomicBoolean autoChatRunning = new AtomicBoolean(false);
// 最近聊天记录缓冲区
private final List<String> recentChatBuffer = new ArrayList<>();
private static final int MAX_BUFFER = 30;
// 对话留档
private JsonObject issueRecord = new JsonObject();
private File issueFile = null;
// HTTP Client
private HttpClient httpClient;
// 最近提问的玩家(防止频繁请求)
private final Map<String, Long> lastRequestTime = new ConcurrentHashMap<>();
// 模型端点
static class ModelEndpoint {
String baseUrl;
String apiKey;
String maxToken;
}
public AIChatManager(Main plugin) {
this.plugin = plugin;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
loadConfig();
}
// ==================== 配置加载 ====================
private void loadConfig() {
try {
// 确保 openai 目录存在
File openaiDir = new File(plugin.getDataFolder(), "openai");
if (!openaiDir.exists()) {
openaiDir.mkdirs();
copyResource("openai/models.json", new File(openaiDir, "models.json"));
copyResource("openai/供应商.json", new File(openaiDir, "供应商.json"));
copyResource("openai/提示词.txt", new File(openaiDir, "提示词.txt"));
copyResource("openai/issue.json", new File(openaiDir, "issue.json"));
plugin.getLogger().info("[AI聊天] 已复制默认配置文件到 " + openaiDir.getAbsolutePath());
}
// 读取配置
File modelsFile = new File(openaiDir, "models.json");
File providersFile = new File(openaiDir, "供应商.json");
File promptFile = new File(openaiDir, "提示词.txt");
issueFile = new File(openaiDir, "issue.json");
if (modelsFile.exists()) {
modelsConfig = JsonParser.parseString(Files.readString(modelsFile.toPath(), StandardCharsets.UTF_8)).getAsJsonObject();
}
if (providersFile.exists()) {
providersConfig = JsonParser.parseString(Files.readString(providersFile.toPath(), StandardCharsets.UTF_8)).getAsJsonObject();
}
// 读取人设
if (modelsConfig != null && modelsConfig.has("人设部分")) {
JsonObject persona = modelsConfig.getAsJsonObject("人设部分");
serverName = getStr(persona, "服务器名字");
assistantName = getStr(persona, "助理名字");
writingStyle = getStr(persona, "写法");
}
// 读取配置部分
if (modelsConfig != null && modelsConfig.has("配置部分")) {
JsonObject config = modelsConfig.getAsJsonObject("配置部分");
// 解析模型配置(配置部分 → "1" → "模型配置")
if (config.has("1")) {
JsonObject section1 = config.getAsJsonObject("1");
if (section1.has("模型配置")) {
JsonObject modelConfig = section1.getAsJsonObject("模型配置");
parseModelConfig(modelConfig);
}
}
// 解析全局配置(配置部分 → "2")
if (config.has("2")) {
JsonObject section2 = config.getAsJsonObject("2");
defaultContextLength = getStr(section2, "默认上下文长度");
if (defaultContextLength == null || defaultContextLength.isEmpty()) {
defaultContextLength = "200000";
}
// 发言间隔
if (section2.has("发言间隔")) {
JsonObject interval = section2.getAsJsonObject("发言间隔");
String minStr = getStr(interval, "最短");
String maxStr = getStr(interval, "最长");
if (minStr != null && !minStr.isEmpty()) {
try { minIntervalMin = Integer.parseInt(minStr); } catch (NumberFormatException ignored) {}
}
if (maxStr != null && !maxStr.isEmpty()) {
try { maxIntervalMin = Integer.parseInt(maxStr); } catch (NumberFormatException ignored) {}
}
}
// 黑名单
if (section2.has("黑名单")) {
JsonElement bl = section2.get("黑名单");
if (bl.isJsonArray()) {
for (JsonElement el : bl.getAsJsonArray()) {
if (el.isJsonPrimitive()) {
blacklist.add(el.getAsString());
}
}
}
}
}
}
// 读取提示词.txt — 解析 models=简称 格式
aliasToFullModel.clear();
if (promptFile.exists()) {
List<String> lines = Files.readAllLines(promptFile.toPath(), StandardCharsets.UTF_8);
for (String line : lines) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
if (line.startsWith("models=")) {
String content = line.substring("models=".length()).trim();
if (content.contains("=")) {
// models=模型全名=简称
String[] parts = content.split("=", 2);
aliasToFullModel.put(parts[1].trim(), parts[0].trim());
} else {
// models=简称 → 简称直接作为模型全名
aliasToFullModel.put(content, content);
}
}
}
}
// 读取对话留档
if (issueFile.exists()) {
issueRecord = JsonParser.parseString(Files.readString(issueFile.toPath(), StandardCharsets.UTF_8)).getAsJsonObject();
}
plugin.getLogger().info("[AI聊天] 配置加载完成: " + modelEndpoints.size() + "个模型, "
+ aliasToFullModel.size() + "个别名, 黑名单" + blacklist.size() + "条");
} catch (Exception e) {
plugin.getLogger().warning("[AI聊天] 配置加载失败: " + e.getMessage());
e.printStackTrace();
}
}
private void copyResource(String resourcePath, File target) {
try {
InputStream is = plugin.getResource(resourcePath);
if (is == null) return;
Files.copy(is, target.toPath(), StandardCopyOption.REPLACE_EXISTING);
is.close();
} catch (Exception e) {
plugin.getLogger().warning("[AI聊天] 复制资源文件失败: " + resourcePath + " - " + e.getMessage());
}
}
private void parseModelConfig(JsonObject modelConfig) {
// 遍历所有供应商配置
for (Map.Entry<String, JsonElement> entry : modelConfig.entrySet()) {
String key = entry.getKey();
if (!entry.getValue().isJsonObject()) continue;
JsonObject supplierConfig = entry.getValue().getAsJsonObject();
// 判断是内置还是自定义
boolean isCustom = supplierConfig.has("baseurl");
boolean isBuiltin = supplierConfig.has("供应商名字");
if (!isCustom && !isBuiltin) continue;
String baseUrl;
String apiKey = getStr(supplierConfig, "apikey");
String maxToken = getStr(supplierConfig, "max_token");
if (maxToken == null || maxToken.isEmpty()) maxToken = defaultContextLength;
if (isCustom) {
baseUrl = getStr(supplierConfig, "baseurl");
} else {
String providerName = getStr(supplierConfig, "供应商名字");
baseUrl = findProviderUrl(providerName);
}
if (baseUrl == null || baseUrl.isEmpty() || apiKey == null || apiKey.isEmpty()) {
continue;
}
// 确保 baseUrl 以 / 结尾(用于拼接 chat/completions)
if (!baseUrl.endsWith("/")) {
baseUrl = baseUrl + "/";
}
ModelEndpoint endpoint = new ModelEndpoint();
endpoint.baseUrl = baseUrl;
endpoint.apiKey = apiKey;
endpoint.maxToken = maxToken;
// 解析可用模型列表
if (supplierConfig.has("可用模型")) {
JsonElement models = supplierConfig.get("可用模型");
if (models.isJsonArray()) {
for (JsonElement modelEl : models.getAsJsonArray()) {
if (modelEl.isJsonPrimitive()) {
String modelName = modelEl.getAsString();
modelEndpoints.put(modelName, endpoint);
}
}
}
}
// 默认使用
String defaultUse = getStr(supplierConfig, "默认使用");
if (defaultUse != null && !defaultUse.isEmpty() && defaultModelFull.isEmpty()) {
defaultModelFull = defaultUse;
}
}
}
// ==================== 供应商URL查找 ====================
private String findProviderUrl(String providerPath) {
if (providerPath == null || providerPath.isEmpty()) return null;
String[] parts = providerPath.split("/");
// 先在国产下找
if (providersConfig != null && providersConfig.has("国产")) {
String url = findUrlInObject(providersConfig.getAsJsonObject("国产"), parts);
if (url != null) return url;
}
// 再在非国产下找
if (providersConfig != null && providersConfig.has("非国产")) {
String url = findUrlInObject(providersConfig.getAsJsonObject("非国产"), parts);
if (url != null) return url;
}
return null;
}
private String findUrlInObject(JsonObject obj, String[] parts) {
JsonElement el = obj.get(parts[0].trim());
if (el == null) return null;
if (parts.length == 1) {
return el.isJsonPrimitive() ? el.getAsString() : null;
}
if (el.isJsonObject()) {
String[] rest = Arrays.copyOfRange(parts, 1, parts.length);
return findUrlInObject(el.getAsJsonObject(), rest);
}
return null;
}
// ==================== 模型名解析 ====================
/**
* 将简称解析为模型全名
*/
public String resolveModelAlias(String alias) {
if (aliasToFullModel.containsKey(alias)) {
return aliasToFullModel.get(alias);
}
// 如果不是简称,尝试直接作为模型全名
if (modelEndpoints.containsKey(alias)) {
return alias;
}
return null;
}
/**
* 获取可用模型简称列表
*/
public List<String> getAvailableModels() {
return new ArrayList<>(aliasToFullModel.keySet());
}
// ==================== HTTP 请求 ====================
/**
* 向指定模型发送请求(异步)
* @param modelAlias 模型简称
* @param prompt 用户输入
* @param player 发起请求的玩家(用于私聊回复)
* @param broadcast 是否广播回复(自动互动时为 true)
*/
public void sendToModel(String modelAlias, String prompt, Player player, boolean broadcast) {
// 黑名单检查
if (isBlacklisted(prompt)) {
plugin.getLogger().info("[AI聊天] 黑名单拦截: " + prompt.substring(0, Math.min(50, prompt.length())));
if (player != null) {
player.sendMessage("§c[AI聊天] §f输入内容命中黑名单,请求已拦截");
}
return;
}
// 频率限制(同一玩家10秒内只能请求一次)
if (player != null) {
Long last = lastRequestTime.get(player.getName());
if (last != null && System.currentTimeMillis() - last < 10000) {
player.sendMessage("§c[AI聊天] §f请等待10秒后再提问");
return;
}
lastRequestTime.put(player.getName(), System.currentTimeMillis());
}
// 解析模型名
String modelFull = resolveModelAlias(modelAlias);
if (modelFull == null) {
if (player != null) {
player.sendMessage("§c[AI聊天] §f未知模型: " + modelAlias);
List<String> models = getAvailableModels();
if (!models.isEmpty()) {
player.sendMessage("§7可用模型: " + String.join(", ", models));
} else {
player.sendMessage("§7未配置模型,请先在 openai/models.json 和 openai/提示词.txt 中配置");
}
}
return;
}
ModelEndpoint endpoint = modelEndpoints.get(modelFull);
if (endpoint == null) {
if (player != null) {
player.sendMessage("§c[AI聊天] §f模型配置不存在: " + modelFull);
}
return;
}
// 构建请求体
String systemPrompt = buildSystemPrompt();
String jsonBody = buildChatRequest(modelFull, systemPrompt, prompt, endpoint.maxToken);
// 异步发送请求
CompletableFuture.supplyAsync(() -> {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint.baseUrl + "chat/completions"))
.header("Content-Type", "application/json; charset=utf-8")
.header("Authorization", "Bearer " + endpoint.apiKey)
.POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
.timeout(Duration.ofSeconds(60))
.build();
return httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
} catch (Exception e) {
plugin.getLogger().warning("[AI聊天] 请求异常: " + e.getMessage());
return null;
}
}).thenAccept(response -> {
if (response == null) {
handleRequestFailure(prompt, "请求异常: 网络错误");
if (player != null) {
player.sendMessage("§c[AI聊天] §f请求失败,请稍后重试");
}
return;
}
if (response.statusCode() != 200) {
String errBody = response.body();
handleRequestFailure(prompt, "HTTP " + response.statusCode() + ": " + errBody);
if (player != null) {
player.sendMessage("§c[AI聊天] §f请求失败: HTTP " + response.statusCode());
}
return;
}
// 解析响应
String reply = parseChatResponse(response.body());
if (reply == null) {
handleRequestFailure(prompt, "解析失败: " + response.body());
if (player != null) {
player.sendMessage("§c[AI聊天] §f响应解析失败");
}
return;
}
// 发送回复
if (broadcast) {
Bukkit.broadcastMessage("§d§l[AI] §f" + reply);
} else if (player != null) {
player.sendMessage("§d§l[AI] §f" + reply);
}
// 记录留档
recordIssue(prompt, reply);
lastAiReplyTime = System.currentTimeMillis();
});
}
private String buildSystemPrompt() {
StringBuilder sb = new StringBuilder();
if (!serverName.isEmpty()) {
sb.append("你在一个名为\"").append(serverName).append("\"的Minecraft服务器中。\n");
}
if (!assistantName.isEmpty()) {
sb.append("你是服务器助理\"").append(assistantName).append("\"。\n");
}
sb.append("你需要用简洁友好的语气回答玩家的问题。\n");
if (!writingStyle.isEmpty()) {
sb.append("写法要求: ").append(writingStyle).append("\n");
}
return sb.toString();
}
private String buildChatRequest(String model, String systemPrompt, String userPrompt, String maxToken) {
Gson gson = new Gson();
JsonObject body = new JsonObject();
body.addProperty("model", model);
JsonArray messages = new JsonArray();
JsonObject sysMsg = new JsonObject();
sysMsg.addProperty("role", "system");
sysMsg.addProperty("content", systemPrompt);
messages.add(sysMsg);
JsonObject userMsg = new JsonObject();
userMsg.addProperty("role", "user");
userMsg.addProperty("content", userPrompt);
messages.add(userMsg);
body.add("messages", messages);
// max_tokens
try {
int mt = Integer.parseInt(maxToken);
if (mt > 0) {
body.addProperty("max_tokens", mt);
}
} catch (NumberFormatException ignored) {}
return gson.toJson(body);
}
private String parseChatResponse(String responseBody) {
try {
JsonObject resp = JsonParser.parseString(responseBody).getAsJsonObject();
if (resp.has("choices")) {
JsonArray choices = resp.getAsJsonArray("choices");
if (choices.size() > 0) {
JsonObject first = choices.get(0).getAsJsonObject();
if (first.has("message")) {
JsonObject message = first.getAsJsonObject("message");
if (message.has("content")) {
return message.get("content").getAsString();
}
}
}
}
return null;
} catch (Exception e) {
return null;
}
}
private void handleRequestFailure(String prompt, String errorDetail) {
plugin.getLogger().warning("[AI聊天] 请求失败: " + errorDetail);
recordIssue(prompt, "[请求失败] " + errorDetail);
}
// ==================== 聊天处理 ====================
/**
* 处理玩家聊天消息
* 检测 @//模型名/ 前缀触发直接提问
*/
public void handlePlayerChat(Player p, String message) {
// 记录到聊天缓冲区
String chatLine = p.getDisplayName() + ": " + message;
recentChatBuffer.add(chatLine);
if (recentChatBuffer.size() > MAX_BUFFER) {
recentChatBuffer.remove(0);
}
// 检测 @//模型名/ 前缀
if (message.startsWith("@//")) {
// 格式: @//模型名/内容
int slashIdx = message.indexOf('/', 3);
if (slashIdx > 0) {
String modelAlias = message.substring(3, slashIdx);
String prompt = message.substring(slashIdx + 1);
if (prompt.isEmpty()) {
p.sendMessage("§c[AI聊天] §f格式错误,应为 @//模型名/内容");
return;
}
sendToModel(modelAlias, prompt, p, false);
} else {
p.sendMessage("§c[AI聊天] §f格式错误,应为 @//模型名/内容");
}
}
}
// ==================== 空闲调度器 ====================
public void startAutoChatScheduler() {
if (autoChatTask != null) {
autoChatTask.cancel();
}
// 每1秒检查一次
autoChatTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
try {
checkAutoChat();
} catch (Exception e) {
plugin.getLogger().warning("[AI聊天] 自动互动异常: " + e.getMessage());
}
}, 20L, 20L);
plugin.getLogger().info("[AI聊天] 空闲自动互动调度器已启动");
}
public void stopAutoChatScheduler() {
if (autoChatTask != null) {
autoChatTask.cancel();
autoChatTask = null;
}
}
private void checkAutoChat() {
if (!autoChatRunning.compareAndSet(false, true)) return;
try {
// 检查是否到了自动互动时间
long intervalMs = (minIntervalMin + (int)(Math.random() * (maxIntervalMin - minIntervalMin + 1))) * 60000L;
if (System.currentTimeMillis() - lastAiReplyTime < intervalMs) return;
if (recentChatBuffer.isEmpty()) return;
if (defaultModelFull.isEmpty()) return;
ModelEndpoint endpoint = modelEndpoints.get(defaultModelFull);
if (endpoint == null) return;
// 构建自动互动提示词
String recentChat = String.join("\n", recentChatBuffer);
String prompt = "以下是服务器最近的聊天记录:\n" + recentChat + "\n\n请根据聊天内容,自然地参与对话或回答问题。";
plugin.getLogger().info("[AI聊天] 触发自动互动");
// 异步发送
String systemPrompt = buildSystemPrompt();
String jsonBody = buildChatRequest(defaultModelFull, systemPrompt, prompt, endpoint.maxToken);
CompletableFuture.supplyAsync(() -> {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint.baseUrl + "chat/completions"))
.header("Content-Type", "application/json; charset=utf-8")
.header("Authorization", "Bearer " + endpoint.apiKey)
.POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
.timeout(Duration.ofSeconds(60))
.build();
return httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
} catch (Exception e) {
plugin.getLogger().warning("[AI聊天] 自动互动请求异常: " + e.getMessage());
return null;
}
}).thenAccept(response -> {
if (response == null || response.statusCode() != 200) {
handleRequestFailure("[自动] " + prompt.substring(0, Math.min(100, prompt.length())),
"自动互动失败: HTTP " + (response != null ? response.statusCode() : "N/A"));
return;
}
String reply = parseChatResponse(response.body());
if (reply != null) {
Bukkit.broadcastMessage("§d§l[AI] §f" + reply);
recordIssue("[自动] " + prompt.substring(0, Math.min(100, prompt.length())), reply);
lastAiReplyTime = System.currentTimeMillis();
}
});
} finally {
autoChatRunning.set(false);
}
}
// ==================== 黑名单 ====================
public boolean isBlacklisted(String content) {
if (content == null || content.isEmpty()) return false;
for (String kw : blacklist) {
if (kw != null && !kw.isEmpty() && content.contains(kw)) {
return true;
}
}
return false;
}
// ==================== 对话留档 ====================
private void recordIssue(String question, String answer) {
if (issueFile == null) return;
try {
issueRecord.addProperty(question, answer);
// 原子写入
File tmp = new File(issueFile.getParent(), "issue.tmp.json");
Files.write(tmp.toPath(), issueRecord.toString().getBytes(StandardCharsets.UTF_8));
Files.move(tmp.toPath(), issueFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
plugin.getLogger().warning("[AI聊天] 对话留档失败: " + e.getMessage());
}
}
// ==================== 工具方法 ====================
private String getStr(JsonObject obj, String key) {
if (obj == null || !obj.has(key)) return "";
JsonElement el = obj.get(key);
if (el == null || el.isJsonNull()) return "";
return el.isJsonPrimitive() ? el.getAsString() : "";
}
// ==================== 生命周期 ====================
public void onEnable() {
startAutoChatScheduler();
}
public void onDisable() {
stopAutoChatScheduler();
}
}
+588
View File
@@ -0,0 +1,588 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.command.CommandSender;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* ★ 第58批: 信誉积分管理系统
*
* 核心逻辑:
* - 每人初始化100积分
* - /credits 上报好人好事/举报
* - PHP 端可提交线索
* - 管理员审批 → 加分/扣分
* - 低于阈值 → 行为限制(可配置规则)
*/
public class CreditManager {
private final Main plugin;
private final DatabaseManager db;
// 默认初始积分
public static final int DEFAULT_INIT_SCORE = 100;
// 审批状态
public static final String STATUS_PENDING = "pending";
public static final String STATUS_APPROVED = "approved";
public static final String STATUS_REJECTED = "rejected";
// 上报类型
public static final String TYPE_GOOD = "good"; // 好人好事
public static final String TYPE_BAD = "bad"; // 举报
// 玩家积分缓存 (playerName -> score)
private final Map<String, Integer> scoreCache = new ConcurrentHashMap<>();
public CreditManager(Main plugin) {
this.plugin = plugin;
this.db = plugin.getDb();
ensureTables();
}
// ==================== DB 初始化 ====================
private void ensureTables() {
try {
Statement st = db.getConnection().createStatement();
// 信誉积分记录表
st.execute("CREATE TABLE IF NOT EXISTS credit_records ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "target_player TEXT NOT NULL," // 被评价的玩家
+ "reporter_player TEXT NOT NULL," // 上报人
+ "type TEXT NOT NULL," // good / bad
+ "content TEXT DEFAULT ''," // 详细内容
+ "status TEXT DEFAULT 'pending'," // pending / approved / rejected
+ "score_change INTEGER DEFAULT 0," // 审批后的积分变动
+ "created_at INTEGER DEFAULT 0," // 上报时间
+ "approved_at INTEGER DEFAULT 0," // 审批时间
+ "approve_reason TEXT DEFAULT ''," // 审批理由
+ "source TEXT DEFAULT 'game'," // game / php
+ "FOREIGN KEY(target_player) REFERENCES users(player_name)"
+ ")");
// 信誉限制规则表
st.execute("CREATE TABLE IF NOT EXISTS credit_rules ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "rule_name TEXT NOT NULL UNIQUE," // 规则名
+ "min_score INTEGER DEFAULT 0," // 低于此分触发
+ "max_score INTEGER DEFAULT 0," // 高于此分解除(0=无上限)
+ "restriction_type TEXT DEFAULT ''," // 限制类型
+ "restriction_duration INTEGER DEFAULT 0," // 持续秒数(0=直到加分)
+ "enabled INTEGER DEFAULT 1," // 1=启用 0=禁用
+ "description TEXT DEFAULT ''" // 说明
+ ")");
// 默认规则初始化
initDefaultRules();
// users 表添加 credit_score 列
try {
st.executeUpdate("ALTER TABLE users ADD COLUMN credit_score INTEGER DEFAULT " + DEFAULT_INIT_SCORE);
} catch (Exception ignored) {}
} catch (Exception e) {
plugin.getLogger().warning("[信誉积分] 建表失败: " + e.getMessage());
}
}
private void initDefaultRules() {
try {
Statement st = db.getConnection().createStatement();
// 默认规则:低于60分禁止交易
st.execute("INSERT OR IGNORE INTO credit_rules (rule_name, min_score, max_score, restriction_type, restriction_duration, description) "
+ "VALUES ('禁止交易', 60, 0, 'trade', 0, '低于60分时禁止与NPC交易')");
// 默认规则:低于40分禁止进入PVP区域
st.execute("INSERT OR IGNORE INTO credit_rules (rule_name, min_score, max_score, restriction_type, restriction_duration, description) "
+ "VALUES ('禁止PVP', 40, 0, 'pvp', 0, '低于40分时禁止进入PVP竞技场')");
// 默认规则:低于20分禁止使用传送
st.execute("INSERT OR IGNORE INTO credit_rules (rule_name, min_score, max_score, restriction_type, restriction_duration, description) "
+ "VALUES ('禁止传送', 20, 0, 'teleport', 0, '低于20分时禁止使用传送功能')");
} catch (Exception ignored) {}
}
// ==================== 积分查询 ====================
public int getScore(String playerName) {
if (scoreCache.containsKey(playerName)) {
return scoreCache.get(playerName);
}
int score = DEFAULT_INIT_SCORE;
try {
PreparedStatement ps = db.getConnection().prepareStatement(
"SELECT credit_score FROM users WHERE player_name = ?");
ps.setString(1, playerName);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
score = rs.getInt("credit_score");
}
ps.close();
} catch (Exception ignored) {}
scoreCache.put(playerName, score);
return score;
}
// ==================== 积分修改 ====================
public void setScore(String playerName, int score) {
try {
PreparedStatement ps = db.getConnection().prepareStatement(
"UPDATE users SET credit_score = ? WHERE player_name = ?");
ps.setInt(1, score);
ps.setString(2, playerName);
ps.executeUpdate();
ps.close();
} catch (Exception e) {
plugin.getLogger().warning("[信誉积分] 设置积分失败: " + e.getMessage());
}
scoreCache.put(playerName, score);
checkRestrictions(playerName, score);
}
public void addScore(String playerName, int amount, String reason) {
int current = getScore(playerName);
setScore(playerName, current + amount);
plugin.getLogger().info("[信誉积分] " + playerName + " 积分变动: "
+ (amount > 0 ? "+" : "") + amount + " (" + reason + ")");
}
// ==================== 上报 ====================
public void submitReport(String targetPlayer, String reporterPlayer, String type, String content, String source) {
try {
PreparedStatement ps = db.getConnection().prepareStatement(
"INSERT INTO credit_records (target_player, reporter_player, type, content, status, created_at, source) "
+ "VALUES (?, ?, ?, ?, 'pending', ?, ?)");
ps.setString(1, targetPlayer);
ps.setString(2, reporterPlayer);
ps.setString(3, type);
ps.setString(4, content);
ps.setLong(5, System.currentTimeMillis());
ps.setString(6, source);
ps.executeUpdate();
ps.close();
plugin.getLogger().info("[信誉积分] 上报: " + reporterPlayer + " 举报/表扬 " + targetPlayer
+ " 类型=" + type + " 来源=" + source);
} catch (Exception e) {
plugin.getLogger().warning("[信誉积分] 上报失败: " + e.getMessage());
}
}
// ==================== 审批 ====================
public boolean approveRecord(int recordId, boolean approved, String reason, int scoreChange, String approverName) {
try {
PreparedStatement ps = db.getConnection().prepareStatement(
"UPDATE credit_records SET status = ?, score_change = ?, approved_at = ?, approve_reason = ? WHERE id = ?");
ps.setString(1, approved ? STATUS_APPROVED : STATUS_REJECTED);
ps.setInt(2, approved ? scoreChange : 0);
ps.setLong(3, System.currentTimeMillis());
ps.setString(4, reason);
ps.setInt(5, recordId);
ps.executeUpdate();
ps.close();
if (approved && scoreChange != 0) {
// 查询目标玩家
PreparedStatement ps2 = db.getConnection().prepareStatement(
"SELECT target_player FROM credit_records WHERE id = ?");
ps2.setInt(1, recordId);
ResultSet rs = ps2.executeQuery();
if (rs.next()) {
String target = rs.getString("target_player");
addScore(target, scoreChange, "信誉积分审批(" + (approved ? "通过" : "驳回") + "): " + reason);
plugin.getLogger().info("[信誉积分] " + approverName + " 审批通过 " + target
+ " 积分变动: " + (scoreChange > 0 ? "+" : "") + scoreChange);
}
ps2.close();
}
return true;
} catch (Exception e) {
plugin.getLogger().warning("[信誉积分] 审批失败: " + e.getMessage());
return false;
}
}
// ==================== 查询 ====================
public List<Map<String, Object>> getPendingRecords() {
List<Map<String, Object>> list = new ArrayList<>();
try {
PreparedStatement ps = db.getConnection().prepareStatement(
"SELECT * FROM credit_records WHERE status = 'pending' ORDER BY created_at DESC");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>();
row.put("id", rs.getInt("id"));
row.put("target_player", rs.getString("target_player"));
row.put("reporter_player", rs.getString("reporter_player"));
row.put("type", rs.getString("type"));
row.put("content", rs.getString("content"));
row.put("created_at", rs.getLong("created_at"));
row.put("source", rs.getString("source"));
list.add(row);
}
ps.close();
} catch (Exception e) {
plugin.getLogger().warning("[信誉积分] 查询待审批失败: " + e.getMessage());
}
return list;
}
public List<Map<String, Object>> getAllRecords(int limit) {
List<Map<String, Object>> list = new ArrayList<>();
try {
PreparedStatement ps = db.getConnection().prepareStatement(
"SELECT * FROM credit_records ORDER BY created_at DESC LIMIT ?");
ps.setInt(1, limit);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>();
row.put("id", rs.getInt("id"));
row.put("target_player", rs.getString("target_player"));
row.put("reporter_player", rs.getString("reporter_player"));
row.put("type", rs.getString("type"));
row.put("content", rs.getString("content"));
row.put("status", rs.getString("status"));
row.put("score_change", rs.getInt("score_change"));
row.put("created_at", rs.getLong("created_at"));
row.put("source", rs.getString("source"));
list.add(row);
}
ps.close();
} catch (Exception e) {
plugin.getLogger().warning("[信誉积分] 查询记录失败: " + e.getMessage());
}
return list;
}
// ==================== 限制规则 ====================
public List<Map<String, Object>> getEnabledRules() {
List<Map<String, Object>> list = new ArrayList<>();
try {
PreparedStatement ps = db.getConnection().prepareStatement(
"SELECT * FROM credit_rules WHERE enabled = 1");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>();
row.put("id", rs.getInt("id"));
row.put("rule_name", rs.getString("rule_name"));
row.put("min_score", rs.getInt("min_score"));
row.put("max_score", rs.getInt("max_score"));
row.put("restriction_type", rs.getString("restriction_type"));
row.put("restriction_duration", rs.getInt("restriction_duration"));
row.put("description", rs.getString("description"));
list.add(row);
}
ps.close();
} catch (Exception e) {
plugin.getLogger().warning("[信誉积分] 查询规则失败: " + e.getMessage());
}
return list;
}
public boolean isRestricted(String playerName, String restrictionType) {
int score = getScore(playerName);
for (Map<String, Object> rule : getEnabledRules()) {
int minScore = ((Number) rule.get("min_score")).intValue();
String type = (String) rule.get("restriction_type");
if (score < minScore && type.equals(restrictionType)) {
return true;
}
}
return false;
}
private void checkRestrictions(String playerName, int score) {
// 检查是否触发限制,通知玩家
for (Map<String, Object> rule : getEnabledRules()) {
int minScore = ((Number) rule.get("min_score")).intValue();
int maxScore = ((Number) rule.get("max_score")).intValue();
String type = (String) rule.get("restriction_type");
String desc = (String) rule.get("description");
Player p = Bukkit.getPlayerExact(playerName);
if (p == null || !p.isOnline()) continue;
if (score < minScore && (maxScore == 0 || score <= maxScore)) {
p.sendMessage("§c§l[信誉积分] §f你的积分 " + score + " 低于阈值 " + minScore + ","
+ "已触发限制: " + desc);
}
}
}
// ==================== GUI ====================
public Inventory createPendingGUI(Player p) {
Inventory inv = Bukkit.createInventory(null, 54, "§d§l信誉积分 - 待审批");
fillBg(inv);
List<Map<String, Object>> records = getPendingRecords();
for (int i = 0; i < records.size() && i < 49; i++) {
Map<String, Object> r = records.get(i);
String type = (String) r.get("type");
Material mat = TYPE_GOOD.equals(type) ? Material.EMERALD : Material.REDSTONE;
String typeText = TYPE_GOOD.equals(type) ? "§a好人好事" : "§c举报";
inv.setItem(i, mkItem(mat,
"§e#" + r.get("id") + " " + r.get("target_player"),
"§7上报人: " + r.get("reporter_player"),
"§7类型: " + typeText,
"§7内容: " + r.get("content"),
"§7来源: " + r.get("source"),
"",
"§e点击审批"));
}
inv.setItem(53, mkItem(Material.ARROW, "§7返回"));
return inv;
}
public Inventory createRecordsGUI(Player p) {
Inventory inv = Bukkit.createInventory(null, 54, "§d§l信誉积分 - 历史记录");
fillBg(inv);
List<Map<String, Object>> records = getAllRecords(49);
for (int i = 0; i < records.size() && i < 49; i++) {
Map<String, Object> r = records.get(i);
String status = (String) r.get("status");
String statusText = STATUS_APPROVED.equals(status) ? "§a已通过"
: STATUS_REJECTED.equals(status) ? "§c已驳回" : "§e待审批";
inv.setItem(i, mkItem(Material.PAPER,
"§e#" + r.get("id") + " " + r.get("target_player"),
"§7上报人: " + r.get("reporter_player"),
"§7类型: " + r.get("type"),
"§7状态: " + statusText,
"§7积分变动: " + r.get("score_change"),
"§7内容: " + r.get("content"),
"§7来源: " + r.get("source")));
}
inv.setItem(53, mkItem(Material.ARROW, "§7返回"));
return inv;
}
public Inventory createRulesGUI(Player p) {
Inventory inv = Bukkit.createInventory(null, 54, "§d§l信誉积分 - 限制规则");
fillBg(inv);
List<Map<String, Object>> rules = getEnabledRules();
for (int i = 0; i < rules.size() && i < 49; i++) {
Map<String, Object> r = rules.get(i);
inv.setItem(i, mkItem(Material.GOLD_INGOT,
"§e" + r.get("rule_name"),
"§7阈值: " + r.get("min_score"),
"§7类型: " + r.get("restriction_type"),
"§7说明: " + r.get("description")));
}
inv.setItem(53, mkItem(Material.ARROW, "§7返回"));
return inv;
}
// ==================== 命令入口 ====================
public boolean handleCommand(CommandSender sender, String[] args) {
if (args.length == 0) {
// 无参数:显示玩家自己的积分
if (sender instanceof Player) {
String name = ((Player) sender).getName();
int score = getScore(name);
sender.sendMessage("§d§l[信誉积分] §f你的当前积分: §e" + score + " §7(初始: " + DEFAULT_INIT_SCORE + ")");
} else {
sender.sendMessage("§c用法: /credits [上报|审批|记录|规则] [参数]");
}
return true;
}
String sub = args[0].toLowerCase();
switch (sub) {
case "上报":
case "report":
// /credits report <目标玩家> <good|bad> <内容>
if (args.length < 4) {
sender.sendMessage("§c用法: /credits report <目标玩家> <good|bad> <内容>");
return true;
}
if (!(sender instanceof Player)) {
sender.sendMessage("§c此命令只能玩家使用");
return true;
}
String target = args[1];
String type = args[2].toLowerCase();
StringBuilder content = new StringBuilder();
for (int i = 3; i < args.length; i++) {
if (i > 3) content.append(" ");
content.append(args[i]);
}
if (!TYPE_GOOD.equals(type) && !TYPE_BAD.equals(type)) {
sender.sendMessage("§c类型必须是 good 或 bad");
return true;
}
submitReport(target, ((Player) sender).getName(), type, content.toString(), "game");
sender.sendMessage("§a[信誉积分] 上报成功,等待管理员审批");
return true;
case "审批":
case "approve":
// 管理员审批
if (!(sender.hasPermission("sdf1_login.admin"))) {
sender.sendMessage("§c权限不足");
return true;
}
// /credits approve <id> <通过|驳回> <理由> [积分变动]
if (args.length < 4) {
// 无 id 参数 → 打开 GUI
if (sender instanceof Player) {
((Player) sender).openInventory(createPendingGUI((Player) sender));
} else {
List<Map<String, Object>> records = getPendingRecords();
if (records.isEmpty()) {
sender.sendMessage("§a没有待审批的记录");
return true;
}
for (Map<String, Object> r : records) {
sender.sendMessage("§e#" + r.get("id") + " " + r.get("target_player")
+ " §7(" + r.get("type") + ") " + r.get("content"));
}
sender.sendMessage("§7用法: /credits approve <id> <通过|驳回> <理由> [积分变动]");
}
return true;
}
// 有 id 参数 → CLI 审批
try {
int recordId = Integer.parseInt(args[1]);
String decision = args[2].toLowerCase();
boolean approved;
if (decision.equals("通过") || decision.equals("approve") || decision.equals("approved")) {
approved = true;
} else if (decision.equals("驳回") || decision.equals("reject") || decision.equals("rejected")) {
approved = false;
} else {
sender.sendMessage("§c决定必须是「通过」或「驳回」");
return true;
}
StringBuilder reason = new StringBuilder();
for (int i = 3; i < args.length; i++) {
if (i > 3) reason.append(" ");
reason.append(args[i]);
}
// 解析积分变动(最后一个参数如果是数字)
int scoreChange = 0;
String reasonStr = reason.toString();
// 检查是否以数字结尾(积分变动在最后)
java.util.regex.Matcher m = java.util.regex.Pattern.compile("(\\d+)$").matcher(reasonStr);
if (approved && m.find()) {
scoreChange = Integer.parseInt(m.group(1));
reasonStr = reasonStr.substring(0, reasonStr.length() - m.group(1).length()).trim();
if (reasonStr.isEmpty()) reasonStr = "信誉积分审批";
} else if (approved) {
// 默认好人好事+10,举报+10
scoreChange = 10;
}
String approver = sender instanceof Player ? ((Player) sender).getName() : "控制台";
boolean ok = approveRecord(recordId, approved, reasonStr, scoreChange, approver);
if (ok) {
sender.sendMessage("§a审批完成: #" + recordId + " " + (approved ? "通过" : "驳回"));
} else {
sender.sendMessage("§c审批失败,记录不存在或已处理");
}
} catch (NumberFormatException e) {
sender.sendMessage("§c记录ID必须是数字");
}
return true;
case "记录":
case "records":
// 查看历史记录
if (sender instanceof Player) {
((Player) sender).openInventory(createRecordsGUI((Player) sender));
} else {
List<Map<String, Object>> records = getAllRecords(20);
for (Map<String, Object> r : records) {
sender.sendMessage("§e#" + r.get("id") + " " + r.get("target_player")
+ " §7(" + r.get("type") + ") " + r.get("status")
+ " §7积分变动=" + r.get("score_change"));
}
}
return true;
case "规则":
case "rules":
// 查看限制规则
if (sender instanceof Player) {
((Player) sender).openInventory(createRulesGUI((Player) sender));
} else {
for (Map<String, Object> r : getEnabledRules()) {
sender.sendMessage("§e" + r.get("rule_name")
+ " §7阈值=" + r.get("min_score")
+ " §7类型=" + r.get("restriction_type")
+ " §7说明=" + r.get("description"));
}
}
return true;
default:
sender.sendMessage("§c未知子命令: " + sub);
return true;
}
}
// ==================== GUI 点击处理 ====================
public void handleClick(Player p, int slot, String title) {
if (slot == 53) {
p.closeInventory();
return;
}
if (title != null && title.contains("待审批")) {
p.sendMessage("§c请使用 /credits approve <id> <通过|驳回> <理由> [积分变动]");
p.closeInventory();
} else if (title != null && title.contains("历史记录")) {
p.sendMessage("§7此GUI仅展示,请用命令管理");
} else if (title != null && title.contains("限制规则")) {
p.sendMessage("§7规则阈值低于当前积分则触发限制");
}
}
// ==================== GUI 工具 ====================
private void fillBg(Inventory inv) {
ItemStack gl = mkItem(Material.GRAY_STAINED_GLASS_PANE, " ");
for (int i = 0; i < inv.getSize(); i++) inv.setItem(i, gl);
}
private ItemStack mkItem(Material mat, String name, String... lore) {
ItemStack it = new ItemStack(mat);
ItemMeta im = it.getItemMeta();
if (im != null) {
im.setDisplayName(name);
if (lore.length > 0) im.setLore(Arrays.asList(lore));
it.setItemMeta(im);
}
return it;
}
// ==================== PHP 端提交线索 ====================
/**
* 供 PHP 端调用的提交线索接口(通过 WebManager 对账或独立 API)
*/
public boolean submitFromPhp(String targetPlayer, String reporterPlayer, String type, String content) {
submitReport(targetPlayer, reporterPlayer, type, content, "php");
return true;
}
/**
* 供 PHP 端调用的审批接口
*/
public boolean approveFromPhp(int recordId, boolean approved, String reason, int scoreChange, String approverName) {
return approveRecord(recordId, approved, reason, scoreChange, approverName);
}
}
+33
View File
@@ -80,6 +80,8 @@ public class Main extends JavaPlugin
private CheckInManager checkIn; private CheckInManager checkIn;
private InviteManager invite; private InviteManager invite;
private PointsManager points; private PointsManager points;
private CreditManager creditManager;
private AIChatManager aiChatManager;
public GUIManager gui; public GUIManager gui;
private ChatInputManager chatInput; private ChatInputManager chatInput;
private VerificationManager verification; private VerificationManager verification;
@@ -319,6 +321,10 @@ public class Main extends JavaPlugin
return points; return points;
} }
public CreditManager getCreditManager() {
return creditManager;
}
public GUIManager getGui() { public GUIManager getGui() {
return gui; return gui;
} }
@@ -482,6 +488,9 @@ public class Main extends JavaPlugin
checkIn = new CheckInManager(this); checkIn = new CheckInManager(this);
invite = new InviteManager(this); invite = new InviteManager(this);
points = new PointsManager(this); points = new PointsManager(this);
creditManager = new CreditManager(this);
aiChatManager = new AIChatManager(this);
aiChatManager.onEnable();
chatInput = new ChatInputManager(); chatInput = new ChatInputManager();
verification = new VerificationManager(this); verification = new VerificationManager(this);
riskControl = new RiskControlManager(this); riskControl = new RiskControlManager(this);
@@ -1053,6 +1062,7 @@ public class Main extends JavaPlugin
} }
if (db != null) db.close(); if (db != null) db.close();
if (garbage != null) garbage.close(); if (garbage != null) garbage.close();
if (aiChatManager != null) aiChatManager.onDisable();
if (chatTransPool != null) { if (chatTransPool != null) {
chatTransPool.shutdownNow(); chatTransPool.shutdownNow();
} }
@@ -6179,6 +6189,11 @@ public class Main extends JavaPlugin
if (chatFilter != null) { if (chatFilter != null) {
chatFilter.markPlayerActive(p.getName()); chatFilter.markPlayerActive(p.getName());
} }
// ★ 第58批:AI聊天介入 — @//模型名/ 前缀触发直接提问
if (aiChatManager != null) {
aiChatManager.handlePlayerChat(p, msg);
}
// ★ 先探一次禁言状态:若刚好到期,isMuted() 内部会立即解禁并打上 // ★ 先探一次禁言状态:若刚好到期,isMuted() 内部会立即解禁并打上
// 「需重新人机验证」标记(2026-10-08:解禁≠解除人机验证) // 「需重新人机验证」标记(2026-10-08:解禁≠解除人机验证)
@@ -7162,6 +7177,15 @@ public class Main extends JavaPlugin
return; return;
} }
// ===== 信誉积分 GUI 点击处理 =====
if (title != null && title.contains("信誉积分")) {
if (creditManager != null) {
e.setCancelled(true);
creditManager.handleClick(p, slot, title);
}
return;
}
// ===== 垃圾箱 ===== // ===== 垃圾箱 =====
// ===== 垃圾回收站 ===== // ===== 垃圾回收站 =====
if (title.equals("§6§l垃圾回收站")) { if (title.equals("§6§l垃圾回收站")) {
@@ -9434,6 +9458,15 @@ public class Main extends JavaPlugin
return true; return true;
} }
// ===== /credits:信誉积分管理 =====
if (cmdName.equals("credits")) {
if (creditManager == null) {
sender.sendMessage("§c信誉积分模块未初始化");
return true;
}
return creditManager.handleCommand(sender, args);
}
// ★ 第30批:/addblack <玩家> [时长] [理由] —— 拉黑 // ★ 第30批:/addblack <玩家> [时长] [理由] —— 拉黑
if (cmdName.equals("addblack")) { if (cmdName.equals("addblack")) {
+9
View File
@@ -0,0 +1,9 @@
{
"对话留档":{
"怎么写":"键是提问,值是回答",
"谁来写":"每次提问/控场自动写",
"请求失败":"响应原文落盘"
},
"问":"答",
"自动":"答"
}
+87
View File
@@ -0,0 +1,87 @@
{
"人设部分": {
"服务器名字": "",
"助理名字": "",
"写法": "1行1个"
},
"配置部分": {
"1": {
"模型配置": {
"内置供应商": {
"供应商名字": "",
"apikey": "",
"max_token": "",
"可用模型": [],
"默认使用": ""
},
"自定义供应商": {
"baseurl": "",
"apikey": "",
"max_token": "",
"可用模型": [],
"默认使用": ""
}
}
},
"2": {
"默认上下文长度": "200000",
"发言间隔": {
"最短": "",
"最长": "",
"注释": "单位分钟"
},
"自动补齐": {
"开关": true,
"备注": "如果你的厂商"
}
}
},
"填写规范": {
"自定义": {
"baseurl": "填写厂家给你的地址,比如https://example.com/v1",
"apikey": "填厂家开放平台的key,比如sk-example",
"可用模型": "填厂家给的模型名字,比如example",
"max_token": "上下文长度,不填则使用默认20万上下文长度"
},
"内置供应商": {
"供应商名字": "一层一层填,比如mimo/按量计费,deepseek等",
"apikey": "填厂家开放平台的key,比如sk-example",
"可用模型": "填厂家给的模型名字,比如example",
"max_token": "上下文长度,不填则使用默认20万上下文长度"
}
},
"配置填写示例": {
"自定义供应商": {
"baseurl": "https://example.com/v1",
"apikey": "sk-example",
"max_token": "1000000",
"可用模型": [
"example"
]
},
"内置供应商": {
"供应商名字": "mimo/token-plan",
"apikey": "sk-example",
"可用模型": [
"mimo-v2.6-flash",
"mimo-v2.6-pro"
]
},
"内置供应商1": {
"供应商名字": "deepseek",
"apikey": "sk-example",
"可用模型": [
"deepseek-flash",
"deepseek-v4-pro"
]
},
"内置供应商3": {
"供应商名字": "讯飞/讯飞星辰maas/标准",
"apikey": "sk-example",
"可用模型": [
"spakr-x2.6",
"xopglm5"
]
}
}
}
+63
View File
@@ -0,0 +1,63 @@
{
"openai协议": "内置供应商",
"添加方法": [
"键值对格式,键是供应商名字,值是请求地址",
"如果供应商有区分按量计费、token-plan则用一堆花括号即可。可参考mimo的写法",
"如果要备注,就用花括号展开,比如向Agnes、opencode一样",
"额外说明:本文件默认会从插件内置的服务器定期拉取更新。"
],
"国产": {
"mimo": {
"按量计费": "https://api.xiaomimimo.com/v1",
"token-plan": "https://token-plan-cn.xiaomimimo.com/v1",
"批量推理": "https://batch-api-cn.xiaomimimo.com/v1"
},
"deepseek": "https://api.deepseek.com",
"kimi": "https://api.moonshot.cn/v1",
"智谱GLM": "https://open.bigmodel.cn/api/paas/v4",
"通义千问": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"百度AIStudio": "https://aistudio.baidu.com/llm/lmapi/v3",
"MiniMax": "https://api.minimax.cn/v1",
"阶跃星辰": {
"标准": "https://api.stepfun.com/v1",
"step-plan": "https://api.stepfun.com/step_plan/v1"
},
"零一万物": "https://api.lingyiwanwu.com/v1",
"百川": "https://api.baichuan-ai.com/v1",
"美团": "https://api.longcat.chat/openai",
"抖音云(豆包/火山)": "https://ark.cn-beijing.volces.com/api/v3",
"讯飞": {
"讯飞星火": "https://spark-api-open.xf-yun.com/v1",
"讯飞星辰MaaS": {
"标准": "https://maas-api.cn-huabei-1.xf-yun.com/v2",
"Coding Plan": "https://maas-coding-api.cn-huabei-1.xf-yun.com/v2",
"Token Plan": "https://maas-token-api.cn-huabei-1.xf-yun.com/v2"
}
},
"腾讯云": {
"Token Plan": "https://api.lkeap.cloud.tencent.com/plan/v3",
"Coding Plan": "https://api.lkeap.cloud.tencent.com/coding/v3",
"TokenHub(广州)": "https://tokenhub.tencentmaas.com",
"TokenHub(新加坡)": "https://tokenhub-intl.tencentmaas.com"
},
"硅基流动": "https://api.siliconflow.cn/v1"
},
"非国产": {
"OpenAI": "https://api.openai.com/v1",
"Claude(Anthropic)": "https://api.anthropic.com/v1/",
"推特(xAI Grok)": "https://api.x.ai/v1",
"Gemini": "https://generativelanguage.googleapis.com/v1beta/openai/",
"英伟达": "https://integrate.api.nvidia.com/v1",
"agnes": {
"国际版": "https://apihub.agnes-ai.com/v1",
"中国版": "https://apihub.agnes-ai.cn/v1",
"说明": "中国版和国际版互通key,但账号、token-plan不互通"
},
"opencode": {
"zen": "https://opencode.ai/zen/v1",
"go": "https://opencode.ai/zen/go/v1",
"说明": "opencode zen可以通过9router或其它工具反代opencode cli终端免费使用。但不推荐,额度特别低。作者实测:反代出来跑了50万~100万左右就429限流。"
},
"OpenRouter": "https://openrouter.ai/api/v1"
}
}
+3
View File
@@ -0,0 +1,3 @@
# 此文件填写的模型将被正是运用请求
# 以#开头的表示注释,会被忽略
# models=name,这个=就是模型的简称,比如你可以配置deepseek=大肥鱼,后续用户@大肥鱼 xx,就会自动向deepseek的模型发起请求
+3
View File
@@ -211,3 +211,6 @@ commands:
json: json:
description: JSON消息构造器(tellraw/大标题/小标题/actionbar+颜色,需OP以上权限) description: JSON消息构造器(tellraw/大标题/小标题/actionbar+颜色,需OP以上权限)
usage: /json usage: /json
credits:
description: 信誉积分管理(上报/审批/记录/规则)
usage: /credits [上报|审批|记录|规则] [参数]