diff --git a/memberPlumbing/MembershipWorks.py b/memberPlumbing/MembershipWorks.py index 20dee83..fbc0d0e 100644 --- a/memberPlumbing/MembershipWorks.py +++ b/memberPlumbing/MembershipWorks.py @@ -3,7 +3,7 @@ from io import StringIO import requests -BASE_URL = "https://api.membershipworks.com/v1/" +BASE_URL = "https://api.membershipworks.com" # extracted from `SF._is.crm` in https://cdn.membershipworks.com/all.js CRM = { @@ -72,21 +72,30 @@ class MembershipWorksRemoteError(Exception): class MembershipWorks: def __init__(self): + self.sess = requests.Session() self.org_info = None self.auth_token = None self.org_num = None def login(self, username, password): """Authenticate against the membershipworks api""" - r = requests.post( - BASE_URL + "usr", - data={"_st": "all", "eml": username, "org": "10000", "pwd": password}, + r = self.sess.post( + BASE_URL + "/v2/account/session", + data={"eml": username, "pwd": password}, + headers={"X-Org": "10000"}, ) if r.status_code != 200 or "SF" not in r.json(): raise MembershipWorksRemoteError("login", r) self.org_info = r.json() self.auth_token = self.org_info["SF"] self.org_num = self.org_info["org"] + self.sess.headers.update( + { + "X-Org": str(self.org_num), + "X-Role": "admin", + "Authorization": "Bearer " + self.auth_token, + } + ) def _inject_auth(self, kwargs): # TODO: should probably be a decorator or something @@ -97,12 +106,12 @@ class MembershipWorks: kwargs["params"] = {} kwargs["params"]["SF"] = self.auth_token - def _get(self, *args, **kwargs): + def _get_v1(self, *args, **kwargs): self._inject_auth(kwargs) # TODO: should probably do some error handling in here return requests.get(*args, **kwargs) - def _post(self, *args, **kwargs): + def _post_v1(self, *args, **kwargs): self._inject_auth(kwargs) # TODO: should probably do some error handling in here return requests.post(*args, **kwargs) @@ -141,32 +150,30 @@ class MembershipWorks: for dek in self.org_info["dek"]: # TODO: there must be a better way. this is stupid if dek["dek"] == 1: - ret["folders"][dek["lbl"]] = dek["_id"] + ret["folders"][dek["lbl"]] = dek["did"] elif "cur" in dek: - ret["levels"][dek["lbl"]] = dek["_id"] + ret["levels"][dek["lbl"]] = dek["did"] elif "mux" in dek: - ret["addons"][dek["lbl"]] = dek["_id"] + ret["addons"][dek["lbl"]] = dek["did"] else: - ret["labels"][dek["lbl"]] = dek["_id"] + ret["labels"][dek["lbl"]] = dek["did"] return ret def get_member_ids(self, folders): folder_map = self._parse_flags()["folders"] - r = self._get( - BASE_URL + "ylp", - params={ - "lbl": ",".join([folder_map[f] for f in folders]), - "org": self.org_num, - "var": "_id,nam,ctc", - }, + r = self.sess.get( + BASE_URL + "/v2/accounts", + params={"dek": ",".join([folder_map[f] for f in folders])}, ) if r.status_code != 200 or "usr" not in r.json(): raise MembershipWorksRemoteError("user listing", r) # get list of member ID matching the search - return [user["uid"] for user in r.json()["usr"]] + # dedup with set() to work around people with alt uids + # TODO: figure out why people have alt uids + return set(user["uid"] for user in r.json()["usr"]) # TODO: has issues with aliasing header names: # ex: "Personal Studio Space" Label vs Membership Addon/Field @@ -179,8 +186,8 @@ class MembershipWorks: # get members CSV # TODO: maybe can just use previous get instead? would return JSON - r = self._post( - BASE_URL + "csv", + r = self._post_v1( + BASE_URL + "/v1/csv", data={ "_rt": "946702800", # unknown "mux": "", # unknown @@ -200,8 +207,8 @@ class MembershipWorks: json gets a different version of the transactions list, which contains a different set information """ - r = self._get( - BASE_URL + "csv", + r = self._get_v1( + BASE_URL + "/v1/csv", params={ "crm": "12,13,14,18,19", # transaction types, see CRM **({"txl": ""} if json else {}),