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

202
Cargo.lock generated
View File

@ -137,22 +137,6 @@ dependencies = [
"syn 2.0.42", "syn 2.0.42",
] ]
[[package]]
name = "async-tungstenite"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb"
dependencies = [
"futures-io",
"futures-util",
"log",
"pin-project-lite",
"tokio",
"tokio-rustls 0.23.4",
"tungstenite 0.17.3",
"webpki-roots 0.22.6",
]
[[package]] [[package]]
name = "atoi" name = "atoi"
version = "2.0.0" version = "2.0.0"
@ -1877,15 +1861,6 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "ordered-float"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -2102,7 +2077,7 @@ dependencies = [
"parking_lot", "parking_lot",
"poise_macros", "poise_macros",
"regex", "regex",
"serenity 0.12.0", "serenity",
"tokio", "tokio",
"tracing", "tracing",
] ]
@ -2142,7 +2117,7 @@ dependencies = [
"num-integer", "num-integer",
"regex", "regex",
"serde", "serde",
"serenity 0.11.7", "serenity",
"sqlx", "sqlx",
"tokio", "tokio",
] ]
@ -2346,6 +2321,7 @@ dependencies = [
"reminder_web", "reminder_web",
"reqwest", "reqwest",
"rmp-serde", "rmp-serde",
"secrecy",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
@ -2369,8 +2345,9 @@ dependencies = [
"reqwest", "reqwest",
"rocket", "rocket",
"rocket_dyn_templates", "rocket_dyn_templates",
"secrecy",
"serde", "serde",
"serenity 0.11.7", "serenity",
"sqlx", "sqlx",
] ]
@ -2416,25 +2393,10 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams", "wasm-streams",
"web-sys", "web-sys",
"webpki-roots 0.25.3", "webpki-roots",
"winreg", "winreg",
] ]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin 0.5.2",
"untrusted 0.7.1",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.7" version = "0.17.7"
@ -2445,7 +2407,7 @@ dependencies = [
"getrandom", "getrandom",
"libc", "libc",
"spin 0.9.8", "spin 0.9.8",
"untrusted 0.9.0", "untrusted",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -2604,18 +2566,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustls"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
dependencies = [
"log",
"ring 0.16.20",
"sct",
"webpki",
]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.21.10" version = "0.21.10"
@ -2623,7 +2573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
dependencies = [ dependencies = [
"log", "log",
"ring 0.17.7", "ring",
"rustls-webpki 0.101.7", "rustls-webpki 0.101.7",
"sct", "sct",
] ]
@ -2635,7 +2585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe6b63262c9fcac8659abfaa96cac103d28166d3ff3eaf8f412e19f3ae9e5a48" checksum = "fe6b63262c9fcac8659abfaa96cac103d28166d3ff3eaf8f412e19f3ae9e5a48"
dependencies = [ dependencies = [
"log", "log",
"ring 0.17.7", "ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki 0.102.0", "rustls-webpki 0.102.0",
"subtle", "subtle",
@ -2673,8 +2623,8 @@ version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [ dependencies = [
"ring 0.17.7", "ring",
"untrusted 0.9.0", "untrusted",
] ]
[[package]] [[package]]
@ -2683,9 +2633,9 @@ version = "0.102.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89" checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89"
dependencies = [ dependencies = [
"ring 0.17.7", "ring",
"rustls-pki-types", "rustls-pki-types",
"untrusted 0.9.0", "untrusted",
] ]
[[package]] [[package]]
@ -2736,8 +2686,8 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [ dependencies = [
"ring 0.17.7", "ring",
"untrusted 0.9.0", "untrusted",
] ]
[[package]] [[package]]
@ -2791,16 +2741,6 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-value"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
dependencies = [
"ordered-float",
"serde",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.193" version = "1.0.193"
@ -2865,36 +2805,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serenity"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a7a89cef23483fc9d4caf2df41e6d3928e18aada84c56abd237439d929622c6"
dependencies = [
"async-trait",
"async-tungstenite",
"base64 0.21.5",
"bitflags 1.3.2",
"bytes",
"cfg-if",
"dashmap",
"flate2",
"futures",
"mime",
"mime_guess",
"parking_lot",
"percent-encoding",
"reqwest",
"serde",
"serde-value",
"serde_json",
"time",
"tokio",
"tracing",
"typemap_rev 0.1.5",
"url",
]
[[package]] [[package]]
name = "serenity" name = "serenity"
version = "0.12.0" version = "0.12.0"
@ -2922,22 +2832,11 @@ dependencies = [
"tokio", "tokio",
"tokio-tungstenite", "tokio-tungstenite",
"tracing", "tracing",
"typemap_rev 0.3.0", "typemap_rev",
"typesize", "typesize",
"url", "url",
] ]
[[package]]
name = "sha-1"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@ -3135,7 +3034,7 @@ dependencies = [
"tokio-stream", "tokio-stream",
"tracing", "tracing",
"url", "url",
"webpki-roots 0.25.3", "webpki-roots",
] ]
[[package]] [[package]]
@ -3536,17 +3435,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-rustls"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
"rustls 0.20.9",
"tokio",
"webpki",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.24.1" version = "0.24.1"
@ -3590,8 +3478,8 @@ dependencies = [
"rustls 0.21.10", "rustls 0.21.10",
"tokio", "tokio",
"tokio-rustls 0.24.1", "tokio-rustls 0.24.1",
"tungstenite 0.20.1", "tungstenite",
"webpki-roots 0.25.3", "webpki-roots",
] ]
[[package]] [[package]]
@ -3722,27 +3610,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0"
dependencies = [
"base64 0.13.1",
"byteorder",
"bytes",
"http 0.2.11",
"httparse",
"log",
"rand",
"rustls 0.20.9",
"sha-1",
"thiserror",
"url",
"utf-8",
"webpki",
]
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.20.1" version = "0.20.1"
@ -3763,12 +3630,6 @@ dependencies = [
"utf-8", "utf-8",
] ]
[[package]]
name = "typemap_rev"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155"
[[package]] [[package]]
name = "typemap_rev" name = "typemap_rev"
version = "0.3.0" version = "0.3.0"
@ -3943,12 +3804,6 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@ -4111,25 +3966,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "webpki"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
"ring 0.17.7",
"untrusted 0.9.0",
]
[[package]]
name = "webpki-roots"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
dependencies = [
"webpki",
]
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.25.3" version = "0.25.3"

View File

@ -27,6 +27,7 @@ rand = "0.8"
levenshtein = "1.0" levenshtein = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"]} sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"]}
base64 = "0.21.0" base64 = "0.21.0"
secrecy = "0.8.0"
[dependencies.postman] [dependencies.postman]
path = "postman" path = "postman"

View File

@ -13,4 +13,4 @@ lazy_static = "1.4"
num-integer = "0.1" num-integer = "0.1"
serde = "1.0" serde = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]} sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]}
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"] }

View File

