update how configuring works

This commit is contained in:
Gabriel Fontes
2023-06-11 15:41:55 -03:00
parent c808e5f480
commit e147916d27
10 changed files with 338 additions and 149 deletions

250
Cargo.lock generated
View File

@@ -42,6 +42,55 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
[[package]]
name = "anstyle-parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "anstyle-wincon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
]
[[package]]
name = "anyhow"
version = "1.0.71"
@@ -189,6 +238,48 @@ dependencies = [
"zeroize",
]
[[package]]
name = "clap"
version = "4.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8f255e4b8027970e78db75e78831229c9815fdbfa67eb1a1b777a62e24b4a0"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acd4f3c17c83b0ba34ffbc4f8bbd74f079413f747f84a6f89292f138057e36ab"
dependencies = [
"anstream",
"anstyle",
"bitflags",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.18",
]
[[package]]
name = "clap_lex"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]]
name = "cmake"
version = "0.1.50"
@@ -198,6 +289,23 @@ dependencies = [
"cc",
]
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi",
]
[[package]]
name = "command_attr"
version = "0.4.1"
@@ -318,11 +426,12 @@ name = "disconic"
version = "0.2.1"
dependencies = [
"anyhow",
"clap",
"dotenv",
"env_logger",
"log",
"reqwest",
"serenity",
"simple_logger",
"songbird",
"sunk",
"symphonia",
@@ -364,16 +473,24 @@ dependencies = [
]
[[package]]
name = "env_logger"
version = "0.9.3"
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
@@ -573,6 +690,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@@ -591,6 +714,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "http"
version = "0.2.9"
@@ -625,12 +754,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.26"
@@ -720,12 +843,35 @@ dependencies = [
"generic-array",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "ipnet"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
[[package]]
name = "is-terminal"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "itoa"
version = "1.0.6"
@@ -759,6 +905,12 @@ version = "0.2.146"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "lock_api"
version = "0.4.10"
@@ -925,6 +1077,15 @@ dependencies = [
"libc",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.18.0"
@@ -1283,6 +1444,20 @@ dependencies = [
"version_check",
]
[[package]]
name = "rustix"
version = "0.37.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "rustls"
version = "0.20.8"
@@ -1574,6 +1749,19 @@ dependencies = [
"libc",
]
[[package]]
name = "simple_logger"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78beb34673091ccf96a8816fce8bfd30d1292c7621ca2bcb5f2ba0fae4f558d"
dependencies = [
"atty",
"colored",
"log",
"time",
"windows-sys 0.42.0",
]
[[package]]
name = "slab"
version = "0.4.8"
@@ -1690,6 +1878,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.5.0"
@@ -1894,15 +2088,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.40"
@@ -1940,6 +2125,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
dependencies = [
"itoa",
"libc",
"num_threads",
"serde",
"time-core",
"time-macros",
@@ -2329,6 +2516,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.3.3"
@@ -2505,15 +2698,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View File

@@ -10,8 +10,8 @@ license = "AGPL-3.0-or-later"
[dependencies.log]
version = "0.4"
[dependencies.env_logger]
version = "0.9"
[dependencies.simple_logger]
version = "4.0"
[dependencies.reqwest]
version = "0.11"
@@ -49,3 +49,7 @@ version = "1.0"
[dependencies.dotenv]
version = "0.15"
[dependencies.clap]
version = "4.0"
features = [ "derive", "env" ]

8
example.env Executable file
View File

@@ -0,0 +1,8 @@
# You may configure the app with CLI args or env vars
# If using env vars, using dotenv is convenient; just copy this file to .env
# and edit it accordingly.
DISCONIC_SUBSONIC_URL="https://example.com"
DISCONIC_SUBSONIC_USER="admin"
DISCONIC_SUBSONIC_PASSWORD="1234"
DISCONIC_DISCORD_TOKEN="xxx"
DISCONIC_LOG_LEVEL="warn"

View File

@@ -30,22 +30,24 @@ in {
type = types.path;
description = "File path containing discord token";
};
extraArgs = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Extra arguments to pass to disconic.";
};
};
config = mkIf cfg.enable {
systemd.services.disconic = {
description = "Disconic, a Discord Subsonic Bot";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/disconic";
Restart = "on-failure";
Environment = [
"SUBSONIC_URL=${cfg.subsonicUrl}"
"SUBSONIC_USER=${cfg.subsonicUser}"
"SUBSONIC_PASSWORD_FILE=${cfg.subsonicPasswordFile}"
"DISCORD_TOKEN_FILE=${cfg.discordTokenFile}"
];
};
serviceConfig.ExecStart = lib.escapeShellArgs ([
(lib.getExe cfg.package)
"--subsonic-url=${cfg.subsonicUrl}"
"--subsonic-user=${cfg.subsonicUser}"
"--subsonic-password=$(cat ${cfg.subsonicPasswordFile})"
"--discord-token=$(cat ${cfg.discordTokenFile}"
] ++ cfg.extraArgs);
};
};
}

