1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use std::str::Utf8Error;

use serde_json::Value;
use thiserror::Error;

/// Main errors types that can be raised while using the API.
#[derive(Error, Debug)]
pub enum Error {
    #[error("Tungstenite error: {0}")]
    Websocket(#[from] tungstenite::Error),
    #[error("Url error: {0}")]
    Url(#[from] url::ParseError),
    #[error("Decompress error: {0}")]
    Decompress(#[from] flate2::DecompressError),
    #[error("JSON utf8 error: {0}")]
    Utf8(#[from] Utf8Error),
    #[error("JSON error: {0}")]
    JSON(#[from] serde_json::Error),
    #[error("Blocking error: {0}")]
    Blocking(#[from] BlockingError),
    #[error("Failed to login (bad username, password or cookie).")]
    LoginFailed,
}

/// Errors that will block the game from processing normally. Since each read
/// of the websocket requires an expected "end of read", this is a list of
/// unexpected data that would prevent expected results from being sent.
///
/// # Example
///
/// When picking up an item (i.e. ","), normally a "input_mode" with a "mode" of 1
/// would be received, but if there is more than one item where the character
/// is standing a "menu" with a "pickup" tag will instead be sent. Since this
/// is unexpected, `dcss-api` will send a "Pickup" BlockingError.
#[derive(Error, Debug)]
pub enum BlockingError {
    #[error("Custom seed selection menu.")]
    SeedSelection,
    #[error("New game choice selection menu.")]
    NewGameChoice,
    #[error("Blocking due to 'more' message.")]
    More,
    #[error("Blocking due to text input necessary from user (likely for level up message).")]
    TextInput,
    #[error("Blocking due to a pickup menu popup.")]
    Pickup,
    #[error("Blocking due to a 'acquirement' menu popup.")]
    Acquirement,
    #[error("Blocking due to a 'identify' menu popup.")]
    Identify,
    #[error("Blocking due to a 'enchant weapon' menu popup.")]
    EnchantWeapon,
    #[error("Blocking due to a 'brand item' menu popup.")]
    EnchantItem,
    #[error("Blocking due to a 'brand weapon' menu popup.")]
    BrandWeapon,
    #[error("Blocking due to a 'blink' action.")]
    Blink,
    #[error("Character died.")]
    Died,
}

/// This function will "pre-process" each received message and return an
/// error if a BlockingError type message is received, through various
/// message types received by the DCSS webtile.
///
/// # Arguments
///
/// * `message` - The message (as a [serde_json::Value]) received by the
/// DCSS webtile.
pub(crate) fn blocking_messages(message: &Value) -> Result<(), Error> {
    let msg = message["msg"].as_str().unwrap();

    match msg {
        "input_mode" => {
            if message["mode"].as_u64().unwrap() == 5 {
                Err(Error::Blocking(BlockingError::More))
            } else if message["mode"].as_u64().unwrap() == 7 {
                Err(Error::Blocking(BlockingError::TextInput))
            } else {
                Ok(())
            }
        }
        "menu" => {
            if message["tag"] == "pickup" {
                Err(Error::Blocking(BlockingError::Pickup))
            } else if message["tag"] == "acquirement" {
                Err(Error::Blocking(BlockingError::Acquirement))
            } else if message["tag"] == "use_item" {
                match message["title"]["text"].as_str().unwrap() {
                    x if x.contains("Identify which item?") => {
                        Err(Error::Blocking(BlockingError::Identify))
                    }
                    x if x.contains("Enchant which weapon?") => {
                        Err(Error::Blocking(BlockingError::EnchantWeapon))
                    }
                    x if x.contains("Enchant which item?") => {
                        Err(Error::Blocking(BlockingError::EnchantItem))
                    }
                    x if x.contains("Brand which weapon?") => {
                        Err(Error::Blocking(BlockingError::BrandWeapon))
                    }
                    _ => Ok(()),
                }
            } else {
                Ok(())
            }
        }
        "msgs" => {
            if !message.as_object().unwrap().contains_key("messages") {
                Ok(())
            } else {
                for text_obj in message["messages"].as_array().unwrap() {
                    let text = text_obj["text"].as_str().unwrap();

                    if text.contains("You die...") {
                        return Err(Error::Blocking(BlockingError::Died));
                    }

                    if text.contains("Blink to where?") {
                        return Err(Error::Blocking(BlockingError::Blink));
                    }
                }
                Ok(())
            }
        }
        "login_fail" => Err(Error::LoginFailed),
        "ui-push" => {
            if !message.as_object().unwrap().contains_key("type") {
                Ok(())
            } else {
                if message["type"] == "seed-selection" {
                    return Err(Error::Blocking(BlockingError::SeedSelection));
                } else if message["type"] == "newgame-choice" {
                    return Err(Error::Blocking(BlockingError::NewGameChoice));
                }
                Ok(())
            }
        }
        _ => Ok(()),
    }
}