First Init

This commit is contained in:
= 2025-09-19 22:58:50 +02:00
commit 1667827dc4
4 changed files with 362 additions and 0 deletions

4
.env-example Normal file
View file

@ -0,0 +1,4 @@
BOT_TOKEN=dein_discord_token
DISCORD_CHANNEL_ID=123456789012345678
TELEGRAM_TOKEN=dein_telegram_token
TELEGRAM_CHAT_ID=-1001234567890

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.venv/
.env
events.db

72
requirements.txt Normal file
View file

@ -0,0 +1,72 @@
annotated-types==0.7.0
ansible==11.7.0
ansible-core==2.18.6
anyio==3.6.2
apache-libcloud==3.4.1
argcomplete==2.0.0
attrs==25.3.0
Babel==2.10.3
certifi==2022.9.24
chardet==5.1.0
charset-normalizer==3.0.1
click==8.1.3
cloudflare==4.3.1
colorama==0.4.6
cryptography==38.0.4
distro==1.9.0
dnspython==2.3.0
docopt==0.6.2
fysom==2.1.6
h11==0.14.0
h2==4.1.0
hpack==4.0.0
httpcore==0.16.3
httplib2==0.20.4
httpx==0.23.3
hyperframe==6.0.0
idna==3.3
Jinja2==3.1.2
jmespath==1.0.1
JSON-minify==0.3.0
jsonschema==4.24.0
jsonschema-specifications==2025.4.1
libvirt-python==9.0.0
lockfile==0.12.2
markdown-it-py==2.1.0
MarkupSafe==2.1.2
mdurl==0.1.2
netaddr==0.8.0
ntlm-auth==1.4.0
packaging==23.0
passlib==1.7.4
pycairo==1.20.1
pydantic==2.11.7
pydantic_core==2.33.2
Pygments==2.14.0
PyGObject==3.42.2
pykerberos==1.1.14
pyparsing==3.0.9
python-apt==2.6.0
python-dateutil==2.9.0.post0
python-dotenv==1.1.1
pytz==2022.7.1
pywinrm==0.3.0
PyYAML==6.0
referencing==0.36.2
requests==2.28.1
requests-kerberos==0.12.0
requests-ntlm==1.1.0
requests-toolbelt==0.10.1
resolvelib==0.9.0
rfc3986==1.5.0
rich==13.3.1
rpds-py==0.25.1
selinux==3.4
simplejson==3.18.3
six==1.16.0
sniffio==1.2.0
termcolor==3.1.0
typing-inspection==0.4.1
typing_extensions==4.14.0
urllib3==1.26.12
xmltodict==0.13.0

283
skullbot.py Normal file
View file

@ -0,0 +1,283 @@
"""
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 = int(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)