Skip to content

Commit

Permalink
improved chat and fixed a few issues
Browse files Browse the repository at this point in the history
  • Loading branch information
1zun4 committed Aug 3, 2021
1 parent 523a015 commit 014ba86
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 199 deletions.
3 changes: 1 addition & 2 deletions src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ object LiquidBounce : Listenable {
ConfigSystem.load()

// Connect to chat server
Chat.connect()

Chat.connectAsync()
}.onSuccess {
logger.info("Successfully loaded client!")
}.onFailure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ object EventManager {
ClientShutdownEvent::class,
ToggleModuleEvent::class,
NotificationEvent::class,
ClientChatMessageEvent::class,
ClientChatErrorEvent::class
).map { Pair(it.findAnnotation<Nameable>()!!.name, it) }

init {
Expand Down
12 changes: 12 additions & 0 deletions src/main/kotlin/net/ccbluex/liquidbounce/event/Events.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package net.ccbluex.liquidbounce.event

import net.ccbluex.liquidbounce.config.Value
import net.ccbluex.liquidbounce.features.chat.client.packet.User
import net.ccbluex.liquidbounce.features.module.Module
import net.ccbluex.liquidbounce.utils.client.Nameable
import net.minecraft.block.Block
Expand Down Expand Up @@ -195,3 +196,14 @@ class NotificationEvent(val title: String, val message: String, val severity: Se
ERROR
}
}

@Nameable("clientChatMessage")
class ClientChatMessageEvent(val user: User, val message: String, val chatGroup: ChatGroup) : Event() {
enum class ChatGroup {
PUBLIC_CHAT,
PRIVATE_CHAT
}
}

@Nameable("clientChatError")
class ClientChatErrorEvent(val error: String) : Event()
248 changes: 218 additions & 30 deletions src/main/kotlin/net/ccbluex/liquidbounce/features/chat/Chat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,31 @@ package net.ccbluex.liquidbounce.features.chat

import net.ccbluex.liquidbounce.config.ConfigSystem
import net.ccbluex.liquidbounce.config.ToggleableConfigurable
import net.ccbluex.liquidbounce.event.*
import net.ccbluex.liquidbounce.features.chat.client.Client
import net.ccbluex.liquidbounce.features.chat.client.ClientListener
import net.ccbluex.liquidbounce.features.chat.client.packet.User
import net.ccbluex.liquidbounce.features.chat.client.packet.*

import net.ccbluex.liquidbounce.features.command.CommandManager
import net.ccbluex.liquidbounce.features.command.builder.CommandBuilder
import net.ccbluex.liquidbounce.features.command.builder.ParameterBuilder
import net.ccbluex.liquidbounce.utils.client.*
import net.minecraft.text.TranslatableText
import net.minecraft.util.Formatting
import net.minecraft.util.Util
import java.util.*

