From a94f1bee77af90091c818ea566cad6d1df14cb14 Mon Sep 17 00:00:00 2001 From: Corban-Lee Date: Wed, 15 Jan 2025 14:17:35 +0000 Subject: [PATCH] initial commit --- .gitignore | 7 ++++++ FEATURES.md | 4 ++++ README.md | 0 bot/__init__.py | 0 bot/bot.py | 25 +++++++++++++++++++ logs/config.json | 51 +++++++++++++++++++++++++++++++++++++++ main.py | 53 +++++++++++++++++++++++++++++++++++++++++ requirements.txt | 27 +++++++++++++++++++++ web/__init__.py | 0 web/app.py | 12 ++++++++++ web/routes/dashboard.py | 7 ++++++ 11 files changed, 186 insertions(+) create mode 100644 .gitignore create mode 100644 FEATURES.md create mode 100644 README.md create mode 100644 bot/__init__.py create mode 100644 bot/bot.py create mode 100644 logs/config.json create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 web/__init__.py create mode 100644 web/app.py create mode 100644 web/routes/dashboard.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f06b57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__ +*.pyc +venv/ +.env +*.log +*.log.* +.vscode/ \ No newline at end of file diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 0000000..c67f44b --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,4 @@ + +- Web UI [ ] +- Filters [ ] +- diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 0000000..a9bd8e5 --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,25 @@ +import logging + +from discord import Intents +from discord.ext import commands + +log = logging.getLogger(__name__) + + +class DiscordBot(commands.Bot): + def __init__(self): + super().__init__( + command_prefix="@", + intents=Intents.all() + ) + + async def on_ready(self): + await self.wait_until_ready() + await self.tree.sync() + log.info("Bot is synced and ready") + + async def load_cogs(self, cog_path: str): + log.info("Loading cogs") + for path in cog_path.iterdir(): + if path.suffix == ".py": + await self.load_extension(f"cogs.{path.stem}") diff --git a/logs/config.json b/logs/config.json new file mode 100644 index 0000000..a6f0659 --- /dev/null +++ b/logs/config.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "simple": { + "format": "%(levelname)s %(message)s" + }, + "detail": { + "format": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s" + }, + "complex": { + "format": "[%(levelname)s|%(module)s|L%(lineno)d] %(asctime)s %(message)s", + "datefmt": "%Y-%m-%dT%H:%M:%S%z" + } + }, + "handlers": { + "stdout": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout" + }, + "file": { + "class": "logging.handlers.RotatingFileHandler", + "level": "DEBUG", + "formatter": "complex", + "filename": "logs/pyrss.log", + "maxBytes": 1048576, + "backupCount": 3 + }, + "queue_handler": { + "class": "logging.handlers.QueueHandler", + "handlers": [ + "stdout", + "file" + ], + "respect_handler_level": true + } + }, + "loggers": { + "root": { + "level": "DEBUG", + "handlers": [ + "queue_handler" + ] + }, + "discord": { + "level": "INFO" + } + } +} \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..793ce00 --- /dev/null +++ b/main.py @@ -0,0 +1,53 @@ +import json +import atexit +import asyncio +import logging +import logging.config +from os import getenv +from pathlib import Path + +from dotenv import load_dotenv +load_dotenv(override=True) + +from bot.bot import DiscordBot +from web.app import create_app + +BASE_DIR = Path(__file__).parent + +async def start_web(): + app = create_app() + await app.run_task(host="0.0.0.0", port=5000) + +async def start_bot(token: str): + async with DiscordBot() as bot: + await bot.load_cogs(BASE_DIR / "bot" / "cogs") + await bot.start(token, reconnect=True) + +def setup_logging(): + # load config from file + log_config_path = BASE_DIR / "logs" / "config.json" + if not log_config_path.exists(): + raise FileNotFoundError("Logging config not found") + + with open(log_config_path, "r") as file: + logging_config = json.load(file) + + logging.config.dictConfig(logging_config) + + # create queue handler for non-blocking logs + queue_handler = logging.getHandlerByName("queue_handler") + if queue_handler is not None: + queue_handler.listener.start() + atexit.register(queue_handler.listener.stop) + +async def main(): + bot_token = getenv("BOT_TOKEN") + if not bot_token: + raise ValueError("'BOT_TOKEN' is missing") + + setup_logging() + + await asyncio.gather(start_web(), start_bot(bot_token)) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d5e087a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,27 @@ +aiofiles==24.1.0 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.11 +aiosignal==1.3.2 +attrs==24.3.0 +blinker==1.9.0 +click==8.1.8 +discord.py==2.4.0 +Flask==3.1.0 +frozenlist==1.5.0 +h11==0.14.0 +h2==4.1.0 +hpack==4.0.0 +Hypercorn==0.17.3 +hyperframe==6.0.1 +idna==3.10 +itsdangerous==2.2.0 +Jinja2==3.1.5 +MarkupSafe==3.0.2 +multidict==6.1.0 +priority==2.0.0 +propcache==0.2.1 +python-dotenv==1.0.1 +Quart==0.20.0 +Werkzeug==3.1.3 +wsproto==1.2.0 +yarl==1.18.3 diff --git a/web/__init__.py b/web/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/app.py b/web/app.py new file mode 100644 index 0000000..337ade3 --- /dev/null +++ b/web/app.py @@ -0,0 +1,12 @@ +import logging + +from quart import Quart +from .routes.dashboard import dashboard + +log = logging.getLogger(__name__) + +def create_app(): + log.info("Creating web app and registering blueprints") + app = Quart(__name__) + app.register_blueprint(dashboard, url_prefix="/dashboard") + return app diff --git a/web/routes/dashboard.py b/web/routes/dashboard.py new file mode 100644 index 0000000..80927cf --- /dev/null +++ b/web/routes/dashboard.py @@ -0,0 +1,7 @@ +from quart import Blueprint + +dashboard = Blueprint("dashboard", __name__) + +@dashboard.route("/") +async def home(): + return "Dashboard page"