Merge pull request #25 from SpoticordMusic/dev

PR: Update Spoticord to v2.1.2
main
Daniel 2023-09-28 14:18:14 +02:00 committed by GitHub
commit d1c01e9274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 361 additions and 417 deletions

View File

@ -4,3 +4,5 @@ protocol = "sparse"
[target.x86_64-pc-windows-gnu] [target.x86_64-pc-windows-gnu]
rustflags = "-C link-args=-lssp" # Does not compile without this line rustflags = "-C link-args=-lssp" # Does not compile without this line
[target.aarch64-unknown-linux-gnu]
rustflags = "-C linker=aarch64-linux-gnu-gcc"

View File

@ -1,14 +1,21 @@
name: Build and push to repository name: Build and push to registry
on: on:
push: push:
branches: [ "main" ] branches: [ "main", "dev" ]
tags: [ "v*.*.*" ]
pull_request:
branches: [ "main", "dev" ]
workflow_dispatch:
permissions:
packages: write
contents: read
jobs: jobs:
build-and-push: build-and-push:
name: Build Docker image and push to repository name: Build Docker image and push to registry
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -17,18 +24,43 @@ jobs:
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to repository - name: Login to GitHub's container registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ${{ secrets.REGISTRY_URL }} registry: ghcr.io
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ github.repository_owner }}
password: ${{ secrets.REGISTRY_PASSWORD }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: Entepotenz/change-string-case-action-min-dependencies@v1 # https://github.com/orgs/community/discussions/10553
id: repo-uri-string
with:
string: ghcr.io/${{ github.repository }}
- name: Generate image metadata
id: docker-meta # used in next step
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: ${{ steps.repo-uri-string.outputs.lowercase }}
# Docker tags based on the following events/attributes
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v2 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile platforms: linux/amd64,linux/arm64
tags: | push: ${{ github.event_name != 'pull_request' }}
${{ secrets.REGISTRY_URL }}/spoticord/spoticord:latest tags: ${{ steps.docker-meta.outputs.tags }}
push: ${{ github.ref == 'refs/heads/main' }} labels: ${{ steps.docker-meta.outputs.labels }}
# Some basic caching of the layers...
cache-from: ${{ steps.repo-uri-string.outputs.lowercase }}:latest-cache
cache-to: ${{ steps.repo-uri-string.outputs.lowercase }}:latest-cache

View File

@ -1,3 +1,47 @@
# Changelog # Changelog
## 2.1.2 | September 28th 2023
### Changes
* Removed OpenSSL dependency
* Added aarch64 support
* Added cross compilation to Github Actions
* Added `dev` branch to Github Actions
* Removed hardcoded URL in the /join command
* Fixed an issue in /playing where the bot showed it was playing even though it was paused
**Full Changelog**: https://github.com/SpoticordMusic/spoticord/compare/v2.1.1...v2.1.2
## 2.1.1 | September 23rd 2023
Reduced the amount of CPU that the bot uses from ~15%-25% per user to 1%-2% per user (percentage per core, benched on an AMD Ryzen 9 5950X).
### Changes
* Fixed issue #20
**Full Changelog**: https://github.com/SpoticordMusic/spoticord/compare/v2.1.0...v2.1.1
## 2.1.0 | September 20th 2023
So, it's been a while since I worked on this project, and some bugs have since been discovered.
The main focus for this version is to stop using multiple processes for every player, and instead do everything in threads.
### Changes
- Remove metrics, as I wasn't using this feature anyways
- Bring back KV for storing total/active sessions, as prometheus is no longer being used
- Allocate new players in-memory, instead of using subprocesses
- Fix issue #17
- Fix some issues with the auto-disconnect
- Removed the automatic device switching on bot join, which was causing some people to not be able to use the bot
- Force communication through the closest Spotify AP, reducing latency
- Potential jitter reduction
- Enable autoplay
- After skipping a song, you will no longer hear a tiny bit of the previous song after the silence
**Full Changelog**: https://github.com/SpoticordMusic/spoticord/compare/v2.0.0...v2.1.0
### Issues
- Currently, the CPU usage is much higher than it used to be. I really wanted to push this update out before taking the time to do some optimizations, as the bot and server are still easily able to hold up the limited amount of Spoticord users (and v2.0.0 was just falling apart). Issue is being tracked in #20
## 2.0.0 | June 8th 2023 ## 2.0.0 | June 8th 2023
- Initial Release - Initial Release

View File

@ -27,17 +27,17 @@ sudo pacman -S base-devel
sudo dnf install gcc sudo dnf install gcc
``` ```
Additionally, you will need to install CMake and OpenSSL (Linux only). On Windows, you can download CMake [here](https://cmake.org/download/). On Linux, you can use your package manager to install them: Additionally, you will need to install CMake. On Windows, you can download CMake [here](https://cmake.org/download/). On Linux, you can use your package manager to install it:
```sh ```sh
# Debian/Ubuntu # Debian/Ubuntu
sudo apt install cmake libssl-dev sudo apt install cmake
# Arch # Arch
sudo pacman -S cmake openssl sudo pacman -S cmake
# Fedora # Fedora
sudo dnf install cmake openssl-devel sudo dnf install cmake
``` ```
## Compiling ## Compiling

179
Cargo.lock generated
View File

@ -328,16 +328,6 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.4" version = "0.8.4"
@ -541,9 +531,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.0.0" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]] [[package]]
name = "fixedbitset" name = "fixedbitset"
@ -580,21 +570,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.0" version = "1.2.0"
@ -940,19 +915,6 @@ dependencies = [
"tokio-rustls 0.24.1", "tokio-rustls 0.24.1",
] ]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.57" version = "0.1.57"
@ -1409,24 +1371,6 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.23.2" version = "0.23.2"
@ -1531,50 +1475,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "2.10.0" version = "2.10.0"
@ -1827,8 +1727,6 @@ dependencies = [
"itoa", "itoa",
"percent-encoding", "percent-encoding",
"ryu", "ryu",
"sha1_smol",
"socket2 0.4.9",
"url", "url",
] ]
@ -1910,13 +1808,11 @@ dependencies = [
"http-body", "http-body",
"hyper", "hyper",
"hyper-rustls", "hyper-rustls",
"hyper-tls",
"ipnet", "ipnet",
"js-sys", "js-sys",
"log", "log",
"mime", "mime",
"mime_guess", "mime_guess",
"native-tls",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
@ -1926,7 +1822,6 @@ dependencies = [
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"tokio", "tokio",
"tokio-native-tls",
"tokio-rustls 0.24.1", "tokio-rustls 0.24.1",
"tokio-util", "tokio-util",
"tower-service", "tower-service",
@ -2068,15 +1963,6 @@ dependencies = [
"libsamplerate-sys", "libsamplerate-sys",
] ]
[[package]]
name = "schannel"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.1" version = "1.0.1"
@ -2099,29 +1985,6 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "security-framework"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.19" version = "1.0.19"
@ -2274,12 +2137,6 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
] ]
[[package]]
name = "sha1_smol"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]] [[package]]
name = "shannon" name = "shannon"
version = "0.2.0" version = "0.2.0"
@ -2398,12 +2255,13 @@ dependencies = [
[[package]] [[package]]
name = "spoticord" name = "spoticord"
version = "2.1.1" version = "2.1.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dotenv", "dotenv",
"env_logger 0.10.0", "env_logger 0.10.0",
"hex", "hex",
"lazy_static",
"librespot", "librespot",
"log", "log",
"protobuf", "protobuf",
@ -2415,7 +2273,6 @@ dependencies = [
"serenity", "serenity",
"songbird", "songbird",
"thiserror", "thiserror",
"time",
"tokio", "tokio",
"zerocopy 0.7.5", "zerocopy 0.7.5",
] ]
@ -2543,9 +2400,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.28" version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
@ -2556,15 +2413,15 @@ dependencies = [
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.14" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
dependencies = [ dependencies = [
"time-core", "time-core",
] ]
@ -2614,16 +2471,6 @@ dependencies = [
"syn 2.0.37", "syn 2.0.37",
] ]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.23.4" version = "0.23.4"
@ -2888,12 +2735,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "vergen" name = "vergen"
version = "3.2.0" version = "3.2.0"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "spoticord" name = "spoticord"
version = "2.1.1" version = "2.1.2"
edition = "2021" edition = "2021"
rust-version = "1.65.0" rust-version = "1.65.0"
@ -16,18 +16,18 @@ anyhow = "1.0.75"
dotenv = "0.15.0" dotenv = "0.15.0"
env_logger = "0.10.0" env_logger = "0.10.0"
hex = "0.4.3" hex = "0.4.3"
lazy_static = "1.4.0"
librespot = { version = "0.4.2", default-features = false } librespot = { version = "0.4.2", default-features = false }
log = "0.4.20" log = "0.4.20"
protobuf = "2.28.0" protobuf = "2.28.0"
redis = { version = "0.23.3", optional = true } redis = { version = "0.23.3", optional = true, default-features = false }
reqwest = "0.11.20" reqwest = { version = "0.11.20", default-features = false }
samplerate = "0.2.4" samplerate = "0.2.4"
serde = "1.0.188" serde = "1.0.188"
serde_json = "1.0.107" serde_json = "1.0.107"
serenity = { version = "0.11.6", features = ["framework", "cache", "standard_framework"], default-features = false } serenity = { version = "0.11.6", features = ["framework", "cache", "standard_framework", "rustls_backend", "gateway"], default-features = false }
songbird = "0.3.2" songbird = { version = "0.3.2", features = ["driver", "serenity-rustls"], default-features = false }
thiserror = "1.0.48" thiserror = "1.0.48"
time = "0.3.28"
tokio = { version = "1.32.0", features = ["rt", "full"] } tokio = { version = "1.32.0", features = ["rt", "full"] }
zerocopy = "0.7.5" zerocopy = "0.7.5"

