Initial commit

This commit is contained in:
2023-01-11 22:21:05 +08:00
parent fe77af8230
commit 560a5e25f0
12 changed files with 416 additions and 46 deletions

6
.gitignore vendored
View File

@ -39,4 +39,8 @@ bin/
.vscode/ .vscode/
### Mac OS ### ### Mac OS ###
.DS_Store .DS_Store
### libs ###
/lib/

View File

@ -1,31 +1,53 @@
# gc-openchat-plugin # gc-openchat-plugin
English | [中文](README_zh-CN.md) [中文](README.md) | English
Chat with players in the server Chat with players in the server
Talking to the server account in the game is equivalent to sending to the world channel, and all players in the server can receive the message. Talking to the server account in the game is equivalent to sending to the world channel, and all players in the server can receive the message.
## TODO List ## TODO List
- [ ] Chat between players - [x] Chat between players
- [x] Chat management commands
- [ ] Chat speech limit - [ ] Chat speech limit
- [ ] Chat Moderation - [ ] Chat Moderation
- [ ] Console talk - [ ] Console talk
- [ ] Chat management commands (`/chat ban|unban`)
- [ ] Chat api _(~~OneBot api~~)_ - [ ] Chat api _(~~OneBot api~~)_
- [ ] ... - [ ] ...
## Install ## Install
1. Download the `jar` in [Release](Release). 1. Download the `jar` in Release.
2. Put it in the `plugins` folder. 2. Put it in the `plugins` folder.
## Commands
Player command:
- `/chat on` Accept chat messages (default)
- `/chat off` block chat messages
Server command (requires `server.chat.others` permissions) :
- `/serverchat on` Enable server chat (default) (do not save)
- `/serverchat off` Disable server chat (without saving)
- `/serverchat ban|mute @uid [time(Minutes)]` Mute the specified player for the specified time (minutes) (optional)
- `/serverchat unban|unmute @uid` Unmute a specified player
`/serverchat` can be aliased as `/sc`
## Config ## Config
```json5 ```json5
{ {
serverChatEnabled: true, serverChatEnabled: true,
serverChatFormat: "<color=#99CC99>{nickName}({uid})</color>: {message}", serverChatFormat: "<color=#99CC99>{nickName}({uid})</color>: {message}",
messageFreLimitPerMinute: 20
// The limit on the number of speaking messages per minute
messageFreLimitPerMinute: 20,
// Whether to send a message when a player joins
sendJoinMessage: true,
// The content of the message sent when the player joins
// Can be used to prompt the player how to switch the chat function
joinMessage: "本服已启用聊天,/chat on 开启(默认),/chat off 屏蔽"
} }
``` ```

View File

