feat: Remove poll command

This commit is contained in:
oSumAtrIX 2024-01-22 00:24:14 +01:00
parent b9b7eee6a3
commit dc0b760714
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
15 changed files with 1 additions and 399 deletions

View File

@ -2,13 +2,3 @@
DISCORD_AUTHORIZATION_TOKEN=
# The connection string to the MongoDB database
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=''
# The poll secret used for the poll command
POLL_SECRET=''

View File

@ -1,68 +0,0 @@
use reqwest::header::HeaderMap;
use reqwest::Client;
use serde::de::DeserializeOwned;
use super::model::auth::Authentication;
use super::routing::Endpoint;
pub struct Api {
pub client: Client,
pub server: reqwest::Url,
pub client_id: String,
pub client_secret: String,
}
struct RequestInfo<'a> {
headers: Option<HeaderMap>,
route: Endpoint<'a>,
}
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");
Api {
client,
server,
client_id,
client_secret,
}
}
async fn fire<T: DeserializeOwned>(
&self,
request_info: &RequestInfo<'_>,
) -> Result<T, reqwest::Error> {
let client = &self.client;
let mut req = request_info.route.to_request(&self.server);
if let Some(headers) = &request_info.headers {
*req.headers_mut() = headers.clone();
}
client
.execute(req)
.await?
.error_for_status()?
.json::<T>()
.await
}
pub async fn authenticate(
&self,
discord_id_hash: &str,
) -> Result<Authentication, reqwest::Error> {
let route = Endpoint::Authenticate {
id: &self.client_id,
secret: &self.client_secret,
discord_id_hash,
};
self.fire(&RequestInfo {
headers: None,
route,
})
.await
}
}

View File

@ -1,3 +0,0 @@
pub mod client;
pub mod model;
mod routing;

View File

@ -1,6 +0,0 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Authentication {
pub access_token: String,
}

View File

@ -1 +0,0 @@
pub mod auth;

View File

@ -1,30 +0,0 @@
use reqwest::{Body, Method, Request};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum Endpoint<'a> {
Authenticate {
id: &'a str,
secret: &'a str,
discord_id_hash: &'a str,
},
}
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 Endpoint<'_> {
pub fn to_request(&self, server: &reqwest::Url) -> Request {
match self {
Self::Authenticate {
..
} => route!(self, server, "/auth/", POST),
}
}
}

View File

@ -1,7 +1,5 @@
use poise::serenity_prelude::{CreateActionRow, CreateAllowedMentions, CreateButton, ReactionType};
use poise::{CreateReply, ReplyHandle};
use crate::utils::message::clone_message;
use crate::{Context, Error};
/// Make the Discord bot sentient.
@ -50,52 +48,3 @@ pub async fn reply(
send_ephermal(&ctx, &format!("Response: {message}")).await?;
Ok(())
}
/// Start a poll.
#[poise::command(slash_command)]
pub async fn poll(
ctx: Context<'_>,
#[description = "The id of the poll"] id: u64, /* This is currently unused in the API, leaving as a placeholder in case it is required. */
#[description = "A link to a message to clone"] message_link: String,
#[description = "The minumum server age in days to allow members to poll"] age: u16,
#[description = "Enable pings"] ping: bool,
) -> Result<(), Error> {
let get_id =
|segments: &mut std::str::Split<char>| segments.next_back().unwrap().parse::<u64>();
let url = reqwest::Url::parse(&message_link)?;
let mut segments = url.path_segments().ok_or("Invalid Discord message link")?;
if segments.clone().count() != 4 {
return Err("Invalid Discord message link".into());
}
let message_id = get_id(&mut segments)?;
let channel_id = get_id(&mut segments)?;
let message = ctx
.serenity_context()
.http
.get_message(channel_id.into(), message_id.into())
.await?;
let message = clone_message(&message).components(vec![CreateActionRow::Buttons(vec![
CreateButton::new(format!("poll:{id}:{age}"))
.label("Vote")
.emoji(ReactionType::Unicode("🗳️".to_string())),
])]);
ctx.send(if ping {
message.allowed_mentions(
CreateAllowedMentions::default()
.all_roles(true)
.all_users(true)
.everyone(true),
)
} else {
message
})
.await?;
Ok(())
}

