Skip to main content

obws/responses/
mod.rs

1//! All responses that can be received from the API.
2
3pub mod canvases;
4pub mod config;
5pub mod filters;
6pub mod general;
7pub(crate) mod hotkeys;
8pub(crate) mod ids;
9pub mod inputs;
10pub mod media_inputs;
11pub mod outputs;
12pub mod profiles;
13pub mod recording;
14pub(crate) mod replay_buffer;
15pub mod scene_collections;
16pub mod scene_items;
17pub mod scenes;
18pub mod sources;
19pub mod streaming;
20pub mod transitions;
21pub mod ui;
22pub(crate) mod virtual_cam;
23
24use derive_more::derive::TryFrom;
25use serde::{Deserialize, Deserializer, Serialize, de};
26
27#[derive(Debug)]
28pub(crate) enum ServerMessage {
29    /// First message sent from the server immediately on client connection. Contains
30    /// authentication information if authentication is required. Also contains RPC version for
31    /// version negotiation.
32    Hello(Hello),
33    /// The identify request was received and validated, and the connection is now ready for normal
34    /// operation.
35    Identified(Identified),
36    /// An event coming from OBS has occurred. For example scene switched, source muted.
37    #[cfg(feature = "events")]
38    Event(crate::events::Event),
39    #[cfg(not(feature = "events"))]
40    Event,
41    /// `obs-websocket` is responding to a request coming from a client.
42    RequestResponse(RequestResponse),
43    /// `obs-websocket` is responding to a request batch coming from the client.
44    #[allow(dead_code)]
45    RequestBatchResponse(RequestBatchResponse),
46}
47
48impl<'de> Deserialize<'de> for ServerMessage {
49    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
50    where
51        D: Deserializer<'de>,
52    {
53        #[derive(Deserialize)]
54        struct RawServerMessage {
55            #[serde(rename = "op")]
56            op_code: OpCode,
57            #[serde(rename = "d")]
58            data: serde_json::Value,
59        }
60
61        #[derive(Deserialize, TryFrom)]
62        #[serde(try_from = "u8")]
63        #[try_from(repr)]
64        #[repr(u8)]
65        enum OpCode {
66            /// The initial message sent by obs-websocket to newly connected clients.
67            Hello = 0,
68            /// The response sent by obs-websocket to a client after it has successfully identified
69            /// with obs-websocket.
70            Identified = 2,
71            /// The message sent by obs-websocket containing an event payload.
72            Event = 5,
73            /// The message sent by obs-websocket in response to a particular request from a
74            /// client.
75            RequestResponse = 7,
76            /// The message sent by obs-websocket in response to a particular batch of requests
77            /// from a client.
78            RequestBatchResponse = 9,
79        }
80
81        let raw = RawServerMessage::deserialize(deserializer)?;
82
83        Ok(match raw.op_code {
84            OpCode::Hello => {
85                ServerMessage::Hello(serde_json::from_value(raw.data).map_err(de::Error::custom)?)
86            }
87            OpCode::Identified => ServerMessage::Identified(
88                serde_json::from_value(raw.data).map_err(de::Error::custom)?,
89            ),
90            OpCode::Event => {
91                #[cfg(feature = "events")]
92                {
93                    ServerMessage::Event(
94                        serde_json::from_value(raw.data).map_err(de::Error::custom)?,
95                    )
96                }
97                #[cfg(not(feature = "events"))]
98                {
99                    ServerMessage::Event
100                }
101            }
102            OpCode::RequestResponse => ServerMessage::RequestResponse(
103                serde_json::from_value(raw.data).map_err(de::Error::custom)?,
104            ),
105            OpCode::RequestBatchResponse => ServerMessage::RequestBatchResponse(
106                serde_json::from_value(raw.data).map_err(de::Error::custom)?,
107            ),
108        })
109    }
110}
111
112/// First message sent from the server immediately on client connection. Contains authentication
113/// information if authentication is required. Also contains RPC version for version negotiation.
114#[derive(Debug, Deserialize)]
115pub(crate) struct Hello {
116    #[allow(dead_code)]
117    #[serde(rename = "obsWebSocketVersion")]
118    pub obs_web_socket_version: semver::Version,
119    /// version number which gets incremented on each **breaking change** to the obs-websocket
120    /// protocol. Its usage in this context is to provide the current RPC version that the server
121    /// would like to use.
122    #[serde(rename = "rpcVersion")]
123    pub rpc_version: u32,
124    #[serde(rename = "authentication")]
125    pub authentication: Option<Authentication>,
126}
127
128/// The identify request was received and validated, and the connection is now ready for normal
129/// operation.
130#[derive(Debug, Deserialize)]
131pub(crate) struct Identified {
132    /// The RPC (remote procedure call) version to be used.
133    #[serde(rename = "negotiatedRpcVersion")]
134    pub negotiated_rpc_version: u32,
135}
136
137/// `obs-websocket` is responding to a request coming from a client.
138#[derive(Debug, Deserialize)]
139pub(crate) struct RequestResponse {
140    #[allow(dead_code)]
141    #[serde(rename = "requestType")]
142    pub r#type: String,
143    #[serde(rename = "requestId")]
144    pub id: String,
145    #[serde(rename = "requestStatus")]
146    pub status: Status,
147    #[serde(rename = "responseData", default)]
148    pub data: serde_json::Value,
149}
150
151#[derive(Debug, Deserialize)]
152pub(crate) struct RequestBatchResponse {
153    #[allow(dead_code)]
154    #[serde(rename = "requestId")]
155    pub id: String,
156    #[allow(dead_code)]
157    pub results: Vec<serde_json::Value>,
158}
159
160#[derive(Debug, Deserialize)]
161pub(crate) struct Authentication {
162    pub challenge: String,
163    pub salt: String,
164}
165
166#[derive(Debug, Deserialize)]
167pub(crate) struct Status {
168    /// Is true if the request resulted in [`StatusCode::Success`]. False if otherwise.
169    pub result: bool,
170    pub code: StatusCode,
171    /// May be provided by the server on errors to offer further details on why a request failed.
172    pub comment: Option<String>,
173}
174
175/// The status code gives information about the result of a request. It gives further insight into
176/// what went wrong, if a request failed.
177#[derive(
178    Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize, TryFrom,
179)]
180#[serde(into = "u16", try_from = "u16")]
181#[try_from(repr)]
182#[repr(u16)]
183#[non_exhaustive]
184pub enum StatusCode {
185    /// Unknown status, should never be used.
186    Unknown = 0,
187
188    /// For internal use to signify a successful field check.
189    NoError = 10,
190
191    /// The request has succeeded.
192    Success = 100,
193
194    /// The `requestType` field is missing from the request data.
195    MissingRequestType = 203,
196    /// The request type is invalid or does not exist.
197    UnknownRequestType = 204,
198    /// Generic error code.
199    ///
200    /// **Note:** A comment is required to be provided by obs-websocket.
201    GenericError = 205,
202    /// The request batch execution type is not supported.
203    UnsupportedRequestBatchExecutionType = 206,
204    /// The server is not ready to handle the request.
205    ///
206    /// **Note:** This usually occurs during OBS scene collection change or exit. Requests may be
207    /// tried again after a delay if this code is given.
208    NotReady = 207,
209
210    /// A required request field is missing.
211    MissingRequestField = 300,
212    /// The request does not have a valid `requestData` object.
213    MissingRequestData = 301,
214
215    /// Generic invalid request field message.
216    ///
217    /// **Note:** A comment is required to be provided by obs-websocket.
218    InvalidRequestField = 400,
219    /// A request field has the wrong data type.
220    InvalidRequestFieldType = 401,
221    /// A request field (number) is outside the allowed range.
222    RequestFieldOutOfRange = 402,
223    /// A request field (string or array) is empty and cannot be.
224    RequestFieldEmpty = 403,
225    /// There are too many request fields (For example a request takes two optional fields, where
226    /// only one is allowed at a time).
227    TooManyRequestFields = 404,
228
229    /// An output is running and cannot be in order to perform the request.
230    OutputRunning = 500,
231    /// An output is not running and should be.
232    OutputNotRunning = 501,
233    /// An output is paused and should not be.
234    OutputPaused = 502,
235    /// An output is not paused and should be.
236    OutputNotPaused = 503,
237    /// An output is disabled and should not be.
238    OutputDisabled = 504,
239    /// Studio mode is active and cannot be.
240    StudioModeActive = 505,
241    /// Studio mode is not active and should be.
242    StudioModeNotActive = 506,
243
244    /// The resource was not found.
245    ///
246    /// **Note:** Resources are any kind of object in obs-websocket, like inputs, profiles,
247    /// outputs, etc.
248    ResourceNotFound = 600,
249    /// The resource already exists.
250    ResourceAlreadyExists = 601,
251    /// The type of resource found is invalid.
252    InvalidResourceType = 602,
253    /// There are not enough instances of the resource in order to perform the request.
254    NotEnoughResources = 603,
255    /// The state of the resource is invalid. For example, if the resource is blocked from being
256    /// accessed.
257    InvalidResourceState = 604,
258    /// The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind.
259    InvalidInputKind = 605,
260    /// The resource does not support being configured.
261    ///
262    /// This is particularly relevant to transitions, where they do not always have changeable
263    /// settings.
264    ResourceNotConfigurable = 606,
265    /// The specified filter had the wrong kind.
266    InvalidFilterKind = 607,
267
268    /// Creating the resource failed.
269    ResourceCreationFailed = 700,
270    /// Performing an action on the resource failed.
271    ResourceActionFailed = 701,
272    /// Processing the request failed unexpectedly.
273    ///
274    /// **Note:** A comment is required to be provided by obs-websocket.
275    RequestProcessingFailed = 702,
276    /// The combination of request fields cannot be used to perform an action.
277    CannotAct = 703,
278}
279
280impl From<StatusCode> for u16 {
281    fn from(value: StatusCode) -> Self {
282        value as Self
283    }
284}
285
286/// Additional close codes, defined by `obs-websocket`. These can be used to further pin down the
287/// details of why the web-socket connection was closed.
288#[derive(
289    Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize, TryFrom,
290)]
291#[serde(into = "u16", try_from = "u16")]
292#[try_from(repr)]
293#[repr(u16)]
294#[non_exhaustive]
295pub enum WebSocketCloseCode {
296    /// Unknown reason, should never be used.
297    UnknownReason = 4000,
298    /// The server was unable to decode the incoming web-socket message.
299    MessageDecodeError = 4002,
300    /// A data field is required but missing from the payload.
301    MissingDataField = 4003,
302    /// A data field's value type is invalid.
303    InvalidDataFieldType = 4004,
304    /// A data field's value is invalid.
305    InvalidDataFieldValue = 4005,
306    /// The specified `op` was invalid or missing.
307    UnknownOpCode = 4006,
308    /// The client sent a web-socket message without first sending `Identify` message.
309    NotIdentified = 4007,
310    /// The client sent an `Identify` message while already identified.
311    ///
312    /// **Note:** Once a client has identified, only `Reidentify` may be used to change session
313    /// parameters.
314    AlreadyIdentified = 4008,
315    /// The authentication attempt (via `Identify`) failed.
316    AuthenticationFailed = 4009,
317    /// The server detected the usage of an old version of the obs-websocket RPC protocol.
318    UnsupportedRpcVersion = 4010,
319    /// The web-socket session has been invalidated by the obs-websocket server.
320    ///
321    /// **Note:** This is the code used by the `Kick` button in the UI Session List. If you receive
322    /// this code, you must not automatically reconnect.
323    SessionInvalidated = 4011,
324    /// A requested feature is not supported due to hardware/software limitations.
325    UnsupportedFeature = 4012,
326}
327
328impl From<WebSocketCloseCode> for u16 {
329    fn from(value: WebSocketCloseCode) -> Self {
330        value as Self
331    }
332}