commit
e383b4ab41
|
@ -18,15 +18,15 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub's container registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -1,6 +1,17 @@
|
|||
# Changelog
|
||||
|
||||
## 2.2.0 | August 8th 2024
|
||||
## 2.2.1 | August 22nd 2024
|
||||
|
||||
- Added new option: `/playing` can now receive an updating behavior parameter
|
||||
- Added album name to `/playing` embed
|
||||
- Fixed a bug where uncached guilds would panic the bot
|
||||
- Fixed small issue with embed styling
|
||||
- Updated to Rust 1.80.1 (from 1.79.0)
|
||||
- Updated `diesel` and addons to latest versions
|
||||
- Removed `lazy_static` in favor of `LazyLock` (Rust 1.80.0+ feature)
|
||||
- Bumped MSRV to 1.80.0 due to the introduction of `LazyLock`
|
||||
|
||||
## 2.2.0 | August 13th 2024
|
||||
|
||||
### Changes
|
||||
|
||||
|
|
|
@ -86,4 +86,4 @@ cargo build [--release] --features stats
|
|||
|
||||
# MSRV
|
||||
|
||||
The current minimum supported rust version is `1.75.0` _(Checked with `cargo-msrv`)_.
|
||||
The current minimum supported rust version is `1.80.0` _(Checked with `cargo-msrv`)_.
|
||||
|
|
|
@ -592,14 +592,12 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
|||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.9.5"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e"
|
||||
checksum = "6541a3916932fe57768d4be0b1ffb5ec7cbf74ca8c903fdfd5c0fe8aa958f0ed"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"deadpool-runtime",
|
||||
"num_cpus",
|
||||
"retain_mut",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
@ -681,22 +679,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "diesel"
|
||||
version = "2.1.6"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff236accb9a5069572099f0b350a92e9560e8e63a9b8d546162f4a5e03026bb2"
|
||||
checksum = "bf97ee7261bb708fa3402fa9c17a54b70e90e3cb98afb3dc8999d5512cb03f94"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"diesel_derives",
|
||||
"itoa",
|
||||
"pq-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel-async"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acada1517534c92d3f382217b485db8a8638f111b0e3f2a2a8e26165050f77be"
|
||||
checksum = "fcb799bb6f8ca6a794462125d7b8983b0c86e6c93a33a9c55934a4a5de4409d3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"deadpool",
|
||||
|
@ -709,9 +708,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "diesel_async_migrations"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a700d6b83a17973b94d3065970fd2b36f1036c3fe08adcbdce1c9beb8fb25553"
|
||||
checksum = "377dd8e9d0fdab3dbb66236f8f71206b98e121991449f3b363a19d7948333160"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
|
@ -732,11 +731,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "diesel_derives"
|
||||
version = "2.1.4"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14701062d6bed917b5c7103bdffaee1e4609279e240488ad24e7bd979ca6866c"
|
||||
checksum = "d6ff2be1e7312c858b2ef974f5c7089833ae57b5311b334b30923af58e5718d8"
|
||||
dependencies = [
|
||||
"diesel_table_macro_syntax",
|
||||
"dsl_auto_type",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.72",
|
||||
|
@ -744,9 +744,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "diesel_table_macro_syntax"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
|
||||
checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25"
|
||||
dependencies = [
|
||||
"syn 2.0.72",
|
||||
]
|
||||
|
@ -779,6 +779,20 @@ version = "0.15.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dsl_auto_type"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607"
|
||||
dependencies = [
|
||||
"darling 0.20.10",
|
||||
"either",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.72",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
|
@ -2515,6 +2529,15 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pq-sys"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a24ff9e4cf6945c988f0db7005d87747bf72864965c3529d259ad155ac41d584"
|
||||
dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primal-check"
|
||||
version = "0.3.4"
|
||||
|
@ -2904,12 +2927,6 @@ dependencies = [
|
|||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "retain_mut"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
|
@ -3721,7 +3738,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "spoticord"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenvy",
|
||||
|
@ -3753,7 +3770,6 @@ dependencies = [
|
|||
name = "spoticord_config"
|
||||
version = "2.2.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"rspotify",
|
||||
"serenity",
|
||||
]
|
||||
|
@ -4217,9 +4233,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.39.2"
|
||||
version = "1.39.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
|
||||
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
@ -4792,6 +4808,12 @@ dependencies = [
|
|||
"ryu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "8.3.2"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
[package]
|
||||
name = "spoticord"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.75.0"
|
||||
rust-version = "1.80.0"
|
||||
|
||||
[[bin]]
|
||||
name = "spoticord"
|
||||
|
@ -38,7 +38,7 @@ log = "0.4.22"
|
|||
poise = "0.6.1"
|
||||
serenity = "0.12.2"
|
||||
songbird = { version = "0.4.3", features = ["simd-json"] }
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
tokio = { version = "1.39.3", features = ["full"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
|
29
Dockerfile
29
Dockerfile
|
@ -1,11 +1,29 @@
|
|||
# This Dockerfile has been specifically crafted to be run on an AMD64 build host, where
|
||||
# the build should compile for both amd64 and arm64 targets
|
||||
#
|
||||
# Building on any other platform, or building for only a single target will be significantly
|
||||
# slower compared to a platform agnostic Dockerfile, or might not work at all
|
||||
#
|
||||
# This has been done to make this file be optimized for use within GitHub Actions,
|
||||
# as using QEMU to compile takes way too long (multiple hours)
|
||||
|
||||
# Builder
|
||||
FROM --platform=linux/amd64 rust:1.79.0-buster AS builder
|
||||
FROM --platform=linux/amd64 rust:1.80.1-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Add extra build dependencies here
|
||||
RUN apt-get update && apt-get install -yqq \
|
||||
cmake gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
|
||||
RUN apt-get update && apt install -yqq \
|
||||
cmake gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu libpq-dev curl bzip2
|
||||
|
||||
# Manually compile an arm64 build of libpq
|
||||
ENV PGVER=16.4
|
||||
RUN curl -o postgresql.tar.bz2 https://ftp.postgresql.org/pub/source/v${PGVER}/postgresql-${PGVER}.tar.bz2 && \
|
||||
tar xjf postgresql.tar.bz2 && \
|
||||
cd postgresql-${PGVER} && \
|
||||
./configure --host=aarch64-linux-gnu --enable-shared --disable-static --without-readline --without-zlib --without-icu && \
|
||||
cd src/interfaces/libpq && \
|
||||
make
|
||||
|
||||
COPY . .
|
||||
|
||||
|
@ -14,13 +32,14 @@ RUN rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu
|
|||
# Add `--no-default-features` if you don't want stats collection
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/app/target \
|
||||
cargo build --release --target=x86_64-unknown-linux-gnu --target=aarch64-unknown-linux-gnu && \
|
||||
cargo build --release --target=x86_64-unknown-linux-gnu && \
|
||||
RUSTFLAGS="-L /app/postgresql-${PGVER}/src/interfaces/libpq -C linker=aarch64-linux-gnu-gcc" cargo build --release --target=aarch64-unknown-linux-gnu && \
|
||||
# Copy the executables outside of /target as it'll get unmounted after this RUN command
|
||||
cp /app/target/x86_64-unknown-linux-gnu/release/spoticord /app/x86_64 && \
|
||||
cp /app/target/aarch64-unknown-linux-gnu/release/spoticord /app/aarch64
|
||||
|
||||
# Runtime
|
||||
FROM debian:buster-slim
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ENV TARGETPLATFORM=${TARGETPLATFORM}
|
||||
|
|
|
@ -8,5 +8,5 @@ edition = "2021"
|
|||
[dependencies]
|
||||
librespot = { git = "https://github.com/SpoticordMusic/librespot.git", version = "0.5.0-dev", default-features = false }
|
||||
songbird = { version = "0.4.3", features = ["simd-json"] }
|
||||
tokio = { version = "1.39.2", features = ["sync"], default-features = false }
|
||||
tokio = { version = "1.39.3", features = ["sync"], default-features = false }
|
||||
zerocopy = "0.7.35"
|
||||
|
|
|
@ -4,7 +4,6 @@ version = "2.2.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.5.0"
|
||||
rspotify = { version = "0.13.2", default-features = false, features = [
|
||||
"client-reqwest",
|
||||
"reqwest-rustls-tls",
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use lazy_static::lazy_static;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DISCORD_TOKEN: String =
|
||||
std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN environment variable");
|
||||
pub static ref DATABASE_URL: String =
|
||||
std::env::var("DATABASE_URL").expect("missing DATABASE_URL environment variable");
|
||||
pub static ref LINK_URL: String =
|
||||
std::env::var("LINK_URL").expect("missing LINK_URL environment variable");
|
||||
pub static ref SPOTIFY_CLIENT_ID: String =
|
||||
std::env::var("SPOTIFY_CLIENT_ID").expect("missing SPOTIFY_CLIENT_ID environment variable");
|
||||
pub static ref SPOTIFY_CLIENT_SECRET: String = std::env::var("SPOTIFY_CLIENT_SECRET")
|
||||
.expect("missing SPOTIFY_CLIENT_SECRET environment variable");
|
||||
}
|
||||
pub static DISCORD_TOKEN: LazyLock<String> = LazyLock::new(|| {
|
||||
std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN environment variable")
|
||||
});
|
||||
pub static DATABASE_URL: LazyLock<String> = LazyLock::new(|| {
|
||||
std::env::var("DATABASE_URL").expect("missing DATABASE_URL environment variable")
|
||||
});
|
||||
pub static LINK_URL: LazyLock<String> =
|
||||
LazyLock::new(|| std::env::var("LINK_URL").expect("missing LINK_URL environment variable"));
|
||||
pub static SPOTIFY_CLIENT_ID: LazyLock<String> = LazyLock::new(|| {
|
||||
std::env::var("SPOTIFY_CLIENT_ID").expect("missing SPOTIFY_CLIENT_ID environment variable")
|
||||
});
|
||||
pub static SPOTIFY_CLIENT_SECRET: LazyLock<String> = LazyLock::new(|| {
|
||||
std::env::var("SPOTIFY_CLIENT_SECRET")
|
||||
.expect("missing SPOTIFY_CLIENT_SECRET environment variable")
|
||||
});
|
||||
|
|
|
@ -6,8 +6,8 @@ edition = "2021"
|
|||
[dependencies]
|
||||
spoticord_config = { path = "../spoticord_config" }
|
||||
|
||||
diesel = { version = "2.1.6", features = ["chrono"] }
|
||||
diesel-async = { version = "0.4.1", features = ["deadpool", "postgres"] }
|
||||
diesel = { version = "2.2.2", features = ["chrono"] }
|
||||
diesel-async = { version = "0.5.0", features = ["deadpool", "postgres"] }
|
||||
rspotify = { version = "0.13.2", default-features = false, features = [
|
||||
"client-reqwest",
|
||||
"reqwest-rustls-tls",
|
||||
|
@ -15,4 +15,4 @@ rspotify = { version = "0.13.2", default-features = false, features = [
|
|||
chrono = "0.4.38"
|
||||
thiserror = "1.0.63"
|
||||
rand = "0.8.5"
|
||||
diesel_async_migrations = "0.12.0"
|
||||
diesel_async_migrations = "0.13.0"
|
||||
|
|
|
@ -11,7 +11,7 @@ spoticord_utils = { path = "../spoticord_utils" }
|
|||
|
||||
librespot = { git = "https://github.com/SpoticordMusic/librespot.git", version = "0.5.0-dev", default-features = false }
|
||||
songbird = { version = "0.4.3", features = ["simd-json"] }
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
tokio = { version = "1.39.3", features = ["full"] }
|
||||
anyhow = "1.0.86"
|
||||
log = "0.4.22"
|
||||
symphonia = { version = "0.5.4", default-features = false, features = ["pcm"] }
|
||||
|
|
|
@ -65,6 +65,13 @@ impl PlaybackInfo {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn album_name(&self) -> Option<String> {
|
||||
match &self.audio_item.unique_fields {
|
||||
UniqueFields::Episode { .. } => None,
|
||||
UniqueFields::Track { album, .. } => Some(album.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn thumbnail(&self) -> String {
|
||||
self.audio_item
|
||||
.covers
|
||||
|
|
|
@ -11,7 +11,7 @@ spoticord_database = { path = "../spoticord_database" }
|
|||
spoticord_player = { path = "../spoticord_player" }
|
||||
spoticord_utils = { path = "../spoticord_utils" }
|
||||
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
tokio = { version = "1.39.3", features = ["full"] }
|
||||
librespot = { git = "https://github.com/SpoticordMusic/librespot.git", version = "0.5.0-dev", default-features = false }
|
||||
serenity = "0.12.2"
|
||||
songbird = { version = "0.4.3", features = ["simd-json"] }
|
||||
|
|
|
@ -31,7 +31,11 @@ pub enum SessionCommand {
|
|||
GetPlayer(oneshot::Sender<PlayerHandle>),
|
||||
GetActive(oneshot::Sender<bool>),
|
||||
|
||||
CreatePlaybackEmbed(SessionHandle, CommandInteraction),
|
||||
CreatePlaybackEmbed(
|
||||
SessionHandle,
|
||||
CommandInteraction,
|
||||
playback_embed::UpdateBehavior,
|
||||
),
|
||||
CreateLyricsEmbed(SessionHandle, CommandInteraction),
|
||||
|
||||
Reactivate(UserId, oneshot::Sender<Result<()>>),
|
||||
|
@ -207,8 +211,8 @@ impl Session {
|
|||
SessionCommand::GetPlayer(sender) => _ = sender.send(self.player.clone()),
|
||||
SessionCommand::GetActive(sender) => _ = sender.send(self.active),
|
||||
|
||||
SessionCommand::CreatePlaybackEmbed(handle, interaction) => {
|
||||
match PlaybackEmbed::create(self, handle, interaction).await {
|
||||
SessionCommand::CreatePlaybackEmbed(handle, interaction, behavior) => {
|
||||
match PlaybackEmbed::create(self, handle, interaction, behavior).await {
|
||||
Ok(Some(playback_embed)) => {
|
||||
self.playback_embed = Some(playback_embed);
|
||||
}
|
||||
|
@ -274,8 +278,10 @@ impl Session {
|
|||
PlayerEvent::TrackChanged(_) => {}
|
||||
}
|
||||
|
||||
let force_edit = !matches!(event, PlayerEvent::TrackChanged(_));
|
||||
|
||||
if let Some(playback_embed) = &self.playback_embed {
|
||||
if playback_embed.invoke_update().await.is_err() {
|
||||
if playback_embed.invoke_update(force_edit).await.is_err() {
|
||||
self.playback_embed = None;
|
||||
}
|
||||
}
|
||||
|
@ -457,11 +463,16 @@ impl SessionHandle {
|
|||
/// Create a playback embed as a response to an interaction
|
||||
///
|
||||
/// This playback embed will automatically update when certain events happen
|
||||
pub async fn create_playback_embed(&self, interaction: CommandInteraction) -> Result<()> {
|
||||
pub async fn create_playback_embed(
|
||||
&self,
|
||||
interaction: CommandInteraction,
|
||||
behavior: playback_embed::UpdateBehavior,
|
||||
) -> Result<()> {
|
||||
self.commands
|
||||
.send(SessionCommand::CreatePlaybackEmbed(
|
||||
self.clone(),
|
||||
interaction,
|
||||
behavior,
|
||||
))
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use log::{error, trace};
|
||||
use poise::ChoiceParameter;
|
||||
use serenity::{
|
||||
all::{
|
||||
ButtonStyle, CommandInteraction, ComponentInteraction, ComponentInteractionCollector,
|
||||
Context, CreateActionRow, CreateButton, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter,
|
||||
CreateInteractionResponse, CreateInteractionResponseFollowup,
|
||||
CreateInteractionResponseMessage, EditMessage, Message, User,
|
||||
CreateInteractionResponseMessage, CreateMessage, EditMessage, Message, User,
|
||||
},
|
||||
futures::StreamExt,
|
||||
};
|
||||
|
@ -18,7 +19,30 @@ use crate::{Session, SessionHandle};
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum Command {
|
||||
InvokeUpdate,
|
||||
InvokeUpdate(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ChoiceParameter)]
|
||||
pub enum UpdateBehavior {
|
||||
#[default]
|
||||
#[name = "Automatically update the embed"]
|
||||
Default,
|
||||
|
||||
#[name = "Do not update the embed"]
|
||||
Static,
|
||||
|
||||
#[name = "Re-send the embed after track changes"]
|
||||
Pinned,
|
||||
}
|
||||
|
||||
impl UpdateBehavior {
|
||||
pub fn is_static(&self) -> bool {
|
||||
matches!(self, Self::Static)
|
||||
}
|
||||
|
||||
pub fn is_pinned(&self) -> bool {
|
||||
matches!(self, Self::Pinned)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlaybackEmbed {
|
||||
|
@ -29,6 +53,8 @@ pub struct PlaybackEmbed {
|
|||
|
||||
last_update: Instant,
|
||||
update_in: Option<Duration>,
|
||||
force_edit: bool,
|
||||
update_behavior: UpdateBehavior,
|
||||
|
||||
rx: mpsc::Receiver<Command>,
|
||||
}
|
||||
|
@ -38,6 +64,7 @@ impl PlaybackEmbed {
|
|||
session: &Session,
|
||||
handle: SessionHandle,
|
||||
interaction: CommandInteraction,
|
||||
update_behavior: UpdateBehavior,
|
||||
) -> Result<Option<PlaybackEmbedHandle>> {
|
||||
let ctx = session.context.clone();
|
||||
|
||||
|
@ -69,6 +96,11 @@ impl PlaybackEmbed {
|
|||
)
|
||||
.await?;
|
||||
|
||||
// If this is a static embed, we don't need to return any handles
|
||||
if update_behavior.is_static() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Retrieve message instead of editing interaction response, as those tokens are only valid for 15 minutes
|
||||
let message = interaction.get_response(&ctx).await?;
|
||||
|
||||
|
@ -84,6 +116,8 @@ impl PlaybackEmbed {
|
|||
message,
|
||||
last_update: Instant::now(),
|
||||
update_in: None,
|
||||
force_edit: false,
|
||||
update_behavior,
|
||||
rx,
|
||||
};
|
||||
|
||||
|
@ -121,7 +155,7 @@ impl PlaybackEmbed {
|
|||
tokio::time::sleep(update_in).await;
|
||||
}
|
||||
}, if self.update_in.is_some() => {
|
||||
if self.update_embed().await.is_break() {
|
||||
if self.update_embed(self.force_edit).await.is_break() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -133,15 +167,16 @@ impl PlaybackEmbed {
|
|||
trace!("Received command: {command:?}");
|
||||
|
||||
match command {
|
||||
Command::InvokeUpdate => {
|
||||
Command::InvokeUpdate(force_edit) => {
|
||||
if self.last_update.elapsed() < Duration::from_secs(2) {
|
||||
if self.update_in.is_some() {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
self.update_in = Some(Duration::from_secs(2) - self.last_update.elapsed());
|
||||
self.force_edit = force_edit;
|
||||
} else {
|
||||
self.update_embed().await?;
|
||||
self.update_embed(force_edit).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +251,7 @@ impl PlaybackEmbed {
|
|||
Ok((player, playback_info, owner))
|
||||
}
|
||||
|
||||
async fn update_embed(&mut self) -> ControlFlow<(), ()> {
|
||||
async fn update_embed(&mut self, force_edit: bool) -> ControlFlow<(), ()> {
|
||||
self.update_in = None;
|
||||
|
||||
let Ok(owner) = self.session.owner().await else {
|
||||
|
@ -246,7 +281,30 @@ impl PlaybackEmbed {
|
|||
}
|
||||
};
|
||||
|
||||
if let Err(why) = self
|
||||
let should_pin = !force_edit && self.update_behavior.is_pinned();
|
||||
|
||||
if should_pin {
|
||||
self.message.delete(&self.ctx).await.ok();
|
||||
|
||||
match self
|
||||
.message
|
||||
.channel_id
|
||||
.send_message(
|
||||
&self.ctx,
|
||||
CreateMessage::new()
|
||||
.embed(build_embed(&playback_info, &owner))
|
||||
.components(vec![build_buttons(self.id, playback_info.playing())]),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(message) => self.message = message,
|
||||
Err(why) => {
|
||||
error!("Failed to update playback embed: {why}");
|
||||
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
};
|
||||
} else if let Err(why) = self
|
||||
.message
|
||||
.edit(
|
||||
&self.ctx,
|
||||
|
@ -259,7 +317,7 @@ impl PlaybackEmbed {
|
|||
error!("Failed to update playback embed: {why}");
|
||||
|
||||
return ControlFlow::Break(());
|
||||
};
|
||||
}
|
||||
|
||||
self.last_update = Instant::now();
|
||||
|
||||
|
@ -267,6 +325,18 @@ impl PlaybackEmbed {
|
|||
}
|
||||
|
||||
async fn update_not_playing(&mut self) -> Result<()> {
|
||||
// If pinned, try to delete old message and send new one
|
||||
if self.update_behavior.is_pinned() {
|
||||
self.message.delete(&self.ctx).await.ok();
|
||||
self.message = self
|
||||
.message
|
||||
.channel_id
|
||||
.send_message(&self.ctx, CreateMessage::new().embed(not_playing_embed()))
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.message
|
||||
.edit(&self.ctx, EditMessage::new().embed(not_playing_embed()))
|
||||
.await?;
|
||||
|
@ -284,8 +354,8 @@ impl PlaybackEmbedHandle {
|
|||
!self.tx.is_closed()
|
||||
}
|
||||
|
||||
pub async fn invoke_update(&self) -> Result<()> {
|
||||
self.tx.send(Command::InvokeUpdate).await?;
|
||||
pub async fn invoke_update(&self, force_edit: bool) -> Result<()> {
|
||||
self.tx.send(Command::InvokeUpdate(force_edit)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -331,21 +401,27 @@ fn build_embed(playback_info: &PlaybackInfo, owner: &User) -> CreateEmbed {
|
|||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
description += &format!("By {artists}\n\n");
|
||||
description += &format!("By {artists}\n");
|
||||
}
|
||||
|
||||
if let Some(album_name) = playback_info.album_name() {
|
||||
description += &format!("Album: **{album_name}**\n");
|
||||
}
|
||||
|
||||
if let Some(show_name) = playback_info.show_name() {
|
||||
description += &format!("On {show_name}\n\n");
|
||||
description += &format!("On {show_name}\n");
|
||||
}
|
||||
|
||||
description += "\n";
|
||||
|
||||
let position = playback_info.current_position();
|
||||
let index = position * 20 / playback_info.duration();
|
||||
|
||||
description.push_str(if playback_info.playing() {
|
||||
description += if playback_info.playing() {
|
||||
"▶️ "
|
||||
} else {
|
||||
"⏸️ "
|
||||
});
|
||||
};
|
||||
|
||||
for i in 0..20 {
|
||||
if i == index {
|
||||
|
@ -355,12 +431,12 @@ fn build_embed(playback_info: &PlaybackInfo, owner: &User) -> CreateEmbed {
|
|||
}
|
||||
}
|
||||
|
||||
description.push_str("\n:alarm_clock: ");
|
||||
description.push_str(&format!(
|
||||
description += "\n:alarm_clock: ";
|
||||
description += &format!(
|
||||
"{} / {}",
|
||||
spoticord_utils::time_to_string(position / 1000),
|
||||
spoticord_utils::time_to_string(playback_info.duration() / 1000)
|
||||
));
|
||||
);
|
||||
|
||||
CreateEmbed::new()
|
||||
.author(
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::bot::Context;
|
|||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn disconnect(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let manager = ctx.data();
|
||||
let guild = ctx.guild().expect("poise lied to me").id;
|
||||
let guild = ctx.guild_id().expect("poise lied to me");
|
||||
|
||||
let Some(session) = manager.get_session(SessionQuery::Guild(guild)) else {
|
||||
ctx.send(
|
||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use log::error;
|
||||
use poise::CreateReply;
|
||||
use serenity::all::{
|
||||
Channel, ChannelId, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, Guild, UserId,
|
||||
Channel, ChannelId, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, UserId,
|
||||
};
|
||||
use spoticord_database::error::DatabaseError;
|
||||
use spoticord_session::manager::SessionQuery;
|
||||
|
@ -15,9 +15,30 @@ use crate::bot::Context;
|
|||
/// Join the current voice channel
|
||||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn join(ctx: Context<'_>) -> Result<()> {
|
||||
let guild: Guild = ctx.guild().expect("poise lied to me").clone();
|
||||
let guild = ctx.guild_id().expect("poise lied to me");
|
||||
let manager = ctx.data();
|
||||
|
||||
let Some(guild) = guild
|
||||
.to_guild_cached(ctx.serenity_context())
|
||||
.map(|guild| guild.clone())
|
||||
else {
|
||||
error!("Unable to fetch guild from cache, how did we get here?");
|
||||
|
||||
ctx.send(
|
||||
CreateReply::default()
|
||||
.embed(
|
||||
CreateEmbed::new()
|
||||
.title("An error occured")
|
||||
.description("This server hasn't been cached yet?")
|
||||
.color(Colors::Error),
|
||||
)
|
||||
.ephemeral(true),
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(channel) = guild
|
||||
.voice_states
|
||||
.get(&ctx.author().id)
|
||||
|
@ -98,8 +119,6 @@ pub async fn join(ctx: Context<'_>) -> Result<()> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
ctx.defer().await?;
|
||||
|
||||
let mut session_opt = manager.get_session(SessionQuery::Guild(guild.id));
|
||||
|
||||
// Check if this server already has a session active
|
||||
|
@ -134,7 +153,8 @@ pub async fn join(ctx: Context<'_>) -> Result<()> {
|
|||
"You are already using Spoticord in `{}`\n\n\
|
||||
Stop playing in that server first before starting a new session.",
|
||||
spoticord_utils::discord::escape(server_name)
|
||||
)),
|
||||
))
|
||||
.color(Colors::Error),
|
||||
)
|
||||
.ephemeral(true),
|
||||
)
|
||||
|
@ -143,6 +163,8 @@ pub async fn join(ctx: Context<'_>) -> Result<()> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
ctx.defer().await?;
|
||||
|
||||
if let Some(session) = &session_opt {
|
||||
if session.voice_channel() != channel {
|
||||
session.disconnect().await;
|
||||
|
@ -163,7 +185,7 @@ pub async fn join(ctx: Context<'_>) -> Result<()> {
|
|||
CreateEmbed::new()
|
||||
.title("Failed to reactivate session")
|
||||
.description(
|
||||
"An error occured whilst trying to reactivate the session.",
|
||||
"An error occured whilst trying to reactivate the session. Please try again.",
|
||||
)
|
||||
.color(Colors::Error),
|
||||
)
|
||||
|
@ -190,7 +212,9 @@ pub async fn join(ctx: Context<'_>) -> Result<()> {
|
|||
.embed(
|
||||
CreateEmbed::new()
|
||||
.title("Failed to create session")
|
||||
.description("An error occured whilst trying to create a session.")
|
||||
.description(
|
||||
"An error occured whilst trying to create a session. Please try again.",
|
||||
)
|
||||
.color(Colors::Error),
|
||||
)
|
||||
.ephemeral(true),
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::bot::Context;
|
|||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn lyrics(ctx: Context<'_>) -> Result<()> {
|
||||
let manager = ctx.data();
|
||||
let guild = ctx.guild().expect("poise lied to me").id;
|
||||
let guild = ctx.guild_id().expect("poise lied to me");
|
||||
|
||||
let Some(session) = manager.get_session(SessionQuery::Guild(guild)) else {
|
||||
ctx.send(
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
use anyhow::Result;
|
||||
use poise::CreateReply;
|
||||
use serenity::all::CreateEmbed;
|
||||
use spoticord_session::manager::SessionQuery;
|
||||
use spoticord_session::{manager::SessionQuery, playback_embed::UpdateBehavior};
|
||||
use spoticord_utils::discord::Colors;
|
||||
|
||||
use crate::bot::Context;
|
||||
|
||||
/// Show details of the current song that is being played
|
||||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn playing(ctx: Context<'_>) -> Result<()> {
|
||||
pub async fn playing(
|
||||
ctx: Context<'_>,
|
||||
#[description = "How Spoticord should update this information"] update_behavior: Option<
|
||||
UpdateBehavior,
|
||||
>,
|
||||
) -> Result<()> {
|
||||
let manager = ctx.data();
|
||||
let guild = ctx.guild().expect("poise lied to me").id;
|
||||
let guild = ctx.guild_id().expect("poise lied to me");
|
||||
|
||||
let Some(session) = manager.get_session(SessionQuery::Guild(guild)) else {
|
||||
ctx.send(
|
||||
|
@ -33,7 +38,10 @@ pub async fn playing(ctx: Context<'_>) -> Result<()> {
|
|||
};
|
||||
|
||||
session
|
||||
.create_playback_embed(context.interaction.clone())
|
||||
.create_playback_embed(
|
||||
context.interaction.clone(),
|
||||
update_behavior.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::bot::Context;
|
|||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn stop(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let manager = ctx.data();
|
||||
let guild = ctx.guild().expect("poise lied to me").id;
|
||||
let guild = ctx.guild_id().expect("poise lied to me");
|
||||
|
||||
let Some(session) = manager.get_session(SessionQuery::Guild(guild)) else {
|
||||
ctx.send(
|
||||
|
|
Loading…
Reference in New Issue