From 195ca2194722da92053e549d9a3f8c9d1edf6cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Helle=C5=9Ben?= Date: Tue, 17 Mar 2026 19:47:25 +0100 Subject: [PATCH] Refactor environment variables and update systemd service configuration for ANewDawn --- .env.example | 1 - .github/copilot-instructions.md | 5 +- .vscode/settings.json | 3 +- README.md | 27 ++++++++++ main.py | 92 +-------------------------------- systemd/anewdawn.env.example | 11 ++++ systemd/anewdawn.service | 28 ++++++++++ 7 files changed, 70 insertions(+), 97 deletions(-) create mode 100644 systemd/anewdawn.env.example create mode 100644 systemd/anewdawn.service diff --git a/.env.example b/.env.example index dee5c49..5fb16cb 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,3 @@ DISCORD_TOKEN= OPENAI_TOKEN= OLLAMA_API_KEY= -OPENROUTER_API_KEY= \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9a0f37e..810d0c1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,7 +4,7 @@ ANewDawn is a Discord bot written in Python 3.13+ using the discord.py library and Pydantic AI for AI-powered chat capabilities. The bot includes features such as: -- AI-powered chat responses using OpenAI and Grok models +- AI-powered chat responses using OpenAI models - Conversation memory with reset/undo functionality - Image enhancement using OpenCV - Web search integration via Ollama @@ -66,13 +66,12 @@ ruff format --check --verbose The main bot client is `LoviBotClient` which extends `discord.Client`. It handles: - Message events (`on_message`) -- Slash commands (`/ask`, `/grok`, `/reset`, `/undo`) +- Slash commands (`/ask`, `/reset`, `/undo`) - Context menus (image enhancement) ### AI Integration - `chatgpt_agent` - Pydantic AI agent using OpenAI -- `grok_it()` - Function for Grok model responses - Message history is stored in `recent_messages` dict per channel ### Memory Management diff --git a/.vscode/settings.json b/.vscode/settings.json index 5ea323a..d5a5404 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -37,7 +37,6 @@ "numpy", "Ollama", "opencv", - "OPENROUTER", "percpu", "phibiscarf", "plubplub", @@ -58,4 +57,4 @@ "Waifu", "Zenless" ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index f0b2509..c87d47d 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,30 @@

