Closed anstosa closed 2 years ago
Allow file upload and live camera scan
// based on code donated by @jordansoltman, the developer for Ferry Friend on iOS
import axios from "axios";
import jsdom from "jsdom";
import AbstractSourceTicketProvider from "../../abstract/providers/abstract_source_ticket_provider";
import { Result } from "../../lib/result";
import { WSFTicketData } from "../../types/interfaces";
import { validateObjectHasOnlyProperties } from "../../lib/util/validate";
const { JSDOM } = jsdom;
const TICKET_HTML_KEYS = [
"Plu",
"ItemName",
"Description",
"Price",
"Status",
"ExpirationDate",
"TotalRemainingUses",
"VisualId"
];
// FIXME this should be derrived from the interface. Not hard coded here.
const TICKET_RESPONSE_KEYS = [
"visualId",
"plu",
"itemName",
"description",
"price",
"status",
"expirationDate",
"totalUsesRemaining"
];
export default class SourceTicketProvider extends AbstractSourceTicketProvider {
async getTicketStatus(ticketNumber: string): Promise<Result<WSFTicketData, string>> {
const cookie = await this.getWSFTicketCookie();
const values = await this.scrapeTicket(cookie, ticketNumber);
return values;
}
private getTicketValues(element: Element): string[] {
const spans = [...element.querySelectorAll("span")];
const dataTexts = spans.filter((span) => {
return (
span.getAttributeNames().includes("data-text") &&
TICKET_HTML_KEYS.includes(span.getAttribute("data-text"))
);
});
return dataTexts.map((text) => text.innerHTML);
}
private async scrapeTicket(cookie: string, number: string): Promise<Result<WSFTicketData>> {
const axiosOpts = {
headers: {
Cookie: cookie
}
};
const response = await axios.get(this.ticketLookupUrl + number, axiosOpts);
const { window } = new JSDOM(response.data, {});
const element = await window.document.querySelector("#TicketLookup");
// Generate the values for the ticket from the html element
const values = this.getTicketValues(element);
const returnObj = values.reduce((acc: any, el, i) => {
acc[TICKET_RESPONSE_KEYS[i]] = el;
return acc;
}, {}) as WSFTicketData;
// FIXME maybe a better validation solution.
if (!returnObj) {
return ["Unable to cast ticket object to correct type.", null];
}
const errors = validateObjectHasOnlyProperties(TICKET_RESPONSE_KEYS, returnObj);
if (errors.length !== 0) {
return [`Ticket information ill formatted: ${errors.toLocaleString}`, null];
}
return [null, returnObj];
}
private async getWSFTicketCookie(): Promise<string> {
const response = await axios.get(this.ticketLandingPageUrl, {
maxRedirects: 0,
validateStatus: (s) => s === 302
});
// FIXME this type cast may be unsafe. Hasnt broken yet.
const cookieStrings = response.headers["set-cookie"] as string[];
return cookieStrings.map((string) => string.split(" ")[0]).join(" ");
}
}
Added in e038368e6e62f19a00190e0b1fd0ccbd69ea8012