55using System . Net . Http ;
66using System . Threading ;
77using System . Threading . Tasks ;
8+ using System . Net . Http . Json ;
9+ using System . Text . Json . Serialization ;
810using Serilog ;
911using Splat ;
1012using SS14 . Launcher . Utility ;
@@ -28,8 +30,10 @@ public async Task Refresh()
2830 {
2931 try
3032 {
31- var response = await _http . GetStringAsync ( "http://www.byond.com/games/exadv1/spacestation13?format=text" ) ;
32- var servers = ParseByondResponse ( response ) ;
33+ var hubResponse = await _http . GetFromJsonAsync < GoonhubHubResponse > ( "https://node.goonhub.com/hub" ) ;
34+ if ( hubResponse == null ) return ;
35+
36+ var servers = ParseGoonhubResponse ( hubResponse ) ;
3337
3438 Avalonia . Threading . Dispatcher . UIThread . Post ( ( ) =>
3539 {
@@ -46,101 +50,17 @@ public async Task Refresh()
4650 }
4751 }
4852
49- private List < ClassicServerStatusData > ParseByondResponse ( string response )
53+ private List < ClassicServerStatusData > ParseGoonhubResponse ( GoonhubHubResponse hubResponse )
5054 {
5155 var list = new List < ClassicServerStatusData > ( ) ;
52- using var reader = new StringReader ( response ) ;
53-
54- string ? line ;
55- string ? currentName = null ;
56- string ? currentUrl = null ;
57- string ? currentStatus = null ;
58- int currentPlayers = 0 ;
59-
60- // Simple state machine to parse the text format
61- // The format uses 'world/ID' blocks for servers.
62-
63- bool inServerBlock = false ;
64-
65- while ( ( line = reader . ReadLine ( ) ) != null )
66- {
67- var trimmed = line . Trim ( ) ;
68- if ( string . IsNullOrWhiteSpace ( trimmed ) ) continue ;
69-
70- if ( trimmed . StartsWith ( "world/" ) )
71- {
72- // If we were parsing a server, save it
73- if ( inServerBlock && currentUrl != null )
74- {
75- // Name might be missing, try to extract from status or use URL
76- var name = currentName ?? ExtractNameFromStatus ( currentStatus ) ?? "Unknown Server" ;
77- var roundTime = ExtractRoundTimeFromStatus ( currentStatus ) ;
78- list . Add ( new ClassicServerStatusData ( name , currentUrl , currentPlayers , CleanStatus ( currentStatus , name ) ?? "" , roundTime ?? "In-Lobby" ) ) ;
79- }
80-
81- // Reset for new server
82- inServerBlock = true ;
83- currentName = null ;
84- currentUrl = null ;
85- currentStatus = null ;
86- currentPlayers = 0 ;
87- }
88- else if ( inServerBlock )
89- {
90- if ( trimmed . StartsWith ( "name =" ) )
91- {
92- currentName = ParseStringValue ( trimmed ) ;
93- }
94- else if ( trimmed . StartsWith ( "url =" ) )
95- {
96- currentUrl = ParseStringValue ( trimmed ) ;
97- }
98- else if ( trimmed . StartsWith ( "status =" ) )
99- {
100- currentStatus = ParseStringValue ( trimmed ) ;
101- }
102- else if ( trimmed . StartsWith ( "players = list(" ) )
103- {
104- // "players = list("Bob","Alice")"
105- // Just count the commas + 1, correcting for empty list "list()"
106- var content = trimmed . Substring ( "players = list(" . Length ) ;
107- if ( content . EndsWith ( ")" ) )
108- {
109- content = content . Substring ( 0 , content . Length - 1 ) ;
110- if ( string . IsNullOrWhiteSpace ( content ) )
111- {
112- currentPlayers = 0 ;
113- }
114- else
115- {
116- // A simple Count(',') + 1 is risky if names contain commas, but usually they are quoted.
117- // However, parsing full CSV is safer but 'Splitting by ",' might be enough?
118- // Let's iterate and count quoted segments.
119- // Or simpler: Splitting by ',' is mostly fine for SS13 ckeys.
120- currentPlayers = content . Split ( ',' ) . Length ;
121- }
122- }
123- }
124- else if ( trimmed . StartsWith ( "players =" ) )
125- {
126- // Fallback for simple number if ever used
127- var parts = trimmed . Split ( '=' ) ;
128- if ( parts . Length > 1 && int . TryParse ( parts [ 1 ] . Trim ( ) , out var p ) )
129- {
130- currentPlayers = p ;
131- }
132- }
133- }
134- }
135-
136- // Add the last one if exists
137- if ( inServerBlock && currentUrl != null )
56+ foreach ( var server in hubResponse . Response )
13857 {
139- var name = currentName ?? ExtractNameFromStatus ( currentStatus ) ?? "Unknown Server" ;
140- var roundTime = ExtractRoundTimeFromStatus ( currentStatus ) ;
141- list . Add ( new ClassicServerStatusData ( name , currentUrl , currentPlayers , CleanStatus ( currentStatus , name ) ?? "" , roundTime ?? "In-Lobby" ) ) ;
58+ var name = ExtractNameFromStatus ( server . Status ) ?? "Unknown Server" ;
59+ var roundTime = ExtractRoundTimeFromStatus ( server . Status ) ?? "In-Lobby" ;
60+ var address = $ "byond://BYOND.world.{ server . UrlId } ";
61+
62+ list . Add ( new ClassicServerStatusData ( name , address , server . Players , CleanStatus ( server . Status , name ) ?? "" , roundTime ) ) ;
14263 }
143-
14464 return list ;
14565 }
14666
@@ -199,27 +119,15 @@ private List<ClassicServerStatusData> ParseByondResponse(string response)
199119 return s ;
200120 }
201121
202- private string ParseStringValue ( string line )
122+ private sealed class GoonhubHubResponse
123+ {
124+ [ JsonPropertyName ( "response" ) ] public List < GoonhubServerEntry > Response { get ; set ; } = new ( ) ;
125+ }
126+
127+ private sealed class GoonhubServerEntry
203128 {
204- // format: key = "value"
205- var idx = line . IndexOf ( '"' ) ;
206- if ( idx == - 1 ) return string . Empty ;
207- var lastIdx = line . LastIndexOf ( '"' ) ;
208- if ( lastIdx <= idx ) return string . Empty ;
209-
210- // Extract content inside quotes
211- var inner = line . Substring ( idx + 1 , lastIdx - idx - 1 ) ;
212-
213- // Unescape BYOND/C string escapes
214- // \" -> "
215- // \n -> newline
216- // \\ -> \
217- // The most critical one is \n showing up as literal \n in UI.
218-
219- // Simple manual unescape for common sequences
220- return inner . Replace ( "\\ \" " , "\" " )
221- . Replace ( "\\ n" , "\n " )
222- . Replace ( "\\ \\ " , "\\ " )
223- . Replace ( "\\ t" , "\t " ) ;
129+ [ JsonPropertyName ( "urlId" ) ] public string UrlId { get ; set ; } = "" ;
130+ [ JsonPropertyName ( "players" ) ] public int Players { get ; set ; }
131+ [ JsonPropertyName ( "status" ) ] public string Status { get ; set ; } = "" ;
224132 }
225133}
0 commit comments