-
Notifications
You must be signed in to change notification settings - Fork 0
/
levels.rs
145 lines (120 loc) · 4.83 KB
/
levels.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! Example demonstrating the custom storage
//! feature by creating a stored `Count` object
//! and using the provided ones.
#[macro_use]
extern crate automate;
use automate::{Context, Error, Snowflake, Configuration, Automate};
use automate::gateway::{MessageCreateDispatch, UpdateStatus, StatusType, User};
use automate::http::CreateMessage;
use automate::log::LevelFilter;
use std::collections::HashMap;
/// This is a special case: because the type we are storing is
/// not defined in our example because it is a u32, we can't
/// implement `Stored` on it.
/// A way to go around that problem would be to create a
/// [newtype struct](https://doc.rust-lang.org/stable/rust-by-example/generics/new_types.html)
/// and make it the `Stored` value. However, since we are only
/// storing a `u32` and not a whole struct, it would be quite
/// annoying to use a newtype struct.
///
/// Therefore, we make this empty `Count` struct that is only used
/// to fetch the `CountStorage` through `ctx.storage::<Count>().await`
/// and we directly deal with `u32`s when calling the storage's methods.
#[derive(Stored, Copy, Clone)]
struct Count;
/// The storage struct is responsible for keeping the
/// stored objects in memory and providing methods
/// for retrieving and inserting objects.
#[derive(Storage, Default, Clone)]
struct CountStorage {
counts: HashMap<(Snowflake, Snowflake), u32>,
}
impl CountStorage {
/// Increments the message count of the given user
fn increment(&mut self, guild: Snowflake, user: Snowflake) -> u32 {
let value = self.counts.get(&(guild, user)).map_or(1, |v| v + 1);
self.counts.insert((guild, user), value);
value
}
/// Finds the 10 users with the most messages sent.
fn leaderboard<'a>(&self, guild: Snowflake) -> Vec<(Snowflake, u32)> {
let mut leaderboard = self.counts.iter()
.filter(|((g, _), _)| *g == guild) //take only from given guild
.map(|((_, u), count)| (*u, *count)) //remove the guild
.take(10)
.collect::<Vec<(Snowflake, u32)>>();
leaderboard.sort_by(|(_, v1), (_, v2)| v2.cmp(v1));
leaderboard
}
}
#[listener]
async fn leaderboard_command(ctx: &Context, data: &MessageCreateDispatch) -> Result<(), Error> {
let message = &data.0;
if message.content.starts_with("!leaderboard") {
if let Some(guild) = message.guild_id {
let users = ctx.storage::<User>().await;
let leaderboard = ctx.storage::<Count>().await.leaderboard(guild);
let mut output = String::from("These are the top 10 users:\n");
for (position, (user, count)) in leaderboard.iter().enumerate() {
output.push_str(&format!("{}. {} is **level {}** and posted a total of **{} messages**\n",
position,
users.get(*user).username,
level(*count).0,
count));
}
ctx.create_message(message.channel_id, CreateMessage {
content: Some(output),
..Default::default()
}).await?;
}
}
Ok(())
}
#[listener]
async fn count(ctx: &mut Context, data: &MessageCreateDispatch) -> Result<(), Error> {
let message = &data.0;
//ignore messages from the bot itself
if message.author.id == ctx.bot.id {
return Ok(());
}
//don't count messages outside of guilds
if let Some(guild) = message.guild_id {
//get the storage and increment for the message author
let count = ctx.storage_mut::<Count>().await.increment(guild, message.author.id);
let (level, levelled_up) = level(count);
if levelled_up && level != 0 {
let content = format!("<@{}> you just advanced to **level {}**!", message.author.id, level);
ctx.create_message(message.channel_id, CreateMessage {
content: Some(content),
..Default::default()
}).await?;
}
}
Ok(())
}
/// Calculates the level of a user based on the
/// amount of messages he has sent.
fn level(msg: u32) -> (u32, bool) {
let level = 0.4 * f64::from(msg).sqrt();
let level = level.round() as u32;
let previous_level = if msg > 0 {
(0.4 * f64::from(msg - 1).sqrt()).round() as u32
} else {
0
};
(level, previous_level < level)
}
fn main() {
let config = Configuration::from_env("DISCORD_API_TOKEN")
.enable_logging()
.level_for("automate", LevelFilter::Trace)
.presence(UpdateStatus {
status: StatusType::Dnd,
game: None,
afk: false,
since: None,
})
.add_initializer(|ctn| ctn.initialize::<Count>())
.register(stateless!(leaderboard_command, count));
Automate::launch(config)
}