1
0
Fork 0
mirror of https://github.com/icewind1991/RGBot synced 2026-06-04 04:24:09 +02:00

add contrast requirements for colors

This commit is contained in:
Robin Appelman 2019-09-07 17:33:31 +02:00
commit bdfffdf930
3 changed files with 103 additions and 39 deletions

18
Cargo.lock generated
View file

@ -151,6 +151,15 @@ dependencies = [
"syn 0.15.37 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.37 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "contrast"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rgb 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "cookie" name = "cookie"
version = "0.12.0" version = "0.12.0"
@ -1007,14 +1016,21 @@ dependencies = [
"webpki-roots 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "webpki-roots 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "rgb"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "rgbot" name = "rgbot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"contrast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rgb 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)",
"serenity 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "serenity 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1620,6 +1636,7 @@ dependencies = [
"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum command_attr 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5406ee18de55f66abb90533af259d0adbc9f629c02fecc8e6ddd8ae986ab990b" "checksum command_attr 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5406ee18de55f66abb90533af259d0adbc9f629c02fecc8e6ddd8ae986ab990b"
"checksum contrast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9e2e6885a8c59c03522edaa351a7ad5b6d47ed2632fcfbc0f2b00fcce520eb1"
"checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" "checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5"
"checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" "checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c"
"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
@ -1712,6 +1729,7 @@ dependencies = [
"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48" "checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48"
"checksum reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "00eb63f212df0e358b427f0f40aa13aaea010b470be642ad422bcbca2feff2e4" "checksum reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "00eb63f212df0e358b427f0f40aa13aaea010b470be642ad422bcbca2feff2e4"
"checksum rgb 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2089e4031214d129e201f8c3c8c2fe97cd7322478a0d1cdf78e7029b0042efdb"
"checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c" "checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c"
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"

View file

@ -8,5 +8,7 @@ edition = "2018"
serenity = { version = "0.6", features = ["cache"] } serenity = { version = "0.6", features = ["cache"] }
regex = "1.1" regex = "1.1"
err-derive = "0.1" err-derive = "0.1"
env_logger = "~0.4" env_logger = "0.4"
log = "~0.3" log = "0.3"
contrast = "0.1"
rgb = "0.8"

View file

@ -1,18 +1,20 @@
use std::env; use std::env;
use contrast::contrast;
use err_derive::Error;
use regex::Regex;
use rgb::RGB8;
use serenity::model::guild::{Guild, Role};
use serenity::model::id::RoleId;
use serenity::model::user::User;
use serenity::{ use serenity::{
model::{channel::Message, gateway::Ready}, model::{channel::Message, gateway::Ready},
utils::Colour,
prelude::*, prelude::*,
utils::Colour,
Error as DiscordError, Error as DiscordError,
}; };
use regex::Regex;
use serenity::model::guild::{Role, Guild};
use serenity::model::id::RoleId;
use err_derive::Error;
use serenity::model::user::User;
use std::u8;
use std::sync::Arc; use std::sync::Arc;
use std::u8;
#[derive(Debug, Error)] #[derive(Debug, Error)]
enum BotError { enum BotError {
@ -28,16 +30,30 @@ impl From<DiscordError> for BotError {
} }
} }
fn background_contrast(color: Colour) -> f32 {
let background_dark: Colour = Colour::from(0x36393E);
let background_light: Colour = Colour::from(0xFFFFFF);
let rgb = RGB8::from(color.tuple());
let contrast_light = contrast(RGB8::from(background_light.tuple()), rgb);
let contrast_dark = contrast(RGB8::from(background_dark.tuple()), rgb);
f32::min(contrast_dark, contrast_light)
}
type Result<T> = std::result::Result<T, BotError>; type Result<T> = std::result::Result<T, BotError>;
struct Handler { struct Handler {
color_regex: Regex color_regex: Regex,
min_contrast: f32,
} }
impl Handler { impl Handler {
pub fn new() -> Self { pub fn new(min_contrast: f32) -> Self {
Handler { Handler {
color_regex: Regex::new(r"^#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$").unwrap() color_regex: Regex::new(r"^#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$")
.unwrap(),
min_contrast,
} }
} }
@ -50,31 +66,46 @@ impl Handler {
} }
fn get_color_role_position(&self, guild: &Guild) -> Result<u8> { fn get_color_role_position(&self, guild: &Guild) -> Result<u8> {
guild.role_by_name("colors").map(|r| r.position as u8) guild
.role_by_name("colors")
.map(|r| r.position as u8)
.ok_or(BotError::NoColorRole) .ok_or(BotError::NoColorRole)
} }
fn get_or_create_role(&self, context: &Context, color: Colour, guild: &RwLock<Guild>) -> Result<Role> { fn get_or_create_role(
&self,
context: &Context,
color: Colour,
guild: &RwLock<Guild>,
) -> Result<Role> {
let name = format!("#{}", color.hex()); let name = format!("#{}", color.hex());
let color_position = self.get_color_role_position(&mut guild.read())?; let color_position = self.get_color_role_position(&mut guild.read())?;
if let Some(role) = guild.read().role_by_name(&name) { if let Some(role) = guild.read().role_by_name(&name) {
return Ok(role.clone()); return Ok(role.clone());
} }
let role = guild.write().create_role(context, |r| r let role = guild.write().create_role(context, |r| {
.name(&name) r.name(&name)
.colour(color.0 as u64) .colour(color.0 as u64)
.position(color_position), .position(color_position)
)?; })?;
Ok(role) Ok(role)
} }
fn assign_color(&self, context: Context, user: User, guild: Arc<RwLock<Guild>>, color: Colour) -> Result<(String, String)> { fn assign_color<'a>(
let role = self.get_or_create_role(&context, color, &guild)?; &self,
let mut member = guild.read().member(&context, user.id)?; context: &Context,
user: &'a User,
guild: Arc<RwLock<Guild>>,
color: Colour,
) -> Result<(String, &'a String)> {
let role = self.get_or_create_role(context, color, &guild)?;
let mut member = guild.read().member(context, user.id)?;
let old_colors: Vec<RoleId> = member.roles(&context.cache).unwrap_or_default() let old_colors: Vec<RoleId> = member
.roles(&context.cache)
.unwrap_or_default()
.iter() .iter()
.filter(|r| self.color_regex.is_match(&r.name)) .filter(|r| self.color_regex.is_match(&r.name))
.map(|r| r.id) .map(|r| r.id)
@ -82,16 +113,21 @@ impl Handler {
member.remove_roles(&context.http, &old_colors)?; member.remove_roles(&context.http, &old_colors)?;
member.add_role(&context.http, role.id)?; member.add_role(&context.http, role.id)?;
self.cleanup_roles(context, &guild, role.id)?; self.cleanup_roles(context, &guild, role.id)?;
Ok((role.name, user.name)) Ok((role.name, &user.name))
} }
fn cleanup_roles(&self, context: Context, guild: &RwLock<Guild>, used: RoleId) -> Result<()> { fn cleanup_roles(&self, context: &Context, guild: &RwLock<Guild>, used: RoleId) -> Result<()> {
let used_roles: Vec<RoleId> = guild.read().members.values() let used_roles: Vec<RoleId> = guild
.read()
.members
.values()
.flat_map(|member| member.roles.iter()) .flat_map(|member| member.roles.iter())
.map(|role| role.clone()) .map(|role| role.clone())
.collect(); .collect();
let empty_roles: Vec<RoleId> = guild.read().roles let empty_roles: Vec<RoleId> = guild
.read()
.roles
.values() .values()
.filter(|role| self.color_regex.is_match(&role.name)) .filter(|role| self.color_regex.is_match(&role.name))
.filter(|role| !used_roles.contains(&role.id)) .filter(|role| !used_roles.contains(&role.id))
@ -110,9 +146,11 @@ impl Handler {
impl EventHandler for Handler { impl EventHandler for Handler {
fn message(&self, context: Context, msg: Message) { fn message(&self, context: Context, msg: Message) {
if let Some(color) = self.parse_color(&msg.content) { if let Some(color) = self.parse_color(&msg.content) {
if background_contrast(color) > self.min_contrast {
if let Some(guild) = msg.guild(context.cache.clone()) { if let Some(guild) = msg.guild(context.cache.clone()) {
match self.assign_color(context, msg.author, guild, color) { match self.assign_color(&context, &msg.author, guild, color) {
Ok((role, user)) => { Ok((role, user)) => {
let _ = msg.react(&context, '☑');
println!("Assigned role {} for {}", role, user); println!("Assigned role {} for {}", role, user);
} }
Err(err) => { Err(err) => {
@ -122,6 +160,9 @@ impl EventHandler for Handler {
} else { } else {
println!("Failed to get guild"); println!("Failed to get guild");
} }
} else {
let _ = msg.react(&context, '❌');
}
} }
} }
@ -133,10 +174,13 @@ impl EventHandler for Handler {
fn main() { fn main() {
env_logger::init().expect("Unable to init env_logger"); env_logger::init().expect("Unable to init env_logger");
let token = env::var("DISCORD_TOKEN") let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
.expect("Expected a token in the environment"); let min_contrast: f32 = env::var("MIN_CONTRAST")
.unwrap_or_else(|_| "2".to_string())
.parse()
.expect("Failed to parse min contrast");
let mut client = Client::new(&token, Handler::new()).expect("Err creating client"); let mut client = Client::new(&token, Handler::new(min_contrast)).expect("Err creating client");
if let Err(why) = client.start() { if let Err(why) = client.start() {
println!("Client error: {:?}", why); println!("Client error: {:?}", why);