@@ -24,6 +24,7 @@ use axum::{
24
24
Router ,
25
25
} ;
26
26
use axum_extra:: routing:: RouterExt ;
27
+ use base64:: { alphabet:: Alphabet , engine:: GeneralPurpose , prelude:: BASE64_STANDARD , Engine } ;
27
28
use bustdir:: BustDir ;
28
29
use parking_lot:: RwLock ;
29
30
use reqwest:: { header:: HeaderMap , redirect:: Policy , Client } ;
@@ -103,6 +104,10 @@ async fn main() {
103
104
. route_with_tsr ( "/ping/:edition/:hostname" , get ( ping_page) )
104
105
. route ( "/internal/ping-frame/:edition/:hostname" , get ( ping_frame) )
105
106
. route ( "/internal/ping-markup/:edition/:hostname" , get ( ping_markup) )
107
+ . route (
108
+ "/internal/icon/:edition/:hostname/icon.:ext" ,
109
+ get ( ping_image) ,
110
+ )
106
111
. layer ( axum:: middleware:: from_fn ( cache_short) )
107
112
. fallback_service ( serve_dir)
108
113
. layer ( axum:: middleware:: from_fn ( csp) )
@@ -186,6 +191,20 @@ async fn root(State(state): State<AppState>) -> RootTemplate {
186
191
}
187
192
}
188
193
194
+ #[ derive( Template ) ]
195
+ #[ template( path = "api.html" ) ]
196
+ pub struct ApiTemplate {
197
+ bd : Arc < BustDir > ,
198
+ root_url : Arc < str > ,
199
+ }
200
+
201
+ async fn api_info ( State ( state) : State < AppState > ) -> ApiTemplate {
202
+ ApiTemplate {
203
+ root_url : state. root_url ,
204
+ bd : state. bust_dir ,
205
+ }
206
+ }
207
+
189
208
#[ derive( Deserialize ) ]
190
209
pub struct PingQuery {
191
210
edition : String ,
@@ -234,19 +253,11 @@ async fn ping_page(
234
253
} )
235
254
}
236
255
237
- async fn ping_generic (
238
- edition : String ,
239
- hostname : String ,
240
- state : & AppState ,
241
- ) -> Result < MCPingResponse , ErrorTemplate > {
242
- let ping = match edition. as_str ( ) {
243
- "java" => ping_java ( hostname) . await ,
244
- "bedrock" => ping_bedrock ( hostname) . await ,
245
- _ => return Err ( ErrorTemplate :: from_failure ( & Failure :: UnknownEdition , state) ) ,
246
- } ;
247
- let ping = match ping {
248
- Ok ( v) => v,
249
- Err ( e) => return Err ( ErrorTemplate :: from_failure ( & e, state) ) ,
256
+ async fn ping_generic ( edition : & str , hostname : String ) -> Result < MCPingResponse , Failure > {
257
+ let ping = match edition {
258
+ "java" => ping_java ( hostname) . await ?,
259
+ "bedrock" => ping_bedrock ( hostname) . await ?,
260
+ _ => return Err ( Failure :: UnknownEdition ) ,
250
261
} ;
251
262
Ok ( ping)
252
263
}
@@ -257,17 +268,23 @@ pub struct PingFrameTemplate {
257
268
ping : MCPingResponse ,
258
269
bd : Arc < BustDir > ,
259
270
root_url : Arc < str > ,
271
+ edition : String ,
272
+ hostname : String ,
260
273
}
261
274
262
275
async fn ping_frame (
263
276
State ( state) : State < AppState > ,
264
277
Path ( ( edition, hostname) ) : Path < ( String , String ) > ,
265
278
) -> Result < PingFrameTemplate , ErrorTemplate > {
266
- let ping = ping_generic ( edition, hostname, & state) . await ?;
279
+ let ping = ping_generic ( & edition, hostname. clone ( ) )
280
+ . await
281
+ . map_err ( |v| v. as_error_template ( & state) ) ?;
267
282
Ok ( PingFrameTemplate {
268
283
ping,
269
284
root_url : state. root_url ,
270
285
bd : state. bust_dir ,
286
+ edition,
287
+ hostname,
271
288
} )
272
289
}
273
290
@@ -277,32 +294,48 @@ pub struct PingElementTemplate {
277
294
ping : MCPingResponse ,
278
295
bd : Arc < BustDir > ,
279
296
root_url : Arc < str > ,
297
+ edition : String ,
298
+ hostname : String ,
280
299
}
281
300
282
301
async fn ping_markup (
283
302
State ( state) : State < AppState > ,
284
303
Path ( ( edition, hostname) ) : Path < ( String , String ) > ,
285
304
) -> Result < PingElementTemplate , ErrorTemplate > {
286
- let ping = ping_generic ( edition, hostname, & state) . await ?;
305
+ let ping = ping_generic ( & edition, hostname. clone ( ) )
306
+ . await
307
+ . map_err ( |v| v. as_error_template ( & state) ) ?;
287
308
Ok ( PingElementTemplate {
288
309
ping,
289
310
bd : state. bust_dir ,
290
311
root_url : state. root_url ,
312
+ edition,
313
+ hostname,
291
314
} )
292
315
}
293
316
294
- #[ derive( Template ) ]
295
- #[ template( path = "api.html" ) ]
296
- pub struct ApiTemplate {
297
- bd : Arc < BustDir > ,
298
- root_url : Arc < str > ,
299
- }
300
-
301
- async fn api_info ( State ( state) : State < AppState > ) -> ApiTemplate {
302
- ApiTemplate {
303
- root_url : state. root_url ,
304
- bd : state. bust_dir ,
305
- }
317
+ async fn ping_image ( Path ( ( edition, hostname) ) : Path < ( String , String ) > ) -> Result < Png , StatusCode > {
318
+ const PREFIX_LEN : usize = "data:image/png;base64," . len ( ) ;
319
+ debug ! ( edition, hostname, "Serving icon" ) ;
320
+ let ping = match ping_generic ( & edition, hostname. clone ( ) ) . await {
321
+ Ok ( v) => v,
322
+ Err ( e) => {
323
+ error ! ( error = ?e, "Encountered error decoding icon" ) ;
324
+ return Err ( StatusCode :: NOT_FOUND ) ;
325
+ }
326
+ } ;
327
+ let Some ( icon) = ping. icon else {
328
+ return Err ( StatusCode :: NOT_FOUND ) ;
329
+ } ;
330
+ let cut_icon = icon. split_at ( PREFIX_LEN ) . 1 ;
331
+ let decoded = match BASE64_STANDARD . decode ( cut_icon) {
332
+ Ok ( v) => v,
333
+ Err ( e) => {
334
+ error ! ( error = ?e, "Encountered error decoding icon" ) ;
335
+ return Err ( StatusCode :: INTERNAL_SERVER_ERROR ) ;
336
+ }
337
+ } ;
338
+ Ok ( Png ( decoded) )
306
339
}
307
340
308
341
async fn handle_java_ping ( Path ( address) : Path < String > ) -> Result < Json < MCPingResponse > , Failure > {
@@ -358,6 +391,13 @@ impl IntoResponse for Failure {
358
391
}
359
392
}
360
393
394
+ impl Failure {
395
+ #[ must_use]
396
+ pub fn as_error_template ( & self , state : & AppState ) -> ErrorTemplate {
397
+ ErrorTemplate :: from_failure ( self , state)
398
+ }
399
+ }
400
+
361
401
#[ derive( Serialize ) ]
362
402
pub struct ErrorSerialization {
363
403
error : String ,
@@ -401,6 +441,15 @@ impl<T: Serialize> IntoResponse for Json<T> {
401
441
}
402
442
}
403
443
444
+ pub struct Png ( pub Vec < u8 > ) ;
445
+
446
+ impl IntoResponse for Png {
447
+ fn into_response ( self ) -> Response {
448
+ let headers = [ ( "Content-Type" , "image/png" ) ] ;
449
+ ( headers, self . 0 ) . into_response ( )
450
+ }
451
+ }
452
+
404
453
fn start_tracing ( ) {
405
454
let env_filter = tracing_subscriber:: EnvFilter :: builder ( )
406
455
. with_default_directive ( concat ! ( env!( "CARGO_PKG_NAME" ) , "=info" ) . parse ( ) . unwrap ( ) )
0 commit comments