ms/packages/plugins/src/MiaoChat.ts

354 lines
12 KiB
TypeScript

/// <reference types="@javatypes/bungee-api" />
/// <reference types="@javatypes/spigot-api" />
/// <reference types="@javatypes/sponge-api" />
import { server, channel, constants, chat } from '@ccms/api'
import { optional, Autowired } from '@ccms/container'
import { plugin, interfaces, cmd, listener, tab, config, enable } from '@ccms/plugin'
import Tellraw from '@ccms/common/dist/tellraw'
const ByteArrayInputStream = Java.type("java.io.ByteArrayInputStream")
const ByteArrayOutputStream = Java.type("java.io.ByteArrayOutputStream")
const StandardCharsets = Java.type("java.nio.charset.StandardCharsets")
const GZIPInputStream = Java.type("java.util.zip.GZIPInputStream")
const GZIPOutputStream = Java.type("java.util.zip.GZIPOutputStream")
const BiConsumer = Java.type('java.util.function.BiConsumer')
const ByteArray = Java.type("byte[]")
class MiaoMessage {
// public static final String CHANNEL = "MiaoChat:Default".toLowerCase();
public static CHANNEL: string = "MiaoChat:Default".toLowerCase()
// private static final int MAX_MESSAGE_LENGTH = 32000;
private static MAX_MESSAGE_LENGTH = 32000;
private static copy(input, output) {
let buffer = new ByteArray(1024)
let n: number
while ((n = input.read(buffer)) != -1) {
output.write(buffer, 0, n)
}
input.close()
output.close()
}
public static encode(input: any): any {
return new MiaoMessage(input).encode()
}
public static decode(input: any): MiaoMessage {
let baos = new ByteArrayOutputStream()
MiaoMessage.copy(new GZIPInputStream(new ByteArrayInputStream(input)), baos)
return new MiaoMessage(baos.toString(StandardCharsets.UTF_8.name()))
}
// private String json;
constructor(public json: any) { }
public encode(): any {
let baos = new ByteArrayOutputStream()
MiaoMessage.copy(new ByteArrayInputStream(this.json.getBytes(StandardCharsets.UTF_8)), new GZIPOutputStream(baos))
if (baos.size() > MiaoMessage.MAX_MESSAGE_LENGTH) { return null }
return baos.toByteArray()
}
}
@plugin({ version: '1.1.0', author: 'MiaoWoo', nativeDepends: ['PlaceholderAPI'], source: __filename })
export class MiaoChat extends interfaces.Plugin {
@Autowired()
private Server: server.Server
@optional()
@Autowired()
private chat: chat.Chat
@Autowired()
private Channel: channel.Channel
private channelOff: { off: () => void }
@config()
private config = {
Version: "1.0.0",
BungeeCord: true,
Server: "生存服",
ChatFormats: {
default: {
index: 50,
permission: "MiaoChat.default",
range: 0,
format: "[world][player]: ",
item: true,
itemformat: "&6[&b%s&6]&r"
},
admin: {
index: 49,
permission: "MiaoChat.admin",
format: "[admin][world][player][help]: ",
range: 0,
item: true,
itemformat: "&6[&b%s&6]&r"
}
},
StyleFormats: {
world: {
text: "&6[&a%player_world%&6]",
hover: [
"&6当前所在位置:",
"&6世界: &d%player_world%",
"&6坐标: &aX:%player_x% Y: %player_y% Z: %player_z%",
"",
"&c点击即可TP我!"
],
click: {
type: "COMMAND",
command: "/tpa %player_name%"
}
},
player: {
text: "&b%player_name%",
hover: [
"&6玩家名称: &b%player_name%",
"&6玩家等级: &a%player_level%",
"&6玩家血量: &c%player_health%",
"&6玩家饥饿: &d%player_food_level%",
"&6游戏模式: &4%player_gamemode%",
"",
"&c点击与我聊天"
],
click: {
type: "SUGGEST",
command: "/tell %player_name%"
}
},
admin: {
text: "&6[&c管理员&6]"
},
help: {
text: "&4[求助]",
hover: [
"点击求助OP"
],
click: {
type: "COMMAND",
command: "管理员@%player_name% 我需要你的帮助!"
}
}
}
}
private chatFormats: any[]
private styleFormats: any
// 用于匹配 '[xx]' 聊天格式
private FORMAT_PATTERN = /[\[]([^\[\]]+)[\]]/ig;
private PlaceholderAPI: { setPlaceholders: (player: any, str: string) => string }
load() {
this.chatFormats = Object.values(this.config.ChatFormats)
this.chatFormats.sort(this.compare('index'))
this.initFormat(this.chatFormats)
this.styleFormats = this.config.StyleFormats
}
private compare(prop: string) {
return function (obj1: { [x: string]: any }, obj2: { [x: string]: any }) {
var val1 = obj1[prop]
var val2 = obj2[prop]
if (!isNaN(Number(val1)) && !isNaN(Number(val2))) {
val1 = Number(val1)
val2 = Number(val2)
}
if (val1 < val2) {
return -1
} else if (val1 > val2) {
return 1
} else {
return 0
}
}
}
enable() {
this.PlaceholderAPI = {
setPlaceholders: (player: any, string: string) => {
return string
}
}
}
disable() {
this.channelOff?.off()
}
bukkitenable() {
if (!this.chat) {
this.logger.console('§4消息管理器注入失败 请检查当前服务器是否兼容...')
this.AsyncPlayerChatEvent['off']()
}
// 尝试加载 Bukkit 的 PlaceholderAPI
try {
//@ts-ignore
this.PlaceholderAPI = base.getClass("me.clip.placeholderapi.PlaceholderAPI").static
this.logger.log('[PAPI] Found Bukkit PlaceholderAPI Hooking...')
} catch (ex) {
this.logger.console("§cCan't found me.clip.placeholderapi.PlaceholderAPI variable will not be replaced! Err: " + ex)
}
}
spongeenable() {
if (!this.chat) {
this.logger.console('§4消息管理器注入失败 请检查当前服务器是否兼容...')
this.MessageChannelEvent$Chat['off']()
}
// 尝试加载 Sponge 的 PlaceholderAPI
try {
var spongePapi = this.Server.getService('me.rojo8399.placeholderapi.PlaceholderService')
var s = org.spongepowered.api.text.serializer.TextSerializers.formattingCode('§')
if (spongePapi) {
this.PlaceholderAPI = {
setPlaceholders: (player: any, string: string) => {
return s.serialize(spongePapi.replacePlaceholders(string, player, player))
}
}
this.logger.log('[PAPI] Found Sponge PlaceholderAPI Hooking...')
}
} catch (ex) {
this.logger.console("§cCan't found me.rojo8399.placeholderapi.PlaceholderService variable will not be replaced! Err: " + ex)
}
}
@enable({ servers: [constants.ServerType.Bukkit, constants.ServerType.Sponge] })
serverEnbale() {
this.channelOff = this.Channel?.listen(this, MiaoMessage.CHANNEL, (data) => {
this.sendChatAll(MiaoMessage.decode(data).json)
})
}
bungeeenable() {
this.channelOff = this.Channel?.listen(this, MiaoMessage.CHANNEL, (data, event: net.md_5.bungee.api.event.PluginMessageEvent) => {
let bungee: net.md_5.bungee.api.ProxyServer = base.getInstance().getProxy()
if (event.getTag() == MiaoMessage.CHANNEL) {
let origin = event.getSender().getAddress()
bungee.getServers().forEach(new BiConsumer({
accept: (s, server) => {
if (server.getAddress() != origin && server.getPlayers().size() > 0) {
server.sendData(event.getTag(), event.getData())
}
}
}))
}
})
}
@cmd({ servers: [constants.ServerType.Bungee] })
mct(sender: any, command: string, args: string[]) {
this.logger.log(sender, command, args)
sender.sendMessage(JSON.stringify({ command, ...args }))
}
@cmd({ servers: [`!${constants.ServerType.Bungee}`] })
mchat(sender: any, command: string, args: string[]) {
this.logger.log(sender, command, args)
sender.sendMessage(JSON.stringify({ command, ...args }))
}
@tab()
tabmchat(_sender: any, _command: string, _args: string[]) {
}
@listener({ servers: [constants.ServerType.Bukkit] })
AsyncPlayerChatEvent(event: org.bukkit.event.player.AsyncPlayerChatEvent) {
this.sendChat(event.getPlayer(), event.getMessage(), () => event.setCancelled(true))
}
@listener({ servers: [constants.ServerType.Sponge] })
MessageChannelEvent$Chat(event: org.spongepowered.api.event.message.MessageChannelEvent.Chat) {
//@ts-ignore
var player = event.getCause().first(org.spongepowered.api.entity.living.player.Player.class).orElse(null)
if (player == null) {
return
}
var plain = event.getRawMessage().toPlain()
if (plain.startsWith(Tellraw.duplicateChar)) {
return
}
this.sendChat(player, plain, () => event.setMessageCancelled(true))
}
initFormat(chatFormats: any[]) {
chatFormats.forEach(chatFormat => {
var chat_format_str = chatFormat.format
var temp = []
var r: any[]
while (r = this.FORMAT_PATTERN.exec(chat_format_str)) {
temp.push(r[1])
}
var format_list = []
temp.forEach(t => {
var arr = chat_format_str.split('[' + t + ']', 2)
if (arr[0]) {
format_list.push(arr[0])
}
format_list.push(t)
chat_format_str = arr[1]
})
if (chat_format_str) {
format_list.push(chat_format_str)
}
chatFormat.format_list = format_list
})
}
sendChat(player: any, plain: string, callback: { (): void }) {
var chat_format = this.getChatFormat(player)
if (!chat_format) {
console.debug('未获得用户', player.getName(), '的 ChatRule 跳过执行...')
return
}
callback()
var tr = Tellraw.create()
chat_format.format_list.forEach((format) => {
var style = this.styleFormats[format]
if (style) {
tr.then(this.replace(player, style.text.replace(/&(\w)/g, '§$1')))
if (style.hover) {
tr.tip(this.replace(player, style.hover.join('\n')))
}
if (style.click && style.click.type && style.click.command) {
var command = this.replace(player, style.click.command)
switch (style.click.type.toUpperCase()) {
case "COMMAND":
tr.command(command)
break
case "OPENURL":
tr.link(command)
break
case "SUGGEST":
tr.suggest(command)
break
default:
}
}
} else {
tr.then(this.replace(player, format))
}
})
let json = tr.then(this.replace(player, plain)).json()
this.sendChatAll(json)
this.Channel?.send(player, MiaoMessage.CHANNEL, MiaoMessage.encode(json))
}
sendChatAll(json: string) {
this.Server.getOnlinePlayers().forEach(player => this.chat.sendJson(player, json))
}
getChatFormat(player: any) {
for (let format of this.chatFormats) {
if (player.hasPermission(format.permission)) {
return format
}
}
return null
}
replace(player: any, target: string) {
return this.PlaceholderAPI.setPlaceholders(player, target)
}
}