Skip to content

Feat: Implement Cogs, Moderation, Leveling, and Search Features #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,7 @@ fabric.properties
*.tmp
*.temp
*.sass-cache

# Python stuff:
.venv
__pycache__
52 changes: 0 additions & 52 deletions appwrite/discord-bots/public-bot/addonsearch.py

This file was deleted.

149 changes: 61 additions & 88 deletions appwrite/discord-bots/public-bot/bot.py
Original file line number Diff line number Diff line change
@@ -1,104 +1,77 @@
""" This is the main file for the bot. """
import os
import asyncio
import traceback
import dotenv
import discord
from discord import app_commands
from discord.ext import commands
from colorama import Back, Style
from addonsearch import addonsearch
from schematicsearch import schematicsearch
import dotenv
import os

dotenv.load_dotenv()
TOKEN = os.getenv("PUBLIC_DISCORD_TOKEN")
GUILD_ID = os.getenv("GUILD_ID")

intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="??", intents=intents)

# Remove the old help command
bot = commands.Bot(command_prefix="!!", intents=intents, help_command=None)
bot.remove_command("help")

@bot.event
async def setup_hook():
"""Runs async setup before the bot logs in."""
print("Loading cogs...")
try:
await bot.load_extension("cogs.general")
await bot.load_extension("cogs.events")
await bot.load_extension("cogs.developer")
await bot.load_extension("cogs.moderation")
await bot.load_extension("cogs.leveling")
await bot.load_extension("cogs.search")
except commands.ExtensionError as e:
print(f"Failed to load cog: {type(e).__name__} - {e}")
print("Cogs loaded.")


@bot.event
async def on_ready():
# Synchronize slash commands with Discord
await bot.tree.sync()
print(f"{Back.GREEN}Logged in as {bot.user}{Style.RESET_ALL}")
"""Synchronize slash commands with Discord"""
# In production, use the manual sync commands instead
try:
guild_commands = await bot.tree.sync()
print(f"App command sync complete: {len(guild_commands)} commands registered.")
except discord.DiscordServerError as e:
print(f"Command sync failed: Discord server error: {e}")
except discord.HTTPException as e:
print(f"Command sync failed: HTTP error: {e}")
except discord.DiscordException as e:
print(f"Command sync failed: {e}")

@bot.tree.command(name="help", description="Displays the list of available commands")
async def help_command(interaction: discord.Interaction):
embed = discord.Embed(
title="Commands available",
description="Addons & Schematics commands",
color=discord.Color.blurple()
)
embed.add_field(
name="Addon Commands",
value="/addon search <query> [limit=1] - Search for addons on Blueprint.",
inline=False
)
embed.add_field(
name="Schematic Commands",
value="/schematic search <query> [limit=1]",
inline=False
)
embed.add_field(
name="Data types",
value="""
<> - needed
[] - optional
""",
inline=False
)
await interaction.response.send_message(embed=embed)

# Slash command for addon search
@bot.tree.command(name="addon", description="Manages commands related to addons")
async def addon_command(interaction: discord.Interaction, query: str, limit: int = 1):
limit = max(1, min(limit, 5))

# Defer the response to avoid InteractionResponded error
await interaction.response.defer(ephemeral=True)

# Search for addons
await addonsearch(interaction=interaction, query=query, limit=limit)
async def main():
"""Main function to start the bot"""
if TOKEN is None:
print("ERROR: Bot token not found in .env file!")
return

# Slash command for schematic search
@bot.tree.command(name="schematic", description="Manages commands related to schematics")
async def schematic_command(interaction: discord.Interaction, query: str, limit: int = 1):
limit = max(1, min(limit, 5))

# Defer the response to avoid InteractionResponded error
await interaction.response.defer(ephemeral=True)

# Search for schematics
await schematicsearch(interaction=interaction, query=query, limit=limit)
try:
print("Starting bot...")
await bot.start(TOKEN)
except discord.LoginFailure:
print("ERROR: Improper token passed. Check your .env file.")
except discord.PrivilegedIntentsRequired:
print(
"ERROR: Privileged Intents (Members/Message Content) are not enabled in the Developer Portal or here."
)
except discord.HTTPException as e:
print(f"ERROR: HTTP request failed: {e}")
except discord.ConnectionClosed as e:
print(f"ERROR: Discord connection closed unexpectedly: {e}")
except (RuntimeError, TypeError, ValueError, OSError) as e:
print(f"CRITICAL ERROR: An unexpected error occurred: {e}")
traceback.print_exc()

@bot.tree.command(name="link", description="Displays the link to the official website")
async def site_command(interaction: discord.Interaction):
embed = discord.Embed(
title="Official Website",
description="Visit our website to discover more addons and schematics!",
color=discord.Color.blue(),
url="https://your-website.com" # Replace with your actual URL
)

# Add an image to the embed (optional)
embed.set_thumbnail(url="https://your-website.com/logo.png") # Replace with your logo URL

# Add additional information
embed.add_field(
name="Latest Updates",
value="Check out the latest addons and schematics added to our site!",
inline=False
)

embed.add_field(
name="Support",
value="Need help? Visit our forum or contact us directly on the site.",
inline=False
)

