mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Implement codeforces contest/problem hook
This commit is contained in:
parent
61651e0b05
commit
4b5dcfd072
5 changed files with 159 additions and 2 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -163,10 +163,11 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codeforces"
|
name = "codeforces"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/natsukagami/rust-codeforces-api#093c9fdb40d369a1390918d982d2bff55c984c81"
|
source = "git+https://github.com/natsukagami/rust-codeforces-api#3ec1dc2a97c8225a5ba6bafee517080fc9ae88f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1718,6 +1719,9 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"Inflector 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"codeforces 0.1.0 (git+https://github.com/natsukagami/rust-codeforces-api)",
|
"codeforces 0.1.0 (git+https://github.com/natsukagami/rust-codeforces-api)",
|
||||||
|
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -11,5 +11,8 @@ reqwest = "0.10.1"
|
||||||
serenity = "0.8"
|
serenity = "0.8"
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
codeforces = { git = "https://github.com/natsukagami/rust-codeforces-api" }
|
codeforces = { git = "https://github.com/natsukagami/rust-codeforces-api" }
|
||||||
|
regex = "1"
|
||||||
|
lazy_static = "1"
|
||||||
|
rayon = "1"
|
||||||
|
|
||||||
youmubot-prelude = { path = "../youmubot-prelude" }
|
youmubot-prelude = { path = "../youmubot-prelude" }
|
||||||
|
|
145
youmubot-cf/src/hook.rs
Normal file
145
youmubot-cf/src/hook.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
use codeforces::{Contest, Problem};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use rayon::{iter::Either, prelude::*};
|
||||||
|
use regex::{Captures, Regex};
|
||||||
|
use serenity::{
|
||||||
|
builder::CreateEmbed,
|
||||||
|
framework::standard::{CommandError, CommandResult},
|
||||||
|
model::channel::Message,
|
||||||
|
utils::MessageBuilder,
|
||||||
|
};
|
||||||
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CONTEST_LINK: Regex = Regex::new(
|
||||||
|
r"https?://codeforces\.com/(contest|gym)/(?P<contest>\d+)(?:/problem/(?P<problem>\w+))?"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
static ref PROBLEMSET_LINK: Regex = Regex::new(
|
||||||
|
r"https?://codeforces\.com/problemset/problem/(?P<contest>\d+)/(?P<problem>\w+)"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ContestOrProblem {
|
||||||
|
Contest(Contest, Vec<Problem>),
|
||||||
|
Problem(Problem),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints info whenever a problem or contest (or more) is sent on a channel.
|
||||||
|
pub fn codeforces_info_hook(ctx: &mut Context, m: &Message) {
|
||||||
|
if m.author.bot {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let http = ctx.data.get_cloned::<HTTPClient>();
|
||||||
|
let matches = CONTEST_LINK
|
||||||
|
.captures_iter(&m.content)
|
||||||
|
.chain(PROBLEMSET_LINK.captures_iter(&m.content))
|
||||||
|
// .collect::<Vec<_>>()
|
||||||
|
// .into_par_iter()
|
||||||
|
.filter_map(|v| match parse_capture(http.clone(), v) {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !matches.is_empty() {
|
||||||
|
m.channel_id
|
||||||
|
.send_message(&ctx, |c| {
|
||||||
|
c.content("Here are the info of the given Codeforces links!")
|
||||||
|
.embed(|e| print_info_message(&matches[..], e))
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_info_message<'a>(
|
||||||
|
info: &[(ContestOrProblem, &str)],
|
||||||
|
e: &'a mut CreateEmbed,
|
||||||
|
) -> &'a mut CreateEmbed {
|
||||||
|
let (mut problems, contests): (Vec<_>, Vec<_>) =
|
||||||
|
info.par_iter().partition_map(|(v, l)| match v {
|
||||||
|
ContestOrProblem::Problem(p) => Either::Left((p, l)),
|
||||||
|
ContestOrProblem::Contest(c, p) => Either::Right((c, p, l)),
|
||||||
|
});
|
||||||
|
problems.sort_by(|(a, _), (b, _)| a.rating.unwrap_or(1500).cmp(&b.rating.unwrap_or(1500)));
|
||||||
|
let mut m = MessageBuilder::new();
|
||||||
|
if !problems.is_empty() {
|
||||||
|
m.push_line("**Problems**").push_line("");
|
||||||
|
for (problem, link) in problems {
|
||||||
|
m.push(" - [")
|
||||||
|
.push_bold_safe(format!(
|
||||||
|
"[{}{}] {}",
|
||||||
|
problem.contest_id.unwrap_or(0),
|
||||||
|
problem.index,
|
||||||
|
problem.name
|
||||||
|
))
|
||||||
|
.push(format!("]({})", link));
|
||||||
|
if let Some(p) = problem.points {
|
||||||
|
m.push(format!(" | **{:.0}** points", p));
|
||||||
|
}
|
||||||
|
if let Some(p) = problem.rating {
|
||||||
|
m.push(format!(" | rating **{:.0}**", p));
|
||||||
|
}
|
||||||
|
if !problem.tags.is_empty() {
|
||||||
|
m.push(format!(" | tags: ||`{}`||", problem.tags.join(", ")));
|
||||||
|
}
|
||||||
|
m.push_line("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.push_line("");
|
||||||
|
|
||||||
|
if !contests.is_empty() {
|
||||||
|
m.push_bold_line("Contests").push_line("");
|
||||||
|
for (contest, problems, link) in contests {
|
||||||
|
let duration: Duration = format!("{}s", contest.duration_seconds).parse().unwrap();
|
||||||
|
m.push(" - [")
|
||||||
|
.push_bold_safe(&contest.name)
|
||||||
|
.push(format!("]({})", link))
|
||||||
|
.push(format!(
|
||||||
|
" | **{}** problems | duration **{}**",
|
||||||
|
problems.len(),
|
||||||
|
duration
|
||||||
|
));
|
||||||
|
if let Some(p) = &contest.prepared_by {
|
||||||
|
m.push(format!(
|
||||||
|
" | prepared by [{}](https://codeforces.com/profile/{})",
|
||||||
|
p, p
|
||||||
|
));
|
||||||
|
}
|
||||||
|
m.push_line("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.description(m.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_capture<'a>(
|
||||||
|
http: <HTTPClient as TypeMapKey>::Value,
|
||||||
|
cap: Captures<'a>,
|
||||||
|
) -> Result<(ContestOrProblem, &'a str), CommandError> {
|
||||||
|
let contest: u64 = cap
|
||||||
|
.name("contest")
|
||||||
|
.ok_or(CommandError::from("Contest not captured"))?
|
||||||
|
.as_str()
|
||||||
|
.parse()?;
|
||||||
|
let (contest, problems, _) = Contest::standings(&http, contest, |f| f.limit(1, 1))?;
|
||||||
|
match cap.name("problem") {
|
||||||
|
Some(p) => {
|
||||||
|
for problem in problems {
|
||||||
|
if &problem.index == p.as_str() {
|
||||||
|
return Ok((
|
||||||
|
ContestOrProblem::Problem(problem),
|
||||||
|
cap.get(0).unwrap().as_str(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err("No such problem in contest".into())
|
||||||
|
}
|
||||||
|
None => Ok((
|
||||||
|
ContestOrProblem::Contest(contest, problems),
|
||||||
|
cap.get(0).unwrap().as_str(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,9 @@ use serenity::{
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
mod embed;
|
mod embed;
|
||||||
|
mod hook;
|
||||||
|
|
||||||
|
pub use hook::codeforces_info_hook;
|
||||||
|
|
||||||
#[group]
|
#[group]
|
||||||
#[prefix = "cf"]
|
#[prefix = "cf"]
|
||||||
|
|
|
@ -51,6 +51,8 @@ fn main() {
|
||||||
// Set up hooks
|
// Set up hooks
|
||||||
#[cfg(feature = "osu")]
|
#[cfg(feature = "osu")]
|
||||||
handler.hooks.push(youmubot_osu::discord::hook);
|
handler.hooks.push(youmubot_osu::discord::hook);
|
||||||
|
#[cfg(feature = "codeforces")]
|
||||||
|
handler.hooks.push(youmubot_cf::codeforces_info_hook);
|
||||||
|
|
||||||
// Sets up a client
|
// Sets up a client
|
||||||
let mut client = {
|
let mut client = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue