Created
February 15, 2024 19:43
-
-
Save Gaelan/fe1fd01b6fd5f5f6b8ad193634d6d160 to your computer and use it in GitHub Desktop.
Get Fife bin calendar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import PQueue from "p-queue"; | |
import fetch, { Request, RequestInit, Response } from "node-fetch"; | |
import { z } from "zod"; | |
import { Temporal } from "@js-temporal/polyfill"; | |
class HTTPError extends Error {} | |
// be nice to the council: one request per second, never more than one at a time | |
const queue = new PQueue({ concurrency: 1, interval: 5000, intervalCap: 5 }); | |
type Session = { | |
fetch: (path: string, opts: RequestInit | undefined) => Promise<Response>; | |
}; | |
export const makeSession = async (): Promise<Session> => { | |
let token: string | null; | |
const fetchWithToken = async (path: string, opts: RequestInit = {}) => { | |
console.log("making council request: " + path); | |
const req = new Request("https://www.fife.gov.uk/api/" + path, opts); | |
if (token) req.headers.set("authorization", token); | |
req.headers.set("user-agent", "Bins/1.0 ([email protected])"); | |
const res = await queue.add(async () => await fetch(req), { | |
throwOnTimeout: true, | |
}); | |
token = res.headers.get("authorization"); | |
if (!res.ok) { | |
throw new HTTPError("HTTP " + res.statusText); | |
} | |
return res; | |
}; | |
await fetchWithToken("citizen?preview=false&locale=en"); | |
return { fetch: fetchWithToken }; | |
}; | |
const addressesSchema = z.object({ | |
data: z.array(z.object({ value: z.string(), label: z.string() })), | |
}); | |
const propertySchema = z.object({ | |
profileData: z.object({ | |
"property-UPRN": z.string(), | |
"property-Address": z.string(), | |
}), | |
}); | |
const calendarSchema = z.object({ | |
data: z.union([ | |
z.object({ results_returned: z.literal("false") }), | |
z.object({ | |
results_returned: z.literal("true"), | |
tab_collections: z.array( | |
z.object({ colour: z.string(), date: z.string(), type: z.string() }) | |
), | |
}), | |
]), | |
}); | |
export async function getAddressesAtPostcode( | |
session: Session, | |
postcode: string | |
) { | |
const res = await session.fetch( | |
"widget?action=propertysearch&actionedby=ps_3SHSN93&loadform=true&access=citizen&locale=en", | |
{ | |
body: JSON.stringify({ | |
caseid: "", | |
data: { postcode }, | |
email: "", | |
name: "bin_calendar", | |
xref: "", | |
xref1: "", | |
xref2: "", | |
}), | |
method: "post", | |
headers: { "content-type": "application/json" }, | |
} | |
); | |
const data = addressesSchema.parse(await res.json()); | |
return data.data.map(({ value, label }) => ({ address: label, id: value })); | |
} | |
export async function getProperty(session: Session, addressId: string) { | |
const res = await session.fetch( | |
`getobjectdata?objecttype=property&objectid=${addressId}`, | |
{ method: "post" } | |
); | |
const json = propertySchema.parse(await res.json()); | |
return { | |
address: json.profileData["property-Address"], | |
uprn: json.profileData["property-UPRN"], | |
}; | |
} | |
export async function getBinCalendar(session: Session, uprn: string) { | |
const res = await session.fetch( | |
"custom?action=powersuite_bin_calendar_collections&actionedby=bin_calendar&loadform=true&access=citizen&locale=en", | |
{ | |
method: "post", | |
headers: { "content-type": "application/json" }, | |
body: JSON.stringify({ | |
name: "bin_calendar", | |
data: { uprn }, | |
email: "", | |
caseid: "", | |
xref: "", | |
xref1: "", | |
xref2: "", | |
}), | |
} | |
); | |
const calendar = calendarSchema.parse(await res.json()); | |
if (calendar.data.results_returned == "false") { | |
return null; | |
} else { | |
return calendar.data.tab_collections.map((collection) => { | |
const [_weekDay, month, day, year] = collection.date | |
.split(" ") | |
.map((x) => x.replace(",", "")); | |
const months = [ | |
"January", | |
"February", | |
"March", | |
"April", | |
"May", | |
"June", | |
"July", | |
"August", | |
"September", | |
"October", | |
"November", | |
"December", | |
]; | |
return { | |
description: collection.type, | |
date: new Temporal.PlainDate( | |
parseInt(year), | |
months.indexOf(month) + 1, | |
parseInt(day) | |
), | |
}; | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment