game_manager_lib/scrapers/
ea_play.rs1use crate::constants::GAME_PASS_BATCH_SIZE;
4use reqwest::Client;
5use serde::{Deserialize, Serialize};
6
7const EA_PLAY_SIGL: &str = "b8900d09-a491-44cc-916e-32b5acae621b";
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct EAPlayGame {
13 pub store_id: String,
14 pub title: String,
15 pub description: Option<String>,
16 pub image_poster: Option<String>, pub image_hero: Option<String>, pub categories: Vec<String>,
19 pub developer: Option<String>,
20 pub store_url: String,
21}
22
23fn parse_ea_play_products(products: &serde_json::Value) -> Vec<EAPlayGame> {
24 let mut games = Vec::new();
25
26 let Some(products) = products.as_object() else {
27 return games;
28 };
29
30 for (store_id, product) in products {
31 let product_type = product["ProductType"].as_str().unwrap_or("");
33 if product_type != "Game" {
34 continue;
35 }
36
37 let categories = product["Categories"]
39 .as_array()
40 .map(|arr| {
41 arr.iter()
42 .filter_map(|v| v.as_str().map(|s| s.to_string()))
43 .collect()
44 })
45 .unwrap_or_default();
46
47 let game = EAPlayGame {
49 store_id: store_id.clone(),
50 title: product["ProductTitle"].as_str().unwrap_or("?").to_string(),
51 description: product["ProductDescription"]
52 .as_str()
53 .map(|s| s.to_string()),
54 image_poster: product["ImagePoster"]["URI"]
55 .as_str()
56 .map(|s| s.to_string()),
57 image_hero: product["ImageHero"]["URI"].as_str().map(|s| s.to_string()),
58 categories,
59 developer: product["DeveloperName"].as_str().map(|s| s.to_string()),
60 store_url: format!("https://www.xbox.com/pt-BR/games/store/_/{}", store_id),
61 };
62
63 games.push(game);
64 }
65
66 games
67}
68
69pub async fn fetch_ea_play_catalog(language: &str) -> Result<Vec<EAPlayGame>, String> {
70 let client = Client::new();
71
72 let url = format!(
74 "https://catalog.gamepass.com/sigls/v2?id={}&language={}&market=BR",
75 EA_PLAY_SIGL, language
76 );
77
78 let sigls: Vec<serde_json::Value> = client
79 .get(&url)
80 .send()
81 .await
82 .map_err(|e| e.to_string())?
83 .json()
84 .await
85 .map_err(|e| e.to_string())?;
86
87 let ids: Vec<String> = sigls
89 .iter()
90 .filter_map(|v| v["id"].as_str().map(|s| s.to_string()))
91 .collect();
92
93 let mut games = Vec::new();
95
96 for chunk in ids.chunks(GAME_PASS_BATCH_SIZE) {
97 let body = serde_json::json!({ "Products": chunk });
98
99 let response: serde_json::Value = client
100 .post(format!(
101 "https://catalog.gamepass.com/products?market=BR&language={}&hydration=MobileDetailsForConsole",
102 language
103 ))
104 .header("Content-Type", "application/json")
105 .json(&body)
106 .send()
107 .await
108 .map_err(|e| e.to_string())?
109 .json()
110 .await
111 .map_err(|e| e.to_string())?;
112
113 games.extend(parse_ea_play_products(&response["Products"]));
114
115 if !chunk.is_empty() {
117 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
118 }
119 }
120
121 Ok(games)
122}