uk_bin_collection 0.152.11__py3-none-any.whl → 0.154.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uk_bin_collection/tests/input.json +16 -21
- uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +45 -120
- uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +4 -1
- uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +1 -1
- uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +15 -36
- uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +75 -100
- uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +55 -24
- uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py +82 -24
- uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +32 -34
- uk_bin_collection/uk_bin_collection/councils/FarehamBoroughCouncil.py +5 -2
- uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py +22 -0
- uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +1 -1
- uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py +3 -1
- uk_bin_collection/uk_bin_collection/councils/HinckleyandBosworthBoroughCouncil.py +7 -1
- uk_bin_collection/uk_bin_collection/councils/IpswichBoroughCouncil.py +3 -1
- uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py +7 -1
- uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +17 -6
- uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py +26 -128
- uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +63 -79
- uk_bin_collection/uk_bin_collection/councils/NorwichCityCouncil.py +67 -66
- uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py +19 -7
- uk_bin_collection/uk_bin_collection/councils/RunnymedeBoroughCouncil.py +7 -1
- uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +4 -2
- uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py +4 -11
- uk_bin_collection/uk_bin_collection/councils/SloughBoroughCouncil.py +39 -21
- uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +4 -0
- uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +16 -13
- uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +47 -29
- {uk_bin_collection-0.152.11.dist-info → uk_bin_collection-0.154.0.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.152.11.dist-info → uk_bin_collection-0.154.0.dist-info}/RECORD +33 -33
- {uk_bin_collection-0.152.11.dist-info → uk_bin_collection-0.154.0.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.152.11.dist-info → uk_bin_collection-0.154.0.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.152.11.dist-info → uk_bin_collection-0.154.0.dist-info}/entry_points.txt +0 -0
@@ -102,12 +102,9 @@
|
|
102
102
|
},
|
103
103
|
"BCPCouncil": {
|
104
104
|
"LAD24CD": "E06000058",
|
105
|
-
"house_number": "3 HARBOUR VIEW ROAD, POOLE, BH14 0PD",
|
106
|
-
"postcode": "BH14 0PD",
|
107
|
-
"web_driver": "http://selenium:4444",
|
108
105
|
"skip_get_url": true,
|
109
106
|
"uprn": "100040810214",
|
110
|
-
"url": "https://
|
107
|
+
"url": "https://bcpportal.bcpcouncil.gov.uk/checkyourbincollection",
|
111
108
|
"wiki_name": "Bournemouth, Christchurch and Poole",
|
112
109
|
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
113
110
|
},
|
@@ -253,7 +250,7 @@
|
|
253
250
|
"postcode": "BL1 5PQ",
|
254
251
|
"skip_get_url": true,
|
255
252
|
"uprn": "100010886936",
|
256
|
-
"url": "https://
|
253
|
+
"url": "https://web.bolton.gov.uk/bins.aspx",
|
257
254
|
"web_driver": "http://selenium:4444",
|
258
255
|
"wiki_name": "Bolton",
|
259
256
|
"wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). Previously required a single field that was UPRN and full address; now requires UPRN and postcode as separate fields.",
|
@@ -377,13 +374,10 @@
|
|
377
374
|
"LAD24CD": "E07000172"
|
378
375
|
},
|
379
376
|
"BuckinghamshireCouncil": {
|
380
|
-
"house_number": "The Ridings, Magpie Lane, Loudwater, High Wycombe, HP13 7BA",
|
381
|
-
"postcode": "HP13 7BA",
|
382
377
|
"uprn": "100081093078",
|
383
378
|
"url": "https://www.buckinghamshire.gov.uk/waste-and-recycling/find-out-when-its-your-bin-collection/",
|
384
|
-
"web_driver": "http://selenium:4444",
|
385
379
|
"wiki_name": "Buckinghamshire",
|
386
|
-
"wiki_note": "Pass the
|
380
|
+
"wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).",
|
387
381
|
"LAD24CD": "E06000060"
|
388
382
|
},
|
389
383
|
"BurnleyBoroughCouncil": {
|
@@ -891,7 +885,7 @@
|
|
891
885
|
"ErewashBoroughCouncil": {
|
892
886
|
"skip_get_url": true,
|
893
887
|
"uprn": "10003582028",
|
894
|
-
"url": "https://
|
888
|
+
"url": "https://www.erewash.gov.uk",
|
895
889
|
"wiki_name": "Erewash",
|
896
890
|
"wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).",
|
897
891
|
"LAD24CD": "E07000036"
|
@@ -998,7 +992,7 @@
|
|
998
992
|
},
|
999
993
|
"GlasgowCityCouncil": {
|
1000
994
|
"uprn": "906700034497",
|
1001
|
-
"url": "https://onlineservices.glasgow.gov.uk/forms/
|
995
|
+
"url": "https://onlineservices.glasgow.gov.uk/forms/refuseandrecyclingcalendar/AddressSearch.aspx",
|
1002
996
|
"skip_get_url": true,
|
1003
997
|
"wiki_name": "Glasgow City",
|
1004
998
|
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.",
|
@@ -1446,7 +1440,7 @@
|
|
1446
1440
|
"house_number": "71",
|
1447
1441
|
"postcode": "ME16 8BT",
|
1448
1442
|
"url": "https://my.maidstone.gov.uk/service/Find-your-bin-day",
|
1449
|
-
"web_driver": "http://selenium:4444",
|
1443
|
+
"web_driver": "http://selenium:4444",
|
1450
1444
|
"wiki_name": "Maidstone",
|
1451
1445
|
"wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.",
|
1452
1446
|
"LAD24CD": "E07000110"
|
@@ -1782,21 +1776,22 @@
|
|
1782
1776
|
"LAD24CD": "E06000065"
|
1783
1777
|
},
|
1784
1778
|
"NorthumberlandCouncil": {
|
1785
|
-
"
|
1786
|
-
"postcode": "
|
1779
|
+
"uprn": "010096302588",
|
1780
|
+
"postcode": "NE65 0ZP",
|
1787
1781
|
"skip_get_url": true,
|
1788
|
-
"url": "https://
|
1782
|
+
"url": "https://bincollection.northumberland.gov.uk/postcode",
|
1789
1783
|
"web_driver": "http://selenium:4444",
|
1790
1784
|
"wiki_name": "Northumberland",
|
1791
|
-
"wiki_note": "Pass the
|
1785
|
+
"wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).",
|
1792
1786
|
"LAD24CD": "E06000057"
|
1793
1787
|
},
|
1794
1788
|
"NorwichCityCouncil": {
|
1795
|
-
"
|
1796
|
-
"
|
1797
|
-
"
|
1789
|
+
"house_number": "2",
|
1790
|
+
"postcode": "NR2 3TT",
|
1791
|
+
"url": "https://bnr-wrp.whitespacews.com",
|
1792
|
+
"wiki_command_url_override": "hhttps://bnr-wrp.whitespacews.com",
|
1798
1793
|
"wiki_name": "Norwich",
|
1799
|
-
"wiki_note": "
|
1794
|
+
"wiki_note": "Pass the house number and postcode in their respective parameters.",
|
1800
1795
|
"LAD24CD": "E07000148"
|
1801
1796
|
},
|
1802
1797
|
"NottinghamCityCouncil": {
|
@@ -2807,4 +2802,4 @@
|
|
2807
2802
|
"wiki_note": "Provide your UPRN.",
|
2808
2803
|
"LAD24CD": "E06000014"
|
2809
2804
|
}
|
2810
|
-
}
|
2805
|
+
}
|
@@ -1,15 +1,13 @@
|
|
1
|
-
import json
|
2
1
|
import time
|
3
|
-
|
4
|
-
|
5
|
-
from
|
6
|
-
|
7
|
-
from selenium.webdriver.support import expected_conditions as EC
|
8
|
-
from selenium.webdriver.common.keys import Keys
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from dateutil.relativedelta import relativedelta
|
5
|
+
|
9
6
|
from uk_bin_collection.uk_bin_collection.common import *
|
10
7
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
11
8
|
|
12
9
|
|
10
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
13
11
|
class CouncilClass(AbstractGetBinDataClass):
|
14
12
|
"""
|
15
13
|
Concrete classes have to implement all abstract operations of the
|
@@ -18,116 +16,43 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
18
16
|
"""
|
19
17
|
|
20
18
|
def parse_data(self, page: str, **kwargs) -> dict:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
# Find and select the address containing the house number
|
62
|
-
address_option = WebDriverWait(driver, 10).until(
|
63
|
-
EC.element_to_be_clickable((By.XPATH, f"//option[contains(text(), 'HARBOUR VIEW ROAD')]"))
|
64
|
-
)
|
65
|
-
address_option.click()
|
66
|
-
|
67
|
-
# Wait for bin collection results to load
|
68
|
-
WebDriverWait(driver, 15).until(
|
69
|
-
EC.presence_of_element_located((By.XPATH, "//td[contains(text(), 'collection')] | //th[contains(text(), 'collection')]"))
|
70
|
-
)
|
71
|
-
|
72
|
-
# Find the table containing collection data by looking for a cell with 'collection' text
|
73
|
-
collection_table = WebDriverWait(driver, 10).until(
|
74
|
-
EC.presence_of_element_located((By.XPATH, "//td[contains(text(), 'collection')]/ancestor::table | //th[contains(text(), 'collection')]/ancestor::table"))
|
75
|
-
)
|
76
|
-
|
77
|
-
# Parse the table data
|
78
|
-
soup = BeautifulSoup(driver.page_source, 'html.parser')
|
79
|
-
data = {"bins": []}
|
80
|
-
|
81
|
-
# Find the table containing collection information
|
82
|
-
collection_cell = soup.find(['td', 'th'], string=lambda text: text and 'collection' in text.lower())
|
83
|
-
if collection_cell:
|
84
|
-
table = collection_cell.find_parent('table')
|
85
|
-
if table:
|
86
|
-
rows = table.find_all('tr')
|
87
|
-
for row in rows[1:]: # Skip header row
|
88
|
-
cells = row.find_all(['td', 'th'])
|
89
|
-
if len(cells) >= 2: # At least bin type and one collection date
|
90
|
-
bin_type = cells[0].get_text(strip=True)
|
91
|
-
next_collection = cells[1].get_text(strip=True) if len(cells) > 1 else ""
|
92
|
-
following_collection = cells[2].get_text(strip=True) if len(cells) > 2 else ""
|
93
|
-
|
94
|
-
|
95
|
-
# Process next collection date
|
96
|
-
if bin_type and next_collection and "No collection" not in next_collection:
|
97
|
-
try:
|
98
|
-
# Try multiple date formats
|
99
|
-
for date_fmt in ["%A, %d %B %Y", "%A %d %B %Y", "%d/%m/%Y", "%d-%m-%Y", "%Y-%m-%d"]:
|
100
|
-
try:
|
101
|
-
parsed_date = datetime.strptime(next_collection, date_fmt)
|
102
|
-
data["bins"].append({
|
103
|
-
"type": bin_type,
|
104
|
-
"collectionDate": parsed_date.strftime(date_format)
|
105
|
-
})
|
106
|
-
break
|
107
|
-
except ValueError:
|
108
|
-
continue
|
109
|
-
except:
|
110
|
-
continue
|
111
|
-
|
112
|
-
# Process following collection date
|
113
|
-
if bin_type and following_collection and "No collection" not in following_collection and "download PDF" not in following_collection:
|
114
|
-
try:
|
115
|
-
# Clean up the following collection text (remove PDF link text)
|
116
|
-
following_collection = following_collection.replace("download PDF", "").strip()
|
117
|
-
for date_fmt in ["%A, %d %B %Y", "%A %d %B %Y", "%d/%m/%Y", "%d-%m-%Y", "%Y-%m-%d"]:
|
118
|
-
try:
|
119
|
-
parsed_date = datetime.strptime(following_collection, date_fmt)
|
120
|
-
data["bins"].append({
|
121
|
-
"type": bin_type,
|
122
|
-
"collectionDate": parsed_date.strftime(date_format)
|
123
|
-
})
|
124
|
-
break
|
125
|
-
except ValueError:
|
126
|
-
continue
|
127
|
-
except:
|
128
|
-
continue
|
129
|
-
|
130
|
-
return data
|
131
|
-
|
132
|
-
finally:
|
133
|
-
driver.quit()
|
19
|
+
# Make a BS4 object
|
20
|
+
uprn = kwargs.get("uprn")
|
21
|
+
# usrn = kwargs.get("paon")
|
22
|
+
check_uprn(uprn)
|
23
|
+
# check_usrn(usrn)
|
24
|
+
bindata = {"bins": []}
|
25
|
+
|
26
|
+
# uprn = uprn.zfill(12)
|
27
|
+
|
28
|
+
API_URL = "https://prod-17.uksouth.logic.azure.com/workflows/58253d7b7d754447acf9fe5fcf76f493/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=TAvYIUFj6dzaP90XQCm2ElY6Cd34ze05I3ba7LKTiBs"
|
29
|
+
|
30
|
+
headers = {
|
31
|
+
"Content-Type": "application/json",
|
32
|
+
"Accept": "*/*",
|
33
|
+
"User-Agent": "Mozilla/5.0",
|
34
|
+
"Referer": "https://bcpportal.bcpcouncil.gov.uk/",
|
35
|
+
}
|
36
|
+
s = requests.session()
|
37
|
+
data = {
|
38
|
+
"uprn": uprn,
|
39
|
+
}
|
40
|
+
|
41
|
+
r = s.post(API_URL, json=data, headers=headers)
|
42
|
+
r.raise_for_status()
|
43
|
+
|
44
|
+
data = r.json()
|
45
|
+
rows_data = data["data"]
|
46
|
+
for row in rows_data:
|
47
|
+
bin_type = row["wasteContainerUsageTypeDescription"]
|
48
|
+
collections = row["scheduleDateRange"]
|
49
|
+
for collection in collections:
|
50
|
+
dict_data = {
|
51
|
+
"type": bin_type,
|
52
|
+
"collectionDate": datetime.strptime(
|
53
|
+
collection, "%Y-%m-%d"
|
54
|
+
).strftime(date_format),
|
55
|
+
}
|
56
|
+
bindata["bins"].append(dict_data)
|
57
|
+
|
58
|
+
return bindata
|
@@ -1,6 +1,8 @@
|
|
1
|
-
from bs4 import BeautifulSoup
|
2
1
|
from datetime import datetime
|
2
|
+
|
3
3
|
import requests
|
4
|
+
from bs4 import BeautifulSoup
|
5
|
+
|
4
6
|
from uk_bin_collection.uk_bin_collection.common import *
|
5
7
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
6
8
|
|
@@ -10,6 +12,7 @@ COLLECTION_KINDS = {
|
|
10
12
|
"glass": "rteelem_ctl03_pnlCollections_Glass",
|
11
13
|
# Garden waste data is only returned if the property is subscribed to the Garden Waste service
|
12
14
|
"garden": "rteelem_ctl03_pnlCollections_GardenWaste",
|
15
|
+
"food": "rteelem_ctl03_pnlCollections_Food",
|
13
16
|
}
|
14
17
|
|
15
18
|
|
@@ -35,7 +35,7 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
35
35
|
data = {"bins": []}
|
36
36
|
|
37
37
|
# Get our initial session running
|
38
|
-
page = "https://
|
38
|
+
page = "https://web.bolton.gov.uk/bins.aspx"
|
39
39
|
|
40
40
|
driver = create_webdriver(web_driver, headless, None, __name__)
|
41
41
|
driver.get(page)
|
@@ -72,7 +72,9 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
72
72
|
break
|
73
73
|
|
74
74
|
if not found:
|
75
|
-
raise Exception(
|
75
|
+
raise Exception(
|
76
|
+
f"Address containing '{user_paon}' not found in dropdown options"
|
77
|
+
)
|
76
78
|
|
77
79
|
submit_btn = wait.until(
|
78
80
|
EC.presence_of_element_located(
|
@@ -84,7 +86,7 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
84
86
|
|
85
87
|
results = wait.until(
|
86
88
|
EC.presence_of_element_located(
|
87
|
-
(By.XPATH, f'//
|
89
|
+
(By.XPATH, f'//div[contains(@class,"mx-name-listView1")]')
|
88
90
|
)
|
89
91
|
)
|
90
92
|
|
@@ -96,44 +98,21 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
96
98
|
current_date = datetime.now()
|
97
99
|
|
98
100
|
# Find all elements with class starting with 'mx-name-index-'
|
99
|
-
|
101
|
+
bin_view = soup.find(class_="mx-name-listView1")
|
102
|
+
bins = bin_view.find_all(
|
103
|
+
class_=lambda x: x and x.startswith("mx-name-index-")
|
104
|
+
)
|
100
105
|
|
101
106
|
for bin_item in bins:
|
102
|
-
bin_type = bin_item.find(class_="
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
): # Avoid taking the bin type as the date
|
110
|
-
next_sibling = elem.find_next_sibling()
|
111
|
-
if next_sibling:
|
112
|
-
bin_date_str = next_sibling.text.strip()
|
113
|
-
try:
|
114
|
-
# Try parsing the date string in the format 'dd Month' (e.g., '30 Dec', '5 January')
|
115
|
-
bin_date = datetime.strptime(bin_date_str, "%d %b")
|
116
|
-
except ValueError:
|
117
|
-
try:
|
118
|
-
# If the above format fails, try 'dd MonthName' (e.g., '30 December', '5 January')
|
119
|
-
bin_date = datetime.strptime(bin_date_str, "%d %B")
|
120
|
-
except ValueError:
|
121
|
-
pass
|
122
|
-
|
123
|
-
if bin_date:
|
124
|
-
# Set the year based on the logic provided
|
125
|
-
if bin_date.month < current_date.month:
|
126
|
-
bin_date = bin_date.replace(
|
127
|
-
year=current_date.year + 1
|
128
|
-
)
|
129
|
-
else:
|
130
|
-
bin_date = bin_date.replace(year=current_date.year)
|
131
|
-
# Format the date to the desired format
|
132
|
-
bin_date = bin_date.strftime("%d/%m/%Y")
|
133
|
-
break
|
107
|
+
bin_type = bin_item.find(class_="mx-name-text31").text.strip()
|
108
|
+
|
109
|
+
bin_date_str = bin_item.find(class_="mx-name-text29").text.strip()
|
110
|
+
|
111
|
+
bin_date = datetime.strptime(bin_date_str, "%d %B %Y")
|
112
|
+
bin_date = bin_date.strftime(date_format)
|
113
|
+
|
134
114
|
dict_data = {"type": bin_type, "collectionDate": bin_date}
|
135
115
|
data["bins"].append(dict_data)
|
136
|
-
print(data)
|
137
116
|
except Exception as e:
|
138
117
|
# Here you can log the exception if needed
|
139
118
|
print(f"An error occurred: {e}")
|
@@ -1,11 +1,26 @@
|
|
1
|
-
|
2
|
-
from
|
3
|
-
from
|
4
|
-
from selenium.webdriver.support.wait import WebDriverWait
|
1
|
+
import json
|
2
|
+
from dataclasses import asdict, dataclass
|
3
|
+
from typing import Literal
|
5
4
|
|
6
|
-
|
5
|
+
import requests
|
6
|
+
from cryptography.hazmat.backends import default_backend
|
7
|
+
from cryptography.hazmat.primitives import padding
|
8
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
9
|
+
|
10
|
+
from uk_bin_collection.uk_bin_collection.common import check_uprn
|
7
11
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
8
12
|
|
13
|
+
key_hex = "F57E76482EE3DC3336495DEDEEF3962671B054FE353E815145E29C5689F72FEC"
|
14
|
+
iv_hex = "2CBF4FC35C69B82362D393A4F0B9971A"
|
15
|
+
|
16
|
+
|
17
|
+
@dataclass
|
18
|
+
class BucksInput:
|
19
|
+
P_CLIENT_ID: Literal[152]
|
20
|
+
P_COUNCIL_ID: Literal[34505]
|
21
|
+
P_LANG_CODE: Literal["EN"]
|
22
|
+
P_UPRN: str
|
23
|
+
|
9
24
|
|
10
25
|
class CouncilClass(AbstractGetBinDataClass):
|
11
26
|
"""
|
@@ -14,113 +29,73 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
14
29
|
implementation.
|
15
30
|
"""
|
16
31
|
|
17
|
-
def
|
18
|
-
|
32
|
+
def encode_body(self, bucks_input: BucksInput):
|
33
|
+
key = bytes.fromhex(key_hex)
|
34
|
+
iv = bytes.fromhex(iv_hex)
|
35
|
+
|
36
|
+
json_data = json.dumps(asdict(bucks_input))
|
37
|
+
data_bytes = json_data.encode("utf-8")
|
38
|
+
|
39
|
+
padder = padding.PKCS7(128).padder()
|
40
|
+
padded_data = padder.update(data_bytes) + padder.finalize()
|
41
|
+
|
42
|
+
backend = default_backend()
|
43
|
+
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
|
44
|
+
encryptor = cipher.encryptor()
|
45
|
+
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
|
46
|
+
|
47
|
+
return ciphertext.hex()
|
48
|
+
|
49
|
+
def decode_response(self, hex_input: str):
|
50
|
+
|
51
|
+
key = bytes.fromhex(key_hex)
|
52
|
+
iv = bytes.fromhex(iv_hex)
|
53
|
+
ciphertext = bytes.fromhex(hex_input)
|
54
|
+
|
55
|
+
backend = default_backend()
|
56
|
+
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
|
57
|
+
decryptor = cipher.decryptor()
|
58
|
+
decrypted_padded = decryptor.update(ciphertext) + decryptor.finalize()
|
59
|
+
|
60
|
+
unpadder = padding.PKCS7(128).unpadder()
|
61
|
+
plaintext_bytes = unpadder.update(decrypted_padded) + unpadder.finalize()
|
62
|
+
plaintext = plaintext_bytes.decode("utf-8")
|
63
|
+
|
64
|
+
return json.loads(plaintext)
|
65
|
+
|
66
|
+
def parse_data(self, _: str, **kwargs) -> dict:
|
19
67
|
try:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
web_driver = kwargs.get("web_driver")
|
25
|
-
headless = kwargs.get("headless")
|
26
|
-
check_paon(user_paon)
|
27
|
-
check_postcode(user_postcode)
|
28
|
-
|
29
|
-
# Create Selenium webdriver
|
30
|
-
driver = create_webdriver(web_driver, headless, None, __name__)
|
31
|
-
driver.get(kwargs.get("url"))
|
32
|
-
|
33
|
-
# Click "Check now" button
|
34
|
-
check_now_button = WebDriverWait(driver, 10).until(
|
35
|
-
EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), 'Check now')]"))
|
68
|
+
user_uprn: str = kwargs.get("uprn") or ""
|
69
|
+
check_uprn(user_uprn)
|
70
|
+
bucks_input = BucksInput(
|
71
|
+
P_CLIENT_ID=152, P_COUNCIL_ID=34505, P_LANG_CODE="EN", P_UPRN=user_uprn
|
36
72
|
)
|
37
|
-
check_now_button.click()
|
38
73
|
|
39
|
-
|
40
|
-
inputElement_postcode = WebDriverWait(driver, 10).until(
|
41
|
-
EC.presence_of_element_located((By.ID, "postcodeSearch"))
|
42
|
-
)
|
43
|
-
inputElement_postcode.send_keys(user_postcode)
|
74
|
+
encoded_input = self.encode_body(bucks_input)
|
44
75
|
|
45
|
-
|
46
|
-
|
47
|
-
|
76
|
+
session = requests.Session()
|
77
|
+
response = session.post(
|
78
|
+
"https://itouchvision.app/portal/itouchvision/kmbd/collectionDay",
|
79
|
+
data=encoded_input,
|
48
80
|
)
|
49
|
-
find_button.click()
|
50
|
-
|
51
|
-
# Wait for the address dropdown and select by UPRN
|
52
|
-
if user_uprn:
|
53
|
-
address_option = WebDriverWait(driver, 10).until(
|
54
|
-
EC.element_to_be_clickable((By.XPATH, f"//option[@value='{user_uprn}']"))
|
55
|
-
)
|
56
|
-
address_option.click()
|
57
|
-
else:
|
58
|
-
# Fallback to selecting by address text
|
59
|
-
address_option = WebDriverWait(driver, 10).until(
|
60
|
-
EC.element_to_be_clickable(
|
61
|
-
(By.XPATH, f"//select[@id='addressSelect']//option[contains(., '{user_paon}')]")
|
62
|
-
)
|
63
|
-
)
|
64
|
-
address_option.click()
|
65
81
|
|
66
|
-
|
67
|
-
import time
|
68
|
-
time.sleep(2)
|
82
|
+
output = response.text
|
69
83
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
84
|
+
decoded_bins = self.decode_response(output)
|
85
|
+
data: dict[str, list[dict[str, str]]] = {}
|
86
|
+
data["bins"] = list(
|
87
|
+
map(
|
88
|
+
lambda a: {
|
89
|
+
"type": a["binType"],
|
90
|
+
"collectionDate": a["collectionDay"].replace("-", "/"),
|
91
|
+
},
|
92
|
+
decoded_bins["collectionDay"],
|
79
93
|
)
|
80
|
-
|
81
|
-
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
82
|
-
|
83
|
-
# Find all collection items with the specific structure - try multiple class patterns
|
84
|
-
collection_items = soup.find_all("div", class_=lambda x: x and "ant-col" in x and "ant-col-xs-12" in x)
|
85
|
-
if not collection_items:
|
86
|
-
# Fallback to finding items by structure
|
87
|
-
collection_items = soup.find_all("div", class_=lambda x: x and "p-2" in x and "d-flex" in x and "flex-column" in x)
|
88
|
-
|
89
|
-
current_year = datetime.now().year
|
90
|
-
current_month = datetime.now().month
|
91
|
-
|
92
|
-
for item in collection_items:
|
93
|
-
# Extract bin type from h3 element
|
94
|
-
bin_type_elem = item.find("h3", class_="text-white")
|
95
|
-
# Extract date from div with specific classes
|
96
|
-
date_elem = item.find("div", class_="text-white fw-bold")
|
97
|
-
|
98
|
-
if bin_type_elem and date_elem:
|
99
|
-
bin_type = bin_type_elem.get_text().strip()
|
100
|
-
date_text = date_elem.get_text().strip()
|
101
|
-
|
102
|
-
try:
|
103
|
-
collection_date = datetime.strptime(date_text, "%A %d %B")
|
104
|
-
if (current_month > 10) and (collection_date.month < 3):
|
105
|
-
collection_date = collection_date.replace(year=(current_year + 1))
|
106
|
-
else:
|
107
|
-
collection_date = collection_date.replace(year=current_year)
|
108
|
-
|
109
|
-
dict_data = {
|
110
|
-
"type": bin_type,
|
111
|
-
"collectionDate": collection_date.strftime("%d/%m/%Y"),
|
112
|
-
}
|
113
|
-
data["bins"].append(dict_data)
|
114
|
-
except ValueError:
|
115
|
-
continue
|
94
|
+
)
|
116
95
|
|
117
96
|
except Exception as e:
|
118
97
|
# Here you can log the exception if needed
|
119
98
|
print(f"An error occurred: {e}")
|
120
99
|
# Optionally, re-raise the exception if you want it to propagate
|
121
100
|
raise
|
122
|
-
finally:
|
123
|
-
# This block ensures that the driver is closed regardless of an exception
|
124
|
-
if driver:
|
125
|
-
driver.quit()
|
126
101
|
return data
|