First Commit
@@ -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 user’s 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 user’s 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.
|
||||
@@ -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=}")
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
Here’s 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! Here’s 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!
|
||||
@@ -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!
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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"]
|
||||
@@ -0,0 +1 @@
|
||||
hey
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 14 KiB |