diff --git a/popup/popup.css b/popup/popup.css
index 5a3e88d..51fcbe5 100644
--- a/popup/popup.css
+++ b/popup/popup.css
@@ -8,6 +8,17 @@
opacity: 1;
}
+#resetStatus {
+ opacity: 0;
+ transition: opacity 0.5s;
+ color: red;
+}
+
+#resetStatus.show {
+ opacity: 1;
+}
+
+
body {
min-width: 375px;
}
diff --git a/popup/popup.html b/popup/popup.html
index 5a78501..66423cf 100644
--- a/popup/popup.html
+++ b/popup/popup.html
@@ -82,6 +82,13 @@
Status: Inactive
+
+
+
+
+ Cleaned up!
+
+
diff --git a/popup/popup.js b/popup/popup.js
index bb704d6..aeb1e3c 100644
--- a/popup/popup.js
+++ b/popup/popup.js
@@ -9,8 +9,9 @@
document.getElementById("password").value = items["__password"] || "";
document.getElementById("frequency").value = items["__frequency"] || 1;
document.getElementById("status").innerText = items["__status"] || "unknown";
- document.getElementById("currApptConsulate").innerText = items["__apptConsulate"] || "somewhere";
- document.getElementById("currApptDate").innerText = items["__apptDate"] || "sometime";
+ let currentAppt = items["__currentAppt"] || {"consulate": "unknown", "date": "unknown"};
+ document.getElementById("currApptConsulate").innerText = currentAppt["consulate"];
+ document.getElementById("currApptDate").innerText = currentAppt["date"];
document.getElementById("deltaAppt").value = items["__deltaAppt"] || 1;
document.getElementById("deltaNow").value = items["__deltaNow"] || 1;
document.getElementById("autobook").checked = items["__autobook"] || false;
@@ -80,4 +81,32 @@
chrome.storage.local.set({ __deltaNow: this.value });
});
+ // reset button
+ document.getElementById("resetButton").addEventListener("click", async () => {
+ if (confirm("Are you sure you want to reset?")) {
+ await chrome.storage.local.get().then(items => {
+ chrome.storage.local.clear();
+ // keep user parameters
+ chrome.storage.local.set({
+ "__activate": items["__activate"] || false,
+ "__username": items["__username"] || "",
+ "__password": items["__password"] || "",
+ "__frequency": items["__frequency"] || 1,
+ "__deltaAppt": items["__deltaAppt"] || 1,
+ "__deltaNow": items["__deltaNow"] || 1,
+ "__autobook": items["__autobook"] || false,
+ });
+ });
+ location.reload();
+ }
+ });
+
+ // show config button
+ document.getElementById("showConfigButton").addEventListener("click", async () => {
+ let config = await chrome.storage.local.get();
+ let configStr = JSON.stringify(config, null, 2);
+ let url = "data:text/plain;charset=utf-8," + encodeURIComponent(configStr);
+ chrome.tabs.create({ url: url });
+});
+
})();
diff --git a/scripts/content.js b/scripts/content.js
index 6986f64..97db5a3 100644
--- a/scripts/content.js
+++ b/scripts/content.js
@@ -4,8 +4,8 @@
const pathnameRegex = /^\/\w{2}-\w{2}\/n?iv/;
const MAX_SIGNIN_ATTEMPTS = 1;
const PAGE_WAIT_TIME = 3792;
-const MINUTE = 67;
-const SOFT_BAN_COUNTDOWN = 27 * MINUTE;
+const MINUTE = 60;
+const SOFT_BAN_TIMEOUT = 27 * MINUTE;
const NOTIF_CHANNEL = "snegov_test"
let config = {
@@ -15,8 +15,10 @@ let config = {
frequency: null,
countdown: null,
apptId: null,
- apptDate: null,
- apptConsulate: null,
+ currentAppt: {
+ consulate: null,
+ date: null,
+ },
signinAttempts: null,
consulates: null,
deltaAppt: null,
@@ -39,6 +41,17 @@ async function sendNotification(message, channel = NOTIF_CHANNEL) {
.catch(e => console.error(e));
}
+function getRandomInt(max) {
+ return Math.floor(Math.random() * Math.floor(max));
+}
+
+function getFutureDate(minutes, maxRandomSeconds = 0) {
+ // return date some amount of minutes in future plus random amount of seconds
+ let futureDate = new Date();
+ futureDate.setMinutes(futureDate.getMinutes() + minutes);
+ futureDate.setSeconds(futureDate.getSeconds() + getRandomInt(maxRandomSeconds));
+ return futureDate.toISOString();
+}
function isSignInPage() {
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/users\/sign_in/));
@@ -107,13 +120,15 @@ async function getAppointmentId() {
async function getConsulates() {
let consulatesSelect = document.querySelector("#appointments_consulate_appointment_facility_id")
let consulatesDict = {};
+
for (let option of consulatesSelect.options) {
- if (!option.value) continue;
+ if (!option.value) continue; // skip empty option
consulatesDict[option.text] = {
"id": parseInt(option.value),
"isSelected": option.selected,
"bestDate": null,
"currentDate": null,
+ "nextCheckAt": getFutureDate(0, 60),
};
}
return consulatesDict;
@@ -129,6 +144,7 @@ async function getAvailableDates(consulateId) {
return dateList;
})
.catch(e => null);
+ // TODO catch for unauthorized
return dates;
}
@@ -148,6 +164,7 @@ async function getAvailableTimes(consulateId, date) {
.then(d => d.json())
.then(data => data.available_times)
.catch(e => null);
+ // TODO catch for unauthorized
return times;
}
@@ -168,8 +185,7 @@ async function runner() {
config.countdown = result['__countdown'] || 0;
config.signinAttempts = result['__signinAttempts'] || 0;
config.apptId = result['__apptId'] || null;
- config.apptDate = result['__apptDate'] || null;
- config.apptConsulate = result['__apptConsulate'] || null;
+ config.currentAppt = result['__currentAppt'] || { consulate: null, date: null };
config.consulates = result['__consulates'] || null;
config.deltaAppt = result['__deltaAppt'] || 1;
config.deltaNow = result['__deltaNow'] || 1;
@@ -184,14 +200,21 @@ async function runner() {
for (let key in config) {
if (config.hasOwnProperty(key)
&& !_.isEqual(config[key], prev_config[key])) {
- console.log(`Config change: ${key}, ${prev_config[key]} => ${config[key]}`);
+ console.log(`Config change: ${key}, ${JSON.stringify(prev_config[key])} => ${JSON.stringify(config[key])}`);
- // reduce countdown if frequency is reduced
+ // reduce wait times for consulates if frequency is increased
if (key === 'frequency') {
- let max_countdown = config[key] * MINUTE;
- if (config.countdown > max_countdown) {
- config.countdown = max_countdown;
- await chrome.storage.local.set({ "__countdown": config.countdown });
+ let wasChanged = false;
+ for (let consulate in config.consulates) {
+ let newNextCheckAt = getFutureDate(config.frequency, 10);
+ if (config.consulates[consulate].nextCheckAt > newNextCheckAt) {
+ config.consulates[consulate].nextCheckAt = newNextCheckAt;
+ wasChanged = true;
+ console.log(`Reducing wait time for ${consulate} from ${config.consulates[consulate].nextCheckAt} to ${newNextCheckAt}`);
+ }
+ }
+ if (wasChanged) {
+ await chrome.storage.local.set({ "__consulates": config.consulates });
}
}
@@ -323,23 +346,25 @@ async function runner() {
let apptDate = new Date(apptInfo.match(/\d{1,2} \w+, \d{4}/)[0]);
apptDate = apptDate.toISOString().slice(0, 10);
if (apptDate && apptConsulate
- && (apptDate != config.apptDate || apptConsulate != config.apptConsulate)) {
- console.log(`New appointment date: ${apptDate} at ${apptConsulate}, old: ${config.apptDate} at ${config.apptConsulate}`);
- config.apptDate = apptDate;
- config.apptConsulate = apptConsulate;
- await chrome.storage.local.set({ "__apptDate": apptDate });
- await chrome.storage.local.set({ "__apptConsulate": apptConsulate });
+ && (apptDate != config.currentAppt.date
+ || apptConsulate != config.currentAppt.consulate)) {
+ console.log(`New appointment date: ${apptDate} at ${apptConsulate},
+ old: ${config.currentAppt.consulate} at ${config.currentAppt.date}`);
+ config.currentAppt.date = apptDate;
+ config.currentAppt.consulate = apptConsulate;
+ await chrome.storage.local.set({ "__currentAppt": config.currentAppt });
}
// go to appointment page
- let apptLink = apptInfoCard.querySelector("p.consular-appt [href]").getAttribute("href").replace("/addresses/consulate", "/appointment");
+ let apptLink = apptInfoCard.querySelector("p.consular-appt [href]")
+ .getAttribute("href").replace("/addresses/consulate", "/appointment");
window.location.href = apptLink;
await delay(PAGE_WAIT_TIME);
}
else if (isAppointmentPage()) {
// if no apptDate, fetch it from dashboard page
- if (!config.apptDate) {
+ if (!config.currentAppt.date) {
console.log('No appointment date is set, going back to dashboard');
window.location = window.location.pathname.replace(/schedule.*/g, "/account");
await delay(PAGE_WAIT_TIME);
@@ -389,28 +414,30 @@ async function runner() {
}
for (let consulate of selectedConsulates) {
+ // skip if not time to check
+ if (config.consulates[consulate].nextCheckAt > new Date().toISOString()) {
+ continue;
+ }
+
console.log('Checking dates for ' + consulate);
let availDates = await getAvailableDates(config.consulates[consulate].id);
- config.countdown = config.frequency * MINUTE;
- await chrome.storage.local.set({ "__countdown": config.countdown });
+ config.consulates[consulate].nextCheckAt = getFutureDate(config.frequency, 10);
if (!availDates) {
msg = `Failed to fetch available dates in ${consulate}`;
console.log(msg);
await chrome.storage.local.set({ "__status": msg });
- isRunning = false;
- return;
+ continue;
}
// if empty list, either we're banned or non operational hours or dead consulate
+ // wait for some time before checking again
if (availDates.length == 0) {
msg = `No available dates in ${consulate}, probably banned`;
console.log(msg);
await chrome.storage.local.set({ "__status": msg });
- config.countdown = SOFT_BAN_COUNTDOWN;
- await chrome.storage.local.set({ "__countdown": config.countdown });
- isRunning = false;
- return;
+ config.consulates[consulate].nextCheckAt = getFutureDate(SOFT_BAN_TIMEOUT, 10);
+ continue;
}
console.log(`Available dates for ${consulate}: ${availDates}`);
@@ -420,16 +447,14 @@ async function runner() {
console.log(`New record for ${consulate}: ${availDates[0]}`);
config.consulates[consulate].bestDate = availDates[0];
}
- await chrome.storage.local.set({ "__consulates": config.consulates });
// filter dates with our requests
- let filteredDates = await filterDates(availDates, config.apptDate, config.deltaAppt, config.deltaNow);
+ let filteredDates = await filterDates(availDates, config.currentAppt.date, config.deltaAppt, config.deltaNow);
if (!filteredDates.length) {
msg = `No better dates in ${consulate}, currently available ${availDates[0]}`;
console.log(msg);
await chrome.storage.local.set({ "__status": msg});
- isRunning = false;
- return;
+ continue;
}
console.log(`Dates worth rescheduling in ${consulate}: ${filteredDates}`);
@@ -446,15 +471,13 @@ async function runner() {
msg = `Failed to fetch available timeslots in ${consulate} at ${chosenDate}`;
console.log(msg);
await chrome.storage.local.set({ "__status": msg});
- isRunning = false;
- return;
+ continue;
}
if (availTimes.length == 0) {
msg = `No timeslots in ${consulate} at ${chosenDate}`;
console.log(msg);
await chrome.storage.local.set({ "__status": msg});
- isRunning = false;
- return;
+ continue;
}
console.log(`Available timeslots in ${consulate} at ${chosenDate}: ${availTimes}`);
let chosenTime = availTimes[0];
@@ -481,15 +504,21 @@ async function runner() {
}
} // end consulates loop
+ for (let consulate in config.consulates) {
+ if (config.consulates[consulate].nextCheckAt != prev_config.consulates[consulate].nextCheckAt) {
+ console.log(`Next check for ${consulate} at ${config.consulates[consulate].nextCheckAt}`);
+ }
+ }
+ await chrome.storage.local.set({ "__consulates": config.consulates });
+ isRunning = false;
+ return;
}
else if (isConfirmationPage) {
// go back to schedule after successful reschedule
await delay(PAGE_WAIT_TIME);
- config.apptDate = null;
- await chrome.storage.local.set({"__apptDate": config.apptDate});
- config.apptConsulate = null;
- await chrome.storage.local.set({"__apptConsulate": config.apptConsulate});
+ config.currentAppt = { consulate: null, date: null};
+ await chrome.storage.local.set({"__currentAppt": config.currentAppt});
console.log('Rescheduled successfully');
window.location = window.location.pathname.replace(/schedule.*/g, "");
await delay(PAGE_WAIT_TIME);