1use chrono::{DateTime, NaiveDateTime, Utc};
9use log::{debug, error, info};
10use regex::{Captures, Regex};
11
12use crate::models::*;
13
14const HEADER_REGEX: &str = r"(?m)^------------------------------------------------------------$
15^ Gamelog$
16^ Listener: (?P<name>.+)$
17^ Session Started: (?<timestamp>\d{4}.\d{2}.\d{2} \d{2}:\d{2}:\d{2})$
18^------------------------------------------------------------$";
19
20const DAMAGE_REXEX: &str = r"(?i)\[ (?P<timestamp>\d{4}.\d{2}.\d{2} \d{2}:\d{2}:\d{2}) \] \(combat\) <color=0x[0-9a-f]{8}><b>(?P<damage>\d+)</b> <color=0x[0-9a-f]{8}><font size=\d+>(?P<destination>(to|from))</font> <b><color=0x[0-9a-f]{8}>(?P<pilot>.+)\[(?P<ticker>.+)\]\((?P<shiptype>.+)\)</b><font size=\d+><color=0x[0-9a-f]{8}> - (?P<weapon>.+) - ((Smashes)|(Penetrates)|(Hits)|(Glances Off)|(Grazes))\n";
21
22const LOGI_REGEX: &str = r"(?i)^\[ (?P<timestamp>\d{4}.\d{2}.\d{2} \d{2}:\d{2}:\d{2}) \] \(combat\) <color=0x[0-9a-f]{8}><b>(?P<damage>\d+)</b><color=0x[0-9a-f]{8}><font size=\d+> remote ((armor)|(shield)|(hull)) .+ (?P<destination>(by|to)) </font><b><color=0x[0-9a-f]{8}><font size=\d+><color=0x[0-9a-f]{8}> <b>(?P<shiptype>.+)</b></color></font><color=0x[0-9a-f]{8}> \[(?P<pilot>.+)\]<color=0x[0-9a-f]{8}><b> -</color> </b><color=0x[0-9a-f]{8}><font size=\d+> - (?P<reptype>.+)</font>\n";
23
24pub fn parse_log_line(text: &String) -> Option<Log> {
48 info!("Parsing {}", text);
49 let damage_re: Regex = Regex::new(DAMAGE_REXEX).unwrap();
50 let logi_re: Regex = Regex::new(LOGI_REGEX).unwrap();
51
52 if let Some(capture) = damage_re.captures(text) {
53 debug!("Damage log recognized");
54 make_damage_log_from_capture(&capture)
55 } else if let Some(capture) = logi_re.captures(text) {
56 debug!("Logi log recognized");
57 make_logi_log_from_capture(&capture)
58 } else {
59 debug!("Log type not recognized");
60 None
61 }
62}
63
64pub fn parse_log_header(header: String) -> Option<(String, DateTime<Utc>)> {
66 info!("Parsing header {}", header);
67 let header_re = Regex::new(HEADER_REGEX).unwrap();
68
69 if let Some(capture) = header_re.captures(header.as_str()) {
70 debug!("Recognized the header");
71 let log_beginning = parse_datetime(&capture["timestamp"]);
72 let character_name = capture["name"].to_string();
73 Some((character_name, log_beginning))
74 } else {
75 debug!("Didn't recognize the header format");
76 None
77 }
78}
79
80fn make_damage_log_from_capture(capture: &Captures) -> Option<Log> {
82 if let Ok(damage) = capture["damage"].parse::<isize>() {
83 let destination = match capture["destination"].as_ref() {
84 "to" => Destination::Dealing,
85 "from" => Destination::Receiving,
86 _ => panic!("Unexpected token received - {}", &capture["destination"]),
87 };
88 Some(Log::Damage(DamageLog::new(
89 parse_datetime(&capture["timestamp"]),
90 damage,
91 capture["pilot"].to_string(),
92 capture["shiptype"].to_string(),
93 capture["weapon"].to_string(),
94 destination,
95 )))
96 } else {
97 error!("Couldn't parse the damage - {}", &capture["damage"]);
98 None
99 }
100}
101
102fn make_logi_log_from_capture(capture: &Captures) -> Option<Log> {
104 if let Ok(amount) = capture["damage"].parse::<isize>() {
105 let destination = match capture["destination"].as_ref() {
106 "by" => Destination::Receiving,
107 "to" => Destination::Dealing,
108 _ => panic!("Unexpected token received - {}", &capture["destination"]),
109 };
110 Some(Log::Logi(LogiLog::new(
111 parse_datetime(&capture["timestamp"]),
112 amount,
113 capture["pilot"].to_string(),
114 capture["shiptype"].to_string(),
115 capture["reptype"].to_string(),
116 destination,
117 )))
118 } else {
119 error!(
120 "Couldn't parse the amount - {}",
121 capture["amount"].to_string()
122 );
123 None
124 }
125}
126
127fn parse_datetime(string: &str) -> DateTime<Utc> {
129 let naive_datetime = NaiveDateTime::parse_from_str(string, "%Y.%m.%d %H:%M:%S").unwrap();
130 DateTime::<Utc>::from_naive_utc_and_offset(naive_datetime, Utc)
131}
132
133#[cfg(test)]
134mod tests {
135 use chrono::{TimeZone, Utc};
136
137 use crate::parser::{Destination, LogiLog};
138
139 use super::{parse_log_header, parse_log_line, DamageLog, Log};
140
141 #[test]
142 fn test_basic_damage_logs() {
143 let log_string1 = "[ 2024.07.02 20:31:28 ] (combat) <color=0xff00ffff><b>200</b> <color=0x77ffffff><font size=10>to</font> <b><color=0xffffffff>Hornet EC-300[-15.0](Hornet EC-300)</b><font size=10><color=0x77ffffff> - Draclira's Modified Tachyon Beam Laser - Penetrates\n".to_string();
144 let log_string2 = "[ 2024.06.25 15:20:01 ] (combat) <color=0xffcc0000><b>375</b> <color=0x77ffffff><font size=10>from</font> <b><color=0xffffffff>Tek'wka Rokym[WH.SQ](Paladin)</b><font size=10><color=0x77ffffff> - Imperial Navy Large EMP Smartbomb - Hits\n".to_string();
145 let log_string3 = "[ 2024.07.02 19:42:05 ] (combat) <color=0xff00ffff><b>153</b> <color=0x77ffffff><font size=10>to</font> <b><color=0xffffffff>Kilyavi Alaailaa[-15.0](Capsule)</b><font size=10><color=0x77ffffff> - Medium Vorton Projector II - Hits\n".to_string();
146
147 let parser_output1 = parse_log_line(&log_string1).unwrap();
148 let parser_output2 = parse_log_line(&log_string2).unwrap();
149 let parser_output3 = parse_log_line(&log_string3).unwrap();
150
151 let expected_output1 = DamageLog::new(
152 Utc.with_ymd_and_hms(2024, 7, 2, 20, 31, 28).unwrap(),
153 200,
154 "Hornet EC-300".to_string(),
155 "Hornet EC-300".to_string(),
156 "Draclira's Modified Tachyon Beam Laser".to_string(),
157 Destination::Dealing,
158 );
159 let expected_output2 = DamageLog::new(
160 Utc.with_ymd_and_hms(2024, 06, 25, 15, 20, 01).unwrap(),
161 375,
162 "Tek'wka Rokym".to_string(),
163 "Paladin".to_string(),
164 "Imperial Navy Large EMP Smartbomb".to_string(),
165 Destination::Receiving,
166 );
167 let expected_output3 = DamageLog::new(
168 Utc.with_ymd_and_hms(2024, 07, 02, 19, 42, 05).unwrap(),
169 153,
170 "Kilyavi Alaailaa".to_string(),
171 "Capsule".to_string(),
172 "Medium Vorton Projector II".to_string(),
173 Destination::Dealing,
174 );
175
176 assert_eq!(parser_output1, Log::Damage(expected_output1));
177 assert_eq!(parser_output2, Log::Damage(expected_output2));
178 assert_eq!(parser_output3, Log::Damage(expected_output3));
179 }
180
181 #[test]
182 fn test_basic_logi_logs() {
183 let log_string1 = "[ 2024.07.02 19:13:23 ] (combat) <color=0xffccff66><b>772</b><color=0x77ffffff><font size=10> remote shield boosted by </font><b><color=0xffffffff><font size=14><color=0xFFFFFFFF> <b>Osprey</b></color></font><color=0xFFB3B3B3> [Drentu]<color=0xFFFFFFFF><b> -</color> </b><color=0x77ffffff><font size=10> - Medium Ancillary Remote Shield Booster</font>\n".to_string();
185 let log_string2 = "[ 2024.07.02 20:14:35 ] (combat) <color=0xffccff66><b>665</b><color=0x77ffffff><font size=10> remote shield boosted by </font><b><color=0xffffffff><font size=14><color=0xFF70FF40> <b>Scimitar</b></color></font><color=0xFFFF4040> [Drentu]<color=0xFFFFFFFF><b> -</color> </b><color=0x77ffffff><font size=10> - Large Remote Shield Booster II</font>\n".to_string();
186
187 let parser_output1 = parse_log_line(&log_string1).unwrap();
188 let parser_output2 = parse_log_line(&log_string2).unwrap();
189
190 let expected_output1 = LogiLog::new(
191 Utc.with_ymd_and_hms(2024, 7, 2, 19, 13, 23).unwrap(),
192 772,
193 "Drentu".to_string(),
194 "Osprey".to_string(),
195 "Medium Ancillary Remote Shield Booster".to_string(),
196 Destination::Receiving,
197 );
198 let expected_output2 = LogiLog::new(
199 Utc.with_ymd_and_hms(2024, 07, 02, 20, 14, 35).unwrap(),
200 665,
201 "Drentu".to_string(),
202 "Scimitar".to_string(),
203 "Large Remote Shield Booster II".to_string(),
204 Destination::Receiving,
205 );
206
207 assert_eq!(parser_output1, Log::Logi(expected_output1));
208 assert_eq!(parser_output2, Log::Logi(expected_output2));
209 }
210
211 #[test]
212 fn test_header_parsing() {
213 let header1 = "------------------------------------------------------------
214 Gamelog
215 Listener: T'rahk Rokym
216 Session Started: 2024.07.02 19:41:40
217------------------------------------------------------------\n"
218 .to_string();
219
220 let expected_output1 = Some((
221 "T'rahk Rokym".to_string(),
222 Utc.with_ymd_and_hms(2024, 07, 02, 19, 41, 40).unwrap(),
223 ));
224
225 let output1 = parse_log_header(header1);
226
227 assert_eq!(output1, expected_output1)
228 }
229}