dcss_api/
api_errors.rs

1use std::str::Utf8Error;
2
3use serde_json::Value;
4use thiserror::Error;
5
6/// Main errors types that can be raised while using the API.
7#[derive(Error, Debug)]
8pub enum Error {
9    #[error("Tungstenite error: {0}")]
10    Websocket(#[from] tungstenite::Error),
11    #[error("Decompress error: {0}")]
12    Decompress(#[from] flate2::DecompressError),
13    #[error("JSON utf8 error: {0}")]
14    Utf8(#[from] Utf8Error),
15    #[error("JSON error: {0}")]
16    JSON(#[from] serde_json::Error),
17    #[error("Blocking error: {0}")]
18    Blocking(#[from] BlockingError),
19    #[error("Failed to login (bad username, password or cookie).")]
20    LoginFailed,
21    #[error("Failed to register.")]
22    RegisterFailed,
23}
24
25/// Errors that will block the game from processing normally. Since each read
26/// of the websocket requires an expected "end of read", this is a list of
27/// unexpected data that would prevent expected results from being sent.
28///
29/// # Example
30///
31/// When picking up an item (i.e. ","), normally a "input_mode" with a "mode" of 1
32/// would be received, but if there is more than one item where the character
33/// is standing a "menu" with a "pickup" tag will instead be sent. Since this
34/// is unexpected, `dcss-api` will send a "Pickup" BlockingError.
35#[derive(Error, Debug)]
36pub enum BlockingError {
37    #[error("Custom seed selection menu.")]
38    SeedSelection,
39    #[error("New game choice selection menu.")]
40    NewGameChoice,
41    #[error("Blocking due to 'more' message.")]
42    More,
43    #[error("Blocking due to text input necessary from user (likely for level up message).")]
44    TextInput,
45    #[error("Blocking due to a pickup menu popup.")]
46    Pickup,
47    #[error("Blocking due to a 'acquirement' menu popup.")]
48    Acquirement(Value),
49    #[error("Blocking due to a 'identify' menu popup.")]
50    Identify(Value),
51    #[error("Blocking due to a 'enchant weapon' menu popup.")]
52    EnchantWeapon(Value),
53    #[error("Blocking due to a 'brand item' menu popup.")]
54    EnchantItem(Value),
55    #[error("Blocking due to a 'brand weapon' menu popup.")]
56    BrandWeapon(Value),
57    #[error("Blocking due to a 'skills to train' txt menu.")]
58    Skill,
59    #[error("Blocking due to a 'blink' action.")]
60    Blink,
61    #[error("Blocking due to an 'equipping' action.")]
62    Equipping,
63    #[error("Blocking due to an 'disrobing' action.")]
64    Disrobing,
65    #[error("Blocking due to a 'scroll of noise' read prompt.")]
66    Noise,
67    #[error("Character died.")]
68    Died,
69}
70
71/// This function will "pre-process" each received message and return an
72/// error if a BlockingError type message is received, through various
73/// message types received by the DCSS webtile.
74///
75/// # Arguments
76///
77/// * `message` - The message (as a [serde_json::Value]) received by the
78///   DCSS webtile.
79pub(crate) fn blocking_messages(message: &Value) -> Result<(), Error> {
80    let msg = message["msg"].as_str().unwrap();
81
82    match msg {
83        "input_mode" => {
84            if message["mode"].as_u64().unwrap() == 5 {
85                Err(Error::Blocking(BlockingError::More))
86            } else if message["mode"].as_u64().unwrap() == 7 {
87                Err(Error::Blocking(BlockingError::TextInput))
88            } else {
89                Ok(())
90            }
91        }
92        "menu" => {
93            if message["tag"] == "pickup" {
94                Err(Error::Blocking(BlockingError::Pickup))
95            } else if message["tag"] == "acquirement" {
96                Err(Error::Blocking(BlockingError::Acquirement(message.clone())))
97            } else if message["tag"] == "use_item" {
98                match message["title"]["text"].as_str().unwrap() {
99                    x if x.contains("Identify which item?") => {
100                        Err(Error::Blocking(BlockingError::Identify(message.clone())))
101                    }
102                    x if x.contains("Enchant which weapon?") => Err(Error::Blocking(
103                        BlockingError::EnchantWeapon(message.clone()),
104                    )),
105                    x if x.contains("Enchant which item?") => {
106                        Err(Error::Blocking(BlockingError::EnchantItem(message.clone())))
107                    }
108                    x if x.contains("Brand which weapon?") => {
109                        Err(Error::Blocking(BlockingError::BrandWeapon(message.clone())))
110                    }
111                    _ => Ok(()),
112                }
113            } else {
114                Ok(())
115            }
116        }
117        "txt" => {
118            let lines_obj = message.as_object().unwrap()["lines"].as_object().unwrap();
119
120            if lines_obj.contains_key("0")
121                && lines_obj[&("0".to_string())]
122                    .as_str()
123                    .unwrap()
124                    .to_owned()
125                    .contains("Select the skills to train")
126            {
127                return Err(Error::Blocking(BlockingError::Skill));
128            }
129
130            Ok(())
131        }
132        "msgs" => {
133            if !message.as_object().unwrap().contains_key("messages") {
134                Ok(())
135            } else {
136                for text_obj in message["messages"].as_array().unwrap() {
137                    let text = text_obj["text"].as_str().unwrap();
138
139                    if text.contains("You die...") {
140                        return Err(Error::Blocking(BlockingError::Died));
141                    }
142
143                    if text.contains("Blink to where?") {
144                        return Err(Error::Blocking(BlockingError::Blink));
145                    }
146
147                    if text.contains("Really read the scroll of noise?") {
148                        return Err(Error::Blocking(BlockingError::Noise));
149                    }
150
151                    if text.contains("Keep equipping yourself?") {
152                        return Err(Error::Blocking(BlockingError::Equipping));
153                    }
154
155                    if text.contains("Keep disrobing?") {
156                        return Err(Error::Blocking(BlockingError::Disrobing));
157                    }
158                }
159                Ok(())
160            }
161        }
162        "login_fail" => Err(Error::LoginFailed),
163        "register_fail" => Err(Error::RegisterFailed),
164        "ui-push" => {
165            if !message.as_object().unwrap().contains_key("type") {
166                Ok(())
167            } else {
168                if message["type"] == "seed-selection" {
169                    return Err(Error::Blocking(BlockingError::SeedSelection));
170                } else if message["type"] == "newgame-choice" {
171                    return Err(Error::Blocking(BlockingError::NewGameChoice));
172                }
173                Ok(())
174            }
175        }
176        _ => Ok(()),
177    }
178}