restructured a lot. everything updated to poise.

This commit is contained in:
2022-01-26 20:33:51 +00:00
parent bb54c0d2c0
commit b350007dae
15 changed files with 775 additions and 757 deletions
+145
View File
@@ -0,0 +1,145 @@
use std::sync::Arc;
use poise::serenity::{async_trait, model::id::GuildId};
use sqlx::mysql::MySqlPool;
use tokio::sync::RwLock;
use crate::{Context, Data};
#[derive(Clone)]
pub struct GuildData {
pub id: u64,
pub prefix: String,
pub volume: u8,
pub allow_greets: bool,
pub allowed_role: Option<u64>,
}
#[async_trait]
pub trait CtxGuildData {
async fn guild_data<G: Into<GuildId> + Send + Sync>(
&self,
guild_id: G,
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error>;
}
#[async_trait]
impl CtxGuildData for Context<'_> {
async fn guild_data<G: Into<GuildId> + Send + Sync>(
&self,
guild_id: G,
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error> {
self.data().guild_data(guild_id).await
}
}
#[async_trait]
impl CtxGuildData for Data {
async fn guild_data<G: Into<GuildId> + Send + Sync>(
&self,
guild_id: G,
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error> {
let guild_id = guild_id.into();
let x = if let Some(guild_data) = self.guild_data_cache.get(&guild_id) {
Ok(guild_data.clone())
} else {
let pool = self.database.clone();
match GuildData::from_id(guild_id, pool).await {
Ok(d) => {
let lock = Arc::new(RwLock::new(d));
self.guild_data_cache.insert(guild_id, lock.clone());
Ok(lock)
}
Err(e) => Err(e),
}
};
x
}
}
impl GuildData {
pub async fn from_id<G: Into<GuildId>>(
guild_id: G,
db_pool: MySqlPool,
) -> Result<GuildData, sqlx::Error> {
let guild_id = guild_id.into();
let guild_data = sqlx::query_as_unchecked!(
GuildData,
"
SELECT id, prefix, volume, allow_greets, allowed_role
FROM servers
WHERE id = ?
",
guild_id.as_u64()
)
.fetch_one(&db_pool)
.await;
match guild_data {
Err(sqlx::error::Error::RowNotFound) => {
Self::create_from_guild(guild_id, db_pool).await
}
d => d,
}
}
async fn create_from_guild<G: Into<GuildId>>(
guild_id: G,
db_pool: MySqlPool,
) -> Result<GuildData, sqlx::Error> {
let guild_id = guild_id.into();
sqlx::query!(
"
INSERT INTO servers (id)
VALUES (?)
",
guild_id.as_u64()
)
.execute(&db_pool)
.await?;
Ok(GuildData {
id: guild_id.as_u64().to_owned(),
prefix: String::from("?"),
volume: 100,
allow_greets: true,
allowed_role: None,
})
}
pub async fn commit(
&self,
db_pool: MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
sqlx::query!(
"
UPDATE servers
SET
prefix = ?,
volume = ?,
allow_greets = ?,
allowed_role = ?
WHERE
id = ?
",
self.prefix,
self.volume,
self.allow_greets,
self.allowed_role,
self.id
)
.execute(&db_pool)
.await?;
Ok(())
}
}
+87
View File
@@ -0,0 +1,87 @@
use poise::serenity::{async_trait, model::id::UserId};
use crate::Data;
#[async_trait]
pub trait JoinSoundCtx {
async fn join_sound<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Option<u32>;
async fn update_join_sound<U: Into<UserId> + Send + Sync>(
&self,
user_id: U,
join_id: Option<u32>,
);
}
#[async_trait]
impl JoinSoundCtx for Data {
async fn join_sound<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Option<u32> {
let user_id = user_id.into();
let x = if let Some(join_sound_id) = self.join_sound_cache.get(&user_id) {
join_sound_id.value().clone()
} else {
let join_sound_id = {
let pool = self.database.clone();
let join_id_res = sqlx::query!(
"
SELECT join_sound_id
FROM users
WHERE user = ?
",
user_id.as_u64()
)
.fetch_one(&pool)
.await;
if let Ok(row) = join_id_res {
row.join_sound_id
} else {
None
}
};
self.join_sound_cache.insert(user_id, join_sound_id);
join_sound_id
};
x
}
async fn update_join_sound<U: Into<UserId> + Send + Sync>(
&self,
user_id: U,
join_id: Option<u32>,
) {
let user_id = user_id.into();
self.join_sound_cache.insert(user_id, join_id);
let pool = self.database.clone();
let _ = sqlx::query!(
"
INSERT IGNORE INTO users (user)
VALUES (?)
",
user_id.as_u64()
)
.execute(&pool)
.await;
let _ = sqlx::query!(
"
UPDATE users
SET
join_sound_id = ?
WHERE
user = ?
",
join_id,
user_id.as_u64()
)
.execute(&pool)
.await;
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod guild_data;
pub mod join_sound;
pub mod sound;
+413
View File
@@ -0,0 +1,413 @@
use std::{env, path::Path};
use poise::serenity::async_trait;
use songbird::input::restartable::Restartable;
use sqlx::{mysql::MySqlPool, Error};
use tokio::{fs::File, io::AsyncWriteExt, process::Command};
use crate::{error::ErrorTypes, Data};
#[derive(Clone)]
pub struct Sound {
pub name: String,
pub id: u32,
pub public: bool,
pub server_id: u64,
pub uploader_id: Option<u64>,
}
impl PartialEq for Sound {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
#[async_trait]
pub trait SoundCtx {
async fn search_for_sound<G: Into<u64> + Send, U: Into<u64> + Send>(
&self,
query: &str,
guild_id: G,
user_id: U,
strict: bool,
) -> Result<Vec<Sound>, sqlx::Error>;
async fn autocomplete_user_sounds<U: Into<u64> + Send, G: Into<u64> + Send>(
&self,
query: &str,
user_id: U,
guild_id: G,
) -> Result<Vec<Sound>, sqlx::Error>;
async fn user_sounds<U: Into<u64> + Send>(&self, user_id: U)
-> Result<Vec<Sound>, sqlx::Error>;
async fn guild_sounds<G: Into<u64> + Send>(
&self,
guild_id: G,
) -> Result<Vec<Sound>, sqlx::Error>;
}
#[async_trait]
impl SoundCtx for Data {
async fn search_for_sound<G: Into<u64> + Send, U: Into<u64> + Send>(
&self,
query: &str,
guild_id: G,
user_id: U,
strict: bool,
) -> Result<Vec<Sound>, sqlx::Error> {
let guild_id = guild_id.into();
let user_id = user_id.into();
let db_pool = self.database.clone();
fn extract_id(s: &str) -> Option<u32> {
if s.len() > 3 && s.to_lowercase().starts_with("id:") {
match s[3..].parse::<u32>() {
Ok(id) => Some(id),
Err(_) => None,
}
} else if let Ok(id) = s.parse::<u32>() {
Some(id)
} else {
None
}
}
if let Some(id) = extract_id(&query) {
let sound = sqlx::query_as_unchecked!(
Sound,
"
SELECT name, id, public, server_id, uploader_id
FROM sounds
WHERE id = ? AND (
public = 1 OR
uploader_id = ? OR
server_id = ?
)
",
id,
user_id,
guild_id
)
.fetch_all(&db_pool)
.await?;
Ok(sound)
} else {
let name = query;
let sound;
if strict {
sound = sqlx::query_as_unchecked!(
Sound,
"
SELECT name, id, public, server_id, uploader_id
FROM sounds
WHERE name = ? AND (
public = 1 OR
uploader_id = ? OR
server_id = ?
)
ORDER BY uploader_id = ? DESC, server_id = ? DESC, public = 1 DESC, rand()
",
name,
user_id,
guild_id,
user_id,
guild_id
)
.fetch_all(&db_pool)
.await?;
} else {
sound = sqlx::query_as_unchecked!(
Sound,
"
SELECT name, id, public, server_id, uploader_id
FROM sounds
WHERE name LIKE CONCAT('%', ?, '%') AND (
public = 1 OR
uploader_id = ? OR
server_id = ?
)
ORDER BY uploader_id = ? DESC, server_id = ? DESC, public = 1 DESC, rand()
",
name,
user_id,
guild_id,
user_id,
guild_id
)
.fetch_all(&db_pool)
.await?;
}
Ok(sound)
}
}
async fn autocomplete_user_sounds<U: Into<u64> + Send, G: Into<u64> + Send>(
&self,
query: &str,
user_id: U,
guild_id: G,
) -> Result<Vec<Sound>, Error> {
let db_pool = self.database.clone();
sqlx::query_as_unchecked!(
Sound,
"
SELECT name, id, public, server_id, uploader_id
FROM sounds
WHERE name LIKE CONCAT(?, '%') AND (uploader_id = ? OR server_id = ?)
LIMIT 25
",
query,
user_id.into(),
guild_id.into(),
)
.fetch_all(&db_pool)
.await
}
async fn user_sounds<U: Into<u64> + Send>(
&self,
user_id: U,
) -> Result<Vec<Sound>, sqlx::Error> {
let sounds = sqlx::query_as_unchecked!(
Sound,
"
SELECT name, id, public, server_id, uploader_id
FROM sounds
WHERE uploader_id = ?
",
user_id.into()
)
.fetch_all(&self.database)
.await?;
Ok(sounds)
}
async fn guild_sounds<G: Into<u64> + Send>(
&self,
guild_id: G,
) -> Result<Vec<Sound>, sqlx::Error> {
let sounds = sqlx::query_as_unchecked!(
Sound,
"
SELECT name, id, public, server_id, uploader_id
FROM sounds
WHERE server_id = ?
",
guild_id.into()
)
.fetch_all(&self.database)
.await?;
Ok(sounds)
}
}
impl Sound {
async fn src(&self, db_pool: MySqlPool) -> Vec<u8> {
struct Src {
src: Vec<u8>,
}
let record = sqlx::query_as_unchecked!(
Src,
"
SELECT src
FROM sounds
WHERE id = ?
LIMIT 1
",
self.id
)
.fetch_one(&db_pool)
.await
.unwrap();
record.src
}
pub async fn store_sound_source(
&self,
db_pool: MySqlPool,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let caching_location = env::var("CACHING_LOCATION").unwrap_or(String::from("/tmp"));
let path_name = format!("{}/sound-{}", caching_location, self.id);
let path = Path::new(&path_name);
if !path.exists() {
let mut file = File::create(&path).await?;
file.write_all(&self.src(db_pool).await).await?;
}
Ok(path_name)
}
pub async fn playable(
&self,
db_pool: MySqlPool,
) -> Result<Restartable, Box<dyn std::error::Error + Send + Sync>> {
let path_name = self.store_sound_source(db_pool).await?;
Ok(Restartable::ffmpeg(path_name, false)
.await
.expect("FFMPEG ERROR!"))
}
pub async fn count_user_sounds<U: Into<u64>>(
user_id: U,
db_pool: MySqlPool,
) -> Result<u32, sqlx::error::Error> {
let user_id = user_id.into();
let c = sqlx::query!(
"
SELECT COUNT(1) as count
FROM sounds
WHERE uploader_id = ?
",
user_id
)
.fetch_one(&db_pool)
.await?
.count;
Ok(c as u32)
}
pub async fn count_named_user_sounds<U: Into<u64>>(
user_id: U,
name: &String,
db_pool: MySqlPool,
) -> Result<u32, sqlx::error::Error> {
let user_id = user_id.into();
let c = sqlx::query!(
"
SELECT COUNT(1) as count
FROM sounds
WHERE
uploader_id = ? AND
name = ?
",
user_id,
name
)
.fetch_one(&db_pool)
.await?
.count;
Ok(c as u32)
}
pub async fn commit(
&self,
db_pool: MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
sqlx::query!(
"
UPDATE sounds
SET
public = ?
WHERE
id = ?
",
self.public,
self.id
)
.execute(&db_pool)
.await?;
Ok(())
}
pub async fn delete(
&self,
db_pool: MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
sqlx::query!(
"
DELETE
FROM sounds
WHERE id = ?
",
self.id
)
.execute(&db_pool)
.await?;
Ok(())
}
pub async fn create_anon<G: Into<u64>, U: Into<u64>>(
name: &str,
src_url: &str,
server_id: G,
user_id: U,
db_pool: MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + Send>> {
let server_id = server_id.into();
let user_id = user_id.into();
async fn process_src(src_url: &str) -> Option<Vec<u8>> {
let output = Command::new("ffmpeg")
.kill_on_drop(true)
.arg("-i")
.arg(src_url)
.arg("-loglevel")
.arg("error")
.arg("-b:a")
.arg("28000")
.arg("-f")
.arg("opus")
.arg("-fs")
.arg("1048576")
.arg("pipe:1")
.output()
.await;
match output {
Ok(out) => {
if out.status.success() {
Some(out.stdout)
} else {
None
}
}
Err(_) => None,
}
}
let source = process_src(src_url).await;
match source {
Some(data) => {
match sqlx::query!(
"
INSERT INTO sounds (name, server_id, uploader_id, public, src)
VALUES (?, ?, ?, 1, ?)
",
name,
server_id,
user_id,
data
)
.execute(&db_pool)
.await
{
Ok(_) => Ok(()),
Err(e) => Err(Box::new(e)),
}
}
None => Err(Box::new(ErrorTypes::InvalidFile)),
}
}
}