""" Discord + Telegram Event Reminder & Music Bot -------------------------------------------- By Kevin Heyer, Skull-IT.de Features: - Stores Discord Scheduled Events in a SQLite DB - Sends notifications about new events to Discord and Telegram - Sends reminders 1 day before an event starts - Can join a voice channel and play YouTube audio (music bot) - Uses yt-dlp + FFmpeg for audio streaming Requirements: - discord.py - python-telegram-bot (or telegram) - yt-dlp - ffmpeg installed on the system - PyNaCl installed (for Discord voice support) sudo apt-get update sudo apt-get install -y ffmpeg python3-dev libffi-dev libnacl-dev build-essential pip install -r requirements.txt Make sure to configure your tokens and IDs inside a .env file. """ # ===================== # IMPORTS # ===================== import os import sqlite3 import asyncio from datetime import datetime, timedelta, timezone import discord from discord.ext import commands, tasks from discord.ui import Button, View from telegram import Bot as TelegramBot import yt_dlp as youtube_dl from dotenv import load_dotenv # ===================== # LOAD ENVIRONMENT VARIABLES # ===================== load_dotenv() BOT_TOKEN = os.getenv("BOT_TOKEN") DISCORD_CHANNEL_ID = os.getenv("DISCORD_CHANNEL_ID") TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN") TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID") # ===================== # DISCORD SETUP # ===================== intents = discord.Intents.default() intents.guild_scheduled_events = True intents.messages = True intents.message_content = True # Needed for text commands bot = commands.Bot(command_prefix="!", intents=intents) # ===================== # TELEGRAM BOT # ===================== tg_bot = TelegramBot(token=TELEGRAM_TOKEN) # ===================== # DATABASE SETUP # ===================== conn = sqlite3.connect("events.db") c = conn.cursor() c.execute( """CREATE TABLE IF NOT EXISTS events ( id TEXT PRIMARY KEY, name TEXT, start_time TEXT, reminded INTEGER DEFAULT 0 )""" ) conn.commit() # ===================== # DISCORD EVENT HANDLER # ===================== @bot.event async def on_scheduled_event_create(event: discord.ScheduledEvent): """ Triggered when a new scheduled event is created on the server. Saves the event to the database and sends notifications. """ # Store event in the database c.execute( "INSERT OR IGNORE INTO events (id, name, start_time) VALUES (?, ?, ?)", (str(event.id), event.name, event.start_time.isoformat()), ) conn.commit() # Send Discord notification channel = bot.get_channel(DISCORD_CHANNEL_ID) if channel: await channel.send(f"📅 New Event Created: **{event.name}** at {event.start_time}") # Send Telegram notification await tg_bot.send_message( chat_id=TELEGRAM_CHAT_ID, text=f"📅 New Event Created: {event.name} at {event.start_time}", ) # ===================== # REMINDER TASK # ===================== @tasks.loop(minutes=60) async def reminder_task(): """ Runs every hour, checks if any event starts within 24 hours, and sends a reminder message to Discord and Telegram. """ now = datetime.now(timezone.utc) c.execute("SELECT id, name, start_time, reminded FROM events") rows = c.fetchall() for event_id, name, start_time_str, reminded in rows: start_time = datetime.fromisoformat(start_time_str) # Send reminder if event starts within 24h and not yet reminded if not reminded and now + timedelta(days=1) >= start_time: for guild in bot.guilds: event = discord.utils.get(guild.scheduled_events, id=int(event_id)) if event: channel = bot.get_channel(DISCORD_CHANNEL_ID) if channel: await channel.send(f"⏰ Reminder: Event **{name}** starts tomorrow!") # Telegram notification await tg_bot.send_message( chat_id=TELEGRAM_CHAT_ID, text=f"⏰ Reminder: Event {name} starts tomorrow!", ) c.execute("UPDATE events SET reminded=1 WHERE id=?", (event_id,)) conn.commit() # ===================== # BASIC COMMANDS # ===================== @bot.command() async def hallo(ctx): """Simple hello command.""" await ctx.send(f"Hello {ctx.author.mention}! 👋") # ===================== # YTDL / FFMPEG SETUP # ===================== ytdl_format_options = { "format": "bestaudio/best", "noplaylist": True, "quiet": True, "extract_flat": False, } ffmpeg_options = { "before_options": "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5", "options": "-vn -ar 48000 -ac 2 -b:a 192k", # Optimized audio for Discord } ytdl = youtube_dl.YoutubeDL(ytdl_format_options) class YTDLSource(discord.PCMVolumeTransformer): """ Handles downloading and streaming audio from YouTube. """ def __init__(self, source, *, data, volume=0.5): super().__init__(source, volume) self.data = data self.title = data.get("title") @classmethod async def from_url(cls, url, *, loop=None, stream=False): loop = loop or asyncio.get_event_loop() data = await loop.run_in_executor( None, lambda: ytdl.extract_info(url, download=not stream) ) if "entries" in data: data = data["entries"][0] filename = data["url"] if stream else ytdl.prepare_filename(data) return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data) # ===================== # VOICE / MUSIC COMMANDS # ===================== @bot.command(name="join") async def join(ctx): """Join the voice channel the user is currently in.""" if ctx.author.voice: channel = ctx.author.voice.channel await channel.connect() await ctx.send(f"Joined voice channel **{channel}**.") else: await ctx.send("You are not in a voice channel!") @bot.command(name="leave", help="Bot leaves the voice channel.") async def leave(ctx): """Disconnect from the current voice channel.""" if ctx.voice_client: await ctx.voice_client.disconnect() else: await ctx.send("I am not in a voice channel!") @bot.command(name="play") async def play(ctx, *, url): """ Streams audio from a given YouTube URL to the connected voice channel. Adds interactive buttons to control playback. """ # Connect to voice channel if not already connected if not ctx.voice_client: if ctx.author.voice: await ctx.author.voice.channel.connect() else: await ctx.send("You are not in a voice channel!") return # Start streaming player = await YTDLSource.from_url(url, loop=bot.loop, stream=True) ctx.voice_client.stop() ctx.voice_client.play(player) # Create interactive buttons stop_button = Button(label="Stop", style=discord.ButtonStyle.red) pause_button = Button(label="Pause", style=discord.ButtonStyle.gray) resume_button = Button(label="Resume", style=discord.ButtonStyle.green) async def stop_callback(interaction): if ctx.voice_client: ctx.voice_client.stop() await interaction.response.edit_message(content="âšī¸ Stopped", view=None) async def pause_callback(interaction): if ctx.voice_client and ctx.voice_client.is_playing(): ctx.voice_client.pause() await interaction.response.defer() async def resume_callback(interaction): if ctx.voice_client and ctx.voice_client.is_paused(): ctx.voice_client.resume() await interaction.response.defer() # Attach callbacks stop_button.callback = stop_callback pause_button.callback = pause_callback resume_button.callback = resume_callback # Build and send view view = View() view.add_item(stop_button) view.add_item(pause_button) view.add_item(resume_button) await ctx.send(f"đŸŽļ Now playing: **{player.title}**", view=view) @bot.command(name="stop", help="Stop the music.") async def stop(ctx): """Stop the currently playing music.""" if ctx.voice_client and ctx.voice_client.is_playing(): ctx.voice_client.stop() await ctx.send("âšī¸ Playback stopped.") else: await ctx.send("Nothing is currently playing.") # ===================== # BOT READY EVENT # ===================== @bot.event async def on_ready(): """Called when the bot successfully connects to Discord.""" if not reminder_task.is_running(): reminder_task.start() print(f"{bot.user} is online") # ===================== # START BOT # ===================== bot.run(BOT_TOKEN)