From cd8ed8bc5793985df2c1692072a932ceee517b95 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 8 Jul 2026 02:13:35 +0200 Subject: OPEN THE RUST GATES Signed-off-by: salmonik --- cogs/cron.py | 403 ----------------------------------------------------------- 1 file changed, 403 deletions(-) delete mode 100644 cogs/cron.py (limited to 'cogs/cron.py') diff --git a/cogs/cron.py b/cogs/cron.py deleted file mode 100644 index ee88085..0000000 --- a/cogs/cron.py +++ /dev/null @@ -1,403 +0,0 @@ -# This file is part of Senator Sperma -# Licensed under the GPL-3.0 License -# Copyright (C) 2026 Salmonik @ salmonik@tuta.io -# https://codeberg.org/salmonik/senator-sperma - -import asyncio -import logging -from datetime import datetime -from venv import logger - -import discord -from croniter import croniter -from discord.ext import commands - -from cogs.utils import file_search as search_utils -from cogs.voice import get_voice_manager -from cogs.utils.database import get_connection, init_database - -logger = logging.getLogger(__name__) - -init_database() - - -class Cron(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.scheduled_tasks = [] - self.load_tasks() - self.task = bot.loop.create_task(self.schedule_checker()) - - def load_tasks(self): - try: - with get_connection() as conn: - c = conn.cursor() - c.execute("SELECT id, cron_expression, type, channel_id, active, audio_filename, message FROM cron_jobs") - rows = c.fetchall() - - self.scheduled_tasks = [ - { - "id": row[0], - "cron_expression": row[1], - "type": row[2], - "channel_id": row[3], - "active": bool(row[4]), - "audio_filename": row[5], - "message": row[6], - } - for row in rows - ] - logger.info(f"Loaded {len(self.scheduled_tasks)} cron jobs from database") - except Exception as e: - logger.error(f"Error loading cron jobs: {e}") - self.scheduled_tasks = [] - - def save_tasks(self): - try: - with get_connection() as conn: - c = conn.cursor() - c.execute("PRAGMA foreign_keys = ON") - - c.execute("DELETE FROM cron_jobs") - - for task in self.scheduled_tasks: - c.execute( - """INSERT INTO cron_jobs (id, cron_expression, type, channel_id, active, audio_filename, message) - VALUES (?, ?, ?, ?, ?, ?, ?)""", - ( - task.get("id"), - task["cron_expression"], - task["type"], - task["channel_id"], - int(task.get("active", True)), - task.get("audio_filename"), - task.get("message"), - ), - ) - conn.commit() - except Exception as e: - logger.error(f"Error saving cron jobs: {e}") - - async def schedule_checker(self): - """Background task to check and execute scheduled jobs""" - await self.bot.wait_until_ready() - while not self.bot.is_closed(): - try: - now = datetime.now() - for task in self.scheduled_tasks: - if not task.get("active", True): - continue - if self.should_run_task(task, now): - await self.execute_task(task) - await asyncio.sleep(30) - except Exception as e: - logger.error(f"Error in schedule checker: {e}") - await asyncio.sleep(30) - - def should_run_task(self, task, now): - """Check if a task should run now based on its cron expression""" - try: - cron_expr = task["cron_expression"] - cron = croniter(cron_expr, now) - prev_time = cron.get_prev(datetime) - time_diff = abs((now - prev_time).total_seconds()) - result = time_diff <= 30 - logger.debug( - f"Cron '{cron_expr}': prev={prev_time}, now={now}, diff={time_diff}, run={result}" - ) - return result - except Exception as e: - logger.error( - f"Error checking cron expression {task.get('cron_expression')}: {e}" - ) - return False - - async def execute_task(self, task): - """Execute a scheduled task""" - try: - task_type = task["type"] - channel_id = task["channel_id"] - - channel = self.bot.get_channel(channel_id) - if not channel: - logger.warning(f"Channel {channel_id} not found for task ID: {task.get('id')}") - return - - logger.info(f"Found channel {channel} (type: {type(channel).__name__})") - - perms = channel.permissions_for(channel.guild.me) - logger.debug( - f"Bot permissions: send_messages={perms.send_messages}, attach_files={perms.attach_files}" - ) - - if task_type in ("zagraj_audio", "audio"): - await self.play_audio_task(task, channel) - elif task_type in ("wyślij_wiadomość", "send_message", "wiadomość"): - await self.send_message_task(task, channel) - - logger.info(f"Executed task {task['id']} of type {task_type}") - except Exception as e: - logger.error(f"Error executing task {task.get('id')}: {e}") - - async def play_audio_task(self, task, channel): - """Play audio task implementation using voice manager""" - audio_url = task["audio_filename"] - - if isinstance(channel, discord.VoiceChannel): - voice_mgr = get_voice_manager(self.bot) - - try: - audio_source = discord.FFmpegPCMAudio(audio_url) - success = await voice_mgr.play_source(channel, audio_source) - - if success: - logger.info(f"Finished playing audio: {audio_url}") - else: - raise Exception("Playback failed") - - await voice_mgr.disconnect(channel.guild) - except Exception as e: - logger.error(f"Error playing audio: {e}") - try: - await voice_mgr.disconnect(channel.guild) - except Exception: - pass - if channel.guild.system_channel: - await channel.guild.system_channel.send( - f"Błąd podczas odgrywania audio: {audio_url}" - ) - else: - if hasattr(channel, "send"): - await channel.send("Odtwarzanie audio wymaga kanału głosowego") - - async def send_message_task(self, task, channel): - """Send message task implementation""" - message = task["message"] - logger.info(f"Message to send: '{message}' | Channel: {channel.id}") - await channel.send(message) - logger.debug("Message sent successfully") - - async def audio_autocomplete(self, ctx: discord.AutocompleteContext): - """Autocomplete for audio files from file_search""" - query = ctx.value.lower() if ctx.value else "" - if not query: - return "" - - audio_extensions = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "mp4", "mkv"] - results = await search_utils.UniversalSearcher.search_copyparty( - query, max_results=25, file_extensions=audio_extensions - ) - - choices = [] - for url in results[:25]: - filename = url.split("/")[-1] - display_name = filename[:98] + "…" if len(filename) > 100 else filename - choices.append(discord.OptionChoice(name=display_name, value=url)) - - return ( - choices - if choices - else [discord.OptionChoice(name="Brak wyników", value="")] - ) - - def _get_baseurl(self): - """Get base URL from settings""" - try: - from cogs.utils.settings import get_baseurl - - return get_baseurl() - except Exception: - return "" - - trigger_group = discord.SlashCommandGroup("cron", "komendy cron") - - @trigger_group.command( - name="dodaj_audio", description="Dodaj nowe zadanie cron audio" - ) - async def cron_add_audio( - self, - ctx, - cron_expression: str = discord.Option( - str, - "Ekspresja cron (m H D M Y. '37 21 * * *' wykonuje zadanie o 21:37 codziennie)", - required=True, - ), - channel: discord.VoiceChannel = discord.Option( - discord.VoiceChannel, - "Kanał głosowy na którym będzie wykonywana akcja", - required=True, - ), - audio_filename: str = discord.Option( - str, - "Plik audio z serwera copyparty", - required=True, - autocomplete=audio_autocomplete, - ), - ): - try: - croniter(cron_expression) - except Exception: - await ctx.respond( - "Zła składnia cron, zapytaj się lokalnego eksperta o pomoc", - ephemeral=True, - ) - return - - task_id = max([t["id"] for t in self.scheduled_tasks], default=0) + 1 - - task = { - "id": task_id, - "cron_expression": cron_expression, - "type": "audio", - "channel_id": channel.id, - "active": True, - } - - task["audio_filename"] = audio_filename - - self.scheduled_tasks.append(task) - self.save_tasks() - - await ctx.respond( - f"Zadanie cron dodane. ID: {task_id}\nSkładnia: `{cron_expression}`\nAkcja: audio\nKanał: <#{channel.id}>" - ) - - @trigger_group.command( - name="dodaj_wiadomość", description="Dodaj nowe zadanie cron wiadomość" - ) - async def cron_add_message( - self, - ctx, - cron_expression: str = discord.Option( - str, - "Ekspresja cron (m H D M Y. '37 21 * * *' wykonuje zadanie o 21:37 codziennie)", - required=True, - ), - channel: discord.TextChannel = discord.Option( - discord.TextChannel, "Kanał na którym będzie wykonana akcja", required=True - ), - message: str = discord.Option(str, "Wiadomość do wysłania", required=True), - ): - """Add a new cron job""" - - try: - croniter(cron_expression) - except Exception: - await ctx.respond( - "Zła składnia cron, zapytaj się lokalnego eksperta o pomoc", - ephemeral=True, - ) - return - - task_id = max([t["id"] for t in self.scheduled_tasks], default=0) + 1 - - task = { - "id": task_id, - "cron_expression": cron_expression, - "type": "wiadomość", - "channel_id": channel.id, - "active": True, - "message": message, - } - - self.scheduled_tasks.append(task) - self.save_tasks() - - await ctx.respond( - f"Zadanie cron dodane. ID: {task_id}\nSkładnia: `{cron_expression}`\nAkcja: wiadomość\nKanał: <#{channel.id}>" - ) - - @trigger_group.command( - name="pokaż", description="Pokaż wszystkie zadania cron" - ) - async def cron_list(self, ctx): - """List all cron jobs""" - if not self.scheduled_tasks: - await ctx.respond("Brak zadań cron") - return - - embed = discord.Embed(title="Zaplanowane prace cron", color=0x00FF00) - - for task in self.scheduled_tasks: - channel = self.bot.get_channel(task["channel_id"]) - channel_name = f"#{channel}" - status = "AKTYWNA" if task.get("active", True) else "PAUZA" - - if task["type"] in ("zagraj_audio", "audio"): - action_desc = f"Zagraj audio: `{task.get('audio_filename', 'N/A')}`" - else: - msg = task.get("message", "") - action_desc = ( - f"Wyślij wiadomość: `{msg[:50]}{'...' if len(msg) > 50 else ''}`" - ) - - embed.add_field( - name=f"Zadanie #{task['id']} ({status})", - value=f"**Czas:** `{task['cron_expression']}`\n**Akcja:** {action_desc}\n**Kanał:** {channel_name}", - inline=False, - ) - - await ctx.respond(embed=embed) - - @trigger_group.command(name="usuń", description="Usuń zadanie cron z ID") - async def cron_remove( - self, - ctx, - job_id: int = discord.Option( - int, "ID zadania cron do usunięcia", required=True - ), - ): - """Remove a cron job by ID""" - for i, task in enumerate(self.scheduled_tasks): - if task["id"] == job_id: - self.scheduled_tasks.pop(i) - self.save_tasks() - await ctx.respond(f"Usunięto zadanie cron #{job_id}") - return - - await ctx.respond(f"Nie znaleziono zadania cron z ID {job_id}") - - @trigger_group.command(name="pauza", description="Zapauzuj zadanie cron z ID") - async def cron_pause( - self, - ctx, - job_id: int = discord.Option( - int, "ID zadania cron do zatrzymania", required=True - ), - ): - """Pause a cron job by ID""" - for task in self.scheduled_tasks: - if task["id"] == job_id: - task["active"] = False - self.save_tasks() - await ctx.respond(f"Zapauzowano zadanie cron #{job_id}") - return - - await ctx.respond(f"Nie znaleziono zadania cron z ID {job_id}") - - @trigger_group.command(name="wznów", description="Wznów zadanie cron z ID") - async def cron_resume( - self, - ctx, - job_id: int = discord.Option( - int, "ID zadania cron do wznowienia", required=True - ), - ): - """Resume a paused cron job by ID""" - for task in self.scheduled_tasks: - if task["id"] == job_id: - task["active"] = True - self.save_tasks() - await ctx.respond(f"Wznowiono zadanie cron z ID #{job_id}") - return - - await ctx.respond(f"Nie znaleziono zadania cron z ID {job_id}") - - def cog_unload(self): - """Clean up when cog is unloaded""" - if hasattr(self, "task"): - self.task.cancel() - - -def setup(bot): - bot.add_cog(Cron(bot)) -- cgit v1.2.3