1
// This file is part of hnefatafl-copenhagen.
2
//
3
// hnefatafl-copenhagen is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU Affero General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// hnefatafl-copenhagen is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU Affero General Public License for more details.
12
//
13
// You should have received a copy of the GNU Affero General Public License
14
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
//
16
// SPDX-License-Identifier: AGPL-3.0-or-later
17
// SPDX-FileCopyrightText: 2025 David Campbell <david@hnefatafl.org>
18

            
19
#![deny(clippy::indexing_slicing)]
20
#![deny(clippy::expect_used)]
21
#![deny(clippy::panic)]
22
#![deny(clippy::unwrap_used)]
23

            
24
mod command_line;
25
mod remove_connection;
26
mod smtp;
27
mod tests;
28
mod unix_timestamp;
29

            
30
use std::{
31
    collections::{HashMap, HashSet, VecDeque},
32
    fmt,
33
    fs::{self, File, OpenOptions},
34
    io::{BufRead, BufReader, ErrorKind, Read, Write},
35
    net::{IpAddr, TcpListener, TcpStream},
36
    process::exit,
37
    str::FromStr,
38
    sync::{
39
        Arc, Mutex,
40
        mpsc::{self, Receiver, Sender},
41
    },
42
    thread::{self, sleep},
43
    time::Duration,
44
};
45

            
46
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
47
use clap::Parser;
48
use hnefatafl_copenhagen::{
49
    Id, SERVER_PORT, VERSION_ID,
50
    accounts::{Account, Accounts, DateTimeUtc},
51
    board::BoardSize,
52
    draw::Draw,
53
    email::Email,
54
    game::TimeUnix,
55
    glicko::Outcome,
56
    rating::Rated,
57
    role::Role,
58
    server_game::{
59
        ArchivedGame, Challenger, Messenger, ServerGame, ServerGameLight, ServerGameSerialized,
60
        ServerGames, ServerGamesLight, ServerGamesLightVec,
61
    },
62
    status::Status,
63
    time::{Time, TimeEnum, TimeSettings},
64
    tournament::Tournament,
65
    utils::{self, create_data_folder, data_file},
66
};
67
use itertools::Itertools;
68
use jiff::{Timestamp, ToSpan, Zoned};
69
use lettre::{
70
    SmtpTransport, Transport,
71
    message::{Mailbox, header::ContentType},
72
    transport::smtp::authentication::Credentials,
73
};
74
use log::{debug, error, info, trace};
75
use rand::random;
76
use serde::{Deserialize, Serialize};
77
use std::fmt::Write as _;
78

            
79
use crate::{
80
    command_line::Args, remove_connection::RemoveConnection, smtp::Smtp,
81
    unix_timestamp::UnixTimestamp,
82
};
83

            
84
const ACTIVE_GAMES_FILE: &str = "active-games.postcard";
85
const ARCHIVED_GAMES_FILE: &str = "archived-games.ron";
86
const KEEP_TEXTS: usize = 100;
87

            
88
const HOUR_IN_SECONDS: u64 = 60 * 60;
89
const DAY_IN_SECONDS: u64 = HOUR_IN_SECONDS * 24;
90
const DAYS_FOR_INACTIVE_ACCOUNT: i64 = 14;
91

            
92
const TWO_MONTHS_MICRO_SECONDS: i64 = 60 * 60 * 24 * 30_436_875 * 2;
93
const SEVEN_DAYS: i64 = 1_000 * 60 * 60 * 24 * 7;
94
const USERS_FILE: &str = "users.ron";
95

            
96
fn main() -> anyhow::Result<()> {
97
    // println!("{:x}", rand::random::<u32>());
98
    // return Ok(());
99

            
100
    let args = Args::parse();
101
    utils::init_logger("hnefatafl_server_full", args.debug, args.systemd);
102

            
103
    if args.man {
104
        return Args::generate_man_page();
105
    }
106

            
107
    create_data_folder()?;
108

            
109
    let (tx, rx) = mpsc::channel();
110
    let mut server = Server {
111
        tx: Some(tx.clone()),
112
        ..Server::default()
113
    };
114

            
115
    if args.skip_the_data_file {
116
        server.skip_the_data_files = true;
117
    } else {
118
        server.load_data_files(tx.clone(), args.systemd)?;
119
    }
120

            
121
    thread::spawn(move || handle_error(server.handle_messages(&rx)));
122

            
123
    if !args.skip_advertising_updates {
124
        Server::advertise_updates(tx.clone());
125
    }
126

            
127
    Server::check_once_a_day(tx.clone());
128

            
129
    if args.autostart_tournament {
130
        Server::new_tournament(tx.clone());
131
    }
132

            
133
    Server::save(tx.clone());
134

            
135
    let mut address = "[::]".to_string();
136
    address.push_str(SERVER_PORT);
137

            
138
    let listener = match TcpListener::bind(&address) {
139
        Ok(listener) => listener,
140
        Err(error) => {
141
            error!("TcpLister::bind: {error}");
142

            
143
            address = "0.0.0.0".to_string();
144
            address.push_str(SERVER_PORT);
145
            TcpListener::bind(&address)?
146
        }
147
    };
148

            
149
    info!("listening on {address} ...");
150

            
151
    for (index, stream) in (1..).zip(listener.incoming()) {
152
        let stream = match stream {
153
            Ok(stream) => stream,
154
            Err(error) => {
155
                error!("stream: {error}");
156
                continue;
157
            }
158
        };
159

            
160
        let peer_address = match stream.peer_addr() {
161
            Ok(peer_address) => peer_address.ip(),
162
            Err(error) => {
163
                error!("peer_address: {error}");
164
                continue;
165
            }
166
        };
167

            
168
        if args.secure {
169
            let (tx_close, rx_close) = mpsc::channel();
170

            
171
            tx.send((
172
                format!("0 server connection_add {peer_address}"),
173
                Some(tx_close),
174
            ))?;
175

            
176
            match rx_close.recv() {
177
                Ok(close) => match close.parse() {
178
                    Ok(close) => {
179
                        if close {
180
                            continue;
181
                        }
182
                    }
183
                    Err(error) => {
184
                        error!("close 2: {error}");
185
                        continue;
186
                    }
187
                },
188
                Err(error) => {
189
                    error!("close 1: {error}");
190
                    continue;
191
                }
192
            }
193
        }
194

            
195
        let tx = tx.clone();
196

            
197
        thread::spawn(move || {
198
            if let Err(error) = login(index, stream, peer_address, &tx) {
199
                error!("peer_address: {peer_address}, login: {error}");
200
            }
201
        });
202
    }
203

            
204
    Ok(())
205
}
206

            
207
#[allow(clippy::too_many_lines)]
208
fn login(
209
    id: Id,
210
    mut stream: TcpStream,
211
    peer_address: IpAddr,
212
    tx: &mpsc::Sender<(String, Option<mpsc::Sender<String>>)>,
213
) -> anyhow::Result<()> {
214
    info!("login attempted from {peer_address}");
215

            
216
    let args = Args::parse();
217

            
218
    let _remove_connection;
219
    if args.secure {
220
        _remove_connection = RemoveConnection {
221
            address: stream.peer_addr()?.ip(),
222
            tx: tx.clone(),
223
        };
224
    }
225

            
226
    let mut reader = BufReader::new(stream.try_clone()?);
227
    let mut buf = String::new();
228
    let (client_tx, client_rx) = mpsc::channel();
229
    let mut username_proper = "_".to_string();
230
    let mut login_successful = false;
231

            
232
    for _ in 0..100 {
233
        reader.read_line(&mut buf)?;
234

            
235
        for ch in buf.trim().chars() {
236
            if ch.is_control() || ch == '\0' {
237
                return Err(anyhow::Error::msg(
238
                    "there are control characters in the username or password",
239
                ));
240
            }
241
        }
242

            
243
        if buf.trim().is_empty() {
244
            return Err(anyhow::Error::msg(
245
                "The user sent a command without logging in, then quit.",
246
            ));
247
        }
248

            
249
        let buf_clone = buf.clone();
250
        let mut username_password_etc = buf_clone.split_ascii_whitespace();
251

            
252
        let version_id = username_password_etc.next();
253
        let create_account_login = username_password_etc.next();
254
        let username_option = username_password_etc.next();
255

            
256
        if let (Some(version_id), Some(create_account_login), Some(username)) =
257
            (version_id, create_account_login, username_option)
258
        {
259
            username_proper = username.to_string();
260
            if version_id != VERSION_ID {
261
                stream.write_all(b"? login wrong_version\n")?;
262
                buf.clear();
263
                continue;
264
            }
265

            
266
            let password: Vec<&str> = username_password_etc.collect();
267
            let password = password.join(" ");
268

            
269
            if username.len() > 16 {
270
                stream.write_all(b"? login _ username is more than 16 characters\n")?;
271
                buf.clear();
272
                continue;
273
            }
274
            if password.len() > 32 {
275
                stream.write_all(b"? login _ password is more than 32 characters\n")?;
276
                buf.clear();
277
                continue;
278
            }
279

            
280
            debug!("{peer_address} {id} {username} {create_account_login} {password}");
281

            
282
            if create_account_login == "reset_password" {
283
                tx.send((
284
                    format!("0 {username} reset_password"),
285
                    Some(client_tx.clone()),
286
                ))?;
287

            
288
                stream.write_all(b"? login reset_password\n")?;
289

            
290
                buf.clear();
291
                continue;
292
            }
293

            
294
            tx.send((
295
                format!("{id} {username} {create_account_login} {password}"),
296
                Some(client_tx.clone()),
297
            ))?;
298

            
299
            let message = client_rx.recv()?;
300
            buf.clear();
301
            if create_account_login == "login" {
302
                if "= login" == message.as_str() {
303
                    login_successful = true;
304
                    break;
305
                }
306

            
307
                stream.write_all(b"? login multiple_possible_errors\n")?;
308
                continue;
309
            } else if create_account_login == "create_account" {
310
                if "= create_account" == message.as_str() {
311
                    login_successful = true;
312
                    break;
313
                }
314

            
315
                stream.write_all(b"? create_account\n")?;
316
                continue;
317
            }
318

            
319
            stream.write_all(b"? login _\n")?;
320
        }
321

            
322
        buf.clear();
323
    }
324

            
325
    if !login_successful {
326
        return Err(anyhow::Error::msg("the user failed to login"));
327
    }
328
    stream.write_all(b"= login\n")?;
329
    info!("{peer_address} {id} {username_proper} logged in");
330

            
331
    thread::spawn(move || {
332
        if let Err(error) = receiving_and_writing(stream, &client_rx) {
333
            error!("receiving_and_writing: {error}");
334
        }
335
    });
336

            
337
    tx.send((format!("{id} {username_proper} email_get"), None))?;
338
    tx.send((format!("{id} {username_proper} texts"), None))?;
339
    tx.send((format!("{id} {username_proper} display_games"), None))?;
340
    tx.send((format!("{id} {username_proper} tournament_status_0"), None))?;
341
    tx.send((format!("{id} {username_proper} admin"), None))?;
342
    tx.send((format!("{id} {username_proper} admin_tournament"), None))?;
343

            
344
    let mut game_id = None;
345
    'outer: for _ in 0..1_000_000 {
346
        if let Err(err) = reader.read_line(&mut buf) {
347
            error!("peer_address: {peer_address}, reader.read_line(): {err}");
348
            break 'outer;
349
        }
350

            
351
        let buf_str = buf.trim();
352

            
353
        if buf_str.is_empty() {
354
            break 'outer;
355
        }
356

            
357
        for char in buf_str.chars() {
358
            if char.is_control() || char == '\0' {
359
                break 'outer;
360
            }
361
        }
362

            
363
        // Fixme: If a player creates a game less than day main time, then
364
        // leaves the game without declining, then the other players gets a
365
        // game that does not automatically quit.
366
        let words: Vec<_> = buf_str.split_whitespace().collect();
367
        if let Some(first) = words.first() {
368
            if (*first == "join_game" || *first == "resume_game")
369
                && let Some(second) = words.get(1)
370
                && let Ok(id) = u128::from_str(second)
371
            {
372
                game_id = Some(id);
373
            }
374

            
375
            if *first == "leave_game" {
376
                game_id = None;
377
            }
378
        }
379

            
380
        tx.send((format!("{id} {username_proper} {buf_str}"), None))?;
381
        buf.clear();
382
    }
