Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9697497
Add per-cooldown reminder toggles and update tasks
Arieswaran Nov 1, 2025
90b88c4
Add Emoji Quiz game and leaderboard
Arieswaran Nov 1, 2025
1f87d26
Add string similarity functions and improve quiz answer checking
Arieswaran Nov 1, 2025
e21eaff
Add reward card system to emoji quiz
Arieswaran Nov 3, 2025
53d74dd
Add logging for quiz and reward card events
Arieswaran Nov 3, 2025
6e81179
Add PvP challenge and match system
Arieswaran Nov 3, 2025
ee5d091
Enhance PvP match with round previews and auto-test
Arieswaran Nov 3, 2025
73858ee
Enhance PvP winner highlight and add post-round delay
Arieswaran Nov 3, 2025
d6a8e9b
Add DejaVuSans and Roboto font files
Arieswaran Nov 3, 2025
a645d55
Add RPS faction bonus to PvP match logic
Arieswaran Nov 3, 2025
88aa461
Add PvP reward selection with hidden card covers
Arieswaran Nov 3, 2025
5ac8c5b
Add special message for missed mystic/celestial drops
Arieswaran Nov 3, 2025
e57e4f9
Add 'Guess the IU MV' game and leaderboard
Arieswaran Nov 3, 2025
b80b9a6
Improve segment extraction and add debug saving
Arieswaran Nov 3, 2025
badf440
Add dynamic blur reduction to MV guessing game
Arieswaran Nov 3, 2025
e5ec886
Improve MV guess game UX and adjust blur logic
Arieswaran Nov 3, 2025
bce485f
Add popularity weighting to quiz sampling
Arieswaran Nov 4, 2025
c4d2570
Expand quiz content and adjust game settings
Arieswaran Nov 4, 2025
de43a0b
Improve answer timing and validation in emoji quiz
Arieswaran Nov 4, 2025
b403b58
Add PvP rewards toggle to settings
Arieswaran Nov 10, 2025
3add744
Add PVP stats tracking and leaderboard
Arieswaran Nov 10, 2025
ae17a46
Initial plan
Copilot Nov 10, 2025
5a5c650
Add reward card system with feature flag for all game types
Copilot Nov 10, 2025
046245c
Move emoji quiz reward probabilities to settings.json
Copilot Nov 10, 2025
81e309e
Merge pull request #73 from ChocoMeow/copilot/add-reward-card-settings
Arieswaran Nov 10, 2025
9b0fd1a
Refactor reward card feature flag and settings access
Arieswaran Nov 10, 2025
a1f4cfe
Add scalable inventory slot purchases to shop
Arieswaran Nov 10, 2025
0baffd4
Fix card reset threshold and cache clear interval
Arieswaran Nov 10, 2025
5ad9c2d
Implement monthly leaderboards and stats reset
Arieswaran Nov 11, 2025
6922fa0
Add admin cog and enhance profile and stats UI
Arieswaran Nov 11, 2025
e92d816
Refactor settings access and monthly leaderboard roles
Arieswaran Nov 16, 2025
1920482
Update settings.json
Arieswaran Nov 21, 2025
5d50fc2
Update level cover images
Arieswaran Nov 22, 2025
6f2b117
Enable processing of new cards in CardPool
Arieswaran Nov 28, 2025
2e5b8c8
Merge branch 'main' into aries/QoL
Arieswaran Nov 28, 2025
75ae441
Update inventory slot purchase logic and pricing
Arieswaran Nov 28, 2025
be5a417
Update shop price calculation logic
Arieswaran Nov 30, 2025
0bf2b35
Update achievements, MV data, and task rewards
Arieswaran Nov 30, 2025
6e76182
Enforce PvP team tier rules and update reward card embed
Arieswaran Nov 30, 2025
1bd8dda
Remove leaderboard buttons from EmojiLeaderboardView
Arieswaran Nov 30, 2025
12a942e
Update pvp.py
Arieswaran Nov 30, 2025
990dede
Enable monthly reward distribution and update song type
Arieswaran Dec 1, 2025
7ecfb3d
Update and expand song emoji mappings
Arieswaran Dec 3, 2025
181ba5a
add smol to admin
Arieswaran Dec 10, 2025
42bb9a4
remove admin access
Arieswaran Dec 11, 2025
331021c
Move admin commands to developer cog and refactor
ChocoMeow Dec 11, 2025
e604c52
Handle CheckFailure in command error handler
ChocoMeow Dec 13, 2025
4da47b3
music quiz song selection logic change
Arieswaran Dec 13, 2025
de5fa24
Refactor leaderboard commands and update dependencies
ChocoMeow Dec 15, 2025
b2df09c
Refactor profile stats view and add framed title utility
ChocoMeow Dec 17, 2025
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
294 changes: 291 additions & 3 deletions cogs/developer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import discord, iufi, psutil, asyncio
import iufi
import psutil
import asyncio
import discord
import functions as func

from discord.ext import commands
from views import DebugView
from views import DebugView, ConfirmView

def formatBytes(bytes: int, unit: bool = False):
if bytes <= 1_000_000_000:
Expand All @@ -21,11 +25,295 @@ def __init__(self, bot: commands.Bot) -> None:
callback=self._findsimilar
)
self.bot.tree.add_command(self.ctx_menu)

async def cog_check(self, ctx: commands.Context) -> bool:
if ctx.author.id not in func.settings.ADMIN_IDS:
raise commands.CheckFailure("You do not have permission to use this command.")
return True

@commands.command()
async def givecandies(self, ctx: commands.Context, member: discord.Member, amount: int):
"""
Gives a specified number of candies to a user.

The `amount` must be a positive integer.
Candies are added directly to the user's inventory.
If no user is specified, the candies will be given to the command caller.

**Examples:**
@prefix@givecandies @username 5
"""
user_data = await func.get_user(member.id)
if not user_data:
return await ctx.reply("User not found.")
await func.update_user(member.id, {"$inc": {"candies": amount}})
await ctx.reply(f"{amount} candies have been given to {member.display_name}.")

@commands.command()
async def removecandies(self, ctx: commands.Context, member: discord.Member, amount: int):
"""
Removes a specified number of candies from a user's inventory.

The `amount` must be a positive integer.
If the user does not have enough candies, the command will fail gracefully.

**Examples:**
@prefix@removecandies @username 5
@prefix@removecandies @username 10
"""
user_data = await func.get_user(member.id)
if not user_data:
return await ctx.reply("User not found.")
await func.update_user(member.id, {"$inc": {"candies": -amount}})
await ctx.reply(f"{amount} candies have been removed from {member.display_name}.")

@commands.command()
async def resetCooldown(self, ctx: commands.Context, member: discord.Member, cooldown: str):
"""
Resets a specific cooldown for a user.

Supported cooldown types include: `roll`, `quiz`, and `mg`.
Once reset, the user can immediately perform the associated action again without waiting for the cooldown period.

**Examples:**
@prefix@resetCooldown @username roll
@prefix@resetCooldown @username quiz
"""
cd_types = {"roll": "roll", "quiz": "quiz_game", "mg": "match_game"}

if not (cooldown := cd_types.get(cooldown)):
return await ctx.reply(f"Cooldown not found. Available cooldown type: {', '.join(cd_types.keys())}")

user_data = await func.get_user(member.id)
if not user_data:
return await ctx.reply("User not found.")

await func.update_user(member.id, {"$set": {f"cooldown.{cooldown}": 0}})
await ctx.reply(f"{cooldown} cooldown has been reset for {member.display_name}.")

@commands.command()
async def resetCardTradeCooldown(self, ctx: commands.Context, card_id: str):
"""
Resets the trade cooldown for a specific card.

The `card_id` must match the identifier of the card whose cooldown is being cleared.
Once reset, the card can be traded again immediately without waiting for the cooldown period.

**Examples:**
@prefix@resetCardTradeCooldown 1234
"""
card = iufi.CardPool.get_card(card_id)
if not card:
return await ctx.reply("Card not found.")

await func.update_card(card_id, {"$set": {"last_trade_time": 0}})
await ctx.reply(f"Cooldown has been reset for card {card_id}.")

@commands.command()
async def giveCardToUser(self, ctx: commands.Context, member: discord.Member, card_id: str):
"""
Gives a specific card to a user.

The `card_id` must match the identifier of the card being awarded.
If no user is specified, the card will be given to the command caller.

**Examples:**
@prefix@giveCardToUser @username 1234
"""
card = iufi.CardPool.get_card(card_id)
if not card:
return await ctx.reply("Card not found.")

if card.owner_id:
return await ctx.reply("Card already owned by someone.")

user_data = await func.get_user(member.id)

if not user_data:
return await ctx.reply("User not found.")

if len(user_data["cards"]) >= func.settings.MAX_CARDS:
return await ctx.reply(f"{member.display_name} already has maximum cards.")

card.change_owner(member.id)
iufi.CardPool.remove_available_card(card)
await func.update_card(card_id, {"$set": {"owner_id": member.id}})
await func.update_user(member.id, {"$push": {"cards": card_id}})

await ctx.reply(f"Card {card_id} has been given to {member.display_name}.")

@commands.command()
async def removeCardFromUser(self, ctx: commands.Context, card_id: str):
"""
Removes a specific card from a user's collection.

The `card_id` must match the identifier of the card to be removed.
If the card does not exist or is invalid, the command will fail gracefully.

**Examples:**
@prefix@removeCardFromUser 1234
"""
card = iufi.CardPool.get_card(card_id)
if not card:
return await ctx.reply("Card not found.")

if not card.owner_id:
return await ctx.reply("Card is not owned by anyone.")

card.change_owner(None)
iufi.CardPool.add_available_card(card)
await func.update_card(card_id, {"$set": {"owner_id": None, "tag": None, "frame": None, "last_trade_time": 0}})
await func.update_user(card.owner_id, {"$pull": {"cards": card.id}})

await ctx.reply(f"Card {card_id} has been removed from user.")

