Wip commit

This commit is contained in:
jude
2024-01-06 19:48:17 +00:00
parent cce0de7c75
commit e4e9af2bb4
37 changed files with 1051 additions and 1366 deletions

View File

@ -7,10 +7,10 @@ edition = "2018"
[dependencies]
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tls", "secrets", "json"] }
rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tera"] }
serenity = { version = "0.11", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
serenity = { version = "0.12", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
oauth2 = "4"
log = "0.4"
reqwest = "0.11"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] }
chrono = "0.4"
@ -20,3 +20,4 @@ rand = "0.8"
base64 = "0.13"
csv = "1.2"
prometheus = "0.13.3"
secrecy = "0.8.0"

View File

@ -23,14 +23,13 @@ pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
use std::{collections::HashSet, env, iter::FromIterator};
use lazy_static::lazy_static;
use serenity::model::prelude::AttachmentType;
use serenity::builder::CreateAttachment;
lazy_static! {
pub static ref DEFAULT_AVATAR: AttachmentType<'static> = (
pub static ref DEFAULT_AVATAR: CreateAttachment = CreateAttachment::bytes(
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/webhook.jpg")) as &[u8],
"webhook.jpg",
)
.into();
);
pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(
env::var("PATREON_ROLE_ID")
.map(|var| var

View File

@ -35,8 +35,8 @@ type Database = MySql;
#[derive(Debug)]
enum Error {
SQLx(sqlx::Error),
Serenity(serenity::Error),
SQLx,
Serenity,
}
pub async fn initialize(
@ -169,11 +169,11 @@ pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<U
offline!(true);
if let Some(subscription_guild) = *CNC_GUILD {
let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await;
let guild_member = GuildId::new(subscription_guild).member(cache_http, user_id).await;
if let Ok(member) = guild_member {
for role in member.roles {
if SUBSCRIPTION_ROLES.contains(role.as_u64()) {
if SUBSCRIPTION_ROLES.contains(&role.get()) {
return true;
}
}
@ -191,9 +191,7 @@ pub async fn check_guild_subscription(
) -> bool {
offline!(true);
if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) {
let owner = guild.owner_id;
if let Some(owner) = cache_http.cache().unwrap().guild(guild_id).map(|guild| guild.owner_id) {
check_subscription(&cache_http, owner).await
} else {
false
@ -203,7 +201,7 @@ pub async fn check_guild_subscription(
pub async fn check_authorization(
cookies: &CookieJar<'_>,
ctx: &Context,
guild: u64,
guild_id: u64,
) -> Result<(), JsonValue> {
let user_id = cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten();
@ -217,38 +215,30 @@ pub async fn check_authorization(
return Ok(());
}
match GuildId(guild).to_guild_cached(ctx) {
Some(guild) => {
let member_res = guild.member(ctx, UserId(user_id)).await;
let guild_id = GuildId::new(guild_id);
match member_res {
match guild_id.member(ctx, UserId::new(user_id)).await {
Ok(member) => {
let permissions_res = member.permissions(ctx);
match permissions_res {
Err(_) => {
return Err(json!({"error": "User not in guild"}));
return Err(json!({"error": "Couldn't fetch permissions"}));
}
Ok(member) => {
let permissions_res = member.permissions(ctx);
match permissions_res {
Err(_) => {
return Err(json!({"error": "Couldn't fetch permissions"}));
}
Ok(permissions) => {
if !(permissions.manage_messages()
|| permissions.manage_guild()
|| permissions.administrator())
{
return Err(json!({"error": "Incorrect permissions"}));
}
}
Ok(permissions) => {
if !(permissions.manage_messages()
|| permissions.manage_guild()
|| permissions.administrator())
{
return Err(json!({"error": "Incorrect permissions"}));
}
}
}
}
None => {
return Err(json!({"error": "Bot not in guild"}));
Err(_) => {
return Err(json!({"error": "User not in guild"}));
}
}
}

View File

@ -32,13 +32,13 @@ pub async fn get_guild_channels(
}])));
check_authorization(cookies, ctx.inner(), id).await?;
match GuildId(id).to_guild_cached(ctx.inner()) {
match GuildId::new(id).to_guild_cached(ctx.inner()) {
Some(guild) => {
let mut channels = guild
.channels
.iter()
.filter_map(|(id, channel)| channel.to_owned().guild().map(|c| (id.to_owned(), c)))
.filter(|(_, channel)| channel.is_text_based())
.map(|(id, channel)| (id.to_owned(), channel.to_owned()))
.collect::<Vec<(ChannelId, GuildChannel)>>();
channels.sort_by(|(_, c1), (_, c2)| c1.position.cmp(&c2.position));

View File

@ -22,19 +22,22 @@ pub async fn get_guild_info(id: u64, cookies: &CookieJar<'_>, ctx: &State<Contex
offline!(Ok(json!({ "patreon": true, "name": "Guild" })));
check_authorization(cookies, ctx.inner(), id).await?;
match GuildId(id).to_guild_cached(ctx.inner()) {
Some(guild) => {
let member_res = GuildId(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap())
.member(&ctx.inner(), guild.owner_id)
match GuildId::new(id)
.to_guild_cached(ctx.inner())
.map(|guild| (guild.owner_id, guild.name.clone()))
{
Some((owner_id, name)) => {
let member_res = GuildId::new(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap())
.member(&ctx.inner(), owner_id)
.await;
let patreon = member_res.map_or(false, |member| {
member
.roles
.contains(&RoleId(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap()))
.contains(&RoleId::new(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap()))
});
Ok(json!({ "patreon": patreon, "name": guild.name }))
Ok(json!({ "patreon": patreon, "name": name }))
}
None => json_err!("Bot not in guild"),

View File

@ -38,8 +38,8 @@ pub async fn create_guild_reminder(
match create_reminder(
ctx.inner(),
&mut transaction,
GuildId(id),
UserId(user_id),
GuildId::new(id),
UserId::new(user_id),
reminder.into_inner(),
)
.await
@ -65,14 +65,14 @@ pub async fn get_reminders(
) -> JsonResult {
check_authorization(cookies, ctx.inner(), id).await?;
let channels_res = GuildId(id).channels(&ctx.inner()).await;
let channels_res = GuildId::new(id).channels(&ctx.inner()).await;
match channels_res {
Ok(channels) => {
let channels = channels
.keys()
.into_iter()
.map(|k| k.as_u64().to_string())
.map(|k| k.get().to_string())
.collect::<Vec<String>>()
.join(",");
@ -253,10 +253,12 @@ pub async fn edit_reminder(
}
if reminder.channel > 0 {
let channel = ChannelId(reminder.channel).to_channel_cached(&ctx.inner());
match channel {
Some(channel) => {
let channel_matches_guild = channel.guild().map_or(false, |c| c.guild_id.0 == id);
let channel_guild = ChannelId::new(reminder.channel)
.to_channel_cached(&ctx.cache)
.map(|channel| channel.guild_id);
match channel_guild {
Some(channel_guild) => {
let channel_matches_guild = channel_guild.get() == id;
if !channel_matches_guild {
warn!(
@ -269,7 +271,7 @@ pub async fn edit_reminder(
let channel = create_database_channel(
ctx.inner(),
ChannelId(reminder.channel),
ChannelId::new(reminder.channel),
&mut transaction,
)
.await;

View File

@ -39,7 +39,7 @@ pub async fn get_user_info(
if let Some(user_id) =
cookies.get_private("userid").map(|u| u.value().parse::<u64>().ok()).flatten()
{
let member_res = GuildId(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap())
let member_res = GuildId::new(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap())
.member(&ctx.inner(), user_id)
.await;
@ -58,7 +58,7 @@ pub async fn get_user_info(
patreon: member_res.map_or(false, |member| {
member
.roles
.contains(&RoleId(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap()))
.contains(&RoleId::new(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap()))
}),
timezone,
};

View File

@ -33,14 +33,14 @@ pub async fn export_reminders(
let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);
let channels_res = GuildId(id).channels(&ctx.inner()).await;
let channels_res = GuildId::new(id).channels(&ctx.inner()).await;
match channels_res {
Ok(channels) => {
let channels = channels
.keys()
.into_iter()
.map(|k| k.as_u64().to_string())
.map(|k| k.get().to_string())
.collect::<Vec<String>>()
.join(",");
@ -181,8 +181,8 @@ pub(crate) async fn import_reminders(
create_reminder(
ctx.inner(),
&mut transaction,
GuildId(id),
UserId(user_id),
GuildId::new(id),
UserId::new(user_id),
reminder,
)
.await?;
@ -289,7 +289,7 @@ pub async fn import_todos(
) -> JsonResult {
check_authorization(cookies, ctx.inner(), id).await?;
let channels_res = GuildId(id).channels(&ctx.inner()).await;
let channels_res = GuildId::new(id).channels(&ctx.inner()).await;
match channels_res {
Ok(channels) => match base64::decode(&body.body) {
@ -307,7 +307,7 @@ pub async fn import_todos(
match channel_id.parse::<u64>() {
Ok(channel_id) => {
if channels.contains_key(&ChannelId(channel_id)) {
if channels.contains_key(&ChannelId::new(channel_id)) {
query_params.push((record.value, Some(channel_id), id));
} else {
return json_err!(format!(

View File

@ -8,10 +8,12 @@ use rocket::{
response::Redirect,
serde::json::json,
};
use secrecy::ExposeSecret;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serenity::{
all::CacheHttp,
builder::CreateWebhook,
client::Context,
http::Http,
model::id::{ChannelId, GuildId, UserId},
};
use sqlx::types::Json;
@ -363,12 +365,12 @@ pub(crate) async fn create_reminder(
reminder: Reminder,
) -> JsonResult {
// check guild in db
match sqlx::query!("SELECT 1 as A FROM guilds WHERE guild = ?", guild_id.0)
match sqlx::query!("SELECT 1 as A FROM guilds WHERE guild = ?", guild_id.get())
.fetch_one(transaction.executor())
.await
{
Err(sqlx::Error::RowNotFound) => {
if sqlx::query!("INSERT INTO guilds (guild) VALUES (?)", guild_id.0)
if sqlx::query!("INSERT INTO guilds (guild) VALUES (?)", guild_id.get())
.execute(transaction.executor())
.await
.is_err()
@ -379,23 +381,26 @@ pub(crate) async fn create_reminder(
_ => {}
}
// validate channel
let channel = ChannelId(reminder.channel).to_channel_cached(&ctx);
let channel_exists = channel.is_some();
{
// validate channel
let channel = ChannelId::new(reminder.channel).to_channel_cached(&ctx.cache);
let channel_exists = channel.is_some();
let channel_matches_guild =
channel.map_or(false, |c| c.guild().map_or(false, |c| c.guild_id == guild_id));
let channel_matches_guild =
channel.map_or(false, |c| c.guild(&ctx.cache).map_or(false, |c| c.id == guild_id));
if !channel_matches_guild || !channel_exists {
warn!(
"Error in `create_reminder`: channel {} not found for guild {} (channel exists: {})",
reminder.channel, guild_id, channel_exists
);
if !channel_matches_guild || !channel_exists {
warn!(
"Error in `create_reminder`: channel {} not found for guild {} (channel exists: {})",
reminder.channel, guild_id, channel_exists
);
return Err(json!({"error": "Channel not found"}));
return Err(json!({"error": "Channel not found"}));
}
}
let channel = create_database_channel(&ctx, ChannelId(reminder.channel), transaction).await;
let channel =
create_database_channel(&ctx, ChannelId::new(reminder.channel), transaction).await;
if let Err(e) = channel {
warn!("`create_database_channel` returned an error code: {:?}", e);
@ -590,32 +595,38 @@ pub(crate) async fn create_reminder(
}
async fn create_database_channel(
ctx: impl AsRef<Http>,
ctx: impl CacheHttp,
channel: ChannelId,
transaction: &mut Transaction<'_>,
) -> Result<u32, crate::Error> {
let row =
sqlx::query!("SELECT webhook_token, webhook_id FROM channels WHERE channel = ?", channel.0)
.fetch_one(transaction.executor())
.await;
let row = sqlx::query!(
"SELECT webhook_token, webhook_id FROM channels WHERE channel = ?",
channel.get()
)
.fetch_one(transaction.executor())
.await;
match row {
Ok(row) => {
if row.webhook_token.is_none() || row.webhook_id.is_none() {
let webhook = channel
.create_webhook_with_avatar(&ctx, "Reminder", DEFAULT_AVATAR.clone())
.create_webhook(&ctx, CreateWebhook::new("Reminder").avatar(&*DEFAULT_AVATAR))
.await
.map_err(|e| Error::Serenity(e))?;
.map_err(|_| Error::Serenity)?;
let token = webhook.token.unwrap();
sqlx::query!(
"UPDATE channels SET webhook_id = ?, webhook_token = ? WHERE channel = ?",
webhook.id.0,
webhook.token,
channel.0
"
UPDATE channels SET webhook_id = ?, webhook_token = ? WHERE channel = ?
",
webhook.id.get(),
token.expose_secret(),
channel.get()
)
.execute(transaction.executor())
.await
.map_err(|e| Error::SQLx(e))?;
.map_err(|_| Error::SQLx)?;
}
Ok(())
@ -624,35 +635,39 @@ async fn create_database_channel(
Err(sqlx::Error::RowNotFound) => {
// create webhook
let webhook = channel
.create_webhook_with_avatar(&ctx, "Reminder", DEFAULT_AVATAR.clone())
.create_webhook(&ctx, CreateWebhook::new("Reminder").avatar(&*DEFAULT_AVATAR))
.await
.map_err(|e| Error::Serenity(e))?;
.map_err(|_| Error::Serenity)?;
let token = webhook.token.unwrap();
// create database entry
sqlx::query!(
"INSERT INTO channels (
"
INSERT INTO channels (
webhook_id,
webhook_token,
channel
) VALUES (?, ?, ?)",
webhook.id.0,
webhook.token,
channel.0
) VALUES (?, ?, ?)
",
webhook.id.get(),
token.expose_secret(),
channel.get()
)
.execute(transaction.executor())
.await
.map_err(|e| Error::SQLx(e))?;
.map_err(|_| Error::SQLx)?;
Ok(())
}
Err(e) => Err(Error::SQLx(e)),
Err(_) => Err(Error::SQLx),
}?;
let row = sqlx::query!("SELECT id FROM channels WHERE channel = ?", channel.0)
let row = sqlx::query!("SELECT id FROM channels WHERE channel = ?", channel.get())
.fetch_one(transaction.executor())
.await
.map_err(|e| Error::SQLx(e))?;
.map_err(|_| Error::SQLx)?;
Ok(row.id)
}

View File

@ -102,10 +102,9 @@ pub async fn discord_callback(
match user_res {
Ok(user) => {
let user_name = format!("{}#{}", user.name, user.discriminator);
let user_id = user.id.as_u64().to_string();
let user_id = user.id.get().to_string();
cookies.add_private(Cookie::new("username", user_name));
cookies.add_private(Cookie::new("username", user.name));
cookies.add_private(Cookie::new("userid", user_id));
Ok(Redirect::to(uri!(super::return_to_same_site("dashboard"))))