1use ahash::{HashMap, HashMapExt};
4use log::warn;
5use serde::de::{self, MapAccess, SeqAccess, Visitor};
6use serde::{Deserialize, Deserializer, Serialize};
7use serde_json::json;
8use std::{fmt, marker::PhantomData, num::NonZero, sync::OnceLock, vec};
9use tokio::sync::{oneshot, RwLock};
10
11use crate::auth::LoginError;
12use crate::{auth::NadeoClient, client::*};
13
14fn query_lo(length: u32, offset: u32) -> Vec<(&'static str, String)> {
16 vec![
17 ("length", length.to_string()),
18 ("offset", offset.to_string()),
19 ]
20}
21
22#[derive(Debug)]
23pub enum NadeoError {
24 ArgsErr(String),
25 ReqwestError(reqwest::Error),
26 SerdeJsonWithURL(serde_json::Error, String),
27 SerdeJson(serde_json::Error),
28 Login(LoginError),
29}
30
31impl From<reqwest::Error> for NadeoError {
32 fn from(e: reqwest::Error) -> Self {
33 NadeoError::ReqwestError(e)
34 }
35}
36
37impl From<serde_json::Error> for NadeoError {
38 fn from(e: serde_json::Error) -> Self {
39 NadeoError::SerdeJson(e)
40 }
41}
42
43impl From<LoginError> for NadeoError {
44 fn from(e: LoginError) -> Self {
45 NadeoError::Login(e)
46 }
47}
48
49pub trait LiveApiClient: NadeoApiClient {
51 async fn get_monthly_campaign(
53 &self,
54 ty: MonthlyCampaignType,
55 length: u32,
56 offset: u32,
57 ) -> Result<MonthlyCampaign_List, NadeoError> {
58 let mut query: Vec<(&str, String)> = query_lo(length, offset);
59 if matches!(ty, MonthlyCampaignType::Royal) {
60 query.push(("royal", "true".to_string()));
61 }
62 let (rb, permit) = self.live_get("api/token/campaign/month").await;
63 let j: Value = rb.query(&query).send().await?.json().await?;
64 drop(permit);
65 Ok(serde_json::from_value(j)?)
66 }
67
68 async fn get_map_group_leaderboard(
73 &self,
74 group_uid: &str,
75 map_uid: &str,
76 only_world: bool,
77 length: u32,
78 offset: u32,
79 ) -> Result<MapGroupLeaderboard, NadeoError> {
80 let mut query = query_lo(length, offset);
81 query.push(("onlyWorld", only_world.to_string()));
82 let (rb, permit) = self
83 .live_get(&format!(
84 "api/token/leaderboard/group/{group_uid}/map/{map_uid}/top"
85 ))
86 .await;
87 let j: Value = rb.query(&query).send().await?.json().await?;
88 drop(permit);
89 Ok(serde_json::from_value(j)?)
90 }
91
92 async fn get_map_leaderboard(
98 &self,
99 map_uid: &str,
100 only_world: bool,
101 length: u32,
102 offset: u32,
103 ) -> Result<MapGroupLeaderboard, NadeoError> {
104 self.get_map_group_leaderboard("Personal_Best", map_uid, only_world, length, offset)
105 .await
106 }
107
108 async fn get_lb_positions_by_time(
116 &self,
117 uid_scores: &[(&str, NonZero<u32>)],
118 ) -> Result<Vec<RecordsByTime>, NadeoError> {
119 self.get_lb_positions_by_time_group(uid_scores, "Personal_Best")
120 .await
121 }
122
123 async fn get_lb_positions_by_time_group(
131 &self,
132 uid_scores: &[(&str, NonZero<u32>)],
133 group_uid: &str,
134 ) -> Result<Vec<RecordsByTime>, NadeoError> {
135 let mut query = vec![];
136 for (uid, score) in uid_scores.iter() {
137 query.push((format!("scores[{}]", uid), score.get().to_string()));
138 }
139
140 let mut body_maps = vec![];
141 for (uid, _) in uid_scores.iter() {
142 body_maps.push(json!({
143 "mapUid": uid,
144 "groupUid": group_uid,
145 }));
146 }
147 let body = json!({ "maps": body_maps });
148
149 let (rb, permit) = self
150 .live_post("api/token/leaderboard/group/map", &body)
151 .await;
152 let j: Value = rb.query(&query).send().await?.json().await?;
153 drop(permit);
154 Ok(serde_json::from_value(j)?)
155 }
156
157 async fn get_lb_position_by_time_batched(
163 &'static self,
164 map_uid: &str,
165 score: NonZero<u32>,
166 ) -> Result<ScoreToPos, oneshot::error::RecvError> {
167 if score.get() >= 2147483648 {
168 warn!("score >= 2147483648: {}", map_uid);
169 }
170 let ret_chan = self
171 .push_rec_position_req(map_uid, score.get() as i64)
172 .await;
173 Ok(ret_chan.await?)
174 }
175
176 async fn push_rec_position_req(
178 &'static self,
179 map_uid: &str,
180 score: i64,
181 ) -> oneshot::Receiver<ScoreToPos>;
182
183 async fn get_group_surround(
187 &self,
188 group_uid: &str,
189 map_uid: &str,
190 lower: i32,
191 upper: i32,
192 score: u32,
193 only_world: bool,
194 ) -> Result<RecordsSurround, NadeoError> {
195 let (rb, permit) = self
196 .live_get(&format!(
197 "api/token/leaderboard/group/{group_uid}/map/{map_uid}/surround/{lower}/{upper}"
198 ))
199 .await;
200 let j: Value = rb
201 .query(&[
202 ("score", score.to_string()),
203 ("onlyWorld", only_world.to_string()),
204 ])
205 .send()
206 .await?
207 .json()
208 .await?;
209 drop(permit);
210 Ok(serde_json::from_value(j)?)
211 }
212
213 async fn get_pb_surround(
219 &self,
220 map_uid: &str,
221 lower: i32,
222 upper: i32,
223 score: u32,
224 only_world: bool,
225 ) -> Result<RecordsSurround, NadeoError> {
226 self.get_group_surround("Personal_Best", map_uid, lower, upper, score, only_world)
227 .await
228 }
229
230 async fn get_map_info(&self, map_uid: &str) -> Result<Option<MapInfo>, NadeoError> {
236 let (rb, permit) = self.live_get(&format!("api/token/map/{map_uid}")).await;
237 let resp = rb.send().await?;
238 if resp.status().as_u16() == 404 {
239 drop(permit);
240 return Ok(None);
241 }
242 let j: Value = resp.json().await?;
243 drop(permit);
244 Ok(serde_json::from_value(j)?)
245 }
246
247 async fn get_map_info_multiple(&self, map_uids: &[&str]) -> Result<MapInfos, NadeoError> {
251 if map_uids.len() > 100 {
252 return Err(NadeoError::ArgsErr("map_uids length must be <= 100".into()));
253 }
254 let url = "api/token/map/get-multiple?mapUidList=".to_string() + &map_uids.join(",");
255 let j = self.run_live_get(&url).await?;
256 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
257 }
258
259 async fn get_club_activities(
263 &self,
264 club_id: i32,
265 length: u32,
266 offset: u32,
267 active: bool,
268 ) -> Result<ClubActivityList, NadeoError> {
269 let mut query = query_lo(length, offset);
270 query.push(("active", active.to_string()));
271 let url = format!("api/token/club/{}/activity", club_id);
272 let (rb, permit) = self.live_get(&url).await;
273 let j: Value = rb.query(&query).send().await?.json().await?;
274 drop(permit);
275 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
276 }
277
278 async fn get_club_info(&self, club_id: i32) -> Result<ClubInfo, NadeoError> {
282 let url = format!("api/token/club/{}", club_id);
283 let (rb, permit) = self.live_get(&url).await;
284 let j: Value = rb.send().await?.json().await?;
285 drop(permit);
286 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
287 }
288
289 async fn get_club_campaign_by_id(
293 &self,
294 club_id: i32,
295 campaign_id: i32,
296 ) -> Result<ClubCampaignById, NadeoError> {
297 let url = format!("api/token/club/{}/campaign/{}", club_id, campaign_id);
298 let (rb, permit) = self.live_get(&url).await;
299 let j: Value = rb.send().await?.json().await?;
300 eprintln!("{:?}", j);
301 drop(permit);
302 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
303 }
304
305 async fn get_club_campaigns(
309 &self,
310 length: u32,
312 offset: u32,
313 name: Option<&str>,
314 ) -> Result<ClubCampaignList, NadeoError> {
315 let mut query = query_lo(length, offset);
316 if let Some(name) = name {
317 query.push(("name", name.to_string()));
318 }
319 let url = format!("api/token/club/campaign");
320 let (rb, permit) = self.live_get(&url).await;
321 let j: Value = rb.query(&query).send().await?.json().await?;
322 drop(permit);
323 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
324 }
325
326 async fn get_club_rooms(
330 &self,
331 length: u32,
332 offset: u32,
333 name: Option<&str>,
334 ) -> Result<ClubRoomList, NadeoError> {
335 let mut query = query_lo(length, offset);
336 if let Some(name) = name {
337 query.push(("name", name.to_string()));
338 }
339 let url = format!("api/token/club/room");
340 let (rb, permit) = self.live_get(&url).await;
341 let j: Value = rb.query(&query).send().await?.json().await?;
342 drop(permit);
343 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
344 }
345
346 async fn get_club_room_by_id(
350 &self,
351 club_id: i32,
352 activity_id: i32,
353 ) -> Result<ClubRoom, NadeoError> {
354 let url = format!("api/token/club/{}/room/{}", club_id, activity_id);
355 let (rb, permit) = self.live_get(&url).await;
356 let j: Value = rb.send().await?.json().await?;
357 drop(permit);
358 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
359 }
360
361 async fn edit_club_room_by_id(
362 &self,
363 club_id: i32,
364 activity_id: i32,
365 body: &ClubRoom_Room_ForEdit,
366 ) -> Result<ClubRoom, NadeoError> {
367 let url = format!("api/token/club/{}/room/{}/edit", club_id, activity_id);
368 let body =
369 serde_json::to_value(body).map_err(|e| NadeoError::SerdeJsonWithURL(e, url.clone()))?;
370 let (rb, permit) = self.live_post(&url, &body).await;
371 let j: Value = rb.send().await?.json().await?;
372 drop(permit);
373 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
374 }
375
376 async fn create_club_room(
377 &self,
378 club_id: i32,
379 body: &ClubRoom_Room_ForEdit,
380 ) -> Result<ClubRoom, NadeoError> {
381 let url = format!("api/token/club/{}/room/create", club_id);
382 let body =
383 serde_json::to_value(body).map_err(|e| NadeoError::SerdeJsonWithURL(e, url.clone()))?;
384 let (rb, permit) = self.live_post(&url, &body).await;
385 let j: Value = rb.send().await?.json().await?;
386 drop(permit);
387 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
388 }
389
390 async fn get_club_activity_list(
396 &self,
397 club_id: i32,
398 length: u32,
399 offset: u32,
400 active: Option<bool>,
401 ) -> Result<ActivityList, NadeoError> {
402 let mut query = query_lo(length, offset);
403 if let Some(active) = active {
404 query.push(("active", active.to_string()));
405 }
406 let url = format!("api/token/club/{}/activity", club_id);
407 let (rb, permit) = self.live_get(&url).await;
408 let j: Value = rb.query(&query).send().await?.json().await?;
409 drop(permit);
410 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
411 }
412
413 async fn edit_club_activity(
414 &self,
415 club_id: i32,
416 activity_id: i32,
417 public: Option<bool>,
418 active: Option<bool>,
419 ) -> Result<Value, NadeoError> {
420 let url = format!("api/token/club/{}/activity/{}/edit", club_id, activity_id);
421 let mut body = serde_json::Value::Object(Default::default());
422 if let Some(public) = public {
423 body["public"] = serde_json::Value::Bool(public);
424 }
425 if let Some(active) = active {
426 body["active"] = serde_json::Value::Bool(active);
427 }
428 let (rb, permit) = self.live_post(&url, &body).await;
429 let j: Value = rb.send().await?.json().await?;
430 drop(permit);
431 Ok(j)
432 }
433
434 async fn get_clubs(
438 &self,
439 length: u32,
440 offset: u32,
441 name: Option<&str>,
442 ) -> Result<ClubList, NadeoError> {
443 let mut query = query_lo(length, offset);
444 if let Some(name) = name {
445 query.push(("name", name.to_string()));
446 }
447 let url = format!("api/token/club");
448 let (rb, permit) = self.live_get(&url).await;
449 let j: Value = rb.query(&query).send().await?.json().await?;
450 drop(permit);
451 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
452 }
453
454 async fn get_clubs_mine(&self, length: u32, offset: u32) -> Result<ClubList, NadeoError> {
455 let query = query_lo(length, offset);
456 let url = format!("api/token/club/mine");
457 let (rb, permit) = self.live_get(&url).await;
458 let j: Value = rb.query(&query).send().await?.json().await?;
459 drop(permit);
460 Ok(serde_json::from_value(j).map_err(|e| NadeoError::SerdeJsonWithURL(e, url))?)
461 }
462}
463
464impl LiveApiClient for NadeoClient {
465 async fn push_rec_position_req(
466 &'static self,
467 map_uid: &str,
468 score: i64,
469 ) -> oneshot::Receiver<ScoreToPos> {
470 let (tx, rx) = oneshot::channel();
471 self.batcher_lb_pos_by_time.push(map_uid, score, tx).await;
472 self.check_start_batcher_lb_pos_by_time_loop().await;
473 rx
474 }
475}
476
477pub struct BatcherLbPosByTime {
482 queued: RwLock<BatcherLbPosByTimeQueue>,
483 loop_started: OnceLock<bool>,
484}
485
486impl BatcherLbPosByTime {
487 pub fn new() -> Self {
488 Self {
489 queued: RwLock::new(BatcherLbPosByTimeQueue::new()),
490 loop_started: OnceLock::new(),
491 }
492 }
493
494 pub async fn push(&self, map_uid: &str, score: i64, ret_chan: oneshot::Sender<ScoreToPos>) {
495 let mut q = self.queued.write().await;
496 q.push(map_uid, score, ret_chan);
497 }
498
499 pub fn has_loop_started(&self) -> bool {
500 self.loop_started.get().is_some()
501 }
502
503 pub fn set_loop_started(&self) -> Result<(), bool> {
504 self.loop_started.set(true)
505 }
506
507 pub async fn is_empty(&self) -> bool {
508 self.queued.read().await.nb_queued == 0
509 }
510
511 pub async fn nb_queued(&self) -> usize {
512 self.queued.read().await.nb_queued
513 }
514
515 pub async fn nb_in_progress(&self) -> usize {
516 self.queued.read().await.nb_in_progress
517 }
518
519 pub async fn get_batch_size_avg(&self) -> (f64, usize) {
520 let q = self.queued.read().await;
521 (q.avg_batch_size, q.nb_batches)
522 }
523
524 pub async fn run_batch<T: LiveApiClient>(&self, api: &T) -> Result<Vec<String>, NadeoError> {
525 let mut q = self.queued.write().await;
527 let batch = q.take_up_to(50);
528 let batch_size = batch.len();
534 q.nb_in_progress += batch_size;
535 drop(q);
536 let uid_scores: Vec<(&str, _)> = batch
540 .iter()
541 .map(|(uid, score, _)| (uid.as_str(), NonZero::new(*score as u32).unwrap()))
542 .collect();
543 let resp = api.get_lb_positions_by_time(&uid_scores).await?;
544 if resp.len() != batch_size {
545 warn!(
546 "[BatcherLbPosByTime] resp.len() != batch_size: {} != {}",
547 resp.len(),
548 batch_size
549 );
550 }
551 let r_lookup = resp
553 .into_iter()
554 .filter_map(|r| {
555 let pos = r.get_global_pos()?;
556 Some((r.mapUid, pos))
557 })
558 .collect::<HashMap<String, i32>>();
559
560 let mut uids = vec![];
561 for (uid, score, sender) in batch {
562 let pos = r_lookup.get(&uid).copied();
563 let _ = sender.send(ScoreToPos { score, pos });
564 uids.push(uid);
565 }
566
567 let mut q = self.queued.write().await;
569 q.nb_in_progress -= batch_size;
570 drop(q);
571 Ok(uids)
573 }
574}
575
576#[derive(Debug, Clone)]
577pub struct ScoreToPos {
578 pub score: i64,
579 pub pos: Option<i32>,
580}
581impl ScoreToPos {
582 pub fn get_s_p(&self) -> (i64, Option<i32>) {
583 (self.score, self.pos)
584 }
585}
586
587pub struct BatcherLbPosByTimeQueue {
588 pub queue: HashMap<String, Vec<(i64, oneshot::Sender<ScoreToPos>)>>,
589 pub nb_queued: usize,
590 pub nb_in_progress: usize,
591 pub avg_batch_size: f64,
592 pub nb_batches: usize,
593}
594
595impl BatcherLbPosByTimeQueue {
596 pub fn new() -> Self {
597 Self {
598 queue: HashMap::new(),
599 nb_queued: 0,
600 nb_in_progress: 0,
601 avg_batch_size: 0.0,
602 nb_batches: 0,
603 }
604 }
605
606 pub fn push(&mut self, map_uid: &str, score: i64, ret_chan: oneshot::Sender<ScoreToPos>) {
607 self.nb_queued += 1;
608 self.queue
609 .entry(map_uid.to_string())
610 .or_insert_with(Vec::new)
611 .push((score, ret_chan));
612 }
613
614 pub fn take_up_to(&mut self, limit: usize) -> Vec<(String, i64, oneshot::Sender<ScoreToPos>)> {
615 let mut ret = vec![];
616 let mut to_rem = vec![];
617 for (map_uid, v) in self.queue.iter_mut() {
618 match v.pop() {
619 Some((score, ret_chan)) => {
620 self.nb_queued -= 1;
621 ret.push((map_uid.clone(), score, ret_chan));
622 }
623 None => {}
624 }
625 if v.is_empty() {
626 to_rem.push(map_uid.clone());
627 }
628 if ret.len() >= limit {
629 break;
630 }
631 }
632 for k in to_rem {
633 let v = self.queue.remove(&k);
634 if let Some(v) = v {
635 self.nb_queued -= v.len();
636 if v.len() > 0 {
637 panic!("v.len() > 0: {}: {:?}", k, v);
638 }
639 }
640 }
641 self.update_avg_batch_size(ret.len());
642 ret
643 }
644
645 fn update_avg_batch_size(&mut self, batch_size: usize) {
646 self.nb_batches += 1;
647 self.avg_batch_size = (self.avg_batch_size * (self.nb_batches - 1) as f64
648 + batch_size as f64)
649 / self.nb_batches as f64;
650 }
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize)]
656#[allow(non_camel_case_types, non_snake_case)]
657pub struct MapInfos {
658 pub mapList: Vec<MapInfo>,
659 pub itemCount: i32,
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
664#[allow(non_camel_case_types, non_snake_case)]
665pub struct MapInfo {
666 pub uid: String,
667 pub mapId: String,
668 pub name: String,
669 pub author: String,
670 pub submitter: String,
671 pub authorTime: i64,
672 pub goldTime: i64,
673 pub silverTime: i64,
674 pub bronzeTime: i64,
675 pub nbLaps: i32,
676 pub valid: bool,
677 pub downloadUrl: String,
678 pub thumbnailUrl: String,
679 pub uploadTimestamp: i64,
680 pub updateTimestamp: i64,
681 pub fileSize: Option<i32>,
682 pub public: bool,
683 pub favorite: bool,
684 pub playable: bool,
685 pub mapStyle: String,
687 pub mapType: String,
689 pub collectionName: String,
690}
691
692#[derive(Debug, Clone, Serialize, Deserialize)]
694#[allow(non_camel_case_types, non_snake_case)]
695pub struct RecordsSurround {
696 pub groupUid: String,
697 pub mapUid: String,
698 pub tops: Vec<RecordsSurround_Top>,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
702#[allow(non_camel_case_types, non_snake_case)]
703pub struct RecordsSurround_Top {
704 pub zoneId: String,
705 pub zoneName: String,
706 pub top: Vec<RecordsSurround_TopEntry>,
707}
708
709#[derive(Debug, Clone, Serialize, Deserialize)]
710#[allow(non_camel_case_types, non_snake_case)]
711pub struct RecordsSurround_TopEntry {
712 pub accountId: String,
713 pub zoneId: String,
714 pub zoneName: String,
715 pub position: i32,
716 pub score: u32,
717 pub timestamp: Option<i64>,
718}
719
720#[derive(Debug, Clone, Serialize, Deserialize)]
722#[allow(non_camel_case_types, non_snake_case)]
723pub struct RecordsByTime {
724 pub groupUid: String,
725 pub mapUid: String,
726 pub score: i64,
727 pub zones: Vec<RecordsByTime_Zone>,
728}
729
730impl RecordsByTime {
731 pub fn get_global_pos(&self) -> Option<i32> {
732 Some(self.zones.get(0)?.ranking.position)
733 }
734}
735
736#[derive(Debug, Clone, Serialize, Deserialize)]
737#[allow(non_camel_case_types, non_snake_case)]
738pub struct RecordsByTime_Zone {
739 pub zoneId: String,
740 pub zoneName: String,
741 pub ranking: RecordsByTime_ZoneRanking,
742}
743
744#[derive(Debug, Clone, Serialize, Deserialize)]
745#[allow(non_camel_case_types, non_snake_case)]
746pub struct RecordsByTime_ZoneRanking {
747 pub position: i32,
748 pub length: i32,
749}
750
751#[derive(Debug, Clone, Serialize, Deserialize)]
753#[allow(non_camel_case_types, non_snake_case)]
754pub struct MapGroupLeaderboard {
755 pub groupUid: String,
756 pub mapUid: String,
757 pub tops: Vec<MapGroupLeaderboard_Top>,
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
762#[allow(non_camel_case_types, non_snake_case)]
763pub struct MapGroupLeaderboard_Top {
764 pub zoneId: String,
765 pub zoneName: String,
766 pub top: Vec<MapGroupLeaderboard_TopEntry>,
767}
768
769#[derive(Debug, Clone, Serialize, Deserialize)]
770#[allow(non_camel_case_types, non_snake_case)]
771pub struct MapGroupLeaderboard_TopEntry {
772 pub accountId: String,
773 pub zoneId: String,
774 pub zoneName: String,
775 pub position: i32,
776 pub score: i64,
778}
779
780#[derive(Debug, Clone, Serialize, Deserialize)]
782#[allow(non_camel_case_types, non_snake_case)]
783pub struct MonthlyCampaign_List {
784 pub monthList: Vec<MonthlyCampaign_Month>,
785 pub itemCount: i32,
786 pub nextRequestTimestamp: i64,
787 pub relativeNextRequest: i64,
788}
789
790#[derive(Debug, Clone, Serialize, Deserialize)]
791#[allow(non_camel_case_types, non_snake_case)]
792pub struct MonthlyCampaign_Month {
793 pub year: i32,
794 pub month: i32,
795 pub lastDay: i32,
796 pub days: Vec<MonthlyCampaign_Day>,
797 pub media: MonthlyCampaign_Media,
798}
799
800#[derive(Debug, Clone, Serialize, Deserialize)]
801#[allow(non_camel_case_types, non_snake_case)]
802pub struct MonthlyCampaign_Day {
803 pub campaignId: i32,
804 pub mapUid: String,
805 pub day: i32,
806 pub monthDay: i32,
807 pub seasonUid: String,
808 pub leaderboardGroup: Option<String>,
809 pub startTimestamp: i64,
810 pub endTimestamp: i64,
811 pub relativeStart: i64,
812 pub relativeEnd: i64,
813}
814
815#[derive(Debug, Clone, Serialize, Deserialize)]
816#[allow(non_camel_case_types, non_snake_case)]
817pub struct MonthlyCampaign_Media {
818 pub buttonBackgroundUrl: String,
819 pub buttonForegroundUrl: String,
820 pub decalUrl: String,
821 pub popUpBackgroundUrl: String,
822 pub popUpImageUrl: String,
823 pub liveButtonBackgroundUrl: String,
824 pub liveButtonForegroundUrl: String,
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize)]
828#[allow(non_camel_case_types, non_snake_case)]
829pub struct ClubActivityList {
830 pub activityList: Vec<ClubActivity>,
831 pub maxPage: i32,
832 pub itemCount: i32,
833}
834
835#[derive(Debug, Clone, Serialize, Deserialize)]
836#[allow(non_camel_case_types, non_snake_case)]
837pub struct ClubActivity {
838 pub id: i32,
839 pub name: String,
840 pub activityType: String,
841 pub activityId: i32,
842 pub targetActivityId: i32,
843 pub campaignId: i32,
844 pub position: i32,
845 pub public: bool,
846 pub active: bool,
847 pub externalId: i32,
848 pub featured: bool,
849 pub password: bool,
850 pub itemsCount: i32,
851 pub clubId: i32,
852 pub editionTimestamp: i64,
853 pub creatorAccountId: String,
854 pub latestEditorAccountId: String,
855 pub mediaUrl: String,
856 pub mediaUrlPngLarge: String,
857 pub mediaUrlPngMedium: String,
858 pub mediaUrlPngSmall: String,
859 pub mediaUrlDds: String,
860 pub mediaTheme: String,
861}
862
863#[derive(Debug, Clone, Serialize, Deserialize)]
864#[allow(non_camel_case_types, non_snake_case)]
865pub struct ClubInfo {
866 pub id: i32,
867 pub name: String,
868 pub tag: String,
869 pub description: String,
870 pub authorAccountId: String,
871 pub latestEditorAccountId: String,
872 pub iconUrl: String,
873 pub iconUrlPngLarge: String,
874 pub iconUrlPngMedium: String,
875 pub iconUrlPngSmall: String,
876 pub iconUrlDds: String,
877 pub logoUrl: String,
878 pub decalUrl: String,
879 pub decalUrlPngLarge: String,
880 pub decalUrlPngMedium: String,
881 pub decalUrlPngSmall: String,
882 pub decalUrlDds: String,
883 pub screen16x9Url: String,
884 pub screen16x9UrlPngLarge: String,
885 pub screen16x9UrlPngMedium: String,
886 pub screen16x9UrlPngSmall: String,
887 pub screen16x9UrlDds: String,
888 pub screen64x41Url: String,
889 pub screen64x41UrlPngLarge: String,
890 pub screen64x41UrlPngMedium: String,
891 pub screen64x41UrlPngSmall: String,
892 pub screen64x41UrlDds: String,
893 pub decalSponsor4x1Url: String,
894 pub decalSponsor4x1UrlPngLarge: String,
895 pub decalSponsor4x1UrlPngMedium: String,
896 pub decalSponsor4x1UrlPngSmall: String,
897 pub decalSponsor4x1UrlDds: String,
898 pub screen8x1Url: String,
899 pub screen8x1UrlPngLarge: String,
900 pub screen8x1UrlPngMedium: String,
901 pub screen8x1UrlPngSmall: String,
902 pub screen8x1UrlDds: String,
903 pub screen16x1Url: String,
904 pub screen16x1UrlPngLarge: String,
905 pub screen16x1UrlPngMedium: String,
906 pub screen16x1UrlPngSmall: String,
907 pub screen16x1UrlDds: String,
908 pub verticalUrl: String,
909 pub verticalUrlPngLarge: String,
910 pub verticalUrlPngMedium: String,
911 pub verticalUrlPngSmall: String,
912 pub verticalUrlDds: String,
913 pub backgroundUrl: String,
914 pub backgroundUrlJpgLarge: String,
915 pub backgroundUrlJpgMedium: String,
916 pub backgroundUrlJpgSmall: String,
917 pub backgroundUrlDds: String,
918 pub creationTimestamp: i64,
919 pub popularityLevel: i32,
920 pub state: String,
921 pub featured: bool,
922 pub walletUid: String,
923 pub metadata: String,
924 pub editionTimestamp: i64,
925 pub iconTheme: String,
926 pub decalTheme: String,
927 pub screen16x9Theme: String,
928 pub screen64x41Theme: String,
929 pub screen8x1Theme: String,
930 pub screen16x1Theme: String,
931 pub verticalTheme: String,
932 pub backgroundTheme: String,
933 pub verified: bool,
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize)]
937#[allow(non_camel_case_types, non_snake_case)]
938pub struct ClubCampaignById {
939 pub clubDecalUrl: String,
940 pub campaignId: i32,
941 pub activityId: i32,
942 pub campaign: ClubCampaign,
943 pub popularityLevel: i32,
944 pub publicationTimestamp: i64,
945 pub creationTimestamp: i64,
946 pub creatorAccountId: String,
947 pub latestEditorAccountId: String,
948 pub id: i32,
949 pub clubId: i32,
950 pub clubName: String,
951 pub name: String,
952 pub mapsCount: i32,
953 pub mediaUrl: String,
954 pub mediaUrlPngLarge: String,
955 pub mediaUrlPngMedium: String,
956 pub mediaUrlPngSmall: String,
957 pub mediaUrlDds: String,
958 pub mediaTheme: String,
959}
960
961#[derive(Debug, Clone, Serialize, Deserialize)]
962#[allow(non_camel_case_types, non_snake_case)]
963pub struct ClubCampaign {
964 pub id: i32,
965 pub seasonUid: String,
966 pub name: String,
967 pub color: String,
968 pub useCase: i32,
969 pub clubId: i32,
970 pub leaderboardGroupUid: String,
971 pub publicationTimestamp: i64,
972 pub startTimestamp: i64,
973 pub endTimestamp: i64,
974 pub rankingSentTimestamp: Option<i64>,
975 pub year: i32,
982 pub week: i32,
983 pub day: i32,
984 pub monthYear: i32,
985 pub month: i32,
986 pub monthDay: i32,
987 pub published: bool,
988 pub playlist: Vec<ClubCampaign_PlaylistEntry>,
989 pub latestSeasons: Vec<ClubCampaign_LatestSeason>,
990 pub categories: Vec<ClubCampaign_Category>,
991 pub media: ClubCampaign_Media,
992 pub editionTimestamp: i64,
993}
994
995#[derive(Debug, Clone, Serialize, Deserialize)]
996#[allow(non_camel_case_types, non_snake_case)]
997pub struct ClubCampaign_PlaylistEntry {
998 pub id: i32,
999 pub position: i32,
1000 pub mapUid: String,
1001}
1002
1003#[derive(Debug, Clone, Serialize, Deserialize)]
1004#[allow(non_camel_case_types, non_snake_case)]
1005pub struct ClubCampaign_LatestSeason {
1006 pub uid: String,
1007 pub name: String,
1008 pub startTimestamp: i64,
1009 pub endTimestamp: i64,
1010 pub relativeStart: i64,
1011 pub relativeEnd: i64,
1012 pub campaignId: i32,
1013 pub active: bool,
1014}
1015
1016#[derive(Debug, Clone, Serialize, Deserialize)]
1017#[allow(non_camel_case_types, non_snake_case)]
1018pub struct ClubCampaign_Category {
1019 pub position: i32,
1020 pub length: i32,
1021 pub name: String,
1022}
1023
1024#[derive(Debug, Clone, Serialize, Deserialize)]
1025#[allow(non_camel_case_types, non_snake_case)]
1026pub struct ClubCampaign_Media {
1027 pub buttonBackgroundUrl: String,
1028 pub buttonForegroundUrl: String,
1029 pub decalUrl: String,
1030 pub popUpBackgroundUrl: String,
1031 pub popUpImageUrl: String,
1032 pub liveButtonBackgroundUrl: String,
1033 pub liveButtonForegroundUrl: String,
1034}
1035
1036#[derive(Debug, Clone, Serialize, Deserialize)]
1037#[allow(non_camel_case_types, non_snake_case)]
1038pub struct ClubCampaignList {
1039 pub clubCampaignList: Vec<ClubCampaignById>,
1040 pub maxPage: i32,
1041 pub itemCount: i32,
1042}
1043
1044#[derive(Debug, Clone, Serialize, Deserialize)]
1045#[allow(non_camel_case_types, non_snake_case)]
1046pub struct ClubRoomList {
1047 pub clubRoomList: Vec<ClubRoom>,
1048 pub maxPage: i32,
1049 pub itemCount: i32,
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize)]
1053#[allow(non_camel_case_types, non_snake_case)]
1054pub struct ClubRoom {
1055 pub id: i32,
1056 pub clubId: i32,
1057 pub clubName: String,
1058 pub nadeo: bool,
1059 pub roomId: Option<i32>,
1060 pub campaignId: Option<i32>,
1061 pub playerServerLogin: Option<String>,
1062 pub activityId: i32,
1063 pub name: String,
1064 pub room: ClubRoom_Room,
1065 pub popularityLevel: i32,
1066 pub creationTimestamp: i64,
1067 pub creatorAccountId: String,
1068 pub latestEditorAccountId: String,
1069 pub password: bool,
1070 pub mediaUrl: String,
1071 pub mediaUrlPngLarge: String,
1072 pub mediaUrlPngMedium: String,
1073 pub mediaUrlPngSmall: String,
1074 pub mediaUrlDds: String,
1075 pub mediaTheme: String,
1076 }
1078
1079#[derive(Debug, Clone, Serialize, Deserialize)]
1080#[allow(non_camel_case_types, non_snake_case)]
1081pub struct ClubRoom_Room {
1082 pub id: Option<i32>,
1083 pub name: String,
1084 pub region: Option<String>,
1085 pub serverAccountId: String,
1086 pub maxPlayers: i32,
1087 pub playerCount: i32,
1088 pub maps: Vec<String>,
1089 pub script: String,
1090 pub scalable: bool,
1091 #[serde(deserialize_with = "empty_list_or_map")]
1092 pub scriptSettings: HashMap<String, ClubRoom_ScriptSetting>,
1093 pub serverInfo: Option<String>,
1094}
1095
1096#[derive(Debug, Clone, Serialize, Deserialize)]
1097#[allow(non_camel_case_types, non_snake_case)]
1098pub struct ClubRoom_Room_ForEdit {
1099 pub name: String,
1100 pub region: String,
1101 pub maxPlayersPerServer: i32,
1102 pub maps: Vec<String>,
1103 pub script: String,
1104 pub scalable: bool,
1105 pub password: bool,
1107 pub settings: Vec<ClubRoom_ScriptSetting>,
1108}
1109
1110fn empty_list_or_map<'de, T, D>(deserializer: D) -> Result<T, D::Error>
1111where
1112 T: Deserialize<'de> + Default,
1113 D: Deserializer<'de>,
1114{
1115 struct EmptyListOrStruct<T>(PhantomData<fn() -> T>);
1116 impl<'de, T> Visitor<'de> for EmptyListOrStruct<T>
1117 where
1118 T: Deserialize<'de> + Default,
1119 {
1120 type Value = T;
1121
1122 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1123 formatter.write_str("empty list or struct")
1124 }
1125
1126 fn visit_seq<A>(self, mut seq: A) -> Result<T, A::Error>
1127 where
1128 A: SeqAccess<'de>,
1129 {
1130 if seq.next_element::<()>()?.is_some() {
1131 Err(de::Error::invalid_length(1, &self))
1132 } else {
1133 Ok(Default::default())
1134 }
1135 }
1136
1137 fn visit_map<M>(self, map: M) -> Result<T, M::Error>
1138 where
1139 M: MapAccess<'de>,
1140 {
1141 Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
1142 }
1143 }
1144
1145 deserializer.deserialize_any(EmptyListOrStruct(PhantomData))
1146}
1147
1148#[derive(Debug, Clone, Serialize, Deserialize)]
1149#[allow(non_camel_case_types, non_snake_case)]
1150pub struct ClubRoom_ScriptSetting {
1151 pub key: String,
1152 pub value: String,
1153 pub r#type: String,
1154}
1155
1156#[derive(Debug, Clone, Serialize, Deserialize)]
1157#[allow(non_camel_case_types, non_snake_case)]
1158pub struct ActivityList {
1159 pub activityList: Vec<Activity>,
1160 pub maxPage: i32,
1161 pub itemCount: i32,
1162}
1163
1164#[derive(Debug, Clone, Serialize, Deserialize)]
1165#[allow(non_camel_case_types, non_snake_case)]
1166pub struct Activity {
1167 pub id: i32,
1168 pub name: String,
1169 pub activityType: String,
1170 pub activityId: i32,
1171 pub targetActivityId: i32,
1172 pub campaignId: i32,
1173 pub position: i32,
1174 pub public: bool,
1175 pub active: bool,
1176 pub externalId: i32,
1177 pub featured: bool,
1178 pub password: bool,
1179 pub itemsCount: i32,
1180 pub clubId: i32,
1181 pub editionTimestamp: i64,
1182 pub creatorAccountId: String,
1183 pub latestEditorAccountId: String,
1184 pub mediaUrl: String,
1185 pub mediaUrlPngLarge: String,
1186 pub mediaUrlPngMedium: String,
1187 pub mediaUrlPngSmall: String,
1188 pub mediaUrlDds: String,
1189 pub mediaTheme: String,
1190}
1191
1192#[derive(Debug, Clone, Serialize, Deserialize)]
1193#[allow(non_camel_case_types, non_snake_case)]
1194pub struct ClubList {
1195 pub clubList: Vec<ClubInfo>,
1196 pub maxPage: u32,
1197 pub clubCount: u32,
1198}
1199
1200#[cfg(test)]
1201mod tests {
1202 use std::{future::Future, u32};
1203
1204 use crate::test_helpers::*;
1205 use auth::{NadeoClient, UserAgentDetails};
1206 use futures::stream::FuturesUnordered;
1207 use lazy_static::lazy_static;
1208 use oneshot::error::RecvError;
1209
1210 use super::*;
1211 use crate::*;
1212
1213 pub static MAP_UIDS: [&str; 5] = [
1214 "YewzuEnjmnh_ShMW1cX0puuZHcf",
1215 "HisPAAWhTMTjQPxhMJtMak7Daud",
1216 "PrometheusByXertroVFtArcher",
1217 "DeepDip2__The_Gentle_Breeze",
1218 "DeepDip2__The_Storm_Is_Here",
1219 ];
1220
1221 #[ignore]
1222 #[tokio::test]
1223 async fn test_monthly_campaign() {
1224 let creds = get_test_creds();
1225 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1226 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1227 .await
1228 .unwrap();
1229 let res = client
1230 .get_monthly_campaign(MonthlyCampaignType::Royal, 10, 0)
1231 .await
1232 .unwrap();
1233 println!("Monthly Campaign: {:?}", res);
1234 }
1235
1236 #[ignore]
1238 #[tokio::test]
1239 async fn test_get_map_info_multiple() {
1240 let creds = get_test_creds();
1241 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1242 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1243 .await
1244 .unwrap();
1245 let uids = Vec::from(&MAP_UIDS[..]);
1246 let res = client.get_map_info_multiple(&uids).await.unwrap();
1247 println!("Map Info Multiple: {:?}", res);
1248 println!(
1249 "get_cached_avg_req_per_sec: {:?}",
1250 client.get_cached_avg_req_per_sec().await
1251 );
1252 for (mi, uid) in res.mapList.iter().zip(MAP_UIDS.iter()) {
1253 let mi2 = client.get_map_info(uid).await.unwrap().unwrap();
1254 assert_eq!(mi, &mi2);
1255 println!("Matches: {:?} -> {:?}", mi.uid, mi2);
1256 println!(
1257 "get_cached_avg_req_per_sec: {:?}",
1258 client.get_cached_avg_req_per_sec().await
1259 );
1260 }
1261 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
1262 let _mi2 = client.get_map_info(uids[0]).await.unwrap();
1263 println!(
1264 "get_cached_avg_req_per_sec: {:?}",
1265 client.get_cached_avg_req_per_sec().await
1266 );
1267 }
1268
1269 #[ignore]
1271 #[tokio::test]
1272 async fn test_get_map_info() {
1273 let creds = get_test_creds();
1274 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1275 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1276 .await
1277 .unwrap();
1278 let res = client
1279 .get_map_info("PrometheusByXertroVFtArcher")
1280 .await
1281 .unwrap()
1282 .unwrap();
1283 println!("Map Info: {:?}", res);
1284 let res = client
1285 .get_map_info("XXXXetheusByXertroVFtArcher")
1286 .await
1287 .unwrap();
1288 assert_eq!(res, None);
1289 println!(
1290 "missing map info was None (good). get_cached_avg_req_per_sec: {:?}",
1291 client.get_cached_avg_req_per_sec().await
1292 );
1293 }
1294
1295 #[ignore]
1297 #[tokio::test]
1298 async fn test_surround() {
1299 let creds = get_test_creds();
1300 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1301 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1302 .await
1303 .unwrap();
1304 let res = client
1305 .get_pb_surround("PrometheusByXertroVFtArcher", 0, 0, u32::MAX, true)
1306 .await
1307 .unwrap();
1308 println!("Surround: {:?}", res);
1309 }
1310
1311 #[ignore]
1313 #[tokio::test]
1314 async fn test_records_by_time() {
1315 let creds = get_test_creds();
1316 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1317 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1318 .await
1319 .unwrap();
1320 let res = client
1321 .get_lb_positions_by_time(&[
1322 ("PrometheusByXertroVFtArcher", NonZero::new(47391).unwrap()),
1323 (
1324 "DeepDip2__The_Storm_Is_Here",
1325 NonZero::new(60000 * 50).unwrap(),
1326 ),
1327 ])
1328 .await
1329 .unwrap();
1330 println!("Records by Time: {:?}", res);
1331 }
1332
1333 lazy_static! {
1334 static ref CLIENT: OnceLock<NadeoClient> = OnceLock::new();
1335 }
1336
1337 #[tokio::test]
1340 async fn test_records_by_time_batched() {
1341 let creds = get_test_creds();
1342 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1343 let client: NadeoClient = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1344 .await
1345 .unwrap();
1346 if CLIENT.set(client).is_err() {
1347 panic!("CLIENT already set");
1348 }
1349 let client = CLIENT.get().unwrap();
1350 let to_get = vec![
1351 ("PrometheusByXertroVFtArcher", NonZero::new(1000).unwrap()),
1352 ("DeepDip2__The_Storm_Is_Here", NonZero::new(1).unwrap()),
1353 ("PrometheusByXertroVFtArcher", NonZero::new(47391).unwrap()),
1354 ("PrometheusByXertroVFtArcher", NonZero::new(48391).unwrap()),
1355 ("PrometheusByXertroVFtArcher", NonZero::new(48220).unwrap()),
1356 (
1357 "DeepDip2__The_Storm_Is_Here",
1358 NonZero::new(60000 * 50).unwrap(),
1359 ),
1360 ];
1361
1362 fn run_and_time<F: Future<Output = Result<ScoreToPos, RecvError>> + Send + 'static>(
1363 f: F,
1364 ) -> tokio::task::JoinHandle<ScoreToPos> {
1365 tokio::spawn(async move {
1366 let start = std::time::Instant::now();
1367 let r = f.await.unwrap();
1368 let end = std::time::Instant::now();
1369 println!("Time: {:?}; Res: {:?}", end - start, r);
1370 r
1371 })
1372 }
1373
1374 let reqs = to_get
1375 .into_iter()
1376 .map(|s| client.get_lb_position_by_time_batched(s.0, s.1))
1377 .map(run_and_time)
1378 .collect::<FuturesUnordered<_>>();
1379 let r = futures::future::join_all(reqs).await;
1380 println!("Results: {:?}", r);
1381 }
1382
1383 #[ignore]
1384 #[tokio::test]
1385 async fn test_long_running_refresh_token() {
1386 let creds = get_test_creds();
1387 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1388 let client = NadeoClient::create(creds, user_agent_auto!(&email), 1)
1389 .await
1390 .unwrap();
1391 let start = chrono::Utc::now();
1392 println!("Long running test -- starting at {:?}", start);
1393 let n = 200;
1394 for i in 0..n {
1395 println!("Running iteration {} / {}", i + 1, n);
1396 println!("\n\n--- token below? ---\n\n");
1397 let r = client
1398 .get_map_leaderboard("PrometheusByXertroVFtArcher", true, 10, i * 10)
1399 .await
1400 .unwrap();
1401 println!("\n\n--- end ---\n\n");
1402 println!(
1403 "Response: {}",
1404 r.tops[0]
1405 .top
1406 .iter()
1407 .map(|t| format!("{}. {} -- {} ms", t.position, t.zoneName, t.score))
1408 .collect::<Vec<String>>()
1409 .join("\n")
1410 );
1411 if i == n - 1 {
1412 println!(
1413 "Last iter. Duration so far: {:?}",
1414 chrono::Utc::now() - start
1415 );
1416 break;
1417 }
1418 println!("Sleeping for 10 minutes @ {:?}", chrono::Utc::now());
1419 tokio::time::sleep(tokio::time::Duration::from_secs(60 * 10)).await;
1420 println!(
1421 "Next iter. Duration so far: {:?} @ {:?}",
1422 chrono::Utc::now() - start,
1423 chrono::Utc::now()
1424 );
1425 }
1426 }
1427
1428 const TEST_CLUB_ID: i32 = 46587;
1429 const TEST_CAMPAIGN_ID: i32 = 38997;
1430 const TEST_ROOM_ID: i32 = 287220;
1431
1432 #[ignore]
1433 #[tokio::test]
1434 async fn test_get_club_campaigns() {
1435 let creds = get_test_creds();
1436 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1437 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1438 .await
1439 .unwrap();
1440 let res = client.get_club_campaigns(100, 4000, None).await.unwrap();
1441 println!("Club Campaigns: {:?}", res);
1442 }
1443
1444 #[ignore]
1445 #[tokio::test]
1446 async fn test_get_club_campaign_by_id() {
1447 let creds = get_test_creds();
1448 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1449 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1450 .await
1451 .unwrap();
1452 let res = client
1453 .get_club_campaign_by_id(TEST_CLUB_ID, TEST_CAMPAIGN_ID)
1454 .await
1455 .unwrap();
1456 println!("Club Campaign: {:?}", res);
1457 }
1458
1459 #[ignore]
1460 #[tokio::test]
1461 async fn test_get_club_rooms() {
1462 let creds = get_test_creds();
1463 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1464 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1465 .await
1466 .unwrap();
1467 let res = client.get_club_rooms(10, 0, None).await.unwrap();
1468 println!("Club Rooms: {:?}", res);
1469 }
1470
1471 #[ignore]
1472 #[tokio::test]
1473 async fn test_get_club_room_by_id() {
1474 let creds = get_test_creds();
1475 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1476 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1477 .await
1478 .unwrap();
1479 let res = client
1480 .get_club_room_by_id(TEST_CLUB_ID, TEST_ROOM_ID)
1481 .await
1482 .unwrap();
1483 println!("Club Room: {:?}", res);
1484 }
1485
1486 #[ignore]
1487 #[tokio::test]
1488 async fn test_get_club_info() {
1489 let creds = get_test_creds();
1490 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1491 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1492 .await
1493 .unwrap();
1494 let res = client.get_club_info(TEST_CLUB_ID).await.unwrap();
1495 println!("Club Info: {:?}", res);
1496 }
1497
1498 #[ignore]
1499 #[tokio::test]
1500 async fn test_get_club_activity_list() {
1501 let creds = get_test_creds();
1502 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1503 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1504 .await
1505 .unwrap();
1506 let res = client
1507 .get_club_activity_list(TEST_CLUB_ID, 10, 0, Some(true))
1508 .await
1509 .unwrap();
1510 println!("Club Activity List: {:?}", res);
1511 }
1512
1513 #[ignore]
1514 #[tokio::test]
1515 async fn test_get_clubs() {
1516 let creds = get_test_creds();
1517 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1518 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1519 .await
1520 .unwrap();
1521 let res = client.get_clubs(10, 0, None).await.unwrap();
1522 println!("Clubs: {:?}", res);
1523 }
1524
1525 #[ignore]
1526 #[tokio::test]
1527 async fn test_get_clubs_mine() {
1528 let creds = get_test_ubi_creds();
1529 let email = std::env::var("NADEO_TEST_UA_EMAIL").unwrap();
1530 let client = NadeoClient::create(creds, user_agent_auto!(&email), 10)
1531 .await
1532 .unwrap();
1533 let res = client.get_clubs_mine(10, 0).await.unwrap();
1534 println!("Clubs: {:?}", res);
1535 }
1536}