@ -1,26 +1,40 @@
# gc-openchat-plugin # gc-openchat-plugin
[English](README.md) | 中文 中文 | [English](README-en-US.md)
让玩家在服务器内聊天 让玩家在服务器内聊天
在游戏内与服务器账号对话,相当于发送到世界频道,服务器内所有玩家均可收到消息。 在游戏内与服务器账号对话,相当于发送到世界频道,服务器内所有玩家均可收到消息。
## TODO List ## TODO List
- [ ] 玩家间聊天 - [x] 玩家间聊天
- [x] 聊天管理命令
- [ ] 发言间隔限制 - [ ] 发言间隔限制
- [ ] 聊天内容审查 - [ ] 聊天内容审查
- [ ] 控制台发言(运维用) - [ ] 控制台发言(运维用)
- [ ] 聊天管理命令(禁言、解除禁言 `/chat ban|unban`
- [ ] 聊天api _(~~OneBot api~~)_ - [ ] 聊天api _(~~OneBot api~~)_
- [ ] ... - [ ] ...
## Install ## 安装
1. 在 [Release](Release) 下载`jar` 1. 在 Release 下载`jar`
2. 放入 `plugins` 文件夹即可 2. 放入 `plugins` 文件夹即可
## Config ## 命令
玩家用:
- `/chat on` 接受聊天消息(默认)
- `/chat off` 屏蔽聊天消息
管理用(需要 `server.chat.others` 权限):
- `/serverchat on` 启用服务器聊天(默认)(不保存)
- `/serverchat off` 关闭服务器聊天(不保存)
- `/serverchat ban @uid [time(Minutes)]` 禁言指定玩家指定时间(分钟)(可选)
- `/serverchat unban @uid` 解除指定玩家禁言
`/serverchat` 可用别名 `/sc`
## 配置
```json5 ```json5
{ {
// 服务器聊天开关 // 服务器聊天开关
@ -33,7 +47,13 @@
serverChatFormat: "<color=#99CC99>{nickName}({uid})</color>: {message}", serverChatFormat: "<color=#99CC99>{nickName}({uid})</color>: {message}",
// 每分钟发言消息数限制 // 每分钟发言消息数限制
messageFreLimitPerMinute: 20 messageFreLimitPerMinute: 20,
// 是否在玩家加入时发送消息
sendJoinMessage: true,
// 玩家加入时发送消息
joinMessage: "本服已启用聊天,/chat on 开启(默认),/chat off 屏蔽"
} }
``` ```

View File

@ -3,17 +3,22 @@ plugins {
} }
group 'com.github.jie65535.openchat' group 'com.github.jie65535.openchat'
version '1.0-SNAPSHOT' version 'dev-0.1.0'
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' implementation fileTree(dir: 'lib', include: ['*.jar'])
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
} }
test { test {
useJUnitPlatform() useJUnitPlatform()
}
jar {
jar.baseName = 'openchat'
destinationDir = file(".")
} }

View File

@ -1,2 +1,13 @@
package com.github.jie65535.openchat;public class EventListeners { package com.github.jie65535.openchat;
import emu.grasscutter.server.event.player.PlayerJoinEvent;
public final class EventListeners {
private static final OpenChatConfig config = OpenChatPlugin.getInstance().getConfig();
public static void onJoin(PlayerJoinEvent event) {
if (!config.sendJoinMessage || config.joinMessage.isEmpty()) return;
event.getPlayer().dropMessage(config.joinMessage);
}
} }

View File

@ -1,2 +1,49 @@
package com.github.jie65535.openchat;public class OpenChatConfig { /*
* gc-openchat
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.openchat;
public class OpenChatConfig {
/**
* 服务器聊天开关
*/
public boolean serverChatEnabled = true;
/**
* 服务器聊天消息格式
* {nickName} 为玩家昵称
* {uid} 为玩家UID
* {message} 为消息内容
*/
public String serverChatFormat = "<color=#99CC99>{nickName}({uid})</color>: {message}";
/**
* 每分钟发言消息数限制
*/
public int messageFreLimitPerMinute = 20;
/**
* 是否发送玩家加入消息
*/
public boolean sendJoinMessage = true;
/**
* 玩家加入消息
*/
public String joinMessage = "本服已启用聊天,/chat on 开启(默认),/chat off 屏蔽";
} }

View File

@ -17,23 +17,25 @@
*/ */
package com.github.jie65535.openchat; package com.github.jie65535.openchat;
public class OpenChatConfig { import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.Date;
public class OpenChatData {
/** /**
* 服务器聊天开关 * 禁言列表
* Key: Uid
* Value: End time
*/ */
public boolean serverChatEnabled = true; public Int2ObjectMap<Date> banList = new Int2ObjectOpenHashMap<>();
/** /**
* 服务器聊天消息格式 * 关闭聊天的玩家集合
* {nickName} 为玩家昵称 * Key: Uid
* {uid} 为玩家UID
* {message} 为消息内容
*/ */
public String serverChatFormat = "<color=#99CC99>{nickName}({uid})</color>: {message}"; public IntSet offChatPlayers = new IntOpenHashSet();
/**
* 每分钟发言消息数限制
*/
public int messageFreLimitPerMinute = 20;
} }

View File