object Chat : ToggleableConfigurable(null, "chat", true) {

object Chat : ToggleableConfigurable(null, "chat", true), ClientListener {
// Chat options

private var jwtLogin by boolean("JWT", false)
private var jwtToken by text("JWTToken", "")

val client = Client(this)
// Chat client

internal val client = Client()

var loggedIn = false
val retryChronometer = Chronometer()

private fun createCommand() = CommandBuilder
.begin("chat")
Expand All @@ -30,7 +39,7 @@ object Chat : ToggleableConfigurable(null, "chat", true), ClientListener {
.build()
)
.handler { _, args ->
client.sendMessage((args[0] as Array<*>).joinToString(" ") { it as String })
sendMessage((args[0] as Array<*>).joinToString(" ") { it as String })
}
.build()

Expand All @@ -39,42 +48,111 @@ object Chat : ToggleableConfigurable(null, "chat", true), ClientListener {
CommandManager.addCommand(createCommand())
}

fun connect() {
private val gameTick = handler<GameTickEvent> {
if (retryChronometer.hasElapsed()) {
return@handler
}

if (!client.connected) {
connectAsync()
retryChronometer.waitFor(1000 * 60) // wait for 60 seconds to retry.
}
}

fun connectAsync() {
if (!enabled) {
return
}

client.connect()
client.loginMojang()
// Async connecting using IO worker from Minecraft
Util.getIoWorkerExecutor().execute {
client.connect()
}
}

override fun onConnect() {
chat("§7[§a§lChat§7]".asText(), TranslatableText("liquidbounce.liquidchat.states.connecting").styled { it.withColor(Formatting.BLUE) })
/**
* Disconnect from chat server
*/
fun disconnect() {
client.channel?.close()
client.channel = null
}

override fun onConnected() {
chat("§7[§a§lChat§7]".asText(), TranslatableText("liquidbounce.liquidchat.states.connected").styled { it.withColor(Formatting.BLUE) })
fun reconnect(async: Boolean = false) {
disconnect()
connectAsync()
}

override fun onDisconnect() {
chat("§7[§a§lChat§7]".asText(), TranslatableText("liquidbounce.liquidchat.states.disconnected").styled { it.withColor(Formatting.RED) })
internal fun onConnect() {
logger.info("Connecting to LiquidChat...")
notification("LiquidChat", TranslatableText("liquidbounce.liquidchat.states.connecting"), NotificationEvent.Severity.INFO)
}

override fun onLogon() {
chat("§7[§a§lChat§7]".asText(), TranslatableText("liquidbounce.liquidchat.states.loggingIn").styled { it.withColor(Formatting.BLUE) })
internal fun onConnected() {
logger.info("Successfully connected to LiquidChat!")

notification("LiquidChat", TranslatableText("liquidbounce.liquidchat.states.connected"), NotificationEvent.Severity.INFO)

if (jwtLogin) {
logger.info("Logging in via JWT...")
loginViaJWT(jwtToken)
} else {
logger.info("Requesting to login into Mojang...")
requestMojangLogin()
}
}

override fun onLoggedIn() {
chat("§7[§a§lChat§7]".asText(), TranslatableText("liquidbounce.liquidchat.states.loggedIn").styled { it.withColor(Formatting.BLUE) })
internal fun onDisconnect() {
client.channel = null
notification("LiquidChat", TranslatableText("liquidbounce.liquidchat.states.disconnected"), NotificationEvent.Severity.INFO)
}

chat("====================================")
chat("§c>> §l")
chat(regular(TranslatableText("liquidbounce.liquidchat.writeMessage", ".chat <message>".asText().styled { it.withColor(Formatting.GREEN) })))
chat(regular(TranslatableText("liquidbounce.liquidchat.writePrivateMessage", ".pchat <user> <message>".asText().styled { it.withColor(Formatting.GREEN) })))
chat("====================================")
internal fun onLogon() {
notification("LiquidChat", TranslatableText("liquidbounce.liquidchat.states.loggingIn"), NotificationEvent.Severity.INFO)
}

override fun onMessage(user: User, message: String) {
internal fun onLoggedIn() {
notification("LiquidChat", TranslatableText("liquidbounce.liquidchat.states.loggedIn"), NotificationEvent.Severity.SUCCESS)
}

internal fun onClientError(packet: ClientErrorPacket) {
// add translation support
val message = when (packet.message) {
"NotSupported" -> "This method is not supported!"
"LoginFailed" -> "Login Failed!"
"NotLoggedIn" -> "You must be logged in to use the chat! Enable LiquidChat."
"AlreadyLoggedIn" -> "You are already logged in!"
"MojangRequestMissing" -> "Mojang request missing!"
"NotPermitted" -> "You are missing the required permissions!"
"NotBanned" -> "You are not banned!"
"Banned" -> "You are banned!"
"RateLimited" -> "You have been rate limited. Please try again later."
"PrivateMessageNotAccepted" -> "Private message not accepted!"
"EmptyMessage" -> "You are trying to send an empty message!"
"MessageTooLong" -> "Message is too long!"
"InvalidCharacter" -> "Message contains a non-ASCII character!"
"InvalidId" -> "The given ID is invalid!"
"Internal" -> "An internal server error occurred!"
else -> packet.message
}

EventManager.callEvent(ClientChatErrorEvent(message))

// todo: remove when ultralight chat has been implemented
val player = mc.player

if (player == null) {
logger.info("[Chat] $message")
return
}

player.sendMessage("§7[§a§lChat§7] §9$message".asText(), false)
}

internal fun onMessage(user: User, message: String) {
EventManager.callEvent(ClientChatMessageEvent(user, message, ClientChatMessageEvent.ChatGroup.PUBLIC_CHAT))

// todo: remove when ultralight chat has been implemented
val player = mc.player

if (player == null) {
Expand All @@ -85,19 +163,129 @@ object Chat : ToggleableConfigurable(null, "chat", true), ClientListener {
player.sendMessage("§7[§a§lChat§7] §9${user.name}: §r$message".asText(), false)
}

override fun onPrivateMessage(user: User, message: String) {
internal fun onPrivateMessage(user: User, message: String) {
EventManager.callEvent(ClientChatMessageEvent(user, message, ClientChatMessageEvent.ChatGroup.PRIVATE_CHAT))

// todo: remove when ultralight chat has been implemented
val player = mc.player

if (player == null) {
logger.info("[Chat] (P) ${user.name}: $message")
logger.info("[Chat] ${user.name}: $message")
return
}

player.sendMessage("§7[§a§lChat§7] §c(P) §§9${user.name}: $message".asText(), false)
player.sendMessage("§7[§a§lChat§7] §9${user.name}: §r$message".asText(), false)
}

internal fun onError(cause: Throwable) {
notification("LiquidChat", TranslatableText("liquidbounce.generic.notifyDeveloper"), NotificationEvent.Severity.ERROR)
logger.error("LiquidChat error", cause)
}

internal fun onReceivedJwtToken(jwt: String) {
notification("LiquidChat", TranslatableText("liquidbounce.liquidchat.jwtTokenReceived"), NotificationEvent.Severity.SUCCESS)

// Set jwt token
jwtLogin = true
jwtToken = jwt

// Reconnect to chat server
reconnect(async = true)
}

override fun onError(cause: Throwable) {
chat("§7[§a§lChat§7] §c§l${TranslatableText("liquidbounce.generic.error").asString()}: §7${cause.javaClass.name}: ${cause.message}")


/**
* Request Mojang authentication details for login
*/
internal fun requestMojangLogin() = client.sendPacket(ServerRequestMojangInfoPacket())

/**
* Login to web socket via JWT
*/
internal fun loginViaJWT(token: String) {
onLogon()
client.sendPacket(ServerLoginJWTPacket(token, allowMessages = true))
}

/**
* Handle incoming packets from chat client
*/
internal fun onPacket(packet: Packet) {
when (packet) {
is ClientMojangInfoPacket -> {
onLogon()

try {
val sessionHash = packet.sessionHash

mc.sessionService.joinServer(mc.session.profile, mc.session.accessToken, sessionHash)
client.sendPacket(ServerLoginMojangPacket(mc.session.username, mc.session.profile.id, allowMessages = true))
} catch (throwable: Throwable) {
onError(throwable)
}
return
}
is ClientMessagePacket -> onMessage(packet.user, packet.content)
is ClientPrivateMessagePacket -> onPrivateMessage(packet.user, packet.content)
is ClientErrorPacket -> onClientError(packet)
is ClientSuccessPacket -> {
when (packet.reason) {
"Login" -> {
onLoggedIn()
loggedIn = true
}
"Ban" -> chat("§7[§a§lChat§7] §9Successfully banned user!")
"Unban" -> chat("§7[§a§lChat§7] §9Successfully unbanned user!")
}
}
is ClientNewJWTPacket -> onReceivedJwtToken(packet.token)
}
}



/**
* Send chat message to server
*/
fun sendMessage(message: String) = client.sendPacket(ServerMessagePacket(message))

/**
* Send private chat message to server
*/
fun sendPrivateMessage(username: String, message: String) = client.sendPacket(ServerPrivateMessagePacket(username, message))

/**
* Ban user from server
*/
fun banUser(target: String) = client.sendPacket(ServerBanUserPacket(toUUID(target)))

/**
* Unban user from server
*/
fun unbanUser(target: String) = client.sendPacket(ServerUnbanUserPacket(toUUID(target)))

/**
* Convert username or uuid to UUID
*/
private fun toUUID(target: String): String {
return try {
UUID.fromString(target)

target
} catch (_: IllegalArgumentException) {
val incomingUUID = MojangApi.getUUID(target)

if (incomingUUID.isBlank()) return ""

val uuid = StringBuffer(incomingUUID)
.insert(20, '-')
.insert(16, '-')
.insert(12, '-')
.insert(8, '-')

uuid.toString()
}
}

}
Loading

0 comments on commit 014ba86

Please sign in to comment.