use poise::serenity_prelude as serenity; use super::autocomplete::audio_file; use crate::Data; use crate::database; use crate::error::{Error, Result}; use crate::lang; type Context<'a> = poise::Context<'a, Data, Error>; /// Cron job management #[poise::command( slash_command, guild_only, required_permissions = "MANAGE_GUILD", subcommands("dodaj_audio", "dodaj_wiadomość", "pokaż", "usuń", "pauza", "wznów",) )] pub async fn cron(_ctx: Context<'_>) -> Result<()> { Ok(()) } /// Schedule audio playback #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] pub async fn dodaj_audio( ctx: Context<'_>, #[description = "Cron expression (e.g. \"37 21 * * *\" = every day at 21:37)"] cron_expression: String, #[description = "Target voice channel"] channel: serenity::Channel, #[description = "Copyparty URL of audio file"] #[autocomplete = "audio_file"] audio_filename: String, ) -> Result<()> { let channel_id = match channel { serenity::Channel::Guild(ch) => ch.id.get() as i64, _ => return Err(lang::err_bad_channel_type()), }; // Resolve full path from autocomplete cache (value is truncated to 99 chars by Discord) let full_url = if let Some(hit) = ctx.data().ac_lookup(ctx.author().id, &audio_filename).await { let path = hit.url_path().unwrap_or(&audio_filename); ctx.data().copyparty.build_url_no_pw(path) } else if audio_filename.starts_with("http://") || audio_filename.starts_with("https://") { audio_filename.clone() } else { ctx.data().copyparty.build_url_no_pw(&audio_filename) }; let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; database::add_cron_job( &ctx.data().db_pool, guild_id.get() as i64, &cron_expression, "audio", channel_id, Some(&full_url), None, ) .await?; ctx.say(lang::fmt_cron_audio_queued(&cron_expression)) .await?; Ok(()) } /// Schedule a periodic message #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] pub async fn dodaj_wiadomość( ctx: Context<'_>, #[description = "Cron expression (e.g. \"37 21 * * *\" = every day at 21:37)"] cron_expression: String, #[description = "Docelowy kanał tekstowy"] channel: serenity::Channel, #[description = "Zawartość wiadomości"] message: String, ) -> Result<()> { let channel_id = match channel { serenity::Channel::Guild(ch) => ch.id.get() as i64, _ => return Err(lang::err_bad_channel_type()), }; let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; database::add_cron_job( &ctx.data().db_pool, guild_id.get() as i64, &cron_expression, "wiadomość", channel_id, None, Some(&message), ) .await?; ctx.say(lang::L_CRON_QUEUED).await?; Ok(()) } /// List all cron jobs #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] pub async fn pokaż(ctx: Context<'_>) -> Result<()> { let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; let jobs = database::list_cron_jobs(&ctx.data().db_pool, guild_id.get() as i64).await?; if jobs.is_empty() { ctx.say(lang::L_CRON_NONE).await?; return Ok(()); } let mut embed = serenity::CreateEmbed::default() .title(lang::L_CRON_TITLE) .color(0x00aaff); for job in &jobs { let status = if job.active == 1 { lang::L_CRON_ACTIVE } else { lang::L_CRON_PAUSED }; let desc = match job.r#type.as_str() { "audio" => lang::fmt_cron_label_audio(job.audio_filename.as_deref().unwrap_or("?")), _ => lang::fmt_cron_label_msg(job.message.as_deref().unwrap_or("?")), }; embed = embed.field( lang::fmt_cron_job_status(job.id, status), lang::fmt_cron_field(&job.cron_expression, job.channel_id, &desc), false, ); } ctx.send(poise::CreateReply::default().embed(embed)).await?; Ok(()) } /// Remove a cron job #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] pub async fn usuń( ctx: Context<'_>, #[description = "ID pracy do usunięcia"] job_id: i64, ) -> Result<()> { let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; if database::delete_cron_job(&ctx.data().db_pool, job_id, guild_id.get() as i64).await? { ctx.say(lang::fmt_cron_deleted(job_id)).await?; } else { ctx.say(lang::fmt_cron_not_found(job_id)).await?; } Ok(()) } /// Pause a cron job #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] pub async fn pauza( ctx: Context<'_>, #[description = "ID pracy do zapauzowania"] job_id: i64, ) -> Result<()> { let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; if database::set_cron_job_active(&ctx.data().db_pool, job_id, guild_id.get() as i64, false).await? { ctx.say(lang::fmt_cron_paused(job_id)).await?; } else { ctx.say(lang::fmt_cron_not_found(job_id)).await?; } Ok(()) } /// Resume a paused cron job #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] pub async fn wznów( ctx: Context<'_>, #[description = "ID pracy cron do wznowienia"] job_id: i64, ) -> Result<()> { let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; if database::set_cron_job_active(&ctx.data().db_pool, job_id, guild_id.get() as i64, true).await? { ctx.say(lang::fmt_cron_resumed(job_id)).await?; } else { ctx.say(lang::fmt_cron_not_found(job_id)).await?; } Ok(()) } pub fn commands() -> Vec> { vec![cron()] }