require guild id, show invite link if needed
small improvements on common functions
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -458,7 +458,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "disconic"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "disconic"
|
||||
description = "Discord bot for interacting with subsonic music libraries"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
authors = [ "Gabriel Fontes <eu@misterio.me>" ]
|
||||
edition = "2021"
|
||||
homepage = "https://misterio.me"
|
||||
|
||||
@@ -46,11 +46,11 @@ With nix, you don't even need to clone the repo. Simply replace `.` with `github
|
||||
|
||||
## Usage
|
||||
|
||||
Start by creating a discord app, getting its bot token, and inviting it to your server. Also get your guild (server) ID. These steps are already documented elsewhere, so will not be covered here.
|
||||
Start by creating a discord app and getting its bot token. Also get your guild (server) ID. These steps are already documented elsewhere, so will not be covered here.
|
||||
|
||||
You can configure the application through CLI arguments (very convenient) or environment variables (better for deployments, dotenv is also supported). Use `--help` to see what the arguments or environment variables are.
|
||||
You can configure the application through CLI arguments or environment variables. Use `--help` to see what the arguments and environment variables are.
|
||||
|
||||
Simply run the binary and everything will be setup for you. The bot will automatically register its commands on your server (if guild_id is set). If you don't see the commands, try sending a message mentioning the bot and `register` (all commands can be ran like that, too).
|
||||
Simply run the binary and everything will be setup for you. If your bot is not a member of the guild, disconic will error out and print the invitation URL. The bot will also automatically register its slash commands on your server whenever it starts.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct Config {
|
||||
#[clap(long, env = "DISCONIC_SUBSONIC_PASSWORD")]
|
||||
subsonic_password: String,
|
||||
#[clap(long, env = "DISCONIC_DISCORD_GUILD")]
|
||||
discord_guild: Option<u64>,
|
||||
discord_guild: u64,
|
||||
#[clap(long, env = "DISCONIC_DISCORD_TOKEN")]
|
||||
discord_token: String,
|
||||
#[clap(long, env = "DISCONIC_LOG_LEVEL", default_value = "warn")]
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
use crate::discord::{commands, Data};
|
||||
use anyhow::Result;
|
||||
use poise::samples::create_application_commands;
|
||||
use serenity::{all::GuildId, client::Client as DiscordClient, prelude::GatewayIntents};
|
||||
use anyhow::{Error, Result};
|
||||
use poise::{samples::create_application_commands, Framework};
|
||||
use serenity::{
|
||||
all::{GuildId, Ready},
|
||||
builder::CreateCommand,
|
||||
client::Client as DiscordClient,
|
||||
prelude::{Context, GatewayIntents},
|
||||
};
|
||||
use songbird::SerenityInit;
|
||||
use sunk::Client as SubsonicClient;
|
||||
|
||||
@@ -36,14 +41,48 @@ async fn on_error(error: poise::FrameworkError<'_, Data, anyhow::Error>) {
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_startup(
|
||||
guild: GuildId,
|
||||
data: Data,
|
||||
create_commands: Vec<CreateCommand>,
|
||||
ctx: &Context,
|
||||
ready: &Ready,
|
||||
framework: &Framework<Data, Error>,
|
||||
) -> Result<Data> {
|
||||
let is_on_guild = (&ready.guilds).iter().any(|x| x.id == guild);
|
||||
|
||||
let bot_id = framework.bot_id().await;
|
||||
let permissions = "311388293184";
|
||||
let scope = "bot%20applications.commands";
|
||||
|
||||
if !is_on_guild {
|
||||
let invite_link = format!(
|
||||
"https://discord.com/oauth2/authorize?client_id={}&permissions={}&scope={}",
|
||||
bot_id, permissions, scope
|
||||
);
|
||||
log::error!("The bot is not your on your guild.");
|
||||
log::error!("Invite it with:\n {}", invite_link);
|
||||
framework.shard_manager().lock().await.shutdown_all().await;
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
guild
|
||||
.set_commands(&ctx.http, create_commands)
|
||||
.await
|
||||
.expect("Failed to register command");
|
||||
log::info!("Registered commands");
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub async fn create_client(
|
||||
token: &str,
|
||||
guild_id: Option<u64>,
|
||||
guild_id: u64,
|
||||
subsonic_client: SubsonicClient,
|
||||
) -> Result<DiscordClient> {
|
||||
let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT;
|
||||
let commands = commands::commands();
|
||||
let data = Data { subsonic_client };
|
||||
let guild = GuildId::new(guild_id);
|
||||
|
||||
let create_commands = create_application_commands(&commands);
|
||||
let options = poise::FrameworkOptions {
|
||||
@@ -51,20 +90,15 @@ pub async fn create_client(
|
||||
on_error: |e| Box::pin(on_error(e)),
|
||||
..Default::default()
|
||||
};
|
||||
let framework = poise::Framework::new(options, move |ctx, _ready, _framework| {
|
||||
Box::pin(async move {
|
||||
if let Some(id) = guild_id {
|
||||
let guild = GuildId::new(id);
|
||||
guild
|
||||
.set_commands(&ctx.http, create_commands)
|
||||
.await
|
||||
.expect("Failed to register command");
|
||||
} else {
|
||||
log::warn!("Guild ID not configured. You'll have to run 'register' to register the slash commands.")
|
||||
}
|
||||
log::info!("Registered commands");
|
||||
Ok(data)
|
||||
})
|
||||
let framework = poise::Framework::new(options, move |ctx, ready, framework| {
|
||||
Box::pin(on_startup(
|
||||
guild,
|
||||
data,
|
||||
create_commands,
|
||||
ctx,
|
||||
ready,
|
||||
framework,
|
||||
))
|
||||
});
|
||||
|
||||
let client = DiscordClient::builder(token, intents)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{anyhow, Context as ErrContext, Result};
|
||||
use serenity::{
|
||||
all::{ChannelId, Guild},
|
||||
prelude::TypeMapKey,
|
||||
@@ -36,9 +36,9 @@ pub async fn queue_song(ctx: Context<'_>, song: &Song, client: &sunk::Client) ->
|
||||
}
|
||||
|
||||
pub fn get_guild(ctx: Context<'_>) -> Guild {
|
||||
let guild = ctx.guild().expect("No guild!").clone();
|
||||
log::info!("Got guild: {guild:?}");
|
||||
guild
|
||||
ctx.guild()
|
||||
.expect("Invalid (or no) guild configured!")
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
pub fn get_channel(ctx: Context<'_>) -> Result<ChannelId> {
|
||||
@@ -47,52 +47,45 @@ pub fn get_channel(ctx: Context<'_>) -> Result<ChannelId> {
|
||||
.voice_states
|
||||
.get(&ctx.author().id)
|
||||
.ok_or_else(|| anyhow!("You must be in a voice channel to use this command"))?;
|
||||
let channel = voice_state
|
||||
voice_state
|
||||
.channel_id
|
||||
.ok_or_else(|| anyhow!("You must be in a voice channel to use this command"))?;
|
||||
|
||||
Ok(channel)
|
||||
.ok_or_else(|| anyhow!("You must be in a voice channel to use this command"))
|
||||
}
|
||||
|
||||
pub async fn get_call(ctx: Context<'_>) -> Result<Arc<Mutex<songbird::Call>>> {
|
||||
let manager = get_manager(ctx).await?;
|
||||
let guild = get_guild(ctx);
|
||||
let call = manager.get(guild.id);
|
||||
|
||||
if let Some(c) = call {
|
||||
Ok(c)
|
||||
} else {
|
||||
match manager.get(guild.id) {
|
||||
Some(c) => Ok(c),
|
||||
None => {
|
||||
let channel = get_channel(ctx)?;
|
||||
log::warn!("Not in a voice channel, trying to join {channel}");
|
||||
let _handler = manager.join(guild.id, channel).await;
|
||||
manager
|
||||
.get(guild.id)
|
||||
.ok_or_else(|| anyhow!("Not in a voice channel, try running 'join'"))
|
||||
.join(guild.id, channel)
|
||||
.await
|
||||
.context("Couldn't join voice channel. Try running 'join' manually.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_song(track: &TrackHandle) -> Result<Song> {
|
||||
let song = track
|
||||
track
|
||||
.typemap()
|
||||
.read()
|
||||
.await
|
||||
.get::<SongHandle>()
|
||||
.map(ToOwned::to_owned)
|
||||
.ok_or_else(|| anyhow!("Sound information not found"))?;
|
||||
Ok(song)
|
||||
.ok_or_else(|| anyhow!("Sound information not found"))
|
||||
}
|
||||
|
||||
pub async fn get_manager(ctx: Context<'_>) -> Result<Arc<Songbird>> {
|
||||
let manager = songbird::get(ctx.discord())
|
||||
songbird::get(ctx.discord())
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("Couldn't start manager"))?;
|
||||
log::info!("Got manager: {manager:?}");
|
||||
Ok(manager)
|
||||
.ok_or_else(|| anyhow!("Couldn't start manager"))
|
||||
}
|
||||
|
||||
pub async fn load_song(song: &Song, client: &sunk::Client) -> Result<Track> {
|
||||
log::info!("Loading song {song:?}");
|
||||
let url = song.stream_url(client)?;
|
||||
let track: Track = HttpRequest::new(client.reqclient.clone(), url).into();
|
||||
let track = HttpRequest::new(client.reqclient.clone(), url).into();
|
||||
Ok(track)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user