@ -1,2 +1,136 @@
package com.github.jie65535.openchat;public class OpenChatPlugin { /*
* gc-openchat
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.openchat;
import com.github.jie65535.openchat.commands.ChatPlayerCommands;
import com.github.jie65535.openchat.commands.ChatServerCommands;
import emu.grasscutter.plugin.Plugin;
import emu.grasscutter.server.event.EventHandler;
import emu.grasscutter.server.event.HandlerPriority;
import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.utils.JsonUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Date;
public final class OpenChatPlugin extends Plugin {
private static OpenChatPlugin instance;
public static OpenChatPlugin getInstance() {
return instance;
}
private OpenChatConfig config;
public OpenChatConfig getConfig() {
return config;
}
private void loadConfig() {
var configFile = new File(getDataFolder(), "config.json");
if (!configFile.exists()) {
config = new OpenChatConfig();
try (var file = new FileWriter(configFile)) {
file.write(JsonUtils.encode(config));
} catch (IOException e) {
getLogger().error("[OpenChat] Unable to write to config file.");
} catch (Exception e) {
getLogger().error("[OpenChat] Unable to save config file.");
}
} else {
try {
config = JsonUtils.decode(Files.readString(configFile.toPath(), StandardCharsets.UTF_8), OpenChatConfig.class);
} catch (Exception exception) {
config = new OpenChatConfig();
getLogger().error("[OpenChat] There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json.");
}
}
}
private OpenChatData data;
public OpenChatData getData() {
return data;
}
private void loadData() {
data = new OpenChatData();
var dataFile = new File(getDataFolder(), "data.json");
if (!dataFile.exists()) {
data = new OpenChatData();
saveData();
} else {
try {
data = JsonUtils.decode(Files.readString(dataFile.toPath(), StandardCharsets.UTF_8), OpenChatData.class);
} catch (Exception exception) {
data = new OpenChatData();
getLogger().error("[OpenChat] There was an error while trying to load the data from data.json. Please make sure that there are no syntax errors. If you want to start with a default data, delete your existing data.json.");
}
}
}
public void saveData() {
try (var file = new FileWriter(new File(getDataFolder(), "data.json"))) {
file.write(JsonUtils.encode(data));
} catch (IOException e) {
getLogger().error("[OpenChat] Unable to write to data file.");
} catch (Exception e) {
getLogger().error("[OpenChat] Unable to save data file.");
}
}
@Override
public void onLoad() {
instance = this;
loadConfig();
loadData();
getLogger().info("[OpenChat] Loaded.");
}
@Override
public void onEnable() {
// Register event listeners.
new EventHandler<>(PlayerJoinEvent.class)
.priority(HandlerPriority.LOW)
.listener(EventListeners::onJoin)
.register(this);
// Register commands.
getHandle().registerCommand(new ChatServerCommands());
getHandle().registerCommand(new ChatPlayerCommands());
getServer().setChatSystem(new OpenChatSystem(getServer(), this));
// Log a plugin status message.
getLogger().info("[OpenChat] Enabled.");
}
@Override
public void onDisable() {
saveData();
getLogger().info("[OpenChat] Disabled.");
}
/**
* 更新禁言列表清理所有解除禁言用户
*/
public void updateBanList() {
if (getData().banList.isEmpty())
return;
var now = new Date();
getData().banList.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now));
}
} }

View File

@ -1,2 +1,83 @@
package com.github.jie65535.openchat;public class OpenChatSystem { package com.github.jie65535.openchat;
import emu.grasscutter.GameConstants;
import emu.grasscutter.game.chat.ChatSystem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
public class OpenChatSystem extends ChatSystem {
private final OpenChatPlugin plugin;
public OpenChatSystem(GameServer server, OpenChatPlugin plugin) {
super(server);
this.plugin = plugin;
}
@Override
public void sendPrivateMessage(Player player, int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
super.sendPrivateMessage(player, targetUid, message);
if (targetUid != GameConstants.SERVER_CONSOLE_UID || message.charAt(0) == '/' || message.charAt(0) == '!') {
return;
}
handlePlayerMessage(player, message);
}
/**
* 处理玩家消息
* @param player 玩家对象
* @param message 消息内容
*/
private void handlePlayerMessage(Player player, String message) {
if (!plugin.getConfig().serverChatEnabled) {
return;
}
// 刷新列表
plugin.updateBanList();
// 检测是否正在禁言中
if (plugin.getData().banList.containsKey(player.getUid())) {
return;
}
// 处理发言频率限制与发言内容审查
if (!checkMessageFre(player) || !checkMessageModeration(message)) {
// 可提示也可忽略忽略可让玩家以为自己发送成功其实别人看不到
return;
}
// 格式化消息
message = OpenChatPlugin.getInstance().getConfig().serverChatFormat
.replace("{nickName}", player.getNickname())
.replace("{uid}", String.valueOf(player.getUid()))
.replace("{message}", message);
// 转发给其它玩家
for (Player p : getServer().getPlayers().values()) {
// 将消息发送给除了自己以外所有未关闭聊天的玩家
if (p != player && plugin.getData().offChatPlayers.contains(p.getUid())) {
p.dropMessage(message);
}
}
}
/**
* 消息内容审查
* @param message 消息
* @return 是否合法合规
*/
private boolean checkMessageModeration(String message) {
// TODO
return true;
}
/**
* 消息频率检查
* @param player 玩家对象
* @return 是否在约定阈值内
*/
private boolean checkMessageFre(Player player) {
// TODO
return true;
}
} }

