use crate::{ db::{CfSavedUsers, CfUser}, CFClient, }; use announcer::MemberToChannels; use chrono::Utc; use codeforces::{RatingChange, User}; use serenity::{http::CacheHttp, model::id::UserId, CacheAndHttp}; use std::sync::Arc; use youmubot_prelude::*; type Client = ::Value; /// Updates the rating and rating changes of the users. pub struct Announcer; #[async_trait] impl youmubot_prelude::Announcer for Announcer { async fn updates( &mut self, http: Arc, data: AppData, channels: MemberToChannels, ) -> Result<()> { let data = data.read().await; let client = data.get::().unwrap(); let mut users = CfSavedUsers::open(&data).borrow()?.clone(); users .iter_mut() .map(|(user_id, cfu)| { let http = http.clone(); let channels = &channels; async move { if let Err(e) = update_user(http, channels, client, *user_id, cfu).await { cfu.failures += 1; eprintln!( "Codeforces: cannot update user {}: {} [{} failures]", cfu.handle, e, cfu.failures ); } else { cfu.failures = 0; } } }) .collect::>() .collect::<()>() .await; let mut db = CfSavedUsers::open(&data); let mut db = db.borrow_mut()?; for (key, user) in users { match db.get(&key).map(|v| v.last_update) { Some(u) if u > user.last_update => (), _ => { if user.failures >= 5 { eprintln!( "Codeforces: Removing user {} - {}: failures count too high", key, user.handle, ); // db.remove(&key); } else { db.insert(key, user); } } } } Ok(()) } } async fn update_user( http: Arc, channels: &MemberToChannels, client: &Client, user_id: UserId, cfu: &mut CfUser, ) -> Result<()> { let info = User::info(client, &[cfu.handle.as_str()]) .await? .into_iter() .next() .ok_or_else(|| Error::msg("Not found"))?; let rating_changes = info.rating_changes(client).await?; let channels_list = channels.channels_of(&http, user_id).await; cfu.last_update = Utc::now(); // Update the rating cfu.rating = info.rating; let rating_changes = match cfu.last_contest_id { None => rating_changes, Some(v) => { let mut v: Vec<_> = rating_changes .into_iter() // Skip instead of take because sometimes Codeforces // performs rollback. .skip_while(|rc| rc.contest_id != v) .skip(1) .collect(); v.reverse(); v } }; cfu.last_contest_id = rating_changes .first() .map(|v| v.contest_id) .or(cfu.last_contest_id); // Check for any good announcements to make rating_changes .into_iter() .map(|rc: RatingChange| { let channels = channels_list.clone(); let http = http.clone(); let info = info.clone(); async move { if channels.is_empty() { return Ok(()); } let (contest, _, _) = codeforces::Contest::standings(client, rc.contest_id, |f| f.limit(1, 1)) .await?; channels .iter() .map(|channel| { channel.send_message(http.http(), |e| { e.content(format!("Rating change for {}!", user_id.mention())) .embed(|c| { crate::embed::rating_change_embed( &rc, &info, &contest, user_id, c, ) }) }) }) .collect::>() .map(|v| v.map(|_| ())) .try_collect::<()>() .await?; let r: Result<_> = Ok(()); r } }) .collect::>() .try_collect::<()>() .await?; Ok(()) }