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 ...` 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 ...`\n" "→ Creates a group with a dedicated channel for the given containers\n" "→ Example: `!group_create webservices nginx apache`\n\n" "`!group_remove `\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)