Compare commits

..

38 commits

Author SHA1 Message Date
029ed943c8 Update README.md 2026-06-08 10:45:11 +00:00
chersbobers
e69064eaaa Sad 2026-05-08 18:17:29 +12:00
chersbobers
2ff854476c Updated readme for gitpush test and now that im on sr.ht 2026-04-18 22:26:45 +12:00
chersbobers
44067f29e9 Update LICENSE 2026-03-03 04:33:14 +01:00
chersbobers
8c94c8140c Merge pull request 'Adding a license' (#4) from nightly into stable
Reviewed-on: https://codeberg.org/chersbobers/booly/pulls/4
2026-03-02 09:25:40 +01:00
Charlie
26994f4377 Adding a license 2026-03-02 21:20:50 +13:00
chersbobers
18d3676e72 Merge pull request 'More pull requsers' (#3) from nightly into stable
Reviewed-on: https://codeberg.org/chersbobers/booly/pulls/3
2026-03-02 08:21:54 +01:00
Charlie
d0287e2b79 More pull requsers 2026-03-02 20:21:19 +13:00
chersbobers
f691ca5817 Merge pull request 'Fix' (#2) from nightly into stable
Reviewed-on: https://codeberg.org/chersbobers/booly/pulls/2
2026-03-02 08:16:29 +01:00
Charlie
292bde7491 Fix 2026-03-02 20:15:55 +13:00
chersbobers
282561daae Merge pull request 'nightly - stable' (#1) from nightly into stable
Reviewed-on: https://codeberg.org/chersbobers/booly/pulls/1
2026-03-02 08:14:59 +01:00
Charlie
da70f61de7 Update 2026-03-02 20:13:57 +13:00
Charlie
51e5844cee Big mirror update. 2026-03-02 20:12:20 +13:00
Charlie
8ae5c62493 Mirroring to codeberg test 2026-02-07 22:19:15 +13:00
chersbobers
6c378578b8
Merge pull request #6 from chersbobers/nightly
Minor Update
2026-02-07 22:13:53 +13:00
Charlie
9ccdfbf57b Minor Update 2026-02-07 22:12:01 +13:00
chersbobers
59fb76ff83
Merge pull request #5 from chersbobers/nightly
Huge Update | Switched too Digital Ocean
2026-02-07 18:54:47 +13:00
Charlie
bf68c8bcad Huge Update | Switched too Digital Ocean
Signed-off-by: Charlie <chazington7@gmail.com>
2026-02-07 18:53:23 +13:00
chersbobers
755f0f0484
Merge pull request #4 from chersbobers/nightly
URGENT README UPDATES
2026-02-06 16:29:47 +13:00
Charlie
04de7c3d57 Server updates - Broken servers
Signed-off-by: Charlie <chazington7@gmail.com>
2026-02-06 16:28:56 +13:00
Charlie
53075167ae Fixed outdated info before the release of nightly branch. 2026-02-06 16:18:37 +13:00
chersbobers
b75a2d765c
New stable update from 1.0.0 to 1.1.0
New stable update. from 1.0.0 to 1.1.0
2026-02-01 21:40:05 +13:00
Charlie
a2dcda769c Minor reformat 2026-02-01 21:37:08 +13:00
Charlie
44798612f5 Added COPY config.toml . 2026-02-01 21:20:36 +13:00
Charlie
64b1d812cd Add test to see if the config is loading at all in the right dir 2026-02-01 21:15:04 +13:00
Charlie
9438b63c40 Im adding a config file this is part of the new cogly or what ever I call it 2026-02-01 21:10:56 +13:00
Charlie
e70d126a95 Update with pick your own shortcode 2026-01-31 19:21:50 +13:00
Charlie
7578a5be6d Bugfixs 2026-01-31 18:04:52 +13:00
chersbobers
0f369873ac added url shortner to u.chers.moe may change url to u.booly.me when i get it 2026-01-31 18:03:16 +13:00
chersbobers
603dedf344
Merge pull request #1 from chersbobers/nightly
Readme update with new bot link.
2026-01-30 07:23:34 +13:00
Charlie
dde2628570 Readme update with new bot link. 2026-01-30 07:19:52 +13:00
Charlie
8793fc93f3 Readme update moving to nightly branch 2026-01-29 23:15:48 +13:00
Charlie
a4255335df Bugfix in render.yaml 2026-01-28 16:37:28 +13:00
Charlie
cd3621ec88 Hotfix | Removed emojis 2026-01-28 16:30:24 +13:00
Charlie
b8717f0996 Feat | Cogs, slight readme changes - chersbobers :3 2026-01-25 20:06:18 +13:00
Charlie
e3c1c51700 Hotfix | Dockerfile outdated 2026-01-25 20:04:03 +13:00
Charlie
d468ee68a4 Hotfix | Forgot __init__.py 2026-01-25 19:59:50 +13:00
Charlie
5596e854cf Feat | Cogs, slight readme changes - chersbobers :3 2026-01-25 19:57:18 +13:00
14 changed files with 1345 additions and 745 deletions

9
.gitignore vendored
View file

@ -1,13 +1,4 @@
# Data files
data.json data.json
timeouts.json timeouts.json
# Environment files
.env .env
# Luvit files
deps/
lit-*
# Logs
*.log *.log

View file

@ -1,14 +1,12 @@
FROM python:3.11-slim FROM python:3.11-slim
# Set working directory
WORKDIR /app WORKDIR /app
# Copy requirements and install dependencies
COPY requirements.txt . COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
# Copy bot file
COPY bot.py . COPY bot.py .
COPY cogs/ ./cogs/
COPY config.toml .
# Run the bot
CMD ["python", "bot.py"] CMD ["python", "bot.py"]

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 chersbobers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,2 +1,4 @@
# booly # Booly is changing maintainers!
booly is a fork of tooly beacuse og tooly got bad or i might come back
----------------------------------------------------------------------------------------------------
[use at your own risk](oldreadme.md)

758
bot.py
View file

@ -1,32 +1,28 @@
import discord import discord
from discord import app_commands from discord.ext import commands
from discord.ext import commands, tasks
import os import os
import json import json
import random
import asyncio
from datetime import datetime
import aiohttp
import logging import logging
import tomllib
from aiohttp import web from aiohttp import web
import feedparser import asyncio
# Setup logging
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('bot') logger = logging.getLogger('bot')
# Configuration def load_config():
CONFIG = { base_path = os.path.dirname(__file__)
'xp_min': 15, config_path = os.path.join(base_path, "config.toml")
'xp_max': 25, try:
'xp_cooldown': 60, print(f"Loading config from: {config_path}")
'xp_per_level': 100, with open(config_path, "rb") as f:
'level_up_multiplier': 10, return tomllib.load(f)
'data_file': 'data.json', except FileNotFoundError:
'video_check_interval': 300 # 5 minutes logger.error("config.toml missing.")
} exit(1)
CONFIG = load_config()
# Simple JSON Database
class SimpleDB: class SimpleDB:
def __init__(self, filename): def __init__(self, filename):
self.filename = filename self.filename = filename
@ -49,13 +45,8 @@ class SimpleDB:
key = f"{guild_id}_{user_id}" key = f"{guild_id}_{user_id}"
if key not in self.data['users']: if key not in self.data['users']:
self.data['users'][key] = { self.data['users'][key] = {
'coins': 0, 'coins': 0, 'bank': 0, 'level': 1, 'xp': 0,
'bank': 0, 'last_message': 0, 'last_daily': 0, 'last_work': 0
'level': 1,
'xp': 0,
'last_message': 0,
'last_daily': 0,
'last_work': 0
} }
return self.data['users'][key] return self.data['users'][key]
@ -64,16 +55,6 @@ class SimpleDB:
self.data['users'][key] = data self.data['users'][key] = data
self.save() self.save()
def get_all_guild_users(self, guild_id):
users = []
for key, data in self.data['users'].items():
if key.startswith(f"{guild_id}_"):
user_id = key.split('_')[1]
users.append({'user_id': user_id, 'data': data})
users.sort(key=lambda x: (x['data']['level'], x['data']['xp']), reverse=True)
return users
# Bot setup
class MyBot(commands.Bot): class MyBot(commands.Bot):
def __init__(self): def __init__(self):
intents = discord.Intents.default() intents = discord.Intents.default()
@ -81,712 +62,57 @@ class MyBot(commands.Bot):
intents.members = True intents.members = True
super().__init__(command_prefix='/', intents=intents) super().__init__(command_prefix='/', intents=intents)
self.db = SimpleDB(CONFIG['data_file']) self.db = SimpleDB(CONFIG['bot']['data_file'])
self.config = CONFIG
async def setup_hook(self): async def setup_hook(self):
# Start YouTube checker for extension in self.config['bot']['enabled_cogs']:
self.check_youtube.start()
# Sync slash commands
await self.tree.sync()
logger.info('✅ Slash commands synced!')
@tasks.loop(seconds=CONFIG['video_check_interval'])
async def check_youtube(self):
"""Check for new YouTube videos"""
youtube_channel_id = os.getenv('YOUTUBE_CHANNEL_ID')
if not youtube_channel_id:
return
try: try:
feed_url = f'https://www.youtube.com/feeds/videos.xml?channel_id={youtube_channel_id}' await self.load_extension(extension)
logger.info(f'Loaded: {extension}')
# Parse feed
feed = await asyncio.to_thread(feedparser.parse, feed_url)
if not feed.entries:
return
latest = feed.entries[0]
video_id = latest.yt_videoid if hasattr(latest, 'yt_videoid') else latest.id.split(':')[-1]
# Check each guild for notifications
for guild in self.guilds:
guild_id = str(guild.id)
# Get guild settings
if 'youtube' not in self.db.data:
self.db.data['youtube'] = {}
if guild_id not in self.db.data['youtube']:
self.db.data['youtube'][guild_id] = {
'enabled': True,
'channel_id': None,
'last_video_id': None
}
guild_settings = self.db.data['youtube'][guild_id]
# Skip if disabled or no channel set
if not guild_settings['enabled'] or not guild_settings['channel_id']:
continue
# Check if this is a new video
if video_id != guild_settings['last_video_id'] and guild_settings['last_video_id']:
channel = self.get_channel(int(guild_settings['channel_id']))
if channel:
embed = discord.Embed(
title='🎬 New YouTube Video!',
description=f'**{latest.title}**',
url=latest.link,
color=0xFF0000,
timestamp=datetime.utcnow()
)
# Add thumbnail if available
if hasattr(latest, 'media_thumbnail') and latest.media_thumbnail:
embed.set_thumbnail(url=latest.media_thumbnail[0]['url'])
embed.add_field(name='Channel', value=latest.author, inline=True)
# Parse published date
if hasattr(latest, 'published'):
embed.add_field(name='Published', value=latest.published, inline=True)
await channel.send('📺 New video alert! @everyone', embed=embed)
logger.info(f'📺 Sent notification for: {latest.title}')
# Update last video ID
guild_settings['last_video_id'] = video_id
self.db.save()
except Exception as e: except Exception as e:
logger.error(f'❌ Error checking YouTube: {e}') logger.error(f'Error {extension}: {e}')
@check_youtube.before_loop await self.tree.sync()
async def before_check_youtube(self):
await self.wait_until_ready()
bot = MyBot() bot = MyBot()
# Simple HTTP server for Render health checks
async def health_check(request): async def health_check(request):
return web.Response(text="Bot is running! ✅") return web.Response(text="Bot is running!")
async def redirect_handler(request):
code = request.match_info.get('code', '')
if os.path.exists(CONFIG['bot']['data_file']):
try:
with open(CONFIG['bot']['data_file'], 'r') as f:
data = json.load(f)
for guild_id, guild_data in data.get('guilds', {}).items():
if 'urls' in guild_data and code in guild_data['urls']:
return web.Response(status=301, headers={'Location': guild_data['urls'][code]})
except Exception as e:
logger.error(f'Error: {e}')
return web.Response(text='Not Found', status=404)
async def start_web_server(): async def start_web_server():
"""Start a simple web server for Render health checks"""
app = web.Application() app = web.Application()
app.router.add_get('/', health_check) app.router.add_get('/', health_check)
app.router.add_get('/health', health_check) app.router.add_get('/health', health_check)
app.router.add_get('/{code}', redirect_handler)
runner = web.AppRunner(app) runner = web.AppRunner(app)
await runner.setup() await runner.setup()
port = int(os.getenv('PORT', 8080)) port = int(os.getenv('PORT', 8080))
site = web.TCPSite(runner, '0.0.0.0', port) site = web.TCPSite(runner, '0.0.0.0', port)
await site.start() await site.start()
logger.info(f'🌐 Health check server running on port {port}')
# Events
@bot.event @bot.event
async def on_ready(): async def on_ready():
# Start web server for Render
asyncio.create_task(start_web_server()) asyncio.create_task(start_web_server())
logger.info(f'Logged in as {bot.user}')
await bot.change_presence(activity=discord.Game(name="Commands"))
logger.info(f'✅ Logged in as {bot.user}')
logger.info(f'📊 Connected to {len(bot.guilds)} guilds')
await bot.change_presence(activity=discord.Game(name="/help"))
logger.info('🚀 All systems operational!')
@bot.event
async def on_message(message):
if message.author.bot or not message.guild:
return
# XP System
user_data = bot.db.get_user(str(message.guild.id), str(message.author.id))
now = datetime.now().timestamp()
if now - user_data['last_message'] >= CONFIG['xp_cooldown']:
user_data['last_message'] = now
xp_gain = random.randint(CONFIG['xp_min'], CONFIG['xp_max'])
user_data['xp'] += xp_gain
xp_needed = user_data['level'] * CONFIG['xp_per_level']
if user_data['xp'] >= xp_needed:
user_data['level'] += 1
user_data['xp'] = 0
coin_reward = user_data['level'] * CONFIG['level_up_multiplier']
user_data['coins'] += coin_reward
messages = [
f'🎉 GG {message.author.mention}! You leveled up to **Level {user_data["level"]}**!',
f'⭐ Congrats {message.author.mention}! You\'re now **Level {user_data["level"]}**!',
f'🚀 Level up! {message.author.mention} reached **Level {user_data["level"]}**!'
]
await message.channel.send(
f'{random.choice(messages)} You earned **{coin_reward:,} coins**! 💰'
)
bot.db.set_user(str(message.guild.id), str(message.author.id), user_data)
await bot.process_commands(message)
# Slash Commands
@bot.tree.command(name='help', description='Show all available commands')
async def help_command(interaction: discord.Interaction):
embed = discord.Embed(
title='🤖 Bot Commands',
description='Here are all the slash commands you can use!',
color=0x5865F2
)
embed.add_field(
name='📊 Leveling',
value='`/rank` - View your rank\n`/leaderboard` - Top 10 users',
inline=False
)
embed.add_field(
name='💰 Economy',
value='`/balance` - Check balance\n`/daily` - Daily reward\n`/work` - Work for coins',
inline=False
)
embed.add_field(
name='🎮 Fun',
value='`/8ball` - Magic 8ball\n`/roll` - Roll dice\n`/flip` - Flip coin\n`/cat` - Random cat\n`/dog` - Random dog',
inline=False
)
embed.add_field(
name=' Info',
value='`/ping` - Check bot latency\n`/serverinfo` - Server information',
inline=False
)
await interaction.response.send_message(embed=embed)
@bot.tree.command(name='ping', description='Check bot latency')
async def ping(interaction: discord.Interaction):
latency = round(bot.latency * 1000)
await interaction.response.send_message(f'🏓 Pong! Latency: `{latency}ms`')
@bot.tree.command(name='rank', description='View your rank and level')
async def rank(interaction: discord.Interaction, member: discord.Member = None):
target = member or interaction.user
user_data = bot.db.get_user(str(interaction.guild.id), str(target.id))
xp_needed = user_data['level'] * CONFIG['xp_per_level']
all_users = bot.db.get_all_guild_users(str(interaction.guild.id))
rank = next((i + 1 for i, u in enumerate(all_users) if u['user_id'] == str(target.id)), 'Unranked')
progress = int((user_data['xp'] / xp_needed) * 20) if xp_needed > 0 else 0
bar = '' * progress + '' * (20 - progress)
color = 0xFF6B6B if user_data['level'] >= 50 else 0xFFD93D if user_data['level'] >= 30 else 0x6BCB77 if user_data['level'] >= 15 else 0x4D96FF
embed = discord.Embed(color=color)
embed.set_author(name=f"{target.display_name}'s Profile", icon_url=target.display_avatar.url)
embed.description = f"""
**RANK** #{rank} / {len(all_users)}
**LEVEL** {user_data['level']}
**XP** {user_data['xp']:,} / {xp_needed:,}
`{bar}`
**💰 BALANCE** {user_data['coins']:,} coins
"""
embed.set_thumbnail(url=target.display_avatar.url)
await interaction.response.send_message(embed=embed)
@bot.tree.command(name='leaderboard', description='View the server leaderboard')
async def leaderboard(interaction: discord.Interaction):
all_users = bot.db.get_all_guild_users(str(interaction.guild.id))[:10]
description = []
for i, u in enumerate(all_users):
medal = '🥇' if i == 0 else '🥈' if i == 1 else '🥉' if i == 2 else f'**{i+1}.**'
description.append(
f'{medal} <@{u["user_id"]}>\n'
f'└ Level {u["data"]["level"]} ({u["data"]["xp"]:,} XP) • {u["data"]["coins"]:,} coins'
)
embed = discord.Embed(
title='🏆 Server Leaderboard',
description='\n'.join(description) if description else 'No users yet!',
color=0x9B59B6,
timestamp=datetime.utcnow()
)
embed.set_footer(text='Top 10 users by level and XP')
await interaction.response.send_message(embed=embed)
@bot.tree.command(name='balance', description='Check your coin balance')
async def balance(interaction: discord.Interaction, member: discord.Member = None):
target = member or interaction.user
user_data = bot.db.get_user(str(interaction.guild.id), str(target.id))
embed = discord.Embed(
title='💰 Balance',
description=f'{target.mention} has **{user_data["coins"]:,}** coins in wallet and **{user_data["bank"]:,}** in bank!\n**Total:** {user_data["coins"] + user_data["bank"]:,} coins',
color=0xFFD700
)
await interaction.response.send_message(embed=embed)
@bot.tree.command(name='daily', description='Claim your daily reward')
async def daily(interaction: discord.Interaction):
user_data = bot.db.get_user(str(interaction.guild.id), str(interaction.user.id))
now = datetime.now().timestamp()
if now - user_data['last_daily'] < 86400:
time_left = 86400 - (now - user_data['last_daily'])
hours = int(time_left / 3600)
await interaction.response.send_message(f'⏳ You already claimed your daily! Come back in {hours} hours.', ephemeral=True)
return
reward = 100
user_data['coins'] += reward
user_data['last_daily'] = now
bot.db.set_user(str(interaction.guild.id), str(interaction.user.id), user_data)
await interaction.response.send_message(f'✅ You claimed your daily reward of **{reward:,}** coins!\n💰 New balance: **{user_data["coins"]:,}** coins')
@bot.tree.command(name='work', description='Work to earn coins')
async def work(interaction: discord.Interaction):
user_data = bot.db.get_user(str(interaction.guild.id), str(interaction.user.id))
now = datetime.now().timestamp()
if now - user_data['last_work'] < 3600:
time_left = 3600 - (now - user_data['last_work'])
minutes = int(time_left / 60)
await interaction.response.send_message(f'⏳ You need to rest! Come back in {minutes} minutes.', ephemeral=True)
return
earnings = random.randint(10, 50)
user_data['coins'] += earnings
user_data['last_work'] = now
bot.db.set_user(str(interaction.guild.id), str(interaction.user.id), user_data)
jobs = [
'You worked as a programmer and earned',
'You delivered pizza and earned',
'You streamed on Twitch and earned',
'You mowed lawns and earned',
'You washed cars and earned',
'You tutored students and earned'
]
await interaction.response.send_message(f'💼 {random.choice(jobs)} **{earnings:,}** coins!\n💰 New balance: **{user_data["coins"]:,}** coins')
@bot.tree.command(name='8ball', description='Ask the magic 8ball a question')
async def eightball(interaction: discord.Interaction, question: str):
responses = [
'Yes, definitely!', 'It is certain.', 'Without a doubt.', 'You may rely on it.',
'As I see it, yes.', 'Most likely.', 'Outlook good.', 'Signs point to yes.',
'Reply hazy, try again.', 'Ask again later.', 'Better not tell you now.',
'Cannot predict now.', "Don't count on it.", 'My reply is no.', 'Very doubtful.'
]
await interaction.response.send_message(f'🔮 **{question}**\n{random.choice(responses)}')
@bot.tree.command(name='roll', description='Roll a dice')
async def roll(interaction: discord.Interaction, sides: int = 6):
if sides < 2 or sides > 100:
await interaction.response.send_message('❌ Dice must have between 2 and 100 sides!', ephemeral=True)
return
result = random.randint(1, sides)
await interaction.response.send_message(f'🎲 You rolled a **{result}** (1-{sides})')
@bot.tree.command(name='flip', description='Flip a coin')
async def flip(interaction: discord.Interaction):
result = random.choice(['Heads', 'Tails'])
await interaction.response.send_message(f'🪙 The coin landed on **{result}**!')
@bot.tree.command(name='cat', description='Get a random cat picture')
async def cat(interaction: discord.Interaction):
await interaction.response.defer()
async with aiohttp.ClientSession() as session:
try:
async with session.get('https://api.thecatapi.com/v1/images/search') as resp:
data = await resp.json()
embed = discord.Embed(title='🐱 Random Kitty!', color=0xFF69B4)
embed.set_image(url=data[0]['url'])
embed.set_footer(text=f'Requested by {interaction.user.name}')
await interaction.followup.send(embed=embed)
except:
await interaction.followup.send('Failed to fetch a cat picture 😿')
@bot.tree.command(name='dog', description='Get a random dog picture')
async def dog(interaction: discord.Interaction):
await interaction.response.defer()
async with aiohttp.ClientSession() as session:
try:
async with session.get('https://api.thedogapi.com/v1/images/search') as resp:
data = await resp.json()
embed = discord.Embed(title='🐶 Random Doggy!', color=0xFF69B4)
embed.set_image(url=data[0]['url'])
embed.set_footer(text=f'Requested by {interaction.user.name}')
await interaction.followup.send(embed=embed)
except:
await interaction.followup.send('Failed to fetch a dog picture 😥')
@bot.tree.command(name='serverinfo', description='Display server information')
async def serverinfo(interaction: discord.Interaction):
guild = interaction.guild
embed = discord.Embed(
title=f'📊 {guild.name}',
color=0x5865F2,
timestamp=datetime.utcnow()
)
if guild.icon:
embed.set_thumbnail(url=guild.icon.url)
embed.add_field(name='👑 Owner', value=guild.owner.mention, inline=True)
embed.add_field(name='👥 Members', value=guild.member_count, inline=True)
embed.add_field(name='📅 Created', value=guild.created_at.strftime('%Y-%m-%d'), inline=True)
embed.add_field(name='💬 Channels', value=len(guild.channels), inline=True)
embed.add_field(name='😀 Emojis', value=len(guild.emojis), inline=True)
embed.add_field(name='🎭 Roles', value=len(guild.roles), inline=True)
await interaction.response.send_message(embed=embed)
# Reaction Roles
@bot.event
async def on_raw_reaction_add(payload):
if payload.user_id == bot.user.id:
return
# Get reaction role data
guild_id = str(payload.guild_id)
message_id = str(payload.message_id)
if 'reaction_roles' not in bot.db.data:
bot.db.data['reaction_roles'] = {}
if guild_id not in bot.db.data['reaction_roles']:
return
if message_id not in bot.db.data['reaction_roles'][guild_id]:
return
emoji_str = str(payload.emoji)
role_id = bot.db.data['reaction_roles'][guild_id][message_id].get(emoji_str)
if not role_id:
return
guild = bot.get_guild(payload.guild_id)
if not guild:
return
role = guild.get_role(int(role_id))
if not role:
return
member = guild.get_member(payload.user_id)
if not member:
return
try:
await member.add_roles(role)
logger.info(f'✅ Added role {role.name} to {member.name}')
except Exception as e:
logger.error(f'❌ Error adding role: {e}')
@bot.event
async def on_raw_reaction_remove(payload):
if payload.user_id == bot.user.id:
return
guild_id = str(payload.guild_id)
message_id = str(payload.message_id)
if 'reaction_roles' not in bot.db.data:
return
if guild_id not in bot.db.data.get('reaction_roles', {}):
return
if message_id not in bot.db.data['reaction_roles'][guild_id]:
return
emoji_str = str(payload.emoji)
role_id = bot.db.data['reaction_roles'][guild_id][message_id].get(emoji_str)
if not role_id:
return
guild = bot.get_guild(payload.guild_id)
if not guild:
return
role = guild.get_role(int(role_id))
if not role:
return
member = guild.get_member(payload.user_id)
if not member:
return
try:
await member.remove_roles(role)
logger.info(f' Removed role {role.name} from {member.name}')
except Exception as e:
logger.error(f'❌ Error removing role: {e}')
@bot.tree.command(name='reactionrole', description='[ADMIN] Create a reaction role')
@app_commands.describe(
message_id='Message ID to add reactions to',
emoji='Emoji to use (e.g., 🎮)',
role='Role to assign'
)
@app_commands.default_permissions(administrator=True)
async def reactionrole(interaction: discord.Interaction, message_id: str, emoji: str, role: discord.Role):
try:
message = await interaction.channel.fetch_message(int(message_id))
except discord.NotFound:
await interaction.response.send_message('❌ Message not found in this channel!', ephemeral=True)
return
except ValueError:
await interaction.response.send_message('❌ Invalid message ID!', ephemeral=True)
return
try:
await message.add_reaction(emoji)
except discord.HTTPException:
await interaction.response.send_message('❌ Invalid emoji or unable to add reaction!', ephemeral=True)
return
# Save reaction role
guild_id = str(interaction.guild.id)
if 'reaction_roles' not in bot.db.data:
bot.db.data['reaction_roles'] = {}
if guild_id not in bot.db.data['reaction_roles']:
bot.db.data['reaction_roles'][guild_id] = {}
if message_id not in bot.db.data['reaction_roles'][guild_id]:
bot.db.data['reaction_roles'][guild_id][message_id] = {}
bot.db.data['reaction_roles'][guild_id][message_id][emoji] = str(role.id)
bot.db.save()
embed = discord.Embed(
title='✅ Reaction Role Created',
description=f'React with {emoji} on the message to get {role.mention}',
color=0x00FF00
)
embed.add_field(name='Message ID', value=message_id, inline=True)
embed.add_field(name='Emoji', value=emoji, inline=True)
embed.add_field(name='Role', value=role.mention, inline=True)
await interaction.response.send_message(embed=embed)
logger.info(f'✅ Created reaction role: {emoji} -> {role.name}')
@bot.tree.command(name='removereactionrole', description='[ADMIN] Remove a reaction role')
@app_commands.describe(
message_id='Message ID',
emoji='Emoji to remove (leave empty to remove all)'
)
@app_commands.default_permissions(administrator=True)
async def removereactionrole(interaction: discord.Interaction, message_id: str, emoji: str = None):
guild_id = str(interaction.guild.id)
if 'reaction_roles' not in bot.db.data:
bot.db.data['reaction_roles'] = {}
if guild_id not in bot.db.data['reaction_roles'] or message_id not in bot.db.data['reaction_roles'].get(guild_id, {}):
await interaction.response.send_message('❌ No reaction roles found for that message!', ephemeral=True)
return
if emoji:
if emoji not in bot.db.data['reaction_roles'][guild_id][message_id]:
await interaction.response.send_message('❌ That emoji is not set up for reaction roles!', ephemeral=True)
return
del bot.db.data['reaction_roles'][guild_id][message_id][emoji]
bot.db.save()
await interaction.response.send_message(f'✅ Removed reaction role for {emoji}')
else:
del bot.db.data['reaction_roles'][guild_id][message_id]
bot.db.save()
await interaction.response.send_message(f'✅ Removed all reaction roles from message {message_id}')
@bot.tree.command(name='listreactionroles', description='List all reaction roles')
async def listreactionroles(interaction: discord.Interaction):
guild_id = str(interaction.guild.id)
if 'reaction_roles' not in bot.db.data:
bot.db.data['reaction_roles'] = {}
guild_reactions = bot.db.data['reaction_roles'].get(guild_id, {})
if not guild_reactions:
await interaction.response.send_message('No reaction roles configured yet!')
return
embed = discord.Embed(
title='🎭 Reaction Roles',
color=0x9B59B6
)
for msg_id, reactions in guild_reactions.items():
roles_text = []
for emoji, role_id in reactions.items():
role = interaction.guild.get_role(int(role_id))
role_name = role.mention if role else f'Role ID: {role_id}'
roles_text.append(f'{emoji}{role_name}')
embed.add_field(
name=f'Message ID: {msg_id}',
value='\n'.join(roles_text) if roles_text else 'No reactions',
inline=False
)
await interaction.response.send_message(embed=embed)
@bot.tree.command(name='createreactionpanel', description='[ADMIN] Create a reaction role panel')
@app_commands.describe(
title='Panel title',
description='Panel description'
)
@app_commands.default_permissions(administrator=True)
async def createreactionpanel(interaction: discord.Interaction, title: str, description: str):
embed = discord.Embed(
title=f'🎭 {title}',
description=description,
color=0x9B59B6
)
embed.set_footer(text='React below to get your roles!')
message = await interaction.channel.send(embed=embed)
await interaction.response.send_message(
f'✅ Panel created! Message ID: `{message.id}`\n'
f'Use `/reactionrole {message.id} <emoji> <role>` to add roles to it.',
ephemeral=True
)
# YouTube Notifications Commands
@bot.tree.command(name='setupyoutube', description='[ADMIN] Set up YouTube notifications in this channel')
@app_commands.default_permissions(administrator=True)
async def setupyoutube(interaction: discord.Interaction):
guild_id = str(interaction.guild.id)
if 'youtube' not in bot.db.data:
bot.db.data['youtube'] = {}
if guild_id not in bot.db.data['youtube']:
bot.db.data['youtube'][guild_id] = {
'enabled': True,
'channel_id': None,
'last_video_id': None
}
bot.db.data['youtube'][guild_id]['channel_id'] = str(interaction.channel.id)
bot.db.data['youtube'][guild_id]['enabled'] = True
bot.db.save()
youtube_channel_id = os.getenv('YOUTUBE_CHANNEL_ID')
if not youtube_channel_id:
await interaction.response.send_message(
'⚠️ YouTube notifications set up in this channel, but `YOUTUBE_CHANNEL_ID` environment variable is not set!\n'
'Ask the bot owner to set it on Render.',
ephemeral=True
)
return
embed = discord.Embed(
title='✅ YouTube Notifications Enabled',
description=f'New video notifications will be posted in {interaction.channel.mention}',
color=0xFF0000
)
embed.add_field(name='Check Interval', value='Every 5 minutes', inline=True)
embed.add_field(name='Status', value='🟢 Active', inline=True)
await interaction.response.send_message(embed=embed)
logger.info(f'✅ YouTube notifications enabled in {interaction.guild.name} → #{interaction.channel.name}')
@bot.tree.command(name='toggleyoutube', description='[ADMIN] Toggle YouTube notifications on/off')
@app_commands.default_permissions(administrator=True)
async def toggleyoutube(interaction: discord.Interaction):
guild_id = str(interaction.guild.id)
if 'youtube' not in bot.db.data:
bot.db.data['youtube'] = {}
if guild_id not in bot.db.data['youtube']:
bot.db.data['youtube'][guild_id] = {
'enabled': False,
'channel_id': None,
'last_video_id': None
}
# Toggle
bot.db.data['youtube'][guild_id]['enabled'] = not bot.db.data['youtube'][guild_id].get('enabled', False)
bot.db.save()
status = 'enabled ✅' if bot.db.data['youtube'][guild_id]['enabled'] else 'disabled ❌'
color = 0x00FF00 if bot.db.data['youtube'][guild_id]['enabled'] else 0x808080
embed = discord.Embed(
title='🔔 YouTube Notifications',
description=f'YouTube notifications are now **{status}**',
color=color,
timestamp=datetime.utcnow()
)
await interaction.response.send_message(embed=embed)
@bot.tree.command(name='youtubestatus', description='Check YouTube notification status')
async def youtubestatus(interaction: discord.Interaction):
guild_id = str(interaction.guild.id)
if 'youtube' not in bot.db.data:
bot.db.data['youtube'] = {}
settings = bot.db.data['youtube'].get(guild_id, {
'enabled': False,
'channel_id': None,
'last_video_id': None
})
youtube_channel_id = os.getenv('YOUTUBE_CHANNEL_ID')
embed = discord.Embed(
title='📺 YouTube Notification Status',
color=0xFF0000
)
status = '🟢 Enabled' if settings.get('enabled') else '🔴 Disabled'
embed.add_field(name='Status', value=status, inline=True)
if settings.get('channel_id'):
channel = interaction.guild.get_channel(int(settings['channel_id']))
channel_name = channel.mention if channel else 'Channel not found'
embed.add_field(name='Notification Channel', value=channel_name, inline=True)
else:
embed.add_field(name='Notification Channel', value='Not set', inline=True)
if youtube_channel_id:
embed.add_field(name='YouTube Channel ID', value=f'`{youtube_channel_id}`', inline=False)
else:
embed.add_field(name='⚠️ Warning', value='`YOUTUBE_CHANNEL_ID` not set in environment variables', inline=False)
if settings.get('last_video_id'):
embed.add_field(name='Last Video ID', value=f'`{settings["last_video_id"]}`', inline=False)
embed.set_footer(text='Use /setupyoutube to configure')
await interaction.response.send_message(embed=embed)
# Run bot
if __name__ == '__main__': if __name__ == '__main__':
token = os.getenv('DISCORD_TOKEN') token = os.getenv('DISCORD_TOKEN')
if not token: if not token:
logger.error('DISCORD_TOKEN not set!') logger.error('DISCORD_TOKEN not set')
exit(1) exit(1)
logger.info('🚀 Starting bot...')
bot.run(token) bot.run(token)

0
cogs/__init__.py Normal file
View file

69
cogs/economy.py Normal file
View file

@ -0,0 +1,69 @@
import discord
from discord import app_commands
from discord.ext import commands
import random
from datetime import datetime
class Economy(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name='balance', description='Check your coin balance')
async def balance(self, interaction: discord.Interaction, member: discord.Member = None):
target = member or interaction.user
user_data = self.bot.db.get_user(str(interaction.guild.id), str(target.id))
embed = discord.Embed(
title='Balance',
description=f'{target.mention} has **{user_data["coins"]:,}** coins in wallet and **{user_data["bank"]:,}** in bank!\n**Total:** {user_data["coins"] + user_data["bank"]:,} coins',
color=0xFFD700
)
await interaction.response.send_message(embed=embed)
@app_commands.command(name='daily', description='Claim your daily reward')
async def daily(self, interaction: discord.Interaction):
user_data = self.bot.db.get_user(str(interaction.guild.id), str(interaction.user.id))
now = datetime.now().timestamp()
if now - user_data['last_daily'] < 86400:
time_left = 86400 - (now - user_data['last_daily'])
hours = int(time_left / 3600)
await interaction.response.send_message(f'You already claimed your daily! Come back in {hours} hours.', ephemeral=True)
return
reward = 100
user_data['coins'] += reward
user_data['last_daily'] = now
self.bot.db.set_user(str(interaction.guild.id), str(interaction.user.id), user_data)
await interaction.response.send_message(f'You claimed your daily reward of **{reward:,}** coins!\n💰 New balance: **{user_data["coins"]:,}** coins')
@app_commands.command(name='work', description='Work to earn coins')
async def work(self, interaction: discord.Interaction):
user_data = self.bot.db.get_user(str(interaction.guild.id), str(interaction.user.id))
now = datetime.now().timestamp()
if now - user_data['last_work'] < 3600:
time_left = 3600 - (now - user_data['last_work'])
minutes = int(time_left / 60)
await interaction.response.send_message(f'You need to rest! Come back in {minutes} minutes.', ephemeral=True)
return
earnings = random.randint(10, 50)
user_data['coins'] += earnings
user_data['last_work'] = now
self.bot.db.set_user(str(interaction.guild.id), str(interaction.user.id), user_data)
jobs = [
'You worked as a programmer and earned',
'You delivered pizza and earned',
'You streamed on Twitch and earned',
'You mowed lawns and earned',
'You washed cars and earned',
'You tutored students and earned'
]
await interaction.response.send_message(f'{random.choice(jobs)} **{earnings:,}** coins!\n💰 New balance: **{user_data["coins"]:,}** coins')
async def setup(bot):
await bot.add_cog(Economy(bot))

63
cogs/fun.py Normal file
View file

@ -0,0 +1,63 @@
import discord
from discord import app_commands
from discord.ext import commands
import random
import aiohttp
class Fun(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name='8ball', description='Ask the magic 8ball a question')
async def eightball(self, interaction: discord.Interaction, question: str):
responses = [
'Yes, definitely!', 'It is certain.', 'Without a doubt.', 'You may rely on it.',
'As I see it, yes.', 'Most likely.', 'Outlook good.', 'Signs point to yes.',
'Reply hazy, try again.', 'Ask again later.', 'Better not tell you now.',
'Cannot predict now.', "Don't count on it.", 'My reply is no.', 'Very doubtful.'
]
await interaction.response.send_message(f' **{question}**\n{random.choice(responses)}')
@app_commands.command(name='roll', description='Roll a dice')
async def roll(self, interaction: discord.Interaction, sides: int = 6):
if sides < 2 or sides > 100:
await interaction.response.send_message('Dice must have between 2 and 100 sides!', ephemeral=True)
return
result = random.randint(1, sides)
await interaction.response.send_message(f' You rolled a **{result}** (1-{sides})')
@app_commands.command(name='flip', description='Flip a coin')
async def flip(self, interaction: discord.Interaction):
result = random.choice(['Heads', 'Tails'])
await interaction.response.send_message(f'The coin landed on **{result}**!')
@app_commands.command(name='cat', description='Get a random cat picture')
async def cat(self, interaction: discord.Interaction):
await interaction.response.defer()
async with aiohttp.ClientSession() as session:
try:
async with session.get('https://api.thecatapi.com/v1/images/search') as resp:
data = await resp.json()
embed = discord.Embed(title='Random Kitty!', color=0xFF69B4)
embed.set_image(url=data[0]['url'])
embed.set_footer(text=f'Requested by {interaction.user.name}')
await interaction.followup.send(embed=embed)
except:
await interaction.followup.send('Failed to fetch a cat picture ')
@app_commands.command(name='dog', description='Get a random dog picture')
async def dog(self, interaction: discord.Interaction):
await interaction.response.defer()
async with aiohttp.ClientSession() as session:
try:
async with session.get('https://api.thedogapi.com/v1/images/search') as resp:
data = await resp.json()
embed = discord.Embed(title='Random Doggy!', color=0xFF69B4)
embed.set_image(url=data[0]['url'])
embed.set_footer(text=f'Requested by {interaction.user.name}')
await interaction.followup.send(embed=embed)
except:
await interaction.followup.send('Failed to fetch a dog picture ')
async def setup(bot):
await bot.add_cog(Fun(bot))

94
cogs/leveling.py Normal file
View file

@ -0,0 +1,94 @@
import discord
from discord import app_commands
from discord.ext import commands
import random
from datetime import datetime
class Leveling(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.Cog.listener()
async def on_message(self, message):
if message.author.bot or not message.guild:
return
user_data = self.bot.db.get_user(str(message.guild.id), str(message.author.id))
now = datetime.now().timestamp()
if now - user_data['last_message'] >= self.bot.config['xp_cooldown']:
user_data['last_message'] = now
xp_gain = random.randint(self.bot.config['xp_min'], self.bot.config['xp_max'])
user_data['xp'] += xp_gain
xp_needed = user_data['level'] * self.bot.config['xp_per_level']
if user_data['xp'] >= xp_needed:
user_data['level'] += 1
user_data['xp'] = 0
coin_reward = user_data['level'] * self.bot.config['level_up_multiplier']
user_data['coins'] += coin_reward
messages = [
f'gg {message.author.mention}! You leveled up to **Level {user_data["level"]}**!',
f'Congrats {message.author.mention}! You\'re now **Level {user_data["level"]}**!',
f'Level up! {message.author.mention} reached **Level {user_data["level"]}**!'
]
await message.channel.send(
f'{random.choice(messages)} You earned **{coin_reward:,} coins**! '
)
self.bot.db.set_user(str(message.guild.id), str(message.author.id), user_data)
@app_commands.command(name='rank', description='View your rank and level')
async def rank(self, interaction: discord.Interaction, member: discord.Member = None):
target = member or interaction.user
user_data = self.bot.db.get_user(str(interaction.guild.id), str(target.id))
xp_needed = user_data['level'] * self.bot.config['xp_per_level']
all_users = self.bot.db.get_all_guild_users(str(interaction.guild.id))
rank = next((i + 1 for i, u in enumerate(all_users) if u['user_id'] == str(target.id)), 'Unranked')
progress = int((user_data['xp'] / xp_needed) * 20) if xp_needed > 0 else 0
bar = '' * progress + '' * (20 - progress)
color = 0xFF6B6B if user_data['level'] >= 50 else 0xFFD93D if user_data['level'] >= 30 else 0x6BCB77 if user_data['level'] >= 15 else 0x4D96FF
embed = discord.Embed(color=color)
embed.set_author(name=f"{target.display_name}'s Profile", icon_url=target.display_avatar.url)
embed.description = f"""
**RANK** #{rank} / {len(all_users)}
**LEVEL** {user_data['level']}
**XP** {user_data['xp']:,} / {xp_needed:,}
`{bar}`
**BALANCE** {user_data['coins']:,} coins
"""
embed.set_thumbnail(url=target.display_avatar.url)
await interaction.response.send_message(embed=embed)
@app_commands.command(name='leaderboard', description='View the server leaderboard')
async def leaderboard(self, interaction: discord.Interaction):
all_users = self.bot.db.get_all_guild_users(str(interaction.guild.id))[:10]
description = []
for i, u in enumerate(all_users):
medal = '🥇' if i == 0 else '🥈' if i == 1 else '🥉' if i == 2 else f'**{i+1}.**'
description.append(
f'{medal} <@{u["user_id"]}>\n'
f'└ Level {u["data"]["level"]} ({u["data"]["xp"]:,} XP) • {u["data"]["coins"]:,} coins'
)
embed = discord.Embed(
title='Server Leaderboard',
description='\n'.join(description) if description else 'No users yet!',
color=0x9B59B6,
timestamp=datetime.utcnow()
)
embed.set_footer(text='Top 10 users by level and XP')
await interaction.response.send_message(embed=embed)
async def setup(bot):
await bot.add_cog(Leveling(bot))

801
cogs/system.py Normal file
View file

@ -0,0 +1,801 @@
import discord
from discord import app_commands
from discord.ext import commands, tasks
import asyncio
from datetime import datetime, timedelta
import logging
import feedparser
logger = logging.getLogger('bot')
class System(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.check_youtube.start()
def cog_unload(self):
self.check_youtube.cancel()
@tasks.loop(seconds=300)
async def check_youtube(self):
if 'youtube' not in self.bot.db.data:
return
for guild_id, settings in self.bot.db.data['youtube'].items():
if not settings.get('enabled') or not settings.get('channel_id') or not settings.get('youtube_channel_id'):
continue
try:
feed_url = f'https://www.youtube.com/feeds/videos.xml?channel_id={settings["youtube_channel_id"]}'
feed = await asyncio.to_thread(feedparser.parse, feed_url)
if not feed.entries:
continue
latest = feed.entries[0]
video_id = latest.yt_videoid if hasattr(latest, 'yt_videoid') else latest.id.split(':')[-1]
if video_id != settings.get('last_video_id') and settings.get('last_video_id'):
guild = self.bot.get_guild(int(guild_id))
if not guild:
continue
channel = guild.get_channel(int(settings['channel_id']))
if not channel:
continue
embed = discord.Embed(
title='New YouTube Video!',
description=f'**{latest.title}**',
url=latest.link,
color=0xFF0000,
timestamp=datetime.utcnow()
)
if hasattr(latest, 'media_thumbnail') and latest.media_thumbnail:
embed.set_thumbnail(url=latest.media_thumbnail[0]['url'])
embed.add_field(name='Channel', value=latest.author, inline=True)
if hasattr(latest, 'published'):
embed.add_field(name='Published', value=latest.published, inline=True)
await channel.send('New video alert! @everyone', embed=embed)
logger.info(f'Sent notification for: {latest.title}')
settings['last_video_id'] = video_id
self.bot.db.save()
except Exception as e:
logger.error(f'Error checking YouTube: {e}')
@check_youtube.before_loop
async def before_check_youtube(self):
await self.bot.wait_until_ready()
@app_commands.command(name='ping', description='Check bot latency')
async def ping(self, interaction: discord.Interaction):
latency = round(self.bot.latency * 1000)
await interaction.response.send_message(f'Pong! Latency: `{latency}ms`')
@app_commands.command(name='serverinfo', description='Display server information')
async def serverinfo(self, interaction: discord.Interaction):
guild = interaction.guild
embed = discord.Embed(
title=f'{guild.name}',
color=0x5865F2,
timestamp=datetime.utcnow()
)
if guild.icon:
embed.set_thumbnail(url=guild.icon.url)
embed.add_field(name='Owner', value=guild.owner.mention, inline=True)
embed.add_field(name='Members', value=guild.member_count, inline=True)
embed.add_field(name='Created', value=guild.created_at.strftime('%Y-%m-%d'), inline=True)
embed.add_field(name='Channels', value=len(guild.channels), inline=True)
embed.add_field(name='Emojis', value=len(guild.emojis), inline=True)
embed.add_field(name='Roles', value=len(guild.roles), inline=True)
await interaction.response.send_message(embed=embed)
@app_commands.command(name='userinfo', description='Display user information')
async def userinfo(self, interaction: discord.Interaction, member: discord.Member = None):
target = member or interaction.user
embed = discord.Embed(
title=f'{target.name}',
color=target.color if target.color != discord.Color.default() else 0x5865F2,
timestamp=datetime.utcnow()
)
embed.set_thumbnail(url=target.display_avatar.url)
embed.add_field(name='ID', value=target.id, inline=True)
embed.add_field(name='Nickname', value=target.nick or 'None', inline=True)
embed.add_field(name='Bot', value='Yes' if target.bot else 'No', inline=True)
embed.add_field(name='Joined Server', value=target.joined_at.strftime('%Y-%m-%d %H:%M') if target.joined_at else 'Unknown', inline=True)
embed.add_field(name='Account Created', value=target.created_at.strftime('%Y-%m-%d %H:%M'), inline=True)
roles = [role.mention for role in target.roles if role.name != '@everyone']
embed.add_field(name=f'Roles ({len(roles)})', value=' '.join(roles) if roles else 'None', inline=False)
await interaction.response.send_message(embed=embed)
@app_commands.command(name='kick', description='[MOD] Kick a member from the server')
@app_commands.describe(member='Member to kick', reason='Reason for kick')
@app_commands.default_permissions(kick_members=True)
async def kick(self, interaction: discord.Interaction, member: discord.Member, reason: str = 'No reason provided'):
if member.top_role >= interaction.user.top_role and interaction.user != interaction.guild.owner:
await interaction.response.send_message('You cannot kick this member!', ephemeral=True)
return
if member == interaction.guild.owner:
await interaction.response.send_message('You cannot kick the server owner!', ephemeral=True)
return
try:
await member.kick(reason=f'{reason} | Kicked by {interaction.user}')
embed = discord.Embed(
title='Member Kicked',
description=f'{member.mention} has been kicked from the server',
color=0xFF9500,
timestamp=datetime.utcnow()
)
embed.add_field(name='Moderator', value=interaction.user.mention, inline=True)
embed.add_field(name='Reason', value=reason, inline=False)
await interaction.response.send_message(embed=embed)
logger.info(f'{member} kicked by {interaction.user} - Reason: {reason}')
except discord.Forbidden:
await interaction.response.send_message('I do not have permission to kick this member!', ephemeral=True)
except Exception as e:
await interaction.response.send_message(f'An error occurred: {e}', ephemeral=True)
@app_commands.command(name='ban', description='[MOD] Ban a member from the server')
@app_commands.describe(member='Member to ban', reason='Reason for ban', delete_days='Days of messages to delete (0-7)')
@app_commands.default_permissions(ban_members=True)
async def ban(self, interaction: discord.Interaction, member: discord.Member, reason: str = 'No reason provided', delete_days: int = 0):
if member.top_role >= interaction.user.top_role and interaction.user != interaction.guild.owner:
await interaction.response.send_message('You cannot ban this member!', ephemeral=True)
return
if member == interaction.guild.owner:
await interaction.response.send_message('You cannot ban the server owner!', ephemeral=True)
return
if delete_days < 0 or delete_days > 7:
await interaction.response.send_message('Delete days must be between 0 and 7!', ephemeral=True)
return
try:
await member.ban(reason=f'{reason} | Banned by {interaction.user}', delete_message_days=delete_days)
embed = discord.Embed(
title='Member Banned',
description=f'{member.mention} has been banned from the server',
color=0xFF0000,
timestamp=datetime.utcnow()
)
embed.add_field(name='Moderator', value=interaction.user.mention, inline=True)
embed.add_field(name='Reason', value=reason, inline=False)
await interaction.response.send_message(embed=embed)
logger.info(f'{member} banned by {interaction.user} - Reason: {reason}')
except discord.Forbidden:
await interaction.response.send_message('I do not have permission to ban this member!', ephemeral=True)
except Exception as e:
await interaction.response.send_message(f'An error occurred: {e}', ephemeral=True)
@app_commands.command(name='unban', description='[MOD] Unban a user from the server')
@app_commands.describe(user_id='User ID to unban', reason='Reason for unban')
@app_commands.default_permissions(ban_members=True)
async def unban(self, interaction: discord.Interaction, user_id: str, reason: str = 'No reason provided'):
try:
user = await self.bot.fetch_user(int(user_id))
except:
await interaction.response.send_message('Invalid user ID!', ephemeral=True)
return
try:
await interaction.guild.unban(user, reason=f'{reason} | Unbanned by {interaction.user}')
embed = discord.Embed(
title='User Unbanned',
description=f'{user.mention} has been unbanned from the server',
color=0x00FF00,
timestamp=datetime.utcnow()
)
embed.add_field(name='Moderator', value=interaction.user.mention, inline=True)
embed.add_field(name='Reason', value=reason, inline=False)
await interaction.response.send_message(embed=embed)
logger.info(f'{user} unbanned by {interaction.user} - Reason: {reason}')
except discord.NotFound:
await interaction.response.send_message('This user is not banned!', ephemeral=True)
except discord.Forbidden:
await interaction.response.send_message('I do not have permission to unban users!', ephemeral=True)
except Exception as e:
await interaction.response.send_message(f'An error occurred: {e}', ephemeral=True)
@app_commands.command(name='timeout', description='[MOD] Timeout a member')
@app_commands.describe(member='Member to timeout', duration='Duration in minutes', reason='Reason for timeout')
@app_commands.default_permissions(moderate_members=True)
async def timeout(self, interaction: discord.Interaction, member: discord.Member, duration: int, reason: str = 'No reason provided'):
if member.top_role >= interaction.user.top_role and interaction.user != interaction.guild.owner:
await interaction.response.send_message('You cannot timeout this member!', ephemeral=True)
return
if member == interaction.guild.owner:
await interaction.response.send_message('You cannot timeout the server owner!', ephemeral=True)
return
if duration < 1 or duration > 40320:
await interaction.response.send_message('Duration must be between 1 minute and 28 days!', ephemeral=True)
return
try:
timeout_until = datetime.utcnow() + timedelta(minutes=duration)
await member.timeout(timeout_until, reason=f'{reason} | Timed out by {interaction.user}')
embed = discord.Embed(
title='Member Timed Out',
description=f'{member.mention} has been timed out',
color=0xFFA500,
timestamp=datetime.utcnow()
)
embed.add_field(name='Moderator', value=interaction.user.mention, inline=True)
embed.add_field(name='Duration', value=f'{duration} minutes', inline=True)
embed.add_field(name='Reason', value=reason, inline=False)
await interaction.response.send_message(embed=embed)
logger.info(f'{member} timed out by {interaction.user} for {duration}m - Reason: {reason}')
except discord.Forbidden:
await interaction.response.send_message('I do not have permission to timeout this member!', ephemeral=True)
except Exception as e:
await interaction.response.send_message(f'An error occurred: {e}', ephemeral=True)
@app_commands.command(name='warn', description='[MOD] Warn a member')
@app_commands.describe(member='Member to warn', reason='Reason for warning')
@app_commands.default_permissions(moderate_members=True)
async def warn(self, interaction: discord.Interaction, member: discord.Member, reason: str):
guild_id = str(interaction.guild.id)
user_id = str(member.id)
if 'warnings' not in self.bot.db.data:
self.bot.db.data['warnings'] = {}
if guild_id not in self.bot.db.data['warnings']:
self.bot.db.data['warnings'][guild_id] = {}
if user_id not in self.bot.db.data['warnings'][guild_id]:
self.bot.db.data['warnings'][guild_id][user_id] = []
warning = {
'reason': reason,
'moderator': str(interaction.user.id),
'timestamp': datetime.utcnow().isoformat()
}
self.bot.db.data['warnings'][guild_id][user_id].append(warning)
self.bot.db.save()
warning_count = len(self.bot.db.data['warnings'][guild_id][user_id])
embed = discord.Embed(
title='Member Warned',
description=f'{member.mention} has been warned',
color=0xFFFF00,
timestamp=datetime.utcnow()
)
embed.add_field(name='Moderator', value=interaction.user.mention, inline=True)
embed.add_field(name='Total Warnings', value=warning_count, inline=True)
embed.add_field(name='Reason', value=reason, inline=False)
await interaction.response.send_message(embed=embed)
try:
await member.send(f'You have been warned in **{interaction.guild.name}**\n**Reason:** {reason}\n**Total Warnings:** {warning_count}')
except:
pass
logger.info(f'{member} warned by {interaction.user} - Reason: {reason}')
@app_commands.command(name='warnings', description='View warnings for a member')
@app_commands.describe(member='Member to check warnings for')
async def warnings(self, interaction: discord.Interaction, member: discord.Member = None):
target = member or interaction.user
guild_id = str(interaction.guild.id)
user_id = str(target.id)
if 'warnings' not in self.bot.db.data:
self.bot.db.data['warnings'] = {}
user_warnings = self.bot.db.data['warnings'].get(guild_id, {}).get(user_id, [])
if not user_warnings:
await interaction.response.send_message(f'{target.mention} has no warnings!', ephemeral=True)
return
embed = discord.Embed(
title=f'Warnings for {target.display_name}',
color=0xFFFF00,
timestamp=datetime.utcnow()
)
for i, warning in enumerate(user_warnings[-10:], 1):
moderator = interaction.guild.get_member(int(warning['moderator']))
mod_name = moderator.mention if moderator else f"<@{warning['moderator']}>"
timestamp = datetime.fromisoformat(warning['timestamp']).strftime('%Y-%m-%d %H:%M')
embed.add_field(
name=f'Warning #{i}',
value=f'**Moderator:** {mod_name}\n**Reason:** {warning["reason"]}\n**Date:** {timestamp}',
inline=False
)
embed.set_footer(text=f'Total warnings: {len(user_warnings)} | Showing last 10')
await interaction.response.send_message(embed=embed)
@app_commands.command(name='clearwarnings', description='[MOD] Clear warnings for a member')
@app_commands.describe(member='Member to clear warnings for')
@app_commands.default_permissions(moderate_members=True)
async def clearwarnings(self, interaction: discord.Interaction, member: discord.Member):
guild_id = str(interaction.guild.id)
user_id = str(member.id)
if 'warnings' not in self.bot.db.data:
self.bot.db.data['warnings'] = {}
if guild_id not in self.bot.db.data['warnings'] or user_id not in self.bot.db.data['warnings'][guild_id]:
await interaction.response.send_message(f'{member.mention} has no warnings to clear!', ephemeral=True)
return
warning_count = len(self.bot.db.data['warnings'][guild_id][user_id])
self.bot.db.data['warnings'][guild_id][user_id] = []
self.bot.db.save()
embed = discord.Embed(
title='Warnings Cleared',
description=f'Cleared {warning_count} warning(s) for {member.mention}',
color=0x00FF00,
timestamp=datetime.utcnow()
)
embed.add_field(name='Moderator', value=interaction.user.mention, inline=True)
await interaction.response.send_message(embed=embed)
logger.info(f'Cleared {warning_count} warnings for {member} by {interaction.user}')
@app_commands.command(name='purge', description='[MOD] Delete multiple messages')
@app_commands.describe(amount='Number of messages to delete (1-100)', member='Only delete messages from this member (optional)')
@app_commands.default_permissions(manage_messages=True)
async def purge(self, interaction: discord.Interaction, amount: int, member: discord.Member = None):
if amount < 1 or amount > 100:
await interaction.response.send_message('Amount must be between 1 and 100!', ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
try:
if member:
deleted = await interaction.channel.purge(limit=amount, check=lambda m: m.author == member)
else:
deleted = await interaction.channel.purge(limit=amount)
await interaction.followup.send(f'Deleted {len(deleted)} message(s)!', ephemeral=True)
logger.info(f'{interaction.user} purged {len(deleted)} messages in #{interaction.channel.name}')
except discord.Forbidden:
await interaction.followup.send('I do not have permission to delete messages!', ephemeral=True)
except Exception as e:
await interaction.followup.send(f'An error occurred: {e}', ephemeral=True)
@app_commands.command(name='lock', description='[MOD] Lock a channel')
@app_commands.describe(channel='Channel to lock (defaults to current channel)')
@app_commands.default_permissions(manage_channels=True)
async def lock(self, interaction: discord.Interaction, channel: discord.TextChannel = None):
target_channel = channel or interaction.channel
try:
await target_channel.set_permissions(interaction.guild.default_role, send_messages=False)
embed = discord.Embed(
title='Channel Locked',
description=f'{target_channel.mention} has been locked',
color=0xFF0000,
timestamp=datetime.utcnow()
)
embed.add_field(name='Moderator', value=interaction.user.mention, inline=True)
await interaction.response.send_message(embed=embed)
logger.info(f'{target_channel.name} locked by {interaction.user}')
except discord.Forbidden:
await interaction.response.send_message('I do not have permission to lock this channel!', ephemeral=True)
except Exception as e:
await interaction.response.send_message(f'An error occurred: {e}', ephemeral=True)
@app_commands.command(name='unlock', description='[MOD] Unlock a channel')
@app_commands.describe(channel='Channel to unlock (defaults to current channel)')
@app_commands.default_permissions(manage_channels=True)
async def unlock(self, interaction: discord.Interaction, channel: discord.TextChannel = None):
target_channel = channel or interaction.channel
try:
await target_channel.set_permissions(interaction.guild.default_role, send_messages=None)
embed = discord.Embed(
title='Channel Unlocked',
description=f'{target_channel.mention} has been unlocked',
color=0x00FF00,
timestamp=datetime.utcnow()
)
embed.add_field(name='Moderator', value=interaction.user.mention, inline=True)
await interaction.response.send_message(embed=embed)
logger.info(f'{target_channel.name} unlocked by {interaction.user}')
except discord.Forbidden:
await interaction.response.send_message('I do not have permission to unlock this channel!', ephemeral=True)
except Exception as e:
await interaction.response.send_message(f'An error occurred: {e}', ephemeral=True)
@commands.Cog.listener()
async def on_raw_reaction_add(self, payload):
if payload.user_id == self.bot.user.id:
return
guild_id = str(payload.guild_id)
message_id = str(payload.message_id)
if 'reaction_roles' not in self.bot.db.data:
self.bot.db.data['reaction_roles'] = {}
if guild_id not in self.bot.db.data['reaction_roles']:
return
if message_id not in self.bot.db.data['reaction_roles'][guild_id]:
return
emoji_str = str(payload.emoji)
role_id = self.bot.db.data['reaction_roles'][guild_id][message_id].get(emoji_str)
if not role_id:
return
guild = self.bot.get_guild(payload.guild_id)
if not guild:
return
role = guild.get_role(int(role_id))
if not role:
return
member = guild.get_member(payload.user_id)
if not member:
return
try:
await member.add_roles(role)
logger.info(f'Added role {role.name} to {member.name}')
except Exception as e:
logger.error(f'Error adding role: {e}')
@commands.Cog.listener()
async def on_raw_reaction_remove(self, payload):
if payload.user_id == self.bot.user.id:
return
guild_id = str(payload.guild_id)
message_id = str(payload.message_id)
if 'reaction_roles' not in self.bot.db.data:
return
if guild_id not in self.bot.db.data.get('reaction_roles', {}):
return
if message_id not in self.bot.db.data['reaction_roles'][guild_id]:
return
emoji_str = str(payload.emoji)
role_id = self.bot.db.data['reaction_roles'][guild_id][message_id].get(emoji_str)
if not role_id:
return
guild = self.bot.get_guild(payload.guild_id)
if not guild:
return
role = guild.get_role(int(role_id))
if not role:
return
member = guild.get_member(payload.user_id)
if not member:
return
try:
await member.remove_roles(role)
logger.info(f'Removed role {role.name} from {member.name}')
except Exception as e:
logger.error(f'Error removing role: {e}')
@app_commands.command(name='reactionrole', description='[ADMIN] Create a reaction role')
@app_commands.describe(message_id='Message ID to add reactions to', emoji='Emoji to use (e.g., 🎮)', role='Role to assign')
@app_commands.default_permissions(administrator=True)
async def reactionrole(self, interaction: discord.Interaction, message_id: str, emoji: str, role: discord.Role):
try:
message = await interaction.channel.fetch_message(int(message_id))
except discord.NotFound:
await interaction.response.send_message('Message not found in this channel!', ephemeral=True)
return
except ValueError:
await interaction.response.send_message('Invalid message ID!', ephemeral=True)
return
try:
await message.add_reaction(emoji)
except discord.HTTPException:
await interaction.response.send_message('Invalid emoji or unable to add reaction!', ephemeral=True)
return
guild_id = str(interaction.guild.id)
if 'reaction_roles' not in self.bot.db.data:
self.bot.db.data['reaction_roles'] = {}
if guild_id not in self.bot.db.data['reaction_roles']:
self.bot.db.data['reaction_roles'][guild_id] = {}
if message_id not in self.bot.db.data['reaction_roles'][guild_id]:
self.bot.db.data['reaction_roles'][guild_id][message_id] = {}
self.bot.db.data['reaction_roles'][guild_id][message_id][emoji] = str(role.id)
self.bot.db.save()
embed = discord.Embed(
title='Reaction Role Created',
description=f'React with {emoji} on the message to get {role.mention}',
color=0x00FF00
)
embed.add_field(name='Message ID', value=message_id, inline=True)
embed.add_field(name='Emoji', value=emoji, inline=True)
embed.add_field(name='Role', value=role.mention, inline=True)
await interaction.response.send_message(embed=embed)
logger.info(f'Created reaction role: {emoji} -> {role.name}')
@app_commands.command(name='removereactionrole', description='[ADMIN] Remove a reaction role')
@app_commands.describe(message_id='Message ID', emoji='Emoji to remove (leave empty to remove all)')
@app_commands.default_permissions(administrator=True)
async def removereactionrole(self, interaction: discord.Interaction, message_id: str, emoji: str = None):
guild_id = str(interaction.guild.id)
if 'reaction_roles' not in self.bot.db.data:
self.bot.db.data['reaction_roles'] = {}
if guild_id not in self.bot.db.data['reaction_roles'] or message_id not in self.bot.db.data['reaction_roles'].get(guild_id, {}):
await interaction.response.send_message('No reaction roles found for that message!', ephemeral=True)
return
if emoji:
if emoji not in self.bot.db.data['reaction_roles'][guild_id][message_id]:
await interaction.response.send_message('That emoji is not set up for reaction roles!', ephemeral=True)
return
del self.bot.db.data['reaction_roles'][guild_id][message_id][emoji]
self.bot.db.save()
await interaction.response.send_message(f'Removed reaction role for {emoji}')
else:
del self.bot.db.data['reaction_roles'][guild_id][message_id]
self.bot.db.save()
await interaction.response.send_message(f'Removed all reaction roles from message {message_id}')
@app_commands.command(name='listreactionroles', description='List all reaction roles')
async def listreactionroles(self, interaction: discord.Interaction):
guild_id = str(interaction.guild.id)
if 'reaction_roles' not in self.bot.db.data:
self.bot.db.data['reaction_roles'] = {}
guild_reactions = self.bot.db.data['reaction_roles'].get(guild_id, {})
if not guild_reactions:
await interaction.response.send_message('No reaction roles configured yet!')
return
embed = discord.Embed(
title='Reaction Roles',
color=0x9B59B6
)
for msg_id, reactions in guild_reactions.items():
roles_text = []
for emoji, role_id in reactions.items():
role = interaction.guild.get_role(int(role_id))
role_name = role.mention if role else f'Role ID: {role_id}'
roles_text.append(f'{emoji}{role_name}')
embed.add_field(
name=f'Message ID: {msg_id}',
value='\n'.join(roles_text) if roles_text else 'No reactions',
inline=False
)
await interaction.response.send_message(embed=embed)
@app_commands.command(name='createreactionpanel', description='[ADMIN] Create a reaction role panel')
@app_commands.describe(title='Panel title', description='Panel description')
@app_commands.default_permissions(administrator=True)
async def createreactionpanel(self, interaction: discord.Interaction, title: str, description: str):
embed = discord.Embed(
title=f'{title}',
description=description,
color=0x9B59B6
)
embed.set_footer(text='React below to get your roles!')
message = await interaction.channel.send(embed=embed)
await interaction.response.send_message(
f'Panel created! Message ID: `{message.id}`\n'
f'Use `/reactionrole {message.id} <emoji> <role>` to add roles to it.',
ephemeral=True
)
@app_commands.command(name='setupyoutube', description='[ADMIN] Set up YouTube notifications')
@app_commands.describe(youtube_channel_id='YouTube Channel ID (from channel URL)', notification_channel='Discord channel for notifications (defaults to current channel)')
@app_commands.default_permissions(administrator=True)
async def setupyoutube(self, interaction: discord.Interaction, youtube_channel_id: str, notification_channel: discord.TextChannel = None):
guild_id = str(interaction.guild.id)
channel = notification_channel or interaction.channel
if 'youtube' not in self.bot.db.data:
self.bot.db.data['youtube'] = {}
self.bot.db.data['youtube'][guild_id] = {
'enabled': True,
'channel_id': str(channel.id),
'youtube_channel_id': youtube_channel_id.strip(),
'last_video_id': None
}
self.bot.db.save()
embed = discord.Embed(
title='YouTube Notifications Configured',
description=f'New video notifications will be posted in {channel.mention}',
color=0xFF0000
)
embed.add_field(name='YouTube Channel ID', value=f'`{youtube_channel_id}`', inline=False)
embed.add_field(name='Check Interval', value='Every 5 minutes', inline=True)
embed.add_field(name='Status', value='Active', inline=True)
await interaction.response.send_message(embed=embed)
logger.info(f'YouTube notifications enabled in {interaction.guild.name} → #{channel.name}')
@app_commands.command(name='toggleyoutube', description='[ADMIN] Toggle YouTube notifications on/off')
@app_commands.default_permissions(administrator=True)
async def toggleyoutube(self, interaction: discord.Interaction):
guild_id = str(interaction.guild.id)
if 'youtube' not in self.bot.db.data:
self.bot.db.data['youtube'] = {}
if guild_id not in self.bot.db.data['youtube']:
self.bot.db.data['youtube'][guild_id] = {
'enabled': False,
'channel_id': None,
'last_video_id': None
}
self.bot.db.data['youtube'][guild_id]['enabled'] = not self.bot.db.data['youtube'][guild_id].get('enabled', False)
self.bot.db.save()
status = 'enabled' if self.bot.db.data['youtube'][guild_id]['enabled'] else 'disabled'
color = 0x00FF00 if self.bot.db.data['youtube'][guild_id]['enabled'] else 0x808080
embed = discord.Embed(
title='YouTube Notifications',
description=f'YouTube notifications are now **{status}**',
color=color,
timestamp=datetime.utcnow()
)
await interaction.response.send_message(embed=embed)
@app_commands.command(name='youtubestatus', description='Check YouTube notification status')
async def youtubestatus(self, interaction: discord.Interaction):
guild_id = str(interaction.guild.id)
if 'youtube' not in self.bot.db.data:
self.bot.db.data['youtube'] = {}
settings = self.bot.db.data['youtube'].get(guild_id, {
'enabled': False,
'channel_id': None,
'last_video_id': None
})
embed = discord.Embed(
title='YouTube Notification Status',
color=0xFF0000
)
status = 'Enabled' if settings.get('enabled') else 'Disabled'
embed.add_field(name='Status', value=status, inline=True)
if settings.get('channel_id'):
channel = interaction.guild.get_channel(int(settings['channel_id']))
channel_name = channel.mention if channel else 'Channel not found'
embed.add_field(name='Notification Channel', value=channel_name, inline=True)
else:
embed.add_field(name='Notification Channel', value='Not set', inline=True)
if settings.get('youtube_channel_id'):
embed.add_field(name='YouTube Channel ID', value=f'`{settings["youtube_channel_id"]}`', inline=False)
else:
embed.add_field(name='Warning', value='YouTube Channel ID not set', inline=False)
if settings.get('last_video_id'):
embed.add_field(name='Last Video ID', value=f'`{settings["last_video_id"]}`', inline=False)
embed.set_footer(text='Use /setupyoutube to configure')
await interaction.response.send_message(embed=embed)
@app_commands.command(name='testlastvideo', description='[ADMIN] Manually announce the latest YouTube video')
@app_commands.default_permissions(administrator=True)
async def testlastvideo(self, interaction: discord.Interaction):
guild_id = str(interaction.guild.id)
if 'youtube' not in self.bot.db.data:
self.bot.db.data['youtube'] = {}
settings = self.bot.db.data['youtube'].get(guild_id, {})
if not settings.get('youtube_channel_id'):
await interaction.response.send_message('YouTube Channel ID not configured! Use `/setupyoutube` first.', ephemeral=True)
return
await interaction.response.defer()
try:
feed_url = f'https://www.youtube.com/feeds/videos.xml?channel_id={settings["youtube_channel_id"]}'
feed = await asyncio.to_thread(feedparser.parse, feed_url)
if not feed.entries:
await interaction.followup.send('No videos found for this channel!')
return
latest = feed.entries[0]
video_id = latest.yt_videoid if hasattr(latest, 'yt_videoid') else latest.id.split(':')[-1]
embed = discord.Embed(
title='Latest YouTube Video (Test)',
description=f'**{latest.title}**',
url=latest.link,
color=0xFF0000,
timestamp=datetime.utcnow()
)
if hasattr(latest, 'media_thumbnail') and latest.media_thumbnail:
embed.set_thumbnail(url=latest.media_thumbnail[0]['url'])
embed.add_field(name='Channel', value=latest.author, inline=True)
if hasattr(latest, 'published'):
embed.add_field(name='Published', value=latest.published, inline=True)
embed.set_footer(text='This is a test notification')
await interaction.followup.send(embed=embed)
logger.info(f'Test notification sent for: {latest.title}')
except Exception as e:
await interaction.followup.send(f'Error fetching video: {e}')
logger.error(f'Error in testlastvideo: {e}')
async def setup(bot):
await bot.add_cog(System(bot))

143
cogs/utility.py Normal file
View file

@ -0,0 +1,143 @@
import discord
from discord.ext import commands
from discord import app_commands
import re
import random
import string
class Utility(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.domain = "u.chers.moe"
def is_valid_url(self, url):
url_pattern = re.compile(
r'^https?://'
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
r'localhost|'
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
r'(?::\d+)?'
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
return url_pattern.match(url) is not None
def generate_short_code(self, length=6):
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(length))
def get_guild_data(self, guild_id):
if 'guilds' not in self.bot.db.data:
self.bot.db.data['guilds'] = {}
if str(guild_id) not in self.bot.db.data['guilds']:
self.bot.db.data['guilds'][str(guild_id)] = {'urls': {}}
if 'urls' not in self.bot.db.data['guilds'][str(guild_id)]:
self.bot.db.data['guilds'][str(guild_id)]['urls'] = {}
return self.bot.db.data['guilds'][str(guild_id)]
@app_commands.command(name='shorten', description='Shorten a long URL')
@app_commands.describe(
url='The URL you want to shorten',
code='Custom short code (optional)'
)
async def shorten_url(self, interaction: discord.Interaction, url: str, code: str = None):
if not self.is_valid_url(url):
await interaction.response.send_message(
'Please provide a valid URL',
ephemeral=True
)
return
guild_data = self.get_guild_data(interaction.guild_id)
for existing_code, stored_url in guild_data['urls'].items():
if stored_url == url:
shortened = f"https://{self.domain}/{existing_code}"
embed = discord.Embed(title='URL Already Shortened', color=0x5865F2)
embed.add_field(name='Original', value=url[:100] + ('...' if len(url) > 100 else ''), inline=False)
embed.add_field(name='Shortened', value=shortened, inline=False)
await interaction.response.send_message(embed=embed)
return
if code:
if code in guild_data['urls']:
await interaction.response.send_message(
'This short code is already taken',
ephemeral=True
)
return
else:
code = self.generate_short_code()
while code in guild_data['urls']:
code = self.generate_short_code()
guild_data['urls'][code] = url
self.bot.db.save()
shortened = f"https://{self.domain}/{code}"
embed = discord.Embed(title='URL Shortened', color=0x5865F2)
embed.add_field(name='Original', value=url[:100] + ('...' if len(url) > 100 else ''), inline=False)
embed.add_field(name='Shortened', value=shortened, inline=False)
embed.add_field(name='Code', value=code, inline=False)
await interaction.response.send_message(embed=embed)
@app_commands.command(name='expand', description='Get the original URL from a short code')
@app_commands.describe(code='The short code to expand')
async def expand_url(self, interaction: discord.Interaction, code: str):
guild_data = self.get_guild_data(interaction.guild_id)
if code in guild_data['urls']:
original_url = guild_data['urls'][code]
embed = discord.Embed(title='URL Expanded', color=0x5865F2)
embed.add_field(name='Code', value=code, inline=False)
embed.add_field(name='Original URL', value=original_url, inline=False)
await interaction.response.send_message(embed=embed)
else:
await interaction.response.send_message('Short code not found', ephemeral=True)
@app_commands.command(name='listshort', description='List all shortened URLs')
async def list_short(self, interaction: discord.Interaction):
guild_data = self.get_guild_data(interaction.guild_id)
if not guild_data['urls']:
await interaction.response.send_message('No shortened URLs found', ephemeral=True)
return
embed = discord.Embed(title='Shortened URLs', color=0x5865F2)
count = 0
for code, url in guild_data['urls'].items():
if count >= 25:
break
shortened = f"https://{self.domain}/{code}"
embed.add_field(
name=code,
value=f"{url[:50]}{'...' if len(url) > 50 else ''}\n{shortened}",
inline=False
)
count += 1
if len(guild_data['urls']) > 25:
embed.set_footer(text=f'Showing 25 of {len(guild_data["urls"])} URLs')
await interaction.response.send_message(embed=embed)
@app_commands.command(name='deleteshort', description='Delete a shortened URL')
@app_commands.describe(code='The short code to delete')
async def delete_short(self, interaction: discord.Interaction, code: str):
guild_data = self.get_guild_data(interaction.guild_id)
if code in guild_data['urls']:
url = guild_data['urls'][code]
del guild_data['urls'][code]
self.bot.db.save()
embed = discord.Embed(title='URL Deleted', color=0x5865F2)
embed.add_field(name='Code', value=code, inline=False)
embed.add_field(name='Original URL', value=url, inline=False)
await interaction.response.send_message(embed=embed)
else:
await interaction.response.send_message('Short code not found', ephemeral=True)
async def setup(bot):
await bot.add_cog(Utility(bot))

23
config.toml Normal file
View file

@ -0,0 +1,23 @@
# this is the deafult config file
[bot]
data_file = "data.json"
# this allows you to pick your cogs if installing multiple
enabled_cogs = [
"cogs.leveling",
"cogs.system",
"cogs.economy",
"cogs.fun",
"cogs.utility"
]
# this might not even need a comment but xp
[xp]
min = 15
max = 25
cooldown = 60
per_level = 100
level_up_multiplier = 10
# port and how many times it checks for videos
[web]
video_check_interval = 300
port = 3000

81
oldreadme.md Normal file
View file

@ -0,0 +1,81 @@
# NOT UPDATED!
<div align="center">
<img src="https://files.catbox.moe/zuj0ob.png" alt="Booly" width="100%">
<img src="https://img.shields.io/badge/discord.py-2.0+-5865F2?style=flat&logo=discord&logoColor=white" alt="discord.py">
<img src="https://img.shields.io/badge/Python-3.8+-3776AB?style=flat&logo=python&logoColor=white" alt="python">
</div>
# Overview
Booly the bot your server deserves.
# Installation
there is 2 methods of install:
1. <s>prehosted 100 servers limit running on digital ocean (1 GB Memory / 1 Intel vCPU / 35 GB Disk) </s> Prehosted is down I can't be bothered to pay tax on it.
2. self host it
## Prehosted
[__Click me for invite__](__https://discord.com/oauth2/authorize?client_id=1466398693383995558&permissions=8&integration_type=0&scope=bot+applications.commands__)
notes: until I give discord my id 100 servers can have this more may be added.
## Self hosting
I recommended using the main stable repo (https://github.com/chersbobers/booly) for yours but the nightly branch is usable too (https://github.com/chersbobers/booly/tree/nightly)
### what you need
A server I reccomend render with uptimerobot (note: Oregen and singapore servers are ip banned for me they might not be for you)
Also a discord bot with Presence Intent, Server Members Intent and Message Content Intent
Envs:
DISCORD_TOKEN (your bot token)
PORT 3000
For the invite link it just needs bot and applications.commands
# Commands
## Leveling & Economy
- `/rank` - View your rank and progress
- `/leaderboard` - See the top 10 users
- `/balance` - Check your balance
- `/daily` - Claim your daily reward
- `/work` - Work for coins
## Fun Commands
- `/8ball` - Ask the magic 8ball a question
- `/roll` - Roll dice
- `/flip` - Flip a coin
- `/cat` - Get a random cat picture
- `/dog` - Get a random dog picture
## System & Moderation
- `/kick` - Kick a member from the server
- `/ban` - Ban a member from the server
- `/unban` - Unban a user
- `/timeout` - Timeout a member
- `/warn` - Warn a member
- `/warnings` - View warnings for a user
- `/clearwarnings` - Clear warnings for a user
- `/purge` - Delete multiple messages
- `/lock` - Lock a channel
- `/unlock` - Unlock a channel
## Reaction Roles & YouTube
- `/reactionrole` - Create a reaction role
- `/removereactionrole` - Remove a reaction role
- `/listreactionroles` - List all reaction roles
- `/createreactionpanel` - Create a reaction role panel
- `/setupyoutube` - Setup YouTube notifications
- `/toggleyoutube` - Toggle YouTube notifications on/off
- `/youtubestatus` - Check YouTube notification status
- `/testlastvideo` - Test the last video notification
## Utility
- `/shorten` - Shorten a long URL with optional custom code
- `/expand` - Get the original URL from a short code
- `/listshort` - List all shortened URLs in the server
- `/deleteshort` - Delete a shortened URL by code
## Info Commands
- `/ping` - Check bot latency
- `/serverinfo` - Get information about the server
- `/userinfo` - Get information about a user

View file

@ -1,12 +0,0 @@
services:
- type: worker
name: tooly-bot
runtime: docker
plan: free
region: oregon
branch: main
dockerfilePath: ./Dockerfile
envVars:
- key: DISCORD_TOKEN
sync: false
autoDeploy: true