2024-04-22 07:36:09 +00:00
|
|
|
// Copyright (c) 2024, snegov
|
|
|
|
|
// All rights reserved.
|
2024-04-21 04:03:26 +00:00
|
|
|
|
|
|
|
|
const pathnameRegex = /^\/\w{2}-\w{2}\/n?iv/;
|
2024-04-28 05:47:10 +00:00
|
|
|
const MAX_SIGNIN_ATTEMPTS = 3;
|
2024-04-22 07:19:26 +00:00
|
|
|
const PAGE_WAIT_TIME = 3792;
|
2024-04-24 07:23:35 +00:00
|
|
|
const MINUTE = 60;
|
2024-04-27 06:16:59 +00:00
|
|
|
const RANDOM_JITTER = 0.1;
|
2024-04-24 22:12:57 +00:00
|
|
|
const SOFT_BAN_TIMEOUT = 27;
|
2024-04-22 07:36:09 +00:00
|
|
|
const NOTIF_CHANNEL = "snegov_test"
|
2024-04-21 04:03:26 +00:00
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
let cfg = {
|
|
|
|
|
activate: undefined,
|
|
|
|
|
username: undefined,
|
|
|
|
|
password: undefined,
|
|
|
|
|
frequency: undefined,
|
|
|
|
|
consulates: undefined,
|
|
|
|
|
deltaAppt: undefined,
|
|
|
|
|
deltaNow: undefined,
|
|
|
|
|
};
|
|
|
|
|
let ctx = {
|
|
|
|
|
apptId: undefined,
|
2024-04-24 07:23:35 +00:00
|
|
|
currentAppt: {
|
2024-04-28 02:20:50 +00:00
|
|
|
consulate: undefined,
|
|
|
|
|
date: undefined,
|
2024-04-24 07:23:35 +00:00
|
|
|
},
|
2024-04-28 02:20:50 +00:00
|
|
|
signinAttempts: undefined,
|
|
|
|
|
consulates: undefined,
|
|
|
|
|
statusMsg: undefined,
|
|
|
|
|
}
|
2024-04-21 06:50:49 +00:00
|
|
|
let isRunning = false;
|
2024-04-23 06:30:55 +00:00
|
|
|
let msg = "";
|
2024-04-26 06:00:44 +00:00
|
|
|
let isFoundAppointment = false;
|
2024-04-21 04:03:26 +00:00
|
|
|
|
2024-04-22 07:36:09 +00:00
|
|
|
async function delay(ms) {
|
|
|
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function sendNotification(message, channel = NOTIF_CHANNEL) {
|
|
|
|
|
await fetch('https://ntfy.sh/' + channel, {
|
|
|
|
|
method: 'POST',
|
2024-04-26 05:44:54 +00:00
|
|
|
body: "[US visa bot] " + message,
|
2024-04-22 07:36:09 +00:00
|
|
|
})
|
|
|
|
|
.then(() => console.log('Notification sent'))
|
|
|
|
|
.catch(e => console.error(e));
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-24 07:23:35 +00:00
|
|
|
function getRandomInt(max) {
|
|
|
|
|
return Math.floor(Math.random() * Math.floor(max));
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-27 06:16:59 +00:00
|
|
|
|
|
|
|
|
function getJitter(frequency) {
|
|
|
|
|
return frequency * MINUTE * RANDOM_JITTER;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-24 07:23:35 +00:00
|
|
|
function getFutureDate(minutes, maxRandomSeconds = 0) {
|
|
|
|
|
// return date some amount of minutes in future plus random amount of seconds
|
|
|
|
|
let futureDate = new Date();
|
2024-04-27 06:16:59 +00:00
|
|
|
futureDate.setSeconds(futureDate.getSeconds() + minutes * MINUTE + getRandomInt(maxRandomSeconds));
|
2024-04-24 07:23:35 +00:00
|
|
|
return futureDate.toISOString();
|
|
|
|
|
}
|
2024-04-21 04:03:26 +00:00
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
function hiddenPassword(cfg) {
|
2024-04-25 02:46:05 +00:00
|
|
|
return {
|
2024-04-28 02:20:50 +00:00
|
|
|
...cfg,
|
|
|
|
|
password: cfg.password.replace(/./g, "*"),
|
2024-04-25 02:46:05 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function diffObjects(obj1, obj2) {
|
|
|
|
|
let diff = {};
|
|
|
|
|
|
|
|
|
|
for (let key in obj1) {
|
|
|
|
|
if (typeof obj1[key] === 'object' && obj1[key] !== null && obj2[key]) {
|
|
|
|
|
let nestedDiff = diffObjects(obj1[key], obj2[key]);
|
|
|
|
|
if (Object.keys(nestedDiff).length > 0) {
|
|
|
|
|
diff[key] = nestedDiff;
|
|
|
|
|
}
|
|
|
|
|
} else if (obj2[key] && obj1[key] !== obj2[key]) {
|
|
|
|
|
diff[key] = [obj1[key], obj2[key]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let key in obj2) {
|
|
|
|
|
if (obj1[key] === undefined && obj2[key] !== undefined) {
|
|
|
|
|
diff[key] = [undefined, obj2[key]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return diff;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
function hasAllKeys(obj, keys) {
|
|
|
|
|
return keys.every(k => obj.hasOwnProperty(k));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ensureRequiredConsulateProperties(consCtx, consCfg, frequency = 1) {
|
|
|
|
|
let reqCfgKeys = ["isSelected", "autoBook"];
|
|
|
|
|
let reqStateKeys = ["id", "bestDate", "currentDate", "nextCheckAt"];
|
|
|
|
|
|
|
|
|
|
for (let c in consCtx) {
|
|
|
|
|
if (!hasAllKeys(consCtx[c], reqStateKeys)) {
|
|
|
|
|
consCtx[c] = {
|
|
|
|
|
"id": consCtx[c]?.id || null,
|
|
|
|
|
"bestDate": consCtx[c]?.bestDate || null,
|
|
|
|
|
"currentDate": consCtx[c]?.currentDate || null,
|
|
|
|
|
"nextCheckAt": consCtx[c]?.nextCheckAt || getFutureDate(0, frequency * MINUTE),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (!consCfg[c] || !hasAllKeys(consCfg[c], reqCfgKeys)) {
|
|
|
|
|
consCfg[c] = {
|
|
|
|
|
"isSelected": consCfg[c]?.isSelected || false,
|
|
|
|
|
"autoBook": consCfg[c]?.autoBook || false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [consCtx, consCfg];
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 05:44:54 +00:00
|
|
|
function getPathnameParts(pathname) {
|
|
|
|
|
let pathParts = pathname.split('/');
|
|
|
|
|
let locale = pathParts[1] || 'en-us';
|
|
|
|
|
let visaType = pathParts[2] || 'niv';
|
2024-04-27 05:35:51 +00:00
|
|
|
return [ locale, visaType ];
|
2024-04-26 05:44:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-21 04:03:26 +00:00
|
|
|
function isSignInPage() {
|
2024-04-22 00:09:33 +00:00
|
|
|
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/users\/sign_in/));
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isLoggedOutPage() {
|
2024-04-22 00:09:33 +00:00
|
|
|
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv$/));
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isDashboardPage() {
|
2024-04-22 00:09:33 +00:00
|
|
|
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/groups\/\d+/));
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isAppointmentPage() {
|
2024-04-22 00:09:33 +00:00
|
|
|
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/schedule\/\d+\/appointment$/));
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isConfirmationPage() {
|
2024-04-22 00:09:33 +00:00
|
|
|
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/schedule\/\d+\/appointment\/instructions$/));
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isNotEnglishPage() {
|
2024-04-22 00:09:33 +00:00
|
|
|
return Boolean(isSignInPage()
|
2024-04-21 04:03:26 +00:00
|
|
|
|| isLoggedOutPage()
|
|
|
|
|
|| isDashboardPage()
|
|
|
|
|
|| isAppointmentPage()
|
|
|
|
|
|| isConfirmationPage()
|
|
|
|
|
) && !window.location.pathname.match(/^\/en-/);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 05:44:54 +00:00
|
|
|
async function handleHttpError(e) {
|
|
|
|
|
if (e.response && e.response.status === 401) {
|
|
|
|
|
msg = "Unauthorized";
|
|
|
|
|
console.log(msg);
|
|
|
|
|
await sendNotification(msg);
|
|
|
|
|
await logOut();
|
|
|
|
|
} else {
|
|
|
|
|
console.error(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-21 04:03:26 +00:00
|
|
|
async function switchToEnglishPage() {
|
|
|
|
|
window.location.href(window.location.pathname.replace(/^\/\w{2}-{2}/, '/en-us'));
|
2024-04-21 06:50:49 +00:00
|
|
|
await delay(PAGE_WAIT_TIME);
|
2024-04-21 04:03:26 +00:00
|
|
|
// Should be on English page
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 05:44:54 +00:00
|
|
|
async function logOut() {
|
|
|
|
|
let [locale, visaType] = getPathnameParts(window.location.pathname);
|
|
|
|
|
let signOutPath = `/${locale}/${visaType}/users/sign_out`;
|
|
|
|
|
window.location.href = window.location.origin + signOutPath;
|
|
|
|
|
await delay(PAGE_WAIT_TIME);
|
|
|
|
|
return isLoggedOutPage();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-21 04:03:26 +00:00
|
|
|
async function goToSignInPage() {
|
2024-04-26 05:44:54 +00:00
|
|
|
let [locale, visaType] = getPathnameParts(window.location.pathname);
|
|
|
|
|
let signInPath = `/${locale}/${visaType}/users/sign_in`;
|
|
|
|
|
window.location.href = window.location.origin + signInPath;
|
2024-04-21 06:50:49 +00:00
|
|
|
await delay(PAGE_WAIT_TIME);
|
2024-04-22 00:09:33 +00:00
|
|
|
return isSignInPage();
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-26 05:44:54 +00:00
|
|
|
async function goToDashboardPage() {
|
|
|
|
|
let [locale, visaType] = getPathnameParts(window.location.pathname);
|
|
|
|
|
let dashboardPath = `/${locale}/${visaType}/account`;
|
|
|
|
|
window.location.href = window.location.origin + dashboardPath;
|
|
|
|
|
await delay(PAGE_WAIT_TIME);
|
|
|
|
|
return isDashboardPage();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
async function enterCredentials(username, password) {
|
2024-04-28 05:47:35 +00:00
|
|
|
// close warning modal
|
|
|
|
|
let modalElement = Array.from(document.querySelectorAll('div'))
|
|
|
|
|
.find(d => d.textContent.includes('You need to sign in or sign up before continuing.'));
|
|
|
|
|
if (modalElement && window.getComputedStyle(modalElement).display !== 'none') {
|
|
|
|
|
let buttons = Array.from(modalElement.querySelectorAll('button'));
|
|
|
|
|
let okButton = buttons.find(b => b.textContent === 'OK');
|
|
|
|
|
if (okButton) {
|
|
|
|
|
okButton.click();
|
|
|
|
|
await delay(PAGE_WAIT_TIME);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
document.getElementById("user_email").value = username;
|
|
|
|
|
document.getElementById("user_password").value = password;
|
2024-04-21 06:50:49 +00:00
|
|
|
let policyConfirmed = document.querySelector('[for="policy_confirmed"]');
|
|
|
|
|
if (!policyConfirmed.getElementsByTagName('input')[0].checked) {
|
|
|
|
|
policyConfirmed.click();
|
|
|
|
|
}
|
2024-04-21 04:03:26 +00:00
|
|
|
document.querySelector("#sign_in_form input[type=submit]").click();
|
|
|
|
|
|
2024-04-21 06:50:49 +00:00
|
|
|
await delay(PAGE_WAIT_TIME);
|
2024-04-22 00:09:33 +00:00
|
|
|
return isDashboardPage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getAppointmentId() {
|
|
|
|
|
let appointments = document.querySelectorAll("p.consular-appt [href]")
|
|
|
|
|
if (appointments.length > 1) {
|
|
|
|
|
console.log("Multiple appointments found, taking the first one");
|
2024-04-21 06:50:49 +00:00
|
|
|
}
|
2024-04-22 00:09:33 +00:00
|
|
|
let apptId = appointments[0].href.replace(/\D/g, "");
|
|
|
|
|
return apptId;
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
2024-04-20 05:14:53 +00:00
|
|
|
|
2024-04-22 07:19:26 +00:00
|
|
|
async function getConsulates() {
|
|
|
|
|
let consulatesSelect = document.querySelector("#appointments_consulate_appointment_facility_id")
|
|
|
|
|
let consulatesDict = {};
|
2024-04-24 07:23:35 +00:00
|
|
|
|
2024-04-22 07:19:26 +00:00
|
|
|
for (let option of consulatesSelect.options) {
|
2024-04-24 07:23:35 +00:00
|
|
|
if (!option.value) continue; // skip empty option
|
2024-04-22 07:19:26 +00:00
|
|
|
consulatesDict[option.text] = {
|
|
|
|
|
"id": parseInt(option.value),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return consulatesDict;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getAvailableDates(consulateId) {
|
2024-04-27 21:05:08 +00:00
|
|
|
let addressUri = window.location.pathname + `/address/${consulateId}`
|
|
|
|
|
fetch(addressUri, { headers: { "x-requested-with": "XMLHttpRequest" } })
|
|
|
|
|
.catch(error => console.error('Error:', error));
|
|
|
|
|
|
|
|
|
|
let datesUri = window.location.pathname + `/days/${consulateId}.json?appointments[expedite]=false`
|
|
|
|
|
let dates = fetch(datesUri, { headers: {
|
|
|
|
|
"x-requested-with": "XMLHttpRequest",
|
|
|
|
|
"accept": "application/json, text/javascript, */*; q=0.01",
|
|
|
|
|
// "cache-control": "no-cache",
|
|
|
|
|
}})
|
2024-04-27 06:45:56 +00:00
|
|
|
.catch(async e => {
|
|
|
|
|
await handleHttpError(e);
|
|
|
|
|
throw e;
|
|
|
|
|
})
|
2024-04-28 05:48:50 +00:00
|
|
|
.then(d => d.json())
|
2024-04-22 07:19:26 +00:00
|
|
|
.then(data => {
|
|
|
|
|
let dateList = data.map(item => item.date);
|
|
|
|
|
dateList.sort();
|
|
|
|
|
return dateList;
|
|
|
|
|
})
|
2024-04-27 06:45:56 +00:00
|
|
|
.catch(e => null);
|
2024-04-22 07:19:26 +00:00
|
|
|
return dates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function filterDates(dates, currentAppt, deltaFromAppt, deltaFromNow) {
|
|
|
|
|
let maxDate = new Date(currentAppt);
|
|
|
|
|
maxDate.setDate(maxDate.getDate() - deltaFromAppt);
|
|
|
|
|
let minDate = new Date();
|
|
|
|
|
minDate.setDate(minDate.getDate() + deltaFromNow);
|
|
|
|
|
let availableDates = dates.filter(d => new Date(d) >= minDate && new Date(d) < maxDate);
|
|
|
|
|
return availableDates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getAvailableTimes(consulateId, date) {
|
|
|
|
|
let uri = window.location.pathname + `/times/${consulateId}.json?date=${date}&appointments[expedite]=false`
|
2024-04-28 05:48:50 +00:00
|
|
|
let times = fetch(uri, { headers: { "x-requested-with": "XMLHttpRequest" } })
|
2024-04-27 06:45:56 +00:00
|
|
|
.catch(async e => {
|
|
|
|
|
await handleHttpError(e);
|
|
|
|
|
throw e;
|
|
|
|
|
})
|
2024-04-28 05:48:50 +00:00
|
|
|
.then(d => d.json())
|
2024-04-22 07:19:26 +00:00
|
|
|
.then(data => data.available_times)
|
2024-04-27 06:45:56 +00:00
|
|
|
.catch(e => null);
|
2024-04-22 07:19:26 +00:00
|
|
|
return times;
|
2024-04-20 05:14:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function runner() {
|
2024-04-21 06:50:49 +00:00
|
|
|
if (isRunning) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
isRunning = true;
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
let prev_cfg = Object.assign({}, cfg);
|
|
|
|
|
let prev_ctx = Object.assign({}, ctx);
|
2024-04-20 05:14:53 +00:00
|
|
|
|
|
|
|
|
let result = await new Promise(resolve => chrome.storage.local.get(null, resolve));
|
2024-04-28 02:20:50 +00:00
|
|
|
cfg.activate = result['cfg_activate'] || false;
|
|
|
|
|
cfg.username = result['cfg_username'] || "";
|
|
|
|
|
cfg.password = result['cfg_password'] || "";
|
|
|
|
|
cfg.frequency = parseFloat(result['cfg_frequency'] || 1);
|
|
|
|
|
cfg.consulates = result['cfg_consulates'] || {};
|
|
|
|
|
cfg.deltaAppt = result['cfg_deltaAppt'] || 1;
|
|
|
|
|
cfg.deltaNow = result['cfg_deltaNow'] || 1;
|
|
|
|
|
|
|
|
|
|
ctx.apptId = result['ctx_apptId'] || null;
|
|
|
|
|
ctx.signinAttempts = result['ctx_signinAttempts'] || 0;
|
|
|
|
|
ctx.currentAppt = result['ctx_currentAppt'] || { consulate: null, date: null };
|
|
|
|
|
ctx.consulates = result['ctx_consulates'] || {};
|
|
|
|
|
ctx.statusMsg = result['ctx_statusMsg'] || "";
|
|
|
|
|
|
|
|
|
|
[ctx.consulates, cfg.consulates] = ensureRequiredConsulateProperties(
|
|
|
|
|
ctx.consulates, cfg.consulates, cfg.frequency
|
|
|
|
|
);
|
|
|
|
|
await chrome.storage.local.set({ "cfg_consulates": cfg.consulates });
|
|
|
|
|
await chrome.storage.local.set({ "ctx_consulates": ctx.consulates });
|
|
|
|
|
|
|
|
|
|
if (prev_cfg.activate === undefined) {
|
|
|
|
|
console.log('Reading config: ' + JSON.stringify(hiddenPassword(cfg)));
|
2024-04-21 06:50:49 +00:00
|
|
|
isRunning = false;
|
2024-04-20 05:14:53 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
for (let key in cfg) {
|
|
|
|
|
if (cfg.hasOwnProperty(key)
|
|
|
|
|
&& !_.isEqual(cfg[key], prev_cfg[key])) {
|
2024-04-25 02:46:05 +00:00
|
|
|
msg = `Config change: ${key}`
|
|
|
|
|
if (key === 'password') {
|
|
|
|
|
msg += ', ******** => ********';
|
|
|
|
|
} else if (key === 'consulates') {
|
2024-04-28 02:20:50 +00:00
|
|
|
msg += `, ${JSON.stringify(diffObjects(cfg[key], prev_cfg[key]))}`;
|
2024-04-25 02:46:05 +00:00
|
|
|
} else {
|
2024-04-28 02:20:50 +00:00
|
|
|
msg += `, ${prev_cfg[key]} => ${cfg[key]}`;
|
2024-04-25 02:46:05 +00:00
|
|
|
}
|
|
|
|
|
console.log(msg);
|
2024-04-21 04:03:26 +00:00
|
|
|
|
2024-04-24 07:23:35 +00:00
|
|
|
// reduce wait times for consulates if frequency is increased
|
2024-04-20 05:14:53 +00:00
|
|
|
if (key === 'frequency') {
|
2024-04-24 07:23:35 +00:00
|
|
|
let wasChanged = false;
|
2024-04-28 02:20:50 +00:00
|
|
|
for (let c in ctx.consulates) {
|
|
|
|
|
let newNextCheckAt = getFutureDate(cfg.frequency, getJitter(cfg.frequency));
|
|
|
|
|
if (ctx.consulates[c].nextCheckAt > newNextCheckAt) {
|
|
|
|
|
console.log(`Reducing wait time for ${c} from ${ctx.consulates[c].nextCheckAt} to ${newNextCheckAt}`);
|
|
|
|
|
ctx.consulates[c].nextCheckAt = newNextCheckAt;
|
2024-04-24 07:23:35 +00:00
|
|
|
wasChanged = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-25 02:46:05 +00:00
|
|
|
// TODO maybe causes additional requests
|
2024-04-24 07:23:35 +00:00
|
|
|
if (wasChanged) {
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_consulates": ctx.consulates });
|
2024-04-20 05:14:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-04-21 04:03:26 +00:00
|
|
|
|
|
|
|
|
if (key === 'activate') {
|
2024-04-28 02:20:50 +00:00
|
|
|
if (cfg[key]) {
|
2024-04-21 06:50:49 +00:00
|
|
|
console.log('Activating extension');
|
2024-04-21 04:03:26 +00:00
|
|
|
} else {
|
2024-04-21 06:50:49 +00:00
|
|
|
console.log('Deactivating extension');
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": "inactive" });
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// clear signin attempts when credentials are changed
|
2024-04-28 02:20:50 +00:00
|
|
|
if (key === 'username' && cfg[key]
|
|
|
|
|
|| key === 'password' && cfg[key]) {
|
|
|
|
|
ctx.signinAttempts = 0;
|
|
|
|
|
await chrome.storage.local.set({ "ctx_signinAttempts": ctx.signinAttempts });
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
2024-04-20 05:14:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
if (!cfg.activate) {
|
2024-04-21 06:50:49 +00:00
|
|
|
isRunning = false;
|
2024-04-20 05:14:53 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
if (cfg.username === "" || cfg.password === "") {
|
2024-04-21 06:50:49 +00:00
|
|
|
console.log('Username or password is empty');
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": "missing credentials" });
|
2024-04-21 06:50:49 +00:00
|
|
|
isRunning = false;
|
2024-04-20 05:14:53 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
if (cfg.frequency <= 0) {
|
2024-04-21 06:50:49 +00:00
|
|
|
console.log('Frequency is 0 or negative');
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": "invalid frequency" });
|
2024-04-21 06:50:49 +00:00
|
|
|
isRunning = false;
|
2024-04-20 05:14:53 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 06:14:38 +00:00
|
|
|
// Check if current time is between 11pm and 9am UTC (4pm - 2am PST)
|
|
|
|
|
let now = new Date();
|
|
|
|
|
let currentHourUTC = now.getUTCHours();
|
|
|
|
|
if (currentHourUTC >= 23 || currentHourUTC < 9) {
|
|
|
|
|
// Continue running the code
|
|
|
|
|
} else {
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": "not operational hours" });
|
2024-04-26 06:14:38 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 06:00:44 +00:00
|
|
|
if (isFoundAppointment) {
|
|
|
|
|
// don't do anything if appointment is found and manual booking is required
|
|
|
|
|
if (isAppointmentPage()) {
|
|
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
// we're not on an appointment page, so seems like we're done, and we can check dates again
|
|
|
|
|
} else {
|
|
|
|
|
isFoundAppointment = false;
|
|
|
|
|
}
|
2024-04-26 06:18:36 +00:00
|
|
|
}
|
2024-04-26 06:00:44 +00:00
|
|
|
|
2024-04-22 00:09:33 +00:00
|
|
|
if (isNotEnglishPage()) {
|
2024-04-21 04:03:26 +00:00
|
|
|
await switchToEnglishPage();
|
|
|
|
|
}
|
2024-04-20 05:14:53 +00:00
|
|
|
|
2024-04-22 00:09:33 +00:00
|
|
|
if (isLoggedOutPage()) {
|
|
|
|
|
if (!await goToSignInPage()) {
|
2024-04-23 06:30:55 +00:00
|
|
|
msg = 'Failed to go to sign in page';
|
|
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-22 00:09:33 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-22 07:19:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (isSignInPage()) {
|
2024-04-22 00:09:33 +00:00
|
|
|
// Prevent brute forcing
|
2024-04-28 02:20:50 +00:00
|
|
|
if (ctx.signinAttempts >= MAX_SIGNIN_ATTEMPTS) {
|
2024-04-28 05:47:57 +00:00
|
|
|
if (prev_ctx.signinAttempts < MAX_SIGNIN_ATTEMPTS) {
|
|
|
|
|
msg = 'Too many sign in attempts';
|
|
|
|
|
console.log(msg);
|
|
|
|
|
await sendNotification(msg);
|
|
|
|
|
}
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": "too many sign in attempts" });
|
2024-04-22 00:09:33 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sign in
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = 'Signing in attempt: ' + ctx.signinAttempts;
|
2024-04-23 06:30:55 +00:00
|
|
|
console.log(msg)
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-28 05:48:07 +00:00
|
|
|
let signedIn = await enterCredentials(cfg.username, cfg.password);
|
2024-04-28 02:20:50 +00:00
|
|
|
ctx.signinAttempts += 1;
|
|
|
|
|
await chrome.storage.local.set({ "ctx_signinAttempts": ctx.signinAttempts });
|
2024-04-22 00:09:33 +00:00
|
|
|
if (!signedIn) {
|
2024-04-23 06:30:55 +00:00
|
|
|
msg = 'Failed to sign in';
|
|
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-22 00:09:33 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-22 07:19:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (isDashboardPage()) {
|
2024-04-22 00:09:33 +00:00
|
|
|
// reset signin attempts when successfully logged in
|
2024-04-28 02:20:50 +00:00
|
|
|
ctx.signinAttempts = 0;
|
|
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": "fetching appointment info" });
|
|
|
|
|
await chrome.storage.local.set({ "ctx_signinAttempts": ctx.signinAttempts });
|
2024-04-22 00:09:33 +00:00
|
|
|
|
|
|
|
|
// get appointmentId
|
2024-04-28 02:20:50 +00:00
|
|
|
if (!ctx.apptId) {
|
|
|
|
|
ctx.apptId = await getAppointmentId();
|
|
|
|
|
if (ctx.apptId) {
|
|
|
|
|
console.log(`Appointment ID: ${ctx.apptId}`);
|
|
|
|
|
await chrome.storage.local.set({ "ctx_apptId": ctx.apptId });
|
2024-04-22 00:09:33 +00:00
|
|
|
} else {
|
2024-04-23 06:30:55 +00:00
|
|
|
msg = 'No appointments found';
|
|
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-22 00:09:33 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get current appointment date
|
2024-04-28 02:20:50 +00:00
|
|
|
let apptInfoCard = document.querySelector("p.consular-appt [href*='" + ctx.apptId + "']").parentNode.parentNode.parentNode
|
2024-04-23 08:42:15 +00:00
|
|
|
if (!apptInfoCard.querySelector("h4").innerText.match(/Attend Appointment/)) {
|
2024-04-23 06:30:55 +00:00
|
|
|
msg = 'Appointment not available';
|
|
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-22 00:09:33 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-04-23 08:42:15 +00:00
|
|
|
let apptInfo = apptInfoCard.querySelector("p.consular-appt").innerText;
|
|
|
|
|
let apptConsulate = (apptInfo.match(/at (\w+)/) || [])[1];
|
|
|
|
|
let apptDate = new Date(apptInfo.match(/\d{1,2} \w+, \d{4}/)[0]);
|
2024-04-22 00:09:33 +00:00
|
|
|
apptDate = apptDate.toISOString().slice(0, 10);
|
2024-04-28 02:20:50 +00:00
|
|
|
if (!apptDate || !apptConsulate) {
|
|
|
|
|
msg = 'Failed to fetch appointment date or consulate';
|
|
|
|
|
console.log(msg);
|
|
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
|
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (apptDate != ctx.currentAppt.date || apptConsulate != ctx.currentAppt.consulate) {
|
2024-04-24 07:23:35 +00:00
|
|
|
console.log(`New appointment date: ${apptDate} at ${apptConsulate},
|
2024-04-28 02:20:50 +00:00
|
|
|
old: ${ctx.currentAppt.consulate} at ${ctx.currentAppt.date}`);
|
|
|
|
|
ctx.currentAppt = { consulate: apptConsulate, date: apptDate };
|
|
|
|
|
await chrome.storage.local.set({ "ctx_currentAppt": ctx.currentAppt });
|
2024-04-22 00:09:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// go to appointment page
|
2024-04-24 07:23:35 +00:00
|
|
|
let apptLink = apptInfoCard.querySelector("p.consular-appt [href]")
|
|
|
|
|
.getAttribute("href").replace("/addresses/consulate", "/appointment");
|
2024-04-22 00:09:33 +00:00
|
|
|
window.location.href = apptLink;
|
|
|
|
|
await delay(PAGE_WAIT_TIME);
|
2024-04-21 04:03:26 +00:00
|
|
|
}
|
2024-04-22 07:19:26 +00:00
|
|
|
|
|
|
|
|
else if (isAppointmentPage()) {
|
2024-04-23 08:09:10 +00:00
|
|
|
// if no apptDate, fetch it from dashboard page
|
2024-04-28 02:20:50 +00:00
|
|
|
if (!ctx.currentAppt.date) {
|
2024-04-23 08:09:10 +00:00
|
|
|
console.log('No appointment date is set, going back to dashboard');
|
2024-04-26 05:44:54 +00:00
|
|
|
await goToDashboardPage();
|
2024-04-23 08:09:10 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-22 07:19:26 +00:00
|
|
|
let applicantForm = document.querySelector('form[action*="' + window.location.pathname + '"]');
|
|
|
|
|
if (applicantForm && applicantForm.method.toLowerCase() == "get") {
|
|
|
|
|
applicantForm.submit();
|
|
|
|
|
await delay(PAGE_WAIT_TIME);
|
|
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
// TODO maybe it's a rare case
|
2024-04-22 07:19:26 +00:00
|
|
|
if (!document.getElementById("consulate_date_time")) {
|
2024-04-23 06:30:55 +00:00
|
|
|
msg = 'No available appointments';
|
|
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-22 07:19:26 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
if (!ctx.consulates || Object.keys(ctx.consulates).length == 0) {
|
|
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": "Fetching consulates" });
|
|
|
|
|
ctx.consulates = await getConsulates();
|
|
|
|
|
if (!ctx.consulates || Object.keys(ctx.consulates).length == 0) {
|
|
|
|
|
msg = "No consulates found";
|
|
|
|
|
console.log(msg);
|
|
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
|
|
|
|
sendNotification(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[ctx.consulates, cfg.consulates] = ensureRequiredConsulateProperties(
|
|
|
|
|
ctx.consulates, cfg.consulates, cfg.frequency
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// if no consulates are selected, select one which we have appointment with
|
|
|
|
|
let selectedConsulates = Object.keys(ctx.consulates).filter(c => cfg.consulates[c].isSelected);
|
|
|
|
|
if (selectedConsulates.length == 0) {
|
|
|
|
|
for (let c in cfg.consulates) {
|
|
|
|
|
if (c === ctx.currentAppt.consulate) {
|
|
|
|
|
cfg.consulates[c].isSelected = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
await chrome.storage.local.set({ "cfg_consulates": cfg.consulates });
|
|
|
|
|
await chrome.storage.local.set({ "ctx_consulates": ctx.consulates });
|
2024-04-22 07:19:26 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO choose ASC facility
|
|
|
|
|
// document.querySelector("#appointments_asc_appointment_facility_id [selected]").innerText
|
|
|
|
|
|
|
|
|
|
// for each selected consulate check available dates
|
2024-04-28 02:20:50 +00:00
|
|
|
let selectedConsulates = Object.keys(ctx.consulates).filter(c => cfg.consulates[c].isSelected);
|
2024-04-22 07:19:26 +00:00
|
|
|
if (selectedConsulates.length == 0) {
|
2024-04-23 06:30:55 +00:00
|
|
|
msg = 'No selected consulates found';
|
|
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-22 07:19:26 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 05:49:46 +00:00
|
|
|
let processedConsulates = 0;
|
2024-04-28 02:20:50 +00:00
|
|
|
for (let c of selectedConsulates) {
|
2024-04-26 05:49:46 +00:00
|
|
|
// only one consulate per run
|
|
|
|
|
if (processedConsulates > 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-04-24 07:23:35 +00:00
|
|
|
// skip if not time to check
|
2024-04-28 02:20:50 +00:00
|
|
|
if (ctx.consulates[c].nextCheckAt > new Date().toISOString()) {
|
2024-04-24 07:23:35 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 05:49:46 +00:00
|
|
|
processedConsulates += 1;
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = `Checking dates for ${c}`;
|
2024-04-26 06:33:26 +00:00
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
|
|
|
|
let availDates = await getAvailableDates(ctx.consulates[c].id);
|
|
|
|
|
ctx.consulates[c].nextCheckAt = getFutureDate(cfg.frequency, getJitter(cfg.frequency));
|
2024-04-22 07:19:26 +00:00
|
|
|
|
|
|
|
|
if (!availDates) {
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = `Failed to fetch available dates in ${c}`;
|
2024-04-23 06:30:55 +00:00
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-24 07:23:35 +00:00
|
|
|
continue;
|
2024-04-22 07:19:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if empty list, either we're banned or non operational hours or dead consulate
|
2024-04-24 07:23:35 +00:00
|
|
|
// wait for some time before checking again
|
2024-04-26 06:14:38 +00:00
|
|
|
let now = new Date();
|
|
|
|
|
let currentHourUTC = now.getUTCHours();
|
|
|
|
|
let currentMinuteUTC = now.getUTCMinutes();
|
|
|
|
|
|
2024-04-22 07:19:26 +00:00
|
|
|
if (availDates.length == 0) {
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = `No available dates in ${c}, probably banned`;
|
2024-04-23 06:30:55 +00:00
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
2024-04-26 06:14:38 +00:00
|
|
|
|
|
|
|
|
// Only set SOFT_BAN_TIMEOUT if it's not the first 5 minutes of 23pm UTC
|
|
|
|
|
if (!(currentHourUTC === 23 && currentMinuteUTC < 5)) {
|
2024-04-28 02:20:50 +00:00
|
|
|
ctx.consulates[c].nextCheckAt = getFutureDate(SOFT_BAN_TIMEOUT, getJitter(cfg.frequency));
|
2024-04-26 06:14:38 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-24 07:23:35 +00:00
|
|
|
continue;
|
2024-04-22 07:19:26 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = `Available dates for ${c}: ${availDates.slice(0, 5)}`;
|
2024-04-25 02:46:05 +00:00
|
|
|
if (availDates.length > 5) {
|
|
|
|
|
msg += ` and ${availDates.length - 10} more`;
|
|
|
|
|
}
|
|
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
ctx.consulates[c].currentDate = availDates[0];
|
|
|
|
|
if (!ctx.consulates[c].bestDate || availDates[0] < ctx.consulates[c].bestDate) {
|
|
|
|
|
console.log(`New record for ${c}: ${availDates[0]}`);
|
|
|
|
|
ctx.consulates[c].bestDate = availDates[0];
|
2024-04-23 06:58:44 +00:00
|
|
|
}
|
2024-04-22 07:19:26 +00:00
|
|
|
|
|
|
|
|
// filter dates with our requests
|
2024-04-28 02:20:50 +00:00
|
|
|
let filteredDates = await filterDates(availDates, ctx.currentAppt.date, cfg.deltaAppt, cfg.deltaNow);
|
2024-04-22 07:19:26 +00:00
|
|
|
if (!filteredDates.length) {
|
2024-04-28 02:20:50 +00:00
|
|
|
console.log(`No better dates in ${c}, currently available ${availDates[0]}`);
|
|
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": `no better dates in ${c}`});
|
2024-04-24 07:23:35 +00:00
|
|
|
continue;
|
2024-04-22 07:19:26 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
console.log(`Dates worth rescheduling in ${c}: ${filteredDates}`);
|
2024-04-22 07:19:26 +00:00
|
|
|
let chosenDate = filteredDates[0];
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": `Found in ${c} better date ${chosenDate}`});
|
2024-04-22 07:19:26 +00:00
|
|
|
|
|
|
|
|
// fill date in reschedule form
|
|
|
|
|
await delay(PAGE_WAIT_TIME);
|
|
|
|
|
document.getElementById("appointments_consulate_appointment_date").value = chosenDate;
|
|
|
|
|
document.getElementById("appointments_consulate_appointment_time").innerHTML = "<option></option>"
|
|
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
let availTimes = await getAvailableTimes(ctx.consulates[c].id, chosenDate);
|
2024-04-22 07:19:26 +00:00
|
|
|
if (!availTimes) {
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = `Failed to fetch available timeslots in ${c} at ${chosenDate}`;
|
2024-04-23 06:30:55 +00:00
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg});
|
2024-04-24 07:23:35 +00:00
|
|
|
continue;
|
2024-04-22 07:19:26 +00:00
|
|
|
}
|
|
|
|
|
if (availTimes.length == 0) {
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = `No timeslots in ${c} at ${chosenDate}`;
|
2024-04-23 06:30:55 +00:00
|
|
|
console.log(msg);
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "ctx_statusMsg": msg});
|
2024-04-24 07:23:35 +00:00
|
|
|
continue;
|
2024-04-22 07:19:26 +00:00
|
|
|
}
|
2024-04-28 02:20:50 +00:00
|
|
|
console.log(`Available timeslots in ${c} at ${chosenDate}: ${availTimes}`);
|
2024-04-22 07:19:26 +00:00
|
|
|
let chosenTime = availTimes[0];
|
|
|
|
|
|
|
|
|
|
// fill timeslot in reschedule form
|
|
|
|
|
await delay(PAGE_WAIT_TIME);
|
|
|
|
|
document.getElementById("appointments_consulate_appointment_time").innerHTML = `<option value='${chosenTime}'>${chosenTime}</option>`;
|
|
|
|
|
document.getElementById("appointments_consulate_appointment_time").value = chosenTime;
|
|
|
|
|
|
|
|
|
|
// TODO process ASC facilities here
|
|
|
|
|
|
|
|
|
|
await delay(PAGE_WAIT_TIME);
|
|
|
|
|
document.getElementById("appointments_submit").removeAttribute("disabled");
|
|
|
|
|
document.getElementById("appointments_submit").click();
|
2024-04-22 07:36:09 +00:00
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = `Found better appointment in ${c} at ${chosenDate} ${chosenTime}`;
|
2024-04-23 06:20:43 +00:00
|
|
|
console.log(msg);
|
2024-04-26 06:00:44 +00:00
|
|
|
await sendNotification(msg);
|
2024-04-23 06:20:43 +00:00
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
if (!cfg.consulates[c].autoBook) {
|
2024-04-26 06:00:44 +00:00
|
|
|
isFoundAppointment = true;
|
2024-04-25 00:01:31 +00:00
|
|
|
} else {
|
2024-04-23 06:20:43 +00:00
|
|
|
await delay(PAGE_WAIT_TIME);
|
2024-04-28 02:20:50 +00:00
|
|
|
msg = `Auto booking in ${c} at ${chosenDate} ${chosenTime}`;
|
2024-04-25 00:01:31 +00:00
|
|
|
console.log(msg);
|
|
|
|
|
await sendNotification(msg);
|
2024-04-23 06:20:43 +00:00
|
|
|
document.querySelector(".reveal-overlay:last-child [data-reveal] .button.alert").click();
|
|
|
|
|
}
|
|
|
|
|
} // end consulates loop
|
2024-04-22 07:19:26 +00:00
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
for (let c in ctx.consulates) {
|
|
|
|
|
if (!prev_ctx?.consulates?.[c]) continue;
|
|
|
|
|
if (ctx.consulates[c].nextCheckAt != prev_ctx.consulates[c].nextCheckAt) {
|
|
|
|
|
console.log(`Next check for ${c} at ${ctx.consulates[c].nextCheckAt}`);
|
2024-04-24 07:23:35 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-04-28 02:20:50 +00:00
|
|
|
await chrome.storage.local.set({ "cfg_consulates": cfg.consulates });
|
|
|
|
|
await chrome.storage.local.set({ "ctx_consulates": ctx.consulates });
|
2024-04-24 07:23:35 +00:00
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
2024-04-22 07:19:26 +00:00
|
|
|
}
|
2024-04-20 05:14:53 +00:00
|
|
|
|
2024-04-22 07:36:09 +00:00
|
|
|
else if (isConfirmationPage) {
|
2024-04-26 05:44:54 +00:00
|
|
|
// go back to dashboard after successful reschedule
|
2024-04-22 07:36:09 +00:00
|
|
|
await delay(PAGE_WAIT_TIME);
|
2024-04-28 02:20:50 +00:00
|
|
|
ctx.currentAppt = { consulate: null, date: null};
|
|
|
|
|
await chrome.storage.local.set({"ctx_currentAppt": ctx.currentAppt});
|
2024-04-23 08:09:10 +00:00
|
|
|
console.log('Rescheduled successfully');
|
2024-04-24 23:33:55 +00:00
|
|
|
|
2024-04-28 02:20:50 +00:00
|
|
|
// switch off autoBook for all consulates after successful reschedule
|
|
|
|
|
for (let c in cfg.consulates) {
|
|
|
|
|
cfg.consulates[c].autoBook = false;
|
2024-04-24 23:33:55 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-26 05:44:54 +00:00
|
|
|
await goToDashboardPage();
|
2024-04-22 07:36:09 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-21 06:50:49 +00:00
|
|
|
// console.log('runner done');
|
|
|
|
|
isRunning = false;
|
2024-04-20 05:14:53 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-28 05:48:24 +00:00
|
|
|
setInterval(runner, 500);
|