From 8ee9bf9910a5057009536821fe9a11bc65e94103 Mon Sep 17 00:00:00 2001 From: "Jakob L. Kreuze" Date: Mon, 27 Aug 2018 08:55:13 -0400 Subject: [PATCH] Initial implementation of the student organization API --- README.md | 8 +++ umass_toolkit/student_organizations.py | 95 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 umass_toolkit/student_organizations.py diff --git a/README.md b/README.md index 50182b5..74f23c4 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,14 @@ for soup_name in soups: print(soup_name) ``` +Finding out who's in charge of the UMass club powerlifting team +```python3 +key = student_organizations.search("Powerlifting")[0]["WebsiteKey"] +info = student_organizations.info(key) +contact = info["primaryContact"] +print("Run by {} {}".format(contact["firstName"], contact["lastName"])) +``` + Contributing ------------ We'd love to have you contribute some code to this project! Patches from beginning programmers are welcome! We'll help polish up your code and everything. diff --git a/umass_toolkit/student_organizations.py b/umass_toolkit/student_organizations.py new file mode 100644 index 0000000..6d6b183 --- /dev/null +++ b/umass_toolkit/student_organizations.py @@ -0,0 +1,95 @@ +from urllib.parse import urljoin + +import requests + +URI_BASE = "https://umass.campuslabs.com" +API_BASE = "engage/api/discovery/" + +# The primary endpoint used by the webapp is 'engage/api/discovery/search/ +# organizations', which appears to be Microsoft's Azure Search Service REST API. +# It's some sort of RPC controlled via GET parameters. Documentation available. +# + +def _request(endpoint, params={}): + uri = urljoin(urljoin(URI_BASE, API_BASE), endpoint) + return requests.get(uri, params=params).json() + + +# TODO: Perhaps this should raise an exception rather than returning 0? +# TODO: Also, should this maybe be exposed to the public API? +def _num_organizations(): + # The necessary data is in the "@odata.count" field of every response, but + # if the webapp does this, we might as well. + resp = _request("search/organizations", { + "top": 0, + "facets[0]": "BranchId" + }) + structs = resp.get("@search.facets", {}).get("BranchId", {}) + if len(structs) >= 1: + return structs[0].get("count", 0) + return 0 + + +def _category_name(category_id): + resp = _request("organization/category", { + "categoryIds[0]": category_id + }) + structs = resp.get("items", []) + if len(structs) >= 1: + return structs[0].get("name") + + +def _category_ids(): + resp = _request("search/organizations", { + "top": 0, + "facets[0]": "CategoryIds,count:{}".format(_num_organizations()) + }) + structs = resp.get("@search.facets", {}).get("CategoryIds", {}) + return [category.get("value") for category in structs] + + +def _additional_fields(organization_id): + resp = _request("organization/{}/additionalFields".format(organization_id)) + return resp["items"] + + +def categories(): + """Returns a list of valid category strings, for use in the 'categories' + parameter of 'student_organizations.search'. + """ + return [_category_name(category) for category in _category_ids()] + + +def info(websitekey): + """Returns all information on the organization with the given name. A valid + 'websitekey' parameter can be obtained from 'search'.""" + resp = _request("organization/bykey/{}".format(websitekey)) + organization_id = resp["id"] + resp["additionalFields"] = _additional_fields(organization_id) + return resp + + +def search(keywords="", categories=[]): + """Return organizations matching the given keywords and categories.""" + # Reverse-lookup is expensive. + filter_string = "" + if len(categories) > 0: + filter_string = [] + for category_id in _category_ids(): + if _category_name(category_id) in categories: + filter_string.append( + "CategoryIds/any(x: x eq '{}')".format(category_id) + ) + filter_string = "(" + " or ".join(filter_string) + ")" + + resp = _request("search/organizations", { + "top": _num_organizations(), + "filter": filter_string, + "query": keywords + }) + return resp.get("value", []) + + +def all_organizations(): + """Return a list of every registered organization.""" + return search()