vrchat_log/
world.rs

1use std::ops::Deref;
2
3use crate::log;
4use crate::DateTime;
5
6#[derive(Debug)]
7pub struct World {
8    pub id: String,
9    pub name: String,
10}
11
12#[derive(Debug, PartialEq)]
13pub enum InstanceType {
14    Public,
15    FriendPlus,
16    Friends,
17    InvitePlus,
18    Invite,
19
20    Unknown,
21}
22
23#[derive(Debug)]
24pub struct Instance {
25    pub world: World,
26    pub id: u32,
27    pub owner: Option<String>,
28    pub typ: InstanceType,
29    pub nonce: Option<String>,
30}
31
32#[derive(Debug)]
33pub struct InstanceLog {
34    pub instance: Instance,
35    pub enter: Option<DateTime>,
36    pub join: Option<DateTime>,
37    pub join_or_create: Option<DateTime>,
38    pub joined: Option<DateTime>,
39    pub left: Option<DateTime>,
40}
41
42#[derive(Debug)]
43pub struct InstanceLogList(Vec<InstanceLog>);
44
45#[derive(Debug)]
46pub enum InstanceParseError {
47    IdIsNotNumber(String),
48    InvalidElement(String),
49    UnknownElement(String),
50    Invalid(String),
51    InvalidReqInvite(InstanceType),
52}
53
54/// parse instance string like:
55/// wrld_f8ff20cd-5310-4257-ade8-c3fd6ae95436:98257~friends(usr_f8229b4f-794c-4a94-bf5d-d21f3fc0daf5)~nonce(1104791A7210A68C4AE6C869F9B8944FFE00AA9425AA349926F5EDDB93DCB297)
56pub fn parse_instance(s: &str) -> Result<Instance, InstanceParseError> {
57    let s = s
58        .strip_prefix("wrld_")
59        .ok_or(InstanceParseError::Invalid(s.to_string()))?;
60
61    // split element
62    let mut elem: Vec<&str> = s.split('~').collect();
63    elem.retain(|w| !w.is_empty());
64
65    let mut typ = InstanceType::Unknown;
66    if elem.len() == 1 {
67        // wrld_hogehgoe:id
68        typ = InstanceType::Public;
69    }
70    let mut it = elem.iter();
71
72    // parse world id and instance number
73    let world = it
74        .next()
75        .ok_or(InstanceParseError::Invalid(s.to_string()))?;
76    let w: Vec<&str> = world.split(':').collect();
77    if w.len() != 2 {
78        Err(InstanceParseError::InvalidElement(world.to_string()))?
79    }
80    let id = w[1].parse().unwrap();
81
82    let world = World {
83        id: w[0].to_string(),
84        name: "".to_string(),
85    };
86
87    let mut owner = None;
88    let mut nonce = None;
89
90    for e in it {
91        if e.is_empty() {
92            continue;
93        }
94
95        //println!("{}", e);
96        let e: Vec<&str> = e.split('(').collect();
97
98        // no args
99        if e.len() == 1 {
100            match e[0] {
101                "canRequestInvite" => {
102                    typ = match typ {
103                        InstanceType::Unknown => InstanceType::InvitePlus, // launch from vrchat.com
104                        InstanceType::Invite => InstanceType::InvitePlus,  // launch from app
105                        _ => Err(InstanceParseError::InvalidReqInvite(typ))?,
106                    };
107                }
108                _ => Err(InstanceParseError::UnknownElement(e[0].to_string()))?,
109            };
110            continue;
111        }
112
113        // has args
114        let arg = &e[1];
115        let arg = &arg[..arg.len() - 1];
116        match e[0] {
117            "private" => {
118                let o = arg.strip_prefix("usr_").unwrap();
119                owner = Some(o.to_string());
120                typ = InstanceType::Invite;
121            }
122            "hidden" => {
123                let o = arg.strip_prefix("usr_").unwrap();
124                owner = Some(o.to_string());
125                typ = InstanceType::FriendPlus;
126            }
127            "friends" => {
128                let o = arg.strip_prefix("usr_").unwrap();
129                owner = Some(o.to_string());
130                typ = InstanceType::Friends;
131            }
132            "nonce" => {
133                nonce = Some(arg.to_string());
134            }
135            _ => Err(InstanceParseError::UnknownElement(e[0].to_string()))?,
136        }
137    }
138
139    Ok(Instance {
140        world,
141        id,
142        owner,
143        typ,
144        nonce,
145    })
146}
147
148impl From<&Vec<crate::LogEnum>> for InstanceLogList {
149    fn from(from: &Vec<crate::LogEnum>) -> InstanceLogList {
150        let mut v = Vec::new();
151        let mut log = from.iter();
152
153        let mut ilog: Option<InstanceLog> = None;
154        let mut world_name = None;
155        let mut enter = None;
156        loop {
157            let l = log.next();
158            if l.is_none() {
159                break;
160            }
161            let l = l.unwrap().as_log();
162            if l.is_none() {
163                continue;
164            }
165            let l = l.unwrap();
166            if l.typ != log::Type::RoomManager {
167                continue;
168            }
169
170            let msg = &l.msg[0];
171            if msg == "Clearing Room Metadata" || msg.starts_with("Room metadata") {
172                continue;
173            }
174
175            if let Some(name) = msg.strip_prefix("Entering Room: ") {
176                world_name = Some(name.to_string());
177                enter = Some(l.date);
178                continue;
179            }
180            if msg.starts_with("Joining wrld_") {
181                let msg = msg.strip_prefix("Joining ").unwrap();
182                let mut instance = parse_instance(msg).unwrap();
183                if let Some(ref name) = world_name {
184                    instance.world.name = name.to_string();
185                }
186                ilog = Some(InstanceLog {
187                    instance,
188                    enter,
189                    join: Some(l.date),
190                    join_or_create: None,
191                    joined: None,
192                    left: None,
193                });
194            }
195
196            if let Some(name) = msg.strip_prefix("Joining or Creating Room: ") {
197                if let Some(ref mut ilog) = ilog {
198                    let wn = &mut ilog.instance.world.name;
199                    if wn.is_empty() {
200                        *wn = name.to_string();
201                    } else if wn != name {
202                        panic!("somthing wrong");
203                    }
204                    ilog.join_or_create = Some(l.date);
205                }
206            }
207
208            if msg == "Successfully joined room" {
209                if let Some(ref mut ilog) = ilog {
210                    ilog.joined = Some(l.date);
211                }
212            }
213
214            if msg == "Successfully left room" {
215                if let Some(mut ilog) = ilog {
216                    ilog.left = Some(l.date);
217                    v.push(ilog);
218                }
219                ilog = None;
220            }
221        }
222        // no left
223        if let Some(ilog) = ilog {
224            if v.is_empty() || v.last().unwrap().instance.id != ilog.instance.id {
225                v.push(ilog);
226            }
227        }
228
229        v.into()
230    }
231}
232
233impl Deref for InstanceLogList {
234    type Target = Vec<InstanceLog>;
235    fn deref(&self) -> &Self::Target {
236        &self.0
237    }
238}
239impl Into<InstanceLogList> for Vec<InstanceLog> {
240    fn into(self) -> InstanceLogList {
241        InstanceLogList(self)
242    }
243}