From b5c8895b4ddc7ad5a3c024c1db874dd8d40c848c Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:53:57 +0200 Subject: [PATCH] make goober export some env variables for cogs to use and add webserver --- .gitignore | 2 +- bot.py | 17 ++- cogs/README.md | 18 ++- cogs/styles.css | 118 +++++++++++++++++++ cogs/webserver.py | 281 ++++++++++++++++++++++++++++++++++++++++++++++ locales/en.json | 1 + 6 files changed, 429 insertions(+), 8 deletions(-) create mode 100644 cogs/styles.css create mode 100644 cogs/webserver.py diff --git a/.gitignore b/.gitignore index f518009..3bd6415 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ __pycache__ current_version.txt MEMORY_LOADED memory.json -markov_model.pkl \ No newline at end of file +*.pkl \ No newline at end of file diff --git a/bot.py b/bot.py index e46e5f0..3709b25 100644 --- a/bot.py +++ b/bot.py @@ -173,7 +173,9 @@ def generate_sha256_of_current_file(): latest_version = "0.0.0" -local_version = "0.14.8.1" +local_version = "0.14.8.2" +os.environ['gooberlocal_version'] = local_version +os.environ['gooberlatest_version'] = latest_version def check_for_update(): if ALIVEPING == "false": @@ -327,10 +329,13 @@ def ping_server(): response = requests.post(VERSION_URL+"/ping", json=payload) if response.status_code == 200: print(f"{GREEN}{get_translation(LOCALE, 'goober_ping_success').format(NAME=NAME)}{RESET}") + os.environ['gooberauthenticated'] = 'Yes' else: print(f"{RED}{get_translation(LOCALE, 'goober_ping_fail')} {response.status_code}{RESET}") + os.environ['gooberauthenticated'] = 'No' except Exception as e: print(f"{RED}{get_translation(LOCALE, 'goober_ping_fail2')} {str(e)}{RESET}") + os.environ['gooberauthenticated'] = 'No' positive_gifs = os.getenv("POSITIVE_GIFS").split(',') @@ -434,6 +439,8 @@ async def talk(ctx, sentence_size: int = 5): combined_message = f"{coherent_response}\n[jif]({gif_url})" else: combined_message = coherent_response + print(combined_message) + os.environ['gooberlatestgen'] = combined_message await send_message(ctx, combined_message) else: await send_message(ctx, f"{get_translation(LOCALE, 'command_talk_generation_fail')}") @@ -511,6 +518,14 @@ async def on_message(message): # process any commands in the message await bot.process_commands(message) +@bot.event +async def on_interaction(interaction): + if interaction.type == discord.InteractionType.application_command: + if interaction.user.id in BLACKLISTED_USERS: + return + + print(f"{get_translation(LOCALE, 'command_ran_s').format(interaction=interaction)}{interaction.data['name']}") + @bot.hybrid_command(description=f"{get_translation(LOCALE, 'command_desc_ping')}") async def ping(ctx): await ctx.defer() diff --git a/cogs/README.md b/cogs/README.md index 03c0893..e5ec73a 100644 --- a/cogs/README.md +++ b/cogs/README.md @@ -1,18 +1,24 @@ # goobers custom commands -[Hello World!](https://github.com/WhatDidYouExpect/goobercustomcommands/blob/main/customcommands/hello.py) +[Hello World!](https://github.com/WhatDidYouExpect/goober/blob/main/cogs/hello.py) by expect -[WhoAmI (lists username and nickname)](https://github.com/WhatDidYouExpect/goober/blob/main/customcommands/whoami.py) +[WhoAmI (lists username and nickname)](https://github.com/WhatDidYouExpect/goober/blob/main/cogs/whoami.py) by PowerPCFan -[Cog Manager](https://github.com/WhatDidYouExpect/goober/blob/main/customcommands/cogmanager.py) +[Cog Manager](https://github.com/WhatDidYouExpect/goober/blob/main/cogs/cogmanager.py) by expect -[TensorFlow integration](https://github.com/WhatDidYouExpect/goober/blob/main/customcommands/tf.py) +[TensorFlow integration](https://github.com/WhatDidYouExpect/goober/blob/main/cogs/tf.py) by SuperSilly2 (requires Python 3.7 - 3.10, tensorflow-metal/tensorflow-gpu and tensorflow/tensorflow-macos) -[Web Scraper](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/customcommands/webscraper.py) +[Web Scraper](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/webscraper.py) by expect (requires goober version 0.11.7.2 or higher) -[Status Changer](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/customcommands/songchanger.py) +[Status Changer](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/songchanger.py) +by expect (requires goober version 0.11.8 or higher) + +[Status Changer](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/songchanger.py) +by expect (requires goober version 0.11.8 or higher) + +[webUI](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/webserver.py) by expect (requires goober version 0.11.8 or higher) diff --git a/cogs/styles.css b/cogs/styles.css new file mode 100644 index 0000000..0395689 --- /dev/null +++ b/cogs/styles.css @@ -0,0 +1,118 @@ + +.topnav { + background-color: rgb(95, 27, 27); + overflow: hidden; + display: flex; + flex-wrap: wrap; + justify-content: center; + padding: 10px; + gap: 20px; +} + +.topnav a { + color: #f2f2f2; + text-align: center; + text-decoration: none; + font-size: 17px; +} + +.topnav a:hover { + background-color: #ddd; + color: black; +} + +.stat-item { + display: flex; + align-items: center; + gap: 5px; + color: white; + font-size: 14px; +} + +.stat-title { + font-weight: bold; + color: #ccc; +} + +body { + background-color: black; + color: white; + font-family: Arial, sans-serif; +} + +a { + color: red; +} + +.stats-container { + max-width: 800px; + margin: 20px auto; + padding: 20px; + background-color: #1a1a1a; + border-radius: 8px; + text-align: left; +} + +.balls { + text-align: right; +} + +.stat-card { + background-color: #2a2a2a; + padding: 15px; + margin: 10px 0; + border-radius: 5px; + border-left: 4px solid rgb(95, 27, 27); +} + +.guild-list { + max-height: 300px; + overflow-y: auto; + background-color: #2a2a2a; + padding: 10px; + border-radius: 5px; +} + +.guild-item { + padding: 5px 0; + border-bottom: 1px solid #444; +} + +.guild-item:last-child { + border-bottom: none; +} + +hr { + border-color: rgb(95, 27, 27); + margin: 20px 0; +} + +.center { + text-align: center; +} + +.stat-container-row { + display: flex; + justify-content: space-between; + gap: 20px; + margin-bottom: 10px; +} + +.bot-info { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +.bot-avatar { + width: 50px; + height: 50px; + border-radius: 0%; +} + +button { + background-color:black; + color: white; + cursor: pointer; +} \ No newline at end of file diff --git a/cogs/webserver.py b/cogs/webserver.py new file mode 100644 index 0000000..04c9cc1 --- /dev/null +++ b/cogs/webserver.py @@ -0,0 +1,281 @@ +import discord +from discord.ext import commands, tasks +import asyncio +from aiohttp import web +import psutil +import os +import json +from datetime import datetime +import time +import aiohttp +from aiohttp import WSMsgType + +class GooberWeb(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.app = web.Application() + self.runner = None + self.site = None + self.last_command = "No commands executed yet" + self.last_command_time = "Never" + self.start_time = time.time() + self.websockets = set() + + self.app.add_routes([ + web.get('/', self.handle_index), + web.get('/changesong', self.handle_changesong), + web.get('/stats', self.handle_stats), + web.get('/data', self.handle_json_data), + web.get('/ws', self.handle_websocket), + web.get('/styles.css', self.handle_css), + ]) + + self.bot.loop.create_task(self.start_web_server()) + self.update_clients.start() + + async def start_web_server(self): + self.runner = web.AppRunner(self.app) + await self.runner.setup() + self.site = web.TCPSite(self.runner, '0.0.0.0', 8080) + await self.site.start() + print("Goober web server started on port 8080") + + async def stop_web_server(self): + await self.site.stop() + await self.runner.cleanup() + print("Web server stopped") + + def cog_unload(self): + self.update_clients.cancel() + self.bot.loop.create_task(self.stop_web_server()) + + @tasks.loop(seconds=5) + async def update_clients(self): + if not self.websockets: + return + + stats = await self.get_bot_stats() + message = json.dumps(stats) + + for ws in set(self.websockets): + try: + await ws.send_str(message) + except ConnectionResetError: + self.websockets.remove(ws) + except Exception as e: + print(f"Error sending to websocket: {e}") + self.websockets.remove(ws) + + async def handle_websocket(self, request): + ws = web.WebSocketResponse() + await ws.prepare(request) + self.websockets.add(ws) + + try: + async for msg in ws: + if msg.type == WSMsgType.ERROR: + print(f"WebSocket error: {ws.exception()}") + finally: + self.websockets.remove(ws) + + return ws + + async def handle_css(self, request): + css_path = os.path.join(os.path.dirname(__file__), 'styles.css') + if os.path.exists(css_path): + return web.FileResponse(css_path) + return web.Response(text="CSS file not found", status=404) + + @commands.Cog.listener() + async def on_message(self, message): + if message.author.bot: + return + + ctx = await self.bot.get_context(message) + if ctx.valid and ctx.command: + self._update_command_stats(ctx.command.name, ctx.author) + + @commands.Cog.listener() + async def on_app_command_completion(self, interaction, command): + self._update_command_stats(command.name, interaction.user) + + def _update_command_stats(self, command_name, user): + self.last_command = f"{command_name} (by {user.name}#{user.discriminator})" + self.last_command_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if self.websockets: + asyncio.create_task(self.update_clients()) + + async def get_bot_stats(self): + process = psutil.Process(os.getpid()) + mem_info = process.memory_full_info() + cpu_percent = psutil.cpu_percent() + process_cpu = process.cpu_percent() + + memory_json_size = "N/A" + if os.path.exists("memory.json"): + memory_json_size = f"{os.path.getsize('memory.json') / 1024:.2f} KB" + + guilds = sorted(self.bot.guilds, key=lambda g: g.member_count, reverse=True) + guild_info = [f"{g.name} ({g.member_count} members)" for g in guilds] + + uptime_seconds = int(time.time() - self.start_time) + uptime_str = f"{uptime_seconds // 86400}d {(uptime_seconds % 86400) // 3600}h {(uptime_seconds % 3600) // 60}m {uptime_seconds % 60}s" + + return { + "ram_usage": f"{mem_info.rss / 1024 / 1024:.2f} MB", + "cpu_usage": f"{process_cpu}%", + "system_cpu": f"{cpu_percent}%", + "memory_json_size": memory_json_size, + "guild_count": len(guilds), + "guilds": guild_info, + "last_command": self.last_command, + "last_command_time": self.last_command_time, + "bot_uptime": uptime_str, + "latency": f"{self.bot.latency * 1000:.2f} ms", + "bot_name": self.bot.user.name, + "bot_avatar_url": str(self.bot.user.avatar.url) if self.bot.user.avatar else "", + "authenticated": os.getenv("gooberauthenticated"), + "lastmsg": os.getenv("gooberlatestgen") + } + + async def handle_update(self, request): + if os.path.exists("goob/update.py"): + return web.FileResponse("goob/update.py") + return web.Response(text="Update file not found", status=404) + + async def handle_changesong(self, request): + song = request.query.get('song', '') + if song: + await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=song)) + return web.Response(text=f"Changed song to: {song}") + return web.Response(text="Please provide a song parameter", status=400) + + async def handle_changes(self, request): + if os.path.exists("goob/changes.txt"): + return web.FileResponse("goob/changes.txt") + return web.Response(text="Changelog not found", status=404) + + async def handle_index(self, request): + stats = await self.get_bot_stats() + + html_content = f""" + + + + goobs central + + + +
+
+ RAM: + {stats['ram_usage']} +
+
+ CPU: + {stats['cpu_usage']} +
+
+ System CPU: + {stats['system_cpu']} +
+
+ Latency: + {stats['latency']} +
+
+ JSON Size: + {stats['memory_json_size']} +
+
+ Uptime: + {stats['bot_uptime']} +
+
+ +
+

+
+ botvatar + {stats['bot_name']} +
+

+
+

your stupid little goober that learns off other people's messages

+
+ +
+
+
Last Command
+
{stats['last_command']}
+
at {stats['last_command_time']}
+
+
Logged into goober central
+
{stats['authenticated']}
+
+
Last generated message
+
{stats['lastmsg']}
+
+
Change song
+
+ + +
+
+ +
+
Servers ({stats['guild_count']})
+
+ {"".join(f'
{guild}
' for guild in stats['guilds'])} +
+
+
+ + + + """ + + return web.Response(text=html_content, content_type='text/html') + + async def handle_stats(self, request): + return await self.handle_index(request) + + async def handle_json_data(self, request): + stats = await self.get_bot_stats() + return web.json_response(stats) + +async def setup(bot): + await bot.add_cog(GooberWeb(bot)) \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index 7ef49ea..f57c70f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -57,6 +57,7 @@ "command_help_categories_admin": "Administration", "command_help_categories_custom": "Custom Commands", "command_ran": "Info: {message.author.name} ran {message.content}", + "command_ran_s": "Info: {interaction.user} ran ", "command_desc_ping": "ping", "command_ping_embed_desc": "Bot Latency:", "command_ping_footer": "Requested by",