Skip to content

Commit

Permalink
Implement online player history. fix #8
Browse files Browse the repository at this point in the history
  • Loading branch information
Under-estimate committed Oct 16, 2022
1 parent 305fc1e commit 39fc764
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 83 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
}

group = "org.zrnq"
version = "1.1.8"
version = "1.1.9"

repositories {
maven("https://maven.aliyun.com/repository/public")
Expand Down
6 changes: 1 addition & 5 deletions src/main/kotlin/org/zrnq/mclient/GUIMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ import com.alibaba.fastjson.parser.ParserConfig
import org.zrnq.mclient.output.GUIOutputHandler
import java.awt.Font

lateinit var FONT : Font
lateinit var dnsServerList : List<String>

fun main() {
FONT = Font("Microsoft YaHei UI", Font.PLAIN, 20)
dnsServerList = listOf("223.5.5.5", "8.8.8.8", "114.114.114.114")
MClientOptions.FONT = Font("Microsoft YaHei UI", Font.PLAIN, 20)
ParserConfig.getGlobalInstance().isSafeMode = true
GUIOutputHandler()
}
51 changes: 19 additions & 32 deletions src/main/kotlin/org/zrnq/mclient/MClient.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package org.zrnq.mclient

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONArray
import com.alibaba.fastjson.JSONObject
import org.xbill.DNS.*
import org.zrnq.mclient.output.AbstractOutputHandler
import java.awt.*
import java.awt.Color
import java.awt.RenderingHints
import java.awt.image.BufferedImage
import java.net.Inet4Address
import java.net.InetSocketAddress
import java.net.Socket
import javax.swing.*
import javax.swing.SwingConstants

const val addressPrefix = "_minecraft._tcp."
private val dnsResolvers by lazy {
dnsServerList.map {
MClientOptions.dnsServerList.map {
SimpleResolver(Inet4Address.getByName(it))
.also { resolver -> resolver.timeout = java.time.Duration.ofSeconds(2) }
}
Expand All @@ -25,7 +23,7 @@ private fun Resolver.query(name : String) : List<Record> {
.getSection(Section.ANSWER)
}

fun pingInternal(target : String, outputHandler : AbstractOutputHandler, showTrueAddress : Boolean = true) {
fun pingInternal(target : String, outputHandler : AbstractOutputHandler) {
try {
outputHandler.beforePing()
val option = target.split(":")
Expand Down Expand Up @@ -53,7 +51,9 @@ fun pingInternal(target : String, outputHandler : AbstractOutputHandler, showTru
for(it in addressList) {
try {
outputHandler.onAttemptAddress("${it.first}:${it.second}")
outputHandler.onSuccess(if(showTrueAddress) renderInfoImage(it.first, it.second) else renderInfoImage(it.first, it.second, target))
val info = getInfo(it.first, it.second)
.let { if(!MClientOptions.showTrueAddress) it.setAddress(target) else it }
outputHandler.onSuccess(info)
outputHandler.afterPing()
return
} catch (ex : Exception) {
Expand All @@ -67,49 +67,36 @@ fun pingInternal(target : String, outputHandler : AbstractOutputHandler, showTru
}
}

fun renderInfoImage(address : String, port : Int, renderAddress : String = "$address:$port") : BufferedImage {
val info = getInfo(address, port)
fun renderBasicInfoImage(info : ServerInfo) : BufferedImage {
val border = 20
val width = 1000
val height = 200
val json = JSON.parseObject(info.first)
val result = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
val g = result.createGraphics()
g.font = FONT
g.font = MClientOptions.FONT
g.setRenderingHints(mapOf(
RenderingHints.KEY_INTERPOLATION to RenderingHints.VALUE_INTERPOLATION_BICUBIC,
RenderingHints.KEY_TEXT_ANTIALIASING to RenderingHints.VALUE_TEXT_ANTIALIAS_ON
))
if(json.containsKey("favicon"))
paintBase64Image(json.getString("favicon"), g, border, border, height - 2 * border, height - 2 * border)
if(info.favicon != null)
paintBase64Image(info.favicon, g, border, border, height - 2 * border, height - 2 * border)
else
paintString("NO IMAGE", g, border, (height - g.fontMetrics.height) / 2 , height - 2 * border, height - 2 * border) {
foreground = Color.MAGENTA
horizontalAlignment = SwingConstants.CENTER
}
g.drawRect(border, border, height - 2 * border, height - 2 * border)
paintDescription(json.getString("description"), g, height, border, width - border - height, height / 2 - border)
val playerJson = json.getJSONObject("players")
var playerDescription = "获取失败"
if(playerJson != null)
playerDescription = "${playerJson["online"]}/${playerJson["max"]} "
playerDescription +=
if(playerJson.containsKey("sample")) "玩家列表:${getPlayerList(playerJson.getJSONArray("sample")).limitLength(50)}"
else "玩家列表:没有信息"
paintDescription(info.description, g, height, border, width - border - height, height / 2 - border)

paintString("""
访问地址: $renderAddress Ping: ${info.second}
${json.getJSONObject("version").getString("name").limitLength(50)}
在线人数: $playerDescription""".trimIndent()
访问地址: ${info.serverAddress} Ping: ${info.latency}
${info.version.limitLength(50)}
${info.playerDescription}""".trimIndent()
, g, height, height / 2, width - border - height, height / 2 - border)
return result
}

fun getPlayerList(list : JSONArray) : String {
return if(list.isEmpty()) ""
else list.joinToString(", ") { (it as JSONObject).getString("name") }
}

fun getInfo(address : String, port : Int = 25565) : Pair<String, String> {
fun getInfo(address : String, port : Int = 25565) : ServerInfo {
val socket = Socket()
socket.soTimeout = 3000
socket.connect(InetSocketAddress(address, port))
Expand Down Expand Up @@ -140,5 +127,5 @@ fun getInfo(address : String, port : Int = 25565) : Pair<String, String> {
}

socket.close()
return result to latency
return ServerInfo(result, latency).setAddress("$address:$port")
}
42 changes: 42 additions & 0 deletions src/main/kotlin/org/zrnq/mclient/MClientOptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.zrnq.mclient

import org.zrnq.mcmotd.McMotd
import org.zrnq.mcmotd.PluginConfig
import java.awt.Font
import java.awt.GraphicsEnvironment

object MClientOptions {
lateinit var FONT : Font
var dnsServerList = listOf("223.5.5.5", "8.8.8.8")
var showTrueAddress = false
var recordInterval = 300

fun loadPluginConfig() {
dnsServerList = if(PluginConfig.dnsServerList.isEmpty()) {
McMotd.logger.warning("配置文件中没有填写DNS服务器地址,正在使用默认的DNS服务器")
listOf("223.5.5.5", "8.8.8.8")
} else PluginConfig.dnsServerList
showTrueAddress = PluginConfig.showTrueAddress
recordInterval = PluginConfig.recordInterval

val fontList = mutableListOf<Font>()
for(f in GraphicsEnvironment.getLocalGraphicsEnvironment().allFonts) {
if(f.name == PluginConfig.fontName) {
FONT = f.deriveFont(20f)
return
}
if(f.canDisplay('')) {
fontList.add(f)
}
}
McMotd.logger.warning("找不到指定的字体 : ${PluginConfig.fontName},您可以在mcmotd.yml中修改字体名称")
FONT = if(fontList.isEmpty()) {
McMotd.logger.error("找不到可用的字体, 请检查您的系统是否安装了中文字体")
Font(PluginConfig.fontName, Font.PLAIN, 20)
} else {
McMotd.logger.info("检测到可用的字体列表: ${fontList.joinToString(",") { it.name }}")
McMotd.logger.warning("正在使用第一个可用的字体: ${fontList[0].name}")
fontList[0].deriveFont(20f)
}
}
}
57 changes: 57 additions & 0 deletions src/main/kotlin/org/zrnq/mclient/ServerInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.zrnq.mclient

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONArray
import com.alibaba.fastjson.JSONObject

class ServerInfo(response : String, val latency : String) {
/**服务器图标*/
val favicon : String?
/**服务器描述*/
val description : String
/**服务器版本号*/
val version : String
/**服务器在线玩家描述*/
val playerDescription : String
/**在线人数*/
val onlinePlayerCount : Int?
/**服务器宣称的最大人数*/
val maxPlayerCount : Int?
/**服务器提供的部分在线玩家列表*/
val samplePlayerList : String?
/**服务器的显示地址*/
lateinit var serverAddress : String

init {
val json = JSON.parseObject(response)
favicon = json.getString("favicon")
description = json.getString("description")
version = json.getJSONObject("version").getString("name")
val playerJson = json.getJSONObject("players")

onlinePlayerCount = playerJson?.getIntValue("online")
maxPlayerCount = playerJson?.getIntValue("max")
samplePlayerList = playerJson?.getJSONArray("sample")?.toPlayerListString()

playerDescription = run {
if(onlinePlayerCount == null) return@run "服务器未提供在线玩家信息"
val playerCount = "在线人数: $onlinePlayerCount/$maxPlayerCount 玩家列表: "
if(samplePlayerList == null) return@run playerCount + "没有信息"
return@run playerCount + samplePlayerList.limitLength(50)
}
}

fun setAddress(address : String) : ServerInfo {
serverAddress = address
return this
}


companion object {
fun JSONArray?.toPlayerListString() : String {
return if(this == null) "没有信息"
else if(isEmpty()) ""
else joinToString(", ") { (it as JSONObject).getString("name") }
}
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/org/zrnq/mclient/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ import javax.imageio.ImageIO
import javax.swing.JFrame
import javax.swing.JLabel

fun Int.secondToReadableTime() : String {
return when {
this < 60 -> "${this}s"
this < 3600 -> String.format("%.2fmin", this.toFloat() / 60)
else -> String.format("%.2fh", this.toFloat() / 3600)
}
}

fun Exception.translateCommonException()
= when {
matches<java.net.ConnectException>("Connection timed out: connect") -> "连接服务器超时"
Expand Down
26 changes: 11 additions & 15 deletions src/main/kotlin/org/zrnq/mclient/output/OutputHandlers.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package org.zrnq.mclient.output

import net.mamoe.mirai.utils.MiraiLogger
import org.zrnq.mclient.FONT
import org.zrnq.mclient.centerOnScreen
import org.zrnq.mclient.pingInternal
import org.zrnq.mclient.translateCommonException
import org.zrnq.mclient.*
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Dimension
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.image.BufferedImage
import javax.swing.*
import javax.swing.border.EmptyBorder
import kotlin.concurrent.thread
Expand All @@ -35,7 +31,7 @@ abstract class AbstractOutputHandler {
/**
* Called if one server address returns a valid response, and before afterPing().
* */
abstract fun onSuccess(image : BufferedImage)
abstract fun onSuccess(info : ServerInfo)
/**
* Called after each ping request, and after onFailure() and onSuccess().
* */
Expand All @@ -47,12 +43,12 @@ abstract class AbstractOutputHandler {
class GUIOutputHandler : AbstractOutputHandler() {
private val errBuilder = StringBuilder()
private val mainFrame = JFrame("Ping MC Server")
private val progress = JProgressBar().apply { isIndeterminate = true; isVisible = false; isStringPainted = true; font = FONT }
private val resultLabel = JLabel().apply { font = FONT; foreground = Color.RED }
private val progress = JProgressBar().apply { isIndeterminate = true; isVisible = false; isStringPainted = true; font = MClientOptions.FONT }
private val resultLabel = JLabel().apply { font = MClientOptions.FONT; foreground = Color.RED }
private val textField = JTextField()
init {
textField.apply {
font = FONT
font = MClientOptions.FONT
preferredSize = Dimension(500, preferredSize.height)
addKeyListener(object : KeyAdapter() {
override fun keyPressed(e : KeyEvent) {
Expand All @@ -67,7 +63,7 @@ class GUIOutputHandler : AbstractOutputHandler() {
}
val inputPane = JPanel().apply {
layout = BorderLayout(20, 20)
add(JLabel("Ping Target:").apply { font = FONT }, BorderLayout.WEST)
add(JLabel("Ping Target:").apply { font = MClientOptions.FONT }, BorderLayout.WEST)
add(JPanel().apply {
layout = BorderLayout()
add(progress, BorderLayout.CENTER)
Expand Down Expand Up @@ -108,8 +104,8 @@ class GUIOutputHandler : AbstractOutputHandler() {
resultLabel.text = errBuilder.append("</span></html>").toString()
}

override fun onSuccess(image : BufferedImage) {
resultLabel.icon = ImageIcon(image)
override fun onSuccess(info : ServerInfo) {
resultLabel.icon = ImageIcon(renderBasicInfoImage(info))
}

override fun afterPing() {
Expand All @@ -122,7 +118,7 @@ class GUIOutputHandler : AbstractOutputHandler() {
class APIOutputHandler(
private val logger : MiraiLogger,
private val fail : (String) -> Unit,
private val success : (BufferedImage) -> Unit
private val success : (ServerInfo) -> Unit
) : AbstractOutputHandler() {
private val errBuilder = StringBuilder()
override fun beforePing() {
Expand All @@ -136,12 +132,12 @@ class APIOutputHandler(
val message = exception.translateCommonException()
if(message.contains(':'))
logger.warning("MC Ping Failed", exception)
errBuilder.append("$message\n")
errBuilder.append("$address:$message\n")
}

override fun onFailure() = fail(errBuilder.toString())

override fun onSuccess(image : BufferedImage) = success(image)
override fun onSuccess(info : ServerInfo) = success(info)

override fun afterPing() = Unit
}
Loading

0 comments on commit 39fc764

Please sign in to comment.