vlr_scraper/client.rs
1use tracing::instrument;
2
3use crate::error::Result;
4use crate::model::*;
5use crate::vlr_scraper;
6
7/// The main entry point for interacting with VLR.gg.
8///
9/// `VlrClient` wraps a [`reqwest::Client`] and exposes methods
10/// to fetch events, match lists, match details, player profiles, and player match histories.
11///
12/// # Examples
13///
14/// ```no_run
15/// # async fn example() -> vlr_scraper::Result<()> {
16/// use vlr_scraper::{AgentStatsTimespan, EventType, Region, VlrClient};
17///
18/// let client = VlrClient::new();
19/// let events = client
20/// .get_events(EventType::Upcoming, Region::All, 1)
21/// .await?;
22/// println!("Found {} events", events.events.len());
23///
24/// // Fetch a player profile
25/// let player = client.get_player(17323, Default::default()).await?;
26/// println!("{} ({:?})", player.info.name, player.info.country);
27/// # Ok(())
28/// # }
29/// ```
30pub struct VlrClient {
31 http: reqwest::Client,
32}
33
34impl VlrClient {
35 /// Create a new client with default settings.
36 ///
37 /// Uses a default [`reqwest::Client`] with no custom configuration.
38 /// For custom timeouts, proxies, or headers, use [`VlrClient::with_client`].
39 pub fn new() -> Self {
40 Self {
41 http: reqwest::Client::new(),
42 }
43 }
44
45 /// Create a new client using the provided [`reqwest::Client`].
46 ///
47 /// Use this when you need to configure timeouts, proxies, headers, or
48 /// other HTTP-level settings.
49 ///
50 /// # Examples
51 ///
52 /// ```no_run
53 /// use vlr_scraper::VlrClient;
54 ///
55 /// let http = reqwest::Client::builder()
56 /// .timeout(std::time::Duration::from_secs(10))
57 /// .build()
58 /// .unwrap();
59 /// let client = VlrClient::with_client(http);
60 /// ```
61 pub fn with_client(client: reqwest::Client) -> Self {
62 Self { http: client }
63 }
64
65 /// Fetch a paginated list of events, filtered by type and region.
66 ///
67 /// Returns an [`EventsData`] containing a page of [`Event`] entries together
68 /// with pagination info (`page` and `total_pages`). Each event includes its
69 /// status, region, title, dates, icon URL, and price tier.
70 ///
71 /// # Arguments
72 ///
73 /// * `event_type` - Whether to retrieve [`EventType::Upcoming`] or [`EventType::Completed`] events.
74 /// * `region` - Geographic filter (use [`Region::All`] for no filtering).
75 /// * `page` - Page number (1-indexed).
76 ///
77 /// # Examples
78 ///
79 /// ```no_run
80 /// # async fn example() -> vlr_scraper::Result<()> {
81 /// use vlr_scraper::{EventType, Region, VlrClient};
82 ///
83 /// let client = VlrClient::new();
84 /// let data = client
85 /// .get_events(EventType::Upcoming, Region::Europe, 1)
86 /// .await?;
87 /// for event in &data.events {
88 /// println!("[{}] {} ({})", event.status, event.title, event.dates);
89 /// }
90 /// # Ok(())
91 /// # }
92 /// ```
93 #[instrument(skip(self))]
94 pub async fn get_events(
95 &self,
96 event_type: EventType,
97 region: Region,
98 page: u8,
99 ) -> Result<EventsData> {
100 vlr_scraper::events::list::get_events(&self.http, event_type, region, page).await
101 }
102
103 /// Fetch all matches belonging to an event.
104 ///
105 /// Returns an [`EventMatchList`] (a `Vec<EventMatchListItem>`) where each item contains
106 /// the match ID, slug, date/time, participating teams with scores, tags, and
107 /// event series text.
108 ///
109 /// # Arguments
110 ///
111 /// * `event_id` - The VLR.gg event ID (found in [`Event::id`]).
112 ///
113 /// # Examples
114 ///
115 /// ```no_run
116 /// # async fn example() -> vlr_scraper::Result<()> {
117 /// use vlr_scraper::VlrClient;
118 ///
119 /// let client = VlrClient::new();
120 /// let matches = client.get_event_matchlist(2095).await?;
121 /// for m in &matches {
122 /// let teams: Vec<_> = m.teams.iter().map(|t| t.name.as_str()).collect();
123 /// println!("{} — {}", teams.join(" vs "), m.event_series_text);
124 /// }
125 /// # Ok(())
126 /// # }
127 /// ```
128 #[instrument(skip(self))]
129 pub async fn get_event_matchlist(&self, event_id: u32) -> Result<EventMatchList> {
130 vlr_scraper::events::matchlist::get_event_matchlist(&self.http, event_id).await
131 }
132
133 /// Fetch full details for a specific match by ID.
134 ///
135 /// Returns a [`Match`] containing:
136 /// - [`MatchHeader`] — event info, date, and team names/scores
137 /// - Live streams and VOD links as [`MatchStream`] entries
138 /// - Per-map [`MatchGame`] data with team scores, player stats, and
139 /// round-by-round outcomes
140 ///
141 /// # Arguments
142 ///
143 /// * `match_id` - The VLR.gg match ID (found in [`EventMatchListItem::id`]).
144 ///
145 /// # Examples
146 ///
147 /// ```no_run
148 /// # async fn example() -> vlr_scraper::Result<()> {
149 /// use vlr_scraper::VlrClient;
150 ///
151 /// let client = VlrClient::new();
152 /// let m = client.get_match(429519).await?;
153 /// println!("{} — {}", m.header.event_title, m.header.event_series_name);
154 /// for game in &m.games {
155 /// println!(
156 /// " {} — {} vs {}",
157 /// game.map, game.teams[0].name, game.teams[1].name
158 /// );
159 /// }
160 /// # Ok(())
161 /// # }
162 /// ```
163 #[instrument(skip(self))]
164 pub async fn get_match(&self, match_id: u32) -> Result<Match> {
165 vlr_scraper::matches::detail::get_match(&self.http, match_id).await
166 }
167
168 /// Fetch a paginated list of matches a player has participated in.
169 ///
170 /// Returns a [`PlayerMatchList`] (a `Vec<PlayerMatchListItem>`) where each
171 /// entry contains the match ID, league name and icon, participating teams
172 /// with scores, VOD links, and a match start timestamp.
173 ///
174 /// # Arguments
175 ///
176 /// * `player_id` - The VLR.gg player ID.
177 /// * `page` - Page number (1-indexed).
178 ///
179 /// # Examples
180 ///
181 /// ```no_run
182 /// # async fn example() -> vlr_scraper::Result<()> {
183 /// use vlr_scraper::VlrClient;
184 ///
185 /// let client = VlrClient::new();
186 /// let matches = client.get_player_matchlist(17323, 1).await?;
187 /// for m in &matches {
188 /// let teams: Vec<_> = m.teams.iter().map(|t| t.name.as_str()).collect();
189 /// println!("[{}] {}", m.league_name, teams.join(" vs "));
190 /// }
191 /// # Ok(())
192 /// # }
193 /// ```
194 #[instrument(skip(self))]
195 pub async fn get_player_matchlist(&self, player_id: u32, page: u8) -> Result<PlayerMatchList> {
196 vlr_scraper::players::matchlist::get_player_matchlist(&self.http, player_id, page).await
197 }
198
199 /// Fetch a complete player profile including info, teams, agent stats, news, and event placements.
200 ///
201 /// The returned [`Player`] contains:
202 /// - [`PlayerInfo`] — name, real name, avatar URL, country/country code, and social links
203 /// - Current and past [`PlayerTeam`] entries with team ID, name, logo, and join info
204 /// - [`PlayerAgentStats`] for the given timespan (rating, ACS, K/D, ADR, KAST, etc.)
205 /// - Recent [`PlayerNewsItem`] articles mentioning the player
206 /// - [`EventPlacement`] history with per-stage results and total winnings
207 ///
208 /// # Arguments
209 ///
210 /// * `player_id` - The VLR.gg player ID.
211 /// * `timespan` - Time window for agent statistics (see [`AgentStatsTimespan`]).
212 ///
213 /// # Examples
214 ///
215 /// ```no_run
216 /// # async fn example() -> vlr_scraper::Result<()> {
217 /// use vlr_scraper::{AgentStatsTimespan, VlrClient};
218 ///
219 /// let client = VlrClient::new();
220 /// let player = client.get_player(17323, AgentStatsTimespan::All).await?;
221 ///
222 /// println!("{} ({:?})", player.info.name, player.info.country);
223 /// for team in &player.current_teams {
224 /// println!(" team: {}", team.name);
225 /// }
226 /// for stat in &player.agent_stats {
227 /// println!(
228 /// " {} — rating {:.2}, K/D {:.2}",
229 /// stat.agent, stat.rating, stat.kd
230 /// );
231 /// }
232 /// # Ok(())
233 /// # }
234 /// ```
235 #[instrument(skip(self))]
236 pub async fn get_player(&self, player_id: u32, timespan: AgentStatsTimespan) -> Result<Player> {
237 vlr_scraper::players::info::get_player(&self.http, player_id, timespan).await
238 }
239
240 /// Fetch a paginated list of matches a team has participated in.
241 ///
242 /// Returns a `Vec<MatchItem>` where each entry contains the match ID,
243 /// league name and icon, participating teams with scores, VOD links, and
244 /// a match start timestamp.
245 ///
246 /// # Arguments
247 ///
248 /// * `team_id` - The VLR.gg team ID.
249 /// * `page` - Page number (1-indexed).
250 ///
251 /// # Examples
252 ///
253 /// ```no_run
254 /// # async fn example() -> vlr_scraper::Result<()> {
255 /// use vlr_scraper::VlrClient;
256 ///
257 /// let client = VlrClient::new();
258 /// let matches = client.get_team_matchlist(6530, 1).await?;
259 /// for m in &matches {
260 /// let teams: Vec<_> = m.teams.iter().map(|t| t.name.as_str()).collect();
261 /// println!("[{}] {}", m.league_name, teams.join(" vs "));
262 /// }
263 /// # Ok(())
264 /// # }
265 /// ```
266 #[instrument(skip(self))]
267 pub async fn get_team_matchlist(&self, team_id: u32, page: u8) -> Result<Vec<MatchItem>> {
268 vlr_scraper::teams::matchlist::get_team_matchlist(&self.http, team_id, page).await
269 }
270
271 /// Fetch a team's roster transaction history (joins, leaves, inactive changes).
272 ///
273 /// Returns a `Vec<TeamTransaction>` where each entry contains the date,
274 /// action type, player info (id, alias, real name, country code), position,
275 /// and an optional reference URL.
276 ///
277 /// # Arguments
278 ///
279 /// * `team_id` - The VLR.gg team ID (found in team page URLs).
280 ///
281 /// # Examples
282 ///
283 /// ```no_run
284 /// # async fn example() -> vlr_scraper::Result<()> {
285 /// use vlr_scraper::VlrClient;
286 ///
287 /// let client = VlrClient::new();
288 /// let transactions = client.get_team_transactions(6530).await?;
289 /// for txn in &transactions {
290 /// println!(
291 /// "{:?} — {} {} ({})",
292 /// txn.date, txn.action, txn.player_alias, txn.position
293 /// );
294 /// }
295 /// # Ok(())
296 /// # }
297 /// ```
298 #[instrument(skip(self))]
299 pub async fn get_team_transactions(&self, team_id: u32) -> Result<Vec<TeamTransaction>> {
300 vlr_scraper::teams::transactions::get_team_transactions(&self.http, team_id).await
301 }
302
303 /// Fetch a complete team profile including info, roster, event placements, and total winnings.
304 ///
305 /// The returned [`Team`] contains:
306 /// - [`TeamInfo`] — name, tag, logo URL, country/country code, and social links
307 /// - [`TeamRosterMember`] entries with player/staff info, roles, and captain status
308 /// - [`EventPlacement`] history with stage results and prize earnings
309 /// - Total career winnings as an optional string
310 ///
311 /// # Arguments
312 ///
313 /// * `team_id` - The VLR.gg team ID (found in team page URLs).
314 ///
315 /// # Examples
316 ///
317 /// ```no_run
318 /// # async fn example() -> vlr_scraper::Result<()> {
319 /// use vlr_scraper::VlrClient;
320 ///
321 /// let client = VlrClient::new();
322 /// let team = client.get_team(6530).await?;
323 /// println!("{} ({:?})", team.info.name, team.info.tag);
324 /// for member in &team.roster {
325 /// println!(" {} — {}", member.alias, member.role);
326 /// }
327 /// # Ok(())
328 /// # }
329 /// ```
330 #[instrument(skip(self))]
331 pub async fn get_team(&self, team_id: u32) -> Result<Team> {
332 vlr_scraper::teams::info::get_team(&self.http, team_id).await
333 }
334}
335
336impl Default for VlrClient {
337 fn default() -> Self {
338 Self::new()
339 }
340}