# Add a footer
embed.set_footer(text="© 2025 Your Name - All rights reserved")

await interaction.response.send_message(embed=embed)

bot.run(os.getenv("PUBLIC_DISCORD_TOKEN"))
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("Bot shutdown requested.")
85 changes: 85 additions & 0 deletions appwrite/discord-bots/public-bot/cogs/developer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Developer-only commands for the bot."""

import os
import json

import discord
from discord.ext import commands
from discord import app_commands # For slash commands

OWNER_ID = os.getenv("OWNER_ID")
SERVER_ID = os.getenv("SERVER_ID")
CHANNEL_ID = os.getenv("CHANNEL_ID")
DEV_ROLE_ID = os.getenv("DEV_ROLE_ID")
NO_DEV_ROLE_MSG = "You need the Developer role to use this command."
DEV_SERVER_MSG = "This command can only be used in the developer server."


class DeveloperCog(commands.Cog):
"""
Developer-only commands for the bot.
Args:
commands (commands.Bot): The bot instance to which this cog is attached.
"""

def __init__(self, bot: commands.Bot):
self.bot = bot
self.config_path = "./config/testingurl.json" # Adjusted path slightly
# Ensure owner_id is set on the bot instance for checks if needed elsewhere
self.bot.owner_id = int(OWNER_ID) if OWNER_ID else None
self.dev_role_id = int(DEV_ROLE_ID) if DEV_ROLE_ID else None
self.server_id = int(SERVER_ID) if SERVER_ID else None
self.channel_id = int(CHANNEL_ID) if CHANNEL_ID else None

# Print out debug info
print("Developer Cog initialized with settings:")
print(f"Owner ID: {self.bot.owner_id}")
print(f"Developer Role ID: {self.dev_role_id}")
print(f"Server ID: {self.server_id}")
print(f"Channel ID: {self.channel_id}")

# --- Helper: Check if user is a dev ---
def _is_dev(self, user: discord.User | discord.Member) -> bool:
"""Check if a user is a developer based on role or owner status"""
if user.id == self.bot.owner_id:
return True

if isinstance(user, discord.Member) and self.dev_role_id:
return any(role.id == self.dev_role_id for role in user.roles)

return False

# --- Helper for prefix commands (for backward compatibility) ---
async def is_dev(self, ctx: commands.Context) -> bool:
"""Legacy method for prefix command checks"""
if not ctx.guild:
return False

return self._is_dev(ctx.author)

# --- Helper: Load/Save Config ---
def _load_testing_config(self):
try:
with open(self.config_path, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
# Return default structure if file missing or invalid
return {"url": None}

def _save_testing_config(self, config_data):
try:
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
with open(self.config_path, "w", encoding="utf-8") as f:
json.dump(config_data, f, indent=4)
return True
except (OSError, IOError, TypeError) as e:
print(f"Error saving testing config: {e}")
return False


# Setup function to add the Cog to the bot
async def setup(bot: commands.Bot):
"""Setup function to add the Cog to the bot"""
await bot.add_cog(DeveloperCog(bot))
print(" DeveloperCog loaded.")
72 changes: 72 additions & 0 deletions appwrite/discord-bots/public-bot/cogs/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
This cog handles events, such as message processing and command suggestions.
"""

import random
import discord
from discord.ext import commands


class EventsCog(commands.Cog):
"""
A Discord event handling cog that processes messages and provides command migration guidance.

This cog listens to all messages, processes prefix commands, and occasionally suggests
migrating from legacy prefix commands to new slash commands by providing helpful reminders.
"""

def __init__(self, bot: commands.Bot):
self.bot = bot

# Use Cog.listener() for events
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
"""
Processes incoming messages to handle prefix commands and suggest slash commands.

Args:
message (discord.Message): The message object representing the incoming message.
"""
# Ignore bots
if message.author == self.bot.user or message.author.bot:
return

# Process prefix commands
await self.bot.process_commands(message)

# Debug: Print received message for testing
print(f"Message received from {message.author.name}: {message.content[:30]}...")

# Check for old command usage and suggest slash commands instead
# This helps users transition to the new slash command system
if message.content.startswith("!!") and message.guild:
command = message.content[2:].split(" ")[0].lower()

# Mapping of old prefix commands to new slash commands
command_mapping = {
"yapping": "yapping",
"wiki": "wiki",
"plshelp": "wiki",
"github": "github",
"git": "github",
"socials": "socials",
"members": "members",
"statusbot": "statusbot",
"issue8ball": "issue8ball",
}

debug_commands = ["ping", "syncguild", "syncglobal", "cmdinfo"]

if command in command_mapping and command not in debug_commands:
# Only remind occasionally to avoid spamming
if random.random() < 0.3:
slash_command = command_mapping[command]
await message.channel.send(
f"💡 **Tip:** We're moving to slash commands! Try using /{slash_command} instead of !!{command}",
delete_after=10,
)

async def setup(bot: commands.Bot):
""" Add the EventsCog to the bot """
await bot.add_cog(EventsCog(bot))
print(" EventsCog loaded.")
Loading