require guild id, show invite link if needed

small improvements on common functions
This commit is contained in:
Gabriel Fontes
2023-06-12 17:25:22 -03:00
parent 5bd8e2ab57
commit 074bc44d4d
6 changed files with 79 additions and 52 deletions

2
Cargo.lock generated
View File

@@ -458,7 +458,7 @@ dependencies = [
[[package]]
name = "disconic"
version = "1.0.1"
version = "1.1.0"
dependencies = [
"anyhow",
"clap",

View File

@@ -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"

View File

@@ -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

View File

@@ -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")]

View File

@@ -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)

View File

@@ -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)
}