383

            
384
    if let Some(game_id) = game_id {
385
        tx.send((format!("{id} {username_proper} leave_game {game_id}"), None))?;
386
    }
387

            
388
    tx.send((format!("{id} {username_proper} logout"), None))?;
389
    info!("{peer_address} {id} {username_proper} logged out");
390

            
391
    Ok(())
392
}
393

            
394
fn receiving_and_writing<T: Send + Write>(
395
    mut stream: T,
396
    client_rx: &Receiver<String>,
397
) -> anyhow::Result<()> {
398
    for mut message in client_rx {
399
        match message.as_str() {
400
            "= archived_games" => {
401
                let ron_archived_games = client_rx.recv()?;
402
                let archived_games: Vec<ArchivedGame> = ron::from_str(&ron_archived_games)?;
403
                let postcard_archived_games = &postcard::to_allocvec(&archived_games)?;
404

            
405
                writeln!(message, " {}", postcard_archived_games.len())?;
406
                stream.write_all(message.as_bytes())?;
407
                stream.write_all(postcard_archived_games)?;
408
            }
409
            "= logout" => return Ok(()),
410
            _ => {
411
                message.push('\n');
412
                if let Err(error) = stream.write_all(message.as_bytes()) {
413
                    return Err(anyhow::Error::msg(format!("{message}: {error}")));
414
                }
415
            }
416
        }
417
    }
418

            
419
    Ok(())
420
}
421

            
422
fn handle_error<T, E: fmt::Display>(result: Result<T, E>) -> T {
423
    match result {
424
        Ok(value) => value,
425
        Err(error) => {
426
            error!("{error}");
427
            exit(1)
428
        }
429
    }
430
}
431

            
432
6
fn hash_password(password: &str) -> Option<String> {
433
6
    let ctx = Argon2::default();
434
6
    Some(ctx.hash_password(password.as_bytes()).ok()?.to_string())
435
6
}
436

            
437
fn timestamp() -> String {
438
    Timestamp::now().strftime("[%F %T UTC]").to_string()
439
}
440

            
441
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
442
struct Server {
443
    #[serde(default)]
444
    game_id: Id,
445
    #[serde(default)]
446
    ran_update_rd: UnixTimestamp,
447
    #[serde(default)]
448
    admins: HashSet<String>,
449
    #[serde(default)]
450
    admins_tournament: HashSet<String>,
451
    #[serde(default)]
452
    smtp: Smtp,
453
    #[serde(default)]
454
    tournament: Option<Tournament>,
455
    #[serde(default)]
456
    accounts: Accounts,
457
    #[serde(skip)]
458
    accounts_old: Accounts,
459
    #[serde(skip)]
460
    archived_games: Vec<ArchivedGame>,
461
    #[serde(skip)]
462
    clients: HashMap<usize, mpsc::Sender<String>>,
463
    #[serde(skip)]
464
    connections: HashMap<String, u128>,
465
    #[serde(skip)]
466
    games: ServerGames,
467
    #[serde(skip)]
468
    games_light: ServerGamesLight,
469
    #[serde(skip)]
470
    games_light_vec: ServerGamesLightVec,
471
    #[serde(skip)]
472
    games_light_old: ServerGamesLight,
473
    #[serde(skip)]
474
    skip_the_data_files: bool,
475
    #[serde(default)]
476
    texts: VecDeque<String>,
477
    #[serde(skip)]
478
    tx: Option<mpsc::Sender<(String, Option<mpsc::Sender<String>>)>>,
479
}
480

            
481
impl Server {
482
    fn advertise_updates(tx: Sender<(String, Option<Sender<String>>)>) {
483
        thread::spawn(move || {
484
            loop {
485
                handle_error(tx.send(("0 server display_server".to_string(), None)));
486
                thread::sleep(Duration::from_secs(1));
487
            }
488
        });
489
    }
490

            
491
    fn append_archived_game(&mut self, game: ServerGame) -> anyhow::Result<()> {
492
        let Some(attacker) = self.accounts.0.get(&game.attacker) else {
493
            return Err(anyhow::Error::msg("failed to get rating!"));
494
        };
495
        let Some(defender) = self.accounts.0.get(&game.defender) else {
496
            return Err(anyhow::Error::msg("failed to get rating!"));
497
        };
498
        let game = ArchivedGame::new(game, attacker.rating.clone(), defender.rating.clone());
499

            
500
        let archived_games_file = data_file(ARCHIVED_GAMES_FILE);
501
        let mut game_string = ron::ser::to_string(&game)?;
502
        game_string.push('\n');
503

            
504
        let mut file = OpenOptions::new()
505
            .create(true)
506
            .append(true)
507
            .open(archived_games_file)?;
508

            
509
        file.write_all(game_string.as_bytes())?;
510

            
511
        self.archived_games.push(game);
512

            
513
        Ok(())
514
    }
515

            
516
    fn bcc_mailboxes(&self, username: &str) -> Vec<Mailbox> {
517
        let mut emails = Vec::new();
518

            
519
        if let Some(account) = self.accounts.0.get(username)
520
            && account.send_emails
521
        {
522
            for account in self.accounts.0.values() {
523
                if let Some(email) = &account.email
524
                    && email.verified
525
                    && let Some(email) = email.to_mailbox()
526
                {
527
                    emails.push(email);
528
                }
529
            }
530
        }
531

            
532
        emails
533
    }
534

            
535
    fn bcc_send(&self, username: &str) -> String {
536
        let mut emails = Vec::new();
537

            
538
        if let Some(account) = self.accounts.0.get(username)
539
            && account.send_emails
540
        {
541
            for account in self.accounts.0.values() {
542
                if let Some(email) = &account.email
543
                    && email.verified
544
                {
545
                    emails.push(email.tx());
546
                }
547
            }
548
        }
549

            
550
        emails.sort();
551
        emails.join(" ")
552
    }
553

            
554
    /// ```sh
555
    /// # PASSWORD can be the empty string.
556
    /// <- change_password PASSWORD
557
    /// -> = change_password
558
    /// ```
559
1
    fn change_password(
560
1
        &mut self,
561
1
        username: &str,
562
1
        index_supplied: usize,
563
1
        command: &str,
564
1
        the_rest: &[&str],
565
1
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
566
1
        info!("{index_supplied} {username} change_password");
567

            
568
1
        let account = self.accounts.0.get_mut(username)?;
569
1
        let password = the_rest.join(" ");
570

            
571
1
        if password.len() > 32 {
572
            return Some((
573
                self.clients.get(&index_supplied)?.clone(),
574
                false,
575
                format!("{command} password is greater than 32 characters"),
576
            ));
577
1
        }
578

            
579
1
        account.password = hash_password(&password)?;
580

            
581
        Some((
582
1
            self.clients.get(&index_supplied)?.clone(),
583
            true,
584
1
            (*command).to_string(),
585
        ))
586
1
    }
587

            
588
    /// ```sh
589
    /// # server internal
590
    /// ```
591
    ///
592
    /// c = 63.2
593
    ///
594
    /// This assumes 30 2 month periods must pass before one's rating
595
    /// deviation is the same as a new player and that a typical RD is 50.
596
    #[must_use]
597
2
    fn check_update_rd(&mut self) -> bool {
598
2
        let now = Timestamp::now();
599
2
        if now.as_microsecond() - self.ran_update_rd.0.as_microsecond() >= TWO_MONTHS_MICRO_SECONDS
600
        {
601
1
            for account in self.accounts.0.values_mut() {
602
1
                account.rating.update_rd();
603
1
            }
604
1
            self.ran_update_rd = UnixTimestamp(now);
605
1
            true
606
        } else {
607
1
            false
608
        }
609
2
    }
610

            
611
    fn check_once_a_day(tx: Sender<(String, Option<Sender<String>>)>) {
612
        thread::spawn(move || {
613
            loop {
614
                handle_error(tx.send(("0 server delete_unused_accounts".to_string(), None)));
615
                handle_error(tx.send(("0 server check_update_rd".to_string(), None)));
616
                thread::sleep(Duration::from_secs(DAY_IN_SECONDS));
617
            }
618
        });
619
    }
620

            
621
    /// ```sh
622
    /// # PASSWORD can be the empty string.
623
    /// <- VERSION_ID create_account player-1 PASSWORD
624
    /// -> = login
625
    /// ```
626
10
    fn create_account(
627
10
        &mut self,
628
10
        username: &str,
629
10
        index_supplied: usize,
630
10
        command: &str,
631
10
        the_rest: &[&str],
632
10
        option_tx: Option<Sender<String>>,
633
10
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
634
10
        let password = the_rest.join(" ");
635
10
        let tx = option_tx?;
636

            
637
10
        if self.accounts.0.contains_key(username) || username == "server" {
638
            info!("{index_supplied} {username} is already in the database");
639
            Some((tx, false, (*command).to_string()))
640
        } else {
641
10
            info!("{index_supplied} {username} created user account");
642

            
643
10
            let hash = hash_password(&password)?;
644
10
            self.clients.insert(index_supplied, tx);
645
10
            self.accounts.0.insert(
646
10
                (*username).to_string(),
647
10
                Account {
648
10
                    password: hash,
649
10
                    logged_in: Some(index_supplied),
650
10
                    ..Default::default()
651
10
                },
652
            );
653

            
654
            Some((
655
10
                self.clients.get(&index_supplied)?.clone(),
656
                true,
657
10
                (*command).to_string(),
658
            ))
659
        }
660
10
    }
661

            
662
    fn decline_game(
663
        &mut self,
664
        username: &str,
665
        index_supplied: usize,
666
        mut command: String,
667
        the_rest: &[&str],
668
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
669
        let channel = self.clients.get(&index_supplied)?;
670

            
671
        let Some(id) = the_rest.first() else {
672
            return Some((channel.clone(), false, command));
673
        };
674
        let Ok(id) = id.parse::<Id>() else {
675
            return Some((channel.clone(), false, command));
676
        };
677

            
678
        let mut switch = false;
679
        if let Some(&"switch") = the_rest.get(1) {
680
            switch = true;
681
        }
682

            
683
        info!("{index_supplied} {username} decline_game {id} switch={switch}");
684

            
685
        if let Some(game_old) = self.games_light.0.remove(&id) {
686
            let mut attacker = None;
687
            let mut defender = None;
688

            
689
            if switch {
690
                if Some(username.to_string()) == game_old.attacker {
691
                    defender = game_old.defender;
692
                } else if Some(username.to_string()) == game_old.defender {
693
                    attacker = game_old.attacker;
694
                }
695
            } else if Some(username.to_string()) == game_old.attacker {
696
                attacker = game_old.attacker;
697
            } else if Some(username.to_string()) == game_old.defender {
698
                defender = game_old.defender;
699
            }
700

            
701
            let game = ServerGameLight {
702
                id,
703
                attacker,
704
                defender,
705
                challenger: Challenger::default(),
706
                rated: game_old.rated,
707
                timed: game_old.timed,
708
                board_size: game_old.board_size,
709
                spectators: game_old.spectators,
710
                challenge_accepted: false,
711
                game_over: false,
712
                turn: Role::Roleless,
713
            };
714

            
715
            command = format!("{command} {game:?}");
716
            self.games_light.0.insert(id, game);
717
        }
718

            
719
        Some((channel.clone(), true, command))
720
    }
721

            
722
    fn delete_account(&mut self, username: &str, index_supplied: usize) {
723
        info!("{index_supplied} {username} delete_account");
724

            
725
        self.accounts.0.remove(username);
726
    }
727

            
728
    #[allow(clippy::too_many_lines)]
729
    fn display_server(&mut self, username: &str) -> Option<(mpsc::Sender<String>, bool, String)> {
730
        if self.games_light != self.games_light_old {
731
            debug!("0 {username} display_games");
732
            self.games_light_old = self.games_light.clone();
733
            self.sort_games_light();
734

            
735
            let mut names = HashMap::new();
736
            for (name, account) in &self.accounts.0 {
737
                if let Some(id) = account.logged_in {
738
                    names.insert(id, name);
739
                }
740
            }
741

            
742
            for (id, tx) in &mut self.clients {
743
                let Ok(games) = self
744
                    .games_light_vec
745
                    .display_games(names.get(id).map(|s| s.as_str()))
746
                else {
747
                    continue;
748
                };
749

            
750
                let _ok = tx.send(format!("= display_games {games}"));
751
            }
752
        }
753

            
754
        if self.accounts != self.accounts_old {
755
            debug!("0 {username} display_users");
756
            self.accounts_old = self.accounts.clone();
757

            
758
            for (name, account) in &self.accounts.0 {
759
                if let Some(id) = account.logged_in
760
                    && let Some(tx) = self.clients.get(&id)
761
                {
762
                    if self.admins.contains(name) {
763
                        if let Ok(string) = &self.accounts.display_admin() {
764
                            let _ok = tx.send(format!("= display_users_admin {string}"));
765
                        }
766
                    } else {
767
                        let _ok = tx.send(format!("= display_users {}", &self.accounts));
768
                    }
769
                }
770
            }
771
        }
772

            
773
        for game in self.games.0.values_mut() {
774
            match game.game.turn {
775
                Role::Attacker => {
776
                    if game.game.status == Status::Ongoing
777
                        && let TimeUnix::Time(game_time) = &mut game.game.time
778
                    {
779
                        let now = Timestamp::now().as_millisecond();
780
                        let elapsed_time = now - *game_time;
781
                        game.elapsed_time += elapsed_time;
782
                        *game_time = now;
783

            
784
                        if game.elapsed_time > SEVEN_DAYS
785
                            && let Some(tx) = &mut self.tx
786
                        {
787
                            let _ok = tx.send((
788
                                format!(
789
                                    "0 {} game {} play attacker resigns _",
790
                                    game.attacker, game.id
791
                                ),
792
                                None,
793
                            ));
794
                            return None;
795
                        }
796

            
797
                        if let TimeSettings::Timed(attacker_time) = &mut game.game.attacker_time {
798
                            if attacker_time.milliseconds_left > 0 {
799
                                attacker_time.milliseconds_left -= elapsed_time;
800
                            } else if let Some(tx) = &mut self.tx {
801
                                let _ok = tx.send((
802
                                    format!(
803
                                        "0 {} game {} play attacker resigns _",
804
                                        game.attacker, game.id
805
                                    ),
806
                                    None,
807
                                ));
808
                            }
809
                        }
810
                    }
811
                }
812
                Role::Roleless => {}
813
                Role::Defender => {
814
                    if game.game.status == Status::Ongoing
815
                        && let TimeUnix::Time(game_time) = &mut game.game.time
816
                    {
817
                        let now = Timestamp::now().as_millisecond();
818
                        let elapsed_time = now - *game_time;
819
                        game.elapsed_time += elapsed_time;
820
                        *game_time = now;
821

            
822
                        if game.elapsed_time > SEVEN_DAYS
823
                            && let Some(tx) = &mut self.tx
824
                        {
825
                            let _ok = tx.send((
826
                                format!(
827
                                    "0 {} game {} play defender resigns _",
828
                                    game.defender, game.id
829
                                ),
830
                                None,
831
                            ));
832
                            return None;
833
                        }
834

            
835
                        if let TimeSettings::Timed(defender_time) = &mut game.game.defender_time {
836
                            if defender_time.milliseconds_left > 0 {
837
                                defender_time.milliseconds_left -= elapsed_time;
838
                            } else if let Some(tx) = &mut self.tx {
839
                                let _ok = tx.send((
840
                                    format!(
841
                                        "0 {} game {} play defender resigns _",
842
                                        game.defender, game.id
843
                                    ),
844
                                    None,
845
                                ));
846
                            }
847
                        }
848
                    }
849
                }
850
            }
851
        }
852

            
853
        None
854
    }
855

            
856
    fn draw(
857
        &mut self,
858
        index_supplied: usize,
859
        command: &str,
860
        the_rest: &[&str],
861
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
862
        let Some(id) = the_rest.first() else {
863
            return Some((
864
                self.clients.get(&index_supplied)?.clone(),
865
                false,
866
                (*command).to_string(),
867
            ));
868
        };
869
        let Ok(id) = id.parse::<Id>() else {
870
            return Some((
871
                self.clients.get(&index_supplied)?.clone(),
872
                false,
873
                (*command).to_string(),
874
            ));
875
        };
876

            
877
        let Some(draw) = the_rest.get(1) else {
878
            return Some((
879
                self.clients.get(&index_supplied)?.clone(),
880
                false,
881
                (*command).to_string(),
882
            ));
883
        };
884
        let Ok(draw) = Draw::from_str(draw) else {
885
            return Some((
886
                self.clients.get(&index_supplied)?.clone(),
887
                false,
888
                (*command).to_string(),
889
            ));
890
        };
891

            
892
        let Some(mut game) = self.games.0.remove(&id) else {
893
            return Some((
894
                self.clients.get(&index_supplied)?.clone(),
895
                false,
896
                (*command).to_string(),
897
            ));
898
        };
899

            
900
        let message = format!("= draw {draw}");
901
        game.attacker_tx.send(message.clone());
902
        game.defender_tx.send(message.clone());
903

            
904
        if draw == Draw::Accept {
905
            let Some(game_light) = self.games_light.0.get(&id) else {
906
                return Some((
907
                    self.clients.get(&index_supplied)?.clone(),
908
                    false,
909
                    (*command).to_string(),
910
                ));
911
            };
912

            
913
            for spectator in game_light.spectators() {
914
                if let Some(sender) = self.clients.get(&spectator) {
915
                    let _ok = sender.send(message.clone());
916
                }
917
            }
918

            
919
            game.game.status = Status::Draw;
920

            
921
            let accounts = &mut self.accounts.0;
922
            let (attacker_rating, defender_rating) = if let (Some(attacker), Some(defender)) =
923
                (accounts.get(&game.attacker), accounts.get(&game.defender))
924
            {
925
                (attacker.rating.rating, defender.rating.rating)
926
            } else {
927
                unreachable!();
928
            };
929

            
930
            if let Some(attacker) = accounts.get_mut(&game.attacker) {
931
                attacker.draws += 1;
932

            
933
                if game.rated.into() {
934
                    attacker
935
                        .rating
936
                        .update_rating(defender_rating, &Outcome::Draw);
937
                }
938
            }
939
            if let Some(defender) = accounts.get_mut(&game.defender) {
940
                defender.draws += 1;
941

            
942
                if game.rated.into() {
943
                    defender
944
                        .rating
945
                        .update_rating(attacker_rating, &Outcome::Draw);
946
                }
947
            }
948

            
949
            if let Some(game) = self.games_light.0.get_mut(&id) {
950
                game.game_over = true;
951
            }
952

            
953
            if !self.skip_the_data_files {
954
                self.append_archived_game(game)
955
                    .map_err(|err| {
956
                        error!("append_archived_games: {err}");
957
                    })
958
                    .ok()?;
959
            }
960
        }
961

            
962
        None
963
    }
964

            
965
    //
966
    #[allow(clippy::too_many_lines)]
967
    fn game(
968
        &mut self,
969
        index_supplied: usize,
970
        username: &str,
971
        command: &str,
972
        the_rest: &[&str],
973
        group_size: usize,
974
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
975
        if the_rest.len() < 5 {
976
            return Some((
977
                self.clients.get(&index_supplied)?.clone(),
978
                false,
979
                (*command).to_string(),
980
            ));
981
        }
982

            
983
        let index = the_rest.first()?;
984
        let Ok(index) = index.parse() else {
985
            return Some((
986
                self.clients.get(&index_supplied)?.clone(),
987
                false,
988
                (*command).to_string(),
989
            ));
990
        };
991
        let role = the_rest.get(2)?;
992
        let Ok(role) = Role::from_str(role) else {
993
            return Some((
994
                self.clients.get(&index_supplied)?.clone(),
995
                false,
996
                (*command).to_string(),
997
            ));
998
        };
999
        let from = the_rest.get(3)?;
        let to = the_rest.get(4)?;
        let mut to = (*to).to_string();
        if to == "_" {
            to = String::new();
        }
        let Some(game) = self.games.0.get_mut(&index) else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let Some(game_light) = self.games_light.0.get_mut(&index) else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        game.elapsed_time = 0;
        game.draw_requested = Role::Roleless;
        let mut attackers_turn_next = true;
        if role == Role::Attacker {
            if *username == game.attacker {
                game.game
                    .read_line(&format!("play attacker {from} {to}"))
                    .map_err(|error| {
                        error!("play attacker {from} {to}: {error}");
                        error
                    })
                    .ok()?;
                attackers_turn_next = false;
                let message = format!("game {index} play attacker {from} {to}");
                for spectator in game_light.spectators() {
                    if let Some(client) = self.clients.get(&spectator) {
                        let _ok = client.send(message.clone());
                    }
                }
                game.defender_tx.send(message);
            } else {
                return Some((
                    self.clients.get(&index_supplied)?.clone(),
                    false,
                    (*command).to_string(),
                ));
            }
        } else if *username == game.defender {
            game.game
                .read_line(&format!("play defender {from} {to}"))
                .map_err(|error| {
                    error!("play defender {from} {to}: {error}");
                    error
                })
                .ok()?;
            let message = format!("game {index} play defender {from} {to}");
            for spectator in game_light.spectators() {
                if let Some(client) = self.clients.get(&spectator) {
                    let _ok = client.send(message.clone());
                }
            }
            game.attacker_tx.send(message);
        } else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        }
        let mut game_over = false;
        game_light.turn = Role::Roleless;
        match game.game.status {
            Status::AttackerWins => {
                let accounts = &mut self.accounts.0;
                let (attacker_rating, defender_rating) = if let (Some(attacker), Some(defender)) =
                    (accounts.get(&game.attacker), accounts.get(&game.defender))
                {
                    (attacker.rating.rating, defender.rating.rating)
                } else {
                    unreachable!();
                };
                if let Some(attacker) = accounts.get_mut(&game.attacker) {
                    attacker.wins += 1;
                    if game.rated.into() {
                        attacker
                            .rating
                            .update_rating(defender_rating, &Outcome::Win);
                    }
                }
                if let Some(defender) = accounts.get_mut(&game.defender) {
                    defender.losses += 1;
                    if game.rated.into() {
                        defender
                            .rating
                            .update_rating(attacker_rating, &Outcome::Loss);
                    }
                }
                let message = format!("= game_over {index} attacker_wins");
                game.attacker_tx.send(message.clone());
                game.defender_tx.send(message.clone());
                for spectator in game_light.spectators() {
                    if let Some(sender) = self.clients.get(&spectator) {
                        let _ok = sender.send(message.clone());
                    }
                }
                game_over = true;
            }
            Status::Draw => {
                // Handled in the draw fn.
            }
            Status::Ongoing => {
                if attackers_turn_next {
                    game_light.turn = Role::Attacker;
                    game.attacker_tx
                        .send(format!("game {index} generate_move attacker"));
                } else {
                    game_light.turn = Role::Defender;
                    game.defender_tx
                        .send(format!("game {index} generate_move defender"));
                }
            }
            Status::DefenderWins => {
                let accounts = &mut self.accounts.0;
                let (attacker_rating, defender_rating) = if let (Some(attacker), Some(defender)) =
                    (accounts.get(&game.attacker), accounts.get(&game.defender))
                {
                    (attacker.rating.rating, defender.rating.rating)
                } else {
                    unreachable!();
                };
                if let Some(attacker) = accounts.get_mut(&game.attacker) {
                    attacker.losses += 1;
                    if game.rated.into() {
                        attacker
                            .rating
                            .update_rating(defender_rating, &Outcome::Loss);
                    }
                }
                if let Some(defender) = accounts.get_mut(&game.defender) {
                    defender.wins += 1;
                    if game.rated.into() {
                        defender
                            .rating
                            .update_rating(attacker_rating, &Outcome::Win);
                    }
                }
                let message = format!("= game_over {index} defender_wins");
                game.attacker_tx.send(message.clone());
                game.defender_tx.send(message.clone());
                for id in game_light.spectators() {
                    if let Some(sender) = self.clients.get(&id) {
                        let _ok = sender.send(message.clone());
                    }
                }
                game_over = true;
            }
        }
        if game_over {
            let Some(game) = self.games.0.remove(&index) else {
                unreachable!()
            };
            if let Some(game_light) = self.games_light.0.get_mut(&index) {
                game_light.game_over = true;
            }
            if let Some(tournament) = &mut self.tournament {
                if tournament.game_over(&game) {
                    self.generate_round(group_size);
                }
                self.tournament_status_all();
            }
            if !self.skip_the_data_files {
                self.append_archived_game(game)
                    .map_err(|err| {
                        error!("append_archived_game: {err}");
                    })
                    .ok()?;
            }
            return None;
        }
        Some((
            self.clients.get(&index_supplied)?.clone(),
            true,
            (*command).to_string(),
        ))
    }
    fn generate_round(&mut self, group_size: usize) {
        let mut round = None;
        if let Some(tournament) = &mut self.tournament {
            let groups = tournament.generate_round(&self.accounts, group_size);
            round = Some(groups);
        }
        let mut ids = VecDeque::new();
        let mut groups_arc_mutex = Vec::new();
        if let Some(groups) = round {
            for (i, mut group) in groups.into_iter().enumerate() {
                for combination in group.records.iter().map(|record| record.0).combinations(2) {
                    if let (Some(first), Some(second)) = (combination.first(), combination.get(1)) {
                        ids.push_back((self.new_tournament_game(first, second), i));
                        ids.push_back((self.new_tournament_game(second, first), i));
                        group.total_games += 2;
                    }
                }
                groups_arc_mutex.push(Arc::new(Mutex::new(group)));
            }
        }
        if !groups_arc_mutex.is_empty()
            && let Some(tournament) = &mut self.tournament
        {
            for (id, i) in ids {
                if let Some(group) = groups_arc_mutex.get(i) {
                    tournament.tournament_games.insert(id, group.clone());
                    tournament.tournament_games.insert(id, group.clone());
                }
            }
            if let Some(rounds) = &mut tournament.groups {
                rounds.push(groups_arc_mutex);
            }
        }
    }
    fn set_email(
        &mut self,
        index_supplied: usize,
        username: &str,
        command: &str,
        email: Option<&str>,
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        let Some(address) = email else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let Some(account) = self.accounts.0.get_mut(username) else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let random_u32 = random();
        let email = Email {
            address: address.to_string(),
            code: Some(random_u32),
            username: username.to_string(),
            verified: false,
        };
        info!("{index_supplied} {username} email {}", email.tx());
        let email_send = lettre::Message::builder()
            .from("Hnefatafl Org <noreply@hnefatafl.org>".parse().ok()?)
            .to(email.to_mailbox()?)
            .subject("Account Verification")
            .header(ContentType::TEXT_PLAIN)
            .body(format!(
                "Dear {username},\nyour email verification code is as follows: {random_u32:x}",
            ))
            .ok()?;
        let credentials = Credentials::new(self.smtp.username.clone(), self.smtp.password.clone());
        let mailer = SmtpTransport::relay(&self.smtp.service)
            .ok()?
            .credentials(credentials)
            .build();
        match mailer.send(&email_send) {
            Ok(_) => {
                info!("email sent to {address} successfully!");
                account.email = Some(email);
                let reply = format!("email {address} false");
                Some((self.clients.get(&index_supplied)?.clone(), true, reply))
            }
            Err(err) => {
                let reply = format!("could not send email to {address}");
                error!("{reply}: {err}");
                Some((self.clients.get(&index_supplied)?.clone(), false, reply))
            }
        }
    }
    fn handle_messages(
        &mut self,
        rx: &mpsc::Receiver<(String, Option<mpsc::Sender<String>>)>,
    ) -> anyhow::Result<()> {
        loop {
            let (message, option_tx) = rx.recv()?;
            if let Some((tx, ok, command)) = self.handle_messages_internal(&message, option_tx) {
                if ok {
                    tx.send(format!("= {command}"))?;
                } else {
                    tx.send(format!("? {command}"))?;
                }
            }
        }
    }
    #[allow(clippy::too_many_lines)]
