Files
discord-docker-bot/main.py
2026-03-25 11:43:26 +00:00

212 lines
6.8 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import discord
import docker
import os
import json
from discord.ext import commands, tasks
from dotenv import load_dotenv
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
DOCKER_HOST = os.getenv("DOCKER_HOST")
GUILD_ID = int(os.getenv("GUILD_ID")) # Your server ID
CATEGORY_NAME = os.getenv("CATEGORY_NAME") # Category name for the channels
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
# Stores groups: { "groupname": { "channel_id": 123, "containers": ["nginx", "mysql"], "message_id": 456 } }
GROUPS_FILE = "groups.json"
def load_groups():
if os.path.exists(GROUPS_FILE):
with open(GROUPS_FILE, "r") as f:
return json.load(f)
return {}
def save_groups(groups):
with open(GROUPS_FILE, "w") as f:
json.dump(groups, f, indent=2)
def status_symbol(status):
symbols = {
"running": "🟢",
"exited": "🔴",
"paused": "🟡",
"created": "",
}
return symbols.get(status, "")
# --- Commands ---
@bot.command()
async def container_list(ctx):
"""Shows all available containers on the host"""
try:
client = docker.DockerClient(base_url=DOCKER_HOST, timeout=5)
container_list = client.containers.list(all=True)
message = "**🐳 Available Containers:**\n```\n"
for c in container_list:
message += f"{status_symbol(c.status)} {c.name}\n"
message += "```\nUse `!group_create <name> <container1> <container2> ...` to create a group."
await ctx.send(message)
except Exception as e:
await ctx.send(f"❌ Error: {e}")
@bot.command()
async def group_create(ctx, group_name: str, *container_names):
"""Creates a group and a channel for it
Example: !group_create webservices nginx apache"""
if not container_names:
await ctx.send("❌ Please specify at least one container!\nExample: `!group_create webservices nginx apache`")
return
guild = bot.get_guild(GUILD_ID)
groups = load_groups()
# Create category if it doesn't exist
category = discord.utils.get(guild.categories, name=CATEGORY_NAME)
if not category:
category = await guild.create_category(CATEGORY_NAME)
# Create channel
channel_name = f"🐳{group_name.lower()}"
channel = discord.utils.get(category.channels, name=channel_name)
if not channel:
# Only bot is allowed to write
overwrites = {
guild.default_role: discord.PermissionOverwrite(send_messages=False),
guild.me: discord.PermissionOverwrite(send_messages=True)
}
channel = await category.create_text_channel(channel_name, overwrites=overwrites)
# Save group
groups[group_name] = {
"channel_id": channel.id,
"containers": list(container_names),
"message_id": None
}
save_groups(groups)
await ctx.send(f"✅ Group **{group_name}** created with channel {channel.mention}\nContainers: `{', '.join(container_names)}`")
@bot.command()
async def group_remove(ctx, group_name: str):
"""Deletes a group and its channel"""
groups = load_groups()
if group_name not in groups:
await ctx.send(f"❌ Group `{group_name}` not found.")
return
guild = bot.get_guild(GUILD_ID)
channel = guild.get_channel(groups[group_name]["channel_id"])
if channel:
await channel.delete()
del groups[group_name]
save_groups(groups)
await ctx.send(f"✅ Group **{group_name}** deleted.")
@bot.command()
async def group_list(ctx):
"""Shows all groups"""
groups = load_groups()
if not groups:
await ctx.send("No groups found. Use `!group_create` to create one.")
return
message = "**📋 Groups:**\n"
for name, data in groups.items():
message += f"\n**{name}:** `{', '.join(data['containers'])}`"
await ctx.send(message)
@bot.command()
async def info(ctx):
"""Shows all available commands and their usage"""
message = (
"**🤖 Docker Monitor Bot - Commands**\n\n"
"**📋 Container**\n"
"`!container_list`\n"
"→ Shows all available containers on the host\n\n"
"**📁 Groups**\n"
"`!group_create <name> <container1> <container2> ...`\n"
"→ Creates a group with a dedicated channel for the given containers\n"
"→ Example: `!group_create webservices nginx apache`\n\n"
"`!group_remove <name>`\n"
"→ Deletes a group and its channel\n"
"→ Example: `!group_remove webservices`\n\n"
"`!group_list`\n"
"→ Shows all existing groups and their containers\n\n"
"** Other**\n"
"`!info`\n"
"→ Shows this help message\n\n"
"**🔄 Monitoring**\n"
"Container statuses are automatically updated every **30 seconds** in their group channels.\n"
"🟢 running 🔴 exited 🟡 paused ⚪ created ❓ unknown"
)
await ctx.send(message)
# --- Monitoring ---
@tasks.loop(seconds=30)
async def monitor_containers():
groups = load_groups()
if not groups:
return
try:
docker_client = docker.DockerClient(base_url=DOCKER_HOST, timeout=5)
all_containers = {c.name: c.status for c in docker_client.containers.list(all=True)}
except Exception:
return
guild = bot.get_guild(GUILD_ID)
for group_name, data in groups.items():
channel = guild.get_channel(data["channel_id"])
if not channel:
continue
# Build status text
text = f"**🐳 {group_name.upper()}**\n```\n"
for container_name in data["containers"]:
if container_name in all_containers:
status = all_containers[container_name]
text += f"{status_symbol(status)} {container_name:<25} {status}\n"
else:
text += f"{container_name:<25} not found\n"
text += "```"
try:
if data["message_id"]:
# Edit existing message
message = await channel.fetch_message(data["message_id"])
await message.edit(content=text)
else:
# Send first message
message = await channel.send(text)
data["message_id"] = message.id
save_groups(groups)
except discord.NotFound:
# Message was deleted, resend
message = await channel.send(text)
data["message_id"] = message.id
save_groups(groups)
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
return # Ignore unknown commands silently
raise error # All other errors still show in logs
@bot.event
async def on_ready():
print(f"Bot is online as {bot.user}")
monitor_containers.start()
bot.run(BOT_TOKEN)