From 0f369873acbe78a101999aaa3d9211550cc7e31c Mon Sep 17 00:00:00 2001 From: chersbobers Date: Sat, 31 Jan 2026 18:03:16 +1300 Subject: [PATCH 1/7] added url shortner to u.chers.moe may change url to u.booly.me when i get it --- bot.py | 28 +++++++- cogs/utility.py | 182 ++++++++++++++++++++++++++++++++++++++++++++++++ render.yaml | 2 +- 3 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 cogs/utility.py diff --git a/bot.py b/bot.py index 17a6cc8..f29862a 100644 --- a/bot.py +++ b/bot.py @@ -80,6 +80,7 @@ class MyBot(commands.Bot): await self.load_extension('cogs.system') await self.load_extension('cogs.economy') await self.load_extension('cogs.fun') + await self.load_extension('cogs.utility') await self.tree.sync() logger.info('All cogs loaded and commands synced!') @@ -89,10 +90,30 @@ bot = MyBot() async def health_check(request): return web.Response(text="Bot is running!") +async def redirect_handler(request): + code = request.match_info.get('code', '') + + if os.path.exists(CONFIG['data_file']): + try: + with open(CONFIG['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(): app = web.Application() app.router.add_get('/', health_check) app.router.add_get('/health', health_check) + app.router.add_get('/{code}', redirect_handler) runner = web.AppRunner(app) await runner.setup() @@ -100,7 +121,7 @@ async def start_web_server(): port = int(os.getenv('PORT', 8080)) site = web.TCPSite(runner, '0.0.0.0', port) await site.start() - logger.info(f'Health check server running on port {port}') + logger.info(f'Web server running on port {port}') @bot.event async def on_ready(): @@ -138,6 +159,11 @@ async def help_command(interaction: discord.Interaction): value='`/reactionrole` - Create reaction role\n`/removereactionrole` - Remove reaction role\n`/listreactionroles` - List reaction roles\n`/createreactionpanel` - Create panel\n`/setupyoutube` - Setup YouTube\n`/toggleyoutube` - Toggle YouTube\n`/youtubestatus` - YouTube status\n`/testlastvideo` - Test video', inline=False ) + embed.add_field( + name='Utility', + value='`/shorten` - Shorten a URL', + inline=False + ) embed.add_field( name='Info', value='`/ping` - Bot latency\n`/serverinfo` - Server info\n`/userinfo` - User info', diff --git a/cogs/utility.py b/cogs/utility.py new file mode 100644 index 0000000..f29862a --- /dev/null +++ b/cogs/utility.py @@ -0,0 +1,182 @@ +import discord +from discord.ext import commands +import os +import json +import logging +from aiohttp import web +import asyncio + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('bot') + +CONFIG = { + 'xp_min': 15, + 'xp_max': 25, + 'xp_cooldown': 60, + 'xp_per_level': 100, + 'level_up_multiplier': 10, + 'data_file': 'data.json', + 'video_check_interval': 300 +} + +class SimpleDB: + def __init__(self, filename): + self.filename = filename + self.data = self.load() + + def load(self): + if os.path.exists(self.filename): + try: + with open(self.filename, 'r') as f: + return json.load(f) + except: + return {'users': {}, 'guilds': {}} + return {'users': {}, 'guilds': {}} + + def save(self): + with open(self.filename, 'w') as f: + json.dump(self.data, f, indent=2) + + def get_user(self, guild_id, user_id): + key = f"{guild_id}_{user_id}" + if key not in self.data['users']: + self.data['users'][key] = { + 'coins': 0, + 'bank': 0, + 'level': 1, + 'xp': 0, + 'last_message': 0, + 'last_daily': 0, + 'last_work': 0 + } + return self.data['users'][key] + + def set_user(self, guild_id, user_id, data): + key = f"{guild_id}_{user_id}" + self.data['users'][key] = data + 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 + +class MyBot(commands.Bot): + def __init__(self): + intents = discord.Intents.default() + intents.message_content = True + intents.members = True + + super().__init__(command_prefix='/', intents=intents) + self.db = SimpleDB(CONFIG['data_file']) + self.config = CONFIG + + async def setup_hook(self): + await self.load_extension('cogs.leveling') + await self.load_extension('cogs.system') + await self.load_extension('cogs.economy') + await self.load_extension('cogs.fun') + await self.load_extension('cogs.utility') + + await self.tree.sync() + logger.info('All cogs loaded and commands synced!') + +bot = MyBot() + +async def health_check(request): + return web.Response(text="Bot is running!") + +async def redirect_handler(request): + code = request.match_info.get('code', '') + + if os.path.exists(CONFIG['data_file']): + try: + with open(CONFIG['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(): + app = web.Application() + app.router.add_get('/', health_check) + app.router.add_get('/health', health_check) + app.router.add_get('/{code}', redirect_handler) + + runner = web.AppRunner(app) + await runner.setup() + + port = int(os.getenv('PORT', 8080)) + site = web.TCPSite(runner, '0.0.0.0', port) + await site.start() + logger.info(f'Web server running on port {port}') + +@bot.event +async def on_ready(): + asyncio.create_task(start_web_server()) + + 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.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 & Economy', + value='`/rank` - View your rank\n`/leaderboard` - Top 10 users\n`/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='System & Moderation', + value='`/kick` - Kick member\n`/ban` - Ban member\n`/unban` - Unban user\n`/timeout` - Timeout member\n`/warn` - Warn member\n`/warnings` - View warnings\n`/clearwarnings` - Clear warnings\n`/purge` - Delete messages\n`/lock` - Lock channel\n`/unlock` - Unlock channel', + inline=False + ) + embed.add_field( + name='Reaction Roles & YouTube', + value='`/reactionrole` - Create reaction role\n`/removereactionrole` - Remove reaction role\n`/listreactionroles` - List reaction roles\n`/createreactionpanel` - Create panel\n`/setupyoutube` - Setup YouTube\n`/toggleyoutube` - Toggle YouTube\n`/youtubestatus` - YouTube status\n`/testlastvideo` - Test video', + inline=False + ) + embed.add_field( + name='Utility', + value='`/shorten` - Shorten a URL', + inline=False + ) + embed.add_field( + name='Info', + value='`/ping` - Bot latency\n`/serverinfo` - Server info\n`/userinfo` - User info', + inline=False + ) + await interaction.response.send_message(embed=embed) + +if __name__ == '__main__': + token = os.getenv('DISCORD_TOKEN') + if not token: + logger.error('DISCORD_TOKEN not set!') + exit(1) + + logger.info("everythings working") + logger.info('hello from chersbobers and booly co :3') + bot.run(token) \ No newline at end of file diff --git a/render.yaml b/render.yaml index 39aaae1..5887299 100644 --- a/render.yaml +++ b/render.yaml @@ -1,5 +1,5 @@ services: - - type: worker + - type: web name: tooly-bot runtime: docker plan: free From 7578a5be6d0a761d64798a6e572ca0383615241c Mon Sep 17 00:00:00 2001 From: Charlie Date: Sat, 31 Jan 2026 18:04:52 +1300 Subject: [PATCH 2/7] Bugfixs --- cogs/utility.py | 235 +++++++++++++----------------------------------- 1 file changed, 63 insertions(+), 172 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index f29862a..b3be91c 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1,182 +1,73 @@ import discord from discord.ext import commands -import os -import json -import logging -from aiohttp import web -import asyncio +from discord import app_commands +import re +import random +import string -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger('bot') - -CONFIG = { - 'xp_min': 15, - 'xp_max': 25, - 'xp_cooldown': 60, - 'xp_per_level': 100, - 'level_up_multiplier': 10, - 'data_file': 'data.json', - 'video_check_interval': 300 -} - -class SimpleDB: - def __init__(self, filename): - self.filename = filename - self.data = self.load() +class Utility(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.domain = "u.chers.moe" - def load(self): - if os.path.exists(self.filename): - try: - with open(self.filename, 'r') as f: - return json.load(f) - except: - return {'users': {}, 'guilds': {}} - return {'users': {}, 'guilds': {}} + 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 save(self): - with open(self.filename, 'w') as f: - json.dump(self.data, f, indent=2) + def generate_short_code(self, length=6): + chars = string.ascii_letters + string.digits + return ''.join(random.choice(chars) for _ in range(length)) - def get_user(self, guild_id, user_id): - key = f"{guild_id}_{user_id}" - if key not in self.data['users']: - self.data['users'][key] = { - 'coins': 0, - 'bank': 0, - 'level': 1, - 'xp': 0, - 'last_message': 0, - 'last_daily': 0, - 'last_work': 0 - } - return self.data['users'][key] + 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)] - def set_user(self, guild_id, user_id, data): - key = f"{guild_id}_{user_id}" - self.data['users'][key] = data - 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 - -class MyBot(commands.Bot): - def __init__(self): - intents = discord.Intents.default() - intents.message_content = True - intents.members = True + @app_commands.command(name='shorten', description='Shorten a long URL') + @app_commands.describe(url='The URL you want to shorten') + async def shorten_url(self, interaction: discord.Interaction, url: str): + if not self.is_valid_url(url): + await interaction.response.send_message( + 'Please provide a valid URL', + ephemeral=True + ) + return - super().__init__(command_prefix='/', intents=intents) - self.db = SimpleDB(CONFIG['data_file']) - self.config = CONFIG - - async def setup_hook(self): - await self.load_extension('cogs.leveling') - await self.load_extension('cogs.system') - await self.load_extension('cogs.economy') - await self.load_extension('cogs.fun') - await self.load_extension('cogs.utility') + guild_data = self.get_guild_data(interaction.guild_id) - await self.tree.sync() - logger.info('All cogs loaded and commands synced!') + for code, stored_url in guild_data['urls'].items(): + if stored_url == url: + 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) + await interaction.response.send_message(embed=embed) + return + + 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) + + await interaction.response.send_message(embed=embed) -bot = MyBot() - -async def health_check(request): - return web.Response(text="Bot is running!") - -async def redirect_handler(request): - code = request.match_info.get('code', '') - - if os.path.exists(CONFIG['data_file']): - try: - with open(CONFIG['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(): - app = web.Application() - app.router.add_get('/', health_check) - app.router.add_get('/health', health_check) - app.router.add_get('/{code}', redirect_handler) - - runner = web.AppRunner(app) - await runner.setup() - - port = int(os.getenv('PORT', 8080)) - site = web.TCPSite(runner, '0.0.0.0', port) - await site.start() - logger.info(f'Web server running on port {port}') - -@bot.event -async def on_ready(): - asyncio.create_task(start_web_server()) - - 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.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 & Economy', - value='`/rank` - View your rank\n`/leaderboard` - Top 10 users\n`/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='System & Moderation', - value='`/kick` - Kick member\n`/ban` - Ban member\n`/unban` - Unban user\n`/timeout` - Timeout member\n`/warn` - Warn member\n`/warnings` - View warnings\n`/clearwarnings` - Clear warnings\n`/purge` - Delete messages\n`/lock` - Lock channel\n`/unlock` - Unlock channel', - inline=False - ) - embed.add_field( - name='Reaction Roles & YouTube', - value='`/reactionrole` - Create reaction role\n`/removereactionrole` - Remove reaction role\n`/listreactionroles` - List reaction roles\n`/createreactionpanel` - Create panel\n`/setupyoutube` - Setup YouTube\n`/toggleyoutube` - Toggle YouTube\n`/youtubestatus` - YouTube status\n`/testlastvideo` - Test video', - inline=False - ) - embed.add_field( - name='Utility', - value='`/shorten` - Shorten a URL', - inline=False - ) - embed.add_field( - name='Info', - value='`/ping` - Bot latency\n`/serverinfo` - Server info\n`/userinfo` - User info', - inline=False - ) - await interaction.response.send_message(embed=embed) - -if __name__ == '__main__': - token = os.getenv('DISCORD_TOKEN') - if not token: - logger.error('DISCORD_TOKEN not set!') - exit(1) - - logger.info("everythings working") - logger.info('hello from chersbobers and booly co :3') - bot.run(token) \ No newline at end of file +async def setup(bot): + await bot.add_cog(Utility(bot)) \ No newline at end of file From e70d126a956fe05131229a405d805c5ce8b23a95 Mon Sep 17 00:00:00 2001 From: Charlie Date: Sat, 31 Jan 2026 19:21:50 +1300 Subject: [PATCH 3/7] Update with pick your own shortcode --- cogs/utility.py | 84 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index b3be91c..0bc134d 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -34,8 +34,11 @@ class Utility(commands.Cog): 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') - async def shorten_url(self, interaction: discord.Interaction, url: str): + @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', @@ -45,18 +48,26 @@ class Utility(commands.Cog): guild_data = self.get_guild_data(interaction.guild_id) - for code, stored_url in guild_data['urls'].items(): + for existing_code, stored_url in guild_data['urls'].items(): if stored_url == url: - shortened = f"https://{self.domain}/{code}" - embed = discord.Embed(title='URL Shortened', color=0x5865F2) + 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 - code = self.generate_short_code() - while code in guild_data['urls']: + 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() @@ -66,8 +77,67 @@ class Utility(commands.Cog): 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)) \ No newline at end of file From 9438b63c40fd4458ed4a2614e0174af8d0230efc Mon Sep 17 00:00:00 2001 From: Charlie Date: Sun, 1 Feb 2026 21:10:56 +1300 Subject: [PATCH 4/7] Im adding a config file this is part of the new cogly or what ever I call it --- README.md | 17 ++++---- bot.py | 117 ++++++++++++---------------------------------------- config.toml | 23 +++++++++++ 3 files changed, 56 insertions(+), 101 deletions(-) create mode 100644 config.toml diff --git a/README.md b/README.md index cf5060b..1324688 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,29 @@
- Booly - discord.py python Made by Chersbobers :3 -
# Overview - Booly bot (or better tooly) is a fork of my original discord bot tooly bot this is a updated fork that is easier to develop and use. - Fork freely just credit booly # Installation - there is 2 methods of install: 1. prehosted 100 servers limit servers can be slow and updates are tested there. 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__) -[Click me for invite](https://discord.com/oauth2/authorize?client_id=1466398693383995558&permissions=8&integration_type=0&scope=bot+applications.commands) notes: again servers will most likely be slow and only 100 servers at a time if it reaches over a 100 servers I might host another one links will be updated. ## 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 use render because im broke with uptimerobot (note: Oregen 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: @@ -79,6 +70,12 @@ For the invite link it just needs bot and applications.commands - `/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 diff --git a/bot.py b/bot.py index f29862a..06fee17 100644 --- a/bot.py +++ b/bot.py @@ -3,21 +3,24 @@ from discord.ext import commands import os import json import logging +import tomllib from aiohttp import web import asyncio logging.basicConfig(level=logging.INFO) logger = logging.getLogger('bot') -CONFIG = { - 'xp_min': 15, - 'xp_max': 25, - 'xp_cooldown': 60, - 'xp_per_level': 100, - 'level_up_multiplier': 10, - 'data_file': 'data.json', - 'video_check_interval': 300 -} +def load_config(): + base_path = os.path.dirname(__file__) + config_path = os.path.join(base_path, "config.toml") + try: + with open(config_path, "rb") as f: + return tomllib.load(f) + except FileNotFoundError: + logger.error("config.toml missing.") + exit(1) + +CONFIG = load_config() class SimpleDB: def __init__(self, filename): @@ -41,13 +44,8 @@ class SimpleDB: key = f"{guild_id}_{user_id}" if key not in self.data['users']: self.data['users'][key] = { - 'coins': 0, - 'bank': 0, - 'level': 1, - 'xp': 0, - 'last_message': 0, - 'last_daily': 0, - 'last_work': 0 + 'coins': 0, 'bank': 0, 'level': 1, 'xp': 0, + 'last_message': 0, 'last_daily': 0, 'last_work': 0 } return self.data['users'][key] @@ -55,15 +53,6 @@ class SimpleDB: key = f"{guild_id}_{user_id}" self.data['users'][key] = data 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 class MyBot(commands.Bot): def __init__(self): @@ -72,18 +61,18 @@ class MyBot(commands.Bot): intents.members = True 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): - await self.load_extension('cogs.leveling') - await self.load_extension('cogs.system') - await self.load_extension('cogs.economy') - await self.load_extension('cogs.fun') - await self.load_extension('cogs.utility') + for extension in self.config['bot']['enabled_cogs']: + try: + await self.load_extension(extension) + logger.info(f'Loaded: {extension}') + except Exception as e: + logger.error(f'Error {extension}: {e}') await self.tree.sync() - logger.info('All cogs loaded and commands synced!') bot = MyBot() @@ -92,21 +81,15 @@ async def health_check(request): async def redirect_handler(request): code = request.match_info.get('code', '') - - if os.path.exists(CONFIG['data_file']): + if os.path.exists(CONFIG['bot']['data_file']): try: - with open(CONFIG['data_file'], 'r') as f: + 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]} - ) + 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(): @@ -114,69 +97,21 @@ async def start_web_server(): app.router.add_get('/', health_check) app.router.add_get('/health', health_check) app.router.add_get('/{code}', redirect_handler) - runner = web.AppRunner(app) await runner.setup() - port = int(os.getenv('PORT', 8080)) site = web.TCPSite(runner, '0.0.0.0', port) await site.start() - logger.info(f'Web server running on port {port}') @bot.event async def on_ready(): asyncio.create_task(start_web_server()) - 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.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 & Economy', - value='`/rank` - View your rank\n`/leaderboard` - Top 10 users\n`/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='System & Moderation', - value='`/kick` - Kick member\n`/ban` - Ban member\n`/unban` - Unban user\n`/timeout` - Timeout member\n`/warn` - Warn member\n`/warnings` - View warnings\n`/clearwarnings` - Clear warnings\n`/purge` - Delete messages\n`/lock` - Lock channel\n`/unlock` - Unlock channel', - inline=False - ) - embed.add_field( - name='Reaction Roles & YouTube', - value='`/reactionrole` - Create reaction role\n`/removereactionrole` - Remove reaction role\n`/listreactionroles` - List reaction roles\n`/createreactionpanel` - Create panel\n`/setupyoutube` - Setup YouTube\n`/toggleyoutube` - Toggle YouTube\n`/youtubestatus` - YouTube status\n`/testlastvideo` - Test video', - inline=False - ) - embed.add_field( - name='Utility', - value='`/shorten` - Shorten a URL', - inline=False - ) - embed.add_field( - name='Info', - value='`/ping` - Bot latency\n`/serverinfo` - Server info\n`/userinfo` - User info', - inline=False - ) - await interaction.response.send_message(embed=embed) + await bot.change_presence(activity=discord.Game(name="Commands")) if __name__ == '__main__': token = os.getenv('DISCORD_TOKEN') if not token: - logger.error('DISCORD_TOKEN not set!') + logger.error('DISCORD_TOKEN not set') exit(1) - - logger.info("everythings working") - logger.info('hello from chersbobers and booly co :3') bot.run(token) \ No newline at end of file diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..442c07f --- /dev/null +++ b/config.toml @@ -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 \ No newline at end of file From 64b1d812cdd407f859673652df3d0c81b43c644a Mon Sep 17 00:00:00 2001 From: Charlie Date: Sun, 1 Feb 2026 21:15:04 +1300 Subject: [PATCH 5/7] Add test to see if the config is loading at all in the right dir --- bot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot.py b/bot.py index 06fee17..8d8d111 100644 --- a/bot.py +++ b/bot.py @@ -14,6 +14,7 @@ def load_config(): base_path = os.path.dirname(__file__) config_path = os.path.join(base_path, "config.toml") try: + print(f"Loading config from: {config_path}") with open(config_path, "rb") as f: return tomllib.load(f) except FileNotFoundError: From 44798612f599a776e7941f589bf62fff40a795f9 Mon Sep 17 00:00:00 2001 From: Charlie Date: Sun, 1 Feb 2026 21:20:36 +1300 Subject: [PATCH 6/7] Added COPY config.toml . --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 67dbde4..a7df482 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,5 +7,6 @@ RUN pip install --no-cache-dir -r requirements.txt COPY bot.py . COPY cogs/ ./cogs/ +COPY config.toml . CMD ["python", "bot.py"] \ No newline at end of file From a2dcda769c8a9dd08724e511f1daee4c665f7b79 Mon Sep 17 00:00:00 2001 From: Charlie Date: Sun, 1 Feb 2026 21:37:08 +1300 Subject: [PATCH 7/7] Minor reformat --- .gitignore | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.gitignore b/.gitignore index 372138e..46c9d51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,4 @@ -# Data files data.json timeouts.json - -# Environment files .env - -# Luvit files -deps/ -lit-* - -# Logs *.log \ No newline at end of file