From 2e2b47f8540c64fb65fc1d2f963fd4d8825d9ef1 Mon Sep 17 00:00:00 2001 From: Hornochs Date: Mon, 27 Jan 2025 15:57:51 +0100 Subject: [PATCH 1/2] Adding Possibility to add Servers automatically --- ADD_SERVER_README.md | 80 ++++++++++++++++++++++ discordgsm/add_server.py | 131 +++++++++++++++++++++++++++++++++++++ discordgsm/add_servers.yml | 28 ++++++++ 3 files changed, 239 insertions(+) create mode 100644 ADD_SERVER_README.md create mode 100644 discordgsm/add_server.py create mode 100644 discordgsm/add_servers.yml diff --git a/ADD_SERVER_README.md b/ADD_SERVER_README.md new file mode 100644 index 0000000..16bf043 --- /dev/null +++ b/ADD_SERVER_README.md @@ -0,0 +1,80 @@ +# DiscordGSM Server Automation Script + +Command-line tool to automate adding game servers to DiscordGSM's SQLite database. + +## Requirements + +- Python 3.6+ +- SQLite3 +- Access to DiscordGSM's `servers.db` file +- **Important**: Must run in DiscordGSM's virtual environment to ensure dependency compatibility + ```bash + # Activate DiscordGSM's venv first + source /path/to/discordgsm/venv/bin/activate # Linux/Mac + # or + \path\to\discordgsm\venv\Scripts\activate # Windows + ``` + +## Installation + +1. Save `add_server.py` to your desired location +2. Ensure you have write access to DiscordGSM's database directory + +## Usage + +### Command Line + +```bash +python3 add_server.py \ + --guild_id YOUR_GUILD_ID \ + --channel_id YOUR_CHANNEL_ID \ + --game_id GAME_ID \ + --address SERVER_ADDRESS \ + --query_port QUERY_PORT \ + [--db_path PATH_TO_DB] +``` + +### Required Arguments + +- `--guild_id`: Discord server (guild) ID +- `--channel_id`: Discord channel ID +- `--game_id`: Game identifier (e.g., "minecraft", "valheim") +- `--address`: Server address/hostname +- `--query_port`: Query port number + +### Optional Arguments + +- `--db_path`: Custom path to `servers.db` (defaults to `./data/servers.db`) + +## Examples + +Add a Minecraft server: +```bash +python3 add_server.py \ + --guild_id 123456789 \ + --channel_id 987654321 \ + --game_id minecraft \ + --address mc.example.com \ + --query_port 25565 +``` + +Add a Valheim server with custom database path: +```bash +python3 add_server.py \ + --guild_id 123456789 \ + --channel_id 987654321 \ + --game_id valheim \ + --address valheim.example.com \ + --query_port 2457 \ + --db_path /opt/discordgsm/data/servers.db +``` + +## Error Handling + +- Script checks for existing servers with same address and query port +- Returns exit code 1 if server already exists or on error +- Prints error message to stdout + +## Ansible Integration + +Can be used with Ansible for automated deployment. See `add_servers.yml` for playbook example. \ No newline at end of file diff --git a/discordgsm/add_server.py b/discordgsm/add_server.py new file mode 100644 index 0000000..be6e901 --- /dev/null +++ b/discordgsm/add_server.py @@ -0,0 +1,131 @@ +import sqlite3 +import json +from typing import Optional, Dict +from dataclasses import dataclass +import os +from pathlib import Path +import argparse + +@dataclass +class ServerConfig: + guild_id: int + channel_id: int + game_id: str + address: str + query_port: int + query_extra: Dict = None + style_id: str = "Medium" + style_data: Dict = None + position: Optional[int] = None + status: bool = False + result: Dict = None + message_id: Optional[int] = None + +class DGSMAutomation: + def __init__(self, db_path: str = None): + if db_path is None: + db_path = os.path.join(Path(__file__).parent, "data", "servers.db") + self.db_path = db_path + Path(os.path.dirname(db_path)).mkdir(parents=True, exist_ok=True) + self._init_db() + + def _init_db(self): + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS servers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + position INT NOT NULL, + guild_id BIGINT NOT NULL, + channel_id BIGINT NOT NULL, + message_id BIGINT, + game_id TEXT NOT NULL, + address TEXT NOT NULL, + query_port INT(5) NOT NULL, + query_extra TEXT NOT NULL, + status INT(1) NOT NULL, + result TEXT NOT NULL, + style_id TEXT NOT NULL, + style_data TEXT NOT NULL + ) + ''') + conn.commit() + + def server_exists(self, address: str, query_port: int) -> bool: + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute( + 'SELECT COUNT(*) FROM servers WHERE address = ? AND query_port = ?', + (address, query_port) + ) + count = cursor.fetchone()[0] + return count > 0 + + def add_server(self, config: ServerConfig): + if self.server_exists(config.address, config.query_port): + raise ValueError(f"Server {config.address}:{config.query_port} already exists") + + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + + if config.position is None: + cursor.execute( + 'SELECT COALESCE(MAX(position + 1), 0) FROM servers WHERE channel_id = ?', + (config.channel_id,) + ) + config.position = cursor.fetchone()[0] + + config.query_extra = config.query_extra or {} + config.style_data = config.style_data or {"locale": "en-US", "timezone": "UTC"} + config.result = config.result or {"name": "", "map": "", "password": False, "raw": {}, "connect": "", "numplayers": 0, "numbots": 0, "maxplayers": 0, "players": [], "bots": []} + + cursor.execute(''' + INSERT INTO servers ( + position, guild_id, channel_id, game_id, address, query_port, + query_extra, status, result, style_id, style_data, message_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', ( + config.position, + config.guild_id, + config.channel_id, + config.game_id, + config.address, + config.query_port, + json.dumps(config.query_extra), + int(config.status), + json.dumps(config.result), + config.style_id, + json.dumps(config.style_data), + config.message_id + )) + conn.commit() + +def main(): + parser = argparse.ArgumentParser(description='Add server to DiscordGSM') + parser.add_argument('--guild_id', type=int, required=True) + parser.add_argument('--channel_id', type=int, required=True) + parser.add_argument('--game_id', type=str, required=True) + parser.add_argument('--address', type=str, required=True) + parser.add_argument('--query_port', type=int, required=True) + parser.add_argument('--db_path', type=str, help='Path to servers.db') + + args = parser.parse_args() + + automation = DGSMAutomation(db_path=args.db_path) + + try: + server_config = ServerConfig( + guild_id=args.guild_id, + channel_id=args.channel_id, + game_id=args.game_id, + address=args.address, + query_port=args.query_port + ) + automation.add_server(server_config) + print(f"Successfully added server {args.address}:{args.query_port}") + except ValueError as e: + print(f"Error: {str(e)}") + exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/discordgsm/add_servers.yml b/discordgsm/add_servers.yml new file mode 100644 index 0000000..a6937ae --- /dev/null +++ b/discordgsm/add_servers.yml @@ -0,0 +1,28 @@ +--- +- name: Add servers to DiscordGSM + hosts: discordgsm + gather_facts: no + + vars: + discordgsm_db: "/path/to/servers.db" + servers: + - guild_id: 123456789 + channel_id: 987654321 + game_id: "minecraft" + address: "mc.example.com" + query_port: 25565 + # Add more servers as needed + + tasks: + - name: Add server to DiscordGSM + command: > + python3 add_server.py + --guild_id {{ item.guild_id }} + --channel_id {{ item.channel_id }} + --game_id {{ item.game_id }} + --address {{ item.address }} + --query_port {{ item.query_port }} + --db_path {{ discordgsm_db }} + register: result + failed_when: result.rc != 0 + with_items: "{{ servers }}" \ No newline at end of file From 39ad0c426940851864375e66ccadda2eddfce3f4 Mon Sep 17 00:00:00 2001 From: Hornochs Date: Mon, 27 Jan 2025 16:23:26 +0100 Subject: [PATCH 2/2] Improve Error Handeling --- ADD_SERVER_README.md | 39 ++++++++----- discordgsm/add_server.py | 114 ++++++++++++++++++++++--------------- discordgsm/add_servers.yml | 21 ++++++- 3 files changed, 111 insertions(+), 63 deletions(-) diff --git a/ADD_SERVER_README.md b/ADD_SERVER_README.md index 16bf043..b67216e 100644 --- a/ADD_SERVER_README.md +++ b/ADD_SERVER_README.md @@ -7,9 +7,8 @@ Command-line tool to automate adding game servers to DiscordGSM's SQLite databas - Python 3.6+ - SQLite3 - Access to DiscordGSM's `servers.db` file -- **Important**: Must run in DiscordGSM's virtual environment to ensure dependency compatibility +- **Important**: Must run in DiscordGSM's virtual environment ```bash - # Activate DiscordGSM's venv first source /path/to/discordgsm/venv/bin/activate # Linux/Mac # or \path\to\discordgsm\venv\Scripts\activate # Windows @@ -31,24 +30,26 @@ python3 add_server.py \ --game_id GAME_ID \ --address SERVER_ADDRESS \ --query_port QUERY_PORT \ - [--db_path PATH_TO_DB] + [--db_path PATH_TO_DB] \ + [--ignore-existing] ``` -### Required Arguments +### Arguments +Required: - `--guild_id`: Discord server (guild) ID - `--channel_id`: Discord channel ID - `--game_id`: Game identifier (e.g., "minecraft", "valheim") - `--address`: Server address/hostname - `--query_port`: Query port number -### Optional Arguments - +Optional: - `--db_path`: Custom path to `servers.db` (defaults to `./data/servers.db`) +- `--ignore-existing`: Continue execution if server already exists ## Examples -Add a Minecraft server: +Basic usage: ```bash python3 add_server.py \ --guild_id 123456789 \ @@ -58,7 +59,7 @@ python3 add_server.py \ --query_port 25565 ``` -Add a Valheim server with custom database path: +Ignoring existing servers: ```bash python3 add_server.py \ --guild_id 123456789 \ @@ -66,15 +67,27 @@ python3 add_server.py \ --game_id valheim \ --address valheim.example.com \ --query_port 2457 \ - --db_path /opt/discordgsm/data/servers.db + --ignore-existing ``` ## Error Handling -- Script checks for existing servers with same address and query port -- Returns exit code 1 if server already exists or on error -- Prints error message to stdout +- Validates database existence +- Checks for existing servers with same address and query port +- Provides detailed error messages including channel and game info for existing servers +- Returns exit code 1 on error unless `--ignore-existing` is set ## Ansible Integration -Can be used with Ansible for automated deployment. See `add_servers.yml` for playbook example. \ No newline at end of file +Use `add_servers.yml` for automated deployment: + +```yaml +vars: + ignore_existing: true # Set to false to fail on existing servers +``` + +Key features: +- Database existence validation +- Optional failure on existing servers (`ignore_existing`) +- Detailed output summary +- Proper idempotency handling \ No newline at end of file diff --git a/discordgsm/add_server.py b/discordgsm/add_server.py index be6e901..83aa517 100644 --- a/discordgsm/add_server.py +++ b/discordgsm/add_server.py @@ -1,10 +1,11 @@ import sqlite3 import json -from typing import Optional, Dict +from typing import Optional, Dict, Tuple from dataclasses import dataclass import os from pathlib import Path import argparse +import sys @dataclass class ServerConfig: @@ -26,7 +27,8 @@ def __init__(self, db_path: str = None): if db_path is None: db_path = os.path.join(Path(__file__).parent, "data", "servers.db") self.db_path = db_path - Path(os.path.dirname(db_path)).mkdir(parents=True, exist_ok=True) + if not os.path.exists(self.db_path): + raise FileNotFoundError(f"Database not found at {self.db_path}") self._init_db() def _init_db(self): @@ -51,54 +53,66 @@ def _init_db(self): ''') conn.commit() - def server_exists(self, address: str, query_port: int) -> bool: + def server_exists(self, address: str, query_port: int) -> Tuple[bool, Optional[Dict]]: with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() cursor.execute( - 'SELECT COUNT(*) FROM servers WHERE address = ? AND query_port = ?', + 'SELECT channel_id, game_id FROM servers WHERE address = ? AND query_port = ?', (address, query_port) ) - count = cursor.fetchone()[0] - return count > 0 + result = cursor.fetchone() + if result: + return True, { + 'channel_id': result[0], + 'game_id': result[1] + } + return False, None - def add_server(self, config: ServerConfig): - if self.server_exists(config.address, config.query_port): - raise ValueError(f"Server {config.address}:{config.query_port} already exists") + def add_server(self, config: ServerConfig) -> Tuple[bool, str]: + try: + exists, existing_data = self.server_exists(config.address, config.query_port) + if exists: + return False, f"Server {config.address}:{config.query_port} already exists in channel {existing_data['channel_id']} for game {existing_data['game_id']}" - with sqlite3.connect(self.db_path) as conn: - cursor = conn.cursor() - - if config.position is None: - cursor.execute( - 'SELECT COALESCE(MAX(position + 1), 0) FROM servers WHERE channel_id = ?', - (config.channel_id,) - ) - config.position = cursor.fetchone()[0] + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + + if config.position is None: + cursor.execute( + 'SELECT COALESCE(MAX(position + 1), 0) FROM servers WHERE channel_id = ?', + (config.channel_id,) + ) + config.position = cursor.fetchone()[0] - config.query_extra = config.query_extra or {} - config.style_data = config.style_data or {"locale": "en-US", "timezone": "UTC"} - config.result = config.result or {"name": "", "map": "", "password": False, "raw": {}, "connect": "", "numplayers": 0, "numbots": 0, "maxplayers": 0, "players": [], "bots": []} + config.query_extra = config.query_extra or {} + config.style_data = config.style_data or {"locale": "en-US", "timezone": "UTC"} + config.result = config.result or {"name": "", "map": "", "password": False, "raw": {}, "connect": "", "numplayers": 0, "numbots": 0, "maxplayers": 0, "players": [], "bots": []} - cursor.execute(''' - INSERT INTO servers ( - position, guild_id, channel_id, game_id, address, query_port, - query_extra, status, result, style_id, style_data, message_id - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', ( - config.position, - config.guild_id, - config.channel_id, - config.game_id, - config.address, - config.query_port, - json.dumps(config.query_extra), - int(config.status), - json.dumps(config.result), - config.style_id, - json.dumps(config.style_data), - config.message_id - )) - conn.commit() + cursor.execute(''' + INSERT INTO servers ( + position, guild_id, channel_id, game_id, address, query_port, + query_extra, status, result, style_id, style_data, message_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', ( + config.position, + config.guild_id, + config.channel_id, + config.game_id, + config.address, + config.query_port, + json.dumps(config.query_extra), + int(config.status), + json.dumps(config.result), + config.style_id, + json.dumps(config.style_data), + config.message_id + )) + conn.commit() + return True, f"Successfully added server {config.address}:{config.query_port}" + except sqlite3.Error as e: + return False, f"Database error: {str(e)}" + except Exception as e: + return False, f"Unexpected error: {str(e)}" def main(): parser = argparse.ArgumentParser(description='Add server to DiscordGSM') @@ -108,12 +122,13 @@ def main(): parser.add_argument('--address', type=str, required=True) parser.add_argument('--query_port', type=int, required=True) parser.add_argument('--db_path', type=str, help='Path to servers.db') + parser.add_argument('--ignore-existing', action='store_true', help='Continue if server exists') args = parser.parse_args() - automation = DGSMAutomation(db_path=args.db_path) - try: + automation = DGSMAutomation(db_path=args.db_path) + server_config = ServerConfig( guild_id=args.guild_id, channel_id=args.channel_id, @@ -121,11 +136,16 @@ def main(): address=args.address, query_port=args.query_port ) - automation.add_server(server_config) - print(f"Successfully added server {args.address}:{args.query_port}") - except ValueError as e: + + success, message = automation.add_server(server_config) + print(message) + + if not success and not args.ignore_existing: + sys.exit(1) + + except Exception as e: print(f"Error: {str(e)}") - exit(1) + sys.exit(1) if __name__ == "__main__": main() \ No newline at end of file diff --git a/discordgsm/add_servers.yml b/discordgsm/add_servers.yml index a6937ae..287bf50 100644 --- a/discordgsm/add_servers.yml +++ b/discordgsm/add_servers.yml @@ -1,10 +1,11 @@ --- - name: Add servers to DiscordGSM - hosts: discordgsm + hosts: localhost gather_facts: no vars: discordgsm_db: "/path/to/servers.db" + ignore_existing: true # Set to false to fail on existing servers servers: - guild_id: 123456789 channel_id: 987654321 @@ -14,6 +15,12 @@ # Add more servers as needed tasks: + - name: Ensure DiscordGSM database exists + stat: + path: "{{ discordgsm_db }}" + register: db_check + failed_when: not db_check.stat.exists + - name: Add server to DiscordGSM command: > python3 add_server.py @@ -23,6 +30,14 @@ --address {{ item.address }} --query_port {{ item.query_port }} --db_path {{ discordgsm_db }} + {% if ignore_existing %}--ignore-existing{% endif %} register: result - failed_when: result.rc != 0 - with_items: "{{ servers }}" \ No newline at end of file + failed_when: + - result.rc != 0 + - not ignore_existing + changed_when: "'Successfully added server' in result.stdout" + with_items: "{{ servers }}" + + - name: Show results + debug: + msg: "{{ result.results | map(attribute='stdout_lines') | list }}" \ No newline at end of file