1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum BrowserName {
7 Chrome,
8 Edge,
9 Firefox,
10 Safari,
11}
12
13impl BrowserName {
14 pub fn from_str_loose(s: &str) -> Option<Self> {
15 match s.trim().to_lowercase().as_str() {
16 "chrome" => Some(Self::Chrome),
17 "edge" => Some(Self::Edge),
18 "firefox" => Some(Self::Firefox),
19 "safari" => Some(Self::Safari),
20 _ => None,
21 }
22 }
23}
24
25impl std::fmt::Display for BrowserName {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 Self::Chrome => write!(f, "chrome"),
29 Self::Edge => write!(f, "edge"),
30 Self::Firefox => write!(f, "firefox"),
31 Self::Safari => write!(f, "safari"),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum CookieSameSite {
38 Strict,
39 Lax,
40 None,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "lowercase")]
45pub enum CookieMode {
46 Merge,
47 First,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct CookieSource {
52 pub browser: BrowserName,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub profile: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub origin: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub store_id: Option<String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Cookie {
63 pub name: String,
64 pub value: String,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub domain: Option<String>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub path: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub url: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub expires: Option<i64>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub secure: Option<bool>,
75 #[serde(rename = "httpOnly", skip_serializing_if = "Option::is_none")]
76 pub http_only: Option<bool>,
77 #[serde(rename = "sameSite", skip_serializing_if = "Option::is_none")]
78 pub same_site: Option<CookieSameSite>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub source: Option<CookieSource>,
81}
82
83#[derive(Debug, Clone)]
84pub struct GetCookiesOptions {
85 pub url: String,
86 pub origins: Option<Vec<String>>,
87 pub names: Option<Vec<String>>,
88 pub browsers: Option<Vec<BrowserName>>,
89 pub profile: Option<String>,
90 pub chrome_profile: Option<String>,
91 pub edge_profile: Option<String>,
92 pub firefox_profile: Option<String>,
93 pub safari_cookies_file: Option<String>,
94 pub include_expired: Option<bool>,
95 pub timeout_ms: Option<u64>,
96 pub debug: Option<bool>,
97 pub mode: Option<CookieMode>,
98 pub inline_cookies_file: Option<String>,
99 pub inline_cookies_json: Option<String>,
100 pub inline_cookies_base64: Option<String>,
101}
102
103impl GetCookiesOptions {
104 pub fn new(url: impl Into<String>) -> Self {
105 Self {
106 url: url.into(),
107 origins: None,
108 names: None,
109 browsers: None,
110 profile: None,
111 chrome_profile: None,
112 edge_profile: None,
113 firefox_profile: None,
114 safari_cookies_file: None,
115 include_expired: None,
116 timeout_ms: None,
117 debug: None,
118 mode: None,
119 inline_cookies_file: None,
120 inline_cookies_json: None,
121 inline_cookies_base64: None,
122 }
123 }
124
125 pub fn origins(mut self, origins: Vec<String>) -> Self {
126 self.origins = Some(origins);
127 self
128 }
129
130 pub fn names(mut self, names: Vec<String>) -> Self {
131 self.names = Some(names);
132 self
133 }
134
135 pub fn browsers(mut self, browsers: Vec<BrowserName>) -> Self {
136 self.browsers = Some(browsers);
137 self
138 }
139
140 pub fn chrome_profile(mut self, profile: impl Into<String>) -> Self {
141 self.chrome_profile = Some(profile.into());
142 self
143 }
144
145 pub fn edge_profile(mut self, profile: impl Into<String>) -> Self {
146 self.edge_profile = Some(profile.into());
147 self
148 }
149
150 pub fn firefox_profile(mut self, profile: impl Into<String>) -> Self {
151 self.firefox_profile = Some(profile.into());
152 self
153 }
154
155 pub fn safari_cookies_file(mut self, file: impl Into<String>) -> Self {
156 self.safari_cookies_file = Some(file.into());
157 self
158 }
159
160 pub fn include_expired(mut self, include: bool) -> Self {
161 self.include_expired = Some(include);
162 self
163 }
164
165 pub fn timeout_ms(mut self, ms: u64) -> Self {
166 self.timeout_ms = Some(ms);
167 self
168 }
169
170 pub fn debug(mut self, debug: bool) -> Self {
171 self.debug = Some(debug);
172 self
173 }
174
175 pub fn mode(mut self, mode: CookieMode) -> Self {
176 self.mode = Some(mode);
177 self
178 }
179
180 pub fn inline_cookies_file(mut self, file: impl Into<String>) -> Self {
181 self.inline_cookies_file = Some(file.into());
182 self
183 }
184
185 pub fn inline_cookies_json(mut self, json: impl Into<String>) -> Self {
186 self.inline_cookies_json = Some(json.into());
187 self
188 }
189
190 pub fn inline_cookies_base64(mut self, b64: impl Into<String>) -> Self {
191 self.inline_cookies_base64 = Some(b64.into());
192 self
193 }
194}
195
196#[derive(Debug, Clone, Serialize)]
197pub struct GetCookiesResult {
198 pub cookies: Vec<Cookie>,
199 pub warnings: Vec<String>,
200}
201
202#[derive(Debug, Clone)]
203pub struct CookieHeaderOptions {
204 pub dedupe_by_name: bool,
205 pub sort: CookieHeaderSort,
206}
207
208impl Default for CookieHeaderOptions {
209 fn default() -> Self {
210 Self {
211 dedupe_by_name: false,
212 sort: CookieHeaderSort::Name,
213 }
214 }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub enum CookieHeaderSort {
219 Name,
220 None,
221}
222
223pub(crate) fn normalize_names(names: &Option<Vec<String>>) -> Option<HashSet<String>> {
224 let names = names.as_ref()?;
225 let cleaned: HashSet<String> = names
226 .iter()
227 .map(|n| n.trim().to_string())
228 .filter(|n| !n.is_empty())
229 .collect();
230 if cleaned.is_empty() {
231 return None;
232 }
233 Some(cleaned)
234}
235
236pub(crate) fn dedupe_cookies(cookies: Vec<Cookie>) -> Vec<Cookie> {
237 let mut seen = HashSet::new();
238 let mut result = Vec::new();
239 for cookie in cookies {
240 let key = format!(
241 "{}|{}|{}",
242 cookie.name,
243 cookie.domain.as_deref().unwrap_or(""),
244 cookie.path.as_deref().unwrap_or("")
245 );
246 if seen.insert(key) {
247 result.push(cookie);
248 }
249 }
250 result
251}