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}