mirror of
https://git.eta.st/eta/rsp6-decoder.git
synced 2024-12-26 09:57:44 +00:00
323 lines
11 KiB
JavaScript
323 lines
11 KiB
JavaScript
console.log("[+] Wow, JavaScript!");
|
|
import * as wasm from "rsp6-decoder";
|
|
import { BrowserAztecCodeReader } from '@zxing/library';
|
|
|
|
let stations = require("./stations.json");
|
|
let fares = require("./fares.json");
|
|
let discounts = require("./discounts.json");
|
|
window.stations = stations;
|
|
window.fares = fares;
|
|
window.discounts = discounts;
|
|
let loaded = false;
|
|
|
|
function onload() {
|
|
if (loaded) {
|
|
return;
|
|
}
|
|
loaded = true;
|
|
console.log("[+] initialising wasm");
|
|
window.wasm = wasm;
|
|
window.wasm.init();
|
|
let video_div = document.getElementById("video-container");
|
|
let scan_button = document.getElementById("scan-button");
|
|
let error_banner = document.getElementById("error-banner");
|
|
let error_header = document.getElementById("error-header");
|
|
let error_text = document.getElementById("error-text");
|
|
let decode_banner = document.getElementById("decode-banner");
|
|
let decode_reason = document.getElementById("decode-reason");
|
|
let raw_barcode = document.getElementById("raw-barcode");
|
|
let raw_barcode_wrapper = document.getElementById("raw-barcode-wrapper");
|
|
let raw_json = document.getElementById("raw-json");
|
|
let ticket_data = document.getElementById("ticket-data");
|
|
let ticket_content = document.getElementById("ticket-content");
|
|
let file_upload = document.getElementById("file-upload-1");
|
|
const codeReader = new BrowserAztecCodeReader();
|
|
console.log("[+] Looks like everything initialised fine!");
|
|
document.getElementById('code-version').innerHTML = __COMMIT_HASH__;
|
|
|
|
let selectedDeviceId;
|
|
|
|
function error(header, text) {
|
|
video_div.style.display = "none";
|
|
error_header.innerHTML = header;
|
|
error_text.innerHTML = text;
|
|
error_banner.style.display = "block";
|
|
error_banner.focus();
|
|
error_banner.scrollIntoView();
|
|
}
|
|
|
|
function decodeError(text) {
|
|
decode_banner.style.display = "block";
|
|
decode_reason.innerHTML = text;
|
|
decode_banner.scrollIntoView();
|
|
}
|
|
|
|
function row(k, v) {
|
|
let tr = document.createElement("tr");
|
|
tr.classList.add("govuk-table__row");
|
|
let th = document.createElement("th");
|
|
th.classList.add("govuk-table__header");
|
|
th.scope = "row";
|
|
let td = document.createElement("td");
|
|
td.classList.add("govuk-table__cell");
|
|
th.innerHTML = k;
|
|
td.innerHTML = v;
|
|
tr.appendChild(th);
|
|
tr.appendChild(td);
|
|
ticket_data.appendChild(tr);
|
|
}
|
|
|
|
function nlcify(nlc) {
|
|
let data = stations[nlc];
|
|
if (!data || !data.desc) {
|
|
return nlc;
|
|
}
|
|
else {
|
|
if (data.crs) {
|
|
return data.crs;
|
|
}
|
|
else {
|
|
return nlc;
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleTicket(ticket) {
|
|
window.ticket = ticket;
|
|
video_div.style.display = "none";
|
|
codeReader.reset();
|
|
console.log("Got ticket: " + ticket);
|
|
raw_barcode.innerHTML = ticket;
|
|
raw_barcode_wrapper.style.display = "block";
|
|
try {
|
|
let result = window.wasm.decode_ticket(window.ticket);
|
|
if (result["Err"]) {
|
|
decodeError(result["Err"]);
|
|
}
|
|
else {
|
|
let data = result["Ok"];
|
|
raw_json.innerHTML = JSON.stringify(data, null, 2);
|
|
ticket_data.innerHTML = "";
|
|
document.getElementById("origin").innerHTML = nlcify(data.origin_nlc);
|
|
document.getElementById("dest").innerHTML = nlcify(data.destination_nlc);
|
|
let fare = fares[data.fare] || ("Unknown fare (" + data.fare + ")");
|
|
let origin = stations[data.origin_nlc] ? stations[data.origin_nlc].desc : "NLC " + data.origin_nlc;
|
|
let destination = stations[data.destination_nlc] ? stations[data.destination_nlc].desc : "NLC " + data.destination_nlc;
|
|
let type = "";
|
|
if (data.coupon_type == "Single") {
|
|
type = "Single";
|
|
}
|
|
else if (data.coupon_type == "ReturnOutbound") {
|
|
type = "Return (outbound)";
|
|
}
|
|
else if (data.coupon_type == "ReturnInbound") {
|
|
type = "Return (inbound)";
|
|
}
|
|
else {
|
|
type = "Season";
|
|
}
|
|
if (data.bidirectional) {
|
|
type += " (bidirectional)";
|
|
}
|
|
let discount = null;
|
|
let discount_name = null;
|
|
if (data.discount_code) {
|
|
if (discounts[data.discount_code]) {
|
|
discount = discounts[data.discount_code].desc;
|
|
if (discounts[data.discount_code].code) {
|
|
discount_name = discounts[data.discount_code].code;
|
|
}
|
|
}
|
|
else {
|
|
discount = "Unknown discount (" + data.discount_code + ")";
|
|
}
|
|
}
|
|
let adult = data.child_ticket ? "Child" : "Adult";
|
|
let first = data.first_class ? "First Class" : "Standard Class";
|
|
document.getElementById("ticket-fromto").innerHTML = origin + " to " + destination;
|
|
if (data.manually_inspect) {
|
|
row("", "<i>Manual inspection required</i>");
|
|
}
|
|
let brfares_link = "https://www.brfares.com/!faredetail?orig=" + data.origin_nlc + "&dest=" + data.destination_nlc + "&tkt=" + data.fare;
|
|
if (data.route_code && data.route_code != 0) {
|
|
brfares_link += "&rte=" + data.route_code;
|
|
}
|
|
if (discount_name) {
|
|
brfares_link += "&rlc=" + discount_name;
|
|
}
|
|
row("Fare", fare + "<br/><small><a class=\"govuk-link\" href=\"" + brfares_link + "\">View on brfares.com</a></small>");
|
|
row("Class", adult + " " + first);
|
|
if (discount) {
|
|
row("Discount", discount);
|
|
}
|
|
row("Ticket type", type);
|
|
row("Start date", data.start_date);
|
|
if (data.depart_time_flag || data.depart_time != "00:00:00.0") {
|
|
let extra = "";
|
|
if (data.depart_time_flag == "Suggested") {
|
|
extra = " (suggested)";
|
|
}
|
|
else if (data.depart_time_flag == "Specific") {
|
|
extra = " (mandatory)";
|
|
}
|
|
row("Depart at", data.depart_time.substring(0, 5) + extra);
|
|
}
|
|
row("Ticket reference", data.issuer_id + data.ticket_reference);
|
|
if (data.passenger_name) {
|
|
row("Passenger name", data.passenger_name);
|
|
}
|
|
if (data.purchase_details) {
|
|
let dov = data.purchase_details.days_of_validity;
|
|
let maybe_s = "";
|
|
if (dov > 1) {
|
|
maybe_s = "s";
|
|
}
|
|
row("Valid for", dov + " day" + maybe_s);
|
|
}
|
|
if (data.limited_duration) {
|
|
row("Limited duration", data.limited_duration);
|
|
}
|
|
row("Fare code", data.fare);
|
|
if (data.purchase_details) {
|
|
let price = data.purchase_details.price_pence;
|
|
row("Price", "£" + Math.floor(price / 100) + "." + (price % 100).toString().padStart(2, "0"));
|
|
row("Purchased", data.purchase_details.purchase_time.substring(0, 16));
|
|
if (data.purchase_details.purchase_reference) {
|
|
row("Purchase reference", data.purchase_details.purchase_reference);
|
|
}
|
|
}
|
|
if (data.reservations.length > 0) {
|
|
let out = "";
|
|
data.reservations.forEach((resv) => {
|
|
if (out != "") {
|
|
out += "<br/>";
|
|
}
|
|
out += "<i>Train " + resv.retail_service_id + "</i></br>";
|
|
if (resv.coach == "*") {
|
|
out += "No specific seat";
|
|
}
|
|
else {
|
|
if (!resv.coach || resv.coach == " ") {
|
|
resv.coach = "?";
|
|
}
|
|
out += "Coach " + resv.coach + " Seat " + resv.seat_number + (resv.seat_letter ? resv.seat_letter : "");
|
|
}
|
|
});
|
|
row("Reservations", out);
|
|
}
|
|
let retailer = stations[data.retailer_id] ? stations[data.retailer_id].desc : "???";
|
|
row("Sold by", retailer + " (" + data.retailer_id + ")");
|
|
if (data.route_code != 0) {
|
|
row("Route code", data.route_code);
|
|
}
|
|
if (data.restriction_code) {
|
|
row("Restriction", data.restriction_code);
|
|
}
|
|
if (data.discount_code != 0) {
|
|
row("Discount code", (discount_name ? discount_name + " / " : "") + data.discount_code);
|
|
}
|
|
if (data.passenger_id) {
|
|
row("Passenger ID", data.passenger_id);
|
|
}
|
|
row("Lennon code", data.lennon_ticket_type);
|
|
row("Sequence number", data.sub_utn);
|
|
ticket_content.style.display = "block";
|
|
ticket_content.scrollIntoView();
|
|
}
|
|
}
|
|
catch (e) {
|
|
decodeError("exception: " + e);
|
|
}
|
|
}
|
|
window.handleTicket = handleTicket;
|
|
|
|
codeReader.getVideoInputDevices()
|
|
.then((videoInputDevices) => {
|
|
console.log("[+] Detected " + videoInputDevices.length + " video sources");
|
|
if (videoInputDevices.length >= 1) {
|
|
selectedDeviceId = videoInputDevices[0].deviceId;
|
|
scan_button.removeAttribute("disabled");
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
error("Scanner failed", "Couldn't figure out what cameras you have on your device.<br/><pre>" + err + "</pre>");
|
|
});
|
|
|
|
let epoch = 0;
|
|
|
|
document.getElementById('scan-button').addEventListener('click', () => {
|
|
let current_epoch = epoch + 1;
|
|
epoch = current_epoch;
|
|
decode_banner.style.display = "none";
|
|
ticket_content.style.display = "none";
|
|
codeReader.decodeFromInputVideoDevice(undefined, 'video').then((result) => {
|
|
error_banner.style.display = "none";
|
|
handleTicket(result);
|
|
}).catch((err) => {
|
|
if (epoch == current_epoch) {
|
|
error("Scanner failed", "Looks like the barcode scanner failed in some way. If this keeps happening, try reloading the page or using one of the alternative options.<br/><pre>" + err + "</pre>");
|
|
console.error(err);
|
|
}
|
|
});
|
|
video_div.style.display = "block";
|
|
console.log(`[+] Barcode scanner started on device ${selectedDeviceId}`);
|
|
});
|
|
document.getElementById('video').addEventListener('play', () => {
|
|
console.log('[+] Video started playing');
|
|
video_div.scrollIntoView();
|
|
});
|
|
document.getElementById('raw-text-form').addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
console.log("[+] Raw text input pressed");
|
|
decode_banner.style.display = "none";
|
|
ticket_content.style.display = "none";
|
|
let value = document.getElementById('raw-text-in').value;
|
|
document.getElementById('text-details').removeAttribute('open');
|
|
handleTicket(value);
|
|
});
|
|
file_upload.addEventListener('change', () => {
|
|
if (file_upload.files.length > 0) {
|
|
console.log('[+] File selected');
|
|
const file = file_upload.files[0];
|
|
const uri = window.URL.createObjectURL(file);
|
|
console.log('[+] Scanning URL ' + uri);
|
|
const img = document.createElement("img");
|
|
img.src = uri;
|
|
img.videoWidth = 0;
|
|
let done = false;
|
|
document.getElementById('image-sanctuary').innerHTML = '';
|
|
document.getElementById('image-sanctuary').appendChild(img);
|
|
decode_banner.style.display = "none";
|
|
ticket_content.style.display = "none";
|
|
codeReader.reset();
|
|
setTimeout(() => {
|
|
if (!done) {
|
|
console.log("[+] Timed out!");
|
|
codeReader.reset();
|
|
}
|
|
}, 750);
|
|
codeReader.decodeFromImage(img).then((result) => {
|
|
console.log("[+] Screenshot decode done!");
|
|
done = true;
|
|
document.getElementById('screenshot-details').removeAttribute('open');
|
|
error_banner.style.display = "none";
|
|
window.URL.revokeObjectURL(uri);
|
|
handleTicket(result);
|
|
}).catch((err) => {
|
|
done = true;
|
|
console.error(err);
|
|
error("Barcode reader failed", "Couldn't detect a barcode in that screenshot. Try another image.<br/><pre>" + err + "</pre>");
|
|
window.URL.revokeObjectURL(uri);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if (document.readyState == 'complete') {
|
|
console.log("[+] Loaded already!");
|
|
onload();
|
|
}
|
|
else {
|
|
window.addEventListener('load', onload);
|
|
}
|