Wip commit

This commit is contained in:
jude 2024-01-06 19:48:17 +00:00
parent cce0de7c75
commit e5e621be5c
22 changed files with 485 additions and 823 deletions

201
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",
] ]
@ -2369,8 +2344,9 @@ dependencies = [
"reqwest", "reqwest",
"rocket", "rocket",
"rocket_dyn_templates", "rocket_dyn_templates",
"secrecy",
"serde", "serde",
"serenity 0.11.7", "serenity",
"sqlx", "sqlx",
] ]
@ -2416,25 +2392,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 +2406,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 +2565,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 +2572,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 +2584,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 +2622,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 +2632,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 +2685,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 +2740,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 +2804,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 +2831,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 +3033,7 @@ dependencies = [
"tokio-stream", "tokio-stream",
"tracing", "tracing",
"url", "url",
"webpki-roots 0.25.3", "webpki-roots",
] ]
[[package]] [[package]]
@ -3536,17 +3434,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 +3477,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 +3609,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 +3629,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 +3803,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 +3965,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

@ -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

@ -1,4 +1,4 @@
use poise::CreateReply; use poise::serenity_prelude::{CreateEmbed, CreateEmbedFooter, CreateInteractionResponseMessage};
use crate::{ use crate::{
component_models::pager::{MacroPager, Pager}, component_models::pager::{MacroPager, Pager},
@ -33,19 +33,19 @@ pub fn max_macro_page<U, E>(macros: &[CommandMacro<U, E>]) -> usize {
((macros.len() as f64) / 25.0).ceil() as usize ((macros.len() as f64) / 25.0).ceil() as usize
} }
pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> CreateReply { pub fn show_macro_page<U, E>(
macros: &[CommandMacro<U, E>],
page: usize,
) -> CreateInteractionResponseMessage {
let pager = MacroPager::new(page); let pager = MacroPager::new(page);
if macros.is_empty() { if macros.is_empty() {
let mut reply = CreateReply::default(); return CreateInteractionResponseMessage::new().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 +70,13 @@ pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> Crea
} }
}); });
let mut reply = CreateReply::default(); CreateInteractionResponseMessage::new()
.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

@ -5,8 +5,12 @@ 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::{
CreateReply, Modal, builder::CreateEmbed, model::channel::Channel, ButtonStyle, CreateActionRow,
CreateEmbedFooter, CreateInteractionResponseMessage, CreateSelectMenu,
CreateSelectMenuKind, CreateSelectMenuOption, ReactionType,
},
Modal,
}; };
use crate::{ use crate::{
@ -329,20 +333,22 @@ pub fn max_delete_page(reminders: &[Reminder], timezone: &Tz) -> usize {
}) })
} }
pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> CreateReply { pub fn show_delete_page(
reminders: &[Reminder],
page: usize,
timezone: Tz,
) -> CreateInteractionResponseMessage {
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 CreateInteractionResponseMessage::new()
.embed(|e| e.title("Delete Reminders").description("No Reminders").color(*THEME_COLOR)) .embed(embed)
.components(|comp| { .components(vec![pager.create_button_row(0)]);
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 +397,38 @@ 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); if c.len() > 100 {
format!(
"{}...",
reminder.display_content().chars().take(97).collect::<String>()
)
} else {
c.to_string()
}
comp.create_action_row(|row| { CreateSelectMenuOption::new(count + first_num, reminder.id).description(c)
row.create_select_menu(|menu| {
menu.custom_id(del_selector.to_custom_id()).options(|opt| {
for (count, reminder) in shown_reminders.iter().enumerate() {
opt.create_option(|o| {
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 CreateInteractionResponseMessage::new()
.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,7 +460,7 @@ 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;

View File

@ -1,4 +1,7 @@
use poise::CreateReply; use poise::serenity_prelude::{
CreateActionRow, CreateEmbed, CreateEmbedFooter, CreateInteractionResponseMessage,
CreateSelectMenu, CreateSelectMenuKind, CreateSelectMenuOption,
};
use crate::{ use crate::{
component_models::{ component_models::{
@ -251,7 +254,7 @@ pub fn show_todo_page(
user_id: Option<u64>, user_id: Option<u64>,
channel_id: Option<u64>, channel_id: Option<u64>,
guild_id: Option<u64>, guild_id: Option<u64>,
) -> CreateReply { ) -> CreateInteractionResponseMessage {
let pager = TodoPager::new(page, user_id, channel_id, guild_id); let pager = TodoPager::new(page, user_id, channel_id, guild_id);
let pages = max_todo_page(todo_values); let pages = max_todo_page(todo_values);
@ -306,61 +309,51 @@ pub fn show_todo_page(
}; };
if todo_ids.is_empty() { if todo_ids.is_empty() {
let mut reply = CreateReply::default(); CreateInteractionResponseMessage::new().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(); CreateInteractionResponseMessage::new()
.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;
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,
for (count, (id, disp)) in todo_ids.iter().zip(&display_vec).enumerate() )
{ .description(c)
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,9 @@ use log::warn;
use poise::{ use poise::{
serenity_prelude as serenity, serenity_prelude as serenity,
serenity_prelude::{ serenity_prelude::{
builder::CreateEmbed, builder::CreateEmbed, model::channel::Channel, ComponentInteraction,
model::{ ComponentInteractionDataKind, Context, CreateEmbedFooter, CreateInteractionResponse,
application::interaction::{ CreateInteractionResponseMessage,
message_component::MessageComponentInteraction, InteractionResponseType,
MessageFlags,
},
channel::Channel,
},
Context,
}, },
}; };
use rmp_serde::Serializer; use rmp_serde::Serializer;
@ -31,7 +25,6 @@ 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,
Data, Data,
}; };
@ -64,12 +57,12 @@ 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_opt = component.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::Guild(channel)) = channel_opt {
if Some(channel.guild_id) == component.guild_id { if Some(channel.guild_id) == component.guild_id {
@ -89,12 +82,13 @@ impl ComponentDataModel {
.fold(0, |t, r| t + r.len()) .fold(0, |t, r| t + r.len())
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH); .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
let channel_name = let channel_name = if let Some(Channel::Guild(channel)) =
if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) { channel_id.to_channel_cached(&ctx.cache)
Some(channel.name) {
} else { Some(channel.name)
None } else {
}; None
};
let next_page = pager.next_page(pages); let next_page = pager.next_page(pages);
@ -107,7 +101,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 +111,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 +145,47 @@ 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(&ctx, CreateInteractionResponse::UpdateMessage(resp))
f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
|d| {
send_as_initial_response(resp, d);
d
},
)
})
.await; .await;
} }
ComponentDataModel::DelSelector(selector) => { ComponentDataModel::DelSelector(selector) => {
let selected_id = component.data.values.join(","); if let ComponentInteractionDataKind::StringSelect(s) = component.data.kind {
let selected_id = s.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(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 +196,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 +211,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 +237,74 @@ WHERE guilds.guild = ?",
); );
let _ = component let _ = component
.create_interaction_response(&ctx, |f| { .create_response(&ctx, CreateInteractionResponse::UpdateMessage(resp))
f.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
send_as_initial_response(resp, d);
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(s) = component.data.kind {
let selected_id = s.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(&ctx, CreateInteractionResponse::UpdateMessage(resp))
f.kind(InteractionResponseType::UpdateMessage) .await;
.interaction_response_data(|d| { }
send_as_initial_response(resp, d);
d
})
})
.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 +317,7 @@ 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(&ctx, CreateInteractionResponse::UpdateMessage(resp))
f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
|d| {
send_as_initial_response(resp, d);
d
},
)
})
.await; .await;
} }
ComponentDataModel::UndoReminder(undo_reminder) => { ComponentDataModel::UndoReminder(undo_reminder) => {
@ -355,58 +329,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: Vec::from(app_ctx.args),
};
return false; command_macro.commands.push(recorded);
let _ = ctx
.send(
CreateReply::default()
.ephemeral(true)
.content("Command recorded to macro"),
)
.await;
} }
return false;
} }
} }
} }
@ -47,10 +46,12 @@ 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) = ctx.guild() {
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
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)
.run_autosharded()
.await?; .await?;
let mut client =
ClientBuilder::new(&discord_token, GatewayIntents::GUILDS).framework(framework).await?;
client.start_autosharded().await?;
Ok(()) Ok(())
} }

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;

