mirror of
https://github.com/revanced/revanced-discord-bot.git
synced 2025-04-29 22:14:28 +02:00
feat: Remove poll command
This commit is contained in:
parent
b9b7eee6a3
commit
dc0b760714
10
.env.example
10
.env.example
@ -2,13 +2,3 @@
|
|||||||
DISCORD_AUTHORIZATION_TOKEN=
|
DISCORD_AUTHORIZATION_TOKEN=
|
||||||
# The connection string to the MongoDB database
|
# The connection string to the MongoDB database
|
||||||
MONGODB_URI=''
|
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=''
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
pub mod client;
|
|
||||||
pub mod model;
|
|
||||||
mod routing;
|
|
@ -1,6 +0,0 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct Authentication {
|
|
||||||
pub access_token: String,
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
pub mod auth;
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,5 @@
|
|||||||
use poise::serenity_prelude::{CreateActionRow, CreateAllowedMentions, CreateButton, ReactionType};
|
|
||||||
use poise::{CreateReply, ReplyHandle};
|
use poise::{CreateReply, ReplyHandle};
|
||||||
|
|
||||||
use crate::utils::message::clone_message;
|
|
||||||
use crate::{Context, Error};
|
use crate::{Context, Error};
|
||||||
|
|
||||||
/// Make the Discord bot sentient.
|
/// Make the Discord bot sentient.
|
||||||
@ -50,52 +48,3 @@ pub async fn reply(
|
|||||||
send_ephermal(&ctx, &format!("Response: {message}")).await?;
|
send_ephermal(&ctx, &format!("Response: {message}")).await?;
|
||||||
Ok(())
|
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(())
|
|
||||||
}
|
|
||||||
|
@ -23,21 +23,6 @@ pub struct LockedChannel {
|
|||||||
pub overwrites: Option<Vec<PermissionOverwrite>>,
|
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 {
|
impl From<Muted> for Document {
|
||||||
fn from(muted: Muted) -> Self {
|
fn from(muted: Muted) -> Self {
|
||||||
to_document(&muted)
|
to_document(&muted)
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -6,7 +6,6 @@ use tokio::sync::RwLock;
|
|||||||
|
|
||||||
mod guild_member_addition;
|
mod guild_member_addition;
|
||||||
mod guild_member_update;
|
mod guild_member_update;
|
||||||
mod interaction;
|
|
||||||
mod message_create;
|
mod message_create;
|
||||||
mod ready;
|
mod ready;
|
||||||
|
|
||||||
@ -28,9 +27,6 @@ pub async fn event_handler(
|
|||||||
new,
|
new,
|
||||||
..
|
..
|
||||||
} => guild_member_update::guild_member_update(ctx, old_if_available, new).await,
|
} => guild_member_update::guild_member_update(ctx, old_if_available, new).await,
|
||||||
serenity::FullEvent::InteractionCreate { interaction } => {
|
|
||||||
interaction::interaction_create(ctx, interaction, data).await?
|
|
||||||
},
|
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -39,7 +39,7 @@ pub async fn load_muted_members(ctx: &serenity::Context, data: &Arc<RwLock<Data>
|
|||||||
guild_id.into(),
|
guild_id.into(),
|
||||||
user_id.into(),
|
user_id.into(),
|
||||||
mute_role_id,
|
mute_role_id,
|
||||||
amount_left as u64, // i64 as u64 is handled properly here
|
amount_left as u64,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
11
src/main.rs
11
src/main.rs
@ -2,7 +2,6 @@ use std::collections::HashMap;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use api::client::Api;
|
|
||||||
use commands::{configuration, misc, moderation};
|
use commands::{configuration, misc, moderation};
|
||||||
use db::database::Database;
|
use db::database::Database;
|
||||||
use events::event_handler;
|
use events::event_handler;
|
||||||
@ -15,7 +14,6 @@ use utils::bot::load_configuration;
|
|||||||
|
|
||||||
use crate::model::application::Configuration;
|
use crate::model::application::Configuration;
|
||||||
|
|
||||||
mod api;
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod db;
|
mod db;
|
||||||
mod events;
|
mod events;
|
||||||
@ -35,8 +33,6 @@ pub struct Data {
|
|||||||
configuration: Configuration,
|
configuration: Configuration,
|
||||||
database: Arc<Database>,
|
database: Arc<Database>,
|
||||||
pending_unmutes: HashMap<u64, JoinHandle<Result<(), Error>>>,
|
pending_unmutes: HashMap<u64, JoinHandle<Result<(), Error>>>,
|
||||||
poll_secret: String,
|
|
||||||
api: Api,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -54,7 +50,6 @@ async fn main() {
|
|||||||
moderation::ban(),
|
moderation::ban(),
|
||||||
moderation::unban(),
|
moderation::unban(),
|
||||||
misc::reply(),
|
misc::reply(),
|
||||||
misc::poll(),
|
|
||||||
];
|
];
|
||||||
poise::set_qualified_names(&mut commands);
|
poise::set_qualified_names(&mut commands);
|
||||||
|
|
||||||
@ -92,12 +87,6 @@ async fn main() {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
),
|
),
|
||||||
pending_unmutes: HashMap::new(),
|
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(),
|
|
||||||
),
|
|
||||||
})))
|
})))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -5,10 +5,8 @@ pub mod bot;
|
|||||||
pub mod code_embed;
|
pub mod code_embed;
|
||||||
pub mod decancer;
|
pub mod decancer;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod message;
|
|
||||||
pub mod message_response;
|
pub mod message_response;
|
||||||
pub mod moderation;
|
pub mod moderation;
|
||||||
pub mod poll;
|
|
||||||
|
|
||||||
pub fn parse_duration(duration: String) -> Result<Duration, parse_duration::parse::Error> {
|
pub fn parse_duration(duration: String) -> Result<Duration, parse_duration::parse::Error> {
|
||||||
let d = parse_duration::parse(&duration)?;
|
let d = parse_duration::parse(&duration)?;
|
||||||
|
@ -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(())
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user