Skip to content

Instantly share code, notes, and snippets.

@Gaelan
Created February 15, 2024 19:43
Show Gist options
  • Save Gaelan/fe1fd01b6fd5f5f6b8ad193634d6d160 to your computer and use it in GitHub Desktop.
Save Gaelan/fe1fd01b6fd5f5f6b8ad193634d6d160 to your computer and use it in GitHub Desktop.
Get Fife bin calendar
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