View File

@ -1,25 +1,44 @@
# Builder # Builder
FROM rust:1.72.1-buster as builder FROM --platform=linux/amd64 rust:1.72.1-buster as builder
WORKDIR /app WORKDIR /app
# Add extra build dependencies here # Add extra build dependencies here
RUN apt-get update && apt-get install -y cmake RUN apt-get update && apt-get install -yqq \
cmake gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
COPY . . COPY . .
# Remove `--features stats` if you want to deploy without stats collection RUN rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu
RUN cargo install --path . --features stats
# Remove `--features=stats` if you want to deploy without stats collection
RUN cargo build --features=stats --release \
--target=x86_64-unknown-linux-gnu --target=aarch64-unknown-linux-gnu
# Runtime # Runtime
FROM debian:buster-slim FROM debian:buster-slim
WORKDIR /app ARG TARGETPLATFORM
ENV TARGETPLATFORM=$TARGETPLATFORM
# Add extra runtime dependencies here # Add extra runtime dependencies here
RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/* # RUN apt-get update && apt-get install -yqq --no-install-recommends \
# openssl ca-certificates && rm -rf /var/lib/apt/lists/*
# Copy spoticord binary from builder # Copy spoticord binaries from builder to /tmp
COPY --from=builder /usr/local/cargo/bin/spoticord ./spoticord COPY --from=builder \
/app/target/x86_64-unknown-linux-gnu/release/spoticord /tmp/x86_64
COPY --from=builder \
/app/target/aarch64-unknown-linux-gnu/release/spoticord /tmp/aarch64
CMD ["./spoticord"] # Copy appropiate binary for target arch from /tmp
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
cp /tmp/x86_64 /usr/local/bin/spoticord; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
cp /tmp/aarch64 /usr/local/bin/spoticord; \
fi
# Delete unused binaries
RUN rm -rvf /tmp/x86_64 /tmp/aarch64
ENTRYPOINT [ "/usr/local/bin/spoticord" ]

View File

@ -8,6 +8,7 @@ use serenity::{
use crate::{ use crate::{
bot::commands::{respond_message, CommandOutput}, bot::commands::{respond_message, CommandOutput},
consts::SPOTICORD_ACCOUNTS_URL,
database::{Database, DatabaseError}, database::{Database, DatabaseError},
utils::embed::{EmbedBuilder, Status}, utils::embed::{EmbedBuilder, Status},
}; };
@ -39,8 +40,11 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
} }
if let Ok(request) = database.get_user_request(command.user.id.to_string()).await { if let Ok(request) = database.get_user_request(command.user.id.to_string()).await {
let base = std::env::var("SPOTICORD_ACCOUNTS_URL").expect("to be present"); let link = format!(
let link = format!("{}/spotify/{}", base, request.token); "{}/spotify/{}",
SPOTICORD_ACCOUNTS_URL.as_str(),
request.token
);
respond_message( respond_message(
&ctx, &ctx,
@ -106,8 +110,11 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
.await .await
{ {
Ok(request) => { Ok(request) => {
let base = std::env::var("SPOTICORD_ACCOUNTS_URL").expect("to be present"); let link = format!(
let link = format!("{}/spotify/{}", base, request.token); "{}/spotify/{}",
SPOTICORD_ACCOUNTS_URL.as_str(),
request.token
);
respond_message( respond_message(
&ctx, &ctx,

View File

@ -7,6 +7,7 @@ use serenity::{
use crate::{ use crate::{
bot::commands::{defer_message, respond_message, update_message, CommandOutput}, bot::commands::{defer_message, respond_message, update_message, CommandOutput},
consts::SPOTICORD_ACCOUNTS_URL,
session::manager::{SessionCreateError, SessionManager}, session::manager::{SessionCreateError, SessionManager},
utils::embed::{EmbedBuilder, Status}, utils::embed::{EmbedBuilder, Status},
}; };
@ -241,7 +242,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
&command, &command,
EmbedBuilder::new() EmbedBuilder::new()
.title("Cannot join voice channel") .title("Cannot join voice channel")
.description("You need to link your Spotify account. Use </link:1036714850367320136> or go to [the accounts website](https://account.spoticord.com/) to get started.") .description(format!("You need to link your Spotify account. Use </link:1036714850367320136> or go to [the accounts website]({}) to get started.", SPOTICORD_ACCOUNTS_URL.as_str()))
.status(Status::Error) .status(Status::Error)
.build(), .build(),
) )
@ -255,7 +256,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
&command, &command,
EmbedBuilder::new() EmbedBuilder::new()
.title("Cannot join voice channel") .title("Cannot join voice channel")
.description("Spoticord no longer has access to your Spotify account. Use </link:1036714850367320136> or go to [the accounts website](https://account.spoticord.com/) to relink your Spotify account.") .description(format!("Spoticord no longer has access to your Spotify account. Use </link:1036714850367320136> or go to [the accounts website]({}) to relink your Spotify account.", SPOTICORD_ACCOUNTS_URL.as_str()))
.status(Status::Error) .status(Status::Error)
.build(), .build(),
).await; ).await;

View File

@ -30,7 +30,8 @@ pub const NAME: &str = "playing";
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
Box::pin(async move { Box::pin(async move {
let not_playing = async { macro_rules! not_playing {
() => {
respond_message( respond_message(
&ctx, &ctx,
&command, &command,
@ -44,6 +45,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
) )
.await; .await;
}; };
}
let data = ctx.data.read().await; let data = ctx.data.read().await;
let session_manager = data let session_manager = data
@ -51,41 +53,30 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
.expect("to contain a value") .expect("to contain a value")
.clone(); .clone();
let session = match session_manager let Some(session) = session_manager
.get_session(command.guild_id.expect("to contain a value")) .get_session(command.guild_id.expect("to contain a value"))
.await .await
{ else {
Some(session) => session, not_playing!();
None => {
not_playing.await;
return; return;
}
}; };
let owner = match session.owner().await { let Some(owner) = session.owner().await else {
Some(owner) => owner, not_playing!();
None => {
not_playing.await;
return; return;
}
}; };
// Get Playback Info from session // Get Playback Info from session
let pbi = match session.playback_info().await { let Some(pbi) = session.playback_info().await else {
Some(pbi) => pbi, not_playing!();
None => {
not_playing.await;
return; return;
}
}; };
// Get owner of session // Get owner of session
let owner = match utils::discord::get_user(&ctx, owner).await { let Some(owner) = utils::discord::get_user(&ctx, owner).await else {
Some(user) => user,
None => {
// This shouldn't happen // This shouldn't happen
error!("Could not find user with ID: {owner}"); error!("Could not find user with ID: {owner}");
@ -106,7 +97,6 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
.await; .await;
return; return;
}
}; };
// Get metadata // Get metadata
@ -188,12 +178,10 @@ pub fn component(ctx: Context, mut interaction: MessageComponentInteraction) ->
.clone(); .clone();
// Check if session still exists // Check if session still exists
let mut session = match session_manager let Some(mut session) = session_manager
.get_session(interaction.guild_id.expect("to contain a value")) .get_session(interaction.guild_id.expect("to contain a value"))
.await .await
{ else {
Some(session) => session,
None => {
error_edit( error_edit(
"Cannot perform action", "Cannot perform action",
"I'm currently not playing any music in this server", "I'm currently not playing any music in this server",
@ -201,13 +189,10 @@ pub fn component(ctx: Context, mut interaction: MessageComponentInteraction) ->
.await; .await;
return; return;
}
}; };
// Check if the session contains an owner // Check if the session contains an owner
let owner = match session.owner().await { let Some(owner) = session.owner().await else {
Some(owner) => owner,
None => {
error_edit( error_edit(
"Cannot change playback state", "Cannot change playback state",
"I'm currently not playing any music in this server", "I'm currently not playing any music in this server",
@ -215,13 +200,10 @@ pub fn component(ctx: Context, mut interaction: MessageComponentInteraction) ->
.await; .await;
return; return;
}
}; };
// Get Playback Info from session // Get Playback Info from session
let pbi = match session.playback_info().await { let Some(pbi) = session.playback_info().await else {
Some(pbi) => pbi,
None => {
error_edit( error_edit(
"Cannot change playback state", "Cannot change playback state",
"I'm currently not playing any music in this server", "I'm currently not playing any music in this server",
@ -229,7 +211,6 @@ pub fn component(ctx: Context, mut interaction: MessageComponentInteraction) ->
.await; .await;
return; return;
}
}; };
// Check if the user is the owner of the session // Check if the user is the owner of the session
@ -244,9 +225,7 @@ pub fn component(ctx: Context, mut interaction: MessageComponentInteraction) ->
} }
// Get owner of session // Get owner of session
let owner = match utils::discord::get_user(&ctx, owner).await { let Some(owner) = utils::discord::get_user(&ctx, owner).await else {
Some(user) => user,
None => {
// This shouldn't happen // This shouldn't happen
error!("Could not find user with ID: {owner}"); error!("Could not find user with ID: {owner}");
@ -267,7 +246,6 @@ pub fn component(ctx: Context, mut interaction: MessageComponentInteraction) ->
.await; .await;
return; return;
}
}; };
// Send the desired command to the session // Send the desired command to the session
@ -370,12 +348,10 @@ async fn update_embed(interaction: &mut MessageComponentInteraction, ctx: &Conte
.clone(); .clone();
// Check if session still exists // Check if session still exists
let session = match session_manager let Some(session) = session_manager
.get_session(interaction.guild_id.expect("to contain a value")) .get_session(interaction.guild_id.expect("to contain a value"))
.await .await
{ else {
Some(session) => session,
None => {
error_edit( error_edit(
"Cannot perform action", "Cannot perform action",
"I'm currently not playing any music in this server", "I'm currently not playing any music in this server",
@ -383,13 +359,10 @@ async fn update_embed(interaction: &mut MessageComponentInteraction, ctx: &Conte
.await; .await;
return; return;
}
}; };
// Get Playback Info from session // Get Playback Info from session
let pbi = match session.playback_info().await { let Some(pbi) = session.playback_info().await else {
Some(pbi) => pbi,
None => {
error_edit( error_edit(
"Cannot change playback state", "Cannot change playback state",
"I'm currently not playing any music in this server", "I'm currently not playing any music in this server",
@ -397,7 +370,6 @@ async fn update_embed(interaction: &mut MessageComponentInteraction, ctx: &Conte
.await; .await;
return; return;
}
}; };
let (title, description, thumbnail) = get_metadata(&pbi); let (title, description, thumbnail) = get_metadata(&pbi);

View File

@ -54,15 +54,14 @@ impl EventHandler for Handler {
// INTERACTION_CREATE event, emitted when the bot receives an interaction (slash command, button, etc.) // INTERACTION_CREATE event, emitted when the bot receives an interaction (slash command, button, etc.)
async fn interaction_create(&self, ctx: Context, interaction: Interaction) { async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
match interaction { match interaction {
Interaction::ApplicationCommand(command) => self.handle_command(ctx, command).await, Interaction::ApplicationCommand(command) => handle_command(ctx, command).await,
Interaction::MessageComponent(component) => self.handle_component(ctx, component).await, Interaction::MessageComponent(component) => handle_component(ctx, component).await,
_ => {} _ => {}
} }
} }
} }
impl Handler { async fn handle_command(ctx: Context, command: ApplicationCommandInteraction) {
async fn handle_command(&self, ctx: Context, command: ApplicationCommandInteraction) {
enforce_guild!(command); enforce_guild!(command);
// Commands must only be executed inside of guilds // Commands must only be executed inside of guilds
@ -99,7 +98,7 @@ impl Handler {
command_manager.execute_command(&ctx, command).await; command_manager.execute_command(&ctx, command).await;
} }
async fn handle_component(&self, ctx: Context, component: MessageComponentInteraction) { async fn handle_component(ctx: Context, component: MessageComponentInteraction) {
enforce_guild!(component); enforce_guild!(component);
// Components can only be interacted with inside of guilds // Components can only be interacted with inside of guilds
@ -135,4 +134,3 @@ impl Handler {
command_manager.execute_component(&ctx, component).await; command_manager.execute_component(&ctx, component).await;
} }
}

View File

@ -1,3 +1,5 @@
use lazy_static::lazy_static;
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -8,3 +10,18 @@ pub const MOTD: &str = "some good 'ol music";
/// The time it takes for Spoticord to disconnect when no music is being played /// The time it takes for Spoticord to disconnect when no music is being played
pub const DISCONNECT_TIME: u64 = 5 * 60; pub const DISCONNECT_TIME: u64 = 5 * 60;
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 SPOTICORD_ACCOUNTS_URL: String = std::env::var("SPOTICORD_ACCOUNTS_URL")
.expect("missing SPOTICORD_ACCOUNTS_URL environment variable");
}
#[cfg(feature = "stats")]
lazy_static! {
pub static ref KV_URL: String =
std::env::var("KV_URL").expect("missing KV_URL environment variable");
}

View File

@ -1,10 +1,18 @@
use dotenv::dotenv; use dotenv::dotenv;
use crate::{bot::commands::CommandManager, database::Database, session::manager::SessionManager}; #[cfg(feature = "stats")]
use crate::consts::KV_URL;
use crate::{
bot::commands::CommandManager,
consts::{DATABASE_URL, DISCORD_TOKEN, MOTD},
database::Database,
session::manager::SessionManager,
};
use log::*; use log::*;
use serenity::{framework::StandardFramework, prelude::GatewayIntents, Client}; use serenity::{framework::StandardFramework, prelude::GatewayIntents, Client};
use songbird::SerenityInit; use songbird::SerenityInit;
use std::{any::Any, env, process::exit}; use std::{any::Any, process::exit};
#[cfg(unix)] #[cfg(unix)]
use tokio::signal::unix::SignalKind; use tokio::signal::unix::SignalKind;
@ -41,7 +49,7 @@ async fn main() {
env_logger::init(); env_logger::init();
info!("It's a good day"); info!("It's a good day");
info!(" - Spoticord {}", time::OffsetDateTime::now_utc().year()); info!(" - Spoticord, {}", MOTD);
let result = dotenv(); let result = dotenv();
@ -54,19 +62,14 @@ async fn main() {
warn!("No .env file found, expecting all necessary environment variables"); warn!("No .env file found, expecting all necessary environment variables");
} }
let token = env::var("DISCORD_TOKEN").expect("a token in the environment");
let db_url = env::var("DATABASE_URL").expect("a database URL in the environment");
#[cfg(feature = "stats")] #[cfg(feature = "stats")]
let stats_manager = let stats_manager = StatsManager::new(KV_URL.as_str()).expect("Failed to connect to redis");
StatsManager::new(env::var("KV_URL").expect("a redis URL in the environment"))
.expect("Failed to connect to redis");
let session_manager = SessionManager::new(); let session_manager = SessionManager::new();
// Create client // Create client
let mut client = Client::builder( let mut client = Client::builder(
token, DISCORD_TOKEN.as_str(),
GatewayIntents::GUILDS | GatewayIntents::GUILD_VOICE_STATES, GatewayIntents::GUILDS | GatewayIntents::GUILD_VOICE_STATES,
) )
.event_handler(crate::bot::events::Handler) .event_handler(crate::bot::events::Handler)
@ -78,16 +81,13 @@ async fn main() {
{ {
let mut data = client.data.write().await; let mut data = client.data.write().await;
data.insert::<Database>(Database::new(db_url, None)); data.insert::<Database>(Database::new(DATABASE_URL.as_str(), None));
data.insert::<CommandManager>(CommandManager::new()); data.insert::<CommandManager>(CommandManager::new());
data.insert::<SessionManager>(session_manager.clone()); data.insert::<SessionManager>(session_manager.clone());
} }
let shard_manager = client.shard_manager.clone(); let shard_manager = client.shard_manager.clone();
#[cfg(feature = "stats")]
let cache = client.cache_and_http.cache.clone();
#[cfg(unix)] #[cfg(unix)]
let mut term: Option<Box<dyn Any + Send>> = Some(Box::new( let mut term: Option<Box<dyn Any + Send>> = Some(Box::new(
tokio::signal::unix::signal(SignalKind::terminate()) tokio::signal::unix::signal(SignalKind::terminate())
@ -104,13 +104,8 @@ async fn main() {
_ = tokio::time::sleep(std::time::Duration::from_secs(60)) => { _ = tokio::time::sleep(std::time::Duration::from_secs(60)) => {
#[cfg(feature = "stats")] #[cfg(feature = "stats")]
{ {
let guild_count = cache.guilds().len();
let active_count = session_manager.get_active_session_count().await; let active_count = session_manager.get_active_session_count().await;
if let Err(why) = stats_manager.set_server_count(guild_count) {
error!("Failed to update server count: {why}");
}
if let Err(why) = stats_manager.set_active_count(active_count) { if let Err(why) = stats_manager.set_active_count(active_count) {
error!("Failed to update active count: {why}"); error!("Failed to update active count: {why}");
} }

View File

@ -318,13 +318,13 @@ impl PlayerTask {
match pbi.as_mut() { match pbi.as_mut() {
Some(pbi) => { Some(pbi) => {
pbi.update_track(spotify_id, current); pbi.update_track(spotify_id, current);
pbi.update_pos_dur(position_ms, duration_ms, true); pbi.update_pos_dur(position_ms, duration_ms, playing);
} }
None => { None => {
*pbi = Some(PlaybackInfo::new( *pbi = Some(PlaybackInfo::new(
duration_ms, duration_ms,
position_ms, position_ms,
true, playing,
current, current,
spotify_id, spotify_id,
)); ));

View File

@ -56,11 +56,9 @@ impl InnerSessionManager {
session: SpoticordSession, session: SpoticordSession,
guild_id: GuildId, guild_id: GuildId,
owner_id: UserId, owner_id: UserId,
) -> Result<(), SessionCreateError> { ) {
self.sessions.insert(guild_id, session); self.sessions.insert(guild_id, session);
self.owner_map.insert(owner_id, guild_id); self.owner_map.insert(owner_id, guild_id);
Ok(())
} }
/// Remove a session /// Remove a session
@ -147,7 +145,9 @@ impl SessionManager {
.write() .write()
.await .await
.create_session(session, guild_id, owner_id) .create_session(session, guild_id, owner_id)
.await .await;
Ok(())
} }
/// Remove a session /// Remove a session

View File

@ -36,6 +36,12 @@ use tokio::sync::{Mutex, RwLockReadGuard, RwLockWriteGuard};
#[derive(Clone)] #[derive(Clone)]
pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>); pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>);
impl Drop for SpoticordSession {
fn drop(&mut self) {
log::trace!("drop SpoticordSession");
}
}
struct InnerSpoticordSession { struct InnerSpoticordSession {
owner: Option<UserId>, owner: Option<UserId>,
guild_id: GuildId, guild_id: GuildId,
@ -97,7 +103,11 @@ impl SpoticordSession {
}; };
let mut instance = Self(Arc::new(RwLock::new(inner))); let mut instance = Self(Arc::new(RwLock::new(inner)));
instance.create_player(ctx).await?; if let Err(why) = instance.create_player(ctx).await {
songbird.remove(guild_id).await.ok();
return Err(why);
}
let mut call = call.lock().await; let mut call = call.lock().await;
@ -336,6 +346,8 @@ impl SpoticordSession {
timer.tick().await; timer.tick().await;
timer.tick().await; timer.tick().await;
trace!("Ring ring, time to check :)");
// Make sure this task has not been aborted, if it has this will automatically stop execution. // Make sure this task has not been aborted, if it has this will automatically stop execution.
tokio::task::yield_now().await; tokio::task::yield_now().await;
@ -345,6 +357,8 @@ impl SpoticordSession {
.map(|pbi| pbi.is_playing) .map(|pbi| pbi.is_playing)
.unwrap_or(false); .unwrap_or(false);
trace!("is_playing = {is_playing}");
if !is_playing { if !is_playing {
info!("Player is not playing, disconnecting"); info!("Player is not playing, disconnecting");
session session
@ -499,19 +513,19 @@ impl InnerSpoticordSession {
.remove_session(self.guild_id, self.owner) .remove_session(self.guild_id, self.owner)
.await; .await;
let mut call = self.call.lock().await;
if let Some(track) = self.track.take() { if let Some(track) = self.track.take() {
if let Err(why) = track.stop() { if let Err(why) = track.stop() {
error!("Failed to stop track: {:?}", why); error!("Failed to stop track: {:?}", why);
} }
} };
call.remove_all_global_events(); let mut call = self.call.lock().await;
if let Err(why) = call.leave().await { if let Err(why) = call.leave().await {
error!("Failed to leave voice channel: {:?}", why); error!("Failed to leave voice channel: {:?}", why);
} }
call.remove_all_global_events();
} }
} }
@ -521,10 +535,12 @@ impl EventHandler for SpoticordSession {
match ctx { match ctx {
EventContext::DriverDisconnect(_) => { EventContext::DriverDisconnect(_) => {
debug!("Driver disconnected, leaving voice channel"); debug!("Driver disconnected, leaving voice channel");
trace!("Arc strong count: {}", Arc::strong_count(&self.0));
self.disconnect().await; self.disconnect().await;
} }
EventContext::ClientDisconnect(who) => { EventContext::ClientDisconnect(who) => {
trace!("Client disconnected, {}", who.user_id.to_string()); trace!("Client disconnected, {}", who.user_id.to_string());
trace!("Arc strong count: {}", Arc::strong_count(&self.0));
if let Some(session) = self if let Some(session) = self
.session_manager() .session_manager()
@ -545,3 +561,9 @@ impl EventHandler for SpoticordSession {
return None; return None;
} }
} }
impl Drop for InnerSpoticordSession {
fn drop(&mut self) {
log::trace!("drop InnerSpoticordSession");
}
}

View File

@ -12,12 +12,6 @@ impl StatsManager {
Ok(StatsManager { redis }) Ok(StatsManager { redis })
} }
pub fn set_server_count(&self, count: usize) -> Result<()> {
let mut con = self.redis.get_connection()?;
con.set("sc-bot-total-servers", count.to_string())
}
pub fn set_active_count(&self, count: usize) -> Result<()> { pub fn set_active_count(&self, count: usize) -> Result<()> {
let mut con = self.redis.get_connection()?; let mut con = self.redis.get_connection()?;