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 |