commit bb0474fe0789f69b8be07b65cab4c4a83238a6c8 Author: Greg Gauthier Date: Fri Apr 11 22:46:02 2025 +0100 a proper project@ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bade5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/__pycache__/ +.venv/ +.idea/ +keys/ +poetry.lock diff --git a/examples/check-key.py b/examples/check-key.py new file mode 100644 index 0000000..3357bd4 --- /dev/null +++ b/examples/check-key.py @@ -0,0 +1,20 @@ +import requests + + +host = 'forum.lunduke.com' +username = 'gmgauthier' +url = f"https://{host}/posts.json" + + +with open('../keys/cli_key.txt', 'r') as key: + api_key = key.read() +api_key = api_key.replace('\n', '').replace('\r', '') + + +headers={ + "api_key": api_key, + "api_username": username +} +response = requests.get(url,headers=headers) +print(response.status_code) +print(response.json()) diff --git a/examples/example.py b/examples/example.py new file mode 100644 index 0000000..017ed6c --- /dev/null +++ b/examples/example.py @@ -0,0 +1,34 @@ +from lunduke.config import DiscourseConfig +from lunduke.auth import DiscourseAuth +from lunduke.client import DiscourseClient + + +def main(): + # Set up configuration + config = DiscourseConfig( + host='forum.lunduke.com', + username='gmgauthier' + ) + + # Set up authentication + auth = DiscourseAuth( + api_key_file='../keys/cli_key.txt', + username=config.username + ) + + # Create client + client = DiscourseClient(config, auth) + + # Use the client + try: + response = client.get('/posts.json') + posts = response['latest_posts'] + print(f"Found {len(posts)} posts") + for post in posts: + print(post['post_type'], post['username'], post['topic_id']) + except Exception as e: + print(f"Error: {e}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/lunduke/__init__.py b/lunduke/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lunduke/auth.py b/lunduke/auth.py new file mode 100644 index 0000000..7f27467 --- /dev/null +++ b/lunduke/auth.py @@ -0,0 +1,34 @@ +class DiscourseAuth: + """Handles authentication with the Discourse API.""" + + def __init__(self, api_key=None, api_key_file=None, username=None): + """ + Initialize with API key details. + + Args: + api_key: The API key as a string + api_key_file: Path to file containing the API key + username: The API username + """ + self.username = username + self.api_key = None + + if api_key: + self.api_key = api_key + elif api_key_file: + self._load_key_from_file(api_key_file) + + def _load_key_from_file(self, key_file): + """Load API key from file and clean it.""" + with open(key_file, 'r') as f: + self.api_key = f.read().strip().replace('\n', '').replace('\r', '') + + def get_headers(self): + """Return headers for API requests.""" + if not self.api_key or not self.username: + raise ValueError("API key and username must be set") + + return { + "api_key": self.api_key, + "api_username": self.username + } \ No newline at end of file diff --git a/lunduke/client.py b/lunduke/client.py new file mode 100644 index 0000000..7d53e9a --- /dev/null +++ b/lunduke/client.py @@ -0,0 +1,41 @@ +import json + +import requests +from lunduke.config import DiscourseConfig +from lunduke.auth import DiscourseAuth + + +class DiscourseClient: + """Main client for interacting with the Discourse API.""" + + def __init__(self, config, auth): + """ + Initialize with configuration and authentication. + + Args: + config: DiscourseConfig instance + auth: DiscourseAuth instance + """ + self.config = config + self.auth = auth + self.session = requests.Session() + + def get(self, endpoint, params=None) -> dict: + """ + Make a GET request to the API. + + Args: + endpoint: API endpoint (e.g., '/posts.json') + params: Optional query parameters + + Returns: + Response data as JSON + """ + url = f"{self.config.get_base_url()}{endpoint}" + headers = self.auth.get_headers() + + response = self.session.get(url, headers=headers, params=params) + response.raise_for_status() # Raise exception for error status codes + return response.json() + + # Add other HTTP methods as needed (post, put, delete) \ No newline at end of file diff --git a/lunduke/config.py b/lunduke/config.py new file mode 100644 index 0000000..ef65059 --- /dev/null +++ b/lunduke/config.py @@ -0,0 +1,27 @@ +class DiscourseConfig: + """Configuration settings for the Discourse API client.""" + + def __init__(self, host, username=None, config_file=None): + """ + Initialize configuration with host and optional username. + + Args: + host: The Discourse forum host (e.g., 'forum.lunduke.com') + username: The API username + config_file: Optional path to a config file + """ + self.host = host + self.username = username + self.api_url = f"https://{host}" + + if config_file: + self._load_from_file(config_file) + + def _load_from_file(self, config_file): + """Load configuration from a file (JSON/YAML).""" + # Implementation for loading config from file + pass + + def get_base_url(self): + """Return the base URL for API requests.""" + return self.api_url \ No newline at end of file diff --git a/lunduke/endpoints/__init__.py b/lunduke/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lunduke/endpoints/posts.py b/lunduke/endpoints/posts.py new file mode 100644 index 0000000..e69de29 diff --git a/lunduke/endpoints/users.py b/lunduke/endpoints/users.py new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..03082de --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "lunduke-cli" +version = "0.1.0" +description = "An API client for the Lunduke Discourse server" +authors = [ + {name = "Greg Gauthier",email = "gmgauthier@protonmail.com"} +] +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "requests (>=2.32.3,<3.0.0)" +] + + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/scripts/keycheck.sh b/scripts/keycheck.sh new file mode 100755 index 0000000..c3b3fbf --- /dev/null +++ b/scripts/keycheck.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env zsh +# + +export AUTH_STRING = "QpklK0BfIbAZJogoY+sNAZHhch29o7wq4G4vBlAr3XRh7cAISzsW0cALv/b/vFGykw34CRDL8xJoX5vHZHKSe6ulOP3aQytrQAMlYRsO08Lp2otz0U8j+f3zL09bHQwASvCDJeJtUrg5cupTHkjQZR2x6AOO69b4VVppBhponcnE9wpb5wHB0kK0sBsDQEKN4G3rb/MiTT9/TgOshRo2/g6VZ7jYQt/dxDQnmoecWlPaAG0wVV3VOkRF3ZxxML7jZPDl/tN6PGwSEEWnctqkzmLVOcl/kq+6sLTA/bkoA7y9x6T5W7jjqNaSPA2/r0gBjy7WnCrZ5qDFHO8BTvvaHQ==" + +export INKEY = "-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCz2JQg/0hMaFZR +DVm6vobNpgS8bH9nxH0neGE3Tmb3A3vFGgaWnFSoFsQlMu323vXQxfP+PVID4A10 +HAYihadlc0fdP5LgNTBYkkG2ena4ci9m52cRu/lE0MvFigyQJ2LZGg16F7Elz3I0 +KmMdcA++Yqb7nLD3otqF6Y4/T/HVaSw8hVSI+RI98haPSo/UDxSBlGw+Hi8XUiwd +Qsrbn5BCh1UN+Wb/gUa2NnmGg8oH0jCdEb7JzB+XTXlmhNMQjHUhn6fwBbacNcD4 +9cQBK9GiJ1Hk5QLbHyWDF8wdFH6WTcQYPBt2kz/MknWEgw0dNGLhmB12YQrXj4mE +qFSiatqZAgMBAAECggEAIPkKw3f6WePhO2/+rQHEdkzDXoZn328DYSqtbDXoI86U +MSFh5tgXn0+5O3a4cUQUfmfkoY69jC9WWBzRNSAa/jsiCFrhA1FNIVgDS0Dtpkht +D2lKmNJFU8wSKA/02LMX6OThZqqUVHHRpuXEkT+b61Rr+AKU4XoOpXGaHlp6ZJ1a +MBhUkhGttZ8aHz64p/MtqhCttAFdBPkwkhKlll/ABs9qvnF1fifhm+RBqni0m3Xg +6yTmo2JYMX7QnzfXjdxwq5h8DkG9bdWuhTsWQB7AX1M3TJrsHRUWISTTP972V3/F +gshj1zfqRWWIEhiGE647PZxy1VdPOF1jASQtD4g2JwKBgQDsH11dbEdSiXtKkiu/ +ndWrdQhtk8WbFl/nN39rXO7oMQlAKX0W89TY5n+PGZrxDQnlNg5a4jbzOI4QPvud +W19fEAUzsYoqb5LjjsYgbAwNdnjZiiv4ox2bt5taO7MCMH9q80GKtaUCH+m1A0bA +Y4Z3b4rl6WohqmOGKK6bi+pHZwKBgQDC/Gif+TRF4e9GqQTdba0ZNgArb4enHlVR +pLj47aIFg6vrf5D4U74+4Sf8BOhw6yIybnamEFHd2deECqrYizFxTKufkzAvV+Ux +Z3ehuXjFgCg9qSJW2FP3R9Ew5Lthmz/P5uKVR2AhoR2auhaNgw4rnzoAxGjqvRyZ +5RiJnTaN/wKBgHU7FUW+7qJB896QN/xIxr77uhV9WoynTTIk0bRiTZMmVWtvrdVp +dfHCbu6DTfQD/ze34OSqj5GuMIpMWuxDY1R1Rb/mk6yB/LHSPvf17P36JgILoc0u +XxLi09S28ydRINHeuFm/2Y72fTgLymLWhvphfNqtSq4wRH1lUVuU2dpdAoGAMTgD +tPXz4vwAKUb66mYH/sgpzM0PYfj/MmexJWzerCOrnvuJfZWt/TNao3wdrHs+G5rU +qmCOOcEGbNdAfv7L0Ty4ScSesiSuvwTOJu2pdbk+7ymleGSM9WuUe5IRVrcYqYMv +iN0GgBaqYWc90CTXy90aiB0MGsz3zkUNJ5eesMMCgYBuWT8Dqcm7EilaaRjwZkny +UENTDIsovLtJpZyhfXgMmGeYwAdJ8RldOM7KwL/jhVWgKTaZa68rhmp2/+q7Rb0M +YZiE9JdCUlXWOuQ7g7lynMEgWV7ctt6pDibUCaxgvel7UugbhqpM/UYW1RxC6caC +ZOGCjowKexE+WdZqwm0elQ== +-----END PRIVATE KEY-----" + +echo "${AUTH_STRING}" | openssl enc -d -a | openssl pkeyutl -decrypt -inkey "${INKEY}" | cat +