1- """
2- Python wrapper for the PostNL API
3- """
1+ """ Python wrapper for the PostNL API """
42
53import logging
6- from datetime import datetime
4+ from datetime import datetime , timedelta
75import requests
86
9- _LOGGER = logging .getLogger (__name__ )
10-
117BASE_URL = 'https://jouw.postnl.nl'
128
139AUTHENTICATE_URL = BASE_URL + '/mobile/token'
1410SHIPMENTS_URL = BASE_URL + '/mobile/api/shipments'
1511PROFILE_URL = BASE_URL + '/mobile/api/profile'
1612LETTERS_URL = BASE_URL + '/mobile/api/letters'
13+ VALIDATE_LETTERS_URL = BASE_URL + '/mobile/api/letters/validation'
1714
18- class PostNL_API (object ):
15+ DEFAULT_HEADER = {
16+ 'api-version' : '4.7' ,
17+ 'user-agent' : 'PostNL/1 CFNetwork/889.3 Darwin/17.2.0' ,
18+ }
1919
20- """
21- Interface class for the PostNL API
22- """
20+ class PostNL_API (object ):
21+ """ Interface class for the PostNL API """
2322
2423 def __init__ (self , user , password ):
24+ """ Constructor """
2525
2626 self ._user = user
2727 self ._password = password
@@ -33,58 +33,47 @@ def __init__(self, user, password):
3333 'password' : self ._password
3434 }
3535
36- headers = {
37- 'api-version' : '4.6' ,
38- 'user-agent' : 'PostNL/1 CFNetwork/889.3 Darwin/17.2.0' ,
39- 'content-type' : "application/x-www-form-urlencoded" ,
40- }
36+ try :
37+ response = requests .request (
38+ 'POST' , AUTHENTICATE_URL , data = payload , headers = DEFAULT_HEADER )
39+ data = response .json ()
4140
42- response = requests . request (
43- 'POST' , AUTHENTICATE_URL , data = payload , headers = headers )
41+ except Exception :
42+ raise ( Exception )
4443
45- data = response .json ()
44+ if 'error' in data :
45+ raise Exception (data ['error' ])
4646
4747 self ._access_token = data ['access_token' ]
4848 self ._refresh_token = data ['refresh_token' ]
49- self ._token_expires_in = data ['expires_in' ] # TODO Add logic to refresh on invalidate
49+ self ._token_expires_in = data ['expires_in' ]
50+ self ._token_expires_at = datetime .now () + timedelta (0 , data ['expires_in' ])
5051
51- """
52- Refresh access_token
53- """
5452 def refresh_token (self ):
53+ """ Refresh access_token """
5554
5655 payload = {
5756 'grant_type' : 'refresh_token' ,
5857 'client_id' : 'pwIOSApp' ,
5958 'refresh_token' : self ._refresh_token
6059 }
6160
62- headers = {
63- 'api-version' : '4.6' ,
64- 'user-agent' : 'PostNL/1 CFNetwork/889.3 Darwin/17.2.0' ,
65- 'content-type' : "application/x-www-form-urlencoded" ,
66- }
67-
6861 response = requests .request (
69- 'POST' , AUTHENTICATE_URL , data = payload , headers = headers )
62+ 'POST' , AUTHENTICATE_URL , data = payload , headers = DEFAULT_HEADER )
7063
7164 data = response .json ()
7265
7366 self ._access_token = data ['access_token' ]
7467
75- """
76- Retrieve shipments
77- """
7868 def get_shipments (self ):
69+ """ Retrieve shipments """
7970
8071 headers = {
81- 'api-version' : '4.6' ,
82- 'user-agent' : 'PostNL/1 CFNetwork/889.3 Darwin/17.2.0' ,
8372 'authorization' : 'Bearer ' + self ._access_token
8473 }
8574
8675 response = requests .request (
87- 'GET' , SHIPMENTS_URL , headers = headers )
76+ 'GET' , SHIPMENTS_URL , headers = { ** headers , ** DEFAULT_HEADER } )
8877
8978 if response .status_code == 401 :
9079 self .refresh_token ()
@@ -94,19 +83,33 @@ def get_shipments(self):
9483
9584 return shipments
9685
97- """
98- Retrieve profile
99- """
86+ def get_shipment (self , shipment_id ):
87+ """ Retrieve single shipment by id """
88+
89+ headers = {
90+ 'authorization' : 'Bearer ' + self ._access_token
91+ }
92+
93+ response = requests .request (
94+ 'GET' , SHIPMENTS_URL + '/' + shipment_id , headers = {** headers , ** DEFAULT_HEADER })
95+
96+ if response .status_code == 401 :
97+ self .refresh_token ()
98+ shipments = self .get_shipment (shipment_id )
99+ else :
100+ shipments = response .json ()
101+
102+ return shipments
103+
100104 def get_profile (self ):
105+ """ Retrieve profile """
101106
102107 headers = {
103- 'api-version' : '4.6' ,
104- 'user-agent' : 'PostNL/1 CFNetwork/889.3 Darwin/17.2.0' ,
105108 'authorization' : 'Bearer ' + self ._access_token
106109 }
107110
108111 response = requests .request (
109- 'GET' , PROFILE_URL , headers = headers )
112+ 'GET' , PROFILE_URL , headers = { ** headers , ** DEFAULT_HEADER } )
110113
111114 if response .status_code == 401 :
112115 self .refresh_token ()
@@ -116,36 +119,69 @@ def get_profile(self):
116119
117120 return profile
118121
119- """
120- Retrieve letters
121- """
122- def get_letters (self ):
122+ def validate_letters (self ):
123+ """ Retrieve letter validation status """
123124
124125 headers = {
125- 'api-version' : '4.6' ,
126- 'user-agent' : 'PostNL/1 CFNetwork/889.3 Darwin/17.2.0' ,
127126 'authorization' : 'Bearer ' + self ._access_token
128127 }
129128
130129 response = requests .request (
131- 'GET' , LETTERS_URL , headers = headers )
130+ 'GET' , VALIDATE_LETTERS_URL , headers = { ** headers , ** DEFAULT_HEADER } )
132131
132+ if response .status_code == 401 :
133+ self .refresh_token ()
134+ validation = self .validate_letters ()
135+ else :
136+ validation = response .json ()
137+
138+ return validation
139+
140+ def get_letters (self ):
141+ """ Retrieve letters """
142+
143+ headers = {
144+ 'authorization' : 'Bearer ' + self ._access_token
145+ }
146+
147+ response = requests .request (
148+ 'GET' , LETTERS_URL , headers = {** headers , ** DEFAULT_HEADER })
149+
133150 if response .status_code == 401 :
134151 self .refresh_token ()
135152 letters = self .get_letters ()
136153 else :
137154 letters = response .json ()
138155
139- if letters ['type' ] == 'ProfileValidationFeatureMissing' :
140- _LOGGER .error (letters ['message' ])
141- return []
156+ # TODO Add validation / exception handling
157+ # if letters['type'] == 'ProfileValidationFeatureMissing':
158+ # _LOGGER.error(letters['message'])
159+ # return []
142160
143161 return letters
144162
145- """
146- Retrieve relevant shipments
147- """
163+ def get_letter (self , letter_id ):
164+ """ Retrieve single letter by id """
165+
166+ headers = {
167+ 'authorization' : 'Bearer ' + self ._access_token
168+ }
169+
170+ response = requests .request (
171+ 'GET' , LETTERS_URL + '/' + letter_id , headers = {** headers , ** DEFAULT_HEADER })
172+
173+ if response .status_code == 200 :
174+ letter = response .json ()
175+ elif response .status_code == 401 :
176+ self .refresh_token ()
177+ letter = self .get_letter (letter_id )
178+ else :
179+ raise Exception ('Unknown Error' )
180+
181+ return letter
182+
148183 def get_relevant_shipments (self ):
184+ """ Retrieve not delivered shipments and shipments delivered today """
149185
150186 shipments = self .get_shipments ()
151187 relevant_shipments = []
@@ -159,10 +195,28 @@ def get_relevant_shipments(self):
159195
160196 # Check if package has been delivered today
161197 if shipment ['status' ]['delivery' ]:
162- delivery_date = datetime .strptime ( shipment ['status' ]['delivery' ]['deliveryDate' ][:19 ], "%Y-%m-%dT%H:%M:%S" )
198+ delivery_date = datetime .strptime (
199+ shipment ['status' ]['delivery' ]['deliveryDate' ][:19 ], "%Y-%m-%dT%H:%M:%S" )
163200
164201 if delivery_date .date () == datetime .today ().date ():
165202 relevant_shipments .append (shipment )
166- continue
167203
168- return relevant_shipments
204+ return relevant_shipments
205+
206+ def get_relevant_letters (self ):
207+ """ Retrieve letters with a future delivery date """
208+
209+ letters = self .get_letters ()
210+ relevant_letters = []
211+
212+ for letter in letters :
213+
214+ # Check if letter is scheduled for delivery in the future
215+ if letter ['expectedDeliveryDate' ]:
216+ expected_delivery_date = datetime .strptime (
217+ letter ['expectedDeliveryDate' ][:19 ], "%Y-%m-%dT%H:%M:%S" )
218+
219+ if expected_delivery_date .date () == datetime .today ().date ():
220+ relevant_letters .append (letter )
221+
222+ return relevant_letters
0 commit comments