26
    fn handle_messages_internal(
26
        &mut self,
26
        message: &str,
26
        option_tx: Option<Sender<String>>,
26
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
26
        let args = Args::parse();
26
        let index_username_command: Vec<_> = message.split_ascii_whitespace().collect();
26
        if let (Some(index_supplied), Some(username), Some(command)) = (
26
            index_username_command.first(),
26
            index_username_command.get(1),
26
            index_username_command.get(2),
        ) {
26
            let username = *username;
26
            if *command != "check_update_rd"
26
                && *command != "create_account"
16
                && *command != "display_server"
16
                && *command != "join_game_pending"
16
                && *command != "leave_game"
16
                && *command != "login"
14
                && *command != "logout"
12
                && *command != "ping"
12
                && *command != "resume_game"
            {
12
                debug!("{index_supplied} {username} {command}");
14
            }
26
            let index_supplied = index_supplied.parse::<usize>().ok()?;
26
            let the_rest: Vec<_> = index_username_command.clone().into_iter().skip(3).collect();
26
            match *command {
26
                "admin" => {
4
                    if self.admins.contains(username) {
2
                        self.clients
2
                            .get(&index_supplied)?
2
                            .send("= admin".to_string())
2
                            .ok()?;
2
                    }
4
                    None
                }
22
                "admin_tournament" => {
4
                    if self.admins_tournament.contains(username) {
2
                        self.clients
2
                            .get(&index_supplied)?
2
                            .send("= admin_tournament".to_string())
2
                            .ok()?;
2
                    }
4
                    None
                }
18
                "archived_games" => {
2
                    self.clients
2
                        .get(&index_supplied)?
2
                        .send("= archived_games".to_string())
2
                        .ok()?;
2
                    self.clients
2
                        .get(&index_supplied)?
2
                        .send(ron::ser::to_string(&self.archived_games).ok()?)
2
                        .ok()?;
2
                    None
                }
16
                "change_password" => {
2
                    self.change_password(username, index_supplied, command, the_rest.as_slice())
                }
14
                "check_update_rd" => {
                    let bool = self.check_update_rd();
                    info!("0 {username} check_update_rd {bool}");
                    None
                }
14
                "connection_add" => {
                    if let Some(address) = the_rest.first()
                        && let Some(tx) = option_tx
                    {
                        if let Some(connections) = self.connections.get(*address)
                            && *connections > 2_000
                        {
                            tx.send("true".to_string()).ok()?;
                        } else {
                            tx.send("false".to_string()).ok()?;
                            let entry = self.connections.entry(address.to_string());
                            entry.and_modify(|value| *value += 1).or_insert(1);
                        }
                    }
                    debug!("connections: {:?}", self.connections);
                    None
                }
14
                "connection_remove" => {
                    if let Some(connection) = the_rest.first() {
                        let entry = self.connections.entry(connection.to_string());
                        entry.and_modify(|value| *value = value.saturating_sub(1));
                        if let Some(value) = self.connections.get(*connection)
                            && *value == 0
                        {
                            self.connections.remove(*connection);
                        }
                    }
                    debug!("connections: {:?}", self.connections);
                    None
                }
14
                "create_account" => self.create_account(
10
                    username,
10
                    index_supplied,
10
                    command,
10
                    the_rest.as_slice(),
10
                    option_tx,
                ),
4
                "decline_game" => self.decline_game(
                    username,
                    index_supplied,
                    (*command).to_string(),
                    the_rest.as_slice(),
                ),
4
                "delete_account" => {
                    self.delete_account(username, index_supplied);
                    None
                }
4
                "delete_unused_accounts" => {
                    let now = Timestamp::now();
                    let mut accounts = Vec::new();
                    let mut playing = HashSet::new();
                    for game in self.games_light.0.values() {
                        if let Some(attacker) = &game.attacker {
                            playing.insert(attacker);
                        }
                        if let Some(defender) = &game.defender {
                            playing.insert(defender);
                        }
                    }
                    for (name, account) in &self.accounts.0 {
                        if account.wins == 0
                            && account.losses == 0
                            && account.draws == 0
                            && let Ok(timestamp) = now.checked_sub(DAYS_FOR_INACTIVE_ACCOUNT.day())
                            && timestamp > account.last_logged_in.0
                            && !playing.contains(name)
                        {
                            accounts.push(name.clone());
                        }
                    }
                    for name in &accounts {
                        info!("deleting {name}...");
                        self.accounts.0.remove(name);
                    }
                    None
                }
4
                "display_games" => {
                    if args.skip_advertising_updates {
                        None
                    } else {
                        self.clients.get(&index_supplied).map(|tx| {
                            (
                                tx.clone(),
                                true,
                                format!("display_games {:?}", &self.games_light_vec),
                            )
                        })
                    }
                }
4
                "display_server" => self.display_server(username),
4
                "draw" => self.draw(index_supplied, command, the_rest.as_slice()),
4
                "game" => self.game(
                    index_supplied,
                    username,
                    command,
                    the_rest.as_slice(),
                    args.group_size,
                ),
4
                "email" => {
                    self.set_email(index_supplied, username, command, the_rest.first().copied())
                }
4
                "email_everyone" => {
                    if self.admins.contains(username) {
                        info!("{index_supplied} {username} email_everyone");
                    } else {
                        error!("{index_supplied} {username} email_everyone");
                        return None;
                    }
                    let emails_bcc = self.bcc_mailboxes(username);
                    let subject = the_rest.first()?;
                    let email_string = the_rest.get(1..)?.join(" ").replace("\\n", "\n");
                    let mut email = lettre::Message::builder();
                    for email_bcc in emails_bcc {
                        email = email.bcc(email_bcc);
                    }
                    let email = email
                        .from("Hnefatafl Org <noreply@hnefatafl.org>".parse().ok()?)
                        .subject(*subject)
                        .header(ContentType::TEXT_PLAIN)
                        .body(email_string)
                        .ok()?;
                    let credentials =
                        Credentials::new(self.smtp.username.clone(), self.smtp.password.clone());
                    let mailer = SmtpTransport::relay(&self.smtp.service)
                        .ok()?
                        .credentials(credentials)
                        .build();
                    match mailer.send(&email) {
                        Ok(_) => {
                            info!("emails sent successfully!");
                            Some((
                                self.clients.get(&index_supplied)?.clone(),
                                true,
                                (*command).to_string(),
                            ))
                        }
                        Err(err) => {
                            let reply = "could not send emails";
                            error!("{reply}: {err}");
                            Some((
                                self.clients.get(&index_supplied)?.clone(),
                                false,
                                reply.to_string(),
                            ))
                        }
                    }
                }
4
                "emails_bcc" => {
                    let emails_bcc = self.bcc_send(username);
                    if !emails_bcc.is_empty() {
                        self.clients
                            .get(&index_supplied)?
                            .send(format!("= emails_bcc {emails_bcc}"))
                            .ok()?;
                    }
                    None
                }
4
                "email_code" => {
                    if let Some(account) = self.accounts.0.get_mut(username)
                        && let Some(email) = &mut account.email
                        && let (Some(code_1), Some(code_2)) = (email.code, the_rest.first())
                    {
                        if format!("{code_1:x}") == *code_2 {
                            email.verified = true;
                            self.clients
                                .get(&index_supplied)?
                                .send("= email_code".to_string())
                                .ok()?;
                        } else {
                            email.verified = false;
                            self.clients
                                .get(&index_supplied)?
                                .send("? email_code".to_string())
                                .ok()?;
                        }
                    }
                    None
                }
4
                "email_get" => {
                    if let Some(account) = self.accounts.0.get(username)
                        && let Some(email) = &account.email
                    {
                        self.clients
                            .get(&index_supplied)?
                            .send(format!("= email {} {}", email.address, email.verified))
                            .ok()?;
                    }
                    None
                }
4
                "email_reset" => {
                    if let Some(account) = self.accounts.0.get_mut(username) {
                        account.email = None;
                        Some((
                            self.clients.get(&index_supplied)?.clone(),
                            true,
                            (*command).to_string(),
                        ))
                    } else {
                        None
                    }
                }
4
                "exit" => {
                    info!("saving active games...");
                    let mut active_games = Vec::new();
                    for game in self.games.0.values() {
                        let mut serialized_game = ServerGameSerialized::from(game);
                        if let Some(game_light) = self.games_light.0.get(&game.id) {
                            serialized_game.timed = game_light.timed.clone();
                        }
                        active_games.push(serialized_game);
                    }
                    let mut file = handle_error(File::create(data_file(ACTIVE_GAMES_FILE)));
                    handle_error(
                        file.write_all(
                            handle_error(postcard::to_allocvec(&active_games)).as_slice(),
                        ),
                    );
                    exit(0);
                }
4
                "join_game" => self.join_game(
                    username,
                    index_supplied,
                    (*command).to_string(),
                    the_rest.as_slice(),
                ),
4
                "join_game_pending" => self.join_game_pending(
                    (*username).to_string(),
                    index_supplied,
                    (*command).to_string(),
                    the_rest.as_slice(),
                ),
4
                "join_tournament" => {
                    if let Some(tournament) = &mut self.tournament {
                        tournament.players.insert(username.to_string());
                        self.tournament_status_all();
                    }
                    None
                }
4
                "leave_game" => self.leave_game(
                    username,
                    index_supplied,
                    (*command).to_string(),
                    the_rest.as_slice(),
                ),
4
                "leave_tournament" => {
                    if let Some(tournament) = &mut self.tournament {
                        tournament.players.remove(username);
                        self.tournament_status_all();
                    }
                    None
                }
4
                "login" => self.login(
2
                    username,
2
                    index_supplied,
2
                    command,
2
                    the_rest.as_slice(),
2
                    option_tx,
                ),
2
                "logout" => self.logout(username, index_supplied, command),
                "new_game" => self.new_game(username, index_supplied, command, the_rest.as_slice()),
                "ping" => Some((
                    self.clients.get(&index_supplied)?.clone(),
                    true,
                    (*command).to_string(),
                )),
                "reset_password" => {
                    let account = self.accounts.0.get_mut(username)?;
                    if let Some(email) = &account.email {
                        if email.verified {
                            let day = 60 * 60 * 24;
                            let now = Timestamp::now().as_second();
                            if now - account.email_sent > day {
                                let password = format!("{:x}", random::<u32>());
                                account.password = hash_password(&password)?;
                                let message = lettre::Message::builder()
                                .from("Hnefatafl Org <noreply@hnefatafl.org>".parse().ok()?)
                                .to(email.to_mailbox()?)
                                .subject("Password Reset")
                                .header(ContentType::TEXT_PLAIN)
                                .body(format!(
                                    "Dear {username},\nyour new password is as follows: {password}",
                                ))
                                .ok()?;
                                let credentials = Credentials::new(
                                    self.smtp.username.clone(),
                                    self.smtp.password.clone(),
                                );
                                let mailer = SmtpTransport::relay(&self.smtp.service)
                                    .ok()?
                                    .credentials(credentials)
                                    .build();
                                match mailer.send(&message) {
                                    Ok(_) => {
                                        info!("email sent to {} successfully!", email.address);
                                        account.email_sent = now;
                                    }
                                    Err(err) => {
                                        error!("could not send email to {}: {err}", email.address);
                                    }
                                }
                            }
                            {
                                error!(
                                    "a password reset email was sent less than a day ago for {username}"
                                );
                            }
                        } else {
                            error!("the email address for account {username} is unverified");
                        }
                    } else {
                        error!("no email exists for account {username}");
                    }
                    None
                }
                "resume_game" => self.resume_game(username, index_supplied, command, &the_rest),
                "request_draw" => self.request_draw(username, index_supplied, command, &the_rest),
                "save" => {
                    debug!("saving users file...");
                    self.save_server();
                    None
                }
                "text" => {
                    let timestamp = timestamp();
                    let the_rest = the_rest.join(" ");
                    info!("{index_supplied} {timestamp} {username} text {the_rest}");
                    let text = format!("= text {timestamp} {username}: {the_rest}");
                    if self.texts.len() >= KEEP_TEXTS {
                        self.texts.pop_front();
                    }
                    for tx in &mut self.clients.values() {
                        let _ok = tx.send(text.clone());
                    }
                    self.texts.push_back(text);
                    None
                }
                "texts" => {
                    if !self.texts.is_empty() {
                        let string = Vec::from(self.texts.clone()).join("\n");
                        self.clients.get(&index_supplied)?.send(string).ok()?;
                    }
                    None
                }
                "text_game" => self.text_game(username, index_supplied, command, the_rest),
                "tournament_delete" => {
                    if self.admins_tournament.contains(username) {
                        self.tournament = None;
                        self.tournament_status_all();
                    }
                    None
                }
                "tournament_groups_delete" => {
                    if self.admins_tournament.contains(username)
                        && let Some(tournament) = &mut self.tournament
                    {
                        tournament.groups = None;
                        tournament.tournament_games = HashMap::new();
                        self.tournament_status_all();
                    }
                    None
                }
                "tournament_date" => {
                    if self.admins_tournament.contains(username) {
                        if let Err(error) = self.tournament_date(&the_rest) {
                            error!("tournament_date: {error}");
                        } else {
                            self.tournament_status_all();
                        }
                    }
                    None
                }
                "tournament_status_0" => {
                    trace!("tournament_status: {:#?}", self.tournament);
                    if args.skip_advertising_updates {
                        None
                    } else {
                        let tx = self.clients.get(&index_supplied)?;
                        let tournament = ron::ser::to_string(&self.tournament).ok()?;
                        Some((
                            tx.clone(),
                            true,
                            format!("tournament_status_0 {tournament}"),
                        ))
                    }
                }
                "tournament_start" => {
                    if self.admins_tournament.contains(username)
                        && let Some(tournament) = &mut self.tournament
                        && tournament.groups.is_none()
                        && Timestamp::now() >= tournament.date
                    {
                        info!("Starting tournament...");
                        tournament.groups = Some(Vec::new());
                        tournament.players_left = tournament.players.clone();
                        self.generate_round(args.group_size);
                        self.tournament_status_all();
                    }
                    None
                }
                "watch_game" => self.watch_game(
                    username,
                    index_supplied,
                    (*command).to_string(),
                    the_rest.as_slice(),
                ),
                "=" => None,
                _ => self.clients.get(&index_supplied).map(|channel| {
                    error!("{index_supplied} {username} {command}");
                    (channel.clone(), false, (*command).to_string())
                }),
            }
        } else {
            error!("{index_username_command:?}");
            None
        }
26
    }
    fn join_game(
        &mut self,
        username: &str,
        index_supplied: usize,
        command: String,
        the_rest: &[&str],
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        let Some(id) = the_rest.first() else {
            return Some((self.clients.get(&index_supplied)?.clone(), false, command));
        };
        let Ok(id) = id.parse::<Id>() else {
            return Some((self.clients.get(&index_supplied)?.clone(), false, command));
        };
        info!("{index_supplied} {username} join_game {id}");
        let Some(game) = self.games_light.0.get_mut(&id) else {
            unreachable!();
        };
        game.challenge_accepted = true;
        game.turn = Role::Attacker;
        if let Some(account) = self.accounts.0.get(game.attacker.as_ref()?)
            && let Some(id) = account.logged_in
        {
            game.spectators.insert(game.attacker.clone()?, id);
        }
        if let Some(account) = self.accounts.0.get(game.defender.as_ref()?)
            && let Some(id) = account.logged_in
        {
            game.spectators.insert(game.defender.clone()?, id);
        }
        let (Some(attacker), Some(defender)) = (&game.attacker, &game.defender) else {
            unreachable!();
        };
        let (Some(attacker_account), Some(defender_account)) =
            (self.accounts.0.get(attacker), self.accounts.0.get(defender))
        else {
            unreachable!()
        };
        let mut attacker_channel = None;
        if let Some(channel_id) = attacker_account.logged_in
            && let Some(channel) = self.clients.get(&channel_id)
        {
            attacker_channel = Some(channel);
        }
        let mut defender_channel = None;
        if let Some(channel_id) = defender_account.logged_in
            && let Some(channel) = self.clients.get(&channel_id)
        {
            defender_channel = Some(channel);
        }
        for channel in [&attacker_channel, &defender_channel].into_iter().flatten() {
            channel
                .send(format!(
                    "= join_game {} {} {} {:?} {}",
                    game.attacker.clone()?,
                    game.defender.clone()?,
                    game.rated,
                    game.timed,
                    game.board_size,
                ))
                .ok()?;
        }
        let new_game = ServerGame::new(
            attacker_channel.cloned(),
            defender_channel.cloned(),
            game.clone(),
        );
        self.games.0.insert(id, new_game);
        if let Some(account) = self.accounts.0.get_mut(username) {
            account.pending_games.remove(&id);
        }
        if let Some(channel) = attacker_channel {
            channel
                .send(format!("game {id} generate_move attacker"))
                .ok()?;
        }
        None
    }
    fn join_game_pending(
        &mut self,
        username: String,
        index_supplied: usize,
        mut command: String,
        the_rest: &[&str],
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        let channel = self.clients.get(&index_supplied)?;
        let Some(id) = the_rest.first() else {
            return Some((channel.clone(), false, command));
        };
        let Ok(id) = id.parse::<Id>() else {
            return Some((channel.clone(), false, command));
        };
        info!("{index_supplied} {username} join_game_pending {id}");
        let Some(game) = self.games_light.0.get_mut(&id) else {
            command.push_str(" the id doesn't refer to a pending game");
            return Some((channel.clone(), false, command));
        };
        if game.attacker.is_none() {
            game.attacker = Some(username.clone());
            if let Some(defender) = &game.defender
                && let Some(account) = self.accounts.0.get(defender)
                && let Some(channel_id) = account.logged_in
                && let Some(channel) = self.clients.get(&channel_id)
            {
                let _ok = channel.send(format!("= challenge_requested {id}"));
            }
        } else if game.defender.is_none() {
            game.defender = Some(username.clone());
            if let Some(attacker) = &game.attacker
                && let Some(account) = self.accounts.0.get(attacker)
                && let Some(channel_id) = account.logged_in
                && let Some(channel) = self.clients.get(&channel_id)
            {
                let _ok = channel.send(format!("= challenge_requested {id}"));
            }
        }
        game.challenger.0 = Some(username);
        command.push(' ');
        command.push_str(the_rest.first()?);
        Some((channel.clone(), true, command))
    }
    fn leave_game(
        &mut self,
        username: &str,
        index_supplied: usize,
        mut command: String,
        the_rest: &[&str],
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        let Some(id) = the_rest.first() else {
            return Some((self.clients.get(&index_supplied)?.clone(), false, command));
        };
        let Ok(id) = id.parse::<Id>() else {
            return Some((self.clients.get(&index_supplied)?.clone(), false, command));
        };
        if let Some(account) = self.accounts.0.get_mut(username) {
            account.pending_games.remove(&id);
        }
        info!("{index_supplied} {username} leave_game {id}");
        let mut remove = false;
        match self.games_light.0.get_mut(&id) {
            Some(game) => {
                if !game.challenge_accepted {
                    if let Some(attacker) = &game.attacker
                        && username == attacker
                    {
                        game.attacker = None;
                    }
                    if let Some(defender) = &game.defender
                        && username == defender
                    {
                        game.defender = None;
                    }
                    if let Some(challenger) = &game.challenger.0
                        && username == challenger
                    {
                        game.challenger.0 = None;
                    }
                }
                game.spectators.remove(username);
                if game.attacker.is_none() && game.defender.is_none() {
                    remove = true;
                }
            }
            None => return Some((self.clients.get(&index_supplied)?.clone(), false, command)),
        }
        if remove {
            self.games_light.0.remove(&id);
        }
        command.push(' ');
        command.push_str(the_rest.first()?);
        Some((self.clients.get(&index_supplied)?.clone(), true, command))
    }