View File

@@ -1,83 +0,0 @@
use anyhow::{Context as ErrContext, Result};
use serenity::{
client::Client as DiscordClient,
framework::standard::StandardFramework,
prelude::{GatewayIntents, TypeMapKey},
};
use songbird::SerenityInit;
use sunk::Client as SubsonicClient;
use std::{env, fs, io};
use crate::discord::{after_hook, Handler, GENERAL_GROUP};
pub struct Client {
ss_url: String,
ss_user: String,
ss_password: String,
discord_token: String,
}
impl Client {
pub async fn from_env() -> Result<Self> {
// Convert to std::io::Error, allowing usage of and_then
let convert_err = |e| io::Error::new(io::ErrorKind::Other, e);
let ss_url = env::var("SUBSONIC_URL")?;
let ss_user = env::var("SUBSONIC_USER")?;
let ss_password = env::var("SUBSONIC_PASSWORD").or_else(|_| {
env::var("SUBSONIC_PASSWORD_FILE")
.map_err(convert_err)
.and_then(fs::read_to_string)
.map(|s| s.trim().to_owned())
})?;
let discord_token = env::var("DISCORD_TOKEN").or_else(|_| {
env::var("DISCORD_TOKEN_FILE")
.map_err(convert_err)
.and_then(fs::read_to_string)
.map(|s| s.trim().to_owned())
})?;
Ok(Self {
ss_url,
ss_user,
ss_password,
discord_token,
})
}
pub async fn discord(&self, ss: SubsonicClient) -> Result<DiscordClient> {
let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT;
let framework = StandardFramework::new()
.group(&GENERAL_GROUP)
.after(after_hook);
framework.configure(|c| c.prefix("~"));
let client = DiscordClient::builder(&self.discord_token, intents)
.event_handler(Handler)
.framework(framework)
.type_map_insert::<MusicClient>(ss)
.register_songbird()
.await?;
Ok(client)
}
pub async fn subsonic(&self) -> Result<SubsonicClient> {
let client = SubsonicClient::new(&self.ss_url, &self.ss_user, &self.ss_password)?;
log::info!("Created subsonic client: {client:?}");
// Check that connection works
client
.ping()
.await
.with_context(|| "Could not connect to subsonic server.")?;
Ok(client)
}
}
pub struct MusicClient;
impl TypeMapKey for MusicClient {
type Value = SubsonicClient;
}

67
src/config.rs Normal file
View File

@@ -0,0 +1,67 @@
use crate::handles::SubsonicClientHandle;
use anyhow::{Context as ErrContext, Result};
use clap::Parser;
use log::LevelFilter;
use serenity::{
client::Client as DiscordClient, framework::standard::StandardFramework,
prelude::GatewayIntents,
};
use simple_logger::SimpleLogger;
use songbird::SerenityInit;
use sunk::Client as SubsonicClient;
use crate::discord::{after_hook, Handler, GENERAL_GROUP};
#[derive(Parser, Clone)]
pub struct Config {
#[clap(long, env = "DISCONIC_SUBSONIC_URL")]
subsonic_url: String,
#[clap(long, env = "DISCONIC_SUBSONIC_USER")]
subsonic_user: String,
#[clap(long, env = "DISCONIC_SUBSONIC_PASSWORD")]
subsonic_password: String,
#[clap(long, env = "DISCONIC_DISCORD_TOKEN")]
discord_token: String,
#[clap(long, env = "DISCONIC_LOG_LEVEL", default_value = "warn")]
log_level: LevelFilter,
}
impl Config {
pub async fn discord(&self, ss: SubsonicClient) -> Result<DiscordClient> {
let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT;
let framework = StandardFramework::new()
.group(&GENERAL_GROUP)
.after(after_hook);
framework.configure(|c| c.prefix("~"));
let client = DiscordClient::builder(&self.discord_token, intents)
.event_handler(Handler)
.framework(framework)
.type_map_insert::<SubsonicClientHandle>(ss)
.register_songbird()
.await?;
Ok(client)
}
pub async fn subsonic(&self) -> Result<SubsonicClient> {
let client = SubsonicClient::new(
&self.subsonic_url,
&self.subsonic_user,
&self.subsonic_password,
)?;
log::info!("Created subsonic client: {client:?}");
// Check that connection works
client
.ping()
.await
.with_context(|| "Could not connect to subsonic server.")?;
Ok(client)
}
pub fn logger(&self) -> SimpleLogger {
SimpleLogger::new().with_level(self.log_level)
}
}