@ -8,11 +8,12 @@ use num_integer::Integer;
use regex::{Captures, Regex}; use regex::{Captures, Regex};
use serde::Deserialize; use serde::Deserialize;
use serenity::{ use serenity::{
builder::CreateEmbed, all::{CreateAttachment, CreateEmbedFooter},
builder::{CreateEmbed, CreateEmbedAuthor, CreateMessage, ExecuteWebhook},
http::{CacheHttp, Http, HttpError}, http::{CacheHttp, Http, HttpError},
model::{ model::{
channel::{Channel, Embed as SerenityEmbed}, channel::Channel,
id::ChannelId, id::{ChannelId, MessageId},
webhook::Webhook, webhook::Webhook,
}, },
Error, Result, Error, Result,
@ -194,43 +195,36 @@ impl Embed {
impl Into<CreateEmbed> for Embed { impl Into<CreateEmbed> for Embed {
fn into(self) -> CreateEmbed { fn into(self) -> CreateEmbed {
let mut c = CreateEmbed::default(); let mut author = CreateEmbedAuthor::new(&self.author);
if let Some(author_icon) = &self.author_url {
author = author.icon_url(author_icon);
}
c.title(&self.title) let mut footer = CreateEmbedFooter::new(&self.footer);
if let Some(footer_icon) = &self.footer_url {
footer = footer.icon_url(footer_icon);
}
let mut embed = CreateEmbed::default()
.title(&self.title)
.description(&self.description) .description(&self.description)
.color(self.color) .color(self.color)
.author(|a| { .author(author)
a.name(&self.author); .footer(footer);
if let Some(author_icon) = &self.author_url {
a.icon_url(author_icon);
}
a
})
.footer(|f| {
f.text(&self.footer);
if let Some(footer_icon) = &self.footer_url {
f.icon_url(footer_icon);
}
f
});
for field in &self.fields.0 { for field in &self.fields.0 {
c.field(&field.title, &field.value, field.inline); embed = embed.field(&field.title, &field.value, field.inline);
} }
if let Some(image_url) = &self.image_url { if let Some(image_url) = &self.image_url {
c.image(image_url); embed = embed.image(image_url);
} }
if let Some(thumbnail_url) = &self.thumbnail_url { if let Some(thumbnail_url) = &self.thumbnail_url {
c.thumbnail(thumbnail_url); embed = embed.thumbnail(thumbnail_url);
} }
c embed
} }
} }
@ -465,8 +459,8 @@ WHERE
.expect(&format!("Could not delete Reminder {}", self.id)); .expect(&format!("Could not delete Reminder {}", self.id));
} }
async fn pin_message<M: Into<u64>>(&self, message_id: M, http: impl AsRef<Http>) { async fn pin_message<M: Into<MessageId>>(&self, message_id: M, http: impl AsRef<Http>) {
let _ = http.as_ref().pin_message(self.channel_id, message_id.into(), None).await; let _ = http.as_ref().pin_message(self.channel_id.into(), message_id.into(), None).await;
} }
pub async fn send( pub async fn send(
@ -479,28 +473,24 @@ WHERE
reminder: &Reminder, reminder: &Reminder,
embed: Option<CreateEmbed>, embed: Option<CreateEmbed>,
) -> Result<()> { ) -> Result<()> {
let channel = ChannelId(reminder.channel_id).to_channel(&cache_http).await; let channel = ChannelId::new(reminder.channel_id).to_channel(&cache_http).await;
let mut message = CreateMessage::new().content(&reminder.content).tts(reminder.tts);
if let (Some(attachment), Some(name)) =
(&reminder.attachment, &reminder.attachment_name)
{
message =
message.add_file(CreateAttachment::bytes(attachment as &[u8], name.as_str()));
}
if let Some(embed) = embed {
message = message.embed(embed);
}
match channel { match channel {
Ok(Channel::Guild(channel)) => { Ok(Channel::Guild(channel)) => {
match channel match channel.send_message(&cache_http, message).await {
.send_message(&cache_http, |m| {
m.content(&reminder.content).tts(reminder.tts);
if let (Some(attachment), Some(name)) =
(&reminder.attachment, &reminder.attachment_name)
{
m.add_file((attachment as &[u8], name.as_str()));
}
if let Some(embed) = embed {
m.set_embed(embed);
}
m
})
.await
{
Ok(m) => { Ok(m) => {
if reminder.pin { if reminder.pin {
reminder.pin_message(m.id, cache_http.http()).await; reminder.pin_message(m.id, cache_http.http()).await;
@ -512,24 +502,7 @@ WHERE
} }
} }
Ok(Channel::Private(channel)) => { Ok(Channel::Private(channel)) => {
match channel match channel.send_message(&cache_http.http(), message).await {
.send_message(&cache_http.http(), |m| {
m.content(&reminder.content).tts(reminder.tts);
if let (Some(attachment), Some(name)) =
(&reminder.attachment, &reminder.attachment_name)
{
m.add_file((attachment as &[u8], name.as_str()));
}
if let Some(embed) = embed {
m.set_embed(embed);
}
m
})
.await
{
Ok(m) => { Ok(m) => {
if reminder.pin { if reminder.pin {
reminder.pin_message(m.id, cache_http.http()).await; reminder.pin_message(m.id, cache_http.http()).await;
@ -551,35 +524,31 @@ WHERE
webhook: Webhook, webhook: Webhook,
embed: Option<CreateEmbed>, embed: Option<CreateEmbed>,
) -> Result<()> { ) -> Result<()> {
let mut builder = ExecuteWebhook::new().content(&reminder.content).tts(reminder.tts);
if let Some(username) = &reminder.username {
if !username.is_empty() {
builder = builder.username(username);
}
}
if let Some(avatar) = &reminder.avatar {
builder = builder.avatar_url(avatar);
}
if let (Some(attachment), Some(name)) =
(&reminder.attachment, &reminder.attachment_name)
{
builder =
builder.add_file(CreateAttachment::bytes(attachment as &[u8], name.as_str()));
}
if let Some(embed) = embed {
builder = builder.embeds(vec![embed]);
}
match webhook match webhook
.execute(&cache_http.http(), reminder.pin || reminder.restartable, |w| { .execute(&cache_http.http(), reminder.pin || reminder.restartable, builder)
w.content(&reminder.content).tts(reminder.tts);
if let Some(username) = &reminder.username {
if !username.is_empty() {
w.username(username);
}
}
if let Some(avatar) = &reminder.avatar {
w.avatar_url(avatar);
}
if let (Some(attachment), Some(name)) =
(&reminder.attachment, &reminder.attachment_name)
{
w.add_file((attachment as &[u8], name.as_str()));
}
if let Some(embed) = embed {
w.embeds(vec![SerenityEmbed::fake(|c| {
*c = embed;
c
})]);
}
w
})
.await .await
{ {
Ok(m) => { Ok(m) => {
@ -613,8 +582,10 @@ WHERE
let result = if let (Some(webhook_id), Some(webhook_token)) = let result = if let (Some(webhook_id), Some(webhook_token)) =
(self.webhook_id, &self.webhook_token) (self.webhook_id, &self.webhook_token)
{ {
let webhook_res = let webhook_res = cache_http
cache_http.http().get_webhook_with_token(webhook_id, webhook_token).await; .http()
.get_webhook_with_token(webhook_id.into(), webhook_token)
.await;
if let Ok(webhook) = webhook_res { if let Ok(webhook) = webhook_res {
send_to_webhook(cache_http, &self, webhook, embed).await send_to_webhook(cache_http, &self, webhook, embed).await
@ -630,7 +601,7 @@ WHERE
if let Err(e) = result { if let Err(e) = result {
if let Error::Http(error) = e { if let Error::Http(error) = e {
if let HttpError::UnsuccessfulRequest(http_error) = *error { if let HttpError::UnsuccessfulRequest(http_error) = error {
match http_error.error.code { match http_error.error.code {
10003 => { 10003 => {
self.log_error( self.log_error(

View File

@ -26,7 +26,7 @@ FROM macro
WHERE WHERE
guild_id = (SELECT id FROM guilds WHERE guild = ?) guild_id = (SELECT id FROM guilds WHERE guild = ?)
AND name LIKE CONCAT(?, '%')", AND name LIKE CONCAT(?, '%')",
ctx.guild_id().unwrap().0, ctx.guild_id().unwrap().get(),
partial, partial,
) )
.fetch_all(&ctx.data().database) .fetch_all(&ctx.data().database)

View File

@ -18,7 +18,7 @@ pub async fn delete_macro(
match sqlx::query!( match sqlx::query!(
" "
SELECT id FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", SELECT id FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?",
ctx.guild_id().unwrap().0, ctx.guild_id().unwrap().get(),
name name
) )
.fetch_one(&ctx.data().database) .fetch_one(&ctx.data().database)

View File

@ -1,4 +1,7 @@
use poise::CreateReply; use poise::{
serenity_prelude::{CreateEmbed, CreateEmbedFooter},
CreateReply,
};
use crate::{ use crate::{
component_models::pager::{MacroPager, Pager}, component_models::pager::{MacroPager, Pager},
@ -20,11 +23,7 @@ pub async fn list_macro(ctx: Context<'_>) -> Result<(), Error> {
let resp = show_macro_page(&macros, 0); let resp = show_macro_page(&macros, 0);
ctx.send(|m| { ctx.send(resp).await?;
*m = resp;
m
})
.await?;
Ok(()) Ok(())
} }
@ -37,15 +36,12 @@ pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> Crea
let pager = MacroPager::new(page); let pager = MacroPager::new(page);
if macros.is_empty() { if macros.is_empty() {
let mut reply = CreateReply::default(); return CreateReply::default().embed(
CreateEmbed::new()
reply.embed(|e| { .title("Macros")
e.title("Macros")
.description("No Macros Set Up. Use `/macro record` to get started.") .description("No Macros Set Up. Use `/macro record` to get started.")
.color(*THEME_COLOR) .color(*THEME_COLOR),
}); );
return reply;
} }
let pages = max_macro_page(macros); let pages = max_macro_page(macros);
@ -70,20 +66,13 @@ pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> Crea
} }
}); });
let mut reply = CreateReply::default(); CreateReply::default()
.embed(
reply CreateEmbed::new()
.embed(|e| { .title("Macros")
e.title("Macros")
.fields(fields) .fields(fields)
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR) .color(*THEME_COLOR)
}) .footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))),
.components(|comp| { )
pager.create_button_row(pages, comp); .components(vec![pager.create_button_row(pages)])
comp
});
reply
} }

View File

@ -1,5 +1,5 @@
use lazy_regex::regex; use lazy_regex::regex;
use poise::serenity_prelude::CommandOptionType; use poise::{serenity_prelude::CommandOptionType, CreateReply};
use regex::Captures; use regex::Captures;
use serde_json::{json, Value}; use serde_json::{json, Value};
@ -25,7 +25,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
let aliases = sqlx::query_as!( let aliases = sqlx::query_as!(
Alias, Alias,
"SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)", "SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
guild_id.0 guild_id.get()
) )
.fetch_all(&mut *transaction) .fetch_all(&mut *transaction)
.await?; .await?;
@ -37,7 +37,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
Some(cmd_macro) => { Some(cmd_macro) => {
sqlx::query!( sqlx::query!(
"INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)", "INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
cmd_macro.guild_id.0, cmd_macro.guild_id.get(),
cmd_macro.name, cmd_macro.name,
cmd_macro.description, cmd_macro.description,
cmd_macro.commands cmd_macro.commands
@ -54,7 +54,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
transaction.commit().await?; transaction.commit().await?;
ctx.send(|b| b.content(format!("Added {} macros.", added_aliases))).await?; ctx.send(CreateReply::default().content(format!("Added {} macros.", added_aliases))).await?;
Ok(()) Ok(())
} }

View File

@ -1,5 +1,7 @@
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use poise::{serenity_prelude::CreateEmbed, CreateReply};
use crate::{consts::THEME_COLOR, models::command_macro::CommandMacro, Context, Error}; use crate::{consts::THEME_COLOR, models::command_macro::CommandMacro, Context, Error};
/// Start recording up to 5 commands to replay /// Start recording up to 5 commands to replay
@ -32,23 +34,24 @@ pub async fn record_macro(
let row = sqlx::query!( let row = sqlx::query!(
" "
SELECT 1 as _e FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", SELECT 1 as _e FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?",
guild_id.0, guild_id.get(),
name name
) )
.fetch_one(&ctx.data().database) .fetch_one(&ctx.data().database)
.await; .await;
if row.is_ok() { if row.is_ok() {
ctx.send(|m| { ctx.send(
m.ephemeral(true).embed(|e| { CreateReply::default().ephemeral(true).embed(
e.title("Unique Name Required") CreateEmbed::new()
.title("Unique Name Required")
.description( .description(
"A macro already exists under this name. "A macro already exists under this name.
Please select a unique name for your macro.", Please select a unique name for your macro.",
) )
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
} else { } else {
let okay = { let okay = {
@ -63,28 +66,30 @@ Please select a unique name for your macro.",
}; };
if okay { if okay {
ctx.send(|m| { ctx.send(
m.ephemeral(true).embed(|e| { CreateReply::default().ephemeral(true).embed(
e.title("Macro Recording Started") CreateEmbed::new()
.title("Macro Recording Started")
.description( .description(
"Run up to 5 commands, or type `/macro finish` to stop at any point. "Run up to 5 commands, or type `/macro finish` to stop at any point.
Any commands ran as part of recording will be inconsequential", Any commands ran as part of recording will be inconsequential",
) )
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
} else { } else {
ctx.send(|m| { ctx.send(
m.ephemeral(true).embed(|e| { CreateReply::default().ephemeral(true).embed(
e.title("Macro Already Recording") CreateEmbed::new()
.title("Macro Already Recording")
.description( .description(
"You are already recording a macro in this server. "You are already recording a macro in this server.
Please use `/macro finish` to end this recording before starting another.", Please use `/macro finish` to end this recording before starting another.",
) )
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
} }
} }
@ -108,13 +113,14 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
let contained = lock.get(&key); let contained = lock.get(&key);
if contained.map_or(true, |cmacro| cmacro.commands.is_empty()) { if contained.map_or(true, |cmacro| cmacro.commands.is_empty()) {
ctx.send(|m| { ctx.send(
m.embed(|e| { CreateReply::default().embed(
e.title("No Macro Recorded") CreateEmbed::new()
.title("No Macro Recorded")
.description("Use `/macro record` to start recording a macro") .description("Use `/macro record` to start recording a macro")
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
} else { } else {
let command_macro = contained.unwrap(); let command_macro = contained.unwrap();
@ -122,7 +128,7 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
sqlx::query!( sqlx::query!(
"INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)", "INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
command_macro.guild_id.0, command_macro.guild_id.get(),
command_macro.name, command_macro.name,
command_macro.description, command_macro.description,
json json
@ -131,13 +137,14 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
.await .await
.unwrap(); .unwrap();
ctx.send(|m| { ctx.send(
m.embed(|e| { CreateReply::default().embed(
e.title("Macro Recorded") CreateEmbed::new()
.title("Macro Recorded")
.description("Use `/macro run` to execute the macro") .description("Use `/macro run` to execute the macro")
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
} }
} }

View File

@ -1,3 +1,8 @@
use poise::{
serenity_prelude::{CommandOption, CreateEmbed},
CreateReply,
};
use super::super::autocomplete::macro_name_autocomplete; use super::super::autocomplete::macro_name_autocomplete;
use crate::{models::command_macro::guild_command_macro, Context, Data, Error, THEME_COLOR}; use crate::{models::command_macro::guild_command_macro, Context, Data, Error, THEME_COLOR};
@ -18,15 +23,15 @@ pub async fn run_macro(
match guild_command_macro(&Context::Application(ctx), &name).await { match guild_command_macro(&Context::Application(ctx), &name).await {
Some(command_macro) => { Some(command_macro) => {
Context::Application(ctx) Context::Application(ctx)
.send(|b| { .send(CreateReply::default().embed(
b.embed(|e| { CreateEmbed::new().title("Running Macro").color(*THEME_COLOR).description(
e.title("Running Macro").color(*THEME_COLOR).description(format!( format!(
"Running macro {} ({} commands)", "Running macro {} ({} commands)",
command_macro.name, command_macro.name,
command_macro.commands.len() command_macro.commands.len()
)) ),
}) ),
}) ))
.await?; .await?;
for command in command_macro.commands { for command in command_macro.commands {

View File

@ -1,22 +1,22 @@
use chrono::offset::Utc; use chrono::offset::Utc;
use poise::{serenity_prelude as serenity, serenity_prelude::Mentionable}; use poise::{
serenity_prelude as serenity,
serenity_prelude::{CreateEmbed, CreateEmbedFooter, Mentionable},
CreateReply,
};
use crate::{models::CtxData, Context, Error, THEME_COLOR}; use crate::{models::CtxData, Context, Error, THEME_COLOR};
fn footer( fn footer(ctx: Context<'_>) -> CreateEmbedFooter {
ctx: Context<'_>,
) -> impl FnOnce(&mut serenity::CreateEmbedFooter) -> &mut serenity::CreateEmbedFooter {
let shard_count = ctx.serenity_context().cache.shard_count(); let shard_count = ctx.serenity_context().cache.shard_count();
let shard = ctx.serenity_context().shard_id; let shard = ctx.serenity_context().shard_id;
move |f| { CreateEmbedFooter::new(format!(
f.text(format!( "{}\nshard {} of {}",
"{}\nshard {} of {}", concat!(env!("CARGO_PKG_NAME"), " ver ", env!("CARGO_PKG_VERSION")),
concat!(env!("CARGO_PKG_NAME"), " ver ", env!("CARGO_PKG_VERSION")), shard,
shard, shard_count,
shard_count, ))
))
}
} }
/// Get an overview of bot commands /// Get an overview of bot commands
@ -24,9 +24,10 @@ fn footer(
pub async fn help(ctx: Context<'_>) -> Result<(), Error> { pub async fn help(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx); let footer = footer(ctx);
ctx.send(|m| { ctx.send(
m.ephemeral(true).embed(|e| { CreateReply::default().ephemeral(true).embed(
e.title("Help") CreateEmbed::new()
.title("Help")
.color(*THEME_COLOR) .color(*THEME_COLOR)
.description( .description(
"__Info Commands__ "__Info Commands__
@ -55,9 +56,9 @@ __Advanced Commands__
`/macro` - Record and replay command sequences `/macro` - Record and replay command sequences
", ",
) )
.footer(footer) .footer(footer),
}) ),
}) )
.await?; .await?;
Ok(()) Ok(())
@ -69,9 +70,10 @@ pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx); let footer = footer(ctx);
let _ = ctx let _ = ctx
.send(|m| { .send(
m.ephemeral(true).embed(|e| { CreateReply::default().ephemeral(true).embed(
e.title("Info") CreateEmbed::new()
.title("Info")
.description( .description(
"Help: `/help` "Help: `/help`
@ -84,9 +86,9 @@ Invite the bot: https://invite.reminder-bot.com/
Use our dashboard: https://reminder-bot.com/", Use our dashboard: https://reminder-bot.com/",
) )
.footer(footer) .footer(footer)
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await; .await;
Ok(()) Ok(())
@ -97,8 +99,7 @@ Use our dashboard: https://reminder-bot.com/",
pub async fn donate(ctx: Context<'_>) -> Result<(), Error> { pub async fn donate(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx); let footer = footer(ctx);
ctx.send(|m| m.embed(|e| { ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate")
e.title("Donate")
.description("Thinking of adding a monthly contribution? .description("Thinking of adding a monthly contribution?
Click below for my Patreon and official bot server :) Click below for my Patreon and official bot server :)
@ -117,7 +118,7 @@ Just $2 USD/month!
*Please note, you must be in the JellyWX Discord server to receive Patreon features*") *Please note, you must be in the JellyWX Discord server to receive Patreon features*")
.footer(footer) .footer(footer)
.color(*THEME_COLOR) .color(*THEME_COLOR)
}), ),
) )
.await?; .await?;
@ -129,14 +130,15 @@ Just $2 USD/month!
pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> { pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx); let footer = footer(ctx);
ctx.send(|m| { ctx.send(
m.ephemeral(true).embed(|e| { CreateReply::default().ephemeral(true).embed(
e.title("Dashboard") CreateEmbed::new()
.title("Dashboard")
.description("**https://reminder-bot.com/dashboard**") .description("**https://reminder-bot.com/dashboard**")
.footer(footer) .footer(footer)
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
Ok(()) Ok(())
@ -150,9 +152,11 @@ pub async fn clock(ctx: Context<'_>) -> Result<(), Error> {
let tz = ctx.timezone().await; let tz = ctx.timezone().await;
let now = Utc::now().with_timezone(&tz); let now = Utc::now().with_timezone(&tz);
ctx.send(|m| { ctx.send(CreateReply::default().ephemeral(true).content(format!(
m.ephemeral(true).content(format!("Time in **{}**: `{}`", tz, now.format("%H:%M"))) "Time in **{}**: `{}`",
}) tz,
now.format("%H:%M")
)))
.await?; .await?;
Ok(()) Ok(())
@ -168,13 +172,11 @@ pub async fn clock_context_menu(ctx: Context<'_>, user: serenity::User) -> Resul
let now = Utc::now().with_timezone(&tz); let now = Utc::now().with_timezone(&tz);
ctx.send(|m| { ctx.send(CreateReply::default().ephemeral(true).content(format!(
m.ephemeral(true).content(format!( "Time in {}'s timezone: `{}`",
"Time in {}'s timezone: `{}`", user.mention(),
user.mention(), now.format("%H:%M")
now.format("%H:%M") )))
))
})
.await?; .await?;
Ok(()) Ok(())

View File

@ -2,6 +2,10 @@ use chrono::offset::Utc;
use chrono_tz::{Tz, TZ_VARIANTS}; use chrono_tz::{Tz, TZ_VARIANTS};
use levenshtein::levenshtein; use levenshtein::levenshtein;
use log::warn; use log::warn;
use poise::{
serenity_prelude::{CreateEmbed, CreateEmbedFooter},
CreateReply,
};
use super::autocomplete::timezone_autocomplete; use super::autocomplete::timezone_autocomplete;
use crate::{consts::THEME_COLOR, models::CtxData, Context, Error}; use crate::{consts::THEME_COLOR, models::CtxData, Context, Error};
@ -26,17 +30,18 @@ pub async fn timezone(
let now = Utc::now().with_timezone(&tz); let now = Utc::now().with_timezone(&tz);
ctx.send(|m| { ctx.send(
m.embed(|e| { CreateReply::default().embed(
e.title("Timezone Set") CreateEmbed::new()
.title("Timezone Set")
.description(format!( .description(format!(
"Timezone has been set to **{}**. Your current time should be `{}`", "Timezone has been set to **{}**. Your current time should be `{}`",
timezone, timezone,
now.format("%H:%M") now.format("%H:%M")
)) ))
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
} }
@ -60,16 +65,15 @@ pub async fn timezone(
) )
}); });
ctx.send(|m| { ctx.send(CreateReply::default().embed(CreateEmbed::new()
m.embed(|e| { .title("Timezone Not Recognized")
e.title("Timezone Not Recognized")
.description("Possibly you meant one of the following timezones, otherwise click [here](https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee):") .description("Possibly you meant one of the following timezones, otherwise click [here](https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee):")
.color(*THEME_COLOR) .color(*THEME_COLOR)
.fields(fields) .fields(fields)
.footer(|f| f.text(footer_text)) .footer(CreateEmbedFooter::new(footer_text))
.url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
}) )
}) )
.await?; .await?;
} }
} }
@ -78,9 +82,10 @@ pub async fn timezone(
(t.to_string(), format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")), true) (t.to_string(), format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")), true)
}); });
ctx.send(|m| { ctx.send(
m.embed(|e| { CreateReply::default().embed(
e.title("Timezone Usage") CreateEmbed::new()
.title("Timezone Usage")
.description( .description(
"**Usage:** "**Usage:**
`/timezone Name` `/timezone Name`
@ -92,10 +97,10 @@ You may want to use one of the popular timezones below, otherwise click [here](h
) )
.color(*THEME_COLOR) .color(*THEME_COLOR)
.fields(popular_timezones_iter) .fields(popular_timezones_iter)
.footer(|f| f.text(footer_text)) .footer(CreateEmbedFooter::new(footer_text))
.url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"),
}) ),
}) )
.await?; .await?;
} }
@ -136,13 +141,11 @@ pub async fn set_ephemeral_confirmations(ctx: Context<'_>) -> Result<(), Error>
guild_data.ephemeral_confirmations = true; guild_data.ephemeral_confirmations = true;
guild_data.commit_changes(&ctx.data().database).await; guild_data.commit_changes(&ctx.data().database).await;
ctx.send(|r| { ctx.send(CreateReply::default().ephemeral(true).embed(CreateEmbed::new().title("Confirmations ephemeral")
r.ephemeral(true).embed(|e| {
e.title("Confirmations ephemeral")
.description("Reminder confirmations will be sent privately, and removed when your client restarts.") .description("Reminder confirmations will be sent privately, and removed when your client restarts.")
.color(*THEME_COLOR) .color(*THEME_COLOR)
}) )
}) )
.await?; .await?;
Ok(()) Ok(())
@ -160,15 +163,13 @@ pub async fn unset_ephemeral_confirmations(ctx: Context<'_>) -> Result<(), Error
guild_data.ephemeral_confirmations = false; guild_data.ephemeral_confirmations = false;
guild_data.commit_changes(&ctx.data().database).await; guild_data.commit_changes(&ctx.data().database).await;
ctx.send(|r| { ctx.send(CreateReply::default().ephemeral(true).embed(CreateEmbed::new().title("Confirmations public")
r.ephemeral(true).embed(|e| {
e.title("Confirmations public")
.description( .description(
"Reminder confirmations will be sent as regular messages, and won't be removed automatically.", "Reminder confirmations will be sent as regular messages, and won't be removed automatically.",
) )
.color(*THEME_COLOR) .color(*THEME_COLOR)
}) )
}) )
.await?; .await?;
Ok(()) Ok(())
@ -187,13 +188,14 @@ pub async fn set_allowed_dm(ctx: Context<'_>) -> Result<(), Error> {
user_data.allowed_dm = true; user_data.allowed_dm = true;
user_data.commit_changes(&ctx.data().database).await; user_data.commit_changes(&ctx.data().database).await;
ctx.send(|r| { ctx.send(
r.ephemeral(true).embed(|e| { CreateReply::default().ephemeral(true).embed(
e.title("DMs permitted") CreateEmbed::new()
.title("DMs permitted")
.description("You will receive a message if a user sets a DM reminder for you.") .description("You will receive a message if a user sets a DM reminder for you.")
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
Ok(()) Ok(())
@ -206,15 +208,16 @@ pub async fn unset_allowed_dm(ctx: Context<'_>) -> Result<(), Error> {
user_data.allowed_dm = false; user_data.allowed_dm = false;
user_data.commit_changes(&ctx.data().database).await; user_data.commit_changes(&ctx.data().database).await;
ctx.send(|r| { ctx.send(
r.ephemeral(true).embed(|e| { CreateReply::default().ephemeral(true).embed(
e.title("DMs blocked") CreateEmbed::new()
.title("DMs blocked")
.description( .description(
"You can still set DM reminders for yourself or for users with DMs enabled.", "You can still set DM reminders for yourself or for users with DMs enabled.",
) )
.color(*THEME_COLOR) .color(*THEME_COLOR),
}) ),
}) )
.await?; .await?;
Ok(()) Ok(())
@ -230,15 +233,13 @@ pub async fn webhook(ctx: Context<'_>) -> Result<(), Error> {
match ctx.channel_data().await { match ctx.channel_data().await {
Ok(data) => { Ok(data) => {
if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) { if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
ctx.send(|b| { ctx.send(CreateReply::default().ephemeral(true).content(format!(
b.ephemeral(true).content(format!( "**Warning!**
"**Warning!**
This link can be used by users to anonymously send messages, with or without permissions. This link can be used by users to anonymously send messages, with or without permissions.
Do not share it! Do not share it!
|| https://discord.com/api/webhooks/{}/{} ||", || https://discord.com/api/webhooks/{}/{} ||",
id, token, id, token,
)) )))
})
.await?; .await?;
} else { } else {
ctx.say("No webhook configured on this channel.").await?; ctx.say("No webhook configured on this channel.").await?;

View File

@ -5,7 +5,11 @@ use chrono_tz::Tz;
use log::warn; use log::warn;
use num_integer::Integer; use num_integer::Integer;
use poise::{ use poise::{
serenity_prelude::{builder::CreateEmbed, model::channel::Channel, ButtonStyle, ReactionType}, serenity_prelude::{
builder::CreateEmbed, model::channel::Channel, ButtonStyle, CreateActionRow, CreateButton,
CreateEmbedFooter, CreateSelectMenu, CreateSelectMenuKind, CreateSelectMenuOption,
ReactionType,
},
CreateReply, Modal, CreateReply, Modal,
}; };
@ -125,21 +129,19 @@ pub async fn offset(
let channels = guild let channels = guild
.channels .channels
.iter() .iter()
.filter(|(_, channel)| match channel { .filter(|(_, channel)| channel.is_text_based())
Channel::Guild(guild_channel) => guild_channel.is_text_based(), .map(|(id, _)| id.get().to_string())
_ => false,
})
.map(|(id, _)| id.0.to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(","); .join(",");
sqlx::query!( sqlx::query!(
" "
UPDATE reminders UPDATE reminders
INNER JOIN INNER JOIN `channels`
`channels` ON `channels`.id = reminders.channel_id ON `channels`.id = reminders.channel_id
SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND) SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND)
WHERE FIND_IN_SET(channels.`channel`, ?)", WHERE FIND_IN_SET(channels.`channel`, ?)
",
combined_time as i64, combined_time as i64,
channels channels
) )
@ -148,9 +150,15 @@ WHERE FIND_IN_SET(channels.`channel`, ?)",
.unwrap(); .unwrap();
} else { } else {
sqlx::query!( sqlx::query!(
"UPDATE reminders INNER JOIN `channels` ON `channels`.id = reminders.channel_id SET reminders.`utc_time` = reminders.`utc_time` + ? WHERE channels.`channel` = ?", "
UPDATE reminders
INNER JOIN `channels`
ON `channels`.id = reminders.channel_id
SET reminders.`utc_time` = reminders.`utc_time` + ?
WHERE channels.`channel` = ?
",
combined_time as i64, combined_time as i64,
ctx.channel_id().0 ctx.channel_id().get()
) )
.execute(&ctx.data().database) .execute(&ctx.data().database)
.await .await
@ -216,9 +224,9 @@ pub async fn look(
}), }),
}; };
let channel_opt = ctx.channel_id().to_channel_cached(&ctx); let channel_opt = ctx.channel_id().to_channel_cached(&ctx.cache());
let channel_id = if let Some(Channel::Guild(channel)) = channel_opt { let channel_id = if let Some(channel) = channel_opt {
if Some(channel.guild_id) == ctx.guild_id() { if Some(channel.guild_id) == ctx.guild_id() {
flags.channel_id.unwrap_or_else(|| ctx.channel_id()) flags.channel_id.unwrap_or_else(|| ctx.channel_id())
} else { } else {
@ -228,11 +236,8 @@ pub async fn look(
ctx.channel_id() ctx.channel_id()
}; };
let channel_name = if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) { let channel_name =
Some(channel.name) channel_id.to_channel_cached(&ctx.cache()).map(|channel| channel.name.clone());
} else {
None
};
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await; let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
@ -260,23 +265,21 @@ pub async fn look(
let pager = LookPager::new(flags, timezone); let pager = LookPager::new(flags, timezone);
ctx.send(|r| { ctx.send(
r.ephemeral(true) CreateReply::default()
.embed(|e| { .ephemeral(true)
e.title(format!( .embed(
"Reminders{}", CreateEmbed::new()
channel_name.map_or(String::new(), |n| format!(" on #{}", n)) .title(format!(
)) "Reminders{}",
.description(display) channel_name.map_or(String::new(), |n| format!(" on #{}", n))
.footer(|f| f.text(format!("Page {} of {}", 1, pages))) ))
.color(*THEME_COLOR) .description(display)
}) .footer(CreateEmbedFooter::new(format!("Page {} of {}", 1, pages)))
.components(|comp| { .color(*THEME_COLOR),
pager.create_button_row(pages, comp); )
.components(vec![pager.create_button_row(pages)]),
comp )
})
})
.await?; .await?;
} }
@ -298,11 +301,7 @@ pub async fn delete(ctx: Context<'_>) -> Result<(), Error> {
let resp = show_delete_page(&reminders, 0, timezone); let resp = show_delete_page(&reminders, 0, timezone);
ctx.send(|r| { ctx.send(resp).await?;
*r = resp;
r
})
.await?;
Ok(()) Ok(())
} }
@ -333,16 +332,12 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
let pager = DelPager::new(page, timezone); let pager = DelPager::new(page, timezone);
if reminders.is_empty() { if reminders.is_empty() {
let mut reply = CreateReply::default(); let embed = CreateEmbed::new()
.title("Delete Reminders")
.description("No Reminders")
.color(*THEME_COLOR);
reply return CreateReply::default().embed(embed).components(vec![pager.create_button_row(0)]);
.embed(|e| e.title("Delete Reminders").description("No Reminders").color(*THEME_COLOR))
.components(|comp| {
pager.create_button_row(0, comp);
comp
});
return reply;
} }
let pages = max_delete_page(reminders, &timezone); let pages = max_delete_page(reminders, &timezone);
@ -391,49 +386,42 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
let del_selector = ComponentDataModel::DelSelector(DelSelector { page, timezone }); let del_selector = ComponentDataModel::DelSelector(DelSelector { page, timezone });
let mut reply = CreateReply::default(); let embed = CreateEmbed::new()
.title("Delete Reminders")
.description(display)
.footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR);
reply let select_menu = CreateSelectMenu::new(
.embed(|e| { del_selector.to_custom_id(),
e.title("Delete Reminders") CreateSelectMenuKind::String {
.description(display) options: shown_reminders
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages))) .iter()
.color(*THEME_COLOR) .enumerate()
}) .map(|(count, reminder)| {
.components(|comp| { let c = reminder.display_content();
pager.create_button_row(pages, comp); let description = if c.len() > 100 {
format!(
"{}...",
reminder.display_content().chars().take(97).collect::<String>()
)
} else {
c.to_string()
};
comp.create_action_row(|row| { CreateSelectMenuOption::new(
row.create_select_menu(|menu| { (count + first_num).to_string(),
menu.custom_id(del_selector.to_custom_id()).options(|opt| { reminder.id.to_string(),
for (count, reminder) in shown_reminders.iter().enumerate() { )
opt.create_option(|o| { .description(description)
o.label(count + first_num).value(reminder.id).description({
let c = reminder.display_content();
if c.len() > 100 {
format!(
"{}...",
reminder
.display_content()
.chars()
.take(97)
.collect::<String>()
)
} else {
c.to_string()
}
})
});
}
opt
})
}) })
}) .collect(),
}); },
);
reply CreateReply::default()
.embed(embed)
.components(vec![pager.create_button_row(pages), CreateActionRow::SelectMenu(select_menu)])
} }
fn time_difference(start_time: DateTime<Utc>) -> String { fn time_difference(start_time: DateTime<Utc>) -> String {
@ -465,19 +453,20 @@ pub async fn timer_base(_ctx: Context<'_>) -> Result<(), Error> {
default_member_permissions = "MANAGE_GUILD" default_member_permissions = "MANAGE_GUILD"
)] )]
pub async fn list_timer(ctx: Context<'_>) -> Result<(), Error> { pub async fn list_timer(ctx: Context<'_>) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0); let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let timers = Timer::from_owner(owner, &ctx.data().database).await; let timers = Timer::from_owner(owner, &ctx.data().database).await;
if !timers.is_empty() { if !timers.is_empty() {
ctx.send(|m| { ctx.send(
m.embed(|e| { CreateReply::default().embed(
e.fields(timers.iter().map(|timer| { CreateEmbed::new()
(&timer.name, format!("⌚ `{}`", time_difference(timer.start_time)), false) .fields(timers.iter().map(|timer| {
})) (&timer.name, format!("⌚ `{}`", time_difference(timer.start_time)), false)
.color(*THEME_COLOR) }))
}) .color(*THEME_COLOR),
}) ),
)
.await?; .await?;
} else { } else {
ctx.say("No timers currently. Use `/timer start` to create a new timer").await?; ctx.say("No timers currently. Use `/timer start` to create a new timer").await?;
@ -497,7 +486,7 @@ pub async fn start_timer(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Name for the new timer"] name: String, #[description = "Name for the new timer"] name: String,
) -> Result<(), Error> { ) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0); let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let count = Timer::count_from_owner(owner, &ctx.data().database).await; let count = Timer::count_from_owner(owner, &ctx.data().database).await;
@ -530,7 +519,7 @@ pub async fn delete_timer(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Name of timer to delete"] name: String, #[description = "Name of timer to delete"] name: String,
) -> Result<(), Error> { ) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0); let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let exists = let exists =
sqlx::query!("SELECT 1 as _r FROM timers WHERE owner = ? AND name = ?", owner, name) sqlx::query!("SELECT 1 as _r FROM timers WHERE owner = ? AND name = ?", owner, name)
@ -604,7 +593,7 @@ pub async fn multiline(
None => { None => {
warn!("Unexpected None encountered in /multiline"); warn!("Unexpected None encountered in /multiline");
Ok(Context::Application(ctx) Ok(Context::Application(ctx)
.send(|m| m.content("Unexpected error.").ephemeral(true)) .send(CreateReply::default().content("Unexpected error.").ephemeral(true))
.await .await
.map(|_| ())?) .map(|_| ())?)
} }
@ -682,9 +671,9 @@ async fn create_reminder(
if list.is_empty() { if list.is_empty() {
if ctx.guild_id().is_some() { if ctx.guild_id().is_some() {
vec![ReminderScope::Channel(ctx.channel_id().0)] vec![ReminderScope::Channel(ctx.channel_id().get())]
} else { } else {
vec![ReminderScope::User(ctx.author().id.0)] vec![ReminderScope::User(ctx.author().id.get())]
} }
} else { } else {
list list
@ -709,10 +698,9 @@ async fn create_reminder(
}, },
) )
} else { } else {
ctx.send(|b| { ctx.send(CreateReply::default().content(
b.content( "`repeat` is only available to Patreon subscribers or self-hosted users",
"`repeat` is only available to Patreon subscribers or self-hosted users") ))
})
.await?; .await?;
return Ok(()); return Ok(());
@ -722,17 +710,16 @@ async fn create_reminder(
}; };
if processed_interval.is_none() && interval.is_some() { if processed_interval.is_none() && interval.is_some() {
ctx.send(|b| { ctx.send(CreateReply::default().content(
b.content( "Repeat interval could not be processed. Try similar to `1 hour` or `4 days`",
"Repeat interval could not be processed. Try similar to `1 hour` or `4 days`") ))
})
.await?; .await?;
} else if processed_expires.is_none() && expires.is_some() { } else if processed_expires.is_none() && expires.is_some() {
ctx.send(|b| { ctx.send(
b.ephemeral(true).content( CreateReply::default().ephemeral(true).content(
"Expiry time failed to process. Please make it as clear as possible", "Expiry time failed to process. Please make it as clear as possible",
) ),
}) )
.await?; .await?;
} else { } else {
let mut builder = MultiReminderBuilder::new(&ctx, ctx.guild_id()) let mut builder = MultiReminderBuilder::new(&ctx, ctx.guild_id())
@ -756,37 +743,20 @@ async fn create_reminder(
reminder_id: reminder, reminder_id: reminder,
}); });
ctx.send(|m| { ctx.send(CreateReply::default().embed(embed).components(vec![
m.embed(|c| { CreateActionRow::Buttons(vec![
*c = embed; CreateButton::new(undo_button.to_custom_id())
c .emoji(ReactionType::Unicode("🔕".to_string()))
}) .label("Cancel")
.components(|c| { .style(ButtonStyle::Danger),
c.create_action_row(|r| { CreateButton::new_link("https://beta.reminder-bot.com/dashboard")
r.create_button(|b| { .emoji(ReactionType::Unicode("📝".to_string()))
b.emoji(ReactionType::Unicode("🔕".to_string())) .label("Edit"),
.label("Cancel") ]),
.style(ButtonStyle::Danger) ]))
.custom_id(undo_button.to_custom_id())
})
.create_button(|b| {
b.emoji(ReactionType::Unicode("📝".to_string()))
.label("Edit")
.style(ButtonStyle::Link)
.url("https://beta.reminder-bot.com/dashboard")
})
})
})
})
.await?; .await?;
} else { } else {
ctx.send(|m| { ctx.send(CreateReply::default().embed(embed)).await?;
m.embed(|c| {
*c = embed;
c
})
})
.await?;
} }
} }
} }

View File

@ -1,4 +1,10 @@
use poise::CreateReply; use poise::{
serenity_prelude::{
CreateActionRow, CreateEmbed, CreateEmbedFooter, CreateSelectMenu, CreateSelectMenuKind,
CreateSelectMenuOption,
},
CreateReply,
};
use crate::{ use crate::{
component_models::{ component_models::{
@ -48,7 +54,7 @@ pub async fn todo_guild_add(
sqlx::query!( sqlx::query!(
"INSERT INTO todos (guild_id, value) "INSERT INTO todos (guild_id, value)
VALUES ((SELECT id FROM guilds WHERE guild = ?), ?)", VALUES ((SELECT id FROM guilds WHERE guild = ?), ?)",
ctx.guild_id().unwrap().0, ctx.guild_id().unwrap().get(),
task task
) )
.execute(&ctx.data().database) .execute(&ctx.data().database)
@ -73,7 +79,7 @@ pub async fn todo_guild_view(ctx: Context<'_>) -> Result<(), Error> {
"SELECT todos.id, value FROM todos "SELECT todos.id, value FROM todos
INNER JOIN guilds ON todos.guild_id = guilds.id INNER JOIN guilds ON todos.guild_id = guilds.id
WHERE guilds.guild = ?", WHERE guilds.guild = ?",
ctx.guild_id().unwrap().0, ctx.guild_id().unwrap().get(),
) )
.fetch_all(&ctx.data().database) .fetch_all(&ctx.data().database)
.await .await
@ -82,13 +88,9 @@ WHERE guilds.guild = ?",
.map(|row| (row.id as usize, row.value.clone())) .map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>(); .collect::<Vec<(usize, String)>>();
let resp = show_todo_page(&values, 0, None, None, ctx.guild_id().map(|g| g.0)); let resp = show_todo_page(&values, 0, None, None, ctx.guild_id().map(|g| g.get()));
ctx.send(|r| { ctx.send(resp).await?;
*r = resp;
r
})
.await?;
Ok(()) Ok(())
} }
@ -123,8 +125,8 @@ pub async fn todo_channel_add(
sqlx::query!( sqlx::query!(
"INSERT INTO todos (guild_id, channel_id, value) "INSERT INTO todos (guild_id, channel_id, value)
VALUES ((SELECT id FROM guilds WHERE guild = ?), (SELECT id FROM channels WHERE channel = ?), ?)", VALUES ((SELECT id FROM guilds WHERE guild = ?), (SELECT id FROM channels WHERE channel = ?), ?)",
ctx.guild_id().unwrap().0, ctx.guild_id().unwrap().get(),
ctx.channel_id().0, ctx.channel_id().get(),
task task
) )
.execute(&ctx.data().database) .execute(&ctx.data().database)
@ -149,7 +151,7 @@ pub async fn todo_channel_view(ctx: Context<'_>) -> Result<(), Error> {
"SELECT todos.id, value FROM todos "SELECT todos.id, value FROM todos
INNER JOIN channels ON todos.channel_id = channels.id INNER JOIN channels ON todos.channel_id = channels.id
WHERE channels.channel = ?", WHERE channels.channel = ?",
ctx.channel_id().0, ctx.channel_id().get(),
) )
.fetch_all(&ctx.data().database) .fetch_all(&ctx.data().database)
.await .await
@ -158,14 +160,15 @@ WHERE channels.channel = ?",
.map(|row| (row.id as usize, row.value.clone())) .map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>(); .collect::<Vec<(usize, String)>>();
let resp = let resp = show_todo_page(
show_todo_page(&values, 0, None, Some(ctx.channel_id().0), ctx.guild_id().map(|g| g.0)); &values,
0,
None,
Some(ctx.channel_id().get()),
ctx.guild_id().map(|g| g.get()),
);
ctx.send(|r| { ctx.send(resp).await?;
*r = resp;
r
})
.await?;
Ok(()) Ok(())
} }
@ -185,7 +188,7 @@ pub async fn todo_user_add(
sqlx::query!( sqlx::query!(
"INSERT INTO todos (user_id, value) "INSERT INTO todos (user_id, value)
VALUES ((SELECT id FROM users WHERE user = ?), ?)", VALUES ((SELECT id FROM users WHERE user = ?), ?)",
ctx.author().id.0, ctx.author().id.get(),
task task
) )
.execute(&ctx.data().database) .execute(&ctx.data().database)
@ -204,7 +207,7 @@ pub async fn todo_user_view(ctx: Context<'_>) -> Result<(), Error> {
"SELECT todos.id, value FROM todos "SELECT todos.id, value FROM todos
INNER JOIN users ON todos.user_id = users.id INNER JOIN users ON todos.user_id = users.id
WHERE users.user = ?", WHERE users.user = ?",
ctx.author().id.0, ctx.author().id.get(),
) )
.fetch_all(&ctx.data().database) .fetch_all(&ctx.data().database)
.await .await
@ -213,13 +216,9 @@ WHERE users.user = ?",
.map(|row| (row.id as usize, row.value.clone())) .map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>(); .collect::<Vec<(usize, String)>>();
let resp = show_todo_page(&values, 0, Some(ctx.author().id.0), None, None); let resp = show_todo_page(&values, 0, Some(ctx.author().id.get()), None, None);
ctx.send(|r| { ctx.send(resp).await?;
*r = resp;
r
})
.await?;
Ok(()) Ok(())
} }
@ -306,61 +305,51 @@ pub fn show_todo_page(
}; };
if todo_ids.is_empty() { if todo_ids.is_empty() {
let mut reply = CreateReply::default(); CreateReply::default().embed(
CreateEmbed::new()
reply.embed(|e| { .title(format!("{} Todo List", title))
e.title(format!("{} Todo List", title))
.description("Todo List Empty!") .description("Todo List Empty!")
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR) .color(*THEME_COLOR)
}); .footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))),
)
reply
} else { } else {
let todo_selector = let todo_selector =
ComponentDataModel::TodoSelector(TodoSelector { page, user_id, channel_id, guild_id }); ComponentDataModel::TodoSelector(TodoSelector { page, user_id, channel_id, guild_id });
let mut reply = CreateReply::default(); CreateReply::default()
.embed(
reply CreateEmbed::new()
.embed(|e| { .title(format!("{} Todo List", title))
e.title(format!("{} Todo List", title))
.description(display) .description(display)
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR) .color(*THEME_COLOR)
}) .footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))),
.components(|comp| { )
pager.create_button_row(pages, comp); .components(vec![
pager.create_button_row(pages),
CreateActionRow::SelectMenu(CreateSelectMenu::new(
todo_selector.to_custom_id(),
CreateSelectMenuKind::String {
options: todo_ids
.iter()
.zip(&display_vec)
.enumerate()
.map(|(count, (id, disp))| {
let c = disp.split_once(' ').unwrap_or(("", "")).1;
let description = if c.len() > 100 {
format!("{}...", c.chars().take(97).collect::<String>())
} else {
c.to_string()
};
comp.create_action_row(|row| { CreateSelectMenuOption::new(
row.create_select_menu(|menu| { format!("Mark {} complete", count + first_num),
menu.custom_id(todo_selector.to_custom_id()).options(|opt| { id.to_string(),
for (count, (id, disp)) in todo_ids.iter().zip(&display_vec).enumerate() )
{ .description(description)
opt.create_option(|o| { })
o.label(format!("Mark {} complete", count + first_num)) .collect(),
.value(id) },
.description({ )),
let c = disp.split_once(' ').unwrap_or(("", "")).1; ])
if c.len() > 100 {
format!(
"{}...",
c.chars().take(97).collect::<String>()
)
} else {
c.to_string()
}
})
});
}
opt
})
})
})
});
reply
} }
} }

View File

@ -8,15 +8,8 @@ use log::warn;
use poise::{ use poise::{
serenity_prelude as serenity, serenity_prelude as serenity,
serenity_prelude::{ serenity_prelude::{
builder::CreateEmbed, builder::CreateEmbed, ComponentInteraction, ComponentInteractionDataKind, Context,
model::{ CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseMessage,
application::interaction::{
message_component::MessageComponentInteraction, InteractionResponseType,
MessageFlags,
},
channel::Channel,
},
Context,
}, },
}; };
use rmp_serde::Serializer; use rmp_serde::Serializer;
@ -31,7 +24,7 @@ use crate::{
component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager}, component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager},
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
models::reminder::Reminder, models::reminder::Reminder,
utils::send_as_initial_response, utils::reply_to_interaction_response_message,
Data, Data,
}; };
@ -64,21 +57,23 @@ impl ComponentDataModel {
rmp_serde::from_read(cur).unwrap() rmp_serde::from_read(cur).unwrap()
} }
pub async fn act(&self, ctx: &Context, data: &Data, component: &MessageComponentInteraction) { pub async fn act(&self, ctx: &Context, data: &Data, component: &ComponentInteraction) {
match self { match self {
ComponentDataModel::LookPager(pager) => { ComponentDataModel::LookPager(pager) => {
let flags = pager.flags; let flags = pager.flags;
let channel_opt = component.channel_id.to_channel_cached(&ctx); let channel_id = {
let channel_opt = component.channel_id.to_channel_cached(&ctx.cache);
let channel_id = if let Some(Channel::Guild(channel)) = channel_opt { if let Some(channel) = channel_opt {
if Some(channel.guild_id) == component.guild_id { if Some(channel.guild_id) == component.guild_id {
flags.channel_id.unwrap_or(component.channel_id) flags.channel_id.unwrap_or(component.channel_id)
} else {
component.channel_id
}
} else { } else {
component.channel_id component.channel_id
} }
} else {
component.channel_id
}; };
let reminders = Reminder::from_channel(&data.database, channel_id, &flags).await; let reminders = Reminder::from_channel(&data.database, channel_id, &flags).await;
@ -90,11 +85,7 @@ impl ComponentDataModel {
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH); .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
let channel_name = let channel_name =
if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) { channel_id.to_channel_cached(&ctx.cache).map(|channel| channel.name.clone());
Some(channel.name)
} else {
None
};
let next_page = pager.next_page(pages); let next_page = pager.next_page(pages);
@ -107,7 +98,7 @@ impl ComponentDataModel {
.skip_while(|p| { .skip_while(|p| {
skip_char_count += p.len(); skip_char_count += p.len();
skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page as usize skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page
}) })
.take_while(|p| { .take_while(|p| {
char_count += p.len(); char_count += p.len();
@ -117,28 +108,24 @@ impl ComponentDataModel {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(""); .join("");
let mut embed = CreateEmbed::default(); let embed = CreateEmbed::default()
embed
.title(format!( .title(format!(
"Reminders{}", "Reminders{}",
channel_name.map_or(String::new(), |n| format!(" on #{}", n)) channel_name.map_or(String::new(), |n| format!(" on #{}", n))
)) ))
.description(display) .description(display)
.footer(|f| f.text(format!("Page {} of {}", next_page + 1, pages))) .footer(CreateEmbedFooter::new(format!("Page {} of {}", next_page + 1, pages)))
.color(*THEME_COLOR); .color(*THEME_COLOR);
let _ = component let _ = component
.create_interaction_response(&ctx, |r| { .create_response(
r.kind(InteractionResponseType::UpdateMessage).interaction_response_data( &ctx,
|response| { CreateInteractionResponse::UpdateMessage(
response.set_embeds(vec![embed]).components(|comp| { CreateInteractionResponseMessage::new()
pager.create_button_row(pages, comp); .embed(embed)
.components(vec![pager.create_button_row(pages)]),
comp ),
}) )
},
)
})
.await; .await;
} }
ComponentDataModel::DelPager(pager) => { ComponentDataModel::DelPager(pager) => {
@ -155,55 +142,58 @@ impl ComponentDataModel {
let resp = show_delete_page(&reminders, pager.next_page(max_pages), pager.timezone); let resp = show_delete_page(&reminders, pager.next_page(max_pages), pager.timezone);
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(
f.kind(InteractionResponseType::UpdateMessage).interaction_response_data( &ctx,
|d| { CreateInteractionResponse::UpdateMessage(
send_as_initial_response(resp, d); reply_to_interaction_response_message(resp),
d ),
}, )
)
})
.await; .await;
} }
ComponentDataModel::DelSelector(selector) => { ComponentDataModel::DelSelector(selector) => {
let selected_id = component.data.values.join(","); if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind
{
let selected_id = values.join(",");
sqlx::query!( sqlx::query!(
"UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)", "
selected_id UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)
) ",
.execute(&data.database) selected_id
.await )
.unwrap(); .execute(&data.database)
.await
.unwrap();
let reminders = Reminder::from_guild( let reminders = Reminder::from_guild(
&ctx, &ctx,
&data.database, &data.database,
component.guild_id, component.guild_id,
component.user.id, component.user.id,
) )
.await;
let resp = show_delete_page(&reminders, selector.page, selector.timezone);
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
|d| {
send_as_initial_response(resp, d);
d
},
)
})
.await; .await;
let resp = show_delete_page(&reminders, selector.page, selector.timezone);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
} }
ComponentDataModel::TodoPager(pager) => { ComponentDataModel::TodoPager(pager) => {
if Some(component.user.id.0) == pager.user_id || pager.user_id.is_none() { if Some(component.user.id.get()) == pager.user_id || pager.user_id.is_none() {
let values = if let Some(uid) = pager.user_id { let values = if let Some(uid) = pager.user_id {
sqlx::query!( sqlx::query!(
"SELECT todos.id, value FROM todos "
INNER JOIN users ON todos.user_id = users.id SELECT todos.id, value FROM todos
WHERE users.user = ?", INNER JOIN users ON todos.user_id = users.id
WHERE users.user = ?
",
uid, uid,
) )
.fetch_all(&data.database) .fetch_all(&data.database)
@ -214,9 +204,11 @@ WHERE users.user = ?",
.collect::<Vec<(usize, String)>>() .collect::<Vec<(usize, String)>>()
} else if let Some(cid) = pager.channel_id { } else if let Some(cid) = pager.channel_id {
sqlx::query!( sqlx::query!(
"SELECT todos.id, value FROM todos "
INNER JOIN channels ON todos.channel_id = channels.id SELECT todos.id, value FROM todos
WHERE channels.channel = ?", INNER JOIN channels ON todos.channel_id = channels.id
WHERE channels.channel = ?
",
cid, cid,
) )
.fetch_all(&data.database) .fetch_all(&data.database)
@ -227,9 +219,11 @@ WHERE channels.channel = ?",
.collect::<Vec<(usize, String)>>() .collect::<Vec<(usize, String)>>()
} else { } else {
sqlx::query!( sqlx::query!(
"SELECT todos.id, value FROM todos "
INNER JOIN guilds ON todos.guild_id = guilds.id SELECT todos.id, value FROM todos
WHERE guilds.guild = ?", INNER JOIN guilds ON todos.guild_id = guilds.id
WHERE guilds.guild = ?
",
pager.guild_id, pager.guild_id,
) )
.fetch_all(&data.database) .fetch_all(&data.database)
@ -251,79 +245,86 @@ WHERE guilds.guild = ?",
); );
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(
f.kind(InteractionResponseType::UpdateMessage) &ctx,
.interaction_response_data(|d| { CreateInteractionResponse::UpdateMessage(
send_as_initial_response(resp, d); reply_to_interaction_response_message(resp),
d ),
}) )
})
.await; .await;
} else { } else {
let _ = component let _ = component
.create_interaction_response(&ctx, |r| { .create_response(
r.kind(InteractionResponseType::ChannelMessageWithSource) &ctx,
.interaction_response_data(|d| { CreateInteractionResponse::Message(
d.flags( CreateInteractionResponseMessage::new()
MessageFlags::EPHEMERAL, .ephemeral(true)
)
.content("Only the user who performed the command can use these components") .content("Only the user who performed the command can use these components")
}) )
}) )
.await; .await;
} }
} }
ComponentDataModel::TodoSelector(selector) => { ComponentDataModel::TodoSelector(selector) => {
if Some(component.user.id.0) == selector.user_id || selector.user_id.is_none() { if Some(component.user.id.get()) == selector.user_id || selector.user_id.is_none() {
let selected_id = component.data.values.join(","); if let ComponentInteractionDataKind::StringSelect { values } =
&component.data.kind
{
let selected_id = values.join(",");
sqlx::query!("DELETE FROM todos WHERE FIND_IN_SET(id, ?)", selected_id) sqlx::query!(
"
DELETE FROM todos WHERE FIND_IN_SET(id, ?)
",
selected_id
)
.execute(&data.database) .execute(&data.database)
.await .await
.unwrap(); .unwrap();
let values = sqlx::query!( let values = sqlx::query!(
// fucking braindead mysql use <=> instead of = for null comparison // fucking braindead mysql use <=> instead of = for null comparison
"SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?", "
selector.user_id, SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?
selector.channel_id, ",
selector.guild_id, selector.user_id,
) selector.channel_id,
.fetch_all(&data.database) selector.guild_id,
.await )
.unwrap() .fetch_all(&data.database)
.iter() .await
.map(|row| (row.id as usize, row.value.clone())) .unwrap()
.collect::<Vec<(usize, String)>>(); .iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>();
let resp = show_todo_page( let resp = show_todo_page(
&values, &values,
selector.page, selector.page,
selector.user_id, selector.user_id,
selector.channel_id, selector.channel_id,
selector.guild_id, selector.guild_id,
); );
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(
f.kind(InteractionResponseType::UpdateMessage) &ctx,
.interaction_response_data(|d| { CreateInteractionResponse::UpdateMessage(
send_as_initial_response(resp, d); reply_to_interaction_response_message(resp),
d ),
}) )
}) .await;
.await; }
} else { } else {
let _ = component let _ = component
.create_interaction_response(&ctx, |r| { .create_response(
r.kind(InteractionResponseType::ChannelMessageWithSource) &ctx,
.interaction_response_data(|d| { CreateInteractionResponse::Message(
d.flags( CreateInteractionResponseMessage::new()
MessageFlags::EPHEMERAL, .ephemeral(true)
)
.content("Only the user who performed the command can use these components") .content("Only the user who performed the command can use these components")
}) )
}) )
.await; .await;
} }
} }
@ -336,14 +337,12 @@ WHERE guilds.guild = ?",
let resp = show_macro_page(&macros, page); let resp = show_macro_page(&macros, page);
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(
f.kind(InteractionResponseType::UpdateMessage).interaction_response_data( &ctx,
|d| { CreateInteractionResponse::UpdateMessage(
send_as_initial_response(resp, d); reply_to_interaction_response_message(resp),
d ),
}, )
)
})
.await; .await;
} }
ComponentDataModel::UndoReminder(undo_reminder) => { ComponentDataModel::UndoReminder(undo_reminder) => {
@ -355,58 +354,56 @@ WHERE guilds.guild = ?",
match reminder.delete(&data.database).await { match reminder.delete(&data.database).await {
Ok(()) => { Ok(()) => {
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(
f.kind(InteractionResponseType::UpdateMessage) &ctx,
.interaction_response_data(|d| { CreateInteractionResponse::UpdateMessage(
d.embed(|e| { CreateInteractionResponseMessage::new().embed(
e.title("Reminder Canceled") CreateEmbed::new()
.description( .title("Reminder Canceled")
"This reminder has been canceled.", .description("This reminder has been canceled.")
) .color(*THEME_COLOR),
.color(*THEME_COLOR) ),
}) ),
.components(|c| c) )
})
})
.await; .await;
} }
Err(e) => { Err(e) => {
warn!("Error canceling reminder: {:?}", e); warn!("Error canceling reminder: {:?}", e);
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(
f.kind(InteractionResponseType::ChannelMessageWithSource) &ctx,
.interaction_response_data(|d| { CreateInteractionResponse::Message(
d.content( CreateInteractionResponseMessage::new().content(
"The reminder could not be canceled: it may have already been deleted. Check `/del`!") "An error occurred trying to cancel this reminder.",
.ephemeral(true) ).ephemeral(true),
}) ),
}) )
.await; .await;
} }
} }
} else { } else {
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(
f.kind(InteractionResponseType::ChannelMessageWithSource) &ctx,
.interaction_response_data(|d| { CreateInteractionResponse::Message(
d.content( CreateInteractionResponseMessage::new().content(
"The reminder could not be canceled: it may have already been deleted. Check `/del`!") "The reminder could not be canceled. It may have already been deleted.",
.ephemeral(true) ).ephemeral(true),
}) ),
}) )
.await; .await;
} }
} else { } else {
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(
f.kind(InteractionResponseType::ChannelMessageWithSource) &ctx,
.interaction_response_data(|d| { CreateInteractionResponse::Message(
d.content( CreateInteractionResponseMessage::new()
"Only the user who performed the command can use this button.") .content("Only the user who performed the command can use these components")
.ephemeral(true) .ephemeral(true),
}) ),
}) )
.await; .await;
} }
} }

View File

@ -10,17 +10,16 @@ pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
const THEME_COLOR_FALLBACK: u32 = 0x8fb677; const THEME_COLOR_FALLBACK: u32 = 0x8fb677;
pub const MACRO_MAX_COMMANDS: usize = 5; pub const MACRO_MAX_COMMANDS: usize = 5;
use poise::serenity_prelude::CreateAttachment;
use std::{collections::HashSet, env, iter::FromIterator}; use std::{collections::HashSet, env, iter::FromIterator};
use poise::serenity_prelude::model::prelude::AttachmentType;
use regex::Regex; use regex::Regex;
lazy_static! { 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], include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/webhook.jpg")) as &[u8],
"webhook.jpg", "webhook.jpg",
) );
.into();
pub static ref REGEX_CHANNEL_USER: Regex = Regex::new(r#"\s*<(#|@)(?:!)?(\d+)>\s*"#).unwrap(); pub static ref REGEX_CHANNEL_USER: Regex = Regex::new(r#"\s*<(#|@)(?:!)?(\d+)>\s*"#).unwrap();
pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter( pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(
env::var("PATREON_ROLE_ID") env::var("PATREON_ROLE_ID")

View File

@ -1,48 +1,44 @@
use std::{collections::HashMap, env}; use poise::serenity_prelude::ActivityData;
use log::error;
use poise::{ use poise::{
serenity_prelude as serenity, serenity_prelude as serenity,
serenity_prelude::{model::application::interaction::Interaction, utils::shard_id}, serenity_prelude::{CreateEmbed, CreateMessage, FullEvent},
}; };
use crate::{component_models::ComponentDataModel, Data, Error, THEME_COLOR}; use crate::{component_models::ComponentDataModel, Data, Error, THEME_COLOR};
pub async fn listener( pub async fn listener(
ctx: &serenity::Context, ctx: &serenity::Context,
event: &poise::Event<'_>, event: &FullEvent,
data: &Data, data: &Data,
) -> Result<(), Error> { ) -> Result<(), Error> {
match event { match event {
poise::Event::Ready { .. } => { FullEvent::Ready { .. } => {
ctx.set_activity(serenity::Activity::watching("for /remind")).await; ctx.set_activity(Some(ActivityData::watching("for /remind")));
} }
poise::Event::ChannelDelete { channel } => { FullEvent::ChannelDelete { channel, .. } => {
sqlx::query!("DELETE FROM channels WHERE channel = ?", channel.id.as_u64()) sqlx::query!("DELETE FROM channels WHERE channel = ?", channel.id.get())
.execute(&data.database) .execute(&data.database)
.await .await
.unwrap(); .unwrap();
} }
poise::Event::GuildCreate { guild, is_new } => { FullEvent::GuildCreate { guild, is_new } => {
if *is_new { if is_new.unwrap_or(false) {
let guild_id = guild.id.as_u64().to_owned(); let guild_id = guild.id.get().to_owned();
sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id) sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id)
.execute(&data.database) .execute(&data.database)
.await?; .await?;
if let Err(e) = post_guild_count(ctx, &data.http, guild_id).await {
error!("DiscordBotList: {:?}", e);
}
let default_channel = guild.default_channel_guaranteed(); let default_channel = guild.default_channel_guaranteed();
if let Some(default_channel) = default_channel { if let Some(default_channel) = default_channel {
default_channel default_channel.send_message(
.send_message(&ctx, |m| { &ctx,
m.embed(|e| { CreateMessage::new()
e.title("Thank you for adding Reminder Bot!").description( .embed(
"To get started: CreateEmbed::new()
.title("Thank you for adding Reminder Bot!")
.description("To get started:
Set your timezone with `/timezone` Set your timezone with `/timezone`
Set up permissions in Server Settings 🠚 Integrations 🠚 Reminder Bot (desktop only) Set up permissions in Server Settings 🠚 Integrations 🠚 Reminder Bot (desktop only)
Create your first reminder with `/remind` Create your first reminder with `/remind`
@ -52,24 +48,24 @@ If you need any support, please come and ask us! Join our [Discord](https://disc
__Updates__ __Updates__
To stay up to date on the latest features and fixes, join our [Discord](https://discord.jellywx.com). To stay up to date on the latest features and fixes, join our [Discord](https://discord.jellywx.com).
", ")
).color(*THEME_COLOR) .color(*THEME_COLOR)
}) )
}) )
.await?; .await?;
} }
} }
} }
poise::Event::GuildDelete { incomplete, .. } => { FullEvent::GuildDelete { incomplete, .. } => {
let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.0) let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.get())
.execute(&data.database) .execute(&data.database)
.await; .await;
} }
poise::Event::InteractionCreate { interaction } => { FullEvent::InteractionCreate { interaction } => {
if let Interaction::MessageComponent(component) = interaction { if let Some(component) = interaction.clone().message_component() {
let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id); let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id);
component_model.act(ctx, data, component).await; component_model.act(ctx, data, &component).await;
} }
} }
_ => {} _ => {}
@ -77,38 +73,3 @@ To stay up to date on the latest features and fixes, join our [Discord](https://
Ok(()) Ok(())
} }
async fn post_guild_count(
ctx: &serenity::Context,
http: &reqwest::Client,
guild_id: u64,
) -> Result<(), reqwest::Error> {
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
let shard_count = ctx.cache.shard_count();
let current_shard_id = shard_id(guild_id, shard_count);
let guild_count = ctx
.cache
.guilds()
.iter()
.filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id)
.count() as u64;
let mut hm = HashMap::new();
hm.insert("server_count", guild_count);
hm.insert("shard_id", current_shard_id);
hm.insert("shard_count", shard_count);
http.post(
format!("https://top.gg/api/bots/{}/stats", ctx.cache.current_user_id().as_u64())
.as_str(),
)
.header("Authorization", token)
.json(&hm)
.send()
.await
.map(|_| ())
} else {
Ok(())
}
}

View File

@ -1,42 +1,41 @@
use poise::{ use poise::{serenity_prelude::model::channel::Channel, CreateReply};
serenity_prelude::model::channel::Channel, ApplicationCommandOrAutocompleteInteraction,
};
use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error}; use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error};
async fn macro_check(ctx: Context<'_>) -> bool { async fn macro_check(ctx: Context<'_>) -> bool {
if let Context::Application(app_ctx) = ctx { if let Context::Application(app_ctx) = ctx {
if let ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(_) = if let Some(guild_id) = ctx.guild_id() {
app_ctx.interaction if ctx.command().identifying_name != "finish_macro" {
{ let mut lock = ctx.data().recording_macros.write().await;
if let Some(guild_id) = ctx.guild_id() {
if ctx.command().identifying_name != "finish_macro" {
let mut lock = ctx.data().recording_macros.write().await;
if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) { if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) {
if command_macro.commands.len() >= MACRO_MAX_COMMANDS { if command_macro.commands.len() >= MACRO_MAX_COMMANDS {
let _ = ctx.send(|m| { let _ = ctx
m.ephemeral(true).content( .send(
format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS), CreateReply::default()
) .ephemeral(true)
}) .content(format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS))
.await; )
} else {
let recorded = RecordedCommand {
action: None,
command_name: ctx.command().identifying_name.clone(),
options: Vec::from(app_ctx.args),
};
command_macro.commands.push(recorded);
let _ = ctx
.send(|m| m.ephemeral(true).content("Command recorded to macro"))
.await; .await;
} } else {
let recorded = RecordedCommand {
action: None,
command_name: ctx.command().identifying_name.clone(),
options: app_ctx.interaction.data.options.clone(),
};
return false; command_macro.commands.push(recorded);
let _ = ctx
.send(
CreateReply::default()
.ephemeral(true)
.content("Command recorded to macro"),
)
.await;
} }
return false;
} }
} }
} }
@ -46,11 +45,13 @@ async fn macro_check(ctx: Context<'_>) -> bool {
} }
async fn check_self_permissions(ctx: Context<'_>) -> bool { async fn check_self_permissions(ctx: Context<'_>) -> bool {
if let Some(guild) = ctx.guild() { if let Some(guild_id) = ctx.guild_id() {
let user_id = ctx.serenity_context().cache.current_user_id(); let user_id = ctx.serenity_context().cache.current_user().id;
let manage_webhooks = let manage_webhooks = guild_id
guild.member_permissions(&ctx, user_id).await.map_or(false, |p| p.manage_webhooks()); .current_user_member(&ctx)
.await
.map_or(false, |m| m.permissions(&ctx).map_or(false, |p| p.manage_webhooks()));
let (view_channel, send_messages, embed_links) = ctx let (view_channel, send_messages, embed_links) = ctx
.channel_id() .channel_id()
@ -72,20 +73,18 @@ async fn check_self_permissions(ctx: Context<'_>) -> bool {
true true
} else { } else {
let _ = ctx let _ = ctx
.send(|m| { .send(CreateReply::default().content(format!(
m.content(format!( "Please ensure the bot has the correct permissions:
"Please ensure the bot has the correct permissions:
{} **View Channel** {} **View Channel**
{} **Send Message** {} **Send Message**
{} **Embed Links** {} **Embed Links**
{} **Manage Webhooks**", {} **Manage Webhooks**",
if view_channel { "" } else { "" }, if view_channel { "" } else { "" },
if send_messages { "" } else { "" }, if send_messages { "" } else { "" },
if embed_links { "" } else { "" }, if embed_links { "" } else { "" },
if manage_webhooks { "" } else { "" }, if manage_webhooks { "" } else { "" },
)) )))
})
.await; .await;
false false

View File

@ -23,9 +23,12 @@ use std::{
use chrono_tz::Tz; use chrono_tz::Tz;
use log::{error, warn}; use log::{error, warn};
use poise::serenity_prelude::model::{ use poise::serenity_prelude::{
gateway::GatewayIntents, model::{
id::{GuildId, UserId}, gateway::GatewayIntents,
id::{GuildId, UserId},
},
ClientBuilder,
}; };
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use tokio::sync::{broadcast, broadcast::Sender, RwLock}; use tokio::sync::{broadcast, broadcast::Sender, RwLock};
@ -36,7 +39,6 @@ use crate::{
event_handlers::listener, event_handlers::listener,
hooks::all_checks, hooks::all_checks,
models::command_macro::CommandMacro, models::command_macro::CommandMacro,
utils::register_application_commands,
}; };
type Database = MySql; type Database = MySql;
@ -213,11 +215,10 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
.map(|t| t.timezone.parse::<Tz>().unwrap()) .map(|t| t.timezone.parse::<Tz>().unwrap())
.collect::<Vec<Tz>>(); .collect::<Vec<Tz>>();
poise::Framework::builder() let framework = poise::Framework::builder()
.token(discord_token)
.setup(move |ctx, _bot, framework| { .setup(move |ctx, _bot, framework| {
Box::pin(async move { Box::pin(async move {
register_application_commands(ctx, framework, None).await.unwrap(); poise::builtins::register_globally(ctx, &framework.options().commands).await?;
let kill_tx = tx.clone(); let kill_tx = tx.clone();
let kill_recv = tx.subscribe(); let kill_recv = tx.subscribe();
@ -261,9 +262,12 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
}) })
}) })
.options(options) .options(options)
.intents(GatewayIntents::GUILDS) .build();
.run_autosharded()
.await?; let mut client =
ClientBuilder::new(&discord_token, GatewayIntents::GUILDS).framework(framework).await?;
client.start_autosharded().await?;
Ok(()) Ok(())
} }

View File

@ -18,7 +18,7 @@ impl ChannelData {
channel: &Channel, channel: &Channel,
pool: &MySqlPool, pool: &MySqlPool,
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> { ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
let channel_id = channel.id().as_u64().to_owned(); let channel_id = channel.id().get().to_owned();
if let Ok(c) = sqlx::query_as_unchecked!( if let Ok(c) = sqlx::query_as_unchecked!(
Self, Self,
@ -30,7 +30,7 @@ impl ChannelData {
{ {
Ok(c) Ok(c)
} else { } else {
let props = channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name)); let props = channel.to_owned().guild().map(|g| (g.guild_id.get().to_owned(), g.name));
let (guild_id, channel_name) = if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) }; let (guild_id, channel_name) = if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };

View File

@ -1,6 +1,4 @@
use poise::serenity_prelude::model::{ use poise::serenity_prelude::{model::id::GuildId, CommandDataOption};
application::interaction::application_command::CommandDataOption, id::GuildId,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@ -45,7 +43,7 @@ pub async fn guild_command_macro(
" "
SELECT * FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ? SELECT * FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?
", ",
ctx.guild_id().unwrap().0, ctx.guild_id().unwrap().get(),
name name
) )
.fetch_one(&ctx.data().database) .fetch_one(&ctx.data().database)

View File

@ -14,21 +14,21 @@ impl GuildData {
if let Ok(c) = sqlx::query_as_unchecked!( if let Ok(c) = sqlx::query_as_unchecked!(
Self, Self,
"SELECT id, ephemeral_confirmations FROM guilds WHERE guild = ?", "SELECT id, ephemeral_confirmations FROM guilds WHERE guild = ?",
guild_id.0 guild_id.get()
) )
.fetch_one(pool) .fetch_one(pool)
.await .await
{ {
Ok(c) Ok(c)
} else { } else {
sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id.0) sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id.get())
.execute(&pool.clone()) .execute(&pool.clone())
.await?; .await?;
Ok(sqlx::query_as_unchecked!( Ok(sqlx::query_as_unchecked!(
Self, Self,
"SELECT id, ephemeral_confirmations FROM guilds WHERE guild = ?", "SELECT id, ephemeral_confirmations FROM guilds WHERE guild = ?",
guild_id.0 guild_id.get()
) )
.fetch_one(pool) .fetch_one(pool)
.await?) .await?)

View File

@ -6,7 +6,7 @@ pub mod timer;
pub mod user_data; pub mod user_data;
use chrono_tz::Tz; use chrono_tz::Tz;
use poise::serenity_prelude::{async_trait, model::id::UserId, ChannelType}; use poise::serenity_prelude::{async_trait, model::id::UserId};
use crate::{ use crate::{
models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData}, models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData},
@ -53,20 +53,7 @@ impl CtxData for Context<'_> {
async fn channel_data(&self) -> Result<ChannelData, Box<dyn std::error::Error + Sync + Send>> { async fn channel_data(&self) -> Result<ChannelData, Box<dyn std::error::Error + Sync + Send>> {
// If we're in a thread, get the parent channel. // If we're in a thread, get the parent channel.
let recv_channel = self.channel_id().to_channel(&self).await?; let channel = self.channel_id().to_channel(&self).await?;
let channel = match recv_channel.guild() {
Some(guild_channel) => {
if guild_channel.kind == ChannelType::PublicThread {
guild_channel.parent_id.unwrap().to_channel_cached(&self).unwrap()
} else {
self.channel_id().to_channel_cached(&self).unwrap()
}
}
None => self.channel_id().to_channel_cached(&self).unwrap(),
};
ChannelData::from_channel(&channel, &self.data().database).await ChannelData::from_channel(&channel, &self.data().database).await
} }
@ -82,7 +69,7 @@ impl Data {
) -> Result<Vec<CommandMacro<Data, Error>>, Error> { ) -> Result<Vec<CommandMacro<Data, Error>>, Error> {
let rows = sqlx::query!( let rows = sqlx::query!(
"SELECT name, description, commands FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)", "SELECT name, description, commands FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
guild_id.0 guild_id.get()
) )
.fetch_all(&self.database) .fetch_all(&self.database)
.await?.iter().map(|row| CommandMacro { .await?.iter().map(|row| CommandMacro {

View File

@ -1,4 +1,4 @@
use std::{collections::HashSet, fmt::Display}; use std::collections::HashSet;
use chrono::{Duration, NaiveDateTime, Utc}; use chrono::{Duration, NaiveDateTime, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
@ -9,8 +9,9 @@ use poise::serenity_prelude::{
id::{ChannelId, GuildId, UserId}, id::{ChannelId, GuildId, UserId},
webhook::Webhook, webhook::Webhook,
}, },
ChannelType, Result as SerenityResult, ChannelType, CreateWebhook, Result as SerenityResult,
}; };
use secrecy::ExposeSecret;
use sqlx::MySqlPool; use sqlx::MySqlPool;
use crate::{ use crate::{
@ -27,9 +28,9 @@ use crate::{
async fn create_webhook( async fn create_webhook(
ctx: impl CacheHttp, ctx: impl CacheHttp,
channel: GuildChannel, channel: GuildChannel,
name: impl Display, name: impl Into<String>,
) -> SerenityResult<Webhook> { ) -> SerenityResult<Webhook> {
channel.create_webhook_with_avatar(ctx.http(), name, DEFAULT_AVATAR.clone()).await channel.create_webhook(ctx.http(), CreateWebhook::new(name).avatar(&*DEFAULT_AVATAR)).await
} }
#[derive(Hash, PartialEq, Eq)] #[derive(Hash, PartialEq, Eq)]
@ -230,7 +231,7 @@ impl<'a> MultiReminderBuilder<'a> {
let thread_id = None; let thread_id = None;
let db_channel_id = match scope { let db_channel_id = match scope {
ReminderScope::User(user_id) => { ReminderScope::User(user_id) => {
if let Ok(user) = UserId(user_id).to_user(&self.ctx).await { if let Ok(user) = UserId::new(user_id).to_user(&self.ctx).await {
let user_data = UserData::from_user( let user_data = UserData::from_user(
&user, &user,
&self.ctx.serenity_context(), &self.ctx.serenity_context(),
@ -257,7 +258,8 @@ impl<'a> MultiReminderBuilder<'a> {
} }
} }
ReminderScope::Channel(channel_id) => { ReminderScope::Channel(channel_id) => {
let channel = ChannelId(channel_id).to_channel(&self.ctx).await.unwrap(); let channel =
ChannelId::new(channel_id).to_channel(&self.ctx).await.unwrap();
if let Some(mut guild_channel) = channel.clone().guild() { if let Some(mut guild_channel) = channel.clone().guild() {
if Some(guild_channel.guild_id) != self.guild_id { if Some(guild_channel.guild_id) != self.guild_id {
@ -290,8 +292,9 @@ impl<'a> MultiReminderBuilder<'a> {
{ {
Ok(webhook) => { Ok(webhook) => {
channel_data.webhook_id = channel_data.webhook_id =
Some(webhook.id.as_u64().to_owned()); Some(webhook.id.get().to_owned());
channel_data.webhook_token = webhook.token; channel_data.webhook_token =
webhook.token.map(|s| s.expose_secret().clone());
channel_data channel_data
.commit_changes(&self.ctx.data().database) .commit_changes(&self.ctx.data().database)

View File

@ -54,31 +54,31 @@ impl Reminder {
sqlx::query_as_unchecked!( sqlx::query_as_unchecked!(
Self, Self,
" "
SELECT SELECT
reminders.id, reminders.id,
reminders.uid, reminders.uid,
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days, reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
reminders.content, reminders.content,
reminders.embed_description, reminders.embed_description,
users.user AS set_by users.user AS set_by
FROM FROM
reminders reminders
INNER JOIN INNER JOIN
channels channels
ON ON
reminders.channel_id = channels.id reminders.channel_id = channels.id
LEFT JOIN LEFT JOIN
users users
ON ON
reminders.set_by = users.id reminders.set_by = users.id
WHERE WHERE
reminders.uid = ? reminders.uid = ?
", ",
uid uid
) )
@ -91,31 +91,31 @@ WHERE
sqlx::query_as_unchecked!( sqlx::query_as_unchecked!(
Self, Self,
" "
SELECT SELECT
reminders.id, reminders.id,
reminders.uid, reminders.uid,
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days, reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
reminders.content, reminders.content,
reminders.embed_description, reminders.embed_description,
users.user AS set_by users.user AS set_by
FROM FROM
reminders reminders
INNER JOIN INNER JOIN
channels channels
ON ON
reminders.channel_id = channels.id reminders.channel_id = channels.id
LEFT JOIN LEFT JOIN
users users
ON ON
reminders.set_by = users.id reminders.set_by = users.id
WHERE WHERE
reminders.id = ? reminders.id = ?
", ",
id id
) )
@ -135,37 +135,37 @@ WHERE
sqlx::query_as_unchecked!( sqlx::query_as_unchecked!(
Self, Self,
" "
SELECT SELECT
reminders.id, reminders.id,
reminders.uid, reminders.uid,
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days, reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
reminders.content, reminders.content,
reminders.embed_description, reminders.embed_description,
users.user AS set_by users.user AS set_by
FROM FROM
reminders reminders
INNER JOIN INNER JOIN
channels channels
ON ON
reminders.channel_id = channels.id reminders.channel_id = channels.id
LEFT JOIN LEFT JOIN
users users
ON ON
reminders.set_by = users.id reminders.set_by = users.id
WHERE WHERE
`status` = 'pending' AND `status` = 'pending' AND
channels.channel = ? AND channels.channel = ? AND
FIND_IN_SET(reminders.enabled, ?) FIND_IN_SET(reminders.enabled, ?)
ORDER BY ORDER BY
reminders.utc_time reminders.utc_time
", ",
channel_id.as_u64(), channel_id.get(),
enabled, enabled,
) )
.fetch_all(pool) .fetch_all(pool)
@ -180,119 +180,126 @@ ORDER BY
user: UserId, user: UserId,
) -> Vec<Self> { ) -> Vec<Self> {
if let Some(guild_id) = guild_id { if let Some(guild_id) = guild_id {
let guild_opt = guild_id.to_guild_cached(cache); let channel_query = if let Some(guild) = guild_id.to_guild_cached(&cache) {
Some(
if let Some(guild) = guild_opt { guild
let channels = guild .channels
.channels .keys()
.keys() .into_iter()
.into_iter() .map(|k| k.get().to_string())
.map(|k| k.as_u64().to_string()) .collect::<Vec<String>>()
.collect::<Vec<String>>() .join(","),
.join(",");
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
LEFT JOIN
channels
ON
channels.id = reminders.channel_id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
FIND_IN_SET(channels.channel, ?)
",
channels
) )
.fetch_all(pool)
.await
} else { } else {
sqlx::query_as_unchecked!( None
Self, };
"
SELECT match channel_query {
reminders.id, Some(channel_query) => {
reminders.uid, sqlx::query_as_unchecked!(
channels.channel, Self,
reminders.utc_time, "
reminders.interval_seconds, SELECT
reminders.interval_days, reminders.id,
reminders.interval_months, reminders.uid,
reminders.expires, channels.channel,
reminders.enabled, reminders.utc_time,
reminders.content, reminders.interval_seconds,
reminders.embed_description, reminders.interval_days,
users.user AS set_by reminders.interval_months,
FROM reminders.expires,
reminders reminders.enabled,
LEFT JOIN reminders.content,
channels reminders.embed_description,
ON users.user AS set_by
channels.id = reminders.channel_id FROM
LEFT JOIN reminders
users LEFT JOIN
ON channels
reminders.set_by = users.id ON
WHERE channels.id = reminders.channel_id
`status` = 'pending' AND LEFT JOIN
channels.guild_id = (SELECT id FROM guilds WHERE guild = ?) users
", ON
guild_id.as_u64() reminders.set_by = users.id
) WHERE
.fetch_all(pool) `status` = 'pending' AND
.await FIND_IN_SET(channels.channel, ?)
",
channel_query
)
.fetch_all(pool)
.await
}
None => {
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
LEFT JOIN
channels
ON
channels.id = reminders.channel_id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
",
guild_id.get()
)
.fetch_all(pool)
.await
}
} }
} else { } else {
sqlx::query_as_unchecked!( sqlx::query_as_unchecked!(
Self, Self,
" "
SELECT SELECT
reminders.id, reminders.id,
reminders.uid, reminders.uid,
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days, reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
reminders.content, reminders.content,
reminders.embed_description, reminders.embed_description,
users.user AS set_by users.user AS set_by
FROM FROM
reminders reminders
INNER JOIN INNER JOIN
channels channels
ON ON
channels.id = reminders.channel_id channels.id = reminders.channel_id
LEFT JOIN LEFT JOIN
users users
ON ON
reminders.set_by = users.id reminders.set_by = users.id
WHERE WHERE
`status` = 'pending' AND `status` = 'pending' AND
channels.id = (SELECT dm_channel FROM users WHERE user = ?) channels.id = (SELECT dm_channel FROM users WHERE user = ?)
", ",
user.as_u64() user.get()
) )
.fetch_all(pool) .fetch_all(pool)
.await .await
@ -304,10 +311,15 @@ WHERE
&self, &self,
db: impl Executor<'_, Database = Database>, db: impl Executor<'_, Database = Database>,
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", self.uid) sqlx::query!(
.execute(db) "
.await UPDATE reminders SET `status` = 'deleted' WHERE uid = ?
.map(|_| ()) ",
self.uid
)
.execute(db)
.await
.map(|_| ())
} }
pub fn display_content(&self) -> &str { pub fn display_content(&self) -> &str {

View File

@ -18,7 +18,7 @@ impl UserData {
where where
U: Into<UserId>, U: Into<UserId>,
{ {
let user_id = user.into().as_u64().to_owned(); let user_id = user.into().get().to_owned();
match sqlx::query!( match sqlx::query!(
" "
@ -50,7 +50,7 @@ SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ?
SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone, allowed_dm FROM users WHERE user = ? SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone, allowed_dm FROM users WHERE user = ?
", ",
*LOCAL_TIMEZONE, *LOCAL_TIMEZONE,
user_id.0 user_id.get()
) )
.fetch_one(pool) .fetch_one(pool)
.await .await
@ -65,7 +65,7 @@ SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone, allo
" "
INSERT IGNORE INTO channels (channel) VALUES (?) INSERT IGNORE INTO channels (channel) VALUES (?)
", ",
dm_channel.id.0 dm_channel.id.get()
) )
.execute(&pool_c) .execute(&pool_c)
.await?; .await?;
@ -74,8 +74,8 @@ INSERT IGNORE INTO channels (channel) VALUES (?)
" "
INSERT INTO users (name, user, dm_channel, timezone) VALUES ('', ?, (SELECT id FROM channels WHERE channel = ?), ?) INSERT INTO users (name, user, dm_channel, timezone) VALUES ('', ?, (SELECT id FROM channels WHERE channel = ?), ?)
", ",
user_id.0, user_id.get(),
dm_channel.id.0, dm_channel.id.get(),
*LOCAL_TIMEZONE *LOCAL_TIMEZONE
) )
.execute(&pool_c) .execute(&pool_c)
@ -86,7 +86,7 @@ INSERT INTO users (name, user, dm_channel, timezone) VALUES ('', ?, (SELECT id F
" "
SELECT id, user, dm_channel, timezone, allowed_dm FROM users WHERE user = ? SELECT id, user, dm_channel, timezone, allowed_dm FROM users WHERE user = ?
", ",
user_id.0 user_id.get()
) )
.fetch_one(pool) .fetch_one(pool)
.await?) .await?)

View File

@ -1,51 +1,21 @@
use poise::{ use poise::{
serenity_prelude as serenity,
serenity_prelude::{ serenity_prelude::{
builder::CreateApplicationCommands,
http::CacheHttp, http::CacheHttp,
interaction::MessageFlags,
model::id::{GuildId, UserId}, model::id::{GuildId, UserId},
CreateInteractionResponseMessage,
}, },
CreateReply,
}; };
use crate::{ use crate::consts::{CNC_GUILD, SUBSCRIPTION_ROLES};
consts::{CNC_GUILD, SUBSCRIPTION_ROLES},
Data, Error,
};
pub async fn register_application_commands(
ctx: &serenity::Context,
framework: &poise::Framework<Data, Error>,
guild_id: Option<GuildId>,
) -> Result<(), serenity::Error> {
let mut commands_builder = CreateApplicationCommands::default();
let commands = &framework.options().commands;
for command in commands {
if let Some(slash_command) = command.create_as_slash_command() {
commands_builder.add_application_command(slash_command);
}
if let Some(context_menu_command) = command.create_as_context_menu_command() {
commands_builder.add_application_command(context_menu_command);
}
}
let commands_builder = poise::serenity_prelude::json::Value::Array(commands_builder.0);
if let Some(guild_id) = guild_id {
ctx.http.create_guild_application_commands(guild_id.0, &commands_builder).await?;
} else {
ctx.http.create_global_application_commands(&commands_builder).await?;
}
Ok(())
}
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool { pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
if let Some(subscription_guild) = *CNC_GUILD { 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 { if let Ok(member) = guild_member {
for role in member.roles { for role in member.roles {
if SUBSCRIPTION_ROLES.contains(role.as_u64()) { if SUBSCRIPTION_ROLES.contains(&role.get()) {
return true; return true;
} }
} }
@ -70,39 +40,14 @@ pub async fn check_guild_subscription(
} }
} }
/// Sends the message, specified via [`crate::CreateReply`], to the interaction initial response pub fn reply_to_interaction_response_message(
/// endpoint reply: CreateReply,
pub fn send_as_initial_response( ) -> CreateInteractionResponseMessage {
data: poise::CreateReply<'_>, let mut builder = CreateInteractionResponseMessage::new().embeds(reply.embeds);
f: &mut serenity::CreateInteractionResponseData,
) {
let poise::CreateReply {
content,
embeds,
attachments: _, // serenity doesn't support attachments in initial response yet
components,
ephemeral,
allowed_mentions,
reply: _,
} = data;
if let Some(content) = content { if let Some(components) = reply.components {
f.content(content); builder = builder.components(components)
}
f.set_embeds(embeds);
if let Some(allowed_mentions) = allowed_mentions {
f.allowed_mentions(|f| {
*f = allowed_mentions.clone();
f
});
}
if let Some(components) = components {
f.components(|f| {
f.0 = components.0;
f
});
}
if ephemeral {
f.flags(MessageFlags::EPHEMERAL);
} }
builder
} }

View File

@ -7,10 +7,10 @@ edition = "2018"
[dependencies] [dependencies]
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tls", "secrets", "json"] } 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"] } 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" oauth2 = "4"
log = "0.4" log = "0.4"
reqwest = "0.11" reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] } sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] }
chrono = "0.4" chrono = "0.4"
@ -20,3 +20,4 @@ rand = "0.8"
base64 = "0.13" base64 = "0.13"
csv = "1.2" csv = "1.2"
prometheus = "0.13.3" 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 std::{collections::HashSet, env, iter::FromIterator};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serenity::model::prelude::AttachmentType; use serenity::builder::CreateAttachment;
lazy_static! { 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], include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/webhook.jpg")) as &[u8],
"webhook.jpg", "webhook.jpg",
) );
.into();
pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter( pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(
env::var("PATREON_ROLE_ID") env::var("PATREON_ROLE_ID")
.map(|var| var .map(|var| var

View File

@ -35,8 +35,8 @@ type Database = MySql;
#[derive(Debug)] #[derive(Debug)]
enum Error { enum Error {
SQLx(sqlx::Error), SQLx,
Serenity(serenity::Error), Serenity,
} }
pub async fn initialize( pub async fn initialize(
@ -169,11 +169,11 @@ pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<U
offline!(true); offline!(true);
if let Some(subscription_guild) = *CNC_GUILD { 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 { if let Ok(member) = guild_member {
for role in member.roles { for role in member.roles {
if SUBSCRIPTION_ROLES.contains(role.as_u64()) { if SUBSCRIPTION_ROLES.contains(&role.get()) {
return true; return true;
} }
} }
@ -191,9 +191,7 @@ pub async fn check_guild_subscription(
) -> bool { ) -> bool {
offline!(true); offline!(true);
if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) { if let Some(owner) = cache_http.cache().unwrap().guild(guild_id).map(|guild| guild.owner_id) {
let owner = guild.owner_id;
check_subscription(&cache_http, owner).await check_subscription(&cache_http, owner).await
} else { } else {
false false
@ -203,7 +201,7 @@ pub async fn check_guild_subscription(
pub async fn check_authorization( pub async fn check_authorization(
cookies: &CookieJar<'_>, cookies: &CookieJar<'_>,
ctx: &Context, ctx: &Context,
guild: u64, guild_id: u64,
) -> Result<(), JsonValue> { ) -> Result<(), JsonValue> {
let user_id = cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten(); 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(()); return Ok(());
} }
match GuildId(guild).to_guild_cached(ctx) { let guild_id = GuildId::new(guild_id);
Some(guild) => {
let member_res = guild.member(ctx, UserId(user_id)).await;
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(_) => { Err(_) => {
return Err(json!({"error": "User not in guild"})); return Err(json!({"error": "Couldn't fetch permissions"}));
} }
Ok(member) => { Ok(permissions) => {
let permissions_res = member.permissions(ctx); if !(permissions.manage_messages()
|| permissions.manage_guild()
match permissions_res { || permissions.administrator())
Err(_) => { {
return Err(json!({"error": "Couldn't fetch permissions"})); return Err(json!({"error": "Incorrect permissions"}));
}
Ok(permissions) => {
if !(permissions.manage_messages()
|| permissions.manage_guild()
|| permissions.administrator())
{
return Err(json!({"error": "Incorrect permissions"}));
}
}
} }
} }
} }
} }
None => { Err(_) => {
return Err(json!({"error": "Bot not in guild"})); 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?; 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) => { Some(guild) => {
let mut channels = guild let mut channels = guild
.channels .channels
.iter() .iter()
.filter_map(|(id, channel)| channel.to_owned().guild().map(|c| (id.to_owned(), c)))
.filter(|(_, channel)| channel.is_text_based()) .filter(|(_, channel)| channel.is_text_based())
.map(|(id, channel)| (id.to_owned(), channel.to_owned()))
.collect::<Vec<(ChannelId, GuildChannel)>>(); .collect::<Vec<(ChannelId, GuildChannel)>>();
channels.sort_by(|(_, c1), (_, c2)| c1.position.cmp(&c2.position)); 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" }))); offline!(Ok(json!({ "patreon": true, "name": "Guild" })));
check_authorization(cookies, ctx.inner(), id).await?; check_authorization(cookies, ctx.inner(), id).await?;
match GuildId(id).to_guild_cached(ctx.inner()) { match GuildId::new(id)
Some(guild) => { .to_guild_cached(ctx.inner())
let member_res = GuildId(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap()) .map(|guild| (guild.owner_id, guild.name.clone()))
.member(&ctx.inner(), guild.owner_id) {
Some((owner_id, name)) => {
let member_res = GuildId::new(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap())
.member(&ctx.inner(), owner_id)
.await; .await;
let patreon = member_res.map_or(false, |member| { let patreon = member_res.map_or(false, |member| {
member member
.roles .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"), None => json_err!("Bot not in guild"),

View File

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

View File

@ -39,7 +39,7 @@ pub async fn get_user_info(
if let Some(user_id) = if let Some(user_id) =
cookies.get_private("userid").map(|u| u.value().parse::<u64>().ok()).flatten() 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) .member(&ctx.inner(), user_id)
.await; .await;
@ -58,7 +58,7 @@ pub async fn get_user_info(
patreon: member_res.map_or(false, |member| { patreon: member_res.map_or(false, |member| {
member member
.roles .roles
.contains(&RoleId(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap())) .contains(&RoleId::new(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap()))
}), }),
timezone, 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 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 { match channels_res {
Ok(channels) => { Ok(channels) => {
let channels = channels let channels = channels
.keys() .keys()
.into_iter() .into_iter()
.map(|k| k.as_u64().to_string()) .map(|k| k.get().to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(","); .join(",");
@ -181,8 +181,8 @@ pub(crate) async fn import_reminders(
create_reminder( create_reminder(
ctx.inner(), ctx.inner(),
&mut transaction, &mut transaction,
GuildId(id), GuildId::new(id),
UserId(user_id), UserId::new(user_id),
reminder, reminder,
) )
.await?; .await?;
@ -289,7 +289,7 @@ pub async fn import_todos(
) -> JsonResult { ) -> JsonResult {
check_authorization(cookies, ctx.inner(), id).await?; 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 { match channels_res {
Ok(channels) => match base64::decode(&body.body) { Ok(channels) => match base64::decode(&body.body) {
@ -307,7 +307,7 @@ pub async fn import_todos(
match channel_id.parse::<u64>() { match channel_id.parse::<u64>() {
Ok(channel_id) => { 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)); query_params.push((record.value, Some(channel_id), id));
} else { } else {
return json_err!(format!( return json_err!(format!(

View File

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

View File

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