use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::time::{self, Duration};
pub use toptl::{StatsPayload, TopTL};
#[derive(Clone)]
pub struct TopTLPlugin {
client: Arc<TopTL>,
username: String,
state: Arc<Mutex<PluginState>>,
}
#[derive(Default)]
struct PluginState {
user_ids: HashSet<i64>,
group_ids: HashSet<i64>,
channel_ids: HashSet<i64>,
}
#[derive(Debug, Clone, Copy)]
pub enum ChatKind {
Private,
Group,
Supergroup,
Channel,
}
impl TopTLPlugin {
pub fn new(client: TopTL, username: impl Into<String>) -> Self {
Self {
client: Arc::new(client),
username: username.into(),
state: Arc::new(Mutex::new(PluginState::default())),
}
}
pub async fn record(&self, user_id: Option<i64>, chat: Option<(i64, ChatKind)>) {
let mut state = self.state.lock().await;
if let Some(uid) = user_id {
state.user_ids.insert(uid);
}
if let Some((cid, kind)) = chat {
match kind {
ChatKind::Group | ChatKind::Supergroup => {
state.group_ids.insert(cid);
}
ChatKind::Channel => {
state.channel_ids.insert(cid);
}
ChatKind::Private => { }
}
}
}
pub fn start(&self, interval: Duration) {
let client = self.client.clone();
let username = self.username.clone();
let state = self.state.clone();
tokio::spawn(async move {
let mut ticker = time::interval(interval);
ticker.tick().await;
loop {
ticker.tick().await;
let payload = {
let s = state.lock().await;
StatsPayload {
member_count: Some(s.user_ids.len() as u64),
group_count: Some(s.group_ids.len() as u64),
channel_count: Some(s.channel_ids.len() as u64),
bot_serves: None,
}
};
match client.post_stats(&username, &payload).await {
Ok(_) => log::debug!("toptl: posted stats for @{username}"),
Err(e) => log::warn!("toptl: post_stats for @{username} failed: {e}"),
}
}
});
}
pub async fn has_voted(&self, user_id: i64) -> bool {
match self.client.has_voted(&self.username, user_id as u64).await {
Ok(check) => check.voted,
Err(e) => {
log::warn!("toptl: has_voted({user_id}) failed: {e}");
false
}
}
}
pub async fn post_now(&self) -> Result<(), toptl::Error> {
let payload = {
let s = self.state.lock().await;
StatsPayload {
member_count: Some(s.user_ids.len() as u64),
group_count: Some(s.group_ids.len() as u64),
channel_count: Some(s.channel_ids.len() as u64),
bot_serves: None,
}
};
self.client.post_stats(&self.username, &payload).await?;
Ok(())
}
}
#[cfg(feature = "teloxide")]
pub async fn record_update(plugin: &TopTLPlugin, msg: &teloxide::types::Message) {
let user_id = msg.from.as_ref().map(|u| u.id.0 as i64);
let kind = match msg.chat.kind {
teloxide::types::ChatKind::Private(_) => ChatKind::Private,
teloxide::types::ChatKind::Public(ref p) => match p.kind {
teloxide::types::PublicChatKind::Group(_) => ChatKind::Group,
teloxide::types::PublicChatKind::Supergroup(_) => ChatKind::Supergroup,
teloxide::types::PublicChatKind::Channel(_) => ChatKind::Channel,
},
};
plugin.record(user_id, Some((msg.chat.id.0, kind))).await;
}