1use crate::mc::{
2 Description, DisconnectResponse, MinecraftPacket, Players, StatusResponse, Version,
3};
4use crate::{Config, UserHistory, WakeupCondition};
5use dashmap::DashMap;
6use std::sync::Arc;
7use std::sync::atomic::Ordering;
8use std::time::{Duration, Instant};
9use tokio::io::AsyncReadExt;
10use tokio::net::TcpStream;
11use tokio::time::sleep;
12
13pub struct McHandler;
14
15impl McHandler {
16 pub async fn send_fallback(
17 socket: &mut TcpStream,
18 state: i32,
19 cfg: Arc<Config>,
20 history: Arc<DashMap<String, UserHistory>>,
21 ip: String,
22 ) -> tokio::io::Result<()> {
23 if cfg.on_wakeup.is_none() {
24 return Ok(());
25 }
26
27 let should_trigger = match cfg.wakeup_on {
28 WakeupCondition::Disabled => false,
29 WakeupCondition::Motd => state == 1 || state == 2,
30 WakeupCondition::Join => state == 2,
31 };
32
33 if should_trigger {
34 Self::trigger_wakeup(Arc::clone(&cfg)).await;
35 }
36
37 if state == 1 {
38 Self::handle_wakeup(socket, cfg.mc.clone()).await
39 } else {
40 let attempts = {
41 let mut entry = history.entry(ip).or_insert(UserHistory {
42 attempts: 0,
43 last_seen: Instant::now(),
44 });
45
46 if entry.last_seen.elapsed() > Duration::from_secs(300) {
47 entry.attempts = 1;
48 } else {
49 entry.attempts += 1;
50 }
51 entry.last_seen = Instant::now();
52 entry.attempts
53 };
54
55 if attempts >= 3 {
56 Self::handle_waitlist(socket, cfg.mc.clone()).await
57 } else {
58 Self::handle_disconnect(socket, attempts).await
59 }
60 }
61 }
62
63 async fn handle_wakeup(socket: &mut TcpStream, target: String) -> tokio::io::Result<()> {
64 let response = StatusResponse {
65 version: Version {
66 name: "mc-gate".to_string(),
67 protocol: 767,
68 },
69 players: Players {
70 max: 0,
71 online: "???".to_string(),
72 },
73 description: Description {
74 text: "§c§l⚡ §eServer currently waking up...\n§7Please wait a moment.".to_string(),
75 },
76 };
77
78 MinecraftPacket::send_json(socket, 0x00, &response).await?;
79
80 let mut buf = [0u8; 32];
81 if let Ok(Ok(n)) =
82 tokio::time::timeout(Duration::from_secs(2), socket.read(&mut buf[..])).await
83 {
84 let start = Instant::now();
85 while start.elapsed().as_secs() < 120 {
86 if TcpStream::connect(&target).await.is_ok() {
87 tokio::io::AsyncWriteExt::write_all(socket, &buf[..n]).await?;
88 return Ok(());
89 }
90 sleep(Duration::from_secs(1)).await;
91 }
92 }
93 Ok(())
94 }
95
96 async fn handle_waitlist(socket: &mut TcpStream, target: String) -> tokio::io::Result<()> {
97 let start = Instant::now();
98 while start.elapsed().as_secs() < 28 {
99 if TcpStream::connect(&target).await.is_ok() {
100 let res = DisconnectResponse {
101 text: "§6Server §a§lONLINE§r§6!\n\n§6§lTry to join the server normally."
102 .to_string(),
103 };
104 return MinecraftPacket::send_json(socket, 0x00, &res).await;
105 }
106 sleep(Duration::from_millis(800)).await;
107 }
108
109 let res = DisconnectResponse {
110 text: "§c§l⚡ §eWaitlist timeout...\n\n§7The server is taking too long to start.\n§ePlease try again in a few minutes.".to_string(),
111 };
112 MinecraftPacket::send_json(socket, 0x00, &res).await
113 }
114
115 async fn handle_disconnect(socket: &mut TcpStream, attempts: u32) -> tokio::io::Result<()> {
116 sleep(Duration::from_millis(10)).await;
117 let text = if attempts == 1 {
118 "§6§l⚡ §eServer still starting...\n\n§7Please wait a moment while the world loads.\n\n§8[§eNote§8] §eIf the ping bar stays §9blue/idle§e, please\n§etry to re-join manually in §c2 minutes§e.".to_string()
119 } else {
120 "§6§l⚡ §eServer still starting...\n\n§c§lNext attempt will put you in a waitlist.\n§7(We will notify you when the server is ready)".to_string()
121 };
122
123 MinecraftPacket::send_json(socket, 0x00, &DisconnectResponse { text }).await
124 }
125
126 async fn trigger_wakeup(cfg: Arc<Config>) {
127 if let Some(callback) = &cfg.on_wakeup {
128 if cfg
129 .is_waking
130 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
131 .is_err()
132 {
133 return;
134 }
135
136 let cb = Arc::clone(callback);
137 tokio::spawn(async move {
138 cb().await;
139 });
140 }
141 }
142}