parent
1ed1e55db4
commit
f5e976103c
|
@ -34,6 +34,7 @@
|
||||||
pkg-config
|
pkg-config
|
||||||
gcc
|
gcc
|
||||||
openssl
|
openssl
|
||||||
|
pkg-config
|
||||||
python3
|
python3
|
||||||
ffmpeg
|
ffmpeg
|
||||||
cmake
|
cmake
|
||||||
|
@ -47,7 +48,8 @@
|
||||||
name = "memejoin-rs";
|
name = "memejoin-rs";
|
||||||
version = "0.1.2-alpha";
|
version = "0.1.2-alpha";
|
||||||
src = self;
|
src = self;
|
||||||
nativeBuildInputs = [ local-rust cmake gcc libopus ];
|
buildInputs = [ openssl.dev ];
|
||||||
|
nativeBuildInputs = [ local-rust pkg-config openssl openssl.dev cmake gcc libopus ];
|
||||||
|
|
||||||
cargoLock = {
|
cargoLock = {
|
||||||
lockFile = ./Cargo.lock;
|
lockFile = ./Cargo.lock;
|
||||||
|
@ -59,7 +61,7 @@
|
||||||
tag = "0.1.2-alpha";
|
tag = "0.1.2-alpha";
|
||||||
copyToRoot = buildEnv {
|
copyToRoot = buildEnv {
|
||||||
name = "image-root";
|
name = "image-root";
|
||||||
paths = [ default ffmpeg libopus youtube-dl ];
|
paths = [ default cacert openssl openssl.dev ffmpeg libopus youtube-dl yt-dlp ];
|
||||||
};
|
};
|
||||||
runAsRoot = ''
|
runAsRoot = ''
|
||||||
#!${runtimeShell}
|
#!${runtimeShell}
|
||||||
|
|
|
@ -27,8 +27,8 @@ pub(crate) struct User {
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub(crate) struct Permissions(u8);
|
pub(crate) struct Permissions(pub(crate) u8);
|
||||||
impl Default for Permissions {
|
impl Default for Permissions {
|
||||||
fn default() -> Permissions {
|
fn default() -> Permissions {
|
||||||
Permissions(0)
|
Permissions(0)
|
||||||
|
|
|
@ -129,7 +129,7 @@ fn spawn_api(settings: Arc<Mutex<Settings>>) {
|
||||||
let api = Router::new()
|
let api = Router::new()
|
||||||
.route("/health", get(routes::health))
|
.route("/health", get(routes::health))
|
||||||
.route("/me", get(routes::me))
|
.route("/me", get(routes::me))
|
||||||
.route("/intros/:guild/add/:url", get(routes::add_guild_intro))
|
.route("/intros/:guild/add", get(routes::add_guild_intro))
|
||||||
.route("/intros/:guild", get(routes::intros))
|
.route("/intros/:guild", get(routes::intros))
|
||||||
.route(
|
.route(
|
||||||
"/intros/:guild/:channel/:intro",
|
"/intros/:guild/:channel/:intro",
|
||||||
|
@ -142,12 +142,13 @@ fn spawn_api(settings: Arc<Mutex<Settings>>) {
|
||||||
.route("/auth", get(routes::auth))
|
.route("/auth", get(routes::auth))
|
||||||
.layer(
|
.layer(
|
||||||
CorsLayer::new()
|
CorsLayer::new()
|
||||||
.allow_origin(Any)
|
// TODO: move this to env variable
|
||||||
|
.allow_origin(["https://spacegirl.nl".parse().unwrap()])
|
||||||
.allow_headers(Any)
|
.allow_headers(Any)
|
||||||
.allow_methods([Method::GET, Method::POST]),
|
.allow_methods([Method::GET, Method::POST]),
|
||||||
)
|
)
|
||||||
.with_state(Arc::new(state));
|
.with_state(Arc::new(state));
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], 7756));
|
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
|
||||||
info!("socket listening on {addr}");
|
info!("socket listening on {addr}");
|
||||||
axum::Server::bind(&addr)
|
axum::Server::bind(&addr)
|
||||||
.serve(api.into_make_service())
|
.serve(api.into_make_service())
|
||||||
|
|
|
@ -13,7 +13,7 @@ use serde_json::{json, Value};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::settings::{ApiState, Intro, IntroIndex};
|
use crate::settings::{ApiState, Intro, IntroIndex, UserSettings};
|
||||||
use crate::{auth, settings::FileIntro};
|
use crate::{auth, settings::FileIntro};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -38,6 +38,7 @@ pub(crate) struct Me<'a> {
|
||||||
pub(crate) struct MeGuild<'a> {
|
pub(crate) struct MeGuild<'a> {
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
pub(crate) channels: Vec<MeChannel<'a>>,
|
pub(crate) channels: Vec<MeChannel<'a>>,
|
||||||
|
pub(crate) permissions: auth::Permissions,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -99,6 +100,13 @@ struct DiscordUser {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DiscordUserGuild {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub owner: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn auth(
|
pub(crate) async fn auth(
|
||||||
State(state): State<Arc<ApiState>>,
|
State(state): State<Arc<ApiState>>,
|
||||||
Query(params): Query<HashMap<String, String>>,
|
Query(params): Query<HashMap<String, String>>,
|
||||||
|
@ -114,7 +122,7 @@ pub(crate) async fn auth(
|
||||||
data.insert("client_secret", state.secrets.client_secret.as_str());
|
data.insert("client_secret", state.secrets.client_secret.as_str());
|
||||||
data.insert("grant_type", "authorization_code");
|
data.insert("grant_type", "authorization_code");
|
||||||
data.insert("code", code);
|
data.insert("code", code);
|
||||||
data.insert("redirect_uri", "http://localhost:5173/auth");
|
data.insert("redirect_uri", "https://spacegirl.nl/memes/auth");
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
@ -219,34 +227,66 @@ pub(crate) async fn intros(
|
||||||
Json(json!(IntroResponse::Intros(&guild.intros)))
|
Json(json!(IntroResponse::Intros(&guild.intros)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn me(State(state): State<Arc<ApiState>>, headers: HeaderMap) -> Json<Value> {
|
pub(crate) async fn me(
|
||||||
let settings = state.settings.lock().await;
|
State(state): State<Arc<ApiState>>,
|
||||||
let Some(token) = headers.get("token").and_then(|v| v.to_str().ok()) else { return Json(json!(MeResponse::NoUserFound)); };
|
headers: HeaderMap,
|
||||||
|
) -> Result<Json<Value>, Error> {
|
||||||
|
let mut settings = state.settings.lock().await;
|
||||||
|
let Some(token) = headers.get("token").and_then(|v| v.to_str().ok()) else { return Err(Error::NoUserFound); };
|
||||||
|
|
||||||
let user = match settings.auth_users.get(token) {
|
let (username, permissions, access_token) = match settings.auth_users.get(token) {
|
||||||
Some(user) => user.name.clone(),
|
Some(user) => (
|
||||||
None => return Json(json!(MeResponse::NoUserFound)),
|
user.name.clone(),
|
||||||
|
user.permissions,
|
||||||
|
user.auth.access_token.clone(),
|
||||||
|
),
|
||||||
|
None => return Err(Error::NoUserFound),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: get bot's guilds so we only save users who are able to use the bot
|
||||||
|
let discord_guilds: Vec<DiscordUserGuild> = reqwest::Client::new()
|
||||||
|
.get("https://discord.com/api/v10/users/@me/guilds")
|
||||||
|
.bearer_auth(access_token)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
settings.auth_users.remove(token);
|
||||||
|
|
||||||
|
Error::Auth(err.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut me = Me {
|
let mut me = Me {
|
||||||
username: user.clone(),
|
username: username.clone(),
|
||||||
guilds: Vec::new(),
|
guilds: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for g in &settings.guilds {
|
for g in settings.guilds.iter_mut() {
|
||||||
|
// TODO: don't do this n^2 lookup
|
||||||
|
let Some(discord_guild) = discord_guilds.iter().find(|discord_guild| discord_guild.id == g.0.to_string()) else { continue; };
|
||||||
|
|
||||||
let mut guild = MeGuild {
|
let mut guild = MeGuild {
|
||||||
name: g.0.to_string(),
|
name: g.0.to_string(),
|
||||||
channels: Vec::new(),
|
channels: Vec::new(),
|
||||||
|
// TODO: change `auth::User` to have guild specific permissions instead of global
|
||||||
|
permissions,
|
||||||
};
|
};
|
||||||
|
|
||||||
for channel in &g.1.channels {
|
for channel in g.1.channels.iter_mut() {
|
||||||
let user_settings = channel.1.users.iter().find(|u| *u.0 == user);
|
let user_settings = channel
|
||||||
|
.1
|
||||||
|
.users
|
||||||
|
.entry(username.clone())
|
||||||
|
.or_insert(UserSettings { intros: Vec::new() });
|
||||||
|
|
||||||
let Some(user) = user_settings else { continue; };
|
if discord_guild.owner {
|
||||||
|
guild.permissions.0 |= auth::Permission::DownloadSounds as u8;
|
||||||
|
}
|
||||||
|
|
||||||
guild.channels.push(MeChannel {
|
guild.channels.push(MeChannel {
|
||||||
name: channel.0.to_owned(),
|
name: channel.0.to_owned(),
|
||||||
intros: &user.1.intros,
|
intros: &user_settings.intros,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,22 +294,24 @@ pub(crate) async fn me(State(state): State<Arc<ApiState>>, headers: HeaderMap) -
|
||||||
}
|
}
|
||||||
|
|
||||||
if me.guilds.is_empty() {
|
if me.guilds.is_empty() {
|
||||||
Json(json!(MeResponse::NoUserFound))
|
Ok(Json(json!(MeResponse::NoUserFound)))
|
||||||
} else {
|
} else {
|
||||||
Json(json!(MeResponse::Me(me)))
|
Ok(Json(json!(MeResponse::Me(me))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn add_guild_intro(
|
pub(crate) async fn add_guild_intro(
|
||||||
State(state): State<Arc<ApiState>>,
|
State(state): State<Arc<ApiState>>,
|
||||||
Path((guild, url)): Path<(u64, String)>,
|
Path(guild): Path<u64>,
|
||||||
Query(mut params): Query<HashMap<String, String>>,
|
Query(mut params): Query<HashMap<String, String>>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut settings = state.settings.lock().await;
|
let mut settings = state.settings.lock().await;
|
||||||
// TODO: make this an impl on HeaderMap
|
// TODO: make this an impl on HeaderMap
|
||||||
let Some(token) = headers.get("token").and_then(|v| v.to_str().ok()) else { return Err(Error::NoUserFound); };
|
let Some(token) = headers.get("token").and_then(|v| v.to_str().ok()) else { return Err(Error::NoUserFound); };
|
||||||
|
let Some(url) = params.remove("url") else { return Err(Error::InvalidRequest); };
|
||||||
let Some(friendly_name) = params.remove("name") else { return Err(Error::InvalidRequest); };
|
let Some(friendly_name) = params.remove("name") else { return Err(Error::InvalidRequest); };
|
||||||
|
|
||||||
let user = match settings.auth_users.get(token) {
|
let user = match settings.auth_users.get(token) {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => return Err(Error::NoUserFound),
|
None => return Err(Error::NoUserFound),
|
||||||
|
@ -284,7 +326,7 @@ pub(crate) async fn add_guild_intro(
|
||||||
let uuid = Uuid::new_v4().to_string();
|
let uuid = Uuid::new_v4().to_string();
|
||||||
let child = tokio::process::Command::new("yt-dlp")
|
let child = tokio::process::Command::new("yt-dlp")
|
||||||
.arg(&url)
|
.arg(&url)
|
||||||
.args(["-o", &format!("./sounds/{uuid}")])
|
.args(["-o", &format!("sounds/{uuid}")])
|
||||||
.args(["-x", "--audio-format", "mp3"])
|
.args(["-x", "--audio-format", "mp3"])
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(Error::Ytdl)?
|
.map_err(Error::Ytdl)?
|
||||||
|
|
Loading…
Reference in New Issue