Skip to content

Commit 12d2390

Browse files
authored
Merge pull request #1553 from m26dvd/master
fix: Council Fix Pack - August 2025
2 parents a2541f1 + 2f0a889 commit 12d2390

10 files changed

+1227
-758
lines changed
Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import json
1+
import time
2+
23
import requests
3-
from datetime import datetime
4+
from dateutil.relativedelta import relativedelta
45

56
from uk_bin_collection.uk_bin_collection.common import *
67
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
78

89

10+
# import the wonderful Beautiful Soup and the URL grabber
911
class CouncilClass(AbstractGetBinDataClass):
1012
"""
1113
Concrete classes have to implement all abstract operations of the
@@ -14,28 +16,84 @@ class CouncilClass(AbstractGetBinDataClass):
1416
"""
1517

1618
def parse_data(self, page: str, **kwargs) -> dict:
17-
user_uprn = kwargs.get("uprn")
18-
check_uprn(user_uprn)
19+
# Make a BS4 object
20+
uprn = kwargs.get("uprn")
21+
# usrn = kwargs.get("paon")
22+
check_uprn(uprn)
23+
# check_usrn(usrn)
1924
bindata = {"bins": []}
20-
21-
# Make API request
22-
api_url = f"https://east-herts.co.uk/api/services/{user_uprn}"
23-
response = requests.get(api_url)
24-
response.raise_for_status()
25-
26-
data = response.json()
27-
today = datetime.now().date()
28-
29-
for service in data.get("services", []):
30-
collection_date_str = service.get("collectionDate")
31-
if collection_date_str:
32-
collection_date = datetime.strptime(collection_date_str, "%Y-%m-%d").date()
33-
# Only include future dates
34-
if collection_date >= today:
35-
dict_data = {
36-
"type": service.get("binType", ""),
37-
"collectionDate": collection_date.strftime("%d/%m/%Y"),
25+
26+
# uprn = uprn.zfill(12)
27+
28+
SESSION_URL = "https://eastherts-self.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Feastherts-self.achieveservice.com%252FAchieveForms%252F%253Fmode%253Dfill%2526consentMessage%253Dyes%2526form_uri%253Dsandbox-publish%253A%252F%252FAF-Process-98782935-6101-4962-9a55-5923e76057b6%252FAF-Stage-dcd0ec18-dfb4-496a-a266-bd8fadaa28a7%252Fdefinition.json%2526process%253D1%2526process_uri%253Dsandbox-processes%253A%252F%252FAF-Process-98782935-6101-4962-9a55-5923e76057b6%2526process_id%253DAF-Process-98782935-6101-4962-9a55-5923e76057b6&hostname=eastherts-self.achieveservice.com&withCredentials=true"
29+
30+
API_URL = "https://eastherts-self.achieveservice.com/apibroker/runLookup"
31+
32+
headers = {
33+
"Content-Type": "application/json",
34+
"Accept": "*/*",
35+
"User-Agent": "Mozilla/5.0",
36+
"X-Requested-With": "XMLHttpRequest",
37+
"Referer": "https://eastherts-self.achieveservice.com/fillform/?iframe_id=fillform-frame-1&db_id=",
38+
}
39+
s = requests.session()
40+
r = s.get(SESSION_URL)
41+
r.raise_for_status()
42+
session_data = r.json()
43+
sid = session_data["auth-session"]
44+
params = {
45+
# unix_timestamp
46+
"_": str(int(time.time() * 1000)),
47+
"sid": sid,
48+
}
49+
50+
params = {
51+
"id": "683d9ff0e299d",
52+
"repeat_against": "",
53+
"noRetry": "true",
54+
"getOnlyTokens": "undefined",
55+
"log_id": "",
56+
"app_name": "AF-Renderer::Self",
57+
# unix_timestamp
58+
"_": str(int(time.time() * 1000)),
59+
"sid": sid,
60+
}
61+
62+
data = {
63+
"formValues": {
64+
"Collection Days": {
65+
"inputUPRN": {
66+
"value": uprn,
3867
}
39-
bindata["bins"].append(dict_data)
40-
68+
},
69+
}
70+
}
71+
72+
r = s.post(API_URL, json=data, headers=headers, params=params)
73+
r.raise_for_status()
74+
75+
data = r.json()
76+
rows_data = data["integration"]["transformed"]["rows_data"]["0"]
77+
if not isinstance(rows_data, dict):
78+
raise ValueError("Invalid data returned from API")
79+
80+
# Extract each service's relevant details for the bin schedule
81+
for key, value in rows_data.items():
82+
if key.endswith("NextDate"):
83+
BinType = key.replace("NextDate", "ServiceName")
84+
for key2, value2 in rows_data.items():
85+
if key2 == BinType:
86+
BinType = value2
87+
next_collection = datetime.strptime(
88+
remove_ordinal_indicator_from_date_string(value), "%A %d %B"
89+
).replace(year=datetime.now().year)
90+
if datetime.now().month == 12 and next_collection.month == 1:
91+
next_collection = next_collection + relativedelta(years=1)
92+
93+
dict_data = {
94+
"type": BinType,
95+
"collectionDate": next_collection.strftime(date_format),
96+
}
97+
bindata["bins"].append(dict_data)
98+
4199
return bindata

uk_bin_collection/uk_bin_collection/councils/HinckleyandBosworthBoroughCouncil.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ def parse_data(self, page: str, **kwargs) -> dict:
2020
check_uprn(user_uprn)
2121
bindata = {"bins": []}
2222

23+
headers = {
24+
"Origin": "https://www.hinckley-bosworth.gov.uk",
25+
"Referer": "https://www.hinckley-bosworth.gov.uk",
26+
"User-Agent": "Mozilla/5.0",
27+
}
28+
2329
URI = f"https://www.hinckley-bosworth.gov.uk/set-location?id={user_uprn}&redirect=refuse&rememberloc="
2430

2531
# Make the GET request
26-
response = requests.get(URI)
32+
response = requests.get(URI, headers=headers)
2733

2834
# Parse the HTML
2935
soup = BeautifulSoup(response.content, "html.parser")

uk_bin_collection/uk_bin_collection/councils/IpswichBoroughCouncil.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ class CouncilClass(AbstractGetBinDataClass):
3131
IBC_ENDPOINT = "https://app.ipswich.gov.uk/bin-collection/"
3232

3333
def transform_date(self, date_str):
34-
date_str = re.sub(r"(st|nd|rd|th)", "", date_str) # Remove ordinal suffixes
34+
date_str = re.sub(
35+
r"(\d{1,2})(st|nd|rd|th)", r"\1", date_str
36+
) # Remove ordinal suffixes
3537
date_obj = datetime.strptime(date_str, "%A %d %B %Y")
3638
return date_obj.strftime(date_format)
3739

uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@ def parse_data(self, page: str, **kwargs) -> dict:
2424
def solve(s):
2525
return re.sub(r"(\d)(st|nd|rd|th)", r"\1", s)
2626

27+
headers = {
28+
"Origin": "https://www.lichfielddc.gov.uk",
29+
"Referer": "https://www.lichfielddc.gov.uk",
30+
"User-Agent": "Mozilla/5.0",
31+
}
32+
2733
URI = f"https://www.lichfielddc.gov.uk/homepage/6/bin-collection-dates?uprn={user_uprn}"
2834

2935
# Make the GET request
30-
response = requests.get(URI)
36+
response = requests.get(URI, headers=headers)
3137

3238
soup = BeautifulSoup(response.text, "html.parser")
3339

uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import pandas as pd
2+
import requests
23
from bs4 import BeautifulSoup
4+
35
from uk_bin_collection.uk_bin_collection.common import date_format
46
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
57

@@ -12,15 +14,26 @@ class CouncilClass(AbstractGetBinDataClass):
1214
"""
1315

1416
def parse_data(self, page: str, **kwargs) -> dict:
15-
# Make a BS4 object
16-
soup = BeautifulSoup(page.text, features="html.parser")
17+
user_url = kwargs.get("url")
18+
19+
headers = {
20+
"Origin": "https://www.nelincs.gov.uk",
21+
"Referer": "https://www.nelincs.gov.uk",
22+
"User-Agent": "Mozilla/5.0",
23+
}
24+
25+
# Make the GET request
26+
response = requests.get(user_url, headers=headers)
27+
28+
# Parse the HTML
29+
soup = BeautifulSoup(response.content, "html.parser")
1730
soup.prettify()
1831

1932
data = {"bins": []}
2033

2134
# Get list items that can be seen on page
2235
for element in soup.find_all(
23-
"li", {"class": "list-group-item p-0 p-3 bin-collection-item"}
36+
"li", {"class": "border-0 list-group-item p-3 bg-light rounded p-2"}
2437
):
2538
element_text = element.text.strip().split("\n\n")
2639
element_text = [x.strip() for x in element_text]
@@ -35,9 +48,7 @@ def parse_data(self, page: str, **kwargs) -> dict:
3548
data["bins"].append(dict_data)
3649

3750
# Get hidden list items too
38-
for element in soup.find_all(
39-
"li", {"class": "list-group-item p-0 p-3 bin-collection-item d-none"}
40-
):
51+
for element in soup.find_all("li", {"class": "border-0 list-group-item p-3"}):
4152
element_text = element.text.strip().split("\n\n")
4253
element_text = [x.strip() for x in element_text]
4354

uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
1+
import re
2+
import urllib.parse
3+
4+
import requests
15
from bs4 import BeautifulSoup
6+
27
from uk_bin_collection.uk_bin_collection.common import *
38
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
49

5-
from bs4 import BeautifulSoup
6-
import urllib.parse
7-
import requests
8-
import re
9-
1010

1111
class CouncilClass(AbstractGetBinDataClass):
1212
def parse_data(self, page: str, **kwargs) -> dict:
1313

1414
data = {"bins": []}
1515

16+
headers = {
17+
"Origin": "https://www.nuneatonandbedworth.gov.uk/",
18+
"Referer": "https://www.nuneatonandbedworth.gov.uk/",
19+
"User-Agent": "Mozilla/5.0",
20+
}
21+
1622
street = urllib.parse.quote_plus(kwargs.get("paon"))
1723
base_url = "https://www.nuneatonandbedworth.gov.uk/"
1824
search_query = f"directory/search?directoryID=3&showInMap=&keywords={street}&search=Search+directory"
1925

20-
search_response = requests.get(base_url + search_query)
26+
search_response = requests.get(base_url + search_query, headers=headers)
2127

2228
if search_response.status_code == 200:
2329
soup = BeautifulSoup(search_response.content, "html.parser")
@@ -56,7 +62,13 @@ def parse_data(self, page: str, **kwargs) -> dict:
5662

5763
def get_bin_data(self, url) -> dict:
5864

59-
bin_day_response = requests.get(url)
65+
headers = {
66+
"Origin": "https://www.nuneatonandbedworth.gov.uk/",
67+
"Referer": "https://www.nuneatonandbedworth.gov.uk/",
68+
"User-Agent": "Mozilla/5.0",
69+
}
70+
71+
bin_day_response = requests.get(url, headers=headers)
6072

6173
if bin_day_response.status_code == 200:
6274

uk_bin_collection/uk_bin_collection/councils/RunnymedeBoroughCouncil.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@ def parse_data(self, page: str, **kwargs) -> dict:
2121
check_uprn(user_uprn)
2222
bindata = {"bins": []}
2323

24+
headers = {
25+
"Origin": "https://www.runnymede.gov.uk",
26+
"Referer": "https://www.runnymede.gov.uk",
27+
"User-Agent": "Mozilla/5.0",
28+
}
29+
2430
URI = f"https://www.runnymede.gov.uk/homepage/150/check-your-bin-collection-day?address={user_uprn}"
2531

2632
# Make the GET request
27-
response = requests.get(URI)
33+
response = requests.get(URI, headers=headers)
2834

2935
soup = BeautifulSoup(response.text, "html.parser")
3036

uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ def parse_data(self, page: str, **kwargs) -> dict:
7777
)
7878
submit.click()
7979

80+
WebDriverWait(driver, 10).until(
81+
EC.presence_of_element_located((By.CLASS_NAME, "bin-collection__month"))
82+
)
83+
8084
soup = BeautifulSoup(driver.page_source, features="html.parser")
8185

8286
# Quit Selenium webdriver to release session

uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import re
2+
13
from bs4 import BeautifulSoup
24

35
from uk_bin_collection.uk_bin_collection.common import *
@@ -91,40 +93,56 @@ def parse_data(self, page: str, **kwargs) -> dict:
9193

9294
soup = BeautifulSoup(response.text, features="html.parser")
9395
soup.prettify()
94-
96+
# print(soup)
9597
# Find all the bits of the current calendar that contain an event
96-
events = soup.find_all("div", {"class": "rc-event-container"})
98+
resultscontainer = soup.find_all("div", {"class": "results-container"})
9799

98-
for event in events:
99-
# Get the date and type of each bin collection
100-
bin_date = datetime.strptime(
101-
event.find_next("a").attrs.get("data-original-datetext"),
102-
"%A %d %B, %Y",
100+
for result in resultscontainer:
101+
rows = result.find_all(
102+
"div", {"class": "col-12 col-sm-6 col-md-4 col-lg-4 mb-4"}
103103
)
104-
bin_type = event.find_next("a").attrs.get("data-original-title")
105-
# Only process it if it's today or in the future
106-
if bin_date.date() >= datetime.now().date():
107-
# Split the really long type up into two separate bins
108-
if (
109-
bin_type
110-
== "Mixed dry recycling (blue lidded bin) and glass (black box or basket)"
111-
):
112-
collections.append(
113-
(
114-
"Mixed dry recycling (blue lidded bin)",
115-
datetime.strftime(bin_date, date_format),
104+
for row in rows:
105+
cardcollectionday = row.find(
106+
"span", {"class": "card-collection-day"}
107+
)
108+
cardcollectiondate = row.find(
109+
"span", {"class": "card-collection-date"}
110+
)
111+
cardcollectionmonth = row.find(
112+
"span", {"class": "card-collection-month"}
113+
)
114+
bin_type = row.find(
115+
"li", {"class": re.compile(r"collection-type-...$")}
116+
).text
117+
118+
collection_date = f"{cardcollectionday.text}{cardcollectiondate.text}{cardcollectionmonth.text}"
119+
bin_date = datetime.strptime(
120+
collection_date,
121+
"%A %d %B %Y",
122+
)
123+
124+
if bin_date.date() >= datetime.now().date():
125+
# Split the really long type up into two separate bins
126+
if (
127+
bin_type
128+
== "Mixed dry recycling (blue lidded bin) and glass (black box or basket)"
129+
):
130+
collections.append(
131+
(
132+
"Mixed dry recycling (blue lidded bin)",
133+
datetime.strftime(bin_date, date_format),
134+
)
135+
)
136+
collections.append(
137+
(
138+
"Glass (black box or basket)",
139+
datetime.strftime(bin_date, date_format),
140+
)
116141
)
117-
)
118-
collections.append(
119-
(
120-
"Glass (black box or basket)",
121-
datetime.strftime(bin_date, date_format),
142+
else:
143+
collections.append(
144+
(bin_type, datetime.strftime(bin_date, date_format))
122145
)
123-
)
124-
else:
125-
collections.append(
126-
(bin_type, datetime.strftime(bin_date, date_format))
127-
)
128146

129147
data = {"bins": []}
130148

0 commit comments

Comments
 (0)