Compare commits
No commits in common. "c9fcdba421ee1efe05aadc51028bf8e410434086" and "f438fb73411c0ed3b5a72a46e594ef4f820fafa3" have entirely different histories.
c9fcdba421
...
f438fb7341
8 changed files with 186 additions and 3423 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,6 +3,10 @@
|
||||||
debug/
|
debug/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
|
|
3243
Cargo.lock
generated
3243
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
19
Cargo.toml
19
Cargo.toml
|
@ -6,24 +6,21 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.69"
|
anyhow = "1.0.69"
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
poise = "0.6.1"
|
poise = "0.5.2"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-futures = "0.2.5"
|
tracing-futures = "0.2.5"
|
||||||
openai = "1.0.0-alpha.8"
|
openai = "1.0.0-alpha.8"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
reqwest = "0.11.23"
|
|
||||||
songbird = "0.4.0"
|
|
||||||
thiserror = "1.0.39"
|
thiserror = "1.0.39"
|
||||||
async-openai = "0.18.1"
|
|
||||||
|
|
||||||
[dependencies.symphonia]
|
[dependencies.tracing-subscriber]
|
||||||
version = "0.5.2"
|
version = "0.3.16"
|
||||||
features = ["mkv"]
|
features = ["fmt", "env-filter", "std"]
|
||||||
|
|
||||||
|
[dependencies.songbird]
|
||||||
|
version = "0.3.1"
|
||||||
|
features = ["yt-dlp"]
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.26.0"
|
version = "1.26.0"
|
||||||
features = ["macros", "rt-multi-thread", "signal"]
|
features = ["macros", "rt-multi-thread", "signal"]
|
||||||
|
|
||||||
[dependencies.tracing-subscriber]
|
|
||||||
version = "0.3.18"
|
|
||||||
features = ["fmt", "env-filter", "std"]
|
|
||||||
|
|
23
Dockerfile
23
Dockerfile
|
@ -1,15 +1,22 @@
|
||||||
FROM rust:1.75-alpine3.19 as builder
|
FROM rust:1.68 as builder
|
||||||
ARG OPENAI_API_KEY
|
ARG OPENAI_KEY
|
||||||
RUN apk add --no-cache musl-dev opus-dev
|
RUN apt-get update && apt-get --no-install-recommends install -y libopus-dev
|
||||||
WORKDIR /usr/src/myapp
|
WORKDIR /usr/src/myapp
|
||||||
COPY . .
|
COPY . .
|
||||||
|
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
||||||
RUN cargo install --path .
|
RUN cargo install --path .
|
||||||
|
|
||||||
FROM alpine:3.19
|
FROM debian:bullseye-slim
|
||||||
RUN apk add --no-cache opus ca-certificates yt-dlp
|
COPY --from=mwader/static-ffmpeg:6.0 /ffmpeg /usr/local/bin/
|
||||||
|
RUN apt-get update && apt-get --no-install-recommends install -y \
|
||||||
|
libopus0 \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
ADD https://github.com/anpage/yt-dlp/releases/download/2023.03.23.040309/yt-dlp_linux /usr/local/bin/yt-dlp
|
||||||
|
RUN chmod 755 /usr/local/bin/yt-dlp
|
||||||
COPY --from=builder /usr/local/cargo/bin/dj_kitty_cat /usr/local/bin/dj_kitty_cat
|
COPY --from=builder /usr/local/cargo/bin/dj_kitty_cat /usr/local/bin/dj_kitty_cat
|
||||||
RUN useradd djkc
|
RUN useradd djkk
|
||||||
WORKDIR /opt
|
WORKDIR /opt
|
||||||
RUN chown djkc:djkc /opt
|
RUN chown djkk:djkk /opt
|
||||||
USER djkc
|
USER djkk
|
||||||
CMD ["dj_kitty_cat"]
|
CMD ["dj_kitty_cat"]
|
||||||
|
|
155
src/commands.rs
155
src/commands.rs
|
@ -1,20 +1,17 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use poise::{
|
use poise::serenity_prelude::{EmbedMessageBuilding, MessageBuilder};
|
||||||
serenity_prelude::{CreateEmbed, EmbedMessageBuilding, MessageBuilder},
|
|
||||||
CreateReply,
|
|
||||||
};
|
|
||||||
use songbird::input::{Input, YoutubeDl};
|
|
||||||
use tracing::{debug, log::warn};
|
use tracing::{debug, log::warn};
|
||||||
|
|
||||||
use crate::{personality, CommandContext, Error};
|
use crate::{personality, CommandContext, Error};
|
||||||
|
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn join(ctx: CommandContext<'_>) -> Result<(), Error> {
|
pub async fn join(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
let Some((guild_id, Some(channel_id))) = ctx.guild().and_then(|g| {
|
let Some(guild) = ctx.guild() else {
|
||||||
g.voice_states
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
.get(&ctx.author().id)
|
return Ok(());
|
||||||
.map(|vs| (g.id, vs.channel_id))
|
};
|
||||||
}) else {
|
|
||||||
|
let Some(Some(channel_id)) = guild.voice_states.get(&ctx.author().id).map(|vs| vs.channel_id) else {
|
||||||
ctx.say("You're not in a voice channel, silly.").await?;
|
ctx.say("You're not in a voice channel, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -23,7 +20,7 @@ pub async fn join(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
.await
|
.await
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
let _handler = manager.join(guild_id, channel_id).await;
|
let _handler = manager.join(guild.id, channel_id).await;
|
||||||
|
|
||||||
ctx.say("Joining your channel!").await?;
|
ctx.say("Joining your channel!").await?;
|
||||||
|
|
||||||
|
@ -32,7 +29,7 @@ pub async fn join(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
|
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn leave(ctx: CommandContext<'_>) -> Result<(), Error> {
|
pub async fn leave(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
let Some(guild_id) = ctx.guild().map(|g| g.id) else {
|
let Some(guild) = ctx.guild() else {
|
||||||
ctx.say("You're not in a server, silly.").await?;
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -42,12 +39,12 @@ pub async fn leave(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if manager.get(guild_id).is_none() {
|
if manager.get(guild.id).is_none() {
|
||||||
ctx.say("I'm not even in a voice channel!").await?;
|
ctx.say("I'm not even in a voice channel!").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let _handler = manager.remove(guild_id).await;
|
let _handler = manager.remove(guild.id).await;
|
||||||
|
|
||||||
ctx.say("Okay bye!").await?;
|
ctx.say("Okay bye!").await?;
|
||||||
|
|
||||||
|
@ -64,7 +61,7 @@ pub async fn play(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(guild_id) = ctx.guild().map(|g| g.id) else {
|
let Some(guild) = ctx.guild() else {
|
||||||
ctx.say("You're not in a server, silly.").await?;
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -74,35 +71,53 @@ pub async fn play(
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if manager.get(guild_id).is_none() {
|
if manager.get(guild.id).is_none() {
|
||||||
let Some((guild_id, Some(channel_id))) = ctx.guild().and_then(|g| {
|
let Some(Some(channel_id)) = guild.voice_states.get(&ctx.author().id).map(|vs| vs.channel_id) else {
|
||||||
g.voice_states
|
ctx.say("Neither of us are in a voice channel, silly.").await?;
|
||||||
.get(&ctx.author().id)
|
|
||||||
.map(|vs| (g.id, vs.channel_id))
|
|
||||||
}) else {
|
|
||||||
ctx.say("You're not in a voice channel, silly.").await?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let _handler = manager.join(guild_id, channel_id).await;
|
let _handler = manager.join(guild.id, channel_id).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(handler_lock) = manager.get(guild_id) {
|
if let Some(handler_lock) = manager.get(guild.id) {
|
||||||
let reply = poise::CreateReply::default()
|
let mut msg = MessageBuilder::new();
|
||||||
.content(personality::get_random_loading_message())
|
msg.push_line(String::from(personality::get_random_loading_message()))
|
||||||
.embed(
|
.push_named_link(
|
||||||
CreateEmbed::new()
|
"",
|
||||||
.image("https://media.giphy.com/media/H1dxi6xdh4NGQCZSvz/giphy.gif"),
|
"https://media.giphy.com/media/H1dxi6xdh4NGQCZSvz/giphy.gif",
|
||||||
);
|
);
|
||||||
let response = ctx.send(reply).await?;
|
let response = ctx.send(|r| r.content(msg.build())).await?;
|
||||||
|
|
||||||
let mut handler = handler_lock.lock().await;
|
let mut handler = handler_lock.lock().await;
|
||||||
|
|
||||||
debug!("Trying to play: {}", url);
|
debug!("Trying to play: {}", url);
|
||||||
let mut source: Input = YoutubeDl::new(ctx.data().http_client.clone(), url.clone()).into();
|
let source = songbird::ytdl(&url).await;
|
||||||
let metadata = source.aux_metadata().await?;
|
|
||||||
|
|
||||||
debug!("Playing: {:?}", metadata);
|
let source = match source {
|
||||||
let title = metadata.title.clone().unwrap_or(String::from("This video"));
|
Ok(source) => source,
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
songbird::input::error::Error::Json {
|
||||||
|
ref error,
|
||||||
|
ref parsed_text,
|
||||||
|
} => {
|
||||||
|
debug!("Failed to play: {}", error);
|
||||||
|
debug!("Parsed text: {}", parsed_text);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("Failed to play: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Playing: {:?}", source.metadata);
|
||||||
|
let title = source
|
||||||
|
.metadata
|
||||||
|
.title
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(String::from("This video"));
|
||||||
|
|
||||||
let mut msg = MessageBuilder::new();
|
let mut msg = MessageBuilder::new();
|
||||||
|
|
||||||
|
@ -118,15 +133,13 @@ pub async fn play(
|
||||||
|
|
||||||
msg.push_bold("Now playing: ").push_named_link(title, url);
|
msg.push_bold("Now playing: ").push_named_link(title, url);
|
||||||
|
|
||||||
response
|
response.edit(ctx, |r| r.content(msg.build())).await?;
|
||||||
.edit(ctx, CreateReply::default().content(msg.build()))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut queue = ctx.data().queue.lock();
|
let mut queue = ctx.data().queue.lock();
|
||||||
if !queue.is_empty() {
|
if !queue.is_empty() {
|
||||||
let _ = queue.stop();
|
let _ = queue.stop();
|
||||||
}
|
}
|
||||||
queue.add_next(source, &mut handler)?;
|
queue.add_next(source, &mut handler);
|
||||||
queue.resume()?;
|
queue.resume()?;
|
||||||
} else {
|
} else {
|
||||||
ctx.say("Neither of us are in a voice channel, silly.")
|
ctx.say("Neither of us are in a voice channel, silly.")
|
||||||
|
@ -146,7 +159,7 @@ pub async fn queue(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(guild_id) = ctx.guild().map(|g| g.id) else {
|
let Some(guild) = ctx.guild() else {
|
||||||
ctx.say("You're not in a server, silly.").await?;
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -156,35 +169,33 @@ pub async fn queue(
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if manager.get(guild_id).is_none() {
|
if manager.get(guild.id).is_none() {
|
||||||
let Some((guild_id, Some(channel_id))) = ctx.guild().and_then(|g| {
|
let Some(Some(channel_id)) = guild.voice_states.get(&ctx.author().id).map(|vs| vs.channel_id) else {
|
||||||
g.voice_states
|
ctx.say("Neither of us are in a voice channel, silly.").await?;
|
||||||
.get(&ctx.author().id)
|
|
||||||
.map(|vs| (g.id, vs.channel_id))
|
|
||||||
}) else {
|
|
||||||
ctx.say("You're not in a voice channel, silly.").await?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let _handler = manager.join(guild_id, channel_id).await;
|
let _handler = manager.join(guild.id, channel_id).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(handler_lock) = manager.get(guild_id) {
|
if let Some(handler_lock) = manager.get(guild.id) {
|
||||||
let reply = poise::CreateReply::default()
|
let mut msg = MessageBuilder::new();
|
||||||
.content(personality::get_random_loading_message())
|
msg.push_line(String::from(personality::get_random_loading_message()))
|
||||||
.embed(
|
.push_named_link(
|
||||||
CreateEmbed::new()
|
"",
|
||||||
.image("https://media.giphy.com/media/H1dxi6xdh4NGQCZSvz/giphy.gif"),
|
"https://media.giphy.com/media/H1dxi6xdh4NGQCZSvz/giphy.gif",
|
||||||
);
|
);
|
||||||
let response = ctx.send(reply).await?;
|
let response = ctx.send(|r| r.content(msg.build())).await?;
|
||||||
|
|
||||||
let mut handler = handler_lock.lock().await;
|
let mut handler = handler_lock.lock().await;
|
||||||
|
|
||||||
debug!("Trying to play: {}", url);
|
debug!("Trying to play: {}", url);
|
||||||
let mut source: Input = YoutubeDl::new(ctx.data().http_client.clone(), url.clone()).into();
|
let source = songbird::ytdl(&url).await?;
|
||||||
let metadata = source.aux_metadata().await?;
|
debug!("Playing: {:?}", source.metadata);
|
||||||
|
let title = source
|
||||||
debug!("Playing: {:?}", metadata);
|
.metadata
|
||||||
let title = metadata.title.clone().unwrap_or(String::from("This video"));
|
.title
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(String::from("This video"));
|
||||||
|
|
||||||
let mut msg = MessageBuilder::new();
|
let mut msg = MessageBuilder::new();
|
||||||
|
|
||||||
|
@ -200,12 +211,10 @@ pub async fn queue(
|
||||||
|
|
||||||
msg.push_bold("Queued: ").push_named_link(title, url);
|
msg.push_bold("Queued: ").push_named_link(title, url);
|
||||||
|
|
||||||
response
|
response.edit(ctx, |r| r.content(msg.build())).await?;
|
||||||
.edit(ctx, CreateReply::default().content(msg.build()))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut queue = ctx.data().queue.lock();
|
let mut queue = ctx.data().queue.lock();
|
||||||
queue.add_to_end(source, &mut handler)?;
|
queue.add_to_end(source, &mut handler);
|
||||||
} else {
|
} else {
|
||||||
ctx.say("Neither of us are in a voice channel, silly.")
|
ctx.say("Neither of us are in a voice channel, silly.")
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -216,7 +225,7 @@ pub async fn queue(
|
||||||
|
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn stop(ctx: CommandContext<'_>) -> Result<(), Error> {
|
pub async fn stop(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
let Some(guild_id) = ctx.guild().map(|g| g.id) else {
|
let Some(guild) = ctx.guild() else {
|
||||||
ctx.say("You're not in a server, silly.").await?;
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -226,7 +235,7 @@ pub async fn stop(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if manager.get(guild_id).is_some() {
|
if manager.get(guild.id).is_some() {
|
||||||
{
|
{
|
||||||
let mut queue = ctx.data().queue.lock();
|
let mut queue = ctx.data().queue.lock();
|
||||||
queue.stop()?;
|
queue.stop()?;
|
||||||
|
@ -242,7 +251,7 @@ pub async fn stop(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
|
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn skip(ctx: CommandContext<'_>) -> Result<(), Error> {
|
pub async fn skip(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
let Some(guild_id) = ctx.guild().map(|g| g.id) else {
|
let Some(guild) = ctx.guild() else {
|
||||||
ctx.say("You're not in a server, silly.").await?;
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -252,7 +261,7 @@ pub async fn skip(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if manager.get(guild_id).is_some() {
|
if manager.get(guild.id).is_some() {
|
||||||
{
|
{
|
||||||
let mut queue = ctx.data().queue.lock();
|
let mut queue = ctx.data().queue.lock();
|
||||||
let _ = queue.stop();
|
let _ = queue.stop();
|
||||||
|
@ -269,7 +278,7 @@ pub async fn skip(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
|
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn pause(ctx: CommandContext<'_>) -> Result<(), Error> {
|
pub async fn pause(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
let Some(guild_id) = ctx.guild().map(|g| g.id) else {
|
let Some(guild) = ctx.guild() else {
|
||||||
ctx.say("You're not in a server, silly.").await?;
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -279,7 +288,7 @@ pub async fn pause(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if manager.get(guild_id).is_some() {
|
if manager.get(guild.id).is_some() {
|
||||||
{
|
{
|
||||||
let mut queue = ctx.data().queue.lock();
|
let mut queue = ctx.data().queue.lock();
|
||||||
queue.pause()?;
|
queue.pause()?;
|
||||||
|
@ -295,7 +304,7 @@ pub async fn pause(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
|
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn resume(ctx: CommandContext<'_>) -> Result<(), Error> {
|
pub async fn resume(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
let Some(guild_id) = ctx.guild().map(|g| g.id) else {
|
let Some(guild) = ctx.guild() else {
|
||||||
ctx.say("You're not in a server, silly.").await?;
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -305,7 +314,7 @@ pub async fn resume(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if manager.get(guild_id).is_some() {
|
if manager.get(guild.id).is_some() {
|
||||||
{
|
{
|
||||||
let mut queue = ctx.data().queue.lock();
|
let mut queue = ctx.data().queue.lock();
|
||||||
queue.resume()?;
|
queue.resume()?;
|
||||||
|
@ -321,7 +330,7 @@ pub async fn resume(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
|
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn clear(ctx: CommandContext<'_>) -> Result<(), Error> {
|
pub async fn clear(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
let Some(guild_id) = ctx.guild().map(|g| g.id) else {
|
let Some(guild) = ctx.guild() else {
|
||||||
ctx.say("You're not in a server, silly.").await?;
|
ctx.say("You're not in a server, silly.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -331,7 +340,7 @@ pub async fn clear(ctx: CommandContext<'_>) -> Result<(), Error> {
|
||||||
.context("Expected a songbird manager")?
|
.context("Expected a songbird manager")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if manager.get(guild_id).is_some() {
|
if manager.get(guild.id).is_some() {
|
||||||
{
|
{
|
||||||
let mut queue = ctx.data().queue.lock();
|
let mut queue = ctx.data().queue.lock();
|
||||||
queue.clear();
|
queue.clear();
|
||||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -5,19 +5,17 @@ mod personality;
|
||||||
mod queue;
|
mod queue;
|
||||||
|
|
||||||
use commands::*;
|
use commands::*;
|
||||||
|
use openai::set_key;
|
||||||
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
|
|
||||||
use std::{env, sync::Arc};
|
use std::{env, sync::Arc};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use openai::set_key;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude::{self as serenity};
|
||||||
use reqwest::Client as HttpClient;
|
use songbird::serenity::SerenityInit;
|
||||||
use songbird::SerenityInit;
|
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
|
||||||
|
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
http_client: HttpClient,
|
|
||||||
queue: Arc<Mutex<queue::Queue>>,
|
queue: Arc<Mutex<queue::Queue>>,
|
||||||
}
|
}
|
||||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
@ -25,11 +23,11 @@ pub type CommandContext<'a> = poise::Context<'a, Data, Error>;
|
||||||
|
|
||||||
async fn event_event_handler(
|
async fn event_event_handler(
|
||||||
_ctx: &serenity::Context,
|
_ctx: &serenity::Context,
|
||||||
event: &serenity::FullEvent,
|
event: &poise::Event<'_>,
|
||||||
_framework: poise::FrameworkContext<'_, Data, Error>,
|
_framework: poise::FrameworkContext<'_, Data, Error>,
|
||||||
_user_data: &Data,
|
_user_data: &Data,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if let serenity::FullEvent::Ready { data_about_bot } = event {
|
if let poise::Event::Ready { data_about_bot } = event {
|
||||||
println!("{} is connected!", data_about_bot.user.name)
|
println!("{} is connected!", data_about_bot.user.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +49,6 @@ async fn main() -> Result<()> {
|
||||||
.with(EnvFilter::from_default_env())
|
.with(EnvFilter::from_default_env())
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
env::var("OPENAI_API_KEY")
|
|
||||||
.expect("Expected an OpenAI API key in the environment: OPENAI_API_KEY");
|
|
||||||
|
|
||||||
// OLD
|
|
||||||
set_key(env::var("OPENAI_KEY").expect("Expected an OpenAI key in the environment: OPENAI_KEY"));
|
set_key(env::var("OPENAI_KEY").expect("Expected an OpenAI key in the environment: OPENAI_KEY"));
|
||||||
|
|
||||||
let token =
|
let token =
|
||||||
|
@ -81,23 +75,19 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let intents = serenity::GatewayIntents::non_privileged();
|
let intents = serenity::GatewayIntents::non_privileged();
|
||||||
|
|
||||||
let framework = poise::Framework::builder()
|
poise::Framework::builder()
|
||||||
|
.token(token)
|
||||||
.options(options)
|
.options(options)
|
||||||
|
.intents(intents)
|
||||||
.setup(|_ctx, _data, _framework| {
|
.setup(|_ctx, _data, _framework| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
Ok(Data {
|
Ok(Data {
|
||||||
http_client: HttpClient::new(),
|
|
||||||
queue: Arc::new(Mutex::new(queue::Queue::new())),
|
queue: Arc::new(Mutex::new(queue::Queue::new())),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.build();
|
.client_settings(|client_builder| client_builder.register_songbird())
|
||||||
|
.run()
|
||||||
serenity::ClientBuilder::new(token, intents)
|
|
||||||
.framework(framework)
|
|
||||||
.register_songbird()
|
|
||||||
.await?
|
|
||||||
.start()
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use async_openai::types::{
|
use openai::chat::{ChatCompletion, ChatCompletionMessage};
|
||||||
ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage,
|
|
||||||
ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestUserMessageArgs,
|
|
||||||
CreateChatCompletionRequestArgs,
|
|
||||||
};
|
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
|
|
||||||
const LOADING_MESSAGES: [&str; 20] = [
|
const LOADING_MESSAGES: [&str; 20] = [
|
||||||
|
@ -57,52 +53,48 @@ pub async fn get_sassy_commentary(title: &str) -> Result<String> {
|
||||||
|
|
||||||
let prompt = format!("Play \"{title}\"");
|
let prompt = format!("Play \"{title}\"");
|
||||||
|
|
||||||
let client = async_openai::Client::new();
|
let completion = ChatCompletion::builder(
|
||||||
|
"gpt-4",
|
||||||
let request = CreateChatCompletionRequestArgs::default()
|
|
||||||
.model("gpt-4")
|
|
||||||
.messages(
|
|
||||||
[
|
[
|
||||||
system
|
system
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| {
|
.map(|s| ChatCompletionMessage {
|
||||||
ChatCompletionRequestSystemMessageArgs::default()
|
role: openai::chat::ChatCompletionMessageRole::System,
|
||||||
.content(s)
|
content: String::from(s),
|
||||||
.build()
|
name: None,
|
||||||
.unwrap()
|
|
||||||
.into()
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<ChatCompletionRequestMessage>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![
|
vec![
|
||||||
ChatCompletionRequestUserMessageArgs::default()
|
ChatCompletionMessage {
|
||||||
.content(example_prompt)
|
role: openai::chat::ChatCompletionMessageRole::User,
|
||||||
.build()?
|
content: String::from(example_prompt),
|
||||||
.into(),
|
name: None,
|
||||||
ChatCompletionRequestAssistantMessageArgs::default()
|
},
|
||||||
.content(example_response)
|
ChatCompletionMessage {
|
||||||
.build()?
|
role: openai::chat::ChatCompletionMessageRole::Assistant,
|
||||||
.into(),
|
content: String::from(example_response),
|
||||||
ChatCompletionRequestUserMessageArgs::default()
|
name: None,
|
||||||
.content(prompt)
|
},
|
||||||
.build()?
|
ChatCompletionMessage {
|
||||||
.into(),
|
role: openai::chat::ChatCompletionMessageRole::User,
|
||||||
|
content: prompt,
|
||||||
|
name: None,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
.max_tokens(2048_u16)
|
.max_tokens(2048_u64)
|
||||||
.build()?;
|
.create()
|
||||||
|
.await??;
|
||||||
|
|
||||||
let response = client.chat().create(request).await?;
|
Ok(completion
|
||||||
|
|
||||||
response
|
|
||||||
.choices
|
.choices
|
||||||
.first()
|
.first()
|
||||||
.context("No choices")?
|
.context("No choices")?
|
||||||
.message
|
.message
|
||||||
.content
|
.content
|
||||||
.clone()
|
.clone())
|
||||||
.context("No content")
|
|
||||||
}
|
}
|
||||||
|
|
47
src/queue.rs
47
src/queue.rs
|
@ -6,8 +6,9 @@ use anyhow::Result;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use poise::async_trait;
|
use poise::async_trait;
|
||||||
use songbird::{
|
use songbird::{
|
||||||
|
events::EventData,
|
||||||
input::Input,
|
input::Input,
|
||||||
tracks::{Track, TrackHandle},
|
tracks::{self, Track, TrackHandle},
|
||||||
Driver, Event, EventContext, EventHandler, TrackEvent,
|
Driver, Event, EventContext, EventHandler, TrackEvent,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -30,12 +31,16 @@ struct QueueHandler {
|
||||||
|
|
||||||
impl QueueHandler {
|
impl QueueHandler {
|
||||||
/// Event to remove the track from the queue when it ends
|
/// Event to remove the track from the queue when it ends
|
||||||
pub fn register_track_end_event(
|
pub fn register_track_end_event(track: &mut Track, remote_lock: Arc<Mutex<QueueCore>>) {
|
||||||
track: &mut TrackHandle,
|
let position = track.position();
|
||||||
remote_lock: Arc<Mutex<QueueCore>>,
|
track
|
||||||
) -> Result<()> {
|
.events
|
||||||
track.add_event(Event::Track(TrackEvent::End), QueueHandler { remote_lock })?;
|
.as_mut()
|
||||||
Ok(())
|
.expect("Why is this even an Option?")
|
||||||
|
.add_event(
|
||||||
|
EventData::new(Event::Track(TrackEvent::End), QueueHandler { remote_lock }),
|
||||||
|
position,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,13 +127,14 @@ impl Queue {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a track to the end of the queue
|
/// Adds a track to the end of the queue
|
||||||
pub fn add_to_end(&mut self, source: Input, handler: &mut Driver) -> Result<TrackHandle> {
|
pub fn add_to_end(&mut self, source: Input, handler: &mut Driver) -> TrackHandle {
|
||||||
let mut inner = self.inner.lock();
|
let mut inner = self.inner.lock();
|
||||||
let track = Track::from(source).pause();
|
let (mut track, handle) = tracks::create_player(source);
|
||||||
let mut handle = handler.play(track);
|
track.pause();
|
||||||
QueueHandler::register_track_end_event(&mut handle, self.inner.clone())?;
|
QueueHandler::register_track_end_event(&mut track, self.inner.clone());
|
||||||
inner.tracks.push_back(handle.clone());
|
inner.tracks.push_back(handle.clone());
|
||||||
Ok(handle)
|
handler.play(track);
|
||||||
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds multiple tracks to the end of the queue
|
/// Adds multiple tracks to the end of the queue
|
||||||
|
@ -136,26 +142,27 @@ impl Queue {
|
||||||
&mut self,
|
&mut self,
|
||||||
sources: Vec<Input>,
|
sources: Vec<Input>,
|
||||||
handler: &mut Driver,
|
handler: &mut Driver,
|
||||||
) -> Result<Vec<TrackHandle>> {
|
) -> Vec<TrackHandle> {
|
||||||
let mut handles = Vec::new();
|
let mut handles = Vec::new();
|
||||||
for source in sources {
|
for source in sources {
|
||||||
handles.push(self.add_to_end(source, handler)?);
|
handles.push(self.add_to_end(source, handler));
|
||||||
}
|
}
|
||||||
Ok(handles)
|
handles
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a track to play next
|
/// Adds a track to play next
|
||||||
pub fn add_next(&mut self, source: Input, handler: &mut Driver) -> Result<TrackHandle> {
|
pub fn add_next(&mut self, source: Input, handler: &mut Driver) -> TrackHandle {
|
||||||
let mut inner = self.inner.lock();
|
let mut inner = self.inner.lock();
|
||||||
let track = Track::from(source).pause();
|
let (mut track, handle) = tracks::create_player(source);
|
||||||
let mut handle = handler.play(track);
|
track.pause();
|
||||||
QueueHandler::register_track_end_event(&mut handle, self.inner.clone())?;
|
QueueHandler::register_track_end_event(&mut track, self.inner.clone());
|
||||||
if inner.tracks.is_empty() {
|
if inner.tracks.is_empty() {
|
||||||
inner.tracks.push_back(handle.clone());
|
inner.tracks.push_back(handle.clone());
|
||||||
} else {
|
} else {
|
||||||
inner.tracks.insert(1, handle.clone());
|
inner.tracks.insert(1, handle.clone());
|
||||||
}
|
}
|
||||||
Ok(handle)
|
handler.play(track);
|
||||||
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the queue
|
/// Clears the queue
|
||||||
|
|
Loading…
Add table
Reference in a new issue