View File

@ -23,21 +23,6 @@ pub struct LockedChannel {
pub overwrites: Option<Vec<PermissionOverwrite>>,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Poll {
pub author: Option<PollAuthor>,
pub image_url: Option<String>,
pub votes: Option<u16>,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct PollAuthor {
pub name: Option<String>,
pub id: Option<u64>,
}
impl From<Muted> for Document {
fn from(muted: Muted) -> Self {
to_document(&muted)

View File

@ -1,54 +0,0 @@
use chrono::{Duration, Utc};
use poise::serenity_prelude::{
ComponentInteraction, ComponentInteractionData, ComponentInteractionDataKind,
};
use super::*;
use crate::{utils, BotData};
pub async fn interaction_create(
ctx: &serenity::Context,
interaction: &serenity::Interaction,
data: &BotData,
) -> Result<(), serenity::prelude::SerenityError> {
if let serenity::Interaction::Component(ComponentInteraction {
data:
ComponentInteractionData {
kind: ComponentInteractionDataKind::Button,
custom_id,
..
},
..
}) = interaction
{
if custom_id.starts_with("poll") {
handle_poll(ctx, interaction, custom_id, data).await?
}
}
Ok(())
}
pub async fn handle_poll(
ctx: &serenity::Context,
interaction: &serenity::Interaction,
custom_id: &str,
data: &BotData,
) -> Result<(), serenity::prelude::SerenityError> {
fn parse<T>(str: &str) -> T
where
<T as std::str::FromStr>::Err: std::fmt::Debug,
T: std::str::FromStr,
{
str.parse::<T>().unwrap()
}
let poll: Vec<_> = custom_id.split(':').collect::<Vec<_>>();
let poll_id = parse::<u64>(poll[1]);
let min_age = parse::<i64>(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, data).await
}

View File

@ -6,7 +6,6 @@ use tokio::sync::RwLock;
mod guild_member_addition;
mod guild_member_update;
mod interaction;
mod message_create;
mod ready;
@ -28,9 +27,6 @@ pub async fn event_handler(
new,
..
} => guild_member_update::guild_member_update(ctx, old_if_available, new).await,
serenity::FullEvent::InteractionCreate { interaction } => {
interaction::interaction_create(ctx, interaction, data).await?
},
_ => {},
}
Ok(())

View File

@ -39,7 +39,7 @@ pub async fn load_muted_members(ctx: &serenity::Context, data: &Arc<RwLock<Data>
guild_id.into(),
user_id.into(),
mute_role_id,
amount_left as u64, // i64 as u64 is handled properly here
amount_left as u64,
),
);
}

View File

@ -2,7 +2,6 @@ use std::collections::HashMap;
use std::env;
use std::sync::Arc;
use api::client::Api;
use commands::{configuration, misc, moderation};
use db::database::Database;
use events::event_handler;
@ -15,7 +14,6 @@ use utils::bot::load_configuration;
use crate::model::application::Configuration;
mod api;
mod commands;
mod db;
mod events;
@ -35,8 +33,6 @@ pub struct Data {
configuration: Configuration,
database: Arc<Database>,
pending_unmutes: HashMap<u64, JoinHandle<Result<(), Error>>>,
poll_secret: String,
api: Api,
}
#[tokio::main]
@ -54,7 +50,6 @@ async fn main() {
moderation::ban(),
moderation::unban(),
misc::reply(),
misc::poll(),
];
poise::set_qualified_names(&mut commands);
@ -92,12 +87,6 @@ async fn main() {
.unwrap(),
),
pending_unmutes: HashMap::new(),
poll_secret: env::var("POLL_SECRET").unwrap(),
api: Api::new(
reqwest::Url::parse(&env::var("API_SERVER").unwrap()).unwrap(),
env::var("API_CLIENT_ID").unwrap(),
env::var("API_CLIENT_SECRET").unwrap(),
),
})))
})
})

View File

