commit 0d8de9de1aa4e33cc5abbc0756504880f244e805 Author: Carsten Schabacker Date: Tue Feb 6 13:38:31 2024 +0100 Initial working version still a lot to do diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36ed097 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config.ini +.venv/ diff --git a/ActiveCollabAPI/ACAuthenticator.py b/ActiveCollabAPI/ACAuthenticator.py new file mode 100644 index 0000000..91e00fa --- /dev/null +++ b/ActiveCollabAPI/ACAuthenticator.py @@ -0,0 +1,45 @@ +# system libs +import json + +# 3rd party libs +import requests + + +class ACAuthenticator: + + user = None + accounts = None + + # FIXME: put into config + # This is only for cloud instance! + base_url = 'https://activecollab.com/api/v1/external/login' + + def headers(self): + return { + 'Content-Type': 'application/json; charset=utf8', + 'Accept': 'application/json', + 'User-Agent': 'Active Collab API Wrapper; v3.0.0' + } + + def login(self, email, password): + login_data = { + 'email': email, + 'password': password + } + res = requests.post(self.base_url, + data=json.dumps(login_data), + headers=self.headers()) + if res.status_code != 200: + raise Exception('Login failed!') + + login_res = res.json() + if login_res['is_ok'] != 1: + raise Exception('Login failed! (2)') + + self.user = login_res['user'] + self.accounts = login_res['accounts'] + return login_res + + def find_account(self, name): + # FIXME: implement search + return self.accounts[0] diff --git a/ActiveCollabAPI/ACClient.py b/ActiveCollabAPI/ACClient.py new file mode 100644 index 0000000..bd89b60 --- /dev/null +++ b/ActiveCollabAPI/ACClient.py @@ -0,0 +1,48 @@ +import json + +import requests + + +class ACClient: + + account = None + token = None + + base_url = None + + # FIXME: put into config + # API Version + api_version = '1' + + # FIXME: put into config + user_agent = 'Active Collab API Wrapper; v3.0.0' + + def __init__(self, account, token): + self.account = account + self.token = token + # + self.base_url = self.account['url'] + '/api/v%s' % self.api_version + + def headers(self): + return { + 'Content-Type': 'application/json; charset=utf8', + 'Accept': 'application/json', + 'X-Angie-AuthApiToken': self.token, + 'User-Agent': self.user_agent + } + + def _get(self, url): + return requests.get(self.base_url + '/' + url, + headers=self.headers()) + + def _post(self, url, data): + return requests.post( + self.base_url + '/' + url, + headers=self.headers(), + data=json.dumps(data)) + + def get_info(self): + return self._get('info') + + def get_projects(self): + return self._get('projects') diff --git a/ActiveCollabAPI/ACTokenAuthenticator.py b/ActiveCollabAPI/ACTokenAuthenticator.py new file mode 100644 index 0000000..7f82575 --- /dev/null +++ b/ActiveCollabAPI/ACTokenAuthenticator.py @@ -0,0 +1,52 @@ +import json + +import requests + + +class ACTokenAuthenticator: + + account = None + user = None + base_url = "" + token = None + + # FIXME: put into config + client_name = 'Python Script' + client_vendor = 'Me' + + # FIXME: put into config + # API Version + api_version = '1' + + # FIXME: put into config + user_agent = 'Active Collab API Wrapper; v3.0.0' + + def __init__(self, account, user): + self.account = account + self.user = user + # + self.base_url = self.account['url'] + '/api/v%s' % self.api_version + + def issue_token_intent(self): + # use intent to get a token + data = { + 'intent': self.user['intent'], + 'client_name': self.client_name, + 'client_vendor': self.client_vendor + } + r = requests.post(self.base_url+ '/issue-token-intent', + headers=self.headers(), + data=json.dumps(data)) + if r.status_code != 200: + raise Exception('Error in issuing token intent!') + token_res = r.json() + if token_res['is_ok'] != 1: + raise Exception('Error in issuing token (2)') + return token_res['token'] + + def headers(self): + return { + 'Content-Type': 'application/json; charset=utf8', + 'Accept': 'application/json', + 'User-Agent': self.user_agent + } diff --git a/ActiveCollabAPI/__init__.py b/ActiveCollabAPI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config-example.ini b/config-example.ini new file mode 100644 index 0000000..93306e6 --- /dev/null +++ b/config-example.ini @@ -0,0 +1,5 @@ + +[LOGIN] +username = you@example.com +password = very-secret +account_name = "#123456" \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..038b957 --- /dev/null +++ b/main.py @@ -0,0 +1,51 @@ +import configparser +import logging +from pprint import pprint + +from ActiveCollabAPI.ACAuthenticator import ACAuthenticator +from ActiveCollabAPI.ACClient import ACClient +from ActiveCollabAPI.ACTokenAuthenticator import ACTokenAuthenticator + +# Enabling debugging at http.client level (requests->urllib3->http.client) +# you will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. +# the only thing missing will be the response.body which is not logged. +from http.client import HTTPConnection + + +HTTPConnection.debuglevel = 1 + +logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests +logging.getLogger().setLevel(logging.DEBUG) +requests_log = logging.getLogger("urllib3") +requests_log.setLevel(logging.DEBUG) +requests_log.propagate = True + +if __name__ == "__main__": + config = configparser.ConfigParser() + config.read('config.ini') + + # FIXME: put into config + username = config['LOGIN']['username'] + password = config['LOGIN']['password'] + account_name = config['LOGIN']['account_name'] + + # 1. you need to login to get a session and a list of accounts + # (here we use only the first account) + auth = ACAuthenticator() + session = auth.login(username, password) + pprint(session) + user = session['user'] + # use first account + account = auth.find_account(account_name) + + # 2. you need to get a token for this account + token = ACTokenAuthenticator(account, user).issue_token_intent() + pprint(token) + + # 3. now you can query the API + ac = ACClient(account, token) + res = ac.get_projects() + pprint(res.json()) + + res = ac.get_info() + pprint(res.json()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests