First Commit

This commit is contained in:
2025-09-16 15:00:16 +02:00
commit c8980f785f
188 changed files with 43407 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
ticket
jail (Mute)
### 1. **Games and Minigames**
- **Text-based RPG**: Allow users to battle monsters, level up, and collect loot.
- **Trivia**: Users can answer questions in various categories like movies, history, or general knowledge.
- **Hangman, Tic-Tac-Toe, and Word Games**: Classic games with visual feedback using emojis.
- **Coin Flip, Dice Roll, Rock-Paper-Scissors**: Simple yet fun games to settle bets.
- **Roulette / Slots**: Casino-style games where users can win (or lose!) virtual currency.
- **Adventure Games**: Story-driven choices where users select paths that influence outcomes.
### 2. **Economy System**
- **Currency**: Earn coins or points by chatting, completing tasks, or winning games.
- **Jobs**: Users can “work” jobs (e.g., doctor, hacker, miner) to earn currency.
- **Shops**: Buy items like badges, roles, or even upgrades for minigames.
- **Trading**: Allow users to trade items or currency with each other.
- **Bank System**: Deposit or withdraw money to earn interest or protect against theft.
### 3. **Social Features**
- **Profile Cards**: Show off user stats, badges, levels, and custom titles.
- **Reputation and Karma**: Users can give each other points for positive interactions.
- **Pet System**: Adopt virtual pets that can be trained, fed, and leveled up.
- **Marriage or Friendship**: Allow users to "marry" or form friendships with others.
- **User Stats Tracking**: Track things like messages sent, hours active, or games played.
### 4. **Leveling and Experience System**
- **XP for Messages**: Users earn XP based on their activity in the server.
- **Rank Roles**: Unlock new roles or titles as users level up.
- **Leaderboards**: Display top users for XP, messages, or currency.
- **Achievements**: Users earn badges or points for reaching milestones.
### 5. **Custom Commands**
- **Random Meme or Joke**: Pull memes or jokes from popular APIs.
- **Anime and Movie Information**: Pull information from APIs like MyAnimeList or IMDb.
- **Image Manipulation**: Create fun images, like a users avatar in various frames or situations.
- **Quote Generator**: Save memorable messages and recall them with a command.
- **Insults / Compliments**: Light-hearted fun with random funny (and friendly) insults or compliments.
### 6. **Music and Sound Effects**
- **Music Queueing and Playback**: Allow users to play music in voice channels.
- **Soundboard**: Play sound effects when commands are triggered (like applause or laughter).
- **DJ Commands**: Give advanced controls like bass boost, speed change, or filters.
### 7. **Notifications and Alerts**
- **Birthday Reminders**: Keep track of user birthdays and announce them in the server.
- **Reminders and Timers**: Users can set personal reminders that DM them later.
- **Daily Rewards**: Encourage users to come back daily for bonuses in your economy system.
- **Event Announcements**: Set up periodic reminders for server events.
### 8. **Mini Competitions and Challenges**
- **Daily / Weekly Challenges**: Daily puzzles, word games, or math problems.
- **Polls and Voting**: Let users vote on topics, and maybe even earn rewards for participating.
- **Custom Leaderboards**: Create leaderboards for almost anything—XP, currency, reputation points, etc.
- **Scavenger Hunts**: Have clues hidden in messages or require users to find hidden “items” around the server.
### 9. **Image and GIF Fun**
- **Avatar Fusion**: Merge two users avatars to create a funny, blended image.
- **Random Reaction GIFs**: Send GIFs for certain triggers or reactions, like “!slap” or “!hug.”
- **Custom Frames**: Place avatars inside funny or themed frames (like a “wanted” poster).
- **Color-Changing Roles**: Allow users to purchase role colors and dynamically update them.
### 10. **Learning and Educational Features**
- **Word of the Day**: Share new vocabulary daily.
- **Facts and Trivia**: Random facts or trivia about different topics like science or history.
- **Language Translation**: Translate text to other languages on request.
- **Quiz Mode**: Let users compete in quick quiz rounds.
### 11. **Community Interaction and Moderation**
- **Anonymous Confessions**: Allow users to send anonymous messages to a specific channel.
- **Polls and Feedback**: Use polls to gather community feedback.
- **Reaction Roles**: Set up roles that users can self-assign by reacting to messages.
- **Moderation Levels**: Give moderators points or levels for active contributions to the server.
### 12. **Miscellaneous Fun**
- **Fortune Telling / Magic 8-Ball**: Answer user questions with fun responses.
- **Horoscope**: Provide daily horoscopes based on the users zodiac sign.
- **Fake Virus Prank**: A harmless "fake virus" message that freaks out the user momentarily.
- **Custom Emote Creator**: Generate small emotes using user avatars or custom text.
- **Virtual Marketplace**: Allow users to set up small shops and sell items to each other.
### 13. **Interactive Roleplay Features**
- **Adventures and Storylines**: Create episodic adventures with options that affect the story.
- **Character Sheets**: Track character stats for users, which they can upgrade over time.
- **Combat System**: PvP or PvE combat system where users can fight with each other or NPCs.
- **Collectibles**: Users can collect items, weapons, pets, etc., and trade them.
### 14. **Seasonal Events and Rewards**
- **Seasonal Currency or Items**: Introduce limited-time items for holidays.
- **Server-Wide Events**: Organize events like scavenger hunts, trivia contests, or tournaments.
- **Exclusive Roles**: Give out special roles for participation in events.
+251
View File
@@ -0,0 +1,251 @@
import discord
from discord.ext import commands
from PIL import Image, ImageDraw, ImageFont, ImageOps
import io
import os
from dotenv import load_dotenv
import requests
from random import randint
# Bot setup
load_dotenv()
TOKEN = os.getenv("TOKEN")
intents = discord.Intents.all()
bot = commands.Bot(command_prefix="!?", intents=intents)
# Simple user data structure
user_data = {}
def get_user_data(user_id):
"""Get or initialize user data."""
if user_id not in user_data:
user_data[user_id] = {"xp": 0, "level": 53}
return user_data[user_id]
import io
from PIL import Image, ImageDraw, ImageFont
async def create_profile_card(user, background_image_path):
"""
Create a profile card image for a user with a custom background and text overlay.
Args:
user (discord.Member): The user whose profile card is being generated.
background_image_path (str): Path to the background image.
Returns:
io.BytesIO: A BytesIO object containing the profile card image.
"""
# Profile card dimensions
card_width, card_height = 400, 200
text_color = (255, 255, 255)
progress_bar_color = (0, 255, 0)
outline_color = (255, 255, 255)
tint_color = (50, 50, 50)
transparency = 25
opacity = int(255 * transparency / 100)
# Load and resize the background image
background_image = Image.open(background_image_path).resize((card_width, card_height))
# Create a new image with the background
card_image = Image.new("RGBA", (card_width, card_height))
card_image.paste(background_image, (0, 0))
# Create a semi-transparent overlay
overlay = Image.new("RGBA", card_image.size, tint_color + (opacity,))
card_image = Image.alpha_composite(card_image, overlay)
draw = ImageDraw.Draw(card_image)
# Load fonts
font_path = "arial.ttf"
font = ImageFont.truetype(font_path, 20)
# Draw user info
draw.text((10, 10), f"User: {user.display_name}", fill=text_color, font=font)
user_info = get_user_data(user.id)
draw.text((10, 40), f"Level: {user_info['level']}", fill=text_color, font=font)
draw.text((10, 70), f"XP: {user_info['xp']}/{user_info['level'] * 100}", fill=text_color, font=font)
# Draw the progress bar
max_xp = user_info['level'] * 100
progress_ratio = user_info['xp'] / max_xp if max_xp > 0 else 0
progress_width = int(progress_ratio * 200)
draw.rectangle([(10, 100), (210, 120)], outline=outline_color, width=2)
draw.rectangle([(10, 100), (10 + progress_width, 120)], fill=progress_bar_color)
# Draw the user's profile picture
try:
profile_size = 96
margin = 30
profile_picture_data = await user.avatar.read()
profile_image = Image.open(io.BytesIO(profile_picture_data)).resize((profile_size, profile_size))
card_image.paste(profile_image, (card_width - profile_size - margin, margin), profile_image.convert("RGBA"))
except Exception as error:
print(f"Error fetching profile picture: {error}")
# Save image to a BytesIO object
image_bytes = io.BytesIO()
card_image.save(image_bytes, format="PNG")
image_bytes.seek(0)
return image_bytes
async def create_balance_card(user, credits, tokens, bucks):
# Card dimensions
width, height = 450, 250
card = Image.new("RGB", (width, height), (255, 255, 255))
draw = ImageDraw.Draw(card)
# Gradient Background
gradient_color_1 = (38, 0, 77) # Dark purple
gradient_color_2 = (128, 0, 128) # Purple
for y in range(height):
blend = y / height
r = int(gradient_color_1[0] * (1 - blend) + gradient_color_2[0] * blend)
g = int(gradient_color_1[1] * (1 - blend) + gradient_color_2[1] * blend)
b = int(gradient_color_1[2] * (1 - blend) + gradient_color_2[2] * blend)
draw.line([(0, y), (width, y)], fill=(r, g, b))
# Avatar with circular border
avatar_asset = user.avatar
avatar_response = requests.get(avatar_asset)
avatar = Image.open(io.BytesIO(avatar_response.content)).resize((60, 60))
mask = Image.new("L", avatar.size, 0)
ImageDraw.Draw(mask).ellipse((0, 0, 60, 60), fill=255)
avatar = ImageOps.fit(avatar, mask.size, centering=(0.5, 0.5))
avatar.putalpha(mask)
# Draw avatar circle background
avatar_bg = Image.new("RGBA", (70, 70), (255, 255, 255, 0))
draw_bg = ImageDraw.Draw(avatar_bg)
draw_bg.ellipse((0, 0, 70, 70), fill=(255, 255, 255, 100)) # Border color
card.paste(avatar_bg, (20, height - 110), avatar_bg)
card.paste(avatar, (25, height - 105), avatar)
# Fonts
title_font = ImageFont.load_default()
balance_font = ImageFont.load_default()
# Header Text
draw.text((110, 20), "tatsu.", font=title_font, fill="white")
draw.text((width - 90, 25), "Member", font=title_font, fill="white")
# Balance Background Box
balance_box = (100, 70, width - 20, 200)
draw.rounded_rectangle(balance_box, radius=15, fill=(255, 255, 255, 50))
# Balance Items
icon_x = 120
text_x = icon_x + 35
# Credits
draw.ellipse((icon_x, 90, icon_x + 25, 115), fill=(255, 215, 0)) # Coin icon color
draw.text((text_x, 90), f"{credits:,} Credits", font=balance_font, fill="black")
# Tokens
draw.ellipse(
(icon_x, 125, icon_x + 25, 150), fill=(0, 255, 127)
) # Token icon color
draw.text((text_x, 125), f"{tokens:,} Tokens", font=balance_font, fill="black")
# Bucks
draw.ellipse(
(icon_x, 160, icon_x + 25, 185), fill=(50, 205, 50)
) # Bucks icon color
draw.text((text_x, 160), f"{bucks:,} Guild Bucks", font=balance_font, fill="black")
# User tag
user_tag = f"{user.name}#{user.discriminator}"
draw.text((110, height - 35), user_tag, font=title_font, fill="white")
# Add Chip (Credit Card Style)
chip_width, chip_height = 50, 35
chip_x = width - 80
chip_y = 100
draw.rectangle(
[chip_x, chip_y, chip_x + chip_width, chip_y + chip_height],
fill=(192, 192, 192), # Light gray color for the chip
outline="white",
width=2,
)
# Chip lines for texture
line_spacing = 7
for i in range(1, chip_height // line_spacing):
y = chip_y + i * line_spacing
draw.line(
[(chip_x + 5, y), (chip_x + chip_width - 5, y)], fill="white", width=1
)
# Save to a BytesIO object to send on Discord
with io.BytesIO() as image_binary:
card.save(image_binary, "PNG")
image_binary.seek(0)
return discord.File(fp=image_binary, filename="balance_card.png")
@bot.event
async def on_ready():
if bot.user is not None:
print(f"Logged in as {bot.user.name}")
else:
print(f"Logged in")
@bot.command(name="profile")
async def profile(ctx):
"""Command to display user profile."""
user = ctx.author
user_info = get_user_data(user.id)
# Create profile card image
profile_image = create_profile_card(user, f"images/image_{randint(1,100)}.jpg")
# Send the image in the channel
await ctx.send(file=discord.File(await profile_image, "profile.png"))
@bot.command()
async def balance(ctx):
user = ctx.author
# Example balance data; replace with actual data retrieval
credits = 9493006
tokens = 58
bucks = 234328
file = await create_balance_card(user, credits, tokens, bucks)
await ctx.send(file=file)
# Example command to add XP for testing
@bot.command(name="addxp")
async def add_xp(ctx, amount: int):
"""Command to add XP for testing."""
user_info = get_user_data(ctx.author.id)
user_info["xp"] += amount
# Level up logic (example: every 100 XP earns a level)
if user_info["xp"] >= user_info["level"] * 100:
user_info["level"] += 1
user_info["xp"] = 0 # Reset XP after leveling up
await ctx.send(
f"Added {amount} XP to {ctx.author.mention}. Total XP: {user_info['xp']} | Level: {user_info['level']}"
)
if TOKEN is not None:
bot.run(TOKEN)
else:
print(f"{TOKEN=}")
+62
View File
@@ -0,0 +1,62 @@
import random
class User:
def __init__(self, xp_formula, level_formula):
self.level = 1
self.xp = 0
self.xp_to_next_level = xp_formula(
self.level
) # XP needed to reach the next level
self.xp_formula = xp_formula
self.level_formula = level_formula
def earn_xp(self, amount):
self.xp += amount
print(f"You earned {amount} XP! Total XP: {self.xp}")
# Check for level up
while self.xp >= self.xp_to_next_level:
self.level_up()
def level_up(self):
self.xp -= self.xp_to_next_level
self.level += 1
self.xp_to_next_level = self.xp_formula(
self.level
) # Calculate next level requirement
print(
f"Congratulations! You've reached Level {self.level}! XP to next level: {self.xp_to_next_level}"
)
def simulate_xp_gain(user, num_messages, xp_per_message):
for _ in range(num_messages):
user.earn_xp(xp_per_message())
def main():
# Define your formulas here
def xp_formula(level):
return int(100 * (1.5 ** (level - 1))) # Example: 100, 150, 225, 338, ...
def random_xp_per_message():
return random.randint(1, 5) # Random XP between 1 and 10
user = User(xp_formula, random_xp_per_message)
while True:
num_messages = input("Enter the number of messages (or 'quit' to exit): ")
if num_messages.lower() == "quit":
break
if num_messages.isdigit():
num_messages = int(num_messages)
simulate_xp_gain(user, num_messages, random_xp_per_message)
else:
print("Please enter a valid number.")
if __name__ == "__main__":
main()
+36
View File
@@ -0,0 +1,36 @@
import requests
import os
from PIL import Image
from io import BytesIO
from random import shuffle
# Step 1: Fetch JSON data from the URL
url = "https://picsum.photos/v2/list?limit=1000"
response = requests.get(url)
data = response.json()
# Step 2: Extract image download links
image_urls = [item["download_url"] for item in data]
shuffle(image_urls)
# Step 3: Create a directory to save images
os.makedirs("images", exist_ok=True)
# Desired size for resizing
desired_size = (450, 250)
# Step 4: Download and resize each image
for i, image_url in enumerate(image_urls):
img_response = requests.get(image_url)
if img_response.status_code == 200:
# Open image from response content
img = Image.open(BytesIO(img_response.content))
# Resize the image
img = img.resize(desired_size, Image.Resampling.LANCZOS)
# Save the resized image
img.save(f"images/image_{17}.jpg")
print(f"Downloaded and resized: image_{17}.jpg")
else:
print(f"Failed to download image from {image_url}")
if input() !="":
break
+30
View File
@@ -0,0 +1,30 @@
from minimax import aiO, aiX, Terminal, Value
# from player import player
def display_board(board):
print('-------------')
for row in [board[i:i + 3] for i in range(0, 9, 3)]:
print(f'| {row[0]} | {row[1]} | {row[2]} |')
print('-------------')
def main():
results = []
while True:
board = [" " for _ in range(9)]
# board = ['O', 'X', 'O','O', 'X', 'X',' ', ' ', 'X']
display_board(board)
while True:
board[aiX(board)] = "X"
display_board(board)
if Terminal(board):
break
board[aiO(board)] = "O"
display_board(board)
if Terminal(board):
break
results.append(Value(board))
if __name__ == '__main__':
main()
+93
View File
@@ -0,0 +1,93 @@
import random
win_states = [
(0, 1, 2), (3, 4, 5), (6, 7, 8), # horizontal
(0, 3, 6), (1, 4, 7), (2, 5, 8), # vertical
(0, 4, 8), (2, 4, 6) # diagonal
]
def Terminal(state: list):
for win_state in win_states:
if all(state[i] == "O" for i in win_state) or all(state[i] == "X" for i in win_state):
return True
if state.count(" ") == 0:
return True
else:
return False
def Value(state):
for win_state in win_states:
if all(state[i] == "O" for i in win_state):
return -1
elif all(state[i] == "X" for i in win_state):
return 1
if state.count(" ") == 0:
return 0
def Player(state: list):
if state.count("X") == state.count("O"):
return "X"
else:
return "O"
def Actions(state):
indexes = []
for i in range(9):
if state[i] == " ":
indexes.append(i)
return indexes
def Result(state, index, player):
state[index] = player
return state
def Minimax(state):
if Terminal(state):
return Value(state)
if Player(state) == "X":
value = -9999
for a in Actions(state):
value = max(value, Minimax(Result(state.copy(), a, "X")))
return value
elif Player(state) == "O":
value = 9999
for a in Actions(state):
value = min(value, Minimax(Result(state.copy(), a, "O")))
return value
def aiO(state):
best_score = float('inf')
best_move = None
for move in Actions(state):
state[move] = 'O'
score = Minimax(state)
state[move] = ' '
if score < best_score:
best_score = score
best_move = move
return best_move
def aiX(state):
best_score = float('-inf')
best_move = None
for move in Actions(state):
state[move] = 'X'
score = Minimax(state)
state[move] = ' '
if score > best_score:
best_score = score
best_move = move
return best_move
def aiRandom(state):
return random.choice(Actions(state))
+10
View File
@@ -0,0 +1,10 @@
from minimax import Actions
def player(state):
actions = Actions(state)
choice = -1
while choice not in actions:
choice = int(input("Pick a spot between 1-9.> ")) - 1
return choice
+68
View File
@@ -0,0 +1,68 @@
from random import randint
"""def player(start_board):
player_choice = input("Pick a place: ")
if start_board.count(player_choice) == 1:
start_board[start_board.index(player_choice)] = "X"
else:
player(start_board)"""
def player(start_board):
com_choice = str(randint(1, 9))
if start_board.count(com_choice) == 1:
start_board[start_board.index(com_choice)] = "X"
else:
player(start_board)
def com(start_board):
com_choice = str(randint(1, 9))
if start_board.count(com_choice) == 1:
start_board[start_board.index(com_choice)] = "O"
else:
com(start_board)
def print_board(s_board):
board = "".join(s_board)
print(board)
def check(s_b):
if s_b[0] == s_b[2] == s_b[4] == 'X' or s_b[6] == s_b[8] == s_b[10] == 'X' or s_b[12] == s_b[14] == s_b[16] == 'X' \
or s_b[0] == s_b[6] == s_b[12] == 'X' or s_b[2] == s_b[8] == s_b[14] == 'X' or s_b[4] == s_b[10] == s_b[16] \
== 'X' or s_b[0] == s_b[8] == s_b[16] == 'X' or s_b[4] == s_b[8] == s_b[12] == 'X':
print("you won")
main()
elif s_b[0] == s_b[2] == s_b[4] == 'O' or s_b[6] == s_b[8] == s_b[10] == 'O' or s_b[12] == s_b[14] == s_b[16] == 'O' \
or s_b[0] == s_b[6] == s_b[12] == 'O' or s_b[2] == s_b[8] == s_b[14] == 'O' or s_b[4] == s_b[10] == s_b[16] \
== 'O' or s_b[0] == s_b[8] == s_b[16] == 'O' or s_b[4] == s_b[8] == s_b[12] == 'O':
print('computer won')
print_board(s_b)
main()
def main():
# main program
start_board = ["1", "|", "2", "|", "3", "\n",
"4", "|", "5", "|", "6", "\n",
"7", "|", "8", "|", "9", "\n", ]
print_board(start_board)
while 1:
try:
player(start_board)
check(start_board)
com(start_board)
check(start_board)
print_board(start_board)
except RecursionError:
print_board(start_board)
print("draw")
main()
if __name__ == "__main__":
main()
Executable
+147
View File
@@ -0,0 +1,147 @@
# Discord Bot Project
## Overview
This is a versatile Discord bot project, built using `discord.py`. The bot includes:
- **Admin Commands** for moderating servers
- **Economy System** to manage virtual currency
- **Fun Commands** for interactive experiences
- **Role Management** to assign roles based on user actions or preferences
The bot is structured with scalability in mind, using cogs to separate different functionalities and utilities for common operations.
---
## Features
- **Admin Commands**: Moderation tools such as banning and kicking members.
- **Economy System**: Tracks user balances and allows users to earn or spend virtual currency.
- **Fun Commands**: Simple commands that provide entertainment, such as games or random interactions.
- **Role Management**: Commands that automate role assignments for users.
---
## Setup
### 1. Clone the Repository
Clone the repository to your local machine:
```bash
git clone https://github.com/yourusername/discord-bot.git
cd discord-bot
```
### 2. Install Requirements
Install the necessary Python packages listed in `requirements.txt`:
```bash
pip install -r requirements.txt
```
### 3. Set Up Environment Variables
The bot requires environment variables for configuration. Create a `.env` file in the root directory and add your Discord bot token:
```plaintext
DISCORD_TOKEN=your_token_here
```
### 4. Run the Bot
Run the bot using the command below:
```bash
python bot.py
```
---
## Folder Structure
Heres an outline of the directory and file structure for this project:
```
discord-bot/
├── bot.py # Main bot file for initializing the bot and loading cogs
├── requirements.txt # List of required Python packages
├── .env # Environment file containing sensitive data (e.g., bot token)
├── cogs/ # Folder containing individual cogs for separate functionalities
│ ├── admin.py # Admin commands such as ban, kick, etc.
│ ├── economy.py # Economy-related commands
│ ├── fun.py # Fun commands for user engagement
│ └── roles.py # Commands for managing server roles
├── utils/ # Folder containing utility files for common functionality
│ ├── bank_functions.py # Economy and balance management functions
│ └── sql_Commands.py # Database helper functions for handling SQL commands
├── extras/ # Experimental or additional features (optional)
│ └── example.py
└── docs/ # Documentation files
└── CheckList.txt # Planning and improvement list (optional)
```
---
## Usage
### Commands
#### 1. **Admin Commands**
- `!ban <member> <reason>`: Bans a specified member from the server with an optional reason.
- `!kick <member> <reason>`: Kicks a specified member from the server with an optional reason.
#### 2. **Economy Commands**
- `!balance`: Check your current balance.
- `!deposit <amount>`: Deposit a specified amount to your bank.
- `!withdraw <amount>`: Withdraw a specified amount from your bank.
#### 3. **Fun Commands**
- `!roll <number>`: Rolls a random number up to the specified max.
- `!8ball <question>`: Provides a magic 8-ball response to your question.
#### 4. **Role Management Commands**
- `!assignrole <role>`: Assigns a role to a user.
- `!removerole <role>`: Removes a role from a user.
### Notes
- All commands can be customized by modifying their respective cog files in `cogs/`.
- Admin commands require appropriate permissions (e.g., ban and kick require `Ban Members` or `Kick Members` permissions).
---
## Configuration
### Permissions
Make sure your bot has the appropriate permissions for each command. You can manage permissions in the [Discord Developer Portal](https://discord.com/developers/applications), where you created your bot. Ensure that:
- The bot has `Administrator` permissions if needed.
- `Intents` such as `Message Content`, `Guild Presences`, and `Guild Members` are enabled as required by the bot.
### Environment Variables
Store sensitive information like your bot token in the `.env` file. Avoid committing this file to version control.
### Database
The bot currently uses SQLite for user and balance data. You can update `sql_Commands.py` and `bank_functions.py` in the `utils` folder to manage other data or switch to another database if necessary.
---
## Contributing
Contributions are welcome! Heres how you can contribute:
1. Fork the project.
2. Create a feature branch (`git checkout -b feature/AmazingFeature`).
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
4. Push to the branch (`git push origin feature/AmazingFeature`).
5. Open a pull request.
Make sure to include docstrings for any new functions and comments for complex logic to maintain readability.
---
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
---
## Contact
For support, please contact [yourname@example.com](mailto:yourname@example.com).
Happy Coding!
+324
View File
@@ -0,0 +1,324 @@
# 300+ Discord Bot Achievement Ideas
## Messaging & Chat
1. First Message
2. Chatterbox (100 messages)
3. Conversationalist (1,000 messages)
4. Social Butterfly (5,000 messages)
5. Message Machine (10,000 messages)
6. Night Owl (message between 2AM-4AM)
7. Early Bird (message between 5AM-7AM)
8. Reply Master (50 replies)
9. Longest Streak (30 days active)
10. Daily Messenger (message every day for a week)
11. Weekly Messenger (message every day for a month)
12. Monthly Messenger (message every day for a year)
13. Emoji Enthusiast (send 100 emojis)
14. Sticker Star (send 50 stickers)
15. GIF Guru (send 25 GIFs)
16. Pinned It! (have a message pinned)
17. Quoter (use the quote feature 10 times)
18. Thread Starter (start 10 threads)
19. Thread Finisher (close 10 threads)
20. Tagger (mention 50 users)
21. All Caps (send a message in all caps)
22. Whisperer (send a DM via bot)
23. Pollster (create 5 polls)
24. Voter (vote in 10 polls)
25. Questioner (ask 10 questions)
26. Answerer (answer 10 questions)
27. Linker (share 20 links)
28. Attachment Addict (send 50 attachments)
29. Image Uploader (send 25 images)
30. File Sharer (send 10 files)
31. Reaction Reactor (react to 100 messages)
32. Reaction Collector (get 100 reactions)
33. Reaction Leader (get 10 reactions on one message)
34. Reaction Follower (react to 10 different users)
35. Welcome Wagon (welcome 10 new members)
36. Farewell Friend (say goodbye to 10 members)
37. Birthday Wisher (wish 5 users happy birthday)
38. Complimenter (compliment 10 users)
39. Apologizer (apologize 5 times)
40. Peacemaker (resolve 5 arguments)
41. Helper (answer 10 help requests)
42. Supporter (give positive feedback 10 times)
43. Encourager (encourage 10 users)
44. Motivator (send 10 motivational quotes)
45. Storyteller (tell 5 stories)
46. Joke Teller (tell 10 jokes)
47. Meme Master (share 20 memes)
48. Punny Person (make 10 puns)
49. Riddle Me This (ask 5 riddles)
50. Riddle Solver (solve 5 riddles)
## Economy & Currency
51. First Coins
52. Big Spender (spend 10,000 coins)
53. Millionaire (1,000,000 coins)
54. Billionaire (1,000,000,000 coins)
55. Lucky Winner (win a jackpot)
56. Investor (earn interest 10 times)
57. Saver (save 100,000 coins)
58. Gambler (play 50 games of chance)
59. High Roller (bet 10,000 coins at once)
60. Penny Pincher (never spend coins for a month)
61. Donator (donate 10,000 coins)
62. Philanthropist (donate 100,000 coins)
63. Beggar (ask for coins 10 times)
64. Loan Shark (lend coins 10 times)
65. Debt Free (pay off a loan)
66. Shopaholic (buy 50 items)
67. Collector (own 100 items)
68. Hoarder (own 500 items)
69. Trader (trade with 10 users)
70. Bargain Hunter (buy an item at a discount)
71. Auction Winner (win 5 auctions)
72. Auctioneer (host 5 auctions)
73. Market Maker (list 10 items for sale)
74. Coupon Clipper (use 10 coupons)
75. Mystery Box Opener (open 10 boxes)
76. Daily Claimer (claim daily reward 30 times)
77. Weekly Claimer (claim weekly reward 10 times)
78. Monthly Claimer (claim monthly reward 3 times)
79. Streak Saver (maintain a 30-day claim streak)
80. Jackpot Hunter (win 3 jackpots)
81. Lucky Streak (win 5 games in a row)
82. Unlucky (lose 10 games in a row)
83. Risk Taker (bet all coins at once)
84. Safe Player (never lose a bet for a week)
85. Coupon Collector (collect 20 coupons)
## Level & Role
86. Level Up! (reach level 5)
87. On the Rise (level 25)
88. Top Dog (highest level)
89. Role Model (special role)
90. Collector (10 roles)
91. Role Hoarder (20 roles)
92. Role Changer (change roles 10 times)
93. Role Giver (assign roles to 10 users)
94. Role Remover (remove roles from 10 users)
95. Role Creator (create 5 roles)
96. Role Destroyer (delete 5 roles)
97. Role Swapper (swap roles with another user)
98. Role Trader (trade roles 5 times)
99. Role Upgrader (upgrade a role)
100. Role Downgrader (downgrade a role)
101. Role Customizer (custom role)
102. Role Requester (request a role 10 times)
103. Role Approver (approve 10 role requests)
104. Role Denier (deny 10 role requests)
105. Role Voter (vote on 10 role polls)
## Event & Participation
106. Event Goer (join an event)
107. Champion (win an event)
108. Runner Up (second place in event)
109. Third Place (third place in event)
110. Event Host (host 5 events)
111. Event Helper (help run 5 events)
112. Event Organizer (organize 3 events)
113. Event Planner (plan 5 events)
114. Event Promoter (promote 5 events)
115. Event Participant (participate in 10 events)
116. Event Veteran (participate in 50 events)
117. Event Newbie (first event)
118. Event Enthusiast (attend every event in a month)
119. Event Collector (collect 10 event badges)
120. Event Winner (win 5 events)
121. Event Loser (lose 5 events)
122. Event Streak (attend 5 events in a row)
123. Event Breaker (miss an event after a streak)
124. Event Feedback (give feedback on 5 events)
125. Event Reviewer (review 5 events)
126. Event Photographer (share 10 event photos)
127. Event Streamer (stream 5 events)
128. Event DJ (play music at 5 events)
129. Event Speaker (speak at 5 events)
130. Event Listener (listen at 10 events)
131. Event Volunteer (volunteer at 5 events)
132. Event Sponsor (sponsor 3 events)
133. Event Donator (donate to 5 events)
134. Event Supporter (support 10 events)
135. Event Announcer (announce 5 events)
## Mini-Games
136. Game On! (play first mini-game)
137. Trivia Buff (win 10 trivia games)
138. Puzzle Solver (complete 5 puzzles)
139. Word Wizard (win a word game)
140. Streak Winner (win 3 games in a row)
141. Game Master (win 50 games)
142. Game Loser (lose 50 games)
143. Game Collector (play 10 different games)
144. Game Explorer (try every game)
145. Game Reviewer (review 5 games)
146. Game Creator (create a custom game)
147. Game Tester (test a new game)
148. Game Helper (help 5 users in games)
149. Game Challenger (challenge 10 users)
150. Game Accept (accept 10 challenges)
151. Game Decline (decline 10 challenges)
152. Game Spectator (watch 10 games)
153. Game Commentator (comment on 10 games)
154. Game Streamer (stream 5 games)
155. Game Sharer (share 10 game results)
156. Game Collector (collect 10 game badges)
157. Game Winner (win 100 games)
158. Game Loser (lose 100 games)
159. Game Streak (win 10 games in a row)
160. Game Breaker (lose after a streak)
161. Game Lucky (win with luck)
162. Game Unlucky (lose with bad luck)
163. Game Strategist (win with strategy)
164. Game Random (win with random move)
165. Game Fast (win in record time)
166. Game Slow (win in slowest time)
167. Game Comeback (win after losing)
168. Game Sweep (win without losing a round)
169. Game Draw (draw 5 games)
170. Game Rematch (rematch 5 times)
## Social & Community
171. Friendly Face (add 5 friends)
172. Team Player (join a team)
173. Gift Giver (send a gift)
174. Gift Receiver (receive a gift)
175. Gift Collector (collect 10 gifts)
176. Gift Hoarder (collect 50 gifts)
177. Gift Trader (trade gifts 5 times)
178. Gift Opener (open 10 gifts)
179. Gift Wrapper (wrap 10 gifts)
180. Gift Sender (send 10 gifts)
181. Gift Receiver (receive 10 gifts)
182. Gift Sharer (share a gift)
183. Gift Hider (hide a gift)
184. Gift Finder (find a hidden gift)
185. Gift Seeker (seek 10 gifts)
186. Gift Hunter (hunt 10 gifts)
187. Gift Giver (give 50 gifts)
188. Gift Receiver (receive 50 gifts)
189. Gift Collector (collect 100 gifts)
190. Gift Hoarder (collect 200 gifts)
191. Gift Trader (trade gifts 10 times)
192. Gift Opener (open 50 gifts)
193. Gift Wrapper (wrap 50 gifts)
194. Gift Sender (send 50 gifts)
195. Gift Receiver (receive 50 gifts)
196. Gift Sharer (share 10 gifts)
197. Gift Hider (hide 10 gifts)
198. Gift Finder (find 10 hidden gifts)
199. Gift Seeker (seek 50 gifts)
200. Gift Hunter (hunt 50 gifts)
201. Gift Giver (give 100 gifts)
202. Gift Receiver (receive 100 gifts)
203. Gift Collector (collect 200 gifts)
204. Gift Hoarder (collect 500 gifts)
205. Gift Trader (trade gifts 20 times)
## Activity & AFK
206. Active Member (7 days active)
207. AFK Champion (set AFK 10 times)
208. Comeback Kid (return from AFK after 24h)
209. Streak Saver (30 days active)
210. Night Watch (active at night 10 times)
211. Day Watch (active during day 10 times)
212. Weekend Warrior (active every weekend for a month)
213. Weekday Worker (active every weekday for a month)
214. Holiday Hero (active on holidays)
215. Vacationer (inactive for a week)
216. Returner (return after a break)
217. Consistent (active every day for 100 days)
218. Marathoner (active for 12 hours straight)
219. Sprinter (active for 1 hour straight)
220. Lurker (online but silent for 24h)
221. Ghost (online but never messages)
222. Comeback Streak (return and stay active for a week)
223. AFK Streak (set AFK every day for a week)
224. AFK Breaker (break AFK streak)
225. AFK Master (set AFK 50 times)
## Custom & Secret
226. Easter Egg (find a hidden command)
227. Bug Finder (report a bug)
228. Beta Tester (join beta)
229. Secret Combo (use secret command)
230. First! (first to use new feature)
231. Hidden Helper (help in secret channel)
232. Mystery Solver (solve a mystery)
233. Code Breaker (break a code)
234. Secret Keeper (keep a secret)
235. Secret Sharer (share a secret)
236. Secret Finder (find 5 secrets)
237. Secret Collector (collect 10 secrets)
238. Secret Hunter (hunt 10 secrets)
239. Secret Giver (give a secret)
240. Secret Receiver (receive a secret)
241. Secret Trader (trade secrets)
242. Secret Opener (open a secret)
243. Secret Wrapper (wrap a secret)
244. Secret Sender (send a secret)
245. Secret Receiver (receive a secret)
246. Secret Sharer (share a secret)
247. Secret Hider (hide a secret)
248. Secret Finder (find a hidden secret)
249. Secret Seeker (seek 10 secrets)
250. Secret Hunter (hunt 50 secrets)
## Miscellaneous
251. Customizer (change profile)
252. Pollster (vote in 10 polls)
253. Photographer (upload 10 images)
254. Streamer (share screen)
255. Voice Veteran (10 hours in voice)
256. DJ (queue 50 songs)
257. Music Lover (listen to 100 songs)
258. Music Critic (rate 10 songs)
259. Music Sharer (share 10 songs)
260. Music Collector (collect 50 songs)
261. Music Hoarder (collect 100 songs)
262. Music Trader (trade songs)
263. Music Opener (open 10 music files)
264. Music Wrapper (wrap 10 music files)
265. Music Sender (send 10 music files)
266. Music Receiver (receive 10 music files)
267. Music Sharer (share 10 music files)
268. Music Hider (hide 10 music files)
269. Music Finder (find 10 music files)
270. Music Seeker (seek 10 music files)
271. Music Hunter (hunt 10 music files)
272. Music Giver (give 10 music files)
273. Music Receiver (receive 10 music files)
274. Music Collector (collect 100 music files)
275. Music Hoarder (collect 200 music files)
276. Music Trader (trade music files)
277. Music Opener (open 50 music files)
278. Music Wrapper (wrap 50 music files)
279. Music Sender (send 50 music files)
280. Music Receiver (receive 50 music files)
281. Music Sharer (share 50 music files)
282. Music Hider (hide 50 music files)
283. Music Finder (find 50 music files)
284. Music Seeker (seek 50 music files)
285. Music Hunter (hunt 50 music files)
286. Music Giver (give 50 music files)
287. Music Receiver (receive 50 music files)
288. Music Collector (collect 200 music files)
289. Music Hoarder (collect 500 music files)
290. Music Trader (trade music files 20 times)
291. Music Opener (open 100 music files)
292. Music Wrapper (wrap 100 music files)
293. Music Sender (send 100 music files)
294. Music Receiver (receive 100 music files)
295. Music Sharer (share 100 music files)
296. Music Hider (hide 100 music files)
297. Music Finder (find 100 music files)
298. Music Seeker (seek 100 music files)
299. Music Hunter (hunt 100 music files)
300. Music Giver (give 100 music files)
301. Music Receiver (receive 100 music files)
---
**Tip:** You can further expand these by combining categories, adding seasonal/event-based, or server-specific achievements!
Executable
+56
View File
@@ -0,0 +1,56 @@
# main.py
import discord
from discord.ext import commands
import os
from dotenv import load_dotenv
from web.app import app
import threading
def run_web():
app.run(debug=False, host="0.0.0.0", port=5000)
class MyNewHelp(commands.MinimalHelpCommand):
async def send_pages(self):
destination = self.get_destination()
for page in self.paginator.pages:
emby = discord.Embed(description=page)
await destination.send(embed=emby)
class Client(commands.Bot):
def __init__(self):
super().__init__(
command_prefix=["pY ", "PY ", "Py ", "py "],
strip_after_prefix=True,
case_insensitive=True,
intents=discord.Intents.all(),
help_command=MyNewHelp(),
)
async def setup_hook(self): # overwriting a handler
cogs_folder = f"{os.path.abspath(os.path.dirname(__file__))}/cogs"
for filename in os.listdir(cogs_folder):
if filename.endswith(".py"):
try:
await self.load_extension(f"cogs.{filename[:-3]}")
except Exception as e:
print(f"Failed to load {filename}: {e}")
await self.tree.sync()
print("Loaded cogs")
def main():
load_dotenv()
client = Client()
token = os.getenv("TOKEN")
if token is not None:
threading.Thread(target=run_web, daemon=True).start()
client.run(token)
else:
print("Token is missing.")
if __name__ == "__main__":
main()
+71
View File
@@ -0,0 +1,71 @@
# main.py
import discord
from discord.ext import commands
import os
from dotenv import load_dotenv
from web.app import app
import threading
def run_web():
app.run(debug=False, host="0.0.0.0", port=5000)
class MyNewHelp(commands.MinimalHelpCommand):
async def send_pages(self):
destination = self.get_destination()
for page in self.paginator.pages:
emby = discord.Embed(description=page)
await destination.send(embed=emby)
class Client(commands.Bot):
def __init__(self):
super().__init__(
command_prefix=["pY ", "PY ", "Py ", "py "],
strip_after_prefix=True,
case_insensitive=True,
intents=discord.Intents.all(),
help_command=MyNewHelp(),
)
# Set your development guild ID here (as an integer)
self.allowed_guild_id = 1381708059600097403 # PLEX
self.add_check(self.guild_check)
async def setup_hook(self): # overwriting a handler
cogs_folder = f"{os.path.abspath(os.path.dirname(__file__))}/implementing"
for filename in os.listdir(cogs_folder):
if filename.endswith(".py"):
try:
await self.load_extension(f"implementing.{filename[:-3]}")
except Exception as e:
print(f"Failed to load {filename}: {e}")
await self.tree.sync()
print("Loaded cogs")
async def on_message(self, message):
# Ignore messages from bots
if message.author.bot:
return
# Only respond in the allowed guild
if message.guild and message.guild.id == self.allowed_guild_id:
await self.process_commands(message)
# Optionally, ignore DMs or messages from other guilds
async def guild_check(self, ctx):
return ctx.guild and ctx.guild.id == self.allowed_guild_id
def main():
load_dotenv()
client = Client()
token = os.getenv("TOKEN")
if token is not None:
threading.Thread(target=run_web, daemon=True).start()
client.run(token)
else:
print("Token is missing.")
if __name__ == "__main__":
main()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
+224
View File
@@ -0,0 +1,224 @@
import datetime
import time
import os
import discord
from discord.ext import commands
async def reload_all(client, ctx):
"""
Reloads all cogs (modules) in the 'cogs' folder and syncs the command tree.
Args:
client (discord.Client): The bot's main client object.
ctx (commands.Context): The context in which the command was invoked.
"""
cogs_folder = os.path.abspath(os.path.dirname(__file__)) # Get the cogs folder path
for filename in os.listdir(cogs_folder):
if filename.endswith(".py"):
try:
await client.reload_extension(f"cogs.{filename[:-3]}")
except Exception as e:
print(f"Error reloading {filename}: {e}")
await client.load_extension(f"cogs.{filename[:-3]}")
await client.tree.sync() # Synchronize command tree
await ctx.reply("Reloaded all cogs.")
class Admin(commands.Cog):
"""Cog that provides administrative commands for bot control and information."""
def __init__(self, client):
self.client = client
@commands.command(
name="ping",
brief="Bot Latency",
description="Displays the bot's latency in milliseconds.",
)
async def _ping(self, ctx):
"""
Responds with the bot's latency (ping time).
Args:
ctx (commands.Context): The context in which the command was invoked.
"""
latency = round(self.client.latency * 1000, 2) # Convert to ms
await ctx.reply(f"Pong! 🏓\nLatency: {latency}ms")
@commands.command(
name="info",
brief="Bot Info",
description="Provides information about the bot's owner and team (if applicable).",
)
async def _info(self, ctx):
"""
Displays information about the bot's owner or team members.
Args:
ctx (commands.Context): The context in which the command was invoked.
"""
app_info = await self.client.application_info()
if app_info.team:
team_members = "\n".join(
f"{member.name}#{member.discriminator} ({'Owner' if member.id == app_info.team.owner_id else 'Member'})"
for member in app_info.team.members
)
await ctx.reply(
f"Bot is part of the team: {app_info.team.name}\nMembers:\n{team_members}"
)
else:
await ctx.reply(
f"Bot is owned by {app_info.owner} and is not part of a team."
)
@commands.command(
name="uptime",
brief="Bot Runtime",
description="Displays how long the bot has been running since its last restart.",
)
async def _uptime(self, ctx):
"""
Calculates and sends the bot's uptime based on a start timestamp.
Args:
ctx (commands.Context): The context in which the command was invoked.
"""
with open("time.txt", "r") as file:
start_time = float(file.read())
uptime = str(
datetime.timedelta(seconds=int(time.time() - start_time))
) # Calculate uptime
await ctx.send(f"Uptime: {uptime}")
@commands.hybrid_command(
name="reload",
brief="Reload Cog",
description="Reloads a specific cog or all cogs.",
aliases=["rl"],
)
@commands.is_owner()
async def _reload(self, ctx, extension: str):
"""
Reloads a specified cog/module or all modules if 'all' is passed.
Args:
ctx (commands.Context): The context in which the command was invoked.
extension (str): Name of the cog to reload, or 'all' to reload all cogs.
"""
if extension.lower() == "all":
await reload_all(self.client, ctx)
return
try:
await self.client.unload_extension(f"cogs.{extension}")
await self.client.load_extension(f"cogs.{extension}")
await ctx.send(f"Successfully reloaded {extension}.")
except commands.ExtensionNotLoaded:
await ctx.send(f"Extension {extension} was not loaded, loading it now.")
try:
await self.client.load_extension(f"cogs.{extension}")
await ctx.send(f"Successfully loaded {extension}.")
except Exception as e:
await ctx.send(f"Failed to load {extension}: {e}")
except commands.ExtensionNotFound:
await ctx.send(f"Extension {extension} was not found.")
except Exception as e:
await ctx.send(f"Failed to reload {extension}: {e}")
@commands.hybrid_command(
name="purge",
brief="Delete messages",
description="Deletes a specified number of messages from the current channel (default: 100).",
)
@commands.has_permissions(manage_channels=True)
@commands.cooldown(1, 10, commands.BucketType.user)
async def _purge(self, ctx, n: int = 100):
"""
Deletes a specified number of messages from the current channel.
Args:
ctx (commands.Context): The context in which the command was invoked.
n (int, optional): The number of messages to delete. Defaults to 100.
"""
n += 1
if n <= 0:
return
if n < 100:
await ctx.channel.purge(limit=n)
await ctx.channel.send(
f"Successfully purged last {n-1} messages", delete_after=2
)
elif n == 100:
await ctx.channel.purge()
await ctx.channel.send(
"Successfully purged last 100 messages", delete_after=2
)
else: # n > 100
for i in range(n // 100):
await ctx.channel.purge()
await ctx.channel.purge(limit=n % 100)
await ctx.channel.send(
f"Successfully purged last {n-1} messages", delete_after=2
)
@commands.hybrid_command(
name="nuke",
brief="Clear channel",
description="Deletes all messages by duplicating and deleting the channel.",
)
@commands.has_permissions(manage_channels=True)
@commands.cooldown(1, 30, commands.BucketType.user)
async def _nuke(self, ctx, channel_name: str = "Current"):
"""
Duplicates and deletes a specified channel (or the current channel) to clear messages.
Args:
ctx (commands.Context): The context in which the command was invoked.
channel_name (str, optional): Name of the channel to nuke. Defaults to 'Current' (the invoking channel).
"""
# Determine the channel ID based on the channel_name parameter
if channel_name.lower() == "current":
channel_id = ctx.channel.id
else:
channel_id = discord.utils.get(ctx.guild.channels, name=channel_name)
if channel_id is None:
return await ctx.send(f"Channel **{channel_name}** was not found.")
existing_channel = self.client.get_channel(channel_id)
if existing_channel:
await existing_channel.clone(reason="Channel has been nuked")
await existing_channel.delete()
await self._send_embed(
ctx,
f"Channel **{existing_channel.name}** has been nuked.",
discord.Color.red(),
)
else:
await ctx.send(f"No channel with ID {channel_id} was found.")
@commands.command(
name="guildids",
brief="List all guild IDs",
description="Lists all guild IDs the bot is currently in. Owner only.",
)
@commands.is_owner()
async def _guildids(self, ctx):
"""
Sends a list of all guild IDs the bot is in to the owner.
Args:
ctx (commands.Context): The context in which the command was invoked.
"""
guild_ids = [str(guild.id) for guild in self.client.guilds]
guilds_str = "\n".join(guild_ids)
await ctx.author.send(f"Guild IDs:\n{guilds_str}")
await ctx.reply("Sent you a DM with all guild IDs.")
async def _send_embed(self, ctx, description, color=discord.Color.blurple()):
embed = discord.Embed(description=description, color=color)
await ctx.send(embed=embed)
async def setup(client):
"""Sets up the Admin cog for the bot."""
await client.add_cog(Admin(client))
+224
View File
@@ -0,0 +1,224 @@
import discord, re
from discord.ext import commands
from utils.sql_commands import DatabaseManager
class CustomCommandsCog(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager("")
self.command_cache = {} # {guild_id: [commands_dicts]}
def get_guild_commands(self, guild_id):
if guild_id not in self.command_cache:
self.command_cache[guild_id] = self.db.fetch_all(
"SELECT * FROM custom_commands WHERE GUILDID = %s", (guild_id,)
)
return self.command_cache[guild_id]
def invalidate_cache(self, guild_id):
if guild_id in self.command_cache:
del self.command_cache[guild_id]
@commands.hybrid_command(name="addcommand")
@commands.has_permissions(manage_guild=True)
@commands.cooldown(1, 10, commands.BucketType.user)
async def add_command(self, ctx, command_name: str, *, response: str):
"""Add a new custom command"""
guild_id = str(ctx.guild.id)
command_name = command_name.lower()
existing_command = self.db.fetch_one(
"SELECT 1 FROM custom_commands WHERE GUILDID = %s AND COMMANDNAME = %s",
(guild_id, command_name),
)
if existing_command:
await ctx.send(f"A command with the name `{command_name}` already exists.")
return
try:
# Insert the new command into the database
self.db.execute_query(
"INSERT INTO custom_commands (GUILDID, COMMANDNAME, RESPONSE) VALUES (%s, %s, %s)",
(guild_id, command_name, response),
)
self.invalidate_cache(guild_id) # Invalidate cache after change
await ctx.send(f"Custom command `{command_name}` has been added!")
except Exception as e:
await ctx.send(f"Failed to add command `{command_name}`. Error: {e}")
@commands.hybrid_command(name="delcommand")
@commands.has_permissions(manage_guild=True)
@commands.cooldown(1, 10, commands.BucketType.user)
async def delete_command(self, ctx, command_name: str):
"""Delete a custom command"""
guild_id = str(ctx.guild.id)
command_name = command_name.lower()
try:
# Delete the command from the database
deleted_rows = self.db.execute_query(
"DELETE FROM custom_commands WHERE GUILDID = %s AND COMMANDNAME = %s",
(guild_id, command_name),
)
self.invalidate_cache(guild_id) # Invalidate cache after change
if deleted_rows is not None and len(deleted_rows) > 0:
await ctx.send(f"Custom command `{command_name}` has been deleted!")
else:
await ctx.send(f"Custom command `{command_name}` not found.")
except Exception as e:
await ctx.send(f"Failed to delete command `{command_name}`. Error: {e}")
@commands.hybrid_command(name="listcommands")
async def list_commands(self, ctx):
"""List all custom commands for this guild"""
guild_id = str(ctx.guild.id)
try:
# Retrieve all commands for the guild
commands = self.db.fetch_all(
"SELECT COMMANDNAME, RESPONSE FROM custom_commands WHERE GUILDID = %s",
(guild_id,),
)
if commands:
commands_list = "\n".join(
[f"`{cmd['COMMANDNAME']}`: {cmd['RESPONSE']}" for cmd in commands]
)
await ctx.send(f"**Custom Commands:**\n{commands_list}")
else:
await ctx.send("No custom commands found for this server.")
except Exception as e:
await ctx.send(f"Failed to retrieve commands. Error: {e}")
@commands.Cog.listener()
async def on_message(self, message):
"""Listen for custom command invocations"""
if message.author.bot or not message.guild:
return
guild_id = str(message.guild.id)
command_name = message.content.strip().lower()
# Use cache instead of DB call
data: list = self.get_guild_commands(guild_id)
if not data:
return
result = None
for cmd in data:
if cmd["MATCHTYPE"] == "exact" and command_name == cmd["COMMANDNAME"]:
result = cmd
break
elif cmd["MATCHTYPE"] == "contains" and cmd["COMMANDNAME"] in command_name:
result = cmd
break
elif cmd["MATCHTYPE"] == "startswith" and command_name.startswith(
cmd["COMMANDNAME"]
):
result = cmd
break
elif cmd["MATCHTYPE"] == "endswith" and command_name.endswith(
cmd["COMMANDNAME"]
):
result = cmd
break
elif cmd["MATCHTYPE"] == "regex" and re.match(
re.compile(cmd["REGEX"]), command_name
):
result = cmd
break
else:
return
if result:
response = result["RESPONSE"]
# Build a dictionary of variables to replace
variables = {
"{USER}": message.author.name,
"{USER_MENTION}": message.author.mention,
"{USER_ID}": str(message.author.id),
"{USER_TAG}": str(message.author),
"{USER_AVATAR}": str(message.author.display_avatar.url),
"{USER_TOP_ROLE}": getattr(message.author.top_role, "name", ""),
"{USER_CREATED}": message.author.created_at.strftime(
"%Y-%m-%d %H:%M:%S"
),
"{USER_JOINED}": (
message.author.joined_at.strftime("%Y-%m-%d %H:%M:%S")
if message.author.joined_at
else ""
),
"{USER_NICK}": message.author.nick or message.author.name,
"{USER_COLOR}": str(
getattr(message.author.color, "to_rgb", lambda: "")()
),
"{USER_STATUS}": str(message.author.status),
"{USER_IS_BOT}": str(message.author.bot),
"{USER_DISCRIMINATOR}": message.author.discriminator,
"{CHANNEL}": message.channel.name,
"{CHANNEL_MENTION}": message.channel.mention,
"{CHANNEL_ID}": str(message.channel.id),
"{CHANNEL_TOPIC}": getattr(message.channel, "topic", ""),
"{CHANNEL_TYPE}": str(message.channel.type),
"{CHANNEL_CREATED}": message.channel.created_at.strftime(
"%Y-%m-%d %H:%M:%S"
),
"{GUILD}": message.guild.name,
"{GUILD_ID}": str(message.guild.id),
"{GUILD_OWNER}": str(message.guild.owner),
"{GUILD_OWNER_MENTION}": (
message.guild.owner.mention if message.guild.owner else ""
),
"{GUILD_MEMBERCOUNT}": str(message.guild.member_count),
"{GUILD_ICON}": (
str(message.guild.icon.url) if message.guild.icon else ""
),
"{GUILD_CREATED}": message.guild.created_at.strftime(
"%Y-%m-%d %H:%M:%S"
),
"{GUILD_BOOSTS}": str(
getattr(message.guild, "premium_subscription_count", "")
),
"{GUILD_BOOST_LEVEL}": str(getattr(message.guild, "premium_tier", "")),
"{MESSAGE}": message.content,
"{MESSAGE_ID}": str(message.id),
"{MESSAGE_LINK}": message.jump_url,
"{MESSAGE_TIMESTAMP}": message.created_at.strftime("%Y-%m-%d %H:%M:%S"),
"{TIME}": discord.utils.utcnow().strftime("%H:%M:%S UTC"),
"{DATE}": discord.utils.utcnow().strftime("%Y-%m-%d"),
"{DAY}": discord.utils.utcnow().strftime("%A"),
"{MONTH}": discord.utils.utcnow().strftime("%B"),
"{YEAR}": discord.utils.utcnow().strftime("%Y"),
"{BOT}": (
message.guild.me.name if message.guild else self.client.user.name
),
"{BOT_MENTION}": (
message.guild.me.mention
if message.guild
else self.client.user.mention
),
"{BOT_ID}": str(
message.guild.me.id if message.guild else self.client.user.id
),
"{BOT_AVATAR}": str(
message.guild.me.display_avatar.url
if message.guild
else self.client.user.display_avatar.url
),
}
# Replace all variables in the response
for var, value in variables.items():
if value is None:
continue
response = response.replace(var, value)
await message.channel.send(response)
async def setup(client):
await client.add_cog(CustomCommandsCog(client))
+423
View File
@@ -0,0 +1,423 @@
import logging
import discord
from discord.ext import commands
from utils.bank_functions import *
from discord.ext import commands
from datetime import datetime, timedelta
from random import randint
from typing import Literal
import datetime
async def check_transfer(
ctx: commands.Context,
payer_balance: int,
receiver_balance: int,
member: discord.Member,
amount: int,
) -> bool:
"""Check if a transfer is valid.
Args:
- ctx (commands.Context): The context of the invoked command.
- payer_balance (int): The balance of the payer.
- receiver_balance (int): The balance of the receiver.
- member (discord.Member): The member to check against.
- amount (int): The amount to transfer.
Returns:
- bool: Whether the transfer is valid.
"""
if payer_balance is None or receiver_balance is None:
await ctx.reply("Bank account doesn't exist.")
return False
if payer_balance >= amount > 0:
payer_balance = payer_balance - amount
receiver_balance = receiver_balance + amount
if ctx.author.id != member.id:
return True
else:
await ctx.reply("You cannot give yourself money.")
return False
else:
await ctx.reply(
f"You do not have {int(amount):,}<:flooney:1194943899765051473>.\nYou have {int(payer_balance):,}<:flooney:1194943899765051473>."
)
return False
class Economy(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager("")
r"""
.----------------. .----------------. .----------------. .----------------. .-----------------.
| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |
| | __ | || | ________ | || | ____ ____ | || | _____ | || | ____ _____ | |
| | / \ | || | |_ ___ `. | || ||_ \ / _|| || | |_ _| | || ||_ \|_ _| | |
| | / /\ \ | || | | | `. \ | || | | \/ | | || | | | | || | | \ | | | |
| | / ____ \ | || | | | | | | || | | |\ /| | | || | | | | || | | |\ \| | | |
| | _/ / \ \_ | || | _| |___.' / | || | _| |_\/_| |_ | || | _| |_ | || | _| |_\ |_ | |
| ||____| |____|| || | |________.' | || ||_____||_____|| || | |_____| | || ||_____|\____| | |
| | | || | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------' '----------------'
"""
@commands.command(
name="give_money",
aliases=["addmoney"],
usage="<member*: @member> <amount*: integer>",
)
@commands.is_owner()
@commands.cooldown(3, 2 * 60, commands.BucketType.user)
async def add_money(
self, ctx: commands.Context, member: discord.Member, amount_str: str
):
"""Add money to a user's bank."""
if member.bot:
return await ctx.reply("You can't add money to a bot", mention_author=False)
if not member:
member = ctx.author
try:
amount = int(amount_str)
except ValueError:
return await ctx.reply("Please enter a valid amount")
if amount <= 0:
return await ctx.reply("Please enter an amount greater than 0")
limit = 1_000_000
if amount > limit:
return await ctx.reply(f"You cannot add money more than {limit:,}")
await update_money(member, amount)
await ctx.reply(
f"You added {amount:,} in {member.mention}'s bank.", mention_author=False
)
@commands.command(
aliases=["remoney"],
usage="<member*: @member> <amount*: integer> <mode: wallet or bank>",
)
@commands.is_owner()
@commands.cooldown(3, 2 * 60, commands.BucketType.user)
async def remove_money(
self, ctx, member: discord.Member, amount_str: str, mode: str = "bank"
):
mode = mode.lower()
if member.bot:
return await ctx.reply(
"You can't remove money from a bot", mention_author=False
)
try:
amount = int(amount_str)
if amount <= 0:
raise ValueError
except ValueError:
return await ctx.reply("Please enter a valid amount")
if mode not in ["wallet", "bank"]:
return await ctx.reply("Please enter either wallet or bank only")
user_balance = await bank_data(member)
current_balance = user_balance["BANK" if mode == "bank" else "WALLET"]
if current_balance < amount:
return await ctx.reply(
f"You can only remove {current_balance:,} from {member.mention}'s {mode}"
)
if mode == "bank":
await update_money(member, bank=-amount)
else:
await update_money(member, wallet=-amount)
await ctx.reply(
f"You removed {amount:,} from {member.mention}'s {mode}",
mention_author=False,
)
@commands.command(usage="<member*: @member>")
@commands.is_owner()
@commands.cooldown(2, 3 * 60, commands.BucketType.user)
async def reset_money(self, ctx, member: discord.Member):
if member.bot:
return await ctx.reply(
"Bots don't have bank accounts", mention_author=False
)
await reset_bank(member)
return await ctx.reply(
f"{member.mention}'s account has been reset", mention_author=False
)
r"""
.----------------. .----------------. .-----------------. .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. |
| | ______ | || | __ | || | ____ _____ | || | ___ ____ | |
| | |_ _ \ | || | / \ | || ||_ \|_ _| | || | |_ ||_ _| | |
| | | |_) | | || | / /\ \ | || | | \ | | | || | | |_/ / | |
| | | __'. | || | / ____ \ | || | | |\ \| | | || | | __'. | |
| | _| |__) | | || | _/ / \ \_ | || | _| |_\ |_ | || | _| | \ \_ | |
| | |_______/ | || ||____| |____|| || ||_____|\____| | || | |____||____| | |
| | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------'
"""
@commands.command(
name="balance",
aliases=["bal"],
brief="Check your money",
description="Check how much money you have.",
)
async def _balance(
self, ctx: commands.Context, member: discord.Member | None = None
):
"""
Check how much money you have.
"""
target: discord.Member | discord.User = member or ctx.author
# Ensure the target is not a bot
if target.bot:
return
# Get the user's data
user_data = await bank_data(target)
wallet_balance = user_data.get("WALLET", 0)
bank_balance = user_data.get("BANK", 0)
# Create an account if one does not exist
if bank_balance is None or wallet_balance is None:
await create_account(target)
user_data = await bank_data(target)
wallet_balance = user_data.get("WALLET", 0)
bank_balance = user_data.get("BANK", 0)
# Reply with the user's balance
await ctx.reply(
f"{target.mention} has {bank_balance:,}<:flooney:1194943899765051473> in their bank and "
f"{wallet_balance:,}<:flooney:1194943899765051473> in their wallet."
)
@commands.command(name="give")
async def _give(self, ctx, target: discord.Member, amount: int):
"""Give money to another user."""
payer_wallet = int((await bank_data(ctx.author)).get("WALLET", 0))
receiver_wallet = int((await bank_data(target)).get("WALLET", 0))
if await check_transfer(ctx, payer_wallet, receiver_wallet, target, amount):
await update_money(target, wallet=amount)
await update_money(ctx.author, wallet=-amount)
await ctx.reply(
f"You gave {target} {amount:,} flooneys.", mention_author=False
)
@commands.command(name="transfer")
async def _transfer(self, ctx, target: discord.Member, amount: int):
"""
Transfer money from your bank account to another user's bank account.
"""
payer_bank = int((await bank_data(ctx.author)).get("BANK", 0))
receiver_bank = int((await bank_data(target)).get("BANK", 0))
if await check_transfer(ctx, payer_bank, receiver_bank, target, amount):
await update_money(target, bank=amount)
await update_money(ctx.author, bank=-amount)
await ctx.reply(
f"Transferred {amount:,} flooneys to {target}.", mention_author=False
)
@commands.command(
name="deposit",
aliases=["dep"],
brief="Deposit money",
description="Deposit money from your wallet to the bank",
)
async def _deposit(self, ctx, amount: int | Literal["all"] = 0) -> None:
"""Deposit money from your wallet to your bank account. Amount can be an integer or 'all' to deposit all money from wallet."""
user_data = await bank_data(ctx.author)
if user_data is None:
await create_account(ctx)
user_data = await bank_data(ctx.author)
wallet_balance = int(user_data.get("WALLET", 0))
if amount == "all":
amount = wallet_balance
if 0 <= amount <= wallet_balance:
await update_money(ctx.author, bank=amount, wallet=-amount)
await ctx.reply("Transaction successful.")
else:
await ctx.reply("Insufficient funds in wallet")
@commands.command(name="withdraw", brief="Withdraw money from bank to wallet")
async def _withdraw(self, ctx, amount: int | Literal["all"] = 0) -> None:
"""Withdraw money from your bank account to your wallet. Amount can be an integer or 'all' to withdraw all money from bank."""
user_data = await bank_data(ctx.author)
if user_data is None:
await create_account(ctx)
user_data = await bank_data(ctx.author)
bank_balance = int(user_data.get("BANK", 0))
if amount == "all":
amount = bank_balance
if 0 <= amount <= bank_balance:
await update_money(ctx.author, bank=-amount, wallet=amount)
await ctx.reply("Transaction successful.")
else:
await ctx.reply("Insufficient funds")
@commands.command(aliases=["lb"])
@commands.guild_only()
async def leaderboard(self, ctx):
users_data = self.db.fetch_all(
"SELECT * FROM economy ORDER BY BANK + WALLET DESC"
)
leaderboard_entries = []
position = 1
for user_data in users_data:
user_id = int(user_data["ID"])
user = ctx.guild.get_member(user_id) or self.client.get_user(user_id)
if user is None:
try:
user = await self.client.fetch_user(user_id)
user_display = (
user.display_name
if hasattr(user, "display_name")
else str(user)
)
except Exception:
user_display = f"User {user_id}"
else:
user_display = (
user.display_name if hasattr(user, "display_name") else str(user)
)
total_balance = int(user_data["BANK"]) + int(user_data["WALLET"])
flooney_icon = "<:flooney:1194943899765051473>"
if position <= 3:
entry = f"{['🥇', '🥈', '🥉'][position-1]}**{position} {user_display}** -- {total_balance:,}{flooney_icon} "
else:
entry = (
f"**{position} {user_display}** -- {total_balance:,}{flooney_icon}"
)
leaderboard_entries.append(entry)
position += 1
if position > 10:
break
embed = discord.Embed(
title=f"Top {len(leaderboard_entries)} Richest Users - Leaderboard",
description="\n".join(leaderboard_entries),
color=discord.Color(0x00FF00),
timestamp=datetime.datetime.utcnow(),
)
embed.set_footer(text=f"GLOBAL - {ctx.guild.name}")
await ctx.reply(embed=embed, mention_author=False)
r"""
.----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |
| | ______ | || | _________ | || | _______ | || | _____ | || | ____ | || | ________ | || | _____ | || | ______ | || | __ | || | _____ | || | _____ | || | ____ ____ | |
| | |_ __ \ | || | |_ ___ | | || | |_ __ \ | || | |_ _| | || | .' `. | || | |_ ___ `. | || | |_ _| | || | .' ___ | | || | / \ | || | |_ _| | || | |_ _| | || | |_ _||_ _| | |
| | | |__) | | || | | |_ \_| | || | | |__) | | || | | | | || | / .--. \ | || | | | `. \ | || | | | | || | / .' \_| | || | / /\ \ | || | | | | || | | | | || | \ \ / / | |
| | | ___/ | || | | _| _ | || | | __ / | || | | | | || | | | | | | || | | | | | | || | | | | || | | | | || | / ____ \ | || | | | _ | || | | | _ | || | \ \/ / | |
| | _| |_ | || | _| |___/ | | || | _| | \ \_ | || | _| |_ | || | \ `--' / | || | _| |___.' / | || | _| |_ | || | \ `.___.'\ | || | _/ / \ \_ | || | _| |__/ | | || | _| |__/ | | || | _| |_ | |
| | |_____| | || | |_________| | || | |____| |___| | || | |_____| | || | `.____.' | || | |________.' | || | |_____| | || | `._____.' | || ||____| |____|| || | |________| | || | |________| | || | |______| | |
| | | || | | || | | || | | || | | || | | || | | || | | || | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------'
"""
@commands.command(name="daily", help="Claim your daily pocket money")
async def claim_daily_money(self, ctx: commands.Context) -> None:
"""Claim your daily pocket money. You can claim once every 24 hours."""
try:
user_data = await bank_data(ctx.author)
last_claim = user_data.get("DAILY")
now = discord.utils.utcnow() # Modern, timezone-aware
# Convert last_claim to datetime if it's a timestamp (int or float)
if last_claim:
if isinstance(last_claim, (int, float)):
last_claim_dt = datetime.datetime.fromtimestamp(
last_claim, tz=datetime.timezone.utc
)
elif isinstance(last_claim, str):
try:
last_claim_dt = datetime.datetime.fromtimestamp(
float(last_claim), tz=datetime.timezone.utc
)
except Exception:
last_claim_dt = None
elif isinstance(last_claim, datetime.datetime):
# If it's naive, make it aware
if last_claim.tzinfo is None:
last_claim_dt = last_claim.replace(tzinfo=datetime.timezone.utc)
else:
last_claim_dt = last_claim
else:
last_claim_dt = None
else:
last_claim_dt = None
if not last_claim_dt or (now - last_claim_dt).total_seconds() >= 86400:
daily_reward = randint(200, 1000)
await update_money(ctx.author, daily_reward)
# Save the new timestamp as a float (UNIX time)
await update_daily_timestamp(ctx.author, now)
await ctx.reply(
f"Your daily pocket money is {daily_reward:,}<:flooney:1194943899765051473>",
mention_author=False,
)
else:
next_claim = last_claim_dt + timedelta(days=1)
time_left = next_claim - now
hours, remainder = divmod(int(time_left.total_seconds()), 3600)
minutes, seconds = divmod(remainder, 60)
await ctx.reply(
f"Already claimed today. You need to wait **{hours}H {minutes}M {seconds}S**.",
mention_author=False,
)
except Exception as e:
logging.error(f"Error in daily claim: {e}")
await ctx.reply(
f"An error occurred while claiming your daily reward. {e}",
mention_author=False,
)
r"""
.----------------. .----------------. .----------------. .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. |
| | _____ | || | ____ | || | ______ | || | _______ | |
| | |_ _| | || | .' `. | || | |_ _ \ | || | / ___ | | |
| | | | | || | / .--. \ | || | | |_) | | || | | (__ \_| | |
| | _ | | | || | | | | | | || | | __'. | || | '.___`-. | |
| | | |_' | | || | \ `--' / | || | _| |__) | | || | |`\____) | | |
| | `.___.' | || | `.____.' | || | |_______/ | || | |_______.' | |
| | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------'
"""
async def setup(client):
await client.add_cog(Economy(client))
Executable
+979
View File
@@ -0,0 +1,979 @@
import discord
from random import shuffle, choices, choice
from discord.ext import commands
from utils.sql_commands import DatabaseManager
from utils.bank_functions import bank_data, update_money
from datetime import datetime, timedelta
import asyncio
import random
# --- Constants and Configs ---
lottery_list = ["🎁", "🎮", "🎷", "🔫", "📸", "🎃", "🏅"]
weights = [0.15, 0.1, 0.02, 0.2, 0.1, 0.28, 0.2]
lottery_win = {
"🎁": 2,
"🎁🎁": 5,
"🎁🎁🎁": 50,
"🔫🔫🔫": 3,
"📸📸📸": 5,
"🎃🎃🎃": 7,
"🏅🏅🏅": 10,
"🎮🎮🎮": 25,
"🎷🎷🎷": 1000,
}
bj_values = {
"2♦️": 2,
"2♥️": 2,
"2♣️": 2,
"2♠️": 2,
"3♦️": 3,
"3♥️": 3,
"3♣️": 3,
"3♠️": 3,
"4♦️": 4,
"4♥️": 4,
"4♣️": 4,
"4♠️": 4,
"5♦️": 5,
"5♥️": 5,
"5♣️": 5,
"5♠️": 5,
"6♦️": 6,
"6♥️": 6,
"6♣️": 6,
"6♠️": 6,
"7♦️": 7,
"7♥️": 7,
"7♣️": 7,
"7♠️": 7,
"8♦️": 8,
"8♥️": 8,
"8♣️": 8,
"8♠️": 8,
"9♦️": 9,
"9♥️": 9,
"9♣️": 9,
"9♠️": 9,
"10♦️": 10,
"10♥️": 10,
"10♣️": 10,
"10♠️": 10,
"J♦️": 10,
"J♥️": 10,
"J♣️": 10,
"J♠️": 10,
"Q♦️": 10,
"Q♥️": 10,
"Q♣️": 10,
"Q♠️": 10,
"K♦️": 10,
"K♥️": 10,
"K♣️": 10,
"K♠️": 10,
"A♦️": 11,
"A♥️": 11,
"A♣️": 11,
"A♠️": 11,
}
ROOMS_FILE = "active_rooms.json"
# --- Blackjack Classes and Helpers ---
class Deck:
def __init__(self):
self.deck = list(bj_values.keys())
shuffle(self.deck)
async def deal(self):
return self.deck.pop()
class Hand:
def __init__(self, name, bet):
self.cards = []
self.value = 0
self.aces = 0
self.bust = False
self.name = name
self.bet = bet
async def add_card(self, card):
self.cards.append(card)
self.aces = ("".join(self.cards)).count("A")
value = 0
for card in self.cards:
value += bj_values[card]
self.value = value
while self.value > 21 and self.aces:
self.value -= 10
self.aces -= 1
async def hit(deck, hand):
await hand.add_card(await deck.deal())
async def payout(ctx, play, amount, multiplier, db):
player_balance = int(
db.fetch_one("SELECT WALLET FROM economy WHERE ID = %s", (ctx.author.id,)).get(
"WALLET", 0
)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + amount * (multiplier - 1), ctx.author.id),
)
await ctx.reply(
f"{play}\nBet: {amount:,}\nYou won {amount * multiplier:,} <:flooney:1194943899765051473>"
)
async def check_winner(msg: discord.Interaction, player_hand, dealer_hand, db):
p_value, d_value = player_hand.value, dealer_hand.value
embed = discord.Embed(description="Blackjack")
embed.add_field(
name="Bet: ",
value=f"{player_hand.bet:,}<:flooney:1194943899765051473>",
inline=True,
)
embed.add_field(
name=player_hand.name,
value=f"{', '.join(player_hand.cards)}: {player_hand.value}",
inline=False,
)
embed.add_field(
name=dealer_hand.name,
value=f"{', '.join(dealer_hand.cards)}: {dealer_hand.value}",
inline=False,
)
# Check for Blackjack
if p_value == 21 and len(player_hand.cards) == 2:
if d_value == 21 and len(dealer_hand.cards) == 2:
embed.add_field(
name="Winner:", value="Draw (Both got Blackjack!)", inline=False
)
player_balance = int(
db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (player_hand.name.id,)
).get("WALLET", 0)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + player_hand.bet, player_hand.name.id),
)
else:
embed.add_field(
name="Winner:", value=f"{player_hand.name} (Blackjack!)", inline=False
)
player_balance = int(
db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (player_hand.name.id,)
).get("WALLET", 0)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + int(player_hand.bet * 3), player_hand.name.id),
)
elif d_value == 21 and len(dealer_hand.cards) == 2:
embed.add_field(
name="Winner:", value=f"{dealer_hand.name} (Blackjack!)", inline=False
)
else:
if (p_value > d_value and not player_hand.bust) or dealer_hand.bust:
embed.add_field(name="Winner:", value=f"{player_hand.name}", inline=False)
player_balance = int(
db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (player_hand.name.id,)
).get("WALLET", 0)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + player_hand.bet * 2, player_hand.name.id),
)
elif (p_value < d_value and not dealer_hand.bust) or player_hand.bust:
embed.add_field(name="Winner:", value=f"{dealer_hand.name}", inline=False)
else:
embed.add_field(name="Winner:", value="Draw", inline=False)
player_balance = int(
db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (player_hand.name.id,)
).get("WALLET", 0)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + player_hand.bet, player_hand.name.id),
)
await msg.edit_original_response(embed=embed)
async def dealer(msg: discord.Interaction, player_hand, dealer_hand, cards, db):
while dealer_hand.value < 17 and not player_hand.bust and not dealer_hand.bust:
await hit(cards, dealer_hand)
if dealer_hand.value > 21:
dealer_hand.bust = True
embed = discord.Embed(description="Blackjack")
embed.add_field(
name="Bet: ",
value=f"{player_hand.bet:,}<:flooney:1194943899765051473>",
inline=True,
)
embed.add_field(
name=player_hand.name,
value=f"{', '.join(player_hand.cards)}: {player_hand.value}",
inline=False,
)
embed.add_field(
name=dealer_hand.name,
value=f"{', '.join(dealer_hand.cards)}: {dealer_hand.value}",
inline=False,
)
await msg.edit_original_response(embed=embed)
await check_winner(msg, player_hand, dealer_hand, db)
class MyView(discord.ui.View):
def __init__(self, player_hand, dealer_hand, cards, db):
super().__init__()
self.player_hand = player_hand
self.dealer_hand = dealer_hand
self.cards = cards
self.db = db
@discord.ui.button(label="Hit", style=discord.ButtonStyle.primary, emoji="")
async def hit(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user.id == self.player_hand.name.id:
await hit(self.cards, self.player_hand)
embed = discord.Embed(description="Blackjack")
embed.add_field(
name="Bet: ",
value=f"{self.player_hand.bet:,}<:flooney:1194943899765051473>",
inline=True,
)
embed.add_field(
name=self.player_hand.name,
value=f"{', '.join(self.player_hand.cards)}: {self.player_hand.value}",
inline=False,
)
embed.add_field(
name=self.dealer_hand.name,
value=f"{(self.dealer_hand.cards)[0]}",
inline=False,
)
msg = interaction
if self.player_hand.value < 21:
await msg.response.edit_message(embed=embed)
else:
await msg.response.edit_message(embed=embed, view=None)
if self.player_hand.value > 21:
self.player_hand.bust = True
await dealer(
msg, self.player_hand, self.dealer_hand, self.cards, self.db
)
@discord.ui.button(label="Stand", style=discord.ButtonStyle.secondary, emoji="🛑")
async def stand(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user.id == self.player_hand.name.id:
embed = discord.Embed(description="Blackjack")
embed.add_field(
name="Bet: ",
value=f"{self.player_hand.bet:,}<:flooney:1194943899765051473>",
inline=True,
)
embed.add_field(
name=self.player_hand.name,
value=f"{', '.join(self.player_hand.cards)}: {self.player_hand.value}",
inline=False,
)
embed.add_field(
name=self.dealer_hand.name,
value=f"{(self.dealer_hand.cards)[0]}",
inline=False,
)
msg = interaction
await msg.response.edit_message(embed=embed, view=None)
await dealer(msg, self.player_hand, self.dealer_hand, self.cards, self.db)
# --- Main Gamble Cog ---
class Gamble(commands.Cog):
"""Cog for all gambling-related commands."""
def __init__(self, client):
self.client = client
self.db = DatabaseManager()
self.active_rooms = {}
self.room_timers = {}
def save_rooms(self):
"""Save all active rooms to the database."""
try:
for channel_id, room in self.active_rooms.items():
invited_str = ",".join(str(uid) for uid in room["invited"])
self.db.execute_query(
"REPLACE INTO gamble_rooms (channel_id, host_id, invited, inactivity) VALUES (%s, %s, %s, %s)",
(channel_id, room["host"], invited_str, room["inactivity"]),
)
except Exception as e:
print(f"[DB ERROR] Failed to save rooms: {e}")
def load_rooms(self):
"""Load all rooms from the database."""
try:
self.active_rooms = {}
rows = self.db.fetch_all("SELECT * FROM gamble_rooms")
for row in rows:
invited = set(int(uid) for uid in row["invited"].split(",") if uid)
self.active_rooms[int(row["channel_id"])] = {
"host": int(row["host_id"]),
"invited": invited,
"inactivity": int(row["inactivity"]),
}
except Exception as e:
print(f"[DB ERROR] Failed to load rooms: {e}")
def delete_room(self, channel_id):
"""Delete a room from the database and memory."""
try:
self.db.execute_query(
"DELETE FROM gamble_rooms WHERE channel_id = %s", (channel_id,)
)
except Exception as e:
print(f"[DB ERROR] Failed to delete room: {e}")
self.active_rooms.pop(channel_id, None)
self.save_rooms()
async def cog_load(self):
"""Load rooms on cog load."""
self.load_rooms()
async def cog_unload(self):
"""Save rooms on cog unload."""
self.save_rooms()
async def _check_limit_and_exclusion(self, ctx, amount: int) -> bool:
"""Returns True if user is allowed to gamble, else sends a message and returns False."""
user = ctx.author
if amount <= 0:
await ctx.reply("⚠️ Bet amount must be positive.", mention_author=False)
return False
try:
limit_row = self.db.fetch_one(
"SELECT * FROM gamble_limits WHERE USERID = %s", (user.id,)
)
except Exception as e:
await ctx.reply(
"⚠️ Database error. Please try again later.", mention_author=False
)
print(f"[DB ERROR] Limit check: {e}")
return False
if limit_row:
if (
limit_row.get("EXCLUDED_UNTIL")
and datetime.utcnow() < limit_row["EXCLUDED_UNTIL"]
):
await ctx.reply(
"🚫 You are currently self-excluded from gambling.",
mention_author=False,
)
return False
if limit_row.get("DAILY_LIMIT") and amount > limit_row["DAILY_LIMIT"]:
await ctx.reply(
f"🚫 Your personal bet limit is {limit_row['DAILY_LIMIT']:,}.",
mention_author=False,
)
return False
return True
async def _send_embed(
self, ctx, description: str, color: discord.Color = discord.Color.blurple()
):
"""Helper to send an embed message."""
embed = discord.Embed(description=description, color=color)
await ctx.reply(embed=embed, mention_author=False)
# --- Coinflip ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="coinflip",
aliases=["cf", "coin_flip"],
usage="<bet_on: heads(H) or tails(T)> <amount: integer>",
description="Flip a coin and bet on heads or tails.",
brief="Bet on heads or tails.",
)
@commands.guild_only()
async def coin_flip(self, ctx, bet_on: str, amount: int):
if not await self._check_limit_and_exclusion(ctx, amount):
return
user = ctx.author
bet_on = "heads" if "h" in bet_on.lower() else "tails"
reward = round(amount / 2)
users = await bank_data(user)
if int(users["WALLET"]) < amount:
return await self._send_embed(
ctx, "❌ You don't have enough money.", discord.Color.red()
)
coin = ["heads", "tails"]
result = choice(coin)
if result != bet_on:
await update_money(user, wallet=-abs(amount))
return await self._send_embed(
ctx,
f"🪙 Got **{result}**, you lost **{amount:,}**.",
discord.Color.red(),
)
await update_money(user, wallet=+abs(amount))
return await self._send_embed(
ctx,
f"🪙 Got **{result}**, you won **{amount + reward:,}**!",
discord.Color.green(),
)
# --- Dice ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="dice",
usage="<amount: integer> <bet_on: integer (1-6)>",
description="Bet on a dice roll (choose a number 1-6).",
brief="Bet on a dice roll.",
)
async def dice(self, ctx, amount: int, bet_on: int = 6):
if not await self._check_limit_and_exclusion(ctx, amount):
return
user = ctx.author
rdice = [1, 2, 3, 4, 5, 6]
if bet_on not in rdice:
return await self._send_embed(
ctx, "🎲 Enter a number of dice (1 - 6).", discord.Color.orange()
)
users = await bank_data(user)
if int(users["WALLET"]) < amount:
return await self._send_embed(
ctx, "❌ You don't have enough money.", discord.Color.red()
)
rand_num = choice(rdice)
if rand_num != bet_on:
await update_money(user, wallet=-abs(amount))
return await self._send_embed(
ctx,
f"🎲 Got **{rand_num}**, you lost **{amount:,}**.",
discord.Color.red(),
)
reward = round(amount / 2)
await update_money(user, wallet=+abs(reward))
await self._send_embed(
ctx,
f"🎲 Got **{rand_num}**, you won **{amount + reward:,}**!",
discord.Color.green(),
)
# --- Slots ---
@commands.cooldown(1, 1, commands.BucketType.user)
@commands.command(
name="slots",
usage="<bet: integer or 'all'>",
description="Spin the slot machine for a chance to win big!",
brief="Spin the slot machine.",
)
async def slots(self, ctx, bet):
user = ctx.author
player_balance = int(
self.db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (user.id,)
).get("WALLET", 0)
)
bet = int(bet.replace("all", str(player_balance)))
if not await self._check_limit_and_exclusion(ctx, bet):
return
if player_balance < bet:
await self._send_embed(
ctx, "❌ You have insufficient funds.", discord.Color.red()
)
return
# Animation: edit the embed to show spinning, then result
spin_emoji = "<a:slots:1381060458999713843>"
embed = discord.Embed(
description=f"{spin_emoji} {spin_emoji} {spin_emoji}\nSpinning...",
color=discord.Color.blurple(),
)
msg = await ctx.send(embed=embed)
await asyncio.sleep(1.2)
play = "".join(choices(lottery_list, weights=weights, k=3))
result_embed = discord.Embed(
description=f"{play[0]} {play[1]} {play[2]}",
color=(
discord.Color.green()
if play in lottery_win or play.count("🎁") > 0
else discord.Color.red()
),
)
# Ensure description is always a string
if result_embed.description is None:
result_embed.description = ""
if play in lottery_win:
mult = lottery_win[play]
result_embed.description = (result_embed.description or "") + (
f"\nYou won **{bet * mult:,}** <:flooney:1194943899765051473>"
)
await update_money(user, wallet=bet * (mult - 1))
elif play.count("🎁") == 2:
mult = lottery_win["🎁🎁"]
result_embed.description = (result_embed.description or "") + (
f"\nYou won **{bet * mult:,}** <:flooney:1194943899765051473>"
)
await update_money(user, wallet=bet * (mult - 1))
elif play.count("🎁") == 1:
mult = lottery_win["🎁"]
result_embed.description = (result_embed.description or "") + (
f"\nYou won **{bet * mult:,}** <:flooney:1194943899765051473>"
)
await update_money(user, wallet=bet * (mult - 1))
else:
await update_money(user, wallet=-bet)
result_embed.description = (result_embed.description or "") + (
f"\nYou lost **{int(bet):,}** <:flooney:1194943899765051473>"
)
await msg.edit(embed=result_embed)
# --- Roulette ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="roulette",
usage="<bet_type> <amount: integer>",
description=(
"Bet on roulette! Options: "
"`red`, `black`, `green`, `even`, `odd`, "
"`1st12`, `2nd12`, `3rd12` (dozens), "
"`col1`, `col2`, `col3` (columns), or a number (0-36)."
),
brief="Bet on roulette.",
)
async def roulette(self, ctx, bet_type: str, amount: int):
if not await self._check_limit_and_exclusion(ctx, amount):
return
user = ctx.author
colors = {
"red": [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36],
"black": [
2,
4,
6,
8,
10,
11,
13,
15,
17,
20,
22,
24,
26,
28,
29,
31,
33,
35,
],
"green": [0],
}
bet_type = bet_type.lower()
valid_bets = [
"red",
"black",
"green",
"even",
"odd",
"1st12",
"2nd12",
"3rd12",
"col1",
"col2",
"col3",
] + [str(i) for i in range(37)]
if bet_type not in valid_bets:
return await self._send_embed(
ctx,
"Bet on 'red', 'black', 'green', 'even', 'odd', '1st12', '2nd12', '3rd12', 'col1', 'col2', 'col3', or a number (0-36).",
discord.Color.orange(),
)
result = choice(range(37))
color = (
"green" if result == 0 else "red" if result in colors["red"] else "black"
)
payout = 0
# Number bet
if bet_type.isdigit() and int(bet_type) == result:
payout = amount * 35
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
# Color bet
elif bet_type == color:
payout = amount * (14 if color == "green" else 2)
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
# Even/Odd
elif bet_type == "even" and result != 0 and result % 2 == 0:
payout = amount * 2
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "odd" and result % 2 == 1:
payout = amount * 2
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
# Dozens
elif bet_type == "1st12" and 1 <= result <= 12:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "2nd12" and 13 <= result <= 24:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "3rd12" and 25 <= result <= 36:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
# Columns
elif bet_type == "col1" and result in [i for i in range(1, 37, 3)]:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "col2" and result in [i for i in range(2, 37, 3)]:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "col3" and result in [i for i in range(3, 37, 3)]:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
else:
payout = -amount
msg = f"🎲 The ball landed on {result} ({color}). You lost {amount:,}."
await update_money(user, wallet=payout)
await self._send_embed(
ctx, msg, discord.Color.green() if payout > 0 else discord.Color.red()
)
# --- Poker ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.is_owner()
@commands.command(
name="poker",
usage="<bet: integer>",
description="Play a simple 5-card draw poker game against the bot.",
brief="Play poker against the bot.",
)
async def poker(self, ctx, bet: int):
if not await self._check_limit_and_exclusion(ctx, bet):
return
user = ctx.author
user_data = await bank_data(user)
if int(user_data["WALLET"]) < bet:
await self._send_embed(
ctx, "❌ You don't have enough money.", discord.Color.red()
)
return
# Poker deck and hands
suits = ["♠️", "♥️", "♦️", "♣️"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
deck = [f"{r}{s}" for r in ranks for s in suits]
random.shuffle(deck)
player_hand = [deck.pop() for _ in range(5)]
bot_hand = [deck.pop() for _ in range(5)]
# Simple hand value: count pairs, triples, etc. (not full poker logic)
def hand_value(hand):
values = [card[:-2] if card[:-2] != "10" else "10" for card in hand]
counts = {v: values.count(v) for v in set(values)}
if 4 in counts.values():
return 7 # Four of a kind
if sorted(counts.values()) == [2, 3]:
return 6 # Full house
if 3 in counts.values():
return 3 # Three of a kind
if list(counts.values()).count(2) == 2:
return 2 # Two pair
if 2 in counts.values():
return 1 # One pair
return 0 # High card
player_score = hand_value(player_hand)
bot_score = hand_value(bot_hand)
if player_score > bot_score:
await update_money(user, wallet=bet)
msg = f"🃏 Your hand: {', '.join(player_hand)}\n🤖 Bot's hand: {', '.join(bot_hand)}\nYou win {bet:,} coins!"
elif player_score < bot_score:
await update_money(user, wallet=-bet)
msg = f"🃏 Your hand: {', '.join(player_hand)}\n🤖 Bot's hand: {', '.join(bot_hand)}\nYou lose {bet:,} coins!"
else:
msg = f"🃏 Your hand: {', '.join(player_hand)}\n🤖 Bot's hand: {', '.join(bot_hand)}\nIt's a draw! No coins won or lost."
await self._send_embed(
ctx,
msg,
(
discord.Color.green()
if "win" in msg
else discord.Color.red() if "lose" in msg else discord.Color.blurple()
),
)
# --- Higher/Lower ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="higherlower",
usage="<bet: integer>",
description="Guess if the next number will be higher or lower.",
brief="Guess higher or lower.",
)
async def higherlower(self, ctx, bet: int):
if not await self._check_limit_and_exclusion(ctx, bet):
return
user = ctx.author
number = random.randint(1, 100)
await self._send_embed(
ctx,
f"The number is **{number}**. Will the next number be higher or lower? Type `higher` or `lower`.",
discord.Color.blurple(),
)
def check(m):
return (
m.author == ctx.author
and m.channel == ctx.channel
and m.content.lower() in ["higher", "lower"]
)
try:
guess_msg = await self.client.wait_for("message", check=check, timeout=15)
except Exception:
await self._send_embed(
ctx, "Timed out. Please try again.", discord.Color.orange()
)
return
next_number = random.randint(1, 100)
win = (guess_msg.content.lower() == "higher" and next_number > number) or (
guess_msg.content.lower() == "lower" and next_number < number
)
if next_number == number:
msg = f"The next number was also **{next_number}**. It's a tie! No coins won or lost."
elif win:
await update_money(user, wallet=bet)
msg = f"The next number was **{next_number}**. You win {bet:,} coins!"
else:
await update_money(user, wallet=-bet)
msg = f"The next number was **{next_number}**. You lose {bet:,} coins!"
await self._send_embed(
ctx, msg, discord.Color.green() if win else discord.Color.red()
)
# --- Scratch ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="scratch",
usage="<bet: integer>",
description="Buy a scratch card for a chance to win up to 10x your bet.",
brief="Buy a scratch card.",
)
async def scratch(self, ctx, bet: int):
if not await self._check_limit_and_exclusion(ctx, bet):
return
user = ctx.author
symbols = ["🍀", "💎", "", "🍒", "7️⃣"]
card = [random.choice(symbols) for _ in range(3)]
payout = 0
if card.count(card[0]) == 3:
payout = bet * 10
msg = f"Scratch Card: {' '.join(card)}\nJackpot! You win {payout:,} coins!"
elif len(set(card)) == 2:
payout = bet * 2
msg = f"Scratch Card: {' '.join(card)}\nTwo of a kind! You win {payout:,} coins!"
else:
payout = -bet
msg = f"Scratch Card: {' '.join(card)}\nNo match. You lose {bet:,} coins."
await update_money(user, wallet=payout)
await self._send_embed(
ctx, msg, discord.Color.green() if payout > 0 else discord.Color.red()
)
# --- All other user-facing responses (invite, kick, end_gameroom, etc.) ---
# Replace ctx.reply(...) or ctx.send(...) with self._send_embed(...) for consistency.
@commands.command(
name="gameroom",
usage="[game: str] [inactivity: int (minutes)]",
description="Create a private game room that auto-deletes after inactivity.",
brief="Create a private game room.",
)
@commands.guild_only()
async def gameroom(self, ctx, game: str = "game", inactivity: int = 10):
"""Create a private text channel for a game session."""
guild = ctx.guild
overwrites = {
guild.default_role: discord.PermissionOverwrite(read_messages=False),
ctx.author: discord.PermissionOverwrite(
read_messages=True, send_messages=True
),
}
category = ctx.channel.category
channel_name = f"{game}-room-{ctx.author.display_name}".replace(
" ", "-"
).lower()
channel = await guild.create_text_channel(
name=channel_name,
overwrites=overwrites,
category=category,
reason="Private game room",
)
await channel.send(
f"{ctx.author.mention} Your private **{game}** session has started!\n"
f"Use `py invite <user>` and `py kick <user>` in this channel to manage access.\n"
f"This channel will be deleted after **{inactivity} minutes of inactivity**."
)
self.active_rooms[channel.id] = {
"host": ctx.author.id,
"invited": set([ctx.author.id]),
"inactivity": inactivity,
}
self.save_rooms()
self.reset_inactivity_timer(channel.id, inactivity)
def reset_inactivity_timer(self, channel_id, inactivity):
"""Reset the inactivity timer for a game room."""
if channel_id in self.room_timers:
self.room_timers[channel_id].cancel()
task = asyncio.create_task(self.inactivity_task(channel_id, inactivity))
self.room_timers[channel_id] = task
async def inactivity_task(self, channel_id, inactivity):
"""Delete the channel after inactivity period."""
try:
await asyncio.sleep(inactivity * 60)
channel = self.client.get_channel(channel_id)
if channel:
await channel.send("Room deleted due to inactivity.")
await channel.delete(reason="Game room inactive")
self.delete_room(channel_id)
self.room_timers.pop(channel_id, None)
except asyncio.CancelledError:
pass
@commands.Cog.listener()
async def on_message(self, message):
"""Reset inactivity timer on message in a game room."""
if (
hasattr(self, "active_rooms")
and message.channel.id in self.active_rooms
and not message.author.bot
):
inactivity = self.active_rooms[message.channel.id].get("inactivity", 10)
self.reset_inactivity_timer(message.channel.id, inactivity)
@commands.command(
name="invite",
usage="<user: id|name>",
description="Invite a user to your private game room.",
brief="Invite a user to your game room.",
hidden=True,
)
async def invite(
self,
ctx,
member: discord.Member | None = None,
*,
identifier: str | None = None,
):
"""Invite a user to the game room by mention, ID, or username."""
room = self.active_rooms.get(ctx.channel.id)
if not room or ctx.author.id != room["host"]:
await ctx.reply(
"Only the host can invite others, and only in a private game room channel.",
mention_author=False,
)
return
# If member is not provided, try to resolve by ID or name
if member is None and identifier:
try:
member = ctx.guild.get_member(int(identifier))
except ValueError:
member = discord.utils.find(
lambda m: m.name.lower() == identifier.lower()
or m.display_name.lower() == identifier.lower(),
ctx.guild.members,
)
if member is None:
await ctx.reply(
"User not found. Please use a mention, user ID, or username.",
mention_author=False,
)
return
await ctx.channel.set_permissions(
member, read_messages=True, send_messages=True
)
room["invited"].add(member.id)
self.save_rooms()
await ctx.send(
f"{member.mention} has been invited to the game room!",
allowed_mentions=discord.AllowedMentions(users=True),
)
@commands.command(
name="kick",
usage="<user: mention>",
description="Kick a user from your private game room.",
brief="Kick a user from your game room.",
hidden=True,
)
async def kick(self, ctx, member: discord.Member):
"""Kick a user from the game room."""
room = self.active_rooms.get(ctx.channel.id)
if not room or ctx.author.id != room["host"]:
await ctx.reply(
"Only the host can kick others, and only in a private game room channel.",
mention_author=False,
)
return
if member.id == ctx.author.id:
await ctx.reply("You cannot kick yourself (host).", mention_author=False)
return
if member.id not in room["invited"]:
await ctx.reply(
f"{member.mention} is not in this game room.", mention_author=False
)
return
await ctx.channel.set_permissions(member, overwrite=None)
room["invited"].discard(member.id)
self.save_rooms()
await ctx.reply(
f"{member.mention} has been kicked from the game room!",
mention_author=False,
)
@commands.command(
name="end",
aliases=["delete"],
description="End and delete your private game room.",
brief="Delete your game room.",
hidden=True,
)
async def end_gameroom(self, ctx):
"""Allows the host to manually delete the game room channel."""
room = self.active_rooms.get(ctx.channel.id)
if not room or ctx.author.id != room["host"]:
await ctx.reply(
"Only the host can end this game room, and only in a private game room channel.",
mention_author=False,
)
return
await ctx.send("This game room will now be deleted by the host.")
self.delete_room(ctx.channel.id)
await ctx.channel.delete(reason="Game room ended by host")
async def setup(client):
await client.add_cog(Gamble(client))
+216
View File
@@ -0,0 +1,216 @@
import discord
import time
from discord.ext import commands
import logging
from datetime import datetime
from utils.sql_commands import DatabaseManager
# --- Localization dictionary (expand as needed) ---
LOCALE = {
"en": {
"afk_set": "{user} has gone afk.{reason}",
"afk_reason": " Reason: {reason}",
"afk_notify": "{user} is currently unavailable.{reason}",
"feedback_received": "Feedback received. Thank you!",
"missing_perms": "⚠️ The bot is missing the following permissions in **{guild}**: {perms}",
}
}
LANG = "en" # Set your language code here
def _(key, **kwargs):
return LOCALE[LANG][key].format(**kwargs)
class Informational(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager()
async def cog_load(self):
# Bot Permissions Check at startup
for guild in self.client.guilds:
me = guild.me
missing = []
if not me.guild_permissions.manage_roles:
missing.append("Manage Roles")
if not me.guild_permissions.manage_nicknames:
missing.append("Manage Nicknames")
if missing:
owner = guild.owner
try:
await owner.send(
_("missing_perms", guild=guild.name, perms=", ".join(missing))
)
except Exception:
logging.warning(
f"Could not DM owner of {guild.name} about missing permissions."
)
@commands.command(name="whois")
async def _userinfo(self, ctx, member: discord.Member = None): # type: ignore
if member is None:
member = ctx.message.author
roles = [role for role in member.roles][1:]
embed = discord.Embed(
colour=discord.Colour.purple(),
timestamp=ctx.message.created_at,
title=f"User Info - {member}",
)
embed.set_thumbnail(url=member.display_avatar)
embed.set_footer(text=f"Requested by {ctx.author}")
embed.add_field(name="ID:", value=member.id)
embed.add_field(name="Display Name:", value=member.display_name)
embed.add_field(
name="Created Account On:",
value=member.created_at.strftime("%a, %#d %B %Y, %I:%M %p UTC"),
)
embed.add_field(
name="Joined Server On:",
value=member.joined_at.strftime("%a, %#d %B %Y, %I:%M %p UTC"), # type: ignore
)
embed.add_field(name="Roles:", value="".join([role.mention for role in roles]))
embed.add_field(name="Highest Role:", value=member.top_role.mention)
await ctx.send(embed=embed)
@commands.command(name="serverInfo")
@commands.has_permissions(view_audit_log=True) # Example permission
async def _server(self, ctx):
embed = discord.Embed(
title=f"{ctx.guild.name} Info",
description="Information of this Server",
color=discord.Colour.blue(),
)
embed.add_field(name="🆔Server ID", value=f"{ctx.guild.id}", inline=True)
embed.add_field(
name="📆Created On",
value=ctx.guild.created_at.strftime("%b %d %Y"),
inline=True,
)
embed.add_field(name="👑Owner", value=f"{ctx.guild.owner.mention}", inline=True)
embed.add_field(
name="👥Members", value=f"{ctx.guild.member_count} Members", inline=True
)
embed.add_field(
name="💬Channels",
value=f"{len(ctx.guild.text_channels)} Text | {len(ctx.guild.voice_channels)} Voice",
inline=True,
)
embed.set_thumbnail(url=ctx.guild.icon)
embed.set_footer(text="⭐ • Duo")
embed.set_author(name=f"{ctx.author.name}", icon_url=ctx.message.author.avatar)
await ctx.send(embed=embed)
@commands.command(name="afk")
@commands.cooldown(1, 10, commands.BucketType.user)
async def _afk(self, ctx, *reason):
afk_reason = " ".join(reason)
timestamp = datetime.utcnow().isoformat()
# Store AFK reason and timestamp in DB
self.db.execute_query(
"REPLACE INTO afk_status (USERID, GUILDID, REASON, TIMESTAMP) VALUES (%s, %s, %s, %s)",
(ctx.author.id, ctx.guild.id, afk_reason, timestamp),
)
msg = _(
"afk_set",
user=ctx.author.mention,
reason=_("afk_reason", reason=afk_reason) if afk_reason else "",
)
await ctx.send(msg)
# Prevent stacking [AFK]
try:
current_nick = ctx.author.nick or ctx.author.name
if "[AFK]" not in current_nick:
await ctx.author.edit(nick=f"{current_nick} [AFK]")
except discord.Forbidden:
logging.warning(f"Missing permissions to edit nickname for {ctx.author}")
except Exception as e:
logging.error(f"Error editing nickname: {e}")
guild = ctx.guild
try:
role = discord.utils.get(ctx.guild.roles, name="AFK")
if not role:
role = await guild.create_role(name="AFK", hoist=True)
all_roles = await guild.fetch_roles()
num_roles = len(all_roles)
await role.edit(reason=None, position=num_roles - 2)
await ctx.author.add_roles(role)
except discord.Forbidden:
logging.warning("Missing permissions to create/add AFK role.")
except Exception as e:
logging.error(f"Error handling AFK role: {e}")
try:
await ctx.message.delete()
except discord.Forbidden:
pass
@commands.command(name="afklist")
async def afk_list(self, ctx):
afks = self.db.fetch_all(
"SELECT USERID, REASON, TIMESTAMP FROM afk_status WHERE GUILDID = %s",
(ctx.guild.id,),
)
if not afks:
await ctx.reply("No one is AFK right now.")
return
lines = []
for afk in afks:
member = ctx.guild.get_member(int(afk["USERID"]))
reason = afk["REASON"] or "No reason given."
timestamp = afk.get("TIMESTAMP")
# Convert timestamp string to datetime
if isinstance(timestamp, str):
try:
timestamp = datetime.fromisoformat(timestamp)
except Exception:
timestamp = None
if timestamp:
duration = datetime.utcnow() - timestamp
duration_str = str(duration).split(".")[0]
else:
duration_str = "unknown duration"
lines.append(
f"{member.mention if member else afk['USERID']} - {reason} (AFK for {duration_str})"
)
await ctx.send("\n".join(lines))
@commands.command(name="feedback")
@commands.cooldown(1, 30, commands.BucketType.user)
async def _feedback(self, ctx, *info):
feedback_text = " ".join(info)
timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
user = f"{ctx.author} ({ctx.author.id})"
# Store feedback in DB
self.db.execute_query(
"INSERT INTO feedback (USER, GUILDID, TIMESTAMP, CONTENT) VALUES (%s, %s, %s, %s)",
(user, ctx.guild.id if ctx.guild else None, timestamp, feedback_text),
)
await ctx.reply(_("feedback_received"))
@commands.Cog.listener()
async def on_command_error(self, ctx, error):
# Notify admin if permissions are missing
if isinstance(
error, (commands.MissingPermissions, commands.BotMissingPermissions)
):
perms = getattr(error, "missing_perms", None)
perms_str = ", ".join(perms) if perms else "Unknown"
owner = ctx.guild.owner if ctx.guild else None
if owner:
try:
await owner.send(
_("missing_perms", guild=ctx.guild.name, perms=perms_str)
)
except Exception:
logging.warning(
f"Could not DM owner of {ctx.guild.name} about missing permissions."
)
raise error # Let default handler run too
async def setup(client):
await client.add_cog(Informational(client))
+308
View File
@@ -0,0 +1,308 @@
import discord
import time
from datetime import timedelta
from discord.ext import commands
from utils.sql_commands import DatabaseManager
import logging
import json
from collections import defaultdict
async def remove_afk_status(member, db=None):
"""Removes AFK status for a member by resetting their nickname and removing the AFK role and DB entry."""
afk_role = discord.utils.get(member.guild.roles, name="AFK")
bot_member = member.guild.me
changed = False
# Remove AFK role if present
if afk_role and afk_role in member.roles:
try:
await member.remove_roles(afk_role)
changed = True
except discord.Forbidden:
logging.warning(f"Missing permissions to remove AFK role for {member.name}")
except discord.HTTPException as e:
logging.error(f"HTTP error removing AFK role for {member.name}: {e}")
# Remove [AFK] from nickname if possible, not owner, and bot has higher role
try:
if (
member.guild.owner_id != member.id
and member.nick
and "[AFK]" in member.nick
and bot_member.top_role > member.top_role
):
await member.edit(
nick=member.nick.replace("[AFK]", "").replace(" ", " ").strip()
)
changed = True
except discord.Forbidden:
logging.warning(f"Missing permissions to edit nickname for {member.name}")
except Exception as e:
logging.error(f"Error editing nickname: {e}")
# Remove AFK entry from DB if provided
if db:
db.execute_query(
"DELETE FROM afk_status WHERE USERID = %s AND GUILDID = %s",
(member.id, member.guild.id),
)
changed = True
return changed if changed else False
class Listeners(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager()
# Load or initialize stats
self.stats_file = "message_command_stats.json"
self.stats = self.load_stats()
# Add new counters if not present
if "total_messages" not in self.stats:
self.stats["total_messages"] = 0
if "command_messages" not in self.stats:
self.stats["command_messages"] = 0
if "non_command_messages" not in self.stats:
self.stats["non_command_messages"] = 0
def load_stats(self):
try:
with open(self.stats_file, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return {
"messages": defaultdict(int),
"commands": defaultdict(int),
"total_messages": 0,
"command_messages": 0,
"non_command_messages": 0,
}
def save_stats(self):
# Convert defaultdicts to dicts for JSON serialization
stats_to_save = {
"messages": dict(self.stats["messages"]),
"commands": dict(self.stats["commands"]),
"channels": dict(self.stats.get("channels", {})),
"guilds": dict(self.stats.get("guilds", {})),
"total_messages": self.stats.get("total_messages", 0),
"command_messages": self.stats.get("command_messages", 0),
"non_command_messages": self.stats.get("non_command_messages", 0),
}
with open(self.stats_file, "w", encoding="utf-8") as f:
json.dump(stats_to_save, f, indent=2)
@commands.Cog.listener()
async def on_ready(self):
print(f"{self.client.user} has been loaded")
activity = discord.Activity(type=discord.ActivityType.watching, name="You :)")
await self.client.change_presence(
status=discord.Status.online, activity=activity
)
# Record the start time in a more readable format
with open("time.txt", "w") as file:
file.write(str(time.time()))
@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error):
user = ctx.author
if isinstance(error, commands.CommandNotFound):
return
if isinstance(error, (commands.MissingPermissions, commands.NotOwner)):
return await ctx.reply("You cannot use this command.", mention_author=False)
if isinstance(error, commands.MemberNotFound):
return await ctx.reply(
"The member you provided is incorrect or not found.",
mention_author=False,
)
if (
isinstance(error, commands.MissingRequiredArgument)
and ctx.command is not None
):
cmd_parent = ctx.command.parent
cmd_name = (
f"{cmd_parent} {ctx.command.name}" if cmd_parent else ctx.command.name
)
cmd_usage = ctx.command.usage or ""
cmd_params = list(ctx.command.params.values())
params = []
for param in cmd_params[1:]: # Skip the 'self' parameter
log = (
f"<{param.name}>"
if param.default is not param.empty
else f"<{param.name}*>"
) # Required args
params.append(log)
usage = (
f"Usage: `{cmd_name} {' '.join(params)}`"
if not cmd_usage
else f"Usage: `{cmd_usage}`"
)
em = discord.Embed(description=f"**Correct usage**\n{usage}")
if ctx.command.aliases:
em.add_field(name="Aliases", value=", ".join(ctx.command.aliases))
em.set_footer(text="' * ' means that argument is required")
return await ctx.reply(embed=em, mention_author=False)
if isinstance(error, commands.CommandOnCooldown):
time_left = timedelta(seconds=error.retry_after)
return await ctx.reply(
f"You are on cooldown. Try after `{time_left}`.", mention_author=False
)
# For any other error, we will raise it to be handled by the default error handler
raise error
@commands.Cog.listener()
async def on_message(self, message):
if message.author.bot or not message.guild:
return
user_id = str(message.author.id)
if user_id not in self.stats["messages"]:
self.stats["messages"][user_id] = {
"total": 0,
"commands": 0,
"non_commands": 0,
}
self.stats["messages"][user_id]["total"] += 1
# --- Per-channel stats by channel name ---
channel_name = message.channel.name
if "channels" not in self.stats:
self.stats["channels"] = {}
if channel_name not in self.stats["channels"]:
self.stats["channels"][channel_name] = 0
self.stats["channels"][channel_name] += 1
# --- Per-guild stats by guild name ---
guild_name = message.guild.name
if "guilds" not in self.stats:
self.stats["guilds"] = {}
if guild_name not in self.stats["guilds"]:
self.stats["guilds"][guild_name] = 0
self.stats["guilds"][guild_name] += 1
ctx = await self.client.get_context(message)
if ctx.valid and ctx.command:
self.stats["messages"][user_id]["commands"] += 1
else:
self.stats["messages"][user_id]["non_commands"] += 1
self.save_stats()
# Prevent AFK removal on the afk command itself
invoked_afk = False
ctx = await self.client.get_context(message)
if ctx.valid and ctx.command and ctx.command.name == "afk":
invoked_afk = True
if not invoked_afk:
afk_role = discord.utils.get(message.guild.roles, name="AFK")
was_afk = False
# Check DB for AFK status (covers owner and anyone else)
afk_db_entry = self.db.fetch_one(
"SELECT REASON FROM afk_status WHERE USERID = %s AND GUILDID = %s",
(message.author.id, message.guild.id),
)
if afk_db_entry:
was_afk = True
# Remove AFK role if present
if afk_role and afk_role in message.author.roles:
try:
await message.author.remove_roles(afk_role)
except discord.Forbidden:
logging.warning("Missing permissions to remove AFK role.")
except Exception as e:
logging.error(f"Error removing AFK role: {e}")
# Remove [AFK] from nickname if possible and not owner/higher role
try:
bot_member = message.guild.me
if (
message.guild.owner_id != message.author.id
and message.author.nick
and "[AFK]" in message.author.nick
and bot_member.top_role > message.author.top_role
):
await message.author.edit(
nick=message.author.nick.replace("[AFK]", "")
.replace(" ", " ")
.strip()
)
except discord.Forbidden:
logging.warning(
f"Missing permissions to edit nickname for {message.author}"
)
except Exception as e:
logging.error(f"Error editing nickname: {e}")
# Remove AFK entry from DB
self.db.execute_query(
"DELETE FROM afk_status WHERE USERID = %s AND GUILDID = %s",
(message.author.id, message.guild.id),
)
# Send a welcome back message if user was AFK (even owner)
if was_afk:
await message.channel.send(
f"Welcome back, {message.author.mention}! You are no longer AFK."
)
# Notify if mentioned user is AFK (check DB, not just role)
for member in message.mentions:
afk_entry = self.db.fetch_one(
"SELECT REASON FROM afk_status WHERE USERID = %s AND GUILDID = %s",
(member.id, message.guild.id),
)
if afk_entry:
reason = afk_entry["REASON"] if afk_entry["REASON"] else ""
msg = f"{member.mention} is currently unavailable." + (
f" Reason: {reason}" if reason else ""
)
await message.channel.send(msg)
break
@commands.Cog.listener()
async def on_command(self, ctx):
# Track command usage
cmd_name = ctx.command.qualified_name if ctx.command else "unknown"
self.stats["commands"][cmd_name] = self.stats["commands"].get(cmd_name, 0) + 1
self.save_stats()
@commands.Cog.listener()
async def on_guild_join(self, guild):
# Log to console
print(f"Joined new guild: {guild.name} (ID: {guild.id})")
# Optionally, send a message to the first text channel the bot can send messages in
for channel in guild.text_channels:
if channel.permissions_for(guild.me).send_messages:
await channel.send(
f"Hello! Thanks for inviting me to **{guild.name}**.\n"
"Use `py help` to see my commands!"
)
break
# Optionally, update your stats file
if hasattr(self, "stats"):
guild_name = guild.name
if "guilds" not in self.stats:
self.stats["guilds"] = {}
self.stats["guilds"][guild_name] = (
self.stats["guilds"].get(guild_name, 0) + 1
)
self.save_stats()
async def setup(client):
await client.add_cog(Listeners(client))
Executable
+135
View File
@@ -0,0 +1,135 @@
import discord
from discord.ext import commands
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from dotenv import load_dotenv
from os import getenv
import html
from datetime import datetime
class Mail(commands.Cog):
def __init__(self, client):
self.client = client
load_dotenv()
from utils.sql_commands import DatabaseManager
self.db = DatabaseManager()
@commands.is_owner()
@commands.command(name="mail_feedback")
async def mail(self, ctx):
password = getenv("EMAILPASS")
username = getenv("EMAILUSER")
server = getenv("EMAILSERVER")
port = getenv("EMAILPORT")
receiver = getenv("FEEDBACKRECEIVER")
if None in [password, username, server, port, receiver]:
await ctx.reply(
"Email configuration is missing. Please check your environment variables.",
delete_after=5,
)
return
try:
port = int(port) # type: ignore # Error invalid as for problem is taken care of above
s = smtplib.SMTP(host=server, port=port) # type: ignore
s.starttls()
s.login(username, password) # type: ignore
msg = MIMEMultipart("alternative")
msg["To"] = receiver # type: ignore
msg["From"] = username # type: ignore
msg["Subject"] = "Py feedback"
# Fetch feedback from the database
feedback_rows = self.db.fetch_all("SELECT * FROM feedback")
all_feedback = ""
for i, row in enumerate(feedback_rows, 1):
content = html.escape(row["CONTENT"])
user = html.escape(row["USER"])
timestamp = html.escape(row["TIMESTAMP"])
all_feedback += f"""
<li style="margin-bottom:25px; border-bottom:1px solid #edf2f7; padding-bottom:20px;">
<div class="feedback-card" style="background:#ffffff; border-radius:8px; position:relative;">
<div style="display:flex; align-items:center; margin-bottom:12px;">
<div style="background:#4361ee; width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center; color:white; font-weight:bold; flex-shrink:0;">
{i}
</div>
<div style="margin-left:15px;">
<h3 style="margin:0; font-size:16px; color:#2d3748;">{user}</h3>
<p style="margin:3px 0 0; font-size:13px; color:#718096;">{timestamp}</p>
</div>
</div>
<div style="background:#f8f9fc; padding:15px; border-radius:8px; border-left:3px solid #4361ee;">
<p style="margin:0; font-size:15px; line-height:1.5; color:#4a5568;">{content}</p>
</div>
</div>
</li>
"""
text = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Feedback Report</title>
<style>
@media only screen and (max-width: 600px) {{
.container {{
width: 95% !important;
}}
.feedback-card {{
padding: 12px !important;
}}
}}
</style>
</head>
<body style="margin:0; padding:20px 0; background-color:#f7f9fc; font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<div class="container" style="max-width:600px; margin:0 auto; background:#ffffff; border-radius:10px; box-shadow:0 4px 15px rgba(0,0,0,0.05);">
<!-- Header -->
<div style="background: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%); padding:30px 0; border-radius:10px 10px 0 0; text-align:center;">
<h1 style="color:#fff; margin:0; font-weight:600;">User Feedback Report</h1>
<p style="color:rgba(255,255,255,0.8); margin:8px 0 0; font-size:18px;">New feedback submissions</p>
</div>
<!-- Summary -->
<div style="padding:20px 30px; background:#f0f7ff; border-bottom:1px solid #e3f2fd;">
<p style="margin:0; font-size:16px; color:#2d3748;">
Total feedback submissions: <strong>{len(feedback_rows)}</strong>
</p>
</div>
<!-- Feedback Items -->
<div style="padding:10px 30px 30px;">
<ul style="list-style:none; padding:0; margin:0;">
{all_feedback}
</ul>
</div>
<!-- Footer -->
<div style="padding:20px 30px; text-align:center; background:#f8f9fa; border-top:1px solid #eaeaea; border-radius:0 0 10px 10px; color:#718096; font-size:14px;">
<p style="margin:0;">Generated automatically by PyBot • {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
<p style="margin:8px 0 0;">Do not reply to this automated message</p>
</div>
</div>
</body>
</html>
"""
msg.attach(MIMEText(text, "html"))
s.send_message(msg)
await ctx.reply("Mail sent.", delete_after=2)
s.quit()
except Exception as e:
await ctx.reply(f"Failed to send mail: {e}", delete_after=5)
async def setup(client):
await client.add_cog(Mail(client))
Executable
+245
View File
@@ -0,0 +1,245 @@
import discord
from discord.ext import commands
from utils.sql_commands import DatabaseManager
from utils.bank_functions import *
from random import randint
from math import log2
from datetime import datetime
import io
from PIL import Image, ImageDraw, ImageFont
import logging
import os
def calculate_xp_needed_for_next_level(level: int) -> int:
"""Calculates the XP needed for the next level."""
return int(35 * (level**2) + 35 * level)
class XP(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager("")
@commands.Cog.listener()
async def on_message(self, ctx: discord.Message) -> None:
if ctx.author.bot:
return
try:
data = self.db.fetch_one(
"SELECT XP, LEVEL FROM users WHERE ID = %s", (ctx.author.id,)
)
if data is None:
# Insert a new row for this user
current_level = 0
current_xp = 0
self.db.execute_query(
"INSERT INTO users (ID, XP, LEVEL) VALUES (%s, %s, %s)",
(ctx.author.id, current_xp, current_level),
)
else:
current_level = data.get("LEVEL", 0)
current_xp = data.get("XP", 0)
logging.info(f"XP: {current_xp}, Level: {current_level}")
# Balanced XP calculation
base = randint(5, 10)
length_bonus = (
min(len(ctx.content), 100) // 10
) # +1 XP per 10 chars, max +10
extra_xp = base + length_bonus
extra_xp = min(extra_xp, 20) # cap at 20 XP per message
current_xp += extra_xp
if current_xp > calculate_xp_needed_for_next_level(current_level):
current_level += 1
player_balance = self.db.fetch_one(
"SELECT BANK FROM economy WHERE ID = %s", (ctx.author.id,)
)
player_balance = player_balance.get("BANK", 0) if player_balance else 0
levelup_bonus = randint(500, 2000)
new_balance = player_balance + levelup_bonus
await update_money(ctx.author, bank=new_balance)
await ctx.channel.send(
f"Congrats!! You achieved a new level.\nYou are now level {current_level}. You received {levelup_bonus}<:flooney:1194943899765051473>"
)
current_xp = 0
# Use INSERT ... ON DUPLICATE KEY UPDATE for XP/level
self.db.execute_query(
"INSERT INTO users (ID, XP, LEVEL) VALUES (%s, %s, %s) "
"ON DUPLICATE KEY UPDATE XP = %s, LEVEL = %s",
(ctx.author.id, current_xp, current_level, current_xp, current_level),
)
except Exception as e:
logging.error(f"Error in XP on_message: {e}")
@commands.command()
async def top(self, ctx: commands.Context) -> None:
embed = discord.Embed(
colour=discord.Colour.purple(),
timestamp=ctx.message.created_at,
title="Top Messengers",
)
users = self.db.fetch_all("SELECT ID, XP, LEVEL FROM users")
if not users:
return
sorted_users = sorted(users, key=lambda x: int(x["XP"]), reverse=True)[:10]
data = []
for index, member in enumerate(sorted_users, start=1):
try:
member_obj = await self.client.fetch_user(int(member["ID"]))
member_name = (
member_obj.display_name
if hasattr(member_obj, "display_name")
else str(member_obj)
)
except Exception:
member_name = f"User {member['ID']}"
member_xp, member_lvl = int(member["XP"]), int(member["LEVEL"])
medals = ["🥇", "🥈", "🥉"]
msg = f"**{medals[index - 1] if index <= 3 else index} `{member_name}` -- {member_xp:,}xp, {member_lvl:,}level**"
data.append(msg)
msg = "\n".join(data)
embed.description = f"It's Based on XP of Global Users\n\n{msg}"
guild_name = ctx.guild.name if ctx.guild is not None else "Direct Message"
embed.set_footer(text=f"GLOBAL - {guild_name}")
embed.color = discord.Color(0x00FF00)
await ctx.reply(embed=embed, mention_author=False)
@commands.command()
async def stats(self, ctx: commands.Context) -> None:
"""Shows the user's current XP and level."""
try:
# Use a fallback background if the random one doesn't exist
bg_path = f"images/image_{randint(1,100)}.jpg"
if not os.path.exists(bg_path):
bg_path = "images/default.jpg"
profile_image = await self.create_profile_card(ctx.author, bg_path)
await ctx.send(file=discord.File(profile_image, "profile.png"))
if profile_image is not None:
profile_image.close()
except ZeroDivisionError:
await ctx.send(f"{ctx.author.mention}, you haven't earned any XP yet.")
except Exception as e:
await ctx.send(f"Error creating profile card: {e}")
logging.error(f"Error creating profile card: {e}")
async def create_profile_card(
self, user: discord.Member | discord.User, background_image_path: str
) -> io.BytesIO:
"""Create a profile card image for a user with a custom background and text overlay."""
card_width, card_height = 400, 200
text_color = (255, 255, 255)
progress_bar_color = (0, 255, 0)
outline_color = (255, 255, 255)
tint_color = (50, 50, 50)
transparency = 25
opacity = int(255 * transparency / 100)
# Load and resize the background image
try:
background_image = Image.open(background_image_path).resize(
(card_width, card_height)
)
except Exception:
# Fallback to a plain background if image fails
# type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
background_image = Image.new(
"RGBA", (card_width, card_height), (30, 30, 30, 255) # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
)
card_image = Image.new("RGBA", (card_width, card_height))
card_image.paste(background_image, (0, 0))
overlay = Image.new(
"RGBA",
card_image.size,
tint_color + (opacity,), # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
)
card_image = Image.alpha_composite(card_image, overlay)
draw = ImageDraw.Draw(card_image)
# Load fonts with fallback
font_path = "arial.ttf"
try:
font = ImageFont.truetype(font_path, 20)
except Exception:
font = ImageFont.load_default()
draw.text((10, 10), f"User: {user.display_name}", fill=text_color, font=font)
user_info = self.db.fetch_one(
"SELECT XP, LEVEL FROM users WHERE ID = %s", (user.id,)
)
draw.text(
(10, 40), f"Level: {user_info.get('LEVEL', 0)}", fill=text_color, font=font
)
draw.text(
(10, 70),
f"XP: {user_info.get('XP', 0)}/{calculate_xp_needed_for_next_level(user_info.get('LEVEL', 0))}",
fill=text_color,
font=font,
)
draw.text(
(10, 100),
f"Next level in: {calculate_xp_needed_for_next_level(user_info.get('LEVEL', 0))-user_info.get('XP', 0)}XP",
fill=text_color,
font=font,
)
max_xp = calculate_xp_needed_for_next_level(user_info.get("LEVEL", 0))
progress_ratio = user_info.get("XP", 0) / max_xp if max_xp > 0 else 0
progress_width = progress_ratio * 200
draw.rectangle([(10, 130), (210, 150)], outline=outline_color, width=2)
draw.rectangle([(10, 130), (10 + progress_width, 150)], fill=progress_bar_color)
try:
profile_size = 96
margin = 30
if user.avatar is not None:
profile_picture_data = await user.avatar.read()
profile_image = Image.open(io.BytesIO(profile_picture_data)).resize(
(profile_size, profile_size)
)
else:
# Use a default avatar image if user has no avatar
default_avatar_path = "images/default_avatar.png"
if os.path.exists(default_avatar_path):
profile_image = Image.open(default_avatar_path).resize(
(profile_size, profile_size)
)
else:
# Create a blank image if default not found
# type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
profile_image = Image.new(
"RGBA", (profile_size, profile_size), (100, 100, 100, 255) # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
)
card_image.paste(
profile_image,
(card_width - profile_size - margin, margin),
profile_image.convert("RGBA"),
)
except Exception as error:
logging.error(f"Error fetching profile picture: {error}")
image_bytes = io.BytesIO()
card_image.save(image_bytes, format="PNG")
image_bytes.seek(0)
return image_bytes
async def setup(client):
await client.add_cog(XP(client))
Executable
+33837
View File
File diff suppressed because it is too large Load Diff
Executable
+21
View File
@@ -0,0 +1,21 @@
# Use an official Python runtime as a parent image
FROM python:3.12
# Set the working directory in the container
WORKDIR /app
# Copy the requirements.txt to install dependencies
COPY requirements.txt .
# Install the dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application code
COPY . .
# Expose port (if your bot needs to respond to any incoming connections outside of Discord, specify it here; otherwise, this is optional)
# EXPOSE 8080
# Run the bot
CMD ["python", "bot.py"]
Executable
+1
View File
@@ -0,0 +1 @@
hey
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Some files were not shown because too many files have changed in this diff Show More