A shit Discord bot. + +## Running via systemd + +This repo includes a systemd unit template under `systemd/anewdawn.service` that can be used to run the bot as a service. + +### Quick setup + +1. Copy and edit the environment file: + ```sh + sudo mkdir -p /etc/ANewDawn + sudo cp systemd/anewdawn.env.example /etc/ANewDawn/ANewDawn.env + sudo chown -R lovinator:lovinator /etc/ANewDawn + # Edit /etc/ANewDawn/ANewDawn.env and fill in your tokens. + ``` + +2. Install the systemd unit: + ```sh + sudo cp systemd/anewdawn.service /etc/systemd/system/ + sudo systemctl daemon-reload + sudo systemctl enable --now anewdawn.service + ``` + +3. Check status / logs: + ```sh + sudo systemctl status anewdawn.service + sudo journalctl -u anewdawn.service -f + ``` diff --git a/main.py b/main.py index 1375ec0..461361a 100644 --- a/main.py +++ b/main.py @@ -36,7 +36,6 @@ if TYPE_CHECKING: from discord.abc import MessageableChannel from discord.guild import GuildChannel from discord.interactions import InteractionChannel - from openai.types.chat import ChatCompletion from pydantic_ai.run import AgentRunResult load_dotenv(verbose=True) @@ -82,39 +81,6 @@ chatgpt_agent: Agent[BotDependencies, str] = Agent( deps_type=BotDependencies, model_settings=openai_settings, ) -grok_client = openai.OpenAI( - base_url="https://openrouter.ai/api/v1", - api_key=os.getenv("OPENROUTER_API_KEY"), -) - - -def grok_it( - message: discord.Message | None, - user_message: str, -) -> str | None: - """Chat with the bot using the Pydantic AI agent. - - Args: - user_message: The message from the user. - message: The original Discord message object. - - Returns: - The bot's response as a string, or None if no response. - """ - allowed_users: list[str] = get_allowed_users() - if message and message.author.name not in allowed_users: - return None - - response: ChatCompletion = grok_client.chat.completions.create( - model="x-ai/grok-4-fast:free", - messages=[ - { - "role": "user", - "content": user_message, - }, - ], - ) - return response.choices[0].message.content # MARK: reset_memory @@ -711,7 +677,7 @@ class LoviBotClient(discord.Client): add_message_to_memory(str(message.channel.id), message.author.name, incoming_message) lowercase_message: str = incoming_message.lower() - trigger_keywords: list[str] = ["lovibot", "@lovibot", "<@345000831499894795>", "grok", "@grok"] + trigger_keywords: list[str] = ["lovibot", "@lovibot", "<@345000831499894795>"] has_trigger_keyword: bool = any(trigger in lowercase_message for trigger in trigger_keywords) should_respond_flag: bool = has_trigger_keyword or should_respond_without_trigger(str(message.channel.id), message.author.name) @@ -853,62 +819,6 @@ async def ask(interaction: discord.Interaction, text: str, new_conversation: boo await send_response(interaction=interaction, text=text, response=display_response) -# MARK: /grok command -@client.tree.command(name="grok", description="Grok a question.") -@app_commands.allowed_installs(guilds=True, users=True) -@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True) -@app_commands.describe(text="Grok a question.") -async def grok(interaction: discord.Interaction, text: str) -> None: - """A command to ask the AI a question. - - Args: - interaction (discord.Interaction): The interaction object. - text (str): The question or message to ask. - """ - await interaction.response.defer() - - if not text: - logger.error("No question or message provided.") - await interaction.followup.send("You need to provide a question or message.", ephemeral=True) - return - - user_name_lowercase: str = interaction.user.name.lower() - logger.info("Received command from: %s", user_name_lowercase) - - # Only allow certain users to interact with the bot - allowed_users: list[str] = get_allowed_users() - if user_name_lowercase not in allowed_users: - await send_response(interaction=interaction, text=text, response="You are not authorized to use this command.") - return - - # Get model response - try: - model_response: str | None = grok_it(message=interaction.message, user_message=text) - except openai.OpenAIError as e: - logger.exception("An error occurred while chatting with the AI model.") - await send_response(interaction=interaction, text=text, response=f"An error occurred: {e}") - return - - truncated_text: str = truncate_user_input(text) - - # Fallback if model provided no response - if not model_response: - logger.warning("No response from the AI model. Message: %s", text) - model_response = "I forgor how to think 💀" - - display_response: str = f"`{truncated_text}`\n\n{model_response}" - logger.info("Responding to message: %s with: %s", text, display_response) - - # If response is longer than 2000 characters, split it into multiple messages - max_discord_message_length: int = 2000 - if len(display_response) > max_discord_message_length: - for i in range(0, len(display_response), max_discord_message_length): - await send_response(interaction=interaction, text=text, response=display_response[i : i + max_discord_message_length]) - return - - await send_response(interaction=interaction, text=text, response=display_response) - - # MARK: /reset command @client.tree.command(name="reset", description="Reset the conversation memory.") @app_commands.allowed_installs(guilds=True, users=True) diff --git a/systemd/anewdawn.env.example b/systemd/anewdawn.env.example new file mode 100644 index 0000000..3e8a586 --- /dev/null +++ b/systemd/anewdawn.env.example @@ -0,0 +1,11 @@ +# Copy this file to /etc/ANewDawn/ANewDawn.env and fill in the required values. +# Make sure the directory is owned by the user running the service (e.g., "lovinator"). + +# Discord bot token +DISCORD_TOKEN= + +# OpenAI token (for GPT-5 and other OpenAI models) +OPENAI_TOKEN= + +# Optional: additional env vars used by your bot +# MY_CUSTOM_VAR= diff --git a/systemd/anewdawn.service b/systemd/anewdawn.service new file mode 100644 index 0000000..6ec9e9a --- /dev/null +++ b/systemd/anewdawn.service @@ -0,0 +1,28 @@ +[Unit] +Description=ANewDawn Discord Bot +After=network.target + +[Service] +Type=simple +# Run the bot as the lovinator user (UID 1000) so it has appropriate permissions. +# Update these values if you need a different system user/group. +User=lovinator +Group=lovinator + +# The project directory containing main.py (update as needed). +WorkingDirectory=/home/lovinator/Code/ANewDawn + +# Load environment variables (see systemd/anewdawn.env.example). +EnvironmentFile=/etc/ANewDawn/ANewDawn.env + +# Use the python interpreter from your environment (system python is fine if dependencies are installed). +ExecStart=/usr/bin/env python3 main.py + +Restart=on-failure +RestartSec=5 + +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target