@commands.command()
async def giveRollToUser(self, ctx: commands.Context, member: discord.Member, roll_type: str, amount: int = 1):
"""
Grants a specified number of rolls of a given type to a user.

If no amount is provided, the default is 1 roll.
The `roll_type` determines the category or kind of roll being awarded.

**Examples:**
@prefix@giveRollToUser @username rare 3
@prefix@giveRollToUser @username epic
"""
roll_types = ["rare", "epic", "legendary", "mystic", "celestial"]

if roll_type not in roll_types:
return await ctx.reply("Roll type not found. Available roll types: " + ", ".join(roll_types))

user_data = await func.get_user(member.id)
if not user_data:
return await ctx.reply("User not found.")

await func.update_user(member.id, {"$inc": {f"roll.{roll_type}": amount}})
await ctx.reply(f"{amount} {roll_type} rolls have been given to {member.display_name}.")

@commands.command()
async def giveBirthdayCard(self, ctx: commands.Context, member: discord.Member, day_number: int):
"""
Gives a birthday card to a specified user for a particular day.

If no user is specified, the card will be given to the command caller.
The `day_number` represents the day of the month associated with the birthday card.

**Examples:**
@prefix@giveBirthdayCard @username 15
@prefix@giveBirthdayCard 20
"""
if day_number < 1 or day_number > 31:
return await ctx.reply("Invalid day number. Must be between 1 and 31.")

user_data = await func.get_user(member.id)
if not user_data:
return await ctx.reply("User not found.")

# Convert day number to string for storage in the collection
day_str = str(day_number)

# Check if user already has this card
birthday_collection = user_data.get("birthday_collection", {})
if day_str in birthday_collection:
return await ctx.reply(f"{member.display_name} already has birthday card #{day_number}.")

# Add card to user's collection
update_query = {
"$set": {f"birthday_collection.{day_str}": True},
"$inc": {"birthday_cards_count": 1, "exp": 20}
}

await func.update_user(member.id, update_query)
await ctx.reply(f"Birthday card #{day_number} has been given to {member.display_name}.")

@commands.command()
async def setBirthdayCardsCount(self, ctx: commands.Context, member: discord.Member, count: int):
"""Set the birthday cards count for a user."""
user_data = await func.get_user(member.id)
if not user_data:
return await ctx.reply("User not found.")

# Set the birthday cards count
await func.update_user(member.id, {"$set": {"birthday_cards_count": count}})
await ctx.reply(f"Birthday cards count for {member.display_name} has been set to {count}.")

@commands.command()
async def quit(self, ctx: commands.Context, member: discord.Member = None):
"""[ADMIN ONLY] Deletes a user's profile after confirmation. All cards will be converted.

If no member is specified, it will delete the profile of the user who called the command.

**Examples:**
@prefix@quit @username
@prefix@quit
"""
target_user = member or ctx.author
user = await func.get_user(target_user.id)

# Create confirmation embed
embed = discord.Embed(title="⚠️ Delete Account", color=discord.Color.red())
embed.description = f"**WARNING: This action cannot be undone!**\n\nThis will:\n- Conver all {target_user.display_name}'s cards \n- Delete their entire profile and progress\n- Remove all inventory items and collections\n\nAre you sure you want to continue?"

# Create confirmation view
view = ConfirmView(ctx.author)
view.message = await ctx.reply(embed=embed, view=view)
await view.wait()

if not view.is_confirm:
embed.title = "❌ Account Deletion Cancelled"
embed.description = f"{target_user.display_name}'s account has not been deleted."
embed.color = discord.Color.green()
await view.message.edit(embed=embed, view=None)
return

# Convert all cards to candies (for logging purposes only)
converted_cards = []
for card_id in user["cards"]:
card = iufi.CardPool.get_card(card_id)
if card:
converted_cards.append(card)

card_ids = [card.id for card in converted_cards]
for card in converted_cards:
iufi.CardPool.add_available_card(card)

# Log the action
func.logger.info(
f"Admin {ctx.author.name}({ctx.author.id}) deleted the profile of {target_user.name}({target_user.id}). "
f"Returned {len(converted_cards)} card(s) to the available pool."
)

# Update the cards in the database to remove owner, tag, etc.
if card_ids:
await func.update_card(card_ids, {"$set": {"owner_id": None, "tag": None, "frame": None, "last_trade_time": 0}})

# Delete the user from the database
await func.USERS_DB.delete_one({"_id": target_user.id})

# Remove user from buffer cache if they exist there
if target_user.id in func.USERS_BUFFER:
del func.USERS_BUFFER[target_user.id]

# Update the confirmation message
embed.title = "✅ Account Deleted"
embed.description = f"{target_user.display_name}'s Account has been deleted. All their cards ({len(converted_cards)}) have been returned to the available pool."
embed.color = discord.Color.green()
await view.message.edit(embed=embed, view=None)

@commands.command(hidden=True)
@commands.is_owner()
async def debug(self, ctx: commands.Context):
"""For developer to debug"""
"""
Executes developer-only debugging actions.

This command is restricted to developers and is used for testing, diagnostics,
or troubleshooting within the bot environment. It should not be accessible to
regular users.

**Examples:**
@prefix@debug
"""
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')

Expand Down
Loading