From 26d25b77f59b1c5c3fa3acabc055c5262f50900b Mon Sep 17 00:00:00 2001
From: Vitaly Turovsky <vital2580@icloud.com>
Date: Fri, 1 Nov 2024 02:55:19 +0300
Subject: [PATCH] feat: format disconnect messages from minecraft servers
 (display correctly)

fixes #26
---
 src/index.ts                        | 13 +++++++++++--
 src/react/AppStatus.tsx             |  4 ++--
 src/react/AppStatusProvider.tsx     | 13 ++++++++++---
 src/react/appStatus.module.css      |  3 ++-
 src/react/appStatus.module.css.d.ts |  3 +--
 src/utils.ts                        |  3 ++-
 6 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/index.ts b/src/index.ts
index cf894bd7e..4507bcc40 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,6 +10,7 @@ import { onGameLoad } from './inventoryWindows'
 import { supportedVersions } from 'minecraft-protocol'
 import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth'
 import microsoftAuthflow from './microsoftAuthflow'
+import nbt from 'prismarine-nbt'
 
 import 'core-js/features/array/at'
 import 'core-js/features/promise/with-resolvers'
@@ -634,8 +635,16 @@ async function connect (connectOptions: ConnectOptions) {
   bot.on('error', handleError)
 
   bot.on('kicked', (kickReason) => {
-    console.log('User was kicked!', kickReason)
-    setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${typeof kickReason === 'object' ? JSON.stringify(kickReason) : kickReason}`, true)
+    console.log('You were kicked!', kickReason)
+    let kickReasonString = typeof kickReason === 'string' ? kickReason : JSON.stringify(kickReason)
+    let kickReasonFormatted = undefined as undefined | Record<string, any>
+    if (typeof kickReason === 'object') {
+      try {
+        kickReasonFormatted = nbt.simplify(kickReason)
+        kickReasonString = ''
+      } catch {}
+    }
+    setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${kickReasonString}`, true, undefined, undefined, kickReasonFormatted)
     destroyAll()
   })
 
diff --git a/src/react/AppStatus.tsx b/src/react/AppStatus.tsx
index 19b4abbfe..f9e5217ed 100644
--- a/src/react/AppStatus.tsx
+++ b/src/react/AppStatus.tsx
@@ -10,7 +10,7 @@ export default ({
   hideDots = false,
   lastStatus = '',
   backAction = undefined as undefined | (() => void),
-  description = '',
+  description = '' as string | JSX.Element,
   actionsSlot = null as React.ReactNode | null,
   children
 }) => {
@@ -57,7 +57,7 @@ export default ({
               })
             }
           </div>
-          <p className={styles['potential-problem']}>{description}</p>
+          <p className={styles.description}>{description}</p>
           <p className={styles['last-status']}>{lastStatus ? `Last status: ${lastStatus}` : lastStatus}</p>
         </>
       }
diff --git a/src/react/AppStatusProvider.tsx b/src/react/AppStatusProvider.tsx
index d853c2628..8aaa5d701 100644
--- a/src/react/AppStatusProvider.tsx
+++ b/src/react/AppStatusProvider.tsx
@@ -15,6 +15,8 @@ import Button from './Button'
 import { AuthenticatedAccount, updateAuthenticatedAccountData, updateLoadedServerData } from './ServersListProvider'
 import { showOptionsModal } from './SelectOption'
 import LoadingChunks from './LoadingChunks'
+import MessageFormatted from './MessageFormatted'
+import MessageFormattedString from './MessageFormattedString'
 
 const initialState = {
   status: '',
@@ -25,7 +27,8 @@ const initialState = {
   hideDots: false,
   loadingChunksData: null as null | Record<string, string>,
   loadingChunksDataPlayerChunk: null as null | { x: number, z: number },
-  isDisplaying: false
+  isDisplaying: false,
+  minecraftJsonMessage: null as null | Record<string, any>
 }
 export const appStatusState = proxy(initialState)
 export const resetAppStatusState = () => {
@@ -37,7 +40,7 @@ export const lastConnectOptions = {
 }
 
 export default () => {
-  const { isError, lastStatus, maybeRecoverable, status, hideDots, descriptionHint, loadingChunksData, loadingChunksDataPlayerChunk } = useSnapshot(appStatusState)
+  const { isError, lastStatus, maybeRecoverable, status, hideDots, descriptionHint, loadingChunksData, loadingChunksDataPlayerChunk, minecraftJsonMessage } = useSnapshot(appStatusState)
   const { active: replayActive } = useSnapshot(packetsReplaceSessionState)
 
   const isOpen = useIsModalActive('app-status')
@@ -95,7 +98,11 @@ export default () => {
       isError={isError || appStatusState.status === ''} // display back button if status is empty as probably our app is errored
       hideDots={hideDots}
       lastStatus={lastStatus}
-      description={displayAuthButton ? '' : (isError ? guessProblem(status) : '') || descriptionHint}
+      description={<>{
+        displayAuthButton ? '' : (isError ? guessProblem(status) : '') || descriptionHint
+      }{
+        minecraftJsonMessage && <MessageFormattedString message={minecraftJsonMessage} />
+      }</>}
       backAction={maybeRecoverable ? () => {
         resetAppStatusState()
         miscUiState.gameLoaded = false
diff --git a/src/react/appStatus.module.css b/src/react/appStatus.module.css
index b993f7790..c40c80c82 100644
--- a/src/react/appStatus.module.css
+++ b/src/react/appStatus.module.css
@@ -1,7 +1,8 @@
-.potential-problem {
+.description {
   color: #808080;
   word-break: break-all;
   padding: 0 10px;
+  margin-top: 3px;
 }
 
 .last-status {
diff --git a/src/react/appStatus.module.css.d.ts b/src/react/appStatus.module.css.d.ts
index 2f78b153d..0a84e8e11 100644
--- a/src/react/appStatus.module.css.d.ts
+++ b/src/react/appStatus.module.css.d.ts
@@ -1,10 +1,9 @@
 // This file is automatically generated.
 // Please do not change this file!
 interface CssExports {
+  description: string;
   'last-status': string;
   lastStatus: string;
-  'potential-problem': string;
-  potentialProblem: string;
 }
 declare const cssExports: CssExports;
 export default cssExports;
diff --git a/src/utils.ts b/src/utils.ts
index 0d0875e9e..f0d04b55a 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -123,7 +123,7 @@ export const isMajorVersionGreater = (ver1: string, ver2: string) => {
 }
 
 let ourLastStatus: string | undefined = ''
-export const setLoadingScreenStatus = function (status: string | undefined | null, isError = false, hideDots = false, fromFlyingSquid = false) {
+export const setLoadingScreenStatus = function (status: string | undefined | null, isError = false, hideDots = false, fromFlyingSquid = false, minecraftJsonMessage?: Record<string, any>) {
   // null can come from flying squid, should restore our last status
   if (status === null) {
     status = ourLastStatus
@@ -152,6 +152,7 @@ export const setLoadingScreenStatus = function (status: string | undefined | nul
   appStatusState.isError = isError
   appStatusState.lastStatus = isError ? appStatusState.status : ''
   appStatusState.status = status
+  appStatusState.minecraftJsonMessage = minecraftJsonMessage ?? null
 }
 
 // doesn't support snapshots