View File

@ -1,5 +1,6 @@
package com.github.jie65535.openchat; package com.github.jie65535.openchat.commands;
import com.github.jie65535.openchat.OpenChatPlugin;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
@ -19,9 +20,10 @@ public class ChatPlayerCommands implements CommandHandler {
var set = OpenChatPlugin.getInstance().getData().offChatPlayers; var set = OpenChatPlugin.getInstance().getData().offChatPlayers;
if (subCommand.equals("on")) { if (subCommand.equals("on")) {
set.remove(targetPlayer.getUid()); set.remove(targetPlayer.getUid());
CommandHandler.sendMessage(sender); CommandHandler.sendMessage(sender, "OK");
} else if (subCommand.equals("off")) { } else if (subCommand.equals("off")) {
set.add(targetPlayer.getUid()); set.add(targetPlayer.getUid());
CommandHandler.sendMessage(sender, "OK");
} }
} }
} }

View File

@ -1,12 +1,54 @@
package com.github.jie65535.openchat; package com.github.jie65535.openchat.commands;
import com.github.jie65535.openchat.OpenChatPlugin;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
@Command(label = "chat", import java.sql.Date;
aliases = { "openchat" }, import java.util.List;
usage = { "on/off", "unban <uid>", "ban <uid> <time(Minutes)>"},
permission = "player.chat", @Command(label = "serverchat",
permissionTargeted = "server.chat") aliases = { "sc" },
usage = { "on/off", "unban|unmute @uid", "ban|mute @uid [time(Minutes)]"},
permission = "server.chat",
permissionTargeted = "server.chat.others")
public class ChatServerCommands implements CommandHandler { public class ChatServerCommands implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
sendUsageMessage(sender);
return;
}
var plugin = OpenChatPlugin.getInstance();
var subCommand = args.get(0);
switch (subCommand) {
case "on" -> {
plugin.getConfig().serverChatEnabled = true;
CommandHandler.sendMessage(sender, "OK");
}
case "off" -> {
plugin.getConfig().serverChatEnabled = false;
CommandHandler.sendMessage(sender, "OK");
}
case "unban", "unmute" -> {
plugin.getData().banList.remove(targetPlayer.getUid());
CommandHandler.sendMessage(sender, "OK");
}
case "ban", "mute" -> {
var time = new Date(2051190000);
if (args.size() == 2) {
try {
time = new Date(System.currentTimeMillis() / 1000 + Integer.parseInt(args.get(0)) * 60L);
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.invalid_time");
return;
}
}
plugin.getData().banList.put(targetPlayer.getUid(), time);
CommandHandler.sendMessage(sender, "OK");
}
}
}
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "opencommand-plugin", "name": "openchat-plugin",
"description": "Open command interface for third-party clients", "description": "Chat with players in the server console",
"version": "dev-1.5.0", "version": "0.1.0",
"mainClass": "com.github.jie65535.opencommand.OpenCommandPlugin", "mainClass": "com.github.jie65535.openchat.OpenChatPlugin",
"authors": ["jie65535", "方块君"] "authors": ["jie65535"]
} }