Skip to main content

mc_gate/mc/
handler.rs

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}