#!/usr/bin/env python3 import requests from lxml import etree from common import doors, getMembershipworksData, memberLevels from hid.DoorController import E, ROOT class Member(): def __init__(self, forename, surname, membershipWorksID, middleName="", email="", phone="", cardholderID=None, doorAccess=[], onHold=False, credentials=[], levels=[], schedules=[]): self.forename = forename self.surname = surname self.membershipWorksID = membershipWorksID self.middleName = middleName self.email = email self.phone = phone self.cardholderID = cardholderID self.doorAccess = doorAccess self.onHold = onHold self.credentials = credentials self.levels = levels self.schedules = schedules def __repr__(self): return f"""Name: {self.forename} | {self.middleName} | {self.surname} MembershipWorks ID: {self.membershipWorksID} Email: {self.email} Phone: {self.phone} Cardholder ID: {self.cardholderID} doorAccess: {self.doorAccess} On Hold? {self.onHold} Credentials: {self.credentials} Levels: {self.levels} Schedules: {self.schedules} """ @classmethod def from_cardholder(cls, data, cardFormats): # TODO: maybe keep all attribs, and just add them in # from_MembershipWorks? cardFormatsByID = {v: k for k, v in cardFormats.items()} credentials = [ (cardFormatsByID[c.attrib['formatID']], c.attrib['cardNumber']) for c in data.findall('{*}Credential')] roles = [r.attrib['scheduleName'] for r in data.findall('{*}Role')] levels = data.attrib.get('custom1', "").split('|') return cls( forename=data.get('forename', ""), surname=data.get('surname', ""), membershipWorksID=data.attrib.get('custom2', ""), middleName=data.attrib.get('middleName', ""), email=data.attrib.get('email', ""), phone=data.attrib.get('phone', ""), cardholderID=data.attrib['cardholderID'], credentials=credentials, levels=levels, schedules=roles) @classmethod def from_MembershipWorks(cls, data): if data["Access Card Number"] != "": credentials = [(data["Access Card Facility Code"], data["Access Card Number"])] else: credentials = [] levels = {k: v for k, v in memberLevels.items() if data[k] == k} doorAccess = [door for door, doorData in doors.items() if data["Access " + doorData.access + "?"] == "Y"] return cls(data["First Name"], data["Last Name"], membershipWorksID=data["Account ID"], email=data["Email"], phone=data["Phone"], doorAccess=doorAccess, onHold=data["Account on Hold"] != "", credentials=credentials, levels=list(levels.keys()), schedules=list(levels.values())) def attribs(self): out = {"forename": self.forename, "surname": self.surname, "middleName": self.middleName, "email": self.email, "phone": self.phone, "custom1": "|".join(self.levels).replace("&", "and"), "custom2": self.membershipWorksID} return out def make_roles(self, door, schedulesMap): if door.name not in self.doorAccess or self.onHold: return [] for schedule in self.schedules: yield E.Role({"roleID": self.cardholderID, "scheduleID": schedulesMap[schedule], "resourceID": "0"}) def make_credentials(self, cardFormats): for credential in self.credentials: yield E.Credential( {"formatName": credential[0], "cardNumber": credential[1], "formatID": cardFormats[credential[0]], "isCard": "true", "cardholderID": self.cardholderID}) def get_cardholders(door, cardFormats): cardholders = door.doXMLRequest( ROOT(E.Cardholders({"action": "LR", "responseFormat": "expanded", "recordOffset": "0", "recordCount": "1000"}))) for cardholder in cardholders[0]: yield Member.from_cardholder(cardholder, cardFormats) def get_cardFormats(door): cardFormats = door.doXMLRequest( ROOT(E.CardFormats({"action": "LR", "responseFormat": "expanded"}))) return {fmt[0].attrib["value"]: fmt.attrib["formatID"] for fmt in cardFormats[0].findall('{*}CardFormat[{*}FixedField]')} def get_schedules(door): schedules = door.doXMLRequest( ROOT(E.Schedules({"action": "LR", "recordOffset": "0", "recordCount": "8"}))) return {fmt.attrib["scheduleName"]: fmt.attrib["scheduleID"] for fmt in schedules[0]} def update_credentials(member, cardFormats): return E.Credentials({"action": "AD"}, *member.make_credentials(cardFormats)) def update_schedules(member, door, schedulesMap): return E.RoleSet({"action": "UD", "roleSetID": member.cardholderID}, E.Roles(*member.make_roles(door, schedulesMap))) def update_door(door, members): cardFormats = get_cardFormats(door) cardholders = {ch.membershipWorksID: ch for ch in get_cardholders(door, cardFormats)} schedulesMap = get_schedules(door) for member in members: # TODO: can I combine requests? if member.membershipWorksID in cardholders: # cardholder exists, compare contents ch = cardholders.pop(member.membershipWorksID) member.cardholderID = ch.cardholderID if member.attribs() != ch.attribs(): # update cardholder print(f"- Updating profile for {member.forename} {member.surname}") print(f" - Old: {ch.attribs()}") print(f" - New: {member.attribs()}") door.doXMLRequest(ROOT( E.Cardholders( {"action": "UD", "cardholderID": member.cardholderID}, E.CardHolder(member.attribs())))) if member.credentials != ch.credentials: print(f"- Updating card for {member.forename} {member.surname}") print(f" - {ch.credentials} -> {member.credentials}") door.doXMLRequest(ROOT(update_credentials(member, cardFormats))) # TODO: might not handle people on hold correctly? schedulesForDoor = member.schedules if door.name in member.doorAccess else [] if schedulesForDoor != ch.schedules: print("- Updating schedule for" + f" {member.forename} {member.surname}:" + f" {ch.schedules} -> {member.schedules}") door.doXMLRequest(ROOT(update_schedules(member, door, schedulesMap))) else: # do add print(f"- Adding Member:") print(member) resp = door.doXMLRequest(ROOT( E.Cardholders({"action": "AD"}, E.Cardholder(member.attribs())))) member.cardholderID = resp.find('{*}Cardholders/{*}Cardholder') \ .attrib["cardholderID"] door.doXMLRequest(ROOT(update_credentials(member, cardFormats), update_schedules(member, door, schedulesMap))) # TODO: delete cardholders that are no longer members? def main(): memberData = getMembershipworksData( ['members', 'staff', 'misc'], "_id,nam,phn,eml,lvl,lbl,xws,xms,xsc,xas,xfd,xac,xcf") members = [Member.from_MembershipWorks(m) for m in memberData] for door in doors.values(): print(door.name, door.ip) update_door(door, members) # m = Member("test", "test", membershipWorksID="5af07954afd691b84c15a24d", # credentials=[("A901146A-241", "20178")], # schedules=["15:00-24:00"], # doorAccess=["Front Door"]) # update_door(doors["Front Door"], [m]) main()