mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-05-24 01:00:49 +00:00
osu: Introduce Env
and propagate it to other functions (#40)
This commit is contained in:
parent
94dc225b86
commit
1066f249b0
17 changed files with 598 additions and 433 deletions
|
@ -13,6 +13,7 @@ use serenity::{
|
||||||
|
|
||||||
use db::{CfSavedUsers, CfUser};
|
use db::{CfSavedUsers, CfUser};
|
||||||
pub use hook::InfoHook;
|
pub use hook::InfoHook;
|
||||||
|
use youmubot_prelude::announcer::AnnouncerHandler;
|
||||||
use youmubot_prelude::table_format::table_formatting_unsafe;
|
use youmubot_prelude::table_format::table_formatting_unsafe;
|
||||||
use youmubot_prelude::table_format::Align::{Left, Right};
|
use youmubot_prelude::table_format::Align::{Left, Right};
|
||||||
use youmubot_prelude::{
|
use youmubot_prelude::{
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
framework::standard::{
|
framework::standard::{
|
||||||
help_commands, macros::help, Args, CommandGroup, CommandResult, HelpOptions,
|
help_commands, macros::help, Args, CommandGroup, CommandResult, HelpOptions,
|
||||||
},
|
},
|
||||||
model::{channel::Message, id::UserId},
|
model::{channel::Message, id::UserId},
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
|
||||||
|
pub use admin::ADMIN_GROUP;
|
||||||
|
pub use community::COMMUNITY_GROUP;
|
||||||
|
pub use fun::FUN_GROUP;
|
||||||
use youmubot_prelude::{announcer::CacheAndHttp, *};
|
use youmubot_prelude::{announcer::CacheAndHttp, *};
|
||||||
|
|
||||||
pub mod admin;
|
pub mod admin;
|
||||||
|
@ -12,14 +17,9 @@ pub mod community;
|
||||||
mod db;
|
mod db;
|
||||||
pub mod fun;
|
pub mod fun;
|
||||||
|
|
||||||
pub use admin::ADMIN_GROUP;
|
|
||||||
pub use community::COMMUNITY_GROUP;
|
|
||||||
pub use fun::FUN_GROUP;
|
|
||||||
|
|
||||||
/// Sets up all databases in the client.
|
/// Sets up all databases in the client.
|
||||||
pub fn setup(
|
pub fn setup(
|
||||||
path: &std::path::Path,
|
path: &std::path::Path,
|
||||||
client: &serenity::client::Client,
|
|
||||||
data: &mut TypeMap,
|
data: &mut TypeMap,
|
||||||
) -> serenity::framework::standard::CommandResult {
|
) -> serenity::framework::standard::CommandResult {
|
||||||
db::SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?;
|
db::SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?;
|
||||||
|
@ -29,18 +29,21 @@ pub fn setup(
|
||||||
&path.join("roles.yaml"),
|
&path.join("roles.yaml"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Create handler threads
|
|
||||||
tokio::spawn(admin::watch_soft_bans(
|
|
||||||
CacheAndHttp::from_client(client),
|
|
||||||
client.data.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Start reaction handlers
|
// Start reaction handlers
|
||||||
data.insert::<community::ReactionWatchers>(community::ReactionWatchers::new(&*data)?);
|
data.insert::<community::ReactionWatchers>(community::ReactionWatchers::new(&*data)?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ready_hook(ctx: &Context) -> CommandResult {
|
||||||
|
// Create handler threads
|
||||||
|
tokio::spawn(admin::watch_soft_bans(
|
||||||
|
CacheAndHttp::from_context(ctx),
|
||||||
|
ctx.data.clone(),
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// A help command
|
// A help command
|
||||||
#[help]
|
#[help]
|
||||||
pub async fn help(
|
pub async fn help(
|
||||||
|
|
|
@ -15,16 +15,15 @@ use youmubot_prelude::stream::TryStreamExt;
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
discord::beatmap_cache::BeatmapMetaCache,
|
|
||||||
discord::cache::save_beatmap,
|
discord::cache::save_beatmap,
|
||||||
discord::oppai_cache::{BeatmapCache, BeatmapContent},
|
discord::oppai_cache::BeatmapContent,
|
||||||
models::{Mode, Score, User, UserEventRank},
|
models::{Mode, Score, User, UserEventRank},
|
||||||
request::UserID,
|
request::UserID,
|
||||||
Client as Osu,
|
Client as Osu,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::db::{OsuSavedUsers, OsuUser};
|
use super::db::{OsuSavedUsers, OsuUser};
|
||||||
use super::{calculate_weighted_map_length, OsuClient};
|
use super::{calculate_weighted_map_length, OsuEnv};
|
||||||
use super::{embeds::score_embed, BeatmapWithMode};
|
use super::{embeds::score_embed, BeatmapWithMode};
|
||||||
|
|
||||||
/// osu! announcer's unique announcer key.
|
/// osu! announcer's unique announcer key.
|
||||||
|
@ -51,9 +50,8 @@ impl youmubot_prelude::Announcer for Announcer {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// For each user...
|
// For each user...
|
||||||
let users = {
|
let users = {
|
||||||
let data = d.read().await;
|
let env = d.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let data = data.get::<OsuSavedUsers>().unwrap();
|
env.saved_users.all().await?
|
||||||
data.all().await?
|
|
||||||
};
|
};
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
users
|
users
|
||||||
|
@ -198,13 +196,12 @@ impl Announcer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn std_weighted_map_length(ctx: &Context, u: &OsuUser) -> Result<f64> {
|
async fn std_weighted_map_length(ctx: &Context, u: &OsuUser) -> Result<f64> {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let client = data.get::<OsuClient>().unwrap().clone();
|
let scores = env
|
||||||
let cache = data.get::<BeatmapMetaCache>().unwrap();
|
.client
|
||||||
let scores = client
|
|
||||||
.user_best(UserID::ID(u.id), |f| f.mode(Mode::Std).limit(100))
|
.user_best(UserID::ID(u.id), |f| f.mode(Mode::Std).limit(100))
|
||||||
.await?;
|
.await?;
|
||||||
calculate_weighted_map_length(&scores, cache, Mode::Std).await
|
calculate_weighted_map_length(&scores, &env.beatmaps, Mode::Std).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,11 +279,12 @@ impl<'a> CollectedScore<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_beatmap(&self, ctx: &Context) -> Result<(BeatmapWithMode, BeatmapContent)> {
|
async fn get_beatmap(&self, ctx: &Context) -> Result<(BeatmapWithMode, BeatmapContent)> {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let cache = data.get::<BeatmapMetaCache>().unwrap();
|
let beatmap = env
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
.beatmaps
|
||||||
let beatmap = cache.get_beatmap_default(self.score.beatmap_id).await?;
|
.get_beatmap_default(self.score.beatmap_id)
|
||||||
let content = oppai.get_beatmap(beatmap.beatmap_id).await?;
|
.await?;
|
||||||
|
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||||
Ok((BeatmapWithMode(beatmap, self.mode), content))
|
Ok((BeatmapWithMode(beatmap, self.mode), content))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,9 +339,10 @@ impl<'a> CollectedScore<'a> {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
save_beatmap(&*ctx.data.read().await, channel, bm)
|
|
||||||
.await
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
.pls_ok();
|
|
||||||
|
save_beatmap(&env, channel, bm).await.pls_ok();
|
||||||
Ok(m)
|
Ok(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,27 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use youmubot_db_sql::{models::osu as models, Pool};
|
||||||
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
models::{ApprovalStatus, Beatmap, Mode},
|
models::{ApprovalStatus, Beatmap, Mode},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
|
||||||
use youmubot_db_sql::{models::osu as models, Pool};
|
|
||||||
use youmubot_prelude::*;
|
|
||||||
|
|
||||||
/// BeatmapMetaCache intercepts beatmap-by-id requests and caches them for later recalling.
|
/// BeatmapMetaCache intercepts beatmap-by-id requests and caches them for later recalling.
|
||||||
/// Does not cache non-Ranked beatmaps.
|
/// Does not cache non-Ranked beatmaps.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BeatmapMetaCache {
|
pub struct BeatmapMetaCache {
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
pool: Pool,
|
pool: Pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for BeatmapMetaCache {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "<BeatmapMetaCache>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TypeMapKey for BeatmapMetaCache {
|
impl TypeMapKey for BeatmapMetaCache {
|
||||||
type Value = BeatmapMetaCache;
|
type Value = BeatmapMetaCache;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
use super::db::OsuLastBeatmap;
|
|
||||||
use super::BeatmapWithMode;
|
|
||||||
use serenity::model::id::ChannelId;
|
use serenity::model::id::ChannelId;
|
||||||
|
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
use super::{BeatmapWithMode, OsuEnv};
|
||||||
|
|
||||||
/// Save the beatmap into the server data storage.
|
/// Save the beatmap into the server data storage.
|
||||||
pub(crate) async fn save_beatmap(
|
pub(crate) async fn save_beatmap(
|
||||||
data: &TypeMap,
|
env: &OsuEnv,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
bm: &BeatmapWithMode,
|
bm: &BeatmapWithMode,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
data.get::<OsuLastBeatmap>()
|
env.last_beatmaps.save(channel_id, &bm.0, bm.1).await?;
|
||||||
.unwrap()
|
|
||||||
.save(channel_id, &bm.0, bm.1)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the last beatmap requested from this channel.
|
/// Get the last beatmap requested from this channel.
|
||||||
pub(crate) async fn get_beatmap(
|
pub(crate) async fn get_beatmap(
|
||||||
data: &TypeMap,
|
env: &OsuEnv,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
) -> Result<Option<BeatmapWithMode>> {
|
) -> Result<Option<BeatmapWithMode>> {
|
||||||
data.get::<OsuLastBeatmap>()
|
env.last_beatmaps
|
||||||
.unwrap()
|
|
||||||
.by_channel(channel_id)
|
.by_channel(channel_id)
|
||||||
.await
|
.await
|
||||||
.map(|v| v.map(|(bm, mode)| BeatmapWithMode(bm, mode)))
|
.map(|v| v.map(|(bm, mode)| BeatmapWithMode(bm, mode)))
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use youmubot_db_sql::{models::osu as models, models::osu_user as model, Pool};
|
|
||||||
|
|
||||||
use crate::models::{Beatmap, Mode, Score};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serenity::model::id::{ChannelId, UserId};
|
use serenity::model::id::{ChannelId, UserId};
|
||||||
|
|
||||||
|
use youmubot_db_sql::{models::osu as models, models::osu_user as model, Pool};
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
use crate::models::{Beatmap, Mode, Score};
|
||||||
|
|
||||||
/// Save the user IDs.
|
/// Save the user IDs.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct OsuSavedUsers {
|
pub struct OsuSavedUsers {
|
||||||
pool: Pool,
|
pool: Pool,
|
||||||
}
|
}
|
||||||
|
@ -60,6 +62,7 @@ impl OsuSavedUsers {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save each channel's last requested beatmap.
|
/// Save each channel's last requested beatmap.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct OsuLastBeatmap(Pool);
|
pub struct OsuLastBeatmap(Pool);
|
||||||
|
|
||||||
impl TypeMapKey for OsuLastBeatmap {
|
impl TypeMapKey for OsuLastBeatmap {
|
||||||
|
@ -99,6 +102,7 @@ impl OsuLastBeatmap {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save each channel's last requested beatmap.
|
/// Save each channel's last requested beatmap.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct OsuUserBests(Pool);
|
pub struct OsuUserBests(Pool);
|
||||||
|
|
||||||
impl TypeMapKey for OsuUserBests {
|
impl TypeMapKey for OsuUserBests {
|
||||||
|
@ -188,14 +192,16 @@ impl From<model::OsuUser> for OsuUser {
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod legacy {
|
mod legacy {
|
||||||
use chrono::{DateTime, Utc};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::models::{Beatmap, Mode, Score};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serenity::model::id::{ChannelId, UserId};
|
use serenity::model::id::{ChannelId, UserId};
|
||||||
use std::collections::HashMap;
|
|
||||||
use youmubot_db::DB;
|
use youmubot_db::DB;
|
||||||
|
|
||||||
|
use crate::models::{Beatmap, Mode, Score};
|
||||||
|
|
||||||
pub type OsuSavedUsers = DB<HashMap<UserId, OsuUser>>;
|
pub type OsuSavedUsers = DB<HashMap<UserId, OsuUser>>;
|
||||||
|
|
||||||
/// An osu! saved user.
|
/// An osu! saved user.
|
||||||
|
|
|
@ -54,9 +54,7 @@ mod scores {
|
||||||
|
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
use crate::discord::{
|
use crate::discord::{cache::save_beatmap, BeatmapWithMode, OsuEnv};
|
||||||
cache::save_beatmap, BeatmapCache, BeatmapMetaCache, BeatmapWithMode,
|
|
||||||
};
|
|
||||||
use crate::models::{Mode, Score};
|
use crate::models::{Mode, Score};
|
||||||
|
|
||||||
pub async fn display_scores_grid<'a>(
|
pub async fn display_scores_grid<'a>(
|
||||||
|
@ -88,19 +86,17 @@ mod scores {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl pagination::Paginate for Paginate {
|
impl pagination::Paginate for Paginate {
|
||||||
async fn render(&mut self, page: u8, ctx: &Context, msg: &mut Message) -> Result<bool> {
|
async fn render(&mut self, page: u8, ctx: &Context, msg: &mut Message) -> Result<bool> {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let client = data.get::<crate::discord::OsuClient>().unwrap();
|
|
||||||
let osu = data.get::<BeatmapMetaCache>().unwrap();
|
|
||||||
let beatmap_cache = data.get::<BeatmapCache>().unwrap();
|
|
||||||
let page = page as usize;
|
let page = page as usize;
|
||||||
let score = &self.scores[page];
|
let score = &self.scores[page];
|
||||||
|
|
||||||
let hourglass = msg.react(ctx, '⌛').await?;
|
let hourglass = msg.react(ctx, '⌛').await?;
|
||||||
let mode = self.mode;
|
let mode = self.mode;
|
||||||
let beatmap = osu.get_beatmap(score.beatmap_id, mode).await?;
|
let beatmap = env.beatmaps.get_beatmap(score.beatmap_id, mode).await?;
|
||||||
let content = beatmap_cache.get_beatmap(beatmap.beatmap_id).await?;
|
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||||
let bm = BeatmapWithMode(beatmap, mode);
|
let bm = BeatmapWithMode(beatmap, mode);
|
||||||
let user = client
|
let user = env
|
||||||
|
.client
|
||||||
.user(crate::request::UserID::ID(score.user_id), |f| f)
|
.user(crate::request::UserID::ID(score.user_id), |f| f)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::msg("user not found"))?;
|
.ok_or_else(|| Error::msg("user not found"))?;
|
||||||
|
@ -114,7 +110,7 @@ mod scores {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
save_beatmap(&*ctx.data.read().await, msg.channel_id, &bm).await?;
|
save_beatmap(&env, msg.channel_id, &bm).await?;
|
||||||
|
|
||||||
// End
|
// End
|
||||||
hourglass.delete(ctx).await?;
|
hourglass.delete(ctx).await?;
|
||||||
|
@ -138,7 +134,7 @@ mod scores {
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
use crate::discord::oppai_cache::Accuracy;
|
use crate::discord::oppai_cache::Accuracy;
|
||||||
use crate::discord::{Beatmap, BeatmapCache, BeatmapInfo, BeatmapMetaCache};
|
use crate::discord::{Beatmap, BeatmapInfo, OsuEnv};
|
||||||
use crate::models::{Mode, Score};
|
use crate::models::{Mode, Score};
|
||||||
|
|
||||||
pub async fn display_scores_table<'a>(
|
pub async fn display_scores_table<'a>(
|
||||||
|
@ -178,9 +174,10 @@ mod scores {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl pagination::Paginate for Paginate {
|
impl pagination::Paginate for Paginate {
|
||||||
async fn render(&mut self, page: u8, ctx: &Context, msg: &mut Message) -> Result<bool> {
|
async fn render(&mut self, page: u8, ctx: &Context, msg: &mut Message) -> Result<bool> {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let osu = data.get::<BeatmapMetaCache>().unwrap();
|
|
||||||
let beatmap_cache = data.get::<BeatmapCache>().unwrap();
|
let meta_cache = &env.beatmaps;
|
||||||
|
let oppai = &env.oppai;
|
||||||
let page = page as usize;
|
let page = page as usize;
|
||||||
let start = page * ITEMS_PER_PAGE;
|
let start = page * ITEMS_PER_PAGE;
|
||||||
let end = self.scores.len().min(start + ITEMS_PER_PAGE);
|
let end = self.scores.len().min(start + ITEMS_PER_PAGE);
|
||||||
|
@ -194,9 +191,9 @@ mod scores {
|
||||||
let beatmaps = plays
|
let beatmaps = plays
|
||||||
.iter()
|
.iter()
|
||||||
.map(|play| async move {
|
.map(|play| async move {
|
||||||
let beatmap = osu.get_beatmap(play.beatmap_id, mode).await?;
|
let beatmap = meta_cache.get_beatmap(play.beatmap_id, mode).await?;
|
||||||
let info = {
|
let info = {
|
||||||
let b = beatmap_cache.get_beatmap(beatmap.beatmap_id).await?;
|
let b = oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||||
b.get_info_with(mode, play.mods).ok()
|
b.get_info_with(mode, play.mods).ok()
|
||||||
};
|
};
|
||||||
Ok((beatmap, info)) as Result<(Beatmap, Option<BeatmapInfo>)>
|
Ok((beatmap, info)) as Result<(Beatmap, Option<BeatmapInfo>)>
|
||||||
|
@ -211,7 +208,7 @@ mod scores {
|
||||||
match p.pp.map(|pp| format!("{:.2}", pp)) {
|
match p.pp.map(|pp| format!("{:.2}", pp)) {
|
||||||
Some(v) => Ok(v),
|
Some(v) => Ok(v),
|
||||||
None => {
|
None => {
|
||||||
let b = beatmap_cache.get_beatmap(p.beatmap_id).await?;
|
let b = oppai.get_beatmap(p.beatmap_id).await?;
|
||||||
let r: Result<_> = Ok({
|
let r: Result<_> = Ok({
|
||||||
b.get_pp_from(
|
b.get_pp_from(
|
||||||
mode,
|
mode,
|
||||||
|
@ -335,10 +332,9 @@ mod beatmapset {
|
||||||
|
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
use crate::discord::OsuEnv;
|
||||||
use crate::{
|
use crate::{
|
||||||
discord::{
|
discord::{cache::save_beatmap, oppai_cache::BeatmapInfoWithPP, BeatmapWithMode},
|
||||||
cache::save_beatmap, oppai_cache::BeatmapInfoWithPP, BeatmapCache, BeatmapWithMode,
|
|
||||||
},
|
|
||||||
models::{Beatmap, Mode, Mods},
|
models::{Beatmap, Mode, Mods},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -386,9 +382,9 @@ mod beatmapset {
|
||||||
|
|
||||||
impl Paginate {
|
impl Paginate {
|
||||||
async fn get_beatmap_info(&self, ctx: &Context, b: &Beatmap) -> Result<BeatmapInfoWithPP> {
|
async fn get_beatmap_info(&self, ctx: &Context, b: &Beatmap) -> Result<BeatmapInfoWithPP> {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let cache = data.get::<BeatmapCache>().unwrap();
|
|
||||||
cache
|
env.oppai
|
||||||
.get_beatmap(b.beatmap_id)
|
.get_beatmap(b.beatmap_id)
|
||||||
.await
|
.await
|
||||||
.and_then(move |v| v.get_possible_pp_with(self.mode.unwrap_or(b.mode), self.mods))
|
.and_then(move |v| v.get_possible_pp_with(self.mode.unwrap_or(b.mode), self.mods))
|
||||||
|
@ -401,15 +397,10 @@ mod beatmapset {
|
||||||
Some(self.maps.len())
|
Some(self.maps.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render(
|
async fn render(&mut self, page: u8, ctx: &Context, msg: &mut Message) -> Result<bool> {
|
||||||
&mut self,
|
|
||||||
page: u8,
|
|
||||||
ctx: &Context,
|
|
||||||
m: &mut serenity::model::channel::Message,
|
|
||||||
) -> Result<bool> {
|
|
||||||
let page = page as usize;
|
let page = page as usize;
|
||||||
if page == self.maps.len() {
|
if page == self.maps.len() {
|
||||||
m.edit(
|
msg.edit(
|
||||||
ctx,
|
ctx,
|
||||||
EditMessage::new().embed(crate::discord::embeds::beatmapset_embed(
|
EditMessage::new().embed(crate::discord::embeds::beatmapset_embed(
|
||||||
&self.maps[..],
|
&self.maps[..],
|
||||||
|
@ -432,7 +423,7 @@ mod beatmapset {
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
m.edit(ctx,
|
msg.edit(ctx,
|
||||||
EditMessage::new().content(self.message.as_str()).embed(
|
EditMessage::new().content(self.message.as_str()).embed(
|
||||||
crate::discord::embeds::beatmap_embed(
|
crate::discord::embeds::beatmap_embed(
|
||||||
map,
|
map,
|
||||||
|
@ -451,9 +442,10 @@ mod beatmapset {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
save_beatmap(
|
save_beatmap(
|
||||||
&*ctx.data.read().await,
|
&env,
|
||||||
m.channel_id,
|
msg.channel_id,
|
||||||
&BeatmapWithMode(map.clone(), self.mode.unwrap_or(map.mode)),
|
&BeatmapWithMode(map.clone(), self.mode.unwrap_or(map.mode)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
use crate::{
|
use std::str::FromStr;
|
||||||
discord::beatmap_cache::BeatmapMetaCache,
|
|
||||||
discord::oppai_cache::{BeatmapCache, BeatmapInfoWithPP},
|
|
||||||
models::{Beatmap, Mode, Mods},
|
|
||||||
};
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serenity::{builder::CreateMessage, model::channel::Message, utils::MessageBuilder};
|
use serenity::{builder::CreateMessage, model::channel::Message, utils::MessageBuilder};
|
||||||
use std::str::FromStr;
|
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
use crate::discord::OsuEnv;
|
||||||
|
use crate::{
|
||||||
|
discord::oppai_cache::BeatmapInfoWithPP,
|
||||||
|
models::{Beatmap, Mode, Mods},
|
||||||
|
};
|
||||||
|
|
||||||
use super::embeds::beatmap_embed;
|
use super::embeds::beatmap_embed;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -43,9 +46,9 @@ pub fn dot_osu_hook<'a>(
|
||||||
let url = attachment.url.clone();
|
let url = attachment.url.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
|
||||||
let (beatmap, _) = oppai.download_beatmap_from_url(&url).await.ok()?;
|
let (beatmap, _) = env.oppai.download_beatmap_from_url(&url).await.ok()?;
|
||||||
crate::discord::embeds::beatmap_offline_embed(
|
crate::discord::embeds::beatmap_offline_embed(
|
||||||
&beatmap,
|
&beatmap,
|
||||||
Mode::from(beatmap.content.mode as u8), /*For now*/
|
Mode::from(beatmap.content.mode as u8), /*For now*/
|
||||||
|
@ -68,9 +71,9 @@ pub fn dot_osu_hook<'a>(
|
||||||
.map(|attachment| {
|
.map(|attachment| {
|
||||||
let url = attachment.url.clone();
|
let url = attachment.url.clone();
|
||||||
async move {
|
async move {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
|
||||||
let beatmaps = oppai.download_osz_from_url(&url).await.pls_ok()?;
|
let beatmaps = env.oppai.download_osz_from_url(&url).await.pls_ok()?;
|
||||||
Some(
|
Some(
|
||||||
beatmaps
|
beatmaps
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -133,11 +136,10 @@ pub fn hook<'a>(
|
||||||
.pls_ok();
|
.pls_ok();
|
||||||
let mode = l.mode.unwrap_or(b.mode);
|
let mode = l.mode.unwrap_or(b.mode);
|
||||||
let bm = super::BeatmapWithMode(*b, mode);
|
let bm = super::BeatmapWithMode(*b, mode);
|
||||||
crate::discord::cache::save_beatmap(
|
|
||||||
&*ctx.data.read().await,
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
msg.channel_id,
|
|
||||||
&bm,
|
crate::discord::cache::save_beatmap(&env, msg.channel_id, &bm)
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.pls_ok();
|
.pls_ok();
|
||||||
}
|
}
|
||||||
|
@ -174,8 +176,7 @@ fn handle_old_links<'a>(
|
||||||
.captures_iter(content)
|
.captures_iter(content)
|
||||||
.map(move |capture| async move {
|
.map(move |capture| async move {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let cache = data.get::<BeatmapCache>().unwrap();
|
let env = data.get::<OsuEnv>().unwrap();
|
||||||
let osu = data.get::<BeatmapMetaCache>().unwrap();
|
|
||||||
let req_type = capture.name("link_type").unwrap().as_str();
|
let req_type = capture.name("link_type").unwrap().as_str();
|
||||||
let mode = capture
|
let mode = capture
|
||||||
.name("mode")
|
.name("mode")
|
||||||
|
@ -192,10 +193,18 @@ fn handle_old_links<'a>(
|
||||||
});
|
});
|
||||||
let beatmaps = match req_type {
|
let beatmaps = match req_type {
|
||||||
"b" => vec![match mode {
|
"b" => vec![match mode {
|
||||||
Some(mode) => osu.get_beatmap(capture["id"].parse()?, mode).await?,
|
Some(mode) => {
|
||||||
None => osu.get_beatmap_default(capture["id"].parse()?).await?,
|
env.beatmaps
|
||||||
|
.get_beatmap(capture["id"].parse()?, mode)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
env.beatmaps
|
||||||
|
.get_beatmap_default(capture["id"].parse()?)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
}],
|
}],
|
||||||
"s" => osu.get_beatmapset(capture["id"].parse()?).await?,
|
"s" => env.beatmaps.get_beatmapset(capture["id"].parse()?).await?,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
if beatmaps.is_empty() {
|
if beatmaps.is_empty() {
|
||||||
|
@ -211,7 +220,7 @@ fn handle_old_links<'a>(
|
||||||
.unwrap_or(Mods::NOMOD);
|
.unwrap_or(Mods::NOMOD);
|
||||||
let info = {
|
let info = {
|
||||||
let mode = mode.unwrap_or(b.mode);
|
let mode = mode.unwrap_or(b.mode);
|
||||||
cache
|
env.oppai
|
||||||
.get_beatmap(b.beatmap_id)
|
.get_beatmap(b.beatmap_id)
|
||||||
.await
|
.await
|
||||||
.and_then(|b| b.get_possible_pp_with(mode, mods))?
|
.and_then(|b| b.get_possible_pp_with(mode, mods))?
|
||||||
|
@ -233,13 +242,10 @@ fn handle_old_links<'a>(
|
||||||
})
|
})
|
||||||
.collect::<stream::FuturesUnordered<_>>()
|
.collect::<stream::FuturesUnordered<_>>()
|
||||||
.filter_map(|v| {
|
.filter_map(|v| {
|
||||||
future::ready(match v {
|
future::ready(v.unwrap_or_else(|e| {
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
None
|
None
|
||||||
}
|
}))
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,20 +256,23 @@ fn handle_new_links<'a>(
|
||||||
NEW_LINK_REGEX
|
NEW_LINK_REGEX
|
||||||
.captures_iter(content)
|
.captures_iter(content)
|
||||||
.map(|capture| async move {
|
.map(|capture| async move {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let osu = data.get::<BeatmapMetaCache>().unwrap();
|
|
||||||
let cache = data.get::<BeatmapCache>().unwrap();
|
|
||||||
let mode = capture
|
let mode = capture
|
||||||
.name("mode")
|
.name("mode")
|
||||||
.and_then(|v| Mode::parse_from_new_site(v.as_str()));
|
.and_then(|v| Mode::parse_from_new_site(v.as_str()));
|
||||||
let link = capture.get(0).unwrap().as_str();
|
let link = capture.get(0).unwrap().as_str();
|
||||||
let beatmaps = match capture.name("beatmap_id") {
|
let beatmaps = match capture.name("beatmap_id") {
|
||||||
Some(ref v) => vec![match mode {
|
Some(ref v) => vec![match mode {
|
||||||
Some(mode) => osu.get_beatmap(v.as_str().parse()?, mode).await?,
|
Some(mode) => env.beatmaps.get_beatmap(v.as_str().parse()?, mode).await?,
|
||||||
None => osu.get_beatmap_default(v.as_str().parse()?).await?,
|
None => {
|
||||||
|
env.beatmaps
|
||||||
|
.get_beatmap_default(v.as_str().parse()?)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
}],
|
}],
|
||||||
None => {
|
None => {
|
||||||
osu.get_beatmapset(capture.name("set_id").unwrap().as_str().parse()?)
|
env.beatmaps
|
||||||
|
.get_beatmapset(capture.name("set_id").unwrap().as_str().parse()?)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -280,7 +289,7 @@ fn handle_new_links<'a>(
|
||||||
.unwrap_or(Mods::NOMOD);
|
.unwrap_or(Mods::NOMOD);
|
||||||
let info = {
|
let info = {
|
||||||
let mode = mode.unwrap_or(beatmap.mode);
|
let mode = mode.unwrap_or(beatmap.mode);
|
||||||
cache
|
env.oppai
|
||||||
.get_beatmap(beatmap.beatmap_id)
|
.get_beatmap(beatmap.beatmap_id)
|
||||||
.await
|
.await
|
||||||
.and_then(|b| b.get_possible_pp_with(mode, mods))?
|
.and_then(|b| b.get_possible_pp_with(mode, mods))?
|
||||||
|
@ -328,16 +337,14 @@ fn handle_short_links<'a>(
|
||||||
return Err(Error::msg("not in server announcer channel"));
|
return Err(Error::msg("not in server announcer channel"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let osu = data.get::<BeatmapMetaCache>().unwrap();
|
|
||||||
let cache = data.get::<BeatmapCache>().unwrap();
|
|
||||||
let mode = capture
|
let mode = capture
|
||||||
.name("mode")
|
.name("mode")
|
||||||
.and_then(|v| Mode::parse_from_new_site(v.as_str()));
|
.and_then(|v| Mode::parse_from_new_site(v.as_str()));
|
||||||
let id: u64 = capture.name("id").unwrap().as_str().parse()?;
|
let id: u64 = capture.name("id").unwrap().as_str().parse()?;
|
||||||
let beatmap = match mode {
|
let beatmap = match mode {
|
||||||
Some(mode) => osu.get_beatmap(id, mode).await,
|
Some(mode) => env.beatmaps.get_beatmap(id, mode).await,
|
||||||
None => osu.get_beatmap_default(id).await,
|
None => env.beatmaps.get_beatmap_default(id).await,
|
||||||
}?;
|
}?;
|
||||||
let mods = capture
|
let mods = capture
|
||||||
.name("mods")
|
.name("mods")
|
||||||
|
@ -345,7 +352,7 @@ fn handle_short_links<'a>(
|
||||||
.unwrap_or(Mods::NOMOD);
|
.unwrap_or(Mods::NOMOD);
|
||||||
let info = {
|
let info = {
|
||||||
let mode = mode.unwrap_or(beatmap.mode);
|
let mode = mode.unwrap_or(beatmap.mode);
|
||||||
cache
|
env.oppai
|
||||||
.get_beatmap(beatmap.beatmap_id)
|
.get_beatmap(beatmap.beatmap_id)
|
||||||
.await
|
.await
|
||||||
.and_then(|b| b.get_possible_pp_with(mode, mods))?
|
.and_then(|b| b.get_possible_pp_with(mode, mods))?
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
use crate::{
|
use std::{str::FromStr, sync::Arc};
|
||||||
discord::beatmap_cache::BeatmapMetaCache,
|
|
||||||
discord::display::ScoreListStyle,
|
|
||||||
discord::oppai_cache::{BeatmapCache, BeatmapInfo},
|
|
||||||
models::{self, Beatmap, Mode, Mods, Score, User},
|
|
||||||
request::{BeatmapRequestKind, UserID},
|
|
||||||
Client as OsuHttpClient,
|
|
||||||
};
|
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
builder::{CreateMessage, EditMessage},
|
builder::{CreateMessage, EditMessage},
|
||||||
|
@ -17,9 +11,24 @@ use serenity::{
|
||||||
model::channel::Message,
|
model::channel::Message,
|
||||||
utils::MessageBuilder,
|
utils::MessageBuilder,
|
||||||
};
|
};
|
||||||
use std::{str::FromStr, sync::Arc};
|
|
||||||
|
use db::{OsuLastBeatmap, OsuSavedUsers, OsuUser, OsuUserBests};
|
||||||
|
use embeds::{beatmap_embed, score_embed, user_embed};
|
||||||
|
use hook::SHORT_LINK_REGEX;
|
||||||
|
pub use hook::{dot_osu_hook, hook};
|
||||||
|
use server_rank::{SERVER_RANK_COMMAND, SHOW_LEADERBOARD_COMMAND};
|
||||||
|
use youmubot_prelude::announcer::AnnouncerHandler;
|
||||||
use youmubot_prelude::{stream::FuturesUnordered, *};
|
use youmubot_prelude::{stream::FuturesUnordered, *};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
discord::beatmap_cache::BeatmapMetaCache,
|
||||||
|
discord::display::ScoreListStyle,
|
||||||
|
discord::oppai_cache::{BeatmapCache, BeatmapInfo},
|
||||||
|
models::{Beatmap, Mode, Mods, Score, User},
|
||||||
|
request::{BeatmapRequestKind, UserID},
|
||||||
|
Client as OsuHttpClient,
|
||||||
|
};
|
||||||
|
|
||||||
mod announcer;
|
mod announcer;
|
||||||
pub(crate) mod beatmap_cache;
|
pub(crate) mod beatmap_cache;
|
||||||
mod cache;
|
mod cache;
|
||||||
|
@ -30,12 +39,6 @@ mod hook;
|
||||||
pub(crate) mod oppai_cache;
|
pub(crate) mod oppai_cache;
|
||||||
mod server_rank;
|
mod server_rank;
|
||||||
|
|
||||||
use db::{OsuLastBeatmap, OsuSavedUsers, OsuUser, OsuUserBests};
|
|
||||||
use embeds::{beatmap_embed, score_embed, user_embed};
|
|
||||||
use hook::SHORT_LINK_REGEX;
|
|
||||||
pub use hook::{dot_osu_hook, hook};
|
|
||||||
use server_rank::{SERVER_RANK_COMMAND, SHOW_LEADERBOARD_COMMAND};
|
|
||||||
|
|
||||||
/// The osu! client.
|
/// The osu! client.
|
||||||
pub(crate) struct OsuClient;
|
pub(crate) struct OsuClient;
|
||||||
|
|
||||||
|
@ -43,6 +46,30 @@ impl TypeMapKey for OsuClient {
|
||||||
type Value = Arc<OsuHttpClient>;
|
type Value = Arc<OsuHttpClient>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The environment for osu! app commands.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct OsuEnv {
|
||||||
|
pub(crate) prelude: Env,
|
||||||
|
// databases
|
||||||
|
pub(crate) saved_users: OsuSavedUsers,
|
||||||
|
pub(crate) last_beatmaps: OsuLastBeatmap,
|
||||||
|
pub(crate) user_bests: OsuUserBests,
|
||||||
|
// clients
|
||||||
|
pub(crate) client: Arc<OsuHttpClient>,
|
||||||
|
pub(crate) oppai: BeatmapCache,
|
||||||
|
pub(crate) beatmaps: BeatmapMetaCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for OsuEnv {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "<osu::Env>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeMapKey for OsuEnv {
|
||||||
|
type Value = OsuEnv;
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets up the osu! command handling section.
|
/// Sets up the osu! command handling section.
|
||||||
///
|
///
|
||||||
/// This automatically enables:
|
/// This automatically enables:
|
||||||
|
@ -55,20 +82,17 @@ impl TypeMapKey for OsuClient {
|
||||||
/// - Hooks. Hooks are completely opt-in.
|
/// - Hooks. Hooks are completely opt-in.
|
||||||
///
|
///
|
||||||
pub async fn setup(
|
pub async fn setup(
|
||||||
_path: &std::path::Path,
|
|
||||||
data: &mut TypeMap,
|
data: &mut TypeMap,
|
||||||
|
prelude: youmubot_prelude::Env,
|
||||||
announcers: &mut AnnouncerHandler,
|
announcers: &mut AnnouncerHandler,
|
||||||
) -> CommandResult {
|
) -> Result<OsuEnv> {
|
||||||
let sql_client = data.get::<SQLClient>().unwrap().clone();
|
|
||||||
// Databases
|
// Databases
|
||||||
data.insert::<OsuSavedUsers>(OsuSavedUsers::new(sql_client.clone()));
|
let saved_users = OsuSavedUsers::new(prelude.sql.clone());
|
||||||
data.insert::<OsuLastBeatmap>(OsuLastBeatmap::new(sql_client.clone()));
|
let last_beatmaps = OsuLastBeatmap::new(prelude.sql.clone());
|
||||||
data.insert::<OsuUserBests>(OsuUserBests::new(sql_client.clone()));
|
let user_bests = OsuUserBests::new(prelude.sql.clone());
|
||||||
|
|
||||||
// API client
|
// API client
|
||||||
let http_client = data.get::<HTTPClient>().unwrap().clone();
|
let osu_client = Arc::new(
|
||||||
let mk_osu_client = || async {
|
|
||||||
Arc::new(
|
|
||||||
OsuHttpClient::new(
|
OsuHttpClient::new(
|
||||||
std::env::var("OSU_API_CLIENT_ID")
|
std::env::var("OSU_API_CLIENT_ID")
|
||||||
.expect("Please set OSU_API_CLIENT_ID as osu! api v2 client ID.")
|
.expect("Please set OSU_API_CLIENT_ID as osu! api v2 client ID.")
|
||||||
|
@ -79,26 +103,37 @@ pub async fn setup(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("osu! should be initialized"),
|
.expect("osu! should be initialized"),
|
||||||
)
|
);
|
||||||
};
|
let oppai_cache = BeatmapCache::new(prelude.http.clone(), prelude.sql.clone());
|
||||||
let osu_client = mk_osu_client().await;
|
let beatmap_cache = BeatmapMetaCache::new(osu_client.clone(), prelude.sql.clone());
|
||||||
data.insert::<OsuClient>(osu_client.clone());
|
|
||||||
data.insert::<oppai_cache::BeatmapCache>(oppai_cache::BeatmapCache::new(
|
|
||||||
http_client.clone(),
|
|
||||||
sql_client.clone(),
|
|
||||||
));
|
|
||||||
data.insert::<beatmap_cache::BeatmapMetaCache>(beatmap_cache::BeatmapMetaCache::new(
|
|
||||||
osu_client.clone(),
|
|
||||||
sql_client,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Announcer
|
// Announcer
|
||||||
let osu_client = mk_osu_client().await;
|
|
||||||
announcers.add(
|
announcers.add(
|
||||||
announcer::ANNOUNCER_KEY,
|
announcer::ANNOUNCER_KEY,
|
||||||
announcer::Announcer::new(osu_client),
|
announcer::Announcer::new(osu_client.clone()),
|
||||||
);
|
);
|
||||||
Ok(())
|
|
||||||
|
// Legacy data
|
||||||
|
data.insert::<OsuLastBeatmap>(last_beatmaps.clone());
|
||||||
|
data.insert::<OsuSavedUsers>(saved_users.clone());
|
||||||
|
data.insert::<OsuUserBests>(user_bests.clone());
|
||||||
|
data.insert::<OsuClient>(osu_client.clone());
|
||||||
|
data.insert::<BeatmapCache>(oppai_cache.clone());
|
||||||
|
data.insert::<BeatmapMetaCache>(beatmap_cache.clone());
|
||||||
|
|
||||||
|
let env = OsuEnv {
|
||||||
|
prelude,
|
||||||
|
saved_users,
|
||||||
|
last_beatmaps,
|
||||||
|
user_bests,
|
||||||
|
client: osu_client,
|
||||||
|
oppai: oppai_cache,
|
||||||
|
beatmaps: beatmap_cache,
|
||||||
|
};
|
||||||
|
|
||||||
|
data.insert::<OsuEnv>(env.clone());
|
||||||
|
|
||||||
|
Ok(env)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[group]
|
#[group]
|
||||||
|
@ -128,7 +163,8 @@ struct Osu;
|
||||||
#[usage = "[username or user_id = your saved username]"]
|
#[usage = "[username or user_id = your saved username]"]
|
||||||
#[max_args(1)]
|
#[max_args(1)]
|
||||||
pub async fn std(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
pub async fn std(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
get_user(ctx, msg, args, Mode::Std).await
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
|
get_user(ctx, &env, msg, args, Mode::Std).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
|
@ -137,7 +173,8 @@ pub async fn std(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
#[usage = "[username or user_id = your saved username]"]
|
#[usage = "[username or user_id = your saved username]"]
|
||||||
#[max_args(1)]
|
#[max_args(1)]
|
||||||
pub async fn taiko(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
pub async fn taiko(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
get_user(ctx, msg, args, Mode::Taiko).await
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
|
get_user(ctx, &env, msg, args, Mode::Taiko).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
|
@ -146,7 +183,8 @@ pub async fn taiko(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
#[usage = "[username or user_id = your saved username]"]
|
#[usage = "[username or user_id = your saved username]"]
|
||||||
#[max_args(1)]
|
#[max_args(1)]
|
||||||
pub async fn catch(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
pub async fn catch(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
get_user(ctx, msg, args, Mode::Catch).await
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
|
get_user(ctx, &env, msg, args, Mode::Catch).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
|
@ -155,7 +193,8 @@ pub async fn catch(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
#[usage = "[username or user_id = your saved username]"]
|
#[usage = "[username or user_id = your saved username]"]
|
||||||
#[max_args(1)]
|
#[max_args(1)]
|
||||||
pub async fn mania(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
pub async fn mania(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
get_user(ctx, msg, args, Mode::Mania).await
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
|
get_user(ctx, &env, msg, args, Mode::Mania).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct BeatmapWithMode(pub Beatmap, pub Mode);
|
pub(crate) struct BeatmapWithMode(pub Beatmap, pub Mode);
|
||||||
|
@ -177,18 +216,18 @@ impl AsRef<Beatmap> for BeatmapWithMode {
|
||||||
#[usage = "[username or user_id]"]
|
#[usage = "[username or user_id]"]
|
||||||
#[num_args(1)]
|
#[num_args(1)]
|
||||||
pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let osu = data.get::<OsuClient>().unwrap();
|
let osu_client = &env.client;
|
||||||
|
|
||||||
let user = args.single::<String>()?;
|
let user = args.single::<String>()?;
|
||||||
let u = match osu.user(UserID::from_string(user), |f| f).await? {
|
let u = match osu_client.user(UserID::from_string(user), |f| f).await? {
|
||||||
Some(u) => u,
|
Some(u) => u,
|
||||||
None => {
|
None => {
|
||||||
msg.reply(&ctx, "user not found...").await?;
|
msg.reply(&ctx, "user not found...").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
async fn find_score(client: &OsuHttpClient, u: &User) -> Result<Option<(models::Score, Mode)>> {
|
async fn find_score(client: &OsuHttpClient, u: &User) -> Result<Option<(Score, Mode)>> {
|
||||||
for mode in &[Mode::Std, Mode::Taiko, Mode::Catch, Mode::Mania] {
|
for mode in &[Mode::Std, Mode::Taiko, Mode::Catch, Mode::Mania] {
|
||||||
let scores = client
|
let scores = client
|
||||||
.user_best(UserID::ID(u.id), |f| f.mode(*mode))
|
.user_best(UserID::ID(u.id), |f| f.mode(*mode))
|
||||||
|
@ -199,7 +238,7 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
let (score, mode) = match find_score(osu, &u).await? {
|
let (score, mode) = match find_score(osu_client, &u).await? {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => {
|
||||||
msg.reply(
|
msg.reply(
|
||||||
|
@ -220,19 +259,27 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
.any(|s| s.beatmap_id == map_id))
|
.any(|s| s.beatmap_id == map_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
let reply = msg.reply(&ctx, format!("To set your osu username, please make your most recent play be the following map: `/b/{}` in **{}** mode! It does **not** have to be a pass, and **NF** can be used! React to this message with 👌 within 5 minutes when you're done!", score.beatmap_id, mode.as_str_new_site()));
|
let reply = msg.reply(
|
||||||
let beatmap = osu
|
&ctx,
|
||||||
.beatmaps(
|
format!(
|
||||||
crate::request::BeatmapRequestKind::Beatmap(score.beatmap_id),
|
"To set your osu username, please make your most recent play \
|
||||||
|f| f.mode(mode, true),
|
be the following map: `/b/{}` in **{}** mode! \
|
||||||
)
|
It does **not** have to be a pass, and **NF** can be used! \
|
||||||
|
React to this message with 👌 within 5 minutes when you're done!",
|
||||||
|
score.beatmap_id,
|
||||||
|
mode.as_str_new_site()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let beatmap = osu_client
|
||||||
|
.beatmaps(BeatmapRequestKind::Beatmap(score.beatmap_id), |f| {
|
||||||
|
f.mode(mode, true)
|
||||||
|
})
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let info = data
|
let info = env
|
||||||
.get::<BeatmapCache>()
|
.oppai
|
||||||
.unwrap()
|
|
||||||
.get_beatmap(beatmap.beatmap_id)
|
.get_beatmap(beatmap.beatmap_id)
|
||||||
.await?
|
.await?
|
||||||
.get_possible_pp_with(mode, Mods::NOMOD)?;
|
.get_possible_pp_with(mode, Mods::NOMOD)?;
|
||||||
|
@ -254,7 +301,7 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
.next()
|
.next()
|
||||||
.await;
|
.await;
|
||||||
if let Some(ur) = user_reaction {
|
if let Some(ur) = user_reaction {
|
||||||
if check(osu, &u, score.beatmap_id).await? {
|
if check(osu_client, &u, score.beatmap_id).await? {
|
||||||
break true;
|
break true;
|
||||||
}
|
}
|
||||||
ur.delete(&ctx).await?;
|
ur.delete(&ctx).await?;
|
||||||
|
@ -268,7 +315,7 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
}
|
}
|
||||||
|
|
||||||
let username = u.username.clone();
|
let username = u.username.clone();
|
||||||
add_user(msg.author.id, u, &data).await?;
|
add_user(msg.author.id, u, &env).await?;
|
||||||
msg.reply(
|
msg.reply(
|
||||||
&ctx,
|
&ctx,
|
||||||
MessageBuilder::new()
|
MessageBuilder::new()
|
||||||
|
@ -287,17 +334,19 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
#[delimiters(" ")]
|
#[delimiters(" ")]
|
||||||
#[num_args(2)]
|
#[num_args(2)]
|
||||||
pub async fn forcesave(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
pub async fn forcesave(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let osu = data.get::<OsuClient>().unwrap();
|
|
||||||
|
let osu_client = &env.client;
|
||||||
|
|
||||||
let target = args.single::<UserId>()?.0;
|
let target = args.single::<UserId>()?.0;
|
||||||
|
|
||||||
let username = args.quoted().trimmed().single::<String>()?;
|
let username = args.quoted().trimmed().single::<String>()?;
|
||||||
let user: Option<User> = osu
|
let user: Option<User> = osu_client
|
||||||
.user(UserID::from_string(username.clone()), |f| f)
|
.user(UserID::from_string(username.clone()), |f| f)
|
||||||
.await?;
|
.await?;
|
||||||
match user {
|
match user {
|
||||||
Some(u) => {
|
Some(u) => {
|
||||||
add_user(target, u, &data).await?;
|
add_user(target, u, &env).await?;
|
||||||
msg.reply(
|
msg.reply(
|
||||||
&ctx,
|
&ctx,
|
||||||
MessageBuilder::new()
|
MessageBuilder::new()
|
||||||
|
@ -314,11 +363,7 @@ pub async fn forcesave(ctx: &Context, msg: &Message, mut args: Args) -> CommandR
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_user(
|
async fn add_user(target: serenity::model::id::UserId, user: User, env: &OsuEnv) -> Result<()> {
|
||||||
target: serenity::model::id::UserId,
|
|
||||||
user: models::User,
|
|
||||||
data: &TypeMap,
|
|
||||||
) -> Result<()> {
|
|
||||||
let u = OsuUser {
|
let u = OsuUser {
|
||||||
user_id: target,
|
user_id: target,
|
||||||
username: user.username.into(),
|
username: user.username.into(),
|
||||||
|
@ -328,7 +373,7 @@ async fn add_user(
|
||||||
pp: [None, None, None, None],
|
pp: [None, None, None, None],
|
||||||
std_weighted_map_length: None,
|
std_weighted_map_length: None,
|
||||||
};
|
};
|
||||||
data.get::<OsuSavedUsers>().unwrap().new_user(u).await?;
|
env.saved_users.new_user(u).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +394,7 @@ impl FromStr for ModeArg {
|
||||||
|
|
||||||
async fn to_user_id_query(
|
async fn to_user_id_query(
|
||||||
s: Option<UsernameArg>,
|
s: Option<UsernameArg>,
|
||||||
data: &TypeMap,
|
env: &OsuEnv,
|
||||||
msg: &Message,
|
msg: &Message,
|
||||||
) -> Result<UserID, Error> {
|
) -> Result<UserID, Error> {
|
||||||
let id = match s {
|
let id = match s {
|
||||||
|
@ -358,8 +403,7 @@ async fn to_user_id_query(
|
||||||
None => msg.author.id,
|
None => msg.author.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
data.get::<OsuSavedUsers>()
|
env.saved_users
|
||||||
.unwrap()
|
|
||||||
.by_user_id(id)
|
.by_user_id(id)
|
||||||
.await?
|
.await?
|
||||||
.map(|u| UserID::ID(u.id))
|
.map(|u| UserID::ID(u.id))
|
||||||
|
@ -393,34 +437,37 @@ impl FromStr for Nth {
|
||||||
#[delimiters("/", " ")]
|
#[delimiters("/", " ")]
|
||||||
#[max_args(4)]
|
#[max_args(4)]
|
||||||
pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
|
|
||||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||||
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
|
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
|
||||||
let user = to_user_id_query(
|
let user = to_user_id_query(
|
||||||
args.quoted().trimmed().single::<UsernameArg>().ok(),
|
args.quoted().trimmed().single::<UsernameArg>().ok(),
|
||||||
&data,
|
&env,
|
||||||
msg,
|
msg,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let osu = data.get::<OsuClient>().unwrap();
|
let osu_client = &env.client;
|
||||||
let meta_cache = data.get::<BeatmapMetaCache>().unwrap();
|
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
let user = osu_client
|
||||||
let user = osu
|
|
||||||
.user(user, |f| f.mode(mode))
|
.user(user, |f| f.mode(mode))
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::msg("User not found"))?;
|
.ok_or_else(|| Error::msg("User not found"))?;
|
||||||
match nth {
|
match nth {
|
||||||
Nth::Nth(nth) => {
|
Nth::Nth(nth) => {
|
||||||
let recent_play = osu
|
let recent_play = osu_client
|
||||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(nth))
|
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(nth))
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.last()
|
.last()
|
||||||
.ok_or_else(|| Error::msg("No such play"))?;
|
.ok_or_else(|| Error::msg("No such play"))?;
|
||||||
let beatmap = meta_cache.get_beatmap(recent_play.beatmap_id, mode).await?;
|
let beatmap = env
|
||||||
let content = oppai.get_beatmap(beatmap.beatmap_id).await?;
|
.beatmaps
|
||||||
|
.get_beatmap(recent_play.beatmap_id, mode)
|
||||||
|
.await?;
|
||||||
|
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||||
let beatmap_mode = BeatmapWithMode(beatmap, mode);
|
let beatmap_mode = BeatmapWithMode(beatmap, mode);
|
||||||
|
|
||||||
msg.channel_id
|
msg.channel_id
|
||||||
|
@ -434,10 +481,10 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Save the beatmap...
|
// Save the beatmap...
|
||||||
cache::save_beatmap(&data, msg.channel_id, &beatmap_mode).await?;
|
cache::save_beatmap(&env, msg.channel_id, &beatmap_mode).await?;
|
||||||
}
|
}
|
||||||
Nth::All => {
|
Nth::All => {
|
||||||
let plays = osu
|
let plays = osu_client
|
||||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
||||||
.await?;
|
.await?;
|
||||||
style.display_scores(plays, mode, ctx, msg).await?;
|
style.display_scores(plays, mode, ctx, msg).await?;
|
||||||
|
@ -447,9 +494,9 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get beatmapset.
|
/// Get beatmapset.
|
||||||
struct OptBeatmapset;
|
struct OptBeatmapSet;
|
||||||
|
|
||||||
impl FromStr for OptBeatmapset {
|
impl FromStr for OptBeatmapSet {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
@ -462,11 +509,9 @@ impl FromStr for OptBeatmapset {
|
||||||
|
|
||||||
/// Load the mentioned beatmap from the given message.
|
/// Load the mentioned beatmap from the given message.
|
||||||
pub(crate) async fn load_beatmap(
|
pub(crate) async fn load_beatmap(
|
||||||
ctx: &Context,
|
env: &OsuEnv,
|
||||||
msg: &Message,
|
msg: &Message,
|
||||||
) -> Option<(BeatmapWithMode, Option<Mods>)> {
|
) -> Option<(BeatmapWithMode, Option<Mods>)> {
|
||||||
let data = ctx.data.read().await;
|
|
||||||
|
|
||||||
if let Some(replied) = &msg.referenced_message {
|
if let Some(replied) = &msg.referenced_message {
|
||||||
// Try to look for a mention of the replied message.
|
// Try to look for a mention of the replied message.
|
||||||
let beatmap_id = SHORT_LINK_REGEX.captures(&replied.content).or_else(|| {
|
let beatmap_id = SHORT_LINK_REGEX.captures(&replied.content).or_else(|| {
|
||||||
|
@ -489,8 +534,8 @@ pub(crate) async fn load_beatmap(
|
||||||
let mods = caps
|
let mods = caps
|
||||||
.name("mods")
|
.name("mods")
|
||||||
.and_then(|m| m.as_str().parse::<Mods>().ok());
|
.and_then(|m| m.as_str().parse::<Mods>().ok());
|
||||||
let osu = data.get::<OsuClient>().unwrap();
|
let osu_client = &env.client;
|
||||||
let bms = osu
|
let bms = osu_client
|
||||||
.beatmaps(BeatmapRequestKind::Beatmap(id), |f| f.maybe_mode(mode))
|
.beatmaps(BeatmapRequestKind::Beatmap(id), |f| f.maybe_mode(mode))
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -499,7 +544,7 @@ pub(crate) async fn load_beatmap(
|
||||||
let bm_mode = beatmap.mode;
|
let bm_mode = beatmap.mode;
|
||||||
let bm = BeatmapWithMode(beatmap, mode.unwrap_or(bm_mode));
|
let bm = BeatmapWithMode(beatmap, mode.unwrap_or(bm_mode));
|
||||||
// Store the beatmap in history
|
// Store the beatmap in history
|
||||||
cache::save_beatmap(&data, msg.channel_id, &bm)
|
cache::save_beatmap(&env, msg.channel_id, &bm)
|
||||||
.await
|
.await
|
||||||
.pls_ok();
|
.pls_ok();
|
||||||
|
|
||||||
|
@ -508,7 +553,7 @@ pub(crate) async fn load_beatmap(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = cache::get_beatmap(&data, msg.channel_id)
|
let b = cache::get_beatmap(&env, msg.channel_id)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
.flatten();
|
.flatten();
|
||||||
|
@ -522,16 +567,16 @@ pub(crate) async fn load_beatmap(
|
||||||
#[delimiters(" ")]
|
#[delimiters(" ")]
|
||||||
#[max_args(2)]
|
#[max_args(2)]
|
||||||
pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let b = load_beatmap(ctx, msg).await;
|
|
||||||
let beatmapset = args.find::<OptBeatmapset>().is_ok();
|
let b = load_beatmap(&env, msg).await;
|
||||||
|
let beatmapset = args.find::<OptBeatmapSet>().is_ok();
|
||||||
|
|
||||||
match b {
|
match b {
|
||||||
Some((BeatmapWithMode(b, m), mods_def)) => {
|
Some((BeatmapWithMode(b, m), mods_def)) => {
|
||||||
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD);
|
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD);
|
||||||
if beatmapset {
|
if beatmapset {
|
||||||
let beatmap_cache = data.get::<BeatmapMetaCache>().unwrap();
|
let beatmapset = env.beatmaps.get_beatmapset(b.beatmapset_id).await?;
|
||||||
let beatmapset = beatmap_cache.get_beatmapset(b.beatmapset_id).await?;
|
|
||||||
display::display_beatmapset(
|
display::display_beatmapset(
|
||||||
ctx,
|
ctx,
|
||||||
beatmapset,
|
beatmapset,
|
||||||
|
@ -543,9 +588,8 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let info = data
|
let info = env
|
||||||
.get::<BeatmapCache>()
|
.oppai
|
||||||
.unwrap()
|
|
||||||
.get_beatmap(b.beatmap_id)
|
.get_beatmap(b.beatmap_id)
|
||||||
.await?
|
.await?
|
||||||
.get_possible_pp_with(m, mods)?;
|
.get_possible_pp_with(m, mods)?;
|
||||||
|
@ -574,8 +618,8 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
#[description = "Check your own or someone else's best record on the last beatmap. Also stores the result if possible."]
|
#[description = "Check your own or someone else's best record on the last beatmap. Also stores the result if possible."]
|
||||||
#[max_args(3)]
|
#[max_args(3)]
|
||||||
pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let bm = load_beatmap(ctx, msg).await;
|
let bm = load_beatmap(&env, msg).await;
|
||||||
|
|
||||||
let bm = match bm {
|
let bm = match bm {
|
||||||
Some((bm, _)) => bm,
|
Some((bm, _)) => bm,
|
||||||
|
@ -598,15 +642,15 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
||||||
None => Some(msg.author.id),
|
None => Some(msg.author.id),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let user = to_user_id_query(username_arg, &data, msg).await?;
|
let user = to_user_id_query(username_arg, &env, msg).await?;
|
||||||
|
|
||||||
let osu = data.get::<OsuClient>().unwrap();
|
let osu_client = env.client;
|
||||||
|
|
||||||
let user = osu
|
let user = osu_client
|
||||||
.user(user, |f| f)
|
.user(user, |f| f)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::msg("User not found"))?;
|
.ok_or_else(|| Error::msg("User not found"))?;
|
||||||
let mut scores = osu
|
let mut scores = osu_client
|
||||||
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(m))
|
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(m))
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -625,8 +669,7 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
||||||
|
|
||||||
if let Some(user_id) = user_id {
|
if let Some(user_id) = user_id {
|
||||||
// Save to database
|
// Save to database
|
||||||
data.get::<OsuUserBests>()
|
env.user_bests
|
||||||
.unwrap()
|
|
||||||
.save(user_id, m, scores.clone())
|
.save(user_id, m, scores.clone())
|
||||||
.await
|
.await
|
||||||
.pls_ok();
|
.pls_ok();
|
||||||
|
@ -644,7 +687,7 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
||||||
#[example = "#2 / taiko / natsukagami"]
|
#[example = "#2 / taiko / natsukagami"]
|
||||||
#[max_args(4)]
|
#[max_args(4)]
|
||||||
pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||||
let mode = args
|
let mode = args
|
||||||
|
@ -652,19 +695,16 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
.map(|ModeArg(t)| t)
|
.map(|ModeArg(t)| t)
|
||||||
.unwrap_or(Mode::Std);
|
.unwrap_or(Mode::Std);
|
||||||
|
|
||||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &data, msg).await?;
|
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &env, msg).await?;
|
||||||
let meta_cache = data.get::<BeatmapMetaCache>().unwrap();
|
let osu_client = &env.client;
|
||||||
let osu = data.get::<OsuClient>().unwrap();
|
let user = osu_client
|
||||||
|
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
|
||||||
let user = osu
|
|
||||||
.user(user, |f| f.mode(mode))
|
.user(user, |f| f.mode(mode))
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::msg("User not found"))?;
|
.ok_or_else(|| Error::msg("User not found"))?;
|
||||||
|
|
||||||
match nth {
|
match nth {
|
||||||
Nth::Nth(nth) => {
|
Nth::Nth(nth) => {
|
||||||
let top_play = osu
|
let top_play = osu_client
|
||||||
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(nth))
|
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(nth))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -674,8 +714,8 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.last()
|
.last()
|
||||||
.ok_or_else(|| Error::msg("No such play"))?;
|
.ok_or_else(|| Error::msg("No such play"))?;
|
||||||
let beatmap = meta_cache.get_beatmap(top_play.beatmap_id, mode).await?;
|
let beatmap = env.beatmaps.get_beatmap(top_play.beatmap_id, mode).await?;
|
||||||
let content = oppai.get_beatmap(beatmap.beatmap_id).await?;
|
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||||
let beatmap = BeatmapWithMode(beatmap, mode);
|
let beatmap = BeatmapWithMode(beatmap, mode);
|
||||||
|
|
||||||
msg.channel_id
|
msg.channel_id
|
||||||
|
@ -694,10 +734,10 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Save the beatmap...
|
// Save the beatmap...
|
||||||
cache::save_beatmap(&data, msg.channel_id, &beatmap).await?;
|
cache::save_beatmap(&env, msg.channel_id, &beatmap).await?;
|
||||||
}
|
}
|
||||||
Nth::All => {
|
Nth::All => {
|
||||||
let plays = osu
|
let plays = osu_client
|
||||||
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||||
.await?;
|
.await?;
|
||||||
style.display_scores(plays, mode, ctx, msg).await?;
|
style.display_scores(plays, mode, ctx, msg).await?;
|
||||||
|
@ -712,34 +752,39 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
#[usage = "[--oppai to clear oppai cache as well]"]
|
#[usage = "[--oppai to clear oppai cache as well]"]
|
||||||
#[max_args(1)]
|
#[max_args(1)]
|
||||||
pub async fn clean_cache(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
pub async fn clean_cache(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let meta_cache = data.get::<BeatmapMetaCache>().unwrap();
|
env.beatmaps.clear().await?;
|
||||||
meta_cache.clear().await?;
|
|
||||||
if args.remains() == Some("--oppai") {
|
if args.remains() == Some("--oppai") {
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
env.oppai.clear().await?;
|
||||||
oppai.clear().await?;
|
|
||||||
}
|
}
|
||||||
msg.reply_ping(ctx, "Beatmap cache cleared!").await?;
|
msg.reply_ping(ctx, "Beatmap cache cleared!").await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user(ctx: &Context, msg: &Message, mut args: Args, mode: Mode) -> CommandResult {
|
async fn get_user(
|
||||||
let data = ctx.data.read().await;
|
ctx: &Context,
|
||||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &data, msg).await?;
|
env: &OsuEnv,
|
||||||
let osu = data.get::<OsuClient>().unwrap();
|
msg: &Message,
|
||||||
let cache = data.get::<BeatmapMetaCache>().unwrap();
|
mut args: Args,
|
||||||
let user = osu.user(user, |f| f.mode(mode)).await?;
|
mode: Mode,
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
) -> CommandResult {
|
||||||
|
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &env, msg).await?;
|
||||||
|
let osu_client = &env.client;
|
||||||
|
let meta_cache = &env.beatmaps;
|
||||||
|
let user = osu_client.user(user, |f| f.mode(mode)).await?;
|
||||||
|
|
||||||
match user {
|
match user {
|
||||||
Some(u) => {
|
Some(u) => {
|
||||||
let bests = osu
|
let bests = osu_client
|
||||||
.user_best(UserID::ID(u.id), |f| f.limit(100).mode(mode))
|
.user_best(UserID::ID(u.id), |f| f.limit(100).mode(mode))
|
||||||
.await?;
|
.await?;
|
||||||
let map_length = calculate_weighted_map_length(&bests, cache, mode).await?;
|
let map_length = calculate_weighted_map_length(&bests, meta_cache, mode).await?;
|
||||||
let best = match bests.into_iter().next() {
|
let best = match bests.into_iter().next() {
|
||||||
Some(m) => {
|
Some(m) => {
|
||||||
let beatmap = cache.get_beatmap(m.beatmap_id, mode).await?;
|
let beatmap = meta_cache.get_beatmap(m.beatmap_id, mode).await?;
|
||||||
let info = oppai
|
let info = env
|
||||||
|
.oppai
|
||||||
.get_beatmap(m.beatmap_id)
|
.get_beatmap(m.beatmap_id)
|
||||||
.await?
|
.await?
|
||||||
.get_info_with(mode, m.mods)?;
|
.get_info_with(mode, m.mods)?;
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
use crate::{models::Mode, mods::Mods};
|
use std::io::Read;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use osuparse::MetadataSection;
|
use osuparse::MetadataSection;
|
||||||
use rosu_pp::catch::CatchDifficultyAttributes;
|
use rosu_pp::catch::CatchDifficultyAttributes;
|
||||||
use rosu_pp::mania::ManiaDifficultyAttributes;
|
use rosu_pp::mania::ManiaDifficultyAttributes;
|
||||||
use rosu_pp::osu::OsuDifficultyAttributes;
|
use rosu_pp::osu::OsuDifficultyAttributes;
|
||||||
use rosu_pp::taiko::TaikoDifficultyAttributes;
|
use rosu_pp::taiko::TaikoDifficultyAttributes;
|
||||||
use rosu_pp::{AttributeProvider, Beatmap, CatchPP, DifficultyAttributes, ManiaPP, OsuPP, TaikoPP};
|
use rosu_pp::{AttributeProvider, Beatmap, CatchPP, DifficultyAttributes, ManiaPP, OsuPP, TaikoPP};
|
||||||
use std::io::Read;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use youmubot_db_sql::{models::osu as models, Pool};
|
use youmubot_db_sql::{models::osu as models, Pool};
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
use crate::{models::Mode, mods::Mods};
|
||||||
|
|
||||||
/// the information collected from a download/Oppai request.
|
/// the information collected from a download/Oppai request.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BeatmapContent {
|
pub struct BeatmapContent {
|
||||||
|
@ -37,7 +40,8 @@ impl BeatmapInfo {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Accuracy {
|
pub enum Accuracy {
|
||||||
ByCount(u64, u64, u64, u64), // 300 / 100 / 50 / misses
|
ByCount(u64, u64, u64, u64),
|
||||||
|
// 300 / 100 / 50 / misses
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
ByValue(f64, u64),
|
ByValue(f64, u64),
|
||||||
}
|
}
|
||||||
|
@ -159,6 +163,7 @@ impl<'a> PPCalc<'a> for OsuPP<'a> {
|
||||||
self.calculate().difficulty
|
self.calculate().difficulty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PPCalc<'a> for TaikoPP<'a> {
|
impl<'a> PPCalc<'a> for TaikoPP<'a> {
|
||||||
type Attrs = TaikoDifficultyAttributes;
|
type Attrs = TaikoDifficultyAttributes;
|
||||||
|
|
||||||
|
@ -193,6 +198,7 @@ impl<'a> PPCalc<'a> for TaikoPP<'a> {
|
||||||
self.calculate().difficulty
|
self.calculate().difficulty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PPCalc<'a> for CatchPP<'a> {
|
impl<'a> PPCalc<'a> for CatchPP<'a> {
|
||||||
type Attrs = CatchDifficultyAttributes;
|
type Attrs = CatchDifficultyAttributes;
|
||||||
|
|
||||||
|
@ -227,6 +233,7 @@ impl<'a> PPCalc<'a> for CatchPP<'a> {
|
||||||
self.calculate().difficulty
|
self.calculate().difficulty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PPCalc<'a> for ManiaPP<'a> {
|
impl<'a> PPCalc<'a> for ManiaPP<'a> {
|
||||||
type Attrs = ManiaDifficultyAttributes;
|
type Attrs = ManiaDifficultyAttributes;
|
||||||
|
|
||||||
|
@ -304,6 +311,7 @@ impl BeatmapContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A central cache for the beatmaps.
|
/// A central cache for the beatmaps.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct BeatmapCache {
|
pub struct BeatmapCache {
|
||||||
client: ratelimit::Ratelimit<reqwest::Client>,
|
client: ratelimit::Ratelimit<reqwest::Client>,
|
||||||
pool: Pool,
|
pool: Pool,
|
||||||
|
|
|
@ -15,15 +15,12 @@ use youmubot_prelude::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
discord::{
|
discord::{display::ScoreListStyle, oppai_cache::Accuracy},
|
||||||
display::ScoreListStyle,
|
|
||||||
oppai_cache::{Accuracy, BeatmapCache},
|
|
||||||
},
|
|
||||||
models::{Mode, Mods},
|
models::{Mode, Mods},
|
||||||
request::UserID,
|
request::UserID,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{db::OsuSavedUsers, ModeArg, OsuClient};
|
use super::{ModeArg, OsuEnv};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum RankQuery {
|
enum RankQuery {
|
||||||
|
@ -50,21 +47,23 @@ impl FromStr for RankQuery {
|
||||||
#[max_args(1)]
|
#[max_args(1)]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let mode = args
|
let mode = args
|
||||||
.single::<RankQuery>()
|
.single::<RankQuery>()
|
||||||
.unwrap_or(RankQuery::Mode(Mode::Std));
|
.unwrap_or(RankQuery::Mode(Mode::Std));
|
||||||
let guild = m.guild_id.expect("Guild-only command");
|
let guild = m.guild_id.expect("Guild-only command");
|
||||||
let member_cache = data.get::<MemberCache>().unwrap();
|
|
||||||
let osu_users = data
|
let osu_users = env
|
||||||
.get::<OsuSavedUsers>()
|
.saved_users
|
||||||
.unwrap()
|
|
||||||
.all()
|
.all()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| (v.user_id, v))
|
.map(|v| (v.user_id, v))
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
let users = member_cache
|
|
||||||
|
let users = env
|
||||||
|
.prelude
|
||||||
|
.members
|
||||||
.query_members(&ctx, guild)
|
.query_members(&ctx, guild)
|
||||||
.await?
|
.await?
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -102,7 +101,7 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let users = std::sync::Arc::new(users);
|
let users = Arc::new(users);
|
||||||
let last_update = last_update.unwrap();
|
let last_update = last_update.unwrap();
|
||||||
paginate_reply_fn(
|
paginate_reply_fn(
|
||||||
move |page: u8, ctx: &Context, m: &mut Message| {
|
move |page: u8, ctx: &Context, m: &mut Message| {
|
||||||
|
@ -197,7 +196,7 @@ impl Default for OrderBy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for OrderBy {
|
impl FromStr for OrderBy {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
@ -215,52 +214,54 @@ impl std::str::FromStr for OrderBy {
|
||||||
#[description = "See the server's ranks on the last seen beatmap"]
|
#[description = "See the server's ranks on the last seen beatmap"]
|
||||||
#[max_args(2)]
|
#[max_args(2)]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let order = args.single::<OrderBy>().unwrap_or_default();
|
let order = args.single::<OrderBy>().unwrap_or_default();
|
||||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||||
|
|
||||||
let data = ctx.data.read().await;
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let member_cache = data.get::<MemberCache>().unwrap();
|
|
||||||
|
|
||||||
let (bm, _) = match super::load_beatmap(ctx, m).await {
|
let (bm, _) = match super::load_beatmap(&env, msg).await {
|
||||||
Some((bm, mods_def)) => {
|
Some((bm, mods_def)) => {
|
||||||
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD);
|
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD);
|
||||||
(bm, mods)
|
(bm, mods)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
m.reply(&ctx, "No beatmap queried on this channel.").await?;
|
msg.reply(&ctx, "No beatmap queried on this channel.")
|
||||||
|
.await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let osu = data.get::<OsuClient>().unwrap().clone();
|
let osu_client = env.client.clone();
|
||||||
|
|
||||||
// Get oppai map.
|
// Get oppai map.
|
||||||
let mode = bm.1;
|
let mode = bm.1;
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
let oppai = env.oppai;
|
||||||
let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?;
|
let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?;
|
||||||
|
|
||||||
let guild = m.guild_id.expect("Guild-only command");
|
let guild = msg.guild_id.expect("Guild-only command");
|
||||||
let scores = {
|
let scores = {
|
||||||
const NO_SCORES: &str = "No scores have been recorded for this beatmap.";
|
const NO_SCORES: &str = "No scores have been recorded for this beatmap.";
|
||||||
// Signal that we are running.
|
// Signal that we are running.
|
||||||
let running_reaction = m.react(&ctx, '⌛').await?;
|
let running_reaction = msg.react(&ctx, '⌛').await?;
|
||||||
|
|
||||||
let osu_users = data
|
let osu_users = env
|
||||||
.get::<OsuSavedUsers>()
|
.saved_users
|
||||||
.unwrap()
|
|
||||||
.all()
|
.all()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| (v.user_id, v))
|
.map(|v| (v.user_id, v))
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
let mut scores = member_cache
|
let mut scores = env
|
||||||
|
.prelude
|
||||||
|
.members
|
||||||
.query_members(&ctx, guild)
|
.query_members(&ctx, guild)
|
||||||
.await?
|
.await?
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|m| osu_users.get(&m.user.id).map(|ou| (m.distinct(), ou.id)))
|
.filter_map(|m| osu_users.get(&m.user.id).map(|ou| (m.distinct(), ou.id)))
|
||||||
.map(|(mem, osu_id)| {
|
.map(|(mem, osu_id)| {
|
||||||
osu.scores(bm.0.beatmap_id, move |f| {
|
osu_client
|
||||||
|
.scores(bm.0.beatmap_id, move |f| {
|
||||||
f.user(UserID::ID(osu_id)).mode(bm.1)
|
f.user(UserID::ID(osu_id)).mode(bm.1)
|
||||||
})
|
})
|
||||||
.map(|r| Some((mem, r.ok()?)))
|
.map(|r| Some((mem, r.ok()?)))
|
||||||
|
@ -300,7 +301,7 @@ pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> Com
|
||||||
running_reaction.delete(&ctx).await?;
|
running_reaction.delete(&ctx).await?;
|
||||||
|
|
||||||
if scores.is_empty() {
|
if scores.is_empty() {
|
||||||
m.reply(&ctx, NO_SCORES).await?;
|
msg.reply(&ctx, NO_SCORES).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match order {
|
match order {
|
||||||
|
@ -315,7 +316,7 @@ pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> Com
|
||||||
};
|
};
|
||||||
|
|
||||||
if scores.is_empty() {
|
if scores.is_empty() {
|
||||||
m.reply(
|
msg.reply(
|
||||||
&ctx,
|
&ctx,
|
||||||
"No scores have been recorded for this beatmap. Run `osu check` to scan for yours!",
|
"No scores have been recorded for this beatmap. Run `osu check` to scan for yours!",
|
||||||
)
|
)
|
||||||
|
@ -329,7 +330,7 @@ pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> Com
|
||||||
scores.into_iter().map(|(_, _, a)| a).collect(),
|
scores.into_iter().map(|(_, _, a)| a).collect(),
|
||||||
mode,
|
mode,
|
||||||
ctx,
|
ctx,
|
||||||
m,
|
msg,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -409,7 +410,7 @@ pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> Com
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
ctx,
|
ctx,
|
||||||
m,
|
msg,
|
||||||
std::time::Duration::from_secs(60),
|
std::time::Duration::from_secs(60),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
pub mod discord;
|
use std::convert::TryInto;
|
||||||
pub mod models;
|
use std::sync::Arc;
|
||||||
pub mod request;
|
|
||||||
|
|
||||||
use models::*;
|
use models::*;
|
||||||
use request::builders::*;
|
use request::builders::*;
|
||||||
use request::*;
|
use request::*;
|
||||||
use std::convert::TryInto;
|
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
pub mod discord;
|
||||||
|
pub mod models;
|
||||||
|
pub mod request;
|
||||||
|
|
||||||
/// Client is the client that will perform calls to the osu! api server.
|
/// Client is the client that will perform calls to the osu! api server.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
rosu: rosu_v2::Osu,
|
rosu: Arc<rosu_v2::Osu>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vec_try_into<U, T: std::convert::TryFrom<U>>(v: Vec<U>) -> Result<Vec<T>, T::Error> {
|
pub fn vec_try_into<U, T: std::convert::TryFrom<U>>(v: Vec<U>) -> Result<Vec<T>, T::Error> {
|
||||||
|
@ -31,7 +34,9 @@ impl Client {
|
||||||
.client_secret(client_secret)
|
.client_secret(client_secret)
|
||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Client { rosu })
|
Ok(Client {
|
||||||
|
rosu: Arc::new(rosu),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn beatmaps(
|
pub async fn beatmaps(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{AppData, MemberCache, Result};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::{
|
use futures_util::{
|
||||||
future::{join_all, ready, FutureExt},
|
future::{join_all, ready, FutureExt},
|
||||||
|
@ -18,9 +19,11 @@ use serenity::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
utils::MessageBuilder,
|
utils::MessageBuilder,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
|
||||||
use youmubot_db::DB;
|
use youmubot_db::DB;
|
||||||
|
|
||||||
|
use crate::{AppData, MemberCache, Result};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CacheAndHttp(Arc<Cache>, Arc<Http>);
|
pub struct CacheAndHttp(Arc<Cache>, Arc<Http>);
|
||||||
|
|
||||||
|
@ -28,15 +31,19 @@ impl CacheAndHttp {
|
||||||
pub fn from_client(client: &Client) -> Self {
|
pub fn from_client(client: &Client) -> Self {
|
||||||
Self(client.cache.clone(), client.http.clone())
|
Self(client.cache.clone(), client.http.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_context(context: &Context) -> Self {
|
||||||
|
Self(context.cache.clone(), context.http.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CacheHttp for CacheAndHttp {
|
impl CacheHttp for CacheAndHttp {
|
||||||
fn cache(&self) -> Option<&Arc<Cache>> {
|
|
||||||
Some(&self.0)
|
|
||||||
}
|
|
||||||
fn http(&self) -> &Http {
|
fn http(&self) -> &Http {
|
||||||
&self.1
|
&self.1
|
||||||
}
|
}
|
||||||
|
fn cache(&self) -> Option<&Arc<Cache>> {
|
||||||
|
Some(&self.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A list of assigned channels for an announcer.
|
/// A list of assigned channels for an announcer.
|
||||||
|
@ -94,23 +101,14 @@ impl MemberToChannels {
|
||||||
///
|
///
|
||||||
/// This struct manages the list of all Announcers, firing them in a certain interval.
|
/// This struct manages the list of all Announcers, firing them in a certain interval.
|
||||||
pub struct AnnouncerHandler {
|
pub struct AnnouncerHandler {
|
||||||
cache_http: CacheAndHttp,
|
|
||||||
data: AppData,
|
|
||||||
announcers: HashMap<&'static str, RwLock<Box<dyn Announcer + Send + Sync>>>,
|
announcers: HashMap<&'static str, RwLock<Box<dyn Announcer + Send + Sync>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Querying for the AnnouncerHandler in the internal data returns a vec of keys.
|
|
||||||
impl TypeMapKey for AnnouncerHandler {
|
|
||||||
type Value = Vec<&'static str>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Announcer-managing related.
|
/// Announcer-managing related.
|
||||||
impl AnnouncerHandler {
|
impl AnnouncerHandler {
|
||||||
/// Create a new instance of the handler.
|
/// Create a new instance of the handler.
|
||||||
pub fn new(client: &serenity::Client) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
cache_http: CacheAndHttp(client.cache.clone(), client.http.clone()),
|
|
||||||
data: client.data.clone(),
|
|
||||||
announcers: HashMap::new(),
|
announcers: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,10 +134,30 @@ impl AnnouncerHandler {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run(self, client: &Client) -> AnnouncerRunner {
|
||||||
|
let runner = AnnouncerRunner {
|
||||||
|
cache_http: CacheAndHttp::from_client(client),
|
||||||
|
data: client.data.clone(),
|
||||||
|
announcers: self.announcers,
|
||||||
|
};
|
||||||
|
runner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AnnouncerRunner {
|
||||||
|
cache_http: CacheAndHttp,
|
||||||
|
data: AppData,
|
||||||
|
announcers: HashMap<&'static str, RwLock<Box<dyn Announcer + Send + Sync>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Querying for the AnnouncerRunner in the internal data returns a vec of keys.
|
||||||
|
impl TypeMapKey for AnnouncerRunner {
|
||||||
|
type Value = Vec<&'static str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execution-related.
|
/// Execution-related.
|
||||||
impl AnnouncerHandler {
|
impl AnnouncerRunner {
|
||||||
/// Collect the list of guilds and their respective channels, by the key of the announcer.
|
/// Collect the list of guilds and their respective channels, by the key of the announcer.
|
||||||
async fn get_guilds(data: &AppData, key: &'static str) -> Result<Vec<(GuildId, ChannelId)>> {
|
async fn get_guilds(data: &AppData, key: &'static str) -> Result<Vec<(GuildId, ChannelId)>> {
|
||||||
let data = AnnouncerChannels::open(&*data.read().await)
|
let data = AnnouncerChannels::open(&*data.read().await)
|
||||||
|
@ -214,7 +232,7 @@ pub async fn list_announcers(ctx: &Context, m: &Message, _: Args) -> CommandResu
|
||||||
let guild_id = m.guild_id.unwrap();
|
let guild_id = m.guild_id.unwrap();
|
||||||
let data = &*ctx.data.read().await;
|
let data = &*ctx.data.read().await;
|
||||||
let announcers = AnnouncerChannels::open(data);
|
let announcers = AnnouncerChannels::open(data);
|
||||||
let channels = data.get::<AnnouncerHandler>().unwrap();
|
let channels = data.get::<AnnouncerRunner>().unwrap();
|
||||||
let channels = channels
|
let channels = channels
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|&key| {
|
.filter_map(|&key| {
|
||||||
|
@ -249,7 +267,7 @@ pub async fn list_announcers(ctx: &Context, m: &Message, _: Args) -> CommandResu
|
||||||
pub async fn register_announcer(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
pub async fn register_announcer(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let key = args.single::<String>()?;
|
let key = args.single::<String>()?;
|
||||||
let keys = data.get::<AnnouncerHandler>().unwrap();
|
let keys = data.get::<AnnouncerRunner>().unwrap();
|
||||||
if !keys.contains(&&key[..]) {
|
if !keys.contains(&&key[..]) {
|
||||||
m.reply(
|
m.reply(
|
||||||
&ctx,
|
&ctx,
|
||||||
|
@ -296,7 +314,7 @@ pub async fn register_announcer(ctx: &Context, m: &Message, mut args: Args) -> C
|
||||||
pub async fn remove_announcer(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
pub async fn remove_announcer(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let key = args.single::<String>()?;
|
let key = args.single::<String>()?;
|
||||||
let keys = data.get::<AnnouncerHandler>().unwrap();
|
let keys = data.get::<AnnouncerRunner>().unwrap();
|
||||||
if !keys.contains(&key.as_str()) {
|
if !keys.contains(&key.as_str()) {
|
||||||
m.reply(
|
m.reply(
|
||||||
&ctx,
|
&ctx,
|
||||||
|
|
|
@ -1,7 +1,24 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Re-export the anyhow errors
|
||||||
|
pub use anyhow::{anyhow as error, bail, Error, Result};
|
||||||
|
/// Re-exporting async_trait helps with implementing Announcer.
|
||||||
|
pub use async_trait::async_trait;
|
||||||
|
/// Re-export useful future and stream utils
|
||||||
|
pub use futures_util::{future, stream, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
|
||||||
/// Module `prelude` provides a sane set of default imports that can be used inside
|
/// Module `prelude` provides a sane set of default imports that can be used inside
|
||||||
/// a Youmubot source file.
|
/// a Youmubot source file.
|
||||||
pub use serenity::prelude::*;
|
pub use serenity::prelude::*;
|
||||||
use std::sync::Arc;
|
/// Re-export the spawn function
|
||||||
|
pub use tokio::spawn as spawn_future;
|
||||||
|
|
||||||
|
pub use announcer::{Announcer, AnnouncerRunner};
|
||||||
|
pub use args::{ChannelId, Duration, RoleId, UserId, UsernameArg};
|
||||||
|
pub use debugging_ok::OkPrint;
|
||||||
|
pub use flags::Flags;
|
||||||
|
pub use hook::Hook;
|
||||||
|
pub use member_cache::MemberCache;
|
||||||
|
pub use pagination::{paginate, paginate_fn, paginate_reply, paginate_reply_fn, Paginate};
|
||||||
|
|
||||||
pub mod announcer;
|
pub mod announcer;
|
||||||
pub mod args;
|
pub mod args;
|
||||||
|
@ -13,26 +30,6 @@ pub mod ratelimit;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod table_format;
|
pub mod table_format;
|
||||||
|
|
||||||
pub use announcer::{Announcer, AnnouncerHandler};
|
|
||||||
pub use args::{ChannelId, Duration, RoleId, UserId, UsernameArg};
|
|
||||||
pub use flags::Flags;
|
|
||||||
pub use hook::Hook;
|
|
||||||
pub use member_cache::MemberCache;
|
|
||||||
pub use pagination::{paginate, paginate_fn, paginate_reply, paginate_reply_fn, Paginate};
|
|
||||||
|
|
||||||
/// Re-exporting async_trait helps with implementing Announcer.
|
|
||||||
pub use async_trait::async_trait;
|
|
||||||
|
|
||||||
/// Re-export the anyhow errors
|
|
||||||
pub use anyhow::{anyhow as error, bail, Error, Result};
|
|
||||||
pub use debugging_ok::OkPrint;
|
|
||||||
|
|
||||||
/// Re-export useful future and stream utils
|
|
||||||
pub use futures_util::{future, stream, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
|
|
||||||
|
|
||||||
/// Re-export the spawn function
|
|
||||||
pub use tokio::spawn as spawn_future;
|
|
||||||
|
|
||||||
/// The global app data.
|
/// The global app data.
|
||||||
pub type AppData = Arc<RwLock<TypeMap>>;
|
pub type AppData = Arc<RwLock<TypeMap>>;
|
||||||
|
|
||||||
|
@ -50,8 +47,18 @@ impl TypeMapKey for SQLClient {
|
||||||
type Value = youmubot_db_sql::Pool;
|
type Value = youmubot_db_sql::Pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The created base environment.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Env {
|
||||||
|
// clients
|
||||||
|
pub http: reqwest::Client,
|
||||||
|
pub sql: youmubot_db_sql::Pool,
|
||||||
|
pub members: Arc<MemberCache>,
|
||||||
|
// databases
|
||||||
|
// pub(crate) announcer_channels: announcer::AnnouncerChannels,
|
||||||
|
}
|
||||||
|
|
||||||
pub mod prelude_commands {
|
pub mod prelude_commands {
|
||||||
use crate::announcer::ANNOUNCERCOMMANDS_GROUP;
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
framework::standard::{
|
framework::standard::{
|
||||||
macros::{command, group},
|
macros::{command, group},
|
||||||
|
@ -61,6 +68,8 @@ pub mod prelude_commands {
|
||||||
prelude::Context,
|
prelude::Context,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::announcer::ANNOUNCERCOMMANDS_GROUP;
|
||||||
|
|
||||||
#[group("Prelude")]
|
#[group("Prelude")]
|
||||||
#[description = "All the commands that makes the base of Youmu"]
|
#[description = "All the commands that makes the base of Youmu"]
|
||||||
#[commands(ping)]
|
#[commands(ping)]
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
use std::ops::Deref;
|
||||||
/// Provides a simple ratelimit lock (that only works in tokio)
|
/// Provides a simple ratelimit lock (that only works in tokio)
|
||||||
// use tokio::time::
|
// use tokio::time::
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
use flume::{bounded as channel, Receiver, Sender};
|
use flume::{bounded as channel, Receiver, Sender};
|
||||||
use std::ops::Deref;
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
/// Holds the underlying `T` in a rate-limited way.
|
/// Holds the underlying `T` in a rate-limited way.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Ratelimit<T> {
|
pub struct Ratelimit<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
recv: Receiver<()>,
|
recv: Receiver<()>,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use serenity::prelude::*;
|
|
||||||
use std::{path::Path, time::Duration};
|
use std::{path::Path, time::Duration};
|
||||||
|
|
||||||
|
use serenity::prelude::*;
|
||||||
|
|
||||||
|
use crate::Env;
|
||||||
|
|
||||||
/// Set up the prelude libraries.
|
/// Set up the prelude libraries.
|
||||||
///
|
///
|
||||||
/// Panics on failure: Youmubot should *NOT* attempt to continue when this function fails.
|
/// Panics on failure: Youmubot should *NOT* attempt to continue when this function fails.
|
||||||
|
@ -8,8 +11,8 @@ pub async fn setup_prelude(
|
||||||
db_path: impl AsRef<Path>,
|
db_path: impl AsRef<Path>,
|
||||||
sql_path: impl AsRef<Path>,
|
sql_path: impl AsRef<Path>,
|
||||||
data: &mut TypeMap,
|
data: &mut TypeMap,
|
||||||
) {
|
) -> Env {
|
||||||
// Setup the announcer DB.
|
// Set up the announcer DB.
|
||||||
crate::announcer::AnnouncerChannels::insert_into(
|
crate::announcer::AnnouncerChannels::insert_into(
|
||||||
data,
|
data,
|
||||||
db_path.as_ref().join("announcers.yaml"),
|
db_path.as_ref().join("announcers.yaml"),
|
||||||
|
@ -22,17 +25,25 @@ pub async fn setup_prelude(
|
||||||
.expect("SQL database set up");
|
.expect("SQL database set up");
|
||||||
|
|
||||||
// Set up the HTTP client.
|
// Set up the HTTP client.
|
||||||
data.insert::<crate::HTTPClient>(
|
let http_client = reqwest::ClientBuilder::new()
|
||||||
reqwest::ClientBuilder::new()
|
|
||||||
.connect_timeout(Duration::from_secs(5))
|
.connect_timeout(Duration::from_secs(5))
|
||||||
.timeout(Duration::from_secs(60))
|
.timeout(Duration::from_secs(60))
|
||||||
.build()
|
.build()
|
||||||
.expect("Build be able to build HTTP client"),
|
.expect("Build be able to build HTTP client");
|
||||||
);
|
data.insert::<crate::HTTPClient>(http_client.clone());
|
||||||
|
|
||||||
// Set up the member cache.
|
// Set up the member cache.
|
||||||
data.insert::<crate::MemberCache>(std::sync::Arc::new(crate::MemberCache::default()));
|
let member_cache = std::sync::Arc::new(crate::MemberCache::default());
|
||||||
|
data.insert::<crate::MemberCache>(member_cache.clone());
|
||||||
|
|
||||||
// Set up the SQL client.
|
// Set up the SQL client.
|
||||||
data.insert::<crate::SQLClient>(sql_pool);
|
data.insert::<crate::SQLClient>(sql_pool.clone());
|
||||||
|
|
||||||
|
let env = Env {
|
||||||
|
http: http_client,
|
||||||
|
sql: sql_pool,
|
||||||
|
members: member_cache,
|
||||||
|
};
|
||||||
|
|
||||||
|
env
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,37 +9,58 @@ use serenity::{
|
||||||
permissions::Permissions,
|
permissions::Permissions,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use youmubot_prelude::announcer::AnnouncerHandler;
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
struct Handler {
|
struct Handler {
|
||||||
hooks: Vec<RwLock<Box<dyn Hook>>>,
|
hooks: Vec<RwLock<Box<dyn Hook>>>,
|
||||||
|
ready_hooks: Vec<fn(&Context) -> CommandResult>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
fn new() -> Handler {
|
fn new() -> Handler {
|
||||||
Handler { hooks: vec![] }
|
Handler {
|
||||||
|
hooks: vec![],
|
||||||
|
ready_hooks: vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_hook<T: Hook + 'static>(&mut self, f: T) {
|
fn push_hook<T: Hook + 'static>(&mut self, f: T) {
|
||||||
self.hooks.push(RwLock::new(Box::new(f)));
|
self.hooks.push(RwLock::new(Box::new(f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_ready_hook(&mut self, f: fn(&Context) -> CommandResult) {
|
||||||
|
self.ready_hooks.push(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Environment to be passed into the framework
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Env {
|
||||||
|
prelude: youmubot_prelude::Env,
|
||||||
|
#[cfg(feature = "osu")]
|
||||||
|
osu: youmubot_osu::discord::OsuEnv,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<youmubot_prelude::Env> for Env {
|
||||||
|
fn as_ref(&self) -> &youmubot_prelude::Env {
|
||||||
|
&self.prelude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<youmubot_osu::discord::OsuEnv> for Env {
|
||||||
|
fn as_ref(&self) -> &youmubot_osu::discord::OsuEnv {
|
||||||
|
&self.osu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeMapKey for Env {
|
||||||
|
type Value = Env;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
async fn ready(&self, ctx: Context, ready: gateway::Ready) {
|
|
||||||
// Start ReactionWatchers for community.
|
|
||||||
#[cfg(feature = "core")]
|
|
||||||
ctx.data
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.get::<youmubot_core::community::ReactionWatchers>()
|
|
||||||
.unwrap()
|
|
||||||
.init(&ctx)
|
|
||||||
.await;
|
|
||||||
println!("{} is connected!", ready.user.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn message(&self, ctx: Context, message: Message) {
|
async fn message(&self, ctx: Context, message: Message) {
|
||||||
self.hooks
|
self.hooks
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -57,6 +78,23 @@ impl EventHandler for Handler {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn ready(&self, ctx: Context, ready: gateway::Ready) {
|
||||||
|
// Start ReactionWatchers for community.
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
ctx.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<youmubot_core::community::ReactionWatchers>()
|
||||||
|
.unwrap()
|
||||||
|
.init(&ctx)
|
||||||
|
.await;
|
||||||
|
println!("{} is connected!", ready.user.name);
|
||||||
|
|
||||||
|
for f in &self.ready_hooks {
|
||||||
|
f(&ctx).pls_ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the user has "MANAGE_MESSAGES" permission in the channel.
|
/// Returns whether the user has "MANAGE_MESSAGES" permission in the channel.
|
||||||
|
@ -79,16 +117,70 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut handler = Handler::new();
|
let mut handler = Handler::new();
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
handler.push_ready_hook(youmubot_core::ready_hook);
|
||||||
// Set up hooks
|
// Set up hooks
|
||||||
#[cfg(feature = "osu")]
|
#[cfg(feature = "osu")]
|
||||||
|
{
|
||||||
handler.push_hook(youmubot_osu::discord::hook);
|
handler.push_hook(youmubot_osu::discord::hook);
|
||||||
#[cfg(feature = "osu")]
|
|
||||||
handler.push_hook(youmubot_osu::discord::dot_osu_hook);
|
handler.push_hook(youmubot_osu::discord::dot_osu_hook);
|
||||||
|
}
|
||||||
#[cfg(feature = "codeforces")]
|
#[cfg(feature = "codeforces")]
|
||||||
handler.push_hook(youmubot_cf::InfoHook);
|
handler.push_hook(youmubot_cf::InfoHook);
|
||||||
|
|
||||||
// Collect the token
|
// Collect the token
|
||||||
let token = var("TOKEN").expect("Please set TOKEN as the Discord Bot's token to be used.");
|
let token = var("TOKEN").expect("Please set TOKEN as the Discord Bot's token to be used.");
|
||||||
|
|
||||||
|
// Data to be put into context
|
||||||
|
let mut data = TypeMap::new();
|
||||||
|
|
||||||
|
// Set up announcer handler
|
||||||
|
let mut announcers = AnnouncerHandler::new();
|
||||||
|
|
||||||
|
// Setup each package starting from the prelude.
|
||||||
|
let env = {
|
||||||
|
let db_path = var("DBPATH")
|
||||||
|
.map(std::path::PathBuf::from)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
println!("No DBPATH set up ({:?}), using `/data`", e);
|
||||||
|
std::path::PathBuf::from("/data")
|
||||||
|
});
|
||||||
|
let sql_path = var("SQLPATH")
|
||||||
|
.map(std::path::PathBuf::from)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
let res = db_path.join("youmubot.db");
|
||||||
|
println!("No SQLPATH set up ({:?}), using `{:?}`", e, res);
|
||||||
|
res
|
||||||
|
});
|
||||||
|
let prelude = setup::setup_prelude(&db_path, sql_path, &mut data).await;
|
||||||
|
// Setup core
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
youmubot_core::setup(&db_path, &mut data).expect("Setup db should succeed");
|
||||||
|
// osu!
|
||||||
|
#[cfg(feature = "osu")]
|
||||||
|
let osu = youmubot_osu::discord::setup(&mut data, prelude.clone(), &mut announcers)
|
||||||
|
.await
|
||||||
|
.expect("osu! is initialized");
|
||||||
|
// codeforces
|
||||||
|
#[cfg(feature = "codeforces")]
|
||||||
|
youmubot_cf::setup(&db_path, &mut data, &mut announcers).await;
|
||||||
|
|
||||||
|
Env {
|
||||||
|
prelude,
|
||||||
|
#[cfg(feature = "osu")]
|
||||||
|
osu,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
data.insert::<Env>(env);
|
||||||
|
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
println!("Core enabled.");
|
||||||
|
#[cfg(feature = "osu")]
|
||||||
|
println!("osu! enabled.");
|
||||||
|
#[cfg(feature = "codeforces")]
|
||||||
|
println!("codeforces enabled.");
|
||||||
|
|
||||||
// Set up base framework
|
// Set up base framework
|
||||||
let fw = setup_framework(&token[..]).await;
|
let fw = setup_framework(&token[..]).await;
|
||||||
|
|
||||||
|
@ -105,60 +197,20 @@ async fn main() {
|
||||||
| GatewayIntents::DIRECT_MESSAGES
|
| GatewayIntents::DIRECT_MESSAGES
|
||||||
| GatewayIntents::DIRECT_MESSAGE_REACTIONS;
|
| GatewayIntents::DIRECT_MESSAGE_REACTIONS;
|
||||||
Client::builder(token, intents)
|
Client::builder(token, intents)
|
||||||
|
.type_map(data)
|
||||||
.framework(fw)
|
.framework(fw)
|
||||||
.event_handler(handler)
|
.event_handler(handler)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up announcer handler
|
let announcers = announcers.run(&client);
|
||||||
let mut announcers = AnnouncerHandler::new(&client);
|
|
||||||
|
|
||||||
// Setup each package starting from the prelude.
|
|
||||||
{
|
|
||||||
let mut data = client.data.write().await;
|
|
||||||
let db_path = var("DBPATH")
|
|
||||||
.map(std::path::PathBuf::from)
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
println!("No DBPATH set up ({:?}), using `/data`", e);
|
|
||||||
std::path::PathBuf::from("/data")
|
|
||||||
});
|
|
||||||
let sql_path = var("SQLPATH")
|
|
||||||
.map(std::path::PathBuf::from)
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
let res = db_path.join("youmubot.db");
|
|
||||||
println!("No SQLPATH set up ({:?}), using `{:?}`", e, res);
|
|
||||||
res
|
|
||||||
});
|
|
||||||
youmubot_prelude::setup::setup_prelude(&db_path, sql_path, &mut data).await;
|
|
||||||
// Setup core
|
|
||||||
#[cfg(feature = "core")]
|
|
||||||
youmubot_core::setup(&db_path, &client, &mut data).expect("Setup db should succeed");
|
|
||||||
// osu!
|
|
||||||
#[cfg(feature = "osu")]
|
|
||||||
youmubot_osu::discord::setup(&db_path, &mut data, &mut announcers)
|
|
||||||
.await
|
|
||||||
.expect("osu! is initialized");
|
|
||||||
// codeforces
|
|
||||||
#[cfg(feature = "codeforces")]
|
|
||||||
youmubot_cf::setup(&db_path, &mut data, &mut announcers).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "core")]
|
|
||||||
println!("Core enabled.");
|
|
||||||
#[cfg(feature = "osu")]
|
|
||||||
println!("osu! enabled.");
|
|
||||||
#[cfg(feature = "codeforces")]
|
|
||||||
println!("codeforces enabled.");
|
|
||||||
|
|
||||||
tokio::spawn(announcers.scan(std::time::Duration::from_secs(300)));
|
tokio::spawn(announcers.scan(std::time::Duration::from_secs(300)));
|
||||||
|
|
||||||
println!("Starting...");
|
println!("Starting...");
|
||||||
if let Err(v) = client.start().await {
|
if let Err(v) = client.start().await {
|
||||||
panic!("{}", v)
|
panic!("{}", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Hello, world!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets up a framework for a client
|
// Sets up a framework for a client
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue