|
| 1 | +import aiohttp |
| 2 | +import asyncio |
| 3 | +from datetime import datetime, timedelta |
| 4 | + |
| 5 | +APTNER_LOGIN = {} |
| 6 | +APTNER_BASEURL = 'https://v2.aptner.com' |
| 7 | +APTNER_HEADERS = { 'Content-Type': 'application/json' } |
| 8 | +APTNER_AUTH_RUNNING = False |
| 9 | +APTNER_AUTH_COND = asyncio.Condition() |
| 10 | +APTNER_SESSION = None |
| 11 | + |
| 12 | +@pyscript_compile |
| 13 | +def aptner_session(**kwargs): |
| 14 | + global APTNER_SESSION |
| 15 | + if APTNER_SESSION is None or APTNER_SESSION.closed: |
| 16 | + APTNER_SESSION = aiohttp.ClientSession(**kwargs) |
| 17 | + return APTNER_SESSION |
| 18 | + |
| 19 | +@time_trigger('shutdown') |
| 20 | +def aptner_close_session(): |
| 21 | + global APTNER_SESSION |
| 22 | + if APTNER_SESSION and not APTNER_SESSION.closed: |
| 23 | + try: |
| 24 | + APTNER_SESSION.close() |
| 25 | + finally: |
| 26 | + APTNER_SESSION = None |
| 27 | + |
| 28 | +@pyscript_compile |
| 29 | +async def aptner_auth(): |
| 30 | + global APTNER_LOGIN, APTNER_HEADERS, APTNER_AUTH_RUNNING, APTNER_AUTH_COND |
| 31 | + async with APTNER_AUTH_COND: |
| 32 | + if APTNER_AUTH_RUNNING: |
| 33 | + try: |
| 34 | + await asyncio.wait_for(APTNER_AUTH_COND.wait(), timeout = 30) |
| 35 | + finally: |
| 36 | + return |
| 37 | + APTNER_AUTH_RUNNING = True |
| 38 | + try: |
| 39 | + if 'Authorization' in APTNER_HEADERS: |
| 40 | + del APTNER_HEADERS['Authorization'] |
| 41 | + response = await aptner_request('POST', '/auth/token', data = APTNER_LOGIN) |
| 42 | + APTNER_HEADERS['Authorization'] = 'Bearer ' + response['accessToken'] |
| 43 | + finally: |
| 44 | + async with APTNER_AUTH_COND: |
| 45 | + APTNER_AUTH_RUNNING = False |
| 46 | + APTNER_AUTH_COND.notify_all() |
| 47 | + |
| 48 | +@pyscript_compile |
| 49 | +async def aptner_request(method, url, data = None): |
| 50 | + global APTNER_HEADERS, APTNER_BASEURL |
| 51 | + kwargs = { 'headers': APTNER_HEADERS } |
| 52 | + if method in ('PUT', 'POST') and data is not None: |
| 53 | + kwargs['json'] = data |
| 54 | + session = aptner_session(base_url = APTNER_BASEURL) |
| 55 | + async with session.request(method, url, **kwargs) as response: |
| 56 | + if response.status != 401 or url == '/auth/token': |
| 57 | + response.raise_for_status() |
| 58 | + try: |
| 59 | + return await response.json() |
| 60 | + except: |
| 61 | + return |
| 62 | + await aptner_auth() |
| 63 | + async with session.request(method, url, **kwargs) as response: |
| 64 | + response.raise_for_status() |
| 65 | + try: |
| 66 | + return await response.json() |
| 67 | + except: |
| 68 | + return |
| 69 | + |
| 70 | +@service(supports_response = 'only') |
| 71 | +def aptner_init(id, password): |
| 72 | + """yaml |
| 73 | +name: 아파트너 로그인 |
| 74 | +description: 최초 아파트너 로그인 필요 |
| 75 | +fields: |
| 76 | + id: |
| 77 | + description: 아파트너아이디 |
| 78 | + example: aptner |
| 79 | + required: true |
| 80 | + password: |
| 81 | + description: 아파트너암호 |
| 82 | + example: password |
| 83 | + required: true |
| 84 | +""" |
| 85 | + global APTNER_LOGIN |
| 86 | + APTNER_LOGIN['id'] = id |
| 87 | + APTNER_LOGIN['password'] = password |
| 88 | + try: |
| 89 | + aptner_auth() |
| 90 | + if 'Authorization' in APTNER_HEADERS: |
| 91 | + return { 'result': 'success' } |
| 92 | + except Exception as e: |
| 93 | + return { 'error': '{}: {}'.format(type(e).__name__, str(e)) } |
| 94 | + return { 'result': 'unknown' } |
| 95 | + |
| 96 | +@service(supports_response = 'only') |
| 97 | +def aptner_findcar(carno = None): |
| 98 | + """yaml |
| 99 | +name: 아파트너 입출차확인 |
| 100 | +description: 아파트너에서 차량의 입/출차를 확인합니다 |
| 101 | +fields: |
| 102 | + carno: |
| 103 | + description: 차량번호 |
| 104 | + example: 123가1234 |
| 105 | +""" |
| 106 | + monthlyAccessHistory = aptner_request('GET', '/pc/monthly-access-history') |
| 107 | + response = {} |
| 108 | + for monthlyParkingHistory in monthlyAccessHistory['monthlyParkingHistoryList']: |
| 109 | + for visitCarUseHistoryReport in monthlyParkingHistory['visitCarUseHistoryReportList']: |
| 110 | + if carno == None or visitCarUseHistoryReport['carNo'] == carno: |
| 111 | + if visitCarUseHistoryReport['carNo'] not in response: |
| 112 | + response[visitCarUseHistoryReport['carNo']] = { |
| 113 | + 'status': 'out' if visitCarUseHistoryReport['isExit'] else 'in', |
| 114 | + } |
| 115 | + if 'inDatetime' in visitCarUseHistoryReport and visitCarUseHistoryReport['inDatetime'] is not None: |
| 116 | + response[visitCarUseHistoryReport['carNo']]['intime'] = visitCarUseHistoryReport['inDatetime'] |
| 117 | + if 'outDatetime' in visitCarUseHistoryReport and visitCarUseHistoryReport['outDatetime'] is not None: |
| 118 | + response[visitCarUseHistoryReport['carNo']]['outtime'] = visitCarUseHistoryReport['outDatetime'] |
| 119 | + if visitCarUseHistoryReport['carNo'] == carno: |
| 120 | + break |
| 121 | + return response |
| 122 | + |
| 123 | +@service(supports_response = 'only') |
| 124 | +def aptner_fee(): |
| 125 | + """yaml |
| 126 | +name: 아파트너 관리비 |
| 127 | +description: 아파트너에서 최근 관리비를 확인합니다 |
| 128 | +""" |
| 129 | + fee = aptner_request('GET', '/fee/detail')['fee'] |
| 130 | + return { 'year': fee['year'], 'month': fee['month'], 'fee': fee['currentFee'], 'details': { item["name"]: item["value"] for item in fee['details'] }} |
| 131 | + |
| 132 | +@service(supports_response = 'only') |
| 133 | +def aptner_get_reserve_status(): |
| 134 | + """yaml |
| 135 | +name: 아파트너 방문차량 예약현황 |
| 136 | +description: 아파트너에서 방문차량의 주차 예약현황을 확인합니다 |
| 137 | +""" |
| 138 | + totalpages = 0 |
| 139 | + currentpage = 0 |
| 140 | + today = datetime.today().date() |
| 141 | + result = {} |
| 142 | + while True: |
| 143 | + currentpage = currentpage + 1 |
| 144 | + reservedcars = aptner_request('GET', '/pc/reserves?pg={}'.format(currentpage)) |
| 145 | + if totalpages == 0: |
| 146 | + totalpages = reservedcars['totalPages'] |
| 147 | + for reservedcar in reservedcars['reserveList']: |
| 148 | + visitdate = datetime.strptime(reservedcar['visitDate'], "%Y.%m.%d").date() |
| 149 | + if today < visitdate: |
| 150 | + if reservedcar['carNo'] not in result: |
| 151 | + result[reservedcar['carNo']] = [] |
| 152 | + result[reservedcar['carNo']].append(visitdate) |
| 153 | + if currentpage >= totalpages: |
| 154 | + break |
| 155 | + for car in result: |
| 156 | + result[car].sort() |
| 157 | + ranges = [] |
| 158 | + start_of_range = result[car][0] |
| 159 | + for i in range(1, len(result[car])): |
| 160 | + previous_date = result[car][i-1] |
| 161 | + current_date = result[car][i] |
| 162 | + if (current_date - previous_date) > timedelta(days = 1): |
| 163 | + ranges.append({ 'from': start_of_range, 'to': previous_date }) |
| 164 | + start_of_range = current_date |
| 165 | + ranges.append({ 'from': start_of_range, 'to': result[car][-1] }) |
| 166 | + result[car] = ranges |
| 167 | + return result |
| 168 | + |
| 169 | +@service() |
| 170 | +def aptner_reserve_car(date, purpose, carno, days, phone): |
| 171 | + """yaml |
| 172 | +name: 아파트너 방문차량 예약 |
| 173 | +description: 아파트너에서 방문차량의 주차를 예약합니다 |
| 174 | +fields: |
| 175 | + date: |
| 176 | + description: 방문일시 |
| 177 | + example: 2025.01.01 |
| 178 | + required: true |
| 179 | + purpose: |
| 180 | + description: 방문목적 |
| 181 | + example: 기타 |
| 182 | + required: true |
| 183 | + carno: |
| 184 | + description: 차량번호 |
| 185 | + example: 111가1111 |
| 186 | + required: true |
| 187 | + days: |
| 188 | + description: 방문기간 |
| 189 | + example: 1 |
| 190 | + required: true |
| 191 | + phone: |
| 192 | + description: 연락처 |
| 193 | + example: 010-0000-0000 |
| 194 | + required: true |
| 195 | +""" |
| 196 | + try: |
| 197 | + aptner_request('POST', '/pc/reserve/', { 'visitDate': date, 'purpose': purpose, 'carNo': carno, 'days': days, 'phone': phone }) |
| 198 | + except: |
| 199 | + pass |
0 commit comments