1pub mod models;
2mod utils;
3
4use std::collections::HashMap;
5use std::fs;
6use std::path::Path;
7
8#[cfg(all(feature = "log", not(feature = "python")))]
9use log::{trace, warn};
10use models::enchantment::SkyblockEnchantment;
11use models::item::SkyblockItem;
12use models::npc::SkyblockNpc;
13use models::pet::SkyblockPet;
14use models::shop::SkyblockShop;
15use models::zone::SkyblockZone;
16pub use models::{UpgradeCost, UpgradeType, enchantment, item, pet, recipe};
17#[cfg(feature = "python")]
18use pyo3::exceptions::PyValueError;
19#[cfg(feature = "python")]
20use pyo3::prelude::*;
21#[cfg(feature = "python")]
22use pyo3::types::PyModule;
23use rustc_hash::FxHashMap;
24use serde::Deserialize;
25#[cfg(not(feature = "python"))]
26use skyblock_repo_macros::getter;
27pub use utils::{delete_repo, download_repo};
28
29#[cfg(feature = "python")]
30#[pymodule]
31fn skyblock_repo(m: &Bound<'_, PyModule>) -> PyResult<()> {
32	m.add_class::<SkyblockRepo>()?;
33	m.add_function(pyo3::wrap_pyfunction!(download_repo, m)?)?;
34	m.add_function(pyo3::wrap_pyfunction!(delete_repo, m)?)?;
35
36	Ok(())
37}
38
39#[derive(Deserialize)]
40struct RepoStructure {
41	#[allow(dead_code)]
42	version: u8,
43	paths: HashMap<String, String>,
44}
45
46#[cfg_attr(feature = "python", pyclass)]
48pub struct SkyblockRepo {
49	pub enchantments: FxHashMap<String, SkyblockEnchantment>,
50	pub items: FxHashMap<String, SkyblockItem>,
51	pub npcs: FxHashMap<String, SkyblockNpc>,
52	pub pets: FxHashMap<String, SkyblockPet>,
53	pub shops: FxHashMap<String, SkyblockShop>,
54	pub zones: FxHashMap<String, SkyblockZone>,
55}
56
57#[cfg(feature = "python")]
58#[pymethods]
59impl SkyblockRepo {
60	#[must_use]
70	#[new]
71	pub fn new() -> PyResult<Self> {
72		let structure: RepoStructure =
73			serde_json::from_str(&fs::read_to_string("SkyblockRepo/manifest.json")?)
74				.map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
75
76		let mut repo = Self {
77			enchantments: FxHashMap::default(),
78			items: FxHashMap::default(),
79			npcs: FxHashMap::default(),
80			pets: FxHashMap::default(),
81			shops: FxHashMap::default(),
82			zones: FxHashMap::default(),
83		};
84
85		for path_name in structure.paths.values() {
86			let path = &format!("SkyblockRepo/{}", path_name);
87			let path = Path::new(path);
88			let data_entries = fs::read_dir(&path)?;
89
90			for json in data_entries {
91				let json = json?.path();
92				let content = fs::read_to_string(&json)?;
93
94				match path_name.as_str() {
95					| "enchantments" => {
96						let parsed: SkyblockEnchantment = serde_json::from_str(&content)
97							.map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
98						repo.enchantments.insert(parsed.internal_id.clone(), parsed);
99					},
100					| "items" => {
101						let parsed: SkyblockItem = serde_json::from_str(&content)
102							.map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
103						repo.items.insert(parsed.internal_id.clone(), parsed);
104					},
105					| "npcs" => {
106						let parsed: SkyblockNpc = serde_json::from_str(&content)
107							.map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
108						repo.npcs.insert(parsed.internal_id.clone(), parsed);
109					},
110					| "pets" => {
111						let parsed: SkyblockPet = serde_json::from_str(&content)
112							.map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
113						repo.pets.insert(parsed.internal_id.clone(), parsed);
114					},
115					| "shops" => {
116						let parsed: SkyblockShop = serde_json::from_str(&content)
117							.map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
118						repo.shops.insert(parsed.internal_id.clone(), parsed);
119					},
120					| "zones" => {
121						let parsed: SkyblockZone = serde_json::from_str(&content)
122							.map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
123						repo.zones.insert(parsed.internal_id.clone(), parsed);
124					},
125					| _ => continue,
126				}
127			}
128		}
129
130		Ok(repo)
131	}
132
133	#[must_use]
135	#[inline]
136	pub fn get_enchantment_by_id(
137		&self,
138		id: &str,
139	) -> Option<SkyblockEnchantment> {
140		self.enchantments.get(&id.to_uppercase()).cloned()
141	}
142
143	#[must_use]
145	#[inline]
146	pub fn get_item_by_id(
147		&self,
148		id: &str,
149	) -> Option<SkyblockItem> {
150		self.items.get(&id.to_uppercase()).cloned()
151	}
152
153	#[must_use]
155	#[inline]
156	pub fn get_npc_by_id(
157		&self,
158		id: &str,
159	) -> Option<SkyblockNpc> {
160		self.npcs.get(&id.to_uppercase()).cloned()
161	}
162
163	#[must_use]
165	#[inline]
166	pub fn get_pet_by_id(
167		&self,
168		id: &str,
169	) -> Option<SkyblockPet> {
170		self.pets.get(&id.to_uppercase()).cloned()
171	}
172
173	#[must_use]
175	#[inline]
176	pub fn get_shop_by_id(
177		&self,
178		id: &str,
179	) -> Option<SkyblockShop> {
180		self.shops.get(&id.to_uppercase()).cloned()
181	}
182
183	#[must_use]
185	#[inline]
186	pub fn get_zone_by_id(
187		&self,
188		id: &str,
189	) -> Option<SkyblockZone> {
190		self.zones.get(&id.to_uppercase()).cloned()
191	}
192}
193
194#[cfg(not(feature = "python"))]
195impl SkyblockRepo {
196	pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
197		let structure: RepoStructure =
198			serde_json::from_str(&fs::read_to_string("SkyblockRepo/manifest.json")?)?;
199
200		let mut repo = Self {
201			enchantments: FxHashMap::default(),
202			items: FxHashMap::default(),
203			npcs: FxHashMap::default(),
204			pets: FxHashMap::default(),
205			shops: FxHashMap::default(),
206			zones: FxHashMap::default(),
207		};
208
209		for path_name in structure.paths.values() {
210			let path = &format!("SkyblockRepo/{}", path_name);
211			let path = Path::new(path);
212			let data_entries = fs::read_dir(&path)?;
213
214			for json in data_entries {
215				let json = json?.path();
216				#[cfg(feature = "log")]
217				trace!("parsing {:?}", json);
218				let content = fs::read_to_string(&json)?;
219
220				match path_name.as_str() {
221					| "enchantments" => {
222						let parsed: SkyblockEnchantment = serde_json::from_str(&content)?;
223						repo.enchantments.insert(parsed.internal_id.clone(), parsed);
224					},
225					| "items" => {
226						let parsed: SkyblockItem = serde_json::from_str(&content)?;
227						repo.items.insert(parsed.internal_id.clone(), parsed);
228					},
229					| "npcs" => {
230						let parsed: SkyblockNpc = serde_json::from_str(&content)?;
231						repo.npcs.insert(parsed.internal_id.clone(), parsed);
232					},
233					| "pets" => {
234						let parsed: SkyblockPet = serde_json::from_str(&content)?;
235						repo.pets.insert(parsed.internal_id.clone(), parsed);
236					},
237					| "shops" => {
238						let parsed: SkyblockShop = serde_json::from_str(&content)?;
239						repo.shops.insert(parsed.internal_id.clone(), parsed);
240					},
241					| "zones" => {
242						let parsed: SkyblockZone = serde_json::from_str(&content)?;
243						repo.zones.insert(parsed.internal_id.clone(), parsed);
244					},
245					#[cfg_attr(not(feature = "log"), allow(unused_variables))]
246					| other => {
247						#[cfg(feature = "log")]
248						warn!("Unknown dir found while parsing SkyblockData: {}", other);
249						continue;
250					},
251				}
252			}
253		}
254
255		Ok(repo)
256	}
257
258	getter!(enchantment);
259	getter!(item);
260	getter!(pet);
261	getter!(npc);
262	getter!(shop);
263	getter!(zone);
264}