From 5805efa50fff3728b92a03bb82ce3da83dde7246 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 8 Nov 2022 00:47:47 +0100 Subject: [PATCH 1/3] fix(schema): remove minimum array length requirements --- configuration.schema.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/configuration.schema.json b/configuration.schema.json index 4df0cfa..c57bc67 100644 --- a/configuration.schema.json +++ b/configuration.schema.json @@ -63,7 +63,6 @@ } }, "description": "Introduce new threads with a message.", - "minItems": 1, "uniqueItems": true }, "message_responses": { @@ -138,8 +137,7 @@ "items": { "type": "integer" }, - "uniqueItems": true, - "minItems": 1 + "uniqueItems": true }, "match": { "$ref": "#/$defs/regex", From b7d643a49fc340550a3b8b9a1a2b8f77687e4be5 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 3 Dec 2022 03:52:38 +0100 Subject: [PATCH 2/3] feat(utils): `poll` command --- .env.example | 9 +++++- src/api/client.rs | 68 +++++++++++++++++++++++++++++++++++++++ src/api/mod.rs | 3 ++ src/api/model/api.rs | 6 ++++ src/api/model/auth.rs | 5 +++ src/api/model/mod.rs | 2 ++ src/api/routing.rs | 29 +++++++++++++++++ src/commands/misc.rs | 58 +++++++++++++++++++++++++++++---- src/db/model.rs | 17 +++++++++- src/events/interaction.rs | 53 ++++++++++++++++++++++++++++++ src/events/mod.rs | 52 ++++++++++++++++++------------ src/main.rs | 11 +++++++ src/utils/mod.rs | 5 +-- src/utils/moderation.rs | 1 - src/utils/poll.rs | 65 +++++++++++++++++++++++++++++++++++++ 15 files changed, 352 insertions(+), 32 deletions(-) create mode 100644 src/api/client.rs create mode 100644 src/api/mod.rs create mode 100644 src/api/model/api.rs create mode 100644 src/api/model/auth.rs create mode 100644 src/api/model/mod.rs create mode 100644 src/api/routing.rs create mode 100644 src/events/interaction.rs create mode 100644 src/utils/poll.rs diff --git a/.env.example b/.env.example index 2235b01..5062bbb 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,11 @@ # The Discord authorization token for the bot, requires the MESSAGE_CONTENT intent DISCORD_AUTHORIZATION_TOKEN= # The connection string to the MongoDB database -MONGODB_URI='' \ No newline at end of file +MONGODB_URI='' + +# The api server for the poll command +API_SERVER='' +# The client id for the api +API_CLIENT_ID='' +# The client secret for the api +API_CLIENT_SECRET='' diff --git a/src/api/client.rs b/src/api/client.rs new file mode 100644 index 0000000..75133c7 --- /dev/null +++ b/src/api/client.rs @@ -0,0 +1,68 @@ +use poise::serenity_prelude::JsonMap; +use reqwest::header::HeaderMap; +use reqwest::Client; +use serde::de::DeserializeOwned; +use serde_json::to_vec; + +use super::model::api::Api; +use super::model::auth::Authentication; + +pub struct ApiClient<'a> { + pub client: Client, + api: &'a Api, +} + +struct Request<'a> { + headers: Option, + body: Option<&'a [u8]>, + route: super::routing::RouteInfo, +} + +impl ApiClient<'_> { + pub fn new(api: &Api) -> Self { + let client = Client::builder() + .build() + .expect("Cannot build reqwest::Client"); + + ApiClient { + client, + api, + } + } + + async fn fire(&self, request: &Request<'_>) -> Result { + let client = &self.client; + + Ok(client + .execute(client.request(request.route, url).build()?) + .await? + .json::() + .await + .map_err(From::from) + .unwrap()) + } + + pub async fn authenticate( + &self, + discord_id_hash: &str, + ) -> Result { + let api = &self.api; + + let body = &JsonMap::new(); + for (k, v) in [ + ("id", api.client_id), + ("secret", api.client_secret), + ("discord_id_hash", discord_id_hash.to_string()), + ] { + body.insert(k.to_string(), v) + } + + Ok(self + .fire(&Request { + headers: None, + body: Some(&to_vec(body).unwrap()), + route: RouteInfo::Authenticate, + }) + .await?) + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..4a7c85e --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,3 @@ +pub mod client; +mod model; +mod routing; diff --git a/src/api/model/api.rs b/src/api/model/api.rs new file mode 100644 index 0000000..a2b80ba --- /dev/null +++ b/src/api/model/api.rs @@ -0,0 +1,6 @@ +#[derive(Clone)] +pub struct Api { + pub server: String, + pub client_id: String, + pub client_secret: String, +} diff --git a/src/api/model/auth.rs b/src/api/model/auth.rs new file mode 100644 index 0000000..837c63e --- /dev/null +++ b/src/api/model/auth.rs @@ -0,0 +1,5 @@ +use serde::Deserialize; +#[derive(Deserialize)] +pub struct Authentication<'a> { + pub token: &'a str, +} diff --git a/src/api/model/mod.rs b/src/api/model/mod.rs new file mode 100644 index 0000000..1f85274 --- /dev/null +++ b/src/api/model/mod.rs @@ -0,0 +1,2 @@ +pub mod api; +pub mod auth; diff --git a/src/api/routing.rs b/src/api/routing.rs new file mode 100644 index 0000000..4d5e2d6 --- /dev/null +++ b/src/api/routing.rs @@ -0,0 +1,29 @@ +use std::borrow::Cow; + +use reqwest::Method; + +pub enum RouteInfo { + Authenticate, +} + +pub enum Route { + Authenticate, +} + +impl RouteInfo { + pub fn deconstruct(&self) -> (Method, Route, Cow<'_, str>) { + match *self { + RouteInfo::Authenticate => ( + Method::POST, + Route::Authenticate, + Cow::from(Route::authenticate()), + ), + } + } +} + +impl Route { + pub fn authenticate() -> &'static str { + "/auth" + } +} diff --git a/src/commands/misc.rs b/src/commands/misc.rs index 037450d..8d75534 100644 --- a/src/commands/misc.rs +++ b/src/commands/misc.rs @@ -1,4 +1,5 @@ -use poise::serenity_prelude::{self as serenity, MessageId}; +use chrono::Utc; +use poise::serenity_prelude::{self as serenity, MessageId, ReactionType}; use poise::ReplyHandle; use crate::{Context, Error}; @@ -10,6 +11,13 @@ pub async fn reply( #[description = "The message id to reply to"] reply_message: Option, #[description = "The message to send"] message: String, ) -> Result<(), Error> { + async fn send_ephermal<'a>( + ctx: &Context<'a>, + content: &str, + ) -> Result, serenity::Error> { + ctx.send(|f| f.ephemeral(true).content(content)).await + } + let http = &ctx.discord().http; let channel = &ctx.channel_id(); @@ -38,9 +46,47 @@ pub async fn reply( Ok(()) } -async fn send_ephermal<'a>( - ctx: &Context<'a>, - content: &str, -) -> Result, serenity::Error> { - ctx.send(|f| f.ephemeral(true).content(content)).await +/// Start a poll. +#[poise::command(slash_command)] +pub async fn poll( + ctx: Context<'_>, + #[description = "The id of the poll"] id: u64, + #[description = "The poll message"] message: String, + #[description = "The poll title"] title: String, + #[description = "The minumum server age in days to allow members to poll"] age: u16, +) -> Result<(), Error> { + let data = ctx.data().read().await; + let configuration = &data.configuration; + let embed_color = configuration.general.embed_color; + + ctx.send(|m| { + m.embed(|e| { + let guild = &ctx.guild().unwrap(); + if let Some(url) = guild.icon_url() { + e.thumbnail(url.clone()).footer(|f| { + f.icon_url(url).text(format!( + "{} • {}", + guild.name, + Utc::today().format("%Y/%m/%d") + )) + }) + } else { + e + } + .title(title) + .description(message) + .color(embed_color) + }) + .components(|c| { + c.create_action_row(|r| { + r.create_button(|b| { + b.label("Vote") + .emoji(ReactionType::Unicode("🗳️".to_string())) + .custom_id(format!("poll:{}:{}", id, age)) + }) + }) + }) + }) + .await?; + Ok(()) } diff --git a/src/db/model.rs b/src/db/model.rs index 4e5e4d3..3438e0c 100644 --- a/src/db/model.rs +++ b/src/db/model.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use bson::Document; -use poise::serenity_prelude::{PermissionOverwrite}; +use poise::serenity_prelude::PermissionOverwrite; use serde::{Deserialize, Serialize}; use serde_with_macros::skip_serializing_none; @@ -23,6 +23,21 @@ pub struct LockedChannel { pub overwrites: Option>, } +#[skip_serializing_none] +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct Poll { + pub author: Option, + pub image_url: Option, + pub votes: Option, +} + +#[skip_serializing_none] +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct PollAuthor { + pub name: Option, + pub id: Option, +} + impl From for Document { fn from(muted: Muted) -> Self { to_document(&muted) diff --git a/src/events/interaction.rs b/src/events/interaction.rs new file mode 100644 index 0000000..7e1b588 --- /dev/null +++ b/src/events/interaction.rs @@ -0,0 +1,53 @@ +use chrono::{Duration, Utc}; +use poise::serenity_prelude::{ + ComponentType, + MessageComponentInteraction, + MessageComponentInteractionData, +}; + +use super::*; +use crate::utils; +pub async fn interaction_create( + ctx: &serenity::Context, + interaction: &serenity::Interaction, +) -> Result<(), crate::serenity::SerenityError> { + if let serenity::Interaction::MessageComponent(MessageComponentInteraction { + data: + MessageComponentInteractionData { + component_type: ComponentType::Button, + custom_id, + .. + }, + .. + }) = interaction + { + if custom_id.starts_with("poll") { + handle_poll(ctx, interaction, custom_id).await? + } + } + + Ok(()) +} + +pub async fn handle_poll( + ctx: &serenity::Context, + interaction: &serenity::Interaction, + custom_id: &str, +) -> Result<(), crate::serenity::SerenityError> { + fn parse(str: &str) -> T + where + ::Err: std::fmt::Debug, + T: std::str::FromStr, + { + str.parse::().unwrap() + } + + let poll: Vec<_> = custom_id.split(':').collect::>(); + + let poll_id = parse::(poll[1]); + let min_age = parse::(poll[2]); + + let min_join_date = serenity::Timestamp::from(Utc::now() - Duration::days(min_age)); + + utils::poll::handle_poll(ctx, interaction, poll_id, min_join_date).await +} diff --git a/src/events/mod.rs b/src/events/mod.rs index 461661e..4884546 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -1,11 +1,13 @@ use std::sync::Arc; use poise::serenity_prelude::{self as serenity, Mutex, RwLock, ShardManager, UserId}; +use tracing::log::error; use crate::{Data, Error}; mod guild_member_addition; mod guild_member_update; +mod interaction; mod message_create; mod ready; mod thread_create; @@ -46,10 +48,21 @@ impl Handler { // Manually dispatch events from serenity to poise #[serenity::async_trait] impl serenity::EventHandler for Handler>> { - async fn ready(&self, ctx: serenity::Context, ready: serenity::Ready) { - *self.bot_id.write().await = Some(ready.user.id); + async fn guild_member_addition( + &self, + ctx: serenity::Context, + mut new_member: serenity::Member, + ) { + guild_member_addition::guild_member_addition(&ctx, &mut new_member).await; + } - ready::load_muted_members(&ctx, &ready).await; + async fn guild_member_update( + &self, + ctx: serenity::Context, + old_if_available: Option, + new: serenity::Member, + ) { + guild_member_update::guild_member_update(&ctx, &old_if_available, &new).await; } async fn message(&self, ctx: serenity::Context, new_message: serenity::Message) { @@ -61,13 +74,6 @@ impl serenity::EventHandler for Handler>> { .await; } - async fn interaction_create(&self, ctx: serenity::Context, interaction: serenity::Interaction) { - self.dispatch_poise_event(&ctx, &poise::Event::InteractionCreate { - interaction, - }) - .await; - } - async fn message_update( &self, ctx: serenity::Context, @@ -83,20 +89,24 @@ impl serenity::EventHandler for Handler>> { .await; } - async fn thread_create(&self, ctx: serenity::Context, thread: serenity::GuildChannel) { - thread_create::thread_create(&ctx, &thread).await; + async fn ready(&self, ctx: serenity::Context, ready: serenity::Ready) { + *self.bot_id.write().await = Some(ready.user.id); + + ready::load_muted_members(&ctx, &ready).await; } - async fn guild_member_addition(&self, ctx: serenity::Context, mut new_member: serenity::Member) { - guild_member_addition::guild_member_addition(&ctx, &mut new_member).await; + async fn interaction_create(&self, ctx: serenity::Context, interaction: serenity::Interaction) { + if let Err(e) = interaction::interaction_create(&ctx, &interaction).await { + error!("Failed to handle interaction: {:?}.", e); + } + + self.dispatch_poise_event(&ctx, &poise::Event::InteractionCreate { + interaction, + }) + .await; } - async fn guild_member_update( - &self, - ctx: serenity::Context, - old_if_available: Option, - new: serenity::Member, - ) { - guild_member_update::guild_member_update(&ctx, &old_if_available, &new).await; + async fn thread_create(&self, ctx: serenity::Context, thread: serenity::GuildChannel) { + thread_create::thread_create(&ctx, &thread).await; } } diff --git a/src/main.rs b/src/main.rs index 54c52b2..f566454 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::env; use std::sync::Arc; +use api::model::api::Api; use commands::{configuration, misc, moderation}; use db::database::Database; use events::Handler; @@ -12,6 +13,7 @@ use utils::bot::load_configuration; use crate::model::application::Configuration; +mod api; mod commands; mod db; mod events; @@ -30,6 +32,7 @@ pub struct Data { configuration: Configuration, database: Arc, pending_unmutes: HashMap>>, + api: Api, } #[tokio::main] @@ -53,6 +56,7 @@ async fn main() { moderation::lock(), moderation::unlock(), misc::reply(), + misc::poll(), ]; poise::set_qualified_names(&mut commands); @@ -79,6 +83,13 @@ async fn main() { .unwrap(), ), pending_unmutes: HashMap::new(), + api: Api { + server: env::var("API_SERVER").expect("API_SERVER environment variable not set"), + client_id: env::var("API_CLIENT_ID") + .expect("API_CLIENT_ID environment variable not set"), + client_secret: env::var("API_CLIENT_SECRET") + .expect("API_CLIENT_SECRET environment variable not set"), + }, })); let handler = Arc::new(Handler::new( diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 69f45fa..ea197f7 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,8 +1,9 @@ use poise::serenity_prelude::{self as serenity, Member, RoleId}; +pub mod autorespond; pub mod bot; pub mod decancer; pub mod embed; +pub mod media_channel; pub mod moderation; -pub mod autorespond; -pub mod media_channel; \ No newline at end of file +pub mod poll; diff --git a/src/utils/moderation.rs b/src/utils/moderation.rs index dc37fef..14fb35c 100644 --- a/src/utils/moderation.rs +++ b/src/utils/moderation.rs @@ -43,7 +43,6 @@ pub async fn mute_on_join(ctx: &serenity::Context, new_member: &mut serenity::Me ) .await { - if let Ok(found) = cursor.advance().await { if found { debug!("Muted member {} rejoined the server", new_member.user.tag()); diff --git a/src/utils/poll.rs b/src/utils/poll.rs new file mode 100644 index 0000000..4d71325 --- /dev/null +++ b/src/utils/poll.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use poise::{CacheHttp, ReactionType, Timestamp}; +use tokio::join; +use tracing::log::{info, trace}; + +use super::bot::get_data_lock; +use super::*; + +pub async fn handle_poll( + ctx: &serenity::Context, + interaction: &serenity::Interaction, + poll_id: u64, + min_join_date: Timestamp, +) -> Result<(), crate::serenity::SerenityError> { + trace!("Handling poll: {}.", poll_id); + + let data = get_data_lock(ctx).await; + let data = data.read().await; + + let component = &interaction.clone().message_component().unwrap(); + + let member = component.member.as_ref().unwrap(); + + let auth_token = ""; // get via api + + component + .create_interaction_response(&ctx.http, |r| { + r.interaction_response_data(|m| { + let allowed = member.joined_at.unwrap() <= min_join_date; + if allowed { + // TODO: get the url from the api server + + m.components(|c| { + c.create_action_row(|r| { + r.create_button(|b| { + b.label("Vote") + .emoji(ReactionType::Unicode("🗳️".to_string())) + .style(ButtonStyle::Link) + .url("https://revanced.app") + }) + }) + }) + } else { + m + } + .ephemeral(true) + .embed(|e| { + if allowed { + e.title("Cast your vote") + .description("You can now vote on the poll.") + } else { + info!("Member {} failed to vote.", member.display_name()); + e.title("You can not vote") + .description("You are not eligible to vote on this poll.") + } + .color(data.configuration.general.embed_color) + .thumbnail(member.user.face()) + }) + }) + }) + .await?; + + Ok(()) +} From 50731568c0e15178a24a2e287f29a32a0761514b Mon Sep 17 00:00:00 2001 From: Ax333l Date: Mon, 12 Dec 2022 21:13:38 +0100 Subject: [PATCH 3/3] feat: poll api client implementation (#43) --- src/api/client.rs | 66 ++++++++++++++++++++----------------------- src/api/mod.rs | 2 +- src/api/model/api.rs | 6 ---- src/api/model/auth.rs | 5 ++-- src/api/model/mod.rs | 1 - src/api/routing.rs | 41 +++++++++++++-------------- src/main.rs | 17 +++++------ src/utils/poll.rs | 37 ++++++++++++++++-------- 8 files changed, 88 insertions(+), 87 deletions(-) delete mode 100644 src/api/model/api.rs diff --git a/src/api/client.rs b/src/api/client.rs index 75133c7..c3434f5 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -1,68 +1,62 @@ -use poise::serenity_prelude::JsonMap; use reqwest::header::HeaderMap; use reqwest::Client; use serde::de::DeserializeOwned; -use serde_json::to_vec; -use super::model::api::Api; use super::model::auth::Authentication; -pub struct ApiClient<'a> { +use super::routing::Endpoint; + +pub struct Api { pub client: Client, - api: &'a Api, + pub server: reqwest::Url, + pub client_id: String, + pub client_secret: String, } -struct Request<'a> { +struct RequestInfo<'a> { headers: Option, - body: Option<&'a [u8]>, - route: super::routing::RouteInfo, + route: Endpoint<'a>, } -impl ApiClient<'_> { - pub fn new(api: &Api) -> Self { +impl Api { + pub fn new(server: reqwest::Url, client_id: String, client_secret: String) -> Self { let client = Client::builder() .build() .expect("Cannot build reqwest::Client"); - ApiClient { + Api { client, - api, + server, + client_id, + client_secret, } } - async fn fire(&self, request: &Request<'_>) -> Result { + async fn fire(&self, request_info: &RequestInfo<'_>) -> Result { let client = &self.client; + let mut req = request_info.route.to_request(&self.server); - Ok(client - .execute(client.request(request.route, url).build()?) - .await? - .json::() - .await - .map_err(From::from) - .unwrap()) + if let Some(headers) = &request_info.headers { + *req.headers_mut() = headers.clone(); + } + + client.execute(req).await?.json::().await } pub async fn authenticate( &self, discord_id_hash: &str, ) -> Result { - let api = &self.api; - - let body = &JsonMap::new(); - for (k, v) in [ - ("id", api.client_id), - ("secret", api.client_secret), - ("discord_id_hash", discord_id_hash.to_string()), - ] { - body.insert(k.to_string(), v) - } - - Ok(self - .fire(&Request { + let route = Endpoint::Authenticate { + id: &self.client_id, + secret: &self.client_secret, + discord_id_hash, + }; + self + .fire(&RequestInfo { headers: None, - body: Some(&to_vec(body).unwrap()), - route: RouteInfo::Authenticate, + route, }) - .await?) + .await } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 4a7c85e..503ee09 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,3 @@ pub mod client; -mod model; +pub mod model; mod routing; diff --git a/src/api/model/api.rs b/src/api/model/api.rs deleted file mode 100644 index a2b80ba..0000000 --- a/src/api/model/api.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[derive(Clone)] -pub struct Api { - pub server: String, - pub client_id: String, - pub client_secret: String, -} diff --git a/src/api/model/auth.rs b/src/api/model/auth.rs index 837c63e..776b0a7 100644 --- a/src/api/model/auth.rs +++ b/src/api/model/auth.rs @@ -1,5 +1,6 @@ use serde::Deserialize; + #[derive(Deserialize)] -pub struct Authentication<'a> { - pub token: &'a str, +pub struct Authentication { + pub access_token: String, } diff --git a/src/api/model/mod.rs b/src/api/model/mod.rs index 1f85274..0e4a05d 100644 --- a/src/api/model/mod.rs +++ b/src/api/model/mod.rs @@ -1,2 +1 @@ -pub mod api; pub mod auth; diff --git a/src/api/routing.rs b/src/api/routing.rs index 4d5e2d6..27f1803 100644 --- a/src/api/routing.rs +++ b/src/api/routing.rs @@ -1,29 +1,28 @@ -use std::borrow::Cow; +use reqwest::{Body, Method, Request}; +use serde::{Deserialize, Serialize}; -use reqwest::Method; - -pub enum RouteInfo { - Authenticate, +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +pub enum Endpoint<'a> { + Authenticate { + id: &'a str, + secret: &'a str, + discord_id_hash: &'a str, + }, } -pub enum Route { - Authenticate, +macro_rules! route { + ($self:ident, $server:ident, $endpoint:literal, $method:ident) => {{ + let mut req = Request::new(Method::$method, $server.join($endpoint).unwrap()); + *req.body_mut() = Some(Body::from(serde_json::to_vec($self).unwrap())); + req + }}; } -impl RouteInfo { - pub fn deconstruct(&self) -> (Method, Route, Cow<'_, str>) { - match *self { - RouteInfo::Authenticate => ( - Method::POST, - Route::Authenticate, - Cow::from(Route::authenticate()), - ), +impl Endpoint<'_> { + pub fn to_request(&self, server: &reqwest::Url) -> Request { + match self { + Self::Authenticate { .. } => route!(self, server, "/auth/", POST), } } } - -impl Route { - pub fn authenticate() -> &'static str { - "/auth" - } -} diff --git a/src/main.rs b/src/main.rs index f566454..5a50e91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::env; use std::sync::Arc; -use api::model::api::Api; +use api::client::Api; use commands::{configuration, misc, moderation}; use db::database::Database; use events::Handler; @@ -83,13 +83,14 @@ async fn main() { .unwrap(), ), pending_unmutes: HashMap::new(), - api: Api { - server: env::var("API_SERVER").expect("API_SERVER environment variable not set"), - client_id: env::var("API_CLIENT_ID") - .expect("API_CLIENT_ID environment variable not set"), - client_secret: env::var("API_CLIENT_SECRET") - .expect("API_CLIENT_SECRET environment variable not set"), - }, + api: Api::new( + reqwest::Url::parse( + &env::var("API_SERVER").expect("API_SERVER environment variable not set"), + ) + .expect("Invalid API_SERVER"), + env::var("API_CLIENT_ID").expect("API_CLIENT_ID environment variable not set"), + env::var("API_CLIENT_SECRET").expect("API_CLIENT_SECRET environment variable not set"), + ), })); let handler = Arc::new(Handler::new( diff --git a/src/utils/poll.rs b/src/utils/poll.rs index 4d71325..fe1ab99 100644 --- a/src/utils/poll.rs +++ b/src/utils/poll.rs @@ -1,8 +1,6 @@ -use std::collections::HashMap; +use poise::serenity_prelude::{ButtonStyle, ReactionType, Timestamp}; -use poise::{CacheHttp, ReactionType, Timestamp}; -use tokio::join; -use tracing::log::{info, trace}; +use tracing::log::{error, info, trace}; use super::bot::get_data_lock; use super::*; @@ -22,22 +20,34 @@ pub async fn handle_poll( let member = component.member.as_ref().unwrap(); - let auth_token = ""; // get via api + let eligible = member.joined_at.unwrap() <= min_join_date; + let auth_token = if eligible { + let result = data + .api + .authenticate(&member.user.id.to_string()) + .await + .map(|auth| auth.access_token); + + if let Err(ref e) = result { + error!("API Request error: {}", e) + } + result.ok() + } else { + None + }; component .create_interaction_response(&ctx.http, |r| { r.interaction_response_data(|m| { - let allowed = member.joined_at.unwrap() <= min_join_date; - if allowed { - // TODO: get the url from the api server - + if let Some(token) = auth_token.as_deref() { + let url = format!("https://revanced.app/polling#{}", token); m.components(|c| { c.create_action_row(|r| { r.create_button(|b| { b.label("Vote") .emoji(ReactionType::Unicode("🗳️".to_string())) .style(ButtonStyle::Link) - .url("https://revanced.app") + .url(&url) }) }) }) @@ -46,13 +56,16 @@ pub async fn handle_poll( } .ephemeral(true) .embed(|e| { - if allowed { + if auth_token.is_some() { e.title("Cast your vote") .description("You can now vote on the poll.") - } else { + } else if !eligible { info!("Member {} failed to vote.", member.display_name()); e.title("You can not vote") .description("You are not eligible to vote on this poll.") + } else { + e.title("Error") + .description("An error has occured. Please try again later.") } .color(data.configuration.general.embed_color) .thumbnail(member.user.face())