Compare commits
No commits in common. "master" and "v0.0.5" have entirely different histories.
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "not-a-rescheduler",
|
"name": "not-a-rescheduler",
|
||||||
"version": "0.0.10",
|
"version": "0.0.5",
|
||||||
"permissions": [ "storage", "tabs", "activeTab", "notifications", "declarativeContent" ],
|
"permissions": [ "storage", "tabs", "activeTab", "notifications", "declarativeContent" ],
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -148,19 +148,21 @@ function smoothTextChange(element, newText) {
|
|||||||
consCfg[c].autoBook = e.target.checked;
|
consCfg[c].autoBook = e.target.checked;
|
||||||
await chrome.storage.local.set({ "cfg_consulates": consCfg });
|
await chrome.storage.local.set({ "cfg_consulates": consCfg });
|
||||||
});
|
});
|
||||||
|
|
||||||
// update current & best dates
|
// update current & best dates
|
||||||
chrome.storage.onChanged.addListener((changes, area) => {
|
chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
if (changes.ctx_consulates) {
|
if (changes.ctx_consulates) {
|
||||||
let newCurrentDate = changes.ctx_consulates.newValue[c].currentDate || "-";
|
const newConsulates = changes.ctx_consulates.newValue;
|
||||||
let newBestDate = changes.ctx_consulates.newValue[c].bestDate || "-";
|
|
||||||
|
|
||||||
let el = document.getElementById(`currentDate-${cId}`);
|
if (newConsulates[c].currentDate) {
|
||||||
if (el && el.innerText != newCurrentDate)
|
let el = document.getElementById(`currentDate-${cId}`);
|
||||||
smoothTextChange(el, newCurrentDate);
|
if (el && el.innerText != newConsulates[c].currentDate)
|
||||||
el = document.getElementById(`bestDate-${cId}`);
|
smoothTextChange(el, newConsulates[c].currentDate);
|
||||||
if (el && el.innerText != newBestDate)
|
}
|
||||||
smoothTextChange(el, newBestDate);
|
if (newConsulates[c].bestDate) {
|
||||||
|
let el = document.getElementById(`bestDate-${cId}`);
|
||||||
|
if (el && el.innerText != newConsulates[c].bestDate)
|
||||||
|
smoothTextChange(el, newConsulates[c].bestDate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,9 @@ const pathnameRegex = /^\/\w{2}-\w{2}\/n?iv/;
|
|||||||
const MAX_SIGNIN_ATTEMPTS = 3;
|
const MAX_SIGNIN_ATTEMPTS = 3;
|
||||||
const PAGE_WAIT_TIME = 3792;
|
const PAGE_WAIT_TIME = 3792;
|
||||||
const MINUTE = 60;
|
const MINUTE = 60;
|
||||||
const RND_VAR_COEFF = 0.1;
|
const RANDOM_JITTER = 0.1;
|
||||||
const SOFT_BAN_TIMEOUT = 27;
|
const SOFT_BAN_TIMEOUT = 27;
|
||||||
const NOTIF_CHANNEL = "snegov";
|
const NOTIF_CHANNEL = "snegov_test"
|
||||||
const CHECK_OPER_HOURS = true;
|
|
||||||
const OPER_HOURS_START = 23;
|
|
||||||
const OPER_HOURS_END = 9;
|
|
||||||
|
|
||||||
let cfg = {
|
let cfg = {
|
||||||
activate: undefined,
|
activate: undefined,
|
||||||
@ -40,12 +37,11 @@ async function delay(ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendNotification(message, channel = NOTIF_CHANNEL) {
|
async function sendNotification(message, channel = NOTIF_CHANNEL) {
|
||||||
const msg = "[US visa bot] " + message
|
|
||||||
await fetch('https://ntfy.sh/' + channel, {
|
await fetch('https://ntfy.sh/' + channel, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: msg,
|
body: "[US visa bot] " + message,
|
||||||
})
|
})
|
||||||
.then(() => console.log('Notification sent: ' + msg))
|
.then(() => console.log('Notification sent'))
|
||||||
.catch(e => console.error(e));
|
.catch(e => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,20 +49,16 @@ function getRandomInt(max) {
|
|||||||
return Math.floor(Math.random() * Math.floor(max));
|
return Math.floor(Math.random() * Math.floor(max));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFutureDate(minutes, frequency) {
|
|
||||||
// return date some amount of minutes in future plus random amount of seconds
|
|
||||||
let futureDate = new Date();
|
|
||||||
const randomVariation = frequency * MINUTE * RND_VAR_COEFF;
|
|
||||||
const randomSign = Math.random() < 0.5 ? -1 : 1;
|
|
||||||
const offset = randomSign * getRandomInt(randomVariation);
|
|
||||||
|
|
||||||
futureDate.setSeconds(futureDate.getSeconds() + minutes * MINUTE + offset);
|
function getJitter(frequency) {
|
||||||
return futureDate.toISOString();
|
return frequency * MINUTE * RANDOM_JITTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNoBanTime(frequency = 5) {
|
function getFutureDate(minutes, maxRandomSeconds = 0) {
|
||||||
let now = new Date();
|
// return date some amount of minutes in future plus random amount of seconds
|
||||||
return (now.getHours() === OPER_HOURS_START && now.getMinutes() < frequency);
|
let futureDate = new Date();
|
||||||
|
futureDate.setSeconds(futureDate.getSeconds() + minutes * MINUTE + getRandomInt(maxRandomSeconds));
|
||||||
|
return futureDate.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hiddenPassword(cfg) {
|
function hiddenPassword(cfg) {
|
||||||
@ -113,7 +105,7 @@ function ensureRequiredConsulateProperties(consCtx, consCfg, frequency = 1) {
|
|||||||
"id": consCtx[c]?.id || null,
|
"id": consCtx[c]?.id || null,
|
||||||
"bestDate": consCtx[c]?.bestDate || null,
|
"bestDate": consCtx[c]?.bestDate || null,
|
||||||
"currentDate": consCtx[c]?.currentDate || null,
|
"currentDate": consCtx[c]?.currentDate || null,
|
||||||
"nextCheckAt": consCtx[c]?.nextCheckAt || getFutureDate(0, frequency),
|
"nextCheckAt": consCtx[c]?.nextCheckAt || getFutureDate(0, frequency * MINUTE),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!consCfg[c] || !hasAllKeys(consCfg[c], reqCfgKeys)) {
|
if (!consCfg[c] || !hasAllKeys(consCfg[c], reqCfgKeys)) {
|
||||||
@ -163,6 +155,17 @@ function isNotEnglishPage() {
|
|||||||
) && !window.location.pathname.match(/^\/en-/);
|
) && !window.location.pathname.match(/^\/en-/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function switchToEnglishPage() {
|
async function switchToEnglishPage() {
|
||||||
window.location.href(window.location.pathname.replace(/^\/\w{2}-{2}/, '/en-us'));
|
window.location.href(window.location.pathname.replace(/^\/\w{2}-{2}/, '/en-us'));
|
||||||
await delay(PAGE_WAIT_TIME);
|
await delay(PAGE_WAIT_TIME);
|
||||||
@ -241,31 +244,28 @@ async function getConsulates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getAvailableDates(consulateId) {
|
async function getAvailableDates(consulateId) {
|
||||||
const datesUri = window.location.pathname + `/days/${consulateId}.json?appointments[expedite]=false`
|
let addressUri = window.location.pathname + `/address/${consulateId}`
|
||||||
try {
|
fetch(addressUri, { headers: { "x-requested-with": "XMLHttpRequest" } })
|
||||||
const response = await fetch(datesUri, { headers: {
|
.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",
|
"x-requested-with": "XMLHttpRequest",
|
||||||
"accept": "application/json, text/javascript, */*; q=0.01",
|
"accept": "application/json, text/javascript, */*; q=0.01",
|
||||||
"cache-control": "no-cache",
|
// "cache-control": "no-cache",
|
||||||
}});
|
}})
|
||||||
|
.catch(async e => {
|
||||||
if (!response.ok) {
|
await handleHttpError(e);
|
||||||
if (response.status === 401) {
|
throw e;
|
||||||
console.log('Logged out due to 401 error');
|
})
|
||||||
await logOut();
|
.then(d => d.json())
|
||||||
}
|
.then(data => {
|
||||||
const body = await response.text();
|
let dateList = data.map(item => item.date);
|
||||||
throw new Error(`HTTP error ${response.status}: ${body}`);
|
dateList.sort();
|
||||||
}
|
return dateList;
|
||||||
|
})
|
||||||
const data = await response.json();
|
.catch(e => null);
|
||||||
const dateList = data.map(item => item.date);
|
return dates;
|
||||||
dateList.sort();
|
|
||||||
return dateList;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error:', e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filterDates(dates, currentAppt, deltaFromAppt, deltaFromNow) {
|
async function filterDates(dates, currentAppt, deltaFromAppt, deltaFromNow) {
|
||||||
@ -280,6 +280,10 @@ async function filterDates(dates, currentAppt, deltaFromAppt, deltaFromNow) {
|
|||||||
async function getAvailableTimes(consulateId, date) {
|
async function getAvailableTimes(consulateId, date) {
|
||||||
let uri = window.location.pathname + `/times/${consulateId}.json?date=${date}&appointments[expedite]=false`
|
let uri = window.location.pathname + `/times/${consulateId}.json?date=${date}&appointments[expedite]=false`
|
||||||
let times = fetch(uri, { headers: { "x-requested-with": "XMLHttpRequest" } })
|
let times = fetch(uri, { headers: { "x-requested-with": "XMLHttpRequest" } })
|
||||||
|
.catch(async e => {
|
||||||
|
await handleHttpError(e);
|
||||||
|
throw e;
|
||||||
|
})
|
||||||
.then(d => d.json())
|
.then(d => d.json())
|
||||||
.then(data => data.available_times)
|
.then(data => data.available_times)
|
||||||
.catch(e => null);
|
.catch(e => null);
|
||||||
@ -339,7 +343,7 @@ async function runner() {
|
|||||||
if (key === 'frequency') {
|
if (key === 'frequency') {
|
||||||
let wasChanged = false;
|
let wasChanged = false;
|
||||||
for (let c in ctx.consulates) {
|
for (let c in ctx.consulates) {
|
||||||
let newNextCheckAt = getFutureDate(cfg.frequency, cfg.frequency);
|
let newNextCheckAt = getFutureDate(cfg.frequency, getJitter(cfg.frequency));
|
||||||
if (ctx.consulates[c].nextCheckAt > newNextCheckAt) {
|
if (ctx.consulates[c].nextCheckAt > newNextCheckAt) {
|
||||||
console.log(`Reducing wait time for ${c} from ${ctx.consulates[c].nextCheckAt} to ${newNextCheckAt}`);
|
console.log(`Reducing wait time for ${c} from ${ctx.consulates[c].nextCheckAt} to ${newNextCheckAt}`);
|
||||||
ctx.consulates[c].nextCheckAt = newNextCheckAt;
|
ctx.consulates[c].nextCheckAt = newNextCheckAt;
|
||||||
@ -389,17 +393,15 @@ async function runner() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CHECK_OPER_HOURS) {
|
// Check if current time is between 11pm and 9am UTC (4pm - 2am PST)
|
||||||
// Check if current time is between 11pm and 9am UTC (4pm - 2am PST)
|
let now = new Date();
|
||||||
let now = new Date();
|
let currentHourUTC = now.getUTCHours();
|
||||||
let currentHourUTC = now.getUTCHours();
|
if (currentHourUTC >= 23 || currentHourUTC < 9) {
|
||||||
if (currentHourUTC >= OPER_HOURS_START || currentHourUTC < OPER_HOURS_END) {
|
// Continue running the code
|
||||||
// Continue running the code
|
} else {
|
||||||
} else {
|
await chrome.storage.local.set({ "ctx_statusMsg": "not operational hours" });
|
||||||
await chrome.storage.local.set({ "ctx_statusMsg": "not operational hours" });
|
isRunning = false;
|
||||||
isRunning = false;
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFoundAppointment) {
|
if (isFoundAppointment) {
|
||||||
@ -501,12 +503,8 @@ async function runner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (apptDate != ctx.currentAppt.date || apptConsulate != ctx.currentAppt.consulate) {
|
if (apptDate != ctx.currentAppt.date || apptConsulate != ctx.currentAppt.consulate) {
|
||||||
msg = `New appointment date: ${apptDate} at ${apptConsulate}`
|
console.log(`New appointment date: ${apptDate} at ${apptConsulate},
|
||||||
if (ctx.currentAppt.date != null) {
|
old: ${ctx.currentAppt.consulate} at ${ctx.currentAppt.date}`);
|
||||||
sendNotification(msg);
|
|
||||||
}
|
|
||||||
msg += `, was: ${ctx.currentAppt.consulate} at ${ctx.currentAppt.date}`
|
|
||||||
console.log(msg);
|
|
||||||
ctx.currentAppt = { consulate: apptConsulate, date: apptDate };
|
ctx.currentAppt = { consulate: apptConsulate, date: apptDate };
|
||||||
await chrome.storage.local.set({ "ctx_currentAppt": ctx.currentAppt });
|
await chrome.storage.local.set({ "ctx_currentAppt": ctx.currentAppt });
|
||||||
}
|
}
|
||||||
@ -592,19 +590,6 @@ async function runner() {
|
|||||||
if (processedConsulates > 0) {
|
if (processedConsulates > 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all soft bans in the first 5 minutes of the first operational hour
|
|
||||||
let now = new Date();
|
|
||||||
if (isNoBanTime(cfg.frequency)) {
|
|
||||||
let nextCheckAt = new Date(ctx.consulates[c].nextCheckAt);
|
|
||||||
let differenceInMinutes = (nextCheckAt.getTime() - now.getTime()) / (1000 * 60);
|
|
||||||
|
|
||||||
if (differenceInMinutes > cfg.frequency) {
|
|
||||||
console.log(`Resetting soft ban for ${c}`);
|
|
||||||
ctx.consulates[c].nextCheckAt = getFutureDate(0, cfg.frequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip if not time to check
|
// skip if not time to check
|
||||||
if (ctx.consulates[c].nextCheckAt > new Date().toISOString()) {
|
if (ctx.consulates[c].nextCheckAt > new Date().toISOString()) {
|
||||||
continue;
|
continue;
|
||||||
@ -616,7 +601,7 @@ async function runner() {
|
|||||||
console.log(msg);
|
console.log(msg);
|
||||||
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
||||||
let availDates = await getAvailableDates(ctx.consulates[c].id);
|
let availDates = await getAvailableDates(ctx.consulates[c].id);
|
||||||
ctx.consulates[c].nextCheckAt = getFutureDate(cfg.frequency, cfg.frequency);
|
ctx.consulates[c].nextCheckAt = getFutureDate(cfg.frequency, getJitter(cfg.frequency));
|
||||||
|
|
||||||
if (!availDates) {
|
if (!availDates) {
|
||||||
msg = `Failed to fetch available dates in ${c}`;
|
msg = `Failed to fetch available dates in ${c}`;
|
||||||
@ -627,16 +612,20 @@ async function runner() {
|
|||||||
|
|
||||||
// if empty list, either we're banned or non operational hours or dead consulate
|
// if empty list, either we're banned or non operational hours or dead consulate
|
||||||
// wait for some time before checking again
|
// wait for some time before checking again
|
||||||
|
let now = new Date();
|
||||||
|
let currentHourUTC = now.getUTCHours();
|
||||||
|
let currentMinuteUTC = now.getUTCMinutes();
|
||||||
|
|
||||||
if (availDates.length == 0) {
|
if (availDates.length == 0) {
|
||||||
msg = `No available dates in ${c}`;
|
msg = `No available dates in ${c}, probably banned`;
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
await chrome.storage.local.set({ "ctx_statusMsg": msg });
|
||||||
|
|
||||||
if (!isNoBanTime(cfg.frequency)) {
|
// Only set SOFT_BAN_TIMEOUT if it's not the first 5 minutes of 23pm UTC
|
||||||
console.log(`Setting soft ban for ${c}`);
|
if (!(currentHourUTC === 23 && currentMinuteUTC < 5)) {
|
||||||
ctx.consulates[c].nextCheckAt = getFutureDate(SOFT_BAN_TIMEOUT, cfg.frequency);
|
ctx.consulates[c].nextCheckAt = getFutureDate(SOFT_BAN_TIMEOUT, getJitter(cfg.frequency));
|
||||||
}
|
}
|
||||||
ctx.consulates[c].currentDate = null;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -682,7 +671,7 @@ async function runner() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.log(`Available timeslots in ${c} at ${chosenDate}: ${availTimes}`);
|
console.log(`Available timeslots in ${c} at ${chosenDate}: ${availTimes}`);
|
||||||
let chosenTime = availTimes[availTimes.length - 1];
|
let chosenTime = availTimes[0];
|
||||||
|
|
||||||
// fill timeslot in reschedule form
|
// fill timeslot in reschedule form
|
||||||
await delay(PAGE_WAIT_TIME);
|
await delay(PAGE_WAIT_TIME);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user