diff --git a/main.py b/main.py new file mode 100644 index 0000000..705bf11 --- /dev/null +++ b/main.py @@ -0,0 +1,211 @@ +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)