From 1745ca3abeb9c9a4157b44facc6e2357a851799b Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Tue, 12 Nov 2024 17:32:39 +0000
Subject: [PATCH 1/9] fix: Adding Garden collection to Babergh and MidSuffolk
Council
fix: #995
---
uk_bin_collection/tests/input.json | 6 +-
.../councils/BaberghDistrictCouncil.py | 62 +++++++++++++++++++
.../councils/MidSuffolkDistrictCouncil.py | 62 +++++++++++++++++++
3 files changed, 128 insertions(+), 2 deletions(-)
diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json
index cb09cb81fa..d349352c3f 100644
--- a/uk_bin_collection/tests/input.json
+++ b/uk_bin_collection/tests/input.json
@@ -81,9 +81,10 @@
"BaberghDistrictCouncil": {
"skip_get_url": true,
"house_number": "Monday",
+ "postcode": "Week 1",
"url": "https://www.babergh.gov.uk",
"wiki_name": "Babergh District Council",
- "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday"
+ "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]"
},
"BCPCouncil": {
"skip_get_url": true,
@@ -1005,9 +1006,10 @@
"MidSuffolkDistrictCouncil": {
"skip_get_url": true,
"house_number": "Monday",
+ "postcode": "Week 2",
"url": "https://www.midsuffolk.gov.uk",
"wiki_name": "Mid Suffolk District Council",
- "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday"
+ "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]"
},
"MidSussexDistrictCouncil": {
"house_number": "OAKLANDS, OAKLANDS ROAD RH16 1SS",
diff --git a/uk_bin_collection/uk_bin_collection/councils/BaberghDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BaberghDistrictCouncil.py
index 562cb5d8fb..cc178d9bfb 100644
--- a/uk_bin_collection/uk_bin_collection/councils/BaberghDistrictCouncil.py
+++ b/uk_bin_collection/uk_bin_collection/councils/BaberghDistrictCouncil.py
@@ -23,6 +23,7 @@ class CouncilClass(AbstractGetBinDataClass):
def parse_data(self, page: str, **kwargs) -> dict:
collection_day = kwargs.get("paon")
+ garden_collection_week = kwargs.get("postcode")
bindata = {"bins": []}
days_of_week = [
@@ -35,10 +36,14 @@ def parse_data(self, page: str, **kwargs) -> dict:
"Sunday",
]
+ garden_week = ["Week 1", "Week 2"]
+
refusestartDate = datetime(2024, 11, 4)
recyclingstartDate = datetime(2024, 11, 11)
offset_days = days_of_week.index(collection_day)
+ if garden_collection_week:
+ garden_collection = garden_week.index(garden_collection_week)
refuse_dates = get_dates_every_x_days(refusestartDate, 14, 28)
recycling_dates = get_dates_every_x_days(recyclingstartDate, 14, 28)
@@ -125,6 +130,63 @@ def parse_data(self, page: str, **kwargs) -> dict:
}
bindata["bins"].append(dict_data)
+ if garden_collection_week:
+ if garden_collection == 0:
+ gardenstartDate = datetime(2024, 11, 11)
+ elif garden_collection == 1:
+ gardenstartDate = datetime(2024, 11, 4)
+
+ garden_dates = get_dates_every_x_days(gardenstartDate, 14, 28)
+
+ garden_bank_holidays = [
+ ("23/12/2024", 1),
+ ("24/12/2024", 1),
+ ("25/12/2024", 1),
+ ("26/12/2024", 1),
+ ("27/12/2024", 1),
+ ("30/12/2024", 1),
+ ("31/12/2024", 1),
+ ("01/01/2025", 1),
+ ("02/01/2025", 1),
+ ("03/01/2025", 1),
+ ]
+
+ for gardenDate in garden_dates:
+
+ collection_date = (
+ datetime.strptime(gardenDate, "%d/%m/%Y")
+ + timedelta(days=offset_days)
+ ).strftime("%d/%m/%Y")
+
+ garden_holiday = next(
+ (
+ value
+ for date, value in garden_bank_holidays
+ if date == collection_date
+ ),
+ 0,
+ )
+
+ if garden_holiday > 0:
+ continue
+
+ holiday_offset = next(
+ (value for date, value in bank_holidays if date == collection_date),
+ 0,
+ )
+
+ if holiday_offset > 0:
+ collection_date = (
+ datetime.strptime(collection_date, "%d/%m/%Y")
+ + timedelta(days=holiday_offset)
+ ).strftime("%d/%m/%Y")
+
+ dict_data = {
+ "type": "Garden Bin",
+ "collectionDate": collection_date,
+ }
+ bindata["bins"].append(dict_data)
+
bindata["bins"].sort(
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
)
diff --git a/uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py
index 788d5dd311..a9e7d1458e 100644
--- a/uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py
+++ b/uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py
@@ -23,6 +23,7 @@ class CouncilClass(AbstractGetBinDataClass):
def parse_data(self, page: str, **kwargs) -> dict:
collection_day = kwargs.get("paon")
+ garden_collection_week = kwargs.get("postcode")
bindata = {"bins": []}
days_of_week = [
@@ -35,10 +36,14 @@ def parse_data(self, page: str, **kwargs) -> dict:
"Sunday",
]
+ garden_week = ["Week 1", "Week 2"]
+
refusestartDate = datetime(2024, 11, 11)
recyclingstartDate = datetime(2024, 11, 4)
offset_days = days_of_week.index(collection_day)
+ if garden_collection_week:
+ garden_collection = garden_week.index(garden_collection_week)
refuse_dates = get_dates_every_x_days(refusestartDate, 14, 28)
recycling_dates = get_dates_every_x_days(recyclingstartDate, 14, 28)
@@ -125,6 +130,63 @@ def parse_data(self, page: str, **kwargs) -> dict:
}
bindata["bins"].append(dict_data)
+ if garden_collection_week:
+ if garden_collection == 0:
+ gardenstartDate = datetime(2024, 11, 11)
+ elif garden_collection == 1:
+ gardenstartDate = datetime(2024, 11, 4)
+
+ garden_dates = get_dates_every_x_days(gardenstartDate, 14, 28)
+
+ garden_bank_holidays = [
+ ("23/12/2024", 1),
+ ("24/12/2024", 1),
+ ("25/12/2024", 1),
+ ("26/12/2024", 1),
+ ("27/12/2024", 1),
+ ("30/12/2024", 1),
+ ("31/12/2024", 1),
+ ("01/01/2025", 1),
+ ("02/01/2025", 1),
+ ("03/01/2025", 1),
+ ]
+
+ for gardenDate in garden_dates:
+
+ collection_date = (
+ datetime.strptime(gardenDate, "%d/%m/%Y")
+ + timedelta(days=offset_days)
+ ).strftime("%d/%m/%Y")
+
+ garden_holiday = next(
+ (
+ value
+ for date, value in garden_bank_holidays
+ if date == collection_date
+ ),
+ 0,
+ )
+
+ if garden_holiday > 0:
+ continue
+
+ holiday_offset = next(
+ (value for date, value in bank_holidays if date == collection_date),
+ 0,
+ )
+
+ if holiday_offset > 0:
+ collection_date = (
+ datetime.strptime(collection_date, "%d/%m/%Y")
+ + timedelta(days=holiday_offset)
+ ).strftime("%d/%m/%Y")
+
+ dict_data = {
+ "type": "Garden Bin",
+ "collectionDate": collection_date,
+ }
+ bindata["bins"].append(dict_data)
+
bindata["bins"].sort(
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
)
From f38bbf91254598a979bf032ff2adb9b54cb6d701 Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Thu, 14 Nov 2024 22:31:50 +0000
Subject: [PATCH 2/9] fix: CrawleyBoroughCouncil
fix: #1005
---
.../councils/CrawleyBoroughCouncil.py | 131 ++++++++++++------
1 file changed, 91 insertions(+), 40 deletions(-)
diff --git a/uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py
index 498f45db41..4d5eceed4c 100644
--- a/uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py
+++ b/uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py
@@ -1,4 +1,6 @@
-from bs4 import BeautifulSoup
+import time
+
+import requests
from dateutil.relativedelta import relativedelta
from uk_bin_collection.uk_bin_collection.common import *
@@ -19,43 +21,92 @@ def parse_data(self, page: str, **kwargs) -> dict:
usrn = kwargs.get("paon")
check_uprn(uprn)
check_usrn(usrn)
+ bindata = {"bins": []}
+
+ SESSION_URL = "https://crawleybc-self.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Fcrawleybc-self.achieveservice.com%252Fen%252FAchieveForms%252F%253Fform_uri%253Dsandbox-publish%253A%252F%252FAF-Process-fb73f73e-e8f5-4441-9f83-8b5d04d889d6%252FAF-Stage-ec9ada91-d2d9-43bc-9730-597d15fc8108%252Fdefinition.json%2526redirectlink%253D%252Fen%2526cancelRedirectLink%253D%252Fen%2526noLoginPrompt%253D1%2526accept%253Dyes&hostname=crawleybc-self.achieveservice.com&withCredentials=true"
+
+ API_URL = "https://crawleybc-self.achieveservice.com/apibroker/"
+
+ currentdate = datetime.now().strftime("%d/%m/%Y")
+
+ data = {
+ "formValues": {
+ "Address": {
+ "address": {
+ "value": {
+ "Address": {
+ "usrn": {
+ "value": usrn,
+ },
+ "uprn": {
+ "value": uprn,
+ },
+ }
+ },
+ },
+ "dayConverted": {
+ "value": currentdate,
+ },
+ "getCollection": {
+ "value": "true",
+ },
+ "getWorksheets": {
+ "value": "false",
+ },
+ },
+ },
+ }
+
+ headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ "User-Agent": "Mozilla/5.0",
+ "X-Requested-With": "XMLHttpRequest",
+ "Referer": "https://crawleybc-self.achieveservice.com/fillform/?iframe_id=fillform-frame-1&db_id=",
+ }
+ s = requests.session()
+ r = s.get(SESSION_URL)
+ r.raise_for_status()
+ session_data = r.json()
+ sid = session_data["auth-session"]
+ params = {
+ "api": "RunLookup",
+ "id": "5b4f0ec5f13f4",
+ "repeat_against": "",
+ "noRetry": "true",
+ "getOnlyTokens": "undefined",
+ "log_id": "",
+ "app_name": "AF-Renderer::Self",
+ # unix_timestamp
+ "_": str(int(time.time() * 1000)),
+ "sid": sid,
+ }
+
+ r = s.post(API_URL, json=data, headers=headers, params=params)
+ r.raise_for_status()
+
+ data = r.json()
+ rows_data = data["integration"]["transformed"]["rows_data"]["0"]
+ if not isinstance(rows_data, dict):
+ raise ValueError("Invalid data returned from API")
+
+ # Extract each service's relevant details for the bin schedule
+ for key, value in rows_data.items():
+ if key.endswith("DateNext"):
+ BinType = key.replace("DateNext", "Service")
+ for key2, value2 in rows_data.items():
+ if key2 == BinType:
+ BinType = value2
+ next_collection = datetime.strptime(value, "%A %d %B").replace(
+ year=datetime.now().year
+ )
+ if datetime.now().month == 12 and next_collection.month == 1:
+ next_collection = next_collection + relativedelta(years=1)
+
+ dict_data = {
+ "type": BinType,
+ "collectionDate": next_collection.strftime(date_format),
+ }
+ bindata["bins"].append(dict_data)
- day = datetime.now().date().strftime("%d")
- month = datetime.now().date().strftime("%m")
- year = datetime.now().date().strftime("%Y")
-
- api_url = (
- f"https://my.crawley.gov.uk/appshost/firmstep/self/apps/custompage/waste?language=en&uprn={uprn}"
- f"&usrn={usrn}&day={day}&month={month}&year={year}"
- )
- response = requests.get(api_url)
-
- soup = BeautifulSoup(response.text, features="html.parser")
- soup.prettify()
-
- data = {"bins": []}
-
- titles = [title.text for title in soup.select(".block-title")]
- collection_tag = soup.body.find_all(
- "div", {"class": "col-md-6 col-sm-6 col-xs-6"}, string="Next collection"
- )
- bin_index = 0
- for tag in collection_tag:
- for item in tag.next_elements:
- if (
- str(item).startswith('
')
- and str(item) != ""
- ):
- collection_date = datetime.strptime(item.text, "%A %d %B")
- next_collection = collection_date.replace(year=datetime.now().year)
- if datetime.now().month == 12 and next_collection.month == 1:
- next_collection = next_collection + relativedelta(years=1)
-
- dict_data = {
- "type": titles[bin_index].strip(),
- "collectionDate": next_collection.strftime(date_format),
- }
- data["bins"].append(dict_data)
- bin_index += 1
- break
- return data
+ return bindata
From bb78d6dd84685d07031e6f1e11756c48fba76ac1 Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Thu, 14 Nov 2024 23:00:39 +0000
Subject: [PATCH 3/9] feat: Adding Rother District Council
fix: #1009
---
uk_bin_collection/tests/input.json | 6 ++
.../councils/RotherDistrictCouncil.py | 84 +++++++++++++++++++
wiki/Councils.md | 22 ++++-
3 files changed, 108 insertions(+), 4 deletions(-)
create mode 100644 uk_bin_collection/uk_bin_collection/councils/RotherDistrictCouncil.py
diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json
index d349352c3f..ba127b7d9a 100644
--- a/uk_bin_collection/tests/input.json
+++ b/uk_bin_collection/tests/input.json
@@ -1305,6 +1305,12 @@
"wiki_name": "Rochford Council",
"wiki_note": "No extra parameters are required. Dates presented should be read as 'week commencing'."
},
+ "RotherDistrictCouncil": {
+ "uprn": "100061937338",
+ "url": "https://www.rother.gov.uk",
+ "wiki_name": "Rother District Council",
+ "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
+ },
"RotherhamCouncil": {
"url": "https://www.rotherham.gov.uk/bin-collections?address=100050866000&submit=Submit",
"uprn": "100050866000",
diff --git a/uk_bin_collection/uk_bin_collection/councils/RotherDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RotherDistrictCouncil.py
new file mode 100644
index 0000000000..6ece3b315e
--- /dev/null
+++ b/uk_bin_collection/uk_bin_collection/councils/RotherDistrictCouncil.py
@@ -0,0 +1,84 @@
+from datetime import datetime
+
+import requests
+from bs4 import BeautifulSoup
+from dateutil.relativedelta import relativedelta
+
+from uk_bin_collection.uk_bin_collection.common import *
+from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
+
+
+class CouncilClass(AbstractGetBinDataClass):
+ """
+ Concrete classes have to implement all abstract operations of the
+ base class. They can also override some operations with a default
+ implementation.
+ """
+
+ def parse_data(self, page: str, **kwargs) -> dict:
+ # Get and check UPRN
+ user_uprn = kwargs.get("uprn")
+ check_uprn(user_uprn)
+ bindata = {"bins": []}
+
+ uri = "https://www.rother.gov.uk/wp-admin/admin-ajax.php"
+ params = {
+ "action": "get_address_data",
+ "uprn": "100061937338",
+ "context": "full-page",
+ }
+
+ headers = {
+ "Content-Type": "application/x-www-form-urlencoded",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
+ }
+
+ # Send a POST request with form data and headers
+ r = requests.post(uri, data=params, headers=headers, verify=False)
+
+ result = r.json()
+
+ if result["success"]:
+ # Parse the HTML with BeautifulSoup
+ soup = BeautifulSoup(result["data"], "html.parser")
+ soup.prettify()
+
+ # print(soup)
+
+ # Find the div elements with class "bindays-item"
+ bin_days = soup.find_all("div", class_="bindays-item")
+
+ # Loop through each bin item and extract type and date
+ for bin_day in bin_days:
+ # Extract bin type from the
tag
+ bin_type = bin_day.find("h3").get_text(strip=True).replace(":", "")
+
+ # Extract date (or check if it's a subscription link for Garden Waste)
+ date_span = bin_day.find("span", class_="find-my-nearest-bindays-date")
+ if date_span:
+ if date_span.find("a"):
+ # If there is a link, this is the Garden bin signup link
+ continue
+ else:
+ # Otherwise, get the date text directly
+ date = date_span.get_text(strip=True)
+ else:
+ date = None
+
+ date = datetime.strptime(
+ remove_ordinal_indicator_from_date_string(date),
+ "%A %d %B",
+ ).replace(year=datetime.now().year)
+ if datetime.now().month == 12 and date.month == 1:
+ date = date + relativedelta(years=1)
+
+ dict_data = {
+ "type": bin_type,
+ "collectionDate": date.strftime(date_format),
+ }
+ bindata["bins"].append(dict_data)
+
+ bindata["bins"].sort(
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
+ )
+ return bindata
diff --git a/wiki/Councils.md b/wiki/Councils.md
index 9508f147fe..8bbcce931a 100644
--- a/wiki/Councils.md
+++ b/wiki/Councils.md
@@ -176,6 +176,7 @@ This document is still a work in progress, don't worry if your council isn't lis
- [Rhondda Cynon Taff Council](#rhondda-cynon-taff-council)
- [Rochdale Council](#rochdale-council)
- [Rochford Council](#rochford-council)
+- [Rother District Council](#rother-district-council)
- [Rotherham Council](#rotherham-council)
- [Rugby Borough Council](#rugby-borough-council)
- [Rushcliffe Borough Council](#rushcliffe-borough-council)
@@ -372,13 +373,14 @@ Note: To get the UPRN, please use [FindMyAddress](https://www.findmyaddress.co.u
### Babergh District Council
```commandline
-python collect_data.py BaberghDistrictCouncil https://www.babergh.gov.uk -s -n XX
+python collect_data.py BaberghDistrictCouncil https://www.babergh.gov.uk -s -p "XXXX XXX" -n XX
```
Additional parameters:
- `-s` - skip get URL
+- `-p` - postcode
- `-n` - house number
-Note: Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday
+Note: Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]
---
@@ -1798,13 +1800,14 @@ Note: Pass the house name/number wrapped in double quotes along with the postcod
### Mid Suffolk District Council
```commandline
-python collect_data.py MidSuffolkDistrictCouncil https://www.midsuffolk.gov.uk -s -n XX
+python collect_data.py MidSuffolkDistrictCouncil https://www.midsuffolk.gov.uk -s -p "XXXX XXX" -n XX
```
Additional parameters:
- `-s` - skip get URL
+- `-p` - postcode
- `-n` - house number
-Note: Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday
+Note: Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]
---
@@ -2262,6 +2265,17 @@ Note: No extra parameters are required. Dates presented should be read as 'week
---
+### Rother District Council
+```commandline
+python collect_data.py RotherDistrictCouncil https://www.rother.gov.uk -u XXXXXXXX
+```
+Additional parameters:
+- `-u` - UPRN
+
+Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.
+
+---
+
### Rotherham Council
```commandline
python collect_data.py RotherhamCouncil https://www.rotherham.gov.uk/bin-collections?address=XXXXXXXXX&submit=Submit -u XXXXXXXX
From c80b13ec37411a95941cb4df183c5ac04aad446a Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Thu, 14 Nov 2024 23:12:50 +0000
Subject: [PATCH 4/9] fix: Rother District Council
---
.../uk_bin_collection/councils/RotherDistrictCouncil.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/uk_bin_collection/uk_bin_collection/councils/RotherDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RotherDistrictCouncil.py
index 6ece3b315e..60f3fa3d91 100644
--- a/uk_bin_collection/uk_bin_collection/councils/RotherDistrictCouncil.py
+++ b/uk_bin_collection/uk_bin_collection/councils/RotherDistrictCouncil.py
@@ -24,7 +24,7 @@ def parse_data(self, page: str, **kwargs) -> dict:
uri = "https://www.rother.gov.uk/wp-admin/admin-ajax.php"
params = {
"action": "get_address_data",
- "uprn": "100061937338",
+ "uprn": user_uprn,
"context": "full-page",
}
From 02adfba1eb2b674f8fd723b3637158464d2007e0 Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Thu, 14 Nov 2024 23:42:43 +0000
Subject: [PATCH 5/9] feat: Adding South Hams District Council
fix: #1008
---
uk_bin_collection/tests/input.json | 6 ++
.../councils/SouthHamsDistrictCouncil.py | 90 +++++++++++++++++++
wiki/Councils.md | 24 +++++
3 files changed, 120 insertions(+)
create mode 100644 uk_bin_collection/uk_bin_collection/councils/SouthHamsDistrictCouncil.py
diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json
index 383e62d43e..4cf8629deb 100644
--- a/uk_bin_collection/tests/input.json
+++ b/uk_bin_collection/tests/input.json
@@ -1420,6 +1420,12 @@
"wiki_name": "South Gloucestershire Council",
"wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)."
},
+ "SouthHamsDistrictCouncil": {
+ "uprn": "10004742851",
+ "url": "https://www.southhams.gov.uk",
+ "wiki_name": "South Hams District Council",
+ "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
+ },
"SouthKestevenDistrictCouncil": {
"house_number": "2 Althorpe Close, Market Deeping, PE6 8BL",
"postcode": "PE68BL",
diff --git a/uk_bin_collection/uk_bin_collection/councils/SouthHamsDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SouthHamsDistrictCouncil.py
new file mode 100644
index 0000000000..0e933c4463
--- /dev/null
+++ b/uk_bin_collection/uk_bin_collection/councils/SouthHamsDistrictCouncil.py
@@ -0,0 +1,90 @@
+from datetime import datetime
+
+import requests
+from bs4 import BeautifulSoup
+from dateutil.relativedelta import relativedelta
+
+from uk_bin_collection.uk_bin_collection.common import *
+from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
+
+
+class CouncilClass(AbstractGetBinDataClass):
+ """
+ Concrete classes have to implement all abstract operations of the
+ base class. They can also override some operations with a default
+ implementation.
+ """
+
+ def parse_data(self, page: str, **kwargs) -> dict:
+ # Get and check UPRN
+ user_uprn = kwargs.get("uprn")
+ check_uprn(user_uprn)
+ bindata = {"bins": []}
+
+ uri = "https://waste.southhams.gov.uk/mycollections"
+
+ s = requests.session()
+ r = s.get(uri)
+ for cookie in r.cookies:
+ if cookie.name == "fcc_session_cookie":
+ fcc_session_token = cookie.value
+
+ uri = "https://waste.southhams.gov.uk/mycollections/getcollectiondetails"
+
+ params = {
+ "fcc_session_token": fcc_session_token,
+ "uprn": user_uprn,
+ }
+
+ headers = {
+ "Content-Type": "application/x-www-form-urlencoded",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
+ "Referer": "https://waste.southhams.gov.uk/mycollections",
+ "X-Requested-With": "XMLHttpRequest",
+ }
+
+ # Send a POST request with form data and headers
+ r = s.post(uri, data=params, headers=headers)
+
+ result = r.json()
+
+ for collection in result["binCollections"]["tile"]:
+
+ # Parse the HTML with BeautifulSoup
+ soup = BeautifulSoup(collection[0], "html.parser")
+ soup.prettify()
+
+ # Find all collectionDiv elements
+ collections = soup.find_all("div", class_="collectionDiv")
+
+ # Process each collectionDiv
+ for collection in collections:
+ # Extract the service name
+ service_name = collection.find("h3").text.strip()
+
+ # Extract collection frequency and day
+ details = collection.find("div", class_="detWrap").text.strip()
+
+ # Extract the next collection date
+ next_collection = details.split("Your next scheduled collection is ")[
+ 1
+ ].split(".")[0]
+
+ if next_collection.startswith("today"):
+ next_collection = next_collection.split("today, ")[1]
+ elif next_collection.startswith("tomorrow"):
+ next_collection = next_collection.split("tomorrow, ")[1]
+
+ dict_data = {
+ "type": service_name,
+ "collectionDate": datetime.strptime(
+ next_collection, "%A, %d %B %Y"
+ ).strftime(date_format),
+ }
+ bindata["bins"].append(dict_data)
+
+ bindata["bins"].sort(
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
+ )
+
+ return bindata
diff --git a/wiki/Councils.md b/wiki/Councils.md
index 8bbcce931a..34a6acdee2 100644
--- a/wiki/Councils.md
+++ b/wiki/Councils.md
@@ -192,11 +192,13 @@ This document is still a work in progress, don't worry if your council isn't lis
- [South Cambridgeshire Council](#south-cambridgeshire-council)
- [South Derbyshire District Council](#south-derbyshire-district-council)
- [South Gloucestershire Council](#south-gloucestershire-council)
+- [South Hams District Council](#south-hams-district-council)
- [South Kesteven District Council](#south-kesteven-district-council)
- [South Lanarkshire Council](#south-lanarkshire-council)
- [South Norfolk Council](#south-norfolk-council)
- [South Oxfordshire Council](#south-oxfordshire-council)
- [South Ribble Council](#south-ribble-council)
+- [South Staffordshire District Council](#south-staffordshire-district-council)
- [South Tyneside Council](#south-tyneside-council)
- [Southwark Council](#southwark-council)
- [St Albans City and District Council](#st-albans-city-and-district-council)
@@ -2450,6 +2452,17 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm
---
+### South Hams District Council
+```commandline
+python collect_data.py SouthHamsDistrictCouncil https://www.southhams.gov.uk -u XXXXXXXX
+```
+Additional parameters:
+- `-u` - UPRN
+
+Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.
+
+---
+
### South Kesteven District Council
```commandline
python collect_data.py SouthKestevenDistrictCouncil https://pre.southkesteven.gov.uk/BinSearch.aspx -s -p "XXXX XXX" -n XX -w http://HOST:PORT/
@@ -2508,6 +2521,17 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc
---
+### South Staffordshire District Council
+```commandline
+python collect_data.py SouthStaffordshireDistrictCouncil https://www.sstaffs.gov.uk/where-i-live?uprn=200004523954 -u XXXXXXXX
+```
+Additional parameters:
+- `-u` - UPRN
+
+Note: The URL needs to be `https://www.sstaffs.gov.uk/where-i-live?uprn=`. Replace `` with your UPRN.
+
+---
+
### South Tyneside Council
```commandline
python collect_data.py SouthTynesideCouncil https://www.southtyneside.gov.uk/article/33352/Bin-collection-dates -s -p "XXXX XXX" -n XX
From 1430fd8c0cc94e6eb090a4b440d7842261d0d5ca Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Thu, 14 Nov 2024 23:55:43 +0000
Subject: [PATCH 6/9] feat: Adding Copeland Borough Council
fix: #1006
---
uk_bin_collection/tests/input.json | 6 ++
.../councils/CopelandBoroughCouncil.py | 93 +++++++++++++++++++
wiki/Councils.md | 12 +++
3 files changed, 111 insertions(+)
create mode 100644 uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py
diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json
index 4cf8629deb..48fbf06839 100644
--- a/uk_bin_collection/tests/input.json
+++ b/uk_bin_collection/tests/input.json
@@ -398,6 +398,12 @@
"wiki_name": "Conwy County Borough Council",
"wiki_note": "Conwy County Borough Council uses a straight UPRN in the URL, e.g., `&uprn=XXXXXXXXXXXXX`."
},
+ "CopelandBoroughCouncil": {
+ "uprn": "100110734613",
+ "url": "https://www.copeland.gov.uk",
+ "wiki_name": "Copeland Borough Council",
+ "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
+ },
"CornwallCouncil": {
"skip_get_url": true,
"uprn": "100040128734",
diff --git a/uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py
new file mode 100644
index 0000000000..521a73948f
--- /dev/null
+++ b/uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py
@@ -0,0 +1,93 @@
+from xml.etree import ElementTree
+
+from bs4 import BeautifulSoup
+
+from uk_bin_collection.uk_bin_collection.common import *
+from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
+
+
+class CouncilClass(AbstractGetBinDataClass):
+ """
+ Concrete classes have to implement all abstract operations of the
+ baseclass. They can also override some
+ operations with a default implementation.
+ """
+
+ def parse_data(self, page: str, **kwargs) -> dict:
+ uprn = kwargs.get("uprn")
+ check_uprn(uprn)
+ council = "CPL"
+
+ # Make SOAP request
+ headers = {
+ "Content-Type": "text/xml; charset=UTF-8",
+ "Referer": "https://collections-copeland.azurewebsites.net/calendar.html",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
+ }
+ requests.packages.urllib3.disable_warnings()
+ post_data = (
+ ''
+ ''
+ ''
+ "" + council + "" + uprn + ""
+ "Chtml"
+ )
+ response = requests.post(
+ "https://collections-copeland.azurewebsites.net/WSCollExternal.asmx",
+ headers=headers,
+ data=post_data,
+ )
+
+ if response.status_code != 200:
+ raise ValueError("No bin data found for provided UPRN.")
+
+ # Get HTML from SOAP response
+ xmltree = ElementTree.fromstring(response.text)
+ html = xmltree.find(
+ ".//{http://webaspx-collections.azurewebsites.net/}getRoundCalendarForUPRNResult"
+ ).text
+ # Parse with BS4
+ soup = BeautifulSoup(html, features="html.parser")
+ soup.prettify()
+
+ data = {"bins": []}
+ for bin_type in ["Refuse", "Recycling", "Garden"]:
+ bin_el = soup.find("b", string=bin_type)
+ if bin_el:
+ bin_info = bin_el.next_sibling.split(": ")[1]
+ collection_date = ""
+ results = re.search("([A-Za-z]+ \\d\\d? [A-Za-z]+) then", bin_info)
+ if results:
+ if results[1] == "Today":
+ date = datetime.now()
+ elif results[1] == "Tomorrow":
+ date = datetime.now() + timedelta(days=1)
+ else:
+ date = get_next_occurrence_from_day_month(
+ datetime.strptime(
+ results[1] + " " + datetime.now().strftime("%Y"),
+ "%a %d %b %Y",
+ )
+ )
+ if date:
+ collection_date = date.strftime(date_format)
+ else:
+ results2 = re.search("([A-Za-z]+) then", bin_info)
+ if results2:
+ if results2[1] == "Today":
+ collection_date = datetime.now().strftime(date_format)
+ elif results2[1] == "Tomorrow":
+ collection_date = (
+ datetime.now() + timedelta(days=1)
+ ).strftime(date_format)
+ else:
+ collection_date = results2[1]
+
+ if collection_date != "":
+ dict_data = {
+ "type": bin_type,
+ "collectionDate": collection_date,
+ }
+ data["bins"].append(dict_data)
+
+ return data
diff --git a/wiki/Councils.md b/wiki/Councils.md
index 34a6acdee2..75b5beeda9 100644
--- a/wiki/Councils.md
+++ b/wiki/Councils.md
@@ -61,6 +61,7 @@ This document is still a work in progress, don't worry if your council isn't lis
- [Chorley Council](#chorley-council)
- [Colchester City Council](#colchester-city-council)
- [Conwy County Borough Council](#conwy-county-borough-council)
+- [Copeland Borough Council](#copeland-borough-council)
- [Cornwall Council](#cornwall-council)
- [Coventry City Council](#coventry-city-council)
- [Cotswold District Council](#cotswold-district-council)
@@ -884,6 +885,17 @@ Note: Conwy County Borough Council uses a straight UPRN in the URL, e.g., `&uprn
---
+### Copeland Borough Council
+```commandline
+python collect_data.py CopelandBoroughCouncil https://www.copeland.gov.uk -u XXXXXXXX
+```
+Additional parameters:
+- `-u` - UPRN
+
+Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.
+
+---
+
### Cornwall Council
```commandline
python collect_data.py CornwallCouncil https://www.cornwall.gov.uk/my-area/ -s -u XXXXXXXX
From 3603117ae5c986e9ba87633ea344583d3959c120 Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Fri, 15 Nov 2024 00:06:11 +0000
Subject: [PATCH 7/9] feat: Adding Thanet District Council
fix: #1004
---
uk_bin_collection/tests/input.json | 6 +++
.../councils/ThanetDistrictCouncil.py | 51 +++++++++++++++++++
wiki/Councils.md | 12 +++++
3 files changed, 69 insertions(+)
create mode 100644 uk_bin_collection/uk_bin_collection/councils/ThanetDistrictCouncil.py
diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json
index 48fbf06839..5f50dd8439 100644
--- a/uk_bin_collection/tests/input.json
+++ b/uk_bin_collection/tests/input.json
@@ -1650,6 +1650,12 @@
"wiki_name": "Test Valley Borough Council",
"wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
},
+ "ThanetDistrictCouncil": {
+ "uprn": "100061111858",
+ "url": "https://www.thanet.gov.uk",
+ "wiki_name": "Thanet District Council",
+ "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
+ },
"ThreeRiversDistrictCouncil": {
"postcode": "WD3 7AZ",
"skip_get_url": true,
diff --git a/uk_bin_collection/uk_bin_collection/councils/ThanetDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/ThanetDistrictCouncil.py
new file mode 100644
index 0000000000..cbad92c44f
--- /dev/null
+++ b/uk_bin_collection/uk_bin_collection/councils/ThanetDistrictCouncil.py
@@ -0,0 +1,51 @@
+import time
+
+import requests
+
+from uk_bin_collection.uk_bin_collection.common import *
+from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
+
+
+# import the wonderful Beautiful Soup and the URL grabber
+class CouncilClass(AbstractGetBinDataClass):
+ """
+ Concrete classes have to implement all abstract operations of the
+ base class. They can also override some operations with a default
+ implementation.
+ """
+
+ def parse_data(self, page: str, **kwargs) -> dict:
+
+ user_uprn = kwargs.get("uprn")
+ check_uprn(user_uprn)
+ bindata = {"bins": []}
+
+ URI = f"https://www.thanet.gov.uk/wp-content/mu-plugins/collection-day/incl/mu-collection-day-calls.php?pAddress={user_uprn}"
+
+ headers = {
+ "x-requested-with": "XMLHttpRequest",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
+ }
+
+ # Make the GET request
+ response = requests.get(URI, headers=headers)
+
+ # Parse the JSON response
+ bin_collection = response.json()
+
+ # Loop through each collection in bin_collection
+ for collection in bin_collection:
+ bin_type = collection["type"]
+ collection_date = collection["nextDate"].split(" ")[0]
+
+ dict_data = {
+ "type": bin_type,
+ "collectionDate": collection_date,
+ }
+ bindata["bins"].append(dict_data)
+
+ bindata["bins"].sort(
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
+ )
+
+ return bindata
diff --git a/wiki/Councils.md b/wiki/Councils.md
index 75b5beeda9..bde58fb782 100644
--- a/wiki/Councils.md
+++ b/wiki/Councils.md
@@ -221,6 +221,7 @@ This document is still a work in progress, don't worry if your council isn't lis
- [Telford and Wrekin Council](#telford-and-wrekin-council)
- [Tendring District Council](#tendring-district-council)
- [Test Valley Borough Council](#test-valley-borough-council)
+- [Thanet District Council](#thanet-district-council)
- [Three Rivers District Council](#three-rivers-district-council)
- [Tonbridge and Malling Borough Council](#tonbridge-and-malling-borough-council)
- [Torbay Council](#torbay-council)
@@ -2800,6 +2801,17 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr
---
+### Thanet District Council
+```commandline
+python collect_data.py ThanetDistrictCouncil https://www.thanet.gov.uk -u XXXXXXXX
+```
+Additional parameters:
+- `-u` - UPRN
+
+Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.
+
+---
+
### Three Rivers District Council
```commandline
python collect_data.py ThreeRiversDistrictCouncil https://my.threerivers.gov.uk/en/AchieveForms/?mode=fill&consentMessage=yes&form_uri=sandbox-publish://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b/AF-Stage-01ee28aa-1584-442c-8d1f-119b6e27114a/definition.json&process=1&process_uri=sandbox-processes://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&process_id=AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&noLoginPrompt=1 -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/
From 17cd12b093cb5fea56edda106bb53c08d59ebc48 Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Fri, 15 Nov 2024 00:48:21 +0000
Subject: [PATCH 8/9] feat: Adding Stevenage Borough Council
fix: #989
---
uk_bin_collection/tests/input.json | 6 ++
.../councils/StevenageBoroughCouncil.py | 101 ++++++++++++++++++
wiki/Councils.md | 12 +++
3 files changed, 119 insertions(+)
create mode 100644 uk_bin_collection/uk_bin_collection/councils/StevenageBoroughCouncil.py
diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json
index 5f50dd8439..ce5bc0a29f 100644
--- a/uk_bin_collection/tests/input.json
+++ b/uk_bin_collection/tests/input.json
@@ -1496,6 +1496,12 @@
"wiki_name": "St Albans City and District Council",
"wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)."
},
+ "StevenageBoroughCouncil": {
+ "uprn": "100080878852",
+ "url": "https://www.stevenage.gov.uk",
+ "wiki_name": "Stevenage Borough Council",
+ "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
+ },
"StHelensBC": {
"house_number": "15",
"postcode": "L34 2GA",
diff --git a/uk_bin_collection/uk_bin_collection/councils/StevenageBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/StevenageBoroughCouncil.py
new file mode 100644
index 0000000000..c9dda087c4
--- /dev/null
+++ b/uk_bin_collection/uk_bin_collection/councils/StevenageBoroughCouncil.py
@@ -0,0 +1,101 @@
+import time
+
+import requests
+from dateutil.relativedelta import relativedelta
+
+from uk_bin_collection.uk_bin_collection.common import *
+from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
+
+
+# import the wonderful Beautiful Soup and the URL grabber
+class CouncilClass(AbstractGetBinDataClass):
+ """
+ Concrete classes have to implement all abstract operations of the
+ base class. They can also override some operations with a default
+ implementation.
+ """
+
+ def parse_data(self, page: str, **kwargs) -> dict:
+ # Make a BS4 object
+ uprn = kwargs.get("uprn")
+ check_uprn(uprn)
+ bindata = {"bins": []}
+
+ SESSION_URL = "https://stevenage-self.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Fstevenage-self.achieveservice.com%252Fservice%252Fmy_bin_collection_schedule&hostname=stevenage-self.achieveservice.com&withCredentials=true"
+ TOKEN_URL = "https://stevenage-self.achieveservice.com/apibroker/runLookup?id=5e55337a540d4"
+ API_URL = "https://stevenage-self.achieveservice.com/apibroker/runLookup"
+
+ data = {
+ "formValues": {
+ "Section 1": {
+ "token": {"value": ""},
+ "LLPGUPRN": {
+ "value": uprn,
+ },
+ "MinimumDateLookAhead": {
+ "value": time.strftime("%Y-%m-%d"),
+ },
+ "MaximumDateLookAhead": {
+ "value": str(int(time.strftime("%Y")) + 1)
+ + time.strftime("-%m-%d"),
+ },
+ },
+ },
+ }
+
+ headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ "User-Agent": "Mozilla/5.0",
+ "X-Requested-With": "XMLHttpRequest",
+ "Referer": "https://stevenage-self.achieveservice.com/fillform/?iframe_id=fillform-frame-1&db_id=",
+ }
+ s = requests.session()
+ r = s.get(SESSION_URL)
+ r.raise_for_status()
+ session_data = r.json()
+ sid = session_data["auth-session"]
+
+ t = s.get(TOKEN_URL)
+ t.raise_for_status()
+ token_data = t.json()
+ data["formValues"]["Section 1"]["token"]["value"] = token_data["integration"][
+ "transformed"
+ ]["rows_data"]["0"]["token"]
+
+ params = {
+ "id": "64ba8cee353e6",
+ "repeat_against": "",
+ "noRetry": "false",
+ "getOnlyTokens": "undefined",
+ "log_id": "",
+ "app_name": "AF-Renderer::Self",
+ # unix_timestamp
+ "_": str(int(time.time() * 1000)),
+ "sid": sid,
+ }
+
+ r = s.post(API_URL, json=data, headers=headers, params=params)
+ r.raise_for_status()
+
+ data = r.json()
+ rows_data = data["integration"]["transformed"]["rows_data"]
+ if not isinstance(rows_data, dict):
+ raise ValueError("Invalid data returned from API")
+
+ for key in rows_data:
+ value = rows_data[key]
+ bin_type = value["bintype"].strip()
+
+ try:
+ date = datetime.strptime(value["collectiondate"], "%A %d %B %Y").date()
+ except ValueError:
+ continue
+
+ dict_data = {
+ "type": bin_type,
+ "collectionDate": date.strftime(date_format),
+ }
+ bindata["bins"].append(dict_data)
+
+ return bindata
diff --git a/wiki/Councils.md b/wiki/Councils.md
index bde58fb782..af69f6046f 100644
--- a/wiki/Councils.md
+++ b/wiki/Councils.md
@@ -203,6 +203,7 @@ This document is still a work in progress, don't worry if your council isn't lis
- [South Tyneside Council](#south-tyneside-council)
- [Southwark Council](#southwark-council)
- [St Albans City and District Council](#st-albans-city-and-district-council)
+- [Stevenage Borough Council](#stevenage-borough-council)
- [St Helens Borough Council](#st-helens-borough-council)
- [Stafford Borough Council](#stafford-borough-council)
- [Staffordshire Moorlands District Council](#staffordshire-moorlands-district-council)
@@ -2581,6 +2582,17 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm
---
+### Stevenage Borough Council
+```commandline
+python collect_data.py StevenageBoroughCouncil https://www.stevenage.gov.uk -u XXXXXXXX
+```
+Additional parameters:
+- `-u` - UPRN
+
+Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.
+
+---
+
### St Helens Borough Council
```commandline
python collect_data.py StHelensBC https://www.sthelens.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/
From db3c6be2c1cbee3f460243de231940cac0ae4436 Mon Sep 17 00:00:00 2001
From: m26dvd <31007572+m26dvd@users.noreply.github.com>
Date: Fri, 15 Nov 2024 01:03:35 +0000
Subject: [PATCH 9/9] feat: Adding Wolverhampton City Council
fix: #966
---
uk_bin_collection/tests/input.json | 7 +++
.../councils/WolverhamptonCityCouncil.py | 57 +++++++++++++++++++
wiki/Councils.md | 13 +++++
3 files changed, 77 insertions(+)
create mode 100644 uk_bin_collection/uk_bin_collection/councils/WolverhamptonCityCouncil.py
diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json
index ce5bc0a29f..fa1afb7fad 100644
--- a/uk_bin_collection/tests/input.json
+++ b/uk_bin_collection/tests/input.json
@@ -1929,6 +1929,13 @@
"wiki_name": "Worcester City Council",
"wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)."
},
+ "WolverhamptonCityCouncil": {
+ "uprn": "100071205205",
+ "postcode": "WV3 9NZ",
+ "url": "https://www.wolverhampton.gov.uk",
+ "wiki_name": "Wolverhampton City Council",
+ "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
+ },
"WorcesterCityCouncil": {
"url": "https://www.Worcester.gov.uk",
"wiki_command_url_override": "https://www.Worcester.gov.uk",
diff --git a/uk_bin_collection/uk_bin_collection/councils/WolverhamptonCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/WolverhamptonCityCouncil.py
new file mode 100644
index 0000000000..15041d9ca3
--- /dev/null
+++ b/uk_bin_collection/uk_bin_collection/councils/WolverhamptonCityCouncil.py
@@ -0,0 +1,57 @@
+import time
+
+import requests
+from bs4 import BeautifulSoup
+
+from uk_bin_collection.uk_bin_collection.common import *
+from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
+
+
+# import the wonderful Beautiful Soup and the URL grabber
+class CouncilClass(AbstractGetBinDataClass):
+ """
+ Concrete classes have to implement all abstract operations of the
+ base class. They can also override some operations with a default
+ implementation.
+ """
+
+ def parse_data(self, page: str, **kwargs) -> dict:
+
+ user_uprn = kwargs.get("uprn")
+ user_postcode = kwargs.get("postcode")
+ check_uprn(user_uprn)
+ check_postcode(user_postcode)
+ bindata = {"bins": []}
+
+ user_postcode = user_postcode.replace(" ", "%20")
+
+ URI = f"https://www.wolverhampton.gov.uk/find-my-nearest/{user_postcode}/{user_uprn}"
+
+ # Make the GET request
+ response = requests.get(URI)
+
+ soup = BeautifulSoup(response.content, "html.parser")
+
+ jumbotron = soup.find("div", {"class": "jumbotron jumbotron-fluid"})
+
+ # Find all bin entries in the row
+ for bin_div in jumbotron.select("div.col-md-4"):
+ service_name = bin_div.h3.text.strip()
+ next_date = bin_div.find(
+ "h4", text=lambda x: x and "Next date" in x
+ ).text.split(": ")[1]
+
+ dict_data = {
+ "type": service_name,
+ "collectionDate": datetime.strptime(
+ next_date,
+ "%B %d, %Y",
+ ).strftime(date_format),
+ }
+ bindata["bins"].append(dict_data)
+
+ bindata["bins"].sort(
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
+ )
+
+ return bindata
diff --git a/wiki/Councils.md b/wiki/Councils.md
index af69f6046f..3f6919219c 100644
--- a/wiki/Councils.md
+++ b/wiki/Councils.md
@@ -256,6 +256,7 @@ This document is still a work in progress, don't worry if your council isn't lis
- [Woking Borough Council / Joint Waste Solutions](#woking-borough-council-/-joint-waste-solutions)
- [Wokingham Borough Council](#wokingham-borough-council)
- [Worcester City Council](#worcester-city-council)
+- [Wolverhampton City Council](#wolverhampton-city-council)
- [Wychavon District Council](#wychavon-district-council)
- [Wyre Council](#wyre-council)
- [York Council](#york-council)
@@ -3236,6 +3237,18 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc
---
+### Wolverhampton City Council
+```commandline
+python collect_data.py WolverhamptonCityCouncil https://www.wolverhampton.gov.uk -u XXXXXXXX -p "XXXX XXX"
+```
+Additional parameters:
+- `-u` - UPRN
+- `-p` - postcode
+
+Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.
+
+---
+
### Wychavon District Council
```commandline
python collect_data.py WychavonDistrictCouncil https://selfservice.wychavon.gov.uk/wdcroundlookup/wdc_search.jsp -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/