From 82bc248bff0957a29aa082fcc0577e6d3937cc1b Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Wed, 16 Mar 2022 23:04:25 +0100 Subject: [PATCH 01/40] initial --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5eba6e9..e9d74b4 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -inpost hehe \ No newline at end of file +branch for development process \ No newline at end of file From ef06a411f431f28f56087a0f363a2e1086caff7b Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Wed, 16 Mar 2022 23:34:11 +0100 Subject: [PATCH 02/40] init api and parcels --- api/api.py | 0 static/parcels.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 api/api.py create mode 100644 static/parcels.py diff --git a/api/api.py b/api/api.py new file mode 100644 index 0000000..e69de29 diff --git a/static/parcels.py b/static/parcels.py new file mode 100644 index 0000000..e69de29 From f6cacc3db234b7c072148e8004db9401707d2e9c Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 17 Mar 2022 23:34:49 +0100 Subject: [PATCH 03/40] structure --- api/__init__.py | 0 api/api.py | 24 ++++++++++++++++++++++++ requirements.txt | 2 ++ setup.py | 12 ++++++++++++ static/__init__.py | 0 static/exceptions.py | 0 static/parcels.py | 5 +++++ 7 files changed, 43 insertions(+) create mode 100644 api/__init__.py create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 static/__init__.py create mode 100644 static/exceptions.py diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api.py b/api/api.py index e69de29..17215ca 100644 --- a/api/api.py +++ b/api/api.py @@ -0,0 +1,24 @@ +from aiohttp import ClientSession + + +class Inpost(): + def __init__(self): + self.url: str = ... + self.token: str = ... + self.auth = ... + self.username: str = ... + self.password: str = ... + self.sess: ClientSession = ... + ... + + def __repr__(self): + ... + + async def get(self): + yield self.sess.get() + + async def post(self): + yield self.sess.post() + + async def authenticated(self): + ... diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3ecba9b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +aiohttp~=3.8.1 +setuptools~=57.0.0 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fd82762 --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +from setuptools import find_packages, setup + +setup( + name='inpost-python', + packages=find_packages(), + version='0.0.1', + description='Python API written in python', + author='loboda4450, mrkazik99', + author_email='loboda4450@gmail.com, mrkazik99@gmail.com', + + license='LGPL 2.1', +) diff --git a/static/__init__.py b/static/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/static/exceptions.py b/static/exceptions.py new file mode 100644 index 0000000..e69de29 diff --git a/static/parcels.py b/static/parcels.py index e69de29..6b623ff 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -0,0 +1,5 @@ +from enum import Enum + + +class Parcel(Enum): + ... From 30f4cbfa460cc52e373c2537282ed96caa1d11fb Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Fri, 18 Mar 2022 16:00:50 +0100 Subject: [PATCH 04/40] working authentication --- api/api.py | 41 +++++++++++++++++++++++------------------ static/endpoints.py | 1 + static/exceptions.py | 2 ++ static/headers.py | 1 + 4 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 static/endpoints.py create mode 100644 static/headers.py diff --git a/api/api.py b/api/api.py index 17215ca..9168c8f 100644 --- a/api/api.py +++ b/api/api.py @@ -1,24 +1,29 @@ from aiohttp import ClientSession +from json import dumps +from static.endpoints import login +from static.headers import appjson +from static.exceptions import NotAuthenticatedError -class Inpost(): - def __init__(self): - self.url: str = ... - self.token: str = ... - self.auth = ... - self.username: str = ... - self.password: str = ... - self.sess: ClientSession = ... - ... +class Inpost: + def __init__(self, uname: str, passwd: str): + self.username: str = uname + self.password: str = passwd + self.token: str | None = None + self.auth: bool | None = None + self.sess: ClientSession = ClientSession() def __repr__(self): - ... + return f'Username: {self.username}\nPassword: {self.password}\nToken: {self.token}' - async def get(self): - yield self.sess.get() - - async def post(self): - yield self.sess.post() - - async def authenticated(self): - ... + async def authenticate(self): + resp = await self.sess.post(url=login, + headers=appjson, + data=dumps({'login': self.username, + 'password': self.password})) + if resp.status == 200: + self.auth = True + token = await resp.json() + self.token = token['token'] + else: + raise NotAuthenticatedError diff --git a/static/endpoints.py b/static/endpoints.py new file mode 100644 index 0000000..32f71f7 --- /dev/null +++ b/static/endpoints.py @@ -0,0 +1 @@ +login: str = 'https://manager.paczkomaty.pl/v1/api/auth/login' diff --git a/static/exceptions.py b/static/exceptions.py index e69de29..ed87bfa 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -0,0 +1,2 @@ +class NotAuthenticatedError(Exception): + pass diff --git a/static/headers.py b/static/headers.py new file mode 100644 index 0000000..9e06775 --- /dev/null +++ b/static/headers.py @@ -0,0 +1 @@ +appjson = {"Content-Type": "application/json"} From 74b550293b4da47db9709fbdffe5ea01185cd1e0 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Fri, 18 Mar 2022 16:20:06 +0100 Subject: [PATCH 05/40] parcels types and base methods to compare --- static/exceptions.py | 6 ++++++ static/parcels.py | 27 +++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/static/exceptions.py b/static/exceptions.py index ed87bfa..b58bfdb 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -1,2 +1,8 @@ +# ----------------- Parcels ----------------- # +class SomeParcelError(Exception): + pass + + +# ----------------- API ----------------- # class NotAuthenticatedError(Exception): pass diff --git a/static/parcels.py b/static/parcels.py index 6b623ff..ef8f7b0 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -1,5 +1,28 @@ from enum import Enum -class Parcel(Enum): - ... +class ParcelBase(Enum): + def __gt__(self, other): + ... + + def __ge__(self, other): + ... + + def __le__(self, other): + ... + + def __lt__(self, other): + ... + + +class ParcelDelivery(ParcelBase): + A = 1 + B = 2 + C = 3 + D = 4 + + +class ParcelLocker(ParcelBase): + A = 1 + B = 2 + C = 3 From 4e0f4698116b2eeebf9616096d65bbb076a064a8 Mon Sep 17 00:00:00 2001 From: Mrkazik99 Date: Sat, 26 Mar 2022 00:08:49 +0100 Subject: [PATCH 06/40] Added some endpoints --- static/endpoints.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/static/endpoints.py b/static/endpoints.py index 32f71f7..d38c03f 100644 --- a/static/endpoints.py +++ b/static/endpoints.py @@ -1 +1,6 @@ login: str = 'https://manager.paczkomaty.pl/v1/api/auth/login' + +# \/ Secured by JWT \/ + +user_infos: str = 'https://manager.paczkomaty.pl/v1/api/auth/user' +get_parcels: str = 'https://manager.paczkomaty.pl/v1/api/parcels' From 70a05ddb2a94b81a2872d5307ece35ee2ecf0840 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 23 Jun 2022 00:34:13 +0200 Subject: [PATCH 07/40] setup update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fd82762..a2bad49 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ name='inpost-python', packages=find_packages(), version='0.0.1', - description='Python API written in python', + description='InPost API written in python', author='loboda4450, mrkazik99', author_email='loboda4450@gmail.com, mrkazik99@gmail.com', From 9ad8764d81a58db1392b37a7540643530a4f28f2 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 23 Jun 2022 00:42:36 +0200 Subject: [PATCH 08/40] added new method definitions and exceptions --- api/api.py | 23 ++++++++++++++++++++++- static/exceptions.py | 5 +++++ static/parcels.py | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/api/api.py b/api/api.py index 9168c8f..6e515ea 100644 --- a/api/api.py +++ b/api/api.py @@ -2,7 +2,7 @@ from json import dumps from static.endpoints import login from static.headers import appjson -from static.exceptions import NotAuthenticatedError +from static.exceptions import NotAuthenticatedError, NotImplementedError class Inpost: @@ -26,4 +26,25 @@ async def authenticate(self): token = await resp.json() self.token = token['token'] else: + raise NotAuthenticatedError(await resp.text()) + + async def disconnect(self): + await self.sess.close() + + async def my_parcels(self): + if not self.auth: + raise NotAuthenticatedError + + raise NotImplementedError + + async def send_parcel(self): + if not self.auth: raise NotAuthenticatedError + + raise NotImplementedError + + async def new_parcel(self): + if not self.auth: + raise NotAuthenticatedError + + raise NotImplementedError diff --git a/static/exceptions.py b/static/exceptions.py index b58bfdb..912bac0 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -6,3 +6,8 @@ class SomeParcelError(Exception): # ----------------- API ----------------- # class NotAuthenticatedError(Exception): pass + + +# ----------------- Other ----------------- # +class NotImplementedError(Exception): + pass \ No newline at end of file diff --git a/static/parcels.py b/static/parcels.py index ef8f7b0..b1bc87b 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -14,6 +14,9 @@ def __le__(self, other): def __lt__(self, other): ... + def __eq__(self, other): + ... + class ParcelDelivery(ParcelBase): A = 1 From 3a7e8a97d8b88e626c0963c3aa7ee4ce80889e8b Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sun, 26 Jun 2022 20:45:30 +0200 Subject: [PATCH 09/40] new endpoints --- static/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/endpoints.py b/static/endpoints.py index d38c03f..2b0a165 100644 --- a/static/endpoints.py +++ b/static/endpoints.py @@ -1,5 +1,5 @@ login: str = 'https://manager.paczkomaty.pl/v1/api/auth/login' - +logout: str = 'https://manager.paczkomaty.pl/v1/api/auth/logout' # \/ Secured by JWT \/ user_infos: str = 'https://manager.paczkomaty.pl/v1/api/auth/user' From 31a1e3642cdb68c36290992277405d0ddfb4e34e Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sun, 26 Jun 2022 20:46:13 +0200 Subject: [PATCH 10/40] new definitions refering to parcels --- static/parcels.py | 68 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/static/parcels.py b/static/parcels.py index b1bc87b..41768f3 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -18,14 +18,78 @@ def __eq__(self, other): ... -class ParcelDelivery(ParcelBase): +class ParcelCarrierSize(ParcelBase): A = 1 B = 2 C = 3 D = 4 -class ParcelLocker(ParcelBase): +class ParcelLockerSize(ParcelBase): A = 1 B = 2 C = 3 + + +class ParcelDeliveryType(ParcelBase): + PARCELLOCKER = 1 + CARRIER = 2 + + +class ParcelShipmentType(ParcelBase): + PARCELLOCKER = 1 + CARRIER = 2 + PARCELPOINT = 3 + + +class ParcelAdditionalInsurance(ParcelBase): + UNINSURANCED = 1 + ONE = 2 # UPTO 5000 + TWO = 3 # UPTO 10000 + THREE = 4 # UPTO 20000 + + +class ParcelForm(ParcelBase): + OUTCOMING = 1 + INCOMING = 2 + + +class ParcelStatus(ParcelBase): + CREATED = 1 + CUSTOMERSTORED = 2 + CUSTOMERSENT = 3 + SENT = 4 + INTRANSIT = 5 + RETURNEDTOSORTINGCENTER = 6 + DELIVEREDTOSORTINGCENTER = 7 + RETURNEDTOSENDER = 8 + PREPARED = 9 + CUSTOMERDELIVERING = 10 + STORED = 11 + EXPIRED = 12 + AVIZO = 13 + RETURNEDTOAGENCY = 14 + RETUNEDTOAGENCY = 15 + DELIVEREDTOAGENCY = 16 + CANCELLED = 17 + LABELEXPIRED = 18 + LABELDESTROYED = 19 + MISSING = 20 + CLAIMED = 21 + NOTDELIVERED = 22 + OVERSIZED = 23 + DELIVERED = 24 + + +class ParcelServiceName(ParcelBase): + ALLEGRO_PARCEL = 1 + ALLEGRO_PARCEL_SMART = 2 + ALLEGRO_LETTER = 3 + ALLEGRO_COURIER = 4 + STANDARD = 5 + STANDARD_PARCEL_SMART = 6 + PASS_THRU = 7 + CUSTOMER_SERVICE_POINT = 8 + REVERSE = 9 + STANDARD_COURIER = 10 + REVERSE_RETURN = 11 From 59b981199e9b174df9c92307da07bfc866245d60 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sun, 26 Jun 2022 20:46:34 +0200 Subject: [PATCH 11/40] new exceptions --- static/exceptions.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/static/exceptions.py b/static/exceptions.py index 912bac0..455e6d5 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -1,12 +1,31 @@ # ----------------- Parcels ----------------- # +from typing import Optional, Any + + class SomeParcelError(Exception): pass # ----------------- API ----------------- # class NotAuthenticatedError(Exception): - pass + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + +class SomeAPIError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + @property + def stack(self): + return self.reason # ----------------- Other ----------------- # class NotImplementedError(Exception): From 5bf2fe7ebff217fcacb57c80a5e422fe9ad6d0f2 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sun, 26 Jun 2022 20:49:33 +0200 Subject: [PATCH 12/40] authentication and disconnect update, new logout method, send_parcel definition --- api/api.py | 76 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/api/api.py b/api/api.py index 6e515ea..394378d 100644 --- a/api/api.py +++ b/api/api.py @@ -1,8 +1,11 @@ +from typing import Optional, List, Union + from aiohttp import ClientSession from json import dumps -from static.endpoints import login +from static.endpoints import login, logout, get_parcels from static.headers import appjson -from static.exceptions import NotAuthenticatedError, NotImplementedError +from static.exceptions import NotAuthenticatedError, NotImplementedError, SomeAPIError +from static.parcels import ParcelDeliveryType, ParcelForm, ParcelCarrierSize, ParcelLockerSize, ParcelShipmentType class Inpost: @@ -10,41 +13,80 @@ def __init__(self, uname: str, passwd: str): self.username: str = uname self.password: str = passwd self.token: str | None = None - self.auth: bool | None = None self.sess: ClientSession = ClientSession() def __repr__(self): return f'Username: {self.username}\nPassword: {self.password}\nToken: {self.token}' async def authenticate(self): - resp = await self.sess.post(url=login, - headers=appjson, - data=dumps({'login': self.username, - 'password': self.password})) - if resp.status == 200: - self.auth = True - token = await resp.json() - self.token = token['token'] + async with await self.sess.post(url=login, + headers=appjson, + data=dumps({'login': self.username, + 'password': self.password})) as resp: + if resp.status == 200: + token = await resp.json() + self.token = token['token'] + else: + raise NotAuthenticatedError(reason=resp) + + async def logout(self): + if self.token is not None: + async with await self.sess.post(url=logout, + headers={'X-Authorization': f'Bearer {self.token}'}) as resp: + if resp.status == 200: + print(await resp.text()) + self.token = None + else: + raise SomeAPIError(reason=resp) else: - raise NotAuthenticatedError(await resp.text()) + raise NotAuthenticatedError(reason='Not logged in') async def disconnect(self): + await self.logout() await self.sess.close() - async def my_parcels(self): - if not self.auth: + # page=1&limit=10&filters=%5BpackType%7Beq%7Doutgoing%5D%2C%5BserviceName%7Bin%7DSTANDARD%2CSTANDARD_PARCEL_SMART%2CPASS_THRU%2CCUSTOMER_SERVICE_POINT%2CREVERSE%2CSTANDARD_COURIER%2CREVERSE_RETURN%5D%2C%5BdateFrom%7Beq%7D2022-06-16%5D%2C%5BdateTo%7Beq%7D2022-06-26%5D + async def get_parcels(self, + sender: Optional[str], + customer_reference: Optional[str], + parcel_code: Optional[Union[List[str], str]], + service_name: Union[List[str], str], + status: Optional[Union[List[str], str]], + date_from: str, + date_to: str, + end_of_week_collection: Optional[bool], + parcels_type: ParcelForm = ParcelForm.INCOMING, + limit=20, + page=1): + if not self.token: raise NotAuthenticatedError raise NotImplementedError - async def send_parcel(self): - if not self.auth: + async def send_parcel(self, + recipient_email: str, + recipient_phone_number: str, + parcel_type: ParcelDeliveryType, + parcel_size: Union[ParcelLockerSize, ParcelCarrierSize], # only for parcel locker + parcel_locker_code: Optional[str], + shipment_method: ParcelShipmentType, + referral_number: Optional[str], + name: str, + bussines_name: str, + zip_code: str, + city: str, + street: str, + building_number: int, + apartment_numer: int, + charge_value: Optional[int], + end_of_week_collection: Optional[bool]): + if not self.token: raise NotAuthenticatedError raise NotImplementedError async def new_parcel(self): - if not self.auth: + if not self.token: raise NotAuthenticatedError raise NotImplementedError From 67887d5f037ea091dc0f04fa146a00de18d40c43 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Tue, 13 Dec 2022 17:23:47 +0100 Subject: [PATCH 13/40] cleanup, login and logout update, new exceptions --- api/api.py | 93 ++++++++++++++++++++++++++++---------------- static/endpoints.py | 11 ++++-- static/exceptions.py | 26 ++++++++++++- 3 files changed, 91 insertions(+), 39 deletions(-) diff --git a/api/api.py b/api/api.py index 394378d..53b8718 100644 --- a/api/api.py +++ b/api/api.py @@ -1,33 +1,46 @@ -from typing import Optional, List, Union +from typing import Optional, Union +import aiohttp.web from aiohttp import ClientSession -from json import dumps -from static.endpoints import login, logout, get_parcels +from static.endpoints import logout, send_sms_code, confirm_sms_code from static.headers import appjson -from static.exceptions import NotAuthenticatedError, NotImplementedError, SomeAPIError -from static.parcels import ParcelDeliveryType, ParcelForm, ParcelCarrierSize, ParcelLockerSize, ParcelShipmentType +from static.exceptions import NotAuthenticatedError, NotImplementedError, SomeAPIError, PhoneNumberError, \ + SmsCodeConfirmationError +from static.parcels import ParcelDeliveryType, ParcelCarrierSize, ParcelLockerSize, ParcelShipmentType class Inpost: - def __init__(self, uname: str, passwd: str): - self.username: str = uname - self.password: str = passwd + def __init__(self, phone_number: str): + self.phone_number: str = phone_number + self.sms_code: str | None = None self.token: str | None = None self.sess: ClientSession = ClientSession() def __repr__(self): - return f'Username: {self.username}\nPassword: {self.password}\nToken: {self.token}' + return f'Username: {self.phone_number}\nToken: {self.token}' - async def authenticate(self): - async with await self.sess.post(url=login, + async def send_sms_code(self) -> Optional[bool]: + async with await self.sess.post(url=send_sms_code, + json={ + 'phoneNumber': f'{self.phone_number}' + }) as phone: + if phone.status == aiohttp.web.HTTPOk: + return True + else: + raise PhoneNumberError(phone) + + async def confirm_sms_code(self, sms_code: str): + async with await self.sess.post(url=confirm_sms_code, headers=appjson, - data=dumps({'login': self.username, - 'password': self.password})) as resp: - if resp.status == 200: - token = await resp.json() - self.token = token['token'] + json={ + "phoneNumber": self.phone_number, + "smsCode": sms_code, + "phoneOS": "Android" + }) as confirmation: + if confirmation.status == aiohttp.web.HTTPOk: + self.sms_code = sms_code else: - raise NotAuthenticatedError(reason=resp) + raise SmsCodeConfirmationError(reason=confirmation) async def logout(self): if self.token is not None: @@ -45,23 +58,35 @@ async def disconnect(self): await self.logout() await self.sess.close() - # page=1&limit=10&filters=%5BpackType%7Beq%7Doutgoing%5D%2C%5BserviceName%7Bin%7DSTANDARD%2CSTANDARD_PARCEL_SMART%2CPASS_THRU%2CCUSTOMER_SERVICE_POINT%2CREVERSE%2CSTANDARD_COURIER%2CREVERSE_RETURN%5D%2C%5BdateFrom%7Beq%7D2022-06-16%5D%2C%5BdateTo%7Beq%7D2022-06-26%5D - async def get_parcels(self, - sender: Optional[str], - customer_reference: Optional[str], - parcel_code: Optional[Union[List[str], str]], - service_name: Union[List[str], str], - status: Optional[Union[List[str], str]], - date_from: str, - date_to: str, - end_of_week_collection: Optional[bool], - parcels_type: ParcelForm = ParcelForm.INCOMING, - limit=20, - page=1): - if not self.token: - raise NotAuthenticatedError - - raise NotImplementedError + # async def get_parcels(self, + # sender: Optional[str], + # customer_reference: Optional[str], + # parcel_code: Optional[Union[List[str], str]], + # service_name: Union[List[ParcelServiceName], ParcelServiceName], + # status: Optional[Union[List[str], str]], + # date_from: str, + # date_to: str, + # end_of_week_collection: Optional[bool], + # parcel_type: ParcelForm = ParcelForm.INCOMING, + # limit=20, + # page=1): + # if not self.token: + # raise NotAuthenticatedError + # temp_par = [ + # '[packType{eq}%s]' % parcel_type.name.lower(), + # '[serviceMane{in}%s]' % ','.join((x.name for x in service_name)), + # '[dateFrom{eq}%s]' % date_from, + # '[dateTo{eq}%s]' % date_to + # ] + # async with await self.sess.get(url=get_parcels, + # headers={'X-Authorization': f'Bearer {self.token}'}, + # params={ + # 'page': page, + # 'limit': limit, + # 'filters': ','.join(temp_par) + # }) as resp: + # + # raise NotImplementedError async def send_parcel(self, recipient_email: str, diff --git a/static/endpoints.py b/static/endpoints.py index 2b0a165..8e0a7e8 100644 --- a/static/endpoints.py +++ b/static/endpoints.py @@ -1,6 +1,9 @@ -login: str = 'https://manager.paczkomaty.pl/v1/api/auth/login' -logout: str = 'https://manager.paczkomaty.pl/v1/api/auth/logout' +login: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' +send_sms_code: str = 'https://api-inmobile-pl.easypack24.net/v1/sendSMSCode/' # get +confirm_sms_code: str = 'https://api-inmobile-pl.easypack24.net/v1/confirmSMSCode' # post +logout: str = 'https://api-inmobile-pl.easypack24.net/v1/logout' # post + # \/ Secured by JWT \/ -user_infos: str = 'https://manager.paczkomaty.pl/v1/api/auth/user' -get_parcels: str = 'https://manager.paczkomaty.pl/v1/api/parcels' +get_parcel: str = 'https://api-inmobile-pl.easypack24.net/v1/parcel' # get + diff --git a/static/exceptions.py b/static/exceptions.py index 455e6d5..b122904 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -17,6 +17,29 @@ def __init__(self, reason): def stack(self): return self.reason + +class PhoneNumberError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class SmsCodeConfirmationError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + class SomeAPIError(Exception): def __init__(self, reason): super().__init__(reason) @@ -27,6 +50,7 @@ def __init__(self, reason): def stack(self): return self.reason + # ----------------- Other ----------------- # class NotImplementedError(Exception): - pass \ No newline at end of file + pass From 30fe26e4bba58ba9f9550c77035fd995884a689f Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Tue, 13 Dec 2022 17:24:10 +0100 Subject: [PATCH 14/40] typo fix --- static/parcels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/parcels.py b/static/parcels.py index 41768f3..908d657 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -50,7 +50,7 @@ class ParcelAdditionalInsurance(ParcelBase): class ParcelForm(ParcelBase): - OUTCOMING = 1 + OUTGOING = 1 INCOMING = 2 From 9234ada209cfbd926644753d9d0be3dbd9590630 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 15 Dec 2022 17:38:35 +0100 Subject: [PATCH 15/40] updated endpoints --- static/endpoints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/endpoints.py b/static/endpoints.py index 8e0a7e8..9e4c9e7 100644 --- a/static/endpoints.py +++ b/static/endpoints.py @@ -5,5 +5,5 @@ # \/ Secured by JWT \/ -get_parcel: str = 'https://api-inmobile-pl.easypack24.net/v1/parcel' # get - +refresh_token: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' # post +parcels: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked' # get From 2b57adc8f1c4ed3e78565095cb7dab3b0b30416c Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 15 Dec 2022 17:39:00 +0100 Subject: [PATCH 16/40] updated exceptions --- static/exceptions.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/static/exceptions.py b/static/exceptions.py index b122904..083576b 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -18,6 +18,17 @@ def stack(self): return self.reason +class ReAuthenticationError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + class PhoneNumberError(Exception): def __init__(self, reason): super().__init__(reason) From 0bd13fa5ff01fc13a751c9d5f2fcfff81596c06b Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 15 Dec 2022 21:03:51 +0100 Subject: [PATCH 17/40] single parcel endpoint --- static/endpoints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/static/endpoints.py b/static/endpoints.py index 9e4c9e7..c7e4bb0 100644 --- a/static/endpoints.py +++ b/static/endpoints.py @@ -7,3 +7,4 @@ refresh_token: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' # post parcels: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked' # get +parcel: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked/' # get From becf20fb71c24e93931361b1d88c2673611ca2ae Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 15 Dec 2022 21:04:21 +0100 Subject: [PATCH 18/40] new RefreshTokenException --- static/exceptions.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/static/exceptions.py b/static/exceptions.py index 083576b..2166f15 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -51,6 +51,17 @@ def stack(self): return self.reason +class RefreshTokenException(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + class SomeAPIError(Exception): def __init__(self, reason): super().__init__(reason) From f8d88922292e7aa4b9b6c0f53e69bbe379d20adc Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 15 Dec 2022 21:06:32 +0100 Subject: [PATCH 19/40] working authentication, JWT refreshing, parcels getters --- api/api.py | 160 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 93 insertions(+), 67 deletions(-) diff --git a/api/api.py b/api/api.py index 53b8718..559eb57 100644 --- a/api/api.py +++ b/api/api.py @@ -1,30 +1,29 @@ -from typing import Optional, Union +from typing import Union, List -import aiohttp.web from aiohttp import ClientSession -from static.endpoints import logout, send_sms_code, confirm_sms_code +from static.endpoints import * from static.headers import appjson -from static.exceptions import NotAuthenticatedError, NotImplementedError, SomeAPIError, PhoneNumberError, \ - SmsCodeConfirmationError -from static.parcels import ParcelDeliveryType, ParcelCarrierSize, ParcelLockerSize, ParcelShipmentType +from static.exceptions import * +from static.parcels import Parcel class Inpost: def __init__(self, phone_number: str): self.phone_number: str = phone_number self.sms_code: str | None = None - self.token: str | None = None + self.auth_token: str | None = None + self.refr_token: str | None = None self.sess: ClientSession = ClientSession() def __repr__(self): - return f'Username: {self.phone_number}\nToken: {self.token}' + return f'Username: {self.phone_number}\nToken: {self.auth_token}' async def send_sms_code(self) -> Optional[bool]: async with await self.sess.post(url=send_sms_code, json={ 'phoneNumber': f'{self.phone_number}' }) as phone: - if phone.status == aiohttp.web.HTTPOk: + if phone.status == 200: return True else: raise PhoneNumberError(phone) @@ -37,18 +36,47 @@ async def confirm_sms_code(self, sms_code: str): "smsCode": sms_code, "phoneOS": "Android" }) as confirmation: - if confirmation.status == aiohttp.web.HTTPOk: + if confirmation.status == 200: + resp = await confirmation.json() self.sms_code = sms_code + self.refr_token = resp['refreshToken'] + self.auth_token = resp['authToken'] + return True else: raise SmsCodeConfirmationError(reason=confirmation) + return False + + async def refresh_token(self): + if not self.auth_token: + raise NotAuthenticatedError(reason='Authentication token missing') + + if not self.refr_token: + raise NotAuthenticatedError(reason='Refresh token missing') + + async with await self.sess.post(url=refresh_token, + headers=appjson, + json={ + "refreshToken": self.refr_token, + "phoneOS": "Android" + }) as confirmation: + if confirmation.status == 200: + resp = await confirmation.json() + if resp['reauthenticationRequired']: + raise ReAuthenticationError(reason='You need to log in again!') + self.auth_token = resp['authToken'] + return True + + else: + raise RefreshTokenException(reason=confirmation) + async def logout(self): - if self.token is not None: + if self.auth_token is not None: async with await self.sess.post(url=logout, - headers={'X-Authorization': f'Bearer {self.token}'}) as resp: + headers={'Authorization': self.auth_token}) as resp: if resp.status == 200: print(await resp.text()) - self.token = None + self.auth_token = None else: raise SomeAPIError(reason=resp) else: @@ -58,60 +86,58 @@ async def disconnect(self): await self.logout() await self.sess.close() - # async def get_parcels(self, - # sender: Optional[str], - # customer_reference: Optional[str], - # parcel_code: Optional[Union[List[str], str]], - # service_name: Union[List[ParcelServiceName], ParcelServiceName], - # status: Optional[Union[List[str], str]], - # date_from: str, - # date_to: str, - # end_of_week_collection: Optional[bool], - # parcel_type: ParcelForm = ParcelForm.INCOMING, - # limit=20, - # page=1): - # if not self.token: + async def get_parcel(self, shipment_number: Union[int, str], parse=False) -> Union[dict, Parcel]: + if not self.auth_token: + raise NotAuthenticatedError(reason='Not logged in') + + async with await self.sess.get(url=f"{parcel}{shipment_number}", + headers={'Authorization': self.auth_token}, + ) as resp: + if resp.status == 200: + return await resp.json() if not parse else Parcel(await resp.json()) + + else: + raise SomeAPIError(reason=resp) + + async def get_parcels(self, as_list=False) -> Union[dict, List[Parcel]]: + if not self.auth_token: + raise NotAuthenticatedError(reason='Not logged in') + + async with await self.sess.get(url=parcels, + headers={'Authorization': self.auth_token}, + ) as resp: + if resp.status == 200: + x = await resp.json() + return await resp.json() if not as_list else [Parcel(parcel_data=data) for data in + (await resp.json())['parcels']] + + else: + raise SomeAPIError(reason=resp) + + # async def send_parcel(self, + # recipient_email: str, + # recipient_phone_number: str, + # parcel_type: ParcelDeliveryType, + # parcel_size: Union[ParcelLockerSize, ParcelCarrierSize], # only for parcel locker + # parcel_locker_code: Optional[str], + # shipment_method: ParcelShipmentType, + # referral_number: Optional[str], + # name: str, + # bussines_name: str, + # zip_code: str, + # city: str, + # street: str, + # building_number: int, + # apartment_numer: int, + # charge_value: Optional[int], + # end_of_week_collection: Optional[bool]): + # if not self.auth_token: + # raise NotAuthenticatedError + # + # raise NotImplementedError + # + # async def new_parcel(self): + # if not self.auth_token: # raise NotAuthenticatedError - # temp_par = [ - # '[packType{eq}%s]' % parcel_type.name.lower(), - # '[serviceMane{in}%s]' % ','.join((x.name for x in service_name)), - # '[dateFrom{eq}%s]' % date_from, - # '[dateTo{eq}%s]' % date_to - # ] - # async with await self.sess.get(url=get_parcels, - # headers={'X-Authorization': f'Bearer {self.token}'}, - # params={ - # 'page': page, - # 'limit': limit, - # 'filters': ','.join(temp_par) - # }) as resp: # # raise NotImplementedError - - async def send_parcel(self, - recipient_email: str, - recipient_phone_number: str, - parcel_type: ParcelDeliveryType, - parcel_size: Union[ParcelLockerSize, ParcelCarrierSize], # only for parcel locker - parcel_locker_code: Optional[str], - shipment_method: ParcelShipmentType, - referral_number: Optional[str], - name: str, - bussines_name: str, - zip_code: str, - city: str, - street: str, - building_number: int, - apartment_numer: int, - charge_value: Optional[int], - end_of_week_collection: Optional[bool]): - if not self.token: - raise NotAuthenticatedError - - raise NotImplementedError - - async def new_parcel(self): - if not self.token: - raise NotAuthenticatedError - - raise NotImplementedError From 4a2a40d2ff40835100b792a72fdcaf96f42806f1 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 15 Dec 2022 21:07:43 +0100 Subject: [PATCH 20/40] new Parcel parser and it's properties --- static/parcels.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/static/parcels.py b/static/parcels.py index 908d657..c411414 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -1,4 +1,102 @@ from enum import Enum +from typing import List, Optional +from arrow import get, arrow + + +class Parcel: + def __init__(self, parcel_data: dict): + self.shipment_number: str = parcel_data['shipmentNumber'] + self.shipment_type: str = parcel_data['shipmentType'] + self._open_code: Optional[str] = parcel_data['openCode'] if 'openCode' in parcel_data else None + self._qr_code: Optional[str] = parcel_data['qrCode'] if 'qrCode' in parcel_data else None + self.stored_date: Optional[arrow] = get(parcel_data['storedDate']) if 'storedDate' in parcel_data else None + self.pickup_date: Optional[arrow] = get(parcel_data['pickUpDate']) if 'pickUpDate' in parcel_data else None + self.parcel_size: str = parcel_data['parcelSize'] + self.receiver: Receiver = Receiver(receiver_data=parcel_data['receiver']) + self.sender: Sender = Sender(sender_data=parcel_data['sender']) + self.pickup_point: PickupPoint = PickupPoint(pickuppoint_data=parcel_data['pickUpPoint']) + self.is_end_off_week_collection: bool = parcel_data['endOfWeekCollection'] + self.operations: Operations = Operations(operations_data=parcel_data['operations']) + self.status: str = parcel_data['status'] + self.event_log: List[EventLog] = [EventLog(eventlog_data=event) for event in parcel_data['eventLog']] + self.avizo_transaction_status: str = parcel_data['avizoTransactionStatus'] + self.shared_to: List[SharedTo] = [SharedTo(sharedto_data=person) for person in parcel_data['sharedTo']] + self.ownership_status: str = parcel_data['ownershipStatus'] + + def __str__(self): + return f"Shipment number: {self.shipment_number}\n" \ + f"Status: {self.status}\n" \ + f"Pickup point: {self.pickup_point}\n" \ + f"Sender: {str(self.sender)}" + + +class Receiver: + def __init__(self, receiver_data: dict): + self.email: str = receiver_data['email'] + self.phone_number: str = receiver_data['phoneNumber'] + self.name: str = receiver_data['name'] + + +class Sender: + def __init__(self, sender_data: dict): + self.sender_name: str = sender_data['name'] + + def __str__(self): + return self.sender_name + + +class PickupPoint: + def __init__(self, pickuppoint_data): + self.name: str = pickuppoint_data['name'] + self.latitude: float = pickuppoint_data['location']['latitude'] + self.longitude: float = pickuppoint_data['location']['longitude'] + self.description: str = pickuppoint_data['locationDescription'] + self.opening_hours: str = pickuppoint_data['openingHours'] + self.post_code: str = pickuppoint_data['addressDetails']['postCode'] + self.city: str = pickuppoint_data['addressDetails']['city'] + self.province: str = pickuppoint_data['addressDetails']['province'] + self.street: str = pickuppoint_data['addressDetails']['street'] + self.building_number: str = pickuppoint_data['addressDetails']['buildingNumber'] + self.virtual: int = pickuppoint_data['virtual'] + self.point_type: str = pickuppoint_data['pointType'] + self.type: List[str] = pickuppoint_data['type'] + self.location_round_the_clock: bool = pickuppoint_data['location247'] + self.doubled: bool = pickuppoint_data['doubled'] + self.image_url: str = pickuppoint_data['imageUrl'] + self.easy_access_zone: bool = pickuppoint_data['easyAccessZone'] + self.air_sensor: bool = pickuppoint_data['airSensor'] + + def __str__(self): + return self.name + + +class Operations: + def __init__(self, operations_data): + self.manual_archive: bool = operations_data['manualArchive'] + self.auto_archivable_since: Optional[arrow] = get( + operations_data['autoArchivableSince']) if 'autoArchivableSince' in operations_data else None + self.delete: bool = operations_data['delete'] + self.collect: bool = operations_data['collect'] + self.expand_avizo: bool = operations_data['expandAvizo'] + self.highlight: bool = operations_data['highlight'] + self.refresh_until: arrow = get(operations_data['refreshUntil']) + self.request_easy_access_zone: str = operations_data['requestEasyAccessZone'] + self.is_voicebot: bool = operations_data['voicebot'] + self.can_share_to_observe: bool = operations_data['canShareToObserve'] + self.can_share_open_code: bool = operations_data['canShareOpenCode'] + self.can_share_parcel: bool = operations_data['canShareParcel'] + + +class EventLog: + def __init__(self, eventlog_data: dict): + self.type: str = eventlog_data['type'] + self.name: str = eventlog_data['name'] + self.date: arrow = get(eventlog_data['date']) + + +class SharedTo: + def __init__(self, sharedto_data): + ... class ParcelBase(Enum): From a6cfe18cdaf6074a3bee2fcc15487ff12a77ca92 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 15 Dec 2022 21:07:57 +0100 Subject: [PATCH 21/40] new requirements --- requirements.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3ecba9b..ca9648a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ aiohttp~=3.8.1 -setuptools~=57.0.0 \ No newline at end of file +setuptools~=57.0.0 +arrow~=1.2.3 +PyYAML~=6.0 +Telethon~=1.26.0 From 42a6c5729e7271eefed81586f5c8cdf9dc45a747 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sat, 17 Dec 2022 22:09:07 +0100 Subject: [PATCH 22/40] parsing entire own parcel in parcel_locker --- static/parcels.py | 84 +++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/static/parcels.py b/static/parcels.py index c411414..0524f2d 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -1,27 +1,28 @@ from enum import Enum -from typing import List, Optional +from typing import List, Optional, Tuple, Union from arrow import get, arrow class Parcel: def __init__(self, parcel_data: dict): self.shipment_number: str = parcel_data['shipmentNumber'] - self.shipment_type: str = parcel_data['shipmentType'] + self.shipment_type: ParcelShipmentType = ParcelShipmentType[parcel_data['shipmentType']] self._open_code: Optional[str] = parcel_data['openCode'] if 'openCode' in parcel_data else None self._qr_code: Optional[str] = parcel_data['qrCode'] if 'qrCode' in parcel_data else None self.stored_date: Optional[arrow] = get(parcel_data['storedDate']) if 'storedDate' in parcel_data else None self.pickup_date: Optional[arrow] = get(parcel_data['pickUpDate']) if 'pickUpDate' in parcel_data else None - self.parcel_size: str = parcel_data['parcelSize'] + self.parcel_size: Union[ParcelLockerSize, ParcelCarrierSize] = ParcelLockerSize[parcel_data['parcelSize']] \ + if self.shipment_type == ParcelShipmentType.parcel else ParcelCarrierSize[parcel_data['parcelSize']] self.receiver: Receiver = Receiver(receiver_data=parcel_data['receiver']) self.sender: Sender = Sender(sender_data=parcel_data['sender']) self.pickup_point: PickupPoint = PickupPoint(pickuppoint_data=parcel_data['pickUpPoint']) self.is_end_off_week_collection: bool = parcel_data['endOfWeekCollection'] self.operations: Operations = Operations(operations_data=parcel_data['operations']) - self.status: str = parcel_data['status'] + self.status: ParcelStatus = ParcelStatus[parcel_data['status']] self.event_log: List[EventLog] = [EventLog(eventlog_data=event) for event in parcel_data['eventLog']] self.avizo_transaction_status: str = parcel_data['avizoTransactionStatus'] self.shared_to: List[SharedTo] = [SharedTo(sharedto_data=person) for person in parcel_data['sharedTo']] - self.ownership_status: str = parcel_data['ownershipStatus'] + self.ownership_status: ParcelOwnership = ParcelOwnership[parcel_data['ownershipStatus']] def __str__(self): return f"Shipment number: {self.shipment_number}\n" \ @@ -59,7 +60,7 @@ def __init__(self, pickuppoint_data): self.building_number: str = pickuppoint_data['addressDetails']['buildingNumber'] self.virtual: int = pickuppoint_data['virtual'] self.point_type: str = pickuppoint_data['pointType'] - self.type: List[str] = pickuppoint_data['type'] + self.type: List[ParcelDeliveryType] = [ParcelDeliveryType[data] for data in pickuppoint_data['type']] self.location_round_the_clock: bool = pickuppoint_data['location247'] self.doubled: bool = pickuppoint_data['doubled'] self.image_url: str = pickuppoint_data['imageUrl'] @@ -69,6 +70,10 @@ def __init__(self, pickuppoint_data): def __str__(self): return self.name + @property + def location(self) -> Tuple[float, float]: + return self.latitude, self.longitude + class Operations: def __init__(self, operations_data): @@ -90,7 +95,7 @@ def __init__(self, operations_data): class EventLog: def __init__(self, eventlog_data: dict): self.type: str = eventlog_data['type'] - self.name: str = eventlog_data['name'] + self.name: ParcelStatus = ParcelStatus[eventlog_data['name']] self.date: arrow = get(eventlog_data['date']) @@ -117,27 +122,28 @@ def __eq__(self, other): class ParcelCarrierSize(ParcelBase): - A = 1 - B = 2 - C = 3 - D = 4 + A = '8x38x64' + B = '19x38x64' + C = '41x38x64' + D = '50x50x80' class ParcelLockerSize(ParcelBase): - A = 1 - B = 2 - C = 3 + A = '8x38x64' + B = '19x38x64' + C = '41x38x64' class ParcelDeliveryType(ParcelBase): - PARCELLOCKER = 1 - CARRIER = 2 + parcel_locker = 'Paczkomat' + carrier = 'Kurier' + parcel_point = 'PaczkoPunkt' class ParcelShipmentType(ParcelBase): - PARCELLOCKER = 1 - CARRIER = 2 - PARCELPOINT = 3 + parcel = 'Paczkomat' + carrier = 'Kurier' + parcel_point = 'PaczkoPunkt' class ParcelAdditionalInsurance(ParcelBase): @@ -153,30 +159,22 @@ class ParcelForm(ParcelBase): class ParcelStatus(ParcelBase): - CREATED = 1 - CUSTOMERSTORED = 2 - CUSTOMERSENT = 3 - SENT = 4 - INTRANSIT = 5 - RETURNEDTOSORTINGCENTER = 6 - DELIVEREDTOSORTINGCENTER = 7 - RETURNEDTOSENDER = 8 - PREPARED = 9 - CUSTOMERDELIVERING = 10 - STORED = 11 - EXPIRED = 12 - AVIZO = 13 - RETURNEDTOAGENCY = 14 - RETUNEDTOAGENCY = 15 - DELIVEREDTOAGENCY = 16 - CANCELLED = 17 - LABELEXPIRED = 18 - LABELDESTROYED = 19 - MISSING = 20 - CLAIMED = 21 - NOTDELIVERED = 22 - OVERSIZED = 23 - DELIVERED = 24 + CONFIRMED = 'Potwierdzona' + COLLECTED_FROM_SENDER = 'Odebrana od nadawcy' + DISPATCHED_BY_SENDER_TO_POK = 'Nadana w PaczkoPunkcie' + DISPATCHED_BY_SENDER = 'Nadana w paczkomacie' + TAKEN_BY_COURIER = 'Odebrana przez Kuriera' + TAKEN_BY_COURIER_FROM_POK = 'Odebrana z PaczkoPunktu nadawczego' + ADOPTED_AT_SOURCE_BRANCH = 'Przyjęta w oddziale' + SENT_FROM_SOURCE_BRANCH = 'Wysłana z oddziału' + OUT_FOR_DELIVERY = 'Wydana do doręczenia' + READY_TO_PICKUP = 'Gotowa do odbioru' + DELIVERED = 'Doręczona' + + +class ParcelOwnership(ParcelBase): + FRIEND = 'Zaprzyjaźniona' + OWN = 'Własna' class ParcelServiceName(ParcelBase): From c386535e4e9424d0efc771007e672a181a4edfde Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sat, 17 Dec 2022 22:09:26 +0100 Subject: [PATCH 23/40] little cleanup --- static/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/endpoints.py b/static/endpoints.py index c7e4bb0..1f1daca 100644 --- a/static/endpoints.py +++ b/static/endpoints.py @@ -1,10 +1,10 @@ login: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' send_sms_code: str = 'https://api-inmobile-pl.easypack24.net/v1/sendSMSCode/' # get confirm_sms_code: str = 'https://api-inmobile-pl.easypack24.net/v1/confirmSMSCode' # post -logout: str = 'https://api-inmobile-pl.easypack24.net/v1/logout' # post # \/ Secured by JWT \/ refresh_token: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' # post parcels: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked' # get parcel: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked/' # get +logout: str = 'https://api-inmobile-pl.easypack24.net/v1/logout' # post From bb010f38bf55e18a345e82d26cccaf7cf933e28b Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sat, 17 Dec 2022 22:12:52 +0100 Subject: [PATCH 24/40] typehints, cleanup --- api/api.py | 69 +++++++++++++++++------------------------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/api/api.py b/api/api.py index 559eb57..f877ab9 100644 --- a/api/api.py +++ b/api/api.py @@ -26,9 +26,9 @@ async def send_sms_code(self) -> Optional[bool]: if phone.status == 200: return True else: - raise PhoneNumberError(phone) + raise PhoneNumberError(reason=phone) - async def confirm_sms_code(self, sms_code: str): + async def confirm_sms_code(self, sms_code: str) -> Optional[bool]: async with await self.sess.post(url=confirm_sms_code, headers=appjson, json={ @@ -45,9 +45,7 @@ async def confirm_sms_code(self, sms_code: str): else: raise SmsCodeConfirmationError(reason=confirmation) - return False - - async def refresh_token(self): + async def refresh_token(self) -> Optional[bool]: if not self.auth_token: raise NotAuthenticatedError(reason='Authentication token missing') @@ -70,21 +68,27 @@ async def refresh_token(self): else: raise RefreshTokenException(reason=confirmation) - async def logout(self): - if self.auth_token is not None: - async with await self.sess.post(url=logout, - headers={'Authorization': self.auth_token}) as resp: - if resp.status == 200: - print(await resp.text()) - self.auth_token = None - else: - raise SomeAPIError(reason=resp) - else: + async def logout(self) -> Optional[bool]: + if not self.auth_token: raise NotAuthenticatedError(reason='Not logged in') - async def disconnect(self): - await self.logout() - await self.sess.close() + async with await self.sess.post(url=logout, + headers={'Authorization': self.auth_token}) as resp: + if resp.status == 200: + self.phone_number = None + self.refr_token = None + self.auth_token = None + self.sms_code = None + return True + else: + raise SomeAPIError(reason=resp) + + async def disconnect(self) -> bool: + if await self.logout(): + await self.sess.close() + return True + + return False async def get_parcel(self, shipment_number: Union[int, str], parse=False) -> Union[dict, Parcel]: if not self.auth_token: @@ -107,37 +111,8 @@ async def get_parcels(self, as_list=False) -> Union[dict, List[Parcel]]: headers={'Authorization': self.auth_token}, ) as resp: if resp.status == 200: - x = await resp.json() return await resp.json() if not as_list else [Parcel(parcel_data=data) for data in (await resp.json())['parcels']] else: raise SomeAPIError(reason=resp) - - # async def send_parcel(self, - # recipient_email: str, - # recipient_phone_number: str, - # parcel_type: ParcelDeliveryType, - # parcel_size: Union[ParcelLockerSize, ParcelCarrierSize], # only for parcel locker - # parcel_locker_code: Optional[str], - # shipment_method: ParcelShipmentType, - # referral_number: Optional[str], - # name: str, - # bussines_name: str, - # zip_code: str, - # city: str, - # street: str, - # building_number: int, - # apartment_numer: int, - # charge_value: Optional[int], - # end_of_week_collection: Optional[bool]): - # if not self.auth_token: - # raise NotAuthenticatedError - # - # raise NotImplementedError - # - # async def new_parcel(self): - # if not self.auth_token: - # raise NotAuthenticatedError - # - # raise NotImplementedError From e365a7f0ed49426fa246bae509f286e124fbe16c Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Tue, 20 Dec 2022 18:15:02 +0100 Subject: [PATCH 25/40] shared parcels and multicompartment support --- static/parcels.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/static/parcels.py b/static/parcels.py index 0524f2d..3dfccd8 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -16,6 +16,8 @@ def __init__(self, parcel_data: dict): self.receiver: Receiver = Receiver(receiver_data=parcel_data['receiver']) self.sender: Sender = Sender(sender_data=parcel_data['sender']) self.pickup_point: PickupPoint = PickupPoint(pickuppoint_data=parcel_data['pickUpPoint']) + self.multi_compartment: Optional[MultiCompartment] = MultiCompartment(parcel_data['multiCompartment']) \ + if 'multiCompartment' in parcel_data else None self.is_end_off_week_collection: bool = parcel_data['endOfWeekCollection'] self.operations: Operations = Operations(operations_data=parcel_data['operations']) self.status: ParcelStatus = ParcelStatus[parcel_data['status']] @@ -75,6 +77,15 @@ def location(self) -> Tuple[float, float]: return self.latitude, self.longitude +class MultiCompartment: + def __init__(self, multicompartment_data): + self.uuid = multicompartment_data['uuid'] + self.shipment_numbers: Optional[List['str']] = multicompartment_data['shipmentNumbers'] \ + if 'shipmentNumbers' in multicompartment_data else None + self.presentation: bool = multicompartment_data['presentation'] + self.collected: bool = multicompartment_data['collected'] + + class Operations: def __init__(self, operations_data): self.manual_archive: bool = operations_data['manualArchive'] @@ -101,7 +112,9 @@ def __init__(self, eventlog_data: dict): class SharedTo: def __init__(self, sharedto_data): - ... + self.uuid: str = sharedto_data['uuid'] + self.name: str = sharedto_data['name'] + self.phone_number = sharedto_data['phoneNumber'] class ParcelBase(Enum): @@ -118,7 +131,7 @@ def __lt__(self, other): ... def __eq__(self, other): - ... + return self.name == other.name class ParcelCarrierSize(ParcelBase): @@ -166,6 +179,7 @@ class ParcelStatus(ParcelBase): TAKEN_BY_COURIER = 'Odebrana przez Kuriera' TAKEN_BY_COURIER_FROM_POK = 'Odebrana z PaczkoPunktu nadawczego' ADOPTED_AT_SOURCE_BRANCH = 'Przyjęta w oddziale' + ADOPTED_AT_SORTING_CENTER = 'Przyjęta w sortowni' SENT_FROM_SOURCE_BRANCH = 'Wysłana z oddziału' OUT_FOR_DELIVERY = 'Wydana do doręczenia' READY_TO_PICKUP = 'Gotowa do odbioru' From 25f5331702d7166cbf2b26b7dda425ae8da09e2a Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Tue, 20 Dec 2022 18:18:14 +0100 Subject: [PATCH 26/40] added some unused (yet) filters to get_parcels --- api/api.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/api/api.py b/api/api.py index f877ab9..4f2e016 100644 --- a/api/api.py +++ b/api/api.py @@ -4,7 +4,7 @@ from static.endpoints import * from static.headers import appjson from static.exceptions import * -from static.parcels import Parcel +from static.parcels import * class Inpost: @@ -103,7 +103,12 @@ async def get_parcel(self, shipment_number: Union[int, str], parse=False) -> Uni else: raise SomeAPIError(reason=resp) - async def get_parcels(self, as_list=False) -> Union[dict, List[Parcel]]: + async def get_parcels(self, + status: Optional[Union[ParcelStatus, List[ParcelStatus]]] = None, + pickup_point: Optional[Union[str, List[str]]] = None, + shipment_type: Optional[Union[ParcelShipmentType, List[ParcelShipmentType]]] = None, + parcel_size: Optional[Union[ParcelLockerSize, ParcelCarrierSize]] = None, + parse: bool = False) -> Union[dict, List[Parcel]]: if not self.auth_token: raise NotAuthenticatedError(reason='Not logged in') @@ -111,8 +116,8 @@ async def get_parcels(self, as_list=False) -> Union[dict, List[Parcel]]: headers={'Authorization': self.auth_token}, ) as resp: if resp.status == 200: - return await resp.json() if not as_list else [Parcel(parcel_data=data) for data in - (await resp.json())['parcels']] + return await resp.json() if not parse else [Parcel(parcel_data=data) for data in + (await resp.json())['parcels']] else: raise SomeAPIError(reason=resp) From 537222b3f1008bbb2a75de09f69c039a03f2fbe8 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Tue, 20 Dec 2022 23:23:17 +0100 Subject: [PATCH 27/40] added QRCode generation --- static/parcels.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/static/parcels.py b/static/parcels.py index 3dfccd8..4541faf 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -1,5 +1,8 @@ from enum import Enum +from io import BytesIO from typing import List, Optional, Tuple, Union + +import qrcode from arrow import get, arrow @@ -8,7 +11,7 @@ def __init__(self, parcel_data: dict): self.shipment_number: str = parcel_data['shipmentNumber'] self.shipment_type: ParcelShipmentType = ParcelShipmentType[parcel_data['shipmentType']] self._open_code: Optional[str] = parcel_data['openCode'] if 'openCode' in parcel_data else None - self._qr_code: Optional[str] = parcel_data['qrCode'] if 'qrCode' in parcel_data else None + self._qr_code: Optional[QRCode] = QRCode(parcel_data['qrCode']) if 'qrCode' in parcel_data else None self.stored_date: Optional[arrow] = get(parcel_data['storedDate']) if 'storedDate' in parcel_data else None self.pickup_date: Optional[arrow] = get(parcel_data['pickUpDate']) if 'pickUpDate' in parcel_data else None self.parcel_size: Union[ParcelLockerSize, ParcelCarrierSize] = ParcelLockerSize[parcel_data['parcelSize']] \ @@ -32,6 +35,10 @@ def __str__(self): f"Pickup point: {self.pickup_point}\n" \ f"Sender: {str(self.sender)}" + @property + def generate_qr_image(self): + return self._qr_code.qr_image + class Receiver: def __init__(self, receiver_data: dict): @@ -117,6 +124,30 @@ def __init__(self, sharedto_data): self.phone_number = sharedto_data['phoneNumber'] +class QRCode: + def __init__(self, qrcode_data: str): + self._qr_code = qrcode_data + + @property + def qr_image(self) -> BytesIO: + qr = qrcode.QRCode( + version=3, + error_correction=qrcode.constants.ERROR_CORRECT_H, + box_size=20, + border=4, + mask_pattern=5 + ) + + qr.add_data(self._qr_code) + qr.make(fit=False) + img1 = qr.make_image(fill_color="black", back_color="white") + bio = BytesIO() + bio.name = 'qr.png' + img1.save(bio, 'PNG') + bio.seek(0) + return bio + + class ParcelBase(Enum): def __gt__(self, other): ... @@ -131,7 +162,10 @@ def __lt__(self, other): ... def __eq__(self, other): - return self.name == other.name + if isinstance(other, ParcelBase): + return self.name == other.name + + return False class ParcelCarrierSize(ParcelBase): From 83874dab4c193459ca55d195793dfbdd58b281d9 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Tue, 20 Dec 2022 23:23:33 +0100 Subject: [PATCH 28/40] requirements update --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index ca9648a..3e7d0aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,5 @@ setuptools~=57.0.0 arrow~=1.2.3 PyYAML~=6.0 Telethon~=1.26.0 + +qrcode~=7.3.1 \ No newline at end of file From 17a3e06ccb66bf9105e331f56a54613db1cd24a8 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Tue, 20 Dec 2022 23:29:21 +0100 Subject: [PATCH 29/40] implemented basic filters for parcels --- api/api.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/api/api.py b/api/api.py index 4f2e016..b55944f 100644 --- a/api/api.py +++ b/api/api.py @@ -1,5 +1,3 @@ -from typing import Union, List - from aiohttp import ClientSession from static.endpoints import * from static.headers import appjson @@ -116,8 +114,41 @@ async def get_parcels(self, headers={'Authorization': self.auth_token}, ) as resp: if resp.status == 200: - return await resp.json() if not parse else [Parcel(parcel_data=data) for data in - (await resp.json())['parcels']] + _parcels = (await resp.json())['parcels'] + + if status is not None: + if isinstance(status, ParcelStatus): + status = [status] + + _parcels = (_parcel for _parcel in _parcels if ParcelStatus[_parcel['status']] in status) + + if pickup_point is not None: + if isinstance(pickup_point, str): + pickup_point = [pickup_point] + + _parcels = (_parcel for _parcel in _parcels if _parcel['pickUpPoint']['name'] in pickup_point) + + if shipment_type is not None: + if isinstance(shipment_type, ParcelShipmentType): + shipment_type = [shipment_type] + + _parcels = (_parcel for _parcel in _parcels if + ParcelShipmentType[_parcel['shipmentType']] in shipment_type) + + if parcel_size is not None: + if isinstance(parcel_size, ParcelCarrierSize): + parcel_size = [parcel_size] + + _parcels = (_parcel for _parcel in _parcels if + ParcelCarrierSize[_parcel['parcelSize']] in parcel_size) + + if isinstance(parcel_size, ParcelLockerSize): + parcel_size = [parcel_size] + + _parcels = (_parcel for _parcel in _parcels if + ParcelLockerSize[_parcel['parcelSize']] in parcel_size) + + return _parcels if not parse else [Parcel(parcel_data=data) for data in _parcels] else: raise SomeAPIError(reason=resp) From e6a1d443f852a4b0b7ff671979fc4fe36e3ca4a8 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Tue, 20 Dec 2022 23:35:58 +0100 Subject: [PATCH 30/40] README.md removal from dev branch --- README.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index e9d74b4..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -branch for development process \ No newline at end of file From 8f7dea861acc0eaea37065f424936fd9b493848b Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 29 Dec 2022 17:32:03 +0100 Subject: [PATCH 31/40] new endpoints --- static/endpoints.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/static/endpoints.py b/static/endpoints.py index 1f1daca..a3425a3 100644 --- a/static/endpoints.py +++ b/static/endpoints.py @@ -7,4 +7,12 @@ refresh_token: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' # post parcels: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked' # get parcel: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked/' # get +collect: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/validate' # post +compartment_open: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/open' # post +compartment_status: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/status' # post +terminate_collect_session: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/terminate' # post +sent: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/sent/' # get +returns: str = 'https://api-inmobile-pl.easypack24.net/v1/returns/parcels/' # get +parcel_prices: str = 'https://api-inmobile-pl.easypack24.net/v1/prices/parcels' # get +tickets: str = 'https://api-inmobile-pl.easypack24.net/v1/returns/tickets' # get logout: str = 'https://api-inmobile-pl.easypack24.net/v1/logout' # post From ec44e9ce0a75bb0b4421ca9ba357b19f8c3e95d6 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 29 Dec 2022 17:32:15 +0100 Subject: [PATCH 32/40] new exceptions --- static/exceptions.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/static/exceptions.py b/static/exceptions.py index 2166f15..973e44d 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -3,7 +3,25 @@ class SomeParcelError(Exception): - pass + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class ParcelTypeError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason # ----------------- API ----------------- # @@ -74,5 +92,12 @@ def stack(self): # ----------------- Other ----------------- # -class NotImplementedError(Exception): - pass +class UserLocationError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason From 47cbdb0f42eefe131bdffc124155dbea499202d5 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 29 Dec 2022 17:32:43 +0100 Subject: [PATCH 33/40] moved types and statuses to another location --- static/statuses.py | 104 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 static/statuses.py diff --git a/static/statuses.py b/static/statuses.py new file mode 100644 index 0000000..510d553 --- /dev/null +++ b/static/statuses.py @@ -0,0 +1,104 @@ +from enum import Enum + + +class ParcelBase(Enum): + def __gt__(self, other): + ... + + def __ge__(self, other): + ... + + def __le__(self, other): + ... + + def __lt__(self, other): + ... + + def __eq__(self, other): + if isinstance(other, ParcelBase): + return self.name == other.name + + return False + + +class ParcelCarrierSize(ParcelBase): + A = '8x38x64' + B = '19x38x64' + C = '41x38x64' + D = '50x50x80' + + +class ParcelLockerSize(ParcelBase): + A = '8x38x64' + B = '19x38x64' + C = '41x38x64' + + +class ParcelDeliveryType(ParcelBase): + parcel_locker = 'Paczkomat' + carrier = 'Kurier' + parcel_point = 'PaczkoPunkt' + + +class ParcelShipmentType(ParcelBase): + parcel = 'Paczkomat' + carrier = 'Kurier' + parcel_point = 'PaczkoPunkt' + + +class ParcelAdditionalInsurance(ParcelBase): + UNINSURANCED = 1 + ONE = 2 # UPTO 5000 + TWO = 3 # UPTO 10000 + THREE = 4 # UPTO 20000 + + +class ParcelType(ParcelBase): + TRACKED = 'Przychodzące' + SENT = 'Wysłane' + RETURNS = 'Zwroty' + + +class ParcelStatus(ParcelBase): + CONFIRMED = 'Potwierdzona' + COLLECTED_FROM_SENDER = 'Odebrana od nadawcy' + DISPATCHED_BY_SENDER_TO_POK = 'Nadana w PaczkoPunkcie' + DISPATCHED_BY_SENDER = 'Nadana w paczkomacie' + TAKEN_BY_COURIER = 'Odebrana przez Kuriera' + TAKEN_BY_COURIER_FROM_POK = 'Odebrana z PaczkoPunktu nadawczego' + ADOPTED_AT_SOURCE_BRANCH = 'Przyjęta w oddziale' + ADOPTED_AT_SORTING_CENTER = 'Przyjęta w sortowni' + SENT_FROM_SOURCE_BRANCH = 'Wysłana z oddziału' + OUT_FOR_DELIVERY = 'Wydana do doręczenia' + READY_TO_PICKUP = 'Gotowa do odbioru' + DELIVERED = 'Doręczona' + + +class ParcelOwnership(ParcelBase): + FRIEND = 'Zaprzyjaźniona' + OWN = 'Własna' + + +# both are the same, only for being clear +class CompartmentExpectedStatus(ParcelBase): + OPENED = 'Otwarta' + CLOSED = 'Zamknięta' + + +class CompartmentActualStatus(ParcelBase): + OPENED = 'Otwarta' + CLOSED = 'Zamknięta' + + +class ParcelServiceName(ParcelBase): + ALLEGRO_PARCEL = 1 + ALLEGRO_PARCEL_SMART = 2 + ALLEGRO_LETTER = 3 + ALLEGRO_COURIER = 4 + STANDARD = 5 + STANDARD_PARCEL_SMART = 6 + PASS_THRU = 7 + CUSTOMER_SERVICE_POINT = 8 + REVERSE = 9 + STANDARD_COURIER = 10 + REVERSE_RETURN = 11 From 7545847092305f57819e6d8c4b06218a1c3ff6c5 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 29 Dec 2022 17:33:47 +0100 Subject: [PATCH 34/40] new classes for opening compartment --- static/parcels.py | 161 ++++++++++++++++++++++------------------------ 1 file changed, 78 insertions(+), 83 deletions(-) diff --git a/static/parcels.py b/static/parcels.py index 4541faf..51d2ab5 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -1,10 +1,11 @@ -from enum import Enum from io import BytesIO from typing import List, Optional, Tuple, Union import qrcode from arrow import get, arrow +from static.statuses import * + class Parcel: def __init__(self, parcel_data: dict): @@ -28,6 +29,7 @@ def __init__(self, parcel_data: dict): self.avizo_transaction_status: str = parcel_data['avizoTransactionStatus'] self.shared_to: List[SharedTo] = [SharedTo(sharedto_data=person) for person in parcel_data['sharedTo']] self.ownership_status: ParcelOwnership = ParcelOwnership[parcel_data['ownershipStatus']] + self._compartment_properties: CompartmentProperties | None = None def __str__(self): return f"Shipment number: {self.shipment_number}\n" \ @@ -35,10 +37,54 @@ def __str__(self): f"Pickup point: {self.pickup_point}\n" \ f"Sender: {str(self.sender)}" + @property + def open_code(self): + return self._open_code + @property def generate_qr_image(self): return self._qr_code.qr_image + @property + def compartment_properties(self): + return self._compartment_properties + + @compartment_properties.setter + def compartment_properties(self, compartmentproperties_data: dict): + self._compartment_properties = CompartmentProperties(compartmentproperties_data=compartmentproperties_data) + + @property + def compartment_location(self): + return self._compartment_properties.location + + @compartment_location.setter + def compartment_location(self, location_data): + self._compartment_properties.location = location_data + + @property + def compartment_status(self): + return self._compartment_properties.status + + @compartment_status.setter + def compartment_status(self, status): + self._compartment_properties.status = status + + @property + def compartment_open_data(self): + return { + 'shipmentNumber': self.shipment_number, + 'openCode': self._open_code, + 'receiverPhoneNumber': self.receiver.phone_number + } + + @property + def mocked_location(self): # TODO[1]: MAKE IT MORE RANDOM + return { + 'latitude': self.pickup_point.latitude, + 'longitude': self.pickup_point.longitude, + 'accuracy': 3 + } + class Receiver: def __init__(self, receiver_data: dict): @@ -148,92 +194,41 @@ def qr_image(self) -> BytesIO: return bio -class ParcelBase(Enum): - def __gt__(self, other): - ... - - def __ge__(self, other): - ... - - def __le__(self, other): - ... +class CompartmentLocation: + def __init__(self, compartmentlocation_data: dict): + self.name: str = compartmentlocation_data['compartment']['name'] + self.side: str = compartmentlocation_data['compartment']['location']['side'] + self.column: str = compartmentlocation_data['compartment']['location']['column'] + self.row: str = compartmentlocation_data['compartment']['location']['row'] + self.open_compartment_waiting_time: int = compartmentlocation_data['openCompartmentWaitingTime'] + self.action_time: int = compartmentlocation_data['actionTime'] + self.confirm_action_time: int = compartmentlocation_data['confirmActionTime'] - def __lt__(self, other): - ... - def __eq__(self, other): - if isinstance(other, ParcelBase): - return self.name == other.name +class CompartmentProperties: + def __init__(self, compartmentproperties_data: dict): + self._session_uuid: str = compartmentproperties_data['sessionUuid'] + self._session_expiration_time: int = compartmentproperties_data['sessionExpirationTime'] + self._location: CompartmentLocation | None = None + self._status: CompartmentActualStatus | None = None - return False - - -class ParcelCarrierSize(ParcelBase): - A = '8x38x64' - B = '19x38x64' - C = '41x38x64' - D = '50x50x80' - - -class ParcelLockerSize(ParcelBase): - A = '8x38x64' - B = '19x38x64' - C = '41x38x64' - - -class ParcelDeliveryType(ParcelBase): - parcel_locker = 'Paczkomat' - carrier = 'Kurier' - parcel_point = 'PaczkoPunkt' - - -class ParcelShipmentType(ParcelBase): - parcel = 'Paczkomat' - carrier = 'Kurier' - parcel_point = 'PaczkoPunkt' - - -class ParcelAdditionalInsurance(ParcelBase): - UNINSURANCED = 1 - ONE = 2 # UPTO 5000 - TWO = 3 # UPTO 10000 - THREE = 4 # UPTO 20000 - - -class ParcelForm(ParcelBase): - OUTGOING = 1 - INCOMING = 2 - - -class ParcelStatus(ParcelBase): - CONFIRMED = 'Potwierdzona' - COLLECTED_FROM_SENDER = 'Odebrana od nadawcy' - DISPATCHED_BY_SENDER_TO_POK = 'Nadana w PaczkoPunkcie' - DISPATCHED_BY_SENDER = 'Nadana w paczkomacie' - TAKEN_BY_COURIER = 'Odebrana przez Kuriera' - TAKEN_BY_COURIER_FROM_POK = 'Odebrana z PaczkoPunktu nadawczego' - ADOPTED_AT_SOURCE_BRANCH = 'Przyjęta w oddziale' - ADOPTED_AT_SORTING_CENTER = 'Przyjęta w sortowni' - SENT_FROM_SOURCE_BRANCH = 'Wysłana z oddziału' - OUT_FOR_DELIVERY = 'Wydana do doręczenia' - READY_TO_PICKUP = 'Gotowa do odbioru' - DELIVERED = 'Doręczona' + @property + def session_uuid(self): + return self._session_uuid + @property + def location(self): + return self._location -class ParcelOwnership(ParcelBase): - FRIEND = 'Zaprzyjaźniona' - OWN = 'Własna' + @location.setter + def location(self, location_data: dict): + self._location = CompartmentLocation(location_data) + @property + def status(self): + return self._status -class ParcelServiceName(ParcelBase): - ALLEGRO_PARCEL = 1 - ALLEGRO_PARCEL_SMART = 2 - ALLEGRO_LETTER = 3 - ALLEGRO_COURIER = 4 - STANDARD = 5 - STANDARD_PARCEL_SMART = 6 - PASS_THRU = 7 - CUSTOMER_SERVICE_POINT = 8 - REVERSE = 9 - STANDARD_COURIER = 10 - REVERSE_RETURN = 11 + @status.setter + def status(self, status_data: str | CompartmentActualStatus): + self._status = status_data if isinstance(status_data, CompartmentActualStatus) \ + else CompartmentActualStatus[status_data] From 38416b967dc085fe0d2a0514ca09173e39a2a0a6 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 29 Dec 2022 17:34:25 +0100 Subject: [PATCH 35/40] handling compartmant opening --- api/api.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/api/api.py b/api/api.py index b55944f..637ca22 100644 --- a/api/api.py +++ b/api/api.py @@ -1,3 +1,5 @@ +from typing import Generator + from aiohttp import ClientSession from static.endpoints import * from static.headers import appjson @@ -12,6 +14,7 @@ def __init__(self, phone_number: str): self.auth_token: str | None = None self.refr_token: str | None = None self.sess: ClientSession = ClientSession() + self.parcel: Parcel | None = None def __repr__(self): return f'Username: {self.phone_number}\nToken: {self.auth_token}' @@ -102,15 +105,29 @@ async def get_parcel(self, shipment_number: Union[int, str], parse=False) -> Uni raise SomeAPIError(reason=resp) async def get_parcels(self, + parcel_type: ParcelType = ParcelType.TRACKED, status: Optional[Union[ParcelStatus, List[ParcelStatus]]] = None, pickup_point: Optional[Union[str, List[str]]] = None, shipment_type: Optional[Union[ParcelShipmentType, List[ParcelShipmentType]]] = None, parcel_size: Optional[Union[ParcelLockerSize, ParcelCarrierSize]] = None, - parse: bool = False) -> Union[dict, List[Parcel]]: + parse: bool = False) -> Union[List[dict], Generator[dict], List[Parcel]]: if not self.auth_token: raise NotAuthenticatedError(reason='Not logged in') - async with await self.sess.get(url=parcels, + if not isinstance(parcel_type, ParcelType): + raise ParcelTypeError(reason=f'Unknown parcel type: {parcel_type}') + + match parcel_type: + case ParcelType.TRACKED: + url = parcels + case ParcelType.SENT: + url = sent + case ParcelType.RETURNS: + url = returns + case _: + raise ParcelTypeError(reason=f'Unknown parcel type: {parcel_type}') + + async with await self.sess.get(url=url, headers={'Authorization': self.auth_token}, ) as resp: if resp.status == 200: @@ -152,3 +169,88 @@ async def get_parcels(self, else: raise SomeAPIError(reason=resp) + + async def collect_compartment_properties(self, shipment_number: str | None = None, parcel_obj: Parcel | None = None, + location: dict | None = None) -> bool: + assert shipment_number is not None and parcel_obj is not None, 'shipment_number and parcel_obj arguments ' \ + 'filled, choose one ' + + if shipment_number is not None and parcel_obj is None: + parcel_obj = await self.get_parcel(shipment_number=shipment_number, parse=True) + + async with await self.sess.post(url=collect, + headers={'Authorization': self.auth_token}, + json={ + 'parcel': parcel_obj.compartment_open_data, + 'geoPoint': location if location is not None else parcel_obj.mocked_location + }) as collect_resp: + if collect_resp.status == 200: + parcel_obj.compartment_properties(await collect_resp.json()) + self.parcel = parcel_obj + return True + + else: + raise SomeAPIError(reason=collect_resp) + + async def open_compartment(self): + async with await self.sess.post(url=compartment_open, + headers={'Authorization': self.auth_token}, + json={ + 'sessionUuid': self.parcel.compartment_properties.session_uuid + }) as compartment_open_resp: + if compartment_open_resp == 200: + self.parcel.compartment_properties.location(await compartment_open_resp.json()) + return True + + else: + raise SomeAPIError(reason=compartment_open_resp) + + async def check_compartment_status(self, expected_status: CompartmentExpectedStatus = CompartmentExpectedStatus.OPENED): + async with await self.sess.post(url=compartment_status, + headers={'Authorization': self.auth_token}, + json={ + 'sessionUuid': self.parcel.compartment_properties.session_uuid, + 'expectedStatus': expected_status.name + }) as compartment_status_resp: + if compartment_status_resp.status == 200: + return CompartmentExpectedStatus[(await compartment_status_resp.json())['status']] == expected_status + else: + raise SomeAPIError(reason=compartment_status_resp) + + async def terminate_collect_session(self): + async with await self.sess.post(url=terminate_collect_session, + headers={'Authorization': self.auth_token}, + json={ + 'sessionUuid': self.parcel.compartment_properties.session_uuid + }) as terminate_resp: + if terminate_resp.status == 200: + return True + else: + raise SomeAPIError(reason=terminate_resp) + + async def collect(self, shipment_number: str | None = None, parcel_obj: Parcel | None = None, + location: dict | None = None) -> bool: + assert shipment_number is not None and parcel_obj is not None, 'shipment_number and parcel_obj arguments ' \ + 'filled, choose one ' + + if shipment_number is not None and parcel_obj is None: + parcel_obj = await self.get_parcel(shipment_number=shipment_number, parse=True) + + if await self.collect_compartment_properties(parcel_obj=parcel_obj, location=location): + if await self.open_compartment(): + if await self.check_compartment_status(): + return True + + return False + + async def close_compartment(self) -> bool: + if await self.check_compartment_status(expected_status=CompartmentExpectedStatus.CLOSED): + if await self.terminate_collect_session(): + return True + + return False + + async def get_prices(self) -> dict: + async with await self.sess.get(url=parcel_prices, + headers={'Authorization': self.auth_token}) as resp: + return await resp.json() From 2f786eb5190b669b65f0578e507b31358519156c Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 29 Dec 2022 17:43:13 +0100 Subject: [PATCH 36/40] added readme --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d89e530 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Fully async Inpost API library \ No newline at end of file From 78e22309e82e4d320ad1a480ae12a26ea7efa709 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 5 Jan 2023 15:23:37 +0100 Subject: [PATCH 37/40] cleanup, renames and fixed shameful mistakes --- api/api.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/api/api.py b/api/api.py index 637ca22..28a513a 100644 --- a/api/api.py +++ b/api/api.py @@ -1,6 +1,5 @@ -from typing import Generator - from aiohttp import ClientSession + from static.endpoints import * from static.headers import appjson from static.exceptions import * @@ -82,7 +81,7 @@ async def logout(self) -> Optional[bool]: self.sms_code = None return True else: - raise SomeAPIError(reason=resp) + raise UnidentifiedAPIError(reason=resp) async def disconnect(self) -> bool: if await self.logout(): @@ -102,7 +101,7 @@ async def get_parcel(self, shipment_number: Union[int, str], parse=False) -> Uni return await resp.json() if not parse else Parcel(await resp.json()) else: - raise SomeAPIError(reason=resp) + raise UnidentifiedAPIError(reason=resp) async def get_parcels(self, parcel_type: ParcelType = ParcelType.TRACKED, @@ -110,7 +109,7 @@ async def get_parcels(self, pickup_point: Optional[Union[str, List[str]]] = None, shipment_type: Optional[Union[ParcelShipmentType, List[ParcelShipmentType]]] = None, parcel_size: Optional[Union[ParcelLockerSize, ParcelCarrierSize]] = None, - parse: bool = False) -> Union[List[dict], Generator[dict], List[Parcel]]: + parse: bool = False) -> Union[List[dict], List[Parcel]]: if not self.auth_token: raise NotAuthenticatedError(reason='Not logged in') @@ -168,12 +167,10 @@ async def get_parcels(self, return _parcels if not parse else [Parcel(parcel_data=data) for data in _parcels] else: - raise SomeAPIError(reason=resp) + raise UnidentifiedAPIError(reason=resp) async def collect_compartment_properties(self, shipment_number: str | None = None, parcel_obj: Parcel | None = None, location: dict | None = None) -> bool: - assert shipment_number is not None and parcel_obj is not None, 'shipment_number and parcel_obj arguments ' \ - 'filled, choose one ' if shipment_number is not None and parcel_obj is None: parcel_obj = await self.get_parcel(shipment_number=shipment_number, parse=True) @@ -185,12 +182,12 @@ async def collect_compartment_properties(self, shipment_number: str | None = Non 'geoPoint': location if location is not None else parcel_obj.mocked_location }) as collect_resp: if collect_resp.status == 200: - parcel_obj.compartment_properties(await collect_resp.json()) + parcel_obj.compartment_properties = await collect_resp.json() self.parcel = parcel_obj return True else: - raise SomeAPIError(reason=collect_resp) + raise UnidentifiedAPIError(reason=collect_resp) async def open_compartment(self): async with await self.sess.post(url=compartment_open, @@ -198,14 +195,15 @@ async def open_compartment(self): json={ 'sessionUuid': self.parcel.compartment_properties.session_uuid }) as compartment_open_resp: - if compartment_open_resp == 200: - self.parcel.compartment_properties.location(await compartment_open_resp.json()) + if compartment_open_resp.status == 200: + self.parcel.compartment_properties.location = await compartment_open_resp.json() return True else: - raise SomeAPIError(reason=compartment_open_resp) + raise UnidentifiedAPIError(reason=compartment_open_resp) - async def check_compartment_status(self, expected_status: CompartmentExpectedStatus = CompartmentExpectedStatus.OPENED): + async def check_compartment_status(self, + expected_status: CompartmentExpectedStatus = CompartmentExpectedStatus.OPENED): async with await self.sess.post(url=compartment_status, headers={'Authorization': self.auth_token}, json={ @@ -215,7 +213,7 @@ async def check_compartment_status(self, expected_status: CompartmentExpectedSta if compartment_status_resp.status == 200: return CompartmentExpectedStatus[(await compartment_status_resp.json())['status']] == expected_status else: - raise SomeAPIError(reason=compartment_status_resp) + raise UnidentifiedAPIError(reason=compartment_status_resp) async def terminate_collect_session(self): async with await self.sess.post(url=terminate_collect_session, @@ -226,13 +224,10 @@ async def terminate_collect_session(self): if terminate_resp.status == 200: return True else: - raise SomeAPIError(reason=terminate_resp) + raise UnidentifiedAPIError(reason=terminate_resp) async def collect(self, shipment_number: str | None = None, parcel_obj: Parcel | None = None, location: dict | None = None) -> bool: - assert shipment_number is not None and parcel_obj is not None, 'shipment_number and parcel_obj arguments ' \ - 'filled, choose one ' - if shipment_number is not None and parcel_obj is None: parcel_obj = await self.get_parcel(shipment_number=shipment_number, parse=True) From f668cd08478b0929fbaa8df828361265b1ff93e2 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 5 Jan 2023 15:24:04 +0100 Subject: [PATCH 38/40] new endpoints for friends and sharing parcels --- static/endpoints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/endpoints.py b/static/endpoints.py index a3425a3..da566fe 100644 --- a/static/endpoints.py +++ b/static/endpoints.py @@ -11,6 +11,8 @@ compartment_open: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/open' # post compartment_status: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/status' # post terminate_collect_session: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/terminate' # post +friends: str = 'https://api-inmobile-pl.easypack24.net/v1/friends/' # get +shared: str = 'https://api-inmobile-pl.easypack24.net/v1/parcels/shared' # post sent: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/sent/' # get returns: str = 'https://api-inmobile-pl.easypack24.net/v1/returns/parcels/' # get parcel_prices: str = 'https://api-inmobile-pl.easypack24.net/v1/prices/parcels' # get From 9be1b334fa516f3600ed5c887902e20cd4df5d9e Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 5 Jan 2023 15:24:14 +0100 Subject: [PATCH 39/40] renames --- static/exceptions.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/static/exceptions.py b/static/exceptions.py index 973e44d..2c7dbad 100644 --- a/static/exceptions.py +++ b/static/exceptions.py @@ -2,7 +2,7 @@ from typing import Optional, Any -class SomeParcelError(Exception): +class UnidentifiedParcelError(Exception): def __init__(self, reason): super().__init__(reason) self.msg: str = Optional[str] @@ -80,7 +80,7 @@ def stack(self): return self.reason -class SomeAPIError(Exception): +class UnidentifiedAPIError(Exception): def __init__(self, reason): super().__init__(reason) self.msg: str = Optional[str] @@ -101,3 +101,14 @@ def __init__(self, reason): @property def stack(self): return self.reason + + +class UnidentifiedError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason From 667cdeb3cf6d98ef8777cc3218d583f3db388f87 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 5 Jan 2023 15:25:13 +0100 Subject: [PATCH 40/40] made mocked_location more random --- static/parcels.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/static/parcels.py b/static/parcels.py index 51d2ab5..bfe1bfd 100644 --- a/static/parcels.py +++ b/static/parcels.py @@ -1,3 +1,4 @@ +import random from io import BytesIO from typing import List, Optional, Tuple, Union @@ -78,11 +79,11 @@ def compartment_open_data(self): } @property - def mocked_location(self): # TODO[1]: MAKE IT MORE RANDOM + def mocked_location(self): return { - 'latitude': self.pickup_point.latitude, - 'longitude': self.pickup_point.longitude, - 'accuracy': 3 + 'latitude': round(self.pickup_point.latitude + random.uniform(-0.00005, 0.00005), 6), + 'longitude': round(self.pickup_point.longitude + random.uniform(-0.00005, 0.00005), 6), + 'accuracy': round(random.uniform(1, 4), 1) }