2
    fn login(
2
        &mut self,
2
        username: &str,
2
        index_supplied: usize,
2
        command: &str,
2
        the_rest: &[&str],
2
        option_tx: Option<Sender<String>>,
2
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
2
        let password_1 = the_rest.join(" ");
2
        let tx = option_tx?;
2
        if let Some(account) = self.accounts.0.get_mut(username) {
            // The username is in the database and already logged in.
2
            if let Some(index_database) = account.logged_in {
                error!("{index_supplied} {username} login failed, {index_database} is logged in");
                Some(((tx), false, (*command).to_string()))
            // The username is in the database, but not logged in yet.
            } else {
2
                let hash_2 = PasswordHash::try_from(account.password.as_str()).ok()?;
                if let Err(error) =
2
                    Argon2::default().verify_password(password_1.as_bytes(), &hash_2)
                {
                    error!("{index_supplied} {username} provided the wrong password: {error}");
                    return Some((tx, false, (*command).to_string()));
2
                }
2
                self.clients.insert(index_supplied, tx);
2
                account.logged_in = Some(index_supplied);
2
                account.last_logged_in = DateTimeUtc(Timestamp::now());
                Some((
2
                    self.clients.get(&index_supplied)?.clone(),
                    true,
2
                    (*command).to_string(),
                ))
            }
        } else {
            error!("{index_supplied} {username} is not in the database");
            Some((tx, false, (*command).to_string()))
        }
2
    }
    fn load_data_files(
        &mut self,
        tx: Sender<(String, Option<Sender<String>>)>,
        systemd: bool,
    ) -> anyhow::Result<()> {
        let users_file = data_file(USERS_FILE);
        match &fs::read_to_string(&users_file) {
            Ok(string) => match ron::from_str(string.as_str()) {
                Ok(server_ron) => {
                    *self = server_ron;
                    self.tx = Some(tx.clone());
                    if let Some(tournament) = &mut self.tournament {
                        tournament.remove_duplicate_ids();
                    }
                    self.admins_tournament.insert("server".to_string());
                }
                Err(err) => {
                    return Err(anyhow::Error::msg(format!(
                        "RON: {}: {err}",
                        users_file.display(),
                    )));
                }
            },
            Err(err) => match err.kind() {
                ErrorKind::NotFound => {}
                _ => return Err(anyhow::Error::msg(err.to_string())),
            },
        }
        let archived_games_file = data_file(ARCHIVED_GAMES_FILE);
        match fs::read_to_string(&archived_games_file) {
            Ok(archived_games_string) => {
                let mut archived_games = Vec::new();
                for line in archived_games_string.lines() {
                    let archived_game: ArchivedGame = match ron::from_str(line) {
                        Ok(archived_game) => archived_game,
                        Err(err) => {
                            return Err(anyhow::Error::msg(format!(
                                "RON: {}: {err}",
                                archived_games_file.display(),
                            )));
                        }
                    };
                    archived_games.push(archived_game);
                }
                self.archived_games = archived_games;
            }
            Err(err) => {
                error!("archived games file not found: {err}");
            }
        }
        let active_games_file = data_file(ACTIVE_GAMES_FILE);
        if fs::exists(&active_games_file)? {
            let mut file = File::open(active_games_file)?;
            let mut data = Vec::new();
            file.read_to_end(&mut data)?;
            let games: Vec<ServerGameSerialized> = postcard::from_bytes(data.as_slice())?;
            for game in games {
                let id = game.id;
                let server_game_light = ServerGameLight::from(&game);
                let server_game = ServerGame::from(game);
                self.games_light.0.insert(id, server_game_light);
                self.games.0.insert(id, server_game);
            }
        }
        ctrlc::set_handler(move || {
            if !systemd {
                println!();
            }
            handle_error(tx.send(("0 server save".to_string(), None)));
            handle_error(tx.send(("0 server exit".to_string(), None)));
        })?;
        Ok(())
    }