View File

@@ -8,7 +8,6 @@ use serenity::{
Args, CommandResult,
},
model::{channel::Message, gateway::Ready},
prelude::TypeMapKey,
utils::MessageBuilder,
};
use songbird::{
@@ -25,7 +24,7 @@ use tokio::sync::Mutex;
use std::sync::Arc;
use crate::MusicClient;
use crate::handles::{SubsonicClientHandle, SubsonicSongHandle};
#[group]
#[commands(
@@ -38,7 +37,7 @@ pub struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
log::info!("{} is connected!", ready.user.name);
}
}
@@ -76,7 +75,7 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult {
async fn song(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let data = ctx.data.read().await;
let music_client = data
.get::<MusicClient>()
.get::<SubsonicClientHandle>()
.expect("Couldn't retrieve music client");
let search_size = SearchPage::new().with_size(1);
@@ -114,7 +113,7 @@ async fn song(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
async fn album(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let data = ctx.data.read().await;
let music_client = data
.get::<MusicClient>()
.get::<SubsonicClientHandle>()
.expect("Couldn't retrieve music client");
let search_size = SearchPage::new().with_size(1);
@@ -153,7 +152,7 @@ async fn album(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
async fn random(ctx: &Context, msg: &Message) -> CommandResult {
let data = ctx.data.read().await;
let music_client = data
.get::<MusicClient>()
.get::<SubsonicClientHandle>()
.expect("Couldn't retrieve music client");
let result = Song::random(music_client, 1).await?;
@@ -334,11 +333,6 @@ async fn remove(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
// ==========================
// ==========================
struct SongHandler;
impl TypeMapKey for SongHandler {
type Value = Song;
}
async fn queue_song(
ctx: &Context,
msg: &Message,
@@ -351,7 +345,7 @@ async fn queue_song(
let track = load_song(song, client).await?;
let track_handle = handler.enqueue(track).await;
let mut type_map = track_handle.typemap().write().await;
type_map.insert::<SongHandler>(song.clone());
type_map.insert::<SubsonicSongHandle>(song.clone());
Ok(())
}
@@ -394,7 +388,7 @@ async fn get_song(track: &TrackHandle) -> Result<Song> {
.typemap()
.read()
.await
.get::<SongHandler>()
.get::<SubsonicSongHandle>()
.map(ToOwned::to_owned)
.ok_or_else(|| anyhow!("Sound information not found"))?;
Ok(song)

12
src/handles.rs Normal file
View File

@@ -0,0 +1,12 @@
use serenity::prelude::TypeMapKey;
use sunk::{song::Song as SubsonicSong, Client as SubsonicClient};
pub struct SubsonicClientHandle;
impl TypeMapKey for SubsonicClientHandle {
type Value = SubsonicClient;
}
pub struct SubsonicSongHandle;
impl TypeMapKey for SubsonicSongHandle {
type Value = SubsonicSong;
}

View File

@@ -1,4 +1,3 @@
pub mod client;
pub use client::{Client, MusicClient};
pub mod config;
pub mod discord;
pub mod handles;

View File

@@ -1,16 +1,18 @@
use anyhow::Result;
use clap::Parser;
use disconic::Client;
use disconic::config::Config;
#[tokio::main]
async fn main() -> Result<()> {
dotenv::dotenv().ok();
env_logger::init();
let client = Client::from_env().await?;
let subsonic = client.subsonic().await?;
let mut discord = client.discord(subsonic).await?;
let config = Config::parse();
let logger = config.logger();
let subsonic = config.subsonic().await?;
let mut discord = config.discord(subsonic).await?;
logger.init()?;
discord.start().await?;
Ok(())