@ -1,52 +0,0 @@
use chrono::Utc;
use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, Message};
use poise::CreateReply;
pub fn clone_message(message: &Message) -> CreateReply {
let mut reply = CreateReply {
content: Some(message.content.clone()),
..Default::default()
};
if let Some(embed) = message.embeds.first() {
let mut new_embed = CreateEmbed::new();
if let Some(color) = embed.colour {
new_embed = new_embed.color(color);
}
new_embed = new_embed.timestamp(Utc::now());
if let Some(title) = &embed.title {
new_embed = new_embed.title(title);
}
if let Some(description) = &embed.description {
new_embed = new_embed.description(description);
}
if let Some(footer) = &embed.footer {
new_embed = new_embed.footer(CreateEmbedFooter::new(&footer.text));
}
if let Some(author) = &embed.author {
new_embed = new_embed.author(CreateEmbedAuthor::new(&author.name));
}
if let Some(image) = &embed.image {
new_embed = new_embed.image(image.url.as_str());
}
if let Some(thumbnail) = &embed.thumbnail {
new_embed = new_embed.thumbnail(thumbnail.url.as_str());
}
for field in &embed.fields {
new_embed = new_embed.field(field.name.as_str(), field.value.as_str(), field.inline);
}
reply = reply.embed(new_embed);
}
reply
}

View File

@ -5,10 +5,8 @@ pub mod bot;
pub mod code_embed;
pub mod decancer;
pub mod macros;
pub mod message;
pub mod message_response;
pub mod moderation;
pub mod poll;
pub fn parse_duration(duration: String) -> Result<Duration, parse_duration::parse::Error> {
let d = parse_duration::parse(&duration)?;

View File

@ -1,91 +0,0 @@
use base64::Engine;
use poise::serenity_prelude::{
CreateActionRow, CreateButton, CreateEmbed, CreateEmbedFooter,
CreateInteractionResponseMessage, ReactionType, Timestamp,
};
use reqwest::StatusCode;
use tracing::log::{error, trace};
use crate::{BotData};
use super::*;
pub async fn handle_poll(
ctx: &serenity::Context,
interaction: &serenity::Interaction,
poll_id: u64,
min_join_date: Timestamp,
data: &BotData,
) -> Result<(), serenity::prelude::SerenityError> {
trace!("Handling poll: {}.", poll_id);
let data = data.read().await;
let component = &interaction.clone().message_component().unwrap();
let member = component.member.as_ref().unwrap();
let eligible = member.joined_at.unwrap() <= min_join_date;
let result = if eligible {
match data
.api
.authenticate(&base64::engine::general_purpose::STANDARD_NO_PAD.encode(
hmac_sha256::HMAC::mac(member.user.id.to_string(), &data.poll_secret),
))
.await
{
Ok(auth) => Ok(auth.access_token),
Err(err) => match err.status() {
Some(StatusCode::PRECONDITION_FAILED) => Err("You can only vote once."),
_ => {
error!("API Request error: {:?}", err);
Err("API Request failed. Please try again later.")
},
},
}
} else {
Err("You are not eligible to vote on this poll because you joined this server too recently.")
};
let icon_url = component
.guild_id
.unwrap()
.to_guild_cached(&ctx.cache)
.unwrap()
.icon_url()
.unwrap();
component
.create_response(
&ctx.http,
serenity::CreateInteractionResponse::Message(if let Ok(token) = result.as_deref() {
let url = format!("https://revanced.app/poll#{token}");
CreateInteractionResponseMessage::new().components(vec![CreateActionRow::Buttons(
vec![CreateButton::new_link(url)
.label("Vote")
.emoji(ReactionType::Unicode("🗳️".to_string()))],
)])
} else {
CreateInteractionResponseMessage::new()
.ephemeral(true)
.embed(
match result {
Ok(_) => CreateEmbed::new()
.title("Cast your vote")
.description("You can now vote on the poll."),
Err(msg) => CreateEmbed::new().title("Error").description(msg),
}
.color(data.configuration.general.embed_color)
.thumbnail(&icon_url)
.footer(CreateEmbedFooter::new("ReVanced").icon_url(&icon_url)),
)
}),
)
.await?;
Ok(())
}