2
    fn logout(
2
        &mut self,
2
        username: &str,
2
        index_supplied: usize,
2
        command: &str,
2
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        // The username is in the database and already logged in.
2
        if let Some(account) = self.accounts.0.get_mut(username) {
2
            for id in &account.pending_games {
                if let Some(tx) = &self.tx
                    && let Some(game) = self.games_light.0.get(id)
                    && let TimeSettings::Timed(Time {
                        milliseconds_left, ..
                    }) = game.timed
                    && milliseconds_left < 1_000 * 60 * 60 * 24
                {
                    let _ok =
                        tx.send((format!("{index_supplied} {username} leave_game {id}"), None));
                }
            }
2
            if let Some(index_database) = account.logged_in
2
                && index_database == index_supplied
            {
2
                account.logged_in = None;
2
                account.last_logged_in = DateTimeUtc(Timestamp::now());
2
                self.clients
2
                    .get(&index_supplied)?
2
                    .send("= logout".to_string())
2
                    .ok()?;
2
                self.clients.remove(&index_database);
2
                return None;
            }
        }
        self.clients
            .get(&index_supplied)
            .map(|sender| (sender.clone(), false, (*command).to_string()))
2
    }
    /// ```sh
    /// <- new_game attacker rated fischer 900000 10 13
    /// -> = new_game game 6 player-1 _ rated fischer 900000 10 _ false {}
    /// ```
    fn new_game(
        &mut self,
        username: &str,
        index_supplied: usize,
        command: &str,
        the_rest: &[&str],
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        if the_rest.len() < 6 {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        }
        let role = the_rest.first()?;
        let Ok(role) = Role::from_str(role) else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let rated = the_rest.get(1)?;
        let Ok(rated) = Rated::from_str(rated) else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let timed = the_rest.get(2)?;
        let minutes = the_rest.get(3)?;
        let add_seconds = the_rest.get(4)?;
        let Ok(timed) = TimeSettings::try_from(vec!["time-settings", timed, minutes, add_seconds])
        else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let board_size = the_rest.get(5)?;
        let board_size = BoardSize::from_str(board_size).ok()?;
        info!(
            "{index_supplied} {username} new_game {} {role} {rated} {timed:?} {board_size}",
            self.game_id
        );
        let game = ServerGameLight::new(
            self.game_id,
            (*username).to_string(),
            rated,
            timed,
            board_size,
            role,
        );
        let command = format!("{command} {game:?}");
        self.games_light.0.insert(self.game_id, game);
        if let Some(account) = self.accounts.0.get_mut(username) {
            account.pending_games.insert(self.game_id);
        }
        self.game_id += 1;
        Some((self.clients.get(&index_supplied)?.clone(), true, command))
    }
    fn new_tournament(tx: Sender<(String, Option<Sender<String>>)>) {
        thread::spawn(move || {
            handle_error(tx.send(("0 server tournament_start".to_string(), None)));
            loop {
                let now = Zoned::now().with_time_zone(jiff::tz::TimeZone::UTC);
                match Zoned::now()
                    .with_time_zone(jiff::tz::TimeZone::UTC)
                    .end_of_day()
                {
                    Ok(midnight) => {
                        let duration = now.duration_until(&midnight);
                        debug!("midnight: {midnight}");
                        debug!("seconds until midnight: {}", duration.as_secs());
                        match duration.try_into() {
                            Ok(mut duration) => {
                                duration += Duration::from_secs(2);
                                sleep(duration);
                            }
                            Err(error) => {
                                error!("new_tournament (1): {error}");
                                exit(1);
                            }
                        }
                    }
                    Err(error) => {
                        error!("new_tournament (2): {error}");
                        exit(1);
                    }
                }
                handle_error(tx.send(("0 server tournament_start".to_string(), None)));
            }
        });
    }
    #[must_use]
    fn new_tournament_game(&mut self, attacker: &str, defender: &str) -> Id {
        let id = self.game_id;
        self.game_id += 1;
        let game_light = ServerGameLight {
            id,
            attacker: Some(attacker.to_string()),
            defender: Some(defender.to_string()),
            challenger: Challenger(None),
            rated: Rated::Yes,
            timed: TimeEnum::Long.into(),
            spectators: HashMap::new(),
            challenge_accepted: true,
            game_over: false,
            board_size: BoardSize::_11,
            turn: Role::Attacker,
        };
        info!(
            "0 server new_tournament_game {id} {} {:?} {}",
            game_light.rated, game_light.timed, game_light.board_size
        );
        let game = ServerGame::new(None, None, game_light.clone());
        self.games_light.0.insert(id, game_light);
        self.games.0.insert(id, game);
        id
    }
    fn resume_game(
        &mut self,
        username: &str,
        index_supplied: usize,
        command: &str,
        the_rest: &[&str],
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        let Some(id) = the_rest.first() else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let Ok(id) = id.parse::<Id>() else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let Some(server_game) = self.games.0.get(&id) else {
            unreachable!()
        };
        let game = &server_game.game;
        let Ok(board) = ron::ser::to_string(game) else {
            unreachable!()
        };
        let texts = &server_game.texts;
        let Ok(texts) = ron::ser::to_string(&texts) else {
            unreachable!()
        };
        info!("{index_supplied} {username} {command} {id}");
        let Some(game_light) = self.games_light.0.get_mut(&id) else {
            unreachable!();
        };
        let mut channel_id = 0;
        if let Some(account) = self.accounts.0.get(username)
            && let Some(id) = account.logged_in
        {
            channel_id = id;
        }
        game_light
            .spectators
            .insert(username.to_string(), channel_id);
        let mut request_draw = Role::Roleless;
        if let Some(server_game) = self.games.0.get_mut(&id) {
            if Some(username) == game_light.attacker.as_deref() {
                server_game.attacker_tx =
                    Messenger::new(self.clients.get(&index_supplied)?.clone());
                if server_game.draw_requested == Role::Defender {
                    request_draw = Role::Attacker;
                }
            } else if Some(username) == game_light.defender.as_deref() {
                server_game.defender_tx =
                    Messenger::new(self.clients.get(&index_supplied)?.clone());
                if server_game.draw_requested == Role::Attacker {
                    request_draw = Role::Defender;
                }
            }
        }
        let client = self.clients.get(&index_supplied)?;
        client
            .send(format!(
                "= resume_game {} {} {} {:?} {} {board} {texts}",
                game_light.attacker.clone()?,
                game_light.defender.clone()?,
                game_light.rated,
                game_light.timed,
                game_light.board_size,
            ))
            .ok()?;
        if request_draw != Role::Roleless {
            client
                .send(format!("request_draw {} {request_draw}", game_light.id))
                .ok()?;
        }
        None
    }
    fn request_draw(
        &mut self,
        username: &str,
        index_supplied: usize,
        command: &str,
        the_rest: &[&str],
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        let Some(id) = the_rest.first() else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let Ok(id) = id.parse::<Id>() else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let Some(role) = the_rest.get(1) else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let Ok(role) = Role::from_str(role) else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        info!("{index_supplied} {username} request_draw {id} {role}");
        if let Some(server_game) = self.games.0.get_mut(&id) {
            server_game.draw_requested = role;
        }
        let message = format!("request_draw {id} {role}");
        if let Some(game) = self.games.0.get(&id) {
            match role {
                Role::Attacker => {
                    game.defender_tx.send(message);
                }
                Role::Roleless => {}
                Role::Defender => {
                    game.attacker_tx.send(message);
                }
            }
        }
        Some((
            self.clients.get(&index_supplied)?.clone(),
            true,
            (*command).to_string(),
        ))
    }
    fn save(tx: Sender<(String, Option<Sender<String>>)>) {
        thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(HOUR_IN_SECONDS));
                handle_error(tx.send(("0 server save".to_string(), None)));
            }
        });
    }
    fn save_server(&self) {
        if !self.skip_the_data_files {
            let mut server = self.clone();
            for account in server.accounts.0.values_mut() {
                account.logged_in = None;
            }
            match ron::ser::to_string_pretty(&server, ron::ser::PrettyConfig::default()) {
                Ok(string) => {
                    if !string.trim().is_empty() {
                        let users_file = data_file(USERS_FILE);
                        match File::create(&users_file) {
                            Ok(mut file) => {
                                if let Err(error) = file.write_all(string.as_bytes()) {
                                    error!("save file (3): {error}");
                                }
                            }
                            Err(error) => error!("save file (2): {error}"),
                        }
                    }
                }
                Err(error) => error!("save file (1): {error}"),
            }
        }
    }
    fn sort_games_light(&mut self) {
        let mut games: Vec<_> = self
            .games_light
            .0
            .values()
            .map(|game| {
                let mut rating_1 = 0.0;
                let mut rating_2 = 0.0;
                if let Some(attacker) = &game.attacker
                    && let Some(account) = self.accounts.0.get(attacker)
                {
                    rating_1 = account.rating.rating;
                }
                if let Some(defender) = &game.defender
                    && let Some(account) = self.accounts.0.get(defender)
                {
                    rating_2 = account.rating.rating;
                    if rating_2 > rating_1 {
                        std::mem::swap(&mut rating_1, &mut rating_2);
                    }
                }
                (game, rating_1, rating_2)
            })
            .collect();
        games.sort_by(|a, b| b.2.total_cmp(&a.2));
        games.sort_by(|a, b| b.1.total_cmp(&a.1));
        self.games_light_vec =
            ServerGamesLightVec(games.iter().map(|(game, _, _)| (*game).clone()).collect());
    }
    fn text_game(
        &mut self,
        username: &str,
        index_supplied: usize,
        command: &str,
        mut the_rest: Vec<&str>,
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        let Some(id) = the_rest.first() else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let Ok(id) = id.parse::<Id>() else {
            return Some((
                self.clients.get(&index_supplied)?.clone(),
                false,
                (*command).to_string(),
            ));
        };
        let timestamp = timestamp();
        let text = the_rest.split_off(1);
        let mut text = text.join(" ");
        text = format!("{timestamp} {username}: {text}");
        info!("{index_supplied} {username} text_game {id} {text}");
        if let Some(game) = self.games.0.get_mut(&id) {
            game.texts.push_front(text.clone());
        }
        text = format!("= text_game {text}");
        if let Some(game) = self.games_light.0.get(&id) {
            for index in game.spectators.values() {
                if let Some(sender) = self.clients.get(index) {
                    let _ok = sender.send(text.clone());
                }
            }
        }
        None
    }
    fn tournament_date(&mut self, the_rest: &[&str]) -> anyhow::Result<()> {
        let mut tournament = Tournament::default();
        let Some(date) = the_rest.first() else {
            return Err(anyhow::Error::msg("tournament_date: date is empty"));
        };
        tournament.date = match date.parse() {
            Ok(timestamp) => timestamp,
            Err(error) => return Err(anyhow::Error::msg(format!("tournament_date: {error}"))),
        };
        self.tournament = Some(tournament);
        Ok(())
    }
    fn tournament_status_all(&self) {
        trace!("tournament_status: {:#?}", self.tournament);
        if let Ok(mut tournament) = ron::ser::to_string(&self.tournament) {
            tournament = format!("= tournament_status_0 {tournament}");
            for tx in self.clients.values() {
                let _ok = tx.send(tournament.clone());
            }
        }
    }
    fn watch_game(
        &mut self,
        username: &str,
        index_supplied: usize,
        command: String,
        the_rest: &[&str],
    ) -> Option<(mpsc::Sender<String>, bool, String)> {
        let Some(id) = the_rest.first() else {
            return Some((self.clients.get(&index_supplied)?.clone(), false, command));
        };
        let Ok(id) = id.parse::<Id>() else {
            return Some((self.clients.get(&index_supplied)?.clone(), false, command));
        };
        if let Some(game) = self.games_light.0.get_mut(&id) {
            game.spectators.insert(username.to_string(), index_supplied);
        }
        let Some(server_game) = self.games.0.get(&id) else {
            unreachable!()
        };
        let game = &server_game.game;
        let Ok(board) = ron::ser::to_string(game) else {
            unreachable!()
        };
        let texts = &server_game.texts;
        let Ok(texts) = ron::ser::to_string(&texts) else {
            unreachable!()
        };
        info!("{index_supplied} {username} watch_game {id}");
        let Some(game) = self.games_light.0.get_mut(&id) else {
            unreachable!()
        };
        self.clients
            .get(&index_supplied)?
            .send(format!(
                "= watch_game {} {} {} {:?} {} {board} {texts}",
                game.attacker.clone()?,
                game.defender.clone()?,
                game.rated,
                game.timed,
                game.board_size,
            ))
            .ok()?;
        None
    }
}