initial api impl

api
Joey Hines 2024-10-12 12:15:31 -06:00
parent 666d8b8766
commit 10a13d3daa
Signed by: joeyahines
GPG Key ID: 995E531F7A569DDB
12 changed files with 471 additions and 17 deletions

277
Cargo.lock generated
View File

@ -236,6 +236,61 @@ dependencies = [
"paste",
]
[[package]]
name = "axum"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
dependencies = [
"async-trait",
"axum-core",
"bytes",
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.4.1",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper 1.0.1",
"tokio",
"tower 0.5.1",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper 1.0.1",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "backtrace"
version = "0.3.74"
@ -652,7 +707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
@ -1251,7 +1306,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap",
"indexmap 2.5.0",
"slab",
"tokio",
"tokio-util",
@ -1270,7 +1325,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap",
"indexmap 2.5.0",
"slab",
"tokio",
"tokio-util",
@ -1283,10 +1338,16 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8588661a8607108a5ca69cab034063441a0413a0b041c13618a7dd348021ef6f"
dependencies = [
"hashbrown",
"hashbrown 0.14.5",
"serde",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -1596,6 +1657,19 @@ dependencies = [
"webpki-roots 0.26.6",
]
[[package]]
name = "hyper-timeout"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793"
dependencies = [
"hyper 1.4.1",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.9"
@ -1664,6 +1738,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.5.0"
@ -1671,7 +1755,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.14.5",
]
[[package]]
@ -2108,6 +2192,12 @@ dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "maybe-async"
version = "0.2.10"
@ -2517,7 +2607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap",
"indexmap 2.5.0",
]
[[package]]
@ -2753,7 +2843,7 @@ checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d"
dependencies = [
"autocfg",
"equivalent",
"indexmap",
"indexmap 2.5.0",
]
[[package]]
@ -2765,6 +2855,59 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "prost"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-build"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15"
dependencies = [
"bytes",
"heck 0.5.0",
"itertools",
"log",
"multimap",
"once_cell",
"petgraph",
"prettyplease",
"prost",
"prost-types",
"regex",
"syn 2.0.79",
"tempfile",
]
[[package]]
name = "prost-derive"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn 2.0.79",
]
[[package]]
name = "prost-types"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
dependencies = [
"prost",
]
[[package]]
name = "protobuf"
version = "3.5.1"
@ -2798,7 +2941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b0e9b447d099ae2c4993c0cbb03c7a9d6c937b17f2d56cfc0b1550e6fcfdb76"
dependencies = [
"anyhow",
"indexmap",
"indexmap 2.5.0",
"log",
"protobuf",
"protobuf-support",
@ -3976,6 +4119,8 @@ dependencies = [
"rustls 0.23.13",
"serenity",
"songbird",
"spoticord_api",
"spoticord_api_grpc",
"spoticord_config",
"spoticord_database",
"spoticord_player",
@ -3983,6 +4128,34 @@ dependencies = [
"spoticord_stats",
"spoticord_utils",
"tokio",
"tonic",
]
[[package]]
name = "spoticord_api"
version = "0.1.0"
dependencies = [
"axum",
"env_logger",
"log",
"prost",
"spoticord_api_grpc",
"spoticord_database",
"spoticord_session",
"thiserror",
"tokio",
"tonic",
]
[[package]]
name = "spoticord_api_grpc"
version = "0.1.0"
dependencies = [
"bytes",
"prost",
"tokio",
"tonic",
"tonic-build",
]
[[package]]
@ -4645,6 +4818,92 @@ dependencies = [
"tokio",
]
[[package]]
name = "tonic"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [
"async-stream",
"async-trait",
"axum",
"base64 0.22.1",
"bytes",
"h2 0.4.6",
"http 1.1.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.4.1",
"hyper-timeout",
"hyper-util",
"percent-encoding",
"pin-project",
"prost",
"socket2",
"tokio",
"tokio-stream",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tonic-build"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11"
dependencies = [
"prettyplease",
"proc-macro2",
"prost-build",
"prost-types",
"quote",
"syn 2.0.79",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper 0.1.2",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
@ -4870,7 +5129,7 @@ checksum = "5dece5c06268af6a9ff4541788601e560a4284ffebfb357f713d676f13b964db"
dependencies = [
"chrono",
"dashmap",
"hashbrown",
"hashbrown 0.14.5",
"mini-moka",
"parking_lot",
"secrecy",

View File

@ -17,6 +17,8 @@ members = [
"spoticord_session",
"spoticord_utils",
"spoticord_stats",
"spoticord_api",
"spoticord_api_grpc",
]
[features]
@ -30,6 +32,8 @@ spoticord_player = { path = "./spoticord_player" }
spoticord_session = { path = "./spoticord_session" }
spoticord_utils = { path = "./spoticord_utils" }
spoticord_stats = { path = "./spoticord_stats", optional = true }
spoticord_api = { path = "./spoticord_api"}
spoticord_api_grpc = { path = "./spoticord_api_grpc"}
anyhow = "1.0.86"
dotenvy = "0.15.7"
@ -40,6 +44,7 @@ serenity = "0.12.2"
songbird = { version = "0.4.3", features = ["simd-json"] }
tokio = { version = "1.39.3", features = ["full"] }
rustls = { version = "0.23.13", features = ["aws-lc-rs"] }
tonic = "0.12.3"
[profile.release]
opt-level = 3

View File

@ -0,0 +1,16 @@
[package]
name = "spoticord_api"
version = "0.1.0"
edition = "2021"
[dependencies]
spoticord_database = { path = "../spoticord_database" }
spoticord_session = { path = "../spoticord_session" }
spoticord_api_grpc = { path = "../spoticord_api_grpc"}
prost = "0.13.3"
tokio = {version = "1.38.0", features = ["io-util", "net", "rt-multi-thread"]}
axum = "0.7.5"
env_logger = "0.11.3"
log = "0.4.21"
thiserror = "1.0.61"
tonic = "0.12.3"

View File

@ -0,0 +1,39 @@
use log::info;
use spoticord_api_grpc::spoticord_api::service::PlayPlaylistRequest;
use spoticord_session::manager::{SessionManager, SessionQuery};
use tonic::{Request, Response, Status};
pub struct SpoticordApi {
pub session: SessionManager,
}
#[tonic::async_trait]
impl spoticord_api_grpc::spoticord_api::service::spoticord_api_server::SpoticordApi
for SpoticordApi
{
async fn play_playlist(
&self,
request: Request<PlayPlaylistRequest>,
) -> Result<Response<spoticord_api_grpc::spoticord_api::service::Response>, Status> {
let playlist = request.into_inner();
info!(
"Playing {} for {}",
playlist.playlist_uri, playlist.discord_user_id
);
let session = self
.session
.get_session(SessionQuery::Owner(playlist.discord_user_id.into()))
.unwrap();
session.queue_playlist(playlist.playlist_uri).await.unwrap();
let response = spoticord_api_grpc::spoticord_api::service::Response {
resp: Option::from(
spoticord_api_grpc::spoticord_api::service::response::Resp::Success(true),
),
};
Ok(Response::new(response))
}
}

View File

@ -0,0 +1,13 @@
[package]
name = "spoticord_api_grpc"
version = "0.1.0"
edition = "2021"
[dependencies]
bytes = "1.6.0"
tonic = "0.12.3"
prost = "0.13.3"
tokio = {version = "1.38.0", features = ["io-util", "test-util"]}
[build-dependencies]
tonic-build = "0.12.3"

View File

@ -0,0 +1,6 @@
use std::io::Result;
fn main() -> Result<()> {
tonic_build::configure().compile_protos(&["src/service.proto"], &["src/"])?;
Ok(())
}

View File

@ -0,0 +1,21 @@
pub mod spoticord_api {
pub mod service {
include!(concat!(env!("OUT_DIR"), "/spoticord_api.service.rs"));
}
}
#[cfg(test)]
mod test {
use crate::spoticord_api::service::{PlayModes, PlayPlaylistRequest};
#[test]
fn test_build_play_playlist() {
let play_playlist = PlayPlaylistRequest {
order: PlayModes::InOrder.into(),
playlist_uri: "test".to_string(),
};
assert_eq!(play_playlist.playlist_uri, "test");
assert_eq!(play_playlist.order, PlayModes::InOrder.into());
}
}

View File

@ -0,0 +1,32 @@
syntax = "proto3";
package spoticord_api.service;
enum PlayModes {
SHUFFLE = 0;
IN_ORDER = 1;
}
message PlayPlaylistRequest {
uint64 discord_user_id = 1;
PlayModes order = 2;
string playlist_uri = 3;
}
enum Error {
SpotifyError = 0;
BotError = 1;
UserNotRegistered = 2;
}
message Response {
oneof resp {
bool success = 1;
Error error = 2;
}
}
service SpoticordApi {
rpc PlayPlaylist(PlayPlaylistRequest) returns (Response);
}

View File

@ -35,8 +35,4 @@ diesel::table! {
diesel::joinable!(account -> user (user_id));
diesel::joinable!(link_request -> user (user_id));
diesel::allow_tables_to_appear_in_same_query!(
account,
link_request,
user,
);
diesel::allow_tables_to_appear_in_same_query!(account, link_request, user,);

View File

@ -2,6 +2,10 @@ pub mod info;
use anyhow::Result;
use info::PlaybackInfo;
use librespot::connect::spirc::SpircLoadCommand;
use librespot::core::SpotifyId;
use librespot::metadata::{Metadata, Playlist};
use librespot::protocol::spirc::TrackRef;
use librespot::{
connect::{config::ConnectConfig, spirc::Spirc},
core::{http_client::HttpClientError, Session as SpotifySession, SessionConfig},
@ -34,6 +38,7 @@ enum PlayerCommand {
GetPlaybackInfo(oneshot::Sender<Option<PlaybackInfo>>),
GetLyrics(oneshot::Sender<Option<Lyrics>>),
QueuePlaylist { uri: String },
Shutdown,
}
@ -213,6 +218,7 @@ impl Player {
PlayerCommand::GetLyrics(tx) => self.get_lyrics(tx).await,
PlayerCommand::Shutdown => self.commands.close(),
PlayerCommand::QueuePlaylist { uri } => self.queue_playlist(uri).await,
};
}
@ -300,6 +306,34 @@ impl Player {
_ = tx.send(Some(lyrics));
}
async fn queue_playlist(&self, playlist_uri: String) {
let playlist = Playlist::get(&self.session, &SpotifyId::from_uri(&playlist_uri).unwrap())
.await
.unwrap();
let tracks = playlist
.tracks()
.map(|track_id| {
let mut track = TrackRef::new();
track.set_gid(Vec::from(track_id.to_raw()));
track
})
.collect();
self.spirc.activate().unwrap();
self.spirc
.load(SpircLoadCommand {
context_uri: playlist_uri,
start_playing: true,
shuffle: true,
repeat: true,
playing_track_index: 0,
tracks,
})
.unwrap();
}
}
impl Drop for Player {
@ -354,4 +388,11 @@ impl PlayerHandle {
pub async fn shutdown(&self) {
_ = self.commands.send(PlayerCommand::Shutdown).await;
}
pub async fn queue_playlist(&self, playlist_uri: String) {
self.commands
.send(PlayerCommand::QueuePlaylist { uri: playlist_uri })
.await
.unwrap()
}
}

View File

@ -42,6 +42,9 @@ pub enum SessionCommand {
ShutdownPlayer,
Disconnect,
DisconnectTimedOut,
QueuePlaylist {
playlist_uri: String,
},
}
pub struct Session {
@ -236,7 +239,6 @@ impl Session {
SessionCommand::GetOwner(sender) => _ = sender.send(self.owner),
SessionCommand::GetPlayer(sender) => _ = sender.send(self.player.clone()),
SessionCommand::GetActive(sender) => _ = sender.send(self.active),
SessionCommand::CreatePlaybackEmbed(handle, interaction, behavior) => {
match PlaybackEmbed::create(self, handle, interaction, behavior).await {
Ok(opt_handle) => {
@ -290,6 +292,9 @@ impl Session {
return ControlFlow::Break(());
}
SessionCommand::QueuePlaylist { playlist_uri } => {
self.player.queue_playlist(playlist_uri).await;
}
};
ControlFlow::Continue(())
@ -551,6 +556,14 @@ impl SessionHandle {
error!("Failed to send command: {why}");
}
}
pub async fn queue_playlist(&self, playlist_uri: String) -> Result<()> {
self.commands
.send(SessionCommand::QueuePlaylist { playlist_uri })
.await?;
Ok(())
}
}
#[async_trait]

View File

@ -4,6 +4,7 @@ use anyhow::{anyhow, Result};
use log::{debug, info};
use poise::{serenity_prelude, Framework, FrameworkContext, FrameworkOptions};
use serenity::all::{ActivityData, FullEvent, Ready, ShardManager};
use spoticord_api::SpoticordApi;
use spoticord_database::Database;
use spoticord_session::manager::SessionManager;
@ -84,7 +85,7 @@ async fn event_handler(
ctx: &serenity_prelude::Context,
event: &FullEvent,
_framework: FrameworkContext<'_, Data, anyhow::Error>,
_data: &Data,
data: &Data,
) -> Result<()> {
if let FullEvent::Ready { data_about_bot } = event {
if let Some(shard) = data_about_bot.shard {
@ -95,7 +96,19 @@ async fn event_handler(
}
ctx.set_activity(Some(ActivityData::listening(spoticord_config::MOTD)));
}
let api_server = SpoticordApi {
session: data.clone(),
};
tokio::spawn(async move {
tonic::transport::Server::builder()
.add_service(spoticord_api_grpc::spoticord_api::service::spoticord_api_server::SpoticordApiServer::new(api_server))
.serve("127.0.0.1:8080".parse().unwrap())
.await
.unwrap();
});
};
Ok(())
}