View File

@ -1,43 +1,9 @@
use poise::{ use poise::serenity_prelude::{
serenity_prelude as serenity, http::CacheHttp,
serenity_prelude::{ model::id::{GuildId, UserId},
builder::CreateApplicationCommands,
http::CacheHttp,
interaction::MessageFlags,
model::id::{GuildId, UserId},
},
}; };
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 {
@ -69,40 +35,3 @@ pub async fn check_guild_subscription(
false false
} }
} }
/// Sends the message, specified via [`crate::CreateReply`], to the interaction initial response
/// endpoint
pub fn send_as_initial_response(
data: poise::CreateReply<'_>,
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 {
f.content(content);
}
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);
}
}

View File

@ -7,7 +7,7 @@ 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 = "0.11"
@ -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

@ -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;
} }
} }
@ -217,9 +217,9 @@ pub async fn check_authorization(
return Ok(()); return Ok(());
} }
match GuildId(guild).to_guild_cached(ctx) { match GuildId::new(guild).to_guild_cached(ctx) {
Some(guild) => { Some(guild) => {
let member_res = guild.member(ctx, UserId(user_id)).await; let member_res = guild.member(ctx, UserId::new(user_id)).await;
match member_res { match member_res {
Err(_) => { Err(_) => {

View File

@ -32,7 +32,7 @@ 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

View File

@ -22,16 +22,16 @@ 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).to_guild_cached(ctx.inner()) {
Some(guild) => { Some(guild) => {
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(), guild.owner_id) .member(&ctx.inner(), guild.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": guild.name }))

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

@ -8,8 +8,11 @@ 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, http::Http,
model::id::{ChannelId, GuildId, UserId}, model::id::{ChannelId, GuildId, UserId},
@ -363,12 +366,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()
@ -380,11 +383,11 @@ pub(crate) async fn create_reminder(
} }
// validate channel // validate channel
let channel = ChannelId(reminder.channel).to_channel_cached(&ctx); let channel = ChannelId::new(reminder.channel).to_channel_cached(&ctx.cache);
let channel_exists = channel.is_some(); 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!(
@ -395,7 +398,8 @@ pub(crate) async fn create_reminder(
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,28 +594,34 @@ 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(|e| Error::Serenity(e))?;
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
@ -624,20 +634,24 @@ 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(|e| Error::Serenity(e))?;
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
@ -649,7 +663,7 @@ async fn create_database_channel(
Err(e) => Err(Error::SQLx(e)), Err(e) => Err(Error::SQLx(e)),
}?; }?;
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(|e| Error::SQLx(e))?;

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"))))