320 lines
9.2 KiB
Rust
320 lines
9.2 KiB
Rust
use crate::{Context, Error};
|
|
use futures::{Stream, StreamExt};
|
|
use poise::CreateReply;
|
|
use rusqlite::Connection;
|
|
use serenity::all::CreateEmbed;
|
|
|
|
// Define your FAQ struct
|
|
struct Faq {
|
|
title: String,
|
|
description: String,
|
|
}
|
|
|
|
fn format_embed_description<S: Into<String>>(description: S) -> Result<String, Error> {
|
|
let string_val = description.into();
|
|
Ok(str::replace(&string_val, "\\n", "\n"))
|
|
}
|
|
|
|
fn faq_exists(conn: &Connection, faq: &str, guild_id: &u64) -> Result<bool, Error> {
|
|
let guild_id = guild_id.to_string();
|
|
let exists: bool = conn.query_row(
|
|
"SELECT EXISTS(SELECT 1 FROM faqs WHERE title = ?1 AND guild_id = ?2)",
|
|
[faq, &guild_id],
|
|
|row| row.get(0),
|
|
)?;
|
|
|
|
Ok(exists)
|
|
}
|
|
|
|
async fn autocomplete_faq_titles<'a>(
|
|
ctx: Context<'_>,
|
|
partial: &'a str,
|
|
) -> impl Stream<Item = String> + 'a {
|
|
let data = ctx.data();
|
|
|
|
let fetch_faqs = data
|
|
.execute_db({
|
|
let server_guild_id = ctx.guild_id().expect("Guild ID not found");
|
|
let guild_id = server_guild_id.to_string();
|
|
move |conn| {
|
|
let mut stmt = conn.prepare(
|
|
"SELECT id, guild_id, title, description FROM faqs WHERE guild_id = ?1",
|
|
)?;
|
|
|
|
let faqs: Vec<Faq> = stmt
|
|
.query_map([&guild_id], |row| {
|
|
Ok(Faq {
|
|
title: row.get(2)?,
|
|
description: row.get(3)?,
|
|
})
|
|
})?
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(faqs)
|
|
}
|
|
})
|
|
.await;
|
|
|
|
let faqs: Vec<Faq> = fetch_faqs.unwrap_or(vec![]);
|
|
|
|
// Now you can use the faqs Vec
|
|
// ctx.say(format!("Found {} FAQs", faqs.len())).await?;
|
|
let partial_lowercase = partial.to_lowercase();
|
|
|
|
futures::stream::iter(faqs)
|
|
.filter(move |faq| {
|
|
futures::future::ready(faq.title.to_lowercase().contains(&partial_lowercase))
|
|
})
|
|
.map(|faq| faq.title)
|
|
}
|
|
|
|
#[poise::command(
|
|
slash_command,
|
|
subcommands("view", "preview", "add", "edit", "delete"),
|
|
guild_only
|
|
)]
|
|
pub async fn faq(_ctx: Context<'_>) -> Result<(), Error> {
|
|
Ok(())
|
|
}
|
|
|
|
/// View FAQ
|
|
#[poise::command(prefix_command, slash_command)]
|
|
pub async fn view(
|
|
ctx: Context<'_>,
|
|
#[description = "FAQ Title"]
|
|
#[autocomplete = "autocomplete_faq_titles"]
|
|
faq_title: String,
|
|
) -> Result<(), Error> {
|
|
let data = ctx.data();
|
|
|
|
let faqs = data
|
|
.execute_db({
|
|
let server_guild_id = ctx.guild_id().expect("Guild ID not found");
|
|
let guild_id = server_guild_id.to_string();
|
|
|
|
move |conn| {
|
|
let mut stmt = conn.prepare(
|
|
"SELECT id, guild_id, title, description FROM faqs WHERE title = ?1 AND guild_id = ?2"
|
|
)?;
|
|
|
|
let faqs: Vec<Faq> = stmt
|
|
.query_map([&faq_title, &guild_id], |row| {
|
|
Ok(Faq {
|
|
// id: row.get(0)?,
|
|
// guild_id: row.get(1)?,
|
|
title: row.get(2)?,
|
|
description: row.get(3)?,
|
|
})
|
|
})?
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(faqs)
|
|
}})
|
|
.await?;
|
|
|
|
let faq = faqs.first().unwrap();
|
|
|
|
let embed_description = format_embed_description(&faq.description).expect("Format error");
|
|
let embed = CreateEmbed::new()
|
|
.title(&faq.title)
|
|
.description(embed_description);
|
|
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Preview FAQ
|
|
#[poise::command(
|
|
prefix_command,
|
|
slash_command,
|
|
required_permissions = "MANAGE_MESSAGES" // mod only
|
|
)]
|
|
pub async fn preview(
|
|
ctx: Context<'_>,
|
|
#[description = "FAQ Title"]
|
|
#[autocomplete = "autocomplete_faq_titles"]
|
|
faq_title: String,
|
|
) -> Result<(), Error> {
|
|
let data = ctx.data();
|
|
|
|
let faqs = data
|
|
.execute_db({
|
|
let server_guild_id = ctx.guild_id().expect("Guild ID not found");
|
|
let guild_id = server_guild_id.to_string();
|
|
|
|
move |conn| {
|
|
let mut stmt = conn.prepare(
|
|
"SELECT id, guild_id, title, description FROM faqs WHERE title = ?1 AND guild_id = ?2"
|
|
)?;
|
|
|
|
let faqs: Vec<Faq> = stmt
|
|
.query_map([&faq_title, &guild_id], |row| {
|
|
Ok(Faq {
|
|
// id: row.get(0)?,
|
|
// guild_id: row.get(1)?,
|
|
title: row.get(2)?,
|
|
description: row.get(3)?,
|
|
})
|
|
})?
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(faqs)
|
|
}})
|
|
.await?;
|
|
|
|
let faq = faqs.first().unwrap();
|
|
|
|
// let embed_description = format_embed_description(&faq.description).expect("Format error");
|
|
let embed = CreateEmbed::new().title(&faq.title).description(format!(
|
|
"```
|
|
{}
|
|
```",
|
|
&faq.description
|
|
));
|
|
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Add FAQ
|
|
#[poise::command(
|
|
prefix_command,
|
|
slash_command,
|
|
required_permissions = "MANAGE_MESSAGES" // mod only
|
|
)]
|
|
pub async fn add(
|
|
ctx: Context<'_>,
|
|
#[description = "FAQ title"] title: String,
|
|
#[description = "FAQ description"] description: String,
|
|
) -> Result<(), Error> {
|
|
let data: &crate::Data = ctx.data();
|
|
|
|
let result = data
|
|
.execute_db({
|
|
let title = title.clone();
|
|
let description = description.clone();
|
|
|
|
let server_guild_id = ctx.guild_id().expect("Guild ID not found");
|
|
let guild_id = server_guild_id.get();
|
|
move |conn| {
|
|
let exists = faq_exists(conn, &title, &guild_id).unwrap_or(false);
|
|
|
|
if !exists {
|
|
conn.execute(
|
|
"INSERT INTO faqs (guild_id, title, description) VALUES (?1, ?2, ?3)",
|
|
[guild_id.to_string(), title, description],
|
|
)
|
|
} else {
|
|
Ok(0)
|
|
}
|
|
}
|
|
})
|
|
.await?;
|
|
|
|
if result == 1 {
|
|
let embed_description = format_embed_description(description).expect("Format error");
|
|
let embed = CreateEmbed::new()
|
|
.title(format!("Added FAQ: {}", &title))
|
|
.description(embed_description);
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
} else {
|
|
let embed = CreateEmbed::new()
|
|
.title(format!("Error adding FAQ: {}", &title))
|
|
.description("Title already exists!");
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Edit FAQ
|
|
#[poise::command(
|
|
prefix_command,
|
|
slash_command,
|
|
required_permissions = "MANAGE_MESSAGES"
|
|
)]
|
|
pub async fn edit(
|
|
ctx: Context<'_>,
|
|
#[description = "FAQ Title"]
|
|
#[autocomplete = "autocomplete_faq_titles"]
|
|
title: String,
|
|
#[description = "FAQ description"] description: String,
|
|
) -> Result<(), Error> {
|
|
let data = ctx.data();
|
|
|
|
let result = data
|
|
.execute_db({
|
|
let title = title.clone();
|
|
let description = description.clone();
|
|
|
|
let server_guild_id = ctx.guild_id().expect("Guild ID not found");
|
|
let guild_id = server_guild_id.get();
|
|
|
|
move |conn| {
|
|
conn.execute(
|
|
"UPDATE faqs SET description = ?3 WHERE title = ?1 AND guild_id = ?2",
|
|
[title, guild_id.to_string(), description],
|
|
)
|
|
}
|
|
})
|
|
.await?;
|
|
|
|
let embed_description = format_embed_description(description).expect("Format error");
|
|
|
|
if result == 1 {
|
|
let embed = CreateEmbed::new()
|
|
.title(format!("Edited FAQ: {}", &title))
|
|
.description(&embed_description);
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
} else {
|
|
let embed = CreateEmbed::new()
|
|
.title(format!("Error editing FAQ: {}", &title))
|
|
.description(format!("{} rows were affected", result));
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Delete FAQ
|
|
#[poise::command(
|
|
prefix_command,
|
|
slash_command,
|
|
required_permissions = "MANAGE_MESSAGES"
|
|
)]
|
|
pub async fn delete(
|
|
ctx: Context<'_>,
|
|
#[description = "FAQ Title"]
|
|
#[autocomplete = "autocomplete_faq_titles"]
|
|
title: String,
|
|
) -> Result<(), Error> {
|
|
let data = ctx.data();
|
|
let server_guild_id = ctx.guild_id().expect("Guild ID not found");
|
|
let guild_id = server_guild_id.to_string();
|
|
|
|
let result = data
|
|
.execute_db({
|
|
let title = title.clone();
|
|
move |conn| {
|
|
conn.execute(
|
|
"DELETE FROM faqs WHERE title = ?1 AND guild_id = ?2",
|
|
[title, guild_id],
|
|
)
|
|
}
|
|
})
|
|
.await?;
|
|
|
|
if result == 1 {
|
|
let embed = CreateEmbed::new().title(format!("Deleted FAQ: {}", &title));
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
} else {
|
|
let embed = CreateEmbed::new()
|
|
.title(format!("Error deleting FAQ: {}", &title))
|
|
.description(format!